diff --git a/.claude/settings.json b/.claude/settings.json index d61c909..ee2a6cc 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,13 +1,34 @@ { + "$schema": "https://json.schemastore.org/claude-code-settings.json", "permissions": { "allow": [ "Bash(pnpm lint:*)", - "Bash(pnpm format:check:*)", "Bash(pnpm typecheck:*)", - "Bash(pnpm test:*)", + "Bash(pnpm build:*)", "Bash(pnpm format:*)", + "Bash(pnpm format:check:*)", + "Bash(pnpm test:*)", + "Bash(pnpm secrets:scan:*)", + "Bash(pnpm knip:*)", "Bash(pnpm --filter:*)", "Bash(gh api:*)" - ] + ], + "deny": [ + "Bash(curl:*)", + "Bash(wget:*)", + "Bash(nc:*)", + "Bash(ssh:*)", + "Read(**/.env*)", + "Read(**/.dev.vars)", + "Read(**/.prod.vars)", + "Read(**/secrets/**)" + ], + "ask": [ + "Bash(pnpm install:*)", + "Bash(pnpm add:*)", + "Bash(pnpm remove:*)", + "Bash(git commit:*)" + ], + "defaultMode": "default" } } diff --git a/.github/workflows/feature.yml b/.github/workflows/feature.yml index 5d742d8..d7196b7 100644 --- a/.github/workflows/feature.yml +++ b/.github/workflows/feature.yml @@ -18,13 +18,13 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false fetch-depth: 1 - name: Setup - uses: ./.github/actions/setup + uses: ./tooling/github/setup - name: Typecheck run: pnpm typecheck @@ -36,17 +36,35 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false fetch-depth: 1 - name: Setup - uses: ./.github/actions/setup + uses: ./tooling/github/setup - name: Lint run: pnpm lint + knip: + name: knip + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + fetch-depth: 1 + + - name: Setup + uses: ./tooling/github/setup + + - name: Knip + run: pnpm knip + format-check: name: format-check runs-on: ubuntu-latest @@ -54,13 +72,13 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false fetch-depth: 1 - name: Setup - uses: ./.github/actions/setup + uses: ./tooling/github/setup - name: Format check run: pnpm format:check @@ -72,13 +90,65 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false fetch-depth: 1 - name: Setup - uses: ./.github/actions/setup + uses: ./tooling/github/setup - name: Test run: pnpm test + + secrets-scan: + name: secrets-scan + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + steps: + # Full history: gitleaks scans every commit, not just the checked-out tree. + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + fetch-depth: 0 + + # Same script as `pnpm secrets:scan`; runs the pinned gitleaks Docker + # image, so no pnpm/Node setup is needed. + - name: Scan for secrets + run: sh tooling/github/scripts/secrets-scan.sh + + codeql: + name: codeql (${{ matrix.language }}) + runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + actions: read + contents: read + security-events: write + strategy: + fail-fast: false + matrix: + # `actions` scans the workflow files themselves (injection, unpinned + # third-party actions, over-broad permissions). + language: [javascript-typescript, actions] + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + fetch-depth: 1 + + # Advanced setup: turn OFF "default setup" under Settings → Code security, + # or GitHub rejects these uploads. Neither language needs a build step. + - name: Initialize CodeQL + uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 + with: + languages: ${{ matrix.language }} + build-mode: none + queries: security-extended + + - name: Analyze + uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 + with: + category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e66298e..aa5deea 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,13 +18,13 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false fetch-depth: 1 - name: Setup - uses: ./.github/actions/setup + uses: ./tooling/github/setup - name: Typecheck run: pnpm typecheck @@ -36,17 +36,35 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false fetch-depth: 1 - name: Setup - uses: ./.github/actions/setup + uses: ./tooling/github/setup - name: Lint run: pnpm lint + knip: + name: knip + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + fetch-depth: 1 + + - name: Setup + uses: ./tooling/github/setup + + - name: Knip + run: pnpm knip + format-check: name: format-check runs-on: ubuntu-latest @@ -54,13 +72,13 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false fetch-depth: 1 - name: Setup - uses: ./.github/actions/setup + uses: ./tooling/github/setup - name: Format check run: pnpm format:check @@ -72,20 +90,72 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false fetch-depth: 1 - name: Setup - uses: ./.github/actions/setup + uses: ./tooling/github/setup - name: Test run: pnpm test + secrets-scan: + name: secrets-scan + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + steps: + # Full history: gitleaks scans every commit, not just the checked-out tree. + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + fetch-depth: 0 + + # Same script as `pnpm secrets:scan`; runs the pinned gitleaks Docker + # image, so no pnpm/Node setup is needed. + - name: Scan for secrets + run: sh tooling/github/scripts/secrets-scan.sh + + codeql: + name: codeql (${{ matrix.language }}) + runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + actions: read + contents: read + security-events: write + strategy: + fail-fast: false + matrix: + # `actions` scans the workflow files themselves (injection, unpinned + # third-party actions, over-broad permissions). + language: [javascript-typescript, actions] + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + fetch-depth: 1 + + # Advanced setup: turn OFF "default setup" under Settings → Code security, + # or GitHub rejects these uploads. Neither language needs a build step. + - name: Initialize CodeQL + uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 + with: + languages: ${{ matrix.language }} + build-mode: none + queries: security-extended + + - name: Analyze + uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 + with: + category: "/language:${{ matrix.language }}" + deploy: name: deploy - needs: [typecheck, lint, format-check, test] + needs: [typecheck, lint, knip, format-check, test, secrets-scan] runs-on: ubuntu-latest timeout-minutes: 10 environment: production @@ -93,13 +163,13 @@ jobs: contents: read deployments: write steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false fetch-depth: 1 - name: Setup - uses: ./.github/actions/setup + uses: ./tooling/github/setup - name: Deploy browser-scraper to Cloudflare Workers run: pnpm --filter @repo/browser-scraper run deploy diff --git a/.github/workflows/migrations.yml b/.github/workflows/migrations.yml index 77d4790..00bae41 100644 --- a/.github/workflows/migrations.yml +++ b/.github/workflows/migrations.yml @@ -21,13 +21,13 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false fetch-depth: 1 - name: Setup - uses: ./.github/actions/setup + uses: ./tooling/github/setup - name: Apply D1 migrations run: pnpm --filter @repo/operator exec wrangler d1 migrations apply switch-operator-db --remote diff --git a/.gitignore b/.gitignore index 6874279..ca5e594 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,34 @@ +# Dependencies node_modules + +# Turbo / Wrangler +.turbo +.cache +.wrangler + +# Local git worktrees +.worktrees/ + +# Build Outputs +dist +build + +# Logs +logs +*.log + +# Environment files +.env +.env.* +!.env.example .dev.vars .prod.vars -.env -.wrangler \ No newline at end of file +*.local + +# Editors +.idea +*.suo +*.sw? + +# Misc +.DS_Store diff --git a/.nvmrc b/.nvmrc index 1e4f392..7baf24b 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -24.12.0 \ No newline at end of file +24.21.0 \ No newline at end of file diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..04cfa88 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,456 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + + // Every rule is listed explicitly with every category off, mirroring + // eslint:recommended + typescript-eslint recommended/recommendedTypeChecked/ + // stylisticTypeChecked + react-hooks recommended-latest + jsx-a11y + // recommended, plus repo-specific boundary, strictness and style rules. + // + // `plugins` is explicit so only the listed ones load. Enabling a plugin adds + // no rules by itself: `correctness` must be turned off by name, since an + // empty `categories` leaves it on and silently adds ~57 rules, including + // ones tsc already reports. + "plugins": [ + "typescript", + "import", + "react", + "jsx-a11y", + "vitest", + "unicorn", + "oxc" + ], + "categories": { "correctness": "off" }, + + // Type-aware rules need `oxlint-tsgolint`; without it oxlint silently skips + // them, so the root `lint` script also passes `--type-aware` to fail loudly. + "options": { "typeAware": true }, + "env": { "builtin": true }, + + // `.gitignore` is read natively. Config files sit outside every tsconfig + // `include`, so type-aware rules cannot resolve them; they also need a + // default export. + "ignorePatterns": ["**/*.config.*"], + + "rules": { + // eslint core -- eslint:recommended, minus the rules typescript-eslint + // turns back off for TS files because tsc already reports them. + "for-direction": "error", + "no-async-promise-executor": "error", + "no-case-declarations": "error", + "no-compare-neg-zero": "error", + "no-cond-assign": "error", + "no-constant-binary-expression": "error", + "no-constant-condition": "error", + "no-control-regex": "error", + "no-debugger": "error", + "no-delete-var": "error", + "no-dupe-else-if": "error", + "no-duplicate-case": "error", + "no-empty": "error", + "no-empty-character-class": "error", + "no-empty-pattern": "error", + "no-empty-static-block": "error", + "no-ex-assign": "error", + "no-extra-boolean-cast": "error", + "no-fallthrough": "error", + "no-global-assign": "error", + "no-invalid-regexp": "error", + "no-irregular-whitespace": "error", + "no-loss-of-precision": "error", + "no-misleading-character-class": "error", + "no-nonoctal-decimal-escape": "error", + "no-prototype-builtins": "error", + "no-regex-spaces": "error", + "no-self-assign": "error", + "no-shadow-restricted-names": "error", + "no-sparse-arrays": "error", + "no-unexpected-multiline": "error", + "no-unsafe-finally": "error", + "no-unsafe-optional-chaining": "error", + "no-unused-labels": "error", + "no-unused-private-class-members": "error", + "no-useless-backreference": "error", + "no-useless-catch": "error", + "no-useless-escape": "error", + "no-var": "error", + "prefer-const": "error", + "prefer-rest-params": "error", + "prefer-spread": "error", + "require-yield": "error", + "use-isnan": "error", + "valid-typeof": "error", + + // typescript-eslint: recommended + recommendedTypeChecked + "typescript/await-thenable": "error", + "typescript/ban-ts-comment": "error", + "typescript/no-array-constructor": "error", + "typescript/no-array-delete": "error", + "typescript/no-base-to-string": "error", + "typescript/no-duplicate-enum-values": "error", + "typescript/no-duplicate-type-constituents": "error", + "typescript/no-empty-object-type": "error", + "typescript/no-explicit-any": "error", + "typescript/no-extra-non-null-assertion": "error", + "typescript/no-floating-promises": "error", + "typescript/no-for-in-array": "error", + "typescript/no-implied-eval": "error", + "typescript/no-misused-new": "error", + "typescript/no-namespace": "error", + "typescript/no-non-null-asserted-optional-chain": "error", + "typescript/no-redundant-type-constituents": "error", + "typescript/no-require-imports": "error", + "typescript/no-this-alias": "error", + "typescript/no-unnecessary-type-assertion": "error", + "typescript/no-unnecessary-type-constraint": "error", + "typescript/no-unsafe-argument": "error", + "typescript/no-unsafe-assignment": "error", + "typescript/no-unsafe-call": "error", + "typescript/no-unsafe-declaration-merging": "error", + "typescript/no-unsafe-enum-comparison": "error", + "typescript/no-unsafe-function-type": "error", + "typescript/no-unsafe-member-access": "error", + "typescript/no-unsafe-return": "error", + "typescript/no-unsafe-unary-minus": "error", + "typescript/no-unused-expressions": "error", + "typescript/no-wrapper-object-types": "error", + "typescript/only-throw-error": "error", + "typescript/prefer-as-const": "error", + "typescript/prefer-namespace-keyword": "error", + "typescript/prefer-promise-reject-errors": "error", + "typescript/require-await": "error", + "typescript/restrict-plus-operands": "error", + "typescript/restrict-template-expressions": "error", + "typescript/triple-slash-reference": "error", + "typescript/unbound-method": "error", + + // typescript-eslint: stylisticTypeChecked + "typescript/adjacent-overload-signatures": "error", + "typescript/array-type": "error", + "typescript/ban-tslint-comment": "error", + "typescript/class-literal-property-style": "error", + "typescript/consistent-generic-constructors": "error", + "typescript/consistent-indexed-object-style": "error", + "typescript/consistent-type-assertions": "error", + // `type` over the `interface` default: unions cannot be interfaces, so + // aliases are the one form that covers both. + "typescript/consistent-type-definitions": ["error", "type"], + "typescript/dot-notation": "error", + "typescript/no-confusing-non-null-assertion": "error", + "typescript/no-empty-function": "error", + "typescript/no-inferrable-types": "error", + "typescript/non-nullable-type-assertion-style": "error", + "typescript/prefer-find": "error", + "typescript/prefer-for-of": "error", + "typescript/prefer-function-type": "error", + "typescript/prefer-includes": "error", + "typescript/prefer-nullish-coalescing": "error", + "typescript/prefer-regexp-exec": "error", + "typescript/prefer-string-starts-ends-with": "error", + + // Repo-specific additions on top of the presets. + "typescript/no-non-null-assertion": "error", + "typescript/no-unused-vars": [ + "error", + { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" } + ], + "typescript/consistent-type-imports": [ + "warn", + { "prefer": "type-imports", "fixStyle": "separate-type-imports" } + ], + "typescript/consistent-type-exports": [ + "error", + { "fixMixedExportsWithInlineTypeSpecifier": false } + ], + "typescript/switch-exhaustiveness-check": "error", + "typescript/no-deprecated": "error", + // Nursery rule, so warn only. + "typescript/no-unnecessary-condition": "warn", + "typescript/no-unnecessary-type-parameters": "error", + "typescript/prefer-readonly": "error", + "typescript/strict-boolean-expressions": "error", + "no-param-reassign": "error", + "no-nested-ternary": "error", + // Code lines only, so comments and spacing are never what trips it. + "max-lines": [ + "error", + { "max": 400, "skipBlankLines": true, "skipComments": true } + ], + + // Style: one way to write functions, and the cleanups below. + "func-style": ["error", "expression"], + "arrow-body-style": ["error", "as-needed"], + "typescript/method-signature-style": ["error", "property"], + "eqeqeq": "error", + "curly": "error", + "object-shorthand": "error", + "prefer-template": "error", + "no-else-return": "error", + "no-lonely-if": "error", + "no-useless-return": "error", + "no-unneeded-ternary": "error", + "no-useless-concat": "error", + "no-useless-computed-key": "error", + "no-useless-constructor": "error", + "logical-assignment-operators": "error", + "prefer-arrow-callback": "error", + "prefer-object-spread": "error", + "yoda": "error", + "no-implicit-coercion": "error", + "default-param-last": "error", + "default-case-last": "error", + "no-return-assign": "error", + "no-multi-assign": "error", + "radix": "error", + "array-callback-return": "error", + "typescript/no-unnecessary-template-expression": "error", + "typescript/no-unnecessary-boolean-literal-compare": "error", + "typescript/no-unnecessary-type-arguments": "error", + "typescript/no-unnecessary-type-conversion": "error", + "typescript/prefer-reduce-type-parameter": "error", + "typescript/consistent-return": "error", + // `await` inside try/catch so the catch sees the rejection; not elsewhere. + "typescript/return-await": ["error", "in-try-catch"], + "typescript/no-misused-promises": [ + "error", + { "checksVoidReturn": { "attributes": false } } + ], + + // eslint-plugin-import + "import/consistent-type-specifier-style": ["error", "prefer-top-level"], + // Circular imports silently break module init order. + "import/no-cycle": "error", + "import/no-duplicates": "error", + "import/no-self-import": "error", + // Named exports only. Config files that need a default export are + // already excluded by `ignorePatterns`. + "import/no-default-export": "error", + // Declarations first, one export list at the bottom, as in `@repo/logger`. + "import/exports-last": "error", + "import/group-exports": "error", + "import/first": "error", + "import/newline-after-import": "error", + "import/no-mutable-exports": "error", + + // Workspace boundaries. `../` is banned outright: it is the only way a + // relative path can reach another workspace (including packages reaching + // into apps -- importing an app by name already fails, since apps are + // never package dependencies). Apps go up the tree with the `~/` alias; + // packages stay flat or use package.json `imports` (`#...`), because + // tsconfig `paths` would resolve against the consuming app's program. + "import/no-relative-parent-imports": "error", + // Deep imports must go through a package's `exports`. + "no-restricted-imports": [ + "error", + { + "patterns": [ + { + "group": ["@repo/*/src", "@repo/*/src/**"], + "message": "Import the package entry, not its internals." + } + ] + } + ], + + // Barrels built on `export *` hide the public API and defeat + // tree-shaking. Packages expose modules directly through package.json + // `exports` subpaths instead (see `@repo/logger`). + "oxc/no-barrel-file": ["error", { "threshold": 0 }], + + "unicorn/filename-case": ["error", { "case": "kebabCase" }], + // Every disable directive must name the rule it silences. + "unicorn/no-abusive-eslint-disable": "error", + "unicorn/catch-error-name": "error", + "unicorn/throw-new-error": "error", + "unicorn/error-message": "error", + "unicorn/prefer-optional-catch-binding": "error", + "unicorn/switch-case-braces": "error", + "unicorn/prefer-at": "error", + "unicorn/prefer-string-slice": "error", + "unicorn/prefer-string-replace-all": "error", + "unicorn/no-typeof-undefined": "error", + "unicorn/prefer-array-some": "error", + "unicorn/prefer-date-now": "error", + "unicorn/prefer-node-protocol": "error", + "unicorn/prefer-export-from": "error", + "unicorn/prefer-structured-clone": "error", + "unicorn/prefer-global-this": "error", + "unicorn/prefer-spread": "error", + "unicorn/consistent-function-scoping": "error", + "unicorn/numeric-separators-style": "error", + + // vitest + "vitest/no-focused-tests": "error", + "vitest/no-disabled-tests": "warn", + "vitest/consistent-test-it": ["error", { "fn": "it" }], + "vitest/valid-title": "error", + "vitest/valid-expect": "error", + "vitest/expect-expect": "error", + "vitest/no-identical-title": "error", + "vitest/no-standalone-expect": "error", + "vitest/no-conditional-expect": "error", + "vitest/no-duplicate-hooks": "error", + "vitest/prefer-hooks-on-top": "error", + "vitest/no-commented-out-tests": "error", + "vitest/prefer-to-be": "error", + "vitest/prefer-to-have-length": "error", + "vitest/prefer-to-contain": "error", + "vitest/require-top-level-describe": "error", + + // eslint-plugin-react `recommended`, minus `react-in-jsx-scope` and + // `jsx-uses-react` (unneeded with the automatic JSX runtime). + "react/display-name": "error", + "react/jsx-filename-extension": ["error", { "extensions": [".tsx"] }], + "react/jsx-key": "error", + "react/jsx-no-comment-textnodes": "error", + "react/jsx-no-duplicate-props": "error", + "react/jsx-no-target-blank": "error", + "react/jsx-no-undef": "error", + "react/no-children-prop": "error", + "react/no-danger-with-children": "error", + "react/no-direct-mutation-state": "error", + "react/no-find-dom-node": "error", + "react/no-is-mounted": "error", + "react/no-render-return-value": "error", + "react/no-string-refs": "error", + "react/no-unescaped-entities": "error", + "react/no-unknown-property": "error", + + // eslint-plugin-react-hooks `recommended-latest`. The hooks and React + // Compiler rules are namespaced `react/*` in oxlint, not `react-hooks/*`. + "react/error-boundaries": "error", + "react/exhaustive-deps": "warn", + "react/globals": "error", + "react/immutability": "error", + "react/incompatible-library": "warn", + "react/preserve-manual-memoization": "error", + "react/purity": "error", + "react/refs": "error", + "react/rules-of-hooks": "error", + "react/set-state-in-effect": "error", + "react/set-state-in-render": "error", + "react/static-components": "error", + "react/unsupported-syntax": "warn", + "react/use-memo": "error", + "react/void-use-memo": "error", + + // JSX conventions. Components are arrow functions, matching `func-style`. + "react/function-component-definition": [ + "error", + { + "namedComponents": "arrow-function", + "unnamedComponents": "arrow-function" + } + ], + "react/self-closing-comp": "error", + "react/jsx-boolean-value": "error", + "react/jsx-curly-brace-presence": "error", + "react/jsx-fragments": "error", + "react/jsx-no-useless-fragment": "error", + "react/jsx-pascal-case": "error", + "react/hook-use-state": "error", + "react/no-unstable-nested-components": "error", + + // eslint-plugin-jsx-a11y `recommended`. Options are spelled out where + // oxlint's defaults differ from the preset's. + "jsx-a11y/alt-text": "error", + "jsx-a11y/anchor-has-content": "error", + "jsx-a11y/anchor-is-valid": "error", + "jsx-a11y/aria-activedescendant-has-tabindex": "error", + "jsx-a11y/aria-props": "error", + "jsx-a11y/aria-proptypes": "error", + "jsx-a11y/aria-role": "error", + "jsx-a11y/aria-unsupported-elements": "error", + "jsx-a11y/autocomplete-valid": "error", + "jsx-a11y/click-events-have-key-events": "error", + "jsx-a11y/heading-has-content": "error", + "jsx-a11y/html-has-lang": "error", + "jsx-a11y/iframe-has-title": "error", + "jsx-a11y/img-redundant-alt": "error", + "jsx-a11y/interactive-supports-focus": "error", + "jsx-a11y/label-has-associated-control": "error", + "jsx-a11y/media-has-caption": "error", + "jsx-a11y/mouse-events-have-key-events": "error", + "jsx-a11y/no-access-key": "error", + "jsx-a11y/no-autofocus": "error", + "jsx-a11y/no-distracting-elements": "error", + "jsx-a11y/no-interactive-element-to-noninteractive-role": [ + "error", + { "tr": ["none", "presentation"], "canvas": ["img"] } + ], + "jsx-a11y/no-noninteractive-element-interactions": [ + "error", + { + "handlers": [ + "onClick", + "onError", + "onLoad", + "onMouseDown", + "onMouseUp", + "onKeyPress", + "onKeyDown", + "onKeyUp" + ], + "alert": ["onKeyUp", "onKeyDown", "onKeyPress"], + "body": ["onError", "onLoad"], + "dialog": ["onKeyUp", "onKeyDown", "onKeyPress"], + "iframe": ["onError", "onLoad"], + "img": ["onError", "onLoad"] + } + ], + "jsx-a11y/no-noninteractive-element-to-interactive-role": [ + "error", + { + "ul": [ + "listbox", + "menu", + "menubar", + "radiogroup", + "tablist", + "tree", + "treegrid" + ], + "ol": [ + "listbox", + "menu", + "menubar", + "radiogroup", + "tablist", + "tree", + "treegrid" + ], + "li": [ + "menuitem", + "menuitemradio", + "menuitemcheckbox", + "option", + "row", + "tab", + "treeitem" + ], + "table": ["grid"], + "td": ["gridcell"], + "fieldset": ["radiogroup", "presentation"] + } + ], + "jsx-a11y/no-noninteractive-tabindex": "error", + "jsx-a11y/no-redundant-roles": "error", + "jsx-a11y/no-static-element-interactions": [ + "error", + { + "allowExpressionValues": true, + "handlers": [ + "onClick", + "onMouseDown", + "onMouseUp", + "onKeyPress", + "onKeyDown", + "onKeyUp" + ] + } + ], + "jsx-a11y/role-has-required-aria-props": "error", + "jsx-a11y/role-supports-aria-props": "error", + "jsx-a11y/scope": "error", + "jsx-a11y/tabindex-no-positive": "error" + } +} diff --git a/.prettierignore b/.prettierignore index a8ca633..62d8a2d 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,8 @@ pnpm-lock.yaml -**/worker-configuration.d.ts \ No newline at end of file +**/worker-configuration.d.ts +# Build output and caches. Only the root .gitignore is consulted by Prettier, +# so per-workspace ignores have to be repeated here. +**/dist/ +**/.turbo/ +**/.cache/ +**/.wrangler/ diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..c3d2d2f --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "Anthropic.claude-code", + "openai.chatgpt", + "oxc.oxc-vscode", + "esbenp.prettier-vscode", + "GitHub.vscode-pull-request-github" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index e252b3f..4956156 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,12 +1,10 @@ { + "oxc.enable": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, "files.associations": { "wrangler.json": "jsonc" }, - "eslint.workingDirectories": [ - { - "mode": "auto" - } - ], "github.copilot.chat.commitMessageGeneration.instructions": [ { "text": "Use Semantic Commit Messages format exactly: `: `.\n\nAllowed types: chore, docs, feat, fix, refactor, style, test.\n\nSubject rules:\n- Present tense, start with a verb\n- Keep it concise (<= 72 chars)\n- No trailing period\n\nBody (if needed): Keep it short: 1-3 bullet points describing what changed, no extra fluff." @@ -14,7 +12,7 @@ ], "github.copilot.chat.pullRequestDescriptionGeneration.instructions": [ { - "text": "Generate a PR title and description from the diff for this repository.\n\nPR Title: Use Semantic Commit Messages format exactly: `: `.\nAllowed types: chore, docs, feat, fix, refactor, style, test.\nSubject rules:\n- Present tense, start with a verb\n- Keep it concise (<= 72 chars)\n- No trailing period\n- Mention the area touched when helpful (cli, tests, tooling, config, docs)\n\nExamples:\n- `feat: add argument parsing to cli`\n- `test: cover main output`\n- `chore: align eslint config`\n\nPR Description (Required Sections):\n\n## What\n- Start with a 3–5 sentence description (plain text, no bullets)\n- Then add optional bullet points describing what changed and why\n- Call out affected areas like `src/`, tests, or config files when relevant\n- If CLI behavior/output changes, note the change and any new flags or args\n\n## How to test\nProvide concrete, reproducible steps using repo scripts, such as:\n- `pnpm test`\n- `pnpm lint`\n- `pnpm typecheck`\n- `pnpm start` (when CLI behavior is part of the change)\nInclude expected results (what \"good\" looks like). If you did not run something, say so and list what should be run.\n\n## Security review\nAlways include a short checklist-style review:\n- **Secrets / env vars:** . (Never add real secrets to the repo.)\n- **Auth / session:** .\n- **Network / API calls:** . (New external calls, endpoints, telemetry.)\n- **Data handling / PII:** . (Logging, storage, user-provided data.)\n- **Dependencies:** . (Call out any new deps and why; prefer minimal deps.)\n\nIf no impact, write exactly: `No security-impacting changes identified.` Then add 1-2 bullets justifying.\n\nTone: Keep it concise and high-signal. Use bullet points. Do not invent scripts/commands/files that are not in the repo." + "text": "Generate a PR title and description from the diff for this repository.\n\nPR Title: Use Semantic Commit Messages format exactly: `: `.\nAllowed types: chore, docs, feat, fix, refactor, style, test.\nSubject rules:\n- Present tense, start with a verb\n- Keep it concise (<= 72 chars)\n- No trailing period\n- Mention the area touched when helpful (cli, tests, tooling, config, docs)\n\nExamples:\n- `feat: add argument parsing to cli`\n- `test: cover main output`\n- `chore: align eslint config`\n\nPR Description (Required Sections):\n\n## What\n- Start with a 3\u20135 sentence description (plain text, no bullets)\n- Then add optional bullet points describing what changed and why\n- Call out affected areas like `src/`, tests, or config files when relevant\n- If CLI behavior/output changes, note the change and any new flags or args\n\n## How to test\nProvide concrete, reproducible steps using repo scripts, such as:\n- `pnpm test`\n- `pnpm lint`\n- `pnpm typecheck`\n- `pnpm start` (when CLI behavior is part of the change)\nInclude expected results (what \"good\" looks like). If you did not run something, say so and list what should be run.\n\n## Security review\nAlways include a short checklist-style review:\n- **Secrets / env vars:** . (Never add real secrets to the repo.)\n- **Auth / session:** .\n- **Network / API calls:** . (New external calls, endpoints, telemetry.)\n- **Data handling / PII:** . (Logging, storage, user-provided data.)\n- **Dependencies:** . (Call out any new deps and why; prefer minimal deps.)\n\nIf no impact, write exactly: `No security-impacting changes identified.` Then add 1-2 bullets justifying.\n\nTone: Keep it concise and high-signal. Use bullet points. Do not invent scripts/commands/files that are not in the repo." } ], "github.copilot.enable": { diff --git a/AGENTS.md b/AGENTS.md index d256544..ccd596a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,59 +1,94 @@ # AGENTS.md -## Purpose +Guidelines for AI agents and contributors working in this Turborepo monorepo. -This document is the repo-level quick-start for coding agents. -Use it for project orientation, quality gates, and safe editing workflow. +`CLAUDE.md` is a symlink to this file. Never edit `CLAUDE.md` directly. -## Canonical File +--- -- `AGENTS.md` is the source of truth. -- `CLAUDE.md` must be a symbolic link to `AGENTS.md`. -- Never edit `CLAUDE.md` directly. Always update `AGENTS.md`. +## Structure -## Project Snapshot +### Apps (`apps/`) -- Project: switch-operator -- Workspace: pnpm workspaces (`apps/`, `packages/`, `tooling/`) -- App workspaces: `apps/operator` (Cloudflare Worker with Hono), `apps/browser-scraper` (Cloudflare Worker), `apps/alerts` (scheduled Cloudflare Worker) -- Package workspaces: `packages/http-client`, `packages/logger`, `packages/telegram`, `packages/url-validator` -- Shared tooling configs: `tooling/eslint`, `tooling/prettier`, `tooling/typescript` -- Package manager: `pnpm` (lockfile: `pnpm-lock.yaml`) -- Required Node version: `24.12.0` (from `.nvmrc`) +| Name | Filter | Description | +| --------------- | ----------------------- | ---------------------------------------------- | +| operator | `@repo/operator` | Telegram operator Cloudflare Worker (Hono, D1) | +| browser-scraper | `@repo/browser-scraper` | Headless-browser scraping Cloudflare Worker | +| alerts | `@repo/alerts` | Scheduled market alerts Cloudflare Worker | -## Commands - -Run from project root. +### Packages (`packages/`) -- Install dependencies: `pnpm install` -- Dev: `pnpm dev` -- Build: `pnpm build` -- Typecheck: `pnpm typecheck` -- Lint: `pnpm lint` -- Test: `pnpm test` -- Operator webhook helper: `pnpm --filter @repo/operator set-webhook [-- --prod]` -- Format all files: `pnpm format` -- Format check: `pnpm format:check` +| Name | Filter | Description | +| ------------- | --------------------- | ------------------------------------------- | +| alpha-vantage | `@repo/alpha-vantage` | Alpha Vantage market data client | +| http-client | `@repo/http-client` | Fetch wrapper with Zod response validation | +| logger | `@repo/logger` | Structured JSON logger | +| telegram | `@repo/telegram` | Telegram Bot API client | +| url-validator | `@repo/url-validator` | Safety policy checks for user-supplied URLs | -## Quality Gates +### Tooling (`tooling/`) -CI enforces the following on PRs and `main`: +| Name | Filter | Description | +| ---------- | ------------------ | ---------------------------------------------------------------- | +| prettier | `@repo/prettier` | Shared Prettier config | +| typescript | `@repo/typescript` | Shared tsconfig presets (`base.json`, `node.json`, `react.json`) | +| github | `@repo/github` | GitHub Actions composite setup action, gitleaks `secrets-scan` | -- `typecheck` -- `lint` -- `format:check` -- `test` +Apps may import packages; packages must never import apps. oxlint bans `../` +imports and deep `@repo/*/src` imports: import other workspaces by package name, +and inside an app use the `~/` alias (`src/*`) to go up the tree. -## Repo Standards +--- -- TypeScript strict mode -- ESLint (type-aware) -- Prettier -- Prefer minimal, targeted edits and preserve existing architecture patterns - -## Safe Agent Workflow +## Commands -1. Read `AGENTS.md` before starting work. -2. Make minimal, targeted edits. -3. Run checks: `pnpm typecheck`, `pnpm lint`, `pnpm format:check`, `pnpm test`. -4. Update docs if architecture or behavior changed. +**Prerequisites:** Node.js 24.21.0 (`.nvmrc`), pnpm 12.6.0 (`packageManager` in root `package.json`). + +```bash +pnpm install # Install all dependencies +pnpm dev # turbo run dev (all workers) + +pnpm lint # oxlint, one process over the whole repo +pnpm knip # unused files, exports and dependencies +pnpm typecheck # turbo run typecheck +pnpm test # turbo run test +pnpm build # turbo run build +pnpm format # prettier --write . +pnpm format:check # prettier --check . (no writes) +pnpm secrets:scan # gitleaks over the full git history +pnpm clean # turbo run clean + +pnpm --filter @repo/operator set-webhook [-- --prod] +``` + +`lint` and `knip` do not go through Turbo — each is a single process over the whole +repo. oxlint is configured by the root `.oxlintrc.json` (including `import/no-cycle`) +and prints nothing when there are no findings, so silent output means clean. Knip is +configured by the root `knip.jsonc` and exits 0 when clean. + +There is no post-edit formatting hook: run `pnpm format` yourself before committing. + +`secrets:scan` runs gitleaks (`tooling/github/scripts/secrets-scan.sh`, version pinned +there) using a local `gitleaks` v8.19+ if one is on PATH, otherwise the pinned Docker image. +It exits 0 when clean and 1 when it finds a leak. + +CI (`.github/workflows/`) runs typecheck, lint, knip, format-check, test, secrets-scan +and CodeQL code scanning (`javascript-typescript` and `actions`) on push to `main` and +on PRs. On `main`, the workers deploy to Cloudflare once those checks pass, and D1 +migrations apply when `apps/operator/migrations/**` changes. + +--- + +## Rules + +- Keep diffs tight and focused; no drive-by refactors or new tooling without discussion. +- Never commit secrets, credentials, `.env`, `.dev.vars` or `.prod.vars` files. +- Add dependencies to the correct workspace with `pnpm --filter add `. + Versions shared by more than one package go in the `catalog:` block of + `pnpm-workspace.yaml`; single-consumer deps are pinned inline. +- Every install enforces the supply-chain settings in `pnpm-workspace.yaml` + (`minimumReleaseAge`, `trustPolicy: no-downgrade`) plus pnpm 12's default + `blockExoticSubdeps`. When one fails, investigate: never disable it, and never + exclude a whole package to get past it. +- Run `pnpm typecheck`, `pnpm lint`, `pnpm knip`, `pnpm format:check` and `pnpm test` + before committing. Update docs when architecture or behavior changes. diff --git a/README.md b/README.md index 58079b2..72bbae9 100644 --- a/README.md +++ b/README.md @@ -20,20 +20,26 @@ The current feature set includes: - **Messaging:** Telegram Bot API - **LLM:** OpenAI API - **Validation:** Zod -- **Monorepo:** pnpm workspaces +- **Monorepo:** pnpm workspaces + Turborepo +- **Tooling:** oxlint (type-aware), Prettier, Knip ## Structure ``` apps/ - operator/ # Cloudflare Worker (Telegram bot) + operator/ # Cloudflare Worker (Telegram bot) + browser-scraper/ # Cloudflare Worker (headless browser scraping) + alerts/ # Cloudflare Worker (scheduled market alerts) packages/ - http-client/ # Shared fetch wrapper with response validation - logger/ # Shared structured logger + alpha-vantage/ # Alpha Vantage market data client + http-client/ # Shared fetch wrapper with response validation + logger/ # Shared structured logger + telegram/ # Telegram Bot API client + url-validator/ # Safety policy checks for user-supplied URLs tooling/ - eslint/ # Shared ESLint config - prettier/ # Shared Prettier config - typescript/ # Shared TypeScript config + github/ # CI setup action and gitleaks secrets scan + prettier/ # Shared Prettier config + typescript/ # Shared TypeScript config ``` ## Flow @@ -57,15 +63,17 @@ flowchart TD ## Development -Requires Node 24.12.0 (see `.nvmrc`) and pnpm. +Requires Node 24.21.0 (see `.nvmrc`) and pnpm 12 (see `packageManager`). ```sh pnpm install # install dependencies pnpm dev # start local dev server pnpm build # build all workspaces pnpm typecheck # type checking -pnpm lint # linting +pnpm lint # oxlint +pnpm knip # unused files, exports and dependencies pnpm test # run tests +pnpm secrets:scan # gitleaks over git history pnpm format:check # check formatting pnpm format # fix formatting ``` diff --git a/apps/alerts/eslint.config.ts b/apps/alerts/eslint.config.ts deleted file mode 100644 index f57754c..0000000 --- a/apps/alerts/eslint.config.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* eslint-disable import/no-default-export */ -import { baseConfig } from "@repo/eslint/base"; -import { defineConfig } from "eslint/config"; - -export default defineConfig(baseConfig); diff --git a/apps/alerts/package.json b/apps/alerts/package.json index 6f7d9c6..994d39f 100644 --- a/apps/alerts/package.json +++ b/apps/alerts/package.json @@ -6,25 +6,23 @@ "dev": "wrangler dev --test-scheduled", "deploy": "wrangler deploy", "typecheck": "tsc", - "lint": "eslint .", - "test": "vitest run --passWithNoTests" + "test": "vitest run --passWithNoTests", + "clean": "rm -rf .turbo node_modules .cache .wrangler" }, "dependencies": { "@repo/alpha-vantage": "workspace:*", "@repo/logger": "workspace:*", "@repo/telegram": "workspace:*", - "zod": "4.3.6" + "zod": "catalog:" }, "devDependencies": { - "@cloudflare/workers-types": "4.20260412.1", - "@repo/eslint": "workspace:*", + "@cloudflare/workers-types": "catalog:", "@repo/prettier": "workspace:*", "@repo/typescript": "workspace:*", - "@types/node": "25.6.0", - "eslint": "9.39.1", - "prettier": "3.8.2", - "typescript": "6.0.2", - "vitest": "4.1.4", - "wrangler": "4.81.1" - } + "@types/node": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:", + "wrangler": "catalog:" + }, + "prettier": "@repo/prettier" } diff --git a/apps/alerts/src/alerts/sp500-close.test.ts b/apps/alerts/src/alerts/sp500-close.test.ts index f557bb1..a79f4f1 100644 --- a/apps/alerts/src/alerts/sp500-close.test.ts +++ b/apps/alerts/src/alerts/sp500-close.test.ts @@ -31,16 +31,14 @@ const seriesFromCloses = (closes: number[]): DailyTimeSeries => ({ symbol: "SPY", lastRefreshed: "2026-07-11", timeZone: "US/Eastern", - bars: closes.map( - (close, i): DailyBar => ({ - date: `2026-07-${String(11 - i).padStart(2, "0")}`, - open: close, - high: close, - low: close, - close, - volume: 1_000_000, - }) - ), + bars: closes.map((close, i): DailyBar => ({ + date: `2026-07-${String(11 - i).padStart(2, "0")}`, + open: close, + high: close, + low: close, + close, + volume: 1_000_000, + })), }); /** @@ -51,16 +49,14 @@ const weeklyFromCloses = (closes: number[]): DailyTimeSeries => ({ symbol: "SPY", lastRefreshed: "2026-06-26", timeZone: "US/Eastern", - bars: closes.map( - (close, i): DailyBar => ({ - date: `2026-06-${String(26 - i * 7).padStart(2, "0")}`, - open: close, - high: close, - low: close, - close, - volume: 5_000_000, - }) - ), + bars: closes.map((close, i): DailyBar => ({ + date: `2026-06-${String(26 - i * 7).padStart(2, "0")}`, + open: close, + high: close, + low: close, + close, + volume: 5_000_000, + })), }); const env = { diff --git a/apps/alerts/src/alerts/sp500-close.ts b/apps/alerts/src/alerts/sp500-close.ts index 59ccadd..800d222 100644 --- a/apps/alerts/src/alerts/sp500-close.ts +++ b/apps/alerts/src/alerts/sp500-close.ts @@ -13,12 +13,12 @@ const sp500Close: Alert = { // (daily `full` history is a premium feature). See `fetchSp500`. The client // is shared across alerts so the fetch de-duplicates within the run. const { bars, priorHigh } = await fetchSp500(alphaVantage); - if (bars.length === 0) { + const [latest] = bars; + if (latest === undefined) { logger.warn("no SPY bars returned", { symbol: SP500_SYMBOL }); return null; } - const latest = bars[0]; // All-time high close: the highest weekly *close* from prior history, // lifted by any fresher close among the recent daily bars. const ath = bars.reduce((max, bar) => Math.max(max, bar.close), priorHigh); diff --git a/apps/alerts/src/alerts/sp500-drawdown.test.ts b/apps/alerts/src/alerts/sp500-drawdown.test.ts index 7927bae..afaeb37 100644 --- a/apps/alerts/src/alerts/sp500-drawdown.test.ts +++ b/apps/alerts/src/alerts/sp500-drawdown.test.ts @@ -37,16 +37,14 @@ const seriesFromCloses = (closes: number[]): DailyTimeSeries => ({ symbol: "SPY", lastRefreshed: "2026-07-11", timeZone: "US/Eastern", - bars: closes.map( - (close, i): DailyBar => ({ - date: `2026-07-${String(11 - i).padStart(2, "0")}`, - open: close, - high: close, - low: close, - close, - volume: 1_000_000, - }) - ), + bars: closes.map((close, i): DailyBar => ({ + date: `2026-07-${String(11 - i).padStart(2, "0")}`, + open: close, + high: close, + low: close, + close, + volume: 1_000_000, + })), }); /** @@ -57,16 +55,14 @@ const weeklyFromCloses = (closes: number[]): DailyTimeSeries => ({ symbol: "SPY", lastRefreshed: "2026-06-26", timeZone: "US/Eastern", - bars: closes.map( - (close, i): DailyBar => ({ - date: `2026-06-${String(26 - i * 7).padStart(2, "0")}`, - open: close, - high: close, - low: close, - close, - volume: 5_000_000, - }) - ), + bars: closes.map((close, i): DailyBar => ({ + date: `2026-06-${String(26 - i * 7).padStart(2, "0")}`, + open: close, + high: close, + low: close, + close, + volume: 5_000_000, + })), }); const env = { diff --git a/apps/alerts/src/alerts/sp500-drawdown.ts b/apps/alerts/src/alerts/sp500-drawdown.ts index eefbfc7..c5ce5e2 100644 --- a/apps/alerts/src/alerts/sp500-drawdown.ts +++ b/apps/alerts/src/alerts/sp500-drawdown.ts @@ -72,16 +72,15 @@ const sp500Drawdown: Alert = { // is shared across alerts so the fetch de-duplicates within the run. const { bars, priorHigh } = await fetchSp500(alphaVantage); - if (bars.length < 2) { + // Bars are newest-first: [0] is today's close, [1] is the prior close. + const [today, prev] = bars; + if (today === undefined || prev === undefined) { logger.warn("need at least 2 SPY bars for drawdown", { count: bars.length, }); return null; } - // Bars are newest-first: [0] is today's close, [1] is the prior close. - const [today, prev] = bars; - // ATH = the highest pre-window weekly *close*, lifted by the highest recent // daily close. `priorHigh` excludes the daily window entirely (so it can't // carry today's close), which lets `athPrev` drop today's bar cleanly and @@ -106,7 +105,11 @@ const sp500Drawdown: Alert = { const crossed = DRAWDOWN_LEVELS.filter( (l) => l.threshold > bandPrev && l.threshold <= bandToday ); - const deepest = crossed[crossed.length - 1]; + // `bandToday` is itself a level threshold, so `crossed` is never empty. + const deepest = crossed.at(-1); + if (deepest === undefined) { + throw new Error(`no drawdown level crossed for band ${bandToday}`); + } const deploySum = crossed.reduce((sum, l) => sum + l.deploy, 0); const action = deploySum > 0 @@ -124,7 +127,10 @@ const sp500Drawdown: Alert = { (l) => l.threshold > bandToday && l.threshold <= bandPrev ); // Deepest level reclaimed (crossed is ascending by threshold). - const reclaimed = crossed[crossed.length - 1].threshold; + const reclaimed = crossed.at(-1)?.threshold; + if (reclaimed === undefined) { + throw new Error(`no drawdown level reclaimed for band ${bandPrev}`); + } const refillSum = crossed.reduce((sum, l) => sum + l.deploy, 0); const action = refillSum > 0 @@ -138,4 +144,4 @@ const sp500Drawdown: Alert = { }, }; -export { sp500Drawdown, DRAWDOWN_LEVELS }; +export { sp500Drawdown }; diff --git a/apps/alerts/src/alerts/sp500.ts b/apps/alerts/src/alerts/sp500.ts index 91e0e9c..f18f2ed 100644 --- a/apps/alerts/src/alerts/sp500.ts +++ b/apps/alerts/src/alerts/sp500.ts @@ -55,4 +55,3 @@ const fetchSp500 = async (client: AlphaVantageClient): Promise => { }; export { fetchSp500, SP500_SYMBOL }; -export type { Sp500Data }; diff --git a/apps/alerts/src/alerts/types.ts b/apps/alerts/src/alerts/types.ts index 5eecdf8..bcff1e7 100644 --- a/apps/alerts/src/alerts/types.ts +++ b/apps/alerts/src/alerts/types.ts @@ -1,7 +1,7 @@ import type { AlphaVantageClient } from "@repo/alpha-vantage"; import type { Logger } from "@repo/logger"; -import type { Env } from "../types/env"; +import type { Env } from "~/types/env"; type AlertContext = { env: Env; @@ -30,4 +30,4 @@ type Alert = { run: (ctx: AlertContext) => Promise; }; -export type { Alert, AlertContext }; +export type { Alert }; diff --git a/apps/alerts/src/scheduled.test.ts b/apps/alerts/src/scheduled.test.ts index 778387e..e235352 100644 --- a/apps/alerts/src/scheduled.test.ts +++ b/apps/alerts/src/scheduled.test.ts @@ -22,7 +22,7 @@ const spySeries = { high: 469.13, low: 464.45, close: 467.28, - volume: 92955850, + volume: 92_955_850, }, ], }; @@ -74,7 +74,7 @@ describe("handleScheduled", () => { ]) ); for (const [params] of sendMessage.mock.calls) { - expect(params.chat_id).toBe(12345); + expect(params.chat_id).toBe(12_345); expect(params.parse_mode).toBe("HTML"); } }); diff --git a/apps/alerts/src/scheduled.ts b/apps/alerts/src/scheduled.ts index 7cfea72..eee645f 100644 --- a/apps/alerts/src/scheduled.ts +++ b/apps/alerts/src/scheduled.ts @@ -50,7 +50,7 @@ const handleScheduled = async ( results.forEach((result, i) => { if (result.status === "rejected") { logger.error("alert failed", { - alert: due[i].name, + alert: due[i]?.name, error: result.reason instanceof Error ? result.reason.message @@ -60,10 +60,9 @@ const handleScheduled = async ( }); }; -const createScheduledHandler = () => { - return (event: ScheduledEvent, env: unknown, ctx: ExecutionContext) => { +const createScheduledHandler = + () => (event: ScheduledEvent, env: unknown, ctx: ExecutionContext) => { ctx.waitUntil(handleScheduled(event, env, ctx)); }; -}; export { createScheduledHandler, handleScheduled }; diff --git a/apps/alerts/src/types/env.ts b/apps/alerts/src/types/env.ts index db2f3b2..9d91687 100644 --- a/apps/alerts/src/types/env.ts +++ b/apps/alerts/src/types/env.ts @@ -8,9 +8,7 @@ const envSchema = z.object({ type Env = z.infer; -const parseEnv = (env: unknown): Env => { - return envSchema.parse(env); -}; +const parseEnv = (env: unknown): Env => envSchema.parse(env); -export { envSchema, parseEnv }; +export { parseEnv }; export type { Env }; diff --git a/apps/alerts/tsconfig.json b/apps/alerts/tsconfig.json index 1eed9fe..7edd798 100644 --- a/apps/alerts/tsconfig.json +++ b/apps/alerts/tsconfig.json @@ -1,7 +1,10 @@ { "extends": "@repo/typescript/base.json", "compilerOptions": { - "types": ["@cloudflare/workers-types"] + "types": ["@cloudflare/workers-types"], + "paths": { + "~/*": ["./src/*"] + } }, "include": ["src"], "exclude": ["node_modules"] diff --git a/apps/alerts/vitest.config.ts b/apps/alerts/vitest.config.ts new file mode 100644 index 0000000..f2a5332 --- /dev/null +++ b/apps/alerts/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + // `~/` resolves through tsconfig `paths`; Wrangler's bundler reads them too. + resolve: { tsconfigPaths: true }, + test: { include: ["src/**/*.test.ts"] }, +}); diff --git a/apps/browser-scraper/eslint.config.ts b/apps/browser-scraper/eslint.config.ts deleted file mode 100644 index f57754c..0000000 --- a/apps/browser-scraper/eslint.config.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* eslint-disable import/no-default-export */ -import { baseConfig } from "@repo/eslint/base"; -import { defineConfig } from "eslint/config"; - -export default defineConfig(baseConfig); diff --git a/apps/browser-scraper/package.json b/apps/browser-scraper/package.json index 65ca09b..81bf991 100644 --- a/apps/browser-scraper/package.json +++ b/apps/browser-scraper/package.json @@ -6,25 +6,23 @@ "dev": "wrangler dev --port 8788 --inspector-port 9230", "deploy": "wrangler deploy", "typecheck": "tsc", - "lint": "eslint .", - "test": "vitest run --passWithNoTests" + "test": "vitest run --passWithNoTests", + "clean": "rm -rf .turbo node_modules .cache .wrangler" }, "dependencies": { - "@cloudflare/playwright": "1.2.0", + "@cloudflare/playwright": "1.3.6", "@repo/logger": "workspace:*", "@repo/url-validator": "workspace:*", - "zod": "4.3.6" + "zod": "catalog:" }, "devDependencies": { - "@cloudflare/workers-types": "4.20260412.1", - "@repo/eslint": "workspace:*", + "@cloudflare/workers-types": "catalog:", "@repo/prettier": "workspace:*", "@repo/typescript": "workspace:*", - "@types/node": "25.6.0", - "eslint": "9.39.1", - "prettier": "3.8.2", - "typescript": "6.0.2", - "vitest": "4.1.4", - "wrangler": "4.81.1" - } + "@types/node": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:", + "wrangler": "catalog:" + }, + "prettier": "@repo/prettier" } diff --git a/apps/browser-scraper/src/services/playwright.test.ts b/apps/browser-scraper/src/services/playwright.test.ts index 65ffb58..cb83a05 100644 --- a/apps/browser-scraper/src/services/playwright.test.ts +++ b/apps/browser-scraper/src/services/playwright.test.ts @@ -85,9 +85,10 @@ describe("PlaywrightService.render", () => { ); expect(result.ok).toBe(false); - if (!result.ok) { - expect(result.error).toMatch(/Unsafe final URL/); + if (result.ok) { + throw new Error("expected render to fail"); } + expect(result.error).toMatch(/Unsafe final URL/); expect(closeMock).toHaveBeenCalledOnce(); }); diff --git a/apps/browser-scraper/src/services/playwright.ts b/apps/browser-scraper/src/services/playwright.ts index 64d099a..a66ece5 100644 --- a/apps/browser-scraper/src/services/playwright.ts +++ b/apps/browser-scraper/src/services/playwright.ts @@ -119,10 +119,4 @@ class PlaywrightService { } } -export { - classifyBrowserError, - MAX_HTML_CHARS, - NAV_TIMEOUT_MS, - PlaywrightService, -}; -export type { BrowserErrorKind, RenderError, RenderResult, RenderSuccess }; +export { classifyBrowserError, PlaywrightService }; diff --git a/apps/browser-scraper/tsconfig.json b/apps/browser-scraper/tsconfig.json index 3416688..2e5780a 100644 --- a/apps/browser-scraper/tsconfig.json +++ b/apps/browser-scraper/tsconfig.json @@ -1,7 +1,10 @@ { "extends": "@repo/typescript/base.json", "compilerOptions": { - "types": ["@cloudflare/workers-types", "vitest/globals"] + "types": ["@cloudflare/workers-types", "vitest/globals"], + "paths": { + "~/*": ["./src/*"] + } }, "include": ["src"], "exclude": ["node_modules"] diff --git a/apps/browser-scraper/vitest.config.ts b/apps/browser-scraper/vitest.config.ts new file mode 100644 index 0000000..f2a5332 --- /dev/null +++ b/apps/browser-scraper/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + // `~/` resolves through tsconfig `paths`; Wrangler's bundler reads them too. + resolve: { tsconfigPaths: true }, + test: { include: ["src/**/*.test.ts"] }, +}); diff --git a/apps/operator/README.md b/apps/operator/README.md index 5e50ed9..d865215 100644 --- a/apps/operator/README.md +++ b/apps/operator/README.md @@ -96,7 +96,7 @@ pnpm --filter @repo/operator set-webhook \ | `pnpm --filter @repo/operator db:migrate:local` | Apply local D1 migrations | | `pnpm --filter @repo/operator db:migrate:remote` | Apply remote D1 migrations | | `pnpm typecheck` | Run TypeScript type checking | -| `pnpm lint` | Run ESLint | +| `pnpm lint` | Run oxlint (repo-wide) | | `pnpm test` | Run tests | | `pnpm --filter @repo/operator set-webhook [-- --prod]` | Register Telegram webhook URL | diff --git a/apps/operator/drizzle.config.ts b/apps/operator/drizzle.config.ts index da45899..29fee65 100644 --- a/apps/operator/drizzle.config.ts +++ b/apps/operator/drizzle.config.ts @@ -1,6 +1,5 @@ import { defineConfig } from "drizzle-kit"; -// eslint-disable-next-line import/no-default-export export default defineConfig({ out: "./migrations", schema: "./src/db/schema.ts", diff --git a/apps/operator/eslint.config.ts b/apps/operator/eslint.config.ts deleted file mode 100644 index 822567f..0000000 --- a/apps/operator/eslint.config.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { baseConfig } from "@repo/eslint/base"; -import { defineConfig } from "eslint/config"; - -export default defineConfig(baseConfig, { ignores: ["scripts/**"] }); diff --git a/apps/operator/package.json b/apps/operator/package.json index f4d0a90..2969ae5 100644 --- a/apps/operator/package.json +++ b/apps/operator/package.json @@ -6,37 +6,35 @@ "dev": "wrangler dev --test-scheduled", "deploy": "wrangler deploy", "typecheck": "tsc", - "lint": "eslint .", "test": "vitest run --passWithNoTests", "set-webhook": "tsx scripts/set-webhook.ts", "db:generate": "drizzle-kit generate", "db:migrate:local": "wrangler d1 migrations apply switch-operator-db --local", - "db:migrate:remote": "wrangler d1 migrations apply switch-operator-db --remote" + "db:migrate:remote": "wrangler d1 migrations apply switch-operator-db --remote", + "clean": "rm -rf .turbo node_modules .cache .wrangler" }, "dependencies": { - "@hono/zod-validator": "0.7.6", + "@hono/zod-validator": "0.9.1", "@repo/http-client": "workspace:*", "@repo/logger": "workspace:*", "@repo/telegram": "workspace:*", "@repo/url-validator": "workspace:*", - "drizzle-orm": "0.45.2", - "hono": "4.12.27", + "drizzle-orm": "0.45.3", + "hono": "4.13.9", "node-html-markdown": "2.0.0", - "openai": "6.34.0", - "zod": "4.3.6" + "openai": "7.23.0", + "zod": "catalog:" }, "devDependencies": { - "@cloudflare/workers-types": "4.20260412.1", - "@repo/eslint": "workspace:*", + "@cloudflare/workers-types": "catalog:", "@repo/prettier": "workspace:*", "@repo/typescript": "workspace:*", - "@types/node": "25.6.0", - "drizzle-kit": "0.31.10", - "eslint": "9.39.1", - "prettier": "3.8.2", - "tsx": "4.21.0", - "typescript": "6.0.2", - "vitest": "4.1.4", - "wrangler": "4.81.1" - } + "@types/node": "catalog:", + "drizzle-kit": "0.31.11", + "tsx": "4.23.15", + "typescript": "catalog:", + "vitest": "catalog:", + "wrangler": "catalog:" + }, + "prettier": "@repo/prettier" } diff --git a/apps/operator/scripts/set-webhook.ts b/apps/operator/scripts/set-webhook.ts index ea61e19..afde55b 100644 --- a/apps/operator/scripts/set-webhook.ts +++ b/apps/operator/scripts/set-webhook.ts @@ -1,6 +1,5 @@ import { readFileSync } from "node:fs"; import { resolve } from "node:path"; - import { HttpClient, HttpClientError } from "@repo/http-client"; import { Logger } from "@repo/logger"; import { z } from "zod"; @@ -28,7 +27,7 @@ const main = async () => { const isProd = args.includes("--prod"); const baseUrl = args.find((a) => !a.startsWith("--")); - if (!baseUrl) { + if (baseUrl === undefined || baseUrl === "") { console.error("Usage: pnpm set-webhook [--prod]"); console.error("Example: pnpm set-webhook https://xxx.trycloudflare.com"); console.error( @@ -50,10 +49,15 @@ const main = async () => { process.exit(1); } - const token = vars["TELEGRAM_BOT_TOKEN"]; - const secret = vars["TELEGRAM_WEBHOOK_SECRET"]; + const token = vars.TELEGRAM_BOT_TOKEN; + const secret = vars.TELEGRAM_WEBHOOK_SECRET; - if (!token || !secret) { + if ( + token === undefined || + token === "" || + secret === undefined || + secret === "" + ) { console.error( `Missing TELEGRAM_BOT_TOKEN or TELEGRAM_WEBHOOK_SECRET in ${varsFile}` ); @@ -93,13 +97,13 @@ const main = async () => { console.error("Webhook registration failed:", result.description); process.exit(1); } - } catch (err) { - if (err instanceof HttpClientError) { - console.error("Webhook registration failed:", err.message, err.body); + } catch (error) { + if (error instanceof HttpClientError) { + console.error("Webhook registration failed:", error.message, error.body); process.exit(1); } - throw err; + throw error; } }; -main(); +await main(); diff --git a/apps/operator/src/middleware/__tests__/cors.test.ts b/apps/operator/src/middleware/cors.test.ts similarity index 96% rename from apps/operator/src/middleware/__tests__/cors.test.ts rename to apps/operator/src/middleware/cors.test.ts index 56dbfc5..6002cdb 100644 --- a/apps/operator/src/middleware/__tests__/cors.test.ts +++ b/apps/operator/src/middleware/cors.test.ts @@ -1,7 +1,7 @@ import { Hono } from "hono"; import { describe, expect, it } from "vitest"; -import { corsMiddleware } from "../cors"; +import { corsMiddleware } from "./cors"; const createApp = () => { const app = new Hono(); diff --git a/apps/operator/src/middleware/env.ts b/apps/operator/src/middleware/env.ts index 60b623a..e752dd4 100644 --- a/apps/operator/src/middleware/env.ts +++ b/apps/operator/src/middleware/env.ts @@ -1,7 +1,7 @@ import { createMiddleware } from "hono/factory"; -import type { AppEnv } from "../types/env"; -import { envSchema } from "../types/env"; +import type { AppEnv } from "~/types/env"; +import { envSchema } from "~/types/env"; const envValidatorMiddleware = createMiddleware(async (c, next) => { const result = envSchema.safeParse(c.env); @@ -11,7 +11,7 @@ const envValidatorMiddleware = createMiddleware(async (c, next) => { }); return c.json({ error: "Server misconfiguration" }, 500); } - await next(); + return next(); }); export { envValidatorMiddleware }; diff --git a/apps/operator/src/middleware/error-handlers.ts b/apps/operator/src/middleware/error-handlers.ts index 30ec2e8..be71680 100644 --- a/apps/operator/src/middleware/error-handlers.ts +++ b/apps/operator/src/middleware/error-handlers.ts @@ -1,9 +1,9 @@ import type { Context, ErrorHandler } from "hono"; import { HTTPException } from "hono/http-exception"; -import type { AppEnv } from "../types/env"; +import type { AppEnv } from "~/types/env"; -export const onErrorHandler: ErrorHandler = (err, c) => { +const onErrorHandler: ErrorHandler = (err, c) => { const logger = c.get("logger"); const requestId = c.get("requestId"); const status = err instanceof HTTPException ? err.status : 500; @@ -25,6 +25,7 @@ export const onErrorHandler: ErrorHandler = (err, c) => { return c.json({ error: "Internal Server Error" }, 500); }; -export const notFoundHandler = (c: Context) => { - return c.json({ error: "Not Found" }, 404); -}; +const notFoundHandler = (c: Context) => + c.json({ error: "Not Found" }, 404); + +export { notFoundHandler, onErrorHandler }; diff --git a/apps/operator/src/middleware/__tests__/logger.test.ts b/apps/operator/src/middleware/logger.test.ts similarity index 95% rename from apps/operator/src/middleware/__tests__/logger.test.ts rename to apps/operator/src/middleware/logger.test.ts index 32e7bdf..22ea48c 100644 --- a/apps/operator/src/middleware/__tests__/logger.test.ts +++ b/apps/operator/src/middleware/logger.test.ts @@ -4,9 +4,10 @@ import type { MockInstance } from "vitest"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { z } from "zod"; -import type { AppEnv } from "../../types/env"; -import { onErrorHandler } from "../error-handlers"; -import { loggerMiddleware } from "../logger"; +import type { AppEnv } from "~/types/env"; + +import { onErrorHandler } from "./error-handlers"; +import { loggerMiddleware } from "./logger"; const IdResponseSchema = z.object({ id: z.string() }); const ErrorResponseSchema = z.object({ error: z.string() }); @@ -14,7 +15,7 @@ const ErrorResponseSchema = z.object({ error: z.string() }); type ConsoleSpy = MockInstance<(...args: unknown[]) => void>; const parseLogEntry = (spy: ConsoleSpy, callIndex = 0): LogEntry => { - const call = spy.mock.calls[callIndex] as unknown[] | undefined; + const call = spy.mock.calls[callIndex]; expect(call).toBeDefined(); return JSON.parse(String(call?.[0])) as LogEntry; }; diff --git a/apps/operator/src/middleware/logger.ts b/apps/operator/src/middleware/logger.ts index ab6693b..d8cfa4e 100644 --- a/apps/operator/src/middleware/logger.ts +++ b/apps/operator/src/middleware/logger.ts @@ -1,7 +1,7 @@ import { Logger } from "@repo/logger"; import { createMiddleware } from "hono/factory"; -import type { AppEnv } from "../types/env"; +import type { AppEnv } from "~/types/env"; export const loggerMiddleware = createMiddleware(async (c, next) => { const requestId = crypto.randomUUID(); diff --git a/apps/operator/src/middleware/__tests__/telegram-ip.test.ts b/apps/operator/src/middleware/telegram-ip.test.ts similarity index 94% rename from apps/operator/src/middleware/__tests__/telegram-ip.test.ts rename to apps/operator/src/middleware/telegram-ip.test.ts index 9758749..e18acdf 100644 --- a/apps/operator/src/middleware/__tests__/telegram-ip.test.ts +++ b/apps/operator/src/middleware/telegram-ip.test.ts @@ -1,9 +1,10 @@ import { Hono } from "hono"; import { afterEach, describe, expect, it, vi } from "vitest"; -import type { AppEnv } from "../../types/env"; -import { loggerMiddleware } from "../logger"; -import { telegramIpMiddleware } from "../telegram-ip"; +import type { AppEnv } from "~/types/env"; + +import { loggerMiddleware } from "./logger"; +import { telegramIpMiddleware } from "./telegram-ip"; const createApp = () => { const app = new Hono(); @@ -18,7 +19,7 @@ const sendRequest = (app: Hono, ip?: string) => { const headers: Record = { "Content-Type": "application/json", }; - if (ip) { + if (ip !== undefined && ip !== "") { headers["cf-connecting-ip"] = ip; } return app.request("/webhook/telegram", { diff --git a/apps/operator/src/middleware/telegram-ip.ts b/apps/operator/src/middleware/telegram-ip.ts index 23c6bb5..1bba231 100644 --- a/apps/operator/src/middleware/telegram-ip.ts +++ b/apps/operator/src/middleware/telegram-ip.ts @@ -1,6 +1,6 @@ import { createMiddleware } from "hono/factory"; -import type { AppEnv } from "../types/env"; +import type { AppEnv } from "~/types/env"; /** * Official Telegram IPv4 CIDR ranges. @@ -26,24 +26,24 @@ type CidrEntry = { mask: number; }; +// Missing octets count as 0, matching how bitwise ops coerce undefined/NaN. +const ipToNumber = (ip: string): number => { + const [a = 0, b = 0, c = 0, d = 0] = ip.split(".").map(Number); + return ((a << 24) | (b << 16) | (c << 8) | d) >>> 0; +}; + const parseCidr = (cidr: string): CidrEntry => { const [ip, prefix] = cidr.split("/"); - const parts = ip.split(".").map(Number); - const network = - ((parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3]) >>> 0; + if (ip === undefined || prefix === undefined) { + throw new Error(`Malformed CIDR: ${cidr}`); + } + const network = ipToNumber(ip); const mask = (~0 << (32 - Number(prefix))) >>> 0; return { network, mask }; }; const parsedCidrs = TELEGRAM_CIDRS.map(parseCidr); -const ipToNumber = (ip: string): number => { - const parts = ip.split(".").map(Number); - return ( - ((parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3]) >>> 0 - ); -}; - const isInTelegramRange = (ip: string): boolean => { const ipNum = ipToNumber(ip); return parsedCidrs.some( @@ -59,13 +59,13 @@ export const telegramIpMiddleware = createMiddleware( async (c, next) => { const ip = c.req.header("cf-connecting-ip"); - if (!ip || !isInTelegramRange(ip)) { + if (ip === undefined || ip === "" || !isInTelegramRange(ip)) { c.get("logger").warn("rejected request from non-Telegram IP", { ip: ip ?? "unknown", }); return c.json({ error: "Forbidden" }, 403); } - await next(); + return next(); } ); diff --git a/apps/operator/src/modules/health/controller.ts b/apps/operator/src/modules/health/controller.ts index 15a4453..80ad241 100644 --- a/apps/operator/src/modules/health/controller.ts +++ b/apps/operator/src/modules/health/controller.ts @@ -1,9 +1,7 @@ import type { Context } from "hono"; -import type { AppEnv } from "../../types/env"; +import type { AppEnv } from "~/types/env"; -const getHealth = (c: Context) => { - return c.json({ status: "ok" }); -}; +const getHealth = (c: Context) => c.json({ status: "ok" }); export { getHealth }; diff --git a/apps/operator/src/modules/health/routes.ts b/apps/operator/src/modules/health/routes.ts index 8a68946..e0cae0a 100644 --- a/apps/operator/src/modules/health/routes.ts +++ b/apps/operator/src/modules/health/routes.ts @@ -1,6 +1,7 @@ import { Hono } from "hono"; -import type { AppEnv } from "../../types/env"; +import type { AppEnv } from "~/types/env"; + import { getHealth } from "./controller"; const healthRoutes = new Hono(); diff --git a/apps/operator/src/modules/telegram/callback-data.ts b/apps/operator/src/modules/telegram/callback-data.ts index 400f51c..21512bb 100644 --- a/apps/operator/src/modules/telegram/callback-data.ts +++ b/apps/operator/src/modules/telegram/callback-data.ts @@ -5,7 +5,7 @@ type ParsedCallback = const parseCallbackData = ( data: string | undefined ): ParsedCallback | undefined => { - if (!data) { + if (data === undefined || data === "") { return undefined; } if (data.startsWith("q:")) { diff --git a/apps/operator/src/modules/telegram/controller.ts b/apps/operator/src/modules/telegram/controller.ts index ee86554..4289348 100644 --- a/apps/operator/src/modules/telegram/controller.ts +++ b/apps/operator/src/modules/telegram/controller.ts @@ -2,17 +2,15 @@ import type { Logger } from "@repo/logger"; import { TelegramService } from "@repo/telegram"; import type { Context } from "hono"; -import { PendingActionService } from "../../services/pending-action"; -import type { PendingAction } from "../../services/pending-action"; -import { PendingConversationService } from "../../services/pending-conversation"; -import { ScheduleService } from "../../services/schedule"; -import type { CreateScheduleInput } from "../../services/schedule"; -import type { AppEnv } from "../../types/env"; -import type { - TelegramCallbackQuery, - TelegramUpdate, -} from "../../types/telegram"; -import { splitMessage } from "../../utils/message"; +import { PendingActionService } from "~/services/pending-action"; +import type { PendingAction } from "~/services/pending-action"; +import { PendingConversationService } from "~/services/pending-conversation"; +import { ScheduleService } from "~/services/schedule"; +import type { CreateScheduleInput } from "~/services/schedule"; +import type { AppEnv } from "~/types/env"; +import type { TelegramCallbackQuery, TelegramUpdate } from "~/types/telegram"; +import { splitMessage } from "~/utils/message"; + import { parseCallbackData } from "./callback-data"; import type { ParsedCallback } from "./callback-data"; import { ConversationRunner } from "./conversation-runner"; @@ -28,6 +26,9 @@ const executePendingAction = async ( if (action.type === "create_schedule") { const input = action.payload as unknown as CreateScheduleInput; const row = await scheduleService.create(chatId, input); + if (row === undefined) { + throw new Error("Schedule insert returned no row"); + } logger.info("schedule created", { scheduleId: row.id }); return `Schedule created: ${action.description}\nID: ${row.id}\nNext run: ${row.nextRunAt}`; } @@ -74,7 +75,7 @@ const handleMessage = async ( isAllowedChat, }); - if (!message?.text) { + if (message?.text === undefined || message.text === "") { return c.json({ ok: true }); } @@ -206,17 +207,17 @@ const ackAndClearKeyboard = async ( ): Promise => { await telegram .answerCallbackQuery({ callback_query_id: cq.id, text: toast }) - .catch((err: unknown) => { + .catch((error: unknown) => { logger.warn("answerCallbackQuery failed", { - error: err instanceof Error ? err.message : String(err), + error: error instanceof Error ? error.message : String(error), }); }); if (messageId !== undefined) { await telegram .editMessageReplyMarkup({ chat_id: chatId, message_id: messageId }) - .catch((err: unknown) => { + .catch((error: unknown) => { logger.warn("editMessageReplyMarkup failed", { - error: err instanceof Error ? err.message : String(err), + error: error instanceof Error ? error.message : String(error), }); }); } @@ -262,7 +263,7 @@ const handleConfirmOrCancel = async ( await ackAndClearKeyboard(cq, chatId, messageId, telegram, logger, toast); } - if (resultMessage) { + if (resultMessage !== undefined && resultMessage !== "") { for (const chunk of splitMessage(resultMessage)) { await telegram.sendMessage({ chat_id: chatId, text: chunk }); } @@ -354,9 +355,14 @@ const handleAnswerCallback = async ( "Recorded" ); - const chosen = consumed.options[parsed.optionIndex]; const runner = new ConversationRunner(chatId, c.env, logger, telegram); try { + const chosen = consumed.options[parsed.optionIndex]; + if (chosen === undefined) { + throw new Error( + `Option index ${String(parsed.optionIndex)} out of range for consumed conversation` + ); + } await runner.resumeFromAnswer( consumed.messages as Parameters[0], consumed.pendingToolCallId, diff --git a/apps/operator/src/modules/telegram/conversation-runner.ts b/apps/operator/src/modules/telegram/conversation-runner.ts index bf80320..f9ddece 100644 --- a/apps/operator/src/modules/telegram/conversation-runner.ts +++ b/apps/operator/src/modules/telegram/conversation-runner.ts @@ -2,28 +2,29 @@ import type { Logger } from "@repo/logger"; import type { TelegramService } from "@repo/telegram"; import { validateSourceUrl } from "@repo/url-validator"; -import { buildInitialMessages, OpenAiService } from "../../services/openai"; +import { buildInitialMessages, OpenAiService } from "~/services/openai"; import type { ToolExecutor, ToolLoopMessages, ToolLoopOutcome, ToolResult, -} from "../../services/openai"; -import { PendingActionService } from "../../services/pending-action"; -import { PendingConversationService } from "../../services/pending-conversation"; -import type { QuestionOption } from "../../services/pending-conversation"; +} from "~/services/openai"; +import { PendingActionService } from "~/services/pending-action"; +import { PendingConversationService } from "~/services/pending-conversation"; +import type { QuestionOption } from "~/services/pending-conversation"; import { createScheduleSchema, MAX_ACTIVE_SCHEDULES, ScheduleService, -} from "../../services/schedule"; -import type { AppEnv } from "../../types/env"; -import { markdownToTelegramHtml } from "../../utils/markdown-to-html"; +} from "~/services/schedule"; +import type { AppEnv } from "~/types/env"; +import { markdownToTelegramHtml } from "~/utils/markdown-to-html"; import { splitMessage, TELEGRAM_HTML_SAFE_LENGTH, TELEGRAM_MAX_MESSAGE_LENGTH, -} from "../../utils/message"; +} from "~/utils/message"; + import { buildConfirmationKeyboard, buildQuestionKeyboard, @@ -148,7 +149,7 @@ class ConversationRunner { return { error: validation.error.message }; } - if (input.sourceUrl) { + if (input.sourceUrl !== undefined && input.sourceUrl !== "") { const urlCheck = validateSourceUrl(input.sourceUrl); if (!urlCheck.valid) { return { error: urlCheck.reason }; @@ -157,7 +158,7 @@ class ConversationRunner { this.pendingButtonToken = await this.pendingActions.set(this.chatId, { type: "create_schedule", - payload: input as unknown as Record, + payload: input, description: formatScheduleDescription("create", args), }); @@ -168,14 +169,14 @@ class ConversationRunner { if (name === "delete_schedule") { const id = args.id as string | undefined; - if (!id) { + if (id === undefined || id === "") { return { error: "Missing schedule ID" }; } const summary = typeof args.summary === "string" && args.summary.trim().length > 0 ? args.summary.trim() : undefined; - if (!summary) { + if (summary === undefined) { return { error: "Missing summary. Pass a human-readable summary built from the matching list_schedules entry (type, time, description) so the user can recognize what's being deleted.", @@ -213,11 +214,11 @@ class ConversationRunner { const chunks = [ ...splitMessage(outcome.question, TELEGRAM_MAX_MESSAGE_LENGTH), ]; - for (let i = 0; i < chunks.length; i++) { + for (const [i, chunk] of chunks.entries()) { const isLast = i === chunks.length - 1; await this.telegram.sendMessage({ chat_id: this.chatId, - text: chunks[i], + text: chunk, ...(isLast ? { reply_markup: replyMarkup } : {}), }); } @@ -228,17 +229,18 @@ class ConversationRunner { return; } - const replyMarkup = this.pendingButtonToken - ? buildConfirmationKeyboard(this.pendingButtonToken) - : undefined; + const replyMarkup = + this.pendingButtonToken !== undefined && this.pendingButtonToken !== "" + ? buildConfirmationKeyboard(this.pendingButtonToken) + : undefined; const chunks = [ ...splitMessage(outcome.content, TELEGRAM_HTML_SAFE_LENGTH), ]; - for (let i = 0; i < chunks.length; i++) { + for (const [i, chunk] of chunks.entries()) { const isLast = i === chunks.length - 1; await this.telegram.sendMessage({ chat_id: this.chatId, - text: markdownToTelegramHtml(chunks[i]), + text: markdownToTelegramHtml(chunk), parse_mode: "HTML" as const, ...(isLast && replyMarkup ? { reply_markup: replyMarkup } : {}), }); diff --git a/apps/operator/src/modules/telegram/routes-callback.test.ts b/apps/operator/src/modules/telegram/routes-callback.test.ts new file mode 100644 index 0000000..b30d517 --- /dev/null +++ b/apps/operator/src/modules/telegram/routes-callback.test.ts @@ -0,0 +1,184 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { + callbackUpdate, + ENV, + fetchedMethods, + mockFetch, + pendingConsumeByTokenMock, + resetCallbackMocks, + sendRequest, +} from "./routes-test-helpers"; + +describe("POST /webhook/telegram — callback_query", () => { + const headers = { + "x-telegram-bot-api-secret-token": ENV.TELEGRAM_WEBHOOK_SECRET, + }; + + beforeEach(() => { + resetCallbackMocks(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("ignores callback_query from disallowed user", async () => { + const res = await sendRequest( + callbackUpdate({ fromId: 99_999, data: "c:tok-x" }), + headers + ); + + expect(res.status).toBe(200); + expect(mockFetch).not.toHaveBeenCalled(); + expect(pendingConsumeByTokenMock).not.toHaveBeenCalled(); + }); + + it("ignores callback_query from disallowed chat", async () => { + const res = await sendRequest( + callbackUpdate({ chatId: 99_999, data: "c:tok-x" }), + headers + ); + + expect(res.status).toBe(200); + expect(mockFetch).not.toHaveBeenCalled(); + expect(pendingConsumeByTokenMock).not.toHaveBeenCalled(); + }); + + it("acks but does nothing when callback has no message field", async () => { + const res = await sendRequest( + callbackUpdate({ includeMessage: false, data: "c:tok-x" }), + headers + ); + + expect(res.status).toBe(200); + expect(pendingConsumeByTokenMock).not.toHaveBeenCalled(); + expect(fetchedMethods()).toContain("answerCallbackQuery"); + expect(fetchedMethods()).not.toContain("editMessageReplyMarkup"); + expect(fetchedMethods()).not.toContain("sendMessage"); + }); + + it("acks and ignores malformed callback_data", async () => { + const res = await sendRequest(callbackUpdate({ data: "garbage" }), headers); + + expect(res.status).toBe(200); + expect(pendingConsumeByTokenMock).not.toHaveBeenCalled(); + expect(fetchedMethods()).toContain("answerCallbackQuery"); + }); + + it("expired/unknown token: acks with toast and clears buttons, no execution", async () => { + pendingConsumeByTokenMock.mockResolvedValueOnce(undefined); + + const res = await sendRequest( + callbackUpdate({ data: "c:tok-stale" }), + headers + ); + + expect(res.status).toBe(200); + const ackCall = mockFetch.mock.calls.find( + (c) => typeof c[0] === "string" && c[0].endsWith("/answerCallbackQuery") + ); + expect(ackCall).toBeDefined(); + if (!ackCall) { + return; + } + const ackBody = JSON.parse((ackCall[1] as { body: string }).body) as Record< + string, + unknown + >; + expect(ackBody.text).toBe("Expired or already used"); + + const editCall = mockFetch.mock.calls.find( + (c) => + typeof c[0] === "string" && c[0].endsWith("/editMessageReplyMarkup") + ); + expect(editCall).toBeDefined(); + const sendCall = mockFetch.mock.calls.find( + (c) => typeof c[0] === "string" && c[0].endsWith("/sendMessage") + ); + expect(sendCall).toBeUndefined(); + }); + + it("cancel: acks with 'Cancelled', clears buttons, no execution", async () => { + pendingConsumeByTokenMock.mockResolvedValueOnce({ + type: "create_schedule", + payload: {}, + description: "any", + }); + + const res = await sendRequest(callbackUpdate({ data: "x:tok-1" }), headers); + + expect(res.status).toBe(200); + expect(pendingConsumeByTokenMock).toHaveBeenCalledWith(12_345, "tok-1"); + const ackCall = mockFetch.mock.calls.find( + (c) => typeof c[0] === "string" && c[0].endsWith("/answerCallbackQuery") + ); + expect(ackCall).toBeDefined(); + if (!ackCall) { + return; + } + const ackBody = JSON.parse((ackCall[1] as { body: string }).body) as Record< + string, + unknown + >; + expect(ackBody.text).toBe("Cancelled"); + const sendCall = mockFetch.mock.calls.find( + (c) => typeof c[0] === "string" && c[0].endsWith("/sendMessage") + ); + expect(sendCall).toBeUndefined(); + }); + + it("confirm delete: acks 'Schedule deleted' and sends result message", async () => { + pendingConsumeByTokenMock.mockResolvedValueOnce({ + type: "delete_schedule", + payload: { id: "abc" }, + description: "Delete schedule abc", + }); + const removeMock = vi.fn().mockResolvedValue(true); + const { ScheduleService } = await import("~/services/schedule"); + vi.spyOn(ScheduleService.prototype, "remove").mockImplementation( + removeMock + ); + + const res = await sendRequest(callbackUpdate({ data: "c:tok-1" }), headers); + + expect(res.status).toBe(200); + expect(removeMock).toHaveBeenCalledWith("abc", 12_345); + const ackCall = mockFetch.mock.calls.find( + (c) => typeof c[0] === "string" && c[0].endsWith("/answerCallbackQuery") + ); + expect(ackCall).toBeDefined(); + if (!ackCall) { + return; + } + const ackBody = JSON.parse((ackCall[1] as { body: string }).body) as Record< + string, + unknown + >; + expect(ackBody.text).toBe("Schedule deleted"); + const sendCall = mockFetch.mock.calls.find( + (c) => typeof c[0] === "string" && c[0].endsWith("/sendMessage") + ); + expect(sendCall).toBeDefined(); + }); + + it("double-tap: second consumeByToken returns undefined → no double execution", async () => { + pendingConsumeByTokenMock + .mockResolvedValueOnce({ + type: "delete_schedule", + payload: { id: "abc" }, + description: "Delete schedule abc", + }) + .mockResolvedValueOnce(undefined); + const removeMock = vi.fn().mockResolvedValue(true); + const { ScheduleService } = await import("~/services/schedule"); + vi.spyOn(ScheduleService.prototype, "remove").mockImplementation( + removeMock + ); + + await sendRequest(callbackUpdate({ data: "c:tok-1" }), headers); + await sendRequest(callbackUpdate({ data: "c:tok-1" }), headers); + + expect(removeMock).toHaveBeenCalledTimes(1); + }); +}); diff --git a/apps/operator/src/modules/telegram/routes-message.test.ts b/apps/operator/src/modules/telegram/routes-message.test.ts new file mode 100644 index 0000000..68e971d --- /dev/null +++ b/apps/operator/src/modules/telegram/routes-message.test.ts @@ -0,0 +1,247 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { + conversationSetMock, + ENV, + mockFetch, + openaiCreateMock, + pendingConsumeByChatIdMock, + pendingSetMock, + resetMessageMocks, + sendRequest, + validUpdate, +} from "./routes-test-helpers"; + +describe("POST /webhook/telegram", () => { + beforeEach(() => { + resetMessageMocks(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("sends AI response as HTML with parse_mode", async () => { + const res = await sendRequest(validUpdate, { + "x-telegram-bot-api-secret-token": ENV.TELEGRAM_WEBHOOK_SECRET, + }); + + expect(res.status).toBe(200); + expect(await res.json()).toEqual({ ok: true }); + expect(mockFetch).toHaveBeenCalledWith( + `https://api.telegram.org/bot${ENV.TELEGRAM_BOT_TOKEN}/sendMessage`, + expect.objectContaining({ + method: "POST", + body: JSON.stringify({ + chat_id: 12_345, + text: "AI response", + parse_mode: "HTML", + }), + }) + ); + }); + + it("converts markdown in AI response to HTML before sending", async () => { + openaiCreateMock.mockResolvedValueOnce({ + choices: [ + { + message: { + content: "# Title\n\nThis is **bold** and `code`.", + }, + }, + ], + }); + + const res = await sendRequest(validUpdate, { + "x-telegram-bot-api-secret-token": ENV.TELEGRAM_WEBHOOK_SECRET, + }); + + expect(res.status).toBe(200); + const firstCall = mockFetch.mock.calls[0]; + expect(firstCall).toBeDefined(); + if (!firstCall) { + return; + } + const sentBody = JSON.parse((firstCall[1] as { body: string }).body) as { + text: string; + parse_mode: string; + }; + expect(sentBody.parse_mode).toBe("HTML"); + expect(sentBody.text).toContain("Title"); + expect(sentBody.text).toContain("bold"); + expect(sentBody.text).toContain("code"); + expect(sentBody.text).not.toContain("# Title"); + expect(sentBody.text).not.toContain("**bold**"); + }); + + it("nudges the model to ask first when create_schedule omits use_browser for a monitor", async () => { + openaiCreateMock.mockClear(); + // First turn: model emits create_schedule with source_url but no use_browser. + openaiCreateMock.mockResolvedValueOnce({ + choices: [ + { + finish_reason: "tool_calls", + message: { + role: "assistant", + content: null, + tool_calls: [ + { + id: "call_create_1", + type: "function", + function: { + name: "create_schedule", + arguments: JSON.stringify({ + schedule_type: "daily", + hour: 9, + minute: 0, + timezone: "Europe/Helsinki", + source_url: "https://twitter.com/elonmusk", + message_prompt: "Check for new posts", + description: "Monitor Twitter", + }), + }, + }, + ], + }, + }, + ], + }); + // Second turn: model corrects and asks the question. + openaiCreateMock.mockResolvedValueOnce({ + choices: [ + { + finish_reason: "tool_calls", + message: { + role: "assistant", + content: null, + tool_calls: [ + { + id: "call_q_1", + type: "function", + function: { + name: "ask_user_question", + arguments: JSON.stringify({ + question: + "Should I use the browser scraper for this page (renders JavaScript)?", + options: [ + { label: "Yes — needs JS rendering", value: true }, + { label: "No — static HTML", value: false }, + ], + }), + }, + }, + ], + }, + }, + ], + }); + conversationSetMock.mockResolvedValueOnce("conv-tok-1"); + + const update = { + ...validUpdate, + message: { ...validUpdate.message, text: "monitor twitter.com" }, + }; + const res = await sendRequest(update, { + "x-telegram-bot-api-secret-token": ENV.TELEGRAM_WEBHOOK_SECRET, + }); + + expect(res.status).toBe(200); + + // The second OpenAI call must include the guard error as the tool result + // for the rejected create_schedule call. + const secondCall = openaiCreateMock.mock.calls[1] as + | [ + { + messages: { + role: string; + tool_call_id?: string; + content?: string; + }[]; + }, + ] + | undefined; + expect(secondCall).toBeDefined(); + if (!secondCall) { + return; + } + const toolMessage = secondCall[0].messages.find( + (m) => m.role === "tool" && m.tool_call_id === "call_create_1" + ); + expect(toolMessage?.content).toContain("use_browser"); + expect(toolMessage?.content).toContain("ask_user_question"); + + // No schedule was committed — pendingActions.set was not invoked. + expect(pendingSetMock).not.toHaveBeenCalled(); + + // Question was dispatched to the user with inline buttons. + expect(conversationSetMock).toHaveBeenCalledTimes(1); + const sendCall = mockFetch.mock.calls.find( + (c) => typeof c[0] === "string" && c[0].endsWith("/sendMessage") + ); + expect(sendCall).toBeDefined(); + if (!sendCall) { + return; + } + const sendBody = JSON.parse( + (sendCall[1] as { body: string }).body + ) as Record; + expect(sendBody.reply_markup).toBeDefined(); + }); + + it("sends error message as plain text when OpenAI fails", async () => { + openaiCreateMock.mockRejectedValueOnce( + new Error("401 insufficient permissions") + ); + + const res = await sendRequest(validUpdate, { + "x-telegram-bot-api-secret-token": ENV.TELEGRAM_WEBHOOK_SECRET, + }); + + expect(res.status).toBe(200); + expect(await res.json()).toEqual({ ok: true }); + const firstCall = mockFetch.mock.calls[0]; + expect(firstCall).toBeDefined(); + if (!firstCall) { + return; + } + const sentBody = JSON.parse( + (firstCall[1] as { body: string }).body + ) as Record; + expect(sentBody).not.toHaveProperty("parse_mode"); + expect(sentBody.text).toBe( + "Something went wrong while generating a response. Please try again." + ); + }); + + it("sends 'Action cancelled.' as plain text without parse_mode for typed non-YES reply", async () => { + pendingConsumeByChatIdMock.mockResolvedValueOnce({ + type: "create_schedule", + payload: {}, + description: "test action", + }); + + const update = { + ...validUpdate, + message: { ...validUpdate.message, text: "no" }, + }; + + const res = await sendRequest(update, { + "x-telegram-bot-api-secret-token": ENV.TELEGRAM_WEBHOOK_SECRET, + }); + + expect(res.status).toBe(200); + const cancellationCall = mockFetch.mock.calls.find((call) => { + const body = (call[1] as { body: string } | undefined)?.body; + return typeof body === "string" && body.includes("Action cancelled."); + }); + expect(cancellationCall).toBeDefined(); + if (!cancellationCall) { + return; + } + const body = JSON.parse( + (cancellationCall[1] as { body: string }).body + ) as Record; + expect(body).not.toHaveProperty("parse_mode"); + expect(body.text).toBe("Action cancelled."); + }); +}); diff --git a/apps/operator/src/modules/telegram/routes-question-callback.test.ts b/apps/operator/src/modules/telegram/routes-question-callback.test.ts new file mode 100644 index 0000000..6073839 --- /dev/null +++ b/apps/operator/src/modules/telegram/routes-question-callback.test.ts @@ -0,0 +1,241 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { + callbackUpdate, + conversationConsumeByTokenMock, + conversationGetByTokenMock, + ENV, + mockFetch, + openaiCreateMock, + resetCallbackMocks, + sendRequest, +} from "./routes-test-helpers"; + +describe("POST /webhook/telegram — callback_query", () => { + const headers = { + "x-telegram-bot-api-secret-token": ENV.TELEGRAM_WEBHOOK_SECRET, + }; + + beforeEach(() => { + resetCallbackMocks(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe("question-answer callback (q::)", () => { + it("acks 'Malformed' for non-numeric option index", async () => { + const res = await sendRequest( + callbackUpdate({ data: "q:tok-1:abc" }), + headers + ); + + expect(res.status).toBe(200); + expect(conversationConsumeByTokenMock).not.toHaveBeenCalled(); + const ackCall = mockFetch.mock.calls.find( + (c) => typeof c[0] === "string" && c[0].endsWith("/answerCallbackQuery") + ); + expect(ackCall).toBeDefined(); + if (!ackCall) { + return; + } + const ackBody = JSON.parse( + (ackCall[1] as { body: string }).body + ) as Record; + expect(ackBody.text).toBe("Malformed"); + }); + + it("acks 'Expired or already used' when conversation token is unknown", async () => { + conversationGetByTokenMock.mockResolvedValueOnce(undefined); + + const res = await sendRequest( + callbackUpdate({ data: "q:tok-x:0" }), + headers + ); + + expect(res.status).toBe(200); + expect(conversationGetByTokenMock).toHaveBeenCalledWith(12_345, "tok-x"); + // Out-of-range/unknown tokens must not consume the row. + expect(conversationConsumeByTokenMock).not.toHaveBeenCalled(); + const ackCall = mockFetch.mock.calls.find( + (c) => typeof c[0] === "string" && c[0].endsWith("/answerCallbackQuery") + ); + expect(ackCall).toBeDefined(); + if (!ackCall) { + return; + } + const ackBody = JSON.parse( + (ackCall[1] as { body: string }).body + ) as Record; + expect(ackBody.text).toBe("Expired or already used"); + }); + + it("acks 'Invalid option' when index is out of range and preserves pending state", async () => { + conversationGetByTokenMock.mockResolvedValueOnce({ + messages: [], + pendingToolCallId: "call_q", + options: [ + { label: "Yes", value: true }, + { label: "No", value: false }, + ], + }); + + const res = await sendRequest( + callbackUpdate({ data: "q:tok-r:9" }), + headers + ); + + expect(res.status).toBe(200); + // The pending row must remain intact so a tampered/malformed callback + // can't strand the user with no way to retry from the real buttons. + expect(conversationConsumeByTokenMock).not.toHaveBeenCalled(); + const ackCall = mockFetch.mock.calls.find( + (c) => typeof c[0] === "string" && c[0].endsWith("/answerCallbackQuery") + ); + expect(ackCall).toBeDefined(); + if (!ackCall) { + return; + } + const ackBody = JSON.parse( + (ackCall[1] as { body: string }).body + ) as Record; + expect(ackBody.text).toBe("Invalid option"); + }); + + it("acks 'Something went wrong' when peek throws", async () => { + conversationGetByTokenMock.mockRejectedValueOnce(new Error("db down")); + + const res = await sendRequest( + callbackUpdate({ data: "q:tok-r:0" }), + headers + ); + + expect(res.status).toBe(200); + const ackCall = mockFetch.mock.calls.find( + (c) => typeof c[0] === "string" && c[0].endsWith("/answerCallbackQuery") + ); + expect(ackCall).toBeDefined(); + if (!ackCall) { + return; + } + const ackBody = JSON.parse( + (ackCall[1] as { body: string }).body + ) as Record; + expect(ackBody.text).toBe("Something went wrong"); + }); + + it("happy path: appends tool result with raw boolean value and resumes", async () => { + const stored = { + messages: [ + { role: "system", content: "sys" }, + { role: "user", content: "monitor twitter.com" }, + { + role: "assistant", + content: null, + tool_calls: [ + { + id: "call_q", + type: "function", + function: { + name: "ask_user_question", + arguments: JSON.stringify({ + question: "Use browser?", + options: [ + { label: "Yes", value: true }, + { label: "No", value: false }, + ], + }), + }, + }, + ], + }, + ], + pendingToolCallId: "call_q", + options: [ + { label: "Yes", value: true }, + { label: "No", value: false }, + ], + }; + conversationGetByTokenMock.mockResolvedValueOnce(stored); + conversationConsumeByTokenMock.mockResolvedValueOnce(stored); + + // OpenAI returns a final answer on resume. + openaiCreateMock.mockResolvedValueOnce({ + choices: [ + { + finish_reason: "stop", + message: { role: "assistant", content: "Got it." }, + }, + ], + }); + + const res = await sendRequest( + callbackUpdate({ data: "q:tok-r:0" }), + headers + ); + + expect(res.status).toBe(200); + expect(conversationConsumeByTokenMock).toHaveBeenCalledWith( + 12_345, + "tok-r" + ); + + // The OpenAI call should have received messages with a tool result that + // carries the raw boolean (NOT a string). + const openaiCall = openaiCreateMock.mock.calls.at(-1) as + [{ messages: { role: string; content?: string }[] }] | undefined; + expect(openaiCall).toBeDefined(); + if (!openaiCall) { + return; + } + const sentMessages = openaiCall[0].messages; + const toolResult = sentMessages.find((m) => m.role === "tool"); + expect(toolResult).toBeDefined(); + expect(toolResult?.content).toBe( + JSON.stringify({ value: true, label: "Yes" }) + ); + + // The bot then sends the LLM's final reply. + const sendCall = mockFetch.mock.calls.find( + (c) => typeof c[0] === "string" && c[0].endsWith("/sendMessage") + ); + expect(sendCall).toBeDefined(); + }); + + it("ack toast says 'Recorded' on happy path", async () => { + const stored = { + messages: [{ role: "system", content: "sys" }], + pendingToolCallId: "call_q", + options: [ + { label: "Yes", value: true }, + { label: "No", value: false }, + ], + }; + conversationGetByTokenMock.mockResolvedValueOnce(stored); + conversationConsumeByTokenMock.mockResolvedValueOnce(stored); + openaiCreateMock.mockResolvedValueOnce({ + choices: [ + { + finish_reason: "stop", + message: { role: "assistant", content: "ok" }, + }, + ], + }); + + await sendRequest(callbackUpdate({ data: "q:tok-r:1" }), headers); + + const ackCall = mockFetch.mock.calls.find( + (c) => typeof c[0] === "string" && c[0].endsWith("/answerCallbackQuery") + ); + expect(ackCall).toBeDefined(); + if (!ackCall) { + return; + } + const ackBody = JSON.parse( + (ackCall[1] as { body: string }).body + ) as Record; + expect(ackBody.text).toBe("Recorded"); + }); + }); +}); diff --git a/apps/operator/src/modules/telegram/routes-test-helpers.ts b/apps/operator/src/modules/telegram/routes-test-helpers.ts new file mode 100644 index 0000000..6506f9d --- /dev/null +++ b/apps/operator/src/modules/telegram/routes-test-helpers.ts @@ -0,0 +1,210 @@ +// Shared setup for the telegram route test files. Importing this module +// registers the module mocks below, so it must be imported before anything +// that pulls in the route/controller graph. +import { Hono } from "hono"; +import { vi } from "vitest"; + +import { onErrorHandler } from "~/middleware/error-handlers"; +import { loggerMiddleware } from "~/middleware/logger"; +import type { AppEnv } from "~/types/env"; + +import { telegramRoutes } from "./routes"; + +const openaiCreateMock = vi.fn().mockResolvedValue({ + choices: [{ message: { content: "AI response" } }], +}); + +vi.mock("openai", () => ({ + default: class { + chat = { completions: { create: openaiCreateMock } }; + }, +})); + +const pendingConsumeByChatIdMock = vi.fn(); +const pendingConsumeByTokenMock = vi.fn(); +const pendingClearMock = vi.fn(); +const pendingSetMock = vi.fn(); + +vi.mock("~/services/pending-action", () => ({ + PendingActionService: class { + consumeByChatId = pendingConsumeByChatIdMock; + consumeByToken = pendingConsumeByTokenMock; + clear = pendingClearMock; + set = pendingSetMock; + }, + generateToken: () => "tok-fake", +})); + +const conversationGetByTokenMock = vi.fn(); +const conversationConsumeByTokenMock = vi.fn(); +const conversationSetMock = vi.fn(); +const conversationClearMock = vi.fn(); + +vi.mock("~/services/pending-conversation", () => ({ + PendingConversationService: class { + getByToken = conversationGetByTokenMock; + consumeByToken = conversationConsumeByTokenMock; + set = conversationSetMock; + clear = conversationClearMock; + }, +})); + +const mockFetch = vi.fn(); +globalThis.fetch = mockFetch; + +const createMockD1 = () => { + const mockStatement = { + bind: vi.fn().mockReturnThis(), + all: vi.fn().mockResolvedValue({ results: [], meta: {} }), + run: vi.fn().mockResolvedValue({ meta: { changes: 0 } }), + first: vi.fn().mockResolvedValue(null), + raw: vi.fn().mockResolvedValue([]), + }; + return { + prepare: vi.fn().mockReturnValue(mockStatement), + batch: vi.fn().mockResolvedValue([]), + exec: vi.fn().mockResolvedValue({}), + dump: vi.fn().mockResolvedValue(new ArrayBuffer(0)), + } as unknown as D1Database; +}; + +const ENV = { + TELEGRAM_BOT_TOKEN: "test-token", + TELEGRAM_WEBHOOK_SECRET: "test-secret-that-is-at-least-32-chars!", + ALLOWED_CHAT_ID: "12345", + OPENAI_API_KEY: "test-openai-key", + DB: createMockD1(), +}; + +const validUpdate = { + update_id: 1, + message: { + message_id: 1, + chat: { id: 12_345, type: "private" }, + date: 1_234_567_890, + text: "hello", + }, +}; + +const callbackUpdate = (overrides: { + data?: string; + fromId?: number; + chatId?: number; + messageId?: number; + includeMessage?: boolean; +}) => { + const includeMessage = overrides.includeMessage ?? true; + return { + update_id: 2, + callback_query: { + id: "cb-1", + from: { id: overrides.fromId ?? 12_345, is_bot: false, first_name: "U" }, + data: overrides.data ?? "c:tok123", + ...(includeMessage + ? { + message: { + message_id: overrides.messageId ?? 99, + chat: { id: overrides.chatId ?? 12_345, type: "private" }, + date: 1_234_567_890, + }, + } + : {}), + }, + }; +}; + +const app = new Hono(); +app.use("*", loggerMiddleware); +app.onError(onErrorHandler); +app.route("/", telegramRoutes); + +const TELEGRAM_IP = "149.154.167.50"; + +const sendRequest = (body: unknown, headers: Record = {}) => + app.request( + "/webhook/telegram", + { + method: "POST", + headers: { + "Content-Type": "application/json", + "cf-connecting-ip": TELEGRAM_IP, + ...headers, + }, + body: JSON.stringify(body), + }, + ENV + ); + +const fetchedMethods = (): string[] => + mockFetch.mock.calls.map((call) => { + const url = call[0] as string; + return url.split("/").pop() ?? ""; + }); + +const mockFetchOk = () => { + mockFetch.mockImplementation(() => + Promise.resolve( + new Response(JSON.stringify({ ok: true }), { + headers: { "Content-Type": "application/json" }, + }) + ) + ); +}; + +const resetPendingActionMocks = () => { + pendingConsumeByChatIdMock.mockReset(); + pendingConsumeByChatIdMock.mockResolvedValue(undefined); + pendingConsumeByTokenMock.mockReset(); + pendingConsumeByTokenMock.mockResolvedValue(undefined); + pendingClearMock.mockReset(); + pendingClearMock.mockResolvedValue(undefined); + pendingSetMock.mockReset(); + pendingSetMock.mockResolvedValue("tok-default"); +}; + +// Per-test reset for the plain-message webhook tests. +const resetMessageMocks = () => { + mockFetch.mockReset(); + ENV.DB = createMockD1(); + resetPendingActionMocks(); + openaiCreateMock.mockResolvedValue({ + choices: [{ message: { content: "AI response" } }], + }); + mockFetchOk(); +}; + +// Per-test reset for the callback_query webhook tests. +const resetCallbackMocks = () => { + mockFetch.mockReset(); + ENV.DB = createMockD1(); + resetPendingActionMocks(); + conversationGetByTokenMock.mockReset(); + conversationGetByTokenMock.mockResolvedValue(undefined); + conversationConsumeByTokenMock.mockReset(); + conversationConsumeByTokenMock.mockResolvedValue(undefined); + conversationSetMock.mockReset(); + conversationSetMock.mockResolvedValue("conv-tok"); + conversationClearMock.mockReset(); + conversationClearMock.mockResolvedValue(undefined); + mockFetchOk(); +}; + +export { + app, + callbackUpdate, + conversationConsumeByTokenMock, + conversationGetByTokenMock, + conversationSetMock, + ENV, + fetchedMethods, + mockFetch, + openaiCreateMock, + pendingConsumeByChatIdMock, + pendingConsumeByTokenMock, + pendingSetMock, + resetCallbackMocks, + resetMessageMocks, + sendRequest, + TELEGRAM_IP, + validUpdate, +}; diff --git a/apps/operator/src/modules/telegram/routes.test.ts b/apps/operator/src/modules/telegram/routes.test.ts index 9f50e2d..93f7cc3 100644 --- a/apps/operator/src/modules/telegram/routes.test.ts +++ b/apps/operator/src/modules/telegram/routes.test.ts @@ -1,158 +1,18 @@ -import { Hono } from "hono"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -const openaiCreateMock = vi.fn().mockResolvedValue({ - choices: [{ message: { content: "AI response" } }], -}); - -vi.mock("openai", () => ({ - default: class { - chat = { completions: { create: openaiCreateMock } }; - }, -})); - -const pendingConsumeByChatIdMock = vi.fn(); -const pendingConsumeByTokenMock = vi.fn(); -const pendingClearMock = vi.fn(); -const pendingSetMock = vi.fn(); - -vi.mock("../../services/pending-action", () => ({ - PendingActionService: class { - consumeByChatId = pendingConsumeByChatIdMock; - consumeByToken = pendingConsumeByTokenMock; - clear = pendingClearMock; - set = pendingSetMock; - }, - generateToken: () => "tok-fake", -})); - -const conversationGetByTokenMock = vi.fn(); -const conversationConsumeByTokenMock = vi.fn(); -const conversationSetMock = vi.fn(); -const conversationClearMock = vi.fn(); - -vi.mock("../../services/pending-conversation", () => ({ - PendingConversationService: class { - getByToken = conversationGetByTokenMock; - consumeByToken = conversationConsumeByTokenMock; - set = conversationSetMock; - clear = conversationClearMock; - }, -})); - -import { onErrorHandler } from "../../middleware/error-handlers"; -import { loggerMiddleware } from "../../middleware/logger"; -import type { AppEnv } from "../../types/env"; -import { TELEGRAM_WEBHOOK_MAX_BODY_BYTES, telegramRoutes } from "./routes"; - -const mockFetch = vi.fn(); -globalThis.fetch = mockFetch; - -const createMockD1 = () => { - const mockStatement = { - bind: vi.fn().mockReturnThis(), - all: vi.fn().mockResolvedValue({ results: [], meta: {} }), - run: vi.fn().mockResolvedValue({ meta: { changes: 0 } }), - first: vi.fn().mockResolvedValue(null), - raw: vi.fn().mockResolvedValue([]), - }; - return { - prepare: vi.fn().mockReturnValue(mockStatement), - batch: vi.fn().mockResolvedValue([]), - exec: vi.fn().mockResolvedValue({}), - dump: vi.fn().mockResolvedValue(new ArrayBuffer(0)), - } as unknown as D1Database; -}; - -const ENV = { - TELEGRAM_BOT_TOKEN: "test-token", - TELEGRAM_WEBHOOK_SECRET: "test-secret-that-is-at-least-32-chars!", - ALLOWED_CHAT_ID: "12345", - OPENAI_API_KEY: "test-openai-key", - DB: createMockD1(), -}; - -const validUpdate = { - update_id: 1, - message: { - message_id: 1, - chat: { id: 12345, type: "private" }, - date: 1234567890, - text: "hello", - }, -}; - -const callbackUpdate = (overrides: { - data?: string; - fromId?: number; - chatId?: number; - messageId?: number; - includeMessage?: boolean; -}) => { - const includeMessage = overrides.includeMessage ?? true; - return { - update_id: 2, - callback_query: { - id: "cb-1", - from: { id: overrides.fromId ?? 12345, is_bot: false, first_name: "U" }, - data: overrides.data ?? "c:tok123", - ...(includeMessage - ? { - message: { - message_id: overrides.messageId ?? 99, - chat: { id: overrides.chatId ?? 12345, type: "private" }, - date: 1234567890, - }, - } - : {}), - }, - }; -}; - -const app = new Hono(); -app.use("*", loggerMiddleware); -app.onError(onErrorHandler); -app.route("/", telegramRoutes); - -const TELEGRAM_IP = "149.154.167.50"; - -const sendRequest = (body: unknown, headers: Record = {}) => - app.request( - "/webhook/telegram", - { - method: "POST", - headers: { - "Content-Type": "application/json", - "cf-connecting-ip": TELEGRAM_IP, - ...headers, - }, - body: JSON.stringify(body), - }, - ENV - ); +import { + app, + ENV, + mockFetch, + resetMessageMocks, + sendRequest, + TELEGRAM_IP, + validUpdate, +} from "./routes-test-helpers"; describe("POST /webhook/telegram", () => { beforeEach(() => { - mockFetch.mockReset(); - ENV.DB = createMockD1(); - pendingConsumeByChatIdMock.mockReset(); - pendingConsumeByChatIdMock.mockResolvedValue(undefined); - pendingConsumeByTokenMock.mockReset(); - pendingConsumeByTokenMock.mockResolvedValue(undefined); - pendingClearMock.mockReset(); - pendingClearMock.mockResolvedValue(undefined); - pendingSetMock.mockReset(); - pendingSetMock.mockResolvedValue("tok-default"); - openaiCreateMock.mockResolvedValue({ - choices: [{ message: { content: "AI response" } }], - }); - mockFetch.mockImplementation(() => - Promise.resolve( - new Response(JSON.stringify({ ok: true }), { - headers: { "Content-Type": "application/json" }, - }) - ) - ); + resetMessageMocks(); }); afterEach(() => { @@ -235,7 +95,7 @@ describe("POST /webhook/telegram", () => { ...validUpdate, message: { ...validUpdate.message, - chat: { id: 99999, type: "private" }, + chat: { id: 99_999, type: "private" }, }, }; const res = await sendRequest(update, { @@ -247,220 +107,8 @@ describe("POST /webhook/telegram", () => { expect(mockFetch).not.toHaveBeenCalled(); }); - it("sends AI response as HTML with parse_mode", async () => { - const res = await sendRequest(validUpdate, { - "x-telegram-bot-api-secret-token": ENV.TELEGRAM_WEBHOOK_SECRET, - }); - - expect(res.status).toBe(200); - expect(await res.json()).toEqual({ ok: true }); - expect(mockFetch).toHaveBeenCalledWith( - `https://api.telegram.org/bot${ENV.TELEGRAM_BOT_TOKEN}/sendMessage`, - expect.objectContaining({ - method: "POST", - body: JSON.stringify({ - chat_id: 12345, - text: "AI response", - parse_mode: "HTML", - }), - }) - ); - }); - - it("converts markdown in AI response to HTML before sending", async () => { - openaiCreateMock.mockResolvedValueOnce({ - choices: [ - { - message: { - content: "# Title\n\nThis is **bold** and `code`.", - }, - }, - ], - }); - - const res = await sendRequest(validUpdate, { - "x-telegram-bot-api-secret-token": ENV.TELEGRAM_WEBHOOK_SECRET, - }); - - expect(res.status).toBe(200); - const sentBody = JSON.parse( - (mockFetch.mock.calls[0][1] as { body: string }).body - ) as { text: string; parse_mode: string }; - expect(sentBody.parse_mode).toBe("HTML"); - expect(sentBody.text).toContain("Title"); - expect(sentBody.text).toContain("bold"); - expect(sentBody.text).toContain("code"); - expect(sentBody.text).not.toContain("# Title"); - expect(sentBody.text).not.toContain("**bold**"); - }); - - it("nudges the model to ask first when create_schedule omits use_browser for a monitor", async () => { - openaiCreateMock.mockClear(); - // First turn: model emits create_schedule with source_url but no use_browser. - openaiCreateMock.mockResolvedValueOnce({ - choices: [ - { - finish_reason: "tool_calls", - message: { - role: "assistant", - content: null, - tool_calls: [ - { - id: "call_create_1", - type: "function", - function: { - name: "create_schedule", - arguments: JSON.stringify({ - schedule_type: "daily", - hour: 9, - minute: 0, - timezone: "Europe/Helsinki", - source_url: "https://twitter.com/elonmusk", - message_prompt: "Check for new posts", - description: "Monitor Twitter", - }), - }, - }, - ], - }, - }, - ], - }); - // Second turn: model corrects and asks the question. - openaiCreateMock.mockResolvedValueOnce({ - choices: [ - { - finish_reason: "tool_calls", - message: { - role: "assistant", - content: null, - tool_calls: [ - { - id: "call_q_1", - type: "function", - function: { - name: "ask_user_question", - arguments: JSON.stringify({ - question: - "Should I use the browser scraper for this page (renders JavaScript)?", - options: [ - { label: "Yes — needs JS rendering", value: true }, - { label: "No — static HTML", value: false }, - ], - }), - }, - }, - ], - }, - }, - ], - }); - conversationSetMock.mockResolvedValueOnce("conv-tok-1"); - - const update = { - ...validUpdate, - message: { ...validUpdate.message, text: "monitor twitter.com" }, - }; - const res = await sendRequest(update, { - "x-telegram-bot-api-secret-token": ENV.TELEGRAM_WEBHOOK_SECRET, - }); - - expect(res.status).toBe(200); - - // The second OpenAI call must include the guard error as the tool result - // for the rejected create_schedule call. - const secondCall = openaiCreateMock.mock.calls[1] as - | [ - { - messages: { - role: string; - tool_call_id?: string; - content?: string; - }[]; - }, - ] - | undefined; - expect(secondCall).toBeDefined(); - if (!secondCall) { - return; - } - const toolMessage = secondCall[0].messages.find( - (m) => m.role === "tool" && m.tool_call_id === "call_create_1" - ); - expect(toolMessage?.content).toContain("use_browser"); - expect(toolMessage?.content).toContain("ask_user_question"); - - // No schedule was committed — pendingActions.set was not invoked. - expect(pendingSetMock).not.toHaveBeenCalled(); - - // Question was dispatched to the user with inline buttons. - expect(conversationSetMock).toHaveBeenCalledTimes(1); - const sendCall = mockFetch.mock.calls.find( - (c) => typeof c[0] === "string" && c[0].endsWith("/sendMessage") - ); - expect(sendCall).toBeDefined(); - if (!sendCall) { - return; - } - const sendBody = JSON.parse( - (sendCall[1] as { body: string }).body - ) as Record; - expect(sendBody.reply_markup).toBeDefined(); - }); - - it("sends error message as plain text when OpenAI fails", async () => { - openaiCreateMock.mockRejectedValueOnce( - new Error("401 insufficient permissions") - ); - - const res = await sendRequest(validUpdate, { - "x-telegram-bot-api-secret-token": ENV.TELEGRAM_WEBHOOK_SECRET, - }); - - expect(res.status).toBe(200); - expect(await res.json()).toEqual({ ok: true }); - const sentBody = JSON.parse( - (mockFetch.mock.calls[0][1] as { body: string }).body - ) as Record; - expect(sentBody).not.toHaveProperty("parse_mode"); - expect(sentBody.text).toBe( - "Something went wrong while generating a response. Please try again." - ); - }); - - it("sends 'Action cancelled.' as plain text without parse_mode for typed non-YES reply", async () => { - pendingConsumeByChatIdMock.mockResolvedValueOnce({ - type: "create_schedule", - payload: {}, - description: "test action", - }); - - const update = { - ...validUpdate, - message: { ...validUpdate.message, text: "no" }, - }; - - const res = await sendRequest(update, { - "x-telegram-bot-api-secret-token": ENV.TELEGRAM_WEBHOOK_SECRET, - }); - - expect(res.status).toBe(200); - const cancellationCall = mockFetch.mock.calls.find((call) => { - const body = (call[1] as { body: string } | undefined)?.body; - return typeof body === "string" && body.includes("Action cancelled."); - }); - expect(cancellationCall).toBeDefined(); - if (!cancellationCall) { - return; - } - const body = JSON.parse( - (cancellationCall[1] as { body: string }).body - ) as Record; - expect(body).not.toHaveProperty("parse_mode"); - expect(body.text).toBe("Action cancelled."); - }); - it("returns 413 for oversized payloads", async () => { + const { TELEGRAM_WEBHOOK_MAX_BODY_BYTES } = await import("./routes"); const oversizedBody = JSON.stringify({ ...validUpdate, message: { @@ -530,7 +178,7 @@ describe("POST /webhook/telegram", () => { ...validUpdate, message: { ...validUpdate.message, - chat: { id: 99999, type: "private" }, + chat: { id: 99_999, type: "private" }, }, }, { @@ -546,422 +194,3 @@ describe("POST /webhook/telegram", () => { expect(output).not.toContain("99999"); }); }); - -describe("POST /webhook/telegram — callback_query", () => { - const headers = { - "x-telegram-bot-api-secret-token": ENV.TELEGRAM_WEBHOOK_SECRET, - }; - - beforeEach(() => { - mockFetch.mockReset(); - ENV.DB = createMockD1(); - pendingConsumeByChatIdMock.mockReset(); - pendingConsumeByChatIdMock.mockResolvedValue(undefined); - pendingConsumeByTokenMock.mockReset(); - pendingConsumeByTokenMock.mockResolvedValue(undefined); - pendingClearMock.mockReset(); - pendingClearMock.mockResolvedValue(undefined); - pendingSetMock.mockReset(); - pendingSetMock.mockResolvedValue("tok-default"); - conversationGetByTokenMock.mockReset(); - conversationGetByTokenMock.mockResolvedValue(undefined); - conversationConsumeByTokenMock.mockReset(); - conversationConsumeByTokenMock.mockResolvedValue(undefined); - conversationSetMock.mockReset(); - conversationSetMock.mockResolvedValue("conv-tok"); - conversationClearMock.mockReset(); - conversationClearMock.mockResolvedValue(undefined); - mockFetch.mockImplementation(() => - Promise.resolve( - new Response(JSON.stringify({ ok: true }), { - headers: { "Content-Type": "application/json" }, - }) - ) - ); - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); - - const fetchedMethods = (): string[] => - mockFetch.mock.calls.map((call) => { - const url = call[0] as string; - return url.split("/").pop() ?? ""; - }); - - it("ignores callback_query from disallowed user", async () => { - const res = await sendRequest( - callbackUpdate({ fromId: 99999, data: "c:tok-x" }), - headers - ); - - expect(res.status).toBe(200); - expect(mockFetch).not.toHaveBeenCalled(); - expect(pendingConsumeByTokenMock).not.toHaveBeenCalled(); - }); - - it("ignores callback_query from disallowed chat", async () => { - const res = await sendRequest( - callbackUpdate({ chatId: 99999, data: "c:tok-x" }), - headers - ); - - expect(res.status).toBe(200); - expect(mockFetch).not.toHaveBeenCalled(); - expect(pendingConsumeByTokenMock).not.toHaveBeenCalled(); - }); - - it("acks but does nothing when callback has no message field", async () => { - const res = await sendRequest( - callbackUpdate({ includeMessage: false, data: "c:tok-x" }), - headers - ); - - expect(res.status).toBe(200); - expect(pendingConsumeByTokenMock).not.toHaveBeenCalled(); - expect(fetchedMethods()).toContain("answerCallbackQuery"); - expect(fetchedMethods()).not.toContain("editMessageReplyMarkup"); - expect(fetchedMethods()).not.toContain("sendMessage"); - }); - - it("acks and ignores malformed callback_data", async () => { - const res = await sendRequest(callbackUpdate({ data: "garbage" }), headers); - - expect(res.status).toBe(200); - expect(pendingConsumeByTokenMock).not.toHaveBeenCalled(); - expect(fetchedMethods()).toContain("answerCallbackQuery"); - }); - - it("expired/unknown token: acks with toast and clears buttons, no execution", async () => { - pendingConsumeByTokenMock.mockResolvedValueOnce(undefined); - - const res = await sendRequest( - callbackUpdate({ data: "c:tok-stale" }), - headers - ); - - expect(res.status).toBe(200); - const ackCall = mockFetch.mock.calls.find( - (c) => typeof c[0] === "string" && c[0].endsWith("/answerCallbackQuery") - ); - expect(ackCall).toBeDefined(); - if (!ackCall) { - return; - } - const ackBody = JSON.parse((ackCall[1] as { body: string }).body) as Record< - string, - unknown - >; - expect(ackBody.text).toBe("Expired or already used"); - - const editCall = mockFetch.mock.calls.find( - (c) => - typeof c[0] === "string" && c[0].endsWith("/editMessageReplyMarkup") - ); - expect(editCall).toBeDefined(); - const sendCall = mockFetch.mock.calls.find( - (c) => typeof c[0] === "string" && c[0].endsWith("/sendMessage") - ); - expect(sendCall).toBeUndefined(); - }); - - it("cancel: acks with 'Cancelled', clears buttons, no execution", async () => { - pendingConsumeByTokenMock.mockResolvedValueOnce({ - type: "create_schedule", - payload: {}, - description: "any", - }); - - const res = await sendRequest(callbackUpdate({ data: "x:tok-1" }), headers); - - expect(res.status).toBe(200); - expect(pendingConsumeByTokenMock).toHaveBeenCalledWith(12345, "tok-1"); - const ackCall = mockFetch.mock.calls.find( - (c) => typeof c[0] === "string" && c[0].endsWith("/answerCallbackQuery") - ); - expect(ackCall).toBeDefined(); - if (!ackCall) { - return; - } - const ackBody = JSON.parse((ackCall[1] as { body: string }).body) as Record< - string, - unknown - >; - expect(ackBody.text).toBe("Cancelled"); - const sendCall = mockFetch.mock.calls.find( - (c) => typeof c[0] === "string" && c[0].endsWith("/sendMessage") - ); - expect(sendCall).toBeUndefined(); - }); - - it("confirm delete: acks 'Schedule deleted' and sends result message", async () => { - pendingConsumeByTokenMock.mockResolvedValueOnce({ - type: "delete_schedule", - payload: { id: "abc" }, - description: "Delete schedule abc", - }); - const removeMock = vi.fn().mockResolvedValue(true); - const { ScheduleService } = await import("../../services/schedule"); - vi.spyOn(ScheduleService.prototype, "remove").mockImplementation( - removeMock - ); - - const res = await sendRequest(callbackUpdate({ data: "c:tok-1" }), headers); - - expect(res.status).toBe(200); - expect(removeMock).toHaveBeenCalledWith("abc", 12345); - const ackCall = mockFetch.mock.calls.find( - (c) => typeof c[0] === "string" && c[0].endsWith("/answerCallbackQuery") - ); - expect(ackCall).toBeDefined(); - if (!ackCall) { - return; - } - const ackBody = JSON.parse((ackCall[1] as { body: string }).body) as Record< - string, - unknown - >; - expect(ackBody.text).toBe("Schedule deleted"); - const sendCall = mockFetch.mock.calls.find( - (c) => typeof c[0] === "string" && c[0].endsWith("/sendMessage") - ); - expect(sendCall).toBeDefined(); - }); - - it("double-tap: second consumeByToken returns undefined → no double execution", async () => { - pendingConsumeByTokenMock - .mockResolvedValueOnce({ - type: "delete_schedule", - payload: { id: "abc" }, - description: "Delete schedule abc", - }) - .mockResolvedValueOnce(undefined); - const removeMock = vi.fn().mockResolvedValue(true); - const { ScheduleService } = await import("../../services/schedule"); - vi.spyOn(ScheduleService.prototype, "remove").mockImplementation( - removeMock - ); - - await sendRequest(callbackUpdate({ data: "c:tok-1" }), headers); - await sendRequest(callbackUpdate({ data: "c:tok-1" }), headers); - - expect(removeMock).toHaveBeenCalledTimes(1); - }); - - describe("question-answer callback (q::)", () => { - it("acks 'Malformed' for non-numeric option index", async () => { - const res = await sendRequest( - callbackUpdate({ data: "q:tok-1:abc" }), - headers - ); - - expect(res.status).toBe(200); - expect(conversationConsumeByTokenMock).not.toHaveBeenCalled(); - const ackCall = mockFetch.mock.calls.find( - (c) => typeof c[0] === "string" && c[0].endsWith("/answerCallbackQuery") - ); - expect(ackCall).toBeDefined(); - if (!ackCall) { - return; - } - const ackBody = JSON.parse( - (ackCall[1] as { body: string }).body - ) as Record; - expect(ackBody.text).toBe("Malformed"); - }); - - it("acks 'Expired or already used' when conversation token is unknown", async () => { - conversationGetByTokenMock.mockResolvedValueOnce(undefined); - - const res = await sendRequest( - callbackUpdate({ data: "q:tok-x:0" }), - headers - ); - - expect(res.status).toBe(200); - expect(conversationGetByTokenMock).toHaveBeenCalledWith(12345, "tok-x"); - // Out-of-range/unknown tokens must not consume the row. - expect(conversationConsumeByTokenMock).not.toHaveBeenCalled(); - const ackCall = mockFetch.mock.calls.find( - (c) => typeof c[0] === "string" && c[0].endsWith("/answerCallbackQuery") - ); - expect(ackCall).toBeDefined(); - if (!ackCall) { - return; - } - const ackBody = JSON.parse( - (ackCall[1] as { body: string }).body - ) as Record; - expect(ackBody.text).toBe("Expired or already used"); - }); - - it("acks 'Invalid option' when index is out of range and preserves pending state", async () => { - conversationGetByTokenMock.mockResolvedValueOnce({ - messages: [], - pendingToolCallId: "call_q", - options: [ - { label: "Yes", value: true }, - { label: "No", value: false }, - ], - }); - - const res = await sendRequest( - callbackUpdate({ data: "q:tok-r:9" }), - headers - ); - - expect(res.status).toBe(200); - // The pending row must remain intact so a tampered/malformed callback - // can't strand the user with no way to retry from the real buttons. - expect(conversationConsumeByTokenMock).not.toHaveBeenCalled(); - const ackCall = mockFetch.mock.calls.find( - (c) => typeof c[0] === "string" && c[0].endsWith("/answerCallbackQuery") - ); - expect(ackCall).toBeDefined(); - if (!ackCall) { - return; - } - const ackBody = JSON.parse( - (ackCall[1] as { body: string }).body - ) as Record; - expect(ackBody.text).toBe("Invalid option"); - }); - - it("acks 'Something went wrong' when peek throws", async () => { - conversationGetByTokenMock.mockRejectedValueOnce(new Error("db down")); - - const res = await sendRequest( - callbackUpdate({ data: "q:tok-r:0" }), - headers - ); - - expect(res.status).toBe(200); - const ackCall = mockFetch.mock.calls.find( - (c) => typeof c[0] === "string" && c[0].endsWith("/answerCallbackQuery") - ); - expect(ackCall).toBeDefined(); - if (!ackCall) { - return; - } - const ackBody = JSON.parse( - (ackCall[1] as { body: string }).body - ) as Record; - expect(ackBody.text).toBe("Something went wrong"); - }); - - it("happy path: appends tool result with raw boolean value and resumes", async () => { - const stored = { - messages: [ - { role: "system", content: "sys" }, - { role: "user", content: "monitor twitter.com" }, - { - role: "assistant", - content: null, - tool_calls: [ - { - id: "call_q", - type: "function", - function: { - name: "ask_user_question", - arguments: JSON.stringify({ - question: "Use browser?", - options: [ - { label: "Yes", value: true }, - { label: "No", value: false }, - ], - }), - }, - }, - ], - }, - ], - pendingToolCallId: "call_q", - options: [ - { label: "Yes", value: true }, - { label: "No", value: false }, - ], - }; - conversationGetByTokenMock.mockResolvedValueOnce(stored); - conversationConsumeByTokenMock.mockResolvedValueOnce(stored); - - // OpenAI returns a final answer on resume. - openaiCreateMock.mockResolvedValueOnce({ - choices: [ - { - finish_reason: "stop", - message: { role: "assistant", content: "Got it." }, - }, - ], - }); - - const res = await sendRequest( - callbackUpdate({ data: "q:tok-r:0" }), - headers - ); - - expect(res.status).toBe(200); - expect(conversationConsumeByTokenMock).toHaveBeenCalledWith( - 12345, - "tok-r" - ); - - // The OpenAI call should have received messages with a tool result that - // carries the raw boolean (NOT a string). - const openaiCall = openaiCreateMock.mock.calls.at(-1) as - | [{ messages: { role: string; content?: string }[] }] - | undefined; - expect(openaiCall).toBeDefined(); - if (!openaiCall) { - return; - } - const sentMessages = openaiCall[0].messages; - const toolResult = sentMessages.find((m) => m.role === "tool"); - expect(toolResult).toBeDefined(); - expect(toolResult?.content).toBe( - JSON.stringify({ value: true, label: "Yes" }) - ); - - // The bot then sends the LLM's final reply. - const sendCall = mockFetch.mock.calls.find( - (c) => typeof c[0] === "string" && c[0].endsWith("/sendMessage") - ); - expect(sendCall).toBeDefined(); - }); - - it("ack toast says 'Recorded' on happy path", async () => { - const stored = { - messages: [{ role: "system", content: "sys" }], - pendingToolCallId: "call_q", - options: [ - { label: "Yes", value: true }, - { label: "No", value: false }, - ], - }; - conversationGetByTokenMock.mockResolvedValueOnce(stored); - conversationConsumeByTokenMock.mockResolvedValueOnce(stored); - openaiCreateMock.mockResolvedValueOnce({ - choices: [ - { - finish_reason: "stop", - message: { role: "assistant", content: "ok" }, - }, - ], - }); - - await sendRequest(callbackUpdate({ data: "q:tok-r:1" }), headers); - - const ackCall = mockFetch.mock.calls.find( - (c) => typeof c[0] === "string" && c[0].endsWith("/answerCallbackQuery") - ); - expect(ackCall).toBeDefined(); - if (!ackCall) { - return; - } - const ackBody = JSON.parse( - (ackCall[1] as { body: string }).body - ) as Record; - expect(ackBody.text).toBe("Recorded"); - }); - }); -}); diff --git a/apps/operator/src/modules/telegram/routes.ts b/apps/operator/src/modules/telegram/routes.ts index 12b06f8..9f9a641 100644 --- a/apps/operator/src/modules/telegram/routes.ts +++ b/apps/operator/src/modules/telegram/routes.ts @@ -1,12 +1,13 @@ +import { zValidator } from "@hono/zod-validator"; import { Hono } from "hono"; import { bodyLimit } from "hono/body-limit"; -import { zValidator } from "@hono/zod-validator"; import { createMiddleware } from "hono/factory"; import { timingSafeEqual } from "hono/utils/buffer"; -import { telegramIpMiddleware } from "../../middleware/telegram-ip"; -import type { AppEnv } from "../../types/env"; -import { telegramUpdateSchema } from "../../types/telegram"; +import { telegramIpMiddleware } from "~/middleware/telegram-ip"; +import type { AppEnv } from "~/types/env"; +import { telegramUpdateSchema } from "~/types/telegram"; + import { handleWebhook } from "./controller"; const telegramRoutes = new Hono(); @@ -16,13 +17,14 @@ const verifyTelegramSecret = createMiddleware(async (c, next) => { const secret = c.req.header("x-telegram-bot-api-secret-token"); if ( - !secret || + secret === undefined || + secret === "" || !(await timingSafeEqual(secret, c.env.TELEGRAM_WEBHOOK_SECRET)) ) { return c.json({ error: "Unauthorized" }, 401); } - await next(); + return next(); }); telegramRoutes.post( @@ -37,6 +39,7 @@ telegramRoutes.post( if (!result.success) { return c.json({ error: "Invalid payload" }, 400); } + return undefined; }), handleWebhook ); diff --git a/apps/operator/src/modules/telegram/ui.ts b/apps/operator/src/modules/telegram/ui.ts index 2010bbb..b021217 100644 --- a/apps/operator/src/modules/telegram/ui.ts +++ b/apps/operator/src/modules/telegram/ui.ts @@ -1,7 +1,7 @@ import type { InlineKeyboardMarkup } from "@repo/telegram/types"; -import type { QuestionOption } from "../../services/pending-conversation"; -import type { CreateScheduleInput } from "../../services/schedule"; +import type { QuestionOption } from "~/services/pending-conversation"; +import type { CreateScheduleInput } from "~/services/schedule"; const DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; const QUESTION_BUTTON_LABEL_MAX = 32; @@ -13,7 +13,7 @@ const formatScheduleDescription = ( const schedType = typeof args.schedule_type === "string" ? args.schedule_type : type; const parts: string[] = [`${schedType} schedule`]; - if (args.hour != null) { + if (args.hour !== null && args.hour !== undefined) { const h = typeof args.hour === "number" ? String(args.hour) : "0"; const m = typeof args.minute === "number" diff --git a/apps/operator/src/scheduled.ts b/apps/operator/src/scheduled.ts index 3d50125..541d77b 100644 --- a/apps/operator/src/scheduled.ts +++ b/apps/operator/src/scheduled.ts @@ -1,5 +1,6 @@ import { Logger } from "@repo/logger"; import { TelegramService } from "@repo/telegram"; +import { validateSourceUrl } from "@repo/url-validator"; import { OpenAiService } from "./services/openai"; import { MAX_RETRIES, ScheduleService } from "./services/schedule"; @@ -16,7 +17,6 @@ import { TELEGRAM_HTML_SAFE_LENGTH, TELEGRAM_MAX_MESSAGE_LENGTH, } from "./utils/message"; -import { validateSourceUrl } from "@repo/url-validator"; type Env = AppEnv["Bindings"]; @@ -74,7 +74,7 @@ const handleScheduled = async ( const results = await Promise.allSettled( claimed.map(async (schedule) => { // Monitor path - if (schedule.sourceUrl) { + if (schedule.sourceUrl !== null && schedule.sourceUrl !== "") { const urlCheck = validateSourceUrl(schedule.sourceUrl); if (!urlCheck.valid) { logger.error("monitor URL validation failed at execution", { @@ -93,7 +93,7 @@ const handleScheduled = async ( } let previousState: string | null = null; - if (schedule.stateJson) { + if (schedule.stateJson !== null && schedule.stateJson !== "") { try { const parsed = JSON.parse(schedule.stateJson) as Record< string, @@ -189,9 +189,12 @@ const handleScheduled = async ( // Reminder path let text: string; let formatAsHtml = false; - if (schedule.fixedMessage) { + if (schedule.fixedMessage !== null && schedule.fixedMessage !== "") { text = schedule.fixedMessage; - } else if (schedule.messagePrompt) { + } else if ( + schedule.messagePrompt !== null && + schedule.messagePrompt !== "" + ) { const openai = new OpenAiService(env.OPENAI_API_KEY, logger); text = await openai.reply(schedule.messagePrompt); if (text.length > RESPONSE_SIZE_LIMIT) { @@ -225,9 +228,13 @@ const handleScheduled = async ( // Handle failures — mark for retry const completionTime = new Date(); - for (let i = 0; i < results.length; i++) { - const result = results[i]; + for (const [i, result] of results.entries()) { const schedule = claimed[i]; + if (schedule === undefined) { + throw new Error( + `No claimed schedule for settled result at index ${String(i)}` + ); + } if (result.status === "fulfilled") { const { lockLost } = await scheduleService.markSuccess(schedule); if (lockLost) { @@ -238,12 +245,10 @@ const handleScheduled = async ( } if (result.status === "rejected") { + const reason: unknown = result.reason; logger.error("scheduled message failed", { scheduleId: schedule.id, - error: - result.reason instanceof Error - ? result.reason.message - : String(result.reason), + error: reason instanceof Error ? reason.message : String(reason), }); const { exhausted, lockLost } = await scheduleService.markFailed( @@ -265,13 +270,10 @@ const handleScheduled = async ( chat_id: chatId, text: `⚠️ Schedule "${schedule.description}" failed ${String(MAX_RETRIES)} times — skipping until next run.`, }); - } catch (notifyErr) { + } catch (error) { logger.error("failed to send retry-exhaustion notification", { scheduleId: schedule.id, - error: - notifyErr instanceof Error - ? notifyErr.message - : String(notifyErr), + error: error instanceof Error ? error.message : String(error), }); } } @@ -279,10 +281,9 @@ const handleScheduled = async ( } }; -const createScheduledHandler = () => { - return (event: ScheduledEvent, env: Env, ctx: ExecutionContext) => { +const createScheduledHandler = + () => (event: ScheduledEvent, env: Env, ctx: ExecutionContext) => { ctx.waitUntil(handleScheduled(event, env, ctx)); }; -}; -export { createScheduledHandler, handleScheduled }; +export { createScheduledHandler }; diff --git a/apps/operator/src/services/openai-prompts.ts b/apps/operator/src/services/openai-prompts.ts new file mode 100644 index 0000000..f5c277d --- /dev/null +++ b/apps/operator/src/services/openai-prompts.ts @@ -0,0 +1,61 @@ +const SYSTEM_PROMPT = `You are a helpful personal assistant called Switch Operator. Be concise and helpful. + +You can create, list, and delete scheduled messages for the user. +When the user asks to be reminded or wants something scheduled, use the create_schedule tool. +When creating schedules, infer the timezone from context or default to Europe/Helsinki. + +Schedule types: +- hourly: runs every hour at the specified minute +- daily: runs every day at the specified hour:minute +- weekly: runs every week on the specified day at hour:minute +- monthly: runs every month on the specified day at hour:minute + +Use fixed_message for exact text or message_prompt for AI-generated content. + +You can also create web monitors that scrape a URL on a schedule and notify based on conditions. +When the user wants to monitor a website for changes or check for specific content, use create_schedule with source_url + message_prompt. +The message_prompt should describe what to look for or how to analyze the page content. + +For monitors with large pages, use the keywords parameter to pre-filter content before AI analysis. +When keywords are set, the system only calls AI if at least one keyword appears on the page — saving time and cost. + +Use the use_browser parameter (boolean) on create_schedule to opt a monitor into JavaScript rendering via the browser scraper. +Whenever source_url is set on a create_schedule call, you MUST first call ask_user_question to confirm whether the monitor needs the browser scraper. Do not infer use_browser from the URL. Do not omit it. Do not call create_schedule and ask_user_question in the same turn — emit ask_user_question alone, then call create_schedule after the user's answer arrives, passing the chosen boolean verbatim into create_schedule.use_browser. +Use this exact form for the question: + question: "Should I use the browser scraper for this page (renders JavaScript)?" + options: + - { label: "Yes — needs JS rendering", value: true } + - { label: "No — static HTML", value: false } + +Do not ask about timezones, schedule types, or anything you can derive from the user's wording. + +Monitor examples: +- "Notify me when Beck is on TV" → source_url with the TV listings page, message_prompt: "Check if Beck appears in today's listings. Notify with channel and time if found.", keywords: ["Beck"] +- "Weekly report changes" → source_url with the report page, message_prompt: "Compare this week's content to last week. Summarize key changes." + +When listing schedules, render each entry on its own line prefixed with its position number followed by a period and a space (e.g. "1. ", "2. "). Use the position field from the tool result verbatim — do not renumber. Include description, type, time, and next run on each line. +When the user asks to delete a schedule by number, first call list_schedules to get the current list, then call delete_schedule with the ID from the matching position AND a short human-readable summary (type, time, description) so the user can recognize the row in the confirmation prompt.`; + +const MONITOR_ANALYSIS_PROMPT = `You are analyzing a web page for a monitoring task. + +The user will provide: +1. A task describing what to check or monitor +2. The current page content (scraped and converted to markdown) +3. Previous state from the last check (or "First check" if this is the first run) + +Respond in JSON with exactly these fields: +{ + "notify": true or false, + "message": "notification message to send to the user (max 4000 chars, use markdown — bold, italic, lists, links, code, blockquote; headers render as bold)", + "newState": "concise summary of current state for comparison next time (max 5000 chars)" +} + +Rules: +- Only set "notify" to true if the condition described in the task is met +- For diff/change detection tasks: compare current content to previous state and summarize what changed. Notify if there are meaningful changes. +- For condition check tasks: evaluate whether the specific condition is satisfied. Notify only if it is. +- The "message" should be informative and actionable — include relevant details from the page +- The "newState" should contain enough information to compare against next time. Keep it concise. +- If this is the first check, always set notify to true with a summary of current state`; + +export { MONITOR_ANALYSIS_PROMPT, SYSTEM_PROMPT }; diff --git a/apps/operator/src/services/openai-tool-loop.test.ts b/apps/operator/src/services/openai-tool-loop.test.ts new file mode 100644 index 0000000..f19d0c3 --- /dev/null +++ b/apps/operator/src/services/openai-tool-loop.test.ts @@ -0,0 +1,353 @@ +import type { Logger } from "@repo/logger"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { buildInitialMessages, OpenAiService } from "./openai"; +import type { ToolExecutor, ToolLoopMessages } from "./openai"; + +const createMock = vi.fn(); +const constructorMock = vi.fn(); + +vi.mock("openai", () => ({ + default: class { + chat = { completions: { create: createMock } }; + constructor(options: Record) { + constructorMock(options); + } + }, +})); + +const createMockLogger = (): Logger => + ({ + debug: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + }) as unknown as Logger; + +const priorAnswer = (id: string): unknown => ({ + role: "tool", + tool_call_id: id, + content: JSON.stringify({ value: true, label: "Yes" }), +}); + +describe("OpenAiService.runToolLoop", () => { + let service: OpenAiService; + + beforeEach(() => { + service = new OpenAiService("test-api-key", createMockLogger()); + createMock.mockReset(); + constructorMock.mockReset(); + }); + + const validQuestionArgs = JSON.stringify({ + question: "Use browser rendering?", + options: [ + { label: "Yes — needs JS", value: true }, + { label: "No — static HTML", value: false }, + ], + }); + + it("returns ask_user_question outcome when the model emits the tool", async () => { + createMock.mockResolvedValueOnce({ + choices: [ + { + finish_reason: "tool_calls", + message: { + role: "assistant", + content: null, + tool_calls: [ + { + id: "call_q1", + type: "function", + function: { + name: "ask_user_question", + arguments: validQuestionArgs, + }, + }, + ], + }, + }, + ], + }); + + const executor: ToolExecutor = vi.fn(); + const messages = buildInitialMessages("monitor twitter.com"); + const outcome = await service.runToolLoop(messages, executor); + + expect(outcome.kind).toBe("ask_user_question"); + if (outcome.kind !== "ask_user_question") { + throw new Error("expected ask_user_question outcome"); + } + expect(outcome.toolCallId).toBe("call_q1"); + expect(outcome.question).toBe("Use browser rendering?"); + expect(outcome.options).toHaveLength(2); + expect(outcome.options[0]?.value).toBe(true); + expect(outcome.options[1]?.value).toBe(false); + // The paused outcome contains the assistant turn but no tool result yet + // for the question's tool_call_id. + expect(executor).not.toHaveBeenCalled(); + const lastMsg = outcome.messages.at(-1); + expect(lastMsg?.role).toBe("assistant"); + const toolResultsForQuestion = outcome.messages.filter( + (m) => + m.role === "tool" && + (m as { tool_call_id?: string }).tool_call_id === "call_q1" + ); + expect(toolResultsForQuestion).toHaveLength(0); + }); + + it("processes sibling tool calls before suspending on ask_user_question", async () => { + createMock.mockResolvedValueOnce({ + choices: [ + { + finish_reason: "tool_calls", + message: { + role: "assistant", + content: null, + tool_calls: [ + { + id: "call_list", + type: "function", + function: { name: "list_schedules", arguments: "{}" }, + }, + { + id: "call_q", + type: "function", + function: { + name: "ask_user_question", + arguments: validQuestionArgs, + }, + }, + ], + }, + }, + ], + }); + + const executor: ToolExecutor = vi + .fn() + .mockResolvedValueOnce({ result: "[]" }); + + const messages = buildInitialMessages("test"); + const outcome = await service.runToolLoop(messages, executor); + + expect(executor).toHaveBeenCalledTimes(1); + expect(executor).toHaveBeenCalledWith("list_schedules", {}); + expect(outcome.kind).toBe("ask_user_question"); + // Sibling's tool result is in messages so the assistant turn is satisfiable + // on resume. + const siblingResult = outcome.messages.find( + (m) => + m.role === "tool" && + (m as { tool_call_id?: string }).tool_call_id === "call_list" + ); + expect(siblingResult).toBeDefined(); + }); + + it("rejects mutating sibling tool calls when ask_user_question is in the same batch", async () => { + createMock.mockResolvedValueOnce({ + choices: [ + { + finish_reason: "tool_calls", + message: { + role: "assistant", + content: null, + tool_calls: [ + { + id: "call_create", + type: "function", + function: { + name: "create_schedule", + arguments: "{}", + }, + }, + { + id: "call_q", + type: "function", + function: { + name: "ask_user_question", + arguments: validQuestionArgs, + }, + }, + ], + }, + }, + ], + }); + + const executor: ToolExecutor = vi.fn(); + const messages = buildInitialMessages("monitor twitter.com"); + const outcome = await service.runToolLoop(messages, executor); + + // Mutating sibling must NOT execute; the model gets a tool error so + // it can retry on the next turn (after the user answers). + expect(executor).not.toHaveBeenCalled(); + expect(outcome.kind).toBe("ask_user_question"); + const rejection = outcome.messages.find( + (m) => + m.role === "tool" && + (m as { tool_call_id?: string }).tool_call_id === "call_create" + ) as { content?: string } | undefined; + expect(rejection?.content).toContain("create_schedule"); + expect(rejection?.content).toContain("ask_user_question"); + }); + + it("denies the 4th ask_user_question and continues to final", async () => { + const priorAskTurn = (id: string): unknown => ({ + role: "assistant", + content: null, + tool_calls: [ + { + id, + type: "function", + function: { + name: "ask_user_question", + arguments: validQuestionArgs, + }, + }, + ], + }); + + const messages = [ + ...buildInitialMessages("test"), + priorAskTurn("p1"), + priorAnswer("p1"), + priorAskTurn("p2"), + priorAnswer("p2"), + priorAskTurn("p3"), + priorAnswer("p3"), + ] as ToolLoopMessages; + + // Model tries to ask a 4th question; loop denies, then on next round + // model returns a final answer. + createMock + .mockResolvedValueOnce({ + choices: [ + { + finish_reason: "tool_calls", + message: { + role: "assistant", + content: null, + tool_calls: [ + { + id: "call_q4", + type: "function", + function: { + name: "ask_user_question", + arguments: validQuestionArgs, + }, + }, + ], + }, + }, + ], + }) + .mockResolvedValueOnce({ + choices: [ + { + finish_reason: "stop", + message: { role: "assistant", content: "Done." }, + }, + ], + }); + + const executor: ToolExecutor = vi.fn(); + const outcome = await service.runToolLoop(messages, executor); + + expect(outcome.kind).toBe("final"); + if (outcome.kind !== "final") { + throw new Error("expected final outcome"); + } + expect(outcome.content).toBe("Done."); + // Quota error fed back as tool result + const quotaToolResult = messages.find( + (m) => + m.role === "tool" && + (m as { tool_call_id?: string }).tool_call_id === "call_q4" + ) as { content?: string } | undefined; + expect(quotaToolResult).toBeDefined(); + expect(quotaToolResult?.content).toContain("quota"); + }); + + it("returns a tool error for malformed ask_user_question args", async () => { + const badArgs = JSON.stringify({ question: "" }); + + createMock + .mockResolvedValueOnce({ + choices: [ + { + finish_reason: "tool_calls", + message: { + role: "assistant", + content: null, + tool_calls: [ + { + id: "call_bad", + type: "function", + function: { + name: "ask_user_question", + arguments: badArgs, + }, + }, + ], + }, + }, + ], + }) + .mockResolvedValueOnce({ + choices: [ + { + finish_reason: "stop", + message: { role: "assistant", content: "Recovered." }, + }, + ], + }); + + const messages = buildInitialMessages("test"); + const outcome = await service.runToolLoop(messages, vi.fn()); + + expect(outcome.kind).toBe("final"); + const errResult = messages.find( + (m) => + m.role === "tool" && + (m as { tool_call_id?: string }).tool_call_id === "call_bad" + ) as { content?: string } | undefined; + expect(errResult?.content).toContain("Invalid ask_user_question"); + }); + + describe("replyWithTools (back-compat)", () => { + it("throws if the model tries to ask a question", async () => { + createMock.mockResolvedValueOnce({ + choices: [ + { + finish_reason: "tool_calls", + message: { + role: "assistant", + content: null, + tool_calls: [ + { + id: "call_q", + type: "function", + function: { + name: "ask_user_question", + arguments: JSON.stringify({ + question: "?", + options: [ + { label: "A", value: "a" }, + { label: "B", value: "b" }, + ], + }), + }, + }, + ], + }, + }, + ], + }); + + await expect(service.replyWithTools("test", vi.fn())).rejects.toThrow( + /not supported in replyWithTools/ + ); + }); + }); +}); diff --git a/apps/operator/src/services/openai-tools.ts b/apps/operator/src/services/openai-tools.ts new file mode 100644 index 0000000..7163c28 --- /dev/null +++ b/apps/operator/src/services/openai-tools.ts @@ -0,0 +1,155 @@ +import type { ChatCompletionTool } from "openai/resources/chat/completions"; +import { z } from "zod"; + +const SCHEDULE_TOOLS: ChatCompletionTool[] = [ + { + type: "function", + function: { + name: "create_schedule", + description: + "Create a new scheduled message. Use fixed_message for exact text or message_prompt for AI-generated content.", + parameters: { + type: "object", + properties: { + schedule_type: { + type: "string", + enum: ["hourly", "daily", "weekly", "monthly"], + }, + hour: { + type: "number", + description: "Hour (0-23). Required for daily/weekly/monthly.", + }, + minute: { + type: "number", + description: "Minute (0-59). Defaults to 0.", + }, + day_of_week: { + type: "number", + description: "Day of week (0=Sun, 6=Sat). Required for weekly.", + }, + day_of_month: { + type: "number", + description: "Day of month (1-28). Required for monthly.", + }, + timezone: { + type: "string", + description: + "IANA timezone (e.g. Europe/Helsinki). Defaults to Europe/Helsinki.", + }, + fixed_message: { + type: "string", + description: + "Exact message to send. Mutually exclusive with message_prompt.", + }, + message_prompt: { + type: "string", + description: + "Prompt for AI-generated message. Mutually exclusive with fixed_message.", + }, + source_url: { + type: "string", + description: + "URL to monitor/scrape. When set, the schedule becomes a monitor: it will fetch this URL on each run, analyze the content using message_prompt, and notify only if the condition is met. Requires message_prompt. Cannot be used with fixed_message.", + }, + keywords: { + type: "array", + items: { type: "string" }, + description: + "Optional keywords to pre-filter scraped page content before AI analysis. When set, only runs AI if at least one keyword appears on the page. Use for efficiency on large pages. Only valid for monitors (requires source_url).", + }, + use_browser: { + type: "boolean", + description: + "When true, the monitor fetches via the browser scraper which executes JavaScript. Only useful for SPA / JS-rendered pages. Required whenever source_url is set — confirm the value with the user via ask_user_question before calling create_schedule.", + }, + description: { + type: "string", + description: "Short description of this schedule (max 200 chars).", + }, + }, + required: ["schedule_type", "timezone", "description"], + }, + }, + }, + { + type: "function", + function: { + name: "list_schedules", + description: "List all active schedules for the user.", + parameters: { type: "object", properties: {} }, + }, + }, + { + type: "function", + function: { + name: "delete_schedule", + description: "Delete (deactivate) a schedule by its ID.", + parameters: { + type: "object", + properties: { + id: { type: "string", description: "The schedule ID to delete." }, + summary: { + type: "string", + description: + "Human-readable summary of the schedule being deleted (e.g. 'daily monitor at 9:00 — Twitter Elon'). Shown in the user's confirmation prompt so they can recognize what's about to be deleted. Build this from the matching list_schedules entry: include schedule type, time, and description.", + }, + }, + required: ["id", "summary"], + }, + }, + }, + { + type: "function", + function: { + name: "ask_user_question", + description: + "Ask the user a clarifying question with 2–4 button-labeled options. REQUIRED before every create_schedule call that has source_url set, to confirm use_browser. Otherwise use whenever a tool parameter affects observable behavior and you are not certain. The selected option's value flows back as the tool result; pass it directly into the appropriate downstream tool parameter (boolean for yes/no, string for choices).", + parameters: { + type: "object", + properties: { + question: { + type: "string", + description: "The question to display to the user (max 500 chars).", + }, + options: { + type: "array", + minItems: 2, + maxItems: 4, + items: { + type: "object", + properties: { + label: { + type: "string", + description: "Short button label (max 100 chars).", + }, + value: { + description: + "Opaque value passed back as the tool result when this option is chosen. Type matches the downstream parameter (boolean for yes/no, string for choices, number for counts).", + anyOf: [ + { type: "boolean" }, + { type: "string" }, + { type: "number" }, + ], + }, + }, + required: ["label", "value"], + }, + }, + }, + required: ["question", "options"], + }, + }, + }, +]; + +const questionOptionSchema = z.object({ + label: z.string().min(1).max(100), + value: z.union([z.boolean(), z.string(), z.number()]), +}); + +const askUserQuestionSchema = z.object({ + question: z.string().min(1).max(500), + options: z.array(questionOptionSchema).min(2).max(4), +}); + +export { askUserQuestionSchema, SCHEDULE_TOOLS }; diff --git a/apps/operator/src/services/openai.test.ts b/apps/operator/src/services/openai.test.ts index 718132e..0d6ae96 100644 --- a/apps/operator/src/services/openai.test.ts +++ b/apps/operator/src/services/openai.test.ts @@ -1,8 +1,8 @@ import type { Logger } from "@repo/logger"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { buildInitialMessages, OpenAiService } from "./openai"; -import type { ToolExecutor, ToolLoopMessages } from "./openai"; +import { OpenAiService } from "./openai"; +import type { ToolExecutor } from "./openai"; const createMock = vi.fn(); const constructorMock = vi.fn(); @@ -281,325 +281,6 @@ describe("OpenAiService", () => { }); }); - describe("runToolLoop", () => { - const validQuestionArgs = JSON.stringify({ - question: "Use browser rendering?", - options: [ - { label: "Yes — needs JS", value: true }, - { label: "No — static HTML", value: false }, - ], - }); - - it("returns ask_user_question outcome when the model emits the tool", async () => { - createMock.mockResolvedValueOnce({ - choices: [ - { - finish_reason: "tool_calls", - message: { - role: "assistant", - content: null, - tool_calls: [ - { - id: "call_q1", - type: "function", - function: { - name: "ask_user_question", - arguments: validQuestionArgs, - }, - }, - ], - }, - }, - ], - }); - - const executor: ToolExecutor = vi.fn(); - const messages = buildInitialMessages("monitor twitter.com"); - const outcome = await service.runToolLoop(messages, executor); - - expect(outcome.kind).toBe("ask_user_question"); - if (outcome.kind !== "ask_user_question") { - return; - } - expect(outcome.toolCallId).toBe("call_q1"); - expect(outcome.question).toBe("Use browser rendering?"); - expect(outcome.options).toHaveLength(2); - expect(outcome.options[0].value).toBe(true); - expect(outcome.options[1].value).toBe(false); - // The paused outcome contains the assistant turn but no tool result yet - // for the question's tool_call_id. - expect(executor).not.toHaveBeenCalled(); - const lastMsg = outcome.messages[outcome.messages.length - 1]; - expect(lastMsg.role).toBe("assistant"); - const toolResultsForQuestion = outcome.messages.filter( - (m) => - m.role === "tool" && - (m as { tool_call_id?: string }).tool_call_id === "call_q1" - ); - expect(toolResultsForQuestion).toHaveLength(0); - }); - - it("processes sibling tool calls before suspending on ask_user_question", async () => { - createMock.mockResolvedValueOnce({ - choices: [ - { - finish_reason: "tool_calls", - message: { - role: "assistant", - content: null, - tool_calls: [ - { - id: "call_list", - type: "function", - function: { name: "list_schedules", arguments: "{}" }, - }, - { - id: "call_q", - type: "function", - function: { - name: "ask_user_question", - arguments: validQuestionArgs, - }, - }, - ], - }, - }, - ], - }); - - const executor: ToolExecutor = vi - .fn() - .mockResolvedValueOnce({ result: "[]" }); - - const messages = buildInitialMessages("test"); - const outcome = await service.runToolLoop(messages, executor); - - expect(executor).toHaveBeenCalledTimes(1); - expect(executor).toHaveBeenCalledWith("list_schedules", {}); - expect(outcome.kind).toBe("ask_user_question"); - // Sibling's tool result is in messages so the assistant turn is satisfiable - // on resume. - const siblingResult = outcome.messages.find( - (m) => - m.role === "tool" && - (m as { tool_call_id?: string }).tool_call_id === "call_list" - ); - expect(siblingResult).toBeDefined(); - }); - - it("rejects mutating sibling tool calls when ask_user_question is in the same batch", async () => { - createMock.mockResolvedValueOnce({ - choices: [ - { - finish_reason: "tool_calls", - message: { - role: "assistant", - content: null, - tool_calls: [ - { - id: "call_create", - type: "function", - function: { - name: "create_schedule", - arguments: "{}", - }, - }, - { - id: "call_q", - type: "function", - function: { - name: "ask_user_question", - arguments: validQuestionArgs, - }, - }, - ], - }, - }, - ], - }); - - const executor: ToolExecutor = vi.fn(); - const messages = buildInitialMessages("monitor twitter.com"); - const outcome = await service.runToolLoop(messages, executor); - - // Mutating sibling must NOT execute; the model gets a tool error so - // it can retry on the next turn (after the user answers). - expect(executor).not.toHaveBeenCalled(); - expect(outcome.kind).toBe("ask_user_question"); - const rejection = outcome.messages.find( - (m) => - m.role === "tool" && - (m as { tool_call_id?: string }).tool_call_id === "call_create" - ) as { content?: string } | undefined; - expect(rejection?.content).toContain("create_schedule"); - expect(rejection?.content).toContain("ask_user_question"); - }); - - it("denies the 4th ask_user_question and continues to final", async () => { - const priorAskTurn = (id: string): unknown => ({ - role: "assistant", - content: null, - tool_calls: [ - { - id, - type: "function", - function: { - name: "ask_user_question", - arguments: validQuestionArgs, - }, - }, - ], - }); - const priorAnswer = (id: string): unknown => ({ - role: "tool", - tool_call_id: id, - content: JSON.stringify({ value: true, label: "Yes" }), - }); - - const messages = [ - ...buildInitialMessages("test"), - priorAskTurn("p1"), - priorAnswer("p1"), - priorAskTurn("p2"), - priorAnswer("p2"), - priorAskTurn("p3"), - priorAnswer("p3"), - ] as ToolLoopMessages; - - // Model tries to ask a 4th question; loop denies, then on next round - // model returns a final answer. - createMock - .mockResolvedValueOnce({ - choices: [ - { - finish_reason: "tool_calls", - message: { - role: "assistant", - content: null, - tool_calls: [ - { - id: "call_q4", - type: "function", - function: { - name: "ask_user_question", - arguments: validQuestionArgs, - }, - }, - ], - }, - }, - ], - }) - .mockResolvedValueOnce({ - choices: [ - { - finish_reason: "stop", - message: { role: "assistant", content: "Done." }, - }, - ], - }); - - const executor: ToolExecutor = vi.fn(); - const outcome = await service.runToolLoop(messages, executor); - - expect(outcome.kind).toBe("final"); - if (outcome.kind !== "final") { - return; - } - expect(outcome.content).toBe("Done."); - // Quota error fed back as tool result - const quotaToolResult = messages.find( - (m) => - m.role === "tool" && - (m as { tool_call_id?: string }).tool_call_id === "call_q4" - ) as { content?: string } | undefined; - expect(quotaToolResult).toBeDefined(); - expect(quotaToolResult?.content).toContain("quota"); - }); - - it("returns a tool error for malformed ask_user_question args", async () => { - const badArgs = JSON.stringify({ question: "" }); - - createMock - .mockResolvedValueOnce({ - choices: [ - { - finish_reason: "tool_calls", - message: { - role: "assistant", - content: null, - tool_calls: [ - { - id: "call_bad", - type: "function", - function: { - name: "ask_user_question", - arguments: badArgs, - }, - }, - ], - }, - }, - ], - }) - .mockResolvedValueOnce({ - choices: [ - { - finish_reason: "stop", - message: { role: "assistant", content: "Recovered." }, - }, - ], - }); - - const messages = buildInitialMessages("test"); - const outcome = await service.runToolLoop(messages, vi.fn()); - - expect(outcome.kind).toBe("final"); - const errResult = messages.find( - (m) => - m.role === "tool" && - (m as { tool_call_id?: string }).tool_call_id === "call_bad" - ) as { content?: string } | undefined; - expect(errResult?.content).toContain("Invalid ask_user_question"); - }); - }); - - describe("replyWithTools (back-compat)", () => { - it("throws if the model tries to ask a question", async () => { - createMock.mockResolvedValueOnce({ - choices: [ - { - finish_reason: "tool_calls", - message: { - role: "assistant", - content: null, - tool_calls: [ - { - id: "call_q", - type: "function", - function: { - name: "ask_user_question", - arguments: JSON.stringify({ - question: "?", - options: [ - { label: "A", value: "a" }, - { label: "B", value: "b" }, - ], - }), - }, - }, - ], - }, - }, - ], - }); - - await expect(service.replyWithTools("test", vi.fn())).rejects.toThrow( - /not supported in replyWithTools/ - ); - }); - }); - describe("analyzeMonitor", () => { it("returns parsed monitor analysis", async () => { const analysis = { @@ -673,7 +354,7 @@ describe("OpenAiService", () => { const callArgs = createMock.mock.calls[0] as [ { messages: { content: string }[] }, ]; - const userMsg = callArgs[0].messages[1].content; + const userMsg = callArgs[0].messages[1]?.content; expect(userMsg).toContain("old content summary"); }); @@ -701,7 +382,7 @@ describe("OpenAiService", () => { const callArgs = createMock.mock.calls[0] as [ { messages: { content: string }[] }, ]; - const userMsg = callArgs[0].messages[1].content; + const userMsg = callArgs[0].messages[1]?.content; expect(userMsg).toContain("First check"); }); diff --git a/apps/operator/src/services/openai.ts b/apps/operator/src/services/openai.ts index 31b2b46..28dcabb 100644 --- a/apps/operator/src/services/openai.ts +++ b/apps/operator/src/services/openai.ts @@ -1,199 +1,11 @@ import type { Logger } from "@repo/logger"; import OpenAI from "openai"; -import type { ChatCompletionTool } from "openai/resources/chat/completions"; import { z } from "zod"; +import { MONITOR_ANALYSIS_PROMPT, SYSTEM_PROMPT } from "./openai-prompts"; +import { askUserQuestionSchema, SCHEDULE_TOOLS } from "./openai-tools"; import type { QuestionOption } from "./pending-conversation"; -const SYSTEM_PROMPT = `You are a helpful personal assistant called Switch Operator. Be concise and helpful. - -You can create, list, and delete scheduled messages for the user. -When the user asks to be reminded or wants something scheduled, use the create_schedule tool. -When creating schedules, infer the timezone from context or default to Europe/Helsinki. - -Schedule types: -- hourly: runs every hour at the specified minute -- daily: runs every day at the specified hour:minute -- weekly: runs every week on the specified day at hour:minute -- monthly: runs every month on the specified day at hour:minute - -Use fixed_message for exact text or message_prompt for AI-generated content. - -You can also create web monitors that scrape a URL on a schedule and notify based on conditions. -When the user wants to monitor a website for changes or check for specific content, use create_schedule with source_url + message_prompt. -The message_prompt should describe what to look for or how to analyze the page content. - -For monitors with large pages, use the keywords parameter to pre-filter content before AI analysis. -When keywords are set, the system only calls AI if at least one keyword appears on the page — saving time and cost. - -Use the use_browser parameter (boolean) on create_schedule to opt a monitor into JavaScript rendering via the browser scraper. -Whenever source_url is set on a create_schedule call, you MUST first call ask_user_question to confirm whether the monitor needs the browser scraper. Do not infer use_browser from the URL. Do not omit it. Do not call create_schedule and ask_user_question in the same turn — emit ask_user_question alone, then call create_schedule after the user's answer arrives, passing the chosen boolean verbatim into create_schedule.use_browser. -Use this exact form for the question: - question: "Should I use the browser scraper for this page (renders JavaScript)?" - options: - - { label: "Yes — needs JS rendering", value: true } - - { label: "No — static HTML", value: false } - -Do not ask about timezones, schedule types, or anything you can derive from the user's wording. - -Monitor examples: -- "Notify me when Beck is on TV" → source_url with the TV listings page, message_prompt: "Check if Beck appears in today's listings. Notify with channel and time if found.", keywords: ["Beck"] -- "Weekly report changes" → source_url with the report page, message_prompt: "Compare this week's content to last week. Summarize key changes." - -When listing schedules, render each entry on its own line prefixed with its position number followed by a period and a space (e.g. "1. ", "2. "). Use the position field from the tool result verbatim — do not renumber. Include description, type, time, and next run on each line. -When the user asks to delete a schedule by number, first call list_schedules to get the current list, then call delete_schedule with the ID from the matching position AND a short human-readable summary (type, time, description) so the user can recognize the row in the confirmation prompt.`; - -const SCHEDULE_TOOLS: ChatCompletionTool[] = [ - { - type: "function", - function: { - name: "create_schedule", - description: - "Create a new scheduled message. Use fixed_message for exact text or message_prompt for AI-generated content.", - parameters: { - type: "object", - properties: { - schedule_type: { - type: "string", - enum: ["hourly", "daily", "weekly", "monthly"], - }, - hour: { - type: "number", - description: "Hour (0-23). Required for daily/weekly/monthly.", - }, - minute: { - type: "number", - description: "Minute (0-59). Defaults to 0.", - }, - day_of_week: { - type: "number", - description: "Day of week (0=Sun, 6=Sat). Required for weekly.", - }, - day_of_month: { - type: "number", - description: "Day of month (1-28). Required for monthly.", - }, - timezone: { - type: "string", - description: - "IANA timezone (e.g. Europe/Helsinki). Defaults to Europe/Helsinki.", - }, - fixed_message: { - type: "string", - description: - "Exact message to send. Mutually exclusive with message_prompt.", - }, - message_prompt: { - type: "string", - description: - "Prompt for AI-generated message. Mutually exclusive with fixed_message.", - }, - source_url: { - type: "string", - description: - "URL to monitor/scrape. When set, the schedule becomes a monitor: it will fetch this URL on each run, analyze the content using message_prompt, and notify only if the condition is met. Requires message_prompt. Cannot be used with fixed_message.", - }, - keywords: { - type: "array", - items: { type: "string" }, - description: - "Optional keywords to pre-filter scraped page content before AI analysis. When set, only runs AI if at least one keyword appears on the page. Use for efficiency on large pages. Only valid for monitors (requires source_url).", - }, - use_browser: { - type: "boolean", - description: - "When true, the monitor fetches via the browser scraper which executes JavaScript. Only useful for SPA / JS-rendered pages. Required whenever source_url is set — confirm the value with the user via ask_user_question before calling create_schedule.", - }, - description: { - type: "string", - description: "Short description of this schedule (max 200 chars).", - }, - }, - required: ["schedule_type", "timezone", "description"], - }, - }, - }, - { - type: "function", - function: { - name: "list_schedules", - description: "List all active schedules for the user.", - parameters: { type: "object", properties: {} }, - }, - }, - { - type: "function", - function: { - name: "delete_schedule", - description: "Delete (deactivate) a schedule by its ID.", - parameters: { - type: "object", - properties: { - id: { type: "string", description: "The schedule ID to delete." }, - summary: { - type: "string", - description: - "Human-readable summary of the schedule being deleted (e.g. 'daily monitor at 9:00 — Twitter Elon'). Shown in the user's confirmation prompt so they can recognize what's about to be deleted. Build this from the matching list_schedules entry: include schedule type, time, and description.", - }, - }, - required: ["id", "summary"], - }, - }, - }, - { - type: "function", - function: { - name: "ask_user_question", - description: - "Ask the user a clarifying question with 2–4 button-labeled options. REQUIRED before every create_schedule call that has source_url set, to confirm use_browser. Otherwise use whenever a tool parameter affects observable behavior and you are not certain. The selected option's value flows back as the tool result; pass it directly into the appropriate downstream tool parameter (boolean for yes/no, string for choices).", - parameters: { - type: "object", - properties: { - question: { - type: "string", - description: "The question to display to the user (max 500 chars).", - }, - options: { - type: "array", - minItems: 2, - maxItems: 4, - items: { - type: "object", - properties: { - label: { - type: "string", - description: "Short button label (max 100 chars).", - }, - value: { - description: - "Opaque value passed back as the tool result when this option is chosen. Type matches the downstream parameter (boolean for yes/no, string for choices, number for counts).", - anyOf: [ - { type: "boolean" }, - { type: "string" }, - { type: "number" }, - ], - }, - }, - required: ["label", "value"], - }, - }, - }, - required: ["question", "options"], - }, - }, - }, -]; - -const questionOptionSchema = z.object({ - label: z.string().min(1).max(100), - value: z.union([z.boolean(), z.string(), z.number()]), -}); - -const askUserQuestionSchema = z.object({ - question: z.string().min(1).max(500), - options: z.array(questionOptionSchema).min(2).max(4), -}); - const MAX_QUESTIONS_PER_CONVERSATION = 3; // Tools that mutate persistent state. When the model emits one of these in @@ -283,7 +95,7 @@ class OpenAiService { }); const content = response.choices[0]?.message.content; - if (!content) { + if (content === undefined || content === null || content === "") { throw new Error("OpenAI returned empty response"); } @@ -311,15 +123,16 @@ class OpenAiService { }); const choice = response.choices[0]; + if (choice === undefined) { + throw new Error("OpenAI returned no choices"); + } const message = choice.message; messages.push(message); - if ( - choice.finish_reason !== "tool_calls" || - !message.tool_calls?.length - ) { + const toolCalls = message.tool_calls ?? []; + if (choice.finish_reason !== "tool_calls" || toolCalls.length === 0) { const content = message.content; - if (!content) { + if (content === null || content === "") { throw new Error("OpenAI returned empty response"); } return { kind: "final", content, messages }; @@ -332,12 +145,12 @@ class OpenAiService { // Pre-scan: if this batch contains an ask_user_question, mutating // sibling tool calls must be rejected (not executed) so we don't // commit hidden writes while the user only sees a question UI. - const hasAskInBatch = message.tool_calls.some( + const hasAskInBatch = toolCalls.some( (tc) => tc.type === "function" && tc.function.name === "ask_user_question" ); - for (const toolCall of message.tool_calls) { + for (const toolCall of toolCalls) { if (toolCall.type !== "function") { continue; } @@ -470,7 +283,7 @@ class OpenAiService { this.logger.debug("analyzing monitor", { taskLength: params.task.length, contentLength: params.scrapedContent.length, - hasPreviousState: params.previousState != null, + hasPreviousState: params.previousState !== null, }); const previousStateText = @@ -490,7 +303,7 @@ class OpenAiService { }); const content = response.choices[0]?.message.content; - if (!content) { + if (content === undefined || content === null || content === "") { throw new Error("OpenAI returned empty response for monitor analysis"); } @@ -499,28 +312,6 @@ class OpenAiService { } } -const MONITOR_ANALYSIS_PROMPT = `You are analyzing a web page for a monitoring task. - -The user will provide: -1. A task describing what to check or monitor -2. The current page content (scraped and converted to markdown) -3. Previous state from the last check (or "First check" if this is the first run) - -Respond in JSON with exactly these fields: -{ - "notify": true or false, - "message": "notification message to send to the user (max 4000 chars, use markdown — bold, italic, lists, links, code, blockquote; headers render as bold)", - "newState": "concise summary of current state for comparison next time (max 5000 chars)" -} - -Rules: -- Only set "notify" to true if the condition described in the task is met -- For diff/change detection tasks: compare current content to previous state and summarize what changed. Notify if there are meaningful changes. -- For condition check tasks: evaluate whether the specific condition is satisfied. Notify only if it is. -- The "message" should be informative and actionable — include relevant details from the page -- The "newState" should contain enough information to compare against next time. Keep it concise. -- If this is the first check, always set notify to true with a summary of current state`; - const monitorAnalysisSchema = z.object({ notify: z.boolean(), message: z.string().max(4000), @@ -529,19 +320,5 @@ const monitorAnalysisSchema = z.object({ type MonitorAnalysis = z.infer; -export { - buildInitialMessages, - countAskUserQuestionCalls, - MAX_QUESTIONS_PER_CONVERSATION, - MAX_TOOL_ITERATIONS, - OpenAiService, - SCHEDULE_TOOLS, - SYSTEM_PROMPT, -}; -export type { - MonitorAnalysis, - ToolExecutor, - ToolLoopMessages, - ToolLoopOutcome, - ToolResult, -}; +export { buildInitialMessages, OpenAiService }; +export type { ToolExecutor, ToolLoopMessages, ToolLoopOutcome, ToolResult }; diff --git a/apps/operator/src/services/pending-action.ts b/apps/operator/src/services/pending-action.ts index 435d4dc..865efe0 100644 --- a/apps/operator/src/services/pending-action.ts +++ b/apps/operator/src/services/pending-action.ts @@ -2,7 +2,7 @@ import { and, eq, gt, lte } from "drizzle-orm"; import { drizzle } from "drizzle-orm/d1"; import type { DrizzleD1Database } from "drizzle-orm/d1"; -import { pendingActions } from "../db/schema"; +import { pendingActions } from "~/db/schema"; type PendingActionType = "create_schedule" | "delete_schedule"; @@ -22,8 +22,8 @@ const generateToken = (): string => { binary += String.fromCharCode(byte); } return btoa(binary) - .replace(/\+/g, "-") - .replace(/\//g, "_") + .replaceAll("+", "-") + .replaceAll("/", "_") .replace(/=+$/, ""); }; @@ -96,10 +96,11 @@ class PendingActionService { ) ) .returning(); - if (deleted.length === 0) { + const [row] = deleted; + if (row === undefined) { return undefined; } - return rowToAction(deleted[0]); + return rowToAction(row); } /** @@ -118,10 +119,11 @@ class PendingActionService { ) ) .returning(); - if (deleted.length === 0) { + const [row] = deleted; + if (row === undefined) { return undefined; } - return rowToAction(deleted[0]); + return rowToAction(row); } async clear(chatId: number): Promise { @@ -138,5 +140,5 @@ class PendingActionService { } } -export { generateToken, PendingActionService, TTL_MS }; -export type { PendingAction, PendingActionType }; +export { generateToken, PendingActionService }; +export type { PendingAction }; diff --git a/apps/operator/src/services/pending-conversation.ts b/apps/operator/src/services/pending-conversation.ts index 3a4a32f..fe2775d 100644 --- a/apps/operator/src/services/pending-conversation.ts +++ b/apps/operator/src/services/pending-conversation.ts @@ -2,7 +2,8 @@ import { and, eq, gt, lte } from "drizzle-orm"; import { drizzle } from "drizzle-orm/d1"; import type { DrizzleD1Database } from "drizzle-orm/d1"; -import { pendingConversations } from "../db/schema"; +import { pendingConversations } from "~/db/schema"; + import { generateToken } from "./pending-action"; type QuestionOptionValue = boolean | string | number; @@ -93,10 +94,11 @@ class PendingConversationService { gt(pendingConversations.expiresAt, nowIso) ) ); - if (rows.length === 0) { + const [row] = rows; + if (row === undefined) { return undefined; } - return parseRow(rows[0]); + return parseRow(row); } async consumeByToken( @@ -114,10 +116,11 @@ class PendingConversationService { ) ) .returning(); - if (deleted.length === 0) { + const [row] = deleted; + if (row === undefined) { return undefined; } - return parseRow(deleted[0]); + return parseRow(row); } async clear(chatId: number): Promise { @@ -134,5 +137,5 @@ class PendingConversationService { } } -export { PendingConversationService, TTL_MS }; -export type { PendingConversation, QuestionOption, QuestionOptionValue }; +export { PendingConversationService }; +export type { QuestionOption }; diff --git a/apps/operator/src/services/schedule-time.ts b/apps/operator/src/services/schedule-time.ts new file mode 100644 index 0000000..99a15f8 --- /dev/null +++ b/apps/operator/src/services/schedule-time.ts @@ -0,0 +1,223 @@ +const SCHEDULE_TYPES = ["hourly", "daily", "weekly", "monthly"] as const; +type ScheduleType = (typeof SCHEDULE_TYPES)[number]; + +const WEEKDAY_MAP: Record = { + Sun: 0, + Mon: 1, + Tue: 2, + Wed: 3, + Thu: 4, + Fri: 5, + Sat: 6, +}; + +/** + * Get the current local parts (hour, minute, day-of-week, day-of-month, etc.) + * for a given Date in a given timezone. + */ +const getLocalParts = (date: Date, timezone: string) => { + const fmt = new Intl.DateTimeFormat("en-US", { + timeZone: timezone, + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + weekday: "short", + hour12: false, + }); + const parts = Object.fromEntries( + fmt.formatToParts(date).map((p) => [p.type, p.value]) + ); + + return { + year: Number(parts.year), + month: Number(parts.month), + day: Number(parts.day), + hour: Number(parts.hour === "24" ? "0" : parts.hour), + minute: Number(parts.minute), + second: Number(parts.second), + dayOfWeek: + parts.weekday === undefined ? 0 : (WEEKDAY_MAP[parts.weekday] ?? 0), + }; +}; + +/** + * Build a UTC Date from local parts in a given timezone. + * Uses the timezone offset to convert local -> UTC. + */ +const localToUtc = ( + timezone: string, + year: number, + month: number, + day: number, + hour: number, + minute: number +): Date => { + // Start with a guess: treat local parts as UTC + const guess = new Date(Date.UTC(year, month - 1, day, hour, minute, 0, 0)); + + // Find the offset by checking what local time the guess maps to + const localParts = getLocalParts(guess, timezone); + const localAsUtc = new Date( + Date.UTC( + localParts.year, + localParts.month - 1, + localParts.day, + localParts.hour, + localParts.minute, + localParts.second, + 0 + ) + ); + + const offsetMs = localAsUtc.getTime() - guess.getTime(); + const result = new Date(guess.getTime() - offsetMs); + + // Verify by round-tripping: the result, viewed in the target timezone, + // should show the desired hour. If DST caused a shift (spring-forward), + // the hour won't match — advance by 1 hour. + const verify = getLocalParts(result, timezone); + if (verify.hour !== hour) { + // Spring-forward: the target hour doesn't exist. Advance to the next valid hour. + const diff = ((hour - verify.hour + 24) % 24) * 60 * 60 * 1000; + return new Date(result.getTime() + diff); + } + + return result; +}; + +const computeNextRun = ( + scheduleType: ScheduleType, + timezone: string, + from: Date, + opts: { + hour?: number; + minute?: number; + dayOfWeek?: number; + dayOfMonth?: number; + } +): Date => { + const minute = opts.minute ?? 0; + + if (scheduleType === "hourly") { + // Next occurrence of :MM after `from` + const local = getLocalParts(from, timezone); + if (local.minute < minute) { + // Still in the current hour + return localToUtc( + timezone, + local.year, + local.month, + local.day, + local.hour, + minute + ); + } + // Next hour + const next = new Date(from.getTime() + 60 * 60 * 1000); + const nextLocal = getLocalParts(next, timezone); + return localToUtc( + timezone, + nextLocal.year, + nextLocal.month, + nextLocal.day, + nextLocal.hour, + minute + ); + } + + const hour = opts.hour ?? 0; + + if (scheduleType === "daily") { + const local = getLocalParts(from, timezone); + // Try today + const candidate = localToUtc( + timezone, + local.year, + local.month, + local.day, + hour, + minute + ); + if (candidate.getTime() > from.getTime()) { + return candidate; + } + // Tomorrow + const tomorrow = new Date(from.getTime() + 24 * 60 * 60 * 1000); + const tLocal = getLocalParts(tomorrow, timezone); + return localToUtc( + timezone, + tLocal.year, + tLocal.month, + tLocal.day, + hour, + minute + ); + } + + if (scheduleType === "weekly") { + const targetDay = opts.dayOfWeek ?? 0; + const local = getLocalParts(from, timezone); + + // Try this week + let daysAhead = (targetDay - local.dayOfWeek + 7) % 7; + if (daysAhead === 0) { + // Same day — check if time hasn't passed + const candidate = localToUtc( + timezone, + local.year, + local.month, + local.day, + hour, + minute + ); + if (candidate.getTime() > from.getTime()) { + return candidate; + } + daysAhead = 7; + } + + const target = new Date(from.getTime() + daysAhead * 24 * 60 * 60 * 1000); + const tLocal = getLocalParts(target, timezone); + return localToUtc( + timezone, + tLocal.year, + tLocal.month, + tLocal.day, + hour, + minute + ); + } + + // monthly + const targetDom = opts.dayOfMonth ?? 1; + const local = getLocalParts(from, timezone); + + // Try this month + if (local.day <= targetDom) { + const candidate = localToUtc( + timezone, + local.year, + local.month, + targetDom, + hour, + minute + ); + if (candidate.getTime() > from.getTime()) { + return candidate; + } + } + + // Next month + let nextMonth = local.month + 1; + let nextYear = local.year; + if (nextMonth > 12) { + nextMonth = 1; + nextYear++; + } + return localToUtc(timezone, nextYear, nextMonth, targetDom, hour, minute); +}; + +export { computeNextRun, SCHEDULE_TYPES }; diff --git a/apps/operator/src/services/schedule.test.ts b/apps/operator/src/services/schedule.test.ts index 85583f7..26aad09 100644 --- a/apps/operator/src/services/schedule.test.ts +++ b/apps/operator/src/services/schedule.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "vitest"; -import { computeNextRun, createScheduleSchema } from "./schedule"; +import { createScheduleSchema } from "./schedule"; +import { computeNextRun } from "./schedule-time"; describe("computeNextRun", () => { describe("hourly", () => { @@ -346,7 +347,7 @@ describe("createScheduleSchema", () => { it("rejects sourceUrl over 2048 chars", () => { const result = createScheduleSchema.safeParse({ ...base, - sourceUrl: "https://example.com/" + "x".repeat(2048), + sourceUrl: `https://example.com/${"x".repeat(2048)}`, messagePrompt: "test", }); expect(result.success).toBe(false); @@ -360,9 +361,7 @@ describe("createScheduleSchema", () => { useBrowser: true, }); expect(result.success).toBe(true); - if (result.success) { - expect(result.data.useBrowser).toBe(true); - } + expect(result.data?.useBrowser).toBe(true); }); it("accepts useBrowser=false explicitly", () => { @@ -373,9 +372,7 @@ describe("createScheduleSchema", () => { useBrowser: false, }); expect(result.success).toBe(true); - if (result.success) { - expect(result.data.useBrowser).toBe(false); - } + expect(result.data?.useBrowser).toBe(false); }); it("leaves useBrowser undefined when omitted", () => { @@ -385,9 +382,7 @@ describe("createScheduleSchema", () => { messagePrompt: "Summarize changes", }); expect(result.success).toBe(true); - if (result.success) { - expect(result.data.useBrowser).toBeUndefined(); - } + expect(result.data?.useBrowser).toBeUndefined(); }); it("rejects useBrowser of non-boolean type", () => { diff --git a/apps/operator/src/services/schedule.ts b/apps/operator/src/services/schedule.ts index ba5dddc..32cf8c5 100644 --- a/apps/operator/src/services/schedule.ts +++ b/apps/operator/src/services/schedule.ts @@ -3,10 +3,9 @@ import { drizzle } from "drizzle-orm/d1"; import type { DrizzleD1Database } from "drizzle-orm/d1"; import { z } from "zod"; -import { schedules } from "../db/schema"; +import { schedules } from "~/db/schema"; -const SCHEDULE_TYPES = ["hourly", "daily", "weekly", "monthly"] as const; -type ScheduleType = (typeof SCHEDULE_TYPES)[number]; +import { computeNextRun, SCHEDULE_TYPES } from "./schedule-time"; const MAX_ACTIVE_SCHEDULES = 20; const MAX_RETRIES = 3; @@ -44,249 +43,31 @@ const createScheduleSchema = z }) .refine( (d) => { - if (d.sourceUrl) { - return d.messagePrompt != null && d.fixedMessage == null; + if (d.sourceUrl !== undefined && d.sourceUrl !== "") { + return d.messagePrompt !== undefined && d.fixedMessage === undefined; } - return (d.fixedMessage != null) !== (d.messagePrompt != null); + return (d.fixedMessage !== undefined) !== (d.messagePrompt !== undefined); }, { message: "Reminders need exactly one of fixedMessage/messagePrompt. Monitors need sourceUrl + messagePrompt, no fixedMessage.", } ) - .refine((d) => !d.keywords?.length || d.sourceUrl != null, { + .refine((d) => (d.keywords?.length ?? 0) === 0 || d.sourceUrl !== undefined, { message: "keywords can only be used with monitors (sourceUrl must be set)", }) - .refine((d) => d.scheduleType === "hourly" || d.hour != null, { + .refine((d) => d.scheduleType === "hourly" || d.hour !== undefined, { message: "hour is required for daily/weekly/monthly schedules", }) - .refine((d) => d.scheduleType !== "weekly" || d.dayOfWeek != null, { + .refine((d) => d.scheduleType !== "weekly" || d.dayOfWeek !== undefined, { message: "dayOfWeek is required for weekly schedules", }) - .refine((d) => d.scheduleType !== "monthly" || d.dayOfMonth != null, { + .refine((d) => d.scheduleType !== "monthly" || d.dayOfMonth !== undefined, { message: "dayOfMonth is required for monthly schedules", }); type CreateScheduleInput = z.infer; -const WEEKDAY_MAP: Record = { - Sun: 0, - Mon: 1, - Tue: 2, - Wed: 3, - Thu: 4, - Fri: 5, - Sat: 6, -}; - -/** - * Get the current local parts (hour, minute, day-of-week, day-of-month, etc.) - * for a given Date in a given timezone. - */ -const getLocalParts = (date: Date, timezone: string) => { - const fmt = new Intl.DateTimeFormat("en-US", { - timeZone: timezone, - year: "numeric", - month: "2-digit", - day: "2-digit", - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - weekday: "short", - hour12: false, - }); - const parts = Object.fromEntries( - fmt.formatToParts(date).map((p) => [p.type, p.value]) - ); - - return { - year: Number(parts.year), - month: Number(parts.month), - day: Number(parts.day), - hour: Number(parts.hour === "24" ? "0" : parts.hour), - minute: Number(parts.minute), - second: Number(parts.second), - dayOfWeek: WEEKDAY_MAP[parts.weekday] ?? 0, - }; -}; - -/** - * Build a UTC Date from local parts in a given timezone. - * Uses the timezone offset to convert local -> UTC. - */ -const localToUtc = ( - timezone: string, - year: number, - month: number, - day: number, - hour: number, - minute: number -): Date => { - // Start with a guess: treat local parts as UTC - const guess = new Date(Date.UTC(year, month - 1, day, hour, minute, 0, 0)); - - // Find the offset by checking what local time the guess maps to - const localParts = getLocalParts(guess, timezone); - const localAsUtc = new Date( - Date.UTC( - localParts.year, - localParts.month - 1, - localParts.day, - localParts.hour, - localParts.minute, - localParts.second, - 0 - ) - ); - - const offsetMs = localAsUtc.getTime() - guess.getTime(); - const result = new Date(guess.getTime() - offsetMs); - - // Verify by round-tripping: the result, viewed in the target timezone, - // should show the desired hour. If DST caused a shift (spring-forward), - // the hour won't match — advance by 1 hour. - const verify = getLocalParts(result, timezone); - if (verify.hour !== hour) { - // Spring-forward: the target hour doesn't exist. Advance to the next valid hour. - const diff = ((hour - verify.hour + 24) % 24) * 60 * 60 * 1000; - return new Date(result.getTime() + diff); - } - - return result; -}; - -const computeNextRun = ( - scheduleType: ScheduleType, - timezone: string, - from: Date, - opts: { - hour?: number; - minute?: number; - dayOfWeek?: number; - dayOfMonth?: number; - } -): Date => { - const minute = opts.minute ?? 0; - - if (scheduleType === "hourly") { - // Next occurrence of :MM after `from` - const local = getLocalParts(from, timezone); - if (local.minute < minute) { - // Still in the current hour - return localToUtc( - timezone, - local.year, - local.month, - local.day, - local.hour, - minute - ); - } - // Next hour - const next = new Date(from.getTime() + 60 * 60 * 1000); - const nextLocal = getLocalParts(next, timezone); - return localToUtc( - timezone, - nextLocal.year, - nextLocal.month, - nextLocal.day, - nextLocal.hour, - minute - ); - } - - const hour = opts.hour ?? 0; - - if (scheduleType === "daily") { - const local = getLocalParts(from, timezone); - // Try today - const candidate = localToUtc( - timezone, - local.year, - local.month, - local.day, - hour, - minute - ); - if (candidate.getTime() > from.getTime()) { - return candidate; - } - // Tomorrow - const tomorrow = new Date(from.getTime() + 24 * 60 * 60 * 1000); - const tLocal = getLocalParts(tomorrow, timezone); - return localToUtc( - timezone, - tLocal.year, - tLocal.month, - tLocal.day, - hour, - minute - ); - } - - if (scheduleType === "weekly") { - const targetDay = opts.dayOfWeek ?? 0; - const local = getLocalParts(from, timezone); - - // Try this week - let daysAhead = (targetDay - local.dayOfWeek + 7) % 7; - if (daysAhead === 0) { - // Same day — check if time hasn't passed - const candidate = localToUtc( - timezone, - local.year, - local.month, - local.day, - hour, - minute - ); - if (candidate.getTime() > from.getTime()) { - return candidate; - } - daysAhead = 7; - } - - const target = new Date(from.getTime() + daysAhead * 24 * 60 * 60 * 1000); - const tLocal = getLocalParts(target, timezone); - return localToUtc( - timezone, - tLocal.year, - tLocal.month, - tLocal.day, - hour, - minute - ); - } - - // monthly - const targetDom = opts.dayOfMonth ?? 1; - const local = getLocalParts(from, timezone); - - // Try this month - if (local.day <= targetDom) { - const candidate = localToUtc( - timezone, - local.year, - local.month, - targetDom, - hour, - minute - ); - if (candidate.getTime() > from.getTime()) { - return candidate; - } - } - - // Next month - let nextMonth = local.month + 1; - let nextYear = local.year; - if (nextMonth > 12) { - nextMonth = 1; - nextYear++; - } - return localToUtc(timezone, nextYear, nextMonth, targetDom, hour, minute); -}; - class ScheduleService { private readonly db: DrizzleD1Database; @@ -322,9 +103,10 @@ class ScheduleService { fixedMessage: validated.fixedMessage, messagePrompt: validated.messagePrompt, sourceUrl: validated.sourceUrl, - keywords: validated.keywords?.length - ? JSON.stringify(validated.keywords) - : undefined, + keywords: + (validated.keywords?.length ?? 0) > 0 + ? JSON.stringify(validated.keywords) + : undefined, useBrowser: validated.useBrowser ?? false, description: validated.description, nextRunAt: nextRunAt.toISOString(), @@ -350,6 +132,9 @@ class ScheduleService { .select({ count: sql`count(*)` }) .from(schedules) .where(and(eq(schedules.chatId, chatId), eq(schedules.active, true))); + if (result === undefined) { + throw new Error("count(*) query returned no rows"); + } return result.count; } @@ -519,11 +304,9 @@ class ScheduleService { } export { - computeNextRun, createScheduleSchema, MAX_ACTIVE_SCHEDULES, MAX_RETRIES, - SCHEDULE_TYPES, ScheduleService, }; -export type { CreateScheduleInput, ScheduleType }; +export type { CreateScheduleInput }; diff --git a/apps/operator/src/services/scrape-convert.test.ts b/apps/operator/src/services/scrape-convert.test.ts new file mode 100644 index 0000000..46b2824 --- /dev/null +++ b/apps/operator/src/services/scrape-convert.test.ts @@ -0,0 +1,75 @@ +import { describe, expect, it } from "vitest"; + +import { collapseWhitespace, convertContent } from "./scrape"; + +const expectText = (result: ReturnType): string => { + if (!("text" in result)) { + throw new Error(`Expected text result, got error: ${result.error}`); + } + return result.text; +}; + +describe("convertContent", () => { + it("converts HTML to markdown", () => { + const result = convertContent( + "

Title

Paragraph

", + "text/html" + ); + const text = expectText(result); + expect(text).toContain("Title"); + expect(text).toContain("Paragraph"); + }); + + it("handles application/xhtml+xml as HTML", () => { + const result = convertContent("

Content

", "application/xhtml+xml"); + const text = expectText(result); + expect(text).toContain("Content"); + }); + + it("pretty-prints JSON", () => { + const result = convertContent('{"a":1}', "application/json"); + const text = expectText(result); + expect(text).toContain("```json"); + expect(text).toContain('"a": 1'); + }); + + it("handles +json content types", () => { + const result = convertContent( + '{"data":"test"}', + "application/vnd.api+json" + ); + const text = expectText(result); + expect(text).toContain("```json"); + }); + + it("wraps invalid JSON in plain code block", () => { + const result = convertContent("not json", "application/json"); + const text = expectText(result); + expect(text).toContain("```\nnot json\n```"); + }); + + it("passes plain text through", () => { + const result = convertContent("hello world", "text/plain"); + const text = expectText(result); + expect(text).toBe("hello world"); + }); + + it("returns error for unsupported types", () => { + const result = convertContent("data", "application/octet-stream"); + expect("error" in result).toBe(true); + }); +}); + +describe("collapseWhitespace", () => { + it("collapses 3+ newlines to double", () => { + expect(collapseWhitespace("a\n\n\n\nb")).toBe("a\n\nb"); + }); + + it("collapses multiple spaces to single", () => { + expect(collapseWhitespace("a b")).toBe("a b"); + }); + + it("trims leading and trailing whitespace", () => { + expect(collapseWhitespace(" hello ")).toBe("hello"); + }); +}); diff --git a/apps/operator/src/services/scrape.test.ts b/apps/operator/src/services/scrape.test.ts index 0f4f2fc..f09b309 100644 --- a/apps/operator/src/services/scrape.test.ts +++ b/apps/operator/src/services/scrape.test.ts @@ -1,6 +1,26 @@ import { afterEach, describe, expect, it, vi } from "vitest"; -import { collapseWhitespace, convertContent, scrapeUrl } from "./scrape"; +import { scrapeUrl } from "./scrape"; + +type ScrapeOutcome = Awaited>; + +const expectOk = ( + result: ScrapeOutcome +): Extract => { + if (!result.ok) { + throw new Error(`Expected ok result, got error: ${result.error}`); + } + return result; +}; + +const expectError = ( + result: ScrapeOutcome +): Extract => { + if (result.ok) { + throw new Error("Expected error result, got ok"); + } + return result; +}; const createMockResponse = ( body: string, @@ -11,6 +31,9 @@ const createMockResponse = ( headers: init?.headers ?? { "content-type": "text/html" }, }); +const makeFetcher = (response: Response): Fetcher => + ({ fetch: vi.fn().mockResolvedValue(response) }) as unknown as Fetcher; + describe("scrapeUrl", () => { afterEach(() => { vi.restoreAllMocks(); @@ -26,12 +49,10 @@ describe("scrapeUrl", () => { const result = await scrapeUrl("https://example.com"); - expect(result.ok).toBe(true); - if (result.ok) { - expect(result.text).toContain("Hello"); - expect(result.text).toContain("World"); - expect(result.truncated).toBe(false); - } + const ok = expectOk(result); + expect(ok.text).toContain("Hello"); + expect(ok.text).toContain("World"); + expect(ok.truncated).toBe(false); }); it("handles JSON response with pretty-print", async () => { @@ -47,12 +68,10 @@ describe("scrapeUrl", () => { const result = await scrapeUrl("https://api.example.com/data"); - expect(result.ok).toBe(true); - if (result.ok) { - expect(result.text).toContain("```json"); - expect(result.text).toContain('"key": "value"'); - expect(result.text).toContain("```"); - } + const ok = expectOk(result); + expect(ok.text).toContain("```json"); + expect(ok.text).toContain('"key": "value"'); + expect(ok.text).toContain("```"); }); it("handles plain text response", async () => { @@ -67,10 +86,8 @@ describe("scrapeUrl", () => { const result = await scrapeUrl("https://example.com/robots.txt"); - expect(result.ok).toBe(true); - if (result.ok) { - expect(result.text).toBe("Plain text content"); - } + const ok = expectOk(result); + expect(ok.text).toBe("Plain text content"); }); it("returns error for unsupported content type", async () => { @@ -85,10 +102,8 @@ describe("scrapeUrl", () => { const result = await scrapeUrl("https://example.com/file.bin"); - expect(result.ok).toBe(false); - if (!result.ok) { - expect(result.error).toContain("Unsupported content type"); - } + const failure = expectError(result); + expect(failure.error).toContain("Unsupported content type"); }); it("returns error for non-ok HTTP status", async () => { @@ -101,11 +116,9 @@ describe("scrapeUrl", () => { const result = await scrapeUrl("https://example.com"); - expect(result.ok).toBe(false); - if (!result.ok) { - expect(result.error).toBe("Blocked by site"); - expect(result.statusCode).toBe(403); - } + const failure = expectError(result); + expect(failure.error).toBe("Blocked by site"); + expect(failure.statusCode).toBe(403); }); it("returns error for 404", async () => { @@ -118,11 +131,9 @@ describe("scrapeUrl", () => { const result = await scrapeUrl("https://example.com/missing"); - expect(result.ok).toBe(false); - if (!result.ok) { - expect(result.error).toBe("Page not found"); - expect(result.statusCode).toBe(404); - } + const failure = expectError(result); + expect(failure.error).toBe("Page not found"); + expect(failure.statusCode).toBe(404); }); it("returns generic error for unmapped status codes", async () => { @@ -135,10 +146,8 @@ describe("scrapeUrl", () => { const result = await scrapeUrl("https://example.com"); - expect(result.ok).toBe(false); - if (!result.ok) { - expect(result.error).toBe("HTTP 502"); - } + const failure = expectError(result); + expect(failure.error).toBe("HTTP 502"); }); it("truncates text exceeding maxTextLength", async () => { @@ -152,11 +161,9 @@ describe("scrapeUrl", () => { maxTextLength: 100, }); - expect(result.ok).toBe(true); - if (result.ok) { - expect(result.text.length).toBeLessThanOrEqual(100); - expect(result.truncated).toBe(true); - } + const ok = expectOk(result); + expect(ok.text.length).toBeLessThanOrEqual(100); + expect(ok.truncated).toBe(true); }); it("returns error on fetch failure", async () => { @@ -167,10 +174,8 @@ describe("scrapeUrl", () => { const result = await scrapeUrl("https://example.com"); - expect(result.ok).toBe(false); - if (!result.ok) { - expect(result.error).toBe("Network error"); - } + const failure = expectError(result); + expect(failure.error).toBe("Network error"); }); it("returns error on timeout", async () => { @@ -179,10 +184,8 @@ describe("scrapeUrl", () => { const result = await scrapeUrl("https://example.com"); - expect(result.ok).toBe(false); - if (!result.ok) { - expect(result.error).toBe("Request timed out"); - } + const failure = expectError(result); + expect(failure.error).toBe("Request timed out"); }); it("follows safe redirects", async () => { @@ -199,10 +202,8 @@ describe("scrapeUrl", () => { const result = await scrapeUrl("https://example.com/start"); - expect(result.ok).toBe(true); - if (result.ok) { - expect(result.text).toContain("Redirected"); - } + const ok = expectOk(result); + expect(ok.text).toContain("Redirected"); expect(mockFetch).toHaveBeenCalledTimes(2); }); @@ -219,10 +220,8 @@ describe("scrapeUrl", () => { const result = await scrapeUrl("https://example.com"); - expect(result.ok).toBe(false); - if (!result.ok) { - expect(result.error).toContain("Redirect to unsafe URL"); - } + const failure = expectError(result); + expect(failure.error).toContain("Redirect to unsafe URL"); }); it("rejects redirects to HTTP", async () => { @@ -238,10 +237,8 @@ describe("scrapeUrl", () => { const result = await scrapeUrl("https://example.com"); - expect(result.ok).toBe(false); - if (!result.ok) { - expect(result.error).toContain("Redirect to unsafe URL"); - } + const failure = expectError(result); + expect(failure.error).toContain("Redirect to unsafe URL"); }); it("returns error on too many redirects", async () => { @@ -253,10 +250,8 @@ describe("scrapeUrl", () => { const result = await scrapeUrl("https://example.com"); - expect(result.ok).toBe(false); - if (!result.ok) { - expect(result.error).toBe("Too many redirects"); - } + const failure = expectError(result); + expect(failure.error).toBe("Too many redirects"); }); it("truncates response bodies exceeding 2MB and marks truncated", async () => { @@ -268,10 +263,8 @@ describe("scrapeUrl", () => { const result = await scrapeUrl("https://example.com"); - expect(result.ok).toBe(true); - if (result.ok) { - expect(result.truncated).toBe(true); - } + const ok = expectOk(result); + expect(ok.truncated).toBe(true); }); }); @@ -280,9 +273,6 @@ describe("scrapeUrl via browserScraper", () => { vi.restoreAllMocks(); }); - const makeFetcher = (response: Response): Fetcher => - ({ fetch: vi.fn().mockResolvedValue(response) }) as unknown as Fetcher; - it("returns markdown from a successful browser-scraper response", async () => { const browserScraper = makeFetcher( Response.json({ @@ -300,10 +290,8 @@ describe("scrapeUrl via browserScraper", () => { useBrowser: true, }); - expect(result.ok).toBe(true); - if (result.ok) { - expect(result.text).toContain("Hello SPA"); - } + const ok = expectOk(result); + expect(ok.text).toContain("Hello SPA"); }); it("propagates truncated=true from browser-scraper even when text fits within maxTextLength", async () => { @@ -323,10 +311,8 @@ describe("scrapeUrl via browserScraper", () => { useBrowser: true, }); - expect(result.ok).toBe(true); - if (result.ok) { - expect(result.truncated).toBe(true); - } + const ok = expectOk(result); + expect(ok.truncated).toBe(true); }); it("propagates error when browser-scraper returns ok:false without status", async () => { @@ -339,10 +325,8 @@ describe("scrapeUrl via browserScraper", () => { useBrowser: true, }); - expect(result.ok).toBe(false); - if (!result.ok) { - expect(result.error).toBe("Navigation timeout"); - } + const failure = expectError(result); + expect(failure.error).toBe("Navigation timeout"); }); it("maps browser-scraper status through ERROR_MAP", async () => { @@ -355,11 +339,9 @@ describe("scrapeUrl via browserScraper", () => { useBrowser: true, }); - expect(result.ok).toBe(false); - if (!result.ok) { - expect(result.error).toBe("Blocked by site"); - expect(result.statusCode).toBe(403); - } + const failure = expectError(result); + expect(failure.error).toBe("Blocked by site"); + expect(failure.statusCode).toBe(403); }); it("returns error when browser-scraper returns invalid JSON", async () => { @@ -375,10 +357,8 @@ describe("scrapeUrl via browserScraper", () => { useBrowser: true, }); - expect(result.ok).toBe(false); - if (!result.ok) { - expect(result.error).toMatch(/invalid JSON/); - } + const failure = expectError(result); + expect(failure.error).toMatch(/invalid JSON/); }); it("uses native fetch when useBrowser is false even if browserScraper is provided", async () => { @@ -402,80 +382,3 @@ describe("scrapeUrl via browserScraper", () => { expect(browserScraperFetch).not.toHaveBeenCalled(); }); }); - -describe("convertContent", () => { - it("converts HTML to markdown", () => { - const result = convertContent( - "

Title

Paragraph

", - "text/html" - ); - expect("text" in result).toBe(true); - if ("text" in result) { - expect(result.text).toContain("Title"); - expect(result.text).toContain("Paragraph"); - } - }); - - it("handles application/xhtml+xml as HTML", () => { - const result = convertContent("

Content

", "application/xhtml+xml"); - expect("text" in result).toBe(true); - if ("text" in result) { - expect(result.text).toContain("Content"); - } - }); - - it("pretty-prints JSON", () => { - const result = convertContent('{"a":1}', "application/json"); - expect("text" in result).toBe(true); - if ("text" in result) { - expect(result.text).toContain("```json"); - expect(result.text).toContain('"a": 1'); - } - }); - - it("handles +json content types", () => { - const result = convertContent( - '{"data":"test"}', - "application/vnd.api+json" - ); - expect("text" in result).toBe(true); - if ("text" in result) { - expect(result.text).toContain("```json"); - } - }); - - it("wraps invalid JSON in plain code block", () => { - const result = convertContent("not json", "application/json"); - expect("text" in result).toBe(true); - if ("text" in result) { - expect(result.text).toContain("```\nnot json\n```"); - } - }); - - it("passes plain text through", () => { - const result = convertContent("hello world", "text/plain"); - expect("text" in result).toBe(true); - if ("text" in result) { - expect(result.text).toBe("hello world"); - } - }); - - it("returns error for unsupported types", () => { - const result = convertContent("data", "application/octet-stream"); - expect("error" in result).toBe(true); - }); -}); - -describe("collapseWhitespace", () => { - it("collapses 3+ newlines to double", () => { - expect(collapseWhitespace("a\n\n\n\nb")).toBe("a\n\nb"); - }); - - it("collapses multiple spaces to single", () => { - expect(collapseWhitespace("a b")).toBe("a b"); - }); - - it("trims leading and trailing whitespace", () => { - expect(collapseWhitespace(" hello ")).toBe("hello"); - }); -}); diff --git a/apps/operator/src/services/scrape.ts b/apps/operator/src/services/scrape.ts index 78c4f10..e2d7935 100644 --- a/apps/operator/src/services/scrape.ts +++ b/apps/operator/src/services/scrape.ts @@ -93,8 +93,8 @@ const readBodyWithLimit = async ( const collapseWhitespace = (text: string): string => text - .replace(/\n{3,}/g, "\n\n") - .replace(/[ \t]{2,}/g, " ") + .replaceAll(/\n{3,}/g, "\n\n") + .replaceAll(/[ \t]{2,}/g, " ") .trim(); const convertContent = ( @@ -148,7 +148,7 @@ const fetchWithSafeRedirects = async ( } const location = response.headers.get("location"); - if (!location) { + if (location === null || location === "") { return response; } @@ -278,14 +278,4 @@ const scrapeUrl = async ( }; }; -export { - collapseWhitespace, - convertContent, - DEFAULT_MAX_TEXT_LENGTH, - FETCH_TIMEOUT_MS, - MAX_BODY_BYTES, - MAX_REDIRECTS, - scrapeUrl, - USER_AGENT, -}; -export type { ScrapeOptions, ScrapeResult }; +export { collapseWhitespace, convertContent, scrapeUrl }; diff --git a/apps/operator/src/types/env.ts b/apps/operator/src/types/env.ts index a90ba13..fe64b6c 100644 --- a/apps/operator/src/types/env.ts +++ b/apps/operator/src/types/env.ts @@ -19,9 +19,5 @@ type AppEnv = { }; }; -const parseEnv = (env: unknown): z.infer => { - return envSchema.parse(env); -}; - -export { envSchema, parseEnv }; +export { envSchema }; export type { AppEnv }; diff --git a/apps/operator/src/types/telegram.ts b/apps/operator/src/types/telegram.ts index c0fed92..377d877 100644 --- a/apps/operator/src/types/telegram.ts +++ b/apps/operator/src/types/telegram.ts @@ -40,12 +40,8 @@ const telegramUpdateSchema = z }) .loose(); -export { - telegramCallbackQuerySchema, - telegramMessageSchema, - telegramUpdateSchema, -}; +type TelegramUpdate = z.infer; +type TelegramCallbackQuery = z.infer; -export type TelegramUpdate = z.infer; -export type TelegramMessage = z.infer; -export type TelegramCallbackQuery = z.infer; +export { telegramUpdateSchema }; +export type { TelegramCallbackQuery, TelegramUpdate }; diff --git a/apps/operator/src/utils/keywords.test.ts b/apps/operator/src/utils/keywords.test.ts index d9e2732..d20878d 100644 --- a/apps/operator/src/utils/keywords.test.ts +++ b/apps/operator/src/utils/keywords.test.ts @@ -86,24 +86,24 @@ describe("extractWindows", () => { }); it("extracts window around single match", () => { - const text = "a".repeat(100) + "MATCH" + "b".repeat(100); + const text = `${"a".repeat(100)}MATCH${"b".repeat(100)}`; const result = extractWindows(text, [100], 10); - expect(result).toBe("a".repeat(10) + "MATCH" + "b".repeat(5)); - expect(result.length).toBe(20); + expect(result).toBe(`${"a".repeat(10)}MATCH${"b".repeat(5)}`); + expect(result).toHaveLength(20); }); it("clamps to text start", () => { - const text = "MATCH" + "x".repeat(100); + const text = `MATCH${"x".repeat(100)}`; const result = extractWindows(text, [0], 10); expect(result.startsWith("MATCH")).toBe(true); - expect(result.length).toBe(10); + expect(result).toHaveLength(10); }); it("clamps to text end", () => { - const text = "x".repeat(100) + "MATCH"; + const text = `${"x".repeat(100)}MATCH`; const result = extractWindows(text, [100], 10); expect(result.endsWith("MATCH")).toBe(true); - expect(result.length).toBe(15); + expect(result).toHaveLength(15); }); it("merges overlapping windows", () => { @@ -112,13 +112,13 @@ describe("extractWindows", () => { const result = extractWindows(text, [20, 25], 10); // Should be one merged window from [10, 35] expect(result).toBe("x".repeat(25)); - expect(result.includes("---")).toBe(false); + expect(result).not.toContain("---"); }); it("separates non-overlapping windows", () => { const text = "x".repeat(200); // Two positions far apart const result = extractWindows(text, [10, 190], 5); - expect(result.includes("\n\n---\n\n")).toBe(true); + expect(result).toContain("\n\n---\n\n"); }); }); diff --git a/apps/operator/src/utils/keywords.ts b/apps/operator/src/utils/keywords.ts index ba4de60..7669848 100644 --- a/apps/operator/src/utils/keywords.ts +++ b/apps/operator/src/utils/keywords.ts @@ -3,7 +3,7 @@ * Returns [] on null, undefined, or malformed data. Never throws. */ const parseKeywords = (raw: string | null | undefined): string[] => { - if (!raw) { + if (raw === null || raw === undefined || raw === "") { return []; } try { @@ -64,13 +64,13 @@ const extractWindows = ( .sort((a, b) => a[0] - b[0]); // Merge overlapping ranges - const merged: [number, number][] = [[ranges[0][0], ranges[0][1]]]; - for (let i = 1; i < ranges.length; i++) { - const last = merged[merged.length - 1]; - if (ranges[i][0] <= last[1]) { - last[1] = Math.max(last[1], ranges[i][1]); + const merged: [number, number][] = []; + for (const [start, end] of ranges) { + const last = merged.at(-1); + if (last !== undefined && start <= last[1]) { + last[1] = Math.max(last[1], end); } else { - merged.push([ranges[i][0], ranges[i][1]]); + merged.push([start, end]); } } diff --git a/apps/operator/src/utils/markdown-to-html.test.ts b/apps/operator/src/utils/markdown-to-html.test.ts index ef777e8..08b1b56 100644 --- a/apps/operator/src/utils/markdown-to-html.test.ts +++ b/apps/operator/src/utils/markdown-to-html.test.ts @@ -225,8 +225,7 @@ describe("markdownToTelegramHtml — splitMessage + convert pipeline", () => { }); it("produces balanced HTML when split lands inside a fenced code block", () => { - const codeFence = - "```\n" + "code line\n".repeat(500) + "```\n\nFollow-up text."; + const codeFence = `\`\`\`\n${"code line\n".repeat(500)}\`\`\`\n\nFollow-up text.`; expect(codeFence.length).toBeGreaterThan(4096); const chunks = splitMessage(codeFence); @@ -240,13 +239,10 @@ describe("markdownToTelegramHtml — splitMessage + convert pipeline", () => { const countSubstr = (haystack: string, needle: string): number => { let n = 0; - let from = 0; - while (true) { - const idx = haystack.indexOf(needle, from); - if (idx === -1) { - return n; - } + let idx = haystack.indexOf(needle); + while (idx !== -1) { n++; - from = idx + needle.length; + idx = haystack.indexOf(needle, idx + needle.length); } + return n; }; diff --git a/apps/operator/src/utils/markdown-to-html.ts b/apps/operator/src/utils/markdown-to-html.ts index 293ee57..b011b26 100644 --- a/apps/operator/src/utils/markdown-to-html.ts +++ b/apps/operator/src/utils/markdown-to-html.ts @@ -1,5 +1,5 @@ const escapeHtml = (s: string): string => - s.replace(/&/g, "&").replace(//g, ">"); + s.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">"); const PLACEHOLDER_OPEN = ""; const PLACEHOLDER_CLOSE = ""; @@ -10,11 +10,11 @@ const stripSentinels = (s: string): string => s.replace(SENTINEL_RE, ""); const applyEmphasis = (text: string): string => { let s = text; - s = s.replace(/\*\*([^*\n<>]+?)\*\*/g, "$1"); - s = s.replace(/__([^_\n<>]+?)__/g, "$1"); - s = s.replace(/~~([^~\n<>]+?)~~/g, "$1"); - s = s.replace(/(?]+?)\*(?!\*)/g, "$1"); - s = s.replace(/(?]+?)_(?!_)/g, "$1"); + s = s.replaceAll(/\*\*([^*\n<>]+?)\*\*/g, "$1"); + s = s.replaceAll(/__([^_\n<>]+?)__/g, "$1"); + s = s.replaceAll(/~~([^~\n<>]+?)~~/g, "$1"); + s = s.replaceAll(/(?]+?)\*(?!\*)/g, "$1"); + s = s.replaceAll(/(?]+?)_(?!_)/g, "$1"); return s; }; @@ -27,7 +27,7 @@ const inlineConvert = (text: string, protectedSegments: string[]): string => { let s = text; - s = s.replace(/`([^`\n]+)`/g, (_m, content: string) => + s = s.replaceAll(/`([^`\n]+)`/g, (_m, content: string) => protect(`${content}`) ); @@ -40,10 +40,13 @@ const restoreProtected = (s: string, protectedSegments: string[]): string => { let result = s; while (PLACEHOLDER_RE.test(result)) { PLACEHOLDER_RE.lastIndex = 0; - result = result.replace( - PLACEHOLDER_RE, - (_m, idx: string) => protectedSegments[Number(idx)] - ); + result = result.replace(PLACEHOLDER_RE, (_m, idx: string) => { + const segment = protectedSegments[Number(idx)]; + if (segment === undefined) { + throw new Error(`No protected segment for placeholder index ${idx}`); + } + return segment; + }); } return result; }; @@ -61,27 +64,31 @@ const markdownToTelegramHtml = (md: string): string => { while (i < lines.length) { const line = lines[i]; + if (line === undefined) { + break; + } const fenceStart = /^```([A-Za-z0-9_+-]*)\s*$/.exec(line); if (fenceStart) { - let j = i + 1; - while (j < lines.length && !/^```\s*$/.test(lines[j])) { - j++; - } - if (j < lines.length) { + const closeOffset = lines + .slice(i + 1) + .findIndex((l) => /^```\s*$/.test(l)); + if (closeOffset !== -1) { + const j = i + 1 + closeOffset; const lang = fenceStart[1]; const content = lines.slice(i + 1, j).join("\n"); - const langAttr = lang ? ` class="language-${lang}"` : ""; + const langAttr = + lang !== undefined && lang !== "" ? ` class="language-${lang}"` : ""; out.push(`
${escapeHtml(content)}
`); i = j + 1; continue; } } - const heading = /^(#{1,6})\s+(.*)$/.exec(line); - if (heading) { + const headingText = /^(#{1,6})\s+(.*)$/.exec(line)?.[2]; + if (headingText !== undefined) { out.push( - `${inlineConvert(escapeHtml(heading[2]), protectedSegments)}` + `${inlineConvert(escapeHtml(headingText), protectedSegments)}` ); i++; continue; @@ -95,18 +102,20 @@ const markdownToTelegramHtml = (md: string): string => { if (line.startsWith(">")) { const buf: string[] = []; - while (i < lines.length && lines[i].startsWith(">")) { - const stripped = lines[i].replace(/^>\s?/, ""); + let quoted = lines[i]; + while (quoted !== undefined && quoted.startsWith(">")) { + const stripped = quoted.replace(/^>\s?/, ""); buf.push(inlineConvert(escapeHtml(stripped), protectedSegments)); i++; + quoted = lines[i]; } out.push(`
${buf.join("\n")}
`); continue; } - const list = /^\s*(?:[-*]|\d+\.)\s+(.*)$/.exec(line); - if (list) { - out.push(`• ${inlineConvert(escapeHtml(list[1]), protectedSegments)}`); + const listItem = /^\s*(?:[-*]|\d+\.)\s+(.*)$/.exec(line)?.[1]; + if (listItem !== undefined) { + out.push(`• ${inlineConvert(escapeHtml(listItem), protectedSegments)}`); i++; continue; } diff --git a/apps/operator/src/utils/message.test.ts b/apps/operator/src/utils/message.test.ts index e729bd9..68b901c 100644 --- a/apps/operator/src/utils/message.test.ts +++ b/apps/operator/src/utils/message.test.ts @@ -33,7 +33,7 @@ describe("splitMessage", () => { expect(chunks.length).toBeGreaterThan(1); for (const chunk of chunks) { expect(chunk.endsWith("a")).toBe(true); - expect(chunk.includes("\n\n")).toBe(false); + expect(chunk).not.toContain("\n\n"); } expect(chunks.join("\n")).toBe(text); }); diff --git a/apps/operator/tsconfig.json b/apps/operator/tsconfig.json index 1eed9fe..7edd798 100644 --- a/apps/operator/tsconfig.json +++ b/apps/operator/tsconfig.json @@ -1,7 +1,10 @@ { "extends": "@repo/typescript/base.json", "compilerOptions": { - "types": ["@cloudflare/workers-types"] + "types": ["@cloudflare/workers-types"], + "paths": { + "~/*": ["./src/*"] + } }, "include": ["src"], "exclude": ["node_modules"] diff --git a/apps/operator/vitest.config.ts b/apps/operator/vitest.config.ts new file mode 100644 index 0000000..f2a5332 --- /dev/null +++ b/apps/operator/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + // `~/` resolves through tsconfig `paths`; Wrangler's bundler reads them too. + resolve: { tsconfigPaths: true }, + test: { include: ["src/**/*.test.ts"] }, +}); diff --git a/docs/plan.md b/docs/plan.md index f2a02e1..5a17040 100644 --- a/docs/plan.md +++ b/docs/plan.md @@ -48,7 +48,7 @@ packages/ http-client/ # Shared fetch wrapper with response validation logger/ # Shared structured logger used by apps tooling/ - eslint/ # Shared ESLint config + github/ # CI setup action and gitleaks secrets scan prettier/ # Shared Prettier config typescript/ # Shared TypeScript config ``` diff --git a/knip.jsonc b/knip.jsonc new file mode 100644 index 0000000..2906163 --- /dev/null +++ b/knip.jsonc @@ -0,0 +1,12 @@ +{ + "$schema": "https://unpkg.com/knip@6/schema.json", + "workspaces": { + ".": { + // Prettier loads the plugins named in `@repo/prettier` by package name, + // resolved from the directory it runs in -- the root, for `pnpm format`. + // Knip cannot see that indirection, but removing these breaks formatting + // on a clean checkout. + "ignoreDependencies": ["@ianvs/prettier-plugin-sort-imports"], + }, + }, +} diff --git a/package.json b/package.json index 778fcfc..989308a 100644 --- a/package.json +++ b/package.json @@ -1,37 +1,36 @@ { "name": "switch-operator", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "dev": "pnpm -r --parallel --if-present dev", - "build": "pnpm -r --if-present build", - "typecheck": "pnpm -r --if-present typecheck", - "lint": "pnpm -r --if-present lint", - "test": "pnpm -r --if-present test", - "format": "prettier --write .", - "format:check": "prettier --check ." - }, - "keywords": [], + "private": true, "author": "Juha Kangas", "license": "MIT", - "packageManager": "pnpm@10.28.1", "type": "module", - "prettier": "@repo/prettier", + "engines": { + "node": "^24.21.0 || >=26.0.0", + "pnpm": ">=12.0.0" + }, + "packageManager": "pnpm@12.6.0+sha512.3ef68f951cb111ac204b4a5a16f0b2ddf0da56a96e0413e81d855d9f0b55ef926714709028e1cd00c405c2c5fb7b9e8ec4dc46777c805d0373c2f2ff00fd20ec", + "scripts": { + "dev": "turbo run dev", + "build": "turbo run build", + "typecheck": "turbo run typecheck", + "lint": "oxlint --type-aware --report-unused-disable-directives", + "test": "turbo run test", + "format": "prettier --write .", + "format:check": "prettier --check .", + "knip": "knip", + "secrets:scan": "sh tooling/github/scripts/secrets-scan.sh", + "clean": "turbo run clean" + }, "devDependencies": { - "@ianvs/prettier-plugin-sort-imports": "4.7.1", + "@ianvs/prettier-plugin-sort-imports": "catalog:", "@repo/prettier": "workspace:*", - "prettier": "3.8.2", - "prettier-plugin-tailwindcss": "0.7.2", - "vite": "8.0.16" + "@types/node": "catalog:", + "knip": "6.38.0", + "oxlint": "1.85.0", + "oxlint-tsgolint": "7.0.2003", + "prettier": "catalog:", + "turbo": "2.11.4", + "typescript": "catalog:" }, - "pnpm": { - "overrides": { - "esbuild": ">=0.28.1", - "undici": ">=7.28.0", - "ws": ">=8.21.0", - "js-yaml": ">=4.2.0", - "@babel/core": ">=7.29.6" - } - } + "prettier": "@repo/prettier" } diff --git a/packages/alpha-vantage/eslint.config.ts b/packages/alpha-vantage/eslint.config.ts deleted file mode 100644 index f57754c..0000000 --- a/packages/alpha-vantage/eslint.config.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* eslint-disable import/no-default-export */ -import { baseConfig } from "@repo/eslint/base"; -import { defineConfig } from "eslint/config"; - -export default defineConfig(baseConfig); diff --git a/packages/alpha-vantage/package.json b/packages/alpha-vantage/package.json index a7aa183..e423c96 100644 --- a/packages/alpha-vantage/package.json +++ b/packages/alpha-vantage/package.json @@ -8,24 +8,20 @@ "./types": "./src/types.ts" }, "scripts": { - "typecheck": "tsc --noEmit", - "lint": "eslint .", + "typecheck": "tsc", "test": "vitest run --passWithNoTests", - "format": "prettier --check . --ignore-path ../../.gitignore" + "clean": "rm -rf .turbo node_modules .cache" }, "dependencies": { "@repo/http-client": "workspace:*", "@repo/logger": "workspace:*", - "zod": "4.3.6" + "zod": "catalog:" }, "devDependencies": { - "@repo/eslint": "workspace:*", "@repo/prettier": "workspace:*", "@repo/typescript": "workspace:*", - "eslint": "9.39.1", - "prettier": "3.8.2", - "typescript": "6.0.2", - "vitest": "4.1.4" + "typescript": "catalog:", + "vitest": "catalog:" }, "prettier": "@repo/prettier" } diff --git a/packages/alpha-vantage/src/alpha-vantage.test.ts b/packages/alpha-vantage/src/alpha-vantage.test.ts index 5c7fda7..291dfd3 100644 --- a/packages/alpha-vantage/src/alpha-vantage.test.ts +++ b/packages/alpha-vantage/src/alpha-vantage.test.ts @@ -101,7 +101,7 @@ describe("AlphaVantageClient", () => { high: 469.13, low: 464.45, close: 467.28, - volume: 92955850, + volume: 92_955_850, }); }); @@ -157,8 +157,8 @@ describe("AlphaVantageClient", () => { expect(series.bars).toHaveLength(2); const [latest] = series.bars; - expect(latest.date).toBe("2024-01-05"); - expect(latest.close).toBe(467.28); + expect(latest?.date).toBe("2024-01-05"); + expect(latest?.close).toBe(467.28); }); it("sends the expected query parameters without outputsize", async () => { @@ -285,8 +285,11 @@ describe("AlphaVantageClient", () => { throttled.getWeeklyTimeSeries("SPY"), ]); - expect(starts).toHaveLength(2); - expect(starts[1] - starts[0]).toBeGreaterThanOrEqual(interval - 5); + const [first, second] = starts; + if (first === undefined || second === undefined) { + throw new Error("expected two request start times"); + } + expect(second - first).toBeGreaterThanOrEqual(interval - 5); }); }); }); diff --git a/packages/alpha-vantage/src/types.ts b/packages/alpha-vantage/src/types.ts index 30f7e43..3bd69d6 100644 --- a/packages/alpha-vantage/src/types.ts +++ b/packages/alpha-vantage/src/types.ts @@ -83,12 +83,19 @@ const timeSeriesSchema = (seriesKey: string, timeZoneKey: string) => .map(([date, bar]) => toBar(date, bar)) .sort((a, b) => (a.date < b.date ? 1 : -1)); - return { - symbol: meta["2. Symbol"], - lastRefreshed: meta["3. Last Refreshed"], - timeZone: meta[timeZoneKey], - bars, - }; + const symbol = meta["2. Symbol"]; + const lastRefreshed = meta["3. Last Refreshed"]; + const timeZone = meta[timeZoneKey]; + // The object schema above already requires all three keys. + if ( + symbol === undefined || + lastRefreshed === undefined || + timeZone === undefined + ) { + throw new Error("Alpha Vantage Meta Data is missing required fields"); + } + + return { symbol, lastRefreshed, timeZone, bars }; }); /** diff --git a/packages/alpha-vantage/vitest.config.ts b/packages/alpha-vantage/vitest.config.ts index ab8f85c..e2ec332 100644 --- a/packages/alpha-vantage/vitest.config.ts +++ b/packages/alpha-vantage/vitest.config.ts @@ -1,4 +1,3 @@ -/* eslint-disable import/no-default-export */ import { defineConfig } from "vitest/config"; export default defineConfig({ diff --git a/packages/http-client/eslint.config.ts b/packages/http-client/eslint.config.ts deleted file mode 100644 index f57754c..0000000 --- a/packages/http-client/eslint.config.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* eslint-disable import/no-default-export */ -import { baseConfig } from "@repo/eslint/base"; -import { defineConfig } from "eslint/config"; - -export default defineConfig(baseConfig); diff --git a/packages/http-client/package.json b/packages/http-client/package.json index 243a0cf..23ec4ad 100644 --- a/packages/http-client/package.json +++ b/packages/http-client/package.json @@ -8,23 +8,19 @@ "./types": "./src/types.ts" }, "scripts": { - "typecheck": "tsc --noEmit", - "lint": "eslint .", + "typecheck": "tsc", "test": "vitest run --passWithNoTests", - "format": "prettier --check . --ignore-path ../../.gitignore" + "clean": "rm -rf .turbo node_modules .cache" }, "dependencies": { "@repo/logger": "workspace:*", - "zod": "4.3.6" + "zod": "catalog:" }, "devDependencies": { - "@repo/eslint": "workspace:*", "@repo/prettier": "workspace:*", "@repo/typescript": "workspace:*", - "eslint": "9.39.1", - "prettier": "3.8.2", - "typescript": "6.0.2", - "vitest": "4.1.4" + "typescript": "catalog:", + "vitest": "catalog:" }, "prettier": "@repo/prettier" } diff --git a/packages/http-client/src/__tests__/http-client.test.ts b/packages/http-client/src/http-client.test.ts similarity index 87% rename from packages/http-client/src/__tests__/http-client.test.ts rename to packages/http-client/src/http-client.test.ts index 339a11e..77be35b 100644 --- a/packages/http-client/src/__tests__/http-client.test.ts +++ b/packages/http-client/src/http-client.test.ts @@ -1,7 +1,7 @@ import type { Logger } from "@repo/logger"; import { z } from "zod"; -import { HttpClient, HttpClientError } from "../http-client"; +import { HttpClient, HttpClientError } from "./http-client"; const mockFetch = vi.fn(); globalThis.fetch = mockFetch; @@ -163,16 +163,13 @@ describe("HttpClient", () => { HttpClientError ); - try { - mockFetch.mockResolvedValueOnce( - createJsonResponse({ error: "fail" }, 500) - ); - await client.get("/fail", { schema }); - } catch (err) { - expect(err).toBeInstanceOf(HttpClientError); - expect((err as HttpClientError).status).toBe(500); - expect((err as HttpClientError).body).toEqual({ error: "fail" }); - } + mockFetch.mockResolvedValueOnce( + createJsonResponse({ error: "fail" }, 500) + ); + const failure = client.get("/fail", { schema }); + await expect(failure).rejects.toBeInstanceOf(HttpClientError); + await expect(failure).rejects.toHaveProperty("status", 500); + await expect(failure).rejects.toHaveProperty("body", { error: "fail" }); }); it("preserves non-JSON error bodies in HttpClientError", async () => { @@ -183,13 +180,13 @@ describe("HttpClient", () => { }) ); - try { - await client.get("/fail", { schema }); - } catch (err) { - expect(err).toBeInstanceOf(HttpClientError); - expect((err as HttpClientError).status).toBe(502); - expect((err as HttpClientError).body).toBe("bad gateway"); - } + const failure = client.get("/fail", { schema }); + await expect(failure).rejects.toBeInstanceOf(HttpClientError); + await expect(failure).rejects.toHaveProperty("status", 502); + await expect(failure).rejects.toHaveProperty( + "body", + "bad gateway" + ); }); it("handles empty error bodies without throwing SyntaxError", async () => { @@ -199,13 +196,10 @@ describe("HttpClient", () => { }) ); - try { - await client.get("/unavailable", { schema }); - } catch (err) { - expect(err).toBeInstanceOf(HttpClientError); - expect((err as HttpClientError).status).toBe(503); - expect((err as HttpClientError).body).toBeNull(); - } + const failure = client.get("/unavailable", { schema }); + await expect(failure).rejects.toBeInstanceOf(HttpClientError); + await expect(failure).rejects.toHaveProperty("status", 503); + await expect(failure).rejects.toHaveProperty("body", null); }); it("throws on schema validation failure", async () => { diff --git a/packages/http-client/src/http-client.ts b/packages/http-client/src/http-client.ts index 7b16b5d..f8b822e 100644 --- a/packages/http-client/src/http-client.ts +++ b/packages/http-client/src/http-client.ts @@ -78,7 +78,7 @@ class HttpClient { status: response.status, }); - return options.schema.parse(body) as z.infer; + return options.schema.parse(body); } private async parseResponseBody(response: Response): Promise { diff --git a/packages/http-client/vitest.config.ts b/packages/http-client/vitest.config.ts index ab8f85c..e2ec332 100644 --- a/packages/http-client/vitest.config.ts +++ b/packages/http-client/vitest.config.ts @@ -1,4 +1,3 @@ -/* eslint-disable import/no-default-export */ import { defineConfig } from "vitest/config"; export default defineConfig({ diff --git a/packages/logger/eslint.config.ts b/packages/logger/eslint.config.ts deleted file mode 100644 index f57754c..0000000 --- a/packages/logger/eslint.config.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* eslint-disable import/no-default-export */ -import { baseConfig } from "@repo/eslint/base"; -import { defineConfig } from "eslint/config"; - -export default defineConfig(baseConfig); diff --git a/packages/logger/package.json b/packages/logger/package.json index d364a7a..fe1247b 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -8,19 +8,15 @@ "./types": "./src/types.ts" }, "scripts": { - "typecheck": "tsc --noEmit", - "lint": "eslint .", + "typecheck": "tsc", "test": "vitest run --passWithNoTests", - "format": "prettier --check . --ignore-path ../../.gitignore" + "clean": "rm -rf .turbo node_modules .cache" }, "devDependencies": { - "@repo/eslint": "workspace:*", "@repo/prettier": "workspace:*", "@repo/typescript": "workspace:*", - "eslint": "9.39.1", - "prettier": "3.8.2", - "typescript": "6.0.2", - "vitest": "4.1.4" + "typescript": "catalog:", + "vitest": "catalog:" }, "prettier": "@repo/prettier" } diff --git a/packages/logger/src/__tests__/logger.test.ts b/packages/logger/src/logger.test.ts similarity index 97% rename from packages/logger/src/__tests__/logger.test.ts rename to packages/logger/src/logger.test.ts index c89a0e9..3e8f0a8 100644 --- a/packages/logger/src/__tests__/logger.test.ts +++ b/packages/logger/src/logger.test.ts @@ -1,12 +1,12 @@ import type { MockInstance } from "vitest"; -import { Logger } from "../logger"; -import type { LogEntry } from "../types"; +import { Logger } from "./logger"; +import type { LogEntry } from "./types"; type ConsoleSpy = MockInstance<(...args: unknown[]) => void>; const parseLogEntry = (spy: ConsoleSpy): LogEntry => { - const firstCall = spy.mock.calls[0] as unknown[] | undefined; + const firstCall = spy.mock.calls[0]; expect(firstCall).toBeDefined(); return JSON.parse(String(firstCall?.[0])) as LogEntry; }; diff --git a/packages/logger/src/logger.ts b/packages/logger/src/logger.ts index ecabeb8..7299521 100644 --- a/packages/logger/src/logger.ts +++ b/packages/logger/src/logger.ts @@ -59,9 +59,8 @@ export class Logger { this.log("error", message, metadata); }; - private readonly isEnabled = (level: LogLevel): boolean => { - return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[this.minLevel]; - }; + private readonly isEnabled = (level: LogLevel): boolean => + LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[this.minLevel]; private readonly log = ( level: LogLevel, diff --git a/packages/logger/src/types.ts b/packages/logger/src/types.ts index 568ccf1..957b498 100644 --- a/packages/logger/src/types.ts +++ b/packages/logger/src/types.ts @@ -1,18 +1,20 @@ -export type LogLevel = "debug" | "info" | "warn" | "error"; +type LogLevel = "debug" | "info" | "warn" | "error"; -export type LoggerConfig = { +type LoggerConfig = { /** Namespace or context label included in every log entry. */ context: string; /** Minimum log level. Messages below this level are suppressed. Defaults to "debug". */ level?: LogLevel; }; -export type LogMetadata = Record; +type LogMetadata = Record; -export type LogEntry = { +type LogEntry = { timestamp: string; level: LogLevel; context: string; message: string; [key: string]: unknown; }; + +export type { LogEntry, LoggerConfig, LogLevel, LogMetadata }; diff --git a/packages/logger/vitest.config.ts b/packages/logger/vitest.config.ts index ab8f85c..e2ec332 100644 --- a/packages/logger/vitest.config.ts +++ b/packages/logger/vitest.config.ts @@ -1,4 +1,3 @@ -/* eslint-disable import/no-default-export */ import { defineConfig } from "vitest/config"; export default defineConfig({ diff --git a/packages/telegram/eslint.config.ts b/packages/telegram/eslint.config.ts deleted file mode 100644 index f57754c..0000000 --- a/packages/telegram/eslint.config.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* eslint-disable import/no-default-export */ -import { baseConfig } from "@repo/eslint/base"; -import { defineConfig } from "eslint/config"; - -export default defineConfig(baseConfig); diff --git a/packages/telegram/package.json b/packages/telegram/package.json index efaf8dc..9887827 100644 --- a/packages/telegram/package.json +++ b/packages/telegram/package.json @@ -8,24 +8,20 @@ "./types": "./src/types.ts" }, "scripts": { - "typecheck": "tsc --noEmit", - "lint": "eslint .", + "typecheck": "tsc", "test": "vitest run --passWithNoTests", - "format": "prettier --check . --ignore-path ../../.gitignore" + "clean": "rm -rf .turbo node_modules .cache" }, "dependencies": { "@repo/http-client": "workspace:*", "@repo/logger": "workspace:*", - "zod": "4.3.6" + "zod": "catalog:" }, "devDependencies": { - "@repo/eslint": "workspace:*", "@repo/prettier": "workspace:*", "@repo/typescript": "workspace:*", - "eslint": "9.39.1", - "prettier": "3.8.2", - "typescript": "6.0.2", - "vitest": "4.1.4" + "typescript": "catalog:", + "vitest": "catalog:" }, "prettier": "@repo/prettier" } diff --git a/packages/telegram/src/telegram.test.ts b/packages/telegram/src/telegram.test.ts index 57bed11..80148d2 100644 --- a/packages/telegram/src/telegram.test.ts +++ b/packages/telegram/src/telegram.test.ts @@ -1,5 +1,6 @@ import type { Logger } from "@repo/logger"; -import { describe, it, expect, vi, beforeEach } from "vitest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + import { TelegramService } from "./telegram"; const mockFetch = vi.fn(); diff --git a/packages/telegram/src/types.ts b/packages/telegram/src/types.ts index cf28f6f..e3fb37a 100644 --- a/packages/telegram/src/types.ts +++ b/packages/telegram/src/types.ts @@ -33,6 +33,15 @@ const telegramApiResponseSchema = z }) .loose(); +type SendMessageParams = z.infer; +type AnswerCallbackQueryParams = z.infer< + typeof answerCallbackQueryParamsSchema +>; +type EditMessageReplyMarkupParams = z.infer< + typeof editMessageReplyMarkupParamsSchema +>; +type InlineKeyboardMarkup = z.infer; + export { answerCallbackQueryParamsSchema, editMessageReplyMarkupParamsSchema, @@ -41,11 +50,9 @@ export { telegramApiResponseSchema, }; -export type SendMessageParams = z.infer; -export type AnswerCallbackQueryParams = z.infer< - typeof answerCallbackQueryParamsSchema ->; -export type EditMessageReplyMarkupParams = z.infer< - typeof editMessageReplyMarkupParamsSchema ->; -export type InlineKeyboardMarkup = z.infer; +export type { + AnswerCallbackQueryParams, + EditMessageReplyMarkupParams, + InlineKeyboardMarkup, + SendMessageParams, +}; diff --git a/packages/telegram/vitest.config.ts b/packages/telegram/vitest.config.ts index ab8f85c..e2ec332 100644 --- a/packages/telegram/vitest.config.ts +++ b/packages/telegram/vitest.config.ts @@ -1,4 +1,3 @@ -/* eslint-disable import/no-default-export */ import { defineConfig } from "vitest/config"; export default defineConfig({ diff --git a/packages/url-validator/eslint.config.ts b/packages/url-validator/eslint.config.ts deleted file mode 100644 index f57754c..0000000 --- a/packages/url-validator/eslint.config.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* eslint-disable import/no-default-export */ -import { baseConfig } from "@repo/eslint/base"; -import { defineConfig } from "eslint/config"; - -export default defineConfig(baseConfig); diff --git a/packages/url-validator/package.json b/packages/url-validator/package.json index a0ac3cc..13d0c1d 100644 --- a/packages/url-validator/package.json +++ b/packages/url-validator/package.json @@ -7,19 +7,15 @@ ".": "./src/url-validator.ts" }, "scripts": { - "typecheck": "tsc --noEmit", - "lint": "eslint .", + "typecheck": "tsc", "test": "vitest run --passWithNoTests", - "format": "prettier --check . --ignore-path ../../.gitignore" + "clean": "rm -rf .turbo node_modules .cache" }, "devDependencies": { - "@repo/eslint": "workspace:*", "@repo/prettier": "workspace:*", "@repo/typescript": "workspace:*", - "eslint": "9.39.1", - "prettier": "3.8.2", - "typescript": "6.0.2", - "vitest": "4.1.4" + "typescript": "catalog:", + "vitest": "catalog:" }, "prettier": "@repo/prettier" } diff --git a/packages/url-validator/src/url-validator.test.ts b/packages/url-validator/src/url-validator.test.ts index a8eb001..7e4249a 100644 --- a/packages/url-validator/src/url-validator.test.ts +++ b/packages/url-validator/src/url-validator.test.ts @@ -52,7 +52,7 @@ describe("validateSourceUrl", () => { }); it("rejects URLs over 2048 chars", () => { - const result = validateSourceUrl("https://example.com/" + "a".repeat(2048)); + const result = validateSourceUrl(`https://example.com/${"a".repeat(2048)}`); expect(result).toEqual({ valid: false, reason: "URL exceeds 2048 character limit", diff --git a/packages/url-validator/vitest.config.ts b/packages/url-validator/vitest.config.ts index ab8f85c..e2ec332 100644 --- a/packages/url-validator/vitest.config.ts +++ b/packages/url-validator/vitest.config.ts @@ -1,4 +1,3 @@ -/* eslint-disable import/no-default-export */ import { defineConfig } from "vitest/config"; export default defineConfig({ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b74d776..d7e9629 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,35 +1,229 @@ +--- +lockfileVersion: '9.0' + +importers: + + .: + configDependencies: {} + packageManagerDependencies: + pnpm: + specifier: 12.6.0 + version: 12.6.0 + +packages: + + '@pnpm/exe.android-arm64@12.6.0': + resolution: {integrity: sha512-kviIHft9h02q+7N2In7tSL9T/HRUSGwYmU74YPtsWU5ee7vgqPyC0JT6RRX6miW+NxX1CvHgQxg69qq1qTLJYQ==} + cpu: [arm64] + os: [android] + + '@pnpm/exe.android-x64@12.6.0': + resolution: {integrity: sha512-CT8aJKLq2mtZFE71pr4E5Z2xHL8uGnRGr+NclDqsXlVF4SVcQ2QAs14mWi0C8gjT4hbmi+Q0lJRRIkMAWfjjng==} + cpu: [x64] + os: [android] + + '@pnpm/exe.darwin-arm64@12.6.0': + resolution: {integrity: sha512-rafpVkjzugKBMxSvGQd5wK1x5eOTkD/+lcJIFHeQhvd+BJ8kDisOOvwhQDrpGd4vd2Fx+hhW0P2Ptt638OlhFg==} + cpu: [arm64] + os: [darwin] + + '@pnpm/exe.darwin-x64@12.6.0': + resolution: {integrity: sha512-72Jpuv1m24gI8zUNcaylYpBWEhBzz1VPhnDIU2GYM2eRP+KsOqVLwV78i+JrihI39zY2NNXmaN4eAKX1inpaDQ==} + cpu: [x64] + os: [darwin] + + '@pnpm/exe.freebsd-x64@12.6.0': + resolution: {integrity: sha512-LON4QgNy1w/XF4uySZIyC/5hEB0oGdelXsY9tSKqstER+2j2X2oHz67skD11RRd/HWFRtMr64shQZ4I9ZoZ0zg==} + cpu: [x64] + os: [freebsd] + + '@pnpm/exe.linux-arm64-musl@12.6.0': + resolution: {integrity: sha512-BdDpX+DeaMUc5x6WNcE6FkmFy36NwrBbBvwsZU737Zt3u1fTY+iKFPchjEQNCM4rT6q6xvF2oN3ZwExGsJCkxQ==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@pnpm/exe.linux-arm64@12.6.0': + resolution: {integrity: sha512-8h2sNoIhHDpHDYqZZCh9PXr6krqW10btb0GHVGuw710muDHlFAHMtUG2ogmaHge6zJG1xgY4Y7mwX5g/LWvrYg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-ppc64@12.6.0': + resolution: {integrity: sha512-ld6xgcEhFCFrsVsJhbmdmR714PnsEspO/Ij8w0/D7OSiz2hYGu3fZd5tKRxOI5m6H6Gl0/RRFAPncTHbsgXVjQ==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-riscv64@12.6.0': + resolution: {integrity: sha512-HIbImydoC3J8NFt+8MXfhTCf5rO6NjCMyinSAHac0VmV8uCHw+kbQkyAzxV84rzz9ZOzmBOnj0b1N461UIYGsw==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-s390x@12.6.0': + resolution: {integrity: sha512-DjSqT7+BZ/lWcDHwNMuzsRVfyxUimh+D54Xw4tSWbdVVQhtWtNMUyI93LVnwCmsoEpar2gC3SFeAgywx37svKQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-x64-musl@12.6.0': + resolution: {integrity: sha512-31lKeGPmRE6xfV6I3VjaEGVFRei14pl/zgoFDK4w+UkbJqOuNl2Ht3O1GuqNIg3Gl14qTEb4kZRAYFHXifDx9A==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@pnpm/exe.linux-x64@12.6.0': + resolution: {integrity: sha512-qFWBneHJAJ73W4whtbaFOL1M/7DBC6ILHXuxc7ZPtEhfPuT1zeZiGrmKHoMAfJA+mcm6xhOFljqVTUS+00Jabw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.win32-arm64@12.6.0': + resolution: {integrity: sha512-OhfefXEEykZlslSUhR8PPRe6MVPV3Oink/dVfIecE5velpc7j80cZYsHlaDrtRDcm3TPwQHkaRW1SJ1efX9oFg==} + cpu: [arm64] + os: [win32] + + '@pnpm/exe.win32-x64@12.6.0': + resolution: {integrity: sha512-L2tuyrD2+Imgxs3VK/ST/2L3xD6vDP0ktJQxM0TaGw7TbAQEMq+RMc8iK6Ew7Id579uzjJe8jSfJID2ZGsOHCA==} + cpu: [x64] + os: [win32] + + pnpm@12.6.0: + resolution: {integrity: sha512-PvaPlRyxEawgS0paFvCy3fDaVqluBBPoHYVdnwtV75JnFHCQKOHNAMQFwsX7e56OxNxGd3yAXQNzwvL/AP0g7A==} + engines: {node: '>=18.*'} + hasBin: true + +snapshots: + + '@pnpm/exe.android-arm64@12.6.0': + optional: true + + '@pnpm/exe.android-x64@12.6.0': + optional: true + + '@pnpm/exe.darwin-arm64@12.6.0': + optional: true + + '@pnpm/exe.darwin-x64@12.6.0': + optional: true + + '@pnpm/exe.freebsd-x64@12.6.0': + optional: true + + '@pnpm/exe.linux-arm64-musl@12.6.0': + optional: true + + '@pnpm/exe.linux-arm64@12.6.0': + optional: true + + '@pnpm/exe.linux-ppc64@12.6.0': + optional: true + + '@pnpm/exe.linux-riscv64@12.6.0': + optional: true + + '@pnpm/exe.linux-s390x@12.6.0': + optional: true + + '@pnpm/exe.linux-x64-musl@12.6.0': + optional: true + + '@pnpm/exe.linux-x64@12.6.0': + optional: true + + '@pnpm/exe.win32-arm64@12.6.0': + optional: true + + '@pnpm/exe.win32-x64@12.6.0': + optional: true + + pnpm@12.6.0: + optionalDependencies: + '@pnpm/exe.android-arm64': 12.6.0 + '@pnpm/exe.android-x64': 12.6.0 + '@pnpm/exe.darwin-arm64': 12.6.0 + '@pnpm/exe.darwin-x64': 12.6.0 + '@pnpm/exe.freebsd-x64': 12.6.0 + '@pnpm/exe.linux-arm64': 12.6.0 + '@pnpm/exe.linux-arm64-musl': 12.6.0 + '@pnpm/exe.linux-ppc64': 12.6.0 + '@pnpm/exe.linux-riscv64': 12.6.0 + '@pnpm/exe.linux-s390x': 12.6.0 + '@pnpm/exe.linux-x64': 12.6.0 + '@pnpm/exe.linux-x64-musl': 12.6.0 + '@pnpm/exe.win32-arm64': 12.6.0 + '@pnpm/exe.win32-x64': 12.6.0 + +--- lockfileVersion: '9.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false +catalogs: + default: + '@cloudflare/workers-types': + specifier: 5.20260925.1 + version: 5.20260925.1 + '@ianvs/prettier-plugin-sort-imports': + specifier: 4.7.1 + version: 4.7.1 + '@types/node': + specifier: 24.13.6 + version: 24.13.6 + prettier: + specifier: 3.9.9 + version: 3.9.9 + typescript: + specifier: 7.0.2 + version: 7.0.2 + vitest: + specifier: 5.0.1 + version: 5.0.1 + wrangler: + specifier: 4.140.0 + version: 4.140.0 + zod: + specifier: 4.6.5 + version: 4.6.5 + overrides: esbuild: '>=0.28.1' - undici: '>=7.28.0' - ws: '>=8.21.0' - js-yaml: '>=4.2.0' - '@babel/core': '>=7.29.6' + undici: ^7.29.1 importers: .: devDependencies: '@ianvs/prettier-plugin-sort-imports': - specifier: 4.7.1 - version: 4.7.1(prettier@3.8.2) + specifier: 'catalog:' + version: 4.7.1(prettier@3.9.9)(supports-color@10.2.2) '@repo/prettier': specifier: workspace:* version: link:tooling/prettier + '@types/node': + specifier: 'catalog:' + version: 24.13.6 + knip: + specifier: 6.38.0 + version: 6.38.0 + oxlint: + specifier: 1.85.0 + version: 1.85.0(oxlint-tsgolint@7.0.2003) + oxlint-tsgolint: + specifier: 7.0.2003 + version: 7.0.2003 prettier: - specifier: 3.8.2 - version: 3.8.2 - prettier-plugin-tailwindcss: - specifier: 0.7.2 - version: 0.7.2(@ianvs/prettier-plugin-sort-imports@4.7.1(prettier@3.8.2))(prettier@3.8.2) - vite: - specifier: 8.0.16 - version: 8.0.16(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.21.0) + specifier: 'catalog:' + version: 3.9.9 + turbo: + specifier: 2.11.4 + version: 2.11.4 + typescript: + specifier: 'catalog:' + version: 7.0.2 apps/alerts: dependencies: @@ -43,15 +237,12 @@ importers: specifier: workspace:* version: link:../../packages/telegram zod: - specifier: 4.3.6 - version: 4.3.6 + specifier: 'catalog:' + version: 4.6.5 devDependencies: '@cloudflare/workers-types': - specifier: 4.20260412.1 - version: 4.20260412.1 - '@repo/eslint': - specifier: workspace:* - version: link:../../tooling/eslint + specifier: 'catalog:' + version: 5.20260925.1 '@repo/prettier': specifier: workspace:* version: link:../../tooling/prettier @@ -59,29 +250,23 @@ importers: specifier: workspace:* version: link:../../tooling/typescript '@types/node': - specifier: 25.6.0 - version: 25.6.0 - eslint: - specifier: 9.39.1 - version: 9.39.1(jiti@2.6.1) - prettier: - specifier: 3.8.2 - version: 3.8.2 + specifier: 'catalog:' + version: 24.13.6 typescript: - specifier: 6.0.2 - version: 6.0.2 + specifier: 'catalog:' + version: 7.0.2 vitest: - specifier: 4.1.4 - version: 4.1.4(@types/node@25.6.0)(vite@8.0.16(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.21.0)) + specifier: 'catalog:' + version: 5.0.1(@types/node@24.13.6)(vite@8.3.1(@types/node@24.13.6)) wrangler: - specifier: 4.81.1 - version: 4.81.1(@cloudflare/workers-types@4.20260412.1) + specifier: 'catalog:' + version: 4.140.0(@cloudflare/workers-types@5.20260925.1)(@types/node@24.13.6) apps/browser-scraper: dependencies: '@cloudflare/playwright': - specifier: 1.2.0 - version: 1.2.0 + specifier: 1.3.6 + version: 1.3.6 '@repo/logger': specifier: workspace:* version: link:../../packages/logger @@ -89,15 +274,12 @@ importers: specifier: workspace:* version: link:../../packages/url-validator zod: - specifier: 4.3.6 - version: 4.3.6 + specifier: 'catalog:' + version: 4.6.5 devDependencies: '@cloudflare/workers-types': - specifier: 4.20260412.1 - version: 4.20260412.1 - '@repo/eslint': - specifier: workspace:* - version: link:../../tooling/eslint + specifier: 'catalog:' + version: 5.20260925.1 '@repo/prettier': specifier: workspace:* version: link:../../tooling/prettier @@ -105,29 +287,23 @@ importers: specifier: workspace:* version: link:../../tooling/typescript '@types/node': - specifier: 25.6.0 - version: 25.6.0 - eslint: - specifier: 9.39.1 - version: 9.39.1(jiti@2.6.1) - prettier: - specifier: 3.8.2 - version: 3.8.2 + specifier: 'catalog:' + version: 24.13.6 typescript: - specifier: 6.0.2 - version: 6.0.2 + specifier: 'catalog:' + version: 7.0.2 vitest: - specifier: 4.1.4 - version: 4.1.4(@types/node@25.6.0)(vite@8.0.16(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.21.0)) + specifier: 'catalog:' + version: 5.0.1(@types/node@24.13.6)(vite@8.3.1(@types/node@24.13.6)) wrangler: - specifier: 4.81.1 - version: 4.81.1(@cloudflare/workers-types@4.20260412.1) + specifier: 'catalog:' + version: 4.140.0(@cloudflare/workers-types@5.20260925.1)(@types/node@24.13.6) apps/operator: dependencies: '@hono/zod-validator': - specifier: 0.7.6 - version: 0.7.6(hono@4.12.27)(zod@4.3.6) + specifier: 0.9.1 + version: 0.9.1(hono@4.13.9)(zod@4.6.5) '@repo/http-client': specifier: workspace:* version: link:../../packages/http-client @@ -141,27 +317,24 @@ importers: specifier: workspace:* version: link:../../packages/url-validator drizzle-orm: - specifier: 0.45.2 - version: 0.45.2(@cloudflare/workers-types@4.20260412.1) + specifier: 0.45.3 + version: 0.45.3(@cloudflare/workers-types@5.20260925.1) hono: - specifier: 4.12.27 - version: 4.12.27 + specifier: 4.13.9 + version: 4.13.9 node-html-markdown: specifier: 2.0.0 version: 2.0.0 openai: - specifier: 6.34.0 - version: 6.34.0(ws@8.21.0)(zod@4.3.6) + specifier: 7.23.0 + version: 7.23.0(undici@7.30.0)(ws@8.21.0)(zod@4.6.5) zod: - specifier: 4.3.6 - version: 4.3.6 + specifier: 'catalog:' + version: 4.6.5 devDependencies: '@cloudflare/workers-types': - specifier: 4.20260412.1 - version: 4.20260412.1 - '@repo/eslint': - specifier: workspace:* - version: link:../../tooling/eslint + specifier: 'catalog:' + version: 5.20260925.1 '@repo/prettier': specifier: workspace:* version: link:../../tooling/prettier @@ -169,29 +342,23 @@ importers: specifier: workspace:* version: link:../../tooling/typescript '@types/node': - specifier: 25.6.0 - version: 25.6.0 + specifier: 'catalog:' + version: 24.13.6 drizzle-kit: - specifier: 0.31.10 - version: 0.31.10 - eslint: - specifier: 9.39.1 - version: 9.39.1(jiti@2.6.1) - prettier: - specifier: 3.8.2 - version: 3.8.2 + specifier: 0.31.11 + version: 0.31.11 tsx: - specifier: 4.21.0 - version: 4.21.0 + specifier: 4.23.15 + version: 4.23.15 typescript: - specifier: 6.0.2 - version: 6.0.2 + specifier: 'catalog:' + version: 7.0.2 vitest: - specifier: 4.1.4 - version: 4.1.4(@types/node@25.6.0)(vite@8.0.16(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.21.0)) + specifier: 'catalog:' + version: 5.0.1(@types/node@24.13.6)(vite@8.3.1(@types/node@24.13.6)) wrangler: - specifier: 4.81.1 - version: 4.81.1(@cloudflare/workers-types@4.20260412.1) + specifier: 'catalog:' + version: 4.140.0(@cloudflare/workers-types@5.20260925.1)(@types/node@24.13.6) packages/alpha-vantage: dependencies: @@ -202,30 +369,21 @@ importers: specifier: workspace:* version: link:../logger zod: - specifier: 4.3.6 - version: 4.3.6 + specifier: 'catalog:' + version: 4.6.5 devDependencies: - '@repo/eslint': - specifier: workspace:* - version: link:../../tooling/eslint '@repo/prettier': specifier: workspace:* version: link:../../tooling/prettier '@repo/typescript': specifier: workspace:* version: link:../../tooling/typescript - eslint: - specifier: 9.39.1 - version: 9.39.1(jiti@2.6.1) - prettier: - specifier: 3.8.2 - version: 3.8.2 typescript: - specifier: 6.0.2 - version: 6.0.2 + specifier: 'catalog:' + version: 7.0.2 vitest: - specifier: 4.1.4 - version: 4.1.4(@types/node@25.6.0)(vite@8.0.16(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.21.0)) + specifier: 'catalog:' + version: 5.0.1(@types/node@24.13.6)(vite@8.3.1(@types/node@24.13.6)) packages/http-client: dependencies: @@ -233,54 +391,36 @@ importers: specifier: workspace:* version: link:../logger zod: - specifier: 4.3.6 - version: 4.3.6 + specifier: 'catalog:' + version: 4.6.5 devDependencies: - '@repo/eslint': - specifier: workspace:* - version: link:../../tooling/eslint '@repo/prettier': specifier: workspace:* version: link:../../tooling/prettier '@repo/typescript': specifier: workspace:* version: link:../../tooling/typescript - eslint: - specifier: 9.39.1 - version: 9.39.1(jiti@2.6.1) - prettier: - specifier: 3.8.2 - version: 3.8.2 typescript: - specifier: 6.0.2 - version: 6.0.2 + specifier: 'catalog:' + version: 7.0.2 vitest: - specifier: 4.1.4 - version: 4.1.4(@types/node@25.6.0)(vite@8.0.16(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.21.0)) + specifier: 'catalog:' + version: 5.0.1(@types/node@24.13.6)(vite@8.3.1(@types/node@24.13.6)) packages/logger: devDependencies: - '@repo/eslint': - specifier: workspace:* - version: link:../../tooling/eslint '@repo/prettier': specifier: workspace:* version: link:../../tooling/prettier '@repo/typescript': specifier: workspace:* version: link:../../tooling/typescript - eslint: - specifier: 9.39.1 - version: 9.39.1(jiti@2.6.1) - prettier: - specifier: 3.8.2 - version: 3.8.2 typescript: - specifier: 6.0.2 - version: 6.0.2 + specifier: 'catalog:' + version: 7.0.2 vitest: - specifier: 4.1.4 - version: 4.1.4(@types/node@25.6.0)(vite@8.0.16(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.21.0)) + specifier: 'catalog:' + version: 5.0.1(@types/node@24.13.6)(vite@8.3.1(@types/node@24.13.6)) packages/telegram: dependencies: @@ -291,265 +431,151 @@ importers: specifier: workspace:* version: link:../logger zod: - specifier: 4.3.6 - version: 4.3.6 + specifier: 'catalog:' + version: 4.6.5 devDependencies: - '@repo/eslint': - specifier: workspace:* - version: link:../../tooling/eslint '@repo/prettier': specifier: workspace:* version: link:../../tooling/prettier '@repo/typescript': specifier: workspace:* version: link:../../tooling/typescript - eslint: - specifier: 9.39.1 - version: 9.39.1(jiti@2.6.1) - prettier: - specifier: 3.8.2 - version: 3.8.2 typescript: - specifier: 6.0.2 - version: 6.0.2 + specifier: 'catalog:' + version: 7.0.2 vitest: - specifier: 4.1.4 - version: 4.1.4(@types/node@25.6.0)(vite@8.0.16(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.21.0)) + specifier: 'catalog:' + version: 5.0.1(@types/node@24.13.6)(vite@8.3.1(@types/node@24.13.6)) packages/url-validator: devDependencies: - '@repo/eslint': - specifier: workspace:* - version: link:../../tooling/eslint '@repo/prettier': specifier: workspace:* version: link:../../tooling/prettier '@repo/typescript': specifier: workspace:* version: link:../../tooling/typescript - eslint: - specifier: 9.39.1 - version: 9.39.1(jiti@2.6.1) - prettier: - specifier: 3.8.2 - version: 3.8.2 typescript: - specifier: 6.0.2 - version: 6.0.2 + specifier: 'catalog:' + version: 7.0.2 vitest: - specifier: 4.1.4 - version: 4.1.4(@types/node@25.6.0)(vite@8.0.16(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.21.0)) + specifier: 'catalog:' + version: 5.0.1(@types/node@24.13.6)(vite@8.3.1(@types/node@24.13.6)) - tooling/eslint: - dependencies: - '@eslint/compat': - specifier: 2.0.5 - version: 2.0.5(eslint@9.39.1(jiti@2.6.1)) - '@eslint/js': - specifier: 9.39.1 - version: 9.39.1 - eslint-plugin-import: - specifier: 2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.58.1(eslint@9.39.1(jiti@2.6.1))(typescript@6.0.2))(eslint@9.39.1(jiti@2.6.1)) - eslint-plugin-react: - specifier: 7.37.5 - version: 7.37.5(eslint@9.39.1(jiti@2.6.1)) - eslint-plugin-react-hooks: - specifier: 7.0.1 - version: 7.0.1(eslint@9.39.1(jiti@2.6.1)) - jiti: - specifier: 2.6.1 - version: 2.6.1 - typescript-eslint: - specifier: 8.58.1 - version: 8.58.1(eslint@9.39.1(jiti@2.6.1))(typescript@6.0.2) - devDependencies: - '@repo/prettier': - specifier: workspace:* - version: link:../prettier - '@repo/typescript': - specifier: workspace:* - version: link:../typescript - '@types/node': - specifier: 25.6.0 - version: 25.6.0 - eslint: - specifier: 9.39.1 - version: 9.39.1(jiti@2.6.1) - prettier: - specifier: 3.8.2 - version: 3.8.2 - typescript: - specifier: 6.0.2 - version: 6.0.2 + tooling/github: {} tooling/prettier: devDependencies: '@ianvs/prettier-plugin-sort-imports': - specifier: 4.7.1 - version: 4.7.1(prettier@3.8.2) + specifier: 'catalog:' + version: 4.7.1(prettier@3.9.9)(supports-color@10.2.2) '@repo/typescript': specifier: workspace:* version: link:../typescript '@types/node': - specifier: 25.6.0 - version: 25.6.0 + specifier: 'catalog:' + version: 24.13.6 prettier: - specifier: 3.8.2 - version: 3.8.2 - prettier-plugin-tailwindcss: - specifier: 0.7.2 - version: 0.7.2(@ianvs/prettier-plugin-sort-imports@4.7.1(prettier@3.8.2))(prettier@3.8.2) + specifier: 'catalog:' + version: 3.9.9 typescript: - specifier: 6.0.2 - version: 6.0.2 + specifier: 'catalog:' + version: 7.0.2 tooling/typescript: {} packages: - '@babel/code-frame@7.29.0': - resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} - '@babel/code-frame@8.0.0': - resolution: {integrity: sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw==} - engines: {node: ^22.18.0 || >=24.11.0} - - '@babel/compat-data@8.0.0': - resolution: {integrity: sha512-DOjnob/cXOUgDOozCDeq/aK2p5y8dUIVdf6tNhEV1HQRd6I8aQ4f4fbtHRVEvb6lP3BGomrKHiS8ICAASSVQSw==} - engines: {node: ^22.18.0 || >=24.11.0} - - '@babel/core@8.0.1': - resolution: {integrity: sha512-5FgxM4dLQpMJHSiVATk8foW263dVHQHBVpXYiimNECVWG01f4nFyEbQixeT6Mwvg7TayREJ2gpKl3o2RoMdnqw==} - engines: {node: ^22.18.0 || >=24.11.0} - - '@babel/generator@7.29.1': - resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + '@babel/generator@7.29.8': + resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} engines: {node: '>=6.9.0'} - '@babel/generator@8.0.0': - resolution: {integrity: sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==} - engines: {node: ^22.18.0 || >=24.11.0} - - '@babel/helper-compilation-targets@8.0.0': - resolution: {integrity: sha512-JwculLABZvyPvyLBpwU/E/IbH2uM3mnxNtIJpxnIfb24y1PrdVxK5Dqjle4DpgqpGRnwgC7G8IkzPdSXZrO1Ew==} - engines: {node: ^22.18.0 || >=24.11.0} - - '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} engines: {node: '>=6.9.0'} - '@babel/helper-globals@8.0.0': - resolution: {integrity: sha512-lLozHOM6sWWlxNo8CYqHy4MBZeTvHXNgVPBfPOGsjPKUzHC2Az9QwB6gxdQmpwHl6GlQtbGgS+lj5887guDiLw==} - engines: {node: ^22.18.0 || >=24.11.0} - - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@8.0.0': - resolution: {integrity: sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==} - engines: {node: ^22.18.0 || >=24.11.0} - - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@8.0.2': - resolution: {integrity: sha512-9Fr9QeyCAyi1BR1jKZ6uYQ24EIhQUx5ReHfQU7drOE+TPOb+w11/dsqLkMOT2U29OdCT71XajrOT8xDc1C7orA==} - engines: {node: ^22.18.0 || >=24.11.0} - - '@babel/helper-validator-option@8.0.0': - resolution: {integrity: sha512-U4Dybxh4WESWHt5XhBeExi4DrY0/DNK1aHpQbsrQXCUbFHuMweT0TpLEWKvaraV2Y6fS+ZXunsZ8zIuZIgvF2Q==} - engines: {node: ^22.18.0 || >=24.11.0} - - '@babel/helpers@8.0.0': - resolution: {integrity: sha512-wfbi91pM3py96oIiJEz7qIpyXDytgr9zQC1HEWwlGNVRAEmItuU/0a41ZUKu1sJGyhhOIpc4t5vk4PYzt8wpsg==} - engines: {node: ^22.18.0 || >=24.11.0} - - '@babel/parser@7.29.2': - resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} + '@babel/parser@7.29.9': + resolution: {integrity: sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@8.0.0': - resolution: {integrity: sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ==} - engines: {node: ^22.18.0 || >=24.11.0} - hasBin: true - - '@babel/template@7.28.6': - resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} engines: {node: '>=6.9.0'} - '@babel/template@8.0.0': - resolution: {integrity: sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ==} - engines: {node: ^22.18.0 || >=24.11.0} - - '@babel/traverse@7.29.0': - resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + '@babel/traverse@7.29.8': + resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==} engines: {node: '>=6.9.0'} - '@babel/traverse@8.0.0': - resolution: {integrity: sha512-bxTj/W2VclGE6CctlfQOpxg8MPDzXArRqkOBePw8EHfebcjF7fETWSS3BriEECo+UiU/Yblq+xUtSImFu7cTbw==} - engines: {node: ^22.18.0 || >=24.11.0} - - '@babel/types@7.29.0': - resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} engines: {node: '>=6.9.0'} - '@babel/types@8.0.0': - resolution: {integrity: sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw==} - engines: {node: ^22.18.0 || >=24.11.0} - - '@cloudflare/kv-asset-handler@0.4.2': - resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==} - engines: {node: '>=18.0.0'} + '@cloudflare/kv-asset-handler@0.5.0': + resolution: {integrity: sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==} + engines: {node: '>=22.0.0'} - '@cloudflare/playwright@1.2.0': - resolution: {integrity: sha512-+LQo135uWxf0M2nG4T05T0FNH9Qhif7Uq2AhT79hX/FuoTLORX+OmOshl+cObTderrRQ4wkq1AXrUBq3w2eubg==} + '@cloudflare/playwright@1.3.6': + resolution: {integrity: sha512-pwNuh5/eUTcVVhXComAWfS9Im0mqBBVoU73dBXPv5cbUyOIuPGxd3bD2GrTgMOe6jJEK4ZHgQBYctYEwkzPH8w==} + peerDependencies: + playwright-core: '*' + peerDependenciesMeta: + playwright-core: + optional: true - '@cloudflare/unenv-preset@2.16.0': - resolution: {integrity: sha512-8ovsRpwzPoEqPUzoErAYVv8l3FMZNeBVQfJTvtzP4AgLSRGZISRfuChFxHWUQd3n6cnrwkuTGxT+2cGo8EsyYg==} + '@cloudflare/unenv-preset@2.16.2': + resolution: {integrity: sha512-JBP1+Z7ZSNG/d4mRP+y8VC5dka3tZVMLEZRvS+rzQ4DGV1EoxRFQckcJTTkXbHSQiTj0DtNI01Zwb/V2fX0mvQ==} peerDependencies: unenv: 2.0.0-rc.24 - workerd: 1.20260301.1 || ~1.20260302.1 || ~1.20260303.1 || ~1.20260304.1 || >1.20260305.0 <2.0.0-0 + workerd: '>1.20260305.0 <2.0.0-0' peerDependenciesMeta: workerd: optional: true - '@cloudflare/workerd-darwin-64@1.20260409.1': - resolution: {integrity: sha512-h/bkaC0HJL63aqAGnV0oagqpBiTSstabODThkeMSbG8kctl0Jb4jlq1pNHJPmYGazFNtfyagrUZFb6HN22GX7w==} + '@cloudflare/workerd-darwin-64@1.20260923.1': + resolution: {integrity: sha512-sFMyHjABspfVxAf9bmcRdYWm3rO5Vrmhb3fLboMB5Lt+Eo+X1Fj4n/3hgMYmXb6P+zW+cjWjZdH1ilE+Ad5kDw==} engines: {node: '>=16'} cpu: [x64] os: [darwin] - '@cloudflare/workerd-darwin-arm64@1.20260409.1': - resolution: {integrity: sha512-HTAC+B9uSYcm+GjN3UYJjuun19GqYtK1bAFJ0KECXyfsgIDwH1MTzxbTxzJpZUbWLw8s0jcwCU06MWZj6cgnxQ==} + '@cloudflare/workerd-darwin-arm64@1.20260923.1': + resolution: {integrity: sha512-t/O1jcuag+JO5VnFblf20B960FU/WSmn/n2Lcy021cBUhWnhApO9yx1VjJ7I4KhS09yXLgC2shHTzB9EnbSQpg==} engines: {node: '>=16'} cpu: [arm64] os: [darwin] - '@cloudflare/workerd-linux-64@1.20260409.1': - resolution: {integrity: sha512-QIoNq5cgmn1ko8qlngmgZLXQr2KglrjvIwVFOyJI3rbIpt8631n/YMzHPiOWgt38Cb6tcni8fXOzkcvIX2lBDg==} + '@cloudflare/workerd-linux-64@1.20260923.1': + resolution: {integrity: sha512-6X9fp5vFBJG0BXrjOcB4yqyqZRtZ/Jx4KRAmEp5FPxuNUbf3n6ogzZvD2s0YS+DC2zNKb0gd1lIiOye7AaGnvA==} engines: {node: '>=16'} cpu: [x64] os: [linux] - '@cloudflare/workerd-linux-arm64@1.20260409.1': - resolution: {integrity: sha512-HJGBMTfPDb0GCjwdxWFx63wS20TYDVmtOuA5KVri/CiFnit71y++kmseVmemjsgLFFIzoEAuFG/xUh1FJLo6tg==} + '@cloudflare/workerd-linux-arm64@1.20260923.1': + resolution: {integrity: sha512-esOXfxhDp2YPjZmMrO3OLa/9qAfa/uXQGwFGDyUWNF3aXkwE8fc6oauZS01UDk8YwuYlxsyjLQIKjJOrzJ2N+g==} engines: {node: '>=16'} cpu: [arm64] os: [linux] - '@cloudflare/workerd-windows-64@1.20260409.1': - resolution: {integrity: sha512-GttFO0+TvE0rJNQbDlxC6kq2Q7uFxoZRo74Z9d/trUrLgA14HEVTTXobYyiWrDZ9Qp2W5KN1CrXQXiko0zE38Q==} + '@cloudflare/workerd-windows-64@1.20260923.1': + resolution: {integrity: sha512-Hlda2apkBcRtPQD4NnEZiEqKLIhR7au+8skF+tBHBa1jsX1/GfuYEdIR0HJ6He1qkSNkxD/Y0VXo+TNgUc1EUg==} engines: {node: '>=16'} cpu: [x64] os: [win32] - '@cloudflare/workers-types@4.20260412.1': - resolution: {integrity: sha512-4jcYPBKH70/XOW40B6X/bEUlh+rjvem8wuvGJXuGebSScFcbJ5TuO5CjX/Nc8Y+RhH3RnTcynHX4tR6Rm0MNgA==} + '@cloudflare/workers-types@5.20260925.1': + resolution: {integrity: sha512-E/Z3J8CzyNk0sinBu8g/1HjMCViSxG1kd/ARL8nzieSEdqtYZmgdnhlXbi95xyc3oetTwBR/r604/EuwuP6cnQ==} '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} @@ -558,25 +584,25 @@ packages: '@drizzle-team/brocli@0.10.2': resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} - '@emnapi/core@1.10.0': - resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + '@emnapi/core@1.11.2': + resolution: {integrity: sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==} - '@emnapi/runtime@1.10.0': - resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + '@emnapi/runtime@1.11.2': + resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==} - '@emnapi/runtime@1.9.2': - resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} + '@emnapi/runtime@1.11.3': + resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} - '@emnapi/wasi-threads@1.2.1': - resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} '@esbuild-kit/core-utils@3.3.2': resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} - deprecated: 'Merged into tsx: https://tsx.is' + deprecated: 'Merged into tsx: https://tsx.hirok.io' '@esbuild-kit/esm-loader@2.6.5': resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} - deprecated: 'Merged into tsx: https://tsx.is' + deprecated: 'Merged into tsx: https://tsx.hirok.io' '@esbuild/aix-ppc64@0.28.1': resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} @@ -584,239 +610,328 @@ packages: cpu: [ppc64] os: [aix] + '@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==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@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==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@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==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@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==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@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==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@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==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@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==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@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==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@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==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@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==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@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==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@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==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@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==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@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==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@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==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@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==} engines: {node: '>=18'} cpu: [x64] os: [linux] + '@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==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] + '@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==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] + '@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==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@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==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + '@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==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] + '@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==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@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==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@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==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@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==} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@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 + '@esbuild/win32-x64@0.28.2': + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] - '@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} + '@hono/zod-validator@0.9.1': + resolution: {integrity: sha512-iiv6w0qrIc0arfvCtUqBWsvl4fXjzaTcQcCJTTtCnkawF9HHGE+KjEl0ox3gQJ6rKZEgE3mLExlQCF72M+mNuw==} + peerDependencies: + hono: '>=4.11.2' + zod: ^3.25.0 || ^4.0.0 - '@eslint/compat@2.0.5': - resolution: {integrity: sha512-IbHDbHJfkVNv6xjlET8AIVo/K1NQt7YT4Rp6ok/clyBGcpRx1l6gv0Rq3vBvYfPJIZt6ODf66Zq08FJNDpnzgg==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@ianvs/prettier-plugin-sort-imports@4.7.1': + resolution: {integrity: sha512-jmTNYGlg95tlsoG3JLCcuC4BrFELJtLirLAkQW/71lXSyOhVt/Xj7xWbbGcuVbNq1gwWgSyMrPjJc9Z30hynVw==} peerDependencies: - eslint: ^8.40 || 9 || 10 + '@prettier/plugin-oxc': ^0.0.4 || ^0.1.0 + '@vue/compiler-sfc': 2.7.x || 3.x + content-tag: ^4.0.0 + prettier: 2 || 3 || ^4.0.0-0 + prettier-plugin-ember-template-tag: ^2.1.0 peerDependenciesMeta: - eslint: - optional: true - - '@eslint/config-array@0.21.2': - resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/config-helpers@0.4.2': - resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/core@0.17.0': - resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/core@1.2.1': - resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - '@eslint/eslintrc@3.3.5': - resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/js@9.39.1': - resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/object-schema@2.1.7': - resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/plugin-kit@0.4.1': - resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@hono/zod-validator@0.7.6': - resolution: {integrity: sha512-Io1B6d011Gj1KknV4rXYz4le5+5EubcWEU/speUjuw9XMMIaP3n78yXLhjd2A3PXaXaUwEAluOiAyLqhBEJgsw==} - peerDependencies: - hono: '>=3.9.0' - zod: ^3.25.0 || ^4.0.0 - - '@humanfs/core@0.19.1': - resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} - engines: {node: '>=18.18.0'} - - '@humanfs/node@0.16.7': - resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} - engines: {node: '>=18.18.0'} - - '@humanwhocodes/module-importer@1.0.1': - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - - '@humanwhocodes/retry@0.4.3': - resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} - engines: {node: '>=18.18'} - - '@ianvs/prettier-plugin-sort-imports@4.7.1': - resolution: {integrity: sha512-jmTNYGlg95tlsoG3JLCcuC4BrFELJtLirLAkQW/71lXSyOhVt/Xj7xWbbGcuVbNq1gwWgSyMrPjJc9Z30hynVw==} - peerDependencies: - '@prettier/plugin-oxc': ^0.0.4 || ^0.1.0 - '@vue/compiler-sfc': 2.7.x || 3.x - content-tag: ^4.0.0 - prettier: 2 || 3 || ^4.0.0-0 - prettier-plugin-ember-template-tag: ^2.1.0 - peerDependenciesMeta: - '@prettier/plugin-oxc': + '@prettier/plugin-oxc': optional: true '@vue/compiler-sfc': optional: true @@ -829,152 +944,161 @@ packages: resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} engines: {node: '>=18'} - '@img/sharp-darwin-arm64@0.34.5': - resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-darwin-arm64@0.35.4': + resolution: {integrity: sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==} + engines: {node: '>=20.9.0'} cpu: [arm64] os: [darwin] - '@img/sharp-darwin-x64@0.34.5': - resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-darwin-x64@0.35.4': + resolution: {integrity: sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==} + engines: {node: '>=20.9.0'} cpu: [x64] os: [darwin] - '@img/sharp-libvips-darwin-arm64@1.2.4': - resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + '@img/sharp-freebsd-wasm32@0.35.4': + resolution: {integrity: sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==} + engines: {node: '>=20.9.0'} + os: [freebsd] + + '@img/sharp-libvips-darwin-arm64@1.3.3': + resolution: {integrity: sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==} cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.2.4': - resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + '@img/sharp-libvips-darwin-x64@1.3.3': + resolution: {integrity: sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==} cpu: [x64] os: [darwin] - '@img/sharp-libvips-linux-arm64@1.2.4': - resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + '@img/sharp-libvips-linux-arm64@1.3.3': + resolution: {integrity: sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==} cpu: [arm64] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-arm@1.2.4': - resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + '@img/sharp-libvips-linux-arm@1.3.3': + resolution: {integrity: sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==} cpu: [arm] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-ppc64@1.2.4': - resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + '@img/sharp-libvips-linux-ppc64@1.3.3': + resolution: {integrity: sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==} cpu: [ppc64] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-riscv64@1.2.4': - resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + '@img/sharp-libvips-linux-riscv64@1.3.3': + resolution: {integrity: sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==} cpu: [riscv64] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-s390x@1.2.4': - resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + '@img/sharp-libvips-linux-s390x@1.3.3': + resolution: {integrity: sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==} cpu: [s390x] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-x64@1.2.4': - resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + '@img/sharp-libvips-linux-x64@1.3.3': + resolution: {integrity: sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==} cpu: [x64] os: [linux] libc: [glibc] - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': - resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + '@img/sharp-libvips-linuxmusl-arm64@1.3.3': + resolution: {integrity: sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==} cpu: [arm64] os: [linux] libc: [musl] - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + '@img/sharp-libvips-linuxmusl-x64@1.3.3': + resolution: {integrity: sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==} cpu: [x64] os: [linux] libc: [musl] - '@img/sharp-linux-arm64@0.34.5': - resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-arm64@0.35.4': + resolution: {integrity: sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==} + engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] libc: [glibc] - '@img/sharp-linux-arm@0.34.5': - resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-arm@0.35.4': + resolution: {integrity: sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==} + engines: {node: '>=20.9.0'} cpu: [arm] os: [linux] libc: [glibc] - '@img/sharp-linux-ppc64@0.34.5': - resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-ppc64@0.35.4': + resolution: {integrity: sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==} + engines: {node: '>=20.9.0'} cpu: [ppc64] os: [linux] libc: [glibc] - '@img/sharp-linux-riscv64@0.34.5': - resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-riscv64@0.35.4': + resolution: {integrity: sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==} + engines: {node: '>=20.9.0'} cpu: [riscv64] os: [linux] libc: [glibc] - '@img/sharp-linux-s390x@0.34.5': - resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-s390x@0.35.4': + resolution: {integrity: sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==} + engines: {node: '>=20.9.0'} cpu: [s390x] os: [linux] libc: [glibc] - '@img/sharp-linux-x64@0.34.5': - resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-x64@0.35.4': + resolution: {integrity: sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==} + engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] libc: [glibc] - '@img/sharp-linuxmusl-arm64@0.34.5': - resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linuxmusl-arm64@0.35.4': + resolution: {integrity: sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==} + engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] libc: [musl] - '@img/sharp-linuxmusl-x64@0.34.5': - resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linuxmusl-x64@0.35.4': + resolution: {integrity: sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==} + engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] libc: [musl] - '@img/sharp-wasm32@0.34.5': - resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-wasm32@0.35.4': + resolution: {integrity: sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==} + engines: {node: '>=20.9.0'} + + '@img/sharp-webcontainers-wasm32@0.35.4': + resolution: {integrity: sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==} + engines: {node: '>=20.9.0'} cpu: [wasm32] - '@img/sharp-win32-arm64@0.34.5': - resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-win32-arm64@0.35.4': + resolution: {integrity: sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==} + engines: {node: '>=20.9.0'} cpu: [arm64] os: [win32] - '@img/sharp-win32-ia32@0.34.5': - resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-win32-ia32@0.35.4': + resolution: {integrity: sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==} + engines: {node: ^20.9.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-x64@0.34.5': - resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-win32-x64@0.35.4': + resolution: {integrity: sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==} + engines: {node: '>=20.9.0'} cpu: [x64] os: [win32] @@ -985,8 +1109,8 @@ packages: resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} @@ -994,422 +1118,718 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - '@napi-rs/wasm-runtime@1.1.6': - resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} + '@napi-rs/wasm-runtime@1.2.4': + resolution: {integrity: sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g==} + engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} peerDependencies: - '@emnapi/core': ^1.7.1 - '@emnapi/runtime': ^1.7.1 - - '@oxc-project/types@0.133.0': - resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} - - '@poppinss/colors@4.1.6': - resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==} + '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.4 + '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.4 - '@poppinss/dumper@0.6.5': - resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==} - - '@poppinss/exception@1.2.3': - resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} + '@oxc-parser/binding-android-arm-eabi@0.150.0': + resolution: {integrity: sha512-oQef2Zu4Prz1KLKznz3HqZzU9uVoA5PMoDZuuLmqms7hKmKSAPzlaMnLllJq3t+rgKmfJJ2siPrpZfFrW06btw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] - '@rolldown/binding-android-arm64@1.0.3': - resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==} + '@oxc-parser/binding-android-arm64@0.150.0': + resolution: {integrity: sha512-B6ofpoFiAUwIZ0MJ2IgHPvZK8FAtL4qzSZRwOkAKIfxEobE1mQC8nDdiFZj7pUaJiVUVCsfkMsBJKedzO8rZvA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.3': - resolution: {integrity: sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==} + '@oxc-parser/binding-darwin-arm64@0.150.0': + resolution: {integrity: sha512-J+9IHKzx/bSz1JetOfD4zKXSK9sOm4/a7+0qomJODcTL6JsRXGeV/ZdkAPSIDydfFmWzYCraMlQEosSZEyBYkQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.3': - resolution: {integrity: sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==} + '@oxc-parser/binding-darwin-x64@0.150.0': + resolution: {integrity: sha512-v6IPfcAcSYrBWXV5Tce1DmxDMXLhEYpIIWiRFPJopgCVscZdLaV4MRQOUxvL3usQlw+yrwYOjBFB9IwBx+jUiQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.3': - resolution: {integrity: sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==} + '@oxc-parser/binding-freebsd-x64@0.150.0': + resolution: {integrity: sha512-AoR/4jD02HET0KO0yNT4nbTE+XJUxiO9jB1X/ycEjUE3WrIJ3IjV/Vf+gskhWLcqqeXfvNnkDtBR7epllAm88A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.3': - resolution: {integrity: sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==} + '@oxc-parser/binding-linux-arm-gnueabihf@0.150.0': + resolution: {integrity: sha512-A/hycCFjLUrCmLoL5O/vBp40ohlaXO1Ta3v5hqYZxicYFs129wZmgZJggcW4mYGYbZebQLhup+N8JjeQl8qwyw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.3': - resolution: {integrity: sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==} + '@oxc-parser/binding-linux-arm-musleabihf@0.150.0': + resolution: {integrity: sha512-pbqahg1Pkz7J4RKmXm30s/iQu99hv64ayXCu8P4p75HcEH5azOdR4eGqiB6lFGkFR3mMpzaYsSKLkS8oJbjmeQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm64-gnu@0.150.0': + resolution: {integrity: sha512-HV11aRbBQGwqv8bEo+K/6qr88uB4fEe1mhXncMhlVCrj+WpBSraxrUzHaPVmeQRU6x6x8v/3DuCbbo93rpp+fw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.3': - resolution: {integrity: sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==} + '@oxc-parser/binding-linux-arm64-musl@0.150.0': + resolution: {integrity: sha512-k6pVkJqALwtEuP4zukVhGRhdIy4+ofChUIUUsAHHyqDZlNsknmel3JjbggrJiWGaes6h/dIcWmEXsKW4SmK9oQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.0.3': - resolution: {integrity: sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==} + '@oxc-parser/binding-linux-ppc64-gnu@0.150.0': + resolution: {integrity: sha512-tEFg39mw/rHO5n8GK2DR4ZMFfsPQZtnQIPbsIpjoQRomJc/2tRfsCSmCT6gNit+NZGoeJG5aH9ATDH5bf0WnoQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.0.3': - resolution: {integrity: sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==} + '@oxc-parser/binding-linux-riscv64-gnu@0.150.0': + resolution: {integrity: sha512-0JO7IFkoek6HqV589Menx3hJySNXi6quRhO4tq2P7kpEHKEXoDATnoPVUpn5B7gDH2KLkdo751N0uIrGJFCTCw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-musl@0.150.0': + resolution: {integrity: sha512-7XPzREnyAS5wHU9aB+49Uaqgoo1gVCklHc3DRlc3fJy2tXD/eAJFN1QFz/crj+Ulup5P1+axNREF+/RGaB/IRA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-s390x-gnu@0.150.0': + resolution: {integrity: sha512-7n7ZxcbRWDFaTdyj8M8p0O9OfzoMKm8O6syBAIgcSutbOALq0Bb9G52Me+XH0anMLZV+NgXc726gqgG2q39vcQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.0.3': - resolution: {integrity: sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==} + '@oxc-parser/binding-linux-x64-gnu@0.150.0': + resolution: {integrity: sha512-Vx0GSA9ZCRTUiczoEQnIIyXkMvtEY8uc6IiwLQtMe5ci2sXPqjrry0Ek5fsPP1k2kCzkML3ow9UOOEgnEDi7Bw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.3': - resolution: {integrity: sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==} + '@oxc-parser/binding-linux-x64-musl@0.150.0': + resolution: {integrity: sha512-ii4/9m3viDLssMnfXU7/Pni/3nYplApuGayceX4qsVGaqqQJCx3tHIH9M/HWIeSty5i8LjS21zPYT6lVIkwLxw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.3': - resolution: {integrity: sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==} + '@oxc-parser/binding-openharmony-arm64@0.150.0': + resolution: {integrity: sha512-ZtoxX5rez36ZWkwtiEhjkCPnlTPoQ2I7lIL0IYZ1yjxMYohbvoOjBmUIZzrf9W/HouONq0omIfTeCWEY+R380w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.3': - resolution: {integrity: sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==} + '@oxc-parser/binding-win32-arm64-msvc@0.150.0': + resolution: {integrity: sha512-VqeRb5JX/bKYBrff7TUDvJyKY0914UzuCkuXIGxJS4FUmvMNfqkCbc7Sq90iMz9DlmAtlggwaBYQ9eg3h3ltiw==} engines: {node: ^20.19.0 || >=22.12.0} - cpu: [wasm32] + cpu: [arm64] + os: [win32] - '@rolldown/binding-win32-arm64-msvc@1.0.3': - resolution: {integrity: sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==} + '@oxc-parser/binding-win32-ia32-msvc@0.150.0': + resolution: {integrity: sha512-EPqJfeZ4Pgg2BJSsCV8GozxV0FDltRzpVtGVa1r2noJu1iu+opOw3gtBWXwIo9odCT/MdFfyHxdy0/CRqs3OqA==} engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] + cpu: [ia32] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.3': - resolution: {integrity: sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==} + '@oxc-parser/binding-win32-x64-msvc@0.150.0': + resolution: {integrity: sha512-n5YMzbqwPQqozbkrexi0lNZrUz5cnPHalYpFWe6Dau7AmKIWNo+y24IwzDuZEQtEdUHuKhXmMMih/MPjOI+CMw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@rolldown/pluginutils@1.0.1': - resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} - - '@rtsao/scc@1.1.0': - resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} - - '@sindresorhus/is@7.2.0': - resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==} - engines: {node: '>=18'} + '@oxc-project/types@0.150.0': + resolution: {integrity: sha512-rDS5/31E9HfPl/CIzGrn0DOlvBbXFseQ5URJ9sYMfstbKLD/c6Gm9vmRzRGDdAXyOIL4zmO37lc9RIwYqVruZw==} - '@speed-highlight/core@1.2.15': - resolution: {integrity: sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw==} + '@oxc-project/types@0.151.0': + resolution: {integrity: sha512-J1yXrIlNDZVzE3ada310xeAw7nH8yCAyLPuUIsjKatFPmfn5bS1oW+cM+QsGOtVWd5nhSpbwZWx/rue+r5Z+PA==} - '@standard-schema/spec@1.1.0': - resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@oxc-resolver/binding-android-arm-eabi@11.24.2': + resolution: {integrity: sha512-y09e0L0SRI2OA2tUIrjBgoV3eH5hvUKXNkJqXmNo5V2WxIjyC7I7aJfRLMEVpA8yi95f90gFDvO0VMgrDw+vwA==} + cpu: [arm] + os: [android] - '@tybys/wasm-util@0.10.3': - resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + '@oxc-resolver/binding-android-arm64@11.24.2': + resolution: {integrity: sha512-cl4icWaZFnLdg8m6qtnh5rBMuGbxc/ptStFHLeCNwr+2cZjkjNwQu/jYRS0CHlnPecOJMpuS5M6/BH+0J/YkEg==} + cpu: [arm64] + os: [android] - '@types/chai@5.2.3': - resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@oxc-resolver/binding-darwin-arm64@11.24.2': + resolution: {integrity: sha512-At29QEMF6HajbQvgY8K6OXnHD1x9rad74xBEfmCB6ZqCGsdq75aK7tOYcTbOanMy8qdIBrfL3SMr3p/lfSlb9w==} + cpu: [arm64] + os: [darwin] - '@types/deep-eql@4.0.2': - resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@oxc-resolver/binding-darwin-x64@11.24.2': + resolution: {integrity: sha512-A5Kqr1EUj4oIL5CF4WRssq/o5P0Y11cwoFouMRmQ7YnC/A8V93nv1nb7aSU8HwcgmXropjLNkVTl4MN87cu28Q==} + cpu: [x64] + os: [darwin] - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@oxc-resolver/binding-freebsd-x64@11.24.2': + resolution: {integrity: sha512-R5xkRBRRz7ceH/P5Jrc6G7FmdUdgpLYyESFAUDVTNQ9K0sGPxcp4ljiwEwEqsvNcQ4sYbMRrWcHHBCu7ksAJVw==} + cpu: [x64] + os: [freebsd] - '@types/gensync@1.0.5': - resolution: {integrity: sha512-MbsRCT7mTikHwKZ0X+LVUTLRrZZRLipTuXEO9qOYO+zmjMVk81axyClMROf6uoPD9MRVu46bx8zoR0Ad9q3NAg==} + '@oxc-resolver/binding-linux-arm-gnueabihf@11.24.2': + resolution: {integrity: sha512-k/RuYL4L/R58IBn3wT5ma3Wh4k62bp1eYCFRWCmMsasUOqL+H6sW0VGFadEzKWXFFlz+2uIMoeMk9ySSZJHgbg==} + cpu: [arm] + os: [linux] - '@types/jsesc@2.5.1': - resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} + '@oxc-resolver/binding-linux-arm-musleabihf@11.24.2': + resolution: {integrity: sha512-bnHAak3ujYfH5pKk4NieFNbvYvernfoQDgwLddbZ3OtMYrem87/qjlA+u+aKG0oZcqSLGCful/6/CEA+aeAgaA==} + cpu: [arm] + os: [linux] - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@oxc-resolver/binding-linux-arm64-gnu@11.24.2': + resolution: {integrity: sha512-vDT3KHgzYp47gmtNOqL2VNhCyl5Zv643eyxm//A68J8DeUGXrvD1pZFiaT4jSfe+RInfnn1R2yVHye4enx6RnA==} + cpu: [arm64] + os: [linux] + libc: [glibc] - '@types/json5@0.0.29': - resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@oxc-resolver/binding-linux-arm64-musl@11.24.2': + resolution: {integrity: sha512-+kMlQvbzfyEYtu5FcjE4p+ttBLpKW4d/AsAsuE69BxV6V4twZJeIQZFfD8gh/wqglY0MkPSezWXQH0jBV13MUw==} + cpu: [arm64] + os: [linux] + libc: [musl] - '@types/node@25.6.0': - resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==} + '@oxc-resolver/binding-linux-ppc64-gnu@11.24.2': + resolution: {integrity: sha512-shjfMhmZ3gq9fv/w7bi3PnZlgOPG+2QAOFf0BJF0EgBSIGZ6PMLN2zbGEblTUYB/NKVDRyYhE2ff3dJ1QqNPkA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] - '@typescript-eslint/eslint-plugin@8.58.1': - resolution: {integrity: sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.58.1 - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' + '@oxc-resolver/binding-linux-riscv64-gnu@11.24.2': + resolution: {integrity: sha512-zGelwFR5oRo+b69k8Lrzun86DyUHzfKN6cnjbR9l7Z7NIRznOE/2ZvPa1IUKqAL2PzAXOdwkfVqNvO1H2RlpAw==} + cpu: [riscv64] + os: [linux] + libc: [glibc] - '@typescript-eslint/parser@8.58.1': - resolution: {integrity: sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==} - 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' + '@oxc-resolver/binding-linux-riscv64-musl@11.24.2': + resolution: {integrity: sha512-qxZ1SWCXJY0eyhAlP6Lmo9F2Nrtx7EkYj9oCgL8apDPCwXwCEDA2U697bbT81JIc2IrVjxO4KX6WU2N+oN9Z4w==} + cpu: [riscv64] + os: [linux] + libc: [musl] - '@typescript-eslint/project-service@8.58.1': - resolution: {integrity: sha512-gfQ8fk6cxhtptek+/8ZIqw8YrRW5048Gug8Ts5IYcMLCw18iUgrZAEY/D7s4hkI0FxEfGakKuPK/XUMPzPxi5g==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.1.0' + '@oxc-resolver/binding-linux-s390x-gnu@11.24.2': + resolution: {integrity: sha512-sGCecF3cx2DFlH4t/z7ApnOnXqN48p5p5mlHDEnHTAukQa2P+qMVE4CwyWE9W+q/m3QJ7kKfGrIjax31f44oFQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] - '@typescript-eslint/scope-manager@8.58.1': - resolution: {integrity: sha512-TPYUEqJK6avLcEjumWsIuTpuYODTTDAtoMdt8ZZa93uWMTX13Nb8L5leSje1NluammvU+oI3QRr5lLXPgihX3w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@oxc-resolver/binding-linux-x64-gnu@11.24.2': + resolution: {integrity: sha512-k/VlMMcSzMlahb3/fENM4rTlsJ0s3fFROA0KXPBmKggqmTSaE383sl8F3KCOXPLmVsYfW6hCitMhXCEtNeZxxg==} + cpu: [x64] + os: [linux] + libc: [glibc] - '@typescript-eslint/tsconfig-utils@8.58.1': - resolution: {integrity: sha512-JAr2hOIct2Q+qk3G+8YFfqkqi7sC86uNryT+2i5HzMa2MPjw4qNFvtjnw1IiA1rP7QhNKVe21mSSLaSjwA1Olw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.1.0' + '@oxc-resolver/binding-linux-x64-musl@11.24.2': + resolution: {integrity: sha512-8hbnZyNi97b/8wapYaIF9+t9GmZKBW2vunaOc3h9HGJptH7b7XpvZqOTBSm/MpTjr7H497BlgOaSfLUdhmy2bw==} + cpu: [x64] + os: [linux] + libc: [musl] - '@typescript-eslint/type-utils@8.58.1': - resolution: {integrity: sha512-HUFxvTJVroT+0rXVJC7eD5zol6ID+Sn5npVPWoFuHGg9Ncq5Q4EYstqR+UOqaNRFXi5TYkpXXkLhoCHe3G0+7w==} - 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' + '@oxc-resolver/binding-openharmony-arm64@11.24.2': + resolution: {integrity: sha512-MvyGik3a6pVgZ0t/kWlbmFxFLmXQJwgLsY2eYFHLpy0wGwRbfzeIGgDwQ3kXqE30z+kSXennRkCrT7TUvkptNg==} + cpu: [arm64] + os: [openharmony] - '@typescript-eslint/types@8.58.1': - resolution: {integrity: sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@oxc-resolver/binding-wasm32-wasi@11.24.2': + resolution: {integrity: sha512-vHcssMPwO08RTvj/c0iOBz90attxyG3wQJ0dTcyEQK43LRpcdLWZlV5feBhv6Isn6ahbQIzHbCgfa81+RiML0Q==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] - '@typescript-eslint/typescript-estree@8.58.1': - resolution: {integrity: sha512-w4w7WR7GHOjqqPnvAYbazq+Y5oS68b9CzasGtnd6jIeOIeKUzYzupGTB2T4LTPSv4d+WPeccbxuneTFHYgAAWg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.1.0' + '@oxc-resolver/binding-win32-arm64-msvc@11.24.2': + resolution: {integrity: sha512-uokJqro2iBqkFvJdKQLP7d8/BUmFwESQFVmIJUQKj1Xn1a/LysJoe1vmeECLF5b3jsV8CAL5sEMJXX6SdK9Nhg==} + cpu: [arm64] + os: [win32] - '@typescript-eslint/utils@8.58.1': - resolution: {integrity: sha512-Ln8R0tmWC7pTtLOzgJzYTXSCjJ9rDNHAqTaVONF4FEi2qwce8mD9iSOxOpLFFvWp/wBFlew0mjM1L1ihYWfBdQ==} - 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' + '@oxc-resolver/binding-win32-x64-msvc@11.24.2': + resolution: {integrity: sha512-UqGPmo56KDfLlfXFAFIrNflHT8tFxWGEivWg3Zeyp4Uy2NlKN1FGPr6/BxcLGG3+kZ6Wp14g5Uj+n71boqZfiw==} + cpu: [x64] + os: [win32] - '@typescript-eslint/visitor-keys@8.58.1': - resolution: {integrity: sha512-y+vH7QE8ycjoa0bWciFg7OpFcipUuem1ujhrdLtq1gByKwfbC7bPeKsiny9e0urg93DqwGcHey+bGRKCnF1nZQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@oxlint-tsgolint/darwin-arm64@7.0.2003': + resolution: {integrity: sha512-TgV33rXr6ueXBwvc+0nssUkTBSXHJxv77I8p4RCjDjCnvexHtmoPiudVRDfj3S3puMDdeQdHW6jdUgUPy3nr/w==} + cpu: [arm64] + os: [darwin] - '@vitest/expect@4.1.4': - resolution: {integrity: sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==} + '@oxlint-tsgolint/darwin-x64@7.0.2003': + resolution: {integrity: sha512-hY3FMAjIaPDdK3FNxyRXRfHPOFeoBn6dmnLyKZgQ2IbTTD1adXhl6PfCIqHhCPvurigv7nf7mYuWZZ19MmJzmg==} + cpu: [x64] + os: [darwin] - '@vitest/mocker@4.1.4': - resolution: {integrity: sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==} - peerDependencies: - msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true + '@oxlint-tsgolint/linux-arm64@7.0.2003': + resolution: {integrity: sha512-eAET4JpyfBbg8SO0K74o4R55tEjVC292pIyxGOw2XGf8x/HrPNyxwQ478jMYOM54FIVOHc8sJ6VRHxluy6lucw==} + cpu: [arm64] + os: [linux] - '@vitest/pretty-format@4.1.4': - resolution: {integrity: sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==} + '@oxlint-tsgolint/linux-x64@7.0.2003': + resolution: {integrity: sha512-GXdyO/XyqDJ3s/llR/oOktLsYNjZtWSQBy0JMc+/0gsNPvTcseKPhn9c6KcFyWmnr6H4vljYALbujonqSzzEGw==} + cpu: [x64] + os: [linux] - '@vitest/runner@4.1.4': - resolution: {integrity: sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==} + '@oxlint-tsgolint/win32-arm64@7.0.2003': + resolution: {integrity: sha512-TWauXnPfet0VgpmrstoAK58eJ4gbuwPUuUTQmYQAzE/RG1CpnxXtrKUJULf6lyerPE4KN3gM+DflIA1hOH+38Q==} + cpu: [arm64] + os: [win32] - '@vitest/snapshot@4.1.4': - resolution: {integrity: sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==} + '@oxlint-tsgolint/win32-x64@7.0.2003': + resolution: {integrity: sha512-DoRmfe7j8VqNlukp8liRVW45GQDhzRccNenjD/pdzelgtffW47pCMd1xbJLkPaPbKnTwID3onn3VZlL7JbebEA==} + cpu: [x64] + os: [win32] - '@vitest/spy@4.1.4': - resolution: {integrity: sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==} + '@oxlint/binding-android-arm-eabi@1.85.0': + resolution: {integrity: sha512-q2KO/Zso9UT+OMn0NF9ywn4E4t0MI3yxiDhNyhsQ7DyQJrC4FhFE4TXOi4bktFnOWXTMds8qZSbpv2XwRaNOBg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] - '@vitest/utils@4.1.4': - resolution: {integrity: sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==} + '@oxlint/binding-android-arm64@1.85.0': + resolution: {integrity: sha512-SxLN3ALjoT9NNdvpjEevGeHvfzTAFrF0NBYB5tzK7/GtCKMze3j1e/m/X2ozqGj2U9hfGG/dg/OG8vpVK4PiDA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] - acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + '@oxlint/binding-darwin-arm64@1.85.0': + resolution: {integrity: sha512-Y/Sup/J4f0f9UGsSd/xyCNTeWL+gepO63GBdEDAfue9nBsnk9zMmnIXx1O6b1V8C90vB5nucYNZ0pbMXAp8zJA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] - acorn@8.16.0: - resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} - engines: {node: '>=0.4.0'} - hasBin: true + '@oxlint/binding-darwin-x64@1.85.0': + resolution: {integrity: sha512-ApOSNC04ynpDTwvBD+//0wyfODRSbEzvRoKpX8teffmc27z8AockwSNeMXGJXn5KP85eahDgR/2llICWLkzcnw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] - ajv@6.14.0: - resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + '@oxlint/binding-freebsd-x64@1.85.0': + resolution: {integrity: sha512-bNrVrCOA/kHky3Tu79IXWXe5bhIgLXfUuUEDHlAGOHUk96MkvDZ1ecaQF19rwstrnaqfP1o9nBTqzIr9+ZHkUg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} + '@oxlint/binding-linux-arm-gnueabihf@1.85.0': + resolution: {integrity: sha512-NUrzOJ1s/EqsVvfn2L/1D8Wro2LPIZUbihL8kOJLh5fEdGEN3rdOGUYq3HwnUIL8sjpoP+4N6RaGrgmMJnaMPw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + '@oxlint/binding-linux-arm-musleabihf@1.85.0': + resolution: {integrity: sha512-UJXrAT3E/RWkEqXLIs2ehETja1qfgkPb+5gwLIIS+o/6cf+grHvoOXTa5997a/YNQfcJS0DRBTOfZt95cvOI1g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] - array-buffer-byte-length@1.0.2: - resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} - engines: {node: '>= 0.4'} + '@oxlint/binding-linux-arm64-gnu@1.85.0': + resolution: {integrity: sha512-lK40QLjI0HxigO7CjDDshEtfYIeiYS0020v5BHFPqN4uuQBQxd2K9LNom2dW15o9F1937quSCRVp4ZsVhdbYdg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] - array-includes@3.1.9: - resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} - engines: {node: '>= 0.4'} + '@oxlint/binding-linux-arm64-musl@1.85.0': + resolution: {integrity: sha512-c2zbdBwGKreHXwRx3gWBuFGJxLhxgsg6YlZ+3H+RgRusU/UEV9jNwJ3HGYK+nRo0LvBa7mt6Kj86xoVotUo8cw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] - array.prototype.findlast@1.2.5: - resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} - engines: {node: '>= 0.4'} + '@oxlint/binding-linux-ppc64-gnu@1.85.0': + resolution: {integrity: sha512-tlt/Hy8lZ97/lCPmCgw/B3k/mwh+BzaIPbPkldZEly7TwLmx0xe2CQcaW2g/rR0dOgS9JNGCZsMEqLhUNMGvaw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] - array.prototype.findlastindex@1.2.6: - resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} - engines: {node: '>= 0.4'} + '@oxlint/binding-linux-riscv64-gnu@1.85.0': + resolution: {integrity: sha512-3tNR9Xey82X0zKuY1d8hJ6Rc9gwRDurmqGLnQZa5xqOXy8/YyiqFXjAtugkKLY82obOlpK1eSiDRlgcNPuxtIg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] - array.prototype.flat@1.3.3: - resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} - engines: {node: '>= 0.4'} + '@oxlint/binding-linux-riscv64-musl@1.85.0': + resolution: {integrity: sha512-wbGRd5PqCcjkJFHhZuZ2OBSUQY9czlQsoA/cQQB9JK/L9mC5MQgGoKAh+xd8QjA5V+0D3j+Qd1lAWn1I8zlelA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] - array.prototype.flatmap@1.3.3: - resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} - engines: {node: '>= 0.4'} + '@oxlint/binding-linux-s390x-gnu@1.85.0': + resolution: {integrity: sha512-3Sn0kSrE4DPZCWV/8o+n4x3aFZxI9ulMnkYlwCbJ8eUVkwRK2IerohE/A/z3SNbCwoPFOCJmGE5Avrq0rrvdvQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] - array.prototype.tosorted@1.1.4: - resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} - engines: {node: '>= 0.4'} + '@oxlint/binding-linux-x64-gnu@1.85.0': + resolution: {integrity: sha512-JY2pxxYfB62bAGfejljVCqc44etItehPuAyaeSAdMuEMtwNA00ggMnS66lC1oIhos6oOXUkuU6mZ9bpFh3BqWg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] - arraybuffer.prototype.slice@1.0.4: - resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} - engines: {node: '>= 0.4'} + '@oxlint/binding-linux-x64-musl@1.85.0': + resolution: {integrity: sha512-5k74vZ6qJBjBHEOlBk9B/iv68Yu0F1Afw/vvT2ar6OGCqEeXLaSjXz2n/IPCbhLG22UoKoYEJTzpYraRdcp6PA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] - assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} + '@oxlint/binding-openharmony-arm64@1.85.0': + resolution: {integrity: sha512-GbAl5qt5TCkPLXTaIISZJnugrcBhra6rodcXc9jYt620UtdsTt71NlNmJmm0frxzFpd54x/G+MkitEJA8I/BoA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] - async-function@1.0.0: - resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} - engines: {node: '>= 0.4'} + '@oxlint/binding-win32-arm64-msvc@1.85.0': + resolution: {integrity: sha512-kjmws5MK0et2swk4ND85D7NVQyDHw162i6whtZDLUA/lo6FQyBZDcmMRCMcVZcNrAhIaftVb00x9ChGDOjjNJA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] - available-typed-arrays@1.0.7: - resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} - engines: {node: '>= 0.4'} + '@oxlint/binding-win32-ia32-msvc@1.85.0': + resolution: {integrity: sha512-eSsIJx9n4yxvOqYTZyPEMyEXRmE60XH7xGAU7i0Qbsn1lf6Za3CWJ9aRd82oSFKXaxhp+sA6/yMJVRIpLpna6A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + '@oxlint/binding-win32-x64-msvc@1.85.0': + resolution: {integrity: sha512-pBebIPUpKKhWrhSMWhy8TdAZBewiXnfxmaAGxhzxM1068GagqFaTwgKlU6e+UyJ2sPR+VoHouhXuGJkQjsrDvA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] - balanced-match@4.0.4: - resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} - engines: {node: 18 || 20 || >=22} + '@poppinss/colors@4.1.6': + resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==} - baseline-browser-mapping@2.10.40: - resolution: {integrity: sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw==} - engines: {node: '>=6.0.0'} - hasBin: true + '@poppinss/dumper@0.6.5': + resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==} - blake3-wasm@2.1.5: - resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} + '@poppinss/exception@1.2.3': + resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} - boolbase@1.0.0: - resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + '@rolldown/binding-android-arm-eabi@1.2.11': + resolution: {integrity: sha512-A5kXfGKvKWWZE0TtPrfsvT+q4Y5d1QG8gGUzpYjGydM+fARM9MuX90PrXYXe0XbsDVgyxxNzHo6giCj90bsFNw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] - brace-expansion@1.1.15: - resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==} + '@rolldown/binding-android-arm64@1.2.11': + resolution: {integrity: sha512-z6cTycz+iJ4PVkuL4HHW4DfTfoeU/2nqYYuSOrTmH7yHK5Y0LCOnA03V4ZNxavyVaU1oOqUgIg2klN/s+USGOA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] - brace-expansion@5.0.6: - resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} - engines: {node: 18 || 20 || >=22} + '@rolldown/binding-darwin-arm64@1.2.11': + resolution: {integrity: sha512-jShvqNtP6vDC6/A5JOAzbVV+DkgHqhl/ScVCJEbt+TUY6QYz7YnXcrg3sLtFBniro0f/Ld50ZwCWA6f7KYD1nQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] - browserslist@4.28.4: - resolution: {integrity: sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true + '@rolldown/binding-darwin-x64@1.2.11': + resolution: {integrity: sha512-f2i2xiNWq1Z1l2++q2fuhZRdLAT3aqxD6vRNm1RAxpUoBcdqNB3C0s1Bt+K+PbEx2F5F4gQp6hqKkphCY/xF9w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] - buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + '@rolldown/binding-freebsd-x64@1.2.11': + resolution: {integrity: sha512-4Ir5FSOKIAMr4r0kExpt1s3bMgzJU3rA45AYOHtQpls0oNeqcYBKrWMlckrYH4KCfGLfkfn1tN1dmZPMVsdXow==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} + '@rolldown/binding-linux-arm-gnueabihf@1.2.11': + resolution: {integrity: sha512-/gnRDM+39BROzAN/k1OZjDPnDMcZxB/0EUxKjONO5yVkNEvlsoMDrxGNKgZi/ttFriS2gwlDNzB65pvNbFOXIQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] - call-bind@1.0.8: - resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} - engines: {node: '>= 0.4'} + '@rolldown/binding-linux-arm64-gnu@1.2.11': + resolution: {integrity: sha512-PFaK8HwvAHbaKbBcDNQihjMKYvFnA5hiENx/l5tphTDz1E0WFp32l0A7aq7lyUwGsRw/xSrNIy/gIK4thrSCrw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] - call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} + '@rolldown/binding-linux-arm64-musl@1.2.11': + resolution: {integrity: sha512-AskzJUIKRLPxkruR1wLKewGbOw+EYfU/9lOrBFj4AFrEA8hPpKFnODWNu2WLaNs0QNkEb9QIJufmVZZIL/bJlg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} + '@rolldown/binding-linux-ppc64-gnu@1.2.11': + resolution: {integrity: sha512-qlUGAheh2yh8afH7QBgx0PrRHN85hKnNd78x8MeMhXivuevgd8vgf6/CstOzmNKY/lLTHvNTrPy98cLnAugzJw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] - caniuse-lite@1.0.30001799: - resolution: {integrity: sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==} + '@rolldown/binding-linux-s390x-gnu@1.2.11': + resolution: {integrity: sha512-secpEad+0vCbSfn8upFySkDskv+bGPk3THSDS9Y89yc4rb4kzqHp8Dmyd9BkQW4SnhNXBZCl/6CrO//hZahNJQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] - chai@6.2.2: - resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} - engines: {node: '>=18'} + '@rolldown/binding-linux-x64-gnu@1.2.11': + resolution: {integrity: sha512-mOVBT3dPpkWm8XBWPmU4bf+U6dYDLeMo/9ojUmis4N0L5uu10qra5vOyngZ7/PSdoE4G9KvRt4bloRxNjLas7A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} + '@rolldown/binding-linux-x64-musl@1.2.11': + resolution: {integrity: sha512-Is78i9A8Ui4SqcxUwFJ9uMmjDn58IbVTjFWYdQestFEgeuEmHMLGNriXnVJKkwG2YiZjw8cP0zCTyDMdDGtOOg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} + '@rolldown/binding-openharmony-arm64@1.2.11': + resolution: {integrity: sha512-dUCXneZ87INUMyQ0D+C0HrEBNUPNXHaPmU5GTjyKTJEiussw9Kaj5Ln8UztPe4epV/ffvgNBEadksdYhmW6xJA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + '@rolldown/binding-win32-arm64-msvc@1.2.11': + resolution: {integrity: sha512-jByxb6qfd+bH1xUd0qnfFnb17i9sWBPY2tOavJ0l3tdr3OTu+Kvtm8cd/JV5nFt657b1VqGltxg9olOEfofXWw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + '@rolldown/binding-win32-x64-msvc@1.2.11': + resolution: {integrity: sha512-/PzKqzAJ03i19oy2ItPvyvaVjOjBCNnfaJs8yvUdGBKmiESgnrJSQ2awd81QzFbbnAmu7YO9ZnJrDCb9VSJPRA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} - cookie@1.1.1: - resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + '@sindresorhus/is@7.2.0': + resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==} engines: {node: '>=18'} - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} + '@speed-highlight/core@1.2.24': + resolution: {integrity: sha512-qeW2e1l78afw8VhRPfPQ1Gjj+KU5XFQ/OFV5ti6eTa9bruO7mJyZtA4vw0ofqmA3tKCkROE9xLk3VZoeRc98nw==} - css-select@5.2.2: - resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + '@turbo/darwin-64@2.11.4': + resolution: {integrity: sha512-fEmcQVa95k79tZjcay5b2A4cpR/VrlNUki4AN7EbA+32vj5C5rBUE1q3i2yOFxsB2n01sOlzIZ+s2p2zADCTig==} + cpu: [x64] + os: [darwin] - css-what@6.2.2: - resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} - engines: {node: '>= 6'} + '@turbo/darwin-arm64@2.11.4': + resolution: {integrity: sha512-SgXCVgBC1v+bdE6+KQGV6Kob7nBYo75zxzNFFuA4jLdM0J8lYamdTUoZSHLR0dy+kXVEYZqSC98CKqC17NG3Og==} + cpu: [arm64] + os: [darwin] + + '@turbo/linux-64@2.11.4': + resolution: {integrity: sha512-MWM7Au6NPJMBmHJAzFzQ/PACYNRpBOqnBkj+7csdR+YhJ+0XBg4Z64wFwLnGFDac0gc+YlasWOZ5t05bGs54aQ==} + cpu: [x64] + os: [android, linux] + + '@turbo/linux-arm64@2.11.4': + resolution: {integrity: sha512-rRVOh0UNlLbKPuf70FokqjahO/hCGgBmgvZcNXUPayn8Wk0zXIsVoNKpr0UQUhhR+KMiibvBKkVAGDViDFQHKA==} + cpu: [arm64] + os: [android, linux] + + '@turbo/windows-64@2.11.4': + resolution: {integrity: sha512-COr+++1GwDg09XbJdm3sP1gEQApeV43UknYP9h82qet1tAGJrGQAxKVTMrf2JMsMw8MkJRnQj3gAVuQmV1kIyQ==} + cpu: [x64] + os: [win32] + + '@turbo/windows-arm64@2.11.4': + resolution: {integrity: sha512-gh5lwDA2d4aBdLvTANhFsFEgxbBqKZ4m4dCyChw9GoX8b+Esk8H3p25Q+rlWCUVJ2o6Hne10ZTL0QSFfWD4gIA==} + cpu: [arm64] + os: [win32] + + '@tybys/wasm-util@0.10.4': + resolution: {integrity: sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/node@24.13.6': + resolution: {integrity: sha512-SGrw/h3KPFshy3OE6ZL53LMBG5vGQQ8/gIpiqz/kRZhPJ7HgwCEs8LBuNtWLa8dvGZVpSF7+Bf+c11HUrCb/yg==} + + '@typescript/typescript-aix-ppc64@7.0.2': + resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [aix] + + '@typescript/typescript-darwin-arm64@7.0.2': + resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] + + '@typescript/typescript-darwin-x64@7.0.2': + resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] + + '@typescript/typescript-freebsd-arm64@7.0.2': + resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [freebsd] + + '@typescript/typescript-freebsd-x64@7.0.2': + resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [freebsd] + + '@typescript/typescript-linux-arm64@7.0.2': + resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + + '@typescript/typescript-linux-arm@7.0.2': + resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} + engines: {node: '>=16.20.0'} + cpu: [arm] + os: [linux] + + '@typescript/typescript-linux-loong64@7.0.2': + resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} + engines: {node: '>=16.20.0'} + cpu: [loong64] + os: [linux] + + '@typescript/typescript-linux-mips64el@7.0.2': + resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} + engines: {node: '>=16.20.0'} + cpu: [mips64el] + os: [linux] + + '@typescript/typescript-linux-ppc64@7.0.2': + resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [linux] + + '@typescript/typescript-linux-riscv64@7.0.2': + resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} + engines: {node: '>=16.20.0'} + cpu: [riscv64] + os: [linux] - data-view-buffer@1.0.2: - resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} - engines: {node: '>= 0.4'} + '@typescript/typescript-linux-s390x@7.0.2': + resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} + engines: {node: '>=16.20.0'} + cpu: [s390x] + os: [linux] + + '@typescript/typescript-linux-x64@7.0.2': + resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + + '@typescript/typescript-netbsd-arm64@7.0.2': + resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [netbsd] + + '@typescript/typescript-netbsd-x64@7.0.2': + resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [netbsd] + + '@typescript/typescript-openbsd-arm64@7.0.2': + resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [openbsd] + + '@typescript/typescript-openbsd-x64@7.0.2': + resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [openbsd] + + '@typescript/typescript-sunos-x64@7.0.2': + resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [sunos] - data-view-byte-length@1.0.2: - resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} - engines: {node: '>= 0.4'} + '@typescript/typescript-win32-arm64@7.0.2': + resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] - data-view-byte-offset@1.0.1: - resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} - engines: {node: '>= 0.4'} + '@typescript/typescript-win32-x64@7.0.2': + resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] - debug@3.2.7: - resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + '@vitest/mocker@5.0.1': + resolution: {integrity: sha512-6K1DoBNAPGvuOcSsGA4D6x+5zEEff/KmOOP3uetT2TrGpVfI+HRHRnJJfKi5ib/g1vx8IYHQD8s0pbJz8WQI7Q==} peerDependencies: - supports-color: '*' + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: - supports-color: + msw: + optional: true + vite: optional: true + '@vitest/spy@5.0.1': + resolution: {integrity: sha512-rbto/mF/SGERxEgYOek7Xm6B9b+y+mVoo+f4b2LymYO8zM1b7uB5nHuhVMTP2hxdzgxvGiZYGxGIaMvL5y180Q==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + blake3-wasm@2.1.5: + resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + css-select@5.2.2: + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1419,25 +1839,10 @@ packages: supports-color: optional: true - 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'} - detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} - doctrine@2.1.0: - resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} - engines: {node: '>=0.10.0'} - dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} @@ -1451,12 +1856,12 @@ packages: domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} - drizzle-kit@0.31.10: - resolution: {integrity: sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw==} + drizzle-kit@0.31.11: + resolution: {integrity: sha512-YCYqxTLIB2OCqf6w9/Vef13baBVbwQIPcbT4Y56AfO6B9ajZhDhD8Jkveg/hJvT/iuMloKD/IYYkyMMNhr7Kqg==} hasBin: true - drizzle-orm@0.45.2: - resolution: {integrity: sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==} + drizzle-orm@0.45.3: + resolution: {integrity: sha512-CAloER21cDdcgZ1OmjrZX82EeRNsdP+y0onX/eyoYDaYzki/adrAa91lzM5jWh7zZPK4qUbw/6LGv9J+A+uizA==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' '@cloudflare/workers-types': '>=4' @@ -1464,6 +1869,7 @@ packages: '@libsql/client': '>=0.10.0' '@libsql/client-wasm': '>=0.10.0' '@neondatabase/serverless': '>=0.10.0' + '@netlify/db': '>=0.4.0' '@op-engineering/op-sqlite': '>=2' '@opentelemetry/api': ^1.4.1 '@planetscale/database': '>=1.13' @@ -1500,6 +1906,8 @@ packages: optional: true '@neondatabase/serverless': optional: true + '@netlify/db': + optional: true '@op-engineering/op-sqlite': optional: true '@opentelemetry/api': @@ -1547,17 +1955,6 @@ packages: sqlite3: optional: true - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} - - electron-to-chromium@1.5.380: - resolution: {integrity: sha512-W6d5AbuEoRayO447cqrg6lKJIlscgRnnxOZl/08kfV71BQDoEBC7Wwis68z87LjyK6f4kWyTaubuDbhHKrZkbA==} - - empathic@2.0.1: - resolution: {integrity: sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==} - engines: {node: '>=14'} - entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -1565,161 +1962,28 @@ packages: error-stack-parser-es@1.0.5: resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} - es-abstract@1.24.1: - resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} - engines: {node: '>= 0.4'} - - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - - es-iterator-helpers@1.3.1: - resolution: {integrity: sha512-zWwRvqWiuBPr0muUG/78cW3aHROFCNIQ3zpmYDpwdbnt2m+xlNyRWpHBpa2lJjSBit7BQ+RXA1iwbSmu5yJ/EQ==} - engines: {node: '>= 0.4'} - - es-module-lexer@2.0.0: - resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} - - es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} - 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.0: - resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} - engines: {node: '>= 0.4'} + es-module-lexer@2.3.2: + resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} esbuild@0.28.1: resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} engines: {node: '>=18'} hasBin: true - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - - eslint-import-resolver-node@0.3.9: - resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} - - eslint-module-utils@2.12.1: - resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} - 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-react-hooks@7.0.1: - resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==} + esbuild@0.28.2: + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} engines: {node: '>=18'} - peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 - - eslint-plugin-react@7.37.5: - resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} - engines: {node: '>=4'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - - eslint-scope@8.4.0: - resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint-visitor-keys@4.2.1: - resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - eslint-visitor-keys@5.0.1: - resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - eslint@9.39.1: - resolution: {integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true - peerDependencies: - jiti: '*' - peerDependenciesMeta: - jiti: - optional: true - - espree@10.4.0: - resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - esquery@1.7.0: - resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} - engines: {node: '>=0.10'} - - esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - - estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - - expect-type@1.3.0: - resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} engines: {node: '>=12.0.0'} - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - - fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fd-package-json@2.0.0: + resolution: {integrity: sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==} fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} @@ -1730,427 +1994,137 @@ packages: picomatch: optional: true - file-entry-cache@8.0.0: - resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} - engines: {node: '>=16.0.0'} - - find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - - flat-cache@4.0.1: - resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} - engines: {node: '>=16'} - - flatted@3.4.2: - resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} - - for-each@0.3.5: - resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} - engines: {node: '>= 0.4'} + formatly@0.7.1: + resolution: {integrity: sha512-ESuCEJE0SGgabl9q+zABI8LhJDmF+8nhpuqDzuM48HfJ9QTD5o6PqFJDFZY7w6f8xsh5lixuj5nJsQNvYOWvKQ==} + engines: {node: '>=18.3.0'} + hasBin: true fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + get-tsconfig@4.14.3: + resolution: {integrity: sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==} - function.prototype.name@1.1.8: - resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} - engines: {node: '>= 0.4'} + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true - functions-have-names@1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + hono@4.13.9: + resolution: {integrity: sha512-7dMkQmZoC4E6F7AtaQSPhlWAdnBti+j7rreMZl8QB4jFiEhP9TWbGWUMi8WYzBCgmgulxuvLQupKqo+Co6Omyg==} + engines: {node: '>=16.9.0'} - generator-function@2.0.1: - resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} - engines: {node: '>= 0.4'} + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} - get-symbol-description@1.1.0: - resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} - engines: {node: '>= 0.4'} + knip@6.38.0: + resolution: {integrity: sha512-umytiMCaZf02MV9SOO1FEDgbDf6h20VZk3ruJE0kh52kkGjYZFRUAXHJWYoLhvhoHGINO/7GXVJmQ2vxTBvl6A==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true - get-tsconfig@4.13.7: - resolution: {integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==} + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] - glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] - globals@14.0.0: - resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} - engines: {node: '>=18'} + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] - globalthis@1.0.4: - resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} - engines: {node: '>= 0.4'} + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] - has-bigints@1.1.0: - resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} - engines: {node: '>= 0.4'} + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] - 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.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - - he@1.2.0: - resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} - hasBin: true - - hermes-estree@0.25.1: - resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} - - hermes-parser@0.25.1: - resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} - - hono@4.12.27: - resolution: {integrity: sha512-1yrb/+w6HWQJrUCLkJ2IF5jNIPvvFkblV5RNOYl6bV+OA6p9GLcMpHFFGTosSvHvcAUibuUukRqhlYI4z32C7Q==} - engines: {node: '>=16.9.0'} - - ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} - - ignore@7.0.5: - resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} - engines: {node: '>= 4'} - - import-fresh@3.3.1: - resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} - engines: {node: '>=6'} - - import-meta-resolve@4.2.0: - resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} - - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - - internal-slot@1.1.0: - resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} - engines: {node: '>= 0.4'} - - 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-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} - - is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} - 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-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-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'} - - 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-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-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-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==} - - iterator.prototype@1.1.5: - resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} - engines: {node: '>= 0.4'} - - jiti@2.6.1: - resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} - hasBin: true - - js-tokens@10.0.0: - resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - js-yaml@5.2.0: - resolution: {integrity: sha512-YeLUMlvR4Ou1B119LIaM0r65JvbOBooJDc9yEu0dClb/uSC5P4FrLU8OCCz/HXWvtPoIrR0dRzABTjo1sTN9Bw==} - hasBin: true - - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} - hasBin: true - - json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - - json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - - 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 - - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - - jsx-ast-utils@3.3.5: - resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} - engines: {node: '>=4.0'} - - keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - - kleur@4.1.5: - resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} - engines: {node: '>=6'} - - levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - - lightningcss-android-arm64@1.32.0: - resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] - - lightningcss-darwin-arm64@1.32.0: - resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - - lightningcss-darwin-x64@1.32.0: - resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - - lightningcss-freebsd-x64@1.32.0: - resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - - lightningcss-linux-arm-gnueabihf@1.32.0: - resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - - lightningcss-linux-arm64-gnu@1.32.0: - resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - lightningcss-linux-arm64-musl@1.32.0: - resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] libc: [musl] - lightningcss-linux-x64-gnu@1.32.0: - resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] libc: [glibc] - lightningcss-linux-x64-musl@1.32.0: - resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] libc: [musl] - lightningcss-win32-arm64-msvc@1.32.0: - resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] - lightningcss-win32-x64-msvc@1.32.0: - resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] - lightningcss@1.32.0: - resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} engines: {node: '>= 12.0.0'} - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - - loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true - - lru-cache@11.5.1: - resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} - engines: {node: 20 || >=22} - - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magic-string@1.4.2: + resolution: {integrity: sha512-vG+rjFRj1PqdIBozIxAGMjPlOhaVe+GXpbttY/iSK7rGcJRMlwNJO7dcUwmUqkymsFLJiNGI06t4D7Fr7yRC9g==} - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} - - miniflare@4.20260409.0: - resolution: {integrity: sha512-ayl6To4av0YuXsSivGgWLj+Ug8xZ0Qz3sGV8+Ok2LhNVl6m8m5ktEBM3LX9iT9MtLZRJwBlJrKcraNs/DlZQfA==} - engines: {node: '>=18.0.0'} - hasBin: true - - minimatch@10.2.5: - resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} - engines: {node: 18 || 20 || >=22} - - minimatch@3.1.5: - resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + miniflare@5.20260923.0-alpha: + resolution: {integrity: sha512-tmICPUd2P+/4WNa2A9kE22SPZexlu+Dw8nZIvaXCzRj2OVu62hc751Xq4S1P4HEscT5viDle3zxyHyxT2XY3Fw==} + engines: {node: '>=22.0.0'} ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - nanoid@3.3.15: - resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==} + nanoid@3.3.19: + resolution: {integrity: sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - - node-exports-info@1.6.0: - resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==} - engines: {node: '>= 0.4'} - node-html-markdown@2.0.0: resolution: {integrity: sha512-DqUC3GGP7pwSYxS93SwHoP+qCw78xcMP6C6H2DuC8rPD2AweJRjBzQb5SdXpKtDlqAQ7hVotJcfhgU7hU5Gthw==} engines: {node: '>=20.0.0'} @@ -2158,91 +2132,63 @@ packages: node-html-parser@6.1.13: resolution: {integrity: sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==} - node-releases@2.0.50: - resolution: {integrity: sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==} - engines: {node: '>=18'} - nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - object-inspect@1.13.4: - 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'} - - obug@2.1.3: - resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==} + obug@2.2.1: + resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==} engines: {node: '>=12.20.0'} - openai@6.34.0: - resolution: {integrity: sha512-yEr2jdGf4tVFYG6ohmr3pF6VJuveP0EA/sS8TBx+4Eq5NT10alu5zg2dmxMXMgqpihRDQlFGpRt2XwsGj+Fyxw==} - hasBin: true + openai@7.23.0: + resolution: {integrity: sha512-0ecOXnFSMNWZq6cUBcTV6Lf93y+fm8BH/+QzFvpoP1UGNOE5pnytRa6HAPGCw+3IS9SIAW7rc1M8yyzN1PiBAQ==} + engines: {node: '>=22.0.0'} peerDependencies: - ws: '>=8.21.0' + '@aws-sdk/credential-provider-node': '>=3.972.0 <4' + '@smithy/hash-node': '>=4.3.0 <5' + '@smithy/signature-v4': '>=5.4.0 <6' + undici: ^7.29.1 + ws: ^8.21.0 zod: ^3.25 || ^4.0 peerDependenciesMeta: + '@aws-sdk/credential-provider-node': + optional: true + '@smithy/hash-node': + optional: true + '@smithy/signature-v4': + optional: true + undici: + optional: true ws: optional: true zod: optional: true - optionator@0.9.4: - 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-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} + oxc-parser@0.150.0: + resolution: {integrity: sha512-zwajKw1GUa57IOdafIS7/yOObGF2NihwIfZfs5yJTRprMSkiwhWOc/k+n8bwnUPbHr2/XsjfBO8KyTPaW3VpWg==} + engines: {node: ^20.19.0 || >=22.12.0} - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} + oxc-resolver@11.24.2: + resolution: {integrity: sha512-FY91FiDBj7ls5MsFS9jN3tjz2o0/zsdSsymlakySaBwVJZorHhkWyICLZMKxlu1R9vYo+sd3z1jwb4J8x7bNDw==} - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} + oxlint-tsgolint@7.0.2003: + resolution: {integrity: sha512-VnK4zlqgmgq/7ZcjzCk/WpN8kKFsYGcB85Io9qT3wL4K8Un3RKmJkp793crNETieMusPMKOA4a+Mw2wbteI6TQ==} + hasBin: true - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} + oxlint@1.85.0: + resolution: {integrity: sha512-bc26s97nuvPj1ViyPsqmKecVkUWFMEdtayO8MaQ6oiLfs1pj94cQlZZhrh4BPNlr9HQosjhIlwgZKsfcwmcNgg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + oxlint-tsgolint: '>=7.0.2001' + vite-plus: '*' + peerDependenciesMeta: + oxlint-tsgolint: + optional: true + vite-plus: + optional: true - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + package-manager-detector@1.8.0: + resolution: {integrity: sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==} path-to-regexp@6.3.0: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} @@ -2253,191 +2199,48 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@4.0.4: - resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} engines: {node: '>=12'} - possible-typed-array-names@1.1.0: - resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} - engines: {node: '>= 0.4'} - - postcss@8.5.15: - resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + postcss@8.5.28: + resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} engines: {node: ^10 || ^12 || >=14} - prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - - prettier-plugin-tailwindcss@0.7.2: - resolution: {integrity: sha512-LkphyK3Fw+q2HdMOoiEHWf93fNtYJwfamoKPl7UwtjFQdei/iIBoX11G6j706FzN3ymX9mPVi97qIY8328vdnA==} - engines: {node: '>=20.19'} - peerDependencies: - '@ianvs/prettier-plugin-sort-imports': '*' - '@prettier/plugin-hermes': '*' - '@prettier/plugin-oxc': '*' - '@prettier/plugin-pug': '*' - '@shopify/prettier-plugin-liquid': '*' - '@trivago/prettier-plugin-sort-imports': '*' - '@zackad/prettier-plugin-twig': '*' - prettier: ^3.0 - prettier-plugin-astro: '*' - prettier-plugin-css-order: '*' - prettier-plugin-jsdoc: '*' - prettier-plugin-marko: '*' - prettier-plugin-multiline-arrays: '*' - prettier-plugin-organize-attributes: '*' - prettier-plugin-organize-imports: '*' - prettier-plugin-sort-imports: '*' - prettier-plugin-svelte: '*' - peerDependenciesMeta: - '@ianvs/prettier-plugin-sort-imports': - optional: true - '@prettier/plugin-hermes': - optional: true - '@prettier/plugin-oxc': - optional: true - '@prettier/plugin-pug': - optional: true - '@shopify/prettier-plugin-liquid': - optional: true - '@trivago/prettier-plugin-sort-imports': - optional: true - '@zackad/prettier-plugin-twig': - optional: true - prettier-plugin-astro: - optional: true - prettier-plugin-css-order: - optional: true - prettier-plugin-jsdoc: - optional: true - prettier-plugin-marko: - optional: true - prettier-plugin-multiline-arrays: - optional: true - prettier-plugin-organize-attributes: - optional: true - prettier-plugin-organize-imports: - optional: true - prettier-plugin-sort-imports: - optional: true - prettier-plugin-svelte: - optional: true - - prettier@3.8.2: - resolution: {integrity: sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q==} + prettier@3.9.9: + resolution: {integrity: sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==} engines: {node: '>=14'} hasBin: true - prop-types@15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} - - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - - react-is@16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - - 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'} - - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - resolve@1.22.11: - resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} - engines: {node: '>= 0.4'} - hasBin: true - - resolve@2.0.0-next.6: - resolution: {integrity: sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==} - engines: {node: '>= 0.4'} - hasBin: true - - rolldown@1.0.3: - resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==} + rolldown@1.2.11: + resolution: {integrity: sha512-qpSwIyz0jHQq5qXBTNxFmE6664rJ7O+4TvPFOiOaBSrz8IOHc1koKKSqTM2H6u1UG1+TveuC6vaDHKXFOvb1Kw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - safe-array-concat@1.1.3: - resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} - 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'} - - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - - semver@7.7.4: - resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} - engines: {node: '>=10'} - hasBin: true - semver@7.8.5: resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} engines: {node: '>=10'} hasBin: true - 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'} - - sharp@0.34.5: - resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - side-channel-list@1.0.0: - resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} - engines: {node: '>= 0.4'} - - side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} - - side-channel-weakmap@1.0.2: - resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} - engines: {node: '>= 0.4'} - - side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} - engines: {node: '>= 0.4'} + sharp@0.35.4: + resolution: {integrity: sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==} + engines: {node: '>=20.9.0'} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + smol-toml@1.9.0: + resolution: {integrity: sha512-hpd+HLON7HdZXqYchMM/+LaTTbdK0AU3NngIJ4KVyWbY9bfQqdL9cD+4yf6dUoU2Ap4VsU0JkQi6FxAI1B2mXQ==} + engines: {node: '>= 18'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -2452,146 +2255,67 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - std-env@4.0.0: - resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==} - - stop-iteration-iterator@1.1.0: - resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} - engines: {node: '>= 0.4'} - - string.prototype.matchall@4.0.12: - resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} - engines: {node: '>= 0.4'} - - string.prototype.repeat@1.0.0: - resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} - - string.prototype.trim@1.2.10: - resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} - engines: {node: '>= 0.4'} - - string.prototype.trimend@1.0.9: - resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} - engines: {node: '>= 0.4'} - - string.prototype.trimstart@1.0.8: - resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} - engines: {node: '>= 0.4'} + std-env@4.2.0: + resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} - strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} - - 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'} supports-color@10.2.2: resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} engines: {node: '>=18'} - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinybench@6.1.4: + resolution: {integrity: sha512-9APumHG7r4yOk4X4WlkmE71aZcv1gvin1czO3OQ1U9iJcFA5Ja/ygyb0vPOVHTthFozUYs8CLoLUlM8grb2lTQ==} + engines: {node: '>=20.0.0'} - tinyexec@1.1.1: - resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==} + tinyexec@1.3.0: + resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} engines: {node: '>=18'} tinyglobby@0.2.17: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} - tinyrainbow@3.1.0: - resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} - engines: {node: '>=14.0.0'} - - ts-api-utils@2.5.0: - resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} - engines: {node: '>=18.12'} - 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==} - tsx@4.21.0: - resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + tsx@4.23.15: + resolution: {integrity: sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==} engines: {node: '>=18.0.0'} hasBin: true - type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - - 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.7: - resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} - engines: {node: '>= 0.4'} - - typescript-eslint@8.58.1: - resolution: {integrity: sha512-gf6/oHChByg9HJvhMO1iBexJh12AqqTfnuxscMDOVqfJW3htsdRJI/GfPpHTTcyeB8cSTUY2JcZmVgoyPqcrDg==} - 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' + turbo@2.11.4: + resolution: {integrity: sha512-nedZtJ2hjWpU/JBFq7Aj1GQBxQ56si+kIUmNZBUX35O35TIuhSBR6/u3yHHmk81U+RBdBcVjvLQ3/iOFzMwKsA==} + hasBin: true - typescript@6.0.2: - resolution: {integrity: sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==} - engines: {node: '>=14.17'} + typescript@7.0.2: + resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} + engines: {node: '>=16.20.0'} hasBin: true - unbox-primitive@1.1.0: - resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} - engines: {node: '>= 0.4'} + unbash@4.0.11: + resolution: {integrity: sha512-FoSOKV7NEofQSkAefMVHam4ZPKYMxjAydxiV72UFEDNV/YofxjGfiZ2A9pZjdL/lRJzTjcu4PABo1JYJX8N5iQ==} + engines: {node: '>=14'} - undici-types@7.19.2: - resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} - undici@8.5.0: - resolution: {integrity: sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg==} - engines: {node: '>=22.19.0'} + undici@7.30.0: + resolution: {integrity: sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==} + engines: {node: '>=20.18.1'} unenv@2.0.0-rc.24: resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} - update-browserslist-db@1.2.3: - resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - - uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - - vite@8.0.16: - resolution: {integrity: sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==} + vite@8.3.1: + resolution: {integrity: sha512-/bvH9E9tmCXRGp2uXY3WbOldqpTwFkbha/8ANaEQ6VkxhH60KyqLwgZq6lG2y+4uT55x9+9eUHMpQ7uGnOCKjA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.1.18 + '@vitejs/devtools': ^0.7.1 esbuild: '>=0.28.1' jiti: '>=1.21.0' less: ^4.0.0 @@ -2628,23 +2352,23 @@ packages: yaml: optional: true - vitest@4.1.4: - resolution: {integrity: sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + vitest@5.0.1: + resolution: {integrity: sha512-iA95lQbKEkvrtTkdAgnWbXfbipWiiWe/hDl2P5tMi6WFwD76G0NxXAGp/M9EOcYupeGJRr6wppMc7CoA41TQjg==} + engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 - '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.4 - '@vitest/browser-preview': 4.1.4 - '@vitest/browser-webdriverio': 4.1.4 - '@vitest/coverage-istanbul': 4.1.4 - '@vitest/coverage-v8': 4.1.4 - '@vitest/ui': 4.1.4 + '@types/node': ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 5.0.1 + '@vitest/browser-preview': 5.0.1 + '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 + '@vitest/coverage-istanbul': 5.0.1 + '@vitest/coverage-v8': 5.0.1 + '@vitest/ui': 5.0.1 happy-dom: '*' jsdom: '*' - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + vite: ^6.4.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: '@edge-runtime/vm': optional: true @@ -2669,47 +2393,26 @@ packages: jsdom: optional: true - 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.20: - resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} - engines: {node: '>= 0.4'} - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true + walk-up-path@4.0.0: + resolution: {integrity: sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==} + engines: {node: 20 || >=22} why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} hasBin: true - word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} - - workerd@1.20260409.1: - resolution: {integrity: sha512-kuWP20fAaqaLBqLbvUfY9nCF6c3C78L60G9lS6eVwBf+v8trVFIsAdLB/FtrnKm7vgVvpDzvFAfB80VIiVj95w==} + workerd@1.20260923.1: + resolution: {integrity: sha512-1xynegnJJqkgk/zuhNVcOFzAJXzsJ4UxazfThUe0K80eZXKpsRIXI2sHcT4trswLfeHCc+3uWTBvtSWLFRozbQ==} engines: {node: '>=16'} hasBin: true - wrangler@4.81.1: - resolution: {integrity: sha512-fppPXi+W2KJ5bx1zxdUYe1e7CHj5cWPFVBPXy8hSMZhrHeIojMe3ozAktAOw1voVuQjXzbZJf/GVKyVeSjbF8w==} - engines: {node: '>=20.3.0'} + wrangler@4.140.0: + resolution: {integrity: sha512-XeKeC33B4Nglg0RAJ8F8ecGHOW+cPFeE5+fp4WBzn1c0u+H/96cVQYjto3/0ahilfPurnnGcUslJ1xaZXy+NGQ==} + engines: {node: '>=22.0.0'} hasBin: true peerDependencies: - '@cloudflare/workers-types': ^4.20260409.1 + '@cloudflare/workers-types': ^5.20260923.1 peerDependenciesMeta: '@cloudflare/workers-types': optional: true @@ -2726,9 +2429,10 @@ packages: utf-8-validate: optional: true - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} + yaml@2.9.1: + resolution: {integrity: sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==} + engines: {node: '>= 14.6'} + hasBin: true youch-core@0.3.3: resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==} @@ -2736,171 +2440,84 @@ packages: youch@4.1.0-beta.10: resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==} - zod-validation-error@4.0.2: - resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} - engines: {node: '>=18.0.0'} - peerDependencies: - zod: ^3.25.0 || ^4.0.0 - - zod@4.3.6: - resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + zod@4.6.5: + resolution: {integrity: sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==} snapshots: - '@babel/code-frame@7.29.0': + '@babel/code-frame@7.29.7': dependencies: - '@babel/helper-validator-identifier': 7.28.5 + '@babel/helper-validator-identifier': 7.29.7 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/code-frame@8.0.0': + '@babel/generator@7.29.8': dependencies: - '@babel/helper-validator-identifier': 8.0.2 - js-tokens: 10.0.0 - - '@babel/compat-data@8.0.0': {} - - '@babel/core@8.0.1': - dependencies: - '@babel/code-frame': 8.0.0 - '@babel/generator': 8.0.0 - '@babel/helper-compilation-targets': 8.0.0 - '@babel/helpers': 8.0.0 - '@babel/parser': 8.0.0 - '@babel/template': 8.0.0 - '@babel/traverse': 8.0.0 - '@babel/types': 8.0.0 - '@types/gensync': 1.0.5 - convert-source-map: 2.0.0 - empathic: 2.0.1 - gensync: 1.0.0-beta.2 - import-meta-resolve: 4.2.0 - json5: 2.2.3 - obug: 2.1.3 - semver: 7.8.5 - - '@babel/generator@7.29.1': - dependencies: - '@babel/parser': 7.29.2 - '@babel/types': 7.29.0 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 - - '@babel/generator@8.0.0': - dependencies: - '@babel/parser': 8.0.0 - '@babel/types': 8.0.0 + '@babel/parser': 7.29.9 + '@babel/types': 7.29.8 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 - '@types/jsesc': 2.5.1 jsesc: 3.1.0 - '@babel/helper-compilation-targets@8.0.0': - dependencies: - '@babel/compat-data': 8.0.0 - '@babel/helper-validator-option': 8.0.0 - browserslist: 4.28.4 - lru-cache: 11.5.1 - semver: 7.8.5 - - '@babel/helper-globals@7.28.0': {} - - '@babel/helper-globals@8.0.0': {} - - '@babel/helper-string-parser@7.27.1': {} + '@babel/helper-globals@7.29.7': {} - '@babel/helper-string-parser@8.0.0': {} + '@babel/helper-string-parser@7.29.7': {} - '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-identifier@7.29.7': {} - '@babel/helper-validator-identifier@8.0.2': {} - - '@babel/helper-validator-option@8.0.0': {} - - '@babel/helpers@8.0.0': - dependencies: - '@babel/template': 8.0.0 - '@babel/types': 8.0.0 - - '@babel/parser@7.29.2': - dependencies: - '@babel/types': 7.29.0 - - '@babel/parser@8.0.0': - dependencies: - '@babel/types': 8.0.0 - - '@babel/template@7.28.6': + '@babel/parser@7.29.9': dependencies: - '@babel/code-frame': 7.29.0 - '@babel/parser': 7.29.2 - '@babel/types': 7.29.0 + '@babel/types': 7.29.8 - '@babel/template@8.0.0': + '@babel/template@7.29.7': dependencies: - '@babel/code-frame': 8.0.0 - '@babel/parser': 8.0.0 - '@babel/types': 8.0.0 + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.9 + '@babel/types': 7.29.8 - '@babel/traverse@7.29.0': + '@babel/traverse@7.29.8(supports-color@10.2.2)': dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.29.2 - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 - debug: 4.4.3 + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.9 + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + debug: 4.4.3(supports-color@10.2.2) transitivePeerDependencies: - supports-color - '@babel/traverse@8.0.0': - dependencies: - '@babel/code-frame': 8.0.0 - '@babel/generator': 8.0.0 - '@babel/helper-globals': 8.0.0 - '@babel/parser': 8.0.0 - '@babel/template': 8.0.0 - '@babel/types': 8.0.0 - obug: 2.1.3 - - '@babel/types@7.29.0': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - - '@babel/types@8.0.0': + '@babel/types@7.29.8': dependencies: - '@babel/helper-string-parser': 8.0.0 - '@babel/helper-validator-identifier': 8.0.2 + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 - '@cloudflare/kv-asset-handler@0.4.2': {} + '@cloudflare/kv-asset-handler@0.5.0': {} - '@cloudflare/playwright@1.2.0': {} + '@cloudflare/playwright@1.3.6': {} - '@cloudflare/unenv-preset@2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260409.1)': + '@cloudflare/unenv-preset@2.16.2(unenv@2.0.0-rc.24)(workerd@1.20260923.1)': dependencies: unenv: 2.0.0-rc.24 optionalDependencies: - workerd: 1.20260409.1 + workerd: 1.20260923.1 - '@cloudflare/workerd-darwin-64@1.20260409.1': + '@cloudflare/workerd-darwin-64@1.20260923.1': optional: true - '@cloudflare/workerd-darwin-arm64@1.20260409.1': + '@cloudflare/workerd-darwin-arm64@1.20260923.1': optional: true - '@cloudflare/workerd-linux-64@1.20260409.1': + '@cloudflare/workerd-linux-64@1.20260923.1': optional: true - '@cloudflare/workerd-linux-arm64@1.20260409.1': + '@cloudflare/workerd-linux-arm64@1.20260923.1': optional: true - '@cloudflare/workerd-windows-64@1.20260409.1': + '@cloudflare/workerd-windows-64@1.20260923.1': optional: true - '@cloudflare/workers-types@4.20260412.1': {} + '@cloudflare/workers-types@5.20260925.1': {} '@cspotcode/source-map-support@0.8.1': dependencies: @@ -2908,1514 +2525,954 @@ snapshots: '@drizzle-team/brocli@0.10.2': {} - '@emnapi/core@1.10.0': + '@emnapi/core@1.11.2': dependencies: - '@emnapi/wasi-threads': 1.2.1 + '@emnapi/wasi-threads': 1.2.2 tslib: 2.8.1 optional: true - '@emnapi/runtime@1.10.0': + '@emnapi/runtime@1.11.2': dependencies: tslib: 2.8.1 optional: true - '@emnapi/runtime@1.9.2': + '@emnapi/runtime@1.11.3': dependencies: tslib: 2.8.1 optional: true - '@emnapi/wasi-threads@1.2.1': + '@emnapi/wasi-threads@1.2.2': dependencies: tslib: 2.8.1 optional: true '@esbuild-kit/core-utils@3.3.2': dependencies: - esbuild: 0.28.1 + esbuild: 0.28.2 source-map-support: 0.5.21 '@esbuild-kit/esm-loader@2.6.5': dependencies: '@esbuild-kit/core-utils': 3.3.2 - get-tsconfig: 4.13.7 + get-tsconfig: 4.14.3 '@esbuild/aix-ppc64@0.28.1': optional: true + '@esbuild/aix-ppc64@0.28.2': + optional: true + '@esbuild/android-arm64@0.28.1': optional: true + '@esbuild/android-arm64@0.28.2': + optional: true + '@esbuild/android-arm@0.28.1': optional: true + '@esbuild/android-arm@0.28.2': + optional: true + '@esbuild/android-x64@0.28.1': optional: true + '@esbuild/android-x64@0.28.2': + optional: true + '@esbuild/darwin-arm64@0.28.1': optional: true + '@esbuild/darwin-arm64@0.28.2': + optional: true + '@esbuild/darwin-x64@0.28.1': optional: true + '@esbuild/darwin-x64@0.28.2': + optional: true + '@esbuild/freebsd-arm64@0.28.1': optional: true + '@esbuild/freebsd-arm64@0.28.2': + optional: true + '@esbuild/freebsd-x64@0.28.1': optional: true + '@esbuild/freebsd-x64@0.28.2': + optional: true + '@esbuild/linux-arm64@0.28.1': optional: true + '@esbuild/linux-arm64@0.28.2': + optional: true + '@esbuild/linux-arm@0.28.1': optional: true + '@esbuild/linux-arm@0.28.2': + optional: true + '@esbuild/linux-ia32@0.28.1': optional: true + '@esbuild/linux-ia32@0.28.2': + optional: true + '@esbuild/linux-loong64@0.28.1': optional: true + '@esbuild/linux-loong64@0.28.2': + optional: true + '@esbuild/linux-mips64el@0.28.1': optional: true + '@esbuild/linux-mips64el@0.28.2': + optional: true + '@esbuild/linux-ppc64@0.28.1': optional: true - '@esbuild/linux-riscv64@0.28.1': + '@esbuild/linux-ppc64@0.28.2': optional: true - '@esbuild/linux-s390x@0.28.1': + '@esbuild/linux-riscv64@0.28.1': optional: true - '@esbuild/linux-x64@0.28.1': + '@esbuild/linux-riscv64@0.28.2': optional: true - '@esbuild/netbsd-arm64@0.28.1': + '@esbuild/linux-s390x@0.28.1': optional: true - '@esbuild/netbsd-x64@0.28.1': + '@esbuild/linux-s390x@0.28.2': optional: true - '@esbuild/openbsd-arm64@0.28.1': + '@esbuild/linux-x64@0.28.1': optional: true - '@esbuild/openbsd-x64@0.28.1': + '@esbuild/linux-x64@0.28.2': optional: true - '@esbuild/openharmony-arm64@0.28.1': + '@esbuild/netbsd-arm64@0.28.1': optional: true - '@esbuild/sunos-x64@0.28.1': + '@esbuild/netbsd-arm64@0.28.2': optional: true - '@esbuild/win32-arm64@0.28.1': + '@esbuild/netbsd-x64@0.28.1': optional: true - '@esbuild/win32-ia32@0.28.1': + '@esbuild/netbsd-x64@0.28.2': optional: true - '@esbuild/win32-x64@0.28.1': + '@esbuild/openbsd-arm64@0.28.1': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.1(jiti@2.6.1))': - dependencies: - eslint: 9.39.1(jiti@2.6.1) - eslint-visitor-keys: 3.4.3 + '@esbuild/openbsd-arm64@0.28.2': + optional: true - '@eslint-community/regexpp@4.12.2': {} + '@esbuild/openbsd-x64@0.28.1': + optional: true - '@eslint/compat@2.0.5(eslint@9.39.1(jiti@2.6.1))': - dependencies: - '@eslint/core': 1.2.1 - optionalDependencies: - eslint: 9.39.1(jiti@2.6.1) + '@esbuild/openbsd-x64@0.28.2': + optional: true - '@eslint/config-array@0.21.2': - dependencies: - '@eslint/object-schema': 2.1.7 - debug: 4.4.3 - minimatch: 3.1.5 - transitivePeerDependencies: - - supports-color + '@esbuild/openharmony-arm64@0.28.1': + optional: true - '@eslint/config-helpers@0.4.2': - dependencies: - '@eslint/core': 0.17.0 + '@esbuild/openharmony-arm64@0.28.2': + optional: true - '@eslint/core@0.17.0': - dependencies: - '@types/json-schema': 7.0.15 + '@esbuild/sunos-x64@0.28.1': + optional: true - '@eslint/core@1.2.1': - dependencies: - '@types/json-schema': 7.0.15 + '@esbuild/sunos-x64@0.28.2': + optional: true - '@eslint/eslintrc@3.3.5': - dependencies: - ajv: 6.14.0 - debug: 4.4.3 - espree: 10.4.0 - globals: 14.0.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 5.2.0 - minimatch: 3.1.5 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color + '@esbuild/win32-arm64@0.28.1': + optional: true - '@eslint/js@9.39.1': {} + '@esbuild/win32-arm64@0.28.2': + optional: true - '@eslint/object-schema@2.1.7': {} + '@esbuild/win32-ia32@0.28.1': + optional: true - '@eslint/plugin-kit@0.4.1': - dependencies: - '@eslint/core': 0.17.0 - levn: 0.4.1 + '@esbuild/win32-ia32@0.28.2': + optional: true - '@hono/zod-validator@0.7.6(hono@4.12.27)(zod@4.3.6)': - dependencies: - hono: 4.12.27 - zod: 4.3.6 + '@esbuild/win32-x64@0.28.1': + optional: true - '@humanfs/core@0.19.1': {} + '@esbuild/win32-x64@0.28.2': + optional: true - '@humanfs/node@0.16.7': + '@hono/zod-validator@0.9.1(hono@4.13.9)(zod@4.6.5)': dependencies: - '@humanfs/core': 0.19.1 - '@humanwhocodes/retry': 0.4.3 - - '@humanwhocodes/module-importer@1.0.1': {} - - '@humanwhocodes/retry@0.4.3': {} + hono: 4.13.9 + zod: 4.6.5 - '@ianvs/prettier-plugin-sort-imports@4.7.1(prettier@3.8.2)': + '@ianvs/prettier-plugin-sort-imports@4.7.1(prettier@3.9.9)(supports-color@10.2.2)': dependencies: - '@babel/generator': 7.29.1 - '@babel/parser': 7.29.2 - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - prettier: 3.8.2 - semver: 7.7.4 + '@babel/generator': 7.29.8 + '@babel/parser': 7.29.9 + '@babel/traverse': 7.29.8(supports-color@10.2.2) + '@babel/types': 7.29.8 + prettier: 3.9.9 + semver: 7.8.5 transitivePeerDependencies: - supports-color '@img/colour@1.1.0': {} - '@img/sharp-darwin-arm64@0.34.5': + '@img/sharp-darwin-arm64@0.35.4': optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-arm64': 1.3.3 optional: true - '@img/sharp-darwin-x64@0.34.5': + '@img/sharp-darwin-x64@0.35.4': optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.3.3 + optional: true + + '@img/sharp-freebsd-wasm32@0.35.4': + dependencies: + '@img/sharp-wasm32': 0.35.4 optional: true - '@img/sharp-libvips-darwin-arm64@1.2.4': + '@img/sharp-libvips-darwin-arm64@1.3.3': optional: true - '@img/sharp-libvips-darwin-x64@1.2.4': + '@img/sharp-libvips-darwin-x64@1.3.3': optional: true - '@img/sharp-libvips-linux-arm64@1.2.4': + '@img/sharp-libvips-linux-arm64@1.3.3': optional: true - '@img/sharp-libvips-linux-arm@1.2.4': + '@img/sharp-libvips-linux-arm@1.3.3': optional: true - '@img/sharp-libvips-linux-ppc64@1.2.4': + '@img/sharp-libvips-linux-ppc64@1.3.3': optional: true - '@img/sharp-libvips-linux-riscv64@1.2.4': + '@img/sharp-libvips-linux-riscv64@1.3.3': optional: true - '@img/sharp-libvips-linux-s390x@1.2.4': + '@img/sharp-libvips-linux-s390x@1.3.3': optional: true - '@img/sharp-libvips-linux-x64@1.2.4': + '@img/sharp-libvips-linux-x64@1.3.3': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + '@img/sharp-libvips-linuxmusl-arm64@1.3.3': optional: true - '@img/sharp-libvips-linuxmusl-x64@1.2.4': + '@img/sharp-libvips-linuxmusl-x64@1.3.3': optional: true - '@img/sharp-linux-arm64@0.34.5': + '@img/sharp-linux-arm64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.3.3 optional: true - '@img/sharp-linux-arm@0.34.5': + '@img/sharp-linux-arm@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.3.3 optional: true - '@img/sharp-linux-ppc64@0.34.5': + '@img/sharp-linux-ppc64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.3.3 optional: true - '@img/sharp-linux-riscv64@0.34.5': + '@img/sharp-linux-riscv64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.3.3 optional: true - '@img/sharp-linux-s390x@0.34.5': + '@img/sharp-linux-s390x@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.3.3 optional: true - '@img/sharp-linux-x64@0.34.5': + '@img/sharp-linux-x64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.3.3 optional: true - '@img/sharp-linuxmusl-arm64@0.34.5': + '@img/sharp-linuxmusl-arm64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.3 optional: true - '@img/sharp-linuxmusl-x64@0.34.5': + '@img/sharp-linuxmusl-x64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.3.3 + optional: true + + '@img/sharp-wasm32@0.35.4': + dependencies: + '@emnapi/runtime': 1.11.3 optional: true - '@img/sharp-wasm32@0.34.5': + '@img/sharp-webcontainers-wasm32@0.35.4': dependencies: - '@emnapi/runtime': 1.9.2 + '@img/sharp-wasm32': 0.35.4 optional: true - '@img/sharp-win32-arm64@0.34.5': + '@img/sharp-win32-arm64@0.35.4': optional: true - '@img/sharp-win32-ia32@0.34.5': + '@img/sharp-win32-ia32@0.35.4': optional: true - '@img/sharp-win32-x64@0.34.5': + '@img/sharp-win32-x64@0.35.4': optional: true '@jridgewell/gen-mapping@0.3.13': dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/sourcemap-codec': 1.6.0 '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/resolve-uri@3.1.2': {} - '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/sourcemap-codec@1.6.0': {} '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/sourcemap-codec': 1.6.0 '@jridgewell/trace-mapping@0.3.9': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/sourcemap-codec': 1.6.0 - '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + '@napi-rs/wasm-runtime@1.2.4(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)': dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.3 + '@emnapi/core': 1.11.2 + '@emnapi/runtime': 1.11.2 + '@tybys/wasm-util': 0.10.4 optional: true - '@oxc-project/types@0.133.0': {} - - '@poppinss/colors@4.1.6': - dependencies: - kleur: 4.1.5 - - '@poppinss/dumper@0.6.5': - dependencies: - '@poppinss/colors': 4.1.6 - '@sindresorhus/is': 7.2.0 - supports-color: 10.2.2 - - '@poppinss/exception@1.2.3': {} - - '@rolldown/binding-android-arm64@1.0.3': + '@oxc-parser/binding-android-arm-eabi@0.150.0': optional: true - '@rolldown/binding-darwin-arm64@1.0.3': + '@oxc-parser/binding-android-arm64@0.150.0': optional: true - '@rolldown/binding-darwin-x64@1.0.3': + '@oxc-parser/binding-darwin-arm64@0.150.0': optional: true - '@rolldown/binding-freebsd-x64@1.0.3': + '@oxc-parser/binding-darwin-x64@0.150.0': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + '@oxc-parser/binding-freebsd-x64@0.150.0': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.3': + '@oxc-parser/binding-linux-arm-gnueabihf@0.150.0': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.3': + '@oxc-parser/binding-linux-arm-musleabihf@0.150.0': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.0.3': + '@oxc-parser/binding-linux-arm64-gnu@0.150.0': optional: true - '@rolldown/binding-linux-s390x-gnu@1.0.3': + '@oxc-parser/binding-linux-arm64-musl@0.150.0': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.3': + '@oxc-parser/binding-linux-ppc64-gnu@0.150.0': optional: true - '@rolldown/binding-linux-x64-musl@1.0.3': + '@oxc-parser/binding-linux-riscv64-gnu@0.150.0': optional: true - '@rolldown/binding-openharmony-arm64@1.0.3': + '@oxc-parser/binding-linux-riscv64-musl@0.150.0': optional: true - '@rolldown/binding-wasm32-wasi@1.0.3': - dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@oxc-parser/binding-linux-s390x-gnu@0.150.0': optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.3': + '@oxc-parser/binding-linux-x64-gnu@0.150.0': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.3': + '@oxc-parser/binding-linux-x64-musl@0.150.0': optional: true - '@rolldown/pluginutils@1.0.1': {} - - '@rtsao/scc@1.1.0': {} - - '@sindresorhus/is@7.2.0': {} - - '@speed-highlight/core@1.2.15': {} - - '@standard-schema/spec@1.1.0': {} - - '@tybys/wasm-util@0.10.3': - dependencies: - tslib: 2.8.1 + '@oxc-parser/binding-openharmony-arm64@0.150.0': optional: true - '@types/chai@5.2.3': - dependencies: - '@types/deep-eql': 4.0.2 - assertion-error: 2.0.1 - - '@types/deep-eql@4.0.2': {} - - '@types/estree@1.0.8': {} - - '@types/gensync@1.0.5': {} - - '@types/jsesc@2.5.1': {} - - '@types/json-schema@7.0.15': {} - - '@types/json5@0.0.29': {} - - '@types/node@25.6.0': - dependencies: - undici-types: 7.19.2 - - '@typescript-eslint/eslint-plugin@8.58.1(@typescript-eslint/parser@8.58.1(eslint@9.39.1(jiti@2.6.1))(typescript@6.0.2))(eslint@9.39.1(jiti@2.6.1))(typescript@6.0.2)': - dependencies: - '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.58.1(eslint@9.39.1(jiti@2.6.1))(typescript@6.0.2) - '@typescript-eslint/scope-manager': 8.58.1 - '@typescript-eslint/type-utils': 8.58.1(eslint@9.39.1(jiti@2.6.1))(typescript@6.0.2) - '@typescript-eslint/utils': 8.58.1(eslint@9.39.1(jiti@2.6.1))(typescript@6.0.2) - '@typescript-eslint/visitor-keys': 8.58.1 - eslint: 9.39.1(jiti@2.6.1) - ignore: 7.0.5 - natural-compare: 1.4.0 - ts-api-utils: 2.5.0(typescript@6.0.2) - typescript: 6.0.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@8.58.1(eslint@9.39.1(jiti@2.6.1))(typescript@6.0.2)': - dependencies: - '@typescript-eslint/scope-manager': 8.58.1 - '@typescript-eslint/types': 8.58.1 - '@typescript-eslint/typescript-estree': 8.58.1(typescript@6.0.2) - '@typescript-eslint/visitor-keys': 8.58.1 - debug: 4.4.3 - eslint: 9.39.1(jiti@2.6.1) - typescript: 6.0.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/project-service@8.58.1(typescript@6.0.2)': - dependencies: - '@typescript-eslint/tsconfig-utils': 8.58.1(typescript@6.0.2) - '@typescript-eslint/types': 8.58.1 - debug: 4.4.3 - typescript: 6.0.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/scope-manager@8.58.1': - dependencies: - '@typescript-eslint/types': 8.58.1 - '@typescript-eslint/visitor-keys': 8.58.1 - - '@typescript-eslint/tsconfig-utils@8.58.1(typescript@6.0.2)': - dependencies: - typescript: 6.0.2 - - '@typescript-eslint/type-utils@8.58.1(eslint@9.39.1(jiti@2.6.1))(typescript@6.0.2)': - dependencies: - '@typescript-eslint/types': 8.58.1 - '@typescript-eslint/typescript-estree': 8.58.1(typescript@6.0.2) - '@typescript-eslint/utils': 8.58.1(eslint@9.39.1(jiti@2.6.1))(typescript@6.0.2) - debug: 4.4.3 - eslint: 9.39.1(jiti@2.6.1) - ts-api-utils: 2.5.0(typescript@6.0.2) - typescript: 6.0.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/types@8.58.1': {} - - '@typescript-eslint/typescript-estree@8.58.1(typescript@6.0.2)': - dependencies: - '@typescript-eslint/project-service': 8.58.1(typescript@6.0.2) - '@typescript-eslint/tsconfig-utils': 8.58.1(typescript@6.0.2) - '@typescript-eslint/types': 8.58.1 - '@typescript-eslint/visitor-keys': 8.58.1 - debug: 4.4.3 - minimatch: 10.2.5 - semver: 7.7.4 - tinyglobby: 0.2.17 - ts-api-utils: 2.5.0(typescript@6.0.2) - typescript: 6.0.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@8.58.1(eslint@9.39.1(jiti@2.6.1))(typescript@6.0.2)': - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.1(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.58.1 - '@typescript-eslint/types': 8.58.1 - '@typescript-eslint/typescript-estree': 8.58.1(typescript@6.0.2) - eslint: 9.39.1(jiti@2.6.1) - typescript: 6.0.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/visitor-keys@8.58.1': - dependencies: - '@typescript-eslint/types': 8.58.1 - eslint-visitor-keys: 5.0.1 - - '@vitest/expect@4.1.4': - dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.1.4 - '@vitest/utils': 4.1.4 - chai: 6.2.2 - tinyrainbow: 3.1.0 - - '@vitest/mocker@4.1.4(vite@8.0.16(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.21.0))': - dependencies: - '@vitest/spy': 4.1.4 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 8.0.16(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.21.0) - - '@vitest/pretty-format@4.1.4': - dependencies: - tinyrainbow: 3.1.0 - - '@vitest/runner@4.1.4': - dependencies: - '@vitest/utils': 4.1.4 - pathe: 2.0.3 - - '@vitest/snapshot@4.1.4': - dependencies: - '@vitest/pretty-format': 4.1.4 - '@vitest/utils': 4.1.4 - magic-string: 0.30.21 - pathe: 2.0.3 - - '@vitest/spy@4.1.4': {} - - '@vitest/utils@4.1.4': - dependencies: - '@vitest/pretty-format': 4.1.4 - convert-source-map: 2.0.0 - tinyrainbow: 3.1.0 - - acorn-jsx@5.3.2(acorn@8.16.0): - dependencies: - acorn: 8.16.0 - - acorn@8.16.0: {} - - ajv@6.14.0: - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - - 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.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-object-atoms: 1.1.1 - get-intrinsic: 1.3.0 - is-string: 1.1.1 - math-intrinsics: 1.1.0 - - array.prototype.findlast@1.2.5: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - es-shim-unscopables: 1.1.0 - - array.prototype.findlastindex@1.2.6: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - es-shim-unscopables: 1.1.0 - - array.prototype.flat@1.3.3: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-shim-unscopables: 1.1.0 - - array.prototype.flatmap@1.3.3: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-shim-unscopables: 1.1.0 - - array.prototype.tosorted@1.1.4: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-errors: 1.3.0 - es-shim-unscopables: 1.1.0 - - arraybuffer.prototype.slice@1.0.4: - dependencies: - array-buffer-byte-length: 1.0.2 - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.1 - 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.40: {} - - blake3-wasm@2.1.5: {} - - boolbase@1.0.0: {} - - brace-expansion@1.1.15: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 + '@oxc-parser/binding-win32-arm64-msvc@0.150.0': + optional: true - brace-expansion@5.0.6: - dependencies: - balanced-match: 4.0.4 + '@oxc-parser/binding-win32-ia32-msvc@0.150.0': + optional: true - browserslist@4.28.4: - dependencies: - baseline-browser-mapping: 2.10.40 - caniuse-lite: 1.0.30001799 - electron-to-chromium: 1.5.380 - node-releases: 2.0.50 - update-browserslist-db: 1.2.3(browserslist@4.28.4) + '@oxc-parser/binding-win32-x64-msvc@0.150.0': + optional: true - buffer-from@1.1.2: {} + '@oxc-project/types@0.150.0': {} - call-bind-apply-helpers@1.0.2: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 + '@oxc-project/types@0.151.0': {} - call-bind@1.0.8: - 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 + '@oxc-resolver/binding-android-arm-eabi@11.24.2': + optional: true - call-bound@1.0.4: - dependencies: - call-bind-apply-helpers: 1.0.2 - get-intrinsic: 1.3.0 + '@oxc-resolver/binding-android-arm64@11.24.2': + optional: true - callsites@3.1.0: {} + '@oxc-resolver/binding-darwin-arm64@11.24.2': + optional: true - caniuse-lite@1.0.30001799: {} + '@oxc-resolver/binding-darwin-x64@11.24.2': + optional: true - chai@6.2.2: {} + '@oxc-resolver/binding-freebsd-x64@11.24.2': + optional: true - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 + '@oxc-resolver/binding-linux-arm-gnueabihf@11.24.2': + optional: true - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 + '@oxc-resolver/binding-linux-arm-musleabihf@11.24.2': + optional: true - color-name@1.1.4: {} + '@oxc-resolver/binding-linux-arm64-gnu@11.24.2': + optional: true - concat-map@0.0.1: {} + '@oxc-resolver/binding-linux-arm64-musl@11.24.2': + optional: true - convert-source-map@2.0.0: {} + '@oxc-resolver/binding-linux-ppc64-gnu@11.24.2': + optional: true - cookie@1.1.1: {} + '@oxc-resolver/binding-linux-riscv64-gnu@11.24.2': + optional: true - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 + '@oxc-resolver/binding-linux-riscv64-musl@11.24.2': + optional: true - css-select@5.2.2: - dependencies: - boolbase: 1.0.0 - css-what: 6.2.2 - domhandler: 5.0.3 - domutils: 3.2.2 - nth-check: 2.1.1 + '@oxc-resolver/binding-linux-s390x-gnu@11.24.2': + optional: true - css-what@6.2.2: {} + '@oxc-resolver/binding-linux-x64-gnu@11.24.2': + optional: true - data-view-buffer@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 + '@oxc-resolver/binding-linux-x64-musl@11.24.2': + optional: true - data-view-byte-length@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 + '@oxc-resolver/binding-openharmony-arm64@11.24.2': + optional: true - data-view-byte-offset@1.0.1: + '@oxc-resolver/binding-wasm32-wasi@11.24.2': dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 + '@emnapi/core': 1.11.2 + '@emnapi/runtime': 1.11.2 + '@napi-rs/wasm-runtime': 1.2.4(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) + optional: true - debug@3.2.7: - dependencies: - ms: 2.1.3 + '@oxc-resolver/binding-win32-arm64-msvc@11.24.2': + optional: true - debug@4.4.3: - dependencies: - ms: 2.1.3 + '@oxc-resolver/binding-win32-x64-msvc@11.24.2': + optional: true - deep-is@0.1.4: {} + '@oxlint-tsgolint/darwin-arm64@7.0.2003': + optional: true - define-data-property@1.1.4: - dependencies: - es-define-property: 1.0.1 - es-errors: 1.3.0 - gopd: 1.2.0 + '@oxlint-tsgolint/darwin-x64@7.0.2003': + optional: true - define-properties@1.2.1: - dependencies: - define-data-property: 1.1.4 - has-property-descriptors: 1.0.2 - object-keys: 1.1.1 + '@oxlint-tsgolint/linux-arm64@7.0.2003': + optional: true - detect-libc@2.1.2: {} + '@oxlint-tsgolint/linux-x64@7.0.2003': + optional: true - doctrine@2.1.0: - dependencies: - esutils: 2.0.3 + '@oxlint-tsgolint/win32-arm64@7.0.2003': + optional: true - dom-serializer@2.0.0: - dependencies: - domelementtype: 2.3.0 - domhandler: 5.0.3 - entities: 4.5.0 + '@oxlint-tsgolint/win32-x64@7.0.2003': + optional: true - domelementtype@2.3.0: {} + '@oxlint/binding-android-arm-eabi@1.85.0': + optional: true - domhandler@5.0.3: - dependencies: - domelementtype: 2.3.0 + '@oxlint/binding-android-arm64@1.85.0': + optional: true - domutils@3.2.2: - dependencies: - dom-serializer: 2.0.0 - domelementtype: 2.3.0 - domhandler: 5.0.3 + '@oxlint/binding-darwin-arm64@1.85.0': + optional: true - drizzle-kit@0.31.10: - dependencies: - '@drizzle-team/brocli': 0.10.2 - '@esbuild-kit/esm-loader': 2.6.5 - esbuild: 0.28.1 - tsx: 4.21.0 + '@oxlint/binding-darwin-x64@1.85.0': + optional: true - drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260412.1): - optionalDependencies: - '@cloudflare/workers-types': 4.20260412.1 + '@oxlint/binding-freebsd-x64@1.85.0': + optional: true - dunder-proto@1.0.1: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 + '@oxlint/binding-linux-arm-gnueabihf@1.85.0': + optional: true - electron-to-chromium@1.5.380: {} + '@oxlint/binding-linux-arm-musleabihf@1.85.0': + optional: true - empathic@2.0.1: {} + '@oxlint/binding-linux-arm64-gnu@1.85.0': + optional: true - entities@4.5.0: {} + '@oxlint/binding-linux-arm64-musl@1.85.0': + optional: true - error-stack-parser-es@1.0.5: {} + '@oxlint/binding-linux-ppc64-gnu@1.85.0': + optional: true - es-abstract@1.24.1: - dependencies: - array-buffer-byte-length: 1.0.2 - arraybuffer.prototype.slice: 1.0.4 - available-typed-arrays: 1.0.7 - call-bind: 1.0.8 - 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.1 - es-set-tostringtag: 2.1.0 - es-to-primitive: 1.3.0 - function.prototype.name: 1.1.8 - 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.2 - 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.3 - 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.10 - string.prototype.trimend: 1.0.9 - 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.7 - unbox-primitive: 1.1.0 - which-typed-array: 1.1.20 - - es-define-property@1.0.1: {} - - es-errors@1.3.0: {} - - es-iterator-helpers@1.3.1: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-errors: 1.3.0 - es-set-tostringtag: 2.1.0 - function-bind: 1.1.2 - get-intrinsic: 1.3.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 - internal-slot: 1.1.0 - iterator.prototype: 1.1.5 - math-intrinsics: 1.1.0 - safe-array-concat: 1.1.3 - - es-module-lexer@2.0.0: {} - - es-object-atoms@1.1.1: - dependencies: - es-errors: 1.3.0 + '@oxlint/binding-linux-riscv64-gnu@1.85.0': + optional: true - 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.2 + '@oxlint/binding-linux-riscv64-musl@1.85.0': + optional: true - es-shim-unscopables@1.1.0: - dependencies: - hasown: 2.0.2 + '@oxlint/binding-linux-s390x-gnu@1.85.0': + optional: true - es-to-primitive@1.3.0: - dependencies: - is-callable: 1.2.7 - is-date-object: 1.1.0 - is-symbol: 1.1.1 + '@oxlint/binding-linux-x64-gnu@1.85.0': + optional: true - esbuild@0.28.1: - 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 + '@oxlint/binding-linux-x64-musl@1.85.0': + optional: true - escalade@3.2.0: {} + '@oxlint/binding-openharmony-arm64@1.85.0': + optional: true - escape-string-regexp@4.0.0: {} + '@oxlint/binding-win32-arm64-msvc@1.85.0': + optional: true - eslint-import-resolver-node@0.3.9: - dependencies: - debug: 3.2.7 - is-core-module: 2.16.1 - resolve: 1.22.11 - transitivePeerDependencies: - - supports-color + '@oxlint/binding-win32-ia32-msvc@1.85.0': + optional: true - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.58.1(eslint@9.39.1(jiti@2.6.1))(typescript@6.0.2))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)): - dependencies: - debug: 3.2.7 - optionalDependencies: - '@typescript-eslint/parser': 8.58.1(eslint@9.39.1(jiti@2.6.1))(typescript@6.0.2) - eslint: 9.39.1(jiti@2.6.1) - eslint-import-resolver-node: 0.3.9 - transitivePeerDependencies: - - supports-color + '@oxlint/binding-win32-x64-msvc@1.85.0': + optional: true - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.1(eslint@9.39.1(jiti@2.6.1))(typescript@6.0.2))(eslint@9.39.1(jiti@2.6.1)): + '@poppinss/colors@4.1.6': 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: 9.39.1(jiti@2.6.1) - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.58.1(eslint@9.39.1(jiti@2.6.1))(typescript@6.0.2))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)) - hasown: 2.0.2 - is-core-module: 2.16.1 - is-glob: 4.0.3 - minimatch: 3.1.5 - object.fromentries: 2.0.8 - object.groupby: 1.0.3 - object.values: 1.2.1 - semver: 6.3.1 - string.prototype.trimend: 1.0.9 - tsconfig-paths: 3.15.0 - optionalDependencies: - '@typescript-eslint/parser': 8.58.1(eslint@9.39.1(jiti@2.6.1))(typescript@6.0.2) - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color + kleur: 4.1.5 - eslint-plugin-react-hooks@7.0.1(eslint@9.39.1(jiti@2.6.1)): - dependencies: - '@babel/core': 8.0.1 - '@babel/parser': 7.29.2 - eslint: 9.39.1(jiti@2.6.1) - hermes-parser: 0.25.1 - zod: 4.3.6 - zod-validation-error: 4.0.2(zod@4.3.6) - - eslint-plugin-react@7.37.5(eslint@9.39.1(jiti@2.6.1)): - dependencies: - array-includes: 3.1.9 - array.prototype.findlast: 1.2.5 - array.prototype.flatmap: 1.3.3 - array.prototype.tosorted: 1.1.4 - doctrine: 2.1.0 - es-iterator-helpers: 1.3.1 - eslint: 9.39.1(jiti@2.6.1) - estraverse: 5.3.0 - hasown: 2.0.2 - jsx-ast-utils: 3.3.5 - minimatch: 3.1.5 - object.entries: 1.1.9 - object.fromentries: 2.0.8 - object.values: 1.2.1 - prop-types: 15.8.1 - resolve: 2.0.0-next.6 - semver: 6.3.1 - string.prototype.matchall: 4.0.12 - string.prototype.repeat: 1.0.0 - - eslint-scope@8.4.0: + '@poppinss/dumper@0.6.5': dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - - eslint-visitor-keys@3.4.3: {} + '@poppinss/colors': 4.1.6 + '@sindresorhus/is': 7.2.0 + supports-color: 10.2.2 - eslint-visitor-keys@4.2.1: {} + '@poppinss/exception@1.2.3': {} - eslint-visitor-keys@5.0.1: {} + '@rolldown/binding-android-arm-eabi@1.2.11': + optional: true - eslint@9.39.1(jiti@2.6.1): - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.1(jiti@2.6.1)) - '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.21.2 - '@eslint/config-helpers': 0.4.2 - '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.5 - '@eslint/js': 9.39.1 - '@eslint/plugin-kit': 0.4.1 - '@humanfs/node': 0.16.7 - '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.3 - '@types/estree': 1.0.8 - ajv: 6.14.0 - chalk: 4.1.2 - cross-spawn: 7.0.6 - debug: 4.4.3 - escape-string-regexp: 4.0.0 - eslint-scope: 8.4.0 - eslint-visitor-keys: 4.2.1 - espree: 10.4.0 - esquery: 1.7.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 8.0.0 - find-up: 5.0.0 - glob-parent: 6.0.2 - ignore: 5.3.2 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - json-stable-stringify-without-jsonify: 1.0.1 - lodash.merge: 4.6.2 - minimatch: 3.1.5 - natural-compare: 1.4.0 - optionator: 0.9.4 - optionalDependencies: - jiti: 2.6.1 - transitivePeerDependencies: - - supports-color + '@rolldown/binding-android-arm64@1.2.11': + optional: true - espree@10.4.0: - dependencies: - acorn: 8.16.0 - acorn-jsx: 5.3.2(acorn@8.16.0) - eslint-visitor-keys: 4.2.1 + '@rolldown/binding-darwin-arm64@1.2.11': + optional: true - esquery@1.7.0: - dependencies: - estraverse: 5.3.0 + '@rolldown/binding-darwin-x64@1.2.11': + optional: true - esrecurse@4.3.0: - dependencies: - estraverse: 5.3.0 + '@rolldown/binding-freebsd-x64@1.2.11': + optional: true - estraverse@5.3.0: {} + '@rolldown/binding-linux-arm-gnueabihf@1.2.11': + optional: true - estree-walker@3.0.3: - dependencies: - '@types/estree': 1.0.8 + '@rolldown/binding-linux-arm64-gnu@1.2.11': + optional: true - esutils@2.0.3: {} + '@rolldown/binding-linux-arm64-musl@1.2.11': + optional: true - expect-type@1.3.0: {} + '@rolldown/binding-linux-ppc64-gnu@1.2.11': + optional: true - fast-deep-equal@3.1.3: {} + '@rolldown/binding-linux-s390x-gnu@1.2.11': + optional: true - fast-json-stable-stringify@2.1.0: {} + '@rolldown/binding-linux-x64-gnu@1.2.11': + optional: true - fast-levenshtein@2.0.6: {} + '@rolldown/binding-linux-x64-musl@1.2.11': + optional: true - fdir@6.5.0(picomatch@4.0.4): - optionalDependencies: - picomatch: 4.0.4 + '@rolldown/binding-openharmony-arm64@1.2.11': + optional: true - file-entry-cache@8.0.0: - dependencies: - flat-cache: 4.0.1 + '@rolldown/binding-win32-arm64-msvc@1.2.11': + optional: true - find-up@5.0.0: - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 + '@rolldown/binding-win32-x64-msvc@1.2.11': + optional: true - flat-cache@4.0.1: - dependencies: - flatted: 3.4.2 - keyv: 4.5.4 + '@rolldown/pluginutils@1.0.1': {} - flatted@3.4.2: {} + '@sindresorhus/is@7.2.0': {} - for-each@0.3.5: - dependencies: - is-callable: 1.2.7 + '@speed-highlight/core@1.2.24': {} - fsevents@2.3.3: + '@turbo/darwin-64@2.11.4': optional: true - function-bind@1.1.2: {} - - function.prototype.name@1.1.8: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - functions-have-names: 1.2.3 - hasown: 2.0.2 - is-callable: 1.2.7 + '@turbo/darwin-arm64@2.11.4': + optional: true - functions-have-names@1.2.3: {} + '@turbo/linux-64@2.11.4': + optional: true - generator-function@2.0.1: {} + '@turbo/linux-arm64@2.11.4': + optional: true - gensync@1.0.0-beta.2: {} + '@turbo/windows-64@2.11.4': + optional: true - get-intrinsic@1.3.0: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - math-intrinsics: 1.1.0 - - get-proto@1.0.1: - dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 + '@turbo/windows-arm64@2.11.4': + optional: true - get-symbol-description@1.1.0: + '@tybys/wasm-util@0.10.4': dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 + tslib: 2.8.1 + optional: true - get-tsconfig@4.13.7: + '@types/chai@5.2.3': dependencies: - resolve-pkg-maps: 1.0.0 + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 - glob-parent@6.0.2: - dependencies: - is-glob: 4.0.3 + '@types/deep-eql@4.0.2': {} - globals@14.0.0: {} + '@types/estree@1.0.9': {} - globalthis@1.0.4: + '@types/node@24.13.6': dependencies: - define-properties: 1.2.1 - gopd: 1.2.0 - - gopd@1.2.0: {} + undici-types: 7.18.2 - has-bigints@1.1.0: {} + '@typescript/typescript-aix-ppc64@7.0.2': + optional: true - has-flag@4.0.0: {} + '@typescript/typescript-darwin-arm64@7.0.2': + optional: true - has-property-descriptors@1.0.2: - dependencies: - es-define-property: 1.0.1 + '@typescript/typescript-darwin-x64@7.0.2': + optional: true - has-proto@1.2.0: - dependencies: - dunder-proto: 1.0.1 + '@typescript/typescript-freebsd-arm64@7.0.2': + optional: true - has-symbols@1.1.0: {} + '@typescript/typescript-freebsd-x64@7.0.2': + optional: true - has-tostringtag@1.0.2: - dependencies: - has-symbols: 1.1.0 + '@typescript/typescript-linux-arm64@7.0.2': + optional: true - hasown@2.0.2: - dependencies: - function-bind: 1.1.2 + '@typescript/typescript-linux-arm@7.0.2': + optional: true - he@1.2.0: {} + '@typescript/typescript-linux-loong64@7.0.2': + optional: true - hermes-estree@0.25.1: {} + '@typescript/typescript-linux-mips64el@7.0.2': + optional: true - hermes-parser@0.25.1: - dependencies: - hermes-estree: 0.25.1 + '@typescript/typescript-linux-ppc64@7.0.2': + optional: true - hono@4.12.27: {} + '@typescript/typescript-linux-riscv64@7.0.2': + optional: true - ignore@5.3.2: {} + '@typescript/typescript-linux-s390x@7.0.2': + optional: true - ignore@7.0.5: {} + '@typescript/typescript-linux-x64@7.0.2': + optional: true - import-fresh@3.3.1: - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 + '@typescript/typescript-netbsd-arm64@7.0.2': + optional: true - import-meta-resolve@4.2.0: {} + '@typescript/typescript-netbsd-x64@7.0.2': + optional: true - imurmurhash@0.1.4: {} + '@typescript/typescript-openbsd-arm64@7.0.2': + optional: true - internal-slot@1.1.0: - dependencies: - es-errors: 1.3.0 - hasown: 2.0.2 - side-channel: 1.1.0 + '@typescript/typescript-openbsd-x64@7.0.2': + optional: true - is-array-buffer@3.0.5: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - get-intrinsic: 1.3.0 + '@typescript/typescript-sunos-x64@7.0.2': + optional: true - 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 + '@typescript/typescript-win32-arm64@7.0.2': + optional: true - is-bigint@1.1.0: - dependencies: - has-bigints: 1.1.0 + '@typescript/typescript-win32-x64@7.0.2': + optional: true - is-boolean-object@1.2.2: + '@vitest/mocker@5.0.1(vite@8.3.1(@types/node@24.13.6))': dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.1 + estree-walker: 3.0.3 + magic-string: 1.4.2 + optionalDependencies: + vite: 8.3.1(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1) - is-callable@1.2.7: {} + '@vitest/spy@5.0.1': {} - is-core-module@2.16.1: - dependencies: - hasown: 2.0.2 + assertion-error@2.0.1: {} - is-data-view@1.0.2: - dependencies: - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - is-typed-array: 1.1.15 + blake3-wasm@2.1.5: {} - is-date-object@1.1.0: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 + boolbase@1.0.0: {} - is-extglob@2.1.1: {} + buffer-from@1.1.2: {} - is-finalizationregistry@1.1.1: - dependencies: - call-bound: 1.0.4 + chai@6.2.2: {} - 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 + cookie@1.1.1: {} - is-glob@4.0.3: + css-select@5.2.2: dependencies: - is-extglob: 2.1.1 - - is-map@2.0.3: {} + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 - is-negative-zero@2.0.3: {} + css-what@6.2.2: {} - is-number-object@1.1.1: + debug@4.4.3(supports-color@10.2.2): dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 + ms: 2.1.3 + optionalDependencies: + supports-color: 10.2.2 + + detect-libc@2.1.2: {} - is-regex@1.2.1: + dom-serializer@2.0.0: dependencies: - call-bound: 1.0.4 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - hasown: 2.0.2 + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 - is-set@2.0.3: {} + domelementtype@2.3.0: {} - is-shared-array-buffer@1.0.4: + domhandler@5.0.3: dependencies: - call-bound: 1.0.4 + domelementtype: 2.3.0 - is-string@1.1.1: + domutils@3.2.2: dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 - is-symbol@1.1.1: + drizzle-kit@0.31.11: dependencies: - call-bound: 1.0.4 - has-symbols: 1.1.0 - safe-regex-test: 1.1.0 + '@drizzle-team/brocli': 0.10.2 + '@esbuild-kit/esm-loader': 2.6.5 + esbuild: 0.28.2 + tsx: 4.23.15 - is-typed-array@1.1.15: - dependencies: - which-typed-array: 1.1.20 + drizzle-orm@0.45.3(@cloudflare/workers-types@5.20260925.1): + optionalDependencies: + '@cloudflare/workers-types': 5.20260925.1 - is-weakmap@2.0.2: {} + entities@4.5.0: {} - is-weakref@1.1.1: - dependencies: - call-bound: 1.0.4 + error-stack-parser-es@1.0.5: {} - is-weakset@2.0.4: - dependencies: - call-bound: 1.0.4 - get-intrinsic: 1.3.0 + es-module-lexer@2.3.2: {} - isarray@2.0.5: {} + esbuild@0.28.1: + 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 - isexe@2.0.0: {} + esbuild@0.28.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.2 + '@esbuild/android-arm': 0.28.2 + '@esbuild/android-arm64': 0.28.2 + '@esbuild/android-x64': 0.28.2 + '@esbuild/darwin-arm64': 0.28.2 + '@esbuild/darwin-x64': 0.28.2 + '@esbuild/freebsd-arm64': 0.28.2 + '@esbuild/freebsd-x64': 0.28.2 + '@esbuild/linux-arm': 0.28.2 + '@esbuild/linux-arm64': 0.28.2 + '@esbuild/linux-ia32': 0.28.2 + '@esbuild/linux-loong64': 0.28.2 + '@esbuild/linux-mips64el': 0.28.2 + '@esbuild/linux-ppc64': 0.28.2 + '@esbuild/linux-riscv64': 0.28.2 + '@esbuild/linux-s390x': 0.28.2 + '@esbuild/linux-x64': 0.28.2 + '@esbuild/netbsd-arm64': 0.28.2 + '@esbuild/netbsd-x64': 0.28.2 + '@esbuild/openbsd-arm64': 0.28.2 + '@esbuild/openbsd-x64': 0.28.2 + '@esbuild/openharmony-arm64': 0.28.2 + '@esbuild/sunos-x64': 0.28.2 + '@esbuild/win32-arm64': 0.28.2 + '@esbuild/win32-ia32': 0.28.2 + '@esbuild/win32-x64': 0.28.2 - iterator.prototype@1.1.5: + estree-walker@3.0.3: dependencies: - define-data-property: 1.1.4 - es-object-atoms: 1.1.1 - get-intrinsic: 1.3.0 - get-proto: 1.0.1 - has-symbols: 1.1.0 - set-function-name: 2.0.2 + '@types/estree': 1.0.9 - jiti@2.6.1: {} + expect-type@1.4.0: {} - js-tokens@10.0.0: {} + fd-package-json@2.0.0: + dependencies: + walk-up-path: 4.0.0 - js-tokens@4.0.0: {} + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 - js-yaml@5.2.0: + formatly@0.7.1: dependencies: - argparse: 2.0.1 - - jsesc@3.1.0: {} + fd-package-json: 2.0.0 + package-manager-detector: 1.8.0 - json-buffer@3.0.1: {} + fsevents@2.3.3: + optional: true - json-schema-traverse@0.4.1: {} + get-tsconfig@4.14.3: + dependencies: + resolve-pkg-maps: 1.0.0 - json-stable-stringify-without-jsonify@1.0.1: {} + he@1.2.0: {} - json5@1.0.2: - dependencies: - minimist: 1.2.8 + hono@4.13.9: {} - json5@2.2.3: {} + jiti@2.7.0: {} - jsx-ast-utils@3.3.5: - dependencies: - array-includes: 3.1.9 - array.prototype.flat: 1.3.3 - object.assign: 4.1.7 - object.values: 1.2.1 + js-tokens@4.0.0: {} - keyv@4.5.4: - dependencies: - json-buffer: 3.0.1 + jsesc@3.1.0: {} kleur@4.1.5: {} - levn@0.4.1: - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 + knip@6.38.0: + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + formatly: 0.7.1 + get-tsconfig: 4.14.3 + jiti: 2.7.0 + oxc-parser: 0.150.0 + oxc-resolver: 11.24.2 + picomatch: 4.0.7 + smol-toml: 1.9.0 + strip-json-comments: 5.0.3 + tinyglobby: 0.2.17 + unbash: 4.0.11 + yaml: 2.9.1 + zod: 4.6.5 - lightningcss-android-arm64@1.32.0: + lightningcss-android-arm64@1.33.0: optional: true - lightningcss-darwin-arm64@1.32.0: + lightningcss-darwin-arm64@1.33.0: optional: true - lightningcss-darwin-x64@1.32.0: + lightningcss-darwin-x64@1.33.0: optional: true - lightningcss-freebsd-x64@1.32.0: + lightningcss-freebsd-x64@1.33.0: optional: true - lightningcss-linux-arm-gnueabihf@1.32.0: + lightningcss-linux-arm-gnueabihf@1.33.0: optional: true - lightningcss-linux-arm64-gnu@1.32.0: + lightningcss-linux-arm64-gnu@1.33.0: optional: true - lightningcss-linux-arm64-musl@1.32.0: + lightningcss-linux-arm64-musl@1.33.0: optional: true - lightningcss-linux-x64-gnu@1.32.0: + lightningcss-linux-x64-gnu@1.33.0: optional: true - lightningcss-linux-x64-musl@1.32.0: + lightningcss-linux-x64-musl@1.33.0: optional: true - lightningcss-win32-arm64-msvc@1.32.0: + lightningcss-win32-arm64-msvc@1.33.0: optional: true - lightningcss-win32-x64-msvc@1.32.0: + lightningcss-win32-x64-msvc@1.33.0: optional: true - lightningcss@1.32.0: + lightningcss@1.33.0: dependencies: detect-libc: 2.1.2 optionalDependencies: - lightningcss-android-arm64: 1.32.0 - lightningcss-darwin-arm64: 1.32.0 - lightningcss-darwin-x64: 1.32.0 - lightningcss-freebsd-x64: 1.32.0 - lightningcss-linux-arm-gnueabihf: 1.32.0 - lightningcss-linux-arm64-gnu: 1.32.0 - lightningcss-linux-arm64-musl: 1.32.0 - lightningcss-linux-x64-gnu: 1.32.0 - lightningcss-linux-x64-musl: 1.32.0 - lightningcss-win32-arm64-msvc: 1.32.0 - lightningcss-win32-x64-msvc: 1.32.0 - - locate-path@6.0.0: - dependencies: - p-locate: 5.0.0 - - lodash.merge@4.6.2: {} - - loose-envify@1.4.0: - dependencies: - js-tokens: 4.0.0 + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 - lru-cache@11.5.1: {} - - magic-string@0.30.21: + magic-string@1.4.2: dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - - math-intrinsics@1.1.0: {} + '@jridgewell/sourcemap-codec': 1.6.0 - miniflare@4.20260409.0: + miniflare@5.20260923.0-alpha(@types/node@24.13.6): dependencies: '@cspotcode/source-map-support': 0.8.1 - sharp: 0.34.5 - undici: 8.5.0 - workerd: 1.20260409.1 + sharp: 0.35.4(@types/node@24.13.6) + undici: 7.30.0 + workerd: 1.20260923.1 ws: 8.21.0 youch: 4.1.0-beta.10 transitivePeerDependencies: + - '@types/node' - bufferutil - utf-8-validate - minimatch@10.2.5: - dependencies: - brace-expansion: 5.0.6 - - minimatch@3.1.5: - dependencies: - brace-expansion: 1.1.15 - - minimist@1.2.8: {} - ms@2.1.3: {} - nanoid@3.3.15: {} - - natural-compare@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 + nanoid@3.3.19: {} node-html-markdown@2.0.0: dependencies: @@ -4426,93 +3483,97 @@ snapshots: css-select: 5.2.2 he: 1.2.0 - node-releases@2.0.50: {} - nth-check@2.1.1: dependencies: boolbase: 1.0.0 - object-assign@4.1.1: {} - - object-inspect@1.13.4: {} - - object-keys@1.1.1: {} - - object.assign@4.1.7: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - has-symbols: 1.1.0 - object-keys: 1.1.1 - - object.entries@1.1.9: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - - object.fromentries@2.0.8: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-object-atoms: 1.1.1 - - object.groupby@1.0.3: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.1 - - object.values@1.2.1: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - - obug@2.1.3: {} + obug@2.2.1: {} - openai@6.34.0(ws@8.21.0)(zod@4.3.6): + openai@7.23.0(undici@7.30.0)(ws@8.21.0)(zod@4.6.5): optionalDependencies: + undici: 7.30.0 ws: 8.21.0 - zod: 4.3.6 - - optionator@0.9.4: - dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - 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-limit@3.1.0: - dependencies: - yocto-queue: 0.1.0 - - p-locate@5.0.0: - dependencies: - p-limit: 3.1.0 + zod: 4.6.5 - parent-module@1.0.1: + oxc-parser@0.150.0: dependencies: - callsites: 3.1.0 - - path-exists@4.0.0: {} - - path-key@3.1.1: {} - - path-parse@1.0.7: {} + '@oxc-project/types': 0.150.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.150.0 + '@oxc-parser/binding-android-arm64': 0.150.0 + '@oxc-parser/binding-darwin-arm64': 0.150.0 + '@oxc-parser/binding-darwin-x64': 0.150.0 + '@oxc-parser/binding-freebsd-x64': 0.150.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.150.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.150.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.150.0 + '@oxc-parser/binding-linux-arm64-musl': 0.150.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.150.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.150.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.150.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.150.0 + '@oxc-parser/binding-linux-x64-gnu': 0.150.0 + '@oxc-parser/binding-linux-x64-musl': 0.150.0 + '@oxc-parser/binding-openharmony-arm64': 0.150.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.150.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.150.0 + '@oxc-parser/binding-win32-x64-msvc': 0.150.0 + + oxc-resolver@11.24.2: + optionalDependencies: + '@oxc-resolver/binding-android-arm-eabi': 11.24.2 + '@oxc-resolver/binding-android-arm64': 11.24.2 + '@oxc-resolver/binding-darwin-arm64': 11.24.2 + '@oxc-resolver/binding-darwin-x64': 11.24.2 + '@oxc-resolver/binding-freebsd-x64': 11.24.2 + '@oxc-resolver/binding-linux-arm-gnueabihf': 11.24.2 + '@oxc-resolver/binding-linux-arm-musleabihf': 11.24.2 + '@oxc-resolver/binding-linux-arm64-gnu': 11.24.2 + '@oxc-resolver/binding-linux-arm64-musl': 11.24.2 + '@oxc-resolver/binding-linux-ppc64-gnu': 11.24.2 + '@oxc-resolver/binding-linux-riscv64-gnu': 11.24.2 + '@oxc-resolver/binding-linux-riscv64-musl': 11.24.2 + '@oxc-resolver/binding-linux-s390x-gnu': 11.24.2 + '@oxc-resolver/binding-linux-x64-gnu': 11.24.2 + '@oxc-resolver/binding-linux-x64-musl': 11.24.2 + '@oxc-resolver/binding-openharmony-arm64': 11.24.2 + '@oxc-resolver/binding-wasm32-wasi': 11.24.2 + '@oxc-resolver/binding-win32-arm64-msvc': 11.24.2 + '@oxc-resolver/binding-win32-x64-msvc': 11.24.2 + + oxlint-tsgolint@7.0.2003: + optionalDependencies: + '@oxlint-tsgolint/darwin-arm64': 7.0.2003 + '@oxlint-tsgolint/darwin-x64': 7.0.2003 + '@oxlint-tsgolint/linux-arm64': 7.0.2003 + '@oxlint-tsgolint/linux-x64': 7.0.2003 + '@oxlint-tsgolint/win32-arm64': 7.0.2003 + '@oxlint-tsgolint/win32-x64': 7.0.2003 + + oxlint@1.85.0(oxlint-tsgolint@7.0.2003): + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.85.0 + '@oxlint/binding-android-arm64': 1.85.0 + '@oxlint/binding-darwin-arm64': 1.85.0 + '@oxlint/binding-darwin-x64': 1.85.0 + '@oxlint/binding-freebsd-x64': 1.85.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.85.0 + '@oxlint/binding-linux-arm-musleabihf': 1.85.0 + '@oxlint/binding-linux-arm64-gnu': 1.85.0 + '@oxlint/binding-linux-arm64-musl': 1.85.0 + '@oxlint/binding-linux-ppc64-gnu': 1.85.0 + '@oxlint/binding-linux-riscv64-gnu': 1.85.0 + '@oxlint/binding-linux-riscv64-musl': 1.85.0 + '@oxlint/binding-linux-s390x-gnu': 1.85.0 + '@oxlint/binding-linux-x64-gnu': 1.85.0 + '@oxlint/binding-linux-x64-musl': 1.85.0 + '@oxlint/binding-openharmony-arm64': 1.85.0 + '@oxlint/binding-win32-arm64-msvc': 1.85.0 + '@oxlint/binding-win32-ia32-msvc': 1.85.0 + '@oxlint/binding-win32-x64-msvc': 1.85.0 + oxlint-tsgolint: 7.0.2003 + + package-manager-detector@1.8.0: {} path-to-regexp@6.3.0: {} @@ -4520,210 +3581,78 @@ snapshots: picocolors@1.1.1: {} - picomatch@4.0.4: {} - - possible-typed-array-names@1.1.0: {} + picomatch@4.0.7: {} - postcss@8.5.15: + postcss@8.5.28: dependencies: - nanoid: 3.3.15 + nanoid: 3.3.19 picocolors: 1.1.1 source-map-js: 1.2.1 - prelude-ls@1.2.1: {} - - prettier-plugin-tailwindcss@0.7.2(@ianvs/prettier-plugin-sort-imports@4.7.1(prettier@3.8.2))(prettier@3.8.2): - dependencies: - prettier: 3.8.2 - optionalDependencies: - '@ianvs/prettier-plugin-sort-imports': 4.7.1(prettier@3.8.2) - - prettier@3.8.2: {} - - prop-types@15.8.1: - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react-is: 16.13.1 - - punycode@2.3.1: {} - - react-is@16.13.1: {} - - reflect.getprototypeof@1.0.10: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - 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.8 - 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 - - resolve-from@4.0.0: {} + prettier@3.9.9: {} resolve-pkg-maps@1.0.0: {} - resolve@1.22.11: + rolldown@1.2.11: dependencies: - is-core-module: 2.16.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - resolve@2.0.0-next.6: - dependencies: - es-errors: 1.3.0 - is-core-module: 2.16.1 - node-exports-info: 1.6.0 - object-keys: 1.1.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - rolldown@1.0.3: - dependencies: - '@oxc-project/types': 0.133.0 + '@oxc-project/types': 0.151.0 '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.3 - '@rolldown/binding-darwin-arm64': 1.0.3 - '@rolldown/binding-darwin-x64': 1.0.3 - '@rolldown/binding-freebsd-x64': 1.0.3 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.3 - '@rolldown/binding-linux-arm64-gnu': 1.0.3 - '@rolldown/binding-linux-arm64-musl': 1.0.3 - '@rolldown/binding-linux-ppc64-gnu': 1.0.3 - '@rolldown/binding-linux-s390x-gnu': 1.0.3 - '@rolldown/binding-linux-x64-gnu': 1.0.3 - '@rolldown/binding-linux-x64-musl': 1.0.3 - '@rolldown/binding-openharmony-arm64': 1.0.3 - '@rolldown/binding-wasm32-wasi': 1.0.3 - '@rolldown/binding-win32-arm64-msvc': 1.0.3 - '@rolldown/binding-win32-x64-msvc': 1.0.3 - - safe-array-concat@1.1.3: - dependencies: - call-bind: 1.0.8 - 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 - - semver@6.3.1: {} - - semver@7.7.4: {} + '@rolldown/binding-android-arm-eabi': 1.2.11 + '@rolldown/binding-android-arm64': 1.2.11 + '@rolldown/binding-darwin-arm64': 1.2.11 + '@rolldown/binding-darwin-x64': 1.2.11 + '@rolldown/binding-freebsd-x64': 1.2.11 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.11 + '@rolldown/binding-linux-arm64-gnu': 1.2.11 + '@rolldown/binding-linux-arm64-musl': 1.2.11 + '@rolldown/binding-linux-ppc64-gnu': 1.2.11 + '@rolldown/binding-linux-s390x-gnu': 1.2.11 + '@rolldown/binding-linux-x64-gnu': 1.2.11 + '@rolldown/binding-linux-x64-musl': 1.2.11 + '@rolldown/binding-openharmony-arm64': 1.2.11 + '@rolldown/binding-win32-arm64-msvc': 1.2.11 + '@rolldown/binding-win32-x64-msvc': 1.2.11 semver@7.8.5: {} - 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.1 - - sharp@0.34.5: + sharp@0.35.4(@types/node@24.13.6): dependencies: '@img/colour': 1.1.0 detect-libc: 2.1.2 - semver: 7.7.4 + semver: 7.8.5 optionalDependencies: - '@img/sharp-darwin-arm64': 0.34.5 - '@img/sharp-darwin-x64': 0.34.5 - '@img/sharp-libvips-darwin-arm64': 1.2.4 - '@img/sharp-libvips-darwin-x64': 1.2.4 - '@img/sharp-libvips-linux-arm': 1.2.4 - '@img/sharp-libvips-linux-arm64': 1.2.4 - '@img/sharp-libvips-linux-ppc64': 1.2.4 - '@img/sharp-libvips-linux-riscv64': 1.2.4 - '@img/sharp-libvips-linux-s390x': 1.2.4 - '@img/sharp-libvips-linux-x64': 1.2.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 - '@img/sharp-linux-arm': 0.34.5 - '@img/sharp-linux-arm64': 0.34.5 - '@img/sharp-linux-ppc64': 0.34.5 - '@img/sharp-linux-riscv64': 0.34.5 - '@img/sharp-linux-s390x': 0.34.5 - '@img/sharp-linux-x64': 0.34.5 - '@img/sharp-linuxmusl-arm64': 0.34.5 - '@img/sharp-linuxmusl-x64': 0.34.5 - '@img/sharp-wasm32': 0.34.5 - '@img/sharp-win32-arm64': 0.34.5 - '@img/sharp-win32-ia32': 0.34.5 - '@img/sharp-win32-x64': 0.34.5 - - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - - side-channel-list@1.0.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - - side-channel-map@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - - side-channel-weakmap@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - side-channel-map: 1.0.1 - - side-channel@1.1.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - side-channel-list: 1.0.0 - side-channel-map: 1.0.1 - side-channel-weakmap: 1.0.2 + '@img/sharp-darwin-arm64': 0.35.4 + '@img/sharp-darwin-x64': 0.35.4 + '@img/sharp-freebsd-wasm32': 0.35.4 + '@img/sharp-libvips-darwin-arm64': 1.3.3 + '@img/sharp-libvips-darwin-x64': 1.3.3 + '@img/sharp-libvips-linux-arm': 1.3.3 + '@img/sharp-libvips-linux-arm64': 1.3.3 + '@img/sharp-libvips-linux-ppc64': 1.3.3 + '@img/sharp-libvips-linux-riscv64': 1.3.3 + '@img/sharp-libvips-linux-s390x': 1.3.3 + '@img/sharp-libvips-linux-x64': 1.3.3 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.3 + '@img/sharp-libvips-linuxmusl-x64': 1.3.3 + '@img/sharp-linux-arm': 0.35.4 + '@img/sharp-linux-arm64': 0.35.4 + '@img/sharp-linux-ppc64': 0.35.4 + '@img/sharp-linux-riscv64': 0.35.4 + '@img/sharp-linux-s390x': 0.35.4 + '@img/sharp-linux-x64': 0.35.4 + '@img/sharp-linuxmusl-arm64': 0.35.4 + '@img/sharp-linuxmusl-x64': 0.35.4 + '@img/sharp-webcontainers-wasm32': 0.35.4 + '@img/sharp-win32-arm64': 0.35.4 + '@img/sharp-win32-ia32': 0.35.4 + '@img/sharp-win32-x64': 0.35.4 + '@types/node': 24.13.6 siginfo@2.0.0: {} + smol-toml@1.9.0: {} + source-map-js@1.2.1: {} source-map-support@0.5.21: @@ -4735,297 +3664,144 @@ snapshots: stackback@0.0.2: {} - std-env@4.0.0: {} - - stop-iteration-iterator@1.1.0: - dependencies: - es-errors: 1.3.0 - internal-slot: 1.1.0 - - string.prototype.matchall@4.0.12: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - get-intrinsic: 1.3.0 - gopd: 1.2.0 - has-symbols: 1.1.0 - internal-slot: 1.1.0 - regexp.prototype.flags: 1.5.4 - set-function-name: 2.0.2 - side-channel: 1.1.0 - - string.prototype.repeat@1.0.0: - dependencies: - define-properties: 1.2.1 - es-abstract: 1.24.1 - - string.prototype.trim@1.2.10: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-data-property: 1.1.4 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-object-atoms: 1.1.1 - has-property-descriptors: 1.0.2 - - string.prototype.trimend@1.0.9: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - - string.prototype.trimstart@1.0.8: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - - strip-bom@3.0.0: {} + std-env@4.2.0: {} - strip-json-comments@3.1.1: {} + strip-json-comments@5.0.3: {} supports-color@10.2.2: {} - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - - supports-preserve-symlinks-flag@1.0.0: {} + tinybench@6.1.4: {} - tinybench@2.9.0: {} - - tinyexec@1.1.1: {} + tinyexec@1.3.0: {} tinyglobby@0.2.17: dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 - - tinyrainbow@3.1.0: {} - - ts-api-utils@2.5.0(typescript@6.0.2): - dependencies: - typescript: 6.0.2 - - tsconfig-paths@3.15.0: - dependencies: - '@types/json5': 0.0.29 - json5: 1.0.2 - minimist: 1.2.8 - strip-bom: 3.0.0 + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 tslib@2.8.1: optional: true - tsx@4.21.0: + tsx@4.23.15: dependencies: - esbuild: 0.28.1 - get-tsconfig: 4.13.7 + esbuild: 0.28.2 optionalDependencies: fsevents: 2.3.3 - type-check@0.4.0: - dependencies: - prelude-ls: 1.2.1 - - 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.8 - 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.8 - 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.7: - dependencies: - call-bind: 1.0.8 - 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.58.1(eslint@9.39.1(jiti@2.6.1))(typescript@6.0.2): - dependencies: - '@typescript-eslint/eslint-plugin': 8.58.1(@typescript-eslint/parser@8.58.1(eslint@9.39.1(jiti@2.6.1))(typescript@6.0.2))(eslint@9.39.1(jiti@2.6.1))(typescript@6.0.2) - '@typescript-eslint/parser': 8.58.1(eslint@9.39.1(jiti@2.6.1))(typescript@6.0.2) - '@typescript-eslint/typescript-estree': 8.58.1(typescript@6.0.2) - '@typescript-eslint/utils': 8.58.1(eslint@9.39.1(jiti@2.6.1))(typescript@6.0.2) - eslint: 9.39.1(jiti@2.6.1) - typescript: 6.0.2 - transitivePeerDependencies: - - supports-color - - typescript@6.0.2: {} - - 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@7.19.2: {} - - undici@8.5.0: {} + turbo@2.11.4: + optionalDependencies: + '@turbo/darwin-64': 2.11.4 + '@turbo/darwin-arm64': 2.11.4 + '@turbo/linux-64': 2.11.4 + '@turbo/linux-arm64': 2.11.4 + '@turbo/windows-64': 2.11.4 + '@turbo/windows-arm64': 2.11.4 + + typescript@7.0.2: + optionalDependencies: + '@typescript/typescript-aix-ppc64': 7.0.2 + '@typescript/typescript-darwin-arm64': 7.0.2 + '@typescript/typescript-darwin-x64': 7.0.2 + '@typescript/typescript-freebsd-arm64': 7.0.2 + '@typescript/typescript-freebsd-x64': 7.0.2 + '@typescript/typescript-linux-arm': 7.0.2 + '@typescript/typescript-linux-arm64': 7.0.2 + '@typescript/typescript-linux-loong64': 7.0.2 + '@typescript/typescript-linux-mips64el': 7.0.2 + '@typescript/typescript-linux-ppc64': 7.0.2 + '@typescript/typescript-linux-riscv64': 7.0.2 + '@typescript/typescript-linux-s390x': 7.0.2 + '@typescript/typescript-linux-x64': 7.0.2 + '@typescript/typescript-netbsd-arm64': 7.0.2 + '@typescript/typescript-netbsd-x64': 7.0.2 + '@typescript/typescript-openbsd-arm64': 7.0.2 + '@typescript/typescript-openbsd-x64': 7.0.2 + '@typescript/typescript-sunos-x64': 7.0.2 + '@typescript/typescript-win32-arm64': 7.0.2 + '@typescript/typescript-win32-x64': 7.0.2 + + unbash@4.0.11: {} + + undici-types@7.18.2: {} + + undici@7.30.0: {} unenv@2.0.0-rc.24: dependencies: pathe: 2.0.3 - update-browserslist-db@1.2.3(browserslist@4.28.4): - dependencies: - browserslist: 4.28.4 - escalade: 3.2.0 - picocolors: 1.1.1 - - uri-js@4.4.1: + vite@8.3.1(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1): dependencies: - punycode: 2.3.1 - - vite@8.0.16(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.21.0): - dependencies: - lightningcss: 1.32.0 - picomatch: 4.0.4 - postcss: 8.5.15 - rolldown: 1.0.3 + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.28 + rolldown: 1.2.11 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 25.6.0 - esbuild: 0.28.1 + '@types/node': 24.13.6 + esbuild: 0.28.2 fsevents: 2.3.3 - jiti: 2.6.1 - tsx: 4.21.0 + jiti: 2.7.0 + tsx: 4.23.15 + yaml: 2.9.1 - vitest@4.1.4(@types/node@25.6.0)(vite@8.0.16(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.21.0)): + vitest@5.0.1(@types/node@24.13.6)(vite@8.3.1(@types/node@24.13.6)): dependencies: - '@vitest/expect': 4.1.4 - '@vitest/mocker': 4.1.4(vite@8.0.16(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.21.0)) - '@vitest/pretty-format': 4.1.4 - '@vitest/runner': 4.1.4 - '@vitest/snapshot': 4.1.4 - '@vitest/spy': 4.1.4 - '@vitest/utils': 4.1.4 - es-module-lexer: 2.0.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.3 - pathe: 2.0.3 - picomatch: 4.0.4 - std-env: 4.0.0 - tinybench: 2.9.0 - tinyexec: 1.1.1 + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.1(vite@8.3.1(@types/node@24.13.6)) + chai: 6.2.2 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 1.4.2 + obug: 2.2.1 + picomatch: 4.0.7 + std-env: 4.2.0 + tinybench: 6.1.4 + tinyexec: 1.3.0 tinyglobby: 0.2.17 - tinyrainbow: 3.1.0 - vite: 8.0.16(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.21.0) + vite: 8.3.1(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 25.6.0 + '@types/node': 24.13.6 transitivePeerDependencies: - msw - 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.1.8 - 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.20 - - 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.20: - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.8 - 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@2.0.2: - dependencies: - isexe: 2.0.0 + walk-up-path@4.0.0: {} why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 stackback: 0.0.2 - word-wrap@1.2.5: {} - - workerd@1.20260409.1: + workerd@1.20260923.1: optionalDependencies: - '@cloudflare/workerd-darwin-64': 1.20260409.1 - '@cloudflare/workerd-darwin-arm64': 1.20260409.1 - '@cloudflare/workerd-linux-64': 1.20260409.1 - '@cloudflare/workerd-linux-arm64': 1.20260409.1 - '@cloudflare/workerd-windows-64': 1.20260409.1 + '@cloudflare/workerd-darwin-64': 1.20260923.1 + '@cloudflare/workerd-darwin-arm64': 1.20260923.1 + '@cloudflare/workerd-linux-64': 1.20260923.1 + '@cloudflare/workerd-linux-arm64': 1.20260923.1 + '@cloudflare/workerd-windows-64': 1.20260923.1 - wrangler@4.81.1(@cloudflare/workers-types@4.20260412.1): + wrangler@4.140.0(@cloudflare/workers-types@5.20260925.1)(@types/node@24.13.6): dependencies: - '@cloudflare/kv-asset-handler': 0.4.2 - '@cloudflare/unenv-preset': 2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260409.1) + '@cloudflare/kv-asset-handler': 0.5.0 + '@cloudflare/unenv-preset': 2.16.2(unenv@2.0.0-rc.24)(workerd@1.20260923.1) blake3-wasm: 2.1.5 esbuild: 0.28.1 - miniflare: 4.20260409.0 + miniflare: 5.20260923.0-alpha(@types/node@24.13.6) path-to-regexp: 6.3.0 unenv: 2.0.0-rc.24 - workerd: 1.20260409.1 + workerd: 1.20260923.1 optionalDependencies: - '@cloudflare/workers-types': 4.20260412.1 + '@cloudflare/workers-types': 5.20260925.1 fsevents: 2.3.3 transitivePeerDependencies: + - '@types/node' - bufferutil - utf-8-validate ws@8.21.0: {} - yocto-queue@0.1.0: {} + yaml@2.9.1: {} youch-core@0.3.3: dependencies: @@ -5036,12 +3812,8 @@ snapshots: dependencies: '@poppinss/colors': 4.1.6 '@poppinss/dumper': 0.6.5 - '@speed-highlight/core': 1.2.15 + '@speed-highlight/core': 1.2.24 cookie: 1.1.1 youch-core: 0.3.3 - zod-validation-error@4.0.2(zod@4.3.6): - dependencies: - zod: 4.3.6 - - zod@4.3.6: {} + zod@4.6.5: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 7c517bc..fbe75d8 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,4 +3,56 @@ packages: - "packages/*" - "tooling/*" +# Versions used by more than one package, referenced as `catalog:`. Bump with +# `pnpm up --latest -r `, which rewrites only this file. +# +# Not here: `@repo/*` deps (stay `workspace:*`) and single-consumer deps (pinned +# inline in that manifest). Scoped keys must be double-quoted. +catalog: + # `typescript` is the native (Go) compiler; its binary is still `tsc`. + "@types/node": 24.13.6 + prettier: 3.9.9 + typescript: 7.0.2 + vitest: 5.0.1 + + # Loaded by the root manifest and tooling/prettier. + "@ianvs/prettier-plugin-sort-imports": 4.7.1 + + # Shared by the Cloudflare Workers apps and packages. + "@cloudflare/workers-types": 5.20260925.1 + wrangler: 4.140.0 + zod: 4.6.5 + +# Makes `pnpm add` / `pnpm update` use the catalog and reject conflicting +# versions. Plain `pnpm install` never evaluates it, so it is an authoring +# guardrail, not a CI check. +catalogMode: strict + +# Security floors for transitive deps whose parents still resolve a vulnerable +# version. Drop an entry once `pnpm audit` stays clean without it. +overrides: + # drizzle-kit > @esbuild-kit/core-utils pins esbuild 0.18 (GHSA-67mh-4wv8-2f99). + esbuild: ">=0.28.1" + # openai, wrangler > miniflare. + undici: ^7.29.1 + +allowBuilds: + # Still used by Vitest's transforms and Wrangler's bundler. + esbuild: true + # Wrangler's local runtime. Its binary ships in the optional + # @cloudflare/workerd- package, so the postinstall (a check plus a + # fallback download) isn't needed. It has to be listed, even as false, or the + # install fails with ERR_PNPM_IGNORED_BUILDS. + workerd: false + +# 14 days, in minutes: a new release is not resolvable until it is this old. +# So `pnpm up --latest` deliberately skips fresh releases; use +# `minimumReleaseAgeExclude` if one package must move faster. minimumReleaseAge: 20160 + +# Fails when a version has weaker trust evidence (provenance, trusted +# publishing) than an earlier one, the usual sign of a hijacked account. +# Also re-checked on `--frozen-lockfile` installs. If a downgrade is +# legitimate, exclude that exact version in `trustPolicyExclude` with a reason, +# never a bare package name. +trustPolicy: no-downgrade diff --git a/tooling/eslint/base.ts b/tooling/eslint/base.ts deleted file mode 100644 index 9cec1e5..0000000 --- a/tooling/eslint/base.ts +++ /dev/null @@ -1,105 +0,0 @@ -import path from "path"; -import { includeIgnoreFile } from "@eslint/compat"; -import eslint from "@eslint/js"; -import importPlugin from "eslint-plugin-import"; -import { defineConfig } from "eslint/config"; -import tseslint from "typescript-eslint"; - -export const baseConfig = defineConfig( - // Ignore files not tracked by VCS and any config files - includeIgnoreFile(path.join(import.meta.dirname, "../../.gitignore")), - { ignores: ["**/*.config.*"] }, - { - files: ["**/*.js", "**/*.ts", "**/*.tsx"], - plugins: { - import: importPlugin, - }, - extends: [ - eslint.configs.recommended, - ...tseslint.configs.recommended, - ...tseslint.configs.recommendedTypeChecked, - ...tseslint.configs.strictTypeChecked, - ...tseslint.configs.stylisticTypeChecked, - ], - rules: { - "@typescript-eslint/no-unused-vars": [ - "error", - { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, - ], - "@typescript-eslint/consistent-type-imports": [ - "warn", - { prefer: "type-imports", fixStyle: "separate-type-imports" }, - ], - "@typescript-eslint/no-misused-promises": [ - 2, - { checksVoidReturn: { attributes: false } }, - ], - "@typescript-eslint/no-unnecessary-condition": [ - "error", - { - allowConstantLoopConditions: true, - }, - ], - // Enforce arrow functions over function declarations - "func-style": ["error", "expression"], - "@typescript-eslint/no-floating-promises": [ - "error", - { ignoreVoid: true }, - ], - "@typescript-eslint/switch-exhaustiveness-check": "error", - "@typescript-eslint/no-non-null-assertion": "error", - "@typescript-eslint/consistent-type-exports": "error", - "@typescript-eslint/consistent-type-definitions": ["error", "type"], - "@typescript-eslint/restrict-template-expressions": [ - "error", - { - allowAny: false, - allowBoolean: true, - allowNever: false, - allowNullish: false, - allowNumber: true, - allowRegExp: false, - }, - ], - "prefer-const": "error", - "no-var": "error", - // --- Async correctness --- - "@typescript-eslint/await-thenable": "error", - - // --- Safer error handling --- - "@typescript-eslint/only-throw-error": "error", - - // --- Better modern TS patterns --- - "@typescript-eslint/prefer-nullish-coalescing": "error", - "@typescript-eslint/prefer-optional-chain": "error", - eqeqeq: ["error", "smart"], - curly: ["error", "all"], - "import/no-default-export": "error", - "import/consistent-type-specifier-style": ["error", "prefer-top-level"], - // No barrel re-exports: export from the defining module and expose it via - // a package.json subpath, so consumers import from a stable, explicit path. - "no-restricted-syntax": [ - "error", - { - selector: "ExportAllDeclaration", - message: - "No barrel re-exports — export from the defining module and expose it via a package.json subpath.", - }, - { - selector: "ExportNamedDeclaration[source]", - message: - "No barrel re-exports — export from the defining module and expose it via a package.json subpath.", - }, - ], - }, - }, - { - linterOptions: { reportUnusedDisableDirectives: true }, - languageOptions: { - parserOptions: { - projectService: true, - tsconfigRootDir: import.meta.dirname, - }, - }, - } -); diff --git a/tooling/eslint/package.json b/tooling/eslint/package.json deleted file mode 100644 index 4474572..0000000 --- a/tooling/eslint/package.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "@repo/eslint", - "private": true, - "type": "module", - "exports": { - "./base": "./base.ts", - "./react": "./react.ts" - }, - "dependencies": { - "@eslint/compat": "2.0.5", - "@eslint/js": "9.39.1", - "eslint-plugin-import": "2.32.0", - "eslint-plugin-react": "7.37.5", - "eslint-plugin-react-hooks": "7.0.1", - "jiti": "2.6.1", - "typescript-eslint": "8.58.1" - }, - "devDependencies": { - "@repo/prettier": "workspace:*", - "@repo/typescript": "workspace:*", - "@types/node": "25.6.0", - "eslint": "9.39.1", - "prettier": "3.8.2", - "typescript": "6.0.2" - } -} diff --git a/tooling/eslint/react.ts b/tooling/eslint/react.ts deleted file mode 100644 index dfad8af..0000000 --- a/tooling/eslint/react.ts +++ /dev/null @@ -1,46 +0,0 @@ -import reactPlugin from "eslint-plugin-react"; -import reactHooks from "eslint-plugin-react-hooks"; -import { defineConfig } from "eslint/config"; - -export const reactConfig = defineConfig( - { - files: ["**/*.ts", "**/*.tsx"], - ...reactPlugin.configs.flat.recommended, - ...reactPlugin.configs.flat["jsx-runtime"], - languageOptions: { - ...reactPlugin.configs.flat.recommended?.languageOptions, - ...reactPlugin.configs.flat["jsx-runtime"]?.languageOptions, - globals: { - React: "writable", - }, - }, - }, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- react-hooks always has this config - reactHooks.configs.flat["recommended-latest"]!, - { - files: ["**/*.ts", "**/*.tsx"], - rules: { - "no-restricted-syntax": [ - "error", - { - selector: "MemberExpression[object.name='React']", - message: - "Avoid `React.` namespace usage. Import the specific symbol instead.", - }, - { - selector: "TSQualifiedName[left.name='React']", - message: - "Avoid `React.` type namespace usage. Import the type directly instead.", - }, - { - selector: "JSXMemberExpression[object.name='React']", - message: - "Avoid `React.` namespace usage in JSX. Import the component instead.", - }, - ], - // React compiler rules are too strict for the current codebase - "react-hooks/set-state-in-effect": "off", - "react-hooks/preserve-manual-memoization": "off", - }, - } -); diff --git a/tooling/eslint/tsconfig.json b/tooling/eslint/tsconfig.json deleted file mode 100644 index 3438bde..0000000 --- a/tooling/eslint/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "@repo/typescript/base.json", - "include": ["."], - "exclude": ["node_modules"] -} diff --git a/tooling/github/package.json b/tooling/github/package.json new file mode 100644 index 0000000..baceaab --- /dev/null +++ b/tooling/github/package.json @@ -0,0 +1,4 @@ +{ + "name": "@repo/github", + "private": true +} diff --git a/tooling/github/scripts/secrets-scan.sh b/tooling/github/scripts/secrets-scan.sh new file mode 100644 index 0000000..e393edb --- /dev/null +++ b/tooling/github/scripts/secrets-scan.sh @@ -0,0 +1,46 @@ +#!/bin/sh +# Scan the repo's git history for committed secrets with gitleaks. +# +# Used by `pnpm secrets:scan` and by the `secrets-scan` CI job, so the pinned +# version below is the single source of truth for both. A local `gitleaks` +# binary is preferred when it is on PATH and new enough to have the `git` +# subcommand (v8.19+); otherwise the pinned Docker image runs instead. Extra +# arguments are passed through to `gitleaks git`, e.g. +# `pnpm secrets:scan --log-opts="origin/main..HEAD"`. +# +# Exit code is gitleaks' own: 0 clean, 1 leaks found. 2 means neither runner +# is available. +set -eu + +GITLEAKS_VERSION="8.30.1" +# Digest of the v$GITLEAKS_VERSION multi-arch image, so a re-pushed tag cannot +# change what runs. Bump both together. +GITLEAKS_DIGEST="sha256:c00b6bd0aeb3071cbcb79009cb16a60dd9e0a7c60e2be9ab65d25e6bc8abbb7f" + +repo_root=$(git rev-parse --show-toplevel) + +if command -v gitleaks >/dev/null 2>&1 && gitleaks git --help >/dev/null 2>&1; then + exec gitleaks git --redact --verbose "$@" "$repo_root" +fi + +if command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then + # Mount read-only (gitleaks only reads) at the real absolute path, not /repo: + # in a git worktree `.git` is a file pointing at an absolute path inside the + # main checkout's git dir, which therefore has to be mounted at its own path. + git_common_dir=$(cd "$repo_root" && cd "$(git rev-parse --git-common-dir)" && pwd) + set -- -v "$repo_root:$repo_root:ro" \ + "ghcr.io/gitleaks/gitleaks:v$GITLEAKS_VERSION@$GITLEAKS_DIGEST" \ + git --redact --verbose "$@" "$repo_root" + case "$git_common_dir" in + "$repo_root"/*) ;; + *) set -- -v "$git_common_dir:$git_common_dir:ro" "$@" ;; + esac + exec docker run --rm "$@" +fi + +cat >&2 <", "", "^~/(.*)$", "", "^[./]"], }; export default config; diff --git a/tooling/typescript/base.json b/tooling/typescript/base.json index 8b33ae3..02091f7 100644 --- a/tooling/typescript/base.json +++ b/tooling/typescript/base.json @@ -1,11 +1,12 @@ { "$schema": "https://json.schemastore.org/tsconfig", "compilerOptions": { - "target": "es2023", - "lib": ["ES2023"], - "module": "ESNext", + "target": "ES2024", + "lib": ["ES2024"], + "module": "Preserve", "moduleResolution": "Bundler", "strict": true, + "noUncheckedIndexedAccess": true, "noEmit": true, "isolatedModules": true, "esModuleInterop": true, diff --git a/tooling/typescript/node.json b/tooling/typescript/node.json new file mode 100644 index 0000000..1d34888 --- /dev/null +++ b/tooling/typescript/node.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "node", + "extends": "./base.json", + "compilerOptions": { + "types": ["node"] + } +} diff --git a/tooling/typescript/package.json b/tooling/typescript/package.json index 258948b..bd7a8d4 100644 --- a/tooling/typescript/package.json +++ b/tooling/typescript/package.json @@ -4,8 +4,5 @@ "private": true, "files": [ "*.json" - ], - "scripts": { - "format": "prettier --write ." - } + ] } diff --git a/tooling/typescript/react.json b/tooling/typescript/react.json index e6e4f7c..89e9736 100644 --- a/tooling/typescript/react.json +++ b/tooling/typescript/react.json @@ -3,7 +3,7 @@ "display": "react", "extends": "./base.json", "compilerOptions": { - "lib": ["ES2023", "DOM", "DOM.Iterable"], + "lib": ["ES2024", "DOM", "DOM.Iterable"], "jsx": "react-jsx" } } diff --git a/turbo.json b/turbo.json new file mode 100644 index 0000000..d40b9b6 --- /dev/null +++ b/turbo.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://turborepo.com/schema.json", + "tasks": { + "build": { + "dependsOn": ["^build"], + "outputs": ["dist/**"] + }, + "typecheck": { + "dependsOn": ["^typecheck"] + }, + "test": { + "dependsOn": ["^test"] + }, + "clean": { + "cache": false + }, + "dev": { + "dependsOn": ["^dev"], + "cache": false, + "persistent": true + } + } +}