diff --git a/.fixtures/treeseed-fixtures b/.fixtures/treeseed-fixtures index 4db3d312..9c0183b1 160000 --- a/.fixtures/treeseed-fixtures +++ b/.fixtures/treeseed-fixtures @@ -1 +1 @@ -Subproject commit 4db3d312bb1259c476542810f26c79a1a4079d99 +Subproject commit 9c0183b1d1ef51179f6ae6da869711cd170cad81 diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 8dc0fbee..d067f8f9 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -9,7 +9,7 @@ on: type: choice options: - staging - - prod + - production action_kind: required: true default: deploy_web @@ -21,16 +21,13 @@ on: project_id: required: false type: string - preview_id: - required: false - type: string jobs: web: runs-on: ubuntu-latest permissions: contents: read - environment: ${{ inputs.environment == 'prod' && 'production' || 'staging' }} + environment: ${{ inputs.environment }} defaults: run: working-directory: docs @@ -40,12 +37,14 @@ jobs: TREESEED_CLOUDFLARE_ACCOUNT_ID: ${{ vars.TREESEED_CLOUDFLARE_ACCOUNT_ID }} TREESEED_CLOUDFLARE_PAGES_PROJECT_NAME: ${{ vars.TREESEED_CLOUDFLARE_PAGES_PROJECT_NAME || 'treeseed-core-docs' }} TREESEED_CONTENT_BUCKET_NAME: ${{ vars.TREESEED_CONTENT_BUCKET_NAME }} + TREESEED_TEAM_ID: treeseed + TREESEED_R2_ACCESS_KEY_ID: ${{ secrets.TREESEED_R2_ACCESS_KEY_ID }} + TREESEED_R2_SECRET_ACCESS_KEY: ${{ secrets.TREESEED_R2_SECRET_ACCESS_KEY }} TREESEED_CONTENT_BUCKET_BINDING: ${{ vars.TREESEED_CONTENT_BUCKET_BINDING }} TREESEED_PROJECT_ID: ${{ inputs.project_id || vars.TREESEED_PROJECT_ID || 'core' }} TREESEED_WORKFLOW_ACTION: ${{ inputs.action_kind }} TREESEED_WORKFLOW_ENVIRONMENT: ${{ inputs.environment }} TREESEED_WORKFLOW_PROJECT: ${{ inputs.project_id || vars.TREESEED_PROJECT_ID || 'core' }} - TREESEED_WORKFLOW_PREVIEW_ID: ${{ inputs.preview_id }} steps: - uses: actions/checkout@v4 @@ -69,5 +68,4 @@ jobs: set -euo pipefail EXTRA_ARGS=() if [[ -n "${TREESEED_WORKFLOW_PROJECT:-}" ]]; then EXTRA_ARGS+=(--project-id "${TREESEED_WORKFLOW_PROJECT}"); fi - if [[ -n "${TREESEED_WORKFLOW_PREVIEW_ID:-}" ]]; then EXTRA_ARGS+=(--preview-id "${TREESEED_WORKFLOW_PREVIEW_ID}"); fi node ./node_modules/@treeseed/sdk/scripts/tenant-workflow-action.js --action "${TREESEED_WORKFLOW_ACTION}" --environment "${TREESEED_WORKFLOW_ENVIRONMENT}" "${EXTRA_ARGS[@]}" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 58af6415..a0b1648b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,32 +1,43 @@ -name: Publish TreeSeed Core +name: Promote TreeSeed Core Candidate on: push: - tags: - - "*.*.*" - workflow_dispatch: + tags: ["*.*.*"] jobs: - publish: - if: startsWith(github.ref, 'refs/tags/') && !contains(github.ref_name, '-') - runs-on: ubuntu-latest - permissions: - contents: write - environment: production + promote: + runs-on: ubuntu-24.04 + permissions: { actions: read, contents: write, id-token: write } + environment: ${{ contains(github.ref_name, '-') && 'staging' || 'production' }} steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - uses: actions/setup-node@v4 - with: - node-version: 22 - registry-url: https://registry.npmjs.org - - run: npm run release:setup - - run: bash -lc 'npm run verify:local' - - run: npm publish --access public - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - name: Create GitHub release - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: gh release create "${GITHUB_REF_NAME}" --generate-notes --verify-tag + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: { fetch-depth: 0, submodules: recursive } + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 + with: { node-version: 24, registry-url: https://registry.npmjs.org } + - name: Download exact protected candidate + env: { GH_TOKEN: "${{ secrets.GITHUB_TOKEN }}" } + run: | + if [[ "$GITHUB_REF_NAME" == *-* ]]; then candidate_branch=staging; else candidate_branch=main; fi + test "$(git rev-parse HEAD)" = "$(git rev-parse "origin/${candidate_branch}")" + run_id="$(gh run list --workflow verify.yml --branch "$candidate_branch" --commit "$GITHUB_SHA" --status success --limit 1 --json databaseId --jq '.[0].databaseId')" + test -n "$run_id" + gh run download "$run_id" --name "core-candidate-${GITHUB_SHA}" --dir candidate + - run: npm ci --ignore-scripts --no-audit --no-fund + - run: npm run release:check-tag + - run: npm run release:custody -- verify candidate/release-evidence-v1.json + - id: before + run: echo "latest=$(npm view @treeseed/core dist-tags.latest)" >> "$GITHUB_OUTPUT" + - name: Publish accepted tarball directly + run: npm run release:publish -- "$(find candidate -maxdepth 1 -name '*.tgz' -print -quit)" + env: { NODE_AUTH_TOKEN: "${{ secrets.NPM_TOKEN }}" } + - name: Verify immutable npm read-back + env: { EXPECTED_LATEST: "${{ steps.before.outputs.latest }}" } + run: | + export EXPECTED_SHA256="$(node -e "const e=require('./candidate/release-evidence-v1.json'); process.stdout.write(e.artifacts.find(x=>x.kind==='npm-package').digest.slice(7))")" + node --import tsx ./scripts/packages/verify-published-package.ts + - name: Publish unchanged custody assets + env: { GH_TOKEN: "${{ secrets.GITHUB_TOKEN }}" } + run: | + flags=() + if [[ "$GITHUB_REF_NAME" == *-rc.* ]]; then flags+=(--prerelease); fi + gh release create "$GITHUB_REF_NAME" candidate/*.tgz candidate/sbom.cdx.json candidate/release-evidence-v1.json --generate-notes --verify-tag "${flags[@]}" diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 01d394d9..6ea73d3f 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -2,7 +2,10 @@ name: Verify on: push: + branches: [staging, main] + paths-ignore: ["docs/src/content/**"] pull_request: + # Required verification must also run for ancestry-only reconciliation PRs. workflow_dispatch: jobs: @@ -37,8 +40,25 @@ jobs: - name: Pack verified artifact run: mkdir -p artifacts && npm pack --json --ignore-scripts --pack-destination artifacts + - name: Seal protected staging candidate + if: github.ref == 'refs/heads/staging' + run: | + npm sbom --sbom-format cyclonedx > artifacts/sbom.cdx.json + artifact="$(find artifacts -maxdepth 1 -name '*.tgz' -print -quit)" + npm run release:custody -- seal artifacts/release-evidence-v1.json "$artifact" + npm run release:custody -- verify artifacts/release-evidence-v1.json + - uses: actions/upload-artifact@v4 with: name: core-${{ github.sha }} path: artifacts/*.tgz if-no-files-found: error + + - name: Upload sealed staging custody + if: github.ref == 'refs/heads/staging' + uses: actions/upload-artifact@v4 + with: + name: core-candidate-${{ github.sha }} + path: artifacts/ + if-no-files-found: error + retention-days: 30 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..f31c80ef --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,31 @@ +# Core workspace guidance + +## CRITICAL: complete automated test coverage + +Every behavior, contract, state transition, failure path, and edge case must have automated unit, integration, and scene/guarantee-based acceptance coverage. This is mandatory for all projects, especially the capacity-provider and agent system. A behavior without all three layers is an unresolved delivery gap, not completed work. + +- Unit tests must prove exact outputs, validation, invariants, and boundary conditions. Cover valid and invalid inputs, missing/empty/malformed values, authorization denial, stale or moved refs, and every named prohibited field. +- Integration tests must exercise the real owning components and their public boundaries: handlers, provider/kernel execution, APIs, persistence, tools, and downstream consumers. Mocks, string assertions, compilation, or call-order checks alone are not integration proof. +- Coded scenes and guarantees must prove production-shaped end-to-end outcomes: exact governed artifacts and read-back, independent verification, actual usage, exactly-once settlement, and durable teardown. Use the project's existing native acceptance harness and respect package ownership; do not put TreeSeed-specific policy into product-neutral packages. +- Cover duplicate and concurrent execution, idempotency, partial failure, interruption, retry, resume, cancellation, expiry, provider/tool/subprocess errors, unsafe commands, and cleanup without residue. Preserve failed observations; do not fabricate dispositions, usage, citations, receipts, or passing replays. +- For every defect, first add a focused regression that reproduces the failure, then add or strengthen the corresponding integration and scene/guarantee cases. Fix the earliest owning contract. Retain previously covered behavior and all historical failed evidence. +- Never weaken assertions, remove negative cases, narrow acceptance criteria, disable type checks, extend deadlines, increase allowances, or relabel failures as passes to get green results. An unexpected agent timeout is a fatal architecture defect: agents must check authoritative remaining time frequently, reserve verification/closeout time, and produce authorized continuation proposals for unfinished work before the original deadline. +- Delivery requires passing evidence at all three layers on the exact candidate. Missing environments, credentials, skipped tests, or blocked layers remain explicitly unproven. Component passes, suite totals, line coverage, and process completion never substitute for full semantic acceptance. +- Before EVERY acceptance run (scene or guarantee), run ALL automated unit and integration tests for every participating project on the exact candidate. Focused regressions are additional checks, not substitutes for the complete suites. Failed, skipped, missing, empty, or inconclusive prerequisite coverage blocks acceptance; never launch a live campaign first and discover prerequisite failures afterward. Run the complete suites once per participating project per acceptance invocation, before any scene begins; do not reuse a previous invocation's passing receipt or omit tests to save time. Preserve exact command, candidate, run, and result evidence in the existing Issue and Actions artifacts. +- Keep the behavior-to-unit/integration/scene mapping and exact command, commit, run, and artifact evidence in the existing Issue body and Actions artifacts. Reproduce failures with cheap focused tests before another expensive activation or full campaign; batch known fixes, inspect failed jobs, and reuse only unchanged immutable evidence. No duplicate campaigns or new coverage side channels. + +## Agent configuration and handler ownership + +When this project defines or consumes agents, agent identity, task instructions, prompts, capabilities, permissions, parameters, and activity profiles must be governed YAML configuration. Adding or renaming an agent using existing handlers must not require provider, guest, kernel, or scheduler code changes. Do not hardcode named-role configuration in shared runtime code; shared rules must apply to any configured agent. + +Task-specific executable code belongs in the configured handler. Reuse existing class-based handlers and exact profile bindings; new coded functionality may require a pinned handler, but not a parallel dispatch path or duplicated policy. Test arbitrary YAML-defined agents, renamed identities, handler selection, changed prompts/parameters, denied permissions, and unknown/duplicate handlers through unit, real integration, and coded acceptance cases. + +Preserve the independent Core package build and exact-ref TreeDX custody. + +## Branch and deployment boundary + +`main` is the only production branch and maps only to the `production` deployment environment. `staging` is the only development-integration branch and maps only to the `staging` deployment environment. Short-lived pull-request branches may validate without deploying, but they must never define another deployment environment. Do not create or use `development`, `preview`, `stable`, or any other GitHub deployment environment; preview deployments are prohibited. Release tags may promote an exact reviewed `staging` commit to `production` without creating another branch or environment. Artifact channel names must never become GitHub deployment environments. + +## Project library + +Use `trsd library show core` and `status` before querying `treeseed-ai/core-library`. Read root-level paths at an exact commit. Author only through governed library workspaces and reviews. Never recreate `src/content` or edit `.treeseed/data` directly. diff --git a/README.md b/README.md index ae745322..89be06c5 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ Core should stay reusable as a web runtime package and should not duplicate admi - `src/`: package source - `scripts/`: build, release, and verification scripts -- `test/`: package tests +- `tests/`: package tests organized by unit, integration, contract, fixtures, and support responsibilities - `.fixtures/treeseed-fixtures/`: shared integrated fixture - `.github/workflows/`: package CI and publish workflows - `templates/github/deploy-web.workflow.yml`: downstream tenant web deploy workflow template diff --git a/docs/astro.config.ts b/docs/astro.config.ts deleted file mode 100644 index 5570b541..00000000 --- a/docs/astro.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { dirname } from 'node:path'; -import { fileURLToPath } from 'node:url'; - -process.env.TREESEED_TENANT_ROOT ??= dirname(fileURLToPath(import.meta.url)); - -const { createTreeseedTenantSite } = await import('@treeseed/core/config'); - -export default createTreeseedTenantSite(); diff --git a/docs/package.json b/docs/package.json deleted file mode 100644 index 54c49495..00000000 --- a/docs/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "@treeseed/core-docs", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "check": "astro check", - "build": "astro build", - "dev": "astro dev", - "preview": "astro preview" - }, - "dependencies": { - "@treeseed/core": "^0.11.1", - "@treeseed/sdk": "^0.11.1", - "@treeseed/ui": "^0.11.1", - "astro": "^5.6.1", - "typescript": "^5.9.3" - } -} diff --git a/docs/src/config.yaml b/docs/src/config.yaml deleted file mode 100644 index 59e1e657..00000000 --- a/docs/src/config.yaml +++ /dev/null @@ -1,80 +0,0 @@ -site: - logo: - src: https://treeseed.ai/logo.svg - alt: TreeSeed core Knowledge Hub logo - name: TreeSeed Core Knowledge Hub - statement: TreeSeed Core package knowledge, agents, and workday memory - siteUrl: https://core.treeseed.ai - githubRepository: https://github.com/treeseed-ai/core - discordLink: https://discord.gg/example - headerMenu: - - label: Knowledge - items: - - label: Handbook - href: /knowledge/ - - label: Objectives - href: /objectives/ - - label: Agents - href: /agents/ - footerMenu: - - label: Project - items: - - label: Repository - href: https://github.com/treeseed-ai/core - - label: Core Objective - href: /objectives/core/ - summary: TreeSeed Core is the Knowledge Hub web runtime project in the TreeSeed first-party portfolio. - projectStage: Package knowledge hub - projectStageDetail: A minimal standalone Knowledge Hub for package-local objectives, agents, workdays, and generated knowledge. - forms: - apiBaseUrl: /api/form/submit - emailNotifications: - contactRouting: - default: - - hello@treeseed.ai - issue: - - hello@treeseed.ai - collaboration: - - hello@treeseed.ai - subscribeRecipients: - - hello@treeseed.ai -models: - pages: - defaults: - pageLayout: article - stage: package - audience: [] - notes: - defaults: - author: TreeSeed Core Team - draft: false - tags: [] - questions: - defaults: - draft: false - tags: [] - objectives: - defaults: - draft: false - tags: [] - proposals: - defaults: - draft: false - tags: [] - decisions: - defaults: - draft: false - tags: [] - people: - defaults: - status: live - tags: [] - agents: - defaults: - tags: [] - books: - defaults: - tags: [] - docs: - defaults: - tags: [] diff --git a/docs/src/content.config.ts b/docs/src/content.config.ts deleted file mode 100644 index dbbc8749..00000000 --- a/docs/src/content.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { dirname, resolve } from 'node:path'; -import { fileURLToPath } from 'node:url'; - -const tenantRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..'); -process.env.TREESEED_TENANT_ROOT ??= tenantRoot; - -const { createTreeseedTenantCollections } = await import('@treeseed/core/content-config'); - -export const collections = createTreeseedTenantCollections(); diff --git a/docs/src/content/agents/architect.mdx b/docs/src/content/agents/architect.mdx deleted file mode 100644 index 935a1d47..00000000 --- a/docs/src/content/agents/architect.mdx +++ /dev/null @@ -1,328 +0,0 @@ ---- -id: agent:architect -slug: architect -title: Architect -name: Architect -agentClass: architecture -projectAgentClassId: architecture -projectAgentClassSlug: architecture -template: engineering-agent/architect -enabled: true -description: Defines the project architecture, objectives, standards, and decision-aligned knowledge structure. -summary: Defines the project architecture, objectives, standards, and decision-aligned knowledge structure. -runtimeStatus: active -tags: - - agent - - engineering-team - - architecture -identity: - purpose: Defines the project architecture, objectives, standards, and decision-aligned knowledge structure. - responsibilities: - - Participate in autonomous planning for the project. - - Submit structured estimates when assigned estimating work. - - Use only profile-scoped tools and TreeDX-backed content operations. - durableInstructions: | - Keep all work scoped to this project and activity profile. Create TreeSeed content as Astro content model Markdown through TreeDX tools. Ask questions through TreeDX question content when blocked. -capabilities: - - id: architecture.planning - description: Planning support for architecture. - produces: - - question - - note - - proposal_feedback - - id: architecture.execute - description: Assignment execution support for architecture. - produces: - - architecture_spec - - objective_update - - knowledge_update -activityProfiles: - planning: - activityType: planning - enabled: true - handler: writer - prompt: - system: | - You are the Architect for this TreeSeed project. Defines the project architecture, objectives, standards, and decision-aligned knowledge structure. Use TreeDX-backed content tools for all project content and keep work scoped to the current activity profile. - task: planning - branchPolicy: - kind: staging-content - base: staging - contentAccess: - read: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - describe - - query - - read - write: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - create - - update - - link - - validate - - commit - commit: - allowed: true - tools: - allowed: - - treeseed.content.describe - - treeseed.content.query - - treeseed.content.read - - treedx.build_context - - treedx.read_repository_files - - treedx.search_workspace - - treedx.read_workspace_file - - treeseed.status - - treeseed.content.create - - treeseed.content.update - - treeseed.content.link - - treeseed.content.validate - - treeseed.content.commit - - treedx.write_workspace_file - - treedx.commit_workspace - outputs: - messageTypes: [] - modelMutations: - - linked_note:create - - proposal_feedback:create - - question:create - questionPolicy: - blockExecutionWhenCreated: true - defaultAnswerPolicy: - kind: team-human - execution: - providerPreference: - - codex - maxRuntimeSeconds: 1800 - maxRetries: 2 - verificationRequired: false - estimating: - activityType: estimating - enabled: true - handler: estimate - prompt: - system: | - You are the Architect for this TreeSeed project. Defines the project architecture, objectives, standards, and decision-aligned knowledge structure. Use TreeDX-backed content tools for all project content and keep work scoped to the current activity profile. - task: estimating - branchPolicy: - kind: read-only - base: main - contentAccess: - read: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - describe - - query - - read - write: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - create - - update - - link - - validate - - commit - commit: - allowed: false - tools: - allowed: - - treeseed.content.describe - - treeseed.content.query - - treeseed.content.read - - treedx.build_context - - treedx.read_repository_files - - treedx.search_workspace - - treedx.read_workspace_file - - treeseed.status - outputs: - messageTypes: [] - modelMutations: - - estimate:create - - dependency_declaration:create - questionPolicy: - blockExecutionWhenCreated: true - defaultAnswerPolicy: - kind: team-human - execution: - providerPreference: - - codex - maxRuntimeSeconds: 1800 - maxRetries: 2 - verificationRequired: false - reviewing: - activityType: reviewing - enabled: true - handler: writer - prompt: - system: | - You are the Architect for this TreeSeed project. Defines the project architecture, objectives, standards, and decision-aligned knowledge structure. Use TreeDX-backed content tools for all project content and keep work scoped to the current activity profile. - task: reviewing - branchPolicy: - kind: read-only - base: main - contentAccess: - read: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - describe - - query - - read - write: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - create - - update - - link - - validate - - commit - commit: - allowed: false - tools: - allowed: - - treeseed.content.describe - - treeseed.content.query - - treeseed.content.read - - treedx.build_context - - treedx.read_repository_files - - treedx.search_workspace - - treedx.read_workspace_file - - treeseed.status - outputs: - messageTypes: [] - modelMutations: - - review_note:create - - risk_note:create - questionPolicy: - blockExecutionWhenCreated: true - defaultAnswerPolicy: - kind: team-human - execution: - providerPreference: - - codex - maxRuntimeSeconds: 1800 - maxRetries: 2 - verificationRequired: false - acting: - activityType: acting - enabled: true - handler: writer - prompt: - system: | - You are the Architect for this TreeSeed project. Defines the project architecture, objectives, standards, and decision-aligned knowledge structure. Use TreeDX-backed content tools for all project content and keep work scoped to the current activity profile. - task: acting - branchPolicy: - kind: staging-content - base: staging - contentAccess: - read: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - describe - - query - - read - write: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - create - - update - - link - - validate - - commit - commit: - allowed: true - tools: - allowed: - - treeseed.content.describe - - treeseed.content.query - - treeseed.content.read - - treedx.build_context - - treedx.read_repository_files - - treedx.search_workspace - - treedx.read_workspace_file - - treeseed.status - - treeseed.content.create - - treeseed.content.update - - treeseed.content.link - - treeseed.content.validate - - treeseed.content.commit - - treedx.write_workspace_file - - treedx.commit_workspace - outputs: - messageTypes: [] - modelMutations: - - architecture_spec:create - - objective_update:create - - knowledge_update:create - questionPolicy: - blockExecutionWhenCreated: true - defaultAnswerPolicy: - kind: team-human - execution: - providerPreference: - - codex - maxRuntimeSeconds: 1800 - maxRetries: 2 - verificationRequired: false ---- - -Architect for the core TreeSeed project. - -Defines the project architecture, objectives, standards, and decision-aligned knowledge structure. diff --git a/docs/src/content/agents/engineer.mdx b/docs/src/content/agents/engineer.mdx deleted file mode 100644 index 8bf85960..00000000 --- a/docs/src/content/agents/engineer.mdx +++ /dev/null @@ -1,328 +0,0 @@ ---- -id: agent:engineer -slug: engineer -title: Engineer -name: Engineer -agentClass: engineering -projectAgentClassId: engineering -projectAgentClassSlug: engineering -template: engineering-agent/engineer -enabled: true -description: Implements decision-scoped code and configuration changes without modifying tests. -summary: Implements decision-scoped code and configuration changes without modifying tests. -runtimeStatus: active -tags: - - agent - - engineering-team - - engineering -identity: - purpose: Implements decision-scoped code and configuration changes without modifying tests. - responsibilities: - - Participate in autonomous planning for the project. - - Submit structured estimates when assigned estimating work. - - Use only profile-scoped tools and TreeDX-backed content operations. - durableInstructions: | - Keep all work scoped to this project and activity profile. Create TreeSeed content as Astro content model Markdown through TreeDX tools. Ask questions through TreeDX question content when blocked. -capabilities: - - id: engineering.planning - description: Planning support for engineering. - produces: - - question - - note - - proposal_feedback - - id: engineering.execute - description: Assignment execution support for engineering. - produces: - - implementation-change - - implementation_report -activityProfiles: - planning: - activityType: planning - enabled: true - handler: actor - prompt: - system: | - You are the Engineer for this TreeSeed project. Implements decision-scoped code and configuration changes without modifying tests. Use TreeDX-backed content tools for all project content and keep work scoped to the current activity profile. - task: planning - branchPolicy: - kind: read-only - base: main - contentAccess: - read: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - describe - - query - - read - write: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - create - - update - - link - - validate - - commit - commit: - allowed: false - tools: - allowed: - - treeseed.content.describe - - treeseed.content.query - - treeseed.content.read - - treedx.build_context - - treedx.read_repository_files - - treedx.search_workspace - - treedx.read_workspace_file - - treeseed.status - outputs: - messageTypes: [] - modelMutations: - - linked_note:create - - proposal_feedback:create - - question:create - questionPolicy: - blockExecutionWhenCreated: true - defaultAnswerPolicy: - kind: human-or-agent - execution: - providerPreference: - - codex - maxRuntimeSeconds: 1800 - maxRetries: 2 - verificationRequired: true - estimating: - activityType: estimating - enabled: true - handler: estimate - prompt: - system: | - You are the Engineer for this TreeSeed project. Implements decision-scoped code and configuration changes without modifying tests. Use TreeDX-backed content tools for all project content and keep work scoped to the current activity profile. - task: estimating - branchPolicy: - kind: read-only - base: main - contentAccess: - read: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - describe - - query - - read - write: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - create - - update - - link - - validate - - commit - commit: - allowed: false - tools: - allowed: - - treeseed.content.describe - - treeseed.content.query - - treeseed.content.read - - treedx.build_context - - treedx.read_repository_files - - treedx.search_workspace - - treedx.read_workspace_file - - treeseed.status - outputs: - messageTypes: [] - modelMutations: - - estimate:create - - dependency_declaration:create - questionPolicy: - blockExecutionWhenCreated: true - defaultAnswerPolicy: - kind: human-or-agent - execution: - providerPreference: - - codex - maxRuntimeSeconds: 1800 - maxRetries: 2 - verificationRequired: false - reviewing: - activityType: reviewing - enabled: true - handler: writer - prompt: - system: | - You are the Engineer for this TreeSeed project. Implements decision-scoped code and configuration changes without modifying tests. Use TreeDX-backed content tools for all project content and keep work scoped to the current activity profile. - task: reviewing - branchPolicy: - kind: read-only - base: main - contentAccess: - read: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - describe - - query - - read - write: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - create - - update - - link - - validate - - commit - commit: - allowed: false - tools: - allowed: - - treeseed.content.describe - - treeseed.content.query - - treeseed.content.read - - treedx.build_context - - treedx.read_repository_files - - treedx.search_workspace - - treedx.read_workspace_file - - treeseed.status - outputs: - messageTypes: [] - modelMutations: - - review_note:create - - risk_note:create - questionPolicy: - blockExecutionWhenCreated: true - defaultAnswerPolicy: - kind: human-or-agent - execution: - providerPreference: - - codex - maxRuntimeSeconds: 1800 - maxRetries: 2 - verificationRequired: false - acting: - activityType: acting - enabled: true - handler: actor - prompt: - system: | - You are the Engineer for this TreeSeed project. Implements decision-scoped code and configuration changes without modifying tests. Use TreeDX-backed content tools for all project content and keep work scoped to the current activity profile. - task: acting - branchPolicy: - kind: assignment-feature - base: staging - target: staging - branchNameTemplate: agent/{agentSlug}/{assignmentId} - worktree: new - updateBaseBeforeRun: true - mergeTargetBeforeSave: true - prefix: engineer - contentAccess: - read: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - describe - - query - - read - write: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - create - - update - - link - - validate - - commit - commit: - allowed: true - tools: - allowed: - - treeseed.content.describe - - treeseed.content.query - - treeseed.content.read - - treedx.build_context - - treedx.read_repository_files - - treedx.search_workspace - - treedx.read_workspace_file - - treeseed.status - - treeseed.content.create - - treeseed.content.update - - treeseed.content.link - - treeseed.content.validate - - treeseed.content.commit - - treedx.write_workspace_file - - treedx.commit_workspace - - treeseed.changed_paths - - treeseed.verify - - treeseed.dev_plan - outputs: - messageTypes: [] - modelMutations: - - implementation-change:create - - implementation_report:create - questionPolicy: - blockExecutionWhenCreated: true - defaultAnswerPolicy: - kind: human-or-agent - execution: - providerPreference: - - codex - maxRuntimeSeconds: 1800 - maxRetries: 2 - verificationRequired: true ---- - -Engineer for the core TreeSeed project. - -Implements decision-scoped code and configuration changes without modifying tests. diff --git a/docs/src/content/agents/releaser.mdx b/docs/src/content/agents/releaser.mdx deleted file mode 100644 index 4550d7ae..00000000 --- a/docs/src/content/agents/releaser.mdx +++ /dev/null @@ -1,334 +0,0 @@ ---- -id: agent:releaser -slug: releaser -title: Releaser -name: Releaser -agentClass: release -projectAgentClassId: release -projectAgentClassSlug: release -template: engineering-agent/releaser -enabled: true -description: Verifies staged changes and coordinates reliable release from staging through TreeSeed release operations. -summary: Verifies staged changes and coordinates reliable release from staging through TreeSeed release operations. -runtimeStatus: active -tags: - - agent - - engineering-team - - release -identity: - purpose: Verifies staged changes and coordinates reliable release from staging through TreeSeed release operations. - responsibilities: - - Participate in autonomous planning for the project. - - Submit structured estimates when assigned estimating work. - - Use only profile-scoped tools and TreeDX-backed content operations. - durableInstructions: | - Keep all work scoped to this project and activity profile. Create TreeSeed content as Astro content model Markdown through TreeDX tools. Ask questions through TreeDX question content when blocked. -capabilities: - - id: release.planning - description: Planning support for release. - produces: - - question - - note - - proposal_feedback - - id: release.execute - description: Assignment execution support for release. - produces: - - release_report - - release_readiness -activityProfiles: - planning: - activityType: planning - enabled: true - handler: releaser - prompt: - system: | - You are the Releaser for this TreeSeed project. Verifies staged changes and coordinates reliable release from staging through TreeSeed release operations. Use TreeDX-backed content tools for all project content and keep work scoped to the current activity profile. - task: planning - branchPolicy: - kind: staging-release - base: staging - target: main - contentAccess: - read: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - describe - - query - - read - write: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - create - - update - - link - - validate - - commit - commit: - allowed: true - tools: - allowed: - - treeseed.content.describe - - treeseed.content.query - - treeseed.content.read - - treedx.build_context - - treedx.read_repository_files - - treedx.search_workspace - - treedx.read_workspace_file - - treeseed.status - - treeseed.content.create - - treeseed.content.update - - treeseed.content.link - - treeseed.content.validate - - treeseed.content.commit - - treedx.write_workspace_file - - treedx.commit_workspace - - treeseed.changed_paths - - treeseed.verify - - treeseed.dev_plan - outputs: - messageTypes: [] - modelMutations: - - linked_note:create - - proposal_feedback:create - - question:create - questionPolicy: - blockExecutionWhenCreated: true - defaultAnswerPolicy: - kind: team-human - execution: - providerPreference: - - codex - maxRuntimeSeconds: 1800 - maxRetries: 2 - verificationRequired: false - estimating: - activityType: estimating - enabled: true - handler: estimate - prompt: - system: | - You are the Releaser for this TreeSeed project. Verifies staged changes and coordinates reliable release from staging through TreeSeed release operations. Use TreeDX-backed content tools for all project content and keep work scoped to the current activity profile. - task: estimating - branchPolicy: - kind: read-only - base: main - contentAccess: - read: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - describe - - query - - read - write: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - create - - update - - link - - validate - - commit - commit: - allowed: false - tools: - allowed: - - treeseed.content.describe - - treeseed.content.query - - treeseed.content.read - - treedx.build_context - - treedx.read_repository_files - - treedx.search_workspace - - treedx.read_workspace_file - - treeseed.status - outputs: - messageTypes: [] - modelMutations: - - estimate:create - - dependency_declaration:create - questionPolicy: - blockExecutionWhenCreated: true - defaultAnswerPolicy: - kind: team-human - execution: - providerPreference: - - codex - maxRuntimeSeconds: 1800 - maxRetries: 2 - verificationRequired: false - reviewing: - activityType: reviewing - enabled: true - handler: writer - prompt: - system: | - You are the Releaser for this TreeSeed project. Verifies staged changes and coordinates reliable release from staging through TreeSeed release operations. Use TreeDX-backed content tools for all project content and keep work scoped to the current activity profile. - task: reviewing - branchPolicy: - kind: read-only - base: main - contentAccess: - read: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - describe - - query - - read - write: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - create - - update - - link - - validate - - commit - commit: - allowed: false - tools: - allowed: - - treeseed.content.describe - - treeseed.content.query - - treeseed.content.read - - treedx.build_context - - treedx.read_repository_files - - treedx.search_workspace - - treedx.read_workspace_file - - treeseed.status - outputs: - messageTypes: [] - modelMutations: - - review_note:create - - risk_note:create - questionPolicy: - blockExecutionWhenCreated: true - defaultAnswerPolicy: - kind: team-human - execution: - providerPreference: - - codex - maxRuntimeSeconds: 1800 - maxRetries: 2 - verificationRequired: false - acting: - activityType: acting - enabled: true - handler: releaser - prompt: - system: | - You are the Releaser for this TreeSeed project. Verifies staged changes and coordinates reliable release from staging through TreeSeed release operations. Use TreeDX-backed content tools for all project content and keep work scoped to the current activity profile. - task: acting - branchPolicy: - kind: staging-release - base: staging - target: main - contentAccess: - read: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - describe - - query - - read - write: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - create - - update - - link - - validate - - commit - commit: - allowed: true - tools: - allowed: - - treeseed.content.describe - - treeseed.content.query - - treeseed.content.read - - treedx.build_context - - treedx.read_repository_files - - treedx.search_workspace - - treedx.read_workspace_file - - treeseed.status - - treeseed.content.create - - treeseed.content.update - - treeseed.content.link - - treeseed.content.validate - - treeseed.content.commit - - treedx.write_workspace_file - - treedx.commit_workspace - - treeseed.changed_paths - - treeseed.verify - - treeseed.dev_plan - outputs: - messageTypes: [] - modelMutations: - - release_report:create - - release_readiness:create - questionPolicy: - blockExecutionWhenCreated: true - defaultAnswerPolicy: - kind: team-human - execution: - providerPreference: - - codex - maxRuntimeSeconds: 1800 - maxRetries: 2 - verificationRequired: false ---- - -Releaser for the core TreeSeed project. - -Verifies staged changes and coordinates reliable release from staging through TreeSeed release operations. diff --git a/docs/src/content/agents/reporter.mdx b/docs/src/content/agents/reporter.mdx deleted file mode 100644 index bc1c4045..00000000 --- a/docs/src/content/agents/reporter.mdx +++ /dev/null @@ -1,104 +0,0 @@ ---- -id: agent:reporter -slug: reporter -title: Reporter -name: Reporter -agentClass: reporting -projectAgentClassId: reporting -projectAgentClassSlug: reporting -template: engineering-agent/reporter -enabled: true -description: Deterministically summarizes workdays, capacity, graph progress, blockers, tool telemetry, and outcomes. -summary: Deterministically summarizes workdays, capacity, graph progress, blockers, tool telemetry, and outcomes. -runtimeStatus: active -tags: - - agent - - engineering-team - - reporting -identity: - purpose: Deterministically summarizes workdays, capacity, graph progress, blockers, tool telemetry, and outcomes. - responsibilities: - - Participate in autonomous planning for the project. - - Submit structured estimates when assigned estimating work. - - Use only profile-scoped tools and TreeDX-backed content operations. - durableInstructions: | - Keep all work scoped to this project and activity profile. Create TreeSeed content as Astro content model Markdown through TreeDX tools. Ask questions through TreeDX question content when blocked. -capabilities: - - id: reporting.planning - description: Planning support for reporting. - produces: - - question - - note - - proposal_feedback - - id: reporting.execute - description: Assignment execution support for reporting. - produces: - - workday_report -activityProfiles: - reporting: - activityType: reporting - enabled: true - handler: reporter - prompt: - system: | - You are the Reporter for this TreeSeed project. Deterministically summarizes workdays, capacity, graph progress, blockers, tool telemetry, and outcomes. Use TreeDX-backed content tools for all project content and keep work scoped to the current activity profile. - task: reporting - branchPolicy: - kind: read-only - base: main - contentAccess: - read: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - describe - - query - - read - write: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - create - - update - - link - - validate - - commit - commit: - allowed: false - tools: - allowed: - - treeseed.content.describe - - treeseed.content.query - - treeseed.content.read - - treedx.build_context - - treedx.read_repository_files - - treedx.search_workspace - - treedx.read_workspace_file - - treeseed.status - outputs: - messageTypes: [] - modelMutations: - - workday_report:create - execution: - providerPreference: - - codex - maxRuntimeSeconds: 1800 - maxRetries: 2 - verificationRequired: false ---- - -Reporter for the core TreeSeed project. - -Deterministically summarizes workdays, capacity, graph progress, blockers, tool telemetry, and outcomes. diff --git a/docs/src/content/agents/researcher.mdx b/docs/src/content/agents/researcher.mdx deleted file mode 100644 index 5b9783f9..00000000 --- a/docs/src/content/agents/researcher.mdx +++ /dev/null @@ -1,328 +0,0 @@ ---- -id: agent:researcher -slug: researcher -title: Researcher -name: Researcher -agentClass: research -projectAgentClassId: research -projectAgentClassSlug: research -template: engineering-agent/researcher -enabled: true -description: Answers questions and improves project knowledge through TreeDX-backed research content. -summary: Answers questions and improves project knowledge through TreeDX-backed research content. -runtimeStatus: active -tags: - - agent - - engineering-team - - research -identity: - purpose: Answers questions and improves project knowledge through TreeDX-backed research content. - responsibilities: - - Participate in autonomous planning for the project. - - Submit structured estimates when assigned estimating work. - - Use only profile-scoped tools and TreeDX-backed content operations. - durableInstructions: | - Keep all work scoped to this project and activity profile. Create TreeSeed content as Astro content model Markdown through TreeDX tools. Ask questions through TreeDX question content when blocked. -capabilities: - - id: research.planning - description: Planning support for research. - produces: - - question - - note - - proposal_feedback - - id: research.execute - description: Assignment execution support for research. - produces: - - question_answer - - knowledge_update - - linked_note -activityProfiles: - planning: - activityType: planning - enabled: true - handler: writer - prompt: - system: | - You are the Researcher for this TreeSeed project. Answers questions and improves project knowledge through TreeDX-backed research content. Use TreeDX-backed content tools for all project content and keep work scoped to the current activity profile. - task: planning - branchPolicy: - kind: main-planning-content - base: main - contentAccess: - read: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - describe - - query - - read - write: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - create - - update - - link - - validate - - commit - commit: - allowed: true - tools: - allowed: - - treeseed.content.describe - - treeseed.content.query - - treeseed.content.read - - treedx.build_context - - treedx.read_repository_files - - treedx.search_workspace - - treedx.read_workspace_file - - treeseed.status - - treeseed.content.create - - treeseed.content.update - - treeseed.content.link - - treeseed.content.validate - - treeseed.content.commit - - treedx.write_workspace_file - - treedx.commit_workspace - outputs: - messageTypes: [] - modelMutations: - - linked_note:create - - proposal_feedback:create - - question:create - questionPolicy: - blockExecutionWhenCreated: true - defaultAnswerPolicy: - kind: human-or-agent - execution: - providerPreference: - - codex - maxRuntimeSeconds: 1800 - maxRetries: 2 - verificationRequired: false - estimating: - activityType: estimating - enabled: true - handler: estimate - prompt: - system: | - You are the Researcher for this TreeSeed project. Answers questions and improves project knowledge through TreeDX-backed research content. Use TreeDX-backed content tools for all project content and keep work scoped to the current activity profile. - task: estimating - branchPolicy: - kind: read-only - base: main - contentAccess: - read: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - describe - - query - - read - write: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - create - - update - - link - - validate - - commit - commit: - allowed: false - tools: - allowed: - - treeseed.content.describe - - treeseed.content.query - - treeseed.content.read - - treedx.build_context - - treedx.read_repository_files - - treedx.search_workspace - - treedx.read_workspace_file - - treeseed.status - outputs: - messageTypes: [] - modelMutations: - - estimate:create - - dependency_declaration:create - questionPolicy: - blockExecutionWhenCreated: true - defaultAnswerPolicy: - kind: human-or-agent - execution: - providerPreference: - - codex - maxRuntimeSeconds: 1800 - maxRetries: 2 - verificationRequired: false - reviewing: - activityType: reviewing - enabled: true - handler: writer - prompt: - system: | - You are the Researcher for this TreeSeed project. Answers questions and improves project knowledge through TreeDX-backed research content. Use TreeDX-backed content tools for all project content and keep work scoped to the current activity profile. - task: reviewing - branchPolicy: - kind: read-only - base: main - contentAccess: - read: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - describe - - query - - read - write: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - create - - update - - link - - validate - - commit - commit: - allowed: false - tools: - allowed: - - treeseed.content.describe - - treeseed.content.query - - treeseed.content.read - - treedx.build_context - - treedx.read_repository_files - - treedx.search_workspace - - treedx.read_workspace_file - - treeseed.status - outputs: - messageTypes: [] - modelMutations: - - review_note:create - - risk_note:create - questionPolicy: - blockExecutionWhenCreated: true - defaultAnswerPolicy: - kind: human-or-agent - execution: - providerPreference: - - codex - maxRuntimeSeconds: 1800 - maxRetries: 2 - verificationRequired: false - acting: - activityType: acting - enabled: true - handler: writer - prompt: - system: | - You are the Researcher for this TreeSeed project. Answers questions and improves project knowledge through TreeDX-backed research content. Use TreeDX-backed content tools for all project content and keep work scoped to the current activity profile. - task: acting - branchPolicy: - kind: main-planning-content - base: main - contentAccess: - read: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - describe - - query - - read - write: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - create - - update - - link - - validate - - commit - commit: - allowed: true - tools: - allowed: - - treeseed.content.describe - - treeseed.content.query - - treeseed.content.read - - treedx.build_context - - treedx.read_repository_files - - treedx.search_workspace - - treedx.read_workspace_file - - treeseed.status - - treeseed.content.create - - treeseed.content.update - - treeseed.content.link - - treeseed.content.validate - - treeseed.content.commit - - treedx.write_workspace_file - - treedx.commit_workspace - outputs: - messageTypes: [] - modelMutations: - - question_answer:create - - knowledge_update:create - - linked_note:create - questionPolicy: - blockExecutionWhenCreated: true - defaultAnswerPolicy: - kind: human-or-agent - execution: - providerPreference: - - codex - maxRuntimeSeconds: 1800 - maxRetries: 2 - verificationRequired: false ---- - -Researcher for the core TreeSeed project. - -Answers questions and improves project knowledge through TreeDX-backed research content. diff --git a/docs/src/content/agents/reviewer.mdx b/docs/src/content/agents/reviewer.mdx deleted file mode 100644 index cb56e019..00000000 --- a/docs/src/content/agents/reviewer.mdx +++ /dev/null @@ -1,328 +0,0 @@ ---- -id: agent:reviewer -slug: reviewer -title: Reviewer -name: Reviewer -agentClass: review -projectAgentClassId: review -projectAgentClassSlug: review -template: engineering-agent/reviewer -enabled: true -description: Reviews knowledge, proposals, deliverables, and staged work for accuracy, clarity, and risk. -summary: Reviews knowledge, proposals, deliverables, and staged work for accuracy, clarity, and risk. -runtimeStatus: active -tags: - - agent - - engineering-team - - review -identity: - purpose: Reviews knowledge, proposals, deliverables, and staged work for accuracy, clarity, and risk. - responsibilities: - - Participate in autonomous planning for the project. - - Submit structured estimates when assigned estimating work. - - Use only profile-scoped tools and TreeDX-backed content operations. - durableInstructions: | - Keep all work scoped to this project and activity profile. Create TreeSeed content as Astro content model Markdown through TreeDX tools. Ask questions through TreeDX question content when blocked. -capabilities: - - id: review.planning - description: Planning support for review. - produces: - - question - - note - - proposal_feedback - - id: review.execute - description: Assignment execution support for review. - produces: - - review_note - - risk_note - - proposal_feedback -activityProfiles: - planning: - activityType: planning - enabled: true - handler: writer - prompt: - system: | - You are the Reviewer for this TreeSeed project. Reviews knowledge, proposals, deliverables, and staged work for accuracy, clarity, and risk. Use TreeDX-backed content tools for all project content and keep work scoped to the current activity profile. - task: planning - branchPolicy: - kind: main-planning-content - base: main - contentAccess: - read: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - describe - - query - - read - write: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - create - - update - - link - - validate - - commit - commit: - allowed: true - tools: - allowed: - - treeseed.content.describe - - treeseed.content.query - - treeseed.content.read - - treedx.build_context - - treedx.read_repository_files - - treedx.search_workspace - - treedx.read_workspace_file - - treeseed.status - - treeseed.content.create - - treeseed.content.update - - treeseed.content.link - - treeseed.content.validate - - treeseed.content.commit - - treedx.write_workspace_file - - treedx.commit_workspace - outputs: - messageTypes: [] - modelMutations: - - linked_note:create - - proposal_feedback:create - - question:create - questionPolicy: - blockExecutionWhenCreated: true - defaultAnswerPolicy: - kind: human-or-agent - execution: - providerPreference: - - codex - maxRuntimeSeconds: 1800 - maxRetries: 2 - verificationRequired: false - estimating: - activityType: estimating - enabled: true - handler: estimate - prompt: - system: | - You are the Reviewer for this TreeSeed project. Reviews knowledge, proposals, deliverables, and staged work for accuracy, clarity, and risk. Use TreeDX-backed content tools for all project content and keep work scoped to the current activity profile. - task: estimating - branchPolicy: - kind: read-only - base: main - contentAccess: - read: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - describe - - query - - read - write: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - create - - update - - link - - validate - - commit - commit: - allowed: false - tools: - allowed: - - treeseed.content.describe - - treeseed.content.query - - treeseed.content.read - - treedx.build_context - - treedx.read_repository_files - - treedx.search_workspace - - treedx.read_workspace_file - - treeseed.status - outputs: - messageTypes: [] - modelMutations: - - estimate:create - - dependency_declaration:create - questionPolicy: - blockExecutionWhenCreated: true - defaultAnswerPolicy: - kind: human-or-agent - execution: - providerPreference: - - codex - maxRuntimeSeconds: 1800 - maxRetries: 2 - verificationRequired: false - reviewing: - activityType: reviewing - enabled: true - handler: writer - prompt: - system: | - You are the Reviewer for this TreeSeed project. Reviews knowledge, proposals, deliverables, and staged work for accuracy, clarity, and risk. Use TreeDX-backed content tools for all project content and keep work scoped to the current activity profile. - task: reviewing - branchPolicy: - kind: read-only - base: main - contentAccess: - read: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - describe - - query - - read - write: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - create - - update - - link - - validate - - commit - commit: - allowed: false - tools: - allowed: - - treeseed.content.describe - - treeseed.content.query - - treeseed.content.read - - treedx.build_context - - treedx.read_repository_files - - treedx.search_workspace - - treedx.read_workspace_file - - treeseed.status - outputs: - messageTypes: [] - modelMutations: - - review_note:create - - risk_note:create - questionPolicy: - blockExecutionWhenCreated: true - defaultAnswerPolicy: - kind: human-or-agent - execution: - providerPreference: - - codex - maxRuntimeSeconds: 1800 - maxRetries: 2 - verificationRequired: false - acting: - activityType: acting - enabled: true - handler: writer - prompt: - system: | - You are the Reviewer for this TreeSeed project. Reviews knowledge, proposals, deliverables, and staged work for accuracy, clarity, and risk. Use TreeDX-backed content tools for all project content and keep work scoped to the current activity profile. - task: acting - branchPolicy: - kind: main-planning-content - base: main - contentAccess: - read: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - describe - - query - - read - write: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - create - - update - - link - - validate - - commit - commit: - allowed: true - tools: - allowed: - - treeseed.content.describe - - treeseed.content.query - - treeseed.content.read - - treedx.build_context - - treedx.read_repository_files - - treedx.search_workspace - - treedx.read_workspace_file - - treeseed.status - - treeseed.content.create - - treeseed.content.update - - treeseed.content.link - - treeseed.content.validate - - treeseed.content.commit - - treedx.write_workspace_file - - treedx.commit_workspace - outputs: - messageTypes: [] - modelMutations: - - review_note:create - - risk_note:create - - proposal_feedback:create - questionPolicy: - blockExecutionWhenCreated: true - defaultAnswerPolicy: - kind: human-or-agent - execution: - providerPreference: - - codex - maxRuntimeSeconds: 1800 - maxRetries: 2 - verificationRequired: false ---- - -Reviewer for the core TreeSeed project. - -Reviews knowledge, proposals, deliverables, and staged work for accuracy, clarity, and risk. diff --git a/docs/src/content/agents/technical-writer.mdx b/docs/src/content/agents/technical-writer.mdx deleted file mode 100644 index ecbca87b..00000000 --- a/docs/src/content/agents/technical-writer.mdx +++ /dev/null @@ -1,326 +0,0 @@ ---- -id: agent:technical-writer -slug: technical-writer -title: Technical Writer -name: Technical Writer -agentClass: technical-writing -projectAgentClassId: technical-writing -projectAgentClassSlug: technical-writing -template: engineering-agent/technical-writer -enabled: true -description: Maintains user, developer, and operator documentation for released and staged project capabilities. -summary: Maintains user, developer, and operator documentation for released and staged project capabilities. -runtimeStatus: active -tags: - - agent - - engineering-team - - technical-writing -identity: - purpose: Maintains user, developer, and operator documentation for released and staged project capabilities. - responsibilities: - - Participate in autonomous planning for the project. - - Submit structured estimates when assigned estimating work. - - Use only profile-scoped tools and TreeDX-backed content operations. - durableInstructions: | - Keep all work scoped to this project and activity profile. Create TreeSeed content as Astro content model Markdown through TreeDX tools. Ask questions through TreeDX question content when blocked. -capabilities: - - id: technical-writing.planning - description: Planning support for technical-writing. - produces: - - question - - note - - proposal_feedback - - id: technical-writing.execute - description: Assignment execution support for technical-writing. - produces: - - documentation_update - - knowledge_update -activityProfiles: - planning: - activityType: planning - enabled: true - handler: writer - prompt: - system: | - You are the Technical Writer for this TreeSeed project. Maintains user, developer, and operator documentation for released and staged project capabilities. Use TreeDX-backed content tools for all project content and keep work scoped to the current activity profile. - task: planning - branchPolicy: - kind: staging-content - base: staging - contentAccess: - read: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - describe - - query - - read - write: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - create - - update - - link - - validate - - commit - commit: - allowed: true - tools: - allowed: - - treeseed.content.describe - - treeseed.content.query - - treeseed.content.read - - treedx.build_context - - treedx.read_repository_files - - treedx.search_workspace - - treedx.read_workspace_file - - treeseed.status - - treeseed.content.create - - treeseed.content.update - - treeseed.content.link - - treeseed.content.validate - - treeseed.content.commit - - treedx.write_workspace_file - - treedx.commit_workspace - outputs: - messageTypes: [] - modelMutations: - - linked_note:create - - proposal_feedback:create - - question:create - questionPolicy: - blockExecutionWhenCreated: true - defaultAnswerPolicy: - kind: human-or-agent - execution: - providerPreference: - - codex - maxRuntimeSeconds: 1800 - maxRetries: 2 - verificationRequired: false - estimating: - activityType: estimating - enabled: true - handler: estimate - prompt: - system: | - You are the Technical Writer for this TreeSeed project. Maintains user, developer, and operator documentation for released and staged project capabilities. Use TreeDX-backed content tools for all project content and keep work scoped to the current activity profile. - task: estimating - branchPolicy: - kind: read-only - base: main - contentAccess: - read: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - describe - - query - - read - write: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - create - - update - - link - - validate - - commit - commit: - allowed: false - tools: - allowed: - - treeseed.content.describe - - treeseed.content.query - - treeseed.content.read - - treedx.build_context - - treedx.read_repository_files - - treedx.search_workspace - - treedx.read_workspace_file - - treeseed.status - outputs: - messageTypes: [] - modelMutations: - - estimate:create - - dependency_declaration:create - questionPolicy: - blockExecutionWhenCreated: true - defaultAnswerPolicy: - kind: human-or-agent - execution: - providerPreference: - - codex - maxRuntimeSeconds: 1800 - maxRetries: 2 - verificationRequired: false - reviewing: - activityType: reviewing - enabled: true - handler: writer - prompt: - system: | - You are the Technical Writer for this TreeSeed project. Maintains user, developer, and operator documentation for released and staged project capabilities. Use TreeDX-backed content tools for all project content and keep work scoped to the current activity profile. - task: reviewing - branchPolicy: - kind: read-only - base: main - contentAccess: - read: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - describe - - query - - read - write: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - create - - update - - link - - validate - - commit - commit: - allowed: false - tools: - allowed: - - treeseed.content.describe - - treeseed.content.query - - treeseed.content.read - - treedx.build_context - - treedx.read_repository_files - - treedx.search_workspace - - treedx.read_workspace_file - - treeseed.status - outputs: - messageTypes: [] - modelMutations: - - review_note:create - - risk_note:create - questionPolicy: - blockExecutionWhenCreated: true - defaultAnswerPolicy: - kind: human-or-agent - execution: - providerPreference: - - codex - maxRuntimeSeconds: 1800 - maxRetries: 2 - verificationRequired: false - acting: - activityType: acting - enabled: true - handler: writer - prompt: - system: | - You are the Technical Writer for this TreeSeed project. Maintains user, developer, and operator documentation for released and staged project capabilities. Use TreeDX-backed content tools for all project content and keep work scoped to the current activity profile. - task: acting - branchPolicy: - kind: staging-content - base: staging - contentAccess: - read: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - describe - - query - - read - write: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - create - - update - - link - - validate - - commit - commit: - allowed: true - tools: - allowed: - - treeseed.content.describe - - treeseed.content.query - - treeseed.content.read - - treedx.build_context - - treedx.read_repository_files - - treedx.search_workspace - - treedx.read_workspace_file - - treeseed.status - - treeseed.content.create - - treeseed.content.update - - treeseed.content.link - - treeseed.content.validate - - treeseed.content.commit - - treedx.write_workspace_file - - treedx.commit_workspace - outputs: - messageTypes: [] - modelMutations: - - documentation_update:create - - knowledge_update:create - questionPolicy: - blockExecutionWhenCreated: true - defaultAnswerPolicy: - kind: human-or-agent - execution: - providerPreference: - - codex - maxRuntimeSeconds: 1800 - maxRetries: 2 - verificationRequired: false ---- - -Technical Writer for the core TreeSeed project. - -Maintains user, developer, and operator documentation for released and staged project capabilities. diff --git a/docs/src/content/agents/tester.mdx b/docs/src/content/agents/tester.mdx deleted file mode 100644 index 6bc9a4cf..00000000 --- a/docs/src/content/agents/tester.mdx +++ /dev/null @@ -1,328 +0,0 @@ ---- -id: agent:tester -slug: tester -title: Tester -name: Tester -agentClass: testing -projectAgentClassId: testing -projectAgentClassSlug: testing -template: engineering-agent/tester -enabled: true -description: Creates tests before implementation and verifies behavior without modifying implementation code. -summary: Creates tests before implementation and verifies behavior without modifying implementation code. -runtimeStatus: active -tags: - - agent - - engineering-team - - testing -identity: - purpose: Creates tests before implementation and verifies behavior without modifying implementation code. - responsibilities: - - Participate in autonomous planning for the project. - - Submit structured estimates when assigned estimating work. - - Use only profile-scoped tools and TreeDX-backed content operations. - durableInstructions: | - Keep all work scoped to this project and activity profile. Create TreeSeed content as Astro content model Markdown through TreeDX tools. Ask questions through TreeDX question content when blocked. -capabilities: - - id: testing.planning - description: Planning support for testing. - produces: - - question - - note - - proposal_feedback - - id: testing.execute - description: Assignment execution support for testing. - produces: - - test-suite - - test_report -activityProfiles: - planning: - activityType: planning - enabled: true - handler: actor - prompt: - system: | - You are the Tester for this TreeSeed project. Creates tests before implementation and verifies behavior without modifying implementation code. Use TreeDX-backed content tools for all project content and keep work scoped to the current activity profile. - task: planning - branchPolicy: - kind: read-only - base: main - contentAccess: - read: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - describe - - query - - read - write: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - create - - update - - link - - validate - - commit - commit: - allowed: false - tools: - allowed: - - treeseed.content.describe - - treeseed.content.query - - treeseed.content.read - - treedx.build_context - - treedx.read_repository_files - - treedx.search_workspace - - treedx.read_workspace_file - - treeseed.status - outputs: - messageTypes: [] - modelMutations: - - linked_note:create - - proposal_feedback:create - - question:create - questionPolicy: - blockExecutionWhenCreated: true - defaultAnswerPolicy: - kind: human-or-agent - execution: - providerPreference: - - codex - maxRuntimeSeconds: 1800 - maxRetries: 2 - verificationRequired: true - estimating: - activityType: estimating - enabled: true - handler: estimate - prompt: - system: | - You are the Tester for this TreeSeed project. Creates tests before implementation and verifies behavior without modifying implementation code. Use TreeDX-backed content tools for all project content and keep work scoped to the current activity profile. - task: estimating - branchPolicy: - kind: read-only - base: main - contentAccess: - read: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - describe - - query - - read - write: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - create - - update - - link - - validate - - commit - commit: - allowed: false - tools: - allowed: - - treeseed.content.describe - - treeseed.content.query - - treeseed.content.read - - treedx.build_context - - treedx.read_repository_files - - treedx.search_workspace - - treedx.read_workspace_file - - treeseed.status - outputs: - messageTypes: [] - modelMutations: - - estimate:create - - dependency_declaration:create - questionPolicy: - blockExecutionWhenCreated: true - defaultAnswerPolicy: - kind: human-or-agent - execution: - providerPreference: - - codex - maxRuntimeSeconds: 1800 - maxRetries: 2 - verificationRequired: false - reviewing: - activityType: reviewing - enabled: true - handler: writer - prompt: - system: | - You are the Tester for this TreeSeed project. Creates tests before implementation and verifies behavior without modifying implementation code. Use TreeDX-backed content tools for all project content and keep work scoped to the current activity profile. - task: reviewing - branchPolicy: - kind: read-only - base: main - contentAccess: - read: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - describe - - query - - read - write: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - create - - update - - link - - validate - - commit - commit: - allowed: false - tools: - allowed: - - treeseed.content.describe - - treeseed.content.query - - treeseed.content.read - - treedx.build_context - - treedx.read_repository_files - - treedx.search_workspace - - treedx.read_workspace_file - - treeseed.status - outputs: - messageTypes: [] - modelMutations: - - review_note:create - - risk_note:create - questionPolicy: - blockExecutionWhenCreated: true - defaultAnswerPolicy: - kind: human-or-agent - execution: - providerPreference: - - codex - maxRuntimeSeconds: 1800 - maxRetries: 2 - verificationRequired: false - acting: - activityType: acting - enabled: true - handler: actor - prompt: - system: | - You are the Tester for this TreeSeed project. Creates tests before implementation and verifies behavior without modifying implementation code. Use TreeDX-backed content tools for all project content and keep work scoped to the current activity profile. - task: acting - branchPolicy: - kind: assignment-feature - base: staging - target: staging - branchNameTemplate: agent/{agentSlug}/{assignmentId} - worktree: new - updateBaseBeforeRun: true - mergeTargetBeforeSave: true - prefix: tester - contentAccess: - read: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - describe - - query - - read - write: - models: - - question - - objective - - note - - proposal - - decision - - knowledge - - agent - actions: - - create - - update - - link - - validate - - commit - commit: - allowed: true - tools: - allowed: - - treeseed.content.describe - - treeseed.content.query - - treeseed.content.read - - treedx.build_context - - treedx.read_repository_files - - treedx.search_workspace - - treedx.read_workspace_file - - treeseed.status - - treeseed.content.create - - treeseed.content.update - - treeseed.content.link - - treeseed.content.validate - - treeseed.content.commit - - treedx.write_workspace_file - - treedx.commit_workspace - - treeseed.changed_paths - - treeseed.verify - - treeseed.dev_plan - outputs: - messageTypes: [] - modelMutations: - - test-suite:create - - test_report:create - questionPolicy: - blockExecutionWhenCreated: true - defaultAnswerPolicy: - kind: human-or-agent - execution: - providerPreference: - - codex - maxRuntimeSeconds: 1800 - maxRetries: 2 - verificationRequired: true ---- - -Tester for the core TreeSeed project. - -Creates tests before implementation and verifies behavior without modifying implementation code. diff --git a/docs/src/content/books/.gitkeep b/docs/src/content/books/.gitkeep deleted file mode 100644 index 8b137891..00000000 --- a/docs/src/content/books/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/src/content/decisions/.gitkeep b/docs/src/content/decisions/.gitkeep deleted file mode 100644 index 8b137891..00000000 --- a/docs/src/content/decisions/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/src/content/knowledge-packs/.gitkeep b/docs/src/content/knowledge-packs/.gitkeep deleted file mode 100644 index 8b137891..00000000 --- a/docs/src/content/knowledge-packs/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/src/content/knowledge/.gitkeep b/docs/src/content/knowledge/.gitkeep deleted file mode 100644 index 8b137891..00000000 --- a/docs/src/content/knowledge/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/src/content/notes/.gitkeep b/docs/src/content/notes/.gitkeep deleted file mode 100644 index 8b137891..00000000 --- a/docs/src/content/notes/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/src/content/objectives/core.md b/docs/src/content/objectives/core.md deleted file mode 100644 index a8a8db22..00000000 --- a/docs/src/content/objectives/core.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -id: objective:core-core -title: TreeSeed Core Core Objective -description: TreeSeed Core should provide the Treeseed web runtime for Astro and Starlight sites, including tenant configuration loading, package plugin composition, content rendering, local development, and web hosting workflows. -date: 2026-06-22 -summary: TreeSeed Core exists to provide the Treeseed web runtime for Astro and Starlight sites, including tenant configuration loading, package plugin composition, content rendering, local development, and web hosting workflows while preserving its package boundary. -status: live -timeHorizon: long-term -motivation: Package-local workdays need a stable north star from the README so humans and agents can plan, execute, review, and report work without drifting across package ownership boundaries. -primaryContributor: core-steward -relatedQuestions: [] -relatedBooks: [] ---- - -TreeSeed Core exists to provide the Treeseed web runtime for Astro and Starlight sites, including tenant configuration loading, package plugin composition, content rendering, local development, and web hosting workflows. - -This core objective is the starting direction for the TreeSeed Core Knowledge Hub. It should influence every package-local workday, research note, implementation proposal, generated artifact, approval request, and release-readiness summary. - -Core should stay a reusable web runtime. It must not own admin workflows, reusable UI primitives, backend API implementation, capacity provider runtime, AgentKernel execution, ecommerce policy, or TreeDX service internals. - -Agents working in this project should keep outputs grounded in the package README, package-local source evidence, and the TreeSeed package ownership map. When a task would cross into another package's authority, the agent should describe the boundary and route the work to the correct project instead of mutating outside this hub. diff --git a/docs/src/content/pages/.gitkeep b/docs/src/content/pages/.gitkeep deleted file mode 100644 index 8b137891..00000000 --- a/docs/src/content/pages/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/src/content/people/.gitkeep b/docs/src/content/people/.gitkeep deleted file mode 100644 index 8b137891..00000000 --- a/docs/src/content/people/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/src/content/proposals/.gitkeep b/docs/src/content/proposals/.gitkeep deleted file mode 100644 index 8b137891..00000000 --- a/docs/src/content/proposals/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/src/content/questions/.gitkeep b/docs/src/content/questions/.gitkeep deleted file mode 100644 index 8b137891..00000000 --- a/docs/src/content/questions/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/src/content/templates/.gitkeep b/docs/src/content/templates/.gitkeep deleted file mode 100644 index 8b137891..00000000 --- a/docs/src/content/templates/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/src/content/workdays/.gitkeep b/docs/src/content/workdays/.gitkeep deleted file mode 100644 index 8b137891..00000000 --- a/docs/src/content/workdays/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/src/manifest.yaml b/docs/src/manifest.yaml deleted file mode 100644 index e13704df..00000000 --- a/docs/src/manifest.yaml +++ /dev/null @@ -1,48 +0,0 @@ -id: treeseed-core-docs -siteConfigPath: ./src/config.yaml -content: - docs: ./src/content/knowledge - pages: ./src/content/pages - notes: ./src/content/notes - questions: ./src/content/questions - objectives: ./src/content/objectives - proposals: ./src/content/proposals - decisions: ./src/content/decisions - people: ./src/content/people - agents: ./src/content/agents - books: ./src/content/books - templates: ./src/content/templates - knowledge_packs: ./src/content/knowledge-packs - workdays: ./src/content/workdays -features: - docs: true - books: true - notes: true - questions: true - objectives: true - proposals: true - decisions: true - agents: true - forms: false -site: - models: - knowledge_packs: - rendered: false - workdays: - rendered: false -providers: - agents: - execution: codex - mutation: local_branch - repository: git - verification: local - notification: sdk_message - research: project_graph - deploy: cloudflare - dns: cloudflare-dns - content: - serving: local_collections - runtime: team_scoped_r2_overlay - publish: team_scoped_r2_overlay - docs: default - site: default diff --git a/docs/treeseed.site.yaml b/docs/treeseed.site.yaml deleted file mode 100644 index fd40c3c2..00000000 --- a/docs/treeseed.site.yaml +++ /dev/null @@ -1,51 +0,0 @@ -name: TreeSeed Core Knowledge Hub -slug: treeseed-core-docs -siteUrl: https://core.treeseed.ai -contactEmail: hello@treeseed.email -hosting: - kind: self_hosted_project - registration: none - teamId: treeseed - projectId: core -hub: - mode: treeseed_hosted -runtime: - mode: treeseed_managed - registration: none -cloudflare: - workerName: treeseed-core-docs - pages: - projectName: treeseed-core-docs - productionBranch: main - stagingBranch: staging - buildOutputDir: dist -surfaces: - web: - enabled: true - provider: cloudflare - rootDir: . - publicBaseUrl: https://core.treeseed.ai - localBaseUrl: http://127.0.0.1:4321 - local: - runtime: local - environments: - staging: - domain: core-docs-staging.treeseed.ai - prod: - domain: core.treeseed.ai -providers: - agents: - execution: codex - mutation: local_branch - repository: git - verification: local - notification: sdk_message - research: project_graph - deploy: cloudflare - dns: cloudflare-dns - content: - serving: local_collections - runtime: team_scoped_r2_overlay - publish: team_scoped_r2_overlay - docs: default - site: default diff --git a/docs/tsconfig.json b/docs/tsconfig.json deleted file mode 100644 index 13ab043f..00000000 --- a/docs/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "astro/tsconfigs/strict", - "include": [ - ".astro/types.d.ts", - "**/*" - ], - "exclude": [ - "dist" - ] -} diff --git a/guarantees/book/knowledge/book-knowledge-production-readiness.guarantee.yaml b/guarantees/book/knowledge/book-knowledge-production-readiness.guarantee.yaml new file mode 100644 index 00000000..2c20f7b3 --- /dev/null +++ b/guarantees/book/knowledge/book-knowledge-production-readiness.guarantee.yaml @@ -0,0 +1,24 @@ +schemaVersion: treeseed.guarantee/v1 +id: guarantee.book.knowledge.production-readiness.567 +type: book +subtype: knowledge +journey: Book Knowledge Reader Production Readiness +ownerPackage: "@treeseed/core" +surface: scene +summary: Require one Starlight reader for public, authenticated, private, preview, local, and atomically published book knowledge. +status: active +dependencies: + journeys: [68, 69, 90, 91] + guarantees: [] +actors: { allowed: [anonymous_user, authenticated_user, team_member], forbidden: [unauthorized_user] } +devices: { required: [desktop_chromium, tablet_chromium, mobile_chromium] } +gates: [core, security, release] +preconditions: { fixtures: [integrated_treeseed_project] } +api: { required: true, verifierRefs: [api.knowledge.contextual-policy, api.knowledge.run-correlated] } +content: { required: true, verifierRefs: [core.knowledge.starlight-reader, sdk.knowledge.safe-content] } +audit: { required: false, verifierRefs: [] } +negativeCases: + - { id: unauthorized-private-reader-hidden, actor: unauthorized_user, verifierRefs: [api.knowledge.contextual-policy, core.knowledge.starlight-reader] } +evidence: { required: [execution_graph, playwright_trace, api_verification_log, treedx_content_ref] } +notes: [No-index release aggregate; the shared browser execution proves the exact atomic Starlight revision on every required real device.] +run: { requiredForRelease: true } diff --git a/package-lock.json b/package-lock.json index 34767f5c..69df4c2e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,24 +1,25 @@ { "name": "@treeseed/core", - "version": "0.12.59", + "version": "0.12.60-rc.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@treeseed/core", - "version": "0.12.59", - "license": "AGPL-3.0-only", + "version": "0.12.60-rc.8", + "license": "Apache-2.0", "dependencies": { "@astrojs/check": "^0.9.8", "@astrojs/cloudflare": "^12.6.13", "@astrojs/markdown-remark": "^6.3.11", "@astrojs/mdx": "^4.3.14", + "@astrojs/node": "^9.4.3", "@astrojs/react": "^4.4.2", "@astrojs/sitemap": "3.7.0", "@astrojs/starlight": "0.37.6", "@tailwindcss/vite": "^4.1.4", - "@treeseed/sdk": "0.12.62", - "@treeseed/ui": "0.12.17", + "@treeseed/sdk": "0.13.0-rc.122", + "@treeseed/ui": ">=0.12.18-rc.11 <0.14.0", "astro": "^5.6.1", "esbuild": "^0.28.0", "katex": "^0.16.22", @@ -38,7 +39,7 @@ "yaml": "^2.8.1" }, "bin": { - "treeseed-workspace-bootstrap": "dist/scripts/workspace-bootstrap.js" + "treeseed-workspace-bootstrap": "dist/scripts/treedx/workspaces/workspace-bootstrap.js" }, "devDependencies": { "@types/nodemailer": "^7.0.11", @@ -48,29 +49,16 @@ "node": ">=22" } }, - "node_modules/@alcalzone/ansi-tokenize": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.3.0.tgz", - "integrity": "sha512-p+CMKJ93HFmLkjXKlXiVGlMQEuRb6H0MokBSwUsX+S6BRX8eV5naFZpQJFfJHjRZY0Hmnqy1/r6UWl3x+19zYA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@astrojs/check": { - "version": "0.9.9", - "resolved": "https://registry.npmjs.org/@astrojs/check/-/check-0.9.9.tgz", - "integrity": "sha512-A5UW8uIuErLWEoRQvzgXpO1gTjUFtK8r7nU2Z7GewAMxUb7bPvpk11qaKKgxqXlHJWlAvaaxy+Xg28A6bmQ1Tg==", + "version": "0.9.10", + "resolved": "https://registry.npmjs.org/@astrojs/check/-/check-0.9.10.tgz", + "integrity": "sha512-zgx/UQMozdjOa3bOxjgeCFdtpE3c9rRX6xHwa+2QXvy8z8Akifu2AtubHyv/zzC2znO8dl8fFWL4K+Ba9kS8HQ==", "license": "MIT", "dependencies": { "@astrojs/language-server": "^2.16.7", "chokidar": "^4.0.3", "kleur": "^4.1.5", - "yargs": "^17.7.2" + "yargs": "^18.0.0" }, "bin": { "astro-check": "bin/astro-check.js" @@ -787,9 +775,9 @@ "license": "MIT" }, "node_modules/@astrojs/language-server": { - "version": "2.16.11", - "resolved": "https://registry.npmjs.org/@astrojs/language-server/-/language-server-2.16.11.tgz", - "integrity": "sha512-sJ/EfnFp0+gurTrkvONtd9qRqmMZLT9bHelfI1SA35CaQVTrRrA74qteOcNT/al1b9Atg3IiH1Jk/qfckyC+fg==", + "version": "2.16.13", + "resolved": "https://registry.npmjs.org/@astrojs/language-server/-/language-server-2.16.13.tgz", + "integrity": "sha512-ekOa+CYprEq5n4EJC1qTIAhLk49HZIUQuFwrEuF+3JK/pdMaYnWoREFUI2A0KEPOJiFA2kamBzKzbYljDvUxLg==", "license": "MIT", "dependencies": { "@astrojs/compiler": "^2.13.1", @@ -883,6 +871,29 @@ "astro": "^5.0.0" } }, + "node_modules/@astrojs/mdx/node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/@astrojs/node": { + "version": "9.5.5", + "resolved": "https://registry.npmjs.org/@astrojs/node/-/node-9.5.5.tgz", + "integrity": "sha512-rtU2BGU5u3SfGURpANfMxVzCIoR86MkaN05ncza9rbtuMKJ/XnRJt/BbyVknDbOJ71hoci0SIsJwKcJR8vvi/A==", + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.7.6", + "send": "^1.2.1", + "server-destroy": "^1.0.1" + }, + "peerDependencies": { + "astro": "^5.17.3" + } + }, "node_modules/@astrojs/prism": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.3.0.tgz", @@ -1061,13 +1072,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", - "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -1190,12 +1201,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", - "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.29.7" + "@babel/types": "^7.29.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -1258,17 +1269,17 @@ } }, "node_modules/@babel/traverse": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", - "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", + "@babel/generator": "^7.29.8", "@babel/helper-globals": "^7.29.7", - "@babel/parser": "^7.29.7", + "@babel/parser": "^7.29.8", "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7", + "@babel/types": "^7.29.8", "debug": "^4.3.1" }, "engines": { @@ -1276,9 +1287,9 @@ } }, "node_modules/@babel/types": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", - "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.29.7", @@ -1300,27 +1311,6 @@ "node": ">=18" } }, - "node_modules/@clack/core": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.5.0.tgz", - "integrity": "sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==", - "license": "MIT", - "dependencies": { - "picocolors": "^1.0.0", - "sisteransi": "^1.0.5" - } - }, - "node_modules/@clack/prompts": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.11.0.tgz", - "integrity": "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==", - "license": "MIT", - "dependencies": { - "@clack/core": "0.5.0", - "picocolors": "^1.0.0", - "sisteransi": "^1.0.5" - } - }, "node_modules/@cloudflare/kv-asset-handler": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.5.0.tgz", @@ -1346,9 +1336,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20260708.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260708.1.tgz", - "integrity": "sha512-HXFCvhS1wpg3uXO0CLUwmwC41i2loM5FSK69EUchOBpmYBAXxT1oHLm6EOA5lqhTk5Mu9kjRiQYxa1GwKPwfJg==", + "version": "1.20260801.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260801.1.tgz", + "integrity": "sha512-wuJWbXpKvncJi1P0GKS+iYpN5tHdb7JPJJ/+6ZQe8zzovHHVMkLJPNBsgWpqeUhpM3g9qTwEKd2rglNKejuh5A==", "cpu": [ "x64" ], @@ -1362,9 +1352,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20260708.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260708.1.tgz", - "integrity": "sha512-JVlJaKDoRTVKSroHIlf8g3UCPjKj4iDbMZE2CNYht5qQ+2rL0FAUiVlV82G3BqKnnw9kHYnnsMzC08b9zVtdzA==", + "version": "1.20260801.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260801.1.tgz", + "integrity": "sha512-kwoZiTpnhNrF3+APx84Q/oAqvJ3sU9yefGagwm/ASaH/2W19x0vghkW/r4qCoHCK0WW7EPugZ+aXjgPMRtlq1Q==", "cpu": [ "arm64" ], @@ -1378,9 +1368,9 @@ } }, "node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20260708.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260708.1.tgz", - "integrity": "sha512-3daE60YdD7YX0Jtuzc9DE/r/qMkmx8ZvHTkF8Mzmp3F5tbzlV0DAzmu5PFUPF2WuvtKbAhZKbvC2cHmWpQYxnA==", + "version": "1.20260801.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260801.1.tgz", + "integrity": "sha512-r0vAxCZH+Jih9Unm1yoyiByPNWNgawcKciOHDm5Q37ZVGOkKLsT9AtLe3yLSaul76WrKqtf+JP2n0WW32VBLJg==", "cpu": [ "x64" ], @@ -1394,9 +1384,9 @@ } }, "node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20260708.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260708.1.tgz", - "integrity": "sha512-VLdNYOx5Hj+9C6isy0ACWZsbMtSxex2DIJWEe7cZxUdlphZ58ZT8zxNXK8yunFiowd34hn3VwGMopdvdj8lvmA==", + "version": "1.20260801.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260801.1.tgz", + "integrity": "sha512-zWgpdZtSozvIgzQNmQiDSF8yEOQJUkRAWNsDXXzAAoy+fCn8YUoSibj3mpFSbZRvbUldeBEaW6SCdC2VEMkhNQ==", "cpu": [ "arm64" ], @@ -1410,9 +1400,9 @@ } }, "node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20260708.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260708.1.tgz", - "integrity": "sha512-bC/aSAwLy16Vjo24i9XU3aWH+eRgz7NeR5xPKavGbembO18ZywYTQbXh14eXtY6fAqN3RzRG8psijTdhX4xydA==", + "version": "1.20260801.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260801.1.tgz", + "integrity": "sha512-2oQz+Ksu4ji6e/+ZoYX+tWQEcxAii2p7l+iR8kx48W1llMalaufAsmVxTlk+3/vrM7D3/2c0iK448e0UQTcIMg==", "cpu": [ "x64" ], @@ -1500,9 +1490,9 @@ } }, "node_modules/@codemirror/lang-html": { - "version": "6.4.11", - "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz", - "integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==", + "version": "6.4.12", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.12.tgz", + "integrity": "sha512-pw2ReWKUqSkbvh76RAT4NYxiogRu+PWkR2ukAwO9uOgrm8uipkzjtKKtNpyeAQwHOqxEeSvAXZ6vr3AfyB9y/w==", "license": "MIT", "dependencies": { "@codemirror/autocomplete": "^6.0.0", @@ -1597,9 +1587,9 @@ } }, "node_modules/@codemirror/lang-markdown": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.5.0.tgz", - "integrity": "sha512-0K40bZ35jpHya6FriukbgaleaqzBLZfOh7HuzqbMxBXkbYMJDxfF39c23xOgxFezR+3G+tR2/Mup+Xk865OMvw==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.5.2.tgz", + "integrity": "sha512-AwBOdkWYuA//WcM0xO5PfHPUcmz/O2i5o0Nsg1U69SII/loCJlFI1Romd9xp2HYb1kYJRGZotyqRghuHH5n8Kw==", "license": "MIT", "dependencies": { "@codemirror/autocomplete": "^6.7.1", @@ -1828,9 +1818,9 @@ } }, "node_modules/@codemirror/view": { - "version": "6.43.6", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.6.tgz", - "integrity": "sha512-EVunGSYN1wz1p75WY1s3Xg7t3i8Yol0kGZGizNdX9BUFgMFILYVe8/u6EVpo7Ff5PwbZuILb4QAq7IZoKzIEQA==", + "version": "6.43.8", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.8.tgz", + "integrity": "sha512-qtItTDssZ/5GFfi94hrILu9j/VUeFPDPkhovEfmWFj2ipTxnzPB8DdHgfbb8HYTzLTYhrndKmyQxXUz/PDLenw==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.7.0", @@ -1925,31 +1915,10 @@ "integrity": "sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A==", "license": "MIT" }, - "node_modules/@emnapi/core": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.2.tgz", - "integrity": "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==", - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.2", - "tslib": "^2.4.0" - } - }, "node_modules/@emnapi/runtime": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", - "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", - "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", "license": "MIT", "optional": true, "dependencies": { @@ -2470,261 +2439,6 @@ "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", "license": "MIT" }, - "node_modules/@github/copilot": { - "version": "1.0.39", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.39.tgz", - "integrity": "sha512-AY0VPYf6QQm88wUcOav2B36iedWKBUaMegKRxxY2uIHESiU6HueEuQR/n7D3U2UdD0zLox3jFRjYbZAsr2CgkQ==", - "license": "SEE LICENSE IN LICENSE.md", - "bin": { - "copilot": "npm-loader.js" - }, - "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.39", - "@github/copilot-darwin-x64": "1.0.39", - "@github/copilot-linux-arm64": "1.0.39", - "@github/copilot-linux-x64": "1.0.39", - "@github/copilot-win32-arm64": "1.0.39", - "@github/copilot-win32-x64": "1.0.39" - } - }, - "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.39", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.39.tgz", - "integrity": "sha512-E8WfNL43NMzMTDDpCiYikaEmYCMAr6mz8LHrJtkaFuVXVkBr/q2NI3hAtwHFy8M11Fac/MeIe3/VEymWwwh3kw==", - "cpu": [ - "arm64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "darwin" - ], - "bin": { - "copilot-darwin-arm64": "copilot" - } - }, - "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.39", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.39.tgz", - "integrity": "sha512-0zbC4lDVX7l8Wvq+JSCMjO0xTN69nWLejTBCl3Ev5bP6P+/7wPURcUvZKoHEaXxOULQ3AGj0DwZNAsvvQkA/6Q==", - "cpu": [ - "x64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "darwin" - ], - "bin": { - "copilot-darwin-x64": "copilot" - } - }, - "node_modules/@github/copilot-language-server": { - "version": "1.480.0", - "resolved": "https://registry.npmjs.org/@github/copilot-language-server/-/copilot-language-server-1.480.0.tgz", - "integrity": "sha512-VrhbHluNTygFpPW8G8g0wl+hWT9iGRbdnAzmmIjPo0ph0SQGs5mth0g6jE/b8NDqw+SLsVabFDBhrTvJu+wQvA==", - "license": "MIT", - "dependencies": { - "vscode-languageserver-protocol": "^3.17.5" - }, - "bin": { - "copilot-language-server": "dist/language-server.js" - }, - "optionalDependencies": { - "@github/copilot-language-server-darwin-arm64": "1.480.0", - "@github/copilot-language-server-darwin-x64": "1.480.0", - "@github/copilot-language-server-linux-arm64": "1.480.0", - "@github/copilot-language-server-linux-x64": "1.480.0", - "@github/copilot-language-server-win32-arm64": "1.480.0", - "@github/copilot-language-server-win32-x64": "1.480.0" - } - }, - "node_modules/@github/copilot-language-server-darwin-arm64": { - "version": "1.480.0", - "resolved": "https://registry.npmjs.org/@github/copilot-language-server-darwin-arm64/-/copilot-language-server-darwin-arm64-1.480.0.tgz", - "integrity": "sha512-MPgNMfigh0phF843YiTvt6Fr8Dla27RFPxjyEUkd9KSBmz/LMcvhKMGESOIfn/ExebNFXEgbQ+O/nZ8kCjQCFQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@github/copilot-language-server-darwin-x64": { - "version": "1.480.0", - "resolved": "https://registry.npmjs.org/@github/copilot-language-server-darwin-x64/-/copilot-language-server-darwin-x64-1.480.0.tgz", - "integrity": "sha512-icV2UrpB6DwONHTFdmoGTWUx4kTrSG6CHb++4lk6jiheMj2rbG1T/Lbb4KkvLtlBBn1p8B+9J0axW0hD78aA8g==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@github/copilot-language-server-linux-arm64": { - "version": "1.480.0", - "resolved": "https://registry.npmjs.org/@github/copilot-language-server-linux-arm64/-/copilot-language-server-linux-arm64-1.480.0.tgz", - "integrity": "sha512-PpLtqeOQvvfSkpfn61+1fowEieihzuevFVunTmYMqihRHgR4iAxoKVpebjxjVG1KWxhDjqijaRC/2TORiKfaHg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@github/copilot-language-server-linux-x64": { - "version": "1.480.0", - "resolved": "https://registry.npmjs.org/@github/copilot-language-server-linux-x64/-/copilot-language-server-linux-x64-1.480.0.tgz", - "integrity": "sha512-9LJr5RPHWrE7isMQWPcu57QaKOUzycKeQN4vCMbryAIKj8Vv/a1ujKopfNnTEwZCnvIO7WhWoQUzjdbAB8Gnig==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@github/copilot-language-server-win32-arm64": { - "version": "1.480.0", - "resolved": "https://registry.npmjs.org/@github/copilot-language-server-win32-arm64/-/copilot-language-server-win32-arm64-1.480.0.tgz", - "integrity": "sha512-Z+h/8vDG/bHV0tETMZxOUO84cNrcU0IalPS1JLIgWsAXKgI6g2GWd3ljClVuT0HshgAA1lpve+AnHeIxcm+3yw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@github/copilot-language-server-win32-x64": { - "version": "1.480.0", - "resolved": "https://registry.npmjs.org/@github/copilot-language-server-win32-x64/-/copilot-language-server-win32-x64-1.480.0.tgz", - "integrity": "sha512-zIyzU9ozZFMusza+rZrBYExuO8bc4+OGcR5wvisTdo/YtfzzjS+82UeEOJE18KUsRdJ1poHJplEez+3h09+uWg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.39", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.39.tgz", - "integrity": "sha512-x88FuByweJlHlAmUZXjq4JlmtqgoM57Fe7nXzQkGr2Y5wnc2EDydBzFYEOlYDSWozQreimaJIm0KEMAA5T8/Fg==", - "cpu": [ - "arm64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "copilot-linux-arm64": "copilot" - } - }, - "node_modules/@github/copilot-linux-x64": { - "version": "1.0.39", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.39.tgz", - "integrity": "sha512-ssahg8r7a0VCsHVXPRmFFXx70xNAxaTM2SZfG7qPRfFB2OM8gHrW26F2oikTklDF6D+A2MfSAMpzJLBUZbPnhw==", - "cpu": [ - "x64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "copilot-linux-x64": "copilot" - } - }, - "node_modules/@github/copilot-sdk": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@github/copilot-sdk/-/copilot-sdk-0.3.0.tgz", - "integrity": "sha512-SUo35k56pzzgYgwmDPHcu7kZxPrzXbH66IWXaEf6pmb94DlA709F82HrrDeja087TL4djJ9OuvRFWWOKCosAsg==", - "license": "MIT", - "dependencies": { - "@github/copilot": "^1.0.36-0", - "vscode-jsonrpc": "^8.2.1", - "zod": "^4.3.6" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@github/copilot-sdk/node_modules/zod": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", - "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.39", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.39.tgz", - "integrity": "sha512-hhBWGZQIywbp6MBxlqMX2GSmHqtUAOGwpo9b0igscecL4i0kz89QNasC+mKiN+zFEHP6I8gggOu87XPI17Io8Q==", - "cpu": [ - "arm64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "win32" - ], - "bin": { - "copilot-win32-arm64": "copilot.exe" - } - }, - "node_modules/@github/copilot-win32-x64": { - "version": "1.0.39", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.39.tgz", - "integrity": "sha512-0ehlMtBiwKjmfEY3hVZggdn7qrmPMC8ueBQv/b+6UY3SMRS/M/1Y7xkOCwG84NvJsktdSsk3SlQnE2LbkTVpSA==", - "cpu": [ - "x64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "win32" - ], - "bin": { - "copilot-win32-x64": "copilot.exe" - } - }, - "node_modules/@graphql-typed-document-node/core": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", - "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", - "license": "MIT", - "peerDependencies": { - "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, - "node_modules/@hono/node-server": { - "version": "1.19.14", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", - "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", - "license": "MIT", - "engines": { - "node": ">=18.14.1" - }, - "peerDependencies": { - "hono": "^4" - } - }, "node_modules/@img/colour": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", @@ -2778,6 +2492,41 @@ "@img/sharp-libvips-darwin-x64": "1.2.4" } }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.2.tgz", + "integrity": "sha512-YoAxdnd8hPUkvLHd3bWY+YA8nw3xM/RyRopYucNsWHVSan8NLVM3X2volsfoRDcXdUJPg6tXahSd7HXPK7lRnw==", + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.2" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-freebsd-wasm32/node_modules/@img/sharp-wasm32": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.2.tgz", + "integrity": "sha512-Mrv4JQNYVQ94xH+jzZ9r+gowleN8mv2FTgKT+PI6bx5C0G8TdNYndu161pg2i7uoBwxy2ImPMHrJOM2LZef7Bw==", + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.1" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@img/sharp-libvips-darwin-arm64": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", @@ -2817,9 +2566,6 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2836,9 +2582,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2855,9 +2598,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2874,9 +2614,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2893,9 +2630,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2912,9 +2646,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2931,9 +2662,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2950,9 +2678,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2969,9 +2694,6 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -2994,9 +2716,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -3019,9 +2738,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -3044,9 +2760,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -3069,9 +2782,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -3094,9 +2804,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -3119,9 +2826,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -3144,9 +2848,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -3181,14 +2882,49 @@ "url": "https://opencollective.com/libvips" } }, - "node_modules/@img/sharp-win32-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", - "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.2.tgz", + "integrity": "sha512-QNV27pxs9wpApEiCfvHM1RDoP1w1+2KrUWWDPEhEwg+latvOrfuhWrHWZKwdSFwU6jh3myjw/yOCRsUIuOft3g==", "cpu": [ - "arm64" + "wasm32" ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/sharp-wasm32": "0.35.2" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32/node_modules/@img/sharp-wasm32": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.2.tgz", + "integrity": "sha512-Mrv4JQNYVQ94xH+jzZ9r+gowleN8mv2FTgKT+PI6bx5C0G8TdNYndu161pg2i7uoBwxy2ImPMHrJOM2LZef7Bw==", + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.1" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" @@ -3238,18 +2974,6 @@ "url": "https://opencollective.com/libvips" } }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -3279,16 +3003,6 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", - "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -3305,259 +3019,489 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@lexical/a11y": { + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@lexical/a11y/-/a11y-0.48.0.tgz", + "integrity": "sha512-18W4ehyipkUim4YVoDZitoH63Om3j6iCN4c84zdqE9RgkWf/PE4rvI/8BHTm6Ni7NkVE14nimXgkpaP5ok15zA==", + "license": "MIT", + "dependencies": { + "@lexical/extension": "0.48.0", + "@lexical/utils": "0.48.0", + "lexical": "0.48.0" + }, + "peerDependencies": { + "typescript": ">=5.2" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/@lexical/clipboard": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/@lexical/clipboard/-/clipboard-0.35.0.tgz", - "integrity": "sha512-ko7xSIIiayvDiqjNDX6fgH9RlcM6r9vrrvJYTcfGVBor5httx16lhIi0QJZ4+RNPvGtTjyFv4bwRmsixRRwImg==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@lexical/clipboard/-/clipboard-0.48.0.tgz", + "integrity": "sha512-xO2trk6+yBl8XXa/VNe20kXczmPxFoWtUHjidbBLEtlGBj+mo63pJj6H5o/WlZsoMKmIOJxwxsn2ejrS8G0/7A==", "license": "MIT", "dependencies": { - "@lexical/html": "0.35.0", - "@lexical/list": "0.35.0", - "@lexical/selection": "0.35.0", - "@lexical/utils": "0.35.0", - "lexical": "0.35.0" + "@lexical/extension": "0.48.0", + "@lexical/html": "0.48.0", + "@lexical/internal": "0.48.0", + "@lexical/list": "0.48.0", + "@lexical/selection": "0.48.0", + "@lexical/utils": "0.48.0", + "@types/trusted-types": "^2.0.7", + "lexical": "0.48.0" + }, + "peerDependencies": { + "typescript": ">=5.2" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@lexical/code": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/@lexical/code/-/code-0.35.0.tgz", - "integrity": "sha512-ox4DZwETQ9IA7+DS6PN8RJNwSAF7RMjL7YTVODIqFZ5tUFIf+5xoCHbz7Fll0Bvixlp12hVH90xnLwTLRGpkKw==", + "node_modules/@lexical/code-core": { + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@lexical/code-core/-/code-core-0.48.0.tgz", + "integrity": "sha512-+O1Ge06AuSo6+r8R2Xk6SkWG07H5/e4K/Scw9aqCM/BRjITxgvFTHTNvgQbaobCsP0uBJiwW1+ZGeWAWcBCY4w==", "license": "MIT", "dependencies": { - "@lexical/utils": "0.35.0", - "lexical": "0.35.0", - "prismjs": "^1.30.0" + "@lexical/extension": "0.48.0", + "@lexical/html": "0.48.0", + "@lexical/internal": "0.48.0", + "@lexical/utils": "0.48.0", + "lexical": "0.48.0" + }, + "peerDependencies": { + "typescript": ">=5.2" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@lexical/devtools-core": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/@lexical/devtools-core/-/devtools-core-0.35.0.tgz", - "integrity": "sha512-C2wwtsMCR6ZTfO0TqpSM17RLJWyfHmifAfCTjFtOJu15p3M6NO/nHYK5Mt7YMQteuS89mOjB4ng8iwoLEZ6QpQ==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@lexical/devtools-core/-/devtools-core-0.48.0.tgz", + "integrity": "sha512-4kvKWW6ebgQnJNLXPLmw7dqgSChvzYIBNYtfuR6c48Sw+V/QXQTWqfIUbCIe5X4uG8EEXd5O/udXaJx7GBuP+w==", "license": "MIT", "dependencies": { - "@lexical/html": "0.35.0", - "@lexical/link": "0.35.0", - "@lexical/mark": "0.35.0", - "@lexical/table": "0.35.0", - "@lexical/utils": "0.35.0", - "lexical": "0.35.0" + "@lexical/html": "0.48.0", + "@lexical/link": "0.48.0", + "@lexical/mark": "0.48.0", + "@lexical/table": "0.48.0", + "@lexical/utils": "0.48.0", + "lexical": "0.48.0" }, "peerDependencies": { - "react": ">=17.x", - "react-dom": ">=17.x" + "react": ">=18.x", + "react-dom": ">=18.x", + "typescript": ">=5.2" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@lexical/dragon": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/@lexical/dragon/-/dragon-0.35.0.tgz", - "integrity": "sha512-SL6mT5pcqrt6hEbJ16vWxip5+r3uvMd0bQV5UUxuk+cxIeuP86iTgRh0HFR7SM2dRTYovL6/tM/O+8QLAUGTIg==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@lexical/dragon/-/dragon-0.48.0.tgz", + "integrity": "sha512-uPuu7fVca9vmL/Oz30CRZ7FIPIodwMrTgNsRmV8jE6Qd6a7RNiTW7r3+EhbhIdkLb/sjcWsYyOMyUR1TJAB0wQ==", + "license": "MIT", + "dependencies": { + "@lexical/extension": "0.48.0", + "lexical": "0.48.0" + }, + "peerDependencies": { + "typescript": ">=5.2" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@lexical/extension": { + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@lexical/extension/-/extension-0.48.0.tgz", + "integrity": "sha512-4uBObgz84mVbQWiumndmIhkuJL0ojHiMwFSvSUM/FCo1YMVIZvpI56blI0y+2Vix/oLui9EgVQSJjjWV4NAszw==", "license": "MIT", "dependencies": { - "lexical": "0.35.0" + "@lexical/internal": "0.48.0", + "@lexical/utils": "0.48.0", + "@preact/signals-core": "^1.14.1", + "lexical": "0.48.0" + }, + "peerDependencies": { + "typescript": ">=5.2" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@lexical/hashtag": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/@lexical/hashtag/-/hashtag-0.35.0.tgz", - "integrity": "sha512-LYJWzXuO2ZjKsvQwrLkNZiS2TsjwYkKjlDgtugzejquTBQ/o/nfSn/MmVx6EkYLOYizaJemmZbz3IBh+u732FA==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@lexical/hashtag/-/hashtag-0.48.0.tgz", + "integrity": "sha512-hPQtdnbVoNAFsmfnCGfgY7mDbvk6mIznlCRmIR7tLeQKXqz/0Tb6eH3W24EESk9hAF8wFUYNKWE1/Kb3Hl2vEQ==", "license": "MIT", "dependencies": { - "@lexical/utils": "0.35.0", - "lexical": "0.35.0" + "@lexical/text": "0.48.0", + "@lexical/utils": "0.48.0", + "lexical": "0.48.0" + }, + "peerDependencies": { + "typescript": ">=5.2" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@lexical/history": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/@lexical/history/-/history-0.35.0.tgz", - "integrity": "sha512-onjDRLLxGbCfHexSxxrQaDaieIHyV28zCDrbxR5dxTfW8F8PxjuNyuaG0z6o468AXYECmclxkP+P4aT6poHEpQ==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@lexical/history/-/history-0.48.0.tgz", + "integrity": "sha512-NllvUfO+u3mfi5uC8k2CodwdzeeopFFVtMZ/NMifzFbZFysdWi9m9mqfO46NrEA1rSFOydyefv6oMzC8ULXInA==", "license": "MIT", "dependencies": { - "@lexical/utils": "0.35.0", - "lexical": "0.35.0" + "@lexical/extension": "0.48.0", + "@lexical/utils": "0.48.0", + "lexical": "0.48.0" + }, + "peerDependencies": { + "typescript": ">=5.2" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@lexical/html": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/@lexical/html/-/html-0.35.0.tgz", - "integrity": "sha512-rXGFE5S5rKsg3tVnr1s4iEgOfCApNXGpIFI3T2jGEShaCZ5HLaBY9NVBXnE9Nb49e9bkDkpZ8FZd1qokCbQXbw==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@lexical/html/-/html-0.48.0.tgz", + "integrity": "sha512-uBxlgKl4YgSNEgHJSshdBqtGDzruWdx1ewop+u6faT67qHUdP3P0cUXIrG6NToDWvsL6fzCstAbN76PMER1Pnw==", "license": "MIT", "dependencies": { - "@lexical/selection": "0.35.0", - "@lexical/utils": "0.35.0", - "lexical": "0.35.0" + "@lexical/extension": "0.48.0", + "@lexical/internal": "0.48.0", + "@lexical/selection": "0.48.0", + "@lexical/utils": "0.48.0", + "lexical": "0.48.0" + }, + "peerDependencies": { + "typescript": ">=5.2" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@lexical/internal": { + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@lexical/internal/-/internal-0.48.0.tgz", + "integrity": "sha512-sRwg53K7N0ZQ7KNAvcCY38LSwGizbXP1zlR1lIojZp0GoqHWNvR+vL49t1wYXu1nXx3Osf4ilHHm+aGcwq5hTw==", + "license": "MIT", + "peerDependencies": { + "typescript": ">=5.2" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@lexical/link": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/@lexical/link/-/link-0.35.0.tgz", - "integrity": "sha512-+0Wx6cBwO8TfdMzpkYFacsmgFh8X1rkiYbq3xoLvk3qV8upYxaMzK1s8Q1cpKmWyI0aZrU6z7fiK4vUqB7+69w==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@lexical/link/-/link-0.48.0.tgz", + "integrity": "sha512-E0UDmNLUXs/yMCnnE7hbFO0CvhWghmqa+qqPksFfzLkpMHdPpdS1yg59YEbYoNHLi/DXIu4cFRvpHIEuooxwNg==", "license": "MIT", "dependencies": { - "@lexical/utils": "0.35.0", - "lexical": "0.35.0" + "@lexical/extension": "0.48.0", + "@lexical/html": "0.48.0", + "@lexical/internal": "0.48.0", + "@lexical/utils": "0.48.0", + "lexical": "0.48.0" + }, + "peerDependencies": { + "typescript": ">=5.2" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@lexical/list": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/@lexical/list/-/list-0.35.0.tgz", - "integrity": "sha512-owsmc8iwgExBX8sFe8fKTiwJVhYULt9hD1RZ/HwfaiEtRZZkINijqReOBnW2mJfRxBzhFSWc4NG3ISB+fHYzqw==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@lexical/list/-/list-0.48.0.tgz", + "integrity": "sha512-9Qe/Vur44v9F9enj55SUzf79FVsijcGOQug7SpiIU8ekLr7JNzcilKYBYcZ6etGEo7bqQHsYMHXeJcSBbCI2zA==", "license": "MIT", "dependencies": { - "@lexical/selection": "0.35.0", - "@lexical/utils": "0.35.0", - "lexical": "0.35.0" + "@lexical/extension": "0.48.0", + "@lexical/html": "0.48.0", + "@lexical/internal": "0.48.0", + "@lexical/utils": "0.48.0", + "lexical": "0.48.0" + }, + "peerDependencies": { + "typescript": ">=5.2" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@lexical/mark": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/@lexical/mark/-/mark-0.35.0.tgz", - "integrity": "sha512-W0hwMTAVeexvpk9/+J6n1G/sNkpI/Meq1yeDazahFLLAwXLHtvhIAq2P/klgFknDy1hr8X7rcsQuN/bqKcKHYg==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@lexical/mark/-/mark-0.48.0.tgz", + "integrity": "sha512-DTtypWvnYSXyNUxEmsUnh4y0xXkmdk8Y72EZl8WDHcCwjqaLJlUakH7p/TuSJczs3uVSaoJzu0yh7oGkP1Vsvw==", "license": "MIT", "dependencies": { - "@lexical/utils": "0.35.0", - "lexical": "0.35.0" + "@lexical/utils": "0.48.0", + "lexical": "0.48.0" + }, + "peerDependencies": { + "typescript": ">=5.2" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@lexical/markdown": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/@lexical/markdown/-/markdown-0.35.0.tgz", - "integrity": "sha512-BlNyXZAt4gWidMw0SRWrhBETY1BpPglFBZI7yzfqukFqgXRh7HUQA28OYeI/nsx9pgNob8TiUduUwShqqvOdEA==", - "license": "MIT", - "dependencies": { - "@lexical/code": "0.35.0", - "@lexical/link": "0.35.0", - "@lexical/list": "0.35.0", - "@lexical/rich-text": "0.35.0", - "@lexical/text": "0.35.0", - "@lexical/utils": "0.35.0", - "lexical": "0.35.0" - } - }, - "node_modules/@lexical/offset": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/@lexical/offset/-/offset-0.35.0.tgz", - "integrity": "sha512-DRE4Df6qYf2XiV6foh6KpGNmGAv2ANqt3oVXpyS6W8hTx3+cUuAA1APhCZmLNuU107um4zmHym7taCu6uXW5Yg==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@lexical/markdown/-/markdown-0.48.0.tgz", + "integrity": "sha512-1WasBenW4bEsa5xtnycVo8G2hcxIqyYLn3/r98yD2Y+54ZyeFuIQfOeJYhIgCh4YkKpfqyrFwkMQwAOsQDqZjA==", "license": "MIT", "dependencies": { - "lexical": "0.35.0" + "@lexical/code-core": "0.48.0", + "@lexical/internal": "0.48.0", + "@lexical/link": "0.48.0", + "@lexical/list": "0.48.0", + "@lexical/rich-text": "0.48.0", + "@lexical/selection": "0.48.0", + "@lexical/text": "0.48.0", + "@lexical/utils": "0.48.0", + "lexical": "0.48.0" + }, + "peerDependencies": { + "typescript": ">=5.2" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@lexical/overflow": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/@lexical/overflow/-/overflow-0.35.0.tgz", - "integrity": "sha512-B25YvnJQTGlZcrNv7b0PJBLWq3tl8sql497OHfYYLem7EOMPKKDGJScJAKM/91D4H/mMAsx5gnA/XgKobriuTg==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@lexical/overflow/-/overflow-0.48.0.tgz", + "integrity": "sha512-1YEvMz2tW3EbwrON9mjrkjMVl/vdTcPYSn9P1j6mf5gj0LOoLDNI4TbvSD4SViy+TDghxNdG8YdCISyU2b4YKA==", "license": "MIT", "dependencies": { - "lexical": "0.35.0" + "lexical": "0.48.0" + }, + "peerDependencies": { + "typescript": ">=5.2" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@lexical/plain-text": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/@lexical/plain-text/-/plain-text-0.35.0.tgz", - "integrity": "sha512-lwBCUNMJf7Gujp2syVWMpKRahfbTv5Wq+H3HK1Q1gKH1P2IytPRxssCHvexw9iGwprSyghkKBlbF3fGpEdIJvQ==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@lexical/plain-text/-/plain-text-0.48.0.tgz", + "integrity": "sha512-q4f/4VZKVgCrIW2FhDFR2RII1BU0ljedPgEmJ8XQn1zc+JOFPom8Lp0lV5nyEvZaAJYwM/TfoJ9g2V7ESFKznA==", "license": "MIT", "dependencies": { - "@lexical/clipboard": "0.35.0", - "@lexical/selection": "0.35.0", - "@lexical/utils": "0.35.0", - "lexical": "0.35.0" + "@lexical/clipboard": "0.48.0", + "@lexical/dragon": "0.48.0", + "@lexical/extension": "0.48.0", + "@lexical/selection": "0.48.0", + "@lexical/utils": "0.48.0", + "lexical": "0.48.0" + }, + "peerDependencies": { + "typescript": ">=5.2" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@lexical/react": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/@lexical/react/-/react-0.35.0.tgz", - "integrity": "sha512-uYAZSqumH8tRymMef+A0f2hQvMwplKK9DXamcefnk3vSNDHHqRWQXpiUo6kD+rKWuQmMbVa5RW4xRQebXEW+1A==", - "license": "MIT", - "dependencies": { - "@floating-ui/react": "^0.27.8", - "@lexical/devtools-core": "0.35.0", - "@lexical/dragon": "0.35.0", - "@lexical/hashtag": "0.35.0", - "@lexical/history": "0.35.0", - "@lexical/link": "0.35.0", - "@lexical/list": "0.35.0", - "@lexical/mark": "0.35.0", - "@lexical/markdown": "0.35.0", - "@lexical/overflow": "0.35.0", - "@lexical/plain-text": "0.35.0", - "@lexical/rich-text": "0.35.0", - "@lexical/table": "0.35.0", - "@lexical/text": "0.35.0", - "@lexical/utils": "0.35.0", - "@lexical/yjs": "0.35.0", - "lexical": "0.35.0", - "react-error-boundary": "^3.1.4" + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@lexical/react/-/react-0.48.0.tgz", + "integrity": "sha512-uVh9/QSrbtjLjVbxfJ+sfiMyhUq/rv7H6uBEVDDIw1rkZJSDY1fvf/CX+dyKgwcDFjKQZ8/9i5f9UCVPeQ01hA==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.27.19", + "@lexical/a11y": "0.48.0", + "@lexical/devtools-core": "0.48.0", + "@lexical/dragon": "0.48.0", + "@lexical/extension": "0.48.0", + "@lexical/hashtag": "0.48.0", + "@lexical/history": "0.48.0", + "@lexical/internal": "0.48.0", + "@lexical/link": "0.48.0", + "@lexical/list": "0.48.0", + "@lexical/mark": "0.48.0", + "@lexical/markdown": "0.48.0", + "@lexical/overflow": "0.48.0", + "@lexical/plain-text": "0.48.0", + "@lexical/rich-text": "0.48.0", + "@lexical/table": "0.48.0", + "@lexical/text": "0.48.0", + "@lexical/utils": "0.48.0", + "@lexical/yjs": "0.48.0", + "lexical": "0.48.0" + }, + "peerDependencies": { + "react": ">=18.x", + "react-dom": ">=18.x", + "typescript": ">=5.2", + "yjs": ">=13.5.22" }, - "peerDependencies": { - "react": ">=17.x", - "react-dom": ">=17.x" + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "yjs": { + "optional": true + } } }, "node_modules/@lexical/rich-text": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/@lexical/rich-text/-/rich-text-0.35.0.tgz", - "integrity": "sha512-qEHu8g7vOEzz9GUz1VIUxZBndZRJPh9iJUFI+qTDHj+tQqnd5LCs+G9yz6jgNfiuWWpezTp0i1Vz/udNEuDPKQ==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@lexical/rich-text/-/rich-text-0.48.0.tgz", + "integrity": "sha512-QMXFnwCKAQ4yzxvx5FwmANcx3K+NaBkGTxAVD8s8pOKDD/U5rzDS1iIvhH6TLWaFp7VzvLmLB+Sl1Ie/RnkaDQ==", "license": "MIT", "dependencies": { - "@lexical/clipboard": "0.35.0", - "@lexical/selection": "0.35.0", - "@lexical/utils": "0.35.0", - "lexical": "0.35.0" + "@lexical/clipboard": "0.48.0", + "@lexical/dragon": "0.48.0", + "@lexical/extension": "0.48.0", + "@lexical/html": "0.48.0", + "@lexical/selection": "0.48.0", + "@lexical/utils": "0.48.0", + "lexical": "0.48.0" + }, + "peerDependencies": { + "typescript": ">=5.2" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@lexical/selection": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/@lexical/selection/-/selection-0.35.0.tgz", - "integrity": "sha512-mMtDE7Q0nycXdFTTH/+ta6EBrBwxBB4Tg8QwsGntzQ1Cq//d838dpXpFjJOqHEeVHUqXpiuj+cBG8+bvz/rPRw==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@lexical/selection/-/selection-0.48.0.tgz", + "integrity": "sha512-Uc0wTrEtHcYK6z/aHHjkgH3vX/R4Bf8mO+qH3VbxfSAKYzNYktM0j+ZGdq5kIEL4frnw/9SulNCXlH7xpjuDgA==", "license": "MIT", "dependencies": { - "lexical": "0.35.0" + "@lexical/internal": "0.48.0", + "lexical": "0.48.0" + }, + "peerDependencies": { + "typescript": ">=5.2" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@lexical/table": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/@lexical/table/-/table-0.35.0.tgz", - "integrity": "sha512-9jlTlkVideBKwsEnEkqkdg7A3mije1SvmfiqoYnkl1kKJCLA5iH90ywx327PU0p+bdnURAytWUeZPXaEuEl2OA==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@lexical/table/-/table-0.48.0.tgz", + "integrity": "sha512-t9Mz7q6ODLUz0lG5Xn9EY/5YiVpTHCqlPQP4EtFXlnBQT3DuKeDS3cC0Cn8sGSZc11YY5OLDfWpB64Frs9BL3g==", "license": "MIT", "dependencies": { - "@lexical/clipboard": "0.35.0", - "@lexical/utils": "0.35.0", - "lexical": "0.35.0" + "@lexical/clipboard": "0.48.0", + "@lexical/extension": "0.48.0", + "@lexical/html": "0.48.0", + "@lexical/internal": "0.48.0", + "@lexical/utils": "0.48.0", + "lexical": "0.48.0" + }, + "peerDependencies": { + "typescript": ">=5.2" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@lexical/text": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/@lexical/text/-/text-0.35.0.tgz", - "integrity": "sha512-uaMh46BkysV8hK8wQwp5g/ByZW+2hPDt8ahAErxtf8NuzQem1FHG/f5RTchmFqqUDVHO3qLNTv4AehEGmXv8MA==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@lexical/text/-/text-0.48.0.tgz", + "integrity": "sha512-ktTMRbsX4wKxdG2OpZCkrqtt8k9Vg/ZpWdukOQ0r1xPRtCuL1T+q91l7cy2ywIuCfMGYS0aoZGB4LdpUMe/H1g==", "license": "MIT", "dependencies": { - "lexical": "0.35.0" + "@lexical/internal": "0.48.0", + "lexical": "0.48.0" + }, + "peerDependencies": { + "typescript": ">=5.2" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@lexical/utils": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/@lexical/utils/-/utils-0.35.0.tgz", - "integrity": "sha512-2H393EYDnFznYCDFOW3MHiRzwEO5M/UBhtUjvTT+9kc+qhX4U3zc8ixQalo5UmZ5B2nh7L/inXdTFzvSRXtsRA==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@lexical/utils/-/utils-0.48.0.tgz", + "integrity": "sha512-W4k4P+y6jmRfna8+ad4X+iMd5h8es5PC3bUw5tbi7MRApxaaFG/0w+uJiZVSwbT2Q6JnA2xhBaqzPgt/Gn6djg==", "license": "MIT", "dependencies": { - "@lexical/list": "0.35.0", - "@lexical/selection": "0.35.0", - "@lexical/table": "0.35.0", - "lexical": "0.35.0" + "@lexical/internal": "0.48.0", + "@lexical/selection": "0.48.0", + "lexical": "0.48.0" + }, + "peerDependencies": { + "typescript": ">=5.2" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@lexical/yjs": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/@lexical/yjs/-/yjs-0.35.0.tgz", - "integrity": "sha512-3DSP7QpmTGYU9bN/yljP0PIao4tNIQtsR4ycauWNSawxs/GQCZtSmAPcLRnCm6qpqsDDjUtKjO/1Ej8FRp0m0w==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@lexical/yjs/-/yjs-0.48.0.tgz", + "integrity": "sha512-fFsE8EnPM/2KK9rMJ0z6T+Da5UW5V4P+XiAA03LoHTY5YQ/Oy8Q0i7Wcmocv/B/SpsY2o8g07euZEfudl9MVKA==", "license": "MIT", "dependencies": { - "@lexical/offset": "0.35.0", - "@lexical/selection": "0.35.0", - "lexical": "0.35.0" + "@lexical/internal": "0.48.0", + "@lexical/selection": "0.48.0", + "lexical": "0.48.0" }, "peerDependencies": { + "typescript": ">=5.2", "yjs": ">=13.5.22" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@lezer/common": { @@ -3578,9 +3522,9 @@ } }, "node_modules/@lezer/css": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.4.tgz", - "integrity": "sha512-N+tn9tej2hPvyKgHEApMOQfHczDJCwxrRFS3SPn9QjYN+uwHvEDnCgKRrb3mxDYxRS8sKMM8fhC3+lc04Abz5Q==", + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.6.tgz", + "integrity": "sha512-YJE78Wcg+zX8f10hiHWQ4Az48Qr/c13eId0VtRQYLBpxHDmDeSrXIlkbl+fJGW42rWC/uoUco9mhBZeVWP/A1g==", "license": "MIT", "dependencies": { "@lezer/common": "^1.2.0", @@ -3662,9 +3606,9 @@ } }, "node_modules/@lezer/markdown": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.7.1.tgz", - "integrity": "sha512-MEBZeFSBxgteUjEC3Wxg2Dwld5/JxRKG267L3bMFdibm8KjqSdiJYBeFw1Nt1CM8+zKMpSIEHblY8FD9z38sJQ==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.7.2.tgz", + "integrity": "sha512-iTkYvoVcKt3WkeL7qUDyXHONZEwLio4wj8KTNi2dnjQEXBZKMV63BpQrPqfsM+OkvuRbiSTAcycYAsQzLhRNoQ==", "license": "MIT", "dependencies": { "@lezer/common": "^1.5.0", @@ -3780,10 +3724,19 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/@mdx-js/mdx/node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, "node_modules/@mdxeditor/editor": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@mdxeditor/editor/-/editor-4.0.4.tgz", - "integrity": "sha512-fUUWoIFfHB4g+PHkcpKp5zRfoMmDWrVeSaoB4x81759J2xrWsmYYoCYx0oVLcL6HXat8f/8LprlRmAxpcIPnLQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@mdxeditor/editor/-/editor-4.2.0.tgz", + "integrity": "sha512-S/IPY8AjWTV2v1TGbEy5lsbQ5w+J2M2j2QDeswxP4FpNh2B49OPkpSqHbbX5GbV0YBlhfXJxtzwBJv/sQACufw==", "license": "MIT", "dependencies": { "@codemirror/commands": "^6.2.4", @@ -3792,15 +3745,17 @@ "@codemirror/merge": "^6.4.0", "@codemirror/state": "^6.4.0", "@codemirror/view": "^6.23.0", - "@lexical/clipboard": "^0.35.0", - "@lexical/link": "^0.35.0", - "@lexical/list": "^0.35.0", - "@lexical/markdown": "^0.35.0", - "@lexical/plain-text": "^0.35.0", - "@lexical/react": "^0.35.0", - "@lexical/rich-text": "^0.35.0", - "@lexical/selection": "^0.35.0", - "@lexical/utils": "^0.35.0", + "@lexical/clipboard": "^0.48.0", + "@lexical/extension": "^0.48.0", + "@lexical/history": "^0.48.0", + "@lexical/link": "^0.48.0", + "@lexical/list": "^0.48.0", + "@lexical/markdown": "^0.48.0", + "@lexical/plain-text": "^0.48.0", + "@lexical/react": "^0.48.0", + "@lexical/rich-text": "^0.48.0", + "@lexical/selection": "^0.48.0", + "@lexical/utils": "^0.48.0", "@mdxeditor/gurx": "^1.2.4", "@radix-ui/colors": "^3.0.0", "@radix-ui/react-dialog": "^1.1.11", @@ -3815,8 +3770,8 @@ "cm6-theme-basic-light": "^0.2.0", "codemirror": "^6.0.1", "downshift": "^7.6.0", - "js-yaml": "4.2.0", - "lexical": "^0.35.0", + "js-yaml": "4.3.0", + "lexical": "^0.48.0", "mdast-util-directive": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-frontmatter": "^2.0.1", @@ -3850,28 +3805,6 @@ "react-dom": ">= 18 || >= 19" } }, - "node_modules/@mdxeditor/editor/node_modules/js-yaml": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", - "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/nodeca" - } - ], - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/@mdxeditor/gurx": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@mdxeditor/gurx/-/gurx-1.2.4.tgz", @@ -3885,534 +3818,20 @@ "react-dom": ">= 18 || >= 19" } }, - "node_modules/@mediabunny/aac-encoder": { - "version": "1.45.0", - "resolved": "https://registry.npmjs.org/@mediabunny/aac-encoder/-/aac-encoder-1.45.0.tgz", - "integrity": "sha512-vLQw8cY7Me6pvTTMkMhOiH9UCuINzfTOETCeDxbGNeNfDqc/7QlxloUH1Ylp/Zz2ek0O8kc6YdygV2vWAPakrA==", - "license": "MPL-2.0", - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/Vanilagy" - }, - "peerDependencies": { - "mediabunny": "^1.0.0" - } - }, - "node_modules/@mediabunny/flac-encoder": { - "version": "1.45.0", - "resolved": "https://registry.npmjs.org/@mediabunny/flac-encoder/-/flac-encoder-1.45.0.tgz", - "integrity": "sha512-LfKbAMZVkxRS7PpEIVnWOY/l0KcHv+rjO7pYY3O0TPCZvbHWfrnQjn8JPacPIfuq6Yv7r4f8lhcl7yHSynoRkQ==", - "license": "MPL-2.0", - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/Vanilagy" - }, - "peerDependencies": { - "mediabunny": "^1.0.0" - } - }, - "node_modules/@mediabunny/mp3-encoder": { - "version": "1.45.0", - "resolved": "https://registry.npmjs.org/@mediabunny/mp3-encoder/-/mp3-encoder-1.45.0.tgz", - "integrity": "sha512-Bobi6AaQYEc7TWmPJ8Q0/hcUtBN7pLUC2qjoC7oZR4FcGqGztby6k7A1SWlmswoMOEIhYsOrgDaemrSDAC0QVQ==", - "license": "MPL-2.0", - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/Vanilagy" - }, - "peerDependencies": { - "mediabunny": "^1.0.0" - } - }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", - "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", - "license": "MIT", - "dependencies": { - "@hono/node-server": "^1.19.9", - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.5", - "eventsource": "^3.0.2", - "eventsource-parser": "^3.0.0", - "express": "^5.2.1", - "express-rate-limit": "^8.2.1", - "hono": "^4.11.4", - "jose": "^6.1.3", - "json-schema-typed": "^8.0.2", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@cfworker/json-schema": "^4.1.1", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "@cfworker/json-schema": { - "optional": true - }, - "zod": { - "optional": false - } - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@module-federation/error-codes": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-0.22.0.tgz", - "integrity": "sha512-xF9SjnEy7vTdx+xekjPCV5cIHOGCkdn3pIxo9vU7gEZMIw0SvAEdsy6Uh17xaCpm8V0FWvR0SZoK9Ik6jGOaug==", - "license": "MIT" - }, - "node_modules/@module-federation/runtime": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-0.22.0.tgz", - "integrity": "sha512-38g5iPju2tPC3KHMPxRKmy4k4onNp6ypFPS1eKGsNLUkXgHsPMBFqAjDw96iEcjri91BrahG4XcdyKi97xZzlA==", - "license": "MIT", - "dependencies": { - "@module-federation/error-codes": "0.22.0", - "@module-federation/runtime-core": "0.22.0", - "@module-federation/sdk": "0.22.0" - } - }, - "node_modules/@module-federation/runtime-core": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/@module-federation/runtime-core/-/runtime-core-0.22.0.tgz", - "integrity": "sha512-GR1TcD6/s7zqItfhC87zAp30PqzvceoeDGYTgF3Vx2TXvsfDrhP6Qw9T4vudDQL3uJRne6t7CzdT29YyVxlgIA==", - "license": "MIT", - "dependencies": { - "@module-federation/error-codes": "0.22.0", - "@module-federation/sdk": "0.22.0" - } - }, - "node_modules/@module-federation/runtime-tools": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-0.22.0.tgz", - "integrity": "sha512-4ScUJ/aUfEernb+4PbLdhM/c60VHl698Gn1gY21m9vyC1Ucn69fPCA1y2EwcCB7IItseRMoNhdcWQnzt/OPCNA==", - "license": "MIT", - "dependencies": { - "@module-federation/runtime": "0.22.0", - "@module-federation/webpack-bundler-runtime": "0.22.0" - } - }, - "node_modules/@module-federation/sdk": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-0.22.0.tgz", - "integrity": "sha512-x4aFNBKn2KVQRuNVC5A7SnrSCSqyfIWmm1DvubjbO9iKFe7ith5niw8dqSFBekYBg2Fwy+eMg4sEFNVvCAdo6g==", - "license": "MIT" - }, - "node_modules/@module-federation/webpack-bundler-runtime": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.22.0.tgz", - "integrity": "sha512-aM8gCqXu+/4wBmJtVeMeeMN5guw3chf+2i6HajKtQv7SJfxV/f4IyNQJUeUQu9HfiAZHjqtMV5Lvq/Lvh8LdyA==", - "license": "MIT", - "dependencies": { - "@module-federation/runtime": "0.22.0", - "@module-federation/sdk": "0.22.0" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz", - "integrity": "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==", + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], "license": "MIT", "optional": true, - "dependencies": { - "@emnapi/core": "^1.5.0", - "@emnapi/runtime": "^1.5.0", - "@tybys/wasm-util": "^0.10.1" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@octokit/app": { - "version": "16.1.2", - "resolved": "https://registry.npmjs.org/@octokit/app/-/app-16.1.2.tgz", - "integrity": "sha512-8j7sEpUYVj18dxvh0KWj6W/l6uAiVRBl1JBDVRqH1VHKAO/G5eRVl4yEoYACjakWers1DjUkcCHyJNQK47JqyQ==", - "license": "MIT", - "dependencies": { - "@octokit/auth-app": "^8.1.2", - "@octokit/auth-unauthenticated": "^7.0.3", - "@octokit/core": "^7.0.6", - "@octokit/oauth-app": "^8.0.3", - "@octokit/plugin-paginate-rest": "^14.0.0", - "@octokit/types": "^16.0.0", - "@octokit/webhooks": "^14.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-app": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-8.2.0.tgz", - "integrity": "sha512-vVjdtQQwomrZ4V46B9LaCsxsySxGoHsyw6IYBov/TqJVROrlYdyNgw5q6tQbB7KZt53v1l1W53RiqTvpzL907g==", - "license": "MIT", - "dependencies": { - "@octokit/auth-oauth-app": "^9.0.3", - "@octokit/auth-oauth-user": "^6.0.2", - "@octokit/request": "^10.0.6", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "toad-cache": "^3.7.0", - "universal-github-app-jwt": "^2.2.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-oauth-app": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-9.0.3.tgz", - "integrity": "sha512-+yoFQquaF8OxJSxTb7rnytBIC2ZLbLqA/yb71I4ZXT9+Slw4TziV9j/kyGhUFRRTF2+7WlnIWsePZCWHs+OGjg==", - "license": "MIT", - "dependencies": { - "@octokit/auth-oauth-device": "^8.0.3", - "@octokit/auth-oauth-user": "^6.0.2", - "@octokit/request": "^10.0.6", - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-oauth-device": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-8.0.3.tgz", - "integrity": "sha512-zh2W0mKKMh/VWZhSqlaCzY7qFyrgd9oTWmTmHaXnHNeQRCZr/CXy2jCgHo4e4dJVTiuxP5dLa0YM5p5QVhJHbw==", - "license": "MIT", - "dependencies": { - "@octokit/oauth-methods": "^6.0.2", - "@octokit/request": "^10.0.6", - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-oauth-user": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-6.0.2.tgz", - "integrity": "sha512-qLoPPc6E6GJoz3XeDG/pnDhJpTkODTGG4kY0/Py154i/I003O9NazkrwJwRuzgCalhzyIeWQ+6MDvkUmKXjg/A==", - "license": "MIT", - "dependencies": { - "@octokit/auth-oauth-device": "^8.0.3", - "@octokit/oauth-methods": "^6.0.2", - "@octokit/request": "^10.0.6", - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-token": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", - "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", - "license": "MIT", - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-unauthenticated": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-7.0.3.tgz", - "integrity": "sha512-8Jb1mtUdmBHL7lGmop9mU9ArMRUTRhg8vp0T1VtZ4yd9vEm3zcLwmjQkhNEduKawOOORie61xhtYIhTDN+ZQ3g==", - "license": "MIT", - "dependencies": { - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/core": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz", - "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", - "license": "MIT", - "dependencies": { - "@octokit/auth-token": "^6.0.0", - "@octokit/graphql": "^9.0.3", - "@octokit/request": "^10.0.6", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "before-after-hook": "^4.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/endpoint": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.3.tgz", - "integrity": "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/graphql": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.3.tgz", - "integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==", - "license": "MIT", - "dependencies": { - "@octokit/request": "^10.0.6", - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/oauth-app": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-8.0.3.tgz", - "integrity": "sha512-jnAjvTsPepyUaMu9e69hYBuozEPgYqP4Z3UnpmvoIzHDpf8EXDGvTY1l1jK0RsZ194oRd+k6Hm13oRU8EoDFwg==", - "license": "MIT", - "dependencies": { - "@octokit/auth-oauth-app": "^9.0.2", - "@octokit/auth-oauth-user": "^6.0.1", - "@octokit/auth-unauthenticated": "^7.0.2", - "@octokit/core": "^7.0.5", - "@octokit/oauth-authorization-url": "^8.0.0", - "@octokit/oauth-methods": "^6.0.1", - "@types/aws-lambda": "^8.10.83", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/oauth-authorization-url": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-8.0.0.tgz", - "integrity": "sha512-7QoLPRh/ssEA/HuHBHdVdSgF8xNLz/Bc5m9fZkArJE5bb6NmVkDm3anKxXPmN1zh6b5WKZPRr3697xKT/yM3qQ==", - "license": "MIT", - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/oauth-methods": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-6.0.2.tgz", - "integrity": "sha512-HiNOO3MqLxlt5Da5bZbLV8Zarnphi4y9XehrbaFMkcoJ+FL7sMxH/UlUsCVxpddVu4qvNDrBdaTVE2o4ITK8ng==", - "license": "MIT", - "dependencies": { - "@octokit/oauth-authorization-url": "^8.0.0", - "@octokit/request": "^10.0.6", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/openapi-types": { - "version": "27.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", - "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", - "license": "MIT" - }, - "node_modules/@octokit/openapi-webhooks-types": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-webhooks-types/-/openapi-webhooks-types-12.1.0.tgz", - "integrity": "sha512-WiuzhOsiOvb7W3Pvmhf8d2C6qaLHXrWiLBP4nJ/4kydu+wpagV5Fkz9RfQwV2afYzv3PB+3xYgp4mAdNGjDprA==", - "license": "MIT" - }, - "node_modules/@octokit/plugin-paginate-graphql": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-6.0.0.tgz", - "integrity": "sha512-crfpnIoFiBtRkvPqOyLOsw12XsveYuY2ieP6uYDosoUegBJpSVxGwut9sxUgFFcll3VTOTqpUf8yGd8x1OmAkQ==", - "license": "MIT", - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-paginate-rest": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-14.0.0.tgz", - "integrity": "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-17.0.0.tgz", - "integrity": "sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-retry": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-8.1.0.tgz", - "integrity": "sha512-O1FZgXeiGb2sowEr/hYTr6YunGdSAFWnr2fyW39Ah85H8O33ELASQxcvOFF5LE6Tjekcyu2ms4qAzJVhSaJxTw==", - "license": "MIT", - "dependencies": { - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "bottleneck": "^2.15.3" - }, - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "@octokit/core": ">=7" - } - }, - "node_modules/@octokit/plugin-throttling": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-11.0.3.tgz", - "integrity": "sha512-34eE0RkFCKycLl2D2kq7W+LovheM/ex3AwZCYN8udpi6bxsyjZidb2McXs69hZhLmJlDqTSP8cH+jSRpiaijBg==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0", - "bottleneck": "^2.15.3" - }, - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "@octokit/core": "^7.0.0" - } - }, - "node_modules/@octokit/request": { - "version": "10.0.11", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.11.tgz", - "integrity": "sha512-+s7HUxjfFqOMS9VlIwDffq0MikjSAK0gSpG73W+meAvVAvX4MBrHYTK5Bj3Uot55qFT4gzUtfzE4mGWY4Br8/Q==", - "license": "MIT", - "dependencies": { - "@octokit/endpoint": "^11.0.3", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "content-type": "^2.0.0", - "json-with-bigint": "^3.5.3", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/request-error": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", - "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/types": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", - "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^27.0.0" - } - }, - "node_modules/@octokit/webhooks": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-14.2.0.tgz", - "integrity": "sha512-da6KbdNCV5sr1/txD896V+6W0iamFWrvVl8cHkBSPT+YlvmT3DwXa4jxZnQc+gnuTEqSWbBeoSZYTayXH9wXcw==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-webhooks-types": "12.1.0", - "@octokit/request-error": "^7.0.0", - "@octokit/webhooks-methods": "^6.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/webhooks-methods": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-6.0.0.tgz", - "integrity": "sha512-MFlzzoDJVw/GcbfzVC1RLR36QqkTLUf79vLVO3D+xn7r0QgxnFoLZgtrzxiQErAjFUOdH6fas2KeQJ1yr/qaXQ==", - "license": "MIT", + "os": [ + "linux" + ], "engines": { - "node": ">= 20" + "node": "^22.20 || ^24.12 || >=25" } }, "node_modules/@oslojs/encoding": { @@ -4556,31 +3975,41 @@ "integrity": "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==", "license": "MIT" }, - "node_modules/@radix-ui/colors": { - "version": "3.0.0", + "node_modules/@preact/signals-core": { + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.14.4.tgz", + "integrity": "sha512-HNB6HYeYKhQbJ1aKl+YRjrS4+QWHLKX6qKoUsfS/m0vqzsVaEBiZiaKbG/e+NKk2ch5ALQr/ihWaMHxiCuuWHA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/@radix-ui/colors": { + "version": "3.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/colors/-/colors-3.0.0.tgz", "integrity": "sha512-FUOsGBkHrYJwCSEtWRCIfQbZG7q1e6DgxCIOe1SUQzDe/7rXXeA47s8yCn6fuTNQAj1Zq4oTFi9Yjp3wzElcxg==", "license": "MIT" }, "node_modules/@radix-ui/number": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.2.tgz", - "integrity": "sha512-ceTwaxc4I5IOi97DgCotl3pqiyRGvffcc0oOsE2dQYaJOFIDsDt4VWG6xEbg1QePv9QWausCEIppud/tJ1wNig==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.3.tgz", + "integrity": "sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==", "license": "MIT" }, "node_modules/@radix-ui/primitive": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.5.tgz", - "integrity": "sha512-d86WIWFYNtGA0H/d8exstrTRTp7eWJYlYJbtNofxr/3ljupZYn6EFDG/Qgu/0Kc8v7yMUxySagqJsL1+PdYjWg==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.7.tgz", + "integrity": "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==", "license": "MIT" }, "node_modules/@radix-ui/react-arrow": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.11.tgz", - "integrity": "sha512-Kdil9BB1rIFC/khmf4hC35bn8701AJcizTU7G7cUbEbk5XqqbjDuHW60uUfKqO5WojjZcbAW51Q7P0hRmMLw8A==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.15.tgz", + "integrity": "sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.7" + "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", @@ -4598,15 +4027,15 @@ } }, "node_modules/@radix-ui/react-collection": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.12.tgz", - "integrity": "sha512-nb67INpE0IahJKN7EYPp9m9YGwYeKlnzxT3MwXVkgCskaSJia97kG4T0ywpjNUSSnoJk/uvk12V8vbrEHEj+/Q==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.15.tgz", + "integrity": "sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-slot": "1.3.0" + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3" }, "peerDependencies": { "@types/react": "*", @@ -4624,9 +4053,9 @@ } }, "node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.3.tgz", - "integrity": "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.5.tgz", + "integrity": "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -4639,9 +4068,9 @@ } }, "node_modules/@radix-ui/react-context": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.0.tgz", - "integrity": "sha512-fOE+JtN9rygNZkCnHRBEP0TAvLldlhyOxMsbwFvTP4nAs+nBmfnna+o/Zski2wkmY1YMrFC0aSzsHoLY47iLrg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.2.tgz", + "integrity": "sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -4654,23 +4083,24 @@ } }, "node_modules/@radix-ui/react-dialog": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.19.tgz", - "integrity": "sha512-+HhbN2+YtkRgVirjZ2afMeutQRuGOrdkWR5+EFC58SJojGmtyNQwYzgi6tHBpOxvFHefMtPeHdgtjz0BOGxFQg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-dismissable-layer": "1.1.15", - "@radix-ui/react-focus-guards": "1.1.4", - "@radix-ui/react-focus-scope": "1.1.12", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-portal": "1.1.13", - "@radix-ui/react-presence": "1.1.7", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-slot": "1.3.0", - "@radix-ui/react-use-controllable-state": "1.2.3", + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.23.tgz", + "integrity": "sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, @@ -4690,9 +4120,9 @@ } }, "node_modules/@radix-ui/react-direction": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.2.tgz", - "integrity": "sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.4.tgz", + "integrity": "sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -4705,16 +4135,16 @@ } }, "node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.15.tgz", - "integrity": "sha512-b0XaRlzn2QKuo10XyNgi2DAJDf5XC9d1nD3FJcuvCjbR7+4Ad28zmZsLsqx+hvDEzMnRuZaZxZm9gYObV6RmRA==", + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.19.tgz", + "integrity": "sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-effect-event": "0.0.3" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-effect-event": "0.0.5" }, "peerDependencies": { "@types/react": "*", @@ -4732,9 +4162,9 @@ } }, "node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.4.tgz", - "integrity": "sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.6.tgz", + "integrity": "sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -4747,14 +4177,14 @@ } }, "node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.12.tgz", - "integrity": "sha512-jjk/lqTeNL0azUx5ZYzVrl4NgaDIrdzTNE4mABV9yBFI7FQqN7pIgzV1bTleUezP2QiTGA1BFTqY8MegDgWX9A==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.16.tgz", + "integrity": "sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2" + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -4781,12 +4211,12 @@ } }, "node_modules/@radix-ui/react-id": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.2.tgz", - "integrity": "sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.4.tgz", + "integrity": "sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -4799,24 +4229,24 @@ } }, "node_modules/@radix-ui/react-popover": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.19.tgz", - "integrity": "sha512-jkrTdQVxnIB8fpn0NyyxW9CTB5aCXZZelVz5z+Xmii6g5WxMqS3fInNslZ63puP39+Puu4jYohUK31y3dT87gQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-dismissable-layer": "1.1.15", - "@radix-ui/react-focus-guards": "1.1.4", - "@radix-ui/react-focus-scope": "1.1.12", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-popper": "1.3.3", - "@radix-ui/react-portal": "1.1.13", - "@radix-ui/react-presence": "1.1.7", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-slot": "1.3.0", - "@radix-ui/react-use-controllable-state": "1.2.3", + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.23.tgz", + "integrity": "sha512-mw58MrBlyHWFisTOYignD0vf/3gdcgAR+9of1s9G/38CbFiUwH1nCDkc0AUM9IrXFgN5Ue8n45j9WCgyM1sbiQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, @@ -4836,21 +4266,21 @@ } }, "node_modules/@radix-ui/react-popper": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.3.tgz", - "integrity": "sha512-mS7dGpyjv6b+gsDjLF7e0ia1W4Im1B1hSCy2yuXlHuvnZxHKagfDaobt/KAKt27EpZMit2pss8eJBVyVjEWM+g==", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.7.tgz", + "integrity": "sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg==", "license": "MIT", "dependencies": { "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.11", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-layout-effect": "1.1.2", - "@radix-ui/react-use-rect": "1.1.2", - "@radix-ui/react-use-size": "1.1.2", - "@radix-ui/rect": "1.1.2" + "@radix-ui/react-arrow": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-rect": "1.1.4", + "@radix-ui/react-use-size": "1.1.4", + "@radix-ui/rect": "1.1.3" }, "peerDependencies": { "@types/react": "*", @@ -4868,13 +4298,13 @@ } }, "node_modules/@radix-ui/react-portal": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.13.tgz", - "integrity": "sha512-z3oXfmaHLJTF1wktbjgD6cn9jiEbq3WSondB10LIuIt2m2Ym4iJlrW04/euMwENDdWDdE7z+OuY7Qyp1YpRSwA==", + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.17.tgz", + "integrity": "sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -4892,12 +4322,12 @@ } }, "node_modules/@radix-ui/react-presence": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.7.tgz", - "integrity": "sha512-zBZ4QM5XG3JRanDmqXYf3MD6th4AFXFmgU6KNMFzUaV6F3uw9I5/zjMUvFriSEn5ewo1nxuibvyxJdmLlDcslA==", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.10.tgz", + "integrity": "sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -4915,12 +4345,12 @@ } }, "node_modules/@radix-ui/react-primitive": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.7.tgz", - "integrity": "sha512-bC3NiwsprbxKjuon9l7X6BUTw7FPVzEYaL92MPEY5SCd/9hUTPXVFtVwRix7778wtRsVao+zE062gL79FZleeQ==", + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.10.tgz", + "integrity": "sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==", "license": "MIT", "dependencies": { - "@radix-ui/react-slot": "1.3.0" + "@radix-ui/react-slot": "1.3.3" }, "peerDependencies": { "@types/react": "*", @@ -4938,22 +4368,22 @@ } }, "node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.15.tgz", - "integrity": "sha512-40svmmugfM3mUN7VUDGVE1tQGOhyi8enlGD0CNJEcMM36C1f71PKM21DFgNHUfem0XnA+d8H8oN3Z9ZpJjSslg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-collection": "1.1.12", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-is-hydrated": "0.1.1", - "@radix-ui/react-use-layout-effect": "1.1.2" + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.19.tgz", + "integrity": "sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-is-hydrated": "0.1.3", + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -4971,31 +4401,31 @@ } }, "node_modules/@radix-ui/react-select": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.3.3.tgz", - "integrity": "sha512-L5RQTXz6Anxsf9CCv+pTgiAsUpyVj7rJxsGtmhFaEOJ++cVfXucv4qWfsIO0AIB4NAhi3yovWGVMKKS1Xf1Wrg==", - "license": "MIT", - "dependencies": { - "@radix-ui/number": "1.1.2", - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-collection": "1.1.12", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.15", - "@radix-ui/react-focus-guards": "1.1.4", - "@radix-ui/react-focus-scope": "1.1.12", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-popper": "1.3.3", - "@radix-ui/react-portal": "1.1.13", - "@radix-ui/react-presence": "1.1.7", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-slot": "1.3.0", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-layout-effect": "1.1.2", - "@radix-ui/react-use-previous": "1.1.2", - "@radix-ui/react-visually-hidden": "1.2.7", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.3.7.tgz", + "integrity": "sha512-WFGImkmbzcfxeIwq/+4HvRN0pizBwbwQUED4I13ezQsDdfl38ZntN6TmR8XaSzPBqoCToe8rF75j6NPNDSzhbg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-previous": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, @@ -5015,12 +4445,12 @@ } }, "node_modules/@radix-ui/react-separator": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.11.tgz", - "integrity": "sha512-jRhe86+8PF7VZ1u14eOWVOuh2BuAhALg/FT1VcMC4OHedMTRUazDnDlKTt+yxo5cRNKHMfmvZ4sSQtWDeMV4CQ==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.15.tgz", + "integrity": "sha512-jOLO4lssEzWpoDu7G+Ze4VjwMRUBt291pnZD0gmalREZipnTX3wadQo7Fy48GCTfe14/YRN6rw/rOJqrE85Wxw==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.7" + "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", @@ -5038,12 +4468,12 @@ } }, "node_modules/@radix-ui/react-slot": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.0.tgz", - "integrity": "sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.3.tgz", + "integrity": "sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.3" + "@radix-ui/react-compose-refs": "1.1.5" }, "peerDependencies": { "@types/react": "*", @@ -5056,14 +4486,14 @@ } }, "node_modules/@radix-ui/react-toggle": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.14.tgz", - "integrity": "sha512-QI/hB65XKWACA66P64A+aHxtLUgHJeJLkaQa+awUNXT6T3swndtY5DojeHA+vldrTspMTtFBd7HfZ9QGbM1Qrw==", + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.18.tgz", + "integrity": "sha512-7lonPlKfSacd20GlOBx2ltuVKz9oqWYZz+oMQyOltw6t1y2nyftj2ZmwwUHYn49kqfDWcp8dNZm5NgV+5Z+mug==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-controllable-state": "1.2.3" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", @@ -5081,18 +4511,18 @@ } }, "node_modules/@radix-ui/react-toggle-group": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.15.tgz", - "integrity": "sha512-gIC5Q+Xljg7lmUdzSuDoy0t97yZn1sZl00Ra37ZvKrYdWnQLU6sWLd09yG8cIB9jUAlQfHgJ2ACAG00MFwsqSQ==", + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.19.tgz", + "integrity": "sha512-OtnwuSVjd1Ofi+AdnvhsjQdyuhCDwYs1w9RyB5BN/OavXOVQo42SYqQjwUnbPnaiPFBpQ9aX70dWeee+v2oBLA==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-roving-focus": "1.1.15", - "@radix-ui/react-toggle": "1.1.14", - "@radix-ui/react-use-controllable-state": "1.2.3" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-toggle": "1.1.18", + "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", @@ -5110,18 +4540,18 @@ } }, "node_modules/@radix-ui/react-toolbar": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.15.tgz", - "integrity": "sha512-t/iEuVjUnXXtrsGK40AA43uIx37sn3AqZ7oAVnPICK6lFJP6dzMzWR3U9b6eCfFjb6wtSEqkJ9Rn9xDjiOx20g==", + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.19.tgz", + "integrity": "sha512-Ph0IvtYw4VB12ZnZg+YtrGs8yJQsnizwo/zu0R4Y/nWugtJzA7Pg1eWeuDR9+LSqn+xjamss+UOSOJJJ4gx8jw==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-roving-focus": "1.1.15", - "@radix-ui/react-separator": "1.1.11", - "@radix-ui/react-toggle-group": "1.1.15" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-separator": "1.1.15", + "@radix-ui/react-toggle-group": "1.1.19" }, "peerDependencies": { "@types/react": "*", @@ -5139,23 +4569,24 @@ } }, "node_modules/@radix-ui/react-tooltip": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.12.tgz", - "integrity": "sha512-U3HoftgWnmla78vzQbLvKKb7bUYJxoiiqYFzp1wu/TBMyDqMZSuCl3aRICsD6EfVEwcJD2mumGDGUXLFVqQHKA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-dismissable-layer": "1.1.15", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-popper": "1.3.3", - "@radix-ui/react-portal": "1.1.13", - "@radix-ui/react-presence": "1.1.7", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-slot": "1.3.0", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-visually-hidden": "1.2.7" + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.16.tgz", + "integrity": "sha512-6EamKFRRnlpdadndbZ6LMwycfwkwPte1B42hs6QA0gYhjaOKqW4PZ4pjaW9UrlDX5eVt/OjncE7BFTPL5nmZhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11" }, "peerDependencies": { "@types/react": "*", @@ -5173,9 +4604,9 @@ } }, "node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.2.tgz", - "integrity": "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.4.tgz", + "integrity": "sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -5188,13 +4619,14 @@ } }, "node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.3.tgz", - "integrity": "sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.6.tgz", + "integrity": "sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-effect-event": "0.0.3", - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-use-effect-event": "0.0.5", + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -5207,12 +4639,12 @@ } }, "node_modules/@radix-ui/react-use-effect-event": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.3.tgz", - "integrity": "sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==", + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.5.tgz", + "integrity": "sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -5225,9 +4657,9 @@ } }, "node_modules/@radix-ui/react-use-is-hydrated": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.1.tgz", - "integrity": "sha512-qwOiz4Tjo8CNnrOLAYUMXeZwDzXgXpvK4TKQPmWLECM9XoWvA6+0Z2/7Ag3A4ivjS4ovbLJPbskkxioFyBhr8A==", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.3.tgz", + "integrity": "sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -5240,9 +4672,9 @@ } }, "node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.2.tgz", - "integrity": "sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.4.tgz", + "integrity": "sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -5255,9 +4687,9 @@ } }, "node_modules/@radix-ui/react-use-previous": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.2.tgz", - "integrity": "sha512-IGBQPtRFdhN6MQ8dbegVmBq1LVZluya3F1jWY+puIcQC3MHctRwTDSBWCkL/3ZcnMJLTMJ++Z+ktmvg0F89iCw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.4.tgz", + "integrity": "sha512-XoSLhbRbqxFtgJoi2fNHA3C6pDlY34x508vUpUGoFZfvePfHXHbE1lC4FYFMnJWgiCRroSTw6fOsXQoVS9RwZg==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -5270,12 +4702,12 @@ } }, "node_modules/@radix-ui/react-use-rect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.2.tgz", - "integrity": "sha512-d8a+bBY/FxikNPlgJJoaBHZX+zKVbWHYJGTLnLvveQgFSTntkGdEKv3JDtHrMS0DNYpllz2nRsTLGLKYttbpmw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.4.tgz", + "integrity": "sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==", "license": "MIT", "dependencies": { - "@radix-ui/rect": "1.1.2" + "@radix-ui/rect": "1.1.3" }, "peerDependencies": { "@types/react": "*", @@ -5288,12 +4720,12 @@ } }, "node_modules/@radix-ui/react-use-size": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.2.tgz", - "integrity": "sha512-giWQp+4mxjBPt4KZ0MmyuykFNWfbDxKt4x+fPkRYmgRFJSbCZFzUglvMb/Kjn38tm10YP4ufiQZDx3zna4LU6w==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.4.tgz", + "integrity": "sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -5306,12 +4738,12 @@ } }, "node_modules/@radix-ui/react-visually-hidden": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.7.tgz", - "integrity": "sha512-1wNZBggTDK3GRuuQ6nP4k2yi7a6l7I5qbMPbZcRsrGsGVead/f/d5FhEzUvqFs0bcrDLx7n1zKQ3JvLR6whaaw==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.11.tgz", + "integrity": "sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.7" + "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", @@ -5329,9 +4761,9 @@ } }, "node_modules/@radix-ui/rect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.2.tgz", - "integrity": "sha512-xnXE7wG13PI+cxieVssYXlQJuYVRhH9NBoxt3KNwzghDIA69GMm7d4wXRouHIYjE+KvS6U/MsMO73NdS2MH9ZA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.3.tgz", + "integrity": "sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==", "license": "MIT" }, "node_modules/@reduxjs/toolkit": { @@ -5361,324 +4793,6 @@ } } }, - "node_modules/@remotion/bundler": { - "version": "4.0.477", - "resolved": "https://registry.npmjs.org/@remotion/bundler/-/bundler-4.0.477.tgz", - "integrity": "sha512-nbTCxUtiNWg06zbIJ9ia0v8UrK/o486sE/KKvMqs6u+FCdQDO86cOEmqSoGsImsDe3TwHWTUws9V7kpcXA+E8g==", - "license": "SEE LICENSE IN LICENSE.md", - "dependencies": { - "@remotion/media-parser": "4.0.477", - "@remotion/studio": "4.0.477", - "@remotion/studio-shared": "4.0.477", - "@remotion/timeline-utils": "4.0.477", - "@rspack/core": "1.7.11", - "@rspack/plugin-react-refresh": "1.6.1", - "css-loader": "7.1.4", - "esbuild": "0.28.1", - "react-refresh": "0.18.0", - "remotion": "4.0.477", - "style-loader": "4.0.0", - "webpack": "5.105.0" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@remotion/canvas-capture": { - "version": "4.0.477", - "resolved": "https://registry.npmjs.org/@remotion/canvas-capture/-/canvas-capture-4.0.477.tgz", - "integrity": "sha512-/4M9rP/ltAggYXqhAMHj6wxBX7zt1CdekK8JEY7bbIi2uFHltD2qRTfT8B7DZaOjG+b6cWZ/VROusfiuvpMCNg==", - "license": "Remotion License", - "dependencies": { - "mediabunny": "1.45.0", - "remotion": "4.0.477" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@remotion/compositor-darwin-arm64": { - "version": "4.0.477", - "resolved": "https://registry.npmjs.org/@remotion/compositor-darwin-arm64/-/compositor-darwin-arm64-4.0.477.tgz", - "integrity": "sha512-d6PfHTTnlkqqY+C+40m3hgDzkevG/wONd2ZsWJEShx1QRN3UHgTuhW1xKZw/E+DpTGXdvCVmeM2QCsBrpSfnKQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@remotion/compositor-darwin-x64": { - "version": "4.0.477", - "resolved": "https://registry.npmjs.org/@remotion/compositor-darwin-x64/-/compositor-darwin-x64-4.0.477.tgz", - "integrity": "sha512-tnuSFZGV9Vg/9N8YjvATPfOJKD31GM+B9XFuVLdcHc7+hyikdX46xa8pKKKUVLOkeJegs8A22Wi+V1/AEnWqEQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@remotion/compositor-linux-arm64-gnu": { - "version": "4.0.477", - "resolved": "https://registry.npmjs.org/@remotion/compositor-linux-arm64-gnu/-/compositor-linux-arm64-gnu-4.0.477.tgz", - "integrity": "sha512-3xhtIdqufAiO49v8tbbkPDyDkd7uRRB8vx+vSPi9+THUI/h0uqi+ylENHtsWj7veEDCiTB3Y4ZjsmotEVMHQJA==", - "cpu": [ - "arm64" - ], - "libc": [ - "glibc" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@remotion/compositor-linux-arm64-musl": { - "version": "4.0.477", - "resolved": "https://registry.npmjs.org/@remotion/compositor-linux-arm64-musl/-/compositor-linux-arm64-musl-4.0.477.tgz", - "integrity": "sha512-qenbQpmSm9XQn2uaW3yt+P5YBgQuCb+jitTfQunhR1l0tk6oC2kKPn14qKu9jAs7vZPH11bV8V76DcYUhqdbAQ==", - "cpu": [ - "arm64" - ], - "libc": [ - "musl" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@remotion/compositor-linux-x64-gnu": { - "version": "4.0.477", - "resolved": "https://registry.npmjs.org/@remotion/compositor-linux-x64-gnu/-/compositor-linux-x64-gnu-4.0.477.tgz", - "integrity": "sha512-P+5MRPkaRskoUgseJxEko53KvW/Rzu0Ow/htyzJjC9XpigtjYTJPKRKOjw4/ihPYJoVDH+e9s/jyOF06odzLVA==", - "cpu": [ - "x64" - ], - "libc": [ - "glibc" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@remotion/compositor-linux-x64-musl": { - "version": "4.0.477", - "resolved": "https://registry.npmjs.org/@remotion/compositor-linux-x64-musl/-/compositor-linux-x64-musl-4.0.477.tgz", - "integrity": "sha512-uas9/X85QfI6p5vTOCcjqmCBizIK6QeZlIXVrKc1fcmSzhk3udjbzx9XDDo71m6Bz8IfWeqV1ZkX/5pLJYufFg==", - "cpu": [ - "x64" - ], - "libc": [ - "musl" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@remotion/compositor-win32-x64-msvc": { - "version": "4.0.477", - "resolved": "https://registry.npmjs.org/@remotion/compositor-win32-x64-msvc/-/compositor-win32-x64-msvc-4.0.477.tgz", - "integrity": "sha512-7oldAftiUrFDI1U5z0zN87xfhpuJuIrLrNP16GFgj0JZN4AzRQT/iGYoH5+H2/clT910mlq4YtrfAv1RsHe4Cg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@remotion/licensing": { - "version": "4.0.477", - "resolved": "https://registry.npmjs.org/@remotion/licensing/-/licensing-4.0.477.tgz", - "integrity": "sha512-Ayor3/+CyXkyYUvzxEFcTVWe46+ld1i9jyrmZY9OqO77gCQ3khOAivjNsI/4foFgnfb7aRNvcCBYr1D9iZLn/w==", - "license": "MIT" - }, - "node_modules/@remotion/media-parser": { - "version": "4.0.477", - "resolved": "https://registry.npmjs.org/@remotion/media-parser/-/media-parser-4.0.477.tgz", - "integrity": "sha512-E6qAQvY2s5VzvGNi0FOiDjcEm2RWRsRO+5012ZQneAUqp54JVP/6M3oXM5i34wo9PLjMSqfXgmxLPRdF08YokQ==", - "license": "Remotion License https://remotion.dev/license" - }, - "node_modules/@remotion/media-utils": { - "version": "4.0.477", - "resolved": "https://registry.npmjs.org/@remotion/media-utils/-/media-utils-4.0.477.tgz", - "integrity": "sha512-ItEniXSb0OUnZJSjcOxoD6HvHoQ1wbn0FQNz/BkuVo7AQLvVeY1GGf9XWIa0mvqRRd790jYhW9QOqToeJryeeQ==", - "license": "MIT", - "dependencies": { - "mediabunny": "1.45.0", - "remotion": "4.0.477" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@remotion/player": { - "version": "4.0.477", - "resolved": "https://registry.npmjs.org/@remotion/player/-/player-4.0.477.tgz", - "integrity": "sha512-x0aWSsbXQIlW2V5R6hVksHsyw4CBWN0ctbc0b8Aq5kbAg901vZmDXMekNXyMicqPyzwPRfTeDfpPyGq5RwvOSA==", - "license": "SEE LICENSE IN LICENSE.md", - "dependencies": { - "remotion": "4.0.477" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@remotion/renderer": { - "version": "4.0.477", - "resolved": "https://registry.npmjs.org/@remotion/renderer/-/renderer-4.0.477.tgz", - "integrity": "sha512-OKHhmjLZwvXAOKvby7WCpyHGFUGUFMofKjJMGobx/yeyG0k/So/FSFBJQBBpP8XSdHY1RmFqKpMiKIO5SACB5A==", - "license": "SEE LICENSE IN LICENSE.md", - "dependencies": { - "@remotion/licensing": "4.0.477", - "@remotion/streaming": "4.0.477", - "execa": "5.1.1", - "remotion": "4.0.477", - "source-map": "0.8.0-beta.0", - "ws": "8.20.1" - }, - "optionalDependencies": { - "@remotion/compositor-darwin-arm64": "4.0.477", - "@remotion/compositor-darwin-x64": "4.0.477", - "@remotion/compositor-linux-arm64-gnu": "4.0.477", - "@remotion/compositor-linux-arm64-musl": "4.0.477", - "@remotion/compositor-linux-x64-gnu": "4.0.477", - "@remotion/compositor-linux-x64-musl": "4.0.477", - "@remotion/compositor-win32-x64-msvc": "4.0.477" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@remotion/renderer/node_modules/source-map": { - "version": "0.8.0-beta.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", - "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", - "deprecated": "The work that was done in this beta branch won't be included in future versions", - "license": "BSD-3-Clause", - "dependencies": { - "whatwg-url": "^7.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@remotion/streaming": { - "version": "4.0.477", - "resolved": "https://registry.npmjs.org/@remotion/streaming/-/streaming-4.0.477.tgz", - "integrity": "sha512-GXSR5BaC2UaaPHwAEBbuwXXDedrUfa3KQ1GpwDBKfr/F2rm0QxOPgPoVzB14lt7CvaQsuig22NZD6h6BTDAH5A==", - "license": "MIT" - }, - "node_modules/@remotion/studio": { - "version": "4.0.477", - "resolved": "https://registry.npmjs.org/@remotion/studio/-/studio-4.0.477.tgz", - "integrity": "sha512-/Z+yYf1g6dQAQbGqthulE+Ra9PBNqAcgRYb3F6mu3iFf9zfZimP3BIZ03eYrXDDC1SeyVt9LaZu4QcXZv/VkOg==", - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.31", - "@remotion/canvas-capture": "4.0.477", - "@remotion/media-utils": "4.0.477", - "@remotion/player": "4.0.477", - "@remotion/renderer": "4.0.477", - "@remotion/studio-shared": "4.0.477", - "@remotion/timeline-utils": "4.0.477", - "@remotion/web-renderer": "4.0.477", - "@remotion/zod-types": "4.0.477", - "mediabunny": "1.45.0", - "memfs": "3.4.3", - "open": "8.4.2", - "remotion": "4.0.477", - "semver": "7.5.3", - "zod": "4.3.6" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@remotion/studio-shared": { - "version": "4.0.477", - "resolved": "https://registry.npmjs.org/@remotion/studio-shared/-/studio-shared-4.0.477.tgz", - "integrity": "sha512-uZqv9tTBddFbMgAuLr0huCu7f8A8LIlq1r+6Ap5tnRlbj1olmNVGWpA2jkChY8jJZADMIED8klhS4amV5nKiZQ==", - "license": "MIT", - "dependencies": { - "remotion": "4.0.477" - } - }, - "node_modules/@remotion/studio/node_modules/@remotion/zod-types": { - "version": "4.0.477", - "resolved": "https://registry.npmjs.org/@remotion/zod-types/-/zod-types-4.0.477.tgz", - "integrity": "sha512-3gglLjiN6AJo1ayqbxZCBhpLJIieD3XGUnBgJ7b1Ew+C881wjOSH8O5vt+pvrO1E0RizjqbbBkbV99FwLFrC/A==", - "license": "MIT", - "dependencies": { - "remotion": "4.0.477" - }, - "peerDependencies": { - "zod": "4.3.6" - } - }, - "node_modules/@remotion/studio/node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/@remotion/timeline-utils": { - "version": "4.0.477", - "resolved": "https://registry.npmjs.org/@remotion/timeline-utils/-/timeline-utils-4.0.477.tgz", - "integrity": "sha512-OsOjNPCNwp08z0KG6OlcQD0UCsAuyIRD4xG+BIXvKaYvsyUiX0+KmsGD7ULANcd/uj6oj3xldNp0yqOi0v6IYA==", - "license": "MIT", - "dependencies": { - "mediabunny": "1.45.0" - } - }, - "node_modules/@remotion/web-renderer": { - "version": "4.0.477", - "resolved": "https://registry.npmjs.org/@remotion/web-renderer/-/web-renderer-4.0.477.tgz", - "integrity": "sha512-w8xkoDNg14DbfQXGvx5XfgaPu38dHzX1BFG2lUgMKoB7RfVoXMbU/VHEQ3Xm+vYGxW2Pa3oRgCWqUVprikR4+g==", - "license": "UNLICENSED", - "dependencies": { - "@mediabunny/aac-encoder": "1.45.0", - "@mediabunny/flac-encoder": "1.45.0", - "@mediabunny/mp3-encoder": "1.45.0", - "@remotion/licensing": "4.0.477", - "mediabunny": "1.45.0", - "remotion": "4.0.477" - }, - "peerDependencies": { - "react": ">=18.0.0", - "react-dom": ">=18.0.0" - } - }, - "node_modules/@repomix/strip-comments": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@repomix/strip-comments/-/strip-comments-2.4.2.tgz", - "integrity": "sha512-7a18ODb043eszMBr6mpVWz802xIRMzdmptarVxTtnMIW7ZQzba/v8jLp3kcHUHb76uRkyJRPpGSwdm7+8GmsEA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/@repomix/tree-sitter-wasms": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/@repomix/tree-sitter-wasms/-/tree-sitter-wasms-0.1.17.tgz", - "integrity": "sha512-tc3HnFqdMF1pXhIMzG3aTaBDpIiHK2tPfn3fwqA6P3WTbHa+1EuuTubbKshvmN7xCHP5Ojz0/VW4R+XvR88KOw==", - "license": "Unlicense" - }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", @@ -5714,9 +4828,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", - "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.4.tgz", + "integrity": "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==", "cpu": [ "arm" ], @@ -5727,9 +4841,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", - "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.4.tgz", + "integrity": "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==", "cpu": [ "arm64" ], @@ -5740,9 +4854,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", - "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.4.tgz", + "integrity": "sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==", "cpu": [ "arm64" ], @@ -5753,9 +4867,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", - "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.4.tgz", + "integrity": "sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==", "cpu": [ "x64" ], @@ -5766,9 +4880,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", - "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.4.tgz", + "integrity": "sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==", "cpu": [ "arm64" ], @@ -5779,9 +4893,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", - "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.4.tgz", + "integrity": "sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==", "cpu": [ "x64" ], @@ -5792,15 +4906,12 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", - "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.4.tgz", + "integrity": "sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==", "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -5808,15 +4919,12 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", - "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.4.tgz", + "integrity": "sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==", "cpu": [ "arm" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -5824,15 +4932,12 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", - "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.4.tgz", + "integrity": "sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==", "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -5840,15 +4945,12 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", - "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.4.tgz", + "integrity": "sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==", "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -5856,15 +4958,12 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", - "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.4.tgz", + "integrity": "sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==", "cpu": [ "loong64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -5872,15 +4971,12 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", - "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.4.tgz", + "integrity": "sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==", "cpu": [ "loong64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -5888,15 +4984,12 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", - "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.4.tgz", + "integrity": "sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==", "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -5904,15 +4997,12 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", - "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.4.tgz", + "integrity": "sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==", "cpu": [ "ppc64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -5920,15 +5010,12 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", - "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.4.tgz", + "integrity": "sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==", "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -5936,15 +5023,12 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", - "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.4.tgz", + "integrity": "sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==", "cpu": [ "riscv64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -5952,15 +5036,12 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", - "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.4.tgz", + "integrity": "sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==", "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -5968,15 +5049,12 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", - "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.4.tgz", + "integrity": "sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==", "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -5984,15 +5062,12 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", - "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.4.tgz", + "integrity": "sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==", "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -6000,9 +5075,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", - "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.4.tgz", + "integrity": "sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==", "cpu": [ "x64" ], @@ -6013,9 +5088,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", - "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.4.tgz", + "integrity": "sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==", "cpu": [ "arm64" ], @@ -6026,9 +5101,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", - "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.4.tgz", + "integrity": "sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==", "cpu": [ "arm64" ], @@ -6039,9 +5114,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", - "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.4.tgz", + "integrity": "sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==", "cpu": [ "ia32" ], @@ -6052,9 +5127,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", - "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.4.tgz", + "integrity": "sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==", "cpu": [ "x64" ], @@ -6065,9 +5140,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", - "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.4.tgz", + "integrity": "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==", "cpu": [ "x64" ], @@ -6077,274 +5152,28 @@ "win32" ] }, - "node_modules/@rspack/binding": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-1.7.11.tgz", - "integrity": "sha512-2MGdy2s2HimsDT444Bp5XnALzNRxuBNc7y0JzyuqKbHBywd4x2NeXyhWXXoxufaCFu5PBc9Qq9jyfjW2Aeh06Q==", - "license": "MIT", - "optionalDependencies": { - "@rspack/binding-darwin-arm64": "1.7.11", - "@rspack/binding-darwin-x64": "1.7.11", - "@rspack/binding-linux-arm64-gnu": "1.7.11", - "@rspack/binding-linux-arm64-musl": "1.7.11", - "@rspack/binding-linux-x64-gnu": "1.7.11", - "@rspack/binding-linux-x64-musl": "1.7.11", - "@rspack/binding-wasm32-wasi": "1.7.11", - "@rspack/binding-win32-arm64-msvc": "1.7.11", - "@rspack/binding-win32-ia32-msvc": "1.7.11", - "@rspack/binding-win32-x64-msvc": "1.7.11" - } - }, - "node_modules/@rspack/binding-darwin-arm64": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.7.11.tgz", - "integrity": "sha512-oduECiZVqbO5zlVw+q7Vy65sJFth99fWPTyucwvLJJtJkPL5n17Uiql2cYP6Ijn0pkqtf1SXgK8WjiKLG5bIig==", - "cpu": [ - "arm64" - ], + "node_modules/@shikijs/core": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.23.0.tgz", + "integrity": "sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "dependencies": { + "@shikijs/types": "3.23.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + } }, - "node_modules/@rspack/binding-darwin-x64": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.7.11.tgz", - "integrity": "sha512-a1+TtTE9ap6RalgFi7FGIgkJP6O4Vy6ctv+9WGJy53E4kuqHR0RygzaiVxCI/GMc/vBT9vY23hyrpWb3d1vtXA==", - "cpu": [ - "x64" - ], + "node_modules/@shikijs/engine-javascript": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.23.0.tgz", + "integrity": "sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rspack/binding-linux-arm64-gnu": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.7.11.tgz", - "integrity": "sha512-P0QrGRPbTWu6RKWfN0bDtbnEps3rXH0MWIMreZABoUrVmNQKtXR6e73J3ub6a+di5s2+K0M2LJ9Bh2/H4UsDUA==", - "cpu": [ - "arm64" - ], - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rspack/binding-linux-arm64-musl": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.7.11.tgz", - "integrity": "sha512-6ky7R43VMjWwmx3Yx7Jl7faLBBMAgMDt+/bN35RgwjiPgsIByz65EwytUVuW9rikB43BGHvA/eqlnjLrUzNBqw==", - "cpu": [ - "arm64" - ], - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rspack/binding-linux-x64-gnu": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.7.11.tgz", - "integrity": "sha512-cuOJMfCOvb2Wgsry5enXJ3iT1FGUjdPqtGUBVupQlEG4ntSYsQ2PtF4wIDVasR3wdxC5nQbipOrDiN/u6fYsdQ==", - "cpu": [ - "x64" - ], - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rspack/binding-linux-x64-musl": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.7.11.tgz", - "integrity": "sha512-CoK37hva4AmHGh3VCsQXmGr40L36m1/AdnN5LEjUX6kx5rEH7/1nEBN6Ii72pejqDVvk9anEROmPDiPw10tpFg==", - "cpu": [ - "x64" - ], - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rspack/binding-wasm32-wasi": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-1.7.11.tgz", - "integrity": "sha512-OtrmnPUVJMxjNa3eDMfHyPdtlLRmmp/aIm0fQHlAOATbZvlGm12q7rhPW5BXTu1yh+1rQ1/uqvz+SzKEZXuJaQ==", - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "1.0.7" - } - }, - "node_modules/@rspack/binding-win32-arm64-msvc": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.7.11.tgz", - "integrity": "sha512-lObFW6e5lCWNgTBNwT//yiEDbsxm9QG4BYUojqeXxothuzJ/L6ibXz6+gLMvbOvLGV3nKgkXmx8GvT9WDKR0mA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rspack/binding-win32-ia32-msvc": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.7.11.tgz", - "integrity": "sha512-0pYGnZd8PPqNR68zQ8skamqNAXEA1sUfXuAdYcknIIRq2wsbiwFzIc0Pov1cIfHYab37G7sSIPBiOUdOWF5Ivw==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rspack/binding-win32-x64-msvc": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.7.11.tgz", - "integrity": "sha512-EeQXayoQk/uBkI3pdoXfQBXNIUrADq56L3s/DFyM2pJeUDrWmhfIw2UFIGkYPTMSCo8F2JcdcGM32FGJrSnU0Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rspack/core": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@rspack/core/-/core-1.7.11.tgz", - "integrity": "sha512-rsD9b+Khmot5DwCMiB3cqTQo53ioPG3M/A7BySu8+0+RS7GCxKm+Z+mtsjtG/vsu4Tn2tcqCdZtA3pgLoJB+ew==", - "license": "MIT", - "dependencies": { - "@module-federation/runtime-tools": "0.22.0", - "@rspack/binding": "1.7.11", - "@rspack/lite-tapable": "1.1.0" - }, - "engines": { - "node": ">=18.12.0" - }, - "peerDependencies": { - "@swc/helpers": ">=0.5.1" - }, - "peerDependenciesMeta": { - "@swc/helpers": { - "optional": true - } - } - }, - "node_modules/@rspack/lite-tapable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rspack/lite-tapable/-/lite-tapable-1.1.0.tgz", - "integrity": "sha512-E2B0JhYFmVAwdDiG14+DW0Di4Ze4Jg10Pc4/lILUrd5DRCaklduz2OvJ5HYQ6G+hd+WTzqQb3QnDNfK4yvAFYw==", - "license": "MIT" - }, - "node_modules/@rspack/plugin-react-refresh": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@rspack/plugin-react-refresh/-/plugin-react-refresh-1.6.1.tgz", - "integrity": "sha512-eqqW5645VG3CzGzFgNg5HqNdHVXY+567PGjtDhhrM8t67caxmsSzRmT5qfoEIfBcGgFkH9vEg7kzXwmCYQdQDw==", - "license": "MIT", - "dependencies": { - "error-stack-parser": "^2.1.4", - "html-entities": "^2.6.0" - }, - "peerDependencies": { - "react-refresh": ">=0.10.0 <1.0.0", - "webpack-hot-middleware": "2.x" - }, - "peerDependenciesMeta": { - "webpack-hot-middleware": { - "optional": true - } - } - }, - "node_modules/@secretlint/core": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/@secretlint/core/-/core-13.0.2.tgz", - "integrity": "sha512-15vg+zCmjQuDFVoOPNt0TyEu0Z95eir2MO19++wG82yM/vbPtFu/m9qd6oh54ZOBXgMcchdGDTxMwJNDnCLxcA==", - "license": "MIT", - "dependencies": { - "@secretlint/profiler": "13.0.2", - "@secretlint/types": "13.0.2", - "debug": "^4.4.3", - "structured-source": "^4.0.0" - }, - "engines": { - "node": ">=22.0.0" - } - }, - "node_modules/@secretlint/profiler": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/@secretlint/profiler/-/profiler-13.0.2.tgz", - "integrity": "sha512-vGaLZCoi9KewOF+sM0iIEBviWaH9mls1HmQFBgEPq4fnvmVO4PfKIkVr4CcAFm3Msg4NjzFE2RBAGwRR+5HEmQ==", - "license": "MIT" - }, - "node_modules/@secretlint/secretlint-rule-preset-recommend": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-preset-recommend/-/secretlint-rule-preset-recommend-13.0.2.tgz", - "integrity": "sha512-D03Kw51qOgffj9zNlJFZUarVmP8zGcCZNi7A14+vZtHskbbBPEsS/f09idb48rrlMSaRMBN29IkqfbmPyL9xtw==", - "license": "MIT", - "engines": { - "node": ">=22.0.0" - } - }, - "node_modules/@secretlint/types": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/@secretlint/types/-/types-13.0.2.tgz", - "integrity": "sha512-cAZjr6ZTKgSuJMLyTGDrUEtK3XEZa+JStIkD+DmdkT32VGAN+dQJiYr1So3XJopsKSrqhP5kjZKT8WWtftZIpQ==", - "license": "MIT", - "engines": { - "node": ">=22.0.0" - } - }, - "node_modules/@shikijs/core": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.23.0.tgz", - "integrity": "sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==", - "license": "MIT", - "dependencies": { - "@shikijs/types": "3.23.0", - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4", - "hast-util-to-html": "^9.0.5" - } - }, - "node_modules/@shikijs/engine-javascript": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.23.0.tgz", - "integrity": "sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==", - "license": "MIT", - "dependencies": { - "@shikijs/types": "3.23.0", - "@shikijs/vscode-textmate": "^10.0.2", - "oniguruma-to-es": "^4.3.4" - } + "dependencies": { + "@shikijs/types": "3.23.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.4" + } }, "node_modules/@shikijs/engine-oniguruma": { "version": "3.23.0", @@ -6402,22 +5231,10 @@ "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/@sindresorhus/merge-streams": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", - "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@speed-highlight/core": { - "version": "1.2.17", - "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.17.tgz", - "integrity": "sha512-Z92FwKpCtfaW1V0jTU/fh3QzYEZN8wDwrzRIBoADCJfn4mJCNcJN/XegifX7BDrQ8/h9Xh/JnbyMchL0FqXrkg==", + "version": "1.2.23", + "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.23.tgz", + "integrity": "sha512-iRoq6i6JDJP6Mt2A5JaPvzw0pgYHH6k92ij+yXiTrB7T2y9N789aWE3EHWj/5ztlJBokcCBja3iYLVdu5wgnkg==", "license": "CC0-1.0" }, "node_modules/@standard-schema/spec": { @@ -6434,47 +5251,47 @@ "peer": true }, "node_modules/@tailwindcss/node": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.2.tgz", - "integrity": "sha512-yWP/sqEcBLaD8JuA6zNwxoYKr75qxTioYwlRwekj5Jr/I5GXnoJfjetH/psLUIv74cYTH2lBUEzBkinthoYcBg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.5", - "enhanced-resolve": "5.21.6", + "enhanced-resolve": "^5.24.1", "jiti": "^2.7.0", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", - "tailwindcss": "4.3.2" + "tailwindcss": "4.3.3" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.2.tgz", - "integrity": "sha512-z8ZgnzX8gdNoWLBLqBPoh/sjnxkwvf9ZuWjnO0l0yIzbLa5/9S+eC5QxGZKRobVHIC3/1BoMWjHblqWjcgFgag==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", "license": "MIT", "engines": { "node": ">= 20" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.3.2", - "@tailwindcss/oxide-darwin-arm64": "4.3.2", - "@tailwindcss/oxide-darwin-x64": "4.3.2", - "@tailwindcss/oxide-freebsd-x64": "4.3.2", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.2", - "@tailwindcss/oxide-linux-arm64-gnu": "4.3.2", - "@tailwindcss/oxide-linux-arm64-musl": "4.3.2", - "@tailwindcss/oxide-linux-x64-gnu": "4.3.2", - "@tailwindcss/oxide-linux-x64-musl": "4.3.2", - "@tailwindcss/oxide-wasm32-wasi": "4.3.2", - "@tailwindcss/oxide-win32-arm64-msvc": "4.3.2", - "@tailwindcss/oxide-win32-x64-msvc": "4.3.2" + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.2.tgz", - "integrity": "sha512-WHxqIuHpvZ5VtdX6GTl1Ik/Vp2YuN42Et+0CdeaVd/frQ9jAvGmvR8vLT+jk3e8/Q3x8kECB9+R17pgpp2BulA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", "cpu": [ "arm64" ], @@ -6488,9 +5305,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.2.tgz", - "integrity": "sha512-GZypeUY/IDJW3877KeM+O67vbXr3MBnbtEL4aYhNErv/JWZhye2vGSWWG9tB6iiqR2MqRNkY8IOUy4NdSZV26w==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", "cpu": [ "arm64" ], @@ -6504,9 +5321,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.2.tgz", - "integrity": "sha512-UIIzmefR6KO1sDU7MzRqAxC8iBpft/VhkGjTjnhoS6k7Z3rQ9wEgA1ODSiyH/tcSYssulNm4Ci3hOeK1jH7ccQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", "cpu": [ "x64" ], @@ -6520,9 +5337,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.2.tgz", - "integrity": "sha512-GN+uAmcI6DNspnCDwtOAZrTz6oukJnp337qZvxqCGLd3BHBzJpO0ZbTLRvJNdztOeAmTzewewGIMPb0tk2R4WA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", "cpu": [ "x64" ], @@ -6536,9 +5353,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.2.tgz", - "integrity": "sha512-4ABn7qSbdHRwTiDiuWNegCyb5+2FJ4vKIKc3DmKrvAFw7MU1Lm11dIkTPwUaFdTzc7IsOpDbqBrlh0x6y36U/w==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", "cpu": [ "arm" ], @@ -6552,15 +5369,12 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.2.tgz", - "integrity": "sha512-wDgEIGwoM8w8pufh9LVt1PahDgNdKXrLC2qfAnV3vAmococ9RWbxeAw4pxPttd/TsJfwjyLf90Dg1y9y8I6Emw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -6571,15 +5385,12 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.2.tgz", - "integrity": "sha512-J5Nuk0uZQIiMTJj3LEx4sAA9tMFUoXQZFv1J6An+QGYe53HKRJuFDi0rpq/tuouCZeAbOBY3kQ6g8qeD4TUjtA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -6590,15 +5401,12 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.2.tgz", - "integrity": "sha512-kqCZpSKOBEJO4mz7OqWoofBZeXTAwaVGPj0ErAj7CojmhKpWVWVOnrt9dE8odoIraZq4oj3ausM37kXi+Tow8w==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -6609,15 +5417,12 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.2.tgz", - "integrity": "sha512-cixpqbh2toJDmkuCRI68nXA8ZxNmdK9Y+9v5h3MC3ZQKy/0BO8AWzlkWyRM7JAFSGBlfig4YVTPsK6MVgqz1uw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -6628,9 +5433,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.2.tgz", - "integrity": "sha512-4ec2Z/LOmRsAgU23CS4xeJfcJlmRg94A/XrbGRCF1gyU/zdDfRLYDVsS+ynSZCmGNxQ1jQriQOKMQeQxBA3Isw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -6717,9 +5522,9 @@ "optional": true }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.2.tgz", - "integrity": "sha512-Zyr/M0+XcYZu3bZrUytc7TXvrk0ftWfl8gN2MwekNDzhqhKRUucMPSeOzM0o0wH5AWOU49BsKRrfKxI2atCPMQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", "cpu": [ "arm64" ], @@ -6733,9 +5538,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.2.tgz", - "integrity": "sha512-QI9BO7KlNZsp2GuO0jwAAj5jCDABOKXRkCk2XuKTSaNEFSdfzqswYVTtCHBNKHLsqyjFyFkqlDiwkNbTYSssMQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", "cpu": [ "x64" ], @@ -6749,7768 +5554,5595 @@ } }, "node_modules/@tailwindcss/vite": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.2.tgz", - "integrity": "sha512-eHpMeX4JXfVNJDEcsouTeCBubJBTcTLigeaw/NTUW6PB5ATKKXdyonnXgTBX2VuRbjz1hjfz6C5XAhr52ImQXA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz", + "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==", "license": "MIT", "dependencies": { - "@tailwindcss/node": "4.3.2", - "@tailwindcss/oxide": "4.3.2", - "tailwindcss": "4.3.2" + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "tailwindcss": "4.3.3" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "node_modules/@treeseed/sdk": { - "version": "0.12.62", - "resolved": "https://registry.npmjs.org/@treeseed/sdk/-/sdk-0.12.62.tgz", - "integrity": "sha512-wR5ukK2pqw0fUE9gJMYgJmIUlMTy4NoYKJrHEo6NAN/QFNrpru+9vilaS4kCPrcZQl/YAleuv962Z8c/pwwrBQ==", - "license": "AGPL-3.0-only", - "dependencies": { - "@github/copilot": "1.0.39", - "@github/copilot-language-server": "1.480.0", - "@github/copilot-sdk": "0.3.0", - "@remotion/bundler": "4.0.477", - "@remotion/renderer": "4.0.477", - "drizzle-orm": "^0.45.2", + "version": "0.13.0-rc.122", + "resolved": "https://registry.npmjs.org/@treeseed/sdk/-/sdk-0.13.0-rc.122.tgz", + "integrity": "sha512-JKk6yDA1SDV2rWscnr95XsIGUdOcTYC8Ac6adQXkXArMriSVn0g25IPOntj2l2TK8s83KoKUmPaa9QfXbqg5jg==", + "license": "Apache-2.0", + "dependencies": { + "@treeseed/treedx": "0.3.0-rc.17", "esbuild": "^0.28.0", - "extract-zip": "2.0.1", - "github-slugger": "^2.0.0", - "hono": "^4.8.2", - "ink": "^7.0.0", - "libsodium-wrappers": "^0.7.15", - "mdast-util-to-string": "^4.0.0", - "minisearch": "^7.2.0", - "octokit": "^5.0.3", - "pg": "^8.21.0", - "playwright": "^1.60.0", - "railway": "3.4.1", - "react": "^19.2.5", - "remark-mdx": "^3.1.1", - "remark-parse": "^11.0.0", - "remotion": "4.0.477", - "repomix": "^1.13.1", - "tar": "7.5.13", + "libsodium-wrappers-sumo": "0.8.4", "typescript": "^5.9.3", - "unified": "^11.0.5", - "wrangler": "4.86.0", - "yaml": "^2.8.1" - }, - "bin": { - "treeseed-sdk-verify": "dist/verification.js" + "yaml": "^2.8.1", + "zod": "^3.25.76" }, "engines": { "node": ">=22" } }, - "node_modules/@treeseed/sdk/node_modules/@cloudflare/kv-asset-handler": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.2.tgz", - "integrity": "sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==", - "license": "MIT OR Apache-2.0", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@treeseed/sdk/node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20260426.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260426.1.tgz", - "integrity": "sha512-Ch7DqsmYzSQRTY87pZpsGsFVz9VVBnLPnCBOHxKt1HH25a7oMu1w1PbPWqVmE0VerCLsj/TScX7Ob3v6E14TZw==", - "cpu": [ - "x64" - ], + "node_modules/@treeseed/treedx": { + "version": "0.3.0-rc.17", + "resolved": "https://registry.npmjs.org/@treeseed/treedx/-/treedx-0.3.0-rc.17.tgz", + "integrity": "sha512-L1DCxkOZGS2of2IvbSC70baIpGlEuFBpHUhV4oKq53xXARN8dw4st2SFUz7IaxOIRzR9gD/vH12hIPnTFLJ7tA==", "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": ">=16" + "node": ">=22" } }, - "node_modules/@treeseed/sdk/node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20260426.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260426.1.tgz", - "integrity": "sha512-0m0U8vaPRH25SpKjbSyRql6gmPe4rCsETRV2WW0qBnuMdKNr5Vh5/Uez80xVrfiCCRMTULGeg63Nqg2vg6CDOA==", - "cpu": [ - "arm64" - ], + "node_modules/@treeseed/ui": { + "version": "0.12.18-rc.11", + "resolved": "https://registry.npmjs.org/@treeseed/ui/-/ui-0.12.18-rc.11.tgz", + "integrity": "sha512-ptPfxG3W3eSpHnXzdu8k95ktgymDdPwAugzWHtQ5ZYrhcBFDVTGLpTaxQQfKCCzgf7o9N4Mdm1Lyq4ITp/8uUQ==", "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "@codemirror/lang-markdown": "^6.5.0", + "@codemirror/language": "^6.12.3", + "@codemirror/view": "^6.43.0", + "@lezer/highlight": "^1.2.3", + "@mdxeditor/editor": "^4.0.2", + "codemirror": "^6.0.2", + "rehype-stringify": "^10.0.1", + "remark-gfm": "^4.0.1", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "unified": "^11.0.5", + "yaml": "^2.8.1" + }, "engines": { - "node": ">=16" + "node": ">=22" + }, + "peerDependencies": { + "astro": "^5.0.0 || ^6.0.0", + "react": "^18.3.0 || ^19.0.0", + "react-dom": "^18.3.0 || ^19.0.0", + "recharts": "^3.0.0" } }, - "node_modules/@treeseed/sdk/node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20260426.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260426.1.tgz", - "integrity": "sha512-C8LlC8uSYzg49y51n++75esxZmMp+Uz1OKHHA/4lkv6rjOTbcHQJuEwSLppjybVIXpv7A8MBhbu9iyCTvyv1mw==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16" + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" } }, - "node_modules/@treeseed/sdk/node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20260426.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260426.1.tgz", - "integrity": "sha512-ESVp/OIFMAqjQsa8BOP2BQQz5Vpfv6ncN6lNnIuNeOgsISQBdYk+LA60bwQHMud9tvmnSYtONp1zkZ8OQz+x6w==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@treeseed/sdk/node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20260426.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260426.1.tgz", - "integrity": "sha512-d3Xj/IjINRgNVwH+eKhpUn4xkkcEewbWXbOvBlapiirKWh5zl9m0Epi3qOqmjyRYK6MICqIGXg4qZBEt0lxudw==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=16" + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" } }, - "node_modules/@treeseed/sdk/node_modules/@esbuild/aix-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", - "cpu": [ - "ppc64" - ], + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "node_modules/@treeseed/sdk/node_modules/@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", - "cpu": [ - "arm" - ], + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@babel/types": "^7.28.2" } }, - "node_modules/@treeseed/sdk/node_modules/@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", - "cpu": [ - "arm64" - ], + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" } }, - "node_modules/@treeseed/sdk/node_modules/@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", - "cpu": [ - "x64" - ], + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } + "peer": true }, - "node_modules/@treeseed/sdk/node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", - "cpu": [ - "arm64" - ], + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } + "peer": true }, - "node_modules/@treeseed/sdk/node_modules/@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", - "cpu": [ - "x64" - ], + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } + "peer": true }, - "node_modules/@treeseed/sdk/node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", - "cpu": [ - "arm64" - ], + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" + "peer": true, + "dependencies": { + "@types/d3-color": "*" } }, - "node_modules/@treeseed/sdk/node_modules/@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", - "cpu": [ - "x64" - ], + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } + "peer": true }, - "node_modules/@treeseed/sdk/node_modules/@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", - "cpu": [ - "arm" - ], + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "peer": true, + "dependencies": { + "@types/d3-time": "*" } }, - "node_modules/@treeseed/sdk/node_modules/@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", - "cpu": [ - "arm64" - ], + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "peer": true, + "dependencies": { + "@types/d3-path": "*" } }, - "node_modules/@treeseed/sdk/node_modules/@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", - "cpu": [ - "ia32" - ], + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } + "peer": true }, - "node_modules/@treeseed/sdk/node_modules/@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", - "cpu": [ - "loong64" - ], + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } + "peer": true }, - "node_modules/@treeseed/sdk/node_modules/@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", - "cpu": [ - "mips64el" - ], + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@types/ms": "*" } }, - "node_modules/@treeseed/sdk/node_modules/@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", - "cpu": [ - "ppc64" - ], + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@types/estree": "*" } }, - "node_modules/@treeseed/sdk/node_modules/@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", - "cpu": [ - "riscv64" - ], + "node_modules/@types/hast": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@types/unist": "*" } }, - "node_modules/@treeseed/sdk/node_modules/@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "license": "MIT" }, - "node_modules/@treeseed/sdk/node_modules/@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } + "node_modules/@types/katex": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.8.tgz", + "integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==", + "license": "MIT" }, - "node_modules/@treeseed/sdk/node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", - "cpu": [ - "arm64" - ], + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@types/unist": "*" } }, - "node_modules/@treeseed/sdk/node_modules/@esbuild/netbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } + "node_modules/@types/mdx": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.14.tgz", + "integrity": "sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==", + "license": "MIT" }, - "node_modules/@treeseed/sdk/node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", - "cpu": [ - "arm64" - ], + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/nlcst": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-2.0.3.tgz", + "integrity": "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==", "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@types/unist": "*" } }, - "node_modules/@treeseed/sdk/node_modules/@esbuild/openbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", - "cpu": [ - "x64" - ], + "node_modules/@types/node": { + "version": "26.2.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz", + "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==", "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" + "dependencies": { + "undici-types": "~8.3.0" } }, - "node_modules/@treeseed/sdk/node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", - "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", - "cpu": [ - "arm64" - ], + "node_modules/@types/nodemailer": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.12.tgz", + "integrity": "sha512-80vKwiIsVSyFA1rRovH59jNPLBOuc6dRZIHEu40gXTkBkZnQv8vog1xSGEb9j5q/tdMAs5ivvDR2pLTU0hGHXA==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@types/node": "*" } }, - "node_modules/@treeseed/sdk/node_modules/@esbuild/sunos-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", - "cpu": [ - "x64" - ], + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" + "peer": true, + "dependencies": { + "csstype": "^3.2.2" } }, - "node_modules/@treeseed/sdk/node_modules/@esbuild/win32-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", - "cpu": [ - "arm64" - ], + "node_modules/@types/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "peer": true, + "peerDependencies": { + "@types/react": "^19.2.0" } }, - "node_modules/@treeseed/sdk/node_modules/@esbuild/win32-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", - "cpu": [ - "ia32" - ], + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@types/node": "*" } }, - "node_modules/@treeseed/sdk/node_modules/@esbuild/win32-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", - "cpu": [ - "x64" - ], + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } + "peer": true + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", + "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==", + "license": "ISC" }, - "node_modules/@treeseed/sdk/node_modules/miniflare": { - "version": "4.20260426.0", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20260426.0.tgz", - "integrity": "sha512-KM+v76d04qT+NsPfVKVQEgnnuLNE3uzCCl2QKMTJ5OXor5JbBm1vpkQwQ+l7o5ELCrZ74RnyKhJKLiJyUA39Tw==", + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", "license": "MIT", "dependencies": { - "@cspotcode/source-map-support": "0.8.1", - "sharp": "^0.34.5", - "undici": "7.24.8", - "workerd": "1.20260426.1", - "ws": "8.18.0", - "youch": "4.1.0-beta.10" - }, - "bin": { - "miniflare": "bootstrap.js" + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" }, "engines": { - "node": ">=18.0.0" + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, - "node_modules/@treeseed/sdk/node_modules/path-to-regexp": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", - "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", - "license": "MIT" - }, - "node_modules/@treeseed/sdk/node_modules/undici": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.8.tgz", - "integrity": "sha512-6KQ/+QxK49Z/p3HO6E5ZCZWNnCasyZLa5ExaVYyvPxUwKtbCPMKELJOqh7EqOle0t9cH/7d2TaaTRRa6Nhs4YQ==", + "node_modules/@vitejs/plugin-react/node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", "license": "MIT", "engines": { - "node": ">=20.18.1" + "node": ">=0.10.0" } }, - "node_modules/@treeseed/sdk/node_modules/workerd": { - "version": "1.20260426.1", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260426.1.tgz", - "integrity": "sha512-ELvGgN8c9oo+E6EPyecxk1TEf6/eAK4TxxQTW5mQ87C7jbjCzhMbg0P2ije49UBHV0dkBYPJcJvcklUltipl2A==", - "hasInstallScript": true, - "license": "Apache-2.0", - "bin": { - "workerd": "bin/workerd" - }, - "engines": { - "node": ">=16" + "node_modules/@vitest/expect": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" }, - "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20260426.1", - "@cloudflare/workerd-darwin-arm64": "1.20260426.1", - "@cloudflare/workerd-linux-64": "1.20260426.1", - "@cloudflare/workerd-linux-arm64": "1.20260426.1", - "@cloudflare/workerd-windows-64": "1.20260426.1" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@treeseed/sdk/node_modules/wrangler": { - "version": "4.86.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.86.0.tgz", - "integrity": "sha512-9aa/gbF/HiUeeUEwyQpW5LDPBEzyt7iaE6xHwm0vk2Ly8A6J+jh03pzchqVnCCWR832mNyA28MD8oAYt0Kfvlw==", - "license": "MIT OR Apache-2.0", + "node_modules/@vitest/mocker": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", + "license": "MIT", "dependencies": { - "@cloudflare/kv-asset-handler": "0.4.2", - "@cloudflare/unenv-preset": "2.16.1", - "blake3-wasm": "2.1.5", - "esbuild": "0.27.3", - "miniflare": "4.20260426.0", - "path-to-regexp": "6.3.0", - "unenv": "2.0.0-rc.24", - "workerd": "1.20260426.1" - }, - "bin": { - "wrangler": "bin/wrangler.js", - "wrangler2": "bin/wrangler.js" - }, - "engines": { - "node": ">=20.3.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - }, - "peerDependencies": { - "@cloudflare/workers-types": "^4.20260426.1" - }, - "peerDependenciesMeta": { - "@cloudflare/workers-types": { - "optional": true - } - } - }, - "node_modules/@treeseed/sdk/node_modules/wrangler/node_modules/esbuild": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" + "@vitest/spy": "4.1.10", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" - } - }, - "node_modules/@treeseed/sdk/node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" + "funding": { + "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { - "bufferutil": { + "msw": { "optional": true }, - "utf-8-validate": { + "vite": { "optional": true } } }, - "node_modules/@treeseed/ui": { - "version": "0.12.17", - "resolved": "https://registry.npmjs.org/@treeseed/ui/-/ui-0.12.17.tgz", - "integrity": "sha512-dz+iNHu969vMmh7Cc+0z8O26inALrFGNGfZbc7hNeJ47fIIv2rOgO1nrRKJoDlqZz8GMnmN5d7N+vjab7GPBUw==", - "license": "Apache-2.0", + "node_modules/@vitest/pretty-format": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", + "license": "MIT", "dependencies": { - "@codemirror/lang-markdown": "^6.5.0", - "@codemirror/language": "^6.12.3", - "@codemirror/view": "^6.43.0", - "@lezer/highlight": "^1.2.3", - "@mdxeditor/editor": "^4.0.2", - "codemirror": "^6.0.2", - "rehype-stringify": "^10.0.1", - "remark-gfm": "^4.0.1", - "remark-parse": "^11.0.0", - "remark-rehype": "^11.1.2", - "unified": "^11.0.5", - "yaml": "^2.8.1" - }, - "engines": { - "node": ">=22" + "tinyrainbow": "^3.1.0" }, - "peerDependencies": { - "astro": "^5.0.0 || ^6.0.0", - "react": "^18.3.0 || ^19.0.0", - "react-dom": "^18.3.0 || ^19.0.0", - "recharts": "^3.0.0" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", - "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "node_modules/@vitest/runner": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", "license": "MIT", - "optional": true, "dependencies": { - "tslib": "^2.4.0" + "@vitest/utils": "4.1.10", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@types/aws-lambda": { - "version": "8.10.162", - "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.162.tgz", - "integrity": "sha512-Fn658grtLOci1oxi1391vvDWJRKNGWRSqfxRkmN/Iy3c0tQH1USMKEXcPYHLvope+ZgTFocx9FRQJx1muBL6qw==", - "license": "MIT" - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "node_modules/@vitest/snapshot": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" + "@vitest/pretty-format": "4.1.10", + "@vitest/utils": "4.1.10", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "node_modules/@vitest/spy": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "node_modules/@vitest/utils": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" + "@vitest/pretty-format": "4.1.10", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "node_modules/@volar/kit": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/kit/-/kit-2.4.28.tgz", + "integrity": "sha512-cKX4vK9dtZvDRaAzeoUdaAJEew6IdxHNCRrdp5Kvcl6zZOqb6jTOfk3kXkIkG3T7oTFXguEMt5+9ptyqYR84Pg==", "license": "MIT", "dependencies": { - "@babel/types": "^7.28.2" + "@volar/language-service": "2.4.28", + "@volar/typescript": "2.4.28", + "typesafe-path": "^0.2.2", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "typescript": "*" } }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "node_modules/@volar/language-core": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", + "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", "license": "MIT", "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" + "@volar/source-map": "2.4.28" } }, - "node_modules/@types/d3-array": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", - "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", - "license": "MIT", - "peer": true - }, - "node_modules/@types/d3-color": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", - "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", - "license": "MIT", - "peer": true - }, - "node_modules/@types/d3-ease": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", - "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "node_modules/@volar/language-server": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/language-server/-/language-server-2.4.28.tgz", + "integrity": "sha512-NqcLnE5gERKuS4PUFwlhMxf6vqYo7hXtbMFbViXcbVkbZ905AIVWhnSo0ZNBC2V127H1/2zP7RvVOVnyITFfBw==", "license": "MIT", - "peer": true + "dependencies": { + "@volar/language-core": "2.4.28", + "@volar/language-service": "2.4.28", + "@volar/typescript": "2.4.28", + "path-browserify": "^1.0.1", + "request-light": "^0.7.0", + "vscode-languageserver": "^9.0.1", + "vscode-languageserver-protocol": "^3.17.5", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + } }, - "node_modules/@types/d3-interpolate": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", - "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "node_modules/@volar/language-service": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/language-service/-/language-service-2.4.28.tgz", + "integrity": "sha512-Rh/wYCZJrI5vCwMk9xyw/Z+MsWxlJY1rmMZPsxUoJKfzIRjS/NF1NmnuEcrMbEVGja00aVpCsInJfixQTMdvLw==", "license": "MIT", - "peer": true, "dependencies": { - "@types/d3-color": "*" + "@volar/language-core": "2.4.28", + "vscode-languageserver-protocol": "^3.17.5", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" } }, - "node_modules/@types/d3-path": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", - "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", - "license": "MIT", - "peer": true + "node_modules/@volar/source-map": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz", + "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", + "license": "MIT" }, - "node_modules/@types/d3-scale": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", - "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "node_modules/@volar/typescript": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz", + "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==", "license": "MIT", - "peer": true, "dependencies": { - "@types/d3-time": "*" + "@volar/language-core": "2.4.28", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" } }, - "node_modules/@types/d3-shape": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", - "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "node_modules/@vscode/emmet-helper": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@vscode/emmet-helper/-/emmet-helper-2.11.0.tgz", + "integrity": "sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==", "license": "MIT", - "peer": true, "dependencies": { - "@types/d3-path": "*" + "emmet": "^2.4.3", + "jsonc-parser": "^2.3.0", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "^3.15.1", + "vscode-uri": "^3.0.8" } }, - "node_modules/@types/d3-time": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", - "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", - "license": "MIT", - "peer": true + "node_modules/@vscode/l10n": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.18.tgz", + "integrity": "sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==", + "license": "MIT" }, - "node_modules/@types/d3-timer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", - "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", "license": "MIT", - "peer": true + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } }, - "node_modules/@types/debug": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", - "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "license": "MIT", - "dependencies": { - "@types/ms": "*" + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "license": "MIT" - }, - "node_modules/@types/dom-mediacapture-transform": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/@types/dom-mediacapture-transform/-/dom-mediacapture-transform-0.1.12.tgz", - "integrity": "sha512-d7/QsLRwF864A5mgIM/YrfiglHoYn7zgCcAoJgW404r+2DwnNr7EBbLnCWpmOMgH8y0te73L1AV6H1bmauaWFw==", + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { - "@types/dom-webcodecs": "*" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@types/dom-webcodecs": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@types/dom-webcodecs/-/dom-webcodecs-0.1.13.tgz", - "integrity": "sha512-O5hkiFIcjjszPIYyUSyvScyvrBoV3NOEEZx/pMlsu44TKzWNkLVBBxnxJz42in5n3QIolYOcBYFCPZZ0h8SkwQ==", - "license": "MIT" - }, - "node_modules/@types/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", "license": "MIT", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } } }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "node_modules/ajv-i18n": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ajv-i18n/-/ajv-i18n-4.2.0.tgz", + "integrity": "sha512-v/ei2UkCEeuKNXh8RToiFsUclmU+G57LO1Oo22OagNMENIw+Yb8eMwvHu7Vn9fmkjJyv6XclhJ8TbuigSglPkg==", "license": "MIT", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" + "peerDependencies": { + "ajv": "^8.0.0-beta.0" } }, - "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "license": "MIT" - }, - "node_modules/@types/estree-jsx": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", - "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", - "license": "MIT", + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "license": "ISC", "dependencies": { - "@types/estree": "*" + "string-width": "^4.1.0" } }, - "node_modules/@types/hast": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", - "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", + "node_modules/ansi-align/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "license": "MIT", - "dependencies": { - "@types/unist": "*" + "engines": { + "node": ">=8" } }, - "node_modules/@types/js-yaml": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", - "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "license": "MIT" - }, - "node_modules/@types/katex": { - "version": "0.16.8", - "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.8.tgz", - "integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==", + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, - "node_modules/@types/mdast": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", - "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "node_modules/ansi-align/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "license": "MIT", - "dependencies": { - "@types/unist": "*" + "engines": { + "node": ">=8" } }, - "node_modules/@types/mdx": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.14.tgz", - "integrity": "sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==", - "license": "MIT" - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "license": "MIT" - }, - "node_modules/@types/nlcst": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-2.0.3.tgz", - "integrity": "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==", + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { - "@types/unist": "*" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@types/node": { - "version": "26.1.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz", - "integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==", + "node_modules/ansi-align/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", "dependencies": { - "undici-types": "~8.3.0" + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@types/nodemailer": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.12.tgz", - "integrity": "sha512-80vKwiIsVSyFA1rRovH59jNPLBOuc6dRZIHEu40gXTkBkZnQv8vog1xSGEb9j5q/tdMAs5ivvDR2pLTU0hGHXA==", - "dev": true, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "license": "MIT", - "dependencies": { - "@types/node": "*" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@types/parse-path": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@types/parse-path/-/parse-path-7.0.3.tgz", - "integrity": "sha512-LriObC2+KYZD3FzCrgWGv/qufdUy4eXrxcLgQMfYXgPbLIecKIsVBaQgUPmxSSLcjmYbDTQbMgr6qr6l/eb7Bg==", - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "19.2.17", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", - "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "license": "MIT", - "peer": true, - "dependencies": { - "csstype": "^3.2.2" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@types/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "license": "MIT", - "peer": true, - "peerDependencies": { - "@types/react": "^19.2.0" + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/@types/sax": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", - "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "license": "MIT", - "dependencies": { - "@types/node": "*" + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/@types/unist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", "license": "MIT" }, - "node_modules/@types/use-sync-external-store": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", - "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", - "license": "MIT", - "peer": true + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", "license": "MIT", - "optional": true, "dependencies": { - "@types/node": "*" + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", - "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==", - "license": "ISC" + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } }, - "node_modules/@vitejs/plugin-react": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", - "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "node_modules/array-iterate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz", + "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==", "license": "MIT", - "dependencies": { - "@babel/core": "^7.28.0", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.27", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/@vitejs/plugin-react/node_modules/react-refresh": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=12" } }, - "node_modules/@vitest/expect": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", - "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", + "node_modules/astring": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.10", - "@vitest/utils": "4.1.10", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "bin": { + "astring": "bin/astring" } }, - "node_modules/@vitest/mocker": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", - "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", + "node_modules/astro": { + "version": "5.18.2", + "resolved": "https://registry.npmjs.org/astro/-/astro-5.18.2.tgz", + "integrity": "sha512-TnFwLnAXty5MXKPDGuKXqK4AMBXG+FH6RUdK7Oyc3gyfNoFIthT+4eRbzOK43bdRlLaZuxgciDSjgtggZ3OtGQ==", "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.10", + "@astrojs/compiler": "^2.13.0", + "@astrojs/internal-helpers": "0.7.6", + "@astrojs/markdown-remark": "6.3.11", + "@astrojs/telemetry": "3.3.0", + "@capsizecss/unpack": "^4.0.0", + "@oslojs/encoding": "^1.1.0", + "@rollup/pluginutils": "^5.3.0", + "acorn": "^8.15.0", + "aria-query": "^5.3.2", + "axobject-query": "^4.1.0", + "boxen": "8.0.1", + "ci-info": "^4.3.1", + "clsx": "^2.1.1", + "common-ancestor-path": "^1.0.1", + "cookie": "^1.1.1", + "cssesc": "^3.0.0", + "debug": "^4.4.3", + "deterministic-object-hash": "^2.0.2", + "devalue": "^5.6.2", + "diff": "^8.0.3", + "dlv": "^1.1.3", + "dset": "^3.1.4", + "es-module-lexer": "^1.7.0", + "esbuild": "^0.27.3", "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" + "flattie": "^1.1.1", + "fontace": "~0.4.0", + "github-slugger": "^2.0.0", + "html-escaper": "3.0.3", + "http-cache-semantics": "^4.2.0", + "import-meta-resolve": "^4.2.0", + "js-yaml": "^4.1.1", + "magic-string": "^0.30.21", + "magicast": "^0.5.1", + "mrmime": "^2.0.1", + "neotraverse": "^0.6.18", + "p-limit": "^6.2.0", + "p-queue": "^8.1.1", + "package-manager-detector": "^1.6.0", + "piccolore": "^0.1.3", + "picomatch": "^4.0.3", + "prompts": "^2.4.2", + "rehype": "^13.0.2", + "semver": "^7.7.3", + "shiki": "^3.21.0", + "smol-toml": "^1.6.0", + "svgo": "^4.0.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tsconfck": "^3.1.6", + "ultrahtml": "^1.6.0", + "unifont": "~0.7.3", + "unist-util-visit": "^5.0.0", + "unstorage": "^1.17.4", + "vfile": "^6.0.3", + "vite": "^6.4.1", + "vitefu": "^1.1.1", + "xxhash-wasm": "^1.1.0", + "yargs-parser": "^21.1.1", + "yocto-spinner": "^0.2.3", + "zod": "^3.25.76", + "zod-to-json-schema": "^3.25.1", + "zod-to-ts": "^1.2.0" }, - "funding": { - "url": "https://opencollective.com/vitest" + "bin": { + "astro": "astro.js" }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0" }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/astrodotbuild" + }, + "optionalDependencies": { + "sharp": "^0.34.0" } }, - "node_modules/@vitest/pretty-format": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", - "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", + "node_modules/astro-expressive-code": { + "version": "0.41.7", + "resolved": "https://registry.npmjs.org/astro-expressive-code/-/astro-expressive-code-0.41.7.tgz", + "integrity": "sha512-hUpogGc6DdAd+I7pPXsctyYPRBJDK7Q7d06s4cyP0Vz3OcbziP3FNzN0jZci1BpCvLn9675DvS7B9ctKKX64JQ==", "license": "MIT", "dependencies": { - "tinyrainbow": "^3.1.0" + "rehype-expressive-code": "^0.41.7" }, - "funding": { - "url": "https://opencollective.com/vitest" + "peerDependencies": { + "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0 || ^6.0.0-beta" } }, - "node_modules/@vitest/runner": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", - "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", + "node_modules/astro/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.10", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@vitest/snapshot": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", - "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", + "node_modules/astro/node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.10", - "@vitest/utils": "4.1.10", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@vitest/spy": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", - "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", + "node_modules/astro/node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@vitest/utils": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", - "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.10", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@volar/kit": { - "version": "2.4.28", - "resolved": "https://registry.npmjs.org/@volar/kit/-/kit-2.4.28.tgz", - "integrity": "sha512-cKX4vK9dtZvDRaAzeoUdaAJEew6IdxHNCRrdp5Kvcl6zZOqb6jTOfk3kXkIkG3T7oTFXguEMt5+9ptyqYR84Pg==", + "node_modules/astro/node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@volar/language-service": "2.4.28", - "@volar/typescript": "2.4.28", - "typesafe-path": "^0.2.2", - "vscode-languageserver-textdocument": "^1.0.11", - "vscode-uri": "^3.0.8" - }, - "peerDependencies": { - "typescript": "*" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@volar/language-core": { - "version": "2.4.28", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", - "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", + "node_modules/astro/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@volar/source-map": "2.4.28" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@volar/language-server": { - "version": "2.4.28", - "resolved": "https://registry.npmjs.org/@volar/language-server/-/language-server-2.4.28.tgz", - "integrity": "sha512-NqcLnE5gERKuS4PUFwlhMxf6vqYo7hXtbMFbViXcbVkbZ905AIVWhnSo0ZNBC2V127H1/2zP7RvVOVnyITFfBw==", + "node_modules/astro/node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@volar/language-core": "2.4.28", - "@volar/language-service": "2.4.28", - "@volar/typescript": "2.4.28", - "path-browserify": "^1.0.1", - "request-light": "^0.7.0", - "vscode-languageserver": "^9.0.1", - "vscode-languageserver-protocol": "^3.17.5", - "vscode-languageserver-textdocument": "^1.0.11", - "vscode-uri": "^3.0.8" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@volar/language-service": { - "version": "2.4.28", - "resolved": "https://registry.npmjs.org/@volar/language-service/-/language-service-2.4.28.tgz", - "integrity": "sha512-Rh/wYCZJrI5vCwMk9xyw/Z+MsWxlJY1rmMZPsxUoJKfzIRjS/NF1NmnuEcrMbEVGja00aVpCsInJfixQTMdvLw==", + "node_modules/astro/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@volar/language-core": "2.4.28", - "vscode-languageserver-protocol": "^3.17.5", - "vscode-languageserver-textdocument": "^1.0.11", - "vscode-uri": "^3.0.8" + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@volar/source-map": { - "version": "2.4.28", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz", - "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", - "license": "MIT" - }, - "node_modules/@volar/typescript": { - "version": "2.4.28", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz", - "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==", + "node_modules/astro/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@volar/language-core": "2.4.28", - "path-browserify": "^1.0.1", - "vscode-uri": "^3.0.8" + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@vscode/emmet-helper": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@vscode/emmet-helper/-/emmet-helper-2.11.0.tgz", - "integrity": "sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==", + "node_modules/astro/node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], "license": "MIT", - "dependencies": { - "emmet": "^2.4.3", - "jsonc-parser": "^2.3.0", - "vscode-languageserver-textdocument": "^1.0.1", - "vscode-languageserver-types": "^3.15.1", - "vscode-uri": "^3.0.8" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@vscode/l10n": { - "version": "0.0.18", - "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.18.tgz", - "integrity": "sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "node_modules/astro/node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "node_modules/astro/node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], "license": "MIT", - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", - "@xtuc/long": "4.2.2" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", - "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "node_modules/astro/node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/wasm-gen": "1.14.1" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", - "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "node_modules/astro/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], "license": "MIT", - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", - "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "license": "Apache-2.0", - "dependencies": { - "@xtuc/long": "4.2.2" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", - "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "node_modules/astro/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/helper-wasm-section": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-opt": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1", - "@webassemblyjs/wast-printer": "1.14.1" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "node_modules/astro/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", - "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", - "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-api-error": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", - "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "license": "BSD-3-Clause" - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "license": "Apache-2.0" - }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "node_modules/astro/node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.6" + "node": ">=18" } }, - "node_modules/acorn": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", - "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "node_modules/astro/node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.4.0" + "node": ">=18" } }, - "node_modules/acorn-import-phases": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", - "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "node_modules/astro/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=10.13.0" - }, - "peerDependencies": { - "acorn": "^8.14.0" + "node": ">=18" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "node_modules/astro/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "node_modules/astro/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/ajv-draft-04": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", - "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "node_modules/astro/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], "license": "MIT", - "peerDependencies": { - "ajv": "^8.5.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "node_modules/astro/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, - "node_modules/ajv-i18n": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/ajv-i18n/-/ajv-i18n-4.2.0.tgz", - "integrity": "sha512-v/ei2UkCEeuKNXh8RToiFsUclmU+G57LO1Oo22OagNMENIw+Yb8eMwvHu7Vn9fmkjJyv6XclhJ8TbuigSglPkg==", + "node_modules/astro/node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], "license": "MIT", - "peerDependencies": { - "ajv": "^8.0.0-beta.0" + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" } }, - "node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "node_modules/astro/node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "license": "ISC", - "dependencies": { - "string-width": "^4.1.0" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, - "node_modules/ansi-align/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/astro/node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/ansi-align/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/ansi-align/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/astro/node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/ansi-align/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/astro/node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "bin": { + "esbuild": "bin/esbuild" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" } }, - "node_modules/ansi-align/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" + "node_modules/astro/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/ansi-escapes": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", - "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", "license": "MIT", - "dependencies": { - "environment": "^1.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/base-64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", + "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.12", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.12.tgz", + "integrity": "sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" }, "engines": { - "node": ">=18" + "node": ">=6.0.0" + } + }, + "node_modules/bcp-47": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.1.tgz", + "integrity": "sha512-KLw+H/gd2p4zly1X7Yh/qziuyae5/w/QFnvTng9eZL5fvszL7Whl3MBoWF8yxL7ksUjBfOD+OxkytiqbBpG+Fw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "node_modules/bcp-47-match": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", + "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", "license": "MIT", - "engines": { - "node": ">=12" - }, "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "node_modules/blake3-wasm": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", + "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", + "license": "MIT" + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/boxen": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", + "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==", "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^8.0.0", + "chalk": "^5.3.0", + "cli-boxes": "^3.0.0", + "string-width": "^7.2.0", + "type-fest": "^4.21.0", + "widest-line": "^5.0.0", + "wrap-ansi": "^9.0.0" + }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "license": "ISC", + "node_modules/browserslist": { + "version": "4.28.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", + "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "baseline-browser-mapping": "^2.10.44", + "caniuse-lite": "^1.0.30001806", + "electron-to-chromium": "^1.5.393", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" }, "engines": { - "node": ">= 8" + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "node_modules/camelcase": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", + "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=16" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "license": "MIT" + "node_modules/caniuse-lite": { + "version": "1.0.30001809", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", + "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" }, - "node_modules/argparse": { + "node_modules/ccount": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } }, - "node_modules/aria-hidden": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", - "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", - "license": "Apache-2.0", + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", "engines": { - "node": ">= 0.4" + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/array-iterate": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz", - "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==", + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "license": "MIT", - "engines": { - "node": ">=12" + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/astring": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", - "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", "license": "MIT", - "bin": { - "astring": "bin/astring" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/astro": { - "version": "5.18.2", - "resolved": "https://registry.npmjs.org/astro/-/astro-5.18.2.tgz", - "integrity": "sha512-TnFwLnAXty5MXKPDGuKXqK4AMBXG+FH6RUdK7Oyc3gyfNoFIthT+4eRbzOK43bdRlLaZuxgciDSjgtggZ3OtGQ==", + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", "license": "MIT", - "dependencies": { - "@astrojs/compiler": "^2.13.0", - "@astrojs/internal-helpers": "0.7.6", - "@astrojs/markdown-remark": "6.3.11", - "@astrojs/telemetry": "3.3.0", - "@capsizecss/unpack": "^4.0.0", - "@oslojs/encoding": "^1.1.0", - "@rollup/pluginutils": "^5.3.0", - "acorn": "^8.15.0", - "aria-query": "^5.3.2", - "axobject-query": "^4.1.0", - "boxen": "8.0.1", - "ci-info": "^4.3.1", - "clsx": "^2.1.1", - "common-ancestor-path": "^1.0.1", - "cookie": "^1.1.1", - "cssesc": "^3.0.0", - "debug": "^4.4.3", - "deterministic-object-hash": "^2.0.2", - "devalue": "^5.6.2", - "diff": "^8.0.3", - "dlv": "^1.1.3", - "dset": "^3.1.4", - "es-module-lexer": "^1.7.0", - "esbuild": "^0.27.3", - "estree-walker": "^3.0.3", - "flattie": "^1.1.1", - "fontace": "~0.4.0", - "github-slugger": "^2.0.0", - "html-escaper": "3.0.3", - "http-cache-semantics": "^4.2.0", - "import-meta-resolve": "^4.2.0", - "js-yaml": "^4.1.1", - "magic-string": "^0.30.21", - "magicast": "^0.5.1", - "mrmime": "^2.0.1", - "neotraverse": "^0.6.18", - "p-limit": "^6.2.0", - "p-queue": "^8.1.1", - "package-manager-detector": "^1.6.0", - "piccolore": "^0.1.3", - "picomatch": "^4.0.3", - "prompts": "^2.4.2", - "rehype": "^13.0.2", - "semver": "^7.7.3", - "shiki": "^3.21.0", - "smol-toml": "^1.6.0", - "svgo": "^4.0.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tsconfck": "^3.1.6", - "ultrahtml": "^1.6.0", - "unifont": "~0.7.3", - "unist-util-visit": "^5.0.0", - "unstorage": "^1.17.4", - "vfile": "^6.0.3", - "vite": "^6.4.1", - "vitefu": "^1.1.1", - "xxhash-wasm": "^1.1.0", - "yargs-parser": "^21.1.1", - "yocto-spinner": "^0.2.3", - "zod": "^3.25.76", - "zod-to-json-schema": "^3.25.1", - "zod-to-ts": "^1.2.0" - }, - "bin": { - "astro": "astro.js" - }, - "engines": { - "node": "18.20.8 || ^20.3.0 || >=22.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/astrodotbuild" - }, - "optionalDependencies": { - "sharp": "^0.34.0" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/astro-expressive-code": { - "version": "0.41.7", - "resolved": "https://registry.npmjs.org/astro-expressive-code/-/astro-expressive-code-0.41.7.tgz", - "integrity": "sha512-hUpogGc6DdAd+I7pPXsctyYPRBJDK7Q7d06s4cyP0Vz3OcbziP3FNzN0jZci1BpCvLn9675DvS7B9ctKKX64JQ==", + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "license": "MIT", "dependencies": { - "rehype-expressive-code": "^0.41.7" + "readdirp": "^4.0.1" }, - "peerDependencies": { - "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0 || ^6.0.0-beta" - } - }, - "node_modules/astro/node_modules/@esbuild/aix-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", - "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], "engines": { - "node": ">=18" + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/astro/node_modules/@esbuild/android-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", - "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", - "cpu": [ - "arm" + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } ], "license": "MIT", - "optional": true, - "os": [ - "android" - ], "engines": { - "node": ">=18" + "node": ">=8" } }, - "node_modules/astro/node_modules/@esbuild/android-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", - "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", - "cpu": [ - "arm64" - ], + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], "engines": { - "node": ">=18" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/astro/node_modules/@esbuild/android-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", - "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, "engines": { - "node": ">=18" + "node": ">=20" } }, - "node_modules/astro/node_modules/@esbuild/darwin-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", - "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", - "cpu": [ - "arm64" - ], + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": ">=18" + "node": ">=6" } }, - "node_modules/astro/node_modules/@esbuild/darwin-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", - "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", - "cpu": [ - "x64" - ], + "node_modules/cm6-theme-basic-light": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/cm6-theme-basic-light/-/cm6-theme-basic-light-0.2.0.tgz", + "integrity": "sha512-1prg2gv44sYfpHscP26uLT/ePrh0mlmVwMSoSd3zYKQ92Ab3jPRLzyCnpyOCQLJbK+YdNs4HvMRqMNYdy4pMhA==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" + "peerDependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/highlight": "^1.0.0" } }, - "node_modules/astro/node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", - "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", - "cpu": [ - "arm64" - ], + "node_modules/codemirror": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", + "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" } }, - "node_modules/astro/node_modules/@esbuild/freebsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", - "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", - "cpu": [ - "x64" - ], + "node_modules/collapse-white-space": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", + "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/astro/node_modules/@esbuild/linux-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", - "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", - "cpu": [ - "arm" - ], + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/astro/node_modules/@esbuild/linux-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", - "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", - "cpu": [ - "arm64" - ], + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=18" + "node": ">= 12" } }, - "node_modules/astro/node_modules/@esbuild/linux-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", - "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", - "cpu": [ - "ia32" - ], + "node_modules/common-ancestor-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", + "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", + "license": "ISC" + }, + "node_modules/compute-scroll-into-view": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz", + "integrity": "sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g==", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/astro/node_modules/@esbuild/linux-loong64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", - "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", - "cpu": [ - "loong64" - ], + "node_modules/cookie-es": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.3.tgz", + "integrity": "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==", + "license": "MIT" + }, + "node_modules/crelt": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.7.tgz", + "integrity": "sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==", + "license": "MIT" + }, + "node_modules/crossws": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", + "integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "dependencies": { + "uncrypto": "^0.1.3" } }, - "node_modules/astro/node_modules/@esbuild/linux-mips64el": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", - "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" } }, - "node_modules/astro/node_modules/@esbuild/linux-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", - "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", - "cpu": [ - "ppc64" + "node_modules/css-selector-parser": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.3.0.tgz", + "integrity": "sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } ], + "license": "MIT" + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, "engines": { - "node": ">=18" + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, - "node_modules/astro/node_modules/@esbuild/linux-riscv64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", - "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", "engines": { - "node": ">=18" + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" } }, - "node_modules/astro/node_modules/@esbuild/linux-s390x": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", - "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", - "cpu": [ - "s390x" - ], + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "bin": { + "cssesc": "bin/cssesc" + }, "engines": { - "node": ">=18" + "node": ">=4" } }, - "node_modules/astro/node_modules/@esbuild/linux-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", - "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, "engines": { - "node": ">=18" + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" } }, - "node_modules/astro/node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", - "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", - "cpu": [ - "arm64" - ], + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, "engines": { - "node": ">=18" + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" } }, - "node_modules/astro/node_modules/@esbuild/netbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", - "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", - "cpu": [ - "x64" - ], + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "license": "CC0-1.0" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], + "peer": true + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "peer": true, + "dependencies": { + "internmap": "1 - 2" + }, "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/astro/node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", - "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "peer": true, "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/astro/node_modules/@esbuild/openbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", - "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "peer": true, "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/astro/node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", - "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "peer": true, "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/astro/node_modules/@esbuild/sunos-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", - "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "peer": true, + "dependencies": { + "d3-color": "1 - 3" + }, "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/astro/node_modules/@esbuild/win32-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", - "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "peer": true, "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/astro/node_modules/@esbuild/win32-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", - "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "peer": true, + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/astro/node_modules/@esbuild/win32-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", - "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "peer": true, + "dependencies": { + "d3-path": "^3.1.0" + }, "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/astro/node_modules/esbuild": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", - "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "peer": true, + "dependencies": { + "d3-array": "2 - 3" }, "engines": { - "node": ">=18" + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "peer": true, + "dependencies": { + "d3-time": "1 - 3" }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.7", - "@esbuild/android-arm": "0.27.7", - "@esbuild/android-arm64": "0.27.7", - "@esbuild/android-x64": "0.27.7", - "@esbuild/darwin-arm64": "0.27.7", - "@esbuild/darwin-x64": "0.27.7", - "@esbuild/freebsd-arm64": "0.27.7", - "@esbuild/freebsd-x64": "0.27.7", - "@esbuild/linux-arm": "0.27.7", - "@esbuild/linux-arm64": "0.27.7", - "@esbuild/linux-ia32": "0.27.7", - "@esbuild/linux-loong64": "0.27.7", - "@esbuild/linux-mips64el": "0.27.7", - "@esbuild/linux-ppc64": "0.27.7", - "@esbuild/linux-riscv64": "0.27.7", - "@esbuild/linux-s390x": "0.27.7", - "@esbuild/linux-x64": "0.27.7", - "@esbuild/netbsd-arm64": "0.27.7", - "@esbuild/netbsd-x64": "0.27.7", - "@esbuild/openbsd-arm64": "0.27.7", - "@esbuild/openbsd-x64": "0.27.7", - "@esbuild/openharmony-arm64": "0.27.7", - "@esbuild/sunos-x64": "0.27.7", - "@esbuild/win32-arm64": "0.27.7", - "@esbuild/win32-ia32": "0.27.7", - "@esbuild/win32-x64": "0.27.7" + "engines": { + "node": ">=12" } }, - "node_modules/astro/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, + "peer": true, "engines": { - "node": ">=10" + "node": ">=12" } }, - "node_modules/auto-bind": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", - "integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==", + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=6.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/axobject-query": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT", + "peer": true }, - "node_modules/bail": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", - "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "license": "MIT" + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "license": "MIT", "engines": { - "node": "18 || 20 || >=22" + "node": ">= 0.8" } }, - "node_modules/base-64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", - "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", "license": "MIT" }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.43", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", - "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, "engines": { - "node": ">=6.0.0" + "node": ">=8" } }, - "node_modules/bcp-47": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.1.tgz", - "integrity": "sha512-KLw+H/gd2p4zly1X7Yh/qziuyae5/w/QFnvTng9eZL5fvszL7Whl3MBoWF8yxL7ksUjBfOD+OxkytiqbBpG+Fw==", + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/deterministic-object-hash": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/deterministic-object-hash/-/deterministic-object-hash-2.0.2.tgz", + "integrity": "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==", "license": "MIT", "dependencies": { - "is-alphabetical": "^2.0.0", - "is-alphanumerical": "^2.0.0", - "is-decimal": "^2.0.0" + "base-64": "^1.0.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "engines": { + "node": ">=18" } }, - "node_modules/bcp-47-match": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", - "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", + "node_modules/devalue": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.9.0.tgz", + "integrity": "sha512-RWrqdArjvPbsATEhOPUo6Wndc/iWnkWKlhIrdlF3zMMYo/c3CVtoaVAyLtWxz5h8nSlkHzxnzV2uLydPXmtF+A==", + "license": "MIT" + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/before-after-hook": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", - "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", - "license": "Apache-2.0" + "node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } }, - "node_modules/binary-extensions": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-3.1.0.tgz", - "integrity": "sha512-Jvvd9hy1w+xUad8+ckQsWA/V1AoyubOvqn0aygjMOVM4BfIaRav1NFS3LsTSDaV4n4FtcCtQXvzep1E6MboqwQ==", + "node_modules/direction": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz", + "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==", "license": "MIT", - "engines": { - "node": ">=18.20" + "bin": { + "direction": "cli.js" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/blake3-wasm": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", - "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "license": "MIT" }, - "node_modules/body-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", - "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", "license": "MIT", "dependencies": { - "bytes": "^3.1.2", - "content-type": "^2.0.0", - "debug": "^4.4.3", - "http-errors": "^2.0.1", - "iconv-lite": "^0.7.2", - "on-finished": "^2.4.1", - "qs": "^6.15.2", - "raw-body": "^3.0.2", - "type-is": "^2.1.0" + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", "engines": { - "node": ">=18" + "node": ">=0.12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "license": "ISC" - }, - "node_modules/bottleneck": { - "version": "2.19.5", - "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", - "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", - "license": "MIT" - }, - "node_modules/boundary": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/boundary/-/boundary-2.0.0.tgz", - "integrity": "sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA==", + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], "license": "BSD-2-Clause" }, - "node_modules/boxen": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", - "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==", - "license": "MIT", + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^8.0.0", - "chalk": "^5.3.0", - "cli-boxes": "^3.0.0", - "string-width": "^7.2.0", - "type-fest": "^4.21.0", - "widest-line": "^5.0.0", - "wrap-ansi": "^9.0.0" + "domelementtype": "^2.3.0" }, "engines": { - "node": ">=18" + "node": ">= 4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/fb55/domhandler?sponsor=1" } }, - "node_modules/brace-expansion": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", - "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "license": "MIT", + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", "dependencies": { - "fill-range": "^7.1.1" + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" } }, - "node_modules/browserslist": { - "version": "4.28.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", - "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/downshift": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/downshift/-/downshift-7.6.2.tgz", + "integrity": "sha512-iOv+E1Hyt3JDdL9yYcOgW7nZ7GQ2Uz6YbggwXvKUSleetYhU2nXD482Rz6CzvM4lvI1At34BYruKAL4swRGxaA==", "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.42", - "caniuse-lite": "^1.0.30001803", - "electron-to-chromium": "^1.5.389", - "node-releases": "^2.0.51", - "update-browserslist-db": "^1.2.3" - }, - "bin": { - "browserslist": "cli.js" + "@babel/runtime": "^7.14.8", + "compute-scroll-into-view": "^2.0.4", + "prop-types": "^15.7.2", + "react-is": "^17.0.2", + "tslib": "^2.3.0" }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "peerDependencies": { + "react": ">=16.12.0" } }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "node_modules/downshift/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "license": "MIT" + }, + "node_modules/dset": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", "license": "MIT", "engines": { - "node": "*" + "node": ">=4" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } + "node_modules/electron-to-chromium": { + "version": "1.5.402", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.402.tgz", + "integrity": "sha512-/oOpMaPT6Yg+6/1XQhyIPlzgj7Ye9zf+nNM2Uh6OcE2G2oNptWazFa+qB2Pdqqbsc9KnIDzgAntoYN0dbwOXwA==", + "license": "ISC" }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "node_modules/emmet": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/emmet/-/emmet-2.4.11.tgz", + "integrity": "sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==", "license": "MIT", + "workspaces": [ + "./packages/scanner", + "./packages/abbreviation", + "./packages/css-abbreviation", + "./" + ], "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" + "@emmetio/abbreviation": "^2.3.3", + "@emmetio/css-abbreviation": "^2.1.8" } }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" }, - "node_modules/camelcase": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", - "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "license": "MIT", "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001805", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001805.tgz", - "integrity": "sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "node": ">= 0.8" } }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "node_modules/enhanced-resolve": { + "version": "5.24.5", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, "engines": { - "node": ">=18" + "node": ">=10.13.0" } }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "license": "MIT", + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": ">=0.12" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/character-entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "node_modules/error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", "license": "MIT", "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "license": "MIT" }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "node_modules/es-toolkit": { + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.50.0.tgz", + "integrity": "sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w==", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } + "peer": true, + "workspaces": [ + "docs", + "benchmarks", + "tests/types" + ] }, - "node_modules/character-reference-invalid": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", - "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "node_modules/esast-util-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", + "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "node_modules/esast-util-from-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", + "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", "license": "MIT", "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" + "@types/estree-jsx": "^1.0.0", + "acorn": "^8.0.0", + "esast-util-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" }, "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/chrome-trace-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "hasInstallScript": true, "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, "engines": { - "node": ">=6.0" + "node": ">=18" + }, + "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" } }, - "node_modules/ci-info": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", - "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/classnames": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", - "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, - "node_modules/cli-boxes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", - "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "license": "MIT", "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cli-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "node_modules/estree-util-attach-comments": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", + "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", "license": "MIT", "dependencies": { - "restore-cursor": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "@types/estree": "^1.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/cli-truncate": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-6.1.1.tgz", - "integrity": "sha512-06p9vyLahLa4zkGcgsGxU6iEkSOiuI4fhCH6Emhe2lPAcoUv73n72DnODsnHA+5wwXGnV0n9M9/qOQJSjYhFhw==", + "node_modules/estree-util-build-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", + "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", "license": "MIT", "dependencies": { - "slice-ansi": "^9.0.0", - "string-width": "^8.2.0" - }, - "engines": { - "node": ">=22" + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-walker": "^3.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/cli-truncate/node_modules/string-width": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", - "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.5.0", - "strip-ansi": "^7.1.2" - }, - "engines": { - "node": ">=20" - }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", + "node_modules/estree-util-scope": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", + "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", + "license": "MIT", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "@types/estree": "^1.0.0", + "devlop": "^1.0.0" }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/cliui/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/estree-util-to-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", + "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" + "@types/estree-jsx": "^1.0.0", + "astring": "^1.8.0", + "source-map": "^0.7.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, + "node_modules/estree-util-to-js/node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "license": "BSD-3-Clause", "engines": { - "node": ">=8" + "node": ">= 12" } }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/estree-util-visit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^3.0.0" }, - "engines": { - "node": ">=8" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "@types/estree": "^1.0.0" } }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 0.6" } }, - "node_modules/cm6-theme-basic-light": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/cm6-theme-basic-light/-/cm6-theme-basic-light-0.2.0.tgz", - "integrity": "sha512-1prg2gv44sYfpHscP26uLT/ePrh0mlmVwMSoSd3zYKQ92Ab3jPRLzyCnpyOCQLJbK+YdNs4HvMRqMNYdy4pMhA==", - "license": "MIT", - "peerDependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "@lezer/highlight": "^1.0.0" - } + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" }, - "node_modules/code-excerpt": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", - "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", - "license": "MIT", - "dependencies": { - "convert-to-spaces": "^2.0.1" - }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "license": "Apache-2.0", "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=12.0.0" } }, - "node_modules/codemirror": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", - "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", + "node_modules/expressive-code": { + "version": "0.41.7", + "resolved": "https://registry.npmjs.org/expressive-code/-/expressive-code-0.41.7.tgz", + "integrity": "sha512-2wZjC8OQ3TaVEMcBtYY4Va3lo6J+Ai9jf3d4dbhURMJcU4Pbqe6EcHe424MIZI0VHUA1bR6xdpoHYi3yxokWqA==", "license": "MIT", "dependencies": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/commands": "^6.0.0", - "@codemirror/language": "^6.0.0", - "@codemirror/lint": "^6.0.0", - "@codemirror/search": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0" + "@expressive-code/core": "^0.41.7", + "@expressive-code/plugin-frames": "^0.41.7", + "@expressive-code/plugin-shiki": "^0.41.7", + "@expressive-code/plugin-text-markers": "^0.41.7" } }, - "node_modules/collapse-white-space": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", - "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fault": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", + "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", "license": "MIT", + "dependencies": { + "format": "^0.2.0" + }, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, "engines": { - "node": ">=7.0.0" + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" + "node_modules/flattie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", + "integrity": "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/comma-separated-tokens": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "node_modules/fontace": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/fontace/-/fontace-0.4.1.tgz", + "integrity": "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "dependencies": { + "fontkitten": "^1.0.2" } }, - "node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "node_modules/fontkitten": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fontkitten/-/fontkitten-1.0.3.tgz", + "integrity": "sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==", "license": "MIT", + "dependencies": { + "tiny-inflate": "^1.0.3" + }, "engines": { - "node": ">= 12" + "node": ">=20" } }, - "node_modules/common-ancestor-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", - "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", - "license": "ISC" - }, - "node_modules/compute-scroll-into-view": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz", - "integrity": "sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g==", - "license": "MIT" - }, - "node_modules/content-disposition": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", - "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", - "license": "MIT", + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">=0.4.x" } }, - "node_modules/content-type": { + "node_modules/fresh": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 0.8" } }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "license": "MIT" - }, - "node_modules/convert-to-spaces": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", - "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/cookie": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", - "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "license": "MIT", "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">=6.9.0" } }, - "node_modules/cookie-es": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.3.tgz", - "integrity": "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==", - "license": "MIT" - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", "engines": { - "node": ">=6.6.0" + "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, "engines": { - "node": ">= 0.10" + "node": ">=18" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/crelt": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.7.tgz", - "integrity": "sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==", - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, "engines": { - "node": ">= 8" + "node": ">=6" } }, - "node_modules/crossws": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", - "integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==", + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "license": "ISC" + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/h3": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.11.tgz", + "integrity": "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==", "license": "MIT", "dependencies": { + "cookie-es": "^1.2.3", + "crossws": "^0.3.5", + "defu": "^6.1.6", + "destr": "^2.0.5", + "iron-webcrypto": "^1.2.1", + "node-mock-http": "^1.0.4", + "radix3": "^1.1.2", + "ufo": "^1.6.3", "uncrypto": "^0.1.3" } }, - "node_modules/css-loader": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.4.tgz", - "integrity": "sha512-vv3J9tlOl04WjiMvHQI/9tmIrCxVrj6PFbHemBB1iihpeRbi/I4h033eoFIhwxBBqLhI0KYFS7yvynBFhIZfTw==", + "node_modules/hast-util-embedded": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-embedded/-/hast-util-embedded-3.0.0.tgz", + "integrity": "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==", "license": "MIT", "dependencies": { - "icss-utils": "^5.1.0", - "postcss": "^8.4.40", - "postcss-modules-extract-imports": "^3.1.0", - "postcss-modules-local-by-default": "^4.0.5", - "postcss-modules-scope": "^3.2.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.2.0", - "semver": "^7.6.3" - }, - "engines": { - "node": ">= 18.12.0" + "@types/hast": "^3.0.0", + "hast-util-is-element": "^3.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", - "webpack": "^5.27.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } + "url": "https://opencollective.com/unified" } }, - "node_modules/css-loader/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node_modules/hast-util-format": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hast-util-format/-/hast-util-format-1.1.0.tgz", + "integrity": "sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-minify-whitespace": "^1.0.0", + "hast-util-phrasing": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "html-whitespace-sensitive-tag-names": "^3.0.0", + "unist-util-visit-parents": "^6.0.0" }, - "engines": { - "node": ">=10" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/css-select": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", - "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", - "license": "BSD-2-Clause", + "node_modules/hast-util-from-dom": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.1.tgz", + "integrity": "sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==", + "license": "ISC", "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" + "@types/hast": "^3.0.0", + "hastscript": "^9.0.0", + "web-namespaces": "^2.0.0" }, "funding": { - "url": "https://github.com/sponsors/fb55" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/css-selector-parser": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.3.0.tgz", - "integrity": "sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/mdevils" - }, - { - "type": "patreon", - "url": "https://patreon.com/mdevils" - } - ], - "license": "MIT" - }, - "node_modules/css-tree": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", - "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", "license": "MIT", "dependencies": { - "mdn-data": "2.27.1", - "source-map-js": "^1.2.1" + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/css-what": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", - "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" + "node_modules/hast-util-from-html-isomorphic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz", + "integrity": "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-dom": "^5.0.0", + "hast-util-from-html": "^2.0.0", + "unist-util-remove-position": "^5.0.0" }, "funding": { - "url": "https://github.com/sponsors/fb55" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" }, - "engines": { - "node": ">=4" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/csso": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", - "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "node_modules/hast-util-has-property": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", + "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", "license": "MIT", "dependencies": { - "css-tree": "~2.2.0" + "@types/hast": "^3.0.0" }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/csso/node_modules/css-tree": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", - "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "node_modules/hast-util-is-body-ok-link": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-3.0.1.tgz", + "integrity": "sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==", "license": "MIT", "dependencies": { - "mdn-data": "2.0.28", - "source-map-js": "^1.0.1" + "@types/hast": "^3.0.0" }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/csso/node_modules/mdn-data": { - "version": "2.0.28", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", - "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", - "license": "CC0-1.0" - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", "license": "MIT", - "peer": true - }, - "node_modules/d3-array": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", - "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", - "license": "ISC", - "peer": true, "dependencies": { - "internmap": "1 - 2" + "@types/hast": "^3.0.0" }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "license": "ISC", - "peer": true, - "engines": { - "node": ">=12" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/d3-ease": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", - "license": "BSD-3-Clause", - "peer": true, - "engines": { - "node": ">=12" + "node_modules/hast-util-minify-whitespace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hast-util-minify-whitespace/-/hast-util-minify-whitespace-1.0.1.tgz", + "integrity": "sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/d3-format": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", - "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", - "license": "ISC", - "peer": true, - "engines": { - "node": ">=12" + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/d3-interpolate": { + "node_modules/hast-util-phrasing": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "license": "ISC", - "peer": true, + "resolved": "https://registry.npmjs.org/hast-util-phrasing/-/hast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==", + "license": "MIT", "dependencies": { - "d3-color": "1 - 3" + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-is-body-ok-link": "^3.0.0", + "hast-util-is-element": "^3.0.0" }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", - "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", - "license": "ISC", - "peer": true, - "engines": { - "node": ">=12" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/d3-scale": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", - "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", - "license": "ISC", - "peer": true, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "license": "MIT", "dependencies": { - "d3-array": "2.10.0 - 3", - "d3-format": "1 - 3", - "d3-interpolate": "1.2.0 - 3", - "d3-time": "2.1.1 - 3", - "d3-time-format": "2 - 4" + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" }, - "engines": { - "node": ">=12" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/d3-shape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", - "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", - "license": "ISC", - "peer": true, + "node_modules/hast-util-select": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-6.0.4.tgz", + "integrity": "sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==", + "license": "MIT", "dependencies": { - "d3-path": "^3.1.0" + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "bcp-47-match": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "css-selector-parser": "^3.0.0", + "devlop": "^1.0.0", + "direction": "^2.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "nth-check": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" }, - "engines": { - "node": ">=12" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/d3-time": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", - "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", - "license": "ISC", - "peer": true, + "node_modules/hast-util-to-estree": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", + "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==", + "license": "MIT", "dependencies": { - "d3-array": "2 - 3" + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-attach-comments": "^3.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "zwitch": "^2.0.0" }, - "engines": { - "node": ">=12" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/d3-time-format": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", - "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", - "license": "ISC", - "peer": true, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", "dependencies": { - "d3-time": "1 - 3" + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" }, - "engines": { - "node": ">=12" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/d3-timer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", - "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", - "license": "ISC", - "peer": true, - "engines": { - "node": ">=12" + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/hast-util-to-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz", + "integrity": "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==", "license": "MIT", "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/decimal.js-light": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", - "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "node_modules/hast-util-to-string": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", + "integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==", "license": "MIT", - "peer": true + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "node_modules/decode-named-character-reference": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", - "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", "license": "MIT", "dependencies": { - "character-entities": "^2.0.0" + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/defu": { - "version": "6.1.7", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", - "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", - "license": "MIT" - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", "license": "MIT", - "engines": { - "node": ">= 0.8" + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", "license": "MIT", - "engines": { - "node": ">=6" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/destr": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", - "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", - "license": "MIT" - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" + "node_modules/html-whitespace-sensitive-tag-names": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-whitespace-sensitive-tag-names/-/html-whitespace-sensitive-tag-names-3.0.1.tgz", + "integrity": "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/detect-node-es": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", - "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", - "license": "MIT" + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause" }, - "node_modules/deterministic-object-hash": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/deterministic-object-hash/-/deterministic-object-hash-2.0.2.tgz", - "integrity": "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==", + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { - "base-64": "^1.0.0" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { - "node": ">=18" + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/devalue": { - "version": "5.8.1", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.1.tgz", - "integrity": "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==", - "license": "MIT" - }, - "node_modules/devlop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "node_modules/i18next": { + "version": "23.16.8", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.8.tgz", + "integrity": "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], "license": "MIT", "dependencies": { - "dequal": "^2.0.0" - }, + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/immer": { + "version": "11.1.16", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.16.tgz", + "integrity": "sha512-Xs7H9rBc+kti1J6RueUvbEBkmOz7jqj11XYgf+YMXAYzu8EeE7hwZ9poLXdVfVnGmJu7QAf41T7H2KuF6QoK6Q==", + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/diff": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", - "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", - "license": "BSD-3-Clause", + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "peer": true, "engines": { - "node": ">=0.3.1" + "node": ">=12" } }, - "node_modules/direction": { + "node_modules/iron-webcrypto": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", + "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/brc-dd" + } + }, + "node_modules/is-alphabetical": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz", - "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", "license": "MIT", - "bin": { - "direction": "cli.js" - }, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "license": "MIT" - }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", "license": "MIT", "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" }, "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/dom-serializer/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.3.0" + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" }, "engines": { - "node": ">= 4" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/downshift": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/downshift/-/downshift-7.6.2.tgz", - "integrity": "sha512-iOv+E1Hyt3JDdL9yYcOgW7nZ7GQ2Uz6YbggwXvKUSleetYhU2nXD482Rz6CzvM4lvI1At34BYruKAL4swRGxaA==", + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.14.8", - "compute-scroll-into-view": "^2.0.4", - "prop-types": "^15.7.2", - "react-is": "^17.0.2", - "tslib": "^2.3.0" + "is-docker": "^3.0.0" }, - "peerDependencies": { - "react": ">=16.12.0" - } - }, - "node_modules/downshift/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "license": "MIT" - }, - "node_modules/drizzle-orm": { - "version": "0.45.2", - "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.45.2.tgz", - "integrity": "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==", - "license": "Apache-2.0", - "peerDependencies": { - "@aws-sdk/client-rds-data": ">=3", - "@cloudflare/workers-types": ">=4", - "@electric-sql/pglite": ">=0.2.0", - "@libsql/client": ">=0.10.0", - "@libsql/client-wasm": ">=0.10.0", - "@neondatabase/serverless": ">=0.10.0", - "@op-engineering/op-sqlite": ">=2", - "@opentelemetry/api": "^1.4.1", - "@planetscale/database": ">=1.13", - "@prisma/client": "*", - "@tidbcloud/serverless": "*", - "@types/better-sqlite3": "*", - "@types/pg": "*", - "@types/sql.js": "*", - "@upstash/redis": ">=1.34.7", - "@vercel/postgres": ">=0.8.0", - "@xata.io/client": "*", - "better-sqlite3": ">=7", - "bun-types": "*", - "expo-sqlite": ">=14.0.0", - "gel": ">=2", - "knex": "*", - "kysely": "*", - "mysql2": ">=2", - "pg": ">=8", - "postgres": ">=3", - "sql.js": ">=1", - "sqlite3": ">=5" + "bin": { + "is-inside-container": "cli.js" }, - "peerDependenciesMeta": { - "@aws-sdk/client-rds-data": { - "optional": true - }, - "@cloudflare/workers-types": { - "optional": true - }, - "@electric-sql/pglite": { - "optional": true - }, - "@libsql/client": { - "optional": true - }, - "@libsql/client-wasm": { - "optional": true - }, - "@neondatabase/serverless": { - "optional": true - }, - "@op-engineering/op-sqlite": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@planetscale/database": { - "optional": true - }, - "@prisma/client": { - "optional": true - }, - "@tidbcloud/serverless": { - "optional": true - }, - "@types/better-sqlite3": { - "optional": true - }, - "@types/pg": { - "optional": true - }, - "@types/sql.js": { - "optional": true - }, - "@upstash/redis": { - "optional": true - }, - "@vercel/postgres": { - "optional": true - }, - "@xata.io/client": { - "optional": true - }, - "better-sqlite3": { - "optional": true - }, - "bun-types": { - "optional": true - }, - "expo-sqlite": { - "optional": true - }, - "gel": { - "optional": true - }, - "knex": { - "optional": true - }, - "kysely": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "pg": { - "optional": true - }, - "postgres": { - "optional": true - }, - "prisma": { - "optional": true - }, - "sql.js": { - "optional": true - }, - "sqlite3": { - "optional": true - } + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/dset": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", - "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", "license": "MIT", "engines": { - "node": ">=4" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" + "is-inside-container": "^1.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.389", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.389.tgz", - "integrity": "sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg==", - "license": "ISC" - }, - "node_modules/emmet": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/emmet/-/emmet-2.4.11.tgz", - "integrity": "sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==", + "node_modules/isomorphic.js": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz", + "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==", "license": "MIT", - "workspaces": [ - "./packages/scanner", - "./packages/abbreviation", - "./packages/css-abbreviation", - "./" - ], - "dependencies": { - "@emmetio/abbreviation": "^2.3.3", - "@emmetio/css-abbreviation": "^2.1.8" + "peer": true, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" } }, - "node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", "license": "MIT", - "engines": { - "node": ">= 0.8" + "bin": { + "jiti": "lib/jiti-cli.mjs" } }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { - "once": "^1.4.0" + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/enhanced-resolve": { - "version": "5.21.6", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz", - "integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==", + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.3" + "bin": { + "jsesc": "bin/jsesc" }, "engines": { - "node": ">=10.13.0" + "node": ">=6" } }, - "node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" }, - "node_modules/environment": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", - "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "license": "MIT", - "engines": { - "node": ">=18" + "bin": { + "json5": "lib/cli.js" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=6" } }, - "node_modules/error-stack-parser": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", - "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "node_modules/jsonc-parser": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz", + "integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==", + "license": "MIT" + }, + "node_modules/katex": { + "version": "0.16.47", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", + "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], "license": "MIT", "dependencies": { - "stackframe": "^1.3.4" + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" } }, - "node_modules/error-stack-parser-es": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", - "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" + "engines": { + "node": ">=6" } }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">= 8" } }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "node_modules/lexical": { + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/lexical/-/lexical-0.48.0.tgz", + "integrity": "sha512-KK4Tyr/cPsleoZ7XvhGRiRmcrZidSmoFUdIXK9nPubIifoC+80Dc5THyc4xtGKtsW24S1TsHzk5gmfBU+TxmEg==", "license": "MIT", - "engines": { - "node": ">= 0.4" + "dependencies": { + "@lexical/internal": "0.48.0" + }, + "peerDependencies": { + "typescript": ">=5.2" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "license": "MIT" - }, - "node_modules/es-object-atoms": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", - "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "node_modules/lib0": { + "version": "0.2.117", + "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.117.tgz", + "integrity": "sha512-DeXj9X5xDCjgKLU/7RR+/HQEVzuuEUiwldwOGsHK/sfAfELGWEyTcf0x+uOvCvK3O2zPmZePXWL85vtia6GyZw==", "license": "MIT", + "peer": true, "dependencies": { - "es-errors": "^1.3.0" + "isomorphic.js": "^0.2.4" + }, + "bin": { + "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js", + "0gentesthtml": "bin/gentesthtml.js", + "0serve": "bin/0serve.js" }, "engines": { - "node": ">= 0.4" + "node": ">=16" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" } }, - "node_modules/es-toolkit": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.49.0.tgz", - "integrity": "sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g==", - "license": "MIT", - "workspaces": [ - "docs", - "benchmarks" - ] + "node_modules/libsodium-sumo": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/libsodium-sumo/-/libsodium-sumo-0.8.4.tgz", + "integrity": "sha512-TMtHShQfVVsaxDygyapvUC3o7YsPgXa/hRWeIgzyFz6w5k/1hirGptCxp1U7XwW3rCskaTTYKgV10v86UiGgNw==", + "license": "ISC" }, - "node_modules/esast-util-from-estree": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", - "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", - "license": "MIT", + "node_modules/libsodium-wrappers-sumo": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/libsodium-wrappers-sumo/-/libsodium-wrappers-sumo-0.8.4.tgz", + "integrity": "sha512-ql7hcgulKZ3ekfa2DGAogcCKsWU0diA/0nArz1CFzh93WQdb46/Kj18ka/Hifq6uA3Ush34Pc6vU/6HXeRwUkg==", + "license": "ISC", "dependencies": { - "@types/estree-jsx": "^1.0.0", - "devlop": "^1.0.0", - "estree-util-visit": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "libsodium-sumo": "^0.8.0" } }, - "node_modules/esast-util-from-js": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", - "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", - "license": "MIT", + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "license": "MPL-2.0", "dependencies": { - "@types/estree-jsx": "^1.0.0", - "acorn": "^8.0.0", - "esast-util-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/esbuild": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", - "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" + "url": "https://opencollective.com/parcel" }, "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" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "engines": { - "node": ">=6" + "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" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "license": "MIT", + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=12" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" + "node": ">= 12.0.0" }, - "engines": { - "node": ">=4.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "license": "BSD-2-Clause", + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=4.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "license": "BSD-2-Clause", + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-util-attach-comments": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", - "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/estree-util-build-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", - "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "estree-walker": "^3.0.0" + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/estree-util-is-identifier-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", - "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", - "license": "MIT", + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/estree-util-scope": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", - "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0" + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/estree-util-to-js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", - "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "astring": "^1.8.0", - "source-map": "^0.7.0" + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/estree-util-visit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", - "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/unist": "^3.0.0" + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.6" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/eventemitter3": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", - "license": "MIT" - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", "license": "MIT", - "engines": { - "node": ">=0.8.x" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/eventsource": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", - "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "license": "MIT", "dependencies": { - "eventsource-parser": "^3.0.1" + "js-tokens": "^3.0.0 || ^4.0.0" }, - "engines": { - "node": ">=18.0.0" + "bin": { + "loose-envify": "cli.js" } }, - "node_modules/eventsource-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", - "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "license": "MIT", - "engines": { - "node": ">=18.0.0" + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "node_modules/magicast": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.4.tgz", + "integrity": "sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==", "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "source-map-js": "^1.2.1" + } + }, + "node_modules/markdown-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", + "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=16" }, "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/expect-type": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", - "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "node_modules/mdast-util-definitions": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz", + "integrity": "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==", "license": "MIT", "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://opencollective.com/unified" } }, - "node_modules/express-rate-limit": { - "version": "8.5.2", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", - "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", + "node_modules/mdast-util-directive": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", + "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", "license": "MIT", "dependencies": { - "ip-address": "^10.2.0" - }, - "engines": { - "node": ">= 16" + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-visit-parents": "^6.0.0" }, "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/express/node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", "license": "MIT", - "engines": { - "node": ">= 0.6" + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/expressive-code": { - "version": "0.41.7", - "resolved": "https://registry.npmjs.org/expressive-code/-/expressive-code-0.41.7.tgz", - "integrity": "sha512-2wZjC8OQ3TaVEMcBtYY4Va3lo6J+Ai9jf3d4dbhURMJcU4Pbqe6EcHe424MIZI0VHUA1bR6xdpoHYi3yxokWqA==", + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", "license": "MIT", "dependencies": { - "@expressive-code/core": "^0.41.7", - "@expressive-code/plugin-frames": "^0.41.7", - "@expressive-code/plugin-shiki": "^0.41.7", - "@expressive-code/plugin-text-markers": "^0.41.7" + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT" - }, - "node_modules/extract-zip": { + "node_modules/mdast-util-frontmatter": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "license": "BSD-2-Clause", + "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz", + "integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==", + "license": "MIT", "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "escape-string-regexp": "^5.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-extension-frontmatter": "^2.0.0" }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/extract-zip/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", "license": "MIT", "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" }, - "engines": { - "node": ">=8.6.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/fast-uri": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz", - "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "node_modules/fast-xml-builder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.3.0.tgz", - "integrity": "sha512-F74cZEdCvuw9P41GAC3rod4X04jjWGM1JPEv/GWSqFTWLsdyMSBMBMlm9Hk3GLBgLBbdBNY8yee0pQh2RBVESQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", "license": "MIT", "dependencies": { - "path-expression-matcher": "^1.6.2", - "xml-naming": "^0.3.0" + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "license": "ISC", + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", "dependencies": { - "reusify": "^1.0.4" + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/fault": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", - "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", "license": "MIT", "dependencies": { - "format": "^0.2.0" + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "node_modules/mdast-util-highlight-mark": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/mdast-util-highlight-mark/-/mdast-util-highlight-mark-1.2.2.tgz", + "integrity": "sha512-OYumVoytj+B9YgwzBhBcYUCLYHIPvJtAvwnMyKhUXbfUFuER5S+FDZyu9fadUxm2TCT5fRYK3jQXh2ioWAxrMw==", "license": "MIT", "dependencies": { - "pend": "~1.2.0" + "micromark-extension-highlight-mark": "1.2.0" } }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "node_modules/mdast-util-math": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-3.0.0.tgz", + "integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==", "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "longest-streak": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.1.0", + "unist-util-remove-position": "^5.0.0" }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "node_modules/mdast-util-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" }, - "engines": { - "node": ">=8" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", "license": "MIT", "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18.0.0" + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://opencollective.com/unified" } }, - "node_modules/flattie": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", - "integrity": "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/fontace": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/fontace/-/fontace-0.4.1.tgz", - "integrity": "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==", + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", "license": "MIT", "dependencies": { - "fontkitten": "^1.0.2" + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/fontkitten": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/fontkitten/-/fontkitten-1.0.3.tgz", - "integrity": "sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==", + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", "license": "MIT", "dependencies": { - "tiny-inflate": "^1.0.3" + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" }, - "engines": { - "node": ">=20" - } - }, - "node_modules/format": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", - "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/fs-monkey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", - "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==", - "license": "Unlicense" - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/get-east-asian-width": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", - "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", "license": "MIT", - "engines": { - "node": ">=18" + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", "license": "MIT", "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" - }, - "engines": { - "node": ">= 0.4" + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-nonce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", - "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", - "license": "MIT", - "engines": { - "node": ">=6" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", "license": "MIT", "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" }, - "engines": { - "node": ">= 0.4" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", "license": "MIT", - "engines": { - "node": ">=10" + "dependencies": { + "@types/mdast": "^4.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/git-up": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/git-up/-/git-up-8.1.1.tgz", - "integrity": "sha512-FDenSF3fVqBYSaJoYy1KSc2wosx0gCvKP+c+PRBht7cAaiCeQlBtfBDX9vgnNOHmdePlSFITVcn4pFfcgNvx3g==", + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "license": "CC0-1.0" + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "is-ssh": "^1.4.0", - "parse-url": "^9.2.0" + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/git-url-parse": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-16.1.0.tgz", - "integrity": "sha512-cPLz4HuK86wClEW7iDdeAKcCVlWXmrLpb2L+G9goW0Z1dtpNS6BXXSOckUTlJT/LDQViE1QZKstNORzHsLnobw==", + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "git-up": "^8.1.0" + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/github-slugger": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", - "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", - "license": "ISC" - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "license": "BSD-2-Clause" - }, - "node_modules/globby": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-16.2.1.tgz", - "integrity": "sha512-JmsqJalahxxgW8V2ecSQ2G7UjPlI9cpKdrkG9KoNiXhd/YslXOTEB0cViENWUznuovIuNT+FkMbraDGjr4FCUg==", + "node_modules/micromark-extension-directive": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", + "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", "license": "MIT", "dependencies": { - "@sindresorhus/merge-streams": "^4.0.0", - "fast-glob": "^3.3.3", - "ignore": "^7.0.5", - "is-path-inside": "^4.0.0", - "slash": "^5.1.0", - "unicorn-magic": "^0.4.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gpt-tokenizer": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/gpt-tokenizer/-/gpt-tokenizer-3.4.0.tgz", - "integrity": "sha512-wxFLnhIXTDjYebd9A9pGl3e31ZpSypbpIJSOswbgop5jLte/AsZVDvjlbEuVFlsqZixVKqbcoNmRlFDf6pz/UQ==", - "license": "MIT" - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/graphql": { - "version": "16.14.2", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.2.tgz", - "integrity": "sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==", - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" - } - }, - "node_modules/h3": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.11.tgz", - "integrity": "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==", - "license": "MIT", - "dependencies": { - "cookie-es": "^1.2.3", - "crossws": "^0.3.5", - "defu": "^6.1.6", - "destr": "^2.0.5", - "iron-webcrypto": "^1.2.1", - "node-mock-http": "^1.0.4", - "radix3": "^1.1.2", - "ufo": "^1.6.3", - "uncrypto": "^0.1.3" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/handlebars": { - "version": "4.7.9", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", - "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", + "node_modules/micromark-extension-frontmatter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", + "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==", "license": "MIT", "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/handlebars/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" + "fault": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/hasown": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", "license": "MIT", "dependencies": { - "function-bind": "^1.1.2" + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" }, - "engines": { - "node": ">= 0.4" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-embedded": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-embedded/-/hast-util-embedded-3.0.0.tgz", - "integrity": "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==", + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-is-element": "^3.0.0" + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-format": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/hast-util-format/-/hast-util-format-1.1.0.tgz", - "integrity": "sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==", + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-embedded": "^3.0.0", - "hast-util-minify-whitespace": "^1.0.0", - "hast-util-phrasing": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "html-whitespace-sensitive-tag-names": "^3.0.0", - "unist-util-visit-parents": "^6.0.0" + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-from-dom": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.1.tgz", - "integrity": "sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==", - "license": "ISC", + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "hastscript": "^9.0.0", - "web-namespaces": "^2.0.0" + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-from-html": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", - "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "devlop": "^1.1.0", - "hast-util-from-parse5": "^8.0.0", - "parse5": "^7.0.0", - "vfile": "^6.0.0", - "vfile-message": "^4.0.0" + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-from-html-isomorphic": { + "node_modules/micromark-extension-gfm-tagfilter": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz", - "integrity": "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-from-dom": "^5.0.0", - "hast-util-from-html": "^2.0.0", - "unist-util-remove-position": "^5.0.0" + "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-from-parse5": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", - "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", "devlop": "^1.0.0", - "hastscript": "^9.0.0", - "property-information": "^7.0.0", - "vfile": "^6.0.0", - "vfile-location": "^5.0.0", - "web-namespaces": "^2.0.0" + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-has-property": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", - "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", + "node_modules/micromark-extension-highlight-mark": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-extension-highlight-mark/-/micromark-extension-highlight-mark-1.2.0.tgz", + "integrity": "sha512-huGtbd/9kQsMk8u7nrVMaS5qH/47yDG6ZADggo5Owz5JoY8wdfQjfuy118/QiYNCvdFuFDbzT0A7K7Hp2cBsXA==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0" + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "uvu": "^0.5.6" + } + }, + "node_modules/micromark-extension-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "license": "MIT", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-is-body-ok-link": { + "node_modules/micromark-extension-mdx-expression": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-3.0.1.tgz", - "integrity": "sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", + "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/hast-util-is-element": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", - "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "node_modules/micromark-extension-mdx-jsx": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", + "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0" + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-minify-whitespace": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hast-util-minify-whitespace/-/hast-util-minify-whitespace-1.0.1.tgz", - "integrity": "sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==", + "node_modules/micromark-extension-mdx-md": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", + "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-embedded": "^3.0.0", - "hast-util-is-element": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "unist-util-is": "^6.0.0" + "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-parse-selector": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", - "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "node_modules/micromark-extension-mdxjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", + "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0" + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^3.0.0", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs-esm": "^3.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-phrasing": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/hast-util-phrasing/-/hast-util-phrasing-3.0.1.tgz", - "integrity": "sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==", + "node_modules/micromark-extension-mdxjs-esm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", + "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-embedded": "^3.0.0", - "hast-util-has-property": "^3.0.0", - "hast-util-is-body-ok-link": "^3.0.0", - "hast-util-is-element": "^3.0.0" + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-raw": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", - "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "@ungap/structured-clone": "^1.0.0", - "hast-util-from-parse5": "^8.0.0", - "hast-util-to-parse5": "^8.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "parse5": "^7.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/hast-util-select": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-6.0.4.tgz", - "integrity": "sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==", + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "bcp-47-match": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "css-selector-parser": "^3.0.0", "devlop": "^1.0.0", - "direction": "^2.0.0", - "hast-util-has-property": "^3.0.0", - "hast-util-to-string": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "nth-check": "^2.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "unist-util-visit": "^5.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/hast-util-to-estree": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", - "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==", + "node_modules/micromark-factory-mdx-expression": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", + "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", - "estree-util-attach-comments": "^3.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "style-to-js": "^1.0.0", - "unist-util-position": "^5.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" } }, - "node_modules/hast-util-to-html": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", - "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-whitespace": "^3.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "stringify-entities": "^4.0.0", - "zwitch": "^2.0.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/hast-util-to-jsx-runtime": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", - "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "style-to-js": "^1.0.0", - "unist-util-position": "^5.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/hast-util-to-parse5": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz", - "integrity": "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==", + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/hast-util-to-string": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", - "integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==", + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/hast-util-to-text": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", - "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "hast-util-is-element": "^3.0.0", - "unist-util-find-after": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/hast-util-whitespace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", - "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/hastscript": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", - "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-parse-selector": "^4.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hono": { - "version": "4.12.29", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.29.tgz", - "integrity": "sha512-1hNiRjawYrLq/4m3DQQjPGFg0VZkk4RjQJDff/excI6Dm9BiL75qxGrd7/c6YOxPdq6AscP3LiXhQ6fKFC1Waw==", - "license": "MIT", - "engines": { - "node": ">=16.9.0" + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/html-entities": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", - "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", "funding": [ { - "type": "github", - "url": "https://github.com/sponsors/mdevils" + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" }, { - "type": "patreon", - "url": "https://patreon.com/mdevils" + "type": "OpenCollective", + "url": "https://opencollective.com/unified" } ], - "license": "MIT" - }, - "node_modules/html-escaper": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", - "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", - "license": "MIT" - }, - "node_modules/html-void-elements": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", - "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "dependencies": { + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/html-whitespace-sensitive-tag-names": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/html-whitespace-sensitive-tag-names/-/html-whitespace-sensitive-tag-names-3.0.1.tgz", - "integrity": "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==", + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", - "license": "BSD-2-Clause" - }, - "node_modules/http-errors": { + "node_modules/micromark-util-encode": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/i18next": { - "version": "23.16.8", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.8.tgz", - "integrity": "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", "funding": [ { - "type": "individual", - "url": "https://locize.com" + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" }, { - "type": "individual", - "url": "https://locize.com/i18next.html" + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-events-to-acorn": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", + "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" }, { - "type": "individual", - "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + "type": "OpenCollective", + "url": "https://opencollective.com/unified" } ], "license": "MIT", "dependencies": { - "@babel/runtime": "^7.23.2" - } - }, - "node_modules/iconv-lite": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", - "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "@types/estree": "^1.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" } }, - "node_modules/icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "license": "ISC", - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" }, - "node_modules/ignore": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", - "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", - "engines": { - "node": ">= 4" + "dependencies": { + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/immer": { - "version": "11.1.11", - "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.11.tgz", - "integrity": "sha512-qzXuyXAkPySAGYkfsAwodDPWT8Zm7/Uo5BNt4BjhMhG5WlWyZZ4wQqnWwdS8kjlQ1Cwu6gjw3A6+0gTQwlyYtw==", + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" + "dependencies": { + "micromark-util-types": "^2.0.0" } }, - "node_modules/import-meta-resolve": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", - "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/indent-string": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" }, - "node_modules/ink": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/ink/-/ink-7.1.0.tgz", - "integrity": "sha512-VWE6/yeLtFCJBNLflyI2OSylyXK1Rc24LuXup8Qt+icwkmmycFNdbn8IkSp6Frc0h1iA0NOvvi1ajW44U/w3Qg==", - "license": "MIT", - "dependencies": { - "@alcalzone/ansi-tokenize": "^0.3.0", - "ansi-escapes": "^7.3.0", - "ansi-styles": "^6.2.3", - "auto-bind": "^5.0.1", - "chalk": "^5.6.2", - "cli-boxes": "^4.0.1", - "cli-cursor": "^4.0.0", - "cli-truncate": "^6.0.0", - "code-excerpt": "^4.0.0", - "es-toolkit": "^1.45.1", - "indent-string": "^5.0.0", - "is-in-ci": "^2.0.0", - "patch-console": "^2.0.0", - "react-reconciler": "^0.33.0", - "scheduler": "^0.27.0", - "signal-exit": "^3.0.7", - "slice-ansi": "^9.0.0", - "stack-utils": "^2.0.6", - "string-width": "^8.2.0", - "terminal-size": "^4.0.1", - "type-fest": "^5.5.0", - "widest-line": "^6.0.0", - "wrap-ansi": "^10.0.0", - "ws": "^8.20.0", - "yoga-layout": "~3.2.1" - }, - "engines": { - "node": ">=22" - }, - "peerDependencies": { - "@types/react": ">=19.2.0", - "react": ">=19.2.0", - "react-devtools-core": ">=6.1.2" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" }, - "react-devtools-core": { - "optional": true + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" } - } + ], + "license": "MIT" }, - "node_modules/ink/node_modules/cli-boxes": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-4.0.1.tgz", - "integrity": "sha512-5IOn+jcCEHEraYolBPs/sT4BxYCe2nHg374OPiItB1O96KZFseS2gthU4twyYzeDcFew4DaUM/xwc5BQf08JJw==", + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", "engines": { - "node": ">=18.20 <19 || >=20.10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.6" } }, - "node_modules/ink/node_modules/string-width": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", - "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { - "get-east-asian-width": "^1.5.0", - "strip-ansi": "^7.1.2" + "mime-db": "^1.54.0" }, "engines": { - "node": ">=20" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/ink/node_modules/type-fest": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.8.0.tgz", - "integrity": "sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA==", - "license": "(MIT OR CC0-1.0)", + "node_modules/miniflare": { + "version": "5.20260801.1-alpha", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-5.20260801.1-alpha.tgz", + "integrity": "sha512-BHPVzIDA6mbx7LefxpvkXW7DHx9FKB9GorZatbnrrFTt3CVMU8zuUpbgyCuebwDKcTTOZos43ta8GQ0eMVEpxA==", + "license": "MIT", "dependencies": { - "tagged-tag": "^1.0.0" + "@cspotcode/source-map-support": "0.8.1", + "sharp": "0.35.2", + "undici": "7.29.0", + "workerd": "1.20260801.1", + "ws": "8.21.0", + "youch": "4.1.0-beta.10" }, "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=22.0.0" } }, - "node_modules/ink/node_modules/widest-line": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-6.0.0.tgz", - "integrity": "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA==", - "license": "MIT", - "dependencies": { - "string-width": "^8.1.0" - }, + "node_modules/miniflare/node_modules/@img/sharp-darwin-arm64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.2.tgz", + "integrity": "sha512-eEieHsMksAW4IiO5NzauESRl2D2qz3J/kwUxUrSfV06A93eEaRfMpHXyUb1mAqrR7i8U9A0GRqE9pjn6u1Jjpg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=20" + "node": ">=20.9.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.3.1" } }, - "node_modules/ink/node_modules/wrap-ansi": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-10.0.0.tgz", - "integrity": "sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.3", - "string-width": "^8.2.0", - "strip-ansi": "^7.1.2" - }, + "node_modules/miniflare/node_modules/@img/sharp-darwin-x64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.2.tgz", + "integrity": "sha512-BaktuGPCeHJMARpodR8jK4uKiZrPAy9WrfQW0sdI37clracq8Bp01AYS3SZgi5FS/y5twa9t4+LIuuxQjqRrWw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=20" + "node": ">=20.9.0" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.3.1" } }, - "node_modules/inline-style-parser": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", - "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", - "license": "MIT" - }, - "node_modules/internmap": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "license": "ISC", - "peer": true, - "engines": { - "node": ">=12" + "node_modules/miniflare/node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.1.tgz", + "integrity": "sha512-4V/M3roRMTYjiwZY9IOVQOE8OyeCxFAkYmyZDrZl51uOKjibm3oeEJ4WAmLxutAfzFbC9jqUiPs2gbnGflH+7g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/ip-address": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", - "license": "MIT", - "engines": { - "node": ">= 12" + "node_modules/miniflare/node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.1.tgz", + "integrity": "sha512-c0/DxItpJv2+dGhgycJBBgotdqruGYDvA79drdh0MD1dFpy7JzJ/PlXwi1H4rFf0eTy8tgbI91aHDnZIceY3jQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" + "node_modules/miniflare/node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.1.tgz", + "integrity": "sha512-aGGy9aWzXgHBG7HNyQPWorZthlp7+x6fDRoPAQbGO3ThcttuTyKIx3NuSHb6zb4gBNq6/yNn9f1cy9nFKS/Vmg==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/iron-webcrypto": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", - "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", - "license": "MIT", + "node_modules/miniflare/node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.1.tgz", + "integrity": "sha512-JznefmcK9j1JKPz8AkQDh89kjojubyfOasWBPKfzMIhPwsgDy9evpE/naJTXXXmghS1iFwR8u/kTwh/I2/+GCw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], "funding": { - "url": "https://github.com/sponsors/brc-dd" + "url": "https://opencollective.com/libvips" } }, - "node_modules/is-alphabetical": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", - "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", - "license": "MIT", + "node_modules/miniflare/node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.1.tgz", + "integrity": "sha512-1EkwGNCZk6iWNCMWqrvdJ+r1j0PT1zIz60CNPhYnJlK/zyeWqlsPZIe+ocBVqPF8k/Ssee/NCk+tE9Ryrko6ng==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://opencollective.com/libvips" } }, - "node_modules/is-alphanumerical": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", - "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", - "license": "MIT", - "dependencies": { - "is-alphabetical": "^2.0.0", - "is-decimal": "^2.0.0" - }, + "node_modules/miniflare/node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.1.tgz", + "integrity": "sha512-Ilays+w2bXdnxzxtQdmXR62u8o8GYa3eL4+Gr+1KiE4xperMZUslRaVPJwwPkzlHEjGfXAfRVAa/7CYCtSqsBw==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://opencollective.com/libvips" } }, - "node_modules/is-binary-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-3.0.0.tgz", - "integrity": "sha512-eSkpSYbqKip82Uw4z0iBK/5KmVzL2pf36kNKRtu6+mKvrow9sqF4w5hocQ9yV5v+9+wzHt620x3B7Wws/8lsGg==", - "license": "MIT", - "dependencies": { - "binary-extensions": "^3.0.0" - }, - "engines": { - "node": ">=18.20" - }, + "node_modules/miniflare/node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.1.tgz", + "integrity": "sha512-VfBwVHQTbRoj4XlpA/KLZ7ltgMpz+4WSejFzQ+GnoImjo1PtEJ59QB2qR1xQEeRPYIkNrPIm2L4cICMvz4C2ew==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/libvips" } }, - "node_modules/is-decimal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", - "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", - "license": "MIT", + "node_modules/miniflare/node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.1.tgz", + "integrity": "sha512-+c8ukgwU62DS54nCAjw7keOfHUkmr0B5QHEdcOqRnodF/MNXJbVI8Eopoj4B/0H8Asr65I+A4Amrn7a85/md6A==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://opencollective.com/libvips" } }, - "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, + "node_modules/miniflare/node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.1.tgz", + "integrity": "sha512-qlKb/pwbkAi1WMsJrYHk7CuDrd12s27U2QnRhFYUoJNrRCmkosMTttuRFat/DDB3IlDm5qE1TJgZ4JDnHX8Ldw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/libvips" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "node_modules/miniflare/node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.1.tgz", + "integrity": "sha512-yO21HwoUVLN8Qa+/SBjQLMYwBWAVJjeGPNe+hc0OUeMeifEtJqu5a1c4HayE1nNpDih9y3/KkoltfkDodmKAlg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/is-fullwidth-code-point": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.1" - }, + "node_modules/miniflare/node_modules/@img/sharp-linux-arm": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.2.tgz", + "integrity": "sha512-SE4kzF2mepn6z+6E7L6lsV8FzuLL6IPQdyX8ZiwROAG/G8td+hP/m7FsFPwidtrF19gvajuC9l6TxAVcsA4S7A==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18" + "node": ">=20.9.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" + "url": "https://opencollective.com/libvips" }, - "engines": { - "node": ">=0.10.0" + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.3.1" } }, - "node_modules/is-hexadecimal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", - "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", - "license": "MIT", + "node_modules/miniflare/node_modules/@img/sharp-linux-arm64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.2.tgz", + "integrity": "sha512-af12Pnd0ZGu2HfP8NayB0kk6eC/lrfbQE6HlR4jD+34wdJ1Vw9TF6TMn6ZvffT+WgqVsl0hRbmNvz2u/23VmwA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.3.1" } }, - "node_modules/is-in-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-2.0.0.tgz", - "integrity": "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w==", - "license": "MIT", - "bin": { - "is-in-ci": "cli.js" - }, + "node_modules/miniflare/node_modules/@img/sharp-linux-ppc64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.2.tgz", + "integrity": "sha512-hYSBm7zcNtDCozCxQHYZJiu63b/bXsgRZuOxCIBZsStMM9Vap47iFHdbX4kCvQsblPB/k+clhELpdQJHQLSHvg==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=20" + "node": ">=20.9.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "license": "MIT", - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-path-inside": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", - "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" - }, - "node_modules/is-ssh": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.1.tgz", - "integrity": "sha512-JNeu1wQsHjyHgn9NcWTaXq6zWSR6hqE0++zhfZlkFBbScNkyvxCdeV8sRkSBaeLKxmbpR21brail63ACNxJ0Tg==", - "license": "MIT", - "dependencies": { - "protocols": "^2.0.1" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-wsl": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", - "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", - "license": "MIT", - "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" + "url": "https://opencollective.com/libvips" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.3.1" } }, - "node_modules/isbinaryfile": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", - "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", - "license": "MIT", + "node_modules/miniflare/node_modules/@img/sharp-linux-riscv64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.2.tgz", + "integrity": "sha512-qQt0Kc13+Hoan/Awq/qMSQw3L+RI1NCRPgD5cUJ/1WSSmIoysLOc72jlRM3E0OHN9Yr313jgeQ2T+zW+F03QFA==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 18.0.0" + "node": ">=20.9.0" }, "funding": { - "url": "https://github.com/sponsors/gjtorikian/" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/isomorphic.js": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz", - "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==", - "license": "MIT", - "peer": true, - "funding": { - "type": "GitHub Sponsors ❤", - "url": "https://github.com/sponsors/dmonad" - } - }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "url": "https://opencollective.com/libvips" }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jiti": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", - "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/jose": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", - "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.3.1" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", - "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/nodeca" - } + "node_modules/miniflare/node_modules/@img/sharp-linux-s390x": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.2.tgz", + "integrity": "sha512-E4fLLfRPzDLlEeDaTzI98OFLcv++WL5ChLLMwPoVd0CIoZQqupBSNbOisPL5am9XsbQ9T84+iiMpUvbFtkunbA==", + "cpu": [ + "s390x" ], - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jschardet": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/jschardet/-/jschardet-3.1.4.tgz", - "integrity": "sha512-/kmVISmrwVwtyYU40iQUOp3SUPk2dhNCMsZBQX0R1/jZ8maaXJ/oZIzUOiyOqcgtLnETFKYChbJ5iDC/eWmFHg==", - "license": "LGPL-2.1+", - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/json-schema-typed": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", - "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", - "license": "BSD-2-Clause" - }, - "node_modules/json-with-bigint": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.10.tgz", - "integrity": "sha512-Vcx+JVNEBts/xfcoCS69sKrOhOk/3TVlvlT+XzUOefVKnnrbYSCKpDCm10pohsJFtsJVYnwa/cXRZ4eElzaM6w==", - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonc-parser": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz", - "integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==", - "license": "MIT" - }, - "node_modules/katex": { - "version": "0.16.47", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", - "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", - "funding": [ - "https://opencollective.com/katex", - "https://github.com/sponsors/katex" + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" ], - "license": "MIT", - "dependencies": { - "commander": "^8.3.0" - }, - "bin": { - "katex": "cli.js" - } - }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/klona": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", - "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/lexical": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/lexical/-/lexical-0.35.0.tgz", - "integrity": "sha512-3VuV8xXhh5xJA6tzvfDvE0YBCMkIZUmxtRilJQDDdCgJCc+eut6qAv2qbN+pbqvarqcQqPN1UF+8YvsjmyOZpw==", - "license": "MIT" - }, - "node_modules/lib0": { - "version": "0.2.117", - "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.117.tgz", - "integrity": "sha512-DeXj9X5xDCjgKLU/7RR+/HQEVzuuEUiwldwOGsHK/sfAfELGWEyTcf0x+uOvCvK3O2zPmZePXWL85vtia6GyZw==", - "license": "MIT", - "peer": true, - "dependencies": { - "isomorphic.js": "^0.2.4" - }, - "bin": { - "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js", - "0gentesthtml": "bin/gentesthtml.js", - "0serve": "bin/0serve.js" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "type": "GitHub Sponsors ❤", - "url": "https://github.com/sponsors/dmonad" - } - }, - "node_modules/libsodium": { - "version": "0.7.16", - "resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.16.tgz", - "integrity": "sha512-3HrzSPuzm6Yt9aTYCDxYEG8x8/6C0+ag655Y7rhhWZM9PT4NpdnbqlzXhGZlDnkgR6MeSTnOt/VIyHLs9aSf+Q==", - "license": "ISC" - }, - "node_modules/libsodium-wrappers": { - "version": "0.7.16", - "resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.16.tgz", - "integrity": "sha512-Gtr/WBx4dKjvRL1pvfwZqu7gO6AfrQ0u9vFL+kXihtHf6NfkROR8pjYWn98MFDI3jN19Ii1ZUfPR9afGiPyfHg==", - "license": "ISC", - "dependencies": { - "libsodium": "^0.7.16" - } - }, - "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "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" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/loader-runner": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.2.tgz", - "integrity": "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==", - "license": "MIT", - "engines": { - "node": ">=6.11.5" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", - "license": "MIT" - }, - "node_modules/longest-streak": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", - "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/magicast": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.3.tgz", - "integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.3", - "@babel/types": "^7.29.0", - "source-map-js": "^1.2.1" - } - }, - "node_modules/markdown-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", - "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/markdown-table": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", - "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mdast-util-definitions": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz", - "integrity": "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "unist-util-visit": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-directive": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", - "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "parse-entities": "^4.0.0", - "stringify-entities": "^4.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-find-and-replace": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", - "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "escape-string-regexp": "^5.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-from-markdown": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", - "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark": "^4.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-frontmatter": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz", - "integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "escape-string-regexp": "^5.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "micromark-extension-frontmatter": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", - "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", - "license": "MIT", - "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-gfm-autolink-literal": "^2.0.0", - "mdast-util-gfm-footnote": "^2.0.0", - "mdast-util-gfm-strikethrough": "^2.0.0", - "mdast-util-gfm-table": "^2.0.0", - "mdast-util-gfm-task-list-item": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-autolink-literal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", - "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "ccount": "^2.0.0", - "devlop": "^1.0.0", - "mdast-util-find-and-replace": "^3.0.0", - "micromark-util-character": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-strikethrough": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", - "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", - "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "markdown-table": "^3.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-task-list-item": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", - "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-highlight-mark": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/mdast-util-highlight-mark/-/mdast-util-highlight-mark-1.2.2.tgz", - "integrity": "sha512-OYumVoytj+B9YgwzBhBcYUCLYHIPvJtAvwnMyKhUXbfUFuER5S+FDZyu9fadUxm2TCT5fRYK3jQXh2ioWAxrMw==", - "license": "MIT", - "dependencies": { - "micromark-extension-highlight-mark": "1.2.0" - } - }, - "node_modules/mdast-util-math": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-3.0.0.tgz", - "integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "longest-streak": "^3.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.1.0", - "unist-util-remove-position": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", - "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", - "license": "MIT", - "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx-expression": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", - "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx-jsx": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", - "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "parse-entities": "^4.0.0", - "stringify-entities": "^4.0.0", - "unist-util-stringify-position": "^4.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdxjs-esm": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", - "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-phrasing": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", - "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-hast": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", - "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@ungap/structured-clone": "^1.0.0", - "devlop": "^1.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "trim-lines": "^3.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-markdown": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", - "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "longest-streak": "^3.0.0", - "mdast-util-phrasing": "^4.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "unist-util-visit": "^5.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", - "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdn-data": { - "version": "2.27.1", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", - "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", - "license": "CC0-1.0" - }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/mediabunny": { - "version": "1.45.0", - "resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.45.0.tgz", - "integrity": "sha512-oK3sMMYbucoF6LUX62L/2M9d+p9ve6KDQgL87kNfhsB0/XmTe9iRLUcgQgg9Gpgvi8Sb96zYfOUL6i17y0bdNg==", - "license": "MPL-2.0", - "workspaces": [ - ".", - "packages/*" - ], - "dependencies": { - "@types/dom-mediacapture-transform": "^0.1.11", - "@types/dom-webcodecs": "0.1.13" - }, - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/Vanilagy" - } - }, - "node_modules/memfs": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.3.tgz", - "integrity": "sha512-eivjfi7Ahr6eQTn44nvTnR60e4a1Fs1Via2kCR5lHo/kyNoiMWaXCNJ/GpSd0ilXas2JSOl9B5FTIhflXu0hlg==", - "license": "Unlicense", - "dependencies": { - "fs-monkey": "1.0.3" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromark": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", - "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-core-commonmark": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", - "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-destination": "^2.0.0", - "micromark-factory-label": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-title": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-html-tag-name": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-directive": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", - "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "parse-entities": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-frontmatter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", - "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==", - "license": "MIT", - "dependencies": { - "fault": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", - "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", - "license": "MIT", - "dependencies": { - "micromark-extension-gfm-autolink-literal": "^2.0.0", - "micromark-extension-gfm-footnote": "^2.0.0", - "micromark-extension-gfm-strikethrough": "^2.0.0", - "micromark-extension-gfm-table": "^2.0.0", - "micromark-extension-gfm-tagfilter": "^2.0.0", - "micromark-extension-gfm-task-list-item": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", - "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-strikethrough": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", - "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-table": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", - "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-tagfilter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", - "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-task-list-item": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", - "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-highlight-mark": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/micromark-extension-highlight-mark/-/micromark-extension-highlight-mark-1.2.0.tgz", - "integrity": "sha512-huGtbd/9kQsMk8u7nrVMaS5qH/47yDG6ZADggo5Owz5JoY8wdfQjfuy118/QiYNCvdFuFDbzT0A7K7Hp2cBsXA==", - "license": "MIT", - "dependencies": { - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "uvu": "^0.5.6" - } - }, - "node_modules/micromark-extension-math": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", - "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", - "license": "MIT", - "dependencies": { - "@types/katex": "^0.16.0", - "devlop": "^1.0.0", - "katex": "^0.16.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdx-expression": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", - "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-mdx-expression": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-mdx-jsx": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", - "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "micromark-factory-mdx-expression": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdx-md": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", - "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdxjs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", - "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", - "license": "MIT", - "dependencies": { - "acorn": "^8.0.0", - "acorn-jsx": "^5.0.0", - "micromark-extension-mdx-expression": "^3.0.0", - "micromark-extension-mdx-jsx": "^3.0.0", - "micromark-extension-mdx-md": "^2.0.0", - "micromark-extension-mdxjs-esm": "^3.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdxjs-esm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", - "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-factory-destination": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", - "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-label": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", - "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-mdx-expression": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", - "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" - } - }, - "node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-title": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", - "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-whitespace": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", - "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-chunked": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", - "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-classify-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", - "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-combine-extensions": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", - "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-chunked": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", - "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-string": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", - "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-encode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-events-to-acorn": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", - "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/unist": "^3.0.0", - "devlop": "^1.0.0", - "estree-util-visit": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "vfile-message": "^4.0.0" - } - }, - "node_modules/micromark-util-html-tag-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", - "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-normalize-identifier": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", - "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-resolve-all": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", - "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.3.1" } }, - "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", - "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } + "node_modules/miniflare/node_modules/@img/sharp-linux-x64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.2.tgz", + "integrity": "sha512-gi0zFJJRLswfCZmHtJdikXPOc5u7qamSOS3NHedLqLd4W8Q0NqjdBr6TTRIgsfFjqfTsHFgdfvJ9LwqSgcHiAA==", + "cpu": [ + "x64" ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-symbol": "^2.0.0" + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.3.1" } }, - "node_modules/micromark-util-subtokenize": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", - "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } + "node_modules/miniflare/node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.2.tgz", + "integrity": "sha512-siWbOW1u6HFnFLrp0waKyW7VEf7jYvcDWdrXEFa8AkdAQgEvuu5Fz8/Y70w9EeqAdwDtfU012BhEHHaDqvQNzg==", + "cpu": [ + "arm64" ], - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.3.1" } }, - "node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } + "node_modules/miniflare/node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.2.tgz", + "integrity": "sha512-YBqMMcjDi4QGYiSn4vNOYBhmlC4z5AXqkOUUqI2e0AFA4urNv4ESgOgwNl3K+4etQhha0twXlzeF20bbULm9Yg==", + "cpu": [ + "x64" ], - "license": "MIT" - }, - "node_modules/micromark-util-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", - "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" ], - "license": "MIT" - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, "engines": { - "node": ">=8.6" + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.3.1" } }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "license": "MIT", + "node_modules/miniflare/node_modules/@img/sharp-win32-arm64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.2.tgz", + "integrity": "sha512-BiVRYc/t6/Vl3e1hBx0hugG4oN9Pydf4fgMSpxTQJmwGUg/YoXTWHiFeRymHfCZzifxu4F4rpk/I67D0LQ20wQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=8.6" + "node": ">=20.9.0" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://opencollective.com/libvips" } }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", + "node_modules/miniflare/node_modules/@img/sharp-win32-ia32": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.2.tgz", + "integrity": "sha512-YYEhx9PImCC7T0tI8JDMi4DB9LwLCXCU5OWNYEXAxh5Q1ShKkyC6byxzoBJ3gEFDnH2lQckWuDe70G7mB2XJog==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.6" + "node": "^20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, + "node_modules/miniflare/node_modules/@img/sharp-win32-x64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.2.tgz", + "integrity": "sha512-imoOyBcoM/iiUr4J6VPpCNjPnjvP/Gks95898yB8YqoGGYmHYbOyCuNv9FMhFgtaiHFGbHW8bxKqRV6VjtXThQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=18" + "node": ">=20.9.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://opencollective.com/libvips" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "license": "MIT", + "node_modules/miniflare/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">=6" + "node": ">=10" } }, - "node_modules/miniflare": { - "version": "4.20260708.1", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20260708.1.tgz", - "integrity": "sha512-c94O9zRDISdqO18EHt6l0iF/fWgWt8p18PJvRsA/L/NJZ9Cfke3s/F5Blg1XXF7WDutVRzWVWy8Vy4LaT5ifsA==", - "license": "MIT", + "node_modules/miniflare/node_modules/sharp": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.2.tgz", + "integrity": "sha512-FVtFjtBCMiJS6yb5CX7Sop45WFMpeGw6oRKuJnXYgf/f1ms/D7LE/ZUSNxnW7rZ/dbslQWYkoqFHGPaDBtaK4w==", + "license": "Apache-2.0", "dependencies": { - "@cspotcode/source-map-support": "0.8.1", - "sharp": "0.34.5", - "undici": "7.28.0", - "workerd": "1.20260708.1", - "ws": "8.21.0", - "youch": "4.1.0-beta.10" - }, - "bin": { - "miniflare": "bootstrap.js" + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.4" }, "engines": { - "node": ">=22.0.0" + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.35.2", + "@img/sharp-darwin-x64": "0.35.2", + "@img/sharp-freebsd-wasm32": "0.35.2", + "@img/sharp-libvips-darwin-arm64": "1.3.1", + "@img/sharp-libvips-darwin-x64": "1.3.1", + "@img/sharp-libvips-linux-arm": "1.3.1", + "@img/sharp-libvips-linux-arm64": "1.3.1", + "@img/sharp-libvips-linux-ppc64": "1.3.1", + "@img/sharp-libvips-linux-riscv64": "1.3.1", + "@img/sharp-libvips-linux-s390x": "1.3.1", + "@img/sharp-libvips-linux-x64": "1.3.1", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.1", + "@img/sharp-libvips-linuxmusl-x64": "1.3.1", + "@img/sharp-linux-arm": "0.35.2", + "@img/sharp-linux-arm64": "0.35.2", + "@img/sharp-linux-ppc64": "0.35.2", + "@img/sharp-linux-riscv64": "0.35.2", + "@img/sharp-linux-s390x": "0.35.2", + "@img/sharp-linux-x64": "0.35.2", + "@img/sharp-linuxmusl-arm64": "0.35.2", + "@img/sharp-linuxmusl-x64": "0.35.2", + "@img/sharp-webcontainers-wasm32": "0.35.2", + "@img/sharp-win32-arm64": "0.35.2", + "@img/sharp-win32-ia32": "0.35.2", + "@img/sharp-win32-x64": "0.35.2" } }, "node_modules/miniflare/node_modules/ws": { @@ -14534,57 +11166,6 @@ } } }, - "node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minisearch": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.2.0.tgz", - "integrity": "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==", - "license": "MIT" - }, - "node_modules/minizlib": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -14616,9 +11197,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.15", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", - "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", "funding": [ { "type": "github", @@ -14633,21 +11214,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "license": "MIT" - }, "node_modules/neotraverse": { "version": "0.6.18", "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz", @@ -14677,15 +11243,15 @@ "license": "MIT" }, "node_modules/node-mock-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.4.tgz", - "integrity": "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.5.tgz", + "integrity": "sha512-KQyt/wLjG3TAc7DOUhpqWzgd4ERxR80JOlTK5VE5R1S12IaPVN5qkj4klBce9HPG1Njuup4Sb5bljaT34lIyjw==", "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.51", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", - "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "version": "2.0.53", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz", + "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==", "license": "MIT", "engines": { "node": ">=18" @@ -14709,18 +11275,6 @@ "node": ">=0.10.0" } }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -14742,51 +11296,17 @@ "node": ">=0.10.0" } }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/obug": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz", - "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", "funding": [ "https://github.com/sponsors/sxzz", "https://opencollective.com/debug" ], "license": "MIT", "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/octokit": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/octokit/-/octokit-5.0.5.tgz", - "integrity": "sha512-4+/OFSqOjoyULo7eN7EA97DE0Xydj/PW5aIckxqQIoFjFwqXKuFCvXUJObyJfBF9Khu4RL/jlDRI9FPaMGfPnw==", - "license": "MIT", - "dependencies": { - "@octokit/app": "^16.1.2", - "@octokit/core": "^7.0.6", - "@octokit/oauth-app": "^8.0.3", - "@octokit/plugin-paginate-graphql": "^6.0.0", - "@octokit/plugin-paginate-rest": "^14.0.0", - "@octokit/plugin-rest-endpoint-methods": "^17.0.0", - "@octokit/plugin-retry": "^8.0.3", - "@octokit/plugin-throttling": "^11.0.3", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "@octokit/webhooks": "^14.0.0" - }, - "engines": { - "node": ">= 20" + "node": ">=12.20.0" } }, "node_modules/ofetch": { @@ -14818,30 +11338,6 @@ "node": ">= 0.8" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/oniguruma-parser": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz", @@ -14859,50 +11355,6 @@ "regex-recursion": "^6.0.2" } }, - "node_modules/open": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", - "license": "MIT", - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open/node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open/node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/p-limit": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz", @@ -14947,9 +11399,9 @@ } }, "node_modules/package-manager-detector": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.7.0.tgz", - "integrity": "sha512-xg1eHpwYL/D/HEdWw2goFZP6vV0FH7W+PZ5rFkGjdIDLtxq7EkzBUeT3m+lndYCt8wKbmofUu1MUdMCXkCk9ZQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.8.0.tgz", + "integrity": "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==", "license": "MIT" }, "node_modules/pagefind": { @@ -15013,28 +11465,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/parse-path": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.1.0.tgz", - "integrity": "sha512-EuCycjZtfPcjWk7KTksnJ5xPMvWGA/6i4zrLYhRG0hGvC3GPU/jGUj3Cy+ZR0v30duV3e23R95T1lE2+lsndSw==", - "license": "MIT", - "dependencies": { - "protocols": "^2.0.0" - } - }, - "node_modules/parse-url": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-9.2.0.tgz", - "integrity": "sha512-bCgsFI+GeGWPAvAiUv63ZorMeif3/U0zaXABGJbOWt5OH2KCaPHF6S+0ok4aqM9RuIPGyZdx9tR9l13PsW4AYQ==", - "license": "MIT", - "dependencies": { - "@types/parse-path": "^7.0.0", - "parse-path": "^7.0.0" - }, - "engines": { - "node": ">=14.13.0" - } - }, "node_modules/parse5": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", @@ -15047,165 +11477,18 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/patch-console": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz", - "integrity": "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, "node_modules/path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", "license": "MIT" }, - "node_modules/path-expression-matcher": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.2.tgz", - "integrity": "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-to-regexp": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", - "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "license": "MIT" }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "license": "MIT" - }, - "node_modules/pg": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.22.0.tgz", - "integrity": "sha512-8wih1vVIBMxoUM2oB4soJsD9tDnDpLv4OXBJ+EJzFsvycD+lfyIreC2gGHq78f8jbLLt+bvlPTFdFZfJkOuzAA==", - "license": "MIT", - "dependencies": { - "pg-connection-string": "^2.14.0", - "pg-pool": "^3.14.0", - "pg-protocol": "^1.15.0", - "pg-types": "2.2.0", - "pgpass": "1.0.5" - }, - "engines": { - "node": ">= 16.0.0" - }, - "optionalDependencies": { - "pg-cloudflare": "^1.4.0" - }, - "peerDependencies": { - "pg-native": ">=3.0.1" - }, - "peerDependenciesMeta": { - "pg-native": { - "optional": true - } - } - }, - "node_modules/pg-cloudflare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz", - "integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==", - "license": "MIT", - "optional": true - }, - "node_modules/pg-connection-string": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.14.0.tgz", - "integrity": "sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg==", - "license": "MIT" - }, - "node_modules/pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", - "license": "ISC", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/pg-pool": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", - "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", - "license": "MIT", - "peerDependencies": { - "pg": ">=8.0" - } - }, - "node_modules/pg-protocol": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.15.0.tgz", - "integrity": "sha512-cq9sECI5s0+uPUXjbz8ioyPJni6RzsRib0US67i5IoTZKw8fNeYlVE7u8F4dG7vEJJtc5wdD1K189lCCUwqWTQ==", - "license": "MIT" - }, - "node_modules/pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "license": "MIT", - "dependencies": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pgpass": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", - "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", - "license": "MIT", - "dependencies": { - "split2": "^4.1.0" - } - }, "node_modules/piccolore": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz", @@ -15230,58 +11513,10 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/picospinner": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/picospinner/-/picospinner-3.0.0.tgz", - "integrity": "sha512-lGA1TNsmy2bxvRsTI2cV01kfTwKzZjnZSDmF9llYNyMHMrU4sP87lQ5taiIKm88L3cbswjl008nwyGc3WpNvzg==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/pkce-challenge": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", - "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", - "license": "MIT", - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/playwright": { - "version": "1.61.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", - "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", - "license": "Apache-2.0", - "dependencies": { - "playwright-core": "1.61.1" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/playwright-core": { - "version": "1.61.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", - "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/postcss": { - "version": "8.5.17", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.17.tgz", - "integrity": "sha512-J7EF+8X+CzRPaJPOv9Ck2wNWJvGnnl3PcNPAdGg6GTLjyVpyQ0yATMSXRFRV01BviT/9Gwuc3rjEyJbDJG9a4w==", + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", "funding": [ { "type": "opencollective", @@ -15298,7 +11533,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -15306,65 +11541,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postcss-modules-extract-imports": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", - "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", - "license": "ISC", - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-local-by-default": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", - "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", - "license": "MIT", - "dependencies": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^7.0.0", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-scope": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", - "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", - "license": "ISC", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", - "license": "ISC", - "dependencies": { - "icss-utils": "^5.0.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, "node_modules/postcss-nested": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", @@ -15374,97 +11550,39 @@ "type": "opencollective", "url": "https://opencollective.com/postcss/" }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.1.1" - }, - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "postcss": "^8.2.14" - } - }, - "node_modules/postcss-nested/node_modules/postcss-selector-parser": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", - "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-selector-parser": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", - "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "license": "MIT" - }, - "node_modules/postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/postgres-bytea": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", - "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-date": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" } }, - "node_modules/postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "node_modules/postcss-nested/node_modules/postcss-selector-parser": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", + "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", "license": "MIT", "dependencies": { - "xtend": "^4.0.0" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, "node_modules/prettier": { - "version": "3.9.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.5.tgz", - "integrity": "sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==", + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", + "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" @@ -15534,103 +11652,12 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/protocols": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.2.tgz", - "integrity": "sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ==", - "license": "MIT" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/pump": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", - "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.15.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", - "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", - "license": "BSD-3-Clause", - "dependencies": { - "es-define-property": "^1.0.1", - "side-channel": "^1.1.1" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/radix3": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", "license": "MIT" }, - "node_modules/railway": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/railway/-/railway-3.4.1.tgz", - "integrity": "sha512-SKzV9Ugyo1Q1RqQo+cYM3mCsIR69g9qMQBpSC6HKoakdFHgDI6p2EUQ+Sq9vJfuQP+ABkaKL29hixWreZoc/dA==", - "license": "MIT", - "dependencies": { - "@graphql-typed-document-node/core": "^3.2.0", - "graphql": "^16.10.0", - "tsx": "^4.20.0" - }, - "bin": { - "railway-iac-ts": "dist/iac/bin.js" - }, - "engines": { - "node": ">=22" - } - }, "node_modules/range-parser": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", @@ -15644,63 +11671,33 @@ "url": "https://opencollective.com/express" } }, - "node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/react": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", - "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", - "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", "license": "MIT", "peer": true, "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.7" - } - }, - "node_modules/react-error-boundary": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz", - "integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.5" - }, - "engines": { - "node": ">=10", - "npm": ">=6" - }, - "peerDependencies": { - "react": ">=16.13.1" + "react": "^19.2.8" } }, "node_modules/react-hook-form": { - "version": "7.81.0", - "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.81.0.tgz", - "integrity": "sha512-ocbmr2p5KBMoAfj4WCUvped33lVi1Kd5DuDUvQDnB6VEAacOjPI/jMbtDdbhco4y9ct4xUuCmMY0b/C9L0QHjw==", + "version": "7.85.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.85.0.tgz", + "integrity": "sha512-U2MTriFXnclmV4rOE20p2DcRFv5WEg3FIcBFOKcOLFHDVvGIMPvLTkTWefUsonmlaVy23khVDxDWym6uJVGOzw==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -15714,27 +11711,12 @@ } }, "node_modules/react-is": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz", - "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz", + "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==", "license": "MIT", "peer": true }, - "node_modules/react-reconciler": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.33.0.tgz", - "integrity": "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==", - "license": "MIT", - "dependencies": { - "scheduler": "^0.27.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "peerDependencies": { - "react": "^19.2.0" - } - }, "node_modules/react-redux": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz", @@ -15759,15 +11741,6 @@ } } }, - "node_modules/react-refresh": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", - "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/react-remove-scroll": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", @@ -15851,9 +11824,9 @@ } }, "node_modules/recharts": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.9.2.tgz", - "integrity": "sha512-G4fy+Pk46RaXgwWMh+Nzhyo/lbFAVqXo9gtetlyehe6Ehge9CsgDuOTwQDD+i1+llaLktNBiNq4bhnGlDRXFtw==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.10.1.tgz", + "integrity": "sha512-QXFrvt6IVcw7eeZCoyXTwkIJAX3Dv1nyVhMicXJ47GsGDDpcN8z6o644DibE9XjpBTThtsomLKnTV6lc+cVFUA==", "license": "MIT", "peer": true, "workspaces": [ @@ -16221,9 +12194,9 @@ } }, "node_modules/remark-smartypants": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-3.0.2.tgz", - "integrity": "sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-3.0.3.tgz", + "integrity": "sha512-gCaK+ndZ0hYezlqFegHFCVh2CQemsi0Npdh1qVM9bxlUFknjkbP6VmojWhddOCrbK0PbbacmYLWfTULRiT1eWA==", "license": "MIT", "dependencies": { "retext": "^9.0.0", @@ -16250,145 +12223,12 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/remotion": { - "version": "4.0.477", - "resolved": "https://registry.npmjs.org/remotion/-/remotion-4.0.477.tgz", - "integrity": "sha512-mxQ8MHmx9YUpxyTSm8hLZW/LGvOzk9b19i02DT9MKKFv+H6ZXkd/1Cn+a908fZWCqM4RRCIy3poXB8KsHiPdow==", - "license": "SEE LICENSE IN LICENSE.md", - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/repomix": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/repomix/-/repomix-1.16.1.tgz", - "integrity": "sha512-xOcuPTo6KstKWxL08wQ5m3PQP0MyHzVU8vsAgsAq4LGAx64nNDhKVZXVdxmFiG2CqiYRqJqhwMoVBkWA32FSow==", - "license": "MIT", - "dependencies": { - "@clack/prompts": "^0.11.0", - "@modelcontextprotocol/sdk": "^1.29.0", - "@repomix/strip-comments": "^2.4.2", - "@repomix/tree-sitter-wasms": "^0.1.17", - "@secretlint/core": "^13.0.2", - "@secretlint/secretlint-rule-preset-recommend": "^13.0.2", - "chokidar": "^5.0.0", - "commander": "^15.0.0", - "fast-xml-builder": "^1.2.1", - "git-url-parse": "^16.1.0", - "globby": "^16.2.0", - "gpt-tokenizer": "^3.4.0", - "handlebars": "^4.7.9", - "iconv-lite": "^0.7.0", - "is-binary-path": "^3.0.0", - "isbinaryfile": "^5.0.2", - "jiti": "^2.7.0", - "jschardet": "^3.1.4", - "json5": "^2.2.3", - "minimatch": "^10.2.5", - "picocolors": "^1.1.1", - "picospinner": "^3.0.0", - "tar": "^7.5.19", - "tinyclip": "^0.1.15", - "tinypool": "^2.1.0", - "valibot": "^1.4.2", - "web-tree-sitter": "^0.26.10", - "zod": "^4.4.3" - }, - "bin": { - "repomix": "bin/repomix.cjs" - }, - "engines": { - "node": ">=22.0.0", - "yarn": ">=1.22.22" - } - }, - "node_modules/repomix/node_modules/chokidar": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", - "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", - "license": "MIT", - "dependencies": { - "readdirp": "^5.0.0" - }, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/repomix/node_modules/commander": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", - "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", - "license": "MIT", - "engines": { - "node": ">=22.12.0" - } - }, - "node_modules/repomix/node_modules/readdirp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", - "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", - "license": "MIT", - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/repomix/node_modules/tar": { - "version": "7.5.19", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.19.tgz", - "integrity": "sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/repomix/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/repomix/node_modules/zod": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", - "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, "node_modules/request-light": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/request-light/-/request-light-0.7.0.tgz", "integrity": "sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q==", "license": "MIT" }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -16405,22 +12245,6 @@ "license": "MIT", "peer": true }, - "node_modules/restore-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", - "license": "MIT", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/retext": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz", @@ -16482,20 +12306,10 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, "node_modules/rollup": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", - "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.4.tgz", + "integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==", "license": "MIT", "dependencies": { "@types/estree": "1.0.9" @@ -16508,73 +12322,35 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.62.2", - "@rollup/rollup-android-arm64": "4.62.2", - "@rollup/rollup-darwin-arm64": "4.62.2", - "@rollup/rollup-darwin-x64": "4.62.2", - "@rollup/rollup-freebsd-arm64": "4.62.2", - "@rollup/rollup-freebsd-x64": "4.62.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", - "@rollup/rollup-linux-arm-musleabihf": "4.62.2", - "@rollup/rollup-linux-arm64-gnu": "4.62.2", - "@rollup/rollup-linux-arm64-musl": "4.62.2", - "@rollup/rollup-linux-loong64-gnu": "4.62.2", - "@rollup/rollup-linux-loong64-musl": "4.62.2", - "@rollup/rollup-linux-ppc64-gnu": "4.62.2", - "@rollup/rollup-linux-ppc64-musl": "4.62.2", - "@rollup/rollup-linux-riscv64-gnu": "4.62.2", - "@rollup/rollup-linux-riscv64-musl": "4.62.2", - "@rollup/rollup-linux-s390x-gnu": "4.62.2", - "@rollup/rollup-linux-x64-gnu": "4.62.2", - "@rollup/rollup-linux-x64-musl": "4.62.2", - "@rollup/rollup-openbsd-x64": "4.62.2", - "@rollup/rollup-openharmony-arm64": "4.62.2", - "@rollup/rollup-win32-arm64-msvc": "4.62.2", - "@rollup/rollup-win32-ia32-msvc": "4.62.2", - "@rollup/rollup-win32-x64-gnu": "4.62.2", - "@rollup/rollup-win32-x64-msvc": "4.62.2", + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.62.4", + "@rollup/rollup-android-arm64": "4.62.4", + "@rollup/rollup-darwin-arm64": "4.62.4", + "@rollup/rollup-darwin-x64": "4.62.4", + "@rollup/rollup-freebsd-arm64": "4.62.4", + "@rollup/rollup-freebsd-x64": "4.62.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.4", + "@rollup/rollup-linux-arm-musleabihf": "4.62.4", + "@rollup/rollup-linux-arm64-gnu": "4.62.4", + "@rollup/rollup-linux-arm64-musl": "4.62.4", + "@rollup/rollup-linux-loong64-gnu": "4.62.4", + "@rollup/rollup-linux-loong64-musl": "4.62.4", + "@rollup/rollup-linux-ppc64-gnu": "4.62.4", + "@rollup/rollup-linux-ppc64-musl": "4.62.4", + "@rollup/rollup-linux-riscv64-gnu": "4.62.4", + "@rollup/rollup-linux-riscv64-musl": "4.62.4", + "@rollup/rollup-linux-s390x-gnu": "4.62.4", + "@rollup/rollup-linux-x64-gnu": "4.62.4", + "@rollup/rollup-linux-x64-musl": "4.62.4", + "@rollup/rollup-openbsd-x64": "4.62.4", + "@rollup/rollup-openharmony-arm64": "4.62.4", + "@rollup/rollup-win32-arm64-msvc": "4.62.4", + "@rollup/rollup-win32-ia32-msvc": "4.62.4", + "@rollup/rollup-win32-x64-gnu": "4.62.4", + "@rollup/rollup-win32-x64-msvc": "4.62.4", "fsevents": "~2.3.2" } }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, "node_modules/sade": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", @@ -16587,63 +12363,22 @@ "node": ">=6" } }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, "node_modules/sax": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", - "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=11.0.0" - } - }, - "node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT" - }, - "node_modules/schema-utils": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", - "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/schema-utils/node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" } }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT", + "peer": true + }, "node_modules/semver": { "version": "7.5.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", @@ -16703,24 +12438,11 @@ "url": "https://opencollective.com/express" } }, - "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } + "node_modules/server-destroy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz", + "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==", + "license": "ISC" }, "node_modules/setprototypeof": { "version": "1.2.0", @@ -16784,27 +12506,6 @@ "node": ">=10" } }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/shiki": { "version": "3.23.0", "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.23.0.tgz", @@ -16821,90 +12522,12 @@ "@types/hast": "^3.0.4" } }, - "node_modules/side-channel": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", - "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4", - "side-channel-list": "^1.0.1", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "license": "ISC" }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC" - }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -16936,38 +12559,10 @@ "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", "license": "MIT" }, - "node_modules/slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/slice-ansi": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-9.0.0.tgz", - "integrity": "sha512-SO/3iYL5S3W57LLEniscOGPZgOqZUPCx6d3dB+52B80yJ0XstzsC/eV8gnA4tM3MHDrKz+OCFSLNjswdSC+/bA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.3", - "is-fullwidth-code-point": "^5.1.0" - }, - "engines": { - "node": ">=22" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, "node_modules/smol-toml": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.7.0.tgz", - "integrity": "sha512-aqVvWoyO21L23mb+drl4RmMXbf6N7FdHjAhTRA9ZBL7apWBgfWC16KjrASI+1p9GAroljyMHj6fK67i0UiTNvQ==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.7.1.tgz", + "integrity": "sha512-PPlsspAZ4jbMBu5DMFhfUGDQLu/vrL4SyBROVS37x8ynnVmFIs1VPBz1Co8Xks3TvpIaZXmU85y4DrQ+UyVFoQ==", "license": "BSD-3-Clause", "engines": { "node": ">= 18" @@ -16976,15 +12571,6 @@ "url": "https://github.com/sponsors/cyyynthia" } }, - "node_modules/source-map": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", - "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">= 12" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -16994,25 +12580,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/space-separated-tokens": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", @@ -17023,48 +12590,12 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "license": "ISC", - "engines": { - "node": ">= 10.x" - } - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "license": "MIT" }, - "node_modules/stackframe": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", - "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", - "license": "MIT" - }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -17132,40 +12663,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/structured-source": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/structured-source/-/structured-source-4.0.0.tgz", - "integrity": "sha512-qGzRFNJDjFieQkl/sVOI2dUjHKRyL9dAJi2gCPGJLbJHBIkyOHxjuocpIEfbLioX+qSJpvbYdT49/YCdMznKxA==", - "license": "BSD-2-Clause", - "dependencies": { - "boundary": "^2.0.0" - } - }, - "node_modules/style-loader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", - "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", - "license": "MIT", - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.27.0" - } - }, "node_modules/style-mod": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", @@ -17190,21 +12687,6 @@ "inline-style-parser": "0.2.7" } }, - "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/svgo": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.2.tgz", @@ -17245,22 +12727,10 @@ "integrity": "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==", "license": "MIT" }, - "node_modules/tagged-tag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", - "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", - "license": "MIT", - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/tailwindcss": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.2.tgz", - "integrity": "sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", "license": "MIT" }, "node_modules/tapable": { @@ -17276,127 +12746,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/tar": { - "version": "7.5.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz", - "integrity": "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tar/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/terminal-size": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/terminal-size/-/terminal-size-4.0.1.tgz", - "integrity": "sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/terser": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.49.0.tgz", - "integrity": "sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA==", - "license": "BSD-2-Clause", - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.15.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.6.1.tgz", - "integrity": "sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "jest-worker": "^27.4.5", - "schema-utils": "^4.3.0", - "terser": "^5.31.1" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@minify-html/node": { - "optional": true - }, - "@swc/core": { - "optional": true - }, - "@swc/css": { - "optional": true - }, - "@swc/html": { - "optional": true - }, - "clean-css": { - "optional": true - }, - "cssnano": { - "optional": true - }, - "csso": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "html-minifier-terser": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "postcss": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "license": "MIT" - }, "node_modules/tiny-inflate": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", @@ -17416,19 +12765,10 @@ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", "license": "MIT" }, - "node_modules/tinyclip": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/tinyclip/-/tinyclip-0.1.15.tgz", - "integrity": "sha512-uo33abH+Ays0xYaDysoBt494Hb3hsEczMpcC0MwFl773pazORx4fmvKhclhR1wonUbB6vvpRsvVMwnhfqeMc+A==", - "license": "MIT", - "engines": { - "node": "^16.14.0 || >= 17.3.0" - } - }, "node_modules/tinyexec": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", - "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", "license": "MIT", "engines": { "node": ">=18" @@ -17450,45 +12790,15 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinypool": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-2.1.0.tgz", - "integrity": "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==", - "license": "MIT", - "engines": { - "node": "^20.0.0 || >=22.0.0" - } - }, "node_modules/tinyrainbow": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", "license": "MIT", "engines": { "node": ">=14.0.0" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toad-cache": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.4.tgz", - "integrity": "sha512-m1TdR/rvT7kgGJZhspNtXdsdYk0fddFpJJFlG5s+UkPFo6lkLoZ3YLOaovPYjq1R75NP5JfeTlSHaOsE09peCg==", - "license": "MIT", - "engines": { - "node": ">=20" - } - }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -17498,15 +12808,6 @@ "node": ">=0.6" } }, - "node_modules/tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", - "license": "MIT", - "dependencies": { - "punycode": "^2.1.0" - } - }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -17555,9 +12856,10 @@ "license": "0BSD" }, "node_modules/tsx": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.0.tgz", - "integrity": "sha512-eUdUIaCr963q2h5u3+QwvYp0+eqPvn+egeqZUm0hwERCqqx1E3kK5ehbGCvqSE5MQAULr67ww0cA3jKc3YkM1w==", + "version": "4.23.11", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.11.tgz", + "integrity": "sha512-Ry2oTEUnhBdeEdWIztY8kf3/nBGnPnjMLVGL0YfdRXMORuPER5NlKmayqxtxRxwB1xBN+RivRaJfe7PM1rtiyw==", + "devOptional": true, "license": "MIT", "dependencies": { "esbuild": "~0.28.0" @@ -17576,6 +12878,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -17598,24 +12901,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/type-is": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", - "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", - "license": "MIT", - "dependencies": { - "content-type": "^2.0.0", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/typesafe-path": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/typesafe-path/-/typesafe-path-0.2.2.tgz", @@ -17650,19 +12935,6 @@ "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", "license": "MIT" }, - "node_modules/uglify-js": { - "version": "3.19.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", - "license": "BSD-2-Clause", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/ultrahtml": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.7.0.tgz", @@ -17676,9 +12948,9 @@ "license": "MIT" }, "node_modules/undici": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", - "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", + "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", "license": "MIT", "engines": { "node": ">=20.18.1" @@ -17699,18 +12971,6 @@ "pathe": "^2.0.3" } }, - "node_modules/unicorn-magic": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz", - "integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==", - "license": "MIT", - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/unidiff": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unidiff/-/unidiff-1.0.4.tgz", @@ -17895,27 +13155,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/universal-github-app-jwt": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-2.2.2.tgz", - "integrity": "sha512-dcmbeSrOdTnsjGjUfAlqNDJrhxXizjAz94ija9Qw8YkZ1uu0d+GoZzyH+Jb9tIIqvGsadUfwg+22k5aDqqwzbw==", - "license": "MIT" - }, - "node_modules/universal-user-agent": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", - "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", - "license": "ISC" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/unstorage": { "version": "1.17.5", "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.5.tgz", @@ -18037,9 +13276,9 @@ } }, "node_modules/unstorage/node_modules/readdirp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", - "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", "license": "MIT", "engines": { "node": ">= 20.19.0" @@ -18050,9 +13289,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.0.tgz", + "integrity": "sha512-x/M6q3w4Ybp91CNaS4S69UnliqR3BzRpOT6LWbksjth0S/+jhfaPJsWjt/TewpT8j9eLIojUf5jr29WextHroA==", "funding": [ { "type": "opencollective", @@ -18165,29 +13404,6 @@ "node": ">=0.3.1" } }, - "node_modules/valibot": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.4.2.tgz", - "integrity": "sha512-gjdCvJ6d3RyHAneqxMYMW9QMCwYMb3jpOO0IyHZV1bnRHFBHrX3VkIILt5XYR0WhwHiH7Mty8ovuPZ/O3gamrg==", - "license": "MIT", - "peerDependencies": { - "typescript": ">=5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", @@ -18907,9 +14123,9 @@ } }, "node_modules/vitest/node_modules/es-module-lexer": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.0.tgz", - "integrity": "sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", "license": "MIT" }, "node_modules/volar-service-css": { @@ -19112,15 +14328,6 @@ "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", "license": "MIT" }, - "node_modules/vscode-jsonrpc": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.1.tgz", - "integrity": "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/vscode-languageserver": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", @@ -19207,18 +14414,6 @@ "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", "license": "MIT" }, - "node_modules/watchpack": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz", - "integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/web-namespaces": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", @@ -19229,128 +14424,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/web-tree-sitter": { - "version": "0.26.10", - "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.26.10.tgz", - "integrity": "sha512-vengBGYS7FpAerkR3o04oBL4L8MkVmjawK50AFBu7v0HZBkmF9ZavPGKoXLSSmRhp7T/YgsJ7joAS3yAxHPEqQ==", - "license": "MIT" - }, - "node_modules/webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "license": "BSD-2-Clause" - }, - "node_modules/webpack": { - "version": "5.105.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", - "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", - "license": "MIT", - "dependencies": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.8", - "@types/json-schema": "^7.0.15", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.15.0", - "acorn-import-phases": "^1.0.3", - "browserslist": "^4.28.1", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.19.0", - "es-module-lexer": "^2.0.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.3.1", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^4.3.3", - "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.16", - "watchpack": "^2.5.1", - "webpack-sources": "^3.3.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-sources": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.1.tgz", - "integrity": "sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw==", - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack/node_modules/es-module-lexer": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.0.tgz", - "integrity": "sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==", - "license": "MIT" - }, - "node_modules/webpack/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "license": "MIT", - "dependencies": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/which-pm-runs": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", @@ -19391,16 +14464,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "license": "MIT" - }, "node_modules/workerd": { - "version": "1.20260708.1", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260708.1.tgz", - "integrity": "sha512-WAK+Kt/VVCSldH2qSr8lx46XCJ4Q+bdlHNaFqUtOHthBEIB8C1N8HVW+VOLrxDoTCk0NGNv0zajnBeQK4JOB9w==", + "version": "1.20260801.1", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260801.1.tgz", + "integrity": "sha512-/g9JGTyqnHtoIscpBHqKD8swE2V4StBs2i69PmLiOhH45OP95jCFICl4F1hKlgN57rqfni5LiCitttoX5OOkVA==", "hasInstallScript": true, "license": "Apache-2.0", "bin": { @@ -19410,27 +14477,27 @@ "node": ">=16" }, "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20260708.1", - "@cloudflare/workerd-darwin-arm64": "1.20260708.1", - "@cloudflare/workerd-linux-64": "1.20260708.1", - "@cloudflare/workerd-linux-arm64": "1.20260708.1", - "@cloudflare/workerd-windows-64": "1.20260708.1" + "@cloudflare/workerd-darwin-64": "1.20260801.1", + "@cloudflare/workerd-darwin-arm64": "1.20260801.1", + "@cloudflare/workerd-linux-64": "1.20260801.1", + "@cloudflare/workerd-linux-arm64": "1.20260801.1", + "@cloudflare/workerd-windows-64": "1.20260801.1" } }, "node_modules/wrangler": { - "version": "4.110.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.110.0.tgz", - "integrity": "sha512-xZeXKYi7hxQRF5anL+v77RkufJNpF9f3Eqeyqq2QBsETpLZgh0Agj0jJ6JPtkbgn6ukZdh8OK5egsGPWIditgg==", + "version": "4.120.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.120.0.tgz", + "integrity": "sha512-cBmu/MeaB/fPacC0JpATs4duTOCagBxrZo+vBzuTX06tLzwSyAHE1drlHUZ8rP0VqVz1fy3ReGYTiHdKkoHltg==", "license": "MIT OR Apache-2.0", "dependencies": { "@cloudflare/kv-asset-handler": "0.5.0", "@cloudflare/unenv-preset": "2.16.1", "blake3-wasm": "2.1.5", "esbuild": "0.28.1", - "miniflare": "4.20260708.1", + "miniflare": "5.20260801.1-alpha", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", - "workerd": "1.20260708.1" + "workerd": "1.20260801.1" }, "bin": { "cf-wrangler": "bin/cf-wrangler.js", @@ -19444,7 +14511,7 @@ "fsevents": "2.3.3" }, "peerDependencies": { - "@cloudflare/workers-types": "^5.20260708.1" + "@cloudflare/workers-types": "^5.20260801.1" }, "peerDependenciesMeta": { "@cloudflare/workers-types": { @@ -19489,57 +14556,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "node_modules/ws": { - "version": "8.20.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", - "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xml-naming": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.3.0.tgz", - "integrity": "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, "node_modules/xxhash-wasm": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", @@ -19621,21 +14637,20 @@ } }, "node_modules/yargs": { - "version": "17.7.3", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", - "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.1.0.tgz", + "integrity": "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==", "license": "MIT", "dependencies": { - "cliui": "^8.0.1", + "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", + "string-width": "^8.2.1", "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "yargs-parser": "^22.0.0" }, "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yargs-parser": { @@ -19647,70 +14662,35 @@ "node": ">=12" } }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", + "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" }, "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" + "node": ">=20" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" + "node_modules/yargs/node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yjs": { - "version": "13.6.31", - "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.31.tgz", - "integrity": "sha512-Eq+5BRfbeGyqGVrTJL3bEcr8gKkxPuyuoHmAwpk52fDb8kOVMrfVSTRPd6yiGgX5Fskb96qCRjzjbRjrL4YEnw==", + "version": "13.6.32", + "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.32.tgz", + "integrity": "sha512-lfiJIIC4Xayt5ItynE407ehlE03pCjeOc4hkR4yxxvvNJ4kuiN25B0g+Qp8XagYz361LLL7DCzR5bvFJ81QKtQ==", "license": "MIT", "peer": true, "dependencies": { @@ -19753,9 +14733,9 @@ } }, "node_modules/yoctocolors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", - "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.2.0.tgz", + "integrity": "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==", "license": "MIT", "engines": { "node": ">=18" @@ -19764,12 +14744,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yoga-layout": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", - "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", - "license": "MIT" - }, "node_modules/youch": { "version": "4.1.0-beta.10", "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", diff --git a/package.json b/package.json index 089f764d..297113fc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@treeseed/core", - "version": "0.12.59", + "version": "0.12.60-rc.8", "description": "Treeseed web framework package for Astro/Starlight site runtimes.", "license": "Apache-2.0", "repository": { @@ -20,7 +20,7 @@ "files": [ "README.md", "dist", - "scripts/verify-driver.ts", + "scripts/support/verify-driver.ts", "tsconfigs", "templates", "style", @@ -30,49 +30,53 @@ "access": "public" }, "bin": { - "treeseed-workspace-bootstrap": "./dist/scripts/workspace-bootstrap.js" + "treeseed-workspace-bootstrap": "./dist/scripts/treedx/workspaces/workspace-bootstrap.js" }, "scripts": { "setup": "npm install", "setup:ci": "npm ci", - "build:dist": "node --import tsx ./scripts/build-dist.ts", - "prepare": "node --import tsx ./scripts/prepare.ts", + "build:dist": "node --import tsx ./scripts/build/build-dist.ts", + "check:file-lengths": "node --import tsx ./scripts/support/check-file-lengths.ts && npm run check:file-architecture", + "check:file-architecture": "node --import tsx ./scripts/support/check-file-architecture.ts", + "prepare": "node --import tsx ./scripts/support/prepare.ts", "prepack": "npm run build:dist", - "dev": "node --import tsx ./scripts/dev-platform.ts", - "dev:web": "node --import tsx ./scripts/dev-platform.ts --surface web", - "dev:watch": "node --import tsx ./scripts/dev-platform.ts --watch", - "starlight:patch": "node --import tsx ./scripts/patch-starlight-content-path.ts", + "dev": "node --import tsx ./scripts/runtime/dev-platform.ts", + "dev:web": "node --import tsx ./scripts/runtime/dev-platform.ts --surface web", + "dev:watch": "node --import tsx ./scripts/runtime/dev-platform.ts --watch", + "starlight:patch": "node --import tsx ./scripts/content/patch-starlight-content-path.ts", "precheck": "npm run starlight:patch", - "check": "node --import tsx ./scripts/run-fixture-astro-command.ts check", - "prebuild": "npm run starlight:patch && node --import tsx ./scripts/aggregate-book.ts", - "build": "TREESEED_LOCAL_DEV_MODE=cloudflare node --import tsx ./scripts/run-fixture-astro-command.ts build", - "preview": "node --import tsx ./scripts/run-fixture-astro-command.ts preview", + "check": "npm run check:file-lengths && node --import tsx ./scripts/testing/run-fixture-astro-command.ts check", + "prebuild": "npm run starlight:patch", + "build": "TREESEED_LOCAL_DEV_MODE=cloudflare node --import tsx ./scripts/testing/run-fixture-astro-command.ts build", + "preview": "node --import tsx ./scripts/testing/run-fixture-astro-command.ts preview", "test:unit": "vitest run --config ./vitest.config.ts", "test": "npm run test:unit", "lint": "npm run fixtures:check && npm run starlight:patch && npm run build:dist", "verify:direct": "npm run release:verify", - "verify:local": "TREESEED_VERIFY_DRIVER=direct TMPDIR=/tmp node --import tsx ./scripts/verify-driver.ts", - "verify:action": "TREESEED_VERIFY_DRIVER=act TMPDIR=/tmp node --import tsx ./scripts/verify-driver.ts", - "verify": "TMPDIR=/tmp node --import tsx ./scripts/verify-driver.ts", - "test:smoke": "node --import tsx ./scripts/test-smoke.ts", - "fixtures:resolve": "node --import tsx ./scripts/fixture-tools.ts resolve", - "fixtures:check": "node --import tsx ./scripts/fixture-tools.ts check", + "verify:local": "TREESEED_VERIFY_DRIVER=direct TMPDIR=/tmp node --import tsx ./scripts/support/verify-driver.ts", + "verify:action": "TREESEED_VERIFY_DRIVER=act TMPDIR=/tmp node --import tsx ./scripts/support/verify-driver.ts", + "verify": "TMPDIR=/tmp node --import tsx ./scripts/support/verify-driver.ts", + "test:smoke": "node --import tsx ./scripts/testing/test-smoke.ts", + "fixtures:resolve": "node --import tsx ./scripts/agents/fixture-tools.ts resolve", + "fixtures:check": "node --import tsx ./scripts/agents/fixture-tools.ts check", "release:setup": "npm run setup:ci", - "release:check-tag": "node --import tsx ./scripts/assert-release-tag-version.ts", - "release:verify": "node --import tsx ./scripts/release-verify.ts", - "release:publish": "node --import tsx ./scripts/publish-package.ts" + "release:check-tag": "node --import tsx ./scripts/packages/assert-release-tag-version.ts", + "release:verify": "npm run check:file-lengths && node --import tsx ./scripts/packages/release-verify.ts", + "release:custody": "node --import tsx ./scripts/packages/release-custody.ts", + "release:publish": "node --import tsx ./scripts/packages/publish-package.ts" }, "dependencies": { "@astrojs/check": "^0.9.8", "@astrojs/cloudflare": "^12.6.13", + "@astrojs/node": "^9.4.3", "@astrojs/markdown-remark": "^6.3.11", "@astrojs/mdx": "^4.3.14", "@astrojs/react": "^4.4.2", "@astrojs/sitemap": "3.7.0", "@astrojs/starlight": "0.37.6", "@tailwindcss/vite": "^4.1.4", - "@treeseed/sdk": "0.12.62", - "@treeseed/ui": "0.12.17", + "@treeseed/sdk": "0.13.0-rc.122", + "@treeseed/ui": ">=0.12.18-rc.11 <0.14.0", "astro": "^5.6.1", "esbuild": "^0.28.0", "katex": "^0.16.22", @@ -102,7 +106,11 @@ }, "./site": { "types": "./dist/site.d.ts", - "default": "./dist/site.js" + "default": "./dist/support/site.js" + }, + "./routes": { + "types": "./dist/routes.d.ts", + "default": "./dist/support/routes.js" }, "./config": { "types": "./dist/config.d.ts", @@ -110,19 +118,19 @@ }, "./platform": { "types": "./dist/platform.d.ts", - "default": "./dist/platform.js" + "default": "./dist/support/platform.js" }, "./dev": { - "types": "./dist/dev.d.ts", - "default": "./dist/dev.js" + "types": "./dist/runtime/dev.d.ts", + "default": "./dist/runtime/dev.js" }, "./plugin-default": { "types": "./dist/plugin-default.d.ts", - "default": "./dist/plugin-default.js" + "default": "./dist/support/plugin-default.js" }, "./content": { "types": "./dist/content.d.ts", - "default": "./dist/content.js" + "default": "./dist/content/content.js" }, "./content-config": { "types": "./dist/content-config.d.ts", @@ -130,32 +138,52 @@ }, "./templates": { "types": "./dist/templates.d.ts", - "default": "./dist/templates.js" + "default": "./dist/support/templates.js" }, "./middleware/editorial-preview": { "types": "./dist/middleware/editorial-preview.d.ts", "default": "./dist/middleware/editorial-preview.js" }, "./tenant": "./dist/tenant/bridge.js", - "./scripts/dev-platform": "./dist/scripts/dev-platform.js", - "./scripts/workspace-bootstrap": "./dist/scripts/workspace-bootstrap.js", + "./tenant-config": { + "types": "./dist/runtime/platform/tenant-config.d.ts", + "default": "./dist/runtime/platform/tenant-config.js" + }, + "./deploy-config": { + "types": "./dist/runtime/platform/deploy-config.d.ts", + "default": "./dist/runtime/platform/deploy-config.js" + }, + "./deploy-runtime": { + "types": "./dist/runtime/platform/deploy-runtime.d.ts", + "default": "./dist/runtime/platform/deploy-runtime.js" + }, + "./plugins": { + "types": "./dist/runtime/platform/plugins.d.ts", + "default": "./dist/runtime/platform/plugins.js" + }, + "./published-content": { + "types": "./dist/runtime/platform/published-content.d.ts", + "default": "./dist/runtime/platform/published-content.js" + }, + "./scripts/runtime/dev-platform": "./dist/scripts/runtime/dev-platform.js", + "./scripts/treedx/workspaces/workspace-bootstrap": "./dist/scripts/treedx/workspaces/workspace-bootstrap.js", "./launch": { - "types": "./dist/launch.d.ts", - "default": "./dist/launch.js" + "types": "./dist/support/launch.d.ts", + "default": "./dist/support/launch.js" }, "./site-resources": { - "types": "./dist/site-resources.d.ts", - "default": "./dist/site-resources.js" + "types": "./dist/support/site-resources.d.ts", + "default": "./dist/support/site-resources.js" }, "./types/cloudflare": { "types": "./dist/types/cloudflare.d.ts", "default": "./dist/types/cloudflare.js" }, - "./utils/seo": "./dist/utils/seo.js", - "./utils/site-config-schema": "./dist/utils/site-config-schema.js", + "./utils/seo": "./dist/utils/support/seo.js", + "./utils/site-config-schema": "./dist/utils/configuration/site-config-schema.js", "./utils/forms/service": { "types": "./dist/utils/forms/service.d.ts", - "default": "./dist/utils/forms/service.js" + "default": "./dist/utils/forms/service/service.js" }, "./tsconfigs/strict": "./dist/tsconfigs/strict.json" } diff --git a/scripts/fixture-tools.ts b/scripts/agents/fixture-tools.ts similarity index 97% rename from scripts/fixture-tools.ts rename to scripts/agents/fixture-tools.ts index 5443fcbc..ff861c8c 100644 --- a/scripts/fixture-tools.ts +++ b/scripts/agents/fixture-tools.ts @@ -1,6 +1,6 @@ import { existsSync, readdirSync, readFileSync } from 'node:fs'; import { join, resolve } from 'node:path'; -import { packageRoot } from './package-tools.ts'; +import { packageRoot } from '../packages/package-tools.ts'; type FixtureManifest = { id?: string; diff --git a/scripts/aggregate-book.ts b/scripts/aggregate-book.ts deleted file mode 100644 index 61ca7cdf..00000000 --- a/scripts/aggregate-book.ts +++ /dev/null @@ -1,19 +0,0 @@ -import path from 'node:path'; -import { exportTenantBookPackages } from '@treeseed/sdk/platform/book-export'; -import { PROJECT_TENANT } from '../src/tenant/bridge.ts'; - -async function main() { - console.log('Generating Treeseed AI book packages...'); - const result = await exportTenantBookPackages({ projectRoot: PROJECT_TENANT.__tenantRoot ?? process.cwd() }); - for (const entry of result.bookPackages) { - console.log(`Generated ${path.relative(result.projectRoot, entry.markdownPath)}`); - console.log(`Generated ${path.relative(result.projectRoot, entry.indexPath)}`); - } - console.log(`Generated ${path.relative(result.projectRoot, result.libraryPackage.markdownPath)}`); - console.log(`Generated ${path.relative(result.projectRoot, result.libraryPackage.indexPath)}`); -} - -main().catch((error) => { - console.error(error instanceof Error ? error.message : String(error)); - process.exitCode = 1; -}); diff --git a/scripts/build-dist.ts b/scripts/build-dist.ts deleted file mode 100644 index 3684bf8d..00000000 --- a/scripts/build-dist.ts +++ /dev/null @@ -1,529 +0,0 @@ -import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs'; -import { createRequire } from 'node:module'; -import { dirname, extname, join, relative, resolve } from 'node:path'; -import { build } from 'esbuild'; -import ts from 'typescript'; -import { packageRoot } from './package-tools.ts'; - -const require = createRequire(import.meta.url); -const srcRoot = resolve(packageRoot, 'src'); -const scriptsRoot = resolve(packageRoot, 'scripts'); -const distRoot = resolve(packageRoot, 'dist'); -const workspaceSdkDistRoot = resolve(packageRoot, '..', 'sdk', 'dist'); - -const JS_SOURCE_EXTENSIONS = new Set(['.ts', '.ts']); -const COPY_EXTENSIONS = new Set(['.astro', '.css', '.d.ts', '.js', '.json', '.jsonc', '.ts', '.yaml', '.yml']); - -function walkFiles(root) { - const files = []; - for (const entry of readdirSync(root, { withFileTypes: true })) { - const fullPath = join(root, entry.name); - if (entry.isDirectory()) { - files.push(...walkFiles(fullPath)); - continue; - } - files.push(fullPath); - } - return files; -} - -function ensureDir(filePath) { - mkdirSync(dirname(filePath), { recursive: true }); -} - -function rewriteRuntimeSpecifiers(contents) { - return contents - .replace(/(['"`])(\.[^'"`\n]+)\.(mjs|ts)\1/g, '$1$2.js$1') - .replaceAll('../../../../sdk/src/sdk.js', '@treeseed/sdk/sdk') - .replaceAll('../../../../sdk/src/sdk.ts', '@treeseed/sdk/sdk') - .replaceAll('../../../../sdk/src/sdk-types.js', '@treeseed/sdk/types') - .replaceAll('../../../../sdk/src/sdk-types.ts', '@treeseed/sdk/types') - .replaceAll('../../../../sdk/src/model-registry.js', '@treeseed/sdk/models') - .replaceAll('../../../../sdk/src/model-registry.ts', '@treeseed/sdk/models') - .replaceAll('../../../../sdk/src/d1-store.js', '@treeseed/sdk/d1-store') - .replaceAll('../../../../sdk/src/d1-store.ts', '@treeseed/sdk/d1-store') - .replaceAll('../../../../sdk/src/content-store.js', '@treeseed/sdk/content-store') - .replaceAll('../../../../sdk/src/content-store.ts', '@treeseed/sdk/content-store') - .replaceAll('../../../../sdk/src/frontmatter.js', '@treeseed/sdk/frontmatter') - .replaceAll('../../../../sdk/src/frontmatter.ts', '@treeseed/sdk/frontmatter') - .replaceAll('../../../../sdk/src/git-runtime.js', '@treeseed/sdk/git-runtime') - .replaceAll('../../../../sdk/src/git-runtime.ts', '@treeseed/sdk/git-runtime') - .replaceAll('../../../../sdk/src/sdk-filters.js', '@treeseed/sdk/sdk-filters') - .replaceAll('../../../../sdk/src/sdk-filters.ts', '@treeseed/sdk/sdk-filters') - .replaceAll('../../../../sdk/src/wrangler-d1.js', '@treeseed/sdk/wrangler-d1') - .replaceAll('../../../../sdk/src/wrangler-d1.ts', '@treeseed/sdk/wrangler-d1') - .replaceAll('../../../../../sdk/src/stores/helpers.js', '@treeseed/sdk/stores/helpers') - .replaceAll('../../../../../sdk/src/stores/helpers.ts', '@treeseed/sdk/stores/helpers') - .replaceAll('../../../../../sdk/src/stores/cursor-store.js', '@treeseed/sdk/stores/cursor-store') - .replaceAll('../../../../../sdk/src/stores/cursor-store.ts', '@treeseed/sdk/stores/cursor-store') - .replaceAll('../../../../../sdk/src/stores/lease-store.js', '@treeseed/sdk/stores/lease-store') - .replaceAll('../../../../../sdk/src/stores/lease-store.ts', '@treeseed/sdk/stores/lease-store') - .replaceAll('../../../../../sdk/src/stores/message-store.js', '@treeseed/sdk/stores/message-store') - .replaceAll('../../../../../sdk/src/stores/message-store.ts', '@treeseed/sdk/stores/message-store') - .replaceAll('../../../../../sdk/src/stores/run-store.js', '@treeseed/sdk/stores/run-store') - .replaceAll('../../../../../sdk/src/stores/run-store.ts', '@treeseed/sdk/stores/run-store') - .replaceAll('../../../../../sdk/src/stores/subscription-store.js', '@treeseed/sdk/stores/subscription-store') - .replaceAll('../../../../../sdk/src/stores/subscription-store.ts', '@treeseed/sdk/stores/subscription-store') - .replaceAll('../../../../sdk/src/types/agents.js', '@treeseed/sdk/types/agents') - .replaceAll('../../../../sdk/src/types/agents.ts', '@treeseed/sdk/types/agents') - .replaceAll('../../../sdk/src/types/agents.js', '@treeseed/sdk/types/agents') - .replaceAll('../../../sdk/src/types/agents.ts', '@treeseed/sdk/types/agents') - .replaceAll('../../../../sdk/src/types/cloudflare.js', '@treeseed/sdk/types/cloudflare') - .replaceAll('../../../../sdk/src/types/cloudflare.ts', '@treeseed/sdk/types/cloudflare') - .replaceAll('../../../../sdk/src/types/cloudflare', '@treeseed/sdk/types/cloudflare') - .replaceAll('../../../sdk/src/types/cloudflare.js', '@treeseed/sdk/types/cloudflare') - .replaceAll('../../../sdk/src/types/cloudflare.ts', '@treeseed/sdk/types/cloudflare') - .replaceAll('../../../sdk/src/types/cloudflare', '@treeseed/sdk/types/cloudflare'); -} - -function rewriteVendorImportSpecifiers(contents, importerFile) { - return contents - .replace( - /(from\s+['"`]|import\s*\(\s*['"`]|export\s+\*\s+from\s+['"`]|export\s+\{[^}]*\}\s+from\s+['"`])(\.{1,2}\/[^'"`\n]+\.(?:json|jsonc))\?raw(['"`])/g, - (match, prefix, specifier, suffix) => `${prefix}${specifier}.js${suffix}`, - ) - .replace( - /(from\s+['"`]|import\s*\(\s*['"`]|export\s+\*\s+from\s+['"`]|export\s+\{[^}]*\}\s+from\s+['"`])(\.{1,2}\/[^'"`\n]+\.json)(['"`])/g, - (match, prefix, specifier, suffix) => `${prefix}${specifier}.js${suffix}`, - ) - .replace( - /(from\s+['"`]|import\s*\(\s*['"`]|export\s+\*\s+from\s+['"`]|export\s+\{[^}]*\}\s+from\s+['"`])(\.{1,2}\/[^'"`\n]+)(['"`])/g, - (match, prefix, specifier, suffix) => { - if ( - specifier.endsWith('/') || - /\.(?:js|mjs|cjs|json|jsonc|css|astro|svg|png|jpg|jpeg|gif|webp|avif|woff2?|ttf|otf)$/.test(specifier) - ) { - return match; - } - - const resolvedPath = resolve(dirname(importerFile), specifier); - if (existsSync(`${resolvedPath}.js`)) { - return `${prefix}${specifier}.js${suffix}`; - } - if (existsSync(resolve(resolvedPath, 'index.js'))) { - return `${prefix}${specifier}/index.js${suffix}`; - } - - return `${prefix}${specifier}.js${suffix}`; - }, - ); -} - -function rewriteAstroAssetSpecifiers(contents, importerFile) { - return contents.replace( - /(]+src=)(['"])(\.{1,2}\/[^'"`\n]+)(\2)/g, - (match, prefix, quote, specifier, suffix) => { - if ( - specifier.endsWith('/') || - /\.(?:js|mjs|cjs|json|jsonc|css|astro|svg|png|jpg|jpeg|gif|webp|avif|woff2?|ttf|otf)$/.test(specifier) - ) { - return match; - } - - const resolvedPath = resolve(dirname(importerFile), specifier); - if (existsSync(`${resolvedPath}.js`)) { - return `${prefix}${quote}${specifier}.js${suffix}`; - } - if (existsSync(resolve(resolvedPath, 'index.js'))) { - return `${prefix}${quote}${specifier}/index.js${suffix}`; - } - - return match; - }, - ); -} - -function writeRawTextModule(sourceFile, sourceRoot, outputRoot) { - const relativePath = relative(sourceRoot, sourceFile); - const outputFile = resolve(outputRoot, `${relativePath}.js`); - ensureDir(outputFile); - const source = readFileSync(sourceFile, 'utf8'); - writeFileSync(outputFile, `export default ${JSON.stringify(source)};\n`, 'utf8'); -} - -function writeJsonModule(sourceFile, sourceRoot, outputRoot) { - const relativePath = relative(sourceRoot, sourceFile); - const outputFile = resolve(outputRoot, `${relativePath}.js`); - ensureDir(outputFile); - const source = readFileSync(sourceFile, 'utf8'); - const parsed = JSON.parse(source); - const namedExports = Object.keys(parsed) - .filter((key) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) - .map((key) => `export const ${key} = __treeseedJson.${key};`) - .join('\n'); - writeFileSync( - outputFile, - `const __treeseedJson = ${JSON.stringify(parsed, null, 2)};\nexport default __treeseedJson;\n${namedExports}\n`, - 'utf8', - ); -} - -function rewriteScriptRuntimeSpecifiers(contents) { - return rewriteRuntimeSpecifiers(contents) - .replace(/(['"`])\.\.\/src\//g, '$1../') - .replace(/(['"`])\.\/src\//g, '$1./dist/') - .replaceAll("'src/worker/forms-worker.ts'", "'dist/worker/forms-worker.js'") - .replaceAll('"src/worker/forms-worker.ts"', '"dist/worker/forms-worker.js"'); -} - -async function compileModule(filePath, sourceRoot, outputRoot) { - const relativePath = relative(sourceRoot, filePath); - const outputFile = resolve(outputRoot, relativePath.replace(/\.(mjs|ts)$/u, '.js')); - ensureDir(outputFile); - - await build({ - entryPoints: [filePath], - outfile: outputFile, - platform: 'node', - format: 'esm', - bundle: false, - logLevel: 'silent', - loader: { - '.jsonc': 'text', - }, - }); - - const builtSource = readFileSync(outputFile, 'utf8'); - const rewritten = rewriteVendorImportSpecifiers(rewriteRuntimeSpecifiers(builtSource), outputFile); - writeFileSync(outputFile, rewritten, 'utf8'); -} - -function copyAsset(filePath, sourceRoot, outputRoot) { - const outputFile = resolve(outputRoot, relative(sourceRoot, filePath)); - ensureDir(outputFile); - copyFileSync(filePath, outputFile); - - if (outputFile.endsWith('.astro') || outputFile.endsWith('.d.ts') || outputFile.endsWith('.js')) { - const contents = readFileSync(outputFile, 'utf8'); - const rewritten = outputFile.endsWith('.js') - ? rewriteVendorImportSpecifiers(rewriteRuntimeSpecifiers(contents), outputFile) - : rewriteRuntimeSpecifiers(contents); - writeFileSync(outputFile, rewritten, 'utf8'); - } -} - -function copyPackageAsset(packageName, relativePath, outputRelativePath) { - let packageRootPath; - try { - packageRootPath = dirname(require.resolve(`${packageName}/package.json`)); - } catch { - packageRootPath = dirname(require.resolve(packageName)); - } - - const sourceFile = resolve(packageRootPath, relativePath); - const outputFile = resolve(packageRoot, outputRelativePath); - ensureDir(outputFile); - copyFileSync(sourceFile, outputFile); -} - -function transpileScript(filePath) { - const source = readFileSync(filePath, 'utf8'); - const relativePath = relative(scriptsRoot, filePath); - if (relativePath === 'fixture-tools.ts') { - return; - } - const outputFile = resolve(distRoot, 'scripts', relativePath.replace(/\.(mjs|ts)$/u, '.js')); - const transformed = extname(filePath) === '.ts' - ? ts.transpileModule(source, { - compilerOptions: { - module: ts.ModuleKind.ESNext, - target: ts.ScriptTarget.ES2022, - }, - }).outputText - : source; - - ensureDir(outputFile); - const rewritten = rewriteVendorImportSpecifiers(rewriteScriptRuntimeSpecifiers(transformed), outputFile); - writeFileSync(outputFile, rewritten, 'utf8'); - chmodSync(outputFile, 0o755); -} - -function rewriteDeclarations() { - for (const filePath of walkFiles(distRoot)) { - if (!filePath.endsWith('.d.ts')) continue; - const contents = readFileSync(filePath, 'utf8'); - writeFileSync(filePath, rewriteRuntimeSpecifiers(contents), 'utf8'); - } -} - -function relativePathForTsconfig(fromRoot, targetPath) { - return relative(fromRoot, targetPath).replaceAll('\\', '/'); -} - -function resolveWorkspaceSdkDeclarationPaths() { - if (!existsSync(resolve(workspaceSdkDistRoot, 'index.d.ts'))) { - return null; - } - return { - '@treeseed/sdk': [relativePathForTsconfig(packageRoot, resolve(workspaceSdkDistRoot, 'index.d.ts'))], - '@treeseed/sdk/hosting': [relativePathForTsconfig(packageRoot, resolve(workspaceSdkDistRoot, 'hosting', 'index.d.ts'))], - '@treeseed/sdk/types': [relativePathForTsconfig(packageRoot, resolve(workspaceSdkDistRoot, 'sdk-types.d.ts'))], - '@treeseed/sdk/types/*': [relativePathForTsconfig(packageRoot, resolve(workspaceSdkDistRoot, 'types', '*.d.ts'))], - '@treeseed/sdk/*/index': [relativePathForTsconfig(packageRoot, resolve(workspaceSdkDistRoot, '*', 'index.d.ts'))], - '@treeseed/sdk/*': [relativePathForTsconfig(packageRoot, resolve(workspaceSdkDistRoot, '*.d.ts'))], - }; -} - -function emitTypeDeclarations() { - const sourceFiles = [ - resolve(srcRoot, 'types/astro-build.d.ts'), - resolve(srcRoot, 'types/cloudflare.ts'), - resolve(srcRoot, 'utils/site-models.ts'), - resolve(srcRoot, 'middleware/editorial-preview.ts'), - resolve(srcRoot, 'site-resources.ts'), - resolve(srcRoot, 'platform-resources.ts'), - resolve(srcRoot, 'api.ts'), - resolve(srcRoot, 'agent.ts'), - resolve(srcRoot, 'agent-runtime.ts'), - resolve(srcRoot, 'railway.ts'), - resolve(srcRoot, 'platform.ts'), - resolve(srcRoot, 'templates.ts'), - resolve(srcRoot, 'services/index.ts'), - resolve(srcRoot, 'plugin-default.ts'), - resolve(srcRoot, 'index.ts'), - ...(existsSync(resolve(srcRoot, 'api')) ? walkFiles(resolve(srcRoot, 'api')).filter((filePath) => filePath.endsWith('.ts') && !filePath.endsWith('.d.ts')) : []), - ...(existsSync(resolve(srcRoot, 'agents')) ? walkFiles(resolve(srcRoot, 'agents')).filter((filePath) => filePath.endsWith('.ts') && !filePath.endsWith('.d.ts')) : []), - ...(existsSync(resolve(srcRoot, 'services')) ? walkFiles(resolve(srcRoot, 'services')).filter((filePath) => filePath.endsWith('.ts') && !filePath.endsWith('.d.ts')) : []), - ].filter((filePath) => existsSync(filePath)); - - if (sourceFiles.length === 0) { - return; - } - - const compilerOptions = { - allowImportingTsExtensions: true, - baseUrl: packageRoot, - declaration: true, - emitDeclarationOnly: true, - module: ts.ModuleKind.ESNext, - moduleResolution: ts.ModuleResolutionKind.Bundler, - outDir: distRoot, - paths: resolveWorkspaceSdkDeclarationPaths() ?? undefined, - rootDir: srcRoot, - skipLibCheck: true, - target: ts.ScriptTarget.ES2022, - }; - - const program = ts.createProgram(sourceFiles, compilerOptions); - const emitResult = program.emit(); - const diagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics ?? []); - - if (diagnostics.length > 0) { - const message = diagnostics - .map((diagnostic) => ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')) - .join('\n'); - throw new Error(`Failed to emit Treeseed core declarations:\n${message}`); - } - - if (emitResult.emitSkipped) { - throw new Error('Failed to emit Treeseed core declarations.'); - } -} - -function toPosixRelative(fromFile, toFile) { - return relative(dirname(fromFile), toFile).replaceAll('\\', '/'); -} - -function rewriteTreeseedStarlightSpecifiers(contents, filePath) { - const componentsIndex = toPosixRelative(filePath, resolve(distRoot, 'vendor', 'starlight', 'components.js')); - const routeData = toPosixRelative(filePath, resolve(distRoot, 'vendor', 'starlight', 'route-data.js')); - - return contents - .replaceAll("'@astrojs/starlight/components'", `'${componentsIndex}'`) - .replaceAll('"@astrojs/starlight/components"', `"${componentsIndex}"`) - .replaceAll("'@astrojs/starlight/route-data'", `'${routeData}'`) - .replaceAll('"@astrojs/starlight/route-data"', `"${routeData}"`) - .replace(/(['"])@astrojs\/starlight\/components\/([^'"\n]+)\1/g, (match, quote, subpath) => { - const target = toPosixRelative(filePath, resolve(distRoot, 'vendor', 'starlight', 'components', subpath)); - return `${quote}${target}${quote}`; - }); -} - -async function compileVendorPackage(sourceRoot, outputRoot) { - for (const filePath of walkFiles(sourceRoot)) { - if (filePath.endsWith('.d.ts')) { - copyAsset(filePath, sourceRoot, outputRoot); - continue; - } - - const extension = extname(filePath); - if (extension === '.ts') { - await compileModule(filePath, sourceRoot, outputRoot); - continue; - } - - if (COPY_EXTENSIONS.has(extension)) { - copyAsset(filePath, sourceRoot, outputRoot); - if (extension === '.jsonc') { - writeRawTextModule(filePath, sourceRoot, outputRoot); - } - if (extension === '.json') { - writeJsonModule(filePath, sourceRoot, outputRoot); - } - } - } - - for (const filePath of walkFiles(outputRoot)) { - if (filePath.endsWith('.astro')) { - const contents = readFileSync(filePath, 'utf8'); - writeFileSync(filePath, rewriteAstroAssetSpecifiers(contents, filePath), 'utf8'); - continue; - } - if (!filePath.endsWith('.js')) continue; - const contents = readFileSync(filePath, 'utf8'); - writeFileSync(filePath, rewriteVendorImportSpecifiers(contents, filePath), 'utf8'); - } -} - -function writeCompatibilityEntrypoint(outputFile, contents) { - ensureDir(outputFile); - writeFileSync(outputFile, `${contents} -`, 'utf8'); -} - -function patchVendoredStarlight(distVendorRoot) { - const indexFile = resolve(distVendorRoot, 'index.js'); - if (readdirSync(dirname(indexFile)).includes('index.js')) { - let source = readFileSync(indexFile, 'utf8') - .replaceAll('@astrojs/starlight/locals', './locals.js') - .replaceAll('@astrojs/starlight/routes/static/404.astro', './routes/static/404.astro') - .replaceAll('@astrojs/starlight/routes/ssr/404.astro', './routes/ssr/404.astro') - .replaceAll('@astrojs/starlight/routes/static/index.astro', './routes/static/index.astro') - .replaceAll('@astrojs/starlight/routes/ssr/index.astro', './routes/ssr/index.astro'); - - if (!source.includes('from "node:url"')) { - source = `import { fileURLToPath } from "node:url";\n${source}`; - } - - source = source - .replace( - 'addMiddleware({ entrypoint: "./locals.js", order: "pre" });', - 'addMiddleware({ entrypoint: fileURLToPath(new URL("./locals.js", import.meta.url)), order: "pre" });', - ) - .replaceAll( - '"./routes/static/404.astro"', - 'fileURLToPath(new URL("./routes/static/404.astro", import.meta.url))', - ) - .replaceAll( - '"./routes/ssr/404.astro"', - 'fileURLToPath(new URL("./routes/ssr/404.astro", import.meta.url))', - ) - .replaceAll( - '"./routes/static/index.astro"', - 'fileURLToPath(new URL("./routes/static/index.astro", import.meta.url))', - ) - .replaceAll( - '"./routes/ssr/index.astro"', - 'fileURLToPath(new URL("./routes/ssr/index.astro", import.meta.url))', - ); - - source = source - .replaceAll('name: "@astrojs/starlight"', 'name: "@treeseed/core-vendored-starlight"') - .replaceAll('i.name === "@astrojs/starlight"', 'i.name === "@treeseed/core-vendored-starlight"'); - - writeFileSync(indexFile, source, 'utf8'); - } -} - -function patchTreeseedRuntime(distRuntimeRoot) { - const middlewareFile = resolve(distRuntimeRoot, 'middleware', 'starlightRouteData.js'); - if (existsSync(middlewareFile)) { - const source = readFileSync(middlewareFile, 'utf8').replaceAll( - '"@astrojs/starlight/route-data"', - '"../vendor/starlight/route-data.js"', - ); - writeFileSync(middlewareFile, source, 'utf8'); - } -} - -async function main() { - rmSync(distRoot, { recursive: true, force: true }); - mkdirSync(distRoot, { recursive: true }); - - for (const filePath of walkFiles(srcRoot)) { - if (filePath.endsWith('.d.ts')) { - copyAsset(filePath, srcRoot, distRoot); - continue; - } - - const extension = extname(filePath); - if (JS_SOURCE_EXTENSIONS.has(extension)) { - await compileModule(filePath, srcRoot, distRoot); - continue; - } - - if (COPY_EXTENSIONS.has(extension)) { - copyAsset(filePath, srcRoot, distRoot); - } - } - - for (const filePath of walkFiles(scriptsRoot)) { - const extension = extname(filePath); - if (JS_SOURCE_EXTENSIONS.has(extension)) { - transpileScript(filePath); - } - } - - emitTypeDeclarations(); - - - const starlightPackageRoot = dirname(require.resolve('@astrojs/starlight')); - const vendoredStarlightRoot = resolve(distRoot, 'vendor', 'starlight'); - await compileVendorPackage(starlightPackageRoot, vendoredStarlightRoot); - patchVendoredStarlight(vendoredStarlightRoot); - patchTreeseedRuntime(distRoot); - - for (const filePath of walkFiles(distRoot)) { - if (filePath.startsWith(`${vendoredStarlightRoot}/`) || filePath === vendoredStarlightRoot) continue; - if (!(filePath.endsWith('.astro') || filePath.endsWith('.js'))) continue; - const contents = readFileSync(filePath, 'utf8'); - writeFileSync(filePath, rewriteTreeseedStarlightSpecifiers(contents, filePath), 'utf8'); - } - - writeCompatibilityEntrypoint( - resolve(distRoot, 'config.js'), - "import starlight from './vendor/starlight/index.js';\nimport { loadTreeseedManifest } from '@treeseed/sdk/platform/tenant-config';\nimport { createTreeseedSite } from './site.js';\n\nexport function createTreeseedTenantSite(manifestPath) {\n\tconst tenant = loadTreeseedManifest(manifestPath);\n\treturn createTreeseedSite(tenant, { starlight });\n}" - ); - writeCompatibilityEntrypoint( - resolve(distRoot, 'config.d.ts'), - "export declare function createTreeseedTenantSite(manifestPath?: string): import('astro').AstroUserConfig;" - ); - - writeCompatibilityEntrypoint( - resolve(distRoot, 'content.d.ts'), - "export declare function createTreeseedCollections(tenantConfig: any, dependencies: any): Record;" - ); - - writeCompatibilityEntrypoint( - resolve(distRoot, 'content-config.js'), - "import { loadTreeseedManifest } from '@treeseed/sdk/platform/tenant-config';\nimport { docsLoader } from './vendor/starlight/loaders.js';\nimport { docsSchema } from './vendor/starlight/schema.js';\nimport { createTreeseedCollections } from './content.js';\n\nexport function createTreeseedTenantCollections(manifestPath) {\n\tconst tenant = loadTreeseedManifest(manifestPath);\n\treturn createTreeseedCollections(tenant, { docsLoader, docsSchema });\n}" - ); - writeCompatibilityEntrypoint( - resolve(distRoot, 'content-config.d.ts'), - "export declare function createTreeseedTenantCollections(manifestPath?: string): {\n\tpages: any;\n\tnotes: any;\n\tquestions: any;\n\tobjectives: any;\n\tpeople: any;\n\tagents: any;\n\tagent_tests?: any;\n\tbooks: any;\n\tdocs: any;\n\tworkdays?: any;\n};" - ); - writeCompatibilityEntrypoint( - resolve(distRoot, 'utils/forms/service.d.ts'), - "import type { APIContext } from 'astro';\nimport type { SubmitResult } from '../../types/forms';\nexport declare function handleTokenRequest(context: APIContext): Promise;\nexport declare function handleFormSubmission(context: APIContext): Promise;" - ); - rmSync(resolve(distRoot, 'config.d.js'), { force: true }); - rmSync(resolve(distRoot, 'content-config.d.js'), { force: true }); - - writeCompatibilityEntrypoint( - resolve(vendoredStarlightRoot, 'utils', 'routing.js'), - "export * from './routing/index.js';" - ); - - copyAsset(resolve(packageRoot, 'tsconfigs/strict.json'), packageRoot, distRoot); - copyPackageAsset('@astrojs/mdx', 'template/content-module-types.d.ts', 'template/content-module-types.d.ts'); - copyPackageAsset('@astrojs/mdx', 'dist/server.js', 'dist/server.js'); - copyPackageAsset('@astrojs/starlight', 'style/anchor-links.css', 'style/anchor-links.css'); - copyPackageAsset('@astrojs/starlight', 'utils/git.ts', 'utils/git.ts'); - copyPackageAsset('@astrojs/starlight', 'utils/gitInlined.ts', 'utils/gitInlined.ts'); - rewriteDeclarations(); -} - -main().catch((error) => { - console.error(error instanceof Error ? error.message : String(error)); - process.exit(1); -}); diff --git a/scripts/build-dist/build-runtime.ts b/scripts/build-dist/build-runtime.ts new file mode 100644 index 00000000..cd6e346b --- /dev/null +++ b/scripts/build-dist/build-runtime.ts @@ -0,0 +1,257 @@ +import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'; +import { createRequire } from 'node:module'; +import { dirname, extname, join, relative, resolve } from 'node:path'; +import { build } from 'esbuild'; +import ts from 'typescript'; +import { packageRoot } from "../packages/package-tools.ts"; + + +export const require = createRequire(import.meta.url); + +export const srcRoot = resolve(packageRoot, 'src'); + +export const scriptsRoot = resolve(packageRoot, 'scripts'); + +export const distRoot = resolve(packageRoot, 'dist'); + +export const workspaceSdkDistRoot = resolve(packageRoot, '..', 'sdk', 'dist'); + +export const JS_SOURCE_EXTENSIONS = new Set(['.ts', '.ts']); + +export const COPY_EXTENSIONS = new Set(['.astro', '.css', '.d.ts', '.js', '.json', '.jsonc', '.ts', '.yaml', '.yml']); + +export function walkFiles(root) { + const files = []; + for (const entry of readdirSync(root, { withFileTypes: true })) { + const fullPath = join(root, entry.name); + if (entry.isDirectory()) { + files.push(...walkFiles(fullPath)); + continue; + } + files.push(fullPath); + } + return files; +} + +export function ensureDir(filePath) { + mkdirSync(dirname(filePath), { recursive: true }); +} + +export function rewriteRuntimeSpecifiers(contents) { + return contents + .replace(/(['"`])(\.[^'"`\n]+)\.(mjs|ts)\1/g, '$1$2.js$1') + .replaceAll('../../../../sdk/src/sdk.js', '@treeseed/sdk/sdk') + .replaceAll('../../../../sdk/src/sdk.ts', '@treeseed/sdk/sdk') + .replaceAll('../../../../sdk/src/sdk-types.js', '@treeseed/sdk/site-contracts/catalog') + .replaceAll('../../../../sdk/src/sdk-types.ts', '@treeseed/sdk/site-contracts/catalog') + .replaceAll('../../../../sdk/src/model-registry.js', '@treeseed/sdk/models') + .replaceAll('../../../../sdk/src/model-registry.ts', '@treeseed/sdk/models') + .replaceAll('../../../../sdk/src/d1-store.js', '@treeseed/sdk/d1-store') + .replaceAll('../../../../sdk/src/d1-store.ts', '@treeseed/sdk/d1-store') + .replaceAll('../../../../sdk/src/content-store.js', '@treeseed/sdk/content-store') + .replaceAll('../../../../sdk/src/content-store.ts', '@treeseed/sdk/content-store') + .replaceAll('../../../../sdk/src/frontmatter.js', '@treeseed/sdk/frontmatter') + .replaceAll('../../../../sdk/src/frontmatter.ts', '@treeseed/sdk/frontmatter') + .replaceAll('../../../../sdk/src/git-runtime.js', '@treeseed/sdk/git-runtime') + .replaceAll('../../../../sdk/src/git-runtime.ts', '@treeseed/sdk/git-runtime') + .replaceAll('../../../../sdk/src/sdk-filters.js', '@treeseed/sdk/sdk-filters') + .replaceAll('../../../../sdk/src/sdk-filters.ts', '@treeseed/sdk/sdk-filters') + .replaceAll('../../../../sdk/src/wrangler-d1.js', '@treeseed/sdk/wrangler-d1') + .replaceAll('../../../../sdk/src/wrangler-d1.ts', '@treeseed/sdk/wrangler-d1') + .replaceAll('../../../../../sdk/src/stores/helpers.js', '@treeseed/sdk/stores/helpers') + .replaceAll('../../../../../sdk/src/stores/helpers.ts', '@treeseed/sdk/stores/helpers') + .replaceAll('../../../../../sdk/src/stores/cursor-store.js', '@treeseed/sdk/stores/cursor-store') + .replaceAll('../../../../../sdk/src/stores/cursor-store.ts', '@treeseed/sdk/stores/cursor-store') + .replaceAll('../../../../../sdk/src/stores/lease-store.js', '@treeseed/sdk/stores/lease-store') + .replaceAll('../../../../../sdk/src/stores/lease-store.ts', '@treeseed/sdk/stores/lease-store') + .replaceAll('../../../../../sdk/src/stores/message-store.js', '@treeseed/sdk/stores/message-store') + .replaceAll('../../../../../sdk/src/stores/message-store.ts', '@treeseed/sdk/stores/message-store') + .replaceAll('../../../../../sdk/src/stores/run-store.js', '@treeseed/sdk/stores/run-store') + .replaceAll('../../../../../sdk/src/stores/run-store.ts', '@treeseed/sdk/stores/run-store') + .replaceAll('../../../../../sdk/src/stores/subscription-store.js', '@treeseed/sdk/stores/subscription-store') + .replaceAll('../../../../../sdk/src/stores/subscription-store.ts', '@treeseed/sdk/stores/subscription-store') + .replaceAll('../../../../sdk/src/types/agents.js', '@treeseed/sdk/types/agents') + .replaceAll('../../../../sdk/src/types/agents.ts', '@treeseed/sdk/types/agents') + .replaceAll('../../../sdk/src/types/agents.js', '@treeseed/sdk/types/agents') + .replaceAll('../../../sdk/src/types/agents.ts', '@treeseed/sdk/types/agents') + .replaceAll('../../../../sdk/src/types/cloudflare.js', '@treeseed/sdk/site-contracts/cloudflare') + .replaceAll('../../../../sdk/src/types/cloudflare.ts', '@treeseed/sdk/site-contracts/cloudflare') + .replaceAll('../../../../sdk/src/types/cloudflare', '@treeseed/sdk/site-contracts/cloudflare') + .replaceAll('../../../sdk/src/types/cloudflare.js', '@treeseed/sdk/site-contracts/cloudflare') + .replaceAll('../../../sdk/src/types/cloudflare.ts', '@treeseed/sdk/site-contracts/cloudflare') + .replaceAll('../../../sdk/src/types/cloudflare', '@treeseed/sdk/site-contracts/cloudflare'); +} + +export function rewriteVendorImportSpecifiers(contents, importerFile) { + return contents + .replace( + /(from\s+['"`]|import\s*\(\s*['"`]|export\s+\*\s+from\s+['"`]|export\s+\{[^}]*\}\s+from\s+['"`])(\.{1,2}\/[^'"`\n]+\.(?:json|jsonc))\?raw(['"`])/g, + (match, prefix, specifier, suffix) => `${prefix}${specifier}.js${suffix}`, + ) + .replace( + /(from\s+['"`]|import\s*\(\s*['"`]|export\s+\*\s+from\s+['"`]|export\s+\{[^}]*\}\s+from\s+['"`])(\.{1,2}\/[^'"`\n]+\.json)(['"`])/g, + (match, prefix, specifier, suffix) => `${prefix}${specifier}.js${suffix}`, + ) + .replace( + /(from\s+['"`]|import\s*\(\s*['"`]|export\s+\*\s+from\s+['"`]|export\s+\{[^}]*\}\s+from\s+['"`])(\.{1,2}\/[^'"`\n]+)(['"`])/g, + (match, prefix, specifier, suffix) => { + if ( + specifier.endsWith('/') || + /\.(?:js|mjs|cjs|json|jsonc|css|astro|svg|png|jpg|jpeg|gif|webp|avif|woff2?|ttf|otf)$/.test(specifier) + ) { + return match; + } + + const resolvedPath = resolve(dirname(importerFile), specifier); + if (existsSync(`${resolvedPath}.js`)) { + return `${prefix}${specifier}.js${suffix}`; + } + if (existsSync(resolve(resolvedPath, 'index.js'))) { + return `${prefix}${specifier}/index.js${suffix}`; + } + + return `${prefix}${specifier}.js${suffix}`; + }, + ); +} + +export function rewriteAstroAssetSpecifiers(contents, importerFile) { + return contents.replace( + /(]+src=)(['"])(\.{1,2}\/[^'"`\n]+)(\2)/g, + (match, prefix, quote, specifier, suffix) => { + if ( + specifier.endsWith('/') || + /\.(?:js|mjs|cjs|json|jsonc|css|astro|svg|png|jpg|jpeg|gif|webp|avif|woff2?|ttf|otf)$/.test(specifier) + ) { + return match; + } + + const resolvedPath = resolve(dirname(importerFile), specifier); + if (existsSync(`${resolvedPath}.js`)) { + return `${prefix}${quote}${specifier}.js${suffix}`; + } + if (existsSync(resolve(resolvedPath, 'index.js'))) { + return `${prefix}${quote}${specifier}/index.js${suffix}`; + } + + return match; + }, + ); +} + +export function writeRawTextModule(sourceFile, sourceRoot, outputRoot) { + const relativePath = relative(sourceRoot, sourceFile); + const outputFile = resolve(outputRoot, `${relativePath}.js`); + ensureDir(outputFile); + const source = readFileSync(sourceFile, 'utf8'); + writeFileSync(outputFile, `export default ${JSON.stringify(source)};\n`, 'utf8'); +} + +export function writeJsonModule(sourceFile, sourceRoot, outputRoot) { + const relativePath = relative(sourceRoot, sourceFile); + const outputFile = resolve(outputRoot, `${relativePath}.js`); + ensureDir(outputFile); + const source = readFileSync(sourceFile, 'utf8'); + const parsed = JSON.parse(source); + const namedExports = Object.keys(parsed) + .filter((key) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) + .map((key) => `export const ${key} = __treeseedJson.${key};`) + .join('\n'); + writeFileSync( + outputFile, + `const __treeseedJson = ${JSON.stringify(parsed, null, 2)};\nexport default __treeseedJson;\n${namedExports}\n`, + 'utf8', + ); +} + +export function rewriteScriptRuntimeSpecifiers(contents) { + return rewriteRuntimeSpecifiers(contents) + .replace(/(['"`])((?:\.\.\/)+)src\//g, '$1$2') + .replace(/(['"`])\.\/src\//g, '$1./dist/') + .replaceAll("'src/worker/forms-worker.ts'", "'dist/worker/forms-worker.js'") + .replaceAll('"src/worker/forms-worker.ts"', '"dist/worker/forms-worker.js"'); +} + +export async function compileModule(filePath, sourceRoot, outputRoot) { + const relativePath = relative(sourceRoot, filePath); + const outputFile = resolve(outputRoot, relativePath.replace(/\.(mjs|ts)$/u, '.js')); + ensureDir(outputFile); + + await build({ + entryPoints: [filePath], + outfile: outputFile, + platform: 'node', + format: 'esm', + bundle: false, + logLevel: 'silent', + loader: { + '.jsonc': 'text', + }, + }); + + const builtSource = readFileSync(outputFile, 'utf8'); + const rewritten = rewriteVendorImportSpecifiers(rewriteRuntimeSpecifiers(builtSource), outputFile); + writeFileSync(outputFile, rewritten, 'utf8'); +} + +export function copyAsset(filePath, sourceRoot, outputRoot) { + const outputFile = resolve(outputRoot, relative(sourceRoot, filePath)); + ensureDir(outputFile); + copyFileSync(filePath, outputFile); + + if (outputFile.endsWith('.astro') || outputFile.endsWith('.d.ts') || outputFile.endsWith('.js')) { + const contents = readFileSync(outputFile, 'utf8'); + const rewritten = outputFile.endsWith('.js') + ? rewriteVendorImportSpecifiers(rewriteRuntimeSpecifiers(contents), outputFile) + : rewriteRuntimeSpecifiers(contents); + writeFileSync(outputFile, rewritten, 'utf8'); + } +} + +export function copyPackageAsset(packageName, relativePath, outputRelativePath) { + let packageRootPath; + try { + packageRootPath = dirname(require.resolve(`${packageName}/package.json`)); + } catch { + packageRootPath = dirname(require.resolve(packageName)); + } + + const sourceFile = resolve(packageRootPath, relativePath); + const outputFile = resolve(packageRoot, outputRelativePath); + ensureDir(outputFile); + copyFileSync(sourceFile, outputFile); +} + +export function transpileScript(filePath) { + const source = readFileSync(filePath, 'utf8'); + const relativePath = relative(scriptsRoot, filePath); + if (relativePath === 'fixture-tools.ts') { + return; + } + const outputFile = resolve(distRoot, 'scripts', relativePath.replace(/\.(mjs|ts)$/u, '.js')); + const transformed = extname(filePath) === '.ts' + ? ts.transpileModule(source, { + compilerOptions: { + module: ts.ModuleKind.ESNext, + target: ts.ScriptTarget.ES2022, + }, + }).outputText + : source; + + ensureDir(outputFile); + const rewritten = rewriteVendorImportSpecifiers(rewriteScriptRuntimeSpecifiers(transformed), outputFile); + writeFileSync(outputFile, rewritten, 'utf8'); + chmodSync(outputFile, 0o755); +} + +export function rewriteDeclarations() { + for (const filePath of walkFiles(distRoot)) { + if (!filePath.endsWith('.d.ts')) continue; + const contents = readFileSync(filePath, 'utf8'); + writeFileSync(filePath, rewriteRuntimeSpecifiers(contents), 'utf8'); + } +} + +export function relativePathForTsconfig(fromRoot, targetPath) { + return relative(fromRoot, targetPath).replaceAll('\\', '/'); +} diff --git a/scripts/build-dist/main.ts b/scripts/build-dist/main.ts new file mode 100644 index 00000000..06e81e23 --- /dev/null +++ b/scripts/build-dist/main.ts @@ -0,0 +1,100 @@ +import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'; +import { dirname, extname, resolve } from 'node:path'; +import { packageRoot } from "../packages/package-tools.ts"; +import { COPY_EXTENSIONS, JS_SOURCE_EXTENSIONS, compileModule, copyAsset, copyPackageAsset, distRoot, require, rewriteDeclarations, scriptsRoot, srcRoot, transpileScript, walkFiles } from './build-runtime.ts'; +import { compileVendorPackage, emitTypeDeclarations, patchRuntime, patchVendoredStarlight, rewriteStarlightSpecifiers, writeCompatibilityEntrypoint } from './resolve-workspace-sdk-declaration-paths.ts'; + +export async function main() { + rmSync(distRoot, { recursive: true, force: true }); + mkdirSync(distRoot, { recursive: true }); + + for (const filePath of walkFiles(srcRoot)) { + if (filePath.endsWith('.d.ts')) { + copyAsset(filePath, srcRoot, distRoot); + continue; + } + + const extension = extname(filePath); + if (JS_SOURCE_EXTENSIONS.has(extension)) { + await compileModule(filePath, srcRoot, distRoot); + continue; + } + + if (COPY_EXTENSIONS.has(extension)) { + copyAsset(filePath, srcRoot, distRoot); + } + } + + for (const filePath of walkFiles(scriptsRoot)) { + const extension = extname(filePath); + if (JS_SOURCE_EXTENSIONS.has(extension)) { + transpileScript(filePath); + } + } + + emitTypeDeclarations(); + + + const starlightPackageRoot = dirname(require.resolve('@astrojs/starlight')); + const vendoredStarlightRoot = resolve(distRoot, 'vendor', 'starlight'); + await compileVendorPackage(starlightPackageRoot, vendoredStarlightRoot); + patchVendoredStarlight(vendoredStarlightRoot); + patchRuntime(distRoot); + + for (const filePath of walkFiles(distRoot)) { + if (filePath.startsWith(`${vendoredStarlightRoot}/`) || filePath === vendoredStarlightRoot) continue; + if (!(filePath.endsWith('.astro') || filePath.endsWith('.js'))) continue; + const contents = readFileSync(filePath, 'utf8'); + writeFileSync(filePath, rewriteStarlightSpecifiers(contents, filePath), 'utf8'); + } + + writeCompatibilityEntrypoint( + resolve(distRoot, 'config.js'), + "import starlight from './vendor/starlight/index.js';\nimport { loadManifest } from './runtime/platform/tenant-config.js';\nimport { createSite } from './support/site.js';\n\nexport function createTenantSite(manifestPath) {\n\tconst tenant = loadManifest(manifestPath);\n\treturn createSite(tenant, { starlight });\n}" + ); + writeCompatibilityEntrypoint( + resolve(distRoot, 'config.d.ts'), + "export declare function createTenantSite(manifestPath?: string): import('astro').AstroUserConfig;" + ); + + writeCompatibilityEntrypoint( + resolve(distRoot, 'content.d.ts'), + "export declare function createCollections(tenantConfig: any, dependencies: any): Record;" + ); + + writeCompatibilityEntrypoint( + resolve(distRoot, 'content-config.js'), + "import { loadManifest } from './runtime/platform/tenant-config.js';\nimport { docsLoader } from './vendor/starlight/loaders.js';\nimport { docsSchema } from './vendor/starlight/schema.js';\nimport { createCollections } from './content/content.js';\n\nexport function createTenantCollections(manifestPath) {\n\tconst tenant = loadManifest(manifestPath);\n\treturn createCollections(tenant, { docsLoader, docsSchema });\n}" + ); + writeCompatibilityEntrypoint( + resolve(distRoot, 'content-config.d.ts'), + "export declare function createTenantCollections(manifestPath?: string): {\n\tpages: any;\n\tnotes: any;\n\tquestions: any;\n\tobjectives: any;\n\tpeople: any;\n\tagents: any;\n\tagent_tests?: any;\n\tbooks: any;\n\tdocs: any;\n\tworkdays?: any;\n};" + ); + writeCompatibilityEntrypoint( + resolve(distRoot, 'utils/forms/service.d.ts'), + "import type { APIContext } from 'astro';\nimport type { SubmitResult } from '../../types/forms';\nexport declare function handleTokenRequest(context: APIContext): Promise;\nexport declare function handleFormSubmission(context: APIContext): Promise;" + ); + rmSync(resolve(distRoot, 'config.d.js'), { force: true }); + rmSync(resolve(distRoot, 'content-config.d.js'), { force: true }); + + writeCompatibilityEntrypoint( + resolve(vendoredStarlightRoot, 'utils', 'routing.js'), + "export * from './routing/index.js';" + ); + + copyAsset(resolve(packageRoot, 'tsconfigs/strict.json'), packageRoot, distRoot); + copyPackageAsset('@astrojs/mdx', 'template/content-module-types.d.ts', 'template/content-module-types.d.ts'); + copyPackageAsset('@astrojs/mdx', 'dist/server.js', 'dist/server.js'); + copyPackageAsset('@astrojs/starlight', 'style/anchor-links.css', 'style/anchor-links.css'); + copyPackageAsset('@astrojs/starlight', 'utils/git.ts', 'utils/git.ts'); + copyPackageAsset('@astrojs/starlight', 'utils/gitInlined.ts', 'utils/gitInlined.ts'); + rewriteDeclarations(); + const marker = resolve(distRoot, '.treeseed-build-complete.json'), temporary = `${marker}.new`; + writeFileSync(temporary, `${JSON.stringify({ completedAt: new Date().toISOString() })}\n`, 'utf8'); + renameSync(temporary, marker); +} + +main().catch((error) => { + console.error(error instanceof Error ? error.message : String(error)); + process.exit(1); +}); diff --git a/scripts/build-dist/resolve-workspace-sdk-declaration-paths.ts b/scripts/build-dist/resolve-workspace-sdk-declaration-paths.ts new file mode 100644 index 00000000..48f3275d --- /dev/null +++ b/scripts/build-dist/resolve-workspace-sdk-declaration-paths.ts @@ -0,0 +1,197 @@ +import { existsSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'; +import { dirname, extname, join, relative, resolve } from 'node:path'; +import ts from 'typescript'; +import { packageRoot } from "../packages/package-tools.ts"; +import { COPY_EXTENSIONS, compileModule, copyAsset, distRoot, ensureDir, relativePathForTsconfig, rewriteAstroAssetSpecifiers, rewriteVendorImportSpecifiers, srcRoot, walkFiles, workspaceSdkDistRoot, writeJsonModule, writeRawTextModule } from './build-runtime.ts'; + +export function resolveWorkspaceSdkDeclarationPaths() { + if (!existsSync(resolve(workspaceSdkDistRoot, 'index.d.ts'))) { + return null; + } + return { + '@treeseed/sdk': [relativePathForTsconfig(packageRoot, resolve(workspaceSdkDistRoot, 'index.d.ts'))], + '@treeseed/sdk/site-contracts/catalog': [relativePathForTsconfig(packageRoot, resolve(workspaceSdkDistRoot, 'sdk-types.d.ts'))], + '@treeseed/sdk/types/*': [relativePathForTsconfig(packageRoot, resolve(workspaceSdkDistRoot, 'types', '*.d.ts'))], + '@treeseed/sdk/*/index': [relativePathForTsconfig(packageRoot, resolve(workspaceSdkDistRoot, '*', 'index.d.ts'))], + '@treeseed/sdk/*': [relativePathForTsconfig(packageRoot, resolve(workspaceSdkDistRoot, '*.d.ts'))], + }; +} + +export function emitTypeDeclarations() { + const sourceFiles = [ + resolve(srcRoot, 'types/astro-build.d.ts'), + resolve(srcRoot, 'types/cloudflare.ts'), + resolve(srcRoot, 'utils/site-models.ts'), + resolve(srcRoot, 'middleware/editorial-preview.ts'), + resolve(srcRoot, 'site-resources.ts'), + resolve(srcRoot, 'platform-resources.ts'), + resolve(srcRoot, 'api.ts'), + resolve(srcRoot, 'agent.ts'), + resolve(srcRoot, 'agent-runtime.ts'), + resolve(srcRoot, 'railway.ts'), + resolve(srcRoot, 'platform.ts'), + resolve(srcRoot, 'templates.ts'), + resolve(srcRoot, 'services/index.ts'), + resolve(srcRoot, 'plugin-default.ts'), + resolve(srcRoot, 'index.ts'), + ...(existsSync(resolve(srcRoot, 'api')) ? walkFiles(resolve(srcRoot, 'api')).filter((filePath) => filePath.endsWith('.ts') && !filePath.endsWith('.d.ts')) : []), + ...(existsSync(resolve(srcRoot, 'agents')) ? walkFiles(resolve(srcRoot, 'agents')).filter((filePath) => filePath.endsWith('.ts') && !filePath.endsWith('.d.ts')) : []), + ...(existsSync(resolve(srcRoot, 'services')) ? walkFiles(resolve(srcRoot, 'services')).filter((filePath) => filePath.endsWith('.ts') && !filePath.endsWith('.d.ts')) : []), + ].filter((filePath) => existsSync(filePath)); + + if (sourceFiles.length === 0) { + return; + } + + const compilerOptions = { + allowImportingTsExtensions: true, + baseUrl: packageRoot, + declaration: true, + emitDeclarationOnly: true, + module: ts.ModuleKind.ESNext, + moduleResolution: ts.ModuleResolutionKind.Bundler, + outDir: distRoot, + paths: resolveWorkspaceSdkDeclarationPaths() ?? undefined, + rootDir: srcRoot, + skipLibCheck: true, + target: ts.ScriptTarget.ES2022, + }; + + const program = ts.createProgram(sourceFiles, compilerOptions); + const emitResult = program.emit(); + const diagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics ?? []); + + if (diagnostics.length > 0) { + const message = diagnostics + .map((diagnostic) => ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')) + .join('\n'); + throw new Error(`Failed to emit Treeseed core declarations:\n${message}`); + } + + if (emitResult.emitSkipped) { + throw new Error('Failed to emit Treeseed core declarations.'); + } +} + +export function toPosixRelative(fromFile, toFile) { + return relative(dirname(fromFile), toFile).replaceAll('\\', '/'); +} + +export function rewriteStarlightSpecifiers(contents, filePath) { + const componentsIndex = toPosixRelative(filePath, resolve(distRoot, 'vendor', 'starlight', 'components.js')); + const routeData = toPosixRelative(filePath, resolve(distRoot, 'vendor', 'starlight', 'route-data.js')); + + return contents + .replaceAll("'@astrojs/starlight/components'", `'${componentsIndex}'`) + .replaceAll('"@astrojs/starlight/components"', `"${componentsIndex}"`) + .replaceAll("'@astrojs/starlight/route-data'", `'${routeData}'`) + .replaceAll('"@astrojs/starlight/route-data"', `"${routeData}"`) + .replace(/(['"])@astrojs\/starlight\/components\/([^'"\n]+)\1/g, (match, quote, subpath) => { + const target = toPosixRelative(filePath, resolve(distRoot, 'vendor', 'starlight', 'components', subpath)); + return `${quote}${target}${quote}`; + }); +} + +export async function compileVendorPackage(sourceRoot, outputRoot) { + for (const filePath of walkFiles(sourceRoot)) { + if (filePath.endsWith('.d.ts')) { + copyAsset(filePath, sourceRoot, outputRoot); + continue; + } + + const extension = extname(filePath); + if (extension === '.ts') { + await compileModule(filePath, sourceRoot, outputRoot); + continue; + } + + if (COPY_EXTENSIONS.has(extension)) { + copyAsset(filePath, sourceRoot, outputRoot); + if (extension === '.jsonc') { + writeRawTextModule(filePath, sourceRoot, outputRoot); + } + if (extension === '.json') { + writeJsonModule(filePath, sourceRoot, outputRoot); + } + } + } + + for (const filePath of walkFiles(outputRoot)) { + if (filePath.endsWith('.astro')) { + const contents = readFileSync(filePath, 'utf8'); + writeFileSync(filePath, rewriteAstroAssetSpecifiers(contents, filePath), 'utf8'); + continue; + } + if (!filePath.endsWith('.js')) continue; + const contents = readFileSync(filePath, 'utf8'); + writeFileSync(filePath, rewriteVendorImportSpecifiers(contents, filePath), 'utf8'); + } +} + +export function writeCompatibilityEntrypoint(outputFile, contents) { + ensureDir(outputFile); + writeFileSync(outputFile, `${contents} +`, 'utf8'); +} + +export function patchVendoredStarlight(distVendorRoot) { + const virtualConfigFile = resolve(distVendorRoot, 'integrations', 'virtual-user-config.js'); + if (existsSync(virtualConfigFile)) { + const virtualConfig = readFileSync(virtualConfigFile, 'utf8') + .replaceAll('"./content/config.js"', '"./content/config.ts"') + .replaceAll('"./content.config.js"', '"./content.config.ts"'); + writeFileSync(virtualConfigFile, virtualConfig, 'utf8'); + } + const indexFile = resolve(distVendorRoot, 'index.js'); + if (readdirSync(dirname(indexFile)).includes('index.js')) { + let source = readFileSync(indexFile, 'utf8') + .replaceAll('@astrojs/starlight/locals', './locals.js') + .replaceAll('@astrojs/starlight/routes/static/404.astro', './routes/static/404.astro') + .replaceAll('@astrojs/starlight/routes/ssr/404.astro', './routes/ssr/404.astro') + .replaceAll('@astrojs/starlight/routes/static/index.astro', './routes/static/index.astro') + .replaceAll('@astrojs/starlight/routes/ssr/index.astro', './routes/ssr/index.astro'); + + if (!source.includes('from "node:url"')) { + source = `import { fileURLToPath } from "node:url";\n${source}`; + } + + source = source + .replace( + 'addMiddleware({ entrypoint: "./locals.js", order: "pre" });', + 'addMiddleware({ entrypoint: fileURLToPath(new URL("./locals.js", import.meta.url)), order: "pre" });', + ) + .replaceAll( + '"./routes/static/404.astro"', + 'fileURLToPath(new URL("./routes/static/404.astro", import.meta.url))', + ) + .replaceAll( + '"./routes/ssr/404.astro"', + 'fileURLToPath(new URL("./routes/ssr/404.astro", import.meta.url))', + ) + .replaceAll( + '"./routes/static/index.astro"', + 'fileURLToPath(new URL("./routes/static/index.astro", import.meta.url))', + ) + .replaceAll( + '"./routes/ssr/index.astro"', + 'fileURLToPath(new URL("./routes/ssr/index.astro", import.meta.url))', + ); + + source = source + .replaceAll('name: "@astrojs/starlight"', 'name: "@treeseed/core-vendored-starlight"') + .replaceAll('i.name === "@astrojs/starlight"', 'i.name === "@treeseed/core-vendored-starlight"'); + + writeFileSync(indexFile, source, 'utf8'); + } +} + +export function patchRuntime(distRuntimeRoot) { + const middlewareFile = resolve(distRuntimeRoot, 'middleware', 'starlightRouteData.js'); + if (existsSync(middlewareFile)) { + const source = readFileSync(middlewareFile, 'utf8').replaceAll( + '"@astrojs/starlight/route-data"', + '"../vendor/starlight/route-data.js"', + ); + writeFileSync(middlewareFile, source, 'utf8'); + } +} diff --git a/scripts/build/build-dist.ts b/scripts/build/build-dist.ts new file mode 100644 index 00000000..f2b3fd1f --- /dev/null +++ b/scripts/build/build-dist.ts @@ -0,0 +1,3 @@ +export * from '../build-dist/build-runtime.ts'; +export * from '../build-dist/resolve-workspace-sdk-declaration-paths.ts'; +export * from '../build-dist/main.ts'; diff --git a/scripts/build-tenant-worker.ts b/scripts/build/build-tenant-worker.ts similarity index 75% rename from scripts/build-tenant-worker.ts rename to scripts/build/build-tenant-worker.ts index 136b15d9..031fafbc 100644 --- a/scripts/build-tenant-worker.ts +++ b/scripts/build/build-tenant-worker.ts @@ -1,8 +1,8 @@ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import { build } from 'esbuild'; -import { loadTreeseedDeployConfig } from '@treeseed/sdk/platform/deploy-config'; -import { loadTreeseedManifest } from '@treeseed/sdk/platform/tenant-config'; +import { loadDeployConfig } from '../../src/runtime/platform/deploy-config.ts'; +import { loadManifest } from '../../src/runtime/platform/tenant-config.ts'; import { parseSiteConfig } from '../src/utils/site-config-schema.js'; import { packageRoot } from './package-tools.ts'; @@ -19,9 +19,9 @@ function loadSiteConfig(tenantConfig) { return parseSiteConfig(readFileSync(siteConfigPath, 'utf8')); } -const tenantConfig = loadTreeseedManifest(); +const tenantConfig = loadManifest(); const siteConfig = loadSiteConfig(tenantConfig); -const deployConfig = loadTreeseedDeployConfig(); +const deployConfig = loadDeployConfig(); ensureDir(outFile); @@ -35,8 +35,8 @@ await build({ logLevel: 'silent', external: ['cloudflare:sockets'], define: { - __TREESEED_SITE_CONFIG__: JSON.stringify(siteConfig), - __TREESEED_DEPLOY_CONFIG__: JSON.stringify(deployConfig), + SITE_CONFIG: JSON.stringify(siteConfig), + DEPLOY_CONFIG: JSON.stringify(deployConfig), }, }); diff --git a/scripts/build/tenant-build.ts b/scripts/build/tenant-build.ts new file mode 100644 index 00000000..5fddde01 --- /dev/null +++ b/scripts/build/tenant-build.ts @@ -0,0 +1,16 @@ +import { astroBin, createProductionBuildEnv, packageScriptPath, runNodeBinary, runNodeScript } from './package-tools.ts'; + +process.env.LOCAL_DEV_MODE = process.env.LOCAL_DEV_MODE ?? 'cloudflare'; +runNodeScript(packageScriptPath('content/patch-starlight-content-path'), [], { cwd: process.cwd() }); +runNodeBinary(astroBin, ['build'], { + cwd: process.cwd(), + env: createProductionBuildEnv({ + LOCAL_DEV_MODE: process.env.LOCAL_DEV_MODE, + }), +}); +runNodeScript(packageScriptPath('build/build-tenant-worker'), [], { + cwd: process.cwd(), + env: createProductionBuildEnv({ + LOCAL_DEV_MODE: process.env.LOCAL_DEV_MODE, + }), +}); diff --git a/scripts/content/extract-content-schemas.ts b/scripts/content/extract-content-schemas.ts new file mode 100644 index 00000000..e796b7ed --- /dev/null +++ b/scripts/content/extract-content-schemas.ts @@ -0,0 +1,110 @@ +import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import ts from 'typescript'; + +const contentPath = resolve('src/content.ts'); +const text = readFileSync(contentPath, 'utf8'); +const source = ts.createSourceFile(contentPath, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS); +const factory = source.statements.find((statement): statement is ts.FunctionDeclaration => + ts.isFunctionDeclaration(statement) && statement.name?.text === 'createCollections'); +if (!factory?.body) throw new Error('createCollections was not found.'); + +function variableName(statement: ts.Statement) { + if (!ts.isVariableStatement(statement) || statement.declarationList.declarations.length !== 1) return null; + const name = statement.declarationList.declarations[0]?.name; + return name && ts.isIdentifier(name) ? name.text : null; +} + +const variables = new Map(factory.body.statements.map((statement) => [variableName(statement), statement])); +const groups = { + governance: [ + 'pageFieldAliases', 'questionFieldAliases', 'objectiveFieldAliases', 'proposalFieldAliases', 'decisionFieldAliases', + 'contributorReference', 'pageSchema', 'noteSchema', 'questionSchema', 'objectiveSchema', 'proposalSchema', 'decisionSchema', + ], + agent: [ + 'agentFieldAliases', 'profileLinkSchema', 'agentWorktreeSchema', 'agentExecutionSchema', 'agentCapabilitySchema', + 'agentIdentitySchema', 'agentPromptSchema', 'agentToolPolicySchema', 'agentContentScopeSchema', 'agentContentAccessSchema', + 'agentBranchPolicySchema', 'agentQuestionPolicySchema', 'agentContentPermissionSchema', 'agentModePermissionPolicySchema', + 'agentPermissionPolicySchema', 'agentOutputsSchema', 'agentActivityExecutionSchema', 'agentActivityProfileSchema', + 'peopleSchema', 'agentSchema', 'agentTestSchema', + ], + catalog: [ + 'bookFieldAliases', 'sidebarItemSchema', 'bookSchema', 'publisherSchema', 'templateGitSourceSchema', + 'templateR2SourceSchema', 'templateProductSchema', 'knowledgePackSchema', + ], + workday: ['workdaySummaryTaskSchema', 'workdayPriorityItemSchema', 'workdayReleaseSchema', 'workdaySchema'], +} as const; + +const headers = { + governance: `import { reference } from 'astro:content'; +import { z } from 'astro/zod'; +import type { TreeseedFieldAliasRegistry } from '@treeseed/sdk/field-aliases'; +import { preprocessAliasedRecord } from '@treeseed/sdk/field-aliases'; +import { DECISION_MODEL_DEFAULTS, NOTE_MODEL_DEFAULTS, OBJECTIVE_MODEL_DEFAULTS, PAGE_MODEL_DEFAULTS, PROPOSAL_MODEL_DEFAULTS, QUESTION_MODEL_DEFAULTS } from '../utils/site-config.ts'; +import { PROPOSAL_TYPE_ID_PATTERN } from '@treeseed/sdk/agent-capacity'; + +const statusValues = ['live', 'in progress', 'exploratory', 'planned', 'speculative'] as const; +const pageLayoutValues = ['article', 'bridge'] as const; +const questionTypeValues = ['research', 'implementation', 'strategy', 'evaluation'] as const; +const decisionTypeValues = ['approved', 'rejected', 'deferred', 'request_changes', 'superseded'] as const; +const governanceStatusValues = ['draft', 'open', 'voting', 'accepted', 'rejected', 'no_decision_quorum_failed', 'withdrawn', 'superseded'] as const; +const timeHorizonValues = ['near-term', 'mid-term', 'long-term'] as const; +function withOptionalDefault TSchema }>(schema: TSchema, value: unknown) { + return value === undefined ? schema : schema.default(value); +}`, + agent: `import { reference } from 'astro:content'; +import { z } from 'astro/zod'; +import type { TreeseedFieldAliasRegistry } from '@treeseed/sdk/field-aliases'; +import { preprocessAliasedRecord } from '@treeseed/sdk/field-aliases'; +import { AGENT_ACTIVITY_TYPES } from '@treeseed/sdk/types/agents'; +import { AGENT_MODEL_DEFAULTS, PEOPLE_MODEL_DEFAULTS } from '../utils/site-config.ts'; + +const statusValues = ['live', 'in progress', 'exploratory', 'planned', 'speculative'] as const; +const runtimeStatusValues = ['active', 'experimental', 'dormant'] as const; +const agentActivityTypeValues = AGENT_ACTIVITY_TYPES; +const agentHandlerValues = ['writer', 'actor', 'estimate', 'releaser', 'reporter'] as const; +function withOptionalDefault TSchema }>(schema: TSchema, value: unknown) { + return value === undefined ? schema : schema.default(value); +}`, + catalog: `import { z } from 'astro/zod'; +import type { TreeseedFieldAliasRegistry } from '@treeseed/sdk/field-aliases'; +import { preprocessAliasedRecord } from '@treeseed/sdk/field-aliases'; +import { COMMERCE_OFFER_MODES, type CommerceOfferMode } from '@treeseed/sdk/site-contracts/catalog'; +import { BOOK_MODEL_DEFAULTS } from '../utils/site-config.ts'; + +const commerceOfferModeValues = [...COMMERCE_OFFER_MODES] as [CommerceOfferMode, ...CommerceOfferMode[]];`, + workday: `import { z } from 'astro/zod';`, +} as const; + +mkdirSync(resolve('src/content'), { recursive: true }); +for (const [group, names] of Object.entries(groups) as Array<[keyof typeof groups, readonly string[]]>) { + const statements = names.map((name) => { + const statement = variables.get(name); + if (!statement) throw new Error(`Missing ${name} in createCollections.`); + return statement.getText(source); + }); + const returned = names.filter((name) => /Schema$/u.test(name)); + const functionName = `create${group[0]!.toUpperCase()}${group.slice(1)}CollectionSchemas`; + const body = statements.map((statement) => statement.split('\n').map((line) => `\t${line}`).join('\n')).join('\n\n'); + const output = `${headers[group]}\n\nexport function ${functionName}() {\n${body}\n\n\treturn { ${returned.join(', ')} };\n}\n`; + writeFileSync(resolve('src/content', `${group}-schemas.ts`), output); +} + +const firstExtracted = variables.get('pageFieldAliases'); +const remaining = variables.get('docsCollectionProvider'); +if (!firstExtracted || !remaining) throw new Error('Could not identify the schema extraction boundaries.'); +const imports = `import { createGovernanceCollectionSchemas } from './content/governance-schemas.ts'; +import { createAgentCollectionSchemas } from './content/agent-schemas.ts'; +import { createCatalogCollectionSchemas } from './content/catalog-schemas.ts'; +import { createWorkdayCollectionSchemas } from './content/workday-schemas.ts'; +`; +const insertionPoint = text.lastIndexOf('\n', factory.getStart(source)) + 1; +const withImports = `${text.slice(0, insertionPoint)}${imports}${text.slice(insertionPoint, firstExtracted.getStart(source))}`; +const destructuring = `const { pageSchema, noteSchema, questionSchema, objectiveSchema, proposalSchema, decisionSchema } = createGovernanceCollectionSchemas(); + const { peopleSchema, agentSchema, agentTestSchema } = createAgentCollectionSchemas(); + const { bookSchema, templateProductSchema, knowledgePackSchema } = createCatalogCollectionSchemas(); + const { workdaySchema } = createWorkdayCollectionSchemas(); + `; +const adjustedRemainingStart = remaining.getStart(source) + imports.length; +const rewritten = `${withImports}${destructuring}${text.slice(adjustedRemainingStart - imports.length)}`; +writeFileSync(contentPath, rewritten); diff --git a/scripts/patch-starlight-content-path.ts b/scripts/content/patch-starlight-content-path.ts similarity index 85% rename from scripts/patch-starlight-content-path.ts rename to scripts/content/patch-starlight-content-path.ts index 95af97f7..0eb0a8f6 100644 --- a/scripts/patch-starlight-content-path.ts +++ b/scripts/content/patch-starlight-content-path.ts @@ -1,19 +1,19 @@ import fs from 'node:fs/promises'; import path from 'node:path'; import process from 'node:process'; -import { packageRoot } from './package-tools.ts'; +import { packageRoot } from '../packages/package-tools.ts'; +import { resolveStarlightRoot } from './resolve-starlight-root.ts'; const legacyDocsRoot = path.resolve(packageRoot, '../..'); -const candidateStarlightRoots = [ +const candidateStarlightRoots = [...new Set([ + resolveStarlightRoot(), path.join(process.cwd(), 'node_modules/@astrojs/starlight'), path.join(packageRoot, 'node_modules/@astrojs/starlight'), path.join(legacyDocsRoot, 'node_modules/@astrojs/starlight'), -]; -const candidateCollectionFiles = [ - path.join(process.cwd(), 'node_modules/@astrojs/starlight/utils/collection.ts'), - path.join(packageRoot, 'node_modules/@astrojs/starlight/utils/collection.ts'), - path.join(legacyDocsRoot, 'node_modules/@astrojs/starlight/utils/collection.ts'), -]; +].filter((root): root is string => Boolean(root)))]; +const candidateCollectionFiles = candidateStarlightRoots.map((root) => + path.join(root, 'utils/collection.ts') +); const originalSource = `export type StarlightCollection = 'docs' | 'i18n'; @@ -130,8 +130,17 @@ async function copyVendoredTree(sourceRoot, targetRoot) { async function patchStarlightPackageRoot(starlightRoot) { const packageJsonPath = path.join(starlightRoot, 'package.json'); const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8')); - const coreVendorRoot = path.join(path.dirname(path.dirname(starlightRoot)), '@treeseed/core/dist/vendor/starlight'); + const coreVendorRoot = path.join(packageRoot, 'dist/vendor/starlight'); await copyVendoredTree(coreVendorRoot, starlightRoot); + const virtualConfigPath = path.join(starlightRoot, 'integrations/virtual-user-config.js'); + const virtualConfig = await fs.readFile(virtualConfigPath, 'utf8'); + const correctedVirtualConfig = virtualConfig + .replaceAll('"./content/config.js"', '"./content/config.ts"') + .replaceAll('"./content.config.js"', '"./content.config.ts"'); + if (correctedVirtualConfig.includes('./content/config.js') || correctedVirtualConfig.includes('./content.config.js')) { + throw new Error(`Unexpected Starlight virtual collection config format in ${virtualConfigPath}`); + } + await fs.writeFile(virtualConfigPath, correctedVirtualConfig, 'utf8'); packageJson.exports = { ...packageJson.exports, diff --git a/scripts/content/resolve-starlight-root.ts b/scripts/content/resolve-starlight-root.ts new file mode 100644 index 00000000..556b7f99 --- /dev/null +++ b/scripts/content/resolve-starlight-root.ts @@ -0,0 +1,11 @@ +import { createRequire } from 'node:module'; +import { dirname } from 'node:path'; + +export function resolveStarlightRoot(fromUrl = import.meta.url): string | null { + try { + const entryPath = createRequire(fromUrl).resolve('@astrojs/starlight'); + return dirname(entryPath); + } catch { + return null; + } +} diff --git a/scripts/development/build-watch.ts b/scripts/development/build-watch.ts new file mode 100644 index 00000000..64603cd3 --- /dev/null +++ b/scripts/development/build-watch.ts @@ -0,0 +1,9 @@ +import { spawnSync } from 'node:child_process'; +import { mkdirSync, renameSync, rmSync, writeFileSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; + +const marker = resolve('dist/.treeseed-build-complete.json'), temporary = `${marker}.new`; +rmSync(marker, { force: true }); +const result = spawnSync('npm', ['run', 'build:dist'], { cwd: process.cwd(), env: process.env, stdio: 'inherit' }); +if (result.status !== 0) process.exitCode = result.status ?? 1; +else { mkdirSync(dirname(marker), { recursive: true }); writeFileSync(temporary, `${JSON.stringify({ completedAt: new Date().toISOString() })}\n`); renameSync(temporary, marker); } diff --git a/scripts/assert-release-tag-version.ts b/scripts/packages/assert-release-tag-version.ts similarity index 76% rename from scripts/assert-release-tag-version.ts rename to scripts/packages/assert-release-tag-version.ts index 4620369b..485cb43d 100644 --- a/scripts/assert-release-tag-version.ts +++ b/scripts/packages/assert-release-tag-version.ts @@ -2,7 +2,7 @@ import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; -const packageRoot = resolve(fileURLToPath(new URL('..', import.meta.url))); +const packageRoot = resolve(fileURLToPath(new URL('../..', import.meta.url))); const packageJsonPath = resolve(packageRoot, 'package.json'); const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')); const packageVersion = packageJson.version; @@ -14,8 +14,8 @@ if (!tagName) { process.exit(1); } -if (!/^\d+\.\d+\.\d+$/.test(tagName)) { - console.error(`Release tag "${tagName}" must use plain semver format "x.y.z".`); +if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.test(tagName)) { + console.error(`Release tag "${tagName}" must use semantic version format.`); process.exit(1); } diff --git a/scripts/package-tools.ts b/scripts/packages/package-tools.ts similarity index 95% rename from scripts/package-tools.ts rename to scripts/packages/package-tools.ts index 9c03d4ad..3f9988ce 100644 --- a/scripts/package-tools.ts +++ b/scripts/packages/package-tools.ts @@ -7,7 +7,7 @@ import { createRequire } from 'node:module'; const require = createRequire(import.meta.url); const scriptRoot = dirname(fileURLToPath(import.meta.url)); -const packageCandidate = resolve(scriptRoot, '..'); +const packageCandidate = resolve(scriptRoot, '../..'); export const packageRoot = packageCandidate.endsWith('/dist') ? resolve(packageCandidate, '..') @@ -55,8 +55,8 @@ export function isWorkspaceRoot(root = process.cwd()) { export function createProductionBuildEnv(extraEnv = {}) { return { - TREESEED_LOCAL_DEV_MODE: 'cloudflare', - TREESEED_FORMS_LOCAL_BYPASS_CLOUDFLARE_GUARDS: '', + LOCAL_DEV_MODE: 'cloudflare', + FORMS_LOCAL_BYPASS_CLOUDFLARE_GUARDS: '', TREESEED_PUBLIC_DEV_WATCH_RELOAD: '', ...extraEnv, }; diff --git a/scripts/packages/publish-package.ts b/scripts/packages/publish-package.ts new file mode 100644 index 00000000..d7d7c132 --- /dev/null +++ b/scripts/packages/publish-package.ts @@ -0,0 +1,44 @@ +import { spawnSync } from 'node:child_process'; +import { resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const packageRoot = resolve(fileURLToPath(new URL('../..', import.meta.url))); +const extraArgs = process.argv.slice(2); +const tagName = process.env.GITHUB_REF_NAME; +const semverTagPattern = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/; + +if (tagName && !semverTagPattern.test(tagName)) { + console.error(`Refusing to publish @treeseed/core from invalid semantic version tag "${tagName}".`); + process.exit(1); +} + +if (extraArgs.some((argument) => argument === '--tag' || argument.startsWith('--tag='))) { + console.error('release:publish owns npm dist-tag selection; a caller cannot override --tag.'); + process.exit(1); +} + +const npmDistTag = tagName?.includes('-') ? 'next' : 'latest'; +const publishTarget = extraArgs.length > 0 ? resolve(packageRoot, extraArgs.shift()!) : '.'; +const npmArgs = ['publish', publishTarget, '--access', 'public', '--tag', npmDistTag]; + +if (process.env.GITHUB_ACTIONS === 'true') { + npmArgs.push('--provenance'); +} + +npmArgs.push(...extraArgs); + +const result = spawnSync('npm', npmArgs, { + cwd: packageRoot, + encoding: 'utf8', + env: process.env, +}); + +if (result.stdout) process.stdout.write(result.stdout); +if (result.stderr) process.stderr.write(result.stderr); + +if (result.error) { + console.error(result.error.message); + process.exit(1); +} + +process.exit(result.status ?? 1); diff --git a/scripts/packages/release-custody.ts b/scripts/packages/release-custody.ts new file mode 100644 index 00000000..3eded9de --- /dev/null +++ b/scripts/packages/release-custody.ts @@ -0,0 +1,29 @@ +import { execFileSync } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { readFileSync, statSync, writeFileSync } from 'node:fs'; +import { basename, resolve } from 'node:path'; +import { releaseEvidenceSchema } from '@treeseed/sdk/development'; + +const root = resolve(import.meta.dirname, '../..'); +const sha256 = (path: string) => `sha256:${createHash('sha256').update(readFileSync(path)).digest('hex')}` as const; +const command = process.argv[2]; +const evidencePath = resolve(root, process.argv[3] ?? 'artifacts/release-evidence-v1.json'); +if (command === 'seal') { + const packagePath = resolve(root, process.argv[4]!); + const sbomPath = resolve(root, 'artifacts/sbom.cdx.json'); + const pkg = JSON.parse(readFileSync(resolve(root, 'package.json'), 'utf8')) as { name: string; version: string }; + const sourceCommit = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' }).trim(); + const artifacts = [ + { id: 'core-package', kind: 'npm-package' as const, identity: basename(packagePath), digest: sha256(packagePath), mediaType: 'application/gzip', size: statSync(packagePath).size }, + { id: 'core-sbom', kind: 'sbom' as const, identity: basename(sbomPath), digest: sha256(sbomPath), mediaType: 'application/vnd.cyclonedx+json', size: statSync(sbomPath).size }, + ]; + const receiptDigest = `sha256:${createHash('sha256').update(`${sourceCommit}\n${artifacts.map(({ digest }) => digest).join('\n')}`).digest('hex')}` as const; + const evidence = releaseEvidenceSchema.parse({ schemaVersion: 'treeseed.release-evidence/v1', candidate: { id: `candidate-${sourceCommit.slice(0, 12)}`, receiptDigest, sourceCommit, stagingRef: process.env.GITHUB_REF ?? 'refs/heads/staging', workflowRunId: process.env.GITHUB_RUN_ID ?? '1', createdAt: new Date().toISOString() }, packages: [{ projectId: 'core', name: pkg.name, version: pkg.version, minimumBump: 'patch' }], artifacts, contractBundles: [], compatibilityAttestations: [], verification: { status: 'passed', operations: ['npm run verify:direct'], completedAt: new Date().toISOString() } }); + writeFileSync(evidencePath, `${JSON.stringify(evidence, null, 2)}\n`); +} else if (command === 'verify') { + const evidence = releaseEvidenceSchema.parse(JSON.parse(readFileSync(evidencePath, 'utf8'))); + const commit = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' }).trim(); + if (evidence.candidate.sourceCommit !== commit) throw new Error('Candidate source commit differs from tagged commit.'); + if (process.env.GITHUB_REF?.startsWith('refs/tags/') && process.env.GITHUB_REF_NAME !== evidence.packages[0]?.version) throw new Error('Tag does not match sealed package version.'); + for (const artifact of evidence.artifacts) if (sha256(resolve(evidencePath, '..', artifact.identity)) !== artifact.digest) throw new Error(`Candidate artifact digest mismatch: ${artifact.identity}.`); +} else throw new Error('release:custody requires seal or verify.'); diff --git a/scripts/release-verify.ts b/scripts/packages/release-verify.ts similarity index 63% rename from scripts/release-verify.ts rename to scripts/packages/release-verify.ts index ea1c0829..8673f4ec 100644 --- a/scripts/release-verify.ts +++ b/scripts/packages/release-verify.ts @@ -1,12 +1,10 @@ -import { existsSync, mkdirSync, readFileSync, readdirSync, symlinkSync } from 'node:fs'; -import { dirname, extname, join, resolve } from 'node:path'; +import { readFileSync, readdirSync } from 'node:fs'; +import { extname, join, resolve } from 'node:path'; import { spawnSync } from 'node:child_process'; import { packageRoot } from './package-tools.ts'; const textExtensions = new Set(['.js', '.ts', '.d.ts', '.json', '.astro', '.css']); const forbiddenPatterns = [ - /['"`]file:[^'"`\n]+['"`]/, - /['"`]workspace:[^'"`\n]+['"`]/, /['"`](?:\.\.\/|\.\/)[^'"`\n]*src\/[^'"`\n]*\.(?:[cm]?js|ts|tsx|json|astro|css)['"`]/, /['"`][^'"`\n]*\/packages\/[^'"`\n]*\/src\/[^'"`\n]*['"`]/, ]; @@ -23,58 +21,10 @@ function run(command: string, args: string[], cwd = packageRoot) { } } -function runtimeDependencyNames() { - const packageJson = JSON.parse(readFileSync(resolve(packageRoot, 'package.json'), 'utf8')) as { - dependencies?: Record; - }; - return Object.keys(packageJson.dependencies ?? {}); -} - function isPackedTarballSpecifier(value: string): boolean { return /^file:/u.test(value) && /\.tgz(?:[?#].*)?$/u.test(value); } -function ensureWorkspaceRuntimePackageLinks() { - for (const packageName of runtimeDependencyNames()) { - if (!packageName.startsWith('@treeseed/')) { - continue; - } - const runtimePackageRoot = resolve(packageRoot, '..', packageName.slice('@treeseed/'.length)); - if (!existsSync(resolve(runtimePackageRoot, 'package.json'))) { - continue; - } - const linkPath = resolve(packageRoot, 'node_modules', ...packageName.split('/')); - if (existsSync(linkPath)) { - continue; - } - mkdirSync(dirname(linkPath), { recursive: true }); - symlinkSync(runtimePackageRoot, linkPath, 'dir'); - } -} - -function prepareWorkspaceRuntimePackageBuilds() { - for (const packageName of runtimeDependencyNames()) { - if (!packageName.startsWith('@treeseed/')) { - continue; - } - const runtimePackageRoot = resolve(packageRoot, '..', packageName.slice('@treeseed/'.length)); - const packageJsonPath = resolve(runtimePackageRoot, 'package.json'); - if (!existsSync(packageJsonPath)) { - continue; - } - const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as { - scripts?: Record; - }; - if (packageJson.scripts?.['build:dist']) { - run('npm', ['run', 'build:dist'], runtimePackageRoot); - continue; - } - if (packageJson.scripts?.build) { - run('npm', ['run', 'build'], runtimePackageRoot); - } - } -} - function assertNoLocalDependencyLinks() { const packageJson = JSON.parse(readFileSync(resolve(packageRoot, 'package.json'), 'utf8')) as Record | undefined>; for (const sectionName of ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']) { @@ -133,8 +83,6 @@ function scanDirectory(root: string) { } assertNoLocalDependencyLinks(); -ensureWorkspaceRuntimePackageLinks(); -prepareWorkspaceRuntimePackageBuilds(); run('npm', ['run', 'lint']); scanDirectory(resolve(packageRoot, 'dist')); run('npm', ['run', 'test:unit']); diff --git a/scripts/packages/verify-published-package.ts b/scripts/packages/verify-published-package.ts new file mode 100644 index 00000000..d10ac042 --- /dev/null +++ b/scripts/packages/verify-published-package.ts @@ -0,0 +1,21 @@ +import { createHash } from 'node:crypto'; +import { mkdtempSync, readFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { execFileSync, spawnSync } from 'node:child_process'; + +const pkg = JSON.parse(readFileSync('package.json', 'utf8')) as { name: string; version: string }; +const directory = mkdtempSync(join(tmpdir(), 'treeseed-core-readback-')); +let packed: Array<{ filename: string }> | undefined; +for (let attempt = 1; attempt <= 60; attempt += 1) { + const result = spawnSync('npm', ['pack', `${pkg.name}@${pkg.version}`, '--json', '--pack-destination', directory], { encoding: 'utf8' }); + if (result.status === 0) { packed = JSON.parse(result.stdout); break; } + if (attempt === 60) throw new Error(result.stderr || `npm registry read-back did not converge for ${pkg.name}@${pkg.version}.`); + await new Promise((resolve) => setTimeout(resolve, 3_000)); +} +if (!packed) throw new Error(`npm registry read-back did not converge for ${pkg.name}@${pkg.version}.`); +const latest = execFileSync('npm', ['view', pkg.name, 'dist-tags.latest'], { encoding: 'utf8' }).trim(); +if (process.env.EXPECTED_LATEST && latest !== process.env.EXPECTED_LATEST) throw new Error('Prerelease changed npm latest.'); +const actual = createHash('sha256').update(readFileSync(join(directory, packed[0]!.filename))).digest('hex'); +if (actual !== process.env.EXPECTED_SHA256) throw new Error(`Published package digest mismatch: ${actual}.`); +console.log(JSON.stringify({ ok: true, package: `${pkg.name}@${pkg.version}`, sha256: actual })); diff --git a/scripts/publish-package.ts b/scripts/publish-package.ts deleted file mode 100644 index 0e18434c..00000000 --- a/scripts/publish-package.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { spawnSync } from 'node:child_process'; -import { resolve } from 'node:path'; -import { fileURLToPath } from 'node:url'; - -const packageRoot = resolve(fileURLToPath(new URL('..', import.meta.url))); -const extraArgs = process.argv.slice(2); -const tagName = process.env.GITHUB_REF_NAME; - -if (tagName && !/^\d+\.\d+\.\d+$/.test(tagName)) { - console.error(`Refusing to publish @treeseed/core from non-stable tag "${tagName}".`); - process.exit(1); -} - -const npmArgs = ['publish', '.', '--access', 'public']; - -if (process.env.GITHUB_ACTIONS === 'true') { - npmArgs.push('--provenance'); -} - -npmArgs.push(...extraArgs); - -const result = spawnSync('npm', npmArgs, { - cwd: packageRoot, - encoding: 'utf8', - env: process.env, -}); - -if (result.stdout) process.stdout.write(result.stdout); -if (result.stderr) process.stderr.write(result.stderr); - -if (result.error) { - console.error(result.error.message); - process.exit(1); -} - -process.exit(result.status ?? 1); diff --git a/scripts/dev-platform.ts b/scripts/runtime/dev-platform.ts similarity index 75% rename from scripts/dev-platform.ts rename to scripts/runtime/dev-platform.ts index 264d4d67..dcc3e074 100644 --- a/scripts/dev-platform.ts +++ b/scripts/runtime/dev-platform.ts @@ -1,19 +1,19 @@ #!/usr/bin/env node import { - runTreeseedIntegratedDev, - type TreeseedIntegratedDevFeedbackMode, - type TreeseedLocalRuntimeMode, - type TreeseedIntegratedDevOpenMode, - type TreeseedIntegratedDevSetupMode, - type TreeseedIntegratedDevSurface, - type TreeseedManagedDevAction, -} from '../src/dev.ts'; -import { runTreeseedManagedDev } from '@treeseed/sdk'; + runIntegratedDev, + runManagedDev, + type IntegratedDevFeedbackMode, + type LocalRuntimeMode, + type IntegratedDevOpenMode, + type IntegratedDevSetupMode, + type IntegratedDevSurface, + type ManagedDevAction, +} from '../../src/runtime/dev.ts'; const rawArgs = process.argv.slice(2); const managedActions = new Set(['start', 'status', 'logs', 'stop', 'restart']); -const action = managedActions.has(rawArgs[0] ?? '') ? rawArgs[0] as TreeseedManagedDevAction : null; +const action = managedActions.has(rawArgs[0] ?? '') ? rawArgs[0] as ManagedDevAction : null; const args = action ? rawArgs.slice(1) : rawArgs; function readFlag(name: string) { @@ -37,7 +37,7 @@ function readNumberOption(name: string) { return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined; } -function parseSurface(value: string | undefined): TreeseedIntegratedDevSurface | undefined { +function parseSurface(value: string | undefined): IntegratedDevSurface | undefined { if ( value === 'web' || value === 'api' || @@ -53,28 +53,28 @@ function parseSurface(value: string | undefined): TreeseedIntegratedDevSurface | return undefined; } -function parseSetupMode(value: string | undefined): TreeseedIntegratedDevSetupMode | undefined { +function parseSetupMode(value: string | undefined): IntegratedDevSetupMode | undefined { if (value === 'auto' || value === 'check' || value === 'off') { return value; } return undefined; } -function parseFeedbackMode(value: string | undefined): TreeseedIntegratedDevFeedbackMode | undefined { +function parseFeedbackMode(value: string | undefined): IntegratedDevFeedbackMode | undefined { if (value === 'live' || value === 'restart' || value === 'off') { return value; } return undefined; } -function parseOpenMode(value: string | undefined): TreeseedIntegratedDevOpenMode | undefined { +function parseOpenMode(value: string | undefined): IntegratedDevOpenMode | undefined { if (value === 'auto' || value === 'on' || value === 'off') { return value; } return undefined; } -function parseLocalRuntimeMode(value: string | undefined): TreeseedLocalRuntimeMode | undefined { +function parseLocalRuntimeMode(value: string | undefined): LocalRuntimeMode | undefined { if (value === 'auto' || value === 'provider' || value === 'local') { return value; } @@ -121,9 +121,9 @@ async function main() { env: readForwardedEnvironment(), }; if (!action) { - return runTreeseedIntegratedDev(common); + return runIntegratedDev(common); } - return runTreeseedManagedDev( + return runManagedDev( { ...common, action, diff --git a/scripts/support/check-file-architecture.ts b/scripts/support/check-file-architecture.ts new file mode 100644 index 00000000..fbb627d9 --- /dev/null +++ b/scripts/support/check-file-architecture.ts @@ -0,0 +1,126 @@ +import { existsSync, readFileSync } from 'node:fs'; +import { dirname } from 'node:path'; +import { spawnSync } from 'node:child_process'; +import ts from 'typescript'; + +function repositoryFiles() { + const result = spawnSync('git', ['ls-files', '-z', '--cached', '--others', '--exclude-standard'], { encoding: 'utf8' }); + if (result.status !== 0) throw new Error(result.stderr || 'Unable to enumerate repository files.'); + return result.stdout.split('\0').filter(Boolean).filter(existsSync); +} + +const violations: string[] = []; +const executableExtensions = /\.(?:astro|c|cjs|cts|ex|exs|h|js|jsx|mjs|mts|py|rs|sh|ts|tsx)$/u; +const excludedSegments = new Set([ + '.astro', '.git', '.treeseed', '_build', 'build', 'coverage', 'deps', 'dist', 'generated', + 'migrations', 'node_modules', 'snapshots', 'target', 'vendor', +]); +const isExcluded = (path: string) => path.split('/').some((segment) => + excludedSegments.has(segment) || segment.startsWith('.treeseed-')); +const isExecutable = (path: string) => executableExtensions.test(path) && !isExcluded(path); +const directFileCounts = new Map(); + +function brandedIdentifier(name: string) { + if (/^TREESEED_[A-Z0-9_]+$/u.test(name)) return false; + if (/^__TREESEED_[A-Z0-9_]+__$/u.test(name)) return false; + return /(?:KnowledgeCoop|TreeSeed|Treeseed|TREESEED_)/u.test(name); +} + +function identifierText(name: ts.DeclarationName | ts.BindingName | undefined): string[] { + if (!name) return []; + if (ts.isIdentifier(name)) return [name.text]; + if (ts.isObjectBindingPattern(name) || ts.isArrayBindingPattern(name)) { + return name.elements.flatMap((element) => + ts.isBindingElement(element) ? identifierText(element.name) : []); + } + return []; +} + +function checkTypeScriptDeclarations(path: string, source: string) { + const executableSource = path.endsWith('.astro') + ? source.match(/^---\s*([\s\S]*?)\s*---/u)?.[1] ?? '' + : source; + const sourceFile = ts.createSourceFile( + path, + executableSource, + ts.ScriptTarget.Latest, + true, + path.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS, + ); + const visit = (node: ts.Node) => { + if ( + ts.isVariableDeclaration(node) + || ts.isParameter(node) + || ts.isFunctionDeclaration(node) + || ts.isClassDeclaration(node) + || ts.isInterfaceDeclaration(node) + || ts.isTypeAliasDeclaration(node) + || ts.isEnumDeclaration(node) + || ts.isMethodDeclaration(node) + || ts.isMethodSignature(node) + || ts.isPropertyDeclaration(node) + || ts.isPropertySignature(node) + || ts.isEnumMember(node) + || ts.isModuleDeclaration(node) + ) { + for (const name of identifierText(node.name)) { + if (brandedIdentifier(name)) violations.push(`${path}: redundant product name in symbol ${name}`); + } + } + ts.forEachChild(node, visit); + }; + visit(sourceFile); +} + +for (const path of repositoryFiles()) { + const basename = path.split('/').at(-1) ?? path; + if (isExecutable(path)) { + directFileCounts.set(dirname(path), (directFileCounts.get(dirname(path)) ?? 0) + 1); + if (/(?:knowledge[-_]?coop|tree[-_]?seed|treeseed)/iu.test(basename)) { + violations.push(`${path}: redundant product name in executable filename`); + } + } + for (const segment of path.split('/').slice(0, -1)) { + if (/^(?:chunk|module|part|section|segment)-\d+$/u.test(segment)) { + violations.push(`${path}: generic numbered directory partition ${segment}`); + } + } + if (/^(?:chunk|module|part|section|segment)-\d+\./u.test(basename)) { + violations.push(`${path}: generic numbered partition`); + } + if (/-\d{1,2}\.(?:scenarios|spec|test)\.[^.]+$/u.test(basename)) { + violations.push(`${path}: ordinal test or scenario suffix`); + } + if (path.startsWith('src/api/routes/') && basename.includes('-through-')) { + violations.push(`${path}: route-order range name`); + } + if (/split-(?:class-methods|large-tests|module-declarations)\.[^.]+$/u.test(basename)) { + violations.push(`${path}: mechanical split script`); + } + if (path.startsWith('scripts/') && /\.(?:spec|test)\.[^.]+$/u.test(basename)) { + violations.push(`${path}: test belongs under tests/`); + } + if (/\.(?:astro|js|jsx|ts|tsx)$/u.test(path)) { + const source = readFileSync(path, 'utf8'); + if (/\b(?:CLI_COMMAND_OVERLAYS|CLI_ONLY_OPERATION_SPECS|MODULE|PART)_\d+\b/u.test(source)) { + violations.push(`${path}: ordinal partition symbol`); + } + checkTypeScriptDeclarations(path, source); + } +} + +for (const [directory, count] of directFileCounts) { + if (count > 10) violations.push(`${directory}: ${count} direct executable files exceeds the limit of 10`); +} + +if (existsSync('test') && existsSync('tests')) { + violations.push('test and tests: duplicate TypeScript test roots'); +} + +if (violations.length > 0) { + console.error('File architecture policy failed:'); + for (const violation of violations) console.error(`- ${violation}`); + process.exit(1); +} + +console.log('File architecture policy passed: filenames and test roots express functional ownership.'); diff --git a/scripts/support/check-file-lengths.ts b/scripts/support/check-file-lengths.ts new file mode 100644 index 00000000..acf383ae --- /dev/null +++ b/scripts/support/check-file-lengths.ts @@ -0,0 +1,47 @@ +import { existsSync, readFileSync } from 'node:fs'; +import { spawnSync } from 'node:child_process'; + +const hardMaximum = 500; +const targetMaximum = 350; +const codeExtensions = /\.(?:astro|cjs|ex|exs|go|js|jsx|mjs|py|rs|svelte|ts|tsx|vue)$/u; +const excludedSegments = new Set([ + '.agent-worktrees', '.git', '.treeseed', '_build', 'build', 'coverage', 'deps', 'dist', + 'generated', 'migrations', 'node_modules', 'snapshots', 'target', 'vendor', +]); + +function isExcluded(path: string) { + const segments = path.split('/'); + return segments.some((segment) => excludedSegments.has(segment)) + || segments.some((segment) => segment.startsWith('.treeseed-')) + || /(?:^|\.)generated\./u.test(segments.at(-1) ?? '') + || /-snapshots(?:\/|$)/u.test(path); +} + +function gitFiles(args: string[]) { + const result = spawnSync('git', ['ls-files', '-z', ...args], { encoding: 'utf8' }); + if (result.status !== 0) throw new Error(result.stderr || 'Unable to enumerate repository files.'); + return result.stdout.split('\0').filter(Boolean); +} + +const codeFiles = [...new Set([ + ...gitFiles([]), + ...gitFiles(['--others', '--exclude-standard']), +])].filter((path) => existsSync(path) && codeExtensions.test(path) && !isExcluded(path)); + +const overLimit: Array<{ path: string; lines: number }> = []; +const aboveTarget: Array<{ path: string; lines: number }> = []; +for (const path of codeFiles) { + const lines = readFileSync(path, 'utf8').split(/\r?\n/u).length; + if (lines > hardMaximum) overLimit.push({ path, lines }); + else if (lines > targetMaximum) aboveTarget.push({ path, lines }); +} + +aboveTarget.sort((left, right) => right.lines - left.lines); +overLimit.sort((left, right) => right.lines - left.lines); +if (aboveTarget.length > 0) console.warn(`File-length target: ${aboveTarget.length} handwritten file(s) are above ${targetMaximum} lines but within the hard limit.`); +if (overLimit.length > 0) { + console.error(`File-length policy failed: ${overLimit.length} handwritten file(s) exceed ${hardMaximum} lines:`); + for (const entry of overLimit) console.error(`- ${entry.lines} ${entry.path}`); + process.exit(1); +} +console.log(`File-length policy passed: no handwritten code exceeds ${hardMaximum} lines.`); diff --git a/scripts/paths.ts b/scripts/support/paths.ts similarity index 77% rename from scripts/paths.ts rename to scripts/support/paths.ts index d9d48cb1..447b6718 100644 --- a/scripts/paths.ts +++ b/scripts/support/paths.ts @@ -1,8 +1,8 @@ import { resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; -import { requireSharedFixtureRoot } from './fixture-tools.ts'; +import { requireSharedFixtureRoot } from '../agents/fixture-tools.ts'; -const packageCandidate = resolve(fileURLToPath(new URL('..', import.meta.url))); +const packageCandidate = resolve(fileURLToPath(new URL('../..', import.meta.url))); export const packageRoot = packageCandidate.endsWith('/dist') ? resolve(packageCandidate, '..') diff --git a/scripts/prepare.ts b/scripts/support/prepare.ts similarity index 100% rename from scripts/prepare.ts rename to scripts/support/prepare.ts diff --git a/scripts/tenant-astro-command.ts b/scripts/support/tenant-astro-command.ts similarity index 56% rename from scripts/tenant-astro-command.ts rename to scripts/support/tenant-astro-command.ts index ba48403e..220004bd 100644 --- a/scripts/tenant-astro-command.ts +++ b/scripts/support/tenant-astro-command.ts @@ -1,4 +1,4 @@ -import { astroBin, runNodeBinary } from './package-tools.ts'; +import { astroBin, runNodeBinary } from '../packages/package-tools.ts'; const args = process.argv.slice(2); diff --git a/scripts/support/tenant-check.ts b/scripts/support/tenant-check.ts new file mode 100644 index 00000000..dbdff8f1 --- /dev/null +++ b/scripts/support/tenant-check.ts @@ -0,0 +1,7 @@ +import { createProductionBuildEnv, packageScriptPath, runNodeScript } from '../packages/package-tools.ts'; + +runNodeScript(packageScriptPath('content/patch-starlight-content-path'), [], { cwd: process.cwd() }); +runNodeScript(packageScriptPath('build/tenant-build'), [], { + cwd: process.cwd(), + env: createProductionBuildEnv(), +}); diff --git a/scripts/support/verify-driver.ts b/scripts/support/verify-driver.ts new file mode 100644 index 00000000..664aca3f --- /dev/null +++ b/scripts/support/verify-driver.ts @@ -0,0 +1,13 @@ +#!/usr/bin/env node + +import { spawnSync } from 'node:child_process'; + +if (process.env.TREESEED_VERIFY_ENTRYPOINT_CHECK === 'true') process.exit(0); + +const result = spawnSync('npm', ['run', 'verify:direct'], { + cwd: process.cwd(), + env: process.env, + stdio: 'inherit', +}); + +process.exit(result.status ?? 1); diff --git a/scripts/tenant-build.ts b/scripts/tenant-build.ts deleted file mode 100644 index 80895a1f..00000000 --- a/scripts/tenant-build.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { astroBin, createProductionBuildEnv, packageScriptPath, runNodeBinary, runNodeScript } from './package-tools.ts'; - -process.env.TREESEED_LOCAL_DEV_MODE = process.env.TREESEED_LOCAL_DEV_MODE ?? 'cloudflare'; -const publishedRuntime = process.env.TREESEED_CONTENT_SERVING_MODE === 'published_runtime'; - -runNodeScript(packageScriptPath('patch-starlight-content-path'), [], { cwd: process.cwd() }); -if (!publishedRuntime) { - runNodeScript(packageScriptPath('aggregate-book'), [], { cwd: process.cwd() }); -} -runNodeBinary(astroBin, ['build'], { - cwd: process.cwd(), - env: createProductionBuildEnv({ - TREESEED_LOCAL_DEV_MODE: process.env.TREESEED_LOCAL_DEV_MODE, - }), -}); -runNodeScript(packageScriptPath('build-tenant-worker'), [], { - cwd: process.cwd(), - env: createProductionBuildEnv({ - TREESEED_LOCAL_DEV_MODE: process.env.TREESEED_LOCAL_DEV_MODE, - }), -}); diff --git a/scripts/tenant-check.ts b/scripts/tenant-check.ts deleted file mode 100644 index d487f02d..00000000 --- a/scripts/tenant-check.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { createProductionBuildEnv, packageScriptPath, runNodeScript } from './package-tools.ts'; - -const publishedRuntime = process.env.TREESEED_CONTENT_SERVING_MODE === 'published_runtime'; - -runNodeScript(packageScriptPath('patch-starlight-content-path'), [], { cwd: process.cwd() }); -if (!publishedRuntime) { - runNodeScript(packageScriptPath('aggregate-book'), [], { cwd: process.cwd() }); -} -runNodeScript(packageScriptPath('tenant-build'), [], { - cwd: process.cwd(), - env: createProductionBuildEnv(), -}); diff --git a/scripts/testing/fixture-support.ts b/scripts/testing/fixture-support.ts new file mode 100644 index 00000000..2422e7cc --- /dev/null +++ b/scripts/testing/fixture-support.ts @@ -0,0 +1,446 @@ +import { existsSync,mkdirSync,readdirSync,readFileSync,rmSync,symlinkSync,writeFileSync } from 'node:fs'; +import { createRequire } from 'node:module'; +import { dirname,join,resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const require = createRequire(import.meta.url); +const sdkPackageRoot = resolve(fileURLToPath(new URL('../..', import.meta.url))); + +type FixtureManifest = { + id?: string; + root?: string; +}; + +export type FixtureInjectionMode = 'workspace-link' | 'installed-link' | 'contracts-only'; + +export type FixtureSupportDeclaration = { + packageName: string; + modes: readonly FixtureInjectionMode[]; + workspaceDirName?: string; + entrySpecifier?: string; + contractsShim?: 'agent-contracts'; +}; + +export type ResolveSharedFixtureOptions = { + packageRoot?: string; + requiredPaths?: string[]; +}; + +export type PrepareFixturePackagesOptions = { + fixtureRoot: string; + packageRoot?: string; + declarations: readonly FixtureSupportDeclaration[]; +}; + +export const DEFAULT_FIXTURE_ID = 'treeseed-working-site'; + +function currentPackageRoot(packageRoot?: string) { + return packageRoot ? resolve(packageRoot) : sdkPackageRoot; +} + +function requiredFixturePaths(requiredPaths?: string[]) { + return requiredPaths && requiredPaths.length > 0 + ? requiredPaths + : ['src/manifest.yaml', 'src/content']; +} + +export function resolveRequestedFixtureId() { + return process.env.TREESEED_FIXTURE_ID?.trim() || DEFAULT_FIXTURE_ID; +} + +export function resolveFixturesRepoRoot(options: ResolveSharedFixtureOptions = {}) { + if (process.env.TREESEED_FIXTURES_ROOT?.trim()) { + return resolve(process.env.TREESEED_FIXTURES_ROOT); + } + + return resolve(currentPackageRoot(options.packageRoot), '.fixtures', 'treeseed-fixtures'); +} + +function fixtureSatisfiesRequiredPaths(root: string, requiredPaths?: string[]) { + return requiredFixturePaths(requiredPaths).every((relativePath) => existsSync(join(root, relativePath))); +} + +export function resolveSharedFixtureRoot(options: ResolveSharedFixtureOptions = {}) { + const fixturesRepoRoot = resolveFixturesRepoRoot(options); + const sitesRoot = join(fixturesRepoRoot, 'sites'); + if (!existsSync(sitesRoot)) { + return null; + } + + const requestedFixtureId = resolveRequestedFixtureId(); + for (const entry of readdirSync(sitesRoot, { withFileTypes: true })) { + if (!entry.isDirectory()) { + continue; + } + + const fixtureRoot = join(sitesRoot, entry.name); + const manifestPath = join(fixtureRoot, 'fixture.manifest.json'); + if (!existsSync(manifestPath)) { + continue; + } + + try { + const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) as FixtureManifest; + if (manifest.id !== requestedFixtureId) { + continue; + } + + const root = resolve(fixtureRoot, manifest.root ?? '.'); + if (fixtureSatisfiesRequiredPaths(root, options.requiredPaths)) { + return root; + } + } catch { + continue; + } + } + + return null; +} + +export function requireSharedFixtureRoot(options: ResolveSharedFixtureOptions = {}) { + const fixtureRoot = resolveSharedFixtureRoot(options); + if (!fixtureRoot) { + throw new Error( + `Unable to resolve shared fixture "${resolveRequestedFixtureId()}". Initialize the submodule with "git submodule update --init --recursive".`, + ); + } + + return fixtureRoot; +} + +export function checkSharedFixture(options: ResolveSharedFixtureOptions = {}) { + const fixtureRoot = requireSharedFixtureRoot(options); + const missing = requiredFixturePaths(options.requiredPaths) + .filter((relativePath) => !existsSync(join(fixtureRoot, relativePath))); + if (missing.length > 0) { + throw new Error(`Shared fixture is missing required paths at ${fixtureRoot}: ${missing.join(', ')}.`); + } + return fixtureRoot; +} + +function resolveInstalledPackageRoot(packageName: string, entrySpecifier?: string) { + const candidates = [ + `${packageName}/package.json`, + entrySpecifier ?? packageName, + packageName, + ]; + + for (const candidate of candidates) { + try { + const resolvedEntry = require.resolve(candidate); + let currentDir = dirname(resolvedEntry); + while (currentDir !== dirname(currentDir)) { + if (existsSync(resolve(currentDir, 'package.json'))) { + return currentDir; + } + currentDir = dirname(currentDir); + } + } catch { + continue; + } + } + + return null; +} + +function resolveWorkspacePackageRoot(packageRoot: string, workspaceDirName?: string) { + if (!workspaceDirName) { + return null; + } + const candidate = resolve(packageRoot, '..', workspaceDirName); + return existsSync(resolve(candidate, 'package.json')) ? candidate : null; +} + +function packageProvidesEntry(packageRoot: string, entrySpecifier?: string) { + if (!entrySpecifier) { + return true; + } + try { + const packageRequire = createRequire(resolve(packageRoot, 'package.json')); + packageRequire.resolve(entrySpecifier); + return true; + } catch { + return false; + } +} + +function exportKeyForEntry(packageName: string, entrySpecifier?: string) { + if (!entrySpecifier || entrySpecifier === packageName) { + return '.'; + } + if (!entrySpecifier.startsWith(`${packageName}/`)) { + return null; + } + return `.${entrySpecifier.slice(packageName.length)}`; +} + +function exportTargets(value: unknown): string[] { + if (typeof value === 'string') { + return [value]; + } + if (!value || typeof value !== 'object') { + return []; + } + const record = value as Record; + return ['types', 'import', 'default', 'require'] + .flatMap((key) => exportTargets(record[key])); +} + +function workspacePackageProvidesEntry(packageRoot: string, packageName: string, entrySpecifier?: string) { + const exportKey = exportKeyForEntry(packageName, entrySpecifier); + if (!exportKey) { + return false; + } + if (exportKey === '.' && !entrySpecifier) { + return true; + } + + try { + const packageJson = JSON.parse(readFileSync(resolve(packageRoot, 'package.json'), 'utf8')) as { + exports?: Record | string; + }; + const entryExport = typeof packageJson.exports === 'string' + ? (exportKey === '.' ? packageJson.exports : null) + : packageJson.exports?.[exportKey]; + const targets = exportTargets(entryExport); + return targets.length > 0 && targets.every((target) => existsSync(resolve(packageRoot, target))); + } catch { + return false; + } +} + +function ensureFixtureLinkedPackage(fixtureRoot: string, packageName: string, resolvedPackageRoot: string) { + const packageDir = resolve(fixtureRoot, 'node_modules', ...packageName.split('/')); + mkdirSync(dirname(packageDir), { recursive: true }); + rmSync(packageDir, { recursive: true, force: true }); + symlinkSync(resolvedPackageRoot, packageDir, 'dir'); +} + +function buildAgentContractsShimPackage(fixtureRoot: string) { + const packageDir = resolve(fixtureRoot, 'node_modules', '@treeseed', 'agent'); + rmSync(packageDir, { recursive: true, force: true }); + mkdirSync(resolve(packageDir, 'contracts'), { recursive: true }); + + writeFileSync( + resolve(packageDir, 'package.json'), + JSON.stringify( + { + name: '@treeseed/agent', + type: 'module', + exports: { + './runtime-types': { + types: './runtime-types.d.ts', + default: './runtime-types.js', + }, + './contracts/messages': { + types: './contracts/messages.d.ts', + default: './contracts/messages.js', + }, + './contracts/run': { + types: './contracts/run.d.ts', + default: './contracts/run.js', + }, + }, + }, + null, + 2, + ), + 'utf8', + ); + + writeFileSync(resolve(packageDir, 'runtime-types.js'), 'export {};\n', 'utf8'); + writeFileSync( + resolve(packageDir, 'runtime-types.d.ts'), + [ + "import type { AgentHandlerKind, AgentRunStatus } from '@treeseed/sdk/types/agents';", + 'export interface AgentTriggerInvocation {', + "\tkind: 'startup' | 'schedule' | 'message' | 'manual' | 'follow';", + '\tsource: string;', + '\tmessage?: { id?: string | number; type?: string; payloadJson?: string | null } | null;', + '}', + 'export interface AgentHandlerOutput {', + '\tstatus: AgentRunStatus;', + '\tsummary: string;', + '\tstdout?: string;', + '\tstderr?: string;', + "\terrorCategory?: import('./contracts/run').AgentErrorCategory | null;", + '\tmetadata?: Record;', + '}', + 'export interface AgentContext {', + '\trunId: string;', + '\trepoRoot: string;', + '\tagent: any;', + '\tsdk: any;', + '\ttrigger: AgentTriggerInvocation;', + '\texecution: any;', + '\tmutations: any;', + '\trepository: any;', + '\tverification: any;', + '\tnotifications: any;', + '\tresearch: any;', + '}', + 'export interface AgentHandler {', + '\tkind: AgentHandlerKind;', + '\tresolveInputs(context: AgentContext): Promise;', + '\texecute(context: AgentContext, inputs: TInputs): Promise;', + '\temitOutputs(context: AgentContext, result: TResult): Promise;', + '}', + '', + ].join('\n'), + 'utf8', + ); + writeFileSync( + resolve(packageDir, 'contracts', 'messages.js'), + [ + 'export const AGENT_MESSAGE_TYPES = [];', + 'export function parseAgentMessagePayload(_type, payloadJson) {', + '\treturn JSON.parse(payloadJson);', + '}', + '', + ].join('\n'), + 'utf8', + ); + writeFileSync( + resolve(packageDir, 'contracts', 'messages.d.ts'), + [ + 'export interface QuestionPriorityUpdatedMessage {', + '\tquestionId: string;', + '\treason: string;', + '\tplannerRunId: string;', + '}', + 'export interface ObjectivePriorityUpdatedMessage {', + '\tobjectiveId: string;', + '\treason: string;', + '\tplannerRunId: string;', + '}', + 'export interface ArchitectureUpdatedMessage {', + '\tobjectiveId: string;', + '\tknowledgeId: string;', + '\tarchitectRunId: string;', + '}', + 'export interface SubscriberNotifiedMessage {', + '\temail: string;', + '\titemCount: number;', + '\tnotifierRunId: string;', + '}', + 'export interface ResearchStartedMessage {', + '\tquestionId: string;', + '\tresearcherRunId: string;', + '}', + 'export interface ResearchCompletedMessage {', + '\tquestionId: string;', + '\tknowledgeId: string | null;', + '\tresearcherRunId: string;', + '}', + 'export interface TaskCompleteMessage {', + '\tbranchName: string | null;', + '\tchangedTargets: string[];', + '\tengineerRunId: string;', + '}', + 'export interface TaskWaitingMessage {', + '\tblockingReason: string;', + '\tengineerRunId: string;', + '}', + 'export interface TaskFailedMessage {', + '\tfailureSummary: string;', + '\tengineerRunId: string;', + '}', + 'export interface TaskVerifiedMessage {', + '\tbranchName: string | null;', + '\treviewerRunId: string;', + '}', + 'export interface ReviewFailedMessage {', + '\tfailureSummary: string;', + '\treviewerRunId: string;', + '}', + 'export interface ReviewWaitingMessage {', + '\tblockingReason: string;', + '\treviewerRunId: string;', + '}', + 'export interface ReleaseStartedMessage {', + '\ttaskRunId: string | null;', + '\treleaserRunId: string;', + '}', + 'export interface ReleaseCompletedMessage {', + '\treleaseSummary: string;', + '\treleaserRunId: string;', + '}', + 'export interface ReleaseFailedMessage {', + '\tfailureSummary: string;', + '\treleaserRunId: string;', + '}', + 'export interface AgentMessageContracts {', + '\tquestion_priority_updated: QuestionPriorityUpdatedMessage;', + '\tobjective_priority_updated: ObjectivePriorityUpdatedMessage;', + '\tarchitecture_updated: ArchitectureUpdatedMessage;', + '\tsubscriber_notified: SubscriberNotifiedMessage;', + '\tresearch_started: ResearchStartedMessage;', + '\tresearch_completed: ResearchCompletedMessage;', + '\ttask_complete: TaskCompleteMessage;', + '\ttask_waiting: TaskWaitingMessage;', + '\ttask_failed: TaskFailedMessage;', + '\ttask_verified: TaskVerifiedMessage;', + '\treview_failed: ReviewFailedMessage;', + '\treview_waiting: ReviewWaitingMessage;', + '\trelease_started: ReleaseStartedMessage;', + '\trelease_completed: ReleaseCompletedMessage;', + '\trelease_failed: ReleaseFailedMessage;', + '}', + 'export type AgentMessageType = keyof AgentMessageContracts;', + 'export type AgentMessagePayload = AgentMessageContracts[TType];', + 'export declare const AGENT_MESSAGE_TYPES: readonly AgentMessageType[];', + 'export declare function parseAgentMessagePayload(_type: TType, payloadJson: string): AgentMessagePayload;', + '', + ].join('\n'), + 'utf8', + ); + writeFileSync(resolve(packageDir, 'contracts', 'run.js'), 'export {};\n', 'utf8'); + writeFileSync( + resolve(packageDir, 'contracts', 'run.d.ts'), + [ + "export type AgentErrorCategory = 'execution_error' | 'mutation_error' | 'verification_error' | 'notification_error' | 'research_error' | 'sdk_error' | 'unknown';", + '', + ].join('\n'), + 'utf8', + ); +} + +export function prepareFixturePackages(options: PrepareFixturePackagesOptions) { + const packageRoot = currentPackageRoot(options.packageRoot); + for (const declaration of options.declarations) { + let satisfied = false; + + for (const mode of declaration.modes) { + if (mode === 'workspace-link') { + const workspaceRoot = resolveWorkspacePackageRoot(packageRoot, declaration.workspaceDirName); + if (!workspaceRoot || !workspacePackageProvidesEntry(workspaceRoot, declaration.packageName, declaration.entrySpecifier)) { + continue; + } + ensureFixtureLinkedPackage(options.fixtureRoot, declaration.packageName, workspaceRoot); + satisfied = true; + break; + } + + if (mode === 'installed-link') { + const installedRoot = resolveInstalledPackageRoot(declaration.packageName, declaration.entrySpecifier); + if (!installedRoot || !packageProvidesEntry(installedRoot, declaration.entrySpecifier)) { + continue; + } + ensureFixtureLinkedPackage(options.fixtureRoot, declaration.packageName, installedRoot); + satisfied = true; + break; + } + + if (mode === 'contracts-only' && declaration.contractsShim === 'agent-contracts') { + buildAgentContractsShimPackage(options.fixtureRoot); + satisfied = true; + break; + } + } + + if (!satisfied) { + throw new Error(`Unable to prepare fixture package "${declaration.packageName}" using modes ${declaration.modes.join(', ')}.`); + } + } +} + diff --git a/scripts/run-fixture-astro-command.ts b/scripts/testing/run-fixture-astro-command.ts similarity index 78% rename from scripts/run-fixture-astro-command.ts rename to scripts/testing/run-fixture-astro-command.ts index e01b911b..b8806024 100644 --- a/scripts/run-fixture-astro-command.ts +++ b/scripts/testing/run-fixture-astro-command.ts @@ -1,9 +1,9 @@ import { spawnSync } from 'node:child_process'; import { resolve } from 'node:path'; import { tmpdir } from 'node:os'; -import { prepareFixturePackages } from '@treeseed/sdk/fixture-support'; -import { fixtureRoot, packageRoot } from './paths.ts'; -import { astroBin } from './package-tools.ts'; +import { prepareFixturePackages } from './fixture-support.ts'; +import { fixtureRoot, packageRoot } from '../support/paths.ts'; +import { astroBin } from '../packages/package-tools.ts'; const [command, ...rest] = process.argv.slice(2); @@ -18,15 +18,14 @@ prepareFixturePackages({ declarations: [ { packageName: '@treeseed/sdk', + entrySpecifier: '@treeseed/sdk/operator-contracts', workspaceDirName: 'sdk', modes: ['workspace-link', 'installed-link'], }, { packageName: '@treeseed/agent', - workspaceDirName: 'agent', - entrySpecifier: '@treeseed/agent/runtime-types', contractsShim: 'agent-contracts', - modes: ['workspace-link', 'installed-link', 'contracts-only'], + modes: ['contracts-only'], }, { packageName: '@treeseed/core', diff --git a/scripts/test-smoke.ts b/scripts/testing/test-smoke.ts similarity index 88% rename from scripts/test-smoke.ts rename to scripts/testing/test-smoke.ts index 4ddf3f2b..f50faa56 100644 --- a/scripts/test-smoke.ts +++ b/scripts/testing/test-smoke.ts @@ -4,10 +4,10 @@ import { tmpdir } from 'node:os'; import { dirname, join, resolve } from 'node:path'; import { spawnSync } from 'node:child_process'; import { createRequire } from 'node:module'; -import { packageRoot } from './package-tools.ts'; +import { packageRoot } from '../packages/package-tools.ts'; const require = createRequire(import.meta.url); -const sdkPackageRoot = resolve(dirname(require.resolve('@treeseed/sdk')), '..'); +const sdkPackageRoot = resolve(dirname(require.resolve('@treeseed/sdk/operator-contracts')), '../..'); const npmCacheDir = resolve(tmpdir(), 'treeseed-npm-cache'); function run(command: string, args: string[], cwd = packageRoot, capture = false) { @@ -39,7 +39,7 @@ function runtimeDependencyNamesFor(root: string) { function resolveWorkspaceRuntimePackageRoots() { const roots = new Map(); for (const dependencyName of runtimeDependencyNamesFor(packageRoot)) { - const dependencyRoot = resolveTreeseedRuntimeDependencyRoot(dependencyName); + const dependencyRoot = resolveRuntimeDependencyRoot(dependencyName); if (dependencyRoot) { roots.set(dependencyName, dependencyRoot); } @@ -64,7 +64,7 @@ function collectRuntimeDependenciesForPackaging() { visited.add(nextRoot); for (const dependencyName of runtimeDependencyNamesFor(nextRoot)) { dependencyNames.add(dependencyName); - const dependencyRoot = resolveTreeseedRuntimeDependencyRoot(dependencyName, searchRoots); + const dependencyRoot = resolveRuntimeDependencyRoot(dependencyName, searchRoots); if (dependencyRoot) { queue.push(dependencyRoot); } @@ -74,15 +74,10 @@ function collectRuntimeDependenciesForPackaging() { return dependencyNames; } -function resolveTreeseedRuntimeDependencyRoot(packageName: string, searchRoots: string[] = [packageRoot, sdkPackageRoot]) { +function resolveRuntimeDependencyRoot(packageName: string, searchRoots: string[] = [packageRoot, sdkPackageRoot]) { if (!packageName.startsWith('@treeseed/')) { return null; } - const folderName = packageName.slice('@treeseed/'.length); - const workspaceCandidateRoot = resolve(packageRoot, '..', folderName); - if (existsSync(resolve(workspaceCandidateRoot, 'package.json'))) { - return workspaceCandidateRoot; - } try { return resolveInstalledPackageRoot(packageName, searchRoots); } catch { @@ -129,18 +124,14 @@ function mirrorDependencies(tempRoot: string, excludedPackages = new Set ...resolveWorkspaceRuntimePackageRoots().values(), ]; const sharedNodeModules = (() => { - let lastCandidate: string | null = null; let current = packageRoot; while (true) { const candidate = resolve(current, 'node_modules'); if (existsSync(candidate)) { - lastCandidate = candidate; + return candidate; } const parent = dirname(current); if (parent === current) { - if (lastCandidate) { - return lastCandidate; - } throw new Error(`Unable to locate shared node_modules from ${packageRoot}.`); } current = parent; @@ -199,7 +190,9 @@ function pack(root: string, outputRoot: string, fallbackName: string) { .sort((left, right) => left.localeCompare(right, undefined, { numeric: true, sensitivity: 'base' })) .at(-1); - if (bundledTarball && !existsSync(resolve(root, 'scripts', 'build-dist.ts'))) { + const hasSourceBuild = existsSync(resolve(root, 'scripts', 'build-dist.ts')) + || existsSync(resolve(root, 'scripts', 'build', 'build-dist.ts')); + if (bundledTarball && !hasSourceBuild) { return bundledTarball; } @@ -240,8 +233,6 @@ try { mkdirSync(packRoot, { recursive: true }); mkdirSync(extractRoot, { recursive: true }); const coreTarball = pack(packageRoot, packRoot, 'treeseed-core.tgz'); - const workspaceRuntimePackageRoots = resolveWorkspaceRuntimePackageRoots(); - if (existsSync(resolve(sdkPackageRoot, 'scripts', 'build-dist.ts'))) { const sdkTarball = pack(sdkPackageRoot, packRoot, 'treeseed-sdk.tgz'); installPackagedPackage(extractRoot, installRoot, sdkTarball, 'sdk'); @@ -249,7 +240,7 @@ try { installPackageDirectory(installRoot, sdkPackageRoot, 'sdk'); } installPackagedPackage(extractRoot, installRoot, coreTarball, 'core'); - mirrorDependencies(installRoot, new Set(workspaceRuntimePackageRoots.keys())); + mirrorDependencies(installRoot, new Set(['@treeseed/sdk'])); writeFileSync(resolve(installRoot, 'package.json'), `${JSON.stringify({ name: 'treeseed-core-smoke', private: true, type: 'module' }, null, 2)}\n`, 'utf8'); run( process.execPath, @@ -257,9 +248,13 @@ try { '--input-type=module', '-e', [ - 'await import("@treeseed/core");', - 'await import("@treeseed/core/site");', - 'await import("@treeseed/core/config");', + 'import { createServer } from "vite";', + 'const server = await createServer({ server: { middlewareMode: true }, appType: "custom", logLevel: "silent" });', + 'try {', + 'await server.ssrLoadModule("@treeseed/core");', + 'await server.ssrLoadModule("@treeseed/core/site");', + 'await server.ssrLoadModule("@treeseed/core/config");', + '} finally { await server.close(); }', ].join(' '), ], installRoot, diff --git a/scripts/workspace-bootstrap.ts b/scripts/treedx/workspaces/workspace-bootstrap.ts similarity index 93% rename from scripts/workspace-bootstrap.ts rename to scripts/treedx/workspaces/workspace-bootstrap.ts index a8334b99..13e93245 100644 --- a/scripts/workspace-bootstrap.ts +++ b/scripts/treedx/workspaces/workspace-bootstrap.ts @@ -27,7 +27,7 @@ function packageState(root, entry) { }; } -export function detectTreeseedBootstrapMode(startRoot = process.cwd()) { +export function detectBootstrapMode(startRoot = process.cwd()) { const root = resolve(startRoot); const forcedMode = process.env.TREESEED_BOOTSTRAP_MODE; if (forcedMode === 'workspace' || forcedMode === 'registry') { @@ -120,19 +120,19 @@ function resolveInstalledCoreScript(root, scriptRelativePath) { } function runStarlightPatchFromRegistry(root) { - const workspacePatchScript = resolve(root, 'packages', 'core', 'scripts', 'patch-starlight-content-path.ts'); + const workspacePatchScript = resolve(root, 'packages', 'core', 'scripts', 'content', 'patch-starlight-content-path.ts'); if (existsSync(workspacePatchScript)) { run('tsx', [workspacePatchScript], { cwd: root }); return; } - const patchScript = resolveInstalledCoreScript(root, 'dist/scripts/patch-starlight-content-path.js'); + const patchScript = resolveInstalledCoreScript(root, 'dist/scripts/content/patch-starlight-content-path.js'); run(process.execPath, [patchScript], { cwd: root }); } function runStarlightPatchFromWorkspace(root, packages) { const corePackage = packages.find((entry) => entry.name === '@treeseed/core'); - run('tsx', [resolve(corePackage.dir, 'scripts/patch-starlight-content-path.ts')], { cwd: root }); + run('tsx', [resolve(corePackage.dir, 'scripts', 'content', 'patch-starlight-content-path.ts')], { cwd: root }); } function linkWorkspacePackages(root, packages) { @@ -162,9 +162,9 @@ function printPartialError(state) { console.error(`Missing package manifests:\n${missing}`); } -export function runTreeseedWorkspaceBootstrap({ root = process.cwd() } = {}) { +export function runWorkspaceBootstrap({ root = process.cwd() } = {}) { const resolvedRoot = resolve(root); - const state = detectTreeseedBootstrapMode(resolvedRoot); + const state = detectBootstrapMode(resolvedRoot); if (state.mode === 'partial') { printPartialError(state); @@ -197,7 +197,7 @@ function isDirectEntrypoint() { if (isDirectEntrypoint()) { try { - process.exitCode = runTreeseedWorkspaceBootstrap(); + process.exitCode = runWorkspaceBootstrap(); } catch (error) { console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 1; diff --git a/scripts/verify-driver.ts b/scripts/verify-driver.ts deleted file mode 100644 index f75e663d..00000000 --- a/scripts/verify-driver.ts +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env node - -import { spawnSync } from 'node:child_process'; -import { existsSync } from 'node:fs'; -import { createRequire } from 'node:module'; -import { resolve } from 'node:path'; -import { pathToFileURL } from 'node:url'; - -const require = createRequire(import.meta.url); - -function hasSourceRunnerDependencies() { - try { - require.resolve('esbuild'); - return true; - } catch { - return false; - } -} - -function ensureSourceRunnerDependencies() { - if (hasSourceRunnerDependencies()) { - return; - } - const result = spawnSync('npm', ['install'], { - cwd: process.cwd(), - env: { ...process.env, npm_config_ignore_scripts: 'true' }, - stdio: 'inherit', - }); - if (result.status !== 0) { - process.exit(result.status ?? 1); - } -} - -function runDirectVerify() { - ensureSourceRunnerDependencies(); - const result = spawnSync('npm', ['run', 'verify:direct'], { - cwd: process.cwd(), - env: process.env, - stdio: 'inherit', - }); - process.exit(result.status ?? 1); -} - -const entrypointCheckOnly = process.env.TREESEED_VERIFY_ENTRYPOINT_CHECK === 'true'; - -async function importSdkVerifier() { - const siblingSdkVerifier = resolve(process.cwd(), '..', 'sdk', 'src', 'verification.ts'); - if (existsSync(siblingSdkVerifier)) { - return import(pathToFileURL(siblingSdkVerifier).href); - } - return import('@treeseed/sdk/verification'); -} - -try { - ensureSourceRunnerDependencies(); - const { runTreeseedVerifyDriver } = await importSdkVerifier(); - if (entrypointCheckOnly) { - process.exit(0); - } - process.exit(runTreeseedVerifyDriver({ - packageRoot: process.cwd(), - localTreeseedExtraSiblingDependencies: ['@treeseed/agent'], - })); -} catch (error) { - if (error && typeof error === 'object' && 'code' in error && error.code === 'ERR_MODULE_NOT_FOUND') { - if (entrypointCheckOnly) { - process.stderr.write('Treeseed core verify: @treeseed/sdk is required for verify entrypoint resolution.\n'); - process.exit(1); - } - if (process.env.TREESEED_VERIFY_DRIVER === 'act') { - process.stderr.write('Treeseed core verify: `act` mode requires @treeseed/sdk to be installed.\n'); - process.exit(1); - } - runDirectVerify(); - } - throw error; -} diff --git a/src/config.ts b/src/config.ts deleted file mode 100644 index 44f9280c..00000000 --- a/src/config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import starlight from '@astrojs/starlight'; -import { createTreeseedSite } from './site'; -import { loadTreeseedManifest } from '@treeseed/sdk/platform/tenant-config'; - -export function createTreeseedTenantSite(manifestPath?: string) { - const tenant = loadTreeseedManifest(manifestPath); - return createTreeseedSite(tenant, { starlight }); -} diff --git a/src/configuration/config.ts b/src/configuration/config.ts new file mode 100644 index 00000000..4d735066 --- /dev/null +++ b/src/configuration/config.ts @@ -0,0 +1,8 @@ +import starlight from '@astrojs/starlight'; +import { createSite } from '../support/site'; +import { loadManifest } from '../runtime/platform/tenant-config.ts'; + +export function createTenantSite(manifestPath?: string) { + const tenant = loadManifest(manifestPath); + return createSite(tenant, { starlight }); +} diff --git a/src/configuration/content-config.ts b/src/configuration/content-config.ts new file mode 100644 index 00000000..3d4cdff4 --- /dev/null +++ b/src/configuration/content-config.ts @@ -0,0 +1,9 @@ +import { docsLoader } from '@astrojs/starlight/loaders'; +import { docsSchema } from '@astrojs/starlight/schema'; +import { createCollections } from '../content/content'; +import { loadManifest } from '../runtime/platform/tenant-config.ts'; + +export function createTenantCollections(manifestPath?: string) { + const tenant = loadManifest(manifestPath); + return createCollections(tenant, { docsLoader, docsSchema }); +} diff --git a/src/content-config.ts b/src/content-config.ts deleted file mode 100644 index 2f0582f6..00000000 --- a/src/content-config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { docsLoader } from '@astrojs/starlight/loaders'; -import { docsSchema } from '@astrojs/starlight/schema'; -import { createTreeseedCollections } from './content'; -import { loadTreeseedManifest } from '@treeseed/sdk/platform/tenant-config'; - -export function createTreeseedTenantCollections(manifestPath?: string) { - const tenant = loadTreeseedManifest(manifestPath); - return createTreeseedCollections(tenant, { docsLoader, docsSchema }); -} diff --git a/src/content.ts b/src/content.ts deleted file mode 100644 index 2abb9698..00000000 --- a/src/content.ts +++ /dev/null @@ -1,719 +0,0 @@ -import { defineCollection, reference } from 'astro:content'; -import { z } from 'astro/zod'; -import { glob, type Loader } from 'astro/loaders'; -import { existsSync, readdirSync } from 'node:fs'; -import { dirname, resolve } from 'node:path'; -import type { TreeseedFieldAliasRegistry } from '@treeseed/sdk/field-aliases'; -import type { TreeseedTenantConfig } from '@treeseed/sdk/platform/contracts'; -import { COMMERCE_OFFER_MODES, type CommerceOfferMode } from '@treeseed/sdk/types'; -import { AGENT_ACTIVITY_TYPES } from '@treeseed/sdk/types/agents'; -import { loadTreeseedPluginRuntime } from '@treeseed/sdk/platform/plugins'; -import { loadTreeseedDeployConfig } from '@treeseed/sdk/platform/deploy-config'; -import { getTreeseedContentServingMode } from '@treeseed/sdk/platform/deploy-runtime'; -import { - AGENT_MODEL_DEFAULTS, - BOOK_MODEL_DEFAULTS, - TREESEED_MODEL_DEFAULTS, - NOTE_MODEL_DEFAULTS, - OBJECTIVE_MODEL_DEFAULTS, - PAGE_MODEL_DEFAULTS, - PEOPLE_MODEL_DEFAULTS, - PROPOSAL_MODEL_DEFAULTS, - QUESTION_MODEL_DEFAULTS, - DECISION_MODEL_DEFAULTS, -} from './utils/site-config'; -import { preprocessAliasedRecord } from '@treeseed/sdk/field-aliases'; - -const statusValues = ['live', 'in progress', 'exploratory', 'planned', 'speculative'] as const; -const pageLayoutValues = ['article', 'bridge'] as const; -const questionTypeValues = ['research', 'implementation', 'strategy', 'evaluation'] as const; -const proposalTypeValues = ['strategy', 'policy', 'implementation', 'research'] as const; -const decisionTypeValues = ['approved', 'rejected', 'deferred', 'request_changes', 'superseded'] as const; -const governanceStatusValues = ['draft', 'open', 'voting', 'accepted', 'rejected', 'no_decision_quorum_failed', 'withdrawn', 'superseded'] as const; -const timeHorizonValues = ['near-term', 'mid-term', 'long-term'] as const; -const runtimeStatusValues = ['active', 'experimental', 'dormant'] as const; -const commerceOfferModeValues = [...COMMERCE_OFFER_MODES] as [CommerceOfferMode, ...CommerceOfferMode[]]; -const agentActivityTypeValues = AGENT_ACTIVITY_TYPES; -const agentHandlerValues = ['writer', 'actor', 'estimate', 'releaser', 'reporter'] as const; - -type DocsDependencies = { - docsLoader: (options: Record) => unknown; - docsSchema: (options: Record) => unknown; -}; - -type DocsCollectionProvider = { - loader: unknown; - schema: unknown; -}; - -const treeseedDocsExtensions = ['markdown', 'mdown', 'mkdn', 'mkd', 'mdwn', 'md', 'mdx']; - -function hasMarkdownContent(base: string): boolean { - if (!existsSync(base)) { - return false; - } - for (const entry of readdirSync(base, { withFileTypes: true, recursive: true })) { - if (entry.isFile() && /\.(md|mdx)$/iu.test(entry.name)) { - return true; - } - } - return false; -} - -function optionalMarkdownGlob( - base: string, - options: { - pattern?: string; - generateId?: (args: { entry: string; data: Record }) => string; - } = {}, -): Loader { - const delegate = glob({ - pattern: options.pattern ?? '**/*.{md,mdx}', - base, - generateId: options.generateId, - }); - return { - name: `treeseed-optional-markdown-glob:${base}`, - async load(context) { - if (!hasMarkdownContent(base)) { - context.store.clear(); - return; - } - await delegate.load(context); - }, - }; -} - -function withOptionalDefault TSchema }>( - schema: TSchema, - defaultValue: unknown, -) { - return defaultValue === undefined ? schema : schema.default(defaultValue); -} - -function createKnowledgeDocId({ entry, data }: { entry: string; data: Record }) { - const rawSlug = typeof data.slug === 'string' ? data.slug : entry; - const normalized = rawSlug - .replace(/\\/g, '/') - .replace(/\.(md|mdx)$/i, '') - .replace(/\/index$/i, '') - .replace(/^\/+|\/+$/g, ''); - - if (normalized === 'knowledge' || normalized.startsWith('knowledge/')) { - return normalized; - } - - return normalized ? `knowledge/${normalized}` : 'knowledge'; -} - -function resolveDocsCollectionProvider( - tenantConfig: TreeseedTenantConfig, - dependencies: DocsDependencies, -): DocsCollectionProvider { - const pluginRuntime = loadTreeseedPluginRuntime(loadTreeseedDeployConfig()); - const selectedId = pluginRuntime.config.providers.content.docs; - - if (selectedId === 'default') { - return { - loader: optionalMarkdownGlob(tenantConfig.content.docs, { - pattern: `**/[^_]*.{${treeseedDocsExtensions.join(',')}}`, - generateId: createKnowledgeDocId, - }), - schema: dependencies.docsSchema({ - extend: z.object({ - tags: z.array(z.string()).default(TREESEED_MODEL_DEFAULTS.tags ?? []), - }), - }), - }; - } - - for (const { plugin, config, package: packageName } of pluginRuntime.plugins) { - const docsProviders = plugin.contentProviders?.docs ?? {}; - if (!(selectedId in docsProviders)) { - continue; - } - const resolved = docsProviders[selectedId]({ - tenantConfig, - dependencies, - pluginConfig: config ?? {}, - }); - if (!resolved?.loader || !resolved?.schema) { - throw new Error(`Treeseed docs provider "${selectedId}" from "${packageName}" must return loader and schema.`); - } - return resolved as DocsCollectionProvider; - } - - throw new Error(`Treeseed docs provider "${selectedId}" is not registered.`); -} - -export function createTreeseedCollections(tenantConfig: TreeseedTenantConfig, { docsLoader, docsSchema }: DocsDependencies) { - const publishedRuntime = getTreeseedContentServingMode() === 'published_runtime'; - const pageFieldAliases: TreeseedFieldAliasRegistry = { - pageLayout: { key: 'pageLayout', aliases: ['page_layout'] }, - seoTitle: { key: 'seoTitle', aliases: ['seo_title'] }, - seoDescription: { key: 'seoDescription', aliases: ['seo_description'] }, - }; - const questionFieldAliases: TreeseedFieldAliasRegistry = { - questionType: { key: 'questionType', aliases: ['question_type'] }, - primaryContributor: { key: 'primaryContributor', aliases: ['primary_contributor'] }, - relatedObjectives: { key: 'relatedObjectives', aliases: ['related_objectives'] }, - relatedBooks: { key: 'relatedBooks', aliases: ['related_books'] }, - }; - const objectiveFieldAliases: TreeseedFieldAliasRegistry = { - timeHorizon: { key: 'timeHorizon', aliases: ['time_horizon'] }, - primaryContributor: { key: 'primaryContributor', aliases: ['primary_contributor'] }, - relatedQuestions: { key: 'relatedQuestions', aliases: ['related_questions'] }, - relatedBooks: { key: 'relatedBooks', aliases: ['related_books'] }, - }; - const proposalFieldAliases: TreeseedFieldAliasRegistry = { - proposalType: { key: 'proposalType', aliases: ['proposal_type'] }, - primaryContributor: { key: 'primaryContributor', aliases: ['primary_contributor'] }, - relatedObjectives: { key: 'relatedObjectives', aliases: ['related_objectives'] }, - relatedQuestions: { key: 'relatedQuestions', aliases: ['related_questions'] }, - relatedNotes: { key: 'relatedNotes', aliases: ['related_notes'] }, - relatedBooks: { key: 'relatedBooks', aliases: ['related_books'] }, - governanceId: { key: 'governanceId', aliases: ['governance_id'] }, - governanceProviderId: { key: 'governanceProviderId', aliases: ['governance_provider_id'] }, - governancePolicyId: { key: 'governancePolicyId', aliases: ['governance_policy_id'] }, - governanceStatus: { key: 'governanceStatus', aliases: ['governance_status'] }, - proposalVersion: { key: 'proposalVersion', aliases: ['proposal_version'] }, - proposalContentHash: { key: 'proposalContentHash', aliases: ['proposal_content_hash'] }, - votingStartsAt: { key: 'votingStartsAt', aliases: ['voting_starts_at'] }, - votingEndsAt: { key: 'votingEndsAt', aliases: ['voting_ends_at'] }, - canonicalRoute: { key: 'canonicalRoute', aliases: ['canonical_route'] }, - }; - const decisionFieldAliases: TreeseedFieldAliasRegistry = { - decisionType: { key: 'decisionType', aliases: ['decision_type'] }, - primaryContributor: { key: 'primaryContributor', aliases: ['primary_contributor'] }, - relatedObjectives: { key: 'relatedObjectives', aliases: ['related_objectives'] }, - relatedQuestions: { key: 'relatedQuestions', aliases: ['related_questions'] }, - relatedNotes: { key: 'relatedNotes', aliases: ['related_notes'] }, - relatedProposals: { key: 'relatedProposals', aliases: ['related_proposals'] }, - relatedBooks: { key: 'relatedBooks', aliases: ['related_books'] }, - governanceDecisionId: { key: 'governanceDecisionId', aliases: ['governance_decision_id'] }, - governanceProviderId: { key: 'governanceProviderId', aliases: ['governance_provider_id'] }, - sourceProposalGovernanceId: { key: 'sourceProposalGovernanceId', aliases: ['source_proposal_governance_id'] }, - sourceProposalVersion: { key: 'sourceProposalVersion', aliases: ['source_proposal_version'] }, - sourceProposalHash: { key: 'sourceProposalHash', aliases: ['source_proposal_hash'] }, - governanceRule: { key: 'governanceRule', aliases: ['governance_rule'] }, - electorateSnapshot: { key: 'electorateSnapshot', aliases: ['electorate_snapshot'] }, - voteResult: { key: 'voteResult', aliases: ['vote_result'] }, - voterReasons: { key: 'voterReasons', aliases: ['voter_reasons'] }, - decidedAt: { key: 'decidedAt', aliases: ['decided_at'] }, - decisionSnapshotHash: { key: 'decisionSnapshotHash', aliases: ['decision_snapshot_hash'] }, - canonicalRoute: { key: 'canonicalRoute', aliases: ['canonical_route'] }, - }; - const agentFieldAliases: TreeseedFieldAliasRegistry = { - runtimeStatus: { key: 'runtimeStatus', aliases: ['runtime_status'] }, - agentClass: { key: 'agentClass', aliases: ['agent_class'] }, - projectAgentClassId: { key: 'projectAgentClassId', aliases: ['project_agent_class_id'] }, - projectAgentClassSlug: { key: 'projectAgentClassSlug', aliases: ['project_agent_class_slug'] }, - activityProfiles: { key: 'activityProfiles', aliases: ['activity_profiles'] }, - }; - const bookFieldAliases: TreeseedFieldAliasRegistry = { - sectionLabel: { key: 'sectionLabel', aliases: ['section_label'] }, - basePath: { key: 'basePath', aliases: ['base_path'] }, - landingPath: { key: 'landingPath', aliases: ['landing_path'] }, - outlinePath: { key: 'outlinePath', aliases: ['outline_path'] }, - downloadFileName: { key: 'downloadFileName', aliases: ['download_file_name'] }, - downloadHref: { key: 'downloadHref', aliases: ['download_href'] }, - downloadTitle: { key: 'downloadTitle', aliases: ['download_title'] }, - exportRoots: { key: 'exportRoots', aliases: ['export_roots'] }, - sidebarItems: { key: 'sidebarItems', aliases: ['sidebar_items'] }, - }; - const contributorReference = z.union([reference('people'), reference('agents')]); - const sidebarItemSchema: z.ZodTypeAny = z.lazy(() => - z.object({ - label: z.string(), - link: z.string().optional(), - autogenerate: z.object({ directory: z.string() }).optional(), - items: z.array(sidebarItemSchema).optional(), - }), - ); - - const pageSchema = z.preprocess((value) => preprocessAliasedRecord(pageFieldAliases, value), z.object({ - title: z.string(), - description: z.string(), - slug: z.string(), - pageLayout: withOptionalDefault(z.enum(pageLayoutValues), PAGE_MODEL_DEFAULTS.pageLayout), - status: withOptionalDefault(z.enum(statusValues), PAGE_MODEL_DEFAULTS.status), - stage: withOptionalDefault(z.string(), PAGE_MODEL_DEFAULTS.stage), - audience: z.array(z.string()).default(PAGE_MODEL_DEFAULTS.audience ?? []), - summary: z.string(), - updated: z.coerce.date(), - seoTitle: z.string().optional(), - seoDescription: z.string().optional(), - })); - - const noteSchema = z.object({ - title: z.string(), - description: z.string(), - date: z.coerce.date(), - status: withOptionalDefault(z.enum(statusValues), NOTE_MODEL_DEFAULTS.status), - tags: z.array(z.string()).default(NOTE_MODEL_DEFAULTS.tags ?? []), - author: withOptionalDefault(z.string(), NOTE_MODEL_DEFAULTS.author), - summary: z.string(), - draft: z.boolean().default(NOTE_MODEL_DEFAULTS.draft ?? false), - canonicalRoute: z.string().optional(), - about: z.array(z.string()).default([]), - relatedObjectives: z.array(z.string()).default([]), - relatedQuestions: z.array(z.string()).default([]), - relatedProposals: z.array(z.string()).default([]), - relatedDecisions: z.array(z.string()).default([]), - relatedBooks: z.array(z.string()).default([]), - }); - - const questionSchema = z.preprocess((value) => preprocessAliasedRecord(questionFieldAliases, value), z.object({ - title: z.string(), - description: z.string(), - date: z.coerce.date(), - status: withOptionalDefault(z.enum(statusValues), QUESTION_MODEL_DEFAULTS.status), - tags: z.array(z.string()).default(QUESTION_MODEL_DEFAULTS.tags ?? []), - summary: z.string(), - draft: z.boolean().default(QUESTION_MODEL_DEFAULTS.draft ?? false), - questionType: z.enum(questionTypeValues), - motivation: z.string(), - primaryContributor: contributorReference, - relatedObjectives: z.array(reference('objectives')).default([]), - relatedBooks: z.array(reference('books')).default([]), - })); - - const objectiveSchema = z.preprocess((value) => preprocessAliasedRecord(objectiveFieldAliases, value), z.object({ - title: z.string(), - description: z.string(), - date: z.coerce.date(), - status: withOptionalDefault(z.enum(statusValues), OBJECTIVE_MODEL_DEFAULTS.status), - tags: z.array(z.string()).default(OBJECTIVE_MODEL_DEFAULTS.tags ?? []), - summary: z.string(), - draft: z.boolean().default(OBJECTIVE_MODEL_DEFAULTS.draft ?? false), - timeHorizon: z.enum(timeHorizonValues), - motivation: z.string(), - primaryContributor: contributorReference, - relatedQuestions: z.array(reference('questions')).default([]), - relatedBooks: z.array(reference('books')).default([]), - })); - - const proposalSchema = z.preprocess((value) => preprocessAliasedRecord(proposalFieldAliases, value), z.object({ - title: z.string(), - description: z.string(), - date: z.coerce.date(), - status: withOptionalDefault(z.enum(statusValues), PROPOSAL_MODEL_DEFAULTS.status), - tags: z.array(z.string()).default(PROPOSAL_MODEL_DEFAULTS.tags ?? []), - summary: z.string(), - draft: z.boolean().default(PROPOSAL_MODEL_DEFAULTS.draft ?? false), - proposalType: z.enum(proposalTypeValues), - motivation: z.string(), - primaryContributor: contributorReference, - relatedObjectives: z.array(reference('objectives')).default([]), - relatedQuestions: z.array(reference('questions')).default([]), - relatedNotes: z.array(reference('notes')).default([]), - relatedBooks: z.array(reference('books')).default([]), - decision: reference('decisions').optional(), - supersedes: z.array(reference('proposals')).default([]), - governanceId: z.string().optional(), - governanceProviderId: z.string().optional(), - governancePolicyId: z.string().optional(), - governanceStatus: z.enum(governanceStatusValues).optional(), - proposalVersion: z.number().int().positive().optional(), - proposalContentHash: z.string().optional(), - votingStartsAt: z.coerce.date().optional(), - votingEndsAt: z.coerce.date().optional(), - canonicalRoute: z.string().optional(), - })); - - const decisionSchema = z.preprocess((value) => preprocessAliasedRecord(decisionFieldAliases, value), z.object({ - title: z.string(), - description: z.string(), - date: z.coerce.date(), - status: withOptionalDefault(z.enum(statusValues), DECISION_MODEL_DEFAULTS.status), - tags: z.array(z.string()).default(DECISION_MODEL_DEFAULTS.tags ?? []), - summary: z.string(), - draft: z.boolean().default(DECISION_MODEL_DEFAULTS.draft ?? false), - decisionType: z.enum(decisionTypeValues), - rationale: z.string(), - authority: z.string(), - primaryContributor: contributorReference, - relatedObjectives: z.array(reference('objectives')).default([]), - relatedQuestions: z.array(reference('questions')).default([]), - relatedNotes: z.array(reference('notes')).default([]), - relatedProposals: z.array(reference('proposals')).default([]), - relatedBooks: z.array(reference('books')).default([]), - supersedes: z.array(reference('decisions')).default([]), - implements: z.array(z.string()).default([]), - immutable: z.boolean().default(false), - governanceDecisionId: z.string().optional(), - governanceProviderId: z.string().optional(), - sourceProposalGovernanceId: z.string().optional(), - sourceProposalVersion: z.number().int().positive().optional(), - sourceProposalHash: z.string().optional(), - governanceRule: z.record(z.unknown()).optional(), - electorateSnapshot: z.record(z.unknown()).optional(), - voteResult: z.record(z.unknown()).optional(), - voterReasons: z.array(z.record(z.unknown())).default([]), - decidedAt: z.coerce.date().optional(), - decisionSnapshotHash: z.string().optional(), - canonicalRoute: z.string().optional(), - })); - - const profileLinkSchema = z.object({ label: z.string(), href: z.string() }); - const agentWorktreeSchema = z.object({ - enabled: z.boolean().default(true), - root: z.string().optional(), - branchPrefix: z.string().optional(), - }); - const agentExecutionSchema = z.object({ - provider: z.string().optional(), - model: z.string().optional(), - approvalPolicy: z.string().optional(), - sandboxMode: z.string().optional(), - reasoningEffort: z.string().optional(), - allowedPaths: z.array(z.string()).default([]), - forbiddenPaths: z.array(z.string()).default([]), - worktree: agentWorktreeSchema.default({}), - maxConcurrency: z.number().int().positive().default(1), - timeoutSeconds: z.number().int().positive().default(900), - cooldownSeconds: z.number().int().nonnegative().default(30), - leaseSeconds: z.number().int().positive().default(300), - retryLimit: z.number().int().nonnegative().default(3), - branchPrefix: z.string().default('agent'), - providerProfile: z.record(z.unknown()).optional(), - }); - const agentCapabilitySchema = z.union([ - z.string(), - z.object({ - id: z.string(), - label: z.string().optional(), - summary: z.string().optional(), - produces: z.array(z.string()).default([]), - requires: z.array(z.string()).default([]), - reviews: z.array(z.string()).default([]), - }).passthrough(), - ]); - const agentIdentitySchema = z.object({ - purpose: z.string().optional(), - instructions: z.string().optional(), - traits: z.array(z.string()).default([]), - }).passthrough(); - const agentPromptSchema = z.union([ - z.string(), - z.object({ - system: z.string(), - task: z.string().optional(), - templates: z.record(z.string()).optional(), - }).passthrough(), - ]); - const agentToolPolicySchema = z.object({ - allowed: z.array(z.string()).default([]), - denied: z.array(z.string()).default([]), - }).passthrough(); - const agentContentScopeSchema = z.object({ - models: z.array(z.string()).default([]), - actions: z.array(z.string()).default([]), - books: z.array(z.string()).default([]), - paths: z.array(z.string()).default([]), - relations: z.array(z.string()).default([]), - }).passthrough(); - const agentContentAccessSchema = z.object({ - read: agentContentScopeSchema.optional(), - write: agentContentScopeSchema.optional(), - commit: z.object({ allowed: z.boolean() }).optional(), - }).passthrough(); - const agentBranchPolicySchema = z.object({ - kind: z.string(), - base: z.string().optional(), - target: z.string().optional(), - prefix: z.string().optional(), - branchNameTemplate: z.string().optional(), - worktree: z.string().optional(), - updateBaseBeforeRun: z.boolean().optional(), - mergeTargetBeforeSave: z.boolean().optional(), - }).passthrough(); - const agentQuestionPolicySchema = z.object({ - blockExecutionWhenCreated: z.boolean().optional(), - defaultAnswerPolicy: z.object({ - kind: z.string(), - teamId: z.string().optional(), - requiredRoles: z.array(z.string()).default([]), - allowedRoles: z.array(z.string()).default([]), - allowedAgentClasses: z.array(z.string()).default([]), - teamMemberId: z.string().optional(), - projectId: z.string().optional(), - agentSlug: z.string().optional(), - }).passthrough().optional(), - }).passthrough(); - const agentOutputsSchema = z.object({ - messageTypes: z.array(z.string()).default([]), - modelMutations: z.array(z.string()).default([]), - requiredArtifacts: z.array(z.string()).default([]), - schemas: z.array(z.string()).default([]), - }).passthrough(); - const agentActivityExecutionSchema = agentExecutionSchema.partial().extend({ - providerPreference: z.array(z.string()).default([]), - maxRuntimeSeconds: z.number().int().positive().optional(), - maxRetries: z.number().int().nonnegative().optional(), - verificationRequired: z.boolean().optional(), - }).passthrough(); - const agentActivityProfileSchema = z.object({ - enabled: z.boolean().default(true), - activityType: z.enum(agentActivityTypeValues), - handler: z.enum(agentHandlerValues), - prompt: agentPromptSchema, - contentAccess: agentContentAccessSchema.optional(), - tools: agentToolPolicySchema.default({ allowed: [] }), - outputs: agentOutputsSchema.default({}), - questionPolicy: agentQuestionPolicySchema.optional(), - branchPolicy: agentBranchPolicySchema, - execution: agentActivityExecutionSchema.optional(), - }).passthrough(); - - const peopleSchema = z.object({ - name: z.string(), - description: z.string(), - summary: z.string(), - role: z.string(), - affiliation: z.string(), - status: withOptionalDefault(z.enum(statusValues), PEOPLE_MODEL_DEFAULTS.status), - tags: z.array(z.string()).default(PEOPLE_MODEL_DEFAULTS.tags ?? []), - links: z.array(profileLinkSchema).default([]), - relatedQuestions: z.array(reference('questions')).default([]), - relatedObjectives: z.array(reference('objectives')).default([]), - }); - - const agentSchema = z.preprocess((value) => preprocessAliasedRecord(agentFieldAliases, value), z.object({ - id: z.string().optional(), - name: z.string(), - slug: z.string(), - title: z.string().optional(), - enabled: z.boolean().default(true), - description: z.string(), - summary: z.string(), - agentClass: z.string(), - projectAgentClassId: z.string().optional(), - projectAgentClassSlug: z.string().optional(), - template: z.string().optional(), - identity: agentIdentitySchema.default({}), - runtimeStatus: withOptionalDefault(z.enum(runtimeStatusValues), AGENT_MODEL_DEFAULTS.runtimeStatus), - capabilities: z.array(agentCapabilitySchema).default([]), - tags: z.array(z.string()).default(AGENT_MODEL_DEFAULTS.tags ?? []), - links: z.array(profileLinkSchema).default([]), - relatedQuestions: z.array(reference('questions')).default([]), - relatedObjectives: z.array(reference('objectives')).default([]), - activityProfiles: z.record(agentActivityProfileSchema).refine((profiles) => Object.keys(profiles).length > 0, { - message: 'activityProfiles must define at least one activity profile', - }), - }).strict()); - - const agentTestSchema = z.object({ - id: z.string(), - agent: z.string(), - kind: z.enum(['spec', 'handler', 'message_chain', 'manager_worker', 'workday', 'api', 'ui']), - fixture: z.string().optional(), - trigger: z.record(z.any()).default({}), - expect: z.record(z.any()).default({}), - tags: z.array(z.string()).default([]), - }); - - const bookSchema = z.preprocess((value) => preprocessAliasedRecord(bookFieldAliases, value), z.object({ - order: z.number().int().nonnegative(), - slug: z.string(), - title: z.string(), - description: z.string(), - summary: z.string(), - sectionLabel: z.string(), - basePath: z.string(), - landingPath: z.string(), - outlinePath: z.string().optional(), - downloadFileName: z.string(), - downloadHref: z.string(), - downloadTitle: z.string(), - exportRoots: z.array(z.string()).min(1).optional(), - sidebarItems: z.array(sidebarItemSchema).min(1), - tags: z.array(z.string()).default(BOOK_MODEL_DEFAULTS.tags ?? []), - })); - - const publisherSchema = z.object({ - id: z.string(), - name: z.string(), - url: z.string().optional(), - }); - const templateGitSourceSchema = z.object({ - kind: z.literal('git'), - repoUrl: z.string(), - directory: z.string(), - ref: z.string(), - integrity: z.string().optional(), - }); - const templateR2SourceSchema = z.object({ - kind: z.literal('r2'), - bucket: z.string().optional(), - objectKey: z.string(), - version: z.string(), - publicUrl: z.string().optional(), - integrity: z.string().optional(), - }); - const templateProductSchema = z.object({ - slug: z.string(), - sourceRef: z.string().optional(), - title: z.string(), - description: z.string(), - summary: z.string(), - status: z.enum(['draft', 'live', 'archived']), - featured: z.boolean().default(false), - teamId: z.string().optional(), - listingEnabled: z.boolean().default(true), - category: z.string(), - audience: z.array(z.string()).default([]), - tags: z.array(z.string()).default([]), - publisher: publisherSchema, - publisherVerified: z.boolean().default(false), - templateVersion: z.string(), - templateApiVersion: z.number().int().positive(), - minCliVersion: z.string(), - minCoreVersion: z.string(), - fulfillment: z.object({ - mode: z.enum(['packaged', 'git', 'r2']).default('packaged'), - source: z.union([templateGitSourceSchema, templateR2SourceSchema]), - hooksPolicy: z.enum(['builtin_only', 'trusted_only', 'disabled']).default('builtin_only'), - supportsReconcile: z.boolean().default(true), - }), - offer: z.object({ - priceModel: z.enum(commerceOfferModeValues).default('free'), - license: z.string().optional(), - support: z.string().optional(), - }).default({ priceModel: 'free' }), - relatedBooks: z.array(z.string()).default([]), - relatedKnowledge: z.array(z.string()).default([]), - relatedObjectives: z.array(z.string()).default([]), - }); - const knowledgePackSchema = z.object({ - slug: z.string(), - title: z.string(), - description: z.string(), - status: z.enum(['draft', 'live', 'archived']).default('draft'), - }); - - const workdaySummaryTaskSchema = z.object({ - id: z.string(), - agentId: z.string().optional(), - type: z.string().optional(), - state: z.string().optional(), - priority: z.number().optional(), - idempotencyKey: z.string().optional(), - createdAt: z.coerce.date().optional(), - startedAt: z.coerce.date().optional(), - completedAt: z.coerce.date().optional(), - lastErrorCode: z.string().nullable().optional(), - lastErrorMessage: z.string().nullable().optional(), - lastEventKind: z.string().nullable().optional(), - outputCount: z.number().int().optional(), - changedFiles: z.array(z.string()).default([]), - }); - const workdayPriorityItemSchema = z.object({ - id: z.string(), - model: z.string(), - slug: z.string().optional(), - title: z.string().optional(), - status: z.string().optional(), - priority: z.number(), - estimatedCredits: z.number().optional(), - reason: z.string().optional(), - }); - const workdayReleaseSchema = z.object({ - id: z.string().optional(), - deploymentKind: z.string(), - status: z.string(), - releaseTag: z.string().nullable().optional(), - commitSha: z.string().nullable().optional(), - sourceRef: z.string().nullable().optional(), - startedAt: z.coerce.date().optional(), - finishedAt: z.coerce.date().optional(), - createdAt: z.coerce.date().optional(), - }); - const workdaySchema = z.object({ - title: z.string(), - slug: z.string(), - workDayId: z.string(), - reportVersion: z.string(), - reportKind: z.string().default('workday_summary'), - projectId: z.string(), - teamId: z.string().optional(), - environment: z.string(), - status: z.string().default('live'), - visibility: z.enum(['public', 'authenticated', 'team', 'private']).default('team'), - workdayState: z.string(), - startedAt: z.coerce.date(), - endedAt: z.coerce.date().nullable().optional(), - generatedAt: z.coerce.date(), - createdAt: z.coerce.date().optional(), - summary: z.string(), - dailyTaskCreditBudget: z.number().default(0), - usedTaskCredits: z.number().default(0), - remainingTaskCredits: z.number().default(0), - creditLedgerEntries: z.number().int().default(0), - prioritySnapshotId: z.string().nullable().optional(), - priorityItemCount: z.number().int().default(0), - priorityItems: z.array(workdayPriorityItemSchema).default([]), - totalTasks: z.number().int().default(0), - completedTasks: z.number().int().default(0), - failedTasks: z.number().int().default(0), - queuedTasks: z.number().int().default(0), - activeTasks: z.number().int().default(0), - taskItems: z.array(workdaySummaryTaskSchema).default([]), - changedFiles: z.array(z.string()).default([]), - releases: z.array(workdayReleaseSchema).default([]), - scaleDecision: z.record(z.any()).default({}), - scaleResult: z.record(z.any()).default({}), - metadata: z.record(z.any()).default({}), - }); - - const docsCollectionProvider = resolveDocsCollectionProvider(tenantConfig, { docsLoader, docsSchema }); - const markdownLoader = (base: string) => publishedRuntime - ? optionalMarkdownGlob(base) - : glob({ pattern: '**/*.{md,mdx}', base }); - const collections: Record = { - pages: defineCollection({ loader: markdownLoader(tenantConfig.content.pages), schema: pageSchema }), - notes: defineCollection({ loader: markdownLoader(tenantConfig.content.notes), schema: noteSchema }), - questions: defineCollection({ loader: markdownLoader(tenantConfig.content.questions), schema: questionSchema }), - objectives: defineCollection({ loader: markdownLoader(tenantConfig.content.objectives), schema: objectiveSchema }), - proposals: defineCollection({ loader: markdownLoader(tenantConfig.content.proposals), schema: proposalSchema }), - decisions: defineCollection({ loader: markdownLoader(tenantConfig.content.decisions), schema: decisionSchema }), - people: defineCollection({ loader: markdownLoader(tenantConfig.content.people), schema: peopleSchema }), - agents: defineCollection({ loader: markdownLoader(tenantConfig.content.agents), schema: agentSchema }), - books: defineCollection({ loader: markdownLoader(tenantConfig.content.books), schema: bookSchema }), - docs: defineCollection({ - loader: (publishedRuntime ? optionalMarkdownGlob(tenantConfig.content.docs) : docsCollectionProvider.loader) as any, - schema: docsCollectionProvider.schema as any, - }), - }; - - const agentTestsRoot = resolve(dirname(tenantConfig.content.agents), 'agent-tests'); - if (existsSync(agentTestsRoot)) { - collections.agent_tests = defineCollection({ - loader: optionalMarkdownGlob(agentTestsRoot), - schema: agentTestSchema, - }); - } - - if (tenantConfig.content.workdays) { - collections.workdays = defineCollection({ - loader: optionalMarkdownGlob(tenantConfig.content.workdays), - schema: workdaySchema, - }); - } - - if (tenantConfig.content.templates) { - collections.templates = defineCollection({ - loader: optionalMarkdownGlob(tenantConfig.content.templates), - schema: templateProductSchema, - }); - } - - if (tenantConfig.content.knowledge_packs) { - collections.knowledge_packs = defineCollection({ - loader: optionalMarkdownGlob(tenantConfig.content.knowledge_packs), - schema: knowledgePackSchema, - }); - } - - return collections; -} diff --git a/src/content/agent-schemas.ts b/src/content/agent-schemas.ts new file mode 100644 index 00000000..ca737b67 --- /dev/null +++ b/src/content/agent-schemas.ts @@ -0,0 +1,240 @@ +import { reference } from 'astro:content'; +import { z } from 'astro/zod'; +import { AGENT_ACTIVITY_TYPES } from '@treeseed/sdk/types/agents'; +import { agentTestContentSchema } from '@treeseed/sdk/content-validation'; +import { AGENT_MODEL_DEFAULTS, PEOPLE_MODEL_DEFAULTS } from '../utils/configuration/site-config.ts'; +import { withPortableContentValidation } from './portable-content-schema.ts'; + +const statusValues = ['live', 'in progress', 'exploratory', 'planned', 'speculative'] as const; +const runtimeStatusValues = ['active', 'experimental', 'dormant'] as const; +const agentActivityTypeValues = AGENT_ACTIVITY_TYPES; +const agentHandlerValues = ['writer', 'actor', 'estimate', 'releaser', 'reporter'] as const; +function withOptionalDefault TSchema }>(schema: TSchema, value: unknown) { + return value === undefined ? schema : schema.default(value); +} + +export function createAgentCollectionSchemas() { + const profileLinkSchema = z.object({ label: z.string(), href: z.string() }); + const exactRevisionRefSchema = z.object({ + id: z.string().min(1), + revision: z.number().int().positive(), + }).strict(); + + const agentWorktreeSchema = z.object({ + enabled: z.boolean().default(true), + root: z.string().optional(), + branchPrefix: z.string().optional(), + }); + + const agentExecutionSchema = z.object({ + approvalPolicy: z.string().optional(), + sandboxMode: z.string().optional(), + reasoningEffort: z.string().optional(), + allowedPaths: z.array(z.string()).default([]), + forbiddenPaths: z.array(z.string()).default([]), + worktree: agentWorktreeSchema.default({}), + maxConcurrency: z.number().int().positive().default(1), + timeoutSeconds: z.number().int().positive().default(900), + cooldownSeconds: z.number().int().nonnegative().default(30), + leaseSeconds: z.number().int().positive().default(300), + retryLimit: z.number().int().nonnegative().default(3), + branchPrefix: z.string().default('agent'), + }); + + const agentCapabilitySchema = z.union([ + z.string(), + z.object({ + id: z.string(), + label: z.string().optional(), + summary: z.string().optional(), + produces: z.array(z.string()).default([]), + requires: z.array(z.string()).default([]), + reviews: z.array(z.string()).default([]), + }).passthrough(), + ]); + + const agentIdentitySchema = z.object({ + purpose: z.string().optional(), + instructions: z.string().optional(), + traits: z.array(z.string()).default([]), + }).passthrough(); + + const agentPromptSchema = z.union([ + z.string(), + z.object({ + system: z.string(), + task: z.string().optional(), + templates: z.record(z.string()).optional(), + }).passthrough(), + ]); + + const agentToolPolicySchema = z.object({ + allowed: z.array(z.string()).default([]), + denied: z.array(z.string()).default([]), + }).passthrough(); + + const agentPermissionsSchema = z.object({ + content:z.record(z.object({ operations:z.array(z.string().min(1)).min(1),filters:z.record(z.unknown()).optional() }).strict()).optional(), + commit: z.object({ allowed: z.boolean() }).optional(), + repository: z.object({ readPaths:z.array(z.string()).optional(),writePaths:z.array(z.string()).optional(),allowCodeMutation:z.boolean().optional() }).strict().optional(), + network: z.object({ allowWeb:z.boolean().optional(),allowedDomains:z.array(z.string()).optional() }).strict().optional(), + shell: z.object({ allowCommands:z.boolean().optional(),allowedCommands:z.array(z.string()).optional(),deniedCommands:z.array(z.string()).optional() }).strict().optional(), + }).passthrough(); + + const agentBranchPolicySchema = z.object({ + kind: z.string(), + base: z.string().optional(), + target: z.string().optional(), + prefix: z.string().optional(), + branchNameTemplate: z.string().optional(), + worktree: z.string().optional(), + updateBaseBeforeRun: z.boolean().optional(), + mergeTargetBeforeSave: z.boolean().optional(), + }).passthrough(); + + const agentQuestionPolicySchema = z.object({ + blockExecutionWhenCreated: z.boolean().optional(), + defaultAnswerPolicy: z.object({ + kind: z.string(), + teamId: z.string().optional(), + requiredRoles: z.array(z.string()).default([]), + allowedRoles: z.array(z.string()).default([]), + allowedAgentIds: z.array(z.string()).default([]), + allowedActivityProfiles: z.array(z.string()).default([]), + teamMemberId: z.string().optional(), + projectId: z.string().optional(), + agentSlug: z.string().optional(), + }).passthrough().optional(), + }).passthrough(); + + const agentOutputsSchema = z.object({ + messageTypes: z.array(z.string()).default([]), + modelMutations: z.array(z.string()).default([]), + requiredArtifacts: z.array(z.string()).default([]), + schemas: z.array(z.string()).default([]), + }).passthrough(); + const agentSignalsSchema = z.object({ + subscribesTo: z.array(z.object({ + contract: z.string().min(1), + filters: z.record(z.unknown()).optional(), + cardinality: z.enum(['single', 'each']).optional(), + producerPolicy: z.enum(['any', 'all', 'quorum']).optional(), + quorum: z.number().int().positive().optional(), + }).strict()).default([]), + publishes: z.array(z.string().min(1)).default([]), + }).strict(); + + const agentActivityExecutionSchema = z.object({ + reasoningEffort: z.enum(['minimal', 'low', 'medium', 'high', 'xhigh']).optional(), + maxRuntimeSeconds: z.number().int().positive().optional(), + preparationSeconds: z.number().int().positive().optional(), + closeoutSeconds: z.number().int().positive().optional(), + closeoutWarningSeconds: z.number().int().positive().optional(), + maxRetries: z.number().int().nonnegative().optional(), + verificationRequired: z.boolean().optional(), + maxTotalTokens: z.number().int().positive().optional(), + warningTokens: z.number().int().positive().optional(), + maxCostAmount: z.number().nonnegative().optional(), + costCurrency: z.string().min(3).max(3).optional(), + nativeLimits: z.array(z.object({ unit: z.string().min(1), amount: z.number().nonnegative(), enforceable: z.boolean().optional() }).strict()).optional(), + pricingGeneration: z.string().optional(), + enforcementConfidence: z.enum(['exact', 'bounded', 'estimated', 'opaque']).optional(), + }).strict(); + const capabilityRequirementSchema = z.object({ + capabilityId: z.string().regex(/^(?:treeseed\.[a-z][a-z0-9.-]*|provider\.[a-f0-9]{16,64}\.[a-z][a-z0-9.-]*)$/u), + versionRange: z.string().min(1), requirement: z.enum(['required', 'preferred']), alternativeGroup: z.string().min(1).nullable().optional(), + requiredFeatures: z.array(z.string().min(1)).default([]), + configuration: z.record(z.object({ value: z.unknown(), requirement: z.enum(['required', 'preferred']) }).strict()).default({}), + }).strict(); + + const agentActivityProfileSchema = z.object({ + enabled: z.boolean().default(true), + activityType: z.enum(agentActivityTypeValues), + handler: z.enum(agentHandlerValues), + prompt: agentPromptSchema, + contextQueryRefs: z.array(exactRevisionRefSchema).default([]), + contextQuerySetRefs: z.array(exactRevisionRefSchema).default([]), + instructionTemplateRefs: z.array(exactRevisionRefSchema).default([]), + permissions: agentPermissionsSchema.optional(), + artifactTriggers: z.array(z.object({ event:z.string().min(1),artifactKind:z.string().min(1),model:z.string().min(1).optional(),required:z.boolean().optional() }).strict()).default([]), + closeoutPolicy: z.object({ warningSeconds:z.number().int().positive().optional(),summaryRequired:z.boolean().optional(),requiredArtifactKinds:z.array(z.string()).optional(),blockOnOpenQuestions:z.boolean().optional() }).strict().optional(), + capabilityRequirements: z.array(capabilityRequirementSchema).min(1), + tools: agentToolPolicySchema.default({ allowed: [] }), + signals: agentSignalsSchema.optional(), + outputs: agentOutputsSchema.default({}), + questionPolicy: agentQuestionPolicySchema.optional(), + branchPolicy: agentBranchPolicySchema, + execution: agentActivityExecutionSchema.optional(), + }).passthrough(); + const agentChatProfileSchema = z.object({ + foundation: z.literal('discussion-v1'), + responseStyle: z.string().optional(), + promptTask: z.string().optional(), + capabilityRequirements: z.array(capabilityRequirementSchema).optional(), + maxRuntimeSeconds: z.number().int().positive().optional(), + maxTotalTokens: z.number().int().positive().optional(), + warningTokens: z.number().int().positive().optional(), + maxCostAmount: z.number().nonnegative().optional(), + costCurrency: z.string().length(3).optional(), + toolAdditions: z.array(z.string()).optional(), + contextModels: z.array(z.string()).optional(), + }).strict(); + const agentGroupSubscriptionSchema = z.object({ + groupIds: z.array(z.string()).min(1), + includeDescendants: z.boolean().default(true), + models: z.array(z.string()).min(1), + events: z.array(z.string()).min(1), + activityProfile: z.string().min(1), + intent: z.enum(['discuss', 'propose', 'act']).optional(), + }).strict(); + + const peopleSchema = z.object({ + name: z.string(), + description: z.string(), + summary: z.string(), + role: z.string(), + affiliation: z.string(), + status: withOptionalDefault(z.enum(statusValues), PEOPLE_MODEL_DEFAULTS.status), + groupIds: z.array(z.string()).default([]), + links: z.array(profileLinkSchema).default([]), + relatedQuestions: z.array(reference('questions')).default([]), + relatedObjectives: z.array(reference('objectives')).default([]), + }); + + const agentSchema = z.object({ + id: z.string().optional(), + name: z.string(), + slug: z.string(), + title: z.string().optional(), + enabled: z.boolean().default(true), + description: z.string(), + summary: z.string(), + agentClass: z.string(), + projectAgentClassId: z.string().optional(), + projectAgentClassSlug: z.string().optional(), + template: z.string().optional(), + identity: agentIdentitySchema.default({}), + runtimeStatus: withOptionalDefault(z.enum(runtimeStatusValues), AGENT_MODEL_DEFAULTS.runtimeStatus), + designMaturity: z.enum(['draft', 'validated', 'simulated', 'proven']).default('draft'), + capabilities: z.array(agentCapabilitySchema).default([]), + groupIds: z.array(z.string()).min(1), + topicIds: z.array(z.string()).default([]), + contextQueryRefs: z.array(exactRevisionRefSchema).default([]), + contextQuerySetRefs: z.array(exactRevisionRefSchema).default([]), + instructionTemplateRefs: z.array(exactRevisionRefSchema).default([]), + groupSubscriptions: z.array(agentGroupSubscriptionSchema).default([]), + links: z.array(profileLinkSchema).default([]), + relatedQuestions: z.array(reference('questions')).default([]), + relatedObjectives: z.array(reference('objectives')).default([]), + activityProfiles: z.record(agentActivityProfileSchema).refine((profiles) => Object.keys(profiles).length > 0, { + message: 'activityProfiles must define at least one activity profile', + }), + chatProfile: agentChatProfileSchema.optional(), + }).strict(); + + const agentTestSchema = agentTestContentSchema; + + return { profileLinkSchema,agentWorktreeSchema,agentExecutionSchema,agentCapabilitySchema,agentIdentitySchema,agentPromptSchema,agentToolPolicySchema,agentPermissionsSchema,agentBranchPolicySchema,agentQuestionPolicySchema,agentOutputsSchema,agentActivityExecutionSchema,agentActivityProfileSchema, + peopleSchema: withPortableContentValidation('person', peopleSchema), + agentSchema: withPortableContentValidation('agent', agentSchema), agentTestSchema }; +} diff --git a/src/content/catalog-schemas.ts b/src/content/catalog-schemas.ts new file mode 100644 index 00000000..56c97af7 --- /dev/null +++ b/src/content/catalog-schemas.ts @@ -0,0 +1,29 @@ +import { z } from 'astro/zod'; +import { BOOK_MODEL_DEFAULTS } from '../utils/configuration/site-config.ts'; +import { BOOK_SCHEMA_VERSION, KNOWLEDGE_STATUSES, KNOWLEDGE_VISIBILITIES } from '@treeseed/sdk/knowledge'; +import { templateProductContentSchema } from '@treeseed/sdk/content-validation'; +import { withPortableContentValidation } from './portable-content-schema.ts'; + +export function createCatalogCollectionSchemas() { + const bookSchema = z.object({ + schemaVersion: z.literal(BOOK_SCHEMA_VERSION), + id: z.string().min(1), + order: z.number().int().nonnegative(), + slug: z.string().min(1), + title: z.string().min(1), + description: z.string().min(1), + summary: z.string().min(1), + status: z.enum(KNOWLEDGE_STATUSES), + visibility: z.enum(KNOWLEDGE_VISIBILITIES), + groupIds: z.array(z.string()).default(BOOK_MODEL_DEFAULTS.groupIds ?? []), + audience: z.array(z.string()).default([]), + relatedBookIds: z.array(z.string()).default([]), + editorialCoreNoteId: z.string().optional(), + packPolicy: z.enum(['allowed', 'restricted', 'disabled']).default('allowed'), + cover: z.object({ image: z.string().optional(), alt: z.string().optional() }).optional(), + }); + + const templateProductSchema = templateProductContentSchema; + + return { bookSchema: withPortableContentValidation('book', bookSchema), templateProductSchema }; +} diff --git a/src/content/content.ts b/src/content/content.ts new file mode 100644 index 00000000..6cf03b5d --- /dev/null +++ b/src/content/content.ts @@ -0,0 +1,271 @@ +import { defineCollection, reference } from 'astro:content'; +import { z } from 'astro/zod'; +import { glob, type Loader } from 'astro/loaders'; +import { existsSync, readdirSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import type { FieldAliasRegistry } from '@treeseed/sdk/field-aliases'; +import type { TenantConfig } from '@treeseed/sdk/site-contracts/platform'; +import { COMMERCE_OFFER_MODES, type CommerceOfferMode } from '@treeseed/sdk/site-contracts/catalog'; +import { AGENT_ACTIVITY_TYPES } from '@treeseed/sdk/types/agents'; +import { loadPluginRuntime } from '../runtime/platform/plugins.ts'; +import { loadDeployConfig } from '../runtime/platform/deploy-config.ts'; +import { getContentServingMode } from '../runtime/platform/deploy-runtime.ts'; +import { + AGENT_MODEL_DEFAULTS, + BOOK_MODEL_DEFAULTS, + MODEL_DEFAULTS, + NOTE_MODEL_DEFAULTS, + OBJECTIVE_MODEL_DEFAULTS, + PAGE_MODEL_DEFAULTS, + PEOPLE_MODEL_DEFAULTS, + PROPOSAL_MODEL_DEFAULTS, + QUESTION_MODEL_DEFAULTS, + DECISION_MODEL_DEFAULTS, +} from '../utils/configuration/site-config'; +import { preprocessAliasedRecord } from '@treeseed/sdk/field-aliases'; +import { KNOWLEDGE_PAGE_SCHEMA_VERSION, KNOWLEDGE_STATUSES, KNOWLEDGE_VISIBILITIES } from '@treeseed/sdk/knowledge'; +import { withPortableContentValidation } from './portable-content-schema.ts'; + +const statusValues = ['live', 'in progress', 'exploratory', 'planned', 'speculative'] as const; +const pageLayoutValues = ['article', 'bridge'] as const; +const questionTypeValues = ['research', 'implementation', 'strategy', 'evaluation'] as const; +const proposalTypeValues = ['strategy', 'policy', 'implementation', 'research'] as const; +const decisionTypeValues = ['approved', 'rejected', 'deferred', 'request_changes', 'superseded'] as const; +const governanceStatusValues = ['draft', 'open', 'voting', 'accepted', 'rejected', 'no_decision_quorum_failed', 'withdrawn', 'superseded'] as const; +const timeHorizonValues = ['near-term', 'mid-term', 'long-term'] as const; +const runtimeStatusValues = ['active', 'experimental', 'dormant'] as const; +const commerceOfferModeValues = [...COMMERCE_OFFER_MODES] as [CommerceOfferMode, ...CommerceOfferMode[]]; +const agentActivityTypeValues = AGENT_ACTIVITY_TYPES; +const agentHandlerValues = ['writer', 'actor', 'estimate', 'releaser', 'reporter'] as const; + +type DocsDependencies = { + docsLoader: (options: Record) => unknown; + docsSchema: (options: Record) => unknown; +}; + +type DocsCollectionProvider = { + loader: unknown; + schema: unknown; +}; + +const DocsExtensions = ['markdown', 'mdown', 'mkdn', 'mkd', 'mdwn', 'md', 'mdx']; + +function hasMarkdownContent(base: string): boolean { + if (!existsSync(base)) { + return false; + } + for (const entry of readdirSync(base, { withFileTypes: true, recursive: true })) { + if (entry.isFile() && /\.(md|mdx)$/iu.test(entry.name)) { + return true; + } + } + return false; +} + +function optionalMarkdownGlob( + base: string, + options: { + pattern?: string; + generateId?: (args: { entry: string; data: Record }) => string; + } = {}, +): Loader { + return { + name: `treeseed-optional-markdown-glob:${base}`, + async load(context) { + context.store.clear(); + if (!hasMarkdownContent(base)) { + return; + } + const delegate = glob({ + pattern: options.pattern ?? '**/*.{md,mdx}', + base, + generateId: options.generateId, + }); + await delegate.load(context); + }, + }; +} + +function emptyContentLoader(collection: string): Loader { + return { + name: `treeseed-published-runtime:${collection}`, + async load(context) { + context.store.clear(); + }, + }; +} + +function withOptionalDefault TSchema }>( + schema: TSchema, + defaultValue: unknown, +) { + return defaultValue === undefined ? schema : schema.default(defaultValue); +} + +function createKnowledgeDocId({ entry, data }: { entry: string; data: Record }) { + const rawSlug = typeof data.slug === 'string' ? data.slug : entry; + const normalized = rawSlug + .replace(/\\/g, '/') + .replace(/\.(md|mdx)$/i, '') + .replace(/\/index$/i, '') + .replace(/^\/+|\/+$/g, ''); + + if (normalized === 'knowledge' || normalized.startsWith('knowledge/')) { + return normalized; + } + + return normalized ? `knowledge/${normalized}` : 'knowledge'; +} + +function resolveDocsCollectionProvider( + tenantConfig: TenantConfig, + dependencies: DocsDependencies, + deployConfig = loadDeployConfig(), +): DocsCollectionProvider { + const pluginRuntime = loadPluginRuntime(deployConfig); + const selectedId = pluginRuntime.config.providers.content.docs; + + if (selectedId === 'default') { + return { + loader: optionalMarkdownGlob(tenantConfig.content.docs, { + pattern: `**/[^_]*.{${DocsExtensions.join(',')}}`, + generateId: createKnowledgeDocId, + }), + schema: dependencies.docsSchema({ + extend: withPortableContentValidation('knowledge', z.object({ + schemaVersion: z.literal(KNOWLEDGE_PAGE_SCHEMA_VERSION), + id: z.string(), + projectId: z.string(), + bookRef: z.object({ + store: z.literal('treedx'), + model: z.literal('book'), + id: z.string(), + revision: z.number().int().positive(), + digest: z.string(), + repository: z.string(), + commit: z.string().optional(), + path: z.string(), + }).passthrough(), + slug: z.string(), + summary: z.string(), + status: z.enum(KNOWLEDGE_STATUSES), + visibility: z.enum(KNOWLEDGE_VISIBILITIES), + order: z.number().int().nonnegative().default(0), + parentId: z.string().optional(), + groupIds: z.array(z.string()).default(MODEL_DEFAULTS.groupIds ?? []), + contributors: z.array(z.string()).default([]), + relatedBookIds: z.array(z.string()).default([]), + relatedKnowledgeIds: z.array(z.string()).default([]), + guaranteeIds: z.array(z.string()).default([]), + audiences: z.object({ + primary: z.array(z.string()).default([]), + secondary: z.array(z.string()).default([]), + excluded: z.array(z.string()).default([]), + }).default({ primary: [], secondary: [], excluded: [] }), + capabilityIds: z.array(z.string()).default([]), + routePatterns: z.array(z.string()).default([]), + resourceTypes: z.array(z.string()).default([]), + actionIds: z.array(z.string()).default([]), + keywords: z.array(z.string()).default([]), + documentationUrls: z.array(z.string()).default([]), + }).transform((value) => ({ ...value, bookId: value.bookRef.id }))), + }), + }; + } + + for (const { plugin, config, package: packageName } of pluginRuntime.plugins) { + const docsProviders = plugin.contentProviders?.docs ?? {}; + if (!(selectedId in docsProviders)) { + continue; + } + const resolved = docsProviders[selectedId]({ + tenantConfig, + dependencies, + pluginConfig: config ?? {}, + }); + if (!resolved?.loader || !resolved?.schema) { + throw new Error(`Treeseed docs provider "${selectedId}" from "${packageName}" must return loader and schema.`); + } + return resolved as DocsCollectionProvider; + } + + throw new Error(`Treeseed docs provider "${selectedId}" is not registered.`); +} + +import { createGovernanceCollectionSchemas } from './governance-schemas.ts'; +import { createAgentCollectionSchemas } from './agent-schemas.ts'; +import { createCatalogCollectionSchemas } from './catalog-schemas.ts'; +import { createWorkdayCollectionSchemas } from './workday-schemas.ts'; +import { createDiscussionCollectionSchemas } from './discussion-schemas.ts'; +import { createGroupCollectionSchemas } from './group-schemas.ts'; +export function createCollections(tenantConfig: TenantConfig, { docsLoader, docsSchema }: DocsDependencies) { + const deployConfig = loadDeployConfig(); + const publishedRuntime = getContentServingMode(deployConfig) === 'published_runtime'; + const { pageSchema, noteSchema, questionSchema, objectiveSchema, proposalSchema, decisionSchema } = createGovernanceCollectionSchemas(); + const { peopleSchema, agentSchema, agentTestSchema } = createAgentCollectionSchemas(); + const { bookSchema, templateProductSchema } = createCatalogCollectionSchemas(); + const { workdaySchema } = createWorkdayCollectionSchemas(); + const { discussionSchema, discussionMessageSchema, discussionEventSchema } = createDiscussionCollectionSchemas(); + const { groupSchema, groupEdgeSchema } = createGroupCollectionSchemas(); + const docsCollectionProvider = resolveDocsCollectionProvider(tenantConfig, { docsLoader, docsSchema }, deployConfig); + const markdownLoader = (base: string, collection: string) => publishedRuntime + ? emptyContentLoader(collection) + : glob({ pattern: '**/*.{md,mdx}', base }); + const collections: Record = { + pages: defineCollection({ loader: markdownLoader(tenantConfig.content.pages, 'pages'), schema: pageSchema }), + notes: defineCollection({ loader: markdownLoader(tenantConfig.content.notes, 'notes'), schema: noteSchema }), + questions: defineCollection({ loader: markdownLoader(tenantConfig.content.questions, 'questions'), schema: questionSchema }), + objectives: defineCollection({ loader: markdownLoader(tenantConfig.content.objectives, 'objectives'), schema: objectiveSchema }), + proposals: defineCollection({ loader: markdownLoader(tenantConfig.content.proposals, 'proposals'), schema: proposalSchema }), + decisions: defineCollection({ loader: markdownLoader(tenantConfig.content.decisions, 'decisions'), schema: decisionSchema }), + people: defineCollection({ loader: markdownLoader(tenantConfig.content.people, 'people'), schema: peopleSchema }), + agents: defineCollection({ loader: markdownLoader(tenantConfig.content.agents, 'agents'), schema: agentSchema }), + discussions: defineCollection({ loader: markdownLoader(tenantConfig.content.discussions ?? resolve(dirname(tenantConfig.content.agents), 'discussions'), 'discussions'), schema: discussionSchema }), + discussion_messages: defineCollection({ loader: markdownLoader(tenantConfig.content.discussion_messages ?? resolve(dirname(tenantConfig.content.agents), 'discussion-messages'), 'discussion_messages'), schema: discussionMessageSchema }), + discussion_events: defineCollection({ loader: markdownLoader(tenantConfig.content.discussion_events ?? resolve(dirname(tenantConfig.content.agents), 'discussion-events'), 'discussion_events'), schema: discussionEventSchema }), + groups: defineCollection({ loader: markdownLoader(tenantConfig.content.groups ?? resolve(dirname(tenantConfig.content.agents), 'groups'), 'groups'), schema: groupSchema }), + group_edges: defineCollection({ loader: markdownLoader(tenantConfig.content.group_edges ?? resolve(dirname(tenantConfig.content.agents), 'group-edges'), 'group_edges'), schema: groupEdgeSchema }), + books: defineCollection({ loader: markdownLoader(tenantConfig.content.books, 'books'), schema: bookSchema }), + docs: defineCollection({ + loader: (publishedRuntime ? emptyContentLoader('docs') : docsCollectionProvider.loader) as any, + schema: docsCollectionProvider.schema as any, + }), + }; + const operationalCollections = { + agent_context_queries: ['agent_context_query','agent-context-queries'], + agent_context_query_sets: ['agent_context_query_set','agent-context-query-sets'], + agent_instruction_templates: ['agent_instruction_template','agent-instruction-templates'], + discussion_topics: ['discussion_topic','discussion-topics'], + agent_evaluations: ['agent_evaluation','agent-evaluations'], + } as const; + for (const [collection,[model,directory]] of Object.entries(operationalCollections)) { + const base = resolve(dirname(tenantConfig.content.agents),directory); + if (existsSync(base)) collections[collection] = defineCollection({ + loader: optionalMarkdownGlob(base),schema: withPortableContentValidation(model,z.object({}).passthrough()), + }); + } + + const agentTestsRoot = resolve(dirname(tenantConfig.content.agents), 'agent-tests'); + if (existsSync(agentTestsRoot)) { + collections.agent_tests = defineCollection({ + loader: optionalMarkdownGlob(agentTestsRoot), + schema: agentTestSchema, + }); + } + + if (tenantConfig.content.workdays) { + collections.workdays = defineCollection({ + loader: optionalMarkdownGlob(tenantConfig.content.workdays), + schema: workdaySchema, + }); + } + + if (tenantConfig.content.templates) { + collections.templates = defineCollection({ + loader: optionalMarkdownGlob(tenantConfig.content.templates), + schema: templateProductSchema, + }); + } + + return collections; +} diff --git a/src/content/discussion-schemas.ts b/src/content/discussion-schemas.ts new file mode 100644 index 00000000..e53461f1 --- /dev/null +++ b/src/content/discussion-schemas.ts @@ -0,0 +1,66 @@ +import { z } from 'astro/zod'; +import { withPortableContentValidation } from './portable-content-schema.ts'; + +const fileReferenceSchema = z.object({ + repository: z.string(), + path: z.string(), + ref: z.string(), + startLine: z.number().int().positive().optional(), + endLine: z.number().int().positive().optional(), +}); + +export function createDiscussionCollectionSchemas() { + const discussionSchema = z.object({ + title: z.string(), + topic: z.string(), + status: z.enum(['active', 'archived']).default('active'), + legacyStatus: z.enum(['open', 'resolved']).optional(), + teamId: z.string(), + projectId: z.string(), + visibility: z.enum(['team', 'private']).default('team'), + participantIds: z.array(z.string()).default([]), + agentIds: z.array(z.string()).default([]), + groupIds: z.array(z.string()).default([]), + parentWorkdayId: z.string().optional(), + trackedBranch: z.string().optional(), + createdAt: z.coerce.date(), + updatedAt: z.coerce.date(), + }); + const discussionMessageSchema = z.object({ + title: z.string(), + discussionId: z.string(), + authorId: z.string(), + authorType: z.enum(['user', 'agent', 'system']), + intent: z.enum(['discuss', 'propose']).default('discuss'), + replyTo: z.string().optional(), + sourceMessageRefs: z.array(z.string()).default([]), + mentionedAgents: z.array(z.string()).default([]), + authorAgentId: z.string().optional(), + handoffId: z.string().optional(), + parentWorkdayId: z.string().optional(), + resultingOperationId: z.string().optional(), + groupIds: z.array(z.string()).default([]), + fileRefs: z.array(fileReferenceSchema).default([]), + contextRefs: z.array(z.record(z.any())).default([]), + createdAt: z.coerce.date(), + }); + const discussionEventSchema = z.object({ + title: z.string(), + discussionId: z.string(), + messageId: z.string().optional(), + phase: z.string(), + sequence: z.number().int().nonnegative(), + agentId: z.string().optional(), + assignmentId: z.string().optional(), + providerId: z.string().optional(), + groupIds: z.array(z.string()).default([]), + occurredAt: z.coerce.date(), + metrics: z.record(z.any()).default({}), + refs: z.array(z.string()).default([]), + }); + return { + discussionSchema: withPortableContentValidation('discussion', discussionSchema), + discussionMessageSchema: withPortableContentValidation('discussion_message', discussionMessageSchema), + discussionEventSchema: withPortableContentValidation('discussion_event', discussionEventSchema), + }; +} diff --git a/src/content/governance-schemas.ts b/src/content/governance-schemas.ts new file mode 100644 index 00000000..2c655f6f --- /dev/null +++ b/src/content/governance-schemas.ts @@ -0,0 +1,48 @@ +import { z } from 'astro/zod'; +import type { FieldAliasRegistry } from '@treeseed/sdk/field-aliases'; +import { preprocessAliasedRecord } from '@treeseed/sdk/field-aliases'; +import { PAGE_MODEL_DEFAULTS } from '../utils/configuration/site-config.ts'; +import { withPortableContentValidation } from './portable-content-schema.ts'; + +const statusValues = ['live', 'in progress', 'exploratory', 'planned', 'speculative'] as const; +const pageLayoutValues = ['article', 'bridge'] as const; + +function withOptionalDefault TSchema }>(schema: TSchema, value: unknown) { + return value === undefined ? schema : schema.default(value); +} + +/** + * Repository-authored site pages retain their presentation schema. Governed + * operational content is validated only by the canonical SDK schemas before + * Astro receives it; Core must not maintain a second proposal/decision model. + */ +export function createGovernanceCollectionSchemas() { + const pageFieldAliases: FieldAliasRegistry = { + pageLayout: { key: 'pageLayout', aliases: ['page_layout'] }, + seoTitle: { key: 'seoTitle', aliases: ['seo_title'] }, + seoDescription: { key: 'seoDescription', aliases: ['seo_description'] }, + }; + const pageSchema = z.preprocess((value) => preprocessAliasedRecord(pageFieldAliases, value), z.object({ + title: z.string(), + description: z.string(), + slug: z.string(), + pageLayout: withOptionalDefault(z.enum(pageLayoutValues), PAGE_MODEL_DEFAULTS.pageLayout), + status: withOptionalDefault(z.enum(statusValues), PAGE_MODEL_DEFAULTS.status), + stage: withOptionalDefault(z.string(), PAGE_MODEL_DEFAULTS.stage), + audience: z.array(z.string()).default(PAGE_MODEL_DEFAULTS.audience ?? []), + summary: z.string(), + updated: z.coerce.date(), + seoTitle: z.string().optional(), + seoDescription: z.string().optional(), + })); + const governed = (model: 'note' | 'question' | 'objective' | 'proposal' | 'decision') => + withPortableContentValidation(model, z.any()); + return { + pageSchema: withPortableContentValidation('page', pageSchema), + noteSchema: governed('note'), + questionSchema: governed('question'), + objectiveSchema: governed('objective'), + proposalSchema: governed('proposal'), + decisionSchema: governed('decision'), + }; +} diff --git a/src/content/group-schemas.ts b/src/content/group-schemas.ts new file mode 100644 index 00000000..8169c776 --- /dev/null +++ b/src/content/group-schemas.ts @@ -0,0 +1,27 @@ +import { z } from 'astro/zod'; +import { withPortableContentValidation } from './portable-content-schema.ts'; + +export function createGroupCollectionSchemas() { + const groupSchema = z.object({ + contract: z.literal('treeseed.group/v1'), + id: z.string().min(1), + slug: z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/u), + name: z.string().min(1), + description: z.string().min(1), + classification: z.string().min(1), + aliases: z.array(z.string()).default([]), + status: z.enum(['active', 'archived']).default('active'), + }); + const groupEdgeSchema = z.object({ + contract: z.literal('treeseed.group-edge/v1'), + id: z.string().min(1), + fromGroupId: z.string().min(1), + toGroupId: z.string().min(1), + predicate: z.string().min(1), + propagatesMembership: z.boolean().default(false), + }); + return { + groupSchema: withPortableContentValidation('group', groupSchema), + groupEdgeSchema: withPortableContentValidation('group_edge', groupEdgeSchema), + }; +} diff --git a/src/content/portable-content-schema.ts b/src/content/portable-content-schema.ts new file mode 100644 index 00000000..16225cb4 --- /dev/null +++ b/src/content/portable-content-schema.ts @@ -0,0 +1,34 @@ +import { z } from 'astro/zod'; +import { validatePortableContentData,type PortableContentModel } from '@treeseed/sdk/content-validation'; + +function inputField(field: string | undefined, value: unknown) { + if (!field || !value || typeof value !== 'object' || Array.isArray(value)) return field; + const [first, ...rest] = field.split('.'); + const camel = first!.replace(/_([a-z])/gu, (_, letter: string) => letter.toUpperCase()); + return camel !== first && Object.prototype.hasOwnProperty.call(value, camel) ? [camel, ...rest].join('.') : field; +} + +function issuePath(field: string | undefined) { + return field?.split('.').filter(Boolean).flatMap((segment) => { + const parts = segment.split(/\[|\]/u).filter(Boolean); + return parts.map((part) => /^\d+$/u.test(part) ? Number(part) : part); + }) ?? []; +} + +export function withPortableContentValidation(model: PortableContentModel, schema: TSchema) { + return z.any().superRefine((value, context) => { + const record = value && typeof value === 'object' && !Array.isArray(value) ? value as Record : {}; + const { prev: _prev, next: _next, tableOfContents: _tableOfContents, sidebar: _sidebar, ...content } = record; + // Astro validates frontmatter before exposing the parsed Markdown body. + // The complete body is validated at the TreeDX/API publication boundary. + const candidate = model === 'knowledge' && content.body === undefined + ? { ...content, body: '[parsed Markdown body]' } : content; + const result = validatePortableContentData(model, candidate); + for (const diagnostic of result.diagnostics) context.addIssue({ + code: z.ZodIssueCode.custom, + path: issuePath(inputField(diagnostic.field, value)), + message: diagnostic.message, + params: { code: diagnostic.code, model }, + }); + }).pipe(schema); +} diff --git a/src/content/workday-schemas.ts b/src/content/workday-schemas.ts new file mode 100644 index 00000000..6385d6fd --- /dev/null +++ b/src/content/workday-schemas.ts @@ -0,0 +1,5 @@ +import { workdayContentSchema } from '@treeseed/sdk/content-validation'; + +export function createWorkdayCollectionSchemas() { + return { workdaySchema: workdayContentSchema }; +} diff --git a/src/dev.ts b/src/dev.ts deleted file mode 100644 index 7060fda0..00000000 --- a/src/dev.ts +++ /dev/null @@ -1,3442 +0,0 @@ -import { appendFileSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs'; -import type { ChildProcess, SpawnOptions } from 'node:child_process'; -import { spawn, spawnSync } from 'node:child_process'; -import { createHash } from 'node:crypto'; -import { createRequire } from 'node:module'; -import { homedir } from 'node:os'; -import { dirname, isAbsolute, relative, resolve, sep } from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { setTimeout as delay } from 'node:timers/promises'; -import { DatabaseSync } from 'node:sqlite'; -import { - applyTreeseedEnvironmentToProcess, - assertTreeseedCommandEnvironment, - createPersistentDeployTarget, - ensureGeneratedWranglerConfig, - ensureLocalWorkspaceLinks, - findNearestTreeseedWorkspaceRoot, - packageScriptPath, - resolveTreeseedMachineEnvironmentValues, - resolveTreeseedToolBinary, - resolveWranglerBin, - runTreeseedGit, -} from '@treeseed/sdk/workflow-support'; -import { discoverTreeseedApplications } from '@treeseed/sdk/hosting'; -import { loadTreeseedDeployConfig } from '@treeseed/sdk/platform/deploy-config'; -import { - startPollingWatch, - type TreeseedDevWatchController, - type TreeseedDevWatchEntry, - type TreeseedDevWatchStarter, -} from './dev-watch'; - -const require = createRequire(import.meta.url); -const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..'); - -export const TREESEED_DEFAULT_WEB_HOST = '127.0.0.1'; -export const TREESEED_DEFAULT_WEB_PORT = 4321; -export const TREESEED_DEFAULT_API_HOST = '127.0.0.1'; -export const TREESEED_DEFAULT_API_PORT = 3000; -export const TREESEED_DEFAULT_LOCAL_SMTP_HOST = '127.0.0.1'; -export const TREESEED_DEFAULT_LOCAL_SMTP_PORT = 1025; -export const TREESEED_DEFAULT_MAILPIT_UI_PORT = 8025; -export const TREESEED_DEFAULT_MARKET_POSTGRES_PORT = 55432; -export const TREESEED_DEFAULT_MARKET_POSTGRES_CONTAINER = 'treeseed-market-local-postgres'; -export const TREESEED_DEFAULT_MARKET_POSTGRES_VOLUME = 'treeseed-market-local-postgres-data'; -export const TREESEED_DEFAULT_MARKET_POSTGRES_URL = `postgres://treeseed:treeseed@127.0.0.1:${TREESEED_DEFAULT_MARKET_POSTGRES_PORT}/market_local`; - -const DEV_RELOAD_FILE = 'public/__treeseed/dev-reload.json'; -const DEV_RUNTIME_DIR = '.treeseed/generated/dev'; -const DEV_RUNTIME_LEGACY_FILE = '.treeseed/generated/dev/runtime.json'; -const DEV_INSTANCE_DIR = '.treeseed/dev/instances'; -const DEV_PID_DIR = '.treeseed/dev/pids'; -const DEV_REPO_INDEX_RELATIVE_PATH = 'treeseed/dev-index.json'; -const DEFAULT_READINESS_TIMEOUT_MS = 90_000; -const DEFAULT_SETUP_STEP_TIMEOUT_MS = 300_000; -const DEFAULT_PROCESS_READY_GRACE_MS = 1_200; -const DEFAULT_SHUTDOWN_GRACE_MS = 2_500; -const DEFAULT_KILL_GRACE_MS = 500; -const INITIAL_RESTART_BACKOFF_MS = 1_000; -const MAX_RESTART_BACKOFF_MS = 15_000; -const SETUP_RETRY_BACKOFF_MS = 3_000; - -export type TreeseedIntegratedDevSurface = 'integrated' | 'all' | 'web' | 'api' | 'manager' | 'worker' | 'agents' | 'services'; -export type TreeseedIntegratedDevSetupMode = 'auto' | 'check' | 'off'; -export type TreeseedIntegratedDevFeedbackMode = 'live' | 'restart' | 'off'; -export type TreeseedIntegratedDevOpenMode = 'auto' | 'on' | 'off'; -export type TreeseedLocalRuntimeMode = 'auto' | 'provider' | 'local'; -export type TreeseedSelectedLocalRuntime = 'astro-local' | 'cloudflare-wrangler-local' | 'node-local'; -export type TreeseedIntegratedDevCommandId = 'web' | 'api' | 'manager' | 'worker' | 'agents' | 'operations-runner'; - -export type TreeseedLocalRuntimeSelection = { - requested: TreeseedLocalRuntimeMode; - selected: TreeseedSelectedLocalRuntime; - provider: string; - reason?: string; -}; - -export type TreeseedIntegratedDevOptions = { - surface?: TreeseedIntegratedDevSurface; - surfaces?: string; - watch?: boolean; - cwd?: string; - stdio?: SpawnOptions['stdio']; - env?: NodeJS.ProcessEnv; - webHost?: string; - webPort?: number; - apiHost?: string; - apiPort?: number; - webRuntime?: TreeseedLocalRuntimeMode; - setupMode?: TreeseedIntegratedDevSetupMode; - feedbackMode?: TreeseedIntegratedDevFeedbackMode; - openMode?: TreeseedIntegratedDevOpenMode; - plan?: boolean; - reset?: boolean; - force?: boolean; - forceConflicts?: boolean; - json?: boolean; - includeServices?: boolean; - projectId?: string; - teamId?: string; - readinessTimeoutMs?: number; - processReadyGraceMs?: number; - shutdownGraceMs?: number; -}; - -export type TreeseedManagedDevAction = 'start' | 'status' | 'logs' | 'stop' | 'restart'; - -export type TreeseedManagedDevOptions = TreeseedIntegratedDevOptions & { - action: TreeseedManagedDevAction; - all?: boolean; - follow?: boolean; -}; - -export type TreeseedIntegratedDevCommand = { - id: TreeseedIntegratedDevCommandId; - label: string; - command: string; - args: string[]; - cwd: string; - env: NodeJS.ProcessEnv; - localRuntime?: TreeseedLocalRuntimeSelection; -}; - -export type TreeseedIntegratedDevWatchEntry = TreeseedDevWatchEntry; - -export type TreeseedIntegratedDevSetupStep = { - id: string; - label: string; - required: boolean; - command?: string; - args?: string[]; - status: 'planned' | 'completed' | 'skipped' | 'degraded' | 'failed'; - detail?: string; -}; - -export type TreeseedIntegratedDevReadinessCheck = { - id: TreeseedIntegratedDevCommand['id']; - label: string; - required: boolean; - strategy: 'http' | 'process'; - url?: string; -}; - -export type TreeseedIntegratedDevResetAction = { - id: - | 'd1-state' - | 'generated-d1-state' - | 'generated-wrangler-state' - | 'legacy-local-sqlite' - | 'market-postgres' - | 'root-wrangler-state' - | 'wrangler-tmp' - | 'worker-bundle' - | 'dev-reload'; - label: string; - kind: 'path' | 'service'; - path?: string; - status: 'planned' | 'removed' | 'refreshed' | 'skipped' | 'failed'; - detail?: string; -}; - -export type TreeseedIntegratedDevResetPlan = { - enabled: boolean; - actions: TreeseedIntegratedDevResetAction[]; - preserved: string[]; -}; - -export type TreeseedIntegratedDevPlan = { - surface: TreeseedIntegratedDevSurface; - setupMode: TreeseedIntegratedDevSetupMode; - feedbackMode: TreeseedIntegratedDevFeedbackMode; - openMode: TreeseedIntegratedDevOpenMode; - watch: boolean; - tenantRoot: string; - apiBaseUrl: string; - webUrl: string | null; - setupSteps: TreeseedIntegratedDevSetupStep[]; - readyChecks: TreeseedIntegratedDevReadinessCheck[]; - watchEntries: TreeseedIntegratedDevWatchEntry[]; - commands: TreeseedIntegratedDevCommand[]; - logPath: string; - localRuntimes: Record; - restartPolicy: { - initialBackoffMs: number; - maxBackoffMs: number; - setupRetryBackoffMs: number; - commandImplementationChangesRequireRestart: boolean; - agentChanges: 'defer'; - }; - reset: TreeseedIntegratedDevResetPlan | null; -}; - -export type TreeseedDevInstanceStatus = 'starting' | 'ready' | 'degraded' | 'stopped' | 'stale'; - -export type TreeseedDevInstanceRecord = { - schemaVersion: 1; - kind: 'treeseed.dev.instance'; - projectRoot: string; - worktreeRoot: string; - branch: string | null; - gitCommonDir: string | null; - status: TreeseedDevInstanceStatus; - pid: number | null; - processGroupId: number | null; - startedAt: string; - updatedAt: string; - ports: Record; - urls: Record; - logPath: string; - runtimeScope: string; - surfaces: TreeseedIntegratedDevCommandId[]; - readyChecks: TreeseedIntegratedDevReadinessCheck[]; - instancePath: string; - pidPath: string; - staleReason?: string; -}; - -type TreeseedDevIndexEntry = { - worktreeRoot: string; - instancePath: string; - branch: string | null; - pid: number | null; - runtimeScope: string; - updatedAt: string; -}; - -type TreeseedDevIndex = { - schemaVersion: 1; - kind: 'treeseed.dev.index'; - repositoryId: string; - gitCommonDir: string | null; - instances: TreeseedDevIndexEntry[]; -}; - -type SpawnLike = (command: string, args: string[], options: SpawnOptions) => ChildProcess; -type SpawnSyncLike = typeof spawnSync; -type SignalRegistrar = (signal: NodeJS.Signals, handler: () => void) => () => void; -type ProcessLike = Pick & Partial>; -type FetchLike = (url: string, init?: RequestInit) => Promise; -type ProcessKiller = (pid: number, signal: NodeJS.Signals) => void; -type ProcessStatusChecker = (pid: number) => boolean; -type WatchController = TreeseedDevWatchController; -type WatchStarter = TreeseedDevWatchStarter; - -type TreeseedIntegratedDevDependencies = { - spawn: SpawnLike; - spawnSync: SpawnSyncLike; - onSignal: SignalRegistrar; - prepareEnvironment: (tenantRoot: string) => void; - fetch: FetchLike; - killProcess: ProcessKiller; - processIsAlive: ProcessStatusChecker; - write: (line: string, stream: 'stdout' | 'stderr') => void; - openBrowser: (url: string) => void | Promise; - startWatch: WatchStarter; - removePath: (path: string) => void; - stopMailpitContainers: () => boolean; - resetMarketPostgres: () => boolean; - stopMarketPostgres: () => boolean; - inspectPortOwners: (ports: readonly number[]) => TreeseedDevPortOwner[]; -}; - -type ManagedStartDependencies = Pick; - -export type TreeseedDevPortOwner = { - port: number; - pid: number | null; - processName?: string; - detail: string; -}; - -type DevEvent = { - type: - | 'plan' - | 'setup' - | 'spawn' - | 'log' - | 'ready' - | 'restart' - | 'reset' - | 'reload' - | 'open' - | 'error' - | 'replace' - | 'shutdown'; - surface?: string; - message?: string; - status?: string; - url?: string; - command?: string; - args?: string[]; - exitCode?: number; - signal?: string | null; - detail?: unknown; -}; - -function resolvePackageRoot(packageName: string, tenantRoot: string) { - const resolvedPath = require.resolve(packageName, { - paths: [tenantRoot, packageRoot, process.cwd()], - }); - let currentDir = dirname(resolvedPath); - while (!existsSync(resolve(currentDir, 'package.json'))) { - const parentDir = dirname(currentDir); - if (parentDir === currentDir) { - throw new Error(`Unable to resolve package root for "${packageName}".`); - } - currentDir = parentDir; - } - return currentDir; -} - -function resolveOptionalPackageRoot(packageName: string, tenantRoot: string) { - try { - return resolvePackageRoot(packageName, tenantRoot); - } catch { - return null; - } -} - -function resolvePackageRootEnvOverride(env: NodeJS.ProcessEnv, envName: string, tenantRoot: string) { - const value = env[envName]?.trim(); - if (!value) return null; - const root = isAbsolute(value) ? value : resolve(tenantRoot, value); - if (!existsSync(resolve(root, 'package.json'))) { - throw new Error(`${envName} must point to a package root containing package.json.`); - } - return root; -} - -function resolveNodeEntrypoint(packageDir: string, sourceRelativePath: string, distRelativePath: string) { - const sourcePath = resolve(packageDir, sourceRelativePath); - - if (existsSync(sourcePath)) { - return { - command: 'tsx', - args: [sourcePath], - }; - } - - return { - command: process.execPath, - args: [resolve(packageDir, distRelativePath)], - }; -} - -function resolveOptionalScriptEntrypoint(packageDir: string, sourceRelativePath: string, distRelativePath: string) { - const sourcePath = resolve(packageDir, sourceRelativePath); - if (existsSync(sourcePath)) { - return { - command: 'tsx', - args: [sourcePath], - }; - } - const distPath = resolve(packageDir, distRelativePath); - if (existsSync(distPath)) { - return { - command: process.execPath, - args: [distPath], - }; - } - return null; -} - -function normalizePort(value: number | undefined, fallback: number) { - return Number.isInteger(value) && Number(value) > 0 ? Number(value) : fallback; -} - -function normalizeSetupMode(value: TreeseedIntegratedDevSetupMode | undefined) { - return value ?? 'auto'; -} - -function normalizeFeedbackMode(value: TreeseedIntegratedDevFeedbackMode | undefined) { - return value ?? 'live'; -} - -function normalizeOpenMode(value: TreeseedIntegratedDevOpenMode | undefined) { - return value ?? 'off'; -} - -function normalizeLocalRuntimeMode(value: unknown): TreeseedLocalRuntimeMode { - return value === 'provider' || value === 'local' ? value : 'auto'; -} - -function normalizeProvider(value: unknown, fallback = 'local') { - return typeof value === 'string' && value.trim().length > 0 ? value.trim() : fallback; -} - -function unsupportedProviderRuntimeMessage(kind: string, name: string, provider: string) { - return [ - `Local provider runtime is not supported for ${kind} "${name}" with provider "${provider}".`, - `Set ${kind === 'surface' ? 'surfaces' : 'services'}.${name}.local.runtime to "auto" or "local" in treeseed.site.yaml,`, - 'or add a provider-local adapter before requiring provider runtime.', - ].join(' '); -} - -function fallbackWebProviderFromDeployConfig(deployConfig: unknown) { - const record = deployConfig && typeof deployConfig === 'object' - ? deployConfig as { providers?: { deploy?: unknown } } - : {}; - return normalizeProvider(record.providers?.deploy, 'local'); -} - -function selectWebLocalRuntime( - surfaceConfig: unknown, - providerFallback = 'local', - overrideRuntime?: TreeseedLocalRuntimeMode, -): TreeseedLocalRuntimeSelection { - const record = surfaceConfig && typeof surfaceConfig === 'object' ? surfaceConfig as { - provider?: unknown; - local?: { runtime?: unknown }; - } : {}; - const provider = normalizeProvider(record.provider, providerFallback); - const requested = overrideRuntime ?? normalizeLocalRuntimeMode(record.local?.runtime); - if (provider === 'cloudflare' && requested !== 'local') { - return { - requested, - provider, - selected: 'cloudflare-wrangler-local', - reason: 'Cloudflare web surfaces support local Wrangler runtime.', - }; - } - if (requested === 'provider') { - throw new Error(unsupportedProviderRuntimeMessage('surface', 'web', provider)); - } - return { - requested, - provider, - selected: 'astro-local', - reason: overrideRuntime === 'local' - ? 'CLI override selected the full local Astro runtime for faster UI development.' - : requested === 'local' - ? 'Configured to use the full local Astro runtime.' - : `Provider "${provider}" has no provider-local web runtime; using Astro local.`, - }; -} - -function loadDevDeployConfig(tenantRoot: string) { - try { - return loadTreeseedDeployConfig(resolve(tenantRoot, 'treeseed.site.yaml')); - } catch { - return null; - } -} - -function generatedLocalWranglerPath(tenantRoot: string) { - return resolve(tenantRoot, '.treeseed', 'generated', 'environments', 'local', 'wrangler.toml'); -} - -function browserHost(host: string) { - return host === '0.0.0.0' || host === '::' || host === '[::]' ? '127.0.0.1' : host; -} - -function webUrlFor(host: string, port: number) { - return `http://${browserHost(host)}:${port}`; -} - -const CANONICAL_COMMAND_IDS: TreeseedIntegratedDevCommandId[] = ['web', 'api', 'manager', 'worker', 'agents']; -const ALL_COMMAND_IDS: TreeseedIntegratedDevCommandId[] = ['web', 'api', 'manager', 'worker', 'agents', 'operations-runner']; -const MARKET_DEV_COMMAND_IDS: TreeseedIntegratedDevCommandId[] = ['web', 'api', 'operations-runner']; - -function isMarketWorkspace(tenantRoot: string) { - try { - const pkg = JSON.parse(readFileSync(resolve(tenantRoot, 'package.json'), 'utf8')) as { name?: unknown }; - const apiPackageRoot = resolve(tenantRoot, 'packages/api'); - return pkg.name === '@treeseed/market' - && existsSync(resolve(apiPackageRoot, 'package.json')) - && existsSync(resolve(apiPackageRoot, 'src/api/server.ts')) - && existsSync(resolve(apiPackageRoot, 'src/operations-runner/entrypoint.ts')); - } catch { - return false; - } -} - -function surfaceCommandIds(surface: TreeseedIntegratedDevSurface): TreeseedIntegratedDevCommandId[] { - switch (surface) { - case 'web': - return ['web']; - case 'api': - return ['api']; - case 'manager': - return ['manager']; - case 'worker': - return ['worker']; - case 'agents': - return ['agents']; - case 'services': - return ['api', 'manager', 'worker', 'agents']; - case 'all': - return ['web', 'api', 'manager', 'worker']; - case 'integrated': - default: - return ['web', 'api', 'manager', 'worker']; - } -} - -function parseSurfaceValue(value: string): TreeseedIntegratedDevSurface | null { - return ( - value === 'web' || - value === 'api' || - value === 'manager' || - value === 'worker' || - value === 'agents' || - value === 'services' || - value === 'all' || - value === 'integrated' - ) ? value : null; -} - -function selectedSurfaceCommandIds(options: Pick) { - const values = (options.surfaces?.trim() || options.surface || 'integrated') - .split(',') - .map((entry) => entry.trim()) - .filter(Boolean); - const selected = new Set(); - for (const value of values.length > 0 ? values : ['integrated']) { - const surface = parseSurfaceValue(value); - if (!surface) continue; - for (const id of surfaceCommandIds(surface)) { - selected.add(id); - } - } - const selectedIds = CANONICAL_COMMAND_IDS.filter((id) => selected.has(id)); - return selectedIds.length > 0 ? selectedIds : surfaceCommandIds('integrated'); -} - -function nodeLocalRuntime(label: string): TreeseedLocalRuntimeSelection { - return { - requested: 'local', - provider: 'local', - selected: 'node-local', - reason: `${label} runs as a local Node.js process.`, - }; -} - -function dockerComposeIsAvailable(env: NodeJS.ProcessEnv) { - const docker = resolveTreeseedToolBinary('docker', { env }); - if (!docker) return false; - const result = spawnSync(docker, ['compose', 'version'], { - encoding: 'utf8', - env, - }); - return (result.status ?? 1) === 0; -} - -function dockerIsAvailable(env: NodeJS.ProcessEnv) { - const docker = resolveTreeseedToolBinary('docker', { env }); - if (!docker) return false; - const result = spawnSync(docker, ['info'], { - encoding: 'utf8', - env, - }); - return (result.status ?? 1) === 0; -} - -function resetActionForPath( - id: TreeseedIntegratedDevResetAction['id'], - label: string, - path: string, -): TreeseedIntegratedDevResetAction { - return { - id, - label, - kind: 'path', - path, - status: existsSync(path) ? 'planned' : 'skipped', - detail: existsSync(path) ? undefined : 'Path does not exist.', - }; -} - -function uniqueResetActions(actions: TreeseedIntegratedDevResetAction[]) { - const seen = new Set(); - return actions.filter((action) => { - const key = action.kind === 'path' ? `${action.kind}:${action.path}` : `${action.kind}:${action.id}`; - if (seen.has(key)) return false; - seen.add(key); - return true; - }); -} - -function optionalResetActionForPath( - id: TreeseedIntegratedDevResetAction['id'], - label: string, - path: string, -) { - return existsSync(path) ? resetActionForPath(id, label, path) : null; -} - -function pathContains(parent: string, child: string) { - const diff = relative(parent, child); - return diff === '' || (diff.length > 0 && !diff.startsWith('..') && !isAbsolute(diff)); -} - -function knownLocalRuntimeStateResetActions(tenantRoot: string, activePersistTo: string) { - const localGeneratedWranglerState = resolve(tenantRoot, '.treeseed', 'generated', 'environments', 'local', '.wrangler', 'state', 'v3'); - const rootWranglerState = resolve(tenantRoot, '.wrangler', 'state', 'v3'); - const stateRoots = [ - optionalResetActionForPath( - 'generated-wrangler-state', - 'Remove generated local Wrangler runtime state', - localGeneratedWranglerState, - ), - optionalResetActionForPath( - 'root-wrangler-state', - 'Remove root Wrangler runtime state', - rootWranglerState, - ), - ].filter((action): action is TreeseedIntegratedDevResetAction => Boolean(action)); - const coveredByStateRoot = stateRoots.some((action) => action.path && pathContains(action.path, activePersistTo)); - return uniqueResetActions([ - ...(coveredByStateRoot ? [] : [resetActionForPath('d1-state', 'Remove active local D1 state', activePersistTo)]), - ...stateRoots, - optionalResetActionForPath( - 'legacy-local-sqlite', - 'Remove legacy local SQLite state', - resolve(tenantRoot, '.treeseed', 'generated', 'environments', 'local', 'site-data.sqlite'), - ), - ].filter((action): action is TreeseedIntegratedDevResetAction => Boolean(action))); -} - -function isTreeseedManagedMarketPostgresUrl(value: string | undefined) { - if (!value?.trim()) return true; - try { - const url = new URL(value); - const port = url.port || (url.protocol === 'postgres:' || url.protocol === 'postgresql:' ? '5432' : ''); - return ['postgres:', 'postgresql:'].includes(url.protocol) - && ['127.0.0.1', 'localhost'].includes(url.hostname) - && port === String(TREESEED_DEFAULT_MARKET_POSTGRES_PORT) - && url.pathname === '/market_local' - && decodeURIComponent(url.username) === 'treeseed'; - } catch { - return false; - } -} - -function resolveLocalD1SqlitePath(persistTo: string) { - if (/\.sqlite$/u.test(persistTo) && existsSync(persistTo)) { - return persistTo; - } - const miniflareRoot = resolve(persistTo, 'miniflare-D1DatabaseObject'); - if (existsSync(miniflareRoot)) { - const candidates = readdirSync(miniflareRoot) - .filter((entry) => /\.sqlite$/u.test(entry) && entry !== 'metadata.sqlite') - .map((entry) => { - const path = resolve(miniflareRoot, entry); - return { - path, - size: statSync(path).size, - }; - }) - .sort((left, right) => right.size - left.size || left.path.localeCompare(right.path)); - if (candidates[0]?.path) { - return candidates[0].path; - } - } - const siteDataPath = resolve(persistTo, 'site-data.sqlite'); - return existsSync(siteDataPath) ? siteDataPath : null; -} - -function resolveSeededLocalProjectId(persistTo: string, projectSlug = 'market') { - const sqlitePath = resolveLocalD1SqlitePath(persistTo); - if (!sqlitePath) return null; - let db: DatabaseSync | null = null; - try { - db = new DatabaseSync(sqlitePath, { readOnly: true }); - const row = db.prepare( - `SELECT id FROM projects WHERE LOWER(slug) = LOWER(?) ORDER BY created_at ASC LIMIT 1`, - ).get(projectSlug) as { id?: unknown } | undefined; - return typeof row?.id === 'string' && row.id.trim() ? row.id.trim() : null; - } catch { - return null; - } finally { - db?.close(); - } -} - -function resolveSeededLocalTeamId(persistTo: string, projectId: string | null, teamSlug = 'treeseed') { - const sqlitePath = resolveLocalD1SqlitePath(persistTo); - if (!sqlitePath) return null; - let db: DatabaseSync | null = null; - try { - db = new DatabaseSync(sqlitePath, { readOnly: true }); - if (projectId) { - const projectRow = db.prepare( - `SELECT team_id FROM projects WHERE id = ? LIMIT 1`, - ).get(projectId) as { team_id?: unknown } | undefined; - if (typeof projectRow?.team_id === 'string' && projectRow.team_id.trim()) { - return projectRow.team_id.trim(); - } - } - const teamRow = db.prepare( - `SELECT id FROM teams WHERE LOWER(slug) = LOWER(?) ORDER BY created_at ASC LIMIT 1`, - ).get(teamSlug) as { id?: unknown } | undefined; - return typeof teamRow?.id === 'string' && teamRow.id.trim() ? teamRow.id.trim() : null; - } catch { - return null; - } finally { - db?.close(); - } -} - -export function createTreeseedIntegratedDevResetPlan(options: { - tenantRoot: string; - env: NodeJS.ProcessEnv; - enabled?: boolean; -}): TreeseedIntegratedDevResetPlan | null { - if (!options.enabled) { - return null; - } - const tenantRoot = options.tenantRoot; - const d1PersistTo = options.env.TREESEED_API_D1_LOCAL_PERSIST_TO?.trim() || resolve(tenantRoot, '.wrangler', 'state', 'v3', 'd1'); - const marketWorkspace = isMarketWorkspace(tenantRoot); - const managedMarketPostgres = options.env.TREESEED_MARKET_LOCAL_POSTGRES_MANAGED === 'true'; - return { - enabled: true, - actions: [ - ...knownLocalRuntimeStateResetActions(tenantRoot, d1PersistTo), - ...(marketWorkspace ? [{ - id: 'market-postgres', - label: managedMarketPostgres ? 'Reset local Market PostgreSQL' : 'Skip external Market PostgreSQL', - kind: 'service', - status: managedMarketPostgres ? 'planned' : 'skipped', - detail: managedMarketPostgres - ? 'The Treeseed-managed Market PostgreSQL container, database, and volume will be removed and recreated on the next dev run.' - : 'TREESEED_DATABASE_URL points at an external database, so dev reset will not drop it.', - } satisfies TreeseedIntegratedDevResetAction] : []), - resetActionForPath('wrangler-tmp', 'Remove Wrangler temporary output', resolve(tenantRoot, '.wrangler', 'tmp')), - resetActionForPath('worker-bundle', 'Remove generated local worker bundle', resolve(tenantRoot, '.treeseed', 'generated', 'worker')), - { - id: 'dev-reload', - label: 'Refresh browser reload marker', - kind: 'path', - path: resolve(tenantRoot, DEV_RELOAD_FILE), - status: 'planned', - detail: 'The browser reload marker will be recreated so open tabs do not poll a missing file after reset.', - }, - ], - preserved: [ - '.env*', - 'treeseed.site.yaml', - 'src/env.yaml', - '.treeseed/config', - '.treeseed/generated/environments', - '.treeseed/state', - '.treeseed/workflow', - '.treeseed/workspace-links.json', - 'migrations', - 'node_modules', - 'Treeseed-managed local service containers', - ], - }; -} - -function createWatchEntries(tenantRoot: string, roots: { - sdkPackageRoot: string; - agentPackageRoot?: string | null; - cliPackageRoot?: string | null; -}): TreeseedIntegratedDevWatchEntry[] { - const entries: TreeseedIntegratedDevWatchEntry[] = [ - { kind: 'tenant', root: resolve(tenantRoot, 'src') }, - { kind: 'tenant', root: resolve(tenantRoot, 'content') }, - { kind: 'tenant', root: resolve(tenantRoot, 'public') }, - { kind: 'tenant', root: resolve(tenantRoot, 'astro.config.ts') }, - { kind: 'tenant', root: resolve(tenantRoot, 'astro.config.ts') }, - { kind: 'tenant', root: resolve(tenantRoot, 'treeseed.site.yaml') }, - { kind: 'tenant', root: resolve(tenantRoot, 'treeseed.config.ts') }, - { kind: 'tenant', root: resolve(tenantRoot, 'package.json') }, - { kind: 'tenant', root: resolve(tenantRoot, 'tsconfig.json') }, - ]; - - if (!packageRoot.split(sep).includes('node_modules')) { - entries.push( - { kind: 'core', root: resolve(packageRoot, 'src') }, - { kind: 'core', root: resolve(packageRoot, 'scripts', 'build-tenant-worker.ts') }, - { kind: 'core', root: resolve(packageRoot, 'package.json') }, - { kind: 'core', root: resolve(packageRoot, 'src', 'dev.ts'), restartRequired: true }, - { kind: 'core', root: resolve(packageRoot, 'scripts', 'dev-platform.ts'), restartRequired: true }, - ); - } - if (!roots.sdkPackageRoot.split(sep).includes('node_modules')) { - entries.push( - { kind: 'sdk', root: resolve(roots.sdkPackageRoot, 'src') }, - { kind: 'sdk', root: resolve(roots.sdkPackageRoot, 'scripts', 'tenant-astro-command.ts') }, - { kind: 'sdk', root: resolve(roots.sdkPackageRoot, 'scripts', 'tenant-d1-migrate-local.ts') }, - { kind: 'sdk', root: resolve(roots.sdkPackageRoot, 'package.json') }, - ); - } - if (roots.agentPackageRoot && !roots.agentPackageRoot.split(sep).includes('node_modules')) { - entries.push( - { kind: 'agent', root: resolve(roots.agentPackageRoot, 'src') }, - { kind: 'agent', root: resolve(roots.agentPackageRoot, 'package.json') }, - ); - } - if (roots.cliPackageRoot && !roots.cliPackageRoot.split(sep).includes('node_modules')) { - entries.push( - { kind: 'cli', root: resolve(roots.cliPackageRoot, 'src', 'cli', 'handlers', 'dev.ts'), restartRequired: true }, - { kind: 'cli', root: resolve(roots.cliPackageRoot, 'dist', 'cli', 'handlers', 'dev.js'), restartRequired: true }, - ); - } - try { - for (const app of discoverTreeseedApplications(tenantRoot)) { - if (app.relativeRoot === '.') continue; - entries.push({ kind: 'tenant', root: app.configPath, restartRequired: true }); - } - } catch { - // The root manifest watcher above still catches config edits while a manifest is mid-edit. - } - - return entries; -} - -function createSetupSteps( - tenantRoot: string, - setupMode: TreeseedIntegratedDevSetupMode, - sdkPackageRoot: string, - planLike: Pick, - env: NodeJS.ProcessEnv, - usesCloudflareWebRuntime: boolean, -): TreeseedIntegratedDevSetupStep[] { - if (setupMode === 'off') { - return [ - { - id: 'setup-disabled', - label: 'Local setup disabled', - required: false, - status: 'skipped', - detail: 'Run without --setup off to prepare workspace links, local D1 state, and generated dev artifacts.', - }, - ]; - } - - const hasWebCommand = planLike.commands.some((command) => command.id === 'web'); - const hasLocalRuntimeCommand = planLike.commands.some((command) => - command.id !== 'web' - && command.id !== 'operations-runner' - && command.label !== 'Treeseed API' - ); - const hasApiCommand = planLike.commands.some((command) => command.label === 'Treeseed API'); - const managedMarketPostgres = env.TREESEED_MARKET_LOCAL_POSTGRES_MANAGED === 'true'; - const needsCloudflareLocalRuntime = usesCloudflareWebRuntime || hasLocalRuntimeCommand; - const coreScripts = [ - ['starlight-patch', 'Patch Starlight content path', 'scripts/patch-starlight-content-path.ts', 'dist/scripts/patch-starlight-content-path.js'], - ['books', 'Generate book/public artifacts', 'scripts/aggregate-book.ts', 'dist/scripts/aggregate-book.js'], - ['worker-bundle', 'Generate local worker bundle', 'scripts/build-tenant-worker.ts', 'dist/scripts/build-tenant-worker.js'], - ] as const; - const tenantBuild = usesCloudflareWebRuntime - ? { - command: process.execPath, - args: [packageScriptPath('tenant-build')], - } - : null; - const dockerReady = dockerIsAvailable(env); - const apiPackageRoot = resolve(tenantRoot, 'packages/api'); - const marketMigrateScript = existsSync(resolve(apiPackageRoot, 'scripts/migrate-db.ts')) - ? { - command: 'tsx', - args: [resolve(apiPackageRoot, 'scripts/migrate-db.ts')], - } - : null; - const steps: TreeseedIntegratedDevSetupStep[] = [ - { - id: 'workspace-links', - label: 'Ensure local workspace links', - required: setupMode === 'auto', - status: 'planned', - }, - ...(hasApiCommand && managedMarketPostgres ? [ - { - id: 'market-postgres', - label: 'Start local Market PostgreSQL', - required: true, - status: dockerReady ? 'planned' : 'failed', - detail: dockerReady - ? 'Treeseed will manage the local Market PostgreSQL container automatically.' - : 'Docker daemon is unavailable; local API requires managed PostgreSQL.', - } satisfies TreeseedIntegratedDevSetupStep, - ] : []), - ...(hasApiCommand ? [ - { - id: 'market-migrations', - label: 'Apply local Market database migrations', - required: true, - command: marketMigrateScript?.command, - args: marketMigrateScript?.args, - status: marketMigrateScript ? 'planned' : 'failed', - detail: marketMigrateScript ? undefined : 'Unable to resolve packages/api/scripts/migrate-db.ts.', - } satisfies TreeseedIntegratedDevSetupStep, - ] : []), - { - id: 'wrangler', - label: 'Verify Wrangler executable', - required: needsCloudflareLocalRuntime, - status: 'planned', - detail: resolveTreeseedToolBinary('wrangler', { env }) ?? undefined, - }, - ...(needsCloudflareLocalRuntime ? [ - { - id: 'wrangler-config', - label: 'Generate local Wrangler config', - required: true, - status: 'planned', - detail: generatedLocalWranglerPath(tenantRoot), - } satisfies TreeseedIntegratedDevSetupStep, - ] : []), - ...(usesCloudflareWebRuntime && hasWebCommand ? [ - { - id: 'web-build', - label: 'Build local Cloudflare web runtime', - required: true, - command: tenantBuild?.command, - args: tenantBuild?.args, - status: tenantBuild ? 'planned' : 'failed', - detail: tenantBuild ? undefined : 'Unable to resolve the tenant build script.', - } satisfies TreeseedIntegratedDevSetupStep, - ] : (hasWebCommand ? coreScripts.map(([id, label, source, dist]) => { - const script = resolveOptionalScriptEntrypoint(packageRoot, source, dist); - return { - id, - label, - required: true, - command: script?.command, - args: script?.args, - status: script ? 'planned' : 'skipped', - detail: script ? undefined : `Script not found at ${source}.`, - } satisfies TreeseedIntegratedDevSetupStep; - }) : [])), - ]; - - if (needsCloudflareLocalRuntime) { - const migrate = resolveOptionalScriptEntrypoint( - sdkPackageRoot, - 'scripts/tenant-d1-migrate-local.ts', - 'dist/scripts/tenant-d1-migrate-local.js', - ); - steps.push({ - id: 'd1-migrations', - label: 'Run local D1 migrations', - required: true, - command: migrate?.command, - args: migrate?.args, - status: migrate ? 'planned' : 'failed', - detail: migrate ? undefined : 'Unable to resolve the local D1 migration script.', - }); - } - - return steps; -} - -function createAgentCommand( - id: Extract, - tenantRoot: string, - agentPackageRoot: string, - sharedEnv: NodeJS.ProcessEnv, - apiHost: string, - apiPort: number, -): TreeseedIntegratedDevCommand { - const configs: Record, { - label: string; - source: string; - dist: string; - extraArgs?: string[]; - extraEnv: NodeJS.ProcessEnv; - }> = { - api: { - label: 'Treeseed API', - source: 'src/api/server.ts', - dist: 'dist/api/server.js', - extraEnv: { - HOST: apiHost, - PORT: String(apiPort), - TREESEED_API_REPO_ROOT: tenantRoot, - }, - }, - manager: { - label: 'Manager', - source: 'src/services/manager.ts', - dist: 'dist/services/manager.js', - extraArgs: ['--mode', 'loop'], - extraEnv: { - TREESEED_MANAGER_MODE: 'loop', - }, - }, - worker: { - label: 'Worker Runner', - source: 'src/services/worker.ts', - dist: 'dist/services/worker.js', - extraEnv: {}, - }, - agents: { - label: 'Agents Loop', - source: 'src/services/agents.ts', - dist: 'dist/services/agents.js', - extraEnv: {}, - }, - }; - const config = configs[id]; - const entrypoint = resolveNodeEntrypoint(agentPackageRoot, config.source, config.dist); - const { - TREESEED_AGENT_D1_DATABASE: _agentD1Database, - TREESEED_AGENT_D1_PERSIST_TO: _agentD1PersistTo, - ...agentSharedEnv - } = sharedEnv; - return { - id, - label: config.label, - command: entrypoint.command, - args: [...entrypoint.args, ...(config.extraArgs ?? [])], - cwd: tenantRoot, - env: { - ...agentSharedEnv, - TREESEED_AGENT_REPO_ROOT: tenantRoot, - TREESEED_ENVIRONMENT: sharedEnv.TREESEED_ENVIRONMENT ?? 'local', - ...config.extraEnv, - }, - localRuntime: nodeLocalRuntime(config.label), - }; -} - -function createApiCommand( - tenantRoot: string, - sharedEnv: NodeJS.ProcessEnv, - apiHost: string, - apiPort: number, -): TreeseedIntegratedDevCommand { - const apiPackageRoot = resolve(tenantRoot, 'packages/api'); - return { - id: 'api', - label: 'Treeseed API', - command: 'tsx', - args: [resolve(apiPackageRoot, 'src/api/server.ts')], - cwd: apiPackageRoot, - env: { - ...sharedEnv, - HOST: apiHost, - PORT: String(apiPort), - TREESEED_ENVIRONMENT: sharedEnv.TREESEED_ENVIRONMENT ?? 'local', - TREESEED_API_ENVIRONMENT: sharedEnv.TREESEED_API_ENVIRONMENT ?? 'local', - TREESEED_API_REQUEST_LOGS: sharedEnv.TREESEED_API_REQUEST_LOGS ?? 'true', - }, - localRuntime: nodeLocalRuntime('Treeseed API'), - }; -} - -function createMarketOperationsRunnerCommand( - tenantRoot: string, - sharedEnv: NodeJS.ProcessEnv, -): TreeseedIntegratedDevCommand { - const apiPackageRoot = resolve(tenantRoot, 'packages/api'); - return { - id: 'operations-runner', - label: 'Treeseed Operations Runner', - command: 'tsx', - args: [ - resolve(apiPackageRoot, 'src/operations-runner/entrypoint.ts'), - 'run', - '--watch', - '--market', - 'local', - '--operation', - 'project:web_deployment', - '--poll-interval-ms', - '5000', - ], - cwd: apiPackageRoot, - env: { - ...sharedEnv, - TREESEED_PLATFORM_RUNNER_ENVIRONMENT: sharedEnv.TREESEED_PLATFORM_RUNNER_ENVIRONMENT ?? 'local', - }, - localRuntime: nodeLocalRuntime('Treeseed Operations Runner'), - }; -} - -export function createTreeseedIntegratedDevPlan(options: TreeseedIntegratedDevOptions = {}): TreeseedIntegratedDevPlan { - const tenantRoot = resolve(options.cwd ?? process.cwd()); - const surface = options.surface ?? 'integrated'; - const setupMode = normalizeSetupMode(options.setupMode); - const feedbackMode = normalizeFeedbackMode(options.feedbackMode); - const openMode = normalizeOpenMode(options.openMode); - const watch = feedbackMode !== 'off' || options.watch === true; - const webHost = options.webHost ?? TREESEED_DEFAULT_WEB_HOST; - const webPort = normalizePort(options.webPort, TREESEED_DEFAULT_WEB_PORT); - const apiHost = options.apiHost ?? TREESEED_DEFAULT_API_HOST; - const apiPort = normalizePort(options.apiPort, TREESEED_DEFAULT_API_PORT); - const machineEnv = resolveLocalMachineEnv(tenantRoot); - const mergedEnv = { ...process.env, ...machineEnv, ...(options.env ?? {}) }; - const teamId = options.teamId ?? mergedEnv.TREESEED_HOSTING_TEAM_ID; - const apiBaseUrl = options.apiHost != null || options.apiPort != null - ? `http://${apiHost}:${apiPort}` - : mergedEnv.TREESEED_API_BASE_URL?.trim() || `http://${apiHost}:${apiPort}`; - const selectedCommandIds = selectedSurfaceCommandIds(options); - const marketWorkspace = isMarketWorkspace(tenantRoot); - const effectiveCommandIds = marketWorkspace - ? MARKET_DEV_COMMAND_IDS.filter((id) => selectedCommandIds.includes(id) || (id === 'operations-runner' && selectedCommandIds.includes('api'))) - : selectedCommandIds; - const devResetId = options.reset ? String(Date.now()) : undefined; - const webUrl = effectiveCommandIds.includes('web') ? webUrlFor(webHost, webPort) : null; - const sdkPackageRoot = resolvePackageRoot('@treeseed/sdk', tenantRoot); - const agentPackageRoot = resolvePackageRootEnvOverride(mergedEnv, 'TREESEED_AGENT_PACKAGE_ROOT', tenantRoot) - ?? resolveOptionalPackageRoot('@treeseed/agent', tenantRoot); - const cliPackageRoot = resolveOptionalPackageRoot('@treeseed/cli', tenantRoot); - const deployConfig = loadDevDeployConfig(tenantRoot); - const webLocalRuntime = selectWebLocalRuntime(deployConfig?.surfaces?.web, fallbackWebProviderFromDeployConfig(deployConfig), options.webRuntime); - const usesCloudflareWebRuntime = webLocalRuntime.selected === 'cloudflare-wrangler-local'; - const usesGeneratedLocalD1State = usesCloudflareWebRuntime - || webLocalRuntime.provider === 'cloudflare' - || (!marketWorkspace && effectiveCommandIds.some((id) => id !== 'web')); - const localD1PersistTo = mergedEnv.TREESEED_API_D1_LOCAL_PERSIST_TO ?? ( - usesGeneratedLocalD1State - ? resolve(tenantRoot, '.treeseed', 'generated', 'environments', 'local', '.wrangler', 'state', 'v3', 'd1') - : resolve(tenantRoot, '.wrangler', 'state', 'v3', 'd1') - ); - const projectId = options.projectId - ?? mergedEnv.TREESEED_PROJECT_ID - ?? resolveSeededLocalProjectId(localD1PersistTo); - const resolvedHostingTeamId = teamId ?? mergedEnv.TREESEED_HOSTING_TEAM_ID; - const resolvedTeamId = mergedEnv.TREESEED_TEAM_ID - ?? resolvedHostingTeamId - ?? resolveSeededLocalTeamId(localD1PersistTo, projectId ?? null); - const marketPostgresPort = mergedEnv.TREESEED_MARKET_LOCAL_POSTGRES_PORT ?? String(TREESEED_DEFAULT_MARKET_POSTGRES_PORT); - const apiDatabaseUrl = mergedEnv.TREESEED_DATABASE_URL - ?? `postgres://treeseed:treeseed@127.0.0.1:${marketPostgresPort}/market_local`; - const managedMarketPostgres = marketWorkspace && isTreeseedManagedMarketPostgresUrl(apiDatabaseUrl); - const mailpitSmtpPort = mergedEnv.TREESEED_MAILPIT_SMTP_PORT ?? String(TREESEED_DEFAULT_LOCAL_SMTP_PORT); - const mailpitUiPort = mergedEnv.TREESEED_MAILPIT_UI_PORT ?? String(TREESEED_DEFAULT_MAILPIT_UI_PORT); - const webEntrypoint = resolveNodeEntrypoint( - sdkPackageRoot, - 'scripts/tenant-astro-command.ts', - 'dist/scripts/tenant-astro-command.js', - ); - const wranglerEntrypoint = { - command: process.execPath, - args: [ - resolveWranglerBin(), - 'dev', - '--local', - '--config', - generatedLocalWranglerPath(tenantRoot), - '--ip', - webHost, - '--port', - String(webPort), - ], - }; - const watchEntries = watch ? createWatchEntries(tenantRoot, { sdkPackageRoot, agentPackageRoot, cliPackageRoot }) : []; - const resetRequested = options.reset === true; - - const sharedEnv: NodeJS.ProcessEnv = { - ...mergedEnv, - TREESEED_LOCAL_DEV_MODE: usesCloudflareWebRuntime ? (mergedEnv.TREESEED_LOCAL_DEV_MODE ?? 'cloudflare') : undefined, - TREESEED_SITE_URL: mergedEnv.TREESEED_SITE_URL ?? webUrl, - BETTER_AUTH_URL: mergedEnv.BETTER_AUTH_URL ?? webUrl, - TREESEED_API_BASE_URL: apiBaseUrl, - TREESEED_MARKET_API_BASE_URL: mergedEnv.TREESEED_MARKET_API_BASE_URL ?? apiBaseUrl, - TREESEED_API_REQUEST_LOGS: mergedEnv.TREESEED_API_REQUEST_LOGS ?? 'true', - ...(marketWorkspace ? { - TREESEED_DATABASE_URL: apiDatabaseUrl, - TREESEED_MARKET_LOCAL_POSTGRES_CONTAINER: mergedEnv.TREESEED_MARKET_LOCAL_POSTGRES_CONTAINER ?? TREESEED_DEFAULT_MARKET_POSTGRES_CONTAINER, - TREESEED_MARKET_LOCAL_POSTGRES_VOLUME: mergedEnv.TREESEED_MARKET_LOCAL_POSTGRES_VOLUME ?? TREESEED_DEFAULT_MARKET_POSTGRES_VOLUME, - TREESEED_MARKET_LOCAL_POSTGRES_PORT: marketPostgresPort, - TREESEED_MARKET_LOCAL_POSTGRES_MANAGED: managedMarketPostgres ? 'true' : 'false', - } : {}), - TREESEED_PROJECT_ID: projectId ?? mergedEnv.TREESEED_PROJECT_ID, - TREESEED_TEAM_ID: resolvedTeamId ?? mergedEnv.TREESEED_TEAM_ID, - TREESEED_HOSTING_TEAM_ID: resolvedHostingTeamId ?? mergedEnv.TREESEED_HOSTING_TEAM_ID, - TREESEED_API_D1_DATABASE_NAME: mergedEnv.TREESEED_API_D1_DATABASE_NAME ?? 'SITE_DATA_DB', - SITE_DATA_DB: mergedEnv.SITE_DATA_DB ?? 'SITE_DATA_DB', - TREESEED_API_D1_LOCAL_PERSIST_TO: localD1PersistTo, - TREESEED_WEB_SERVICE_ID: mergedEnv.TREESEED_WEB_SERVICE_ID ?? mergedEnv.TREESEED_API_WEB_SERVICE_ID ?? 'web', - TREESEED_WEB_SERVICE_SECRET: - mergedEnv.TREESEED_WEB_SERVICE_SECRET - ?? mergedEnv.TREESEED_API_WEB_SERVICE_SECRET - ?? 'treeseed-web-service-dev-secret', - TREESEED_API_WEB_SERVICE_ID: mergedEnv.TREESEED_API_WEB_SERVICE_ID ?? mergedEnv.TREESEED_WEB_SERVICE_ID ?? 'web', - TREESEED_API_WEB_SERVICE_SECRET: - mergedEnv.TREESEED_API_WEB_SERVICE_SECRET - ?? mergedEnv.TREESEED_WEB_SERVICE_SECRET - ?? 'treeseed-web-service-dev-secret', - TREESEED_PLATFORM_RUNNER_SECRET: mergedEnv.TREESEED_PLATFORM_RUNNER_SECRET ?? 'treeseed-platform-runner-dev-secret', - TREESEED_FORM_TOKEN_SECRET: mergedEnv.TREESEED_FORM_TOKEN_SECRET ?? 'treeseed-local-form-token-secret', - TREESEED_BETTER_AUTH_SECRET: mergedEnv.TREESEED_BETTER_AUTH_SECRET ?? 'treeseed-local-better-auth-secret-minimum-32-characters', - ...(devResetId ? { TREESEED_DEV_RESET_ID: devResetId } : {}), - TREESEED_SMTP_HOST: TREESEED_DEFAULT_LOCAL_SMTP_HOST, - TREESEED_SMTP_PORT: mailpitSmtpPort, - TREESEED_SMTP_USERNAME: '', - TREESEED_SMTP_PASSWORD: '', - TREESEED_MAILPIT_SMTP_HOST: TREESEED_DEFAULT_LOCAL_SMTP_HOST, - TREESEED_MAILPIT_SMTP_PORT: mailpitSmtpPort, - TREESEED_MAILPIT_UI_PORT: mailpitUiPort, - TREESEED_AUTH_EMAIL_FROM: mergedEnv.TREESEED_AUTH_EMAIL_FROM ?? 'Treeseed Market ', - }; - const reset = createTreeseedIntegratedDevResetPlan({ - tenantRoot, - env: sharedEnv, - enabled: resetRequested, - }); - - if (watch && feedbackMode === 'live') { - sharedEnv.TREESEED_PUBLIC_DEV_WATCH_RELOAD = sharedEnv.TREESEED_PUBLIC_DEV_WATCH_RELOAD || 'true'; - } - - const commands: TreeseedIntegratedDevCommand[] = []; - - if (effectiveCommandIds.includes('web')) { - commands.push({ - id: 'web', - label: usesCloudflareWebRuntime ? 'Cloudflare Wrangler UI' : 'Astro UI', - command: usesCloudflareWebRuntime ? wranglerEntrypoint.command : webEntrypoint.command, - args: usesCloudflareWebRuntime - ? wranglerEntrypoint.args - : [...webEntrypoint.args, 'dev', '--host', webHost, '--port', String(webPort)], - cwd: tenantRoot, - env: sharedEnv, - localRuntime: webLocalRuntime, - }); - } - if (!marketWorkspace && effectiveCommandIds.some((id) => id !== 'web') && !agentPackageRoot) { - throw new Error('Unable to resolve @treeseed/agent for local API or agent service surfaces.'); - } - for (const id of effectiveCommandIds) { - if (id === 'web') continue; - if (marketWorkspace && id === 'api') { - commands.push(createApiCommand(tenantRoot, sharedEnv, apiHost, apiPort)); - continue; - } - if (marketWorkspace && id === 'operations-runner') { - commands.push(createMarketOperationsRunnerCommand(tenantRoot, sharedEnv)); - continue; - } - commands.push(createAgentCommand(id as Extract, tenantRoot, agentPackageRoot!, sharedEnv, apiHost, apiPort)); - } - - const readyChecks: TreeseedIntegratedDevReadinessCheck[] = commands.map((command) => { - if (command.id === 'web' || command.id === 'api') { - return { - id: command.id, - label: command.label, - required: true, - strategy: 'http', - url: command.id === 'web' ? webUrl ?? undefined : `${apiBaseUrl.replace(/\/+$/u, '')}/healthz`, - }; - } - return { - id: command.id, - label: command.label, - required: command.id === 'operations-runner', - strategy: 'process', - }; - }); - return { - surface, - setupMode, - feedbackMode, - openMode, - watch, - tenantRoot, - apiBaseUrl, - webUrl, - setupSteps: createSetupSteps(tenantRoot, setupMode, sdkPackageRoot, { commands }, sharedEnv, usesCloudflareWebRuntime), - readyChecks, - watchEntries, - commands, - logPath: resolve(tenantRoot, '.treeseed', 'logs', `dev-${runtimeScopeKey(commands.map((command) => command.id))}.jsonl`), - localRuntimes: { - ...(commands.some((command) => command.id === 'web') ? { web: webLocalRuntime } : {}), - ...(commands.some((command) => command.id === 'api') ? { api: nodeLocalRuntime('Treeseed API') } : {}), - ...(commands.some((command) => command.id === 'manager') ? { manager: nodeLocalRuntime('Manager') } : {}), - ...(commands.some((command) => command.id === 'worker') ? { worker: nodeLocalRuntime('Worker Runner') } : {}), - ...(commands.some((command) => command.id === 'agents') ? { agents: nodeLocalRuntime('Agents Loop') } : {}), - ...(commands.some((command) => command.id === 'operations-runner') ? { marketRunner: nodeLocalRuntime('Treeseed Operations Runner') } : {}), - }, - restartPolicy: { - initialBackoffMs: INITIAL_RESTART_BACKOFF_MS, - maxBackoffMs: MAX_RESTART_BACKOFF_MS, - setupRetryBackoffMs: SETUP_RETRY_BACKOFF_MS, - commandImplementationChangesRequireRestart: true, - agentChanges: 'defer', - }, - reset, - }; -} - -function defaultSignalRegistrar(signal: NodeJS.Signals, handler: () => void) { - process.on(signal, handler); - return () => { - process.off(signal, handler); - }; -} - -function defaultPrepareEnvironment(tenantRoot: string) { - applyTreeseedEnvironmentToProcess({ tenantRoot, scope: 'local', override: true }); - assertTreeseedCommandEnvironment({ tenantRoot, scope: 'local', purpose: 'dev' }); -} - -function defaultKillProcess(pid: number, signal: NodeJS.Signals) { - process.kill(pid, signal); -} - -function defaultProcessIsAlive(pid: number) { - if (!Number.isInteger(pid) || pid <= 0) { - return false; - } - try { - process.kill(pid, 0); - return true; - } catch { - return false; - } -} - -function defaultInspectPortOwners(ports: readonly number[]): TreeseedDevPortOwner[] { - const uniquePorts = [...new Set(ports.filter((port) => Number.isInteger(port) && port > 0))]; - if (uniquePorts.length === 0) return []; - const result = spawnSync('ss', ['-ltnp'], { encoding: 'utf8' }); - if ((result.status ?? 1) !== 0) return []; - const lines = String(result.stdout ?? '').split(/\r?\n/u); - const owners: TreeseedDevPortOwner[] = []; - for (const port of uniquePorts) { - const portPattern = new RegExp(`:${port}\\b`, 'u'); - for (const line of lines) { - if (!portPattern.test(line)) continue; - const pidMatch = line.match(/pid=(\d+)/u); - const nameMatch = line.match(/users:\(\("([^"]+)"/u); - owners.push({ - port, - pid: pidMatch ? Number(pidMatch[1]) : null, - processName: nameMatch?.[1], - detail: line.trim(), - }); - } - } - return owners; -} - -function defaultRemovePath(path: string) { - rmSync(path, { recursive: true, force: true }); -} - -function defaultResetMarketPostgres() { - return resetMarketPostgres(process.env, { spawnSync }); -} - -function defaultStopMarketPostgres() { - return stopMarketPostgres(process.env, { spawnSync }); -} - -type ManagedDevProcess = { - id: TreeseedIntegratedDevCommand['id']; - command: TreeseedIntegratedDevCommand; - child: ProcessLike; - pid: number | null; - exited: boolean; - intentionalStop: boolean; - exitCode: number | null; - exitSignal: NodeJS.Signals | null; - resolveExit: () => void; - exitPromise: Promise; -}; - -function createManagedDevProcess(command: TreeseedIntegratedDevCommand, child: ProcessLike): ManagedDevProcess { - let resolveExit: () => void = () => {}; - const exitPromise = new Promise((resolvePromise) => { - resolveExit = resolvePromise; - }); - return { - id: command.id, - command, - child, - pid: typeof child.pid === 'number' ? child.pid : null, - exited: false, - intentionalStop: false, - exitCode: null, - exitSignal: null, - resolveExit, - exitPromise, - }; -} - -function signalManagedProcess( - managed: ManagedDevProcess, - signal: NodeJS.Signals, - killProcess: ProcessKiller, -) { - if (managed.pid != null && process.platform !== 'win32') { - try { - killProcess(-managed.pid, signal); - return; - } catch { - // Fall through to the child handle for environments that do not expose the group. - } - } - if (typeof managed.child.kill !== 'function') { - return; - } - try { - managed.child.kill(signal); - } catch { - // Ignore shutdown races from already-exited child processes. - } -} - -async function stopManagedProcess( - managed: ManagedDevProcess, - signal: NodeJS.Signals, - killProcess: ProcessKiller, - graceMs: number, -) { - managed.intentionalStop = true; - signalManagedProcess(managed, signal, killProcess); - if (!managed.exited) { - await Promise.race([managed.exitPromise, delay(Math.max(0, graceMs))]); - } - if (signal !== 'SIGKILL') { - signalManagedProcess(managed, 'SIGKILL', killProcess); - if (!managed.exited) { - await Promise.race([managed.exitPromise, delay(DEFAULT_KILL_GRACE_MS)]); - } - } -} - -function writeDevReloadStampPath(outputPath: string) { - mkdirSync(dirname(outputPath), { recursive: true }); - writeFileSync( - outputPath, - `${JSON.stringify( - { - buildId: `${Date.now()}`, - updatedAt: new Date().toISOString(), - }, - null, - 2, - )}\n`, - 'utf8', - ); -} - -function writeDevReloadStamp(projectRoot: string) { - writeDevReloadStampPath(resolve(projectRoot, DEV_RELOAD_FILE)); -} - -function defaultWrite(line: string, stream: 'stdout' | 'stderr') { - const target = stream === 'stderr' ? process.stderr : process.stdout; - target.write(line); -} - -function shouldRedactEnvValue(key: string) { - return /(TOKEN|SECRET|PASSWORD|PASSPHRASE|PRIVATE|CREDENTIAL|AUTH)/iu.test(key); -} - -function redactEnvironment(env: NodeJS.ProcessEnv) { - return Object.fromEntries( - Object.entries(env).map(([key, value]) => [key, value == null || !shouldRedactEnvValue(key) ? value : '[redacted]']), - ); -} - -function serializeDevPlanForOutput(plan: TreeseedIntegratedDevPlan): TreeseedIntegratedDevPlan { - return { - ...plan, - commands: plan.commands.map((command) => ({ - ...command, - env: redactEnvironment(command.env), - })), - }; -} - -function resolveLocalMachineEnv(tenantRoot: string) { - try { - return resolveTreeseedMachineEnvironmentValues(tenantRoot, 'local') as NodeJS.ProcessEnv; - } catch { - return {}; - } -} - -function atomicWriteJson(path: string, value: unknown) { - mkdirSync(dirname(path), { recursive: true }); - const tmpPath = `${path}.tmp-${process.pid}-${Date.now()}`; - writeFileSync(tmpPath, `${JSON.stringify(value, null, 2)}\n`, 'utf8'); - renameSync(tmpPath, path); -} - -function runGitText(cwd: string, args: string[]) { - const result = runTreeseedGit(args, { cwd, mode: 'read', allowFailure: true }); - return (result.status ?? 1) === 0 ? String(result.stdout ?? '').trim() : null; -} - -function isInsideTreeseedInternalPath(worktreeRoot: string, tenantRoot: string) { - const rel = relative(worktreeRoot, tenantRoot); - return rel.split(sep).includes('.treeseed'); -} - -function resolveGitWorktreeInfo(tenantRoot: string) { - const resolvedWorktreeRoot = runGitText(tenantRoot, ['rev-parse', '--show-toplevel']); - const isTreeseedTemporaryRoot = resolvedWorktreeRoot - ? relative(resolve(resolvedWorktreeRoot, '.treeseed', 'tmp'), resolve(tenantRoot)).split(sep)[0] !== '..' - : false; - if (!resolvedWorktreeRoot || isInsideTreeseedInternalPath(resolvedWorktreeRoot, tenantRoot) || isTreeseedTemporaryRoot) { - return { worktreeRoot: tenantRoot, gitCommonDir: null, branch: null }; - } - const worktreeRoot = resolvedWorktreeRoot; - const rawCommonDir = runGitText(tenantRoot, ['rev-parse', '--git-common-dir']); - const gitCommonDir = rawCommonDir - ? (isAbsolute(rawCommonDir) ? rawCommonDir : resolve(tenantRoot, rawCommonDir)) - : null; - const rawBranch = runGitText(tenantRoot, ['rev-parse', '--abbrev-ref', 'HEAD']); - const branch = rawBranch && rawBranch !== 'HEAD' ? rawBranch : null; - return { worktreeRoot, gitCommonDir, branch }; -} - -function repositoryIndexId(tenantRoot: string, gitCommonDir: string | null) { - const source = gitCommonDir ?? tenantRoot; - return createHash('sha256').update(source).digest('hex').slice(0, 16); -} - -function worktreeInstanceSuffix(tenantRoot: string) { - return createHash('sha256').update(resolve(tenantRoot)).digest('hex').slice(0, 10); -} - -function repoFamilyIndexPath(tenantRoot: string, gitCommonDir: string | null) { - if (gitCommonDir) { - return resolve(gitCommonDir, DEV_REPO_INDEX_RELATIVE_PATH); - } - const cacheRoot = process.env.XDG_CACHE_HOME - ? resolve(process.env.XDG_CACHE_HOME, 'treeseed', 'dev-instances') - : resolve(homedir(), '.cache', 'treeseed', 'dev-instances'); - return resolve(cacheRoot, `${repositoryIndexId(tenantRoot, null)}.json`); -} - -function instanceRuntimeScope(plan: Pick) { - return runtimeScopeKey(plan.commands.map((command) => command.id)); -} - -function devInstanceDir(tenantRoot: string) { - return resolve(tenantRoot, DEV_INSTANCE_DIR); -} - -function devPidDir(tenantRoot: string) { - return resolve(tenantRoot, DEV_PID_DIR); -} - -function devInstancePath(tenantRoot: string, runtimeScope: string) { - return resolve(devInstanceDir(tenantRoot), `${runtimeScope}.json`); -} - -function devPidPath(tenantRoot: string, runtimeScope: string) { - return resolve(devPidDir(tenantRoot), `${runtimeScope}.pid`); -} - -function portsFromPlan(plan: TreeseedIntegratedDevPlan) { - return Object.fromEntries( - Object.entries({ - web: parsePortFromUrl(plan.webUrl ?? undefined), - api: parsePortFromUrl(plan.apiBaseUrl), - mailpitSmtp: Number(plan.commands[0]?.env.TREESEED_MAILPIT_SMTP_PORT ?? '') || null, - postgres: Number(plan.commands[0]?.env.TREESEED_MARKET_LOCAL_POSTGRES_PORT ?? '') || null, - }).filter((entry): entry is [string, number] => Number.isInteger(entry[1]) && entry[1] > 0), - ); -} - -function urlsFromPlan(plan: TreeseedIntegratedDevPlan) { - return Object.fromEntries( - Object.entries({ - web: plan.webUrl, - api: plan.apiBaseUrl, - apiHealth: `${plan.apiBaseUrl.replace(/\/+$/u, '')}/healthz`, - }).filter((entry): entry is [string, string] => typeof entry[1] === 'string' && entry[1].length > 0), - ); -} - -function createDevInstanceRecord( - plan: TreeseedIntegratedDevPlan, - status: TreeseedDevInstanceStatus, - pid: number | null, - processGroupId: number | null = null, - startedAt = new Date().toISOString(), -): TreeseedDevInstanceRecord { - const runtimeScope = instanceRuntimeScope(plan); - const git = resolveGitWorktreeInfo(plan.tenantRoot); - return { - schemaVersion: 1, - kind: 'treeseed.dev.instance', - projectRoot: plan.tenantRoot, - worktreeRoot: git.worktreeRoot, - branch: git.branch, - gitCommonDir: git.gitCommonDir, - status, - pid, - processGroupId, - startedAt, - updatedAt: new Date().toISOString(), - ports: portsFromPlan(plan), - urls: urlsFromPlan(plan), - logPath: plan.logPath, - runtimeScope, - surfaces: plan.commands.map((command) => command.id), - readyChecks: plan.readyChecks, - instancePath: devInstancePath(plan.tenantRoot, runtimeScope), - pidPath: devPidPath(plan.tenantRoot, runtimeScope), - }; -} - -function readDevInstanceFile(path: string): TreeseedDevInstanceRecord | null { - try { - const parsed = JSON.parse(readFileSync(path, 'utf8')) as Partial; - if (parsed.kind !== 'treeseed.dev.instance' || typeof parsed.projectRoot !== 'string' || typeof parsed.instancePath !== 'string') { - return null; - } - return parsed as TreeseedDevInstanceRecord; - } catch { - return null; - } -} - -function writeDevInstance(record: TreeseedDevInstanceRecord) { - const next = { ...record, updatedAt: new Date().toISOString() }; - atomicWriteJson(next.instancePath, next); - mkdirSync(dirname(next.pidPath), { recursive: true }); - if (next.pid) { - writeFileSync(next.pidPath, `${next.pid}\n`, 'utf8'); - } - writeRepoFamilyIndexEntry(next); - return next; -} - -function readRepoFamilyIndex(tenantRoot: string, gitCommonDir: string | null): TreeseedDevIndex { - const path = repoFamilyIndexPath(tenantRoot, gitCommonDir); - try { - const parsed = JSON.parse(readFileSync(path, 'utf8')) as Partial; - if (parsed.kind === 'treeseed.dev.index' && Array.isArray(parsed.instances)) { - return parsed as TreeseedDevIndex; - } - } catch { - // Create a fresh index below. - } - return { - schemaVersion: 1, - kind: 'treeseed.dev.index', - repositoryId: repositoryIndexId(tenantRoot, gitCommonDir), - gitCommonDir, - instances: [], - }; -} - -function writeRepoFamilyIndex(index: TreeseedDevIndex, tenantRoot: string, gitCommonDir: string | null) { - atomicWriteJson(repoFamilyIndexPath(tenantRoot, gitCommonDir), index); -} - -function writeRepoFamilyIndexEntry(record: TreeseedDevInstanceRecord) { - const index = readRepoFamilyIndex(record.projectRoot, record.gitCommonDir); - const entry: TreeseedDevIndexEntry = { - worktreeRoot: record.worktreeRoot, - instancePath: record.instancePath, - branch: record.branch, - pid: record.pid, - runtimeScope: record.runtimeScope, - updatedAt: record.updatedAt, - }; - const instances = index.instances.filter((candidate) => - !(candidate.worktreeRoot === entry.worktreeRoot && candidate.runtimeScope === entry.runtimeScope), - ); - instances.push(entry); - writeRepoFamilyIndex({ ...index, instances }, record.projectRoot, record.gitCommonDir); -} - -function removeRepoFamilyIndexEntry(record: TreeseedDevInstanceRecord) { - const index = readRepoFamilyIndex(record.projectRoot, record.gitCommonDir); - writeRepoFamilyIndex({ - ...index, - instances: index.instances.filter((candidate) => - !(candidate.worktreeRoot === record.worktreeRoot && candidate.runtimeScope === record.runtimeScope), - ), - }, record.projectRoot, record.gitCommonDir); -} - -function listWorktreeDevInstances(tenantRoot: string) { - try { - return readdirSync(devInstanceDir(tenantRoot)) - .filter((entry) => entry.endsWith('.json')) - .map((entry) => readDevInstanceFile(resolve(devInstanceDir(tenantRoot), entry))) - .filter((entry): entry is TreeseedDevInstanceRecord => Boolean(entry)); - } catch { - return []; - } -} - -function listRepoFamilyDevInstances(tenantRoot: string) { - const git = resolveGitWorktreeInfo(tenantRoot); - const index = readRepoFamilyIndex(tenantRoot, git.gitCommonDir); - return index.instances - .map((entry) => readDevInstanceFile(entry.instancePath)) - .filter((entry): entry is TreeseedDevInstanceRecord => Boolean(entry)); -} - -function evaluateDevInstance( - record: TreeseedDevInstanceRecord, - deps: Pick, -): TreeseedDevInstanceRecord { - if (!record.pid || !deps.processIsAlive(record.pid)) { - return { ...record, status: 'stale', staleReason: record.pid ? `Process ${record.pid} is not running.` : 'No supervisor pid is recorded.' }; - } - return record; -} - -function removeDevInstanceRecord(record: TreeseedDevInstanceRecord) { - rmSync(record.instancePath, { force: true }); - rmSync(record.pidPath, { force: true }); - removeRepoFamilyIndexEntry(record); -} - -function usedPortsFromInstances(records: readonly TreeseedDevInstanceRecord[], processIsAlive: ProcessStatusChecker) { - const ports = new Set(); - for (const record of records) { - const evaluated = evaluateDevInstance(record, { processIsAlive }); - if (evaluated.status === 'stale') continue; - for (const port of Object.values(record.ports)) { - if (Number.isInteger(port) && port > 0) ports.add(port); - } - } - return ports; -} - -function managedPortBlock(blockIndex: number) { - const offset = blockIndex * 10; - return { - web: TREESEED_DEFAULT_WEB_PORT + offset, - api: TREESEED_DEFAULT_API_PORT + offset, - postgres: TREESEED_DEFAULT_MARKET_POSTGRES_PORT + offset, - }; -} - -function resolveManagedPortOverrides( - tenantRoot: string, - options: TreeseedManagedDevOptions, - deps: Pick, -) { - const existing = listWorktreeDevInstances(tenantRoot) - .map((record) => evaluateDevInstance(record, deps)) - .find((record) => record.status !== 'stale'); - if (existing && options.webPort == null && options.apiPort == null) { - return { - webPort: existing.ports.web, - apiPort: existing.ports.api, - env: { - TREESEED_MARKET_LOCAL_POSTGRES_PORT: existing.ports.postgres ? String(existing.ports.postgres) : undefined, - TREESEED_MARKET_LOCAL_POSTGRES_CONTAINER: `treeseed-market-local-postgres-${worktreeInstanceSuffix(tenantRoot)}`, - TREESEED_MARKET_LOCAL_POSTGRES_VOLUME: `treeseed-market-local-postgres-data-${worktreeInstanceSuffix(tenantRoot)}`, - } as NodeJS.ProcessEnv, - }; - } - - const repoInstances = listRepoFamilyDevInstances(tenantRoot); - const usedPorts = usedPortsFromInstances(repoInstances, deps.processIsAlive); - for (const owner of deps.inspectPortOwners([ - TREESEED_DEFAULT_WEB_PORT, - TREESEED_DEFAULT_API_PORT, - TREESEED_DEFAULT_MARKET_POSTGRES_PORT, - ])) { - if (owner.pid) usedPorts.add(owner.port); - } - - for (let block = 0; block < 100; block += 1) { - const candidate = managedPortBlock(block); - const requestedWeb = options.webPort ?? candidate.web; - const requestedApi = options.apiPort ?? candidate.api; - const candidatePorts = [requestedWeb, requestedApi, candidate.postgres]; - const liveOwners = deps.inspectPortOwners(candidatePorts).filter((owner) => owner.pid && owner.pid !== process.pid); - const blocked = candidatePorts.some((port) => usedPorts.has(port)) || liveOwners.length > 0; - if (!blocked || options.forceConflicts === true) { - return { - webPort: requestedWeb, - apiPort: requestedApi, - env: { - TREESEED_MARKET_LOCAL_POSTGRES_PORT: String(candidate.postgres), - TREESEED_MARKET_LOCAL_POSTGRES_CONTAINER: `treeseed-market-local-postgres-${worktreeInstanceSuffix(tenantRoot)}`, - TREESEED_MARKET_LOCAL_POSTGRES_VOLUME: `treeseed-market-local-postgres-data-${worktreeInstanceSuffix(tenantRoot)}`, - } as NodeJS.ProcessEnv, - }; - } - } - - throw new Error('Unable to allocate a free Treeseed dev port block for this worktree.'); -} - -function renderManagedDevStatus(records: readonly TreeseedDevInstanceRecord[]) { - if (records.length === 0) return 'No managed Treeseed dev instances found.'; - return records.map((record) => { - const url = record.urls.web ?? record.urls.api ?? '(no url)'; - const branch = record.branch ? ` ${record.branch}` : ''; - return `${record.status.padEnd(8)} pid ${record.pid ?? '-'}${branch} ${url} log ${record.logPath}`; - }).join('\n'); -} - -function renderDevLogJsonEventForHuman(parsed: Record) { - if (parsed.kind === 'treeseed.dev.log' && parsed.type === 'start') { - const startedAt = typeof parsed.startedAt === 'string' ? ` at ${parsed.startedAt}` : ''; - return `[dev] Log session started${startedAt}.`; - } - if (parsed.kind !== 'treeseed.dev.event') { - return null; - } - const surface = typeof parsed.surface === 'string' - ? `[${parsed.surface}]` - : parsed.type === 'setup' - ? '[setup]' - : '[dev]'; - const message = typeof parsed.message === 'string' - ? parsed.message - : typeof parsed.status === 'string' - ? parsed.status - : ''; - if (!message) { - return null; - } - if (surface === '[operations-runner]') { - try { - const runner = JSON.parse(message) as Record; - if (runner.ok === true && runner.claimed === false && runner.operation == null) { - return null; - } - } catch { - // Not a runner heartbeat payload. - } - } - return `${surface} ${message}`; -} - -function renderDevLogForHuman(raw: string) { - const rendered: string[] = []; - for (const line of raw.split(/\r?\n/u)) { - if (!line) { - rendered.push(line); - continue; - } - if (line.trimStart().startsWith('{')) { - try { - const parsed = JSON.parse(line) as Record; - const human = renderDevLogJsonEventForHuman(parsed); - if (human) rendered.push(human); - continue; - } catch { - // Non-JSON despite the leading brace; keep it as-is. - } - } - rendered.push(line); - } - return rendered.join('\n'); -} - -function readRecentDevLog(path: string, maxLines = 300, maxBytes = 256 * 1024) { - const stats = statSync(path); - const start = Math.max(0, stats.size - maxBytes); - const fd = openSync(path, 'r'); - try { - const buffer = Buffer.alloc(stats.size - start); - readSync(fd, buffer, 0, buffer.length, start); - const lines = buffer.toString('utf8').split(/\r?\n/u); - return lines.slice(Math.max(0, lines.length - maxLines)).join('\n'); - } finally { - closeSync(fd); - } -} - -async function waitForManagedInstanceReady( - instancePath: string, - options: Pick, - deps: Pick, -) { - const startedAt = Date.now(); - const timeoutMs = options.readinessTimeoutMs ?? DEFAULT_READINESS_TIMEOUT_MS; - while (Date.now() - startedAt < timeoutMs) { - const record = readDevInstanceFile(instancePath); - if (record) { - const evaluated = evaluateDevInstance(record, deps); - if (evaluated.status === 'ready' || evaluated.status === 'degraded' || evaluated.status === 'stale') { - return evaluated; - } - } - await delay(500); - } - const record = readDevInstanceFile(instancePath); - return record ? { ...record, status: 'degraded' as const, staleReason: 'Timed out waiting for readiness.' } : null; -} - -function managedDevResult(kind: string, ok: boolean, payload: unknown, options: Pick, write: TreeseedIntegratedDevDependencies['write']) { - if (options.json) { - write(`${JSON.stringify({ schemaVersion: 1, kind, ok, payload }, null, 2)}\n`, 'stdout'); - } else if (typeof payload === 'string') { - write(`${payload}\n`, 'stdout'); - } else if (Array.isArray(payload)) { - write(`${renderManagedDevStatus(payload as TreeseedDevInstanceRecord[])}\n`, 'stdout'); - } else { - write(`${renderManagedDevStatus([payload as TreeseedDevInstanceRecord])}\n`, 'stdout'); - } - return ok ? 0 : 1; -} - -async function stopDevInstance( - record: TreeseedDevInstanceRecord, - options: Pick, - deps: Pick, -) { - if (!record.pid || !deps.processIsAlive(record.pid)) { - removeDevInstanceRecord(record); - return { ...record, status: 'stale' as const, staleReason: 'Process was not running.' }; - } - const targetPid = record.processGroupId && process.platform !== 'win32' ? -record.processGroupId : record.pid; - try { - deps.killProcess(targetPid, 'SIGTERM'); - } catch { - // Already stopped or not owned by this process table. - } - if (!await waitForProcessExit(record.pid, deps.processIsAlive, options.shutdownGraceMs ?? DEFAULT_SHUTDOWN_GRACE_MS)) { - try { - deps.killProcess(targetPid, 'SIGKILL'); - } catch { - // Ignore shutdown races. - } - await waitForProcessExit(record.pid, deps.processIsAlive, DEFAULT_KILL_GRACE_MS); - } - removeDevInstanceRecord(record); - return { ...record, status: 'stopped' as const, updatedAt: new Date().toISOString() }; -} - -export async function runTreeseedManagedDev( - options: TreeseedManagedDevOptions, - deps: Partial & { - supervisorCommand?: string; - supervisorArgs?: string[]; - } = {}, -) { - const tenantRoot = resolve(options.cwd ?? process.cwd()); - const write = deps.write ?? defaultWrite; - const spawnProcess = deps.spawn ?? spawn; - const fetchFn = deps.fetch ?? globalThis.fetch.bind(globalThis); - const processIsAlive = deps.processIsAlive ?? defaultProcessIsAlive; - const killProcess = deps.killProcess ?? defaultKillProcess; - const inspectPortOwners = deps.inspectPortOwners ?? defaultInspectPortOwners; - const baseDeps = { processIsAlive, inspectPortOwners }; - - if (options.action === 'status') { - const records = (options.all ? listRepoFamilyDevInstances(tenantRoot) : listWorktreeDevInstances(tenantRoot)) - .map((record) => evaluateDevInstance(record, { processIsAlive })); - return managedDevResult('treeseed.dev.status', true, records, options, write); - } - - if (options.action === 'logs') { - const record = listWorktreeDevInstances(tenantRoot) - .map((entry) => evaluateDevInstance(entry, { processIsAlive })) - .find((entry) => entry.status !== 'stale') ?? listWorktreeDevInstances(tenantRoot)[0]; - if (!record) { - return managedDevResult('treeseed.dev.logs', false, 'No managed Treeseed dev instance found for this worktree.', options, write); - } - if (options.json) { - return managedDevResult('treeseed.dev.logs', true, { logPath: record.logPath, exists: existsSync(record.logPath) }, options, write); - } - if (!existsSync(record.logPath)) { - write(`Log file does not exist yet: ${record.logPath}\n`, 'stderr'); - return 1; - } - if (options.follow) { - const tail = spawnProcess('tail', ['-f', record.logPath], { cwd: record.projectRoot, stdio: 'inherit' }); - return await new Promise((resolvePromise) => { - tail.on('exit', (code) => resolvePromise(code ?? 0)); - }); - } - write(renderDevLogForHuman(readRecentDevLog(record.logPath)), 'stdout'); - return 0; - } - - if (options.action === 'stop') { - const records = options.all ? listRepoFamilyDevInstances(tenantRoot) : listWorktreeDevInstances(tenantRoot); - const stopped = []; - for (const record of records) { - stopped.push(await stopDevInstance(record, options, { killProcess, processIsAlive })); - } - return managedDevResult('treeseed.dev.stop', true, stopped, options, write); - } - - if (options.action === 'restart') { - await runTreeseedManagedDev({ ...options, action: 'stop' }, { ...deps, write: () => {} }); - return runTreeseedManagedDev({ ...options, action: 'start' }, deps); - } - - const allocated = resolveManagedPortOverrides(tenantRoot, options, baseDeps); - const effectiveEnv = { - ...(options.env ?? {}), - ...allocated.env, - }; - const plan = createTreeseedIntegratedDevPlan({ - ...options, - cwd: tenantRoot, - webPort: allocated.webPort, - apiPort: allocated.apiPort, - env: effectiveEnv, - }); - const runtimeScope = instanceRuntimeScope(plan); - const instancePath = devInstancePath(tenantRoot, runtimeScope); - const logPath = plan.logPath; - if (options.plan) { - writePlan(plan, options, write); - return 0; - } - const existing = readDevInstanceFile(instancePath); - if (existing && evaluateDevInstance(existing, { processIsAlive }).status !== 'stale' && options.force !== true) { - return managedDevResult('treeseed.dev.start', true, evaluateDevInstance(existing, { processIsAlive }), options, write); - } - if (existing && options.force === true) { - await stopDevInstance(existing, options, { killProcess, processIsAlive }); - } - - mkdirSync(dirname(logPath), { recursive: true }); - writeFileSync(logPath, '', { flag: 'a' }); - const supervisorCommand = deps.supervisorCommand ?? process.execPath; - const supervisorArgs = [ - ...(deps.supervisorArgs ?? process.argv.slice(1).filter((arg) => !['start', 'restart', 'status', 'stop', 'logs'].includes(arg))), - '--port', - String(allocated.webPort), - '--api-port', - String(allocated.apiPort), - ...(options.webHost ? ['--host', options.webHost] : []), - ...(options.apiHost ? ['--api-host', options.apiHost] : []), - ...(options.webRuntime ? ['--web-runtime', options.webRuntime] : []), - ...(options.surfaces ? ['--surfaces', options.surfaces] : options.surface ? ['--surface', options.surface] : []), - ...(options.setupMode ? ['--setup', options.setupMode] : []), - ...(options.feedbackMode ? ['--feedback', options.feedbackMode] : []), - ...(options.openMode ? ['--open', options.openMode] : []), - ...(options.reset ? ['--reset'] : []), - ...(options.forceConflicts ? ['--force'] : []), - ...(options.projectId ? ['--project-id', options.projectId] : []), - ...(options.teamId ? ['--team-id', options.teamId] : []), - ]; - const logFd = openSync(logPath, 'a'); - const child = spawnProcess(supervisorCommand, supervisorArgs, { - cwd: tenantRoot, - env: { - ...process.env, - ...effectiveEnv, - TREESEED_MANAGED_DEV_INSTANCE: '1', - TREESEED_MANAGED_DEV_SUPPRESS_STDIO: '1', - }, - stdio: ['ignore', logFd, logFd], - detached: true, - }); - closeSync(logFd); - child.unref?.(); - const childPid = typeof child.pid === 'number' ? child.pid : null; - const starting = writeDevInstance(createDevInstanceRecord(plan, 'starting', childPid, childPid && process.platform !== 'win32' ? childPid : null)); - const ready = await waitForManagedInstanceReady(instancePath, options, { processIsAlive }); - if (!ready) { - return managedDevResult('treeseed.dev.start', false, { ...starting, status: 'degraded', staleReason: 'Supervisor did not publish an instance record.' }, options, write); - } - if (ready.status === 'stale') { - return managedDevResult('treeseed.dev.start', false, ready, options, write); - } - // Touch HTTP readiness once more so stale startup records do not look successful. - for (const check of ready.readyChecks.filter((entry) => entry.required && entry.strategy === 'http' && entry.url)) { - if (!await fetchOk(fetchFn, check.url!, 2_000)) { - const degraded = writeDevInstance({ ...ready, status: 'degraded', staleReason: `${check.label} is not reachable at ${check.url}.` }); - return managedDevResult('treeseed.dev.start', false, degraded, options, write); - } - } - return managedDevResult('treeseed.dev.start', ready.status === 'ready', ready, options, write); -} - -type DevRuntimeState = { - pid: number; - tenantRoot: string; - startedAt: string; - commandIds?: TreeseedIntegratedDevCommandId[]; - statePath?: string; -}; - -function devRuntimeStateDir(tenantRoot: string) { - return resolve(tenantRoot, DEV_RUNTIME_DIR); -} - -function devRuntimeStatePath(tenantRoot: string, key: string) { - return resolve(devRuntimeStateDir(tenantRoot), `runtime-${key}.json`); -} - -function legacyDevRuntimeStatePath(tenantRoot: string) { - return resolve(tenantRoot, DEV_RUNTIME_LEGACY_FILE); -} - -function runtimeScopeKey(commandIds: readonly TreeseedIntegratedDevCommandId[]) { - const selected = CANONICAL_COMMAND_IDS.filter((id) => commandIds.includes(id)); - return selected.length > 0 ? selected.join('-') : 'integrated'; -} - -function readDevRuntimeStateFile(path: string): DevRuntimeState | null { - try { - const parsed = JSON.parse(readFileSync(path, 'utf8')) as Partial; - if (!Number.isInteger(parsed.pid) || typeof parsed.tenantRoot !== 'string' || typeof parsed.startedAt !== 'string') { - return null; - } - const commandIds = Array.isArray(parsed.commandIds) - ? parsed.commandIds.filter((id): id is TreeseedIntegratedDevCommandId => ALL_COMMAND_IDS.includes(id as TreeseedIntegratedDevCommandId)) - : undefined; - return { - pid: parsed.pid!, - tenantRoot: parsed.tenantRoot, - startedAt: parsed.startedAt, - ...(commandIds ? { commandIds } : {}), - statePath: path, - }; - } catch { - return null; - } -} - -function listDevRuntimeStates(tenantRoot: string) { - const states: DevRuntimeState[] = []; - const legacy = readDevRuntimeStateFile(legacyDevRuntimeStatePath(tenantRoot)); - if (legacy) { - states.push(legacy); - } - try { - for (const entry of readdirSync(devRuntimeStateDir(tenantRoot))) { - if (!entry.startsWith('runtime-') || !entry.endsWith('.json')) { - continue; - } - const state = readDevRuntimeStateFile(resolve(devRuntimeStateDir(tenantRoot), entry)); - if (state) { - states.push(state); - } - } - } catch { - // No active dev state directory yet. - } - return states; -} - -function runtimeStateOverlaps(state: DevRuntimeState, commandIds: readonly TreeseedIntegratedDevCommandId[]) { - if (!state.commandIds || state.commandIds.length === 0) { - return true; - } - return state.commandIds.some((id) => commandIds.includes(id)); -} - -function listLiveOverlappingDevRuntimeStates( - tenantRoot: string, - commandIds: readonly TreeseedIntegratedDevCommandId[], - processIsAlive: ProcessStatusChecker, -) { - const live: DevRuntimeState[] = []; - for (const state of listDevRuntimeStates(tenantRoot)) { - const statePath = state.statePath; - if (!statePath || !runtimeStateOverlaps(state, commandIds)) { - continue; - } - if (state.pid === process.pid) { - continue; - } - if (!processIsAlive(state.pid)) { - rmSync(statePath, { force: true }); - continue; - } - live.push(state); - } - return live; -} - -function parsePortFromUrl(value: string | undefined) { - if (!value) return null; - try { - const url = new URL(value); - const port = Number(url.port || (url.protocol === 'https:' ? 443 : 80)); - return Number.isInteger(port) && port > 0 ? port : null; - } catch { - return null; - } -} - -function requiredDevPorts(plan: TreeseedIntegratedDevPlan) { - const ports: number[] = []; - for (const command of plan.commands) { - if (command.id === 'web') { - const port = parsePortFromUrl(plan.webUrl ?? undefined); - if (port) ports.push(port); - } - if (command.id === 'api') { - const port = parsePortFromUrl(plan.apiBaseUrl); - if (port) ports.push(port); - } - } - return [...new Set(ports)]; -} - -function formatPortOwner(owner: TreeseedDevPortOwner) { - return `port ${owner.port}${owner.pid ? ` pid ${owner.pid}` : ''}${owner.processName ? ` (${owner.processName})` : ''}`; -} - -function writeCurrentDevRuntimeState(plan: TreeseedIntegratedDevPlan, status: TreeseedDevInstanceStatus = 'starting') { - const tenantRoot = plan.tenantRoot; - const commandIds = plan.commands.map((command) => command.id); - const outputPath = devRuntimeStatePath(tenantRoot, runtimeScopeKey(commandIds)); - mkdirSync(dirname(outputPath), { recursive: true }); - writeFileSync( - outputPath, - `${JSON.stringify({ - pid: process.pid, - tenantRoot, - commandIds, - startedAt: new Date().toISOString(), - }, null, 2)}\n`, - 'utf8', - ); - const managedProcessGroupId = process.env.TREESEED_MANAGED_DEV_INSTANCE === '1' && process.platform !== 'win32' - ? process.pid - : null; - writeDevInstance(createDevInstanceRecord(plan, status, process.pid, managedProcessGroupId)); - return outputPath; -} - -function updateCurrentDevRuntimeState(plan: TreeseedIntegratedDevPlan, status: TreeseedDevInstanceStatus, staleReason?: string) { - const existing = readDevInstanceFile(devInstancePath(plan.tenantRoot, instanceRuntimeScope(plan))); - if (!existing || existing.pid !== process.pid) { - return; - } - writeDevInstance({ ...existing, status, staleReason }); -} - -function removeCurrentDevRuntimeState(plan: TreeseedIntegratedDevPlan) { - const tenantRoot = plan.tenantRoot; - const commandIds = plan.commands.map((command) => command.id); - const statePath = devRuntimeStatePath(tenantRoot, runtimeScopeKey(commandIds)); - const state = readDevRuntimeStateFile(statePath); - if (!state || state.pid !== process.pid) { - // Still try the richer instance record; a legacy state race should not strand it. - } else { - rmSync(statePath, { force: true }); - } - const instance = readDevInstanceFile(devInstancePath(tenantRoot, instanceRuntimeScope(plan))); - if (instance?.pid === process.pid) { - removeDevInstanceRecord(instance); - } -} - -async function waitForProcessExit(pid: number, processIsAlive: ProcessStatusChecker, timeoutMs: number) { - const startedAt = Date.now(); - while (Date.now() - startedAt < timeoutMs) { - if (!processIsAlive(pid)) { - return true; - } - await delay(100); - } - return !processIsAlive(pid); -} - -async function stopPreviousDevRuntimes( - tenantRoot: string, - commandIds: readonly TreeseedIntegratedDevCommandId[], - options: Pick, - deps: Pick, -) { - for (const state of listLiveOverlappingDevRuntimeStates(tenantRoot, commandIds, deps.processIsAlive)) { - const statePath = state.statePath; - if (!statePath) continue; - - emitEvent(options, deps.write, { - type: 'replace', - message: `Stopping previous Treeseed dev runtime (${state.pid}) before starting overlapping surfaces.`, - detail: { pid: state.pid, startedAt: state.startedAt, commandIds: state.commandIds ?? null }, - }); - - try { - deps.killProcess(state.pid, 'SIGTERM'); - } catch { - // The runtime may have exited after the liveness check. - } - if (await waitForProcessExit(state.pid, deps.processIsAlive, options.shutdownGraceMs ?? DEFAULT_SHUTDOWN_GRACE_MS)) { - rmSync(statePath, { force: true }); - continue; - } - - try { - deps.killProcess(state.pid, 'SIGKILL'); - } catch { - // Ignore shutdown races from already-exited supervisors. - } - await waitForProcessExit(state.pid, deps.processIsAlive, DEFAULT_KILL_GRACE_MS); - rmSync(statePath, { force: true }); - } -} - -async function stopPortOwners( - owners: readonly TreeseedDevPortOwner[], - options: Pick, - deps: Pick, -) { - const pids = [...new Set(owners.map((owner) => owner.pid).filter((pid): pid is number => Number.isInteger(pid) && pid > 0 && pid !== process.pid))]; - for (const pid of pids) { - emitEvent(options, deps.write, { - type: 'replace', - message: `Stopping service on required dev port (pid ${pid}).`, - detail: owners.filter((owner) => owner.pid === pid), - }); - try { - deps.killProcess(pid, 'SIGTERM'); - } catch { - // Ignore races with processes that exited after inspection. - } - if (await waitForProcessExit(pid, deps.processIsAlive, options.shutdownGraceMs ?? DEFAULT_SHUTDOWN_GRACE_MS)) { - continue; - } - try { - deps.killProcess(pid, 'SIGKILL'); - } catch { - // Ignore shutdown races. - } - await waitForProcessExit(pid, deps.processIsAlive, DEFAULT_KILL_GRACE_MS); - } -} - -async function prepareDevRuntimeSlots( - plan: TreeseedIntegratedDevPlan, - options: Pick, - deps: Pick, -) { - const commandIds = plan.commands.map((command) => command.id); - const liveRuntimeStates = listLiveOverlappingDevRuntimeStates(plan.tenantRoot, commandIds, deps.processIsAlive); - const ports = requiredDevPorts(plan); - const portOwners = deps.inspectPortOwners(ports) - .filter((owner) => owner.pid !== process.pid); - if (options.force !== true) { - if (liveRuntimeStates.length > 0 || portOwners.length > 0) { - emitEvent(options, deps.write, { - type: 'error', - status: 'existing-service', - message: [ - 'Treeseed dev found an existing runtime or service on a required port.', - 'Stop it first, or rerun with --force to terminate overlapping Treeseed dev services and port owners.', - ].join(' '), - detail: { - runtimes: liveRuntimeStates.map((state) => ({ - pid: state.pid, - startedAt: state.startedAt, - commandIds: state.commandIds ?? null, - statePath: state.statePath ?? null, - })), - ports: portOwners.map((owner) => ({ ...owner, label: formatPortOwner(owner) })), - }, - }); - return false; - } - return true; - } - - await stopPreviousDevRuntimes(plan.tenantRoot, commandIds, options, deps); - if (portOwners.length > 0) { - const ownersWithoutPid = portOwners.filter((owner) => owner.pid == null); - if (ownersWithoutPid.length > 0) { - emitEvent(options, deps.write, { - type: 'error', - status: 'existing-service', - message: `Cannot force-stop required dev ports because some listeners did not expose process ids: ${ownersWithoutPid.map(formatPortOwner).join(', ')}.`, - detail: ownersWithoutPid, - }); - return false; - } - await stopPortOwners(portOwners, options, deps); - } - const remainingPortOwners = deps.inspectPortOwners(ports) - .filter((owner) => owner.pid !== process.pid); - if (remainingPortOwners.length > 0) { - emitEvent(options, deps.write, { - type: 'error', - status: 'existing-service', - message: `Required dev ports are still occupied after --force: ${remainingPortOwners.map(formatPortOwner).join(', ')}.`, - detail: remainingPortOwners, - }); - return false; - } - return true; -} - -function emitEvent( - options: Pick, - write: TreeseedIntegratedDevDependencies['write'], - event: DevEvent, - stream: 'stdout' | 'stderr' = event.type === 'error' ? 'stderr' : 'stdout', -) { - if (options.json) { - write(`${JSON.stringify({ schemaVersion: 1, kind: 'treeseed.dev.event', ...event })}\n`, stream); - return; - } - const surface = event.surface ? `[${event.surface}]` : event.type === 'setup' ? '[setup]' : '[dev]'; - const message = event.message ?? event.detail ?? event.status ?? ''; - write(`${surface} ${String(message)}\n`, stream); -} - -function createDevLogWrite( - baseWrite: TreeseedIntegratedDevDependencies['write'], - logPath: string, -): TreeseedIntegratedDevDependencies['write'] { - mkdirSync(dirname(logPath), { recursive: true }); - appendFileSync(logPath, `[dev] Log session started at ${new Date().toISOString()}.\n`, 'utf8'); - const suppressBaseWrite = process.env.TREESEED_MANAGED_DEV_SUPPRESS_STDIO === '1'; - return (line, stream) => { - if (!suppressBaseWrite) { - baseWrite(line, stream); - } - appendFileSync(logPath, line, 'utf8'); - }; -} - -export function runTreeseedIntegratedDevReset( - reset: TreeseedIntegratedDevResetPlan | null, - options: Pick, - deps: Pick, -) { - if (!reset?.enabled) { - return null; - } - const results = reset.actions.map((action) => { - if (action.status === 'skipped') { - emitEvent(options, deps.write, { - type: 'reset', - status: action.status, - message: `${action.label}: skipped`, - detail: action, - }); - return action; - } - if (action.kind === 'service') { - const stopped = action.id === 'market-postgres' - ? deps.resetMarketPostgres() - : deps.stopMailpitContainers(); - const result: TreeseedIntegratedDevResetAction = { - ...action, - status: stopped ? 'removed' : 'failed', - detail: stopped - ? (action.id === 'market-postgres' ? 'Market PostgreSQL database state was reset.' : 'Mailpit container and inbox state were reset.') - : (action.id === 'market-postgres' - ? 'Unable to stop or remove the Treeseed-managed Market PostgreSQL container and volume.' - : 'Unable to remove the Treeseed-managed Mailpit container and inbox state.'), - }; - emitEvent(options, deps.write, { - type: 'reset', - status: result.status, - message: `${result.label}: ${result.status}`, - detail: result, - }, result.status === 'failed' ? 'stderr' : 'stdout'); - return result; - } - if (!action.path || !existsSync(action.path)) { - if (action.id === 'dev-reload' && action.path) { - try { - writeDevReloadStampPath(action.path); - const result: TreeseedIntegratedDevResetAction = { - ...action, - status: 'refreshed', - detail: action.path, - }; - emitEvent(options, deps.write, { - type: 'reset', - status: result.status, - message: `${result.label}: refreshed`, - detail: result, - }); - return result; - } catch (error) { - const result: TreeseedIntegratedDevResetAction = { - ...action, - status: 'failed', - detail: error instanceof Error ? error.message : String(error), - }; - emitEvent(options, deps.write, { - type: 'reset', - status: result.status, - message: `${result.label}: failed`, - detail: result, - }, 'stderr'); - return result; - } - } - const result: TreeseedIntegratedDevResetAction = { - ...action, - status: 'skipped', - detail: action.detail ?? 'Path does not exist.', - }; - emitEvent(options, deps.write, { - type: 'reset', - status: result.status, - message: `${result.label}: skipped`, - detail: result, - }); - return result; - } - if (action.id === 'dev-reload') { - try { - writeDevReloadStampPath(action.path); - const result: TreeseedIntegratedDevResetAction = { - ...action, - status: 'refreshed', - detail: action.path, - }; - emitEvent(options, deps.write, { - type: 'reset', - status: result.status, - message: `${result.label}: refreshed`, - detail: result, - }); - return result; - } catch (error) { - const result: TreeseedIntegratedDevResetAction = { - ...action, - status: 'failed', - detail: error instanceof Error ? error.message : String(error), - }; - emitEvent(options, deps.write, { - type: 'reset', - status: result.status, - message: `${result.label}: failed`, - detail: result, - }, 'stderr'); - return result; - } - } - try { - deps.removePath(action.path); - const result: TreeseedIntegratedDevResetAction = { - ...action, - status: 'removed', - detail: action.path, - }; - emitEvent(options, deps.write, { - type: 'reset', - status: result.status, - message: `${result.label}: removed`, - detail: result, - }); - return result; - } catch (error) { - const result: TreeseedIntegratedDevResetAction = { - ...action, - status: 'failed', - detail: error instanceof Error ? error.message : String(error), - }; - emitEvent(options, deps.write, { - type: 'reset', - status: result.status, - message: `${result.label}: failed`, - detail: result, - }, 'stderr'); - return result; - } - }); - return { - ...reset, - actions: results, - }; -} - -function writePlan(plan: TreeseedIntegratedDevPlan, options: Pick, write: TreeseedIntegratedDevDependencies['write']) { - if (options.json) { - write(`${JSON.stringify({ schemaVersion: 1, kind: 'treeseed.dev.plan', ok: true, payload: serializeDevPlanForOutput(plan) }, null, 2)}\n`, 'stdout'); - return; - } - write(`Treeseed dev plan\n`, 'stdout'); - write(`surface: ${plan.surface}\n`, 'stdout'); - write(`setup: ${plan.setupMode}\n`, 'stdout'); - write(`feedback: ${plan.feedbackMode}\n`, 'stdout'); - if (plan.reset) { - write(`reset: enabled\n`, 'stdout'); - for (const action of plan.reset.actions) { - write(`- reset ${action.id}: ${action.status}${action.path ? ` ${action.path}` : ''}\n`, 'stdout'); - } - } - if (plan.webUrl) { - write(`web: ${plan.webUrl}\n`, 'stdout'); - } - write(`api: ${plan.apiBaseUrl}\n`, 'stdout'); - write(`log: ${plan.logPath}\n`, 'stdout'); - for (const [name, runtime] of Object.entries(plan.localRuntimes)) { - write(`runtime ${name}: ${runtime.selected} (${runtime.provider}, requested ${runtime.requested})${runtime.reason ? ` - ${runtime.reason}` : ''}\n`, 'stdout'); - } - for (const command of plan.commands) { - write(`- ${command.id}: ${command.command} ${command.args.join(' ')}\n`, 'stdout'); - } -} - -function attachPrefixedLogReader( - child: ProcessLike, - surface: string, - options: Pick, - write: TreeseedIntegratedDevDependencies['write'], -) { - const filterState: Record<'stdout' | 'stderr', { suppressWorkerdBrokenPipeBlock: boolean }> = { - stdout: { suppressWorkerdBrokenPipeBlock: false }, - stderr: { suppressWorkerdBrokenPipeBlock: false }, - }; - function shouldSuppressLogLine(line: string, name: 'stdout' | 'stderr') { - if (!options.json && surface === 'operations-runner' && name === 'stdout') { - try { - const parsed = JSON.parse(line) as Record; - if (parsed.ok === true && parsed.claimed === false && parsed.operation == null) { - return true; - } - } catch { - // Non-JSON runner output should flow through normally. - } - } - const state = filterState[name]; - if (state.suppressWorkerdBrokenPipeBlock) { - const trimmed = line.trim(); - if (!trimmed || trimmed.startsWith('stack:') || line.includes('/workerd@')) { - return true; - } - state.suppressWorkerdBrokenPipeBlock = false; - } - if (line.includes('kj::getCaughtExceptionAsKj() = kj/async-io-unix.c++:186: disconnected: ::write')) { - state.suppressWorkerdBrokenPipeBlock = true; - return true; - } - return false; - } - function attach(stream: NodeJS.ReadableStream | null | undefined, name: 'stdout' | 'stderr') { - if (!stream || typeof stream.on !== 'function') { - return; - } - let buffer = ''; - stream.on('data', (chunk: Buffer | string) => { - buffer += chunk.toString(); - for (;;) { - const newlineIndex = buffer.indexOf('\n'); - if (newlineIndex < 0) { - break; - } - const line = buffer.slice(0, newlineIndex); - buffer = buffer.slice(newlineIndex + 1); - if (shouldSuppressLogLine(line, name)) { - continue; - } - if (options.json) { - emitEvent(options, write, { type: 'log', surface, message: line, detail: { stream: name } }, name); - } else { - write(`[${surface}] ${line}\n`, name); - } - } - }); - stream.on('end', () => { - if (buffer.length > 0) { - if (shouldSuppressLogLine(buffer, name)) { - buffer = ''; - return; - } - if (options.json) { - emitEvent(options, write, { type: 'log', surface, message: buffer, detail: { stream: name } }, name); - } else { - write(`[${surface}] ${buffer}\n`, name); - } - buffer = ''; - } - }); - } - attach(child.stdout ?? null, 'stdout'); - attach(child.stderr ?? null, 'stderr'); -} - -function runSetupStep( - step: TreeseedIntegratedDevSetupStep, - plan: TreeseedIntegratedDevPlan, - deps: Pick, -) { - if (!step.command || !step.args) { - return { - ...step, - status: step.status === 'failed' ? 'failed' : 'skipped', - } satisfies TreeseedIntegratedDevSetupStep; - } - const result = deps.spawnSync(step.command, step.args, { - cwd: plan.tenantRoot, - env: { - ...process.env, - ...plan.commands[0]?.env, - TREESEED_LOCAL_DEV_MODE: 'cloudflare', - TREESEED_PUBLIC_DEV_WATCH_RELOAD: plan.feedbackMode === 'live' ? 'true' : process.env.TREESEED_PUBLIC_DEV_WATCH_RELOAD, - }, - encoding: 'utf8', - timeout: DEFAULT_SETUP_STEP_TIMEOUT_MS, - }); - if ((result.status ?? 1) === 0) { - return { - ...step, - status: 'completed', - detail: [result.stdout, result.stderr].filter(Boolean).join('\n').trim() || step.detail, - } satisfies TreeseedIntegratedDevSetupStep; - } - const timedOut = result.error && 'code' in result.error && result.error.code === 'ETIMEDOUT'; - const timeoutDetail = timedOut - ? `${step.label} timed out after ${Math.round(DEFAULT_SETUP_STEP_TIMEOUT_MS / 1000)} seconds.` - : null; - return { - ...step, - status: step.required ? 'failed' : 'degraded', - detail: [timeoutDetail, result.stdout, result.stderr] - .filter(Boolean) - .join('\n') - .trim() || `Exited with ${result.status ?? 1}.`, - } satisfies TreeseedIntegratedDevSetupStep; -} - -function marketPostgresConfig(env: NodeJS.ProcessEnv) { - return { - container: env.TREESEED_MARKET_LOCAL_POSTGRES_CONTAINER?.trim() || TREESEED_DEFAULT_MARKET_POSTGRES_CONTAINER, - volume: env.TREESEED_MARKET_LOCAL_POSTGRES_VOLUME?.trim() || TREESEED_DEFAULT_MARKET_POSTGRES_VOLUME, - port: env.TREESEED_MARKET_LOCAL_POSTGRES_PORT?.trim() || String(TREESEED_DEFAULT_MARKET_POSTGRES_PORT), - user: 'treeseed', - password: 'treeseed', - database: 'market_local', - }; -} - -function dockerBinary(env: NodeJS.ProcessEnv) { - return resolveTreeseedToolBinary('docker', { env }) ?? 'docker'; -} - -function spawnDocker( - args: string[], - env: NodeJS.ProcessEnv, - deps: Pick, - timeout = 30_000, -) { - return deps.spawnSync(dockerBinary(env), args, { - cwd: process.cwd(), - env, - encoding: 'utf8', - timeout, - }); -} - -function dockerResultText(result: ReturnType) { - return [result.stdout, result.stderr] - .filter(Boolean) - .join('\n') - .trim(); -} - -function dockerVolumeIsMissing(result: ReturnType) { - if (result.error) return false; - const text = dockerResultText(result).toLowerCase(); - return text.includes('no such volume') || text.includes('not found'); -} - -function ensureMarketPostgres( - env: NodeJS.ProcessEnv, - deps: Pick, -) { - const config = marketPostgresConfig(env); - const inspect = spawnDocker(['inspect', config.container], env, deps); - if ((inspect.status ?? 1) !== 0) { - const run = spawnDocker([ - 'run', - '-d', - '--name', - config.container, - '-e', - `POSTGRES_USER=${config.user}`, - '-e', - `POSTGRES_PASSWORD=${config.password}`, - '-e', - `POSTGRES_DB=${config.database}`, - '-p', - `127.0.0.1:${config.port}:5432`, - '-v', - `${config.volume}:/var/lib/postgresql/data`, - 'postgres:16', - ], env, deps, 60_000); - if ((run.status ?? 1) !== 0) { - throw new Error(dockerResultText(run) || `Unable to start ${config.container}.`); - } - } else { - const start = spawnDocker(['start', config.container], env, deps); - if ((start.status ?? 1) !== 0) { - throw new Error(dockerResultText(start) || `Unable to start existing ${config.container}.`); - } - } - - const startedAt = Date.now(); - let last = ''; - while (Date.now() - startedAt < 45_000) { - const ready = spawnDocker(['exec', config.container, 'pg_isready', '-U', config.user, '-d', config.database], env, deps, 5_000); - last = dockerResultText(ready); - if ((ready.status ?? 1) === 0) { - const query = spawnDocker(['exec', config.container, 'psql', '-U', config.user, '-d', config.database, '-c', 'SELECT 1'], env, deps, 5_000); - last = dockerResultText(query) || last; - if ((query.status ?? 1) === 0) { - return `Market PostgreSQL is ready at 127.0.0.1:${config.port} (${config.container}).`; - } - } - Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 500); - } - throw new Error(last || `Timed out waiting for ${config.container} to accept connections.`); -} - -function resetMarketPostgres(env: NodeJS.ProcessEnv, deps: Pick) { - const config = marketPostgresConfig(env); - spawnDocker(['rm', '-f', config.container], env, deps, 30_000); - const existingVolume = spawnDocker(['volume', 'inspect', config.volume], env, deps, 30_000); - if ((existingVolume.status ?? 1) !== 0) { - return dockerVolumeIsMissing(existingVolume); - } - const volume = spawnDocker(['volume', 'rm', config.volume], env, deps, 30_000); - return (volume.status ?? 1) === 0 || dockerVolumeIsMissing(volume); -} - -function stopMarketPostgres(env: NodeJS.ProcessEnv, deps: Pick) { - if (env.TREESEED_MARKET_LOCAL_POSTGRES_MANAGED !== 'true') { - return true; - } - const config = marketPostgresConfig(env); - const result = spawnDocker(['rm', '-f', config.container], env, deps, 30_000); - return (result.status ?? 1) === 0; -} - -function runLocalSetup( - plan: TreeseedIntegratedDevPlan, - options: Pick, - deps: Pick, -) { - const results: TreeseedIntegratedDevSetupStep[] = []; - if (plan.setupMode === 'off') { - for (const step of plan.setupSteps) { - results.push(step); - emitEvent(options, deps.write, { type: 'setup', status: step.status, message: `${step.label}: ${step.status}`, detail: step.detail }); - } - return results; - } - - for (const step of plan.setupSteps) { - let result = step; - if (step.status === 'planned') { - emitEvent(options, deps.write, { - type: 'setup', - status: 'running', - message: `${step.label}: running`, - detail: step.detail, - }); - } - if (step.id === 'workspace-links') { - if (plan.setupMode === 'check') { - result = { ...step, status: 'skipped', detail: 'Workspace links were checked in non-mutating mode.' }; - } else { - const workspaceRoot = findNearestTreeseedWorkspaceRoot(plan.tenantRoot); - if (workspaceRoot) { - const links = ensureLocalWorkspaceLinks(workspaceRoot, { - mode: 'auto', - env: { ...process.env, ...plan.commands[0]?.env }, - }); - result = { - ...step, - status: links.issues.length > 0 ? 'failed' : 'completed', - detail: links.issues.length > 0 - ? links.issues.join('; ') - : `Verified ${links.links.length} workspace link${links.links.length === 1 ? '' : 's'}.`, - }; - } else { - result = { ...step, status: 'skipped', detail: 'No Treeseed workspace root found.' }; - } - } - } else if (step.id === 'wrangler') { - const wrangler = resolveTreeseedToolBinary('wrangler', { env: { ...process.env, ...plan.commands[0]?.env } }); - result = wrangler - ? { ...step, status: 'completed', detail: wrangler } - : { - ...step, - status: step.required ? 'failed' : 'degraded', - detail: 'Wrangler was not found. Run `npx trsd install --json` and retry `npx trsd dev`.', - }; - } else if (step.id === 'market-postgres') { - if (plan.setupMode === 'check') { - result = { ...step, status: 'skipped', detail: 'Local Market PostgreSQL startup was checked in non-mutating mode.' }; - } else if (step.status === 'failed') { - result = step; - } else { - try { - const detail = ensureMarketPostgres({ ...process.env, ...plan.commands[0]?.env }, deps); - result = { ...step, status: 'completed', detail }; - } catch (error) { - result = { - ...step, - status: 'failed', - detail: error instanceof Error ? error.message : String(error), - }; - } - } - } else if (step.id === 'wrangler-config') { - if (plan.setupMode === 'check') { - result = { ...step, status: 'skipped', detail: 'Local Wrangler config generation was checked in non-mutating mode.' }; - } else { - try { - const { wranglerPath } = ensureGeneratedWranglerConfig(plan.tenantRoot, { - target: createPersistentDeployTarget('local'), - env: plan.commands[0]?.env, - }); - result = { ...step, status: 'completed', detail: wranglerPath }; - } catch (error) { - result = { - ...step, - status: 'failed', - detail: error instanceof Error ? error.message : String(error), - }; - } - } - } else if (plan.setupMode === 'check') { - result = { ...step, status: step.status === 'failed' ? 'failed' : 'skipped', detail: step.detail ?? 'Skipped in setup check mode.' }; - } else { - result = runSetupStep(step, plan, deps); - } - results.push(result); - emitEvent(options, deps.write, { - type: 'setup', - status: result.status, - message: `${result.label}: ${result.status}`, - detail: result.detail, - }, result.status === 'failed' ? 'stderr' : 'stdout'); - } - const failedRequired = results.some((step) => step.required && step.status === 'failed'); - if (plan.feedbackMode === 'live' && plan.setupMode === 'auto' && !failedRequired) { - writeDevReloadStamp(plan.tenantRoot); - emitEvent(options, deps.write, { type: 'reload', message: 'Wrote initial browser reload stamp.' }); - } - return results; -} - -async function fetchOk(fetchFn: FetchLike, url: string, timeoutMs: number) { - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), timeoutMs); - try { - const response = await fetchFn(url, { signal: controller.signal }); - return response.ok; - } catch { - return false; - } finally { - clearTimeout(timeout); - } -} - -async function waitForHttpReady(fetchFn: FetchLike, url: string, timeoutMs: number) { - const startedAt = Date.now(); - while (Date.now() - startedAt < timeoutMs) { - if (await fetchOk(fetchFn, url, 2_000)) { - return true; - } - await delay(500); - } - return false; -} - -async function defaultOpenBrowser(url: string) { - const platform = process.platform; - const command = platform === 'darwin' ? 'open' : platform === 'win32' ? 'cmd' : 'xdg-open'; - const args = platform === 'win32' ? ['/c', 'start', '', url] : [url]; - const child = spawn(command, args, { stdio: 'ignore', detached: true }); - child.unref(); -} - -function shouldOpenBrowser(plan: TreeseedIntegratedDevPlan) { - if (!plan.webUrl || plan.openMode === 'off') { - return false; - } - if (plan.openMode === 'on') { - return true; - } - return process.stdout.isTTY === true && process.env.CI !== 'true'; -} - -function failedSetupMessage(failed: TreeseedIntegratedDevSetupStep) { - return [ - `${failed.label} failed.`, - failed.detail ? String(failed.detail) : null, - 'Run `npx trsd install --json` if a managed executable is missing, then retry `npx trsd dev --setup auto`.', - ].filter(Boolean).join(' '); -} - -export async function runTreeseedIntegratedDev( - options: TreeseedIntegratedDevOptions = {}, - deps: Partial = {}, -) { - const tenantRoot = resolve(options.cwd ?? process.cwd()); - let write = deps.write ?? defaultWrite; - const spawnProcess = deps.spawn ?? spawn; - const spawnSyncProcess = deps.spawnSync ?? spawnSync; - const onSignal = deps.onSignal ?? defaultSignalRegistrar; - const fetchFn = deps.fetch ?? globalThis.fetch.bind(globalThis); - const killProcess = deps.killProcess ?? defaultKillProcess; - const processIsAlive = deps.processIsAlive ?? defaultProcessIsAlive; - const openBrowser = deps.openBrowser ?? defaultOpenBrowser; - const startWatch = deps.startWatch ?? startPollingWatch; - const prepareEnvironment = deps.prepareEnvironment ?? defaultPrepareEnvironment; - const removePath = deps.removePath ?? defaultRemovePath; - const stopMailpit = deps.stopMailpitContainers ?? (() => true); - const resetMarketPostgresContainer = deps.resetMarketPostgres ?? defaultResetMarketPostgres; - const stopMarketPostgresContainer = deps.stopMarketPostgres ?? defaultStopMarketPostgres; - const inspectPortOwners = deps.inspectPortOwners ?? defaultInspectPortOwners; - - prepareEnvironment(tenantRoot); - const plan = createTreeseedIntegratedDevPlan({ - ...options, - cwd: tenantRoot, - env: { - ...process.env, - ...(options.env ?? {}), - }, - }); - - if (options.plan) { - writePlan(plan, options, write); - return 0; - } - - const commandIds = plan.commands.map((command) => command.id); - write = createDevLogWrite(write, plan.logPath); - emitEvent(options, write, { - type: 'log', - message: `Writing Treeseed dev logs to ${plan.logPath}.`, - detail: { logPath: plan.logPath }, - }); - if (!await prepareDevRuntimeSlots(plan, options, { write, killProcess, processIsAlive, inspectPortOwners })) { - return 1; - } - - const resetResults = runTreeseedIntegratedDevReset(plan.reset, options, { - write, - removePath, - stopMailpitContainers: stopMailpit, - resetMarketPostgres: deps.resetMarketPostgres - ? resetMarketPostgresContainer - : () => resetMarketPostgres(plan.commands[0]?.env ?? process.env, { spawnSync: spawnSyncProcess }), - }); - const failedReset = resetResults?.actions.find((action) => action.status === 'failed'); - if (failedReset) { - emitEvent(options, write, { - type: 'error', - message: `${failedReset.label} failed during dev reset.`, - detail: failedReset, - }); - return 1; - } - - writeCurrentDevRuntimeState(plan, 'starting'); - - const children = new Map(); - const commandsById = new Map(plan.commands.map((command) => [command.id, command])); - const requiredSurfaceIds = new Set(plan.readyChecks.filter((check) => check.required).map((check) => check.id)); - const exited = new Map(); - const restartAttempts = new Map(); - const restartTimers = new Map(); - let setupRetryTimer: NodeJS.Timeout | null = null; - let readinessInProgress = false; - let watchController: WatchController | null = null; - let settled = false; - let readinessComplete = false; - let restartInProgress = false; - const shutdownGraceMs = options.shutdownGraceMs ?? DEFAULT_SHUTDOWN_GRACE_MS; - - return await new Promise((resolveExitCode) => { - const disposers = [ - onSignal('SIGINT', () => finalize(130)), - onSignal('SIGTERM', () => finalize(143)), - ]; - - function stopWatching() { - if (!watchController) { - return; - } - watchController.stop(); - watchController = null; - } - - function clearTimers() { - if (setupRetryTimer) { - clearTimeout(setupRetryTimer); - setupRetryTimer = null; - } - for (const timer of restartTimers.values()) { - clearTimeout(timer); - } - restartTimers.clear(); - } - - function finalize(exitCode: number) { - if (settled) { - return; - } - settled = true; - void finalizeAsync(exitCode); - } - - async function finalizeAsync(exitCode: number) { - stopWatching(); - clearTimers(); - await Promise.all( - [...children.values()].map((managed) => stopManagedProcess(managed, 'SIGTERM', killProcess, shutdownGraceMs)), - ); - const marketEnv = plan.commands[0]?.env ?? process.env; - const shouldStopMarketPostgres = marketEnv.TREESEED_MARKET_LOCAL_POSTGRES_MANAGED === 'true'; - if (shouldStopMarketPostgres) { - const stopped = deps.stopMarketPostgres - ? stopMarketPostgresContainer() - : stopMarketPostgres(marketEnv, { spawnSync: spawnSyncProcess }); - if (!stopped) { - emitEvent(options, write, { - type: 'shutdown', - status: 'degraded', - message: 'Unable to stop the managed local Market PostgreSQL container.', - }, 'stderr'); - } - } - children.clear(); - for (const dispose of disposers) { - dispose(); - } - removeCurrentDevRuntimeState(plan); - emitEvent( - options, - write, - { type: 'shutdown', exitCode, message: `Dev runtime stopped with exit code ${exitCode}.` }, - exitCode === 0 ? 'stdout' : 'stderr', - ); - resolveExitCode(exitCode); - } - - function restartDelayFor(id: TreeseedIntegratedDevCommand['id']) { - const attempts = restartAttempts.get(id) ?? 0; - return Math.min(MAX_RESTART_BACKOFF_MS, INITIAL_RESTART_BACKOFF_MS * (2 ** attempts)); - } - - function markRestartAttempt(id: TreeseedIntegratedDevCommand['id']) { - const attempts = restartAttempts.get(id) ?? 0; - restartAttempts.set(id, attempts + 1); - } - - function runSetupOnce() { - const setupResults = runLocalSetup(plan, options, { spawnSync: spawnSyncProcess, write }); - const failedSetup = setupResults.find((step) => step.status === 'failed' && step.required); - if (failedSetup) { - emitEvent(options, write, { type: 'error', message: failedSetupMessage(failedSetup), detail: failedSetup }); - return false; - } - return true; - } - - function scheduleSetupRetry(reason: string) { - if (setupRetryTimer || settled) { - return; - } - emitEvent(options, write, { - type: 'restart', - status: 'retrying', - message: `${reason} Retrying local setup in ${Math.round(SETUP_RETRY_BACKOFF_MS / 1000)}s.`, - }, 'stderr'); - setupRetryTimer = setTimeout(() => { - setupRetryTimer = null; - if (settled) return; - if (!runSetupOnce()) { - scheduleSetupRetry('Local setup is still failing.'); - return; - } - for (const command of plan.commands) { - if (!children.has(command.id)) { - spawnCommand(command); - } - } - void waitForReadiness(); - }, SETUP_RETRY_BACKOFF_MS); - } - - function spawnCommand(command: TreeseedIntegratedDevCommand) { - emitEvent(options, write, { - type: 'spawn', - surface: command.id, - command: command.command, - args: command.args, - message: `Starting ${command.label}.`, - }); - const child = spawnProcess(command.command, command.args, { - cwd: command.cwd, - env: command.env, - stdio: options.stdio ?? ['ignore', 'pipe', 'pipe'], - detached: true, - }); - const managed = createManagedDevProcess(command, child); - children.set(command.id, managed); - attachPrefixedLogReader(child, command.id, options, write); - child.on('exit', (code, signal) => { - managed.exited = true; - managed.exitCode = code; - managed.exitSignal = signal; - managed.resolveExit(); - exited.set(command.id, { code, signal }); - if (managed.intentionalStop || settled) { - return; - } - const exitCode = signal === 'SIGINT' - ? 130 - : signal === 'SIGTERM' - ? 143 - : code ?? 0; - const required = requiredSurfaceIds.has(command.id); - if (required) { - emitEvent(options, write, { - type: 'error', - surface: command.id, - exitCode, - signal, - message: `${command.label} exited unexpectedly during ${readinessComplete ? 'supervision' : 'startup'} with ${signal ?? exitCode}; restarting.`, - }); - children.delete(command.id); - scheduleCommandRestart(command.id); - return; - } - const status = exitCode === 0 ? 'idle' : 'degraded'; - emitEvent(options, write, { - type: 'error', - surface: command.id, - exitCode, - signal, - status, - message: readinessComplete - ? `${command.label} exited with ${signal ?? exitCode}; continuing because it is not a required surface.` - : `${command.label} exited during startup with ${signal ?? exitCode}; continuing because it is not a required surface.`, - }, status === 'idle' ? 'stdout' : 'stderr'); - void stopManagedProcess(managed, 'SIGTERM', killProcess, 0).finally(() => { - children.delete(command.id); - }); - }); - return child; - } - - function scheduleCommandRestart(id: TreeseedIntegratedDevCommand['id']) { - const command = commandsById.get(id); - if (!command || settled || restartTimers.has(id)) { - return; - } - const delayMs = restartDelayFor(id); - markRestartAttempt(id); - emitEvent(options, write, { - type: 'restart', - surface: id, - status: 'scheduled', - message: `Restarting ${command.label} in ${Math.round(delayMs / 1000)}s.`, - }, 'stderr'); - const timer = setTimeout(() => { - restartTimers.delete(id); - void restartCommand(id); - }, delayMs); - restartTimers.set(id, timer); - } - - async function restartCommand(id: TreeseedIntegratedDevCommand['id']) { - const command = commandsById.get(id); - if (!command || settled) { - return; - } - const current = children.get(id); - if (current) { - await stopManagedProcess(current, 'SIGTERM', killProcess, Math.min(shutdownGraceMs, 500)); - } - children.delete(id); - exited.delete(id); - if (settled) { - return; - } - spawnCommand(command); - emitEvent(options, write, { type: 'restart', surface: id, message: `Restarted ${command.label}.` }); - void waitForReadiness(); - } - - function startLiveWatch() { - if (watchController || plan.watchEntries.length === 0 || plan.feedbackMode === 'off' || settled) { - return; - } - watchController = startWatch({ - watchEntries: plan.watchEntries, - onChange: async (change) => { - if (settled) { - return; - } - if (restartInProgress) { - watchController?.rebaseline(); - return; - } - restartInProgress = true; - try { - emitEvent(options, write, { - type: 'restart', - message: `Detected ${change.changedPaths.length} development change${change.changedPaths.length === 1 ? '' : 's'}.`, - detail: { - tenantChanged: change.tenantChanged, - tenantApiChanged: change.tenantApiChanged, - coreChanged: change.coreChanged, - sdkChanged: change.sdkChanged, - agentChanged: change.agentChanged, - cliChanged: change.cliChanged, - commandImplementationChanged: change.commandImplementationChanged, - }, - }); - if (change.commandImplementationChanged) { - emitEvent(options, write, { - type: 'replace', - status: 'restart-required', - message: 'The dev command implementation changed. Stop and rerun `npx trsd dev` to load the new supervisor.', - detail: change.changedPaths, - }, 'stderr'); - return; - } - if (change.agentChanged) { - emitEvent(options, write, { - type: 'restart', - status: 'deferred', - message: 'Agent service changes detected; running agent services will keep their current code until the next workday or a manual restart.', - detail: change.changedPaths, - }); - } - if (change.tenantChanged || change.tenantApiChanged || change.coreChanged || change.sdkChanged) { - if (!runSetupOnce()) { - scheduleSetupRetry('Local setup failed after a development change.'); - return; - } - } - const restartIds = new Set(); - if ((change.tenantChanged || change.coreChanged || change.sdkChanged) && commandsById.has('web')) { - restartIds.add('web'); - } - if ((change.tenantApiChanged || change.sdkChanged) && commandsById.has('api')) { - restartIds.add('api'); - } - if (change.sdkChanged) { - for (const id of commandsById.keys()) { - restartIds.add(id); - } - } - if ((change.tenantApiChanged || change.sdkChanged) && commandsById.has('operations-runner')) { - restartIds.add('operations-runner'); - } - for (const id of restartIds) { - await restartCommand(id); - } - if (plan.feedbackMode === 'live') { - writeDevReloadStamp(plan.tenantRoot); - emitEvent(options, write, { type: 'reload', message: 'Wrote browser reload stamp.' }); - } - } finally { - watchController?.rebaseline(); - restartInProgress = false; - } - }, - }); - watchController.rebaseline(); - } - - async function waitForReadiness() { - if (readinessInProgress) { - return; - } - readinessInProgress = true; - const readinessTimeoutMs = options.readinessTimeoutMs ?? DEFAULT_READINESS_TIMEOUT_MS; - const processReadyGraceMs = options.processReadyGraceMs ?? DEFAULT_PROCESS_READY_GRACE_MS; - let allRequiredReady = true; - for (const check of plan.readyChecks) { - if (settled) { - readinessInProgress = false; - return; - } - let ready = false; - if (check.strategy === 'http' && check.url) { - ready = await waitForHttpReady(fetchFn, check.url, readinessTimeoutMs); - } else { - const commandId = check.id as TreeseedIntegratedDevCommand['id']; - await delay(processReadyGraceMs); - ready = !exited.has(commandId) && children.has(commandId); - } - if (settled) { - readinessInProgress = false; - return; - } - if (!ready && check.required) { - allRequiredReady = false; - emitEvent(options, write, { - type: 'error', - surface: check.id, - url: check.url, - message: `${check.label} did not become ready${check.url ? ` at ${check.url}` : ''}; keeping dev alive and retrying.`, - }); - scheduleCommandRestart(check.id); - continue; - } - if (ready) restartAttempts.set(check.id, 0); - emitEvent(options, write, { - type: 'ready', - surface: check.id, - status: ready ? 'ready' : 'degraded', - url: check.url, - message: `${check.label} is ${ready ? 'ready' : 'degraded'}${check.url ? ` at ${check.url}` : ''}.`, - }); - } - readinessInProgress = false; - if (!allRequiredReady) { - updateCurrentDevRuntimeState(plan, 'degraded', 'One or more required readiness checks failed.'); - startLiveWatch(); - return; - } - readinessComplete = true; - updateCurrentDevRuntimeState(plan, 'ready'); - if (plan.webUrl) { - emitEvent(options, write, { type: 'ready', url: plan.webUrl, message: `Treeseed dev ready at ${plan.webUrl}.` }); - } - if (shouldOpenBrowser(plan)) { - try { - await openBrowser(plan.webUrl!); - emitEvent(options, write, { type: 'open', url: plan.webUrl ?? undefined, message: `Opened ${plan.webUrl}.` }); - } catch (error) { - emitEvent(options, write, { - type: 'open', - status: 'degraded', - url: plan.webUrl ?? undefined, - message: `Could not open ${plan.webUrl}.`, - detail: error instanceof Error ? error.message : String(error), - }); - } - } - startLiveWatch(); - } - - startLiveWatch(); - if (runSetupOnce()) { - for (const command of plan.commands) { - spawnCommand(command); - } - void waitForReadiness().catch((error) => { - readinessInProgress = false; - emitEvent(options, write, { - type: 'error', - message: 'Dev readiness failed; keeping supervisor alive.', - detail: error instanceof Error ? error.message : String(error), - }); - scheduleSetupRetry('Readiness failed unexpectedly.'); - }); - } else { - scheduleSetupRetry('Initial local setup failed.'); - } - }); -} diff --git a/src/dev/configuration/runtime-configuration.ts b/src/dev/configuration/runtime-configuration.ts new file mode 100644 index 00000000..e612a277 --- /dev/null +++ b/src/dev/configuration/runtime-configuration.ts @@ -0,0 +1,257 @@ +import type { ChildProcess, SpawnOptions } from 'node:child_process'; +import { spawnSync } from 'node:child_process'; +import { createRequire } from 'node:module'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { type DevWatchController, type DevWatchEntry, type DevWatchStarter } from "../../runtime/dev-watch"; + + +export const require = createRequire(import.meta.url); + +export const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..'); + +export const DEFAULT_WEB_HOST = '127.0.0.1'; + +export const DEFAULT_WEB_PORT = 4321; + +export const DEFAULT_API_HOST = '127.0.0.1'; + +export const DEFAULT_API_PORT = 3000; + +export const DEFAULT_LOCAL_SMTP_HOST = '127.0.0.1'; + +export const DEFAULT_LOCAL_SMTP_PORT = 1025; + +export const DEFAULT_MAILPIT_UI_PORT = 8025; + +export const DEFAULT_MARKET_POSTGRES_PORT = 55432; + +export const DEFAULT_MARKET_POSTGRES_CONTAINER = 'treeseed-market-local-postgres'; + +export const DEFAULT_MARKET_POSTGRES_VOLUME = 'treeseed-market-local-postgres-data'; + +export const DEFAULT_MARKET_POSTGRES_URL = `postgres://treeseed:treeseed@127.0.0.1:${DEFAULT_MARKET_POSTGRES_PORT}/market_local`; + +export const DEV_RELOAD_FILE = 'public/__treeseed/dev-reload.json'; + +export const DEV_RUNTIME_DIR = '.treeseed/generated/dev'; + +export const DEV_RUNTIME_LEGACY_FILE = '.treeseed/generated/dev/runtime.json'; + +export const DEV_INSTANCE_DIR = '.treeseed/dev/instances'; + +export const DEV_PID_DIR = '.treeseed/dev/pids'; + +export const DEV_REPO_INDEX_RELATIVE_PATH = 'treeseed/dev-index.json'; + +export const DEFAULT_READINESS_TIMEOUT_MS = 90_000; + +export const DEFAULT_SETUP_STEP_TIMEOUT_MS = 300_000; + +export const DEFAULT_PROCESS_READY_GRACE_MS = 1_200; + +export const DEFAULT_SHUTDOWN_GRACE_MS = 2_500; + +export const DEFAULT_KILL_GRACE_MS = 500; + +export const INITIAL_RESTART_BACKOFF_MS = 1_000; + +export const MAX_RESTART_BACKOFF_MS = 15_000; + +export const SETUP_RETRY_BACKOFF_MS = 3_000; + +export type IntegratedDevSurface = 'integrated' | 'all' | 'web' | 'api' | 'manager' | 'worker' | 'agents' | 'services'; + +export type IntegratedDevSetupMode = 'auto' | 'check' | 'off'; + +export type IntegratedDevFeedbackMode = 'live' | 'restart' | 'off'; + +export type IntegratedDevOpenMode = 'auto' | 'on' | 'off'; + +export type LocalRuntimeMode = 'auto' | 'provider' | 'local'; + +export type SelectedLocalRuntime = 'astro-local' | 'cloudflare-wrangler-local' | 'node-local'; + +export type IntegratedDevCommandId = 'web' | 'api' | 'manager' | 'worker' | 'agents' | 'operations-runner'; + +export type LocalRuntimeSelection = { + requested: LocalRuntimeMode; + selected: SelectedLocalRuntime; + provider: string; + reason?: string; +}; + +export type IntegratedDevOptions = { + surface?: IntegratedDevSurface; + surfaces?: string; + watch?: boolean; + cwd?: string; + stdio?: SpawnOptions['stdio']; + env?: NodeJS.ProcessEnv; + webHost?: string; + webPort?: number; + apiHost?: string; + apiPort?: number; + webRuntime?: LocalRuntimeMode; + setupMode?: IntegratedDevSetupMode; + feedbackMode?: IntegratedDevFeedbackMode; + openMode?: IntegratedDevOpenMode; + plan?: boolean; + reset?: boolean; + force?: boolean; + forceConflicts?: boolean; + json?: boolean; + includeServices?: boolean; + projectId?: string; + teamId?: string; + readinessTimeoutMs?: number; + processReadyGraceMs?: number; + shutdownGraceMs?: number; +}; + +export type ManagedDevAction = 'start' | 'status' | 'logs' | 'stop' | 'restart'; + +export type ManagedDevOptions = IntegratedDevOptions & { + action: ManagedDevAction; + all?: boolean; + follow?: boolean; +}; + +export type IntegratedDevCommand = { + id: IntegratedDevCommandId; + label: string; + command: string; + args: string[]; + cwd: string; + env: NodeJS.ProcessEnv; + localRuntime?: LocalRuntimeSelection; +}; + +export type IntegratedDevWatchEntry = DevWatchEntry; + +export type IntegratedDevSetupStep = { + id: string; + label: string; + required: boolean; + command?: string; + args?: string[]; + status: 'planned' | 'completed' | 'skipped' | 'degraded' | 'failed'; + detail?: string; +}; + +export type IntegratedDevReadinessCheck = { + id: IntegratedDevCommand['id']; + label: string; + required: boolean; + strategy: 'http' | 'process'; + url?: string; +}; + +export type IntegratedDevResetAction = { + id: + | 'd1-state' + | 'generated-d1-state' + | 'generated-wrangler-state' + | 'legacy-local-sqlite' + | 'market-postgres' + | 'root-wrangler-state' + | 'wrangler-tmp' + | 'worker-bundle' + | 'dev-reload'; + label: string; + kind: 'path' | 'service'; + path?: string; + status: 'planned' | 'removed' | 'refreshed' | 'skipped' | 'failed'; + detail?: string; +}; + +export type IntegratedDevResetPlan = { + enabled: boolean; + actions: IntegratedDevResetAction[]; + preserved: string[]; +}; + +export type IntegratedDevPlan = { + surface: IntegratedDevSurface; + setupMode: IntegratedDevSetupMode; + feedbackMode: IntegratedDevFeedbackMode; + openMode: IntegratedDevOpenMode; + watch: boolean; + tenantRoot: string; + apiBaseUrl: string; + webUrl: string | null; + setupSteps: IntegratedDevSetupStep[]; + readyChecks: IntegratedDevReadinessCheck[]; + watchEntries: IntegratedDevWatchEntry[]; + commands: IntegratedDevCommand[]; + logPath: string; + localRuntimes: Record; + restartPolicy: { + initialBackoffMs: number; + maxBackoffMs: number; + setupRetryBackoffMs: number; + commandImplementationChangesRequireRestart: boolean; + agentChanges: 'defer'; + }; + reset: IntegratedDevResetPlan | null; +}; + +export type DevInstanceStatus = 'starting' | 'ready' | 'degraded' | 'stopped' | 'stale'; + +export type DevInstanceRecord = { + schemaVersion: 1; + kind: 'treeseed.dev.instance'; + projectRoot: string; + worktreeRoot: string; + branch: string | null; + gitCommonDir: string | null; + status: DevInstanceStatus; + pid: number | null; + processGroupId: number | null; + startedAt: string; + updatedAt: string; + ports: Record; + urls: Record; + logPath: string; + runtimeScope: string; + surfaces: IntegratedDevCommandId[]; + readyChecks: IntegratedDevReadinessCheck[]; + instancePath: string; + pidPath: string; + staleReason?: string; +}; + +export type DevIndexEntry = { + worktreeRoot: string; + instancePath: string; + branch: string | null; + pid: number | null; + runtimeScope: string; + updatedAt: string; +}; + +export type DevIndex = { + schemaVersion: 1; + kind: 'treeseed.dev.index'; + repositoryId: string; + gitCommonDir: string | null; + instances: DevIndexEntry[]; +}; + +export type SpawnLike = (command: string, args: string[], options: SpawnOptions) => ChildProcess; + +export type SpawnSyncLike = typeof spawnSync; + +export type SignalRegistrar = (signal: NodeJS.Signals, handler: () => void) => () => void; + +export type ProcessLike = Pick & Partial>; + +export type FetchLike = (url: string, init?: RequestInit) => Promise; + +export type ProcessKiller = (pid: number, signal: NodeJS.Signals) => void; + +export type ProcessStatusChecker = (pid: number) => boolean; + +export type WatchController = DevWatchController; + +export type WatchStarter = DevWatchStarter; diff --git a/src/dev/runtime/default-kill-process.ts b/src/dev/runtime/default-kill-process.ts new file mode 100644 index 00000000..67d49883 --- /dev/null +++ b/src/dev/runtime/default-kill-process.ts @@ -0,0 +1,260 @@ +import { mkdirSync, renameSync, rmSync, writeFileSync } from 'node:fs'; +import { spawnSync } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { homedir } from 'node:os'; +import { dirname, isAbsolute, relative, resolve, sep } from 'node:path'; +import { setTimeout as delay } from 'node:timers/promises'; +import { resolveMachineEnvironmentValues, runRepositoryGit } from '../../runtime/platform/workflow-support.ts'; +import type { DevPortOwner } from './integrated-dev-dependencies.ts'; +import { resetMarketPostgres, stopMarketPostgres } from '../support/attach-prefixed-log-reader.ts'; +import { DEFAULT_KILL_GRACE_MS, DEV_INSTANCE_DIR, DEV_PID_DIR, DEV_RELOAD_FILE, DEV_REPO_INDEX_RELATIVE_PATH, type ProcessKiller, type ProcessLike, type IntegratedDevCommand, type IntegratedDevPlan } from '../configuration/runtime-configuration.ts'; +import { runtimeScopeKey } from './dev-runtime-state.ts'; + +export function defaultKillProcess(pid: number, signal: NodeJS.Signals) { + process.kill(pid, signal); +} + +export function defaultProcessIsAlive(pid: number) { + if (!Number.isInteger(pid) || pid <= 0) { + return false; + } + try { + process.kill(pid, 0); + return true; + } catch { + return false; + } +} + +export function defaultInspectPortOwners(ports: readonly number[]): DevPortOwner[] { + const uniquePorts = [...new Set(ports.filter((port) => Number.isInteger(port) && port > 0))]; + if (uniquePorts.length === 0) return []; + const result = spawnSync('ss', ['-ltnp'], { encoding: 'utf8' }); + if ((result.status ?? 1) !== 0) return []; + const lines = String(result.stdout ?? '').split(/\r?\n/u); + const owners: DevPortOwner[] = []; + for (const port of uniquePorts) { + const portPattern = new RegExp(`:${port}\\b`, 'u'); + for (const line of lines) { + if (!portPattern.test(line)) continue; + const pidMatch = line.match(/pid=(\d+)/u); + const nameMatch = line.match(/users:\(\("([^"]+)"/u); + owners.push({ + port, + pid: pidMatch ? Number(pidMatch[1]) : null, + processName: nameMatch?.[1], + detail: line.trim(), + }); + } + } + return owners; +} + +export function defaultRemovePath(path: string) { + rmSync(path, { recursive: true, force: true }); +} + +export function defaultResetMarketPostgres() { + return resetMarketPostgres(process.env, { spawnSync }); +} + +export function defaultStopMarketPostgres() { + return stopMarketPostgres(process.env, { spawnSync }); +} + +export type ManagedDevProcess = { + id: IntegratedDevCommand['id']; + command: IntegratedDevCommand; + child: ProcessLike; + pid: number | null; + exited: boolean; + intentionalStop: boolean; + exitCode: number | null; + exitSignal: NodeJS.Signals | null; + resolveExit: () => void; + exitPromise: Promise; +}; + +export function createManagedDevProcess(command: IntegratedDevCommand, child: ProcessLike): ManagedDevProcess { + let resolveExit: () => void = () => {}; + const exitPromise = new Promise((resolvePromise) => { + resolveExit = resolvePromise; + }); + return { + id: command.id, + command, + child, + pid: typeof child.pid === 'number' ? child.pid : null, + exited: false, + intentionalStop: false, + exitCode: null, + exitSignal: null, + resolveExit, + exitPromise, + }; +} + +export function signalManagedProcess( + managed: ManagedDevProcess, + signal: NodeJS.Signals, + killProcess: ProcessKiller, +) { + if (managed.pid != null && process.platform !== 'win32') { + try { + killProcess(-managed.pid, signal); + return; + } catch { + // Fall through to the child handle for environments that do not expose the group. + } + } + if (typeof managed.child.kill !== 'function') { + return; + } + try { + managed.child.kill(signal); + } catch { + // Ignore shutdown races from already-exited child processes. + } +} + +export async function stopManagedProcess( + managed: ManagedDevProcess, + signal: NodeJS.Signals, + killProcess: ProcessKiller, + graceMs: number, +) { + managed.intentionalStop = true; + signalManagedProcess(managed, signal, killProcess); + if (!managed.exited) { + await Promise.race([managed.exitPromise, delay(Math.max(0, graceMs))]); + } + if (signal !== 'SIGKILL') { + signalManagedProcess(managed, 'SIGKILL', killProcess); + if (!managed.exited) { + await Promise.race([managed.exitPromise, delay(DEFAULT_KILL_GRACE_MS)]); + } + } +} + +export function writeDevReloadStampPath(outputPath: string) { + mkdirSync(dirname(outputPath), { recursive: true }); + writeFileSync( + outputPath, + `${JSON.stringify( + { + buildId: `${Date.now()}`, + updatedAt: new Date().toISOString(), + }, + null, + 2, + )}\n`, + 'utf8', + ); +} + +export function writeDevReloadStamp(projectRoot: string) { + writeDevReloadStampPath(resolve(projectRoot, DEV_RELOAD_FILE)); +} + +export function defaultWrite(line: string, stream: 'stdout' | 'stderr') { + const target = stream === 'stderr' ? process.stderr : process.stdout; + target.write(line); +} + +export function shouldRedactEnvValue(key: string) { + return /(TOKEN|SECRET|PASSWORD|PASSPHRASE|PRIVATE|CREDENTIAL|AUTH)/iu.test(key); +} + +export function redactEnvironment(env: NodeJS.ProcessEnv) { + return Object.fromEntries( + Object.entries(env).map(([key, value]) => [key, value == null || !shouldRedactEnvValue(key) ? value : '[redacted]']), + ); +} + +export function serializeDevPlanForOutput(plan: IntegratedDevPlan): IntegratedDevPlan { + return { + ...plan, + commands: plan.commands.map((command) => ({ + ...command, + env: redactEnvironment(command.env), + })), + }; +} + +export function resolveLocalMachineEnv(tenantRoot: string) { + try { + return resolveMachineEnvironmentValues(tenantRoot, 'local') as NodeJS.ProcessEnv; + } catch { + return {}; + } +} + +export function atomicWriteJson(path: string, value: unknown) { + mkdirSync(dirname(path), { recursive: true }); + const tmpPath = `${path}.tmp-${process.pid}-${Date.now()}`; + writeFileSync(tmpPath, `${JSON.stringify(value, null, 2)}\n`, 'utf8'); + renameSync(tmpPath, path); +} + +export function runGitText(cwd: string, args: string[]) { + const result = runRepositoryGit(args, { cwd, mode: 'read', allowFailure: true }); + return (result.status ?? 1) === 0 ? String(result.stdout ?? '').trim() : null; +} + +export function isInsideInternalPath(worktreeRoot: string, tenantRoot: string) { + const rel = relative(worktreeRoot, tenantRoot); + return rel.split(sep).includes('.treeseed'); +} + +export function resolveGitWorktreeInfo(tenantRoot: string) { + const resolvedWorktreeRoot = runGitText(tenantRoot, ['rev-parse', '--show-toplevel']); + const isTemporaryRoot = resolvedWorktreeRoot + ? relative(resolve(resolvedWorktreeRoot, '.treeseed', 'tmp'), resolve(tenantRoot)).split(sep)[0] !== '..' + : false; + if (!resolvedWorktreeRoot || isInsideInternalPath(resolvedWorktreeRoot, tenantRoot) || isTemporaryRoot) { + return { worktreeRoot: tenantRoot, gitCommonDir: null, branch: null }; + } + const worktreeRoot = resolvedWorktreeRoot; + const rawCommonDir = runGitText(tenantRoot, ['rev-parse', '--git-common-dir']); + const gitCommonDir = rawCommonDir + ? (isAbsolute(rawCommonDir) ? rawCommonDir : resolve(tenantRoot, rawCommonDir)) + : null; + const rawBranch = runGitText(tenantRoot, ['rev-parse', '--abbrev-ref', 'HEAD']); + const branch = rawBranch && rawBranch !== 'HEAD' ? rawBranch : null; + return { worktreeRoot, gitCommonDir, branch }; +} + +export function repositoryIndexId(tenantRoot: string, gitCommonDir: string | null) { + const source = gitCommonDir ?? tenantRoot; + return createHash('sha256').update(source).digest('hex').slice(0, 16); +} + +export function worktreeInstanceSuffix(tenantRoot: string) { + return createHash('sha256').update(resolve(tenantRoot)).digest('hex').slice(0, 10); +} + +export function repoFamilyIndexPath(tenantRoot: string, gitCommonDir: string | null) { + if (gitCommonDir) { + return resolve(gitCommonDir, DEV_REPO_INDEX_RELATIVE_PATH); + } + const cacheRoot = process.env.XDG_CACHE_HOME + ? resolve(process.env.XDG_CACHE_HOME, 'treeseed', 'dev-instances') + : resolve(homedir(), '.cache', 'treeseed', 'dev-instances'); + return resolve(cacheRoot, `${repositoryIndexId(tenantRoot, null)}.json`); +} + +export function instanceRuntimeScope(plan: Pick) { + return runtimeScopeKey(plan.commands.map((command) => command.id)); +} + +export function devInstanceDir(tenantRoot: string) { + return resolve(tenantRoot, DEV_INSTANCE_DIR); +} + +export function devPidDir(tenantRoot: string) { + return resolve(tenantRoot, DEV_PID_DIR); +} + +export function devInstancePath(tenantRoot: string, runtimeScope: string) { + return resolve(devInstanceDir(tenantRoot), `${runtimeScope}.json`); +} diff --git a/src/dev/runtime/dev-pid-path.ts b/src/dev/runtime/dev-pid-path.ts new file mode 100644 index 00000000..0562eaf7 --- /dev/null +++ b/src/dev/runtime/dev-pid-path.ts @@ -0,0 +1,254 @@ +import { mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { atomicWriteJson, devInstanceDir, devInstancePath, devPidDir, instanceRuntimeScope, repoFamilyIndexPath, repositoryIndexId, resolveGitWorktreeInfo, worktreeInstanceSuffix } from './default-kill-process.ts'; +import { DEFAULT_API_PORT, DEFAULT_MARKET_POSTGRES_PORT, DEFAULT_WEB_PORT, type ProcessStatusChecker, type DevIndex, type DevIndexEntry, type DevInstanceRecord, type DevInstanceStatus, type IntegratedDevPlan, type ManagedDevOptions } from '../configuration/runtime-configuration.ts'; +import { parsePortFromUrl } from './dev-runtime-state.ts'; +import type { IntegratedDevDependencies } from './integrated-dev-dependencies.ts'; + +export function devPidPath(tenantRoot: string, runtimeScope: string) { + return resolve(devPidDir(tenantRoot), `${runtimeScope}.pid`); +} + +export function portsFromPlan(plan: IntegratedDevPlan) { + return Object.fromEntries( + Object.entries({ + web: parsePortFromUrl(plan.webUrl ?? undefined), + api: parsePortFromUrl(plan.apiBaseUrl), + mailpitSmtp: Number(plan.commands[0]?.env.TREESEED_MAILPIT_SMTP_PORT ?? '') || null, + postgres: Number(plan.commands[0]?.env.TREESEED_MARKET_LOCAL_POSTGRES_PORT ?? '') || null, + }).filter((entry): entry is [string, number] => Number.isInteger(entry[1]) && entry[1] > 0), + ); +} + +export function urlsFromPlan(plan: IntegratedDevPlan) { + return Object.fromEntries( + Object.entries({ + web: plan.webUrl, + api: plan.apiBaseUrl, + apiHealth: `${plan.apiBaseUrl.replace(/\/+$/u, '')}/healthz`, + }).filter((entry): entry is [string, string] => typeof entry[1] === 'string' && entry[1].length > 0), + ); +} + +export function createDevInstanceRecord( + plan: IntegratedDevPlan, + status: DevInstanceStatus, + pid: number | null, + processGroupId: number | null = null, + startedAt = new Date().toISOString(), +): DevInstanceRecord { + const runtimeScope = instanceRuntimeScope(plan); + const git = resolveGitWorktreeInfo(plan.tenantRoot); + return { + schemaVersion: 1, + kind: 'treeseed.dev.instance', + projectRoot: plan.tenantRoot, + worktreeRoot: git.worktreeRoot, + branch: git.branch, + gitCommonDir: git.gitCommonDir, + status, + pid, + processGroupId, + startedAt, + updatedAt: new Date().toISOString(), + ports: portsFromPlan(plan), + urls: urlsFromPlan(plan), + logPath: plan.logPath, + runtimeScope, + surfaces: plan.commands.map((command) => command.id), + readyChecks: plan.readyChecks, + instancePath: devInstancePath(plan.tenantRoot, runtimeScope), + pidPath: devPidPath(plan.tenantRoot, runtimeScope), + }; +} + +export function readDevInstanceFile(path: string): DevInstanceRecord | null { + try { + const parsed = JSON.parse(readFileSync(path, 'utf8')) as Partial; + if (parsed.kind !== 'treeseed.dev.instance' || typeof parsed.projectRoot !== 'string' || typeof parsed.instancePath !== 'string') { + return null; + } + return parsed as DevInstanceRecord; + } catch { + return null; + } +} + +export function writeDevInstance(record: DevInstanceRecord) { + const next = { ...record, updatedAt: new Date().toISOString() }; + atomicWriteJson(next.instancePath, next); + mkdirSync(dirname(next.pidPath), { recursive: true }); + if (next.pid) { + writeFileSync(next.pidPath, `${next.pid}\n`, 'utf8'); + } + writeRepoFamilyIndexEntry(next); + return next; +} + +export function readRepoFamilyIndex(tenantRoot: string, gitCommonDir: string | null): DevIndex { + const path = repoFamilyIndexPath(tenantRoot, gitCommonDir); + try { + const parsed = JSON.parse(readFileSync(path, 'utf8')) as Partial; + if (parsed.kind === 'treeseed.dev.index' && Array.isArray(parsed.instances)) { + return parsed as DevIndex; + } + } catch { + // Create a fresh index below. + } + return { + schemaVersion: 1, + kind: 'treeseed.dev.index', + repositoryId: repositoryIndexId(tenantRoot, gitCommonDir), + gitCommonDir, + instances: [], + }; +} + +export function writeRepoFamilyIndex(index: DevIndex, tenantRoot: string, gitCommonDir: string | null) { + atomicWriteJson(repoFamilyIndexPath(tenantRoot, gitCommonDir), index); +} + +export function writeRepoFamilyIndexEntry(record: DevInstanceRecord) { + const index = readRepoFamilyIndex(record.projectRoot, record.gitCommonDir); + const entry: DevIndexEntry = { + worktreeRoot: record.worktreeRoot, + instancePath: record.instancePath, + branch: record.branch, + pid: record.pid, + runtimeScope: record.runtimeScope, + updatedAt: record.updatedAt, + }; + const instances = index.instances.filter((candidate) => + !(candidate.worktreeRoot === entry.worktreeRoot && candidate.runtimeScope === entry.runtimeScope), + ); + instances.push(entry); + writeRepoFamilyIndex({ ...index, instances }, record.projectRoot, record.gitCommonDir); +} + +export function removeRepoFamilyIndexEntry(record: DevInstanceRecord) { + const index = readRepoFamilyIndex(record.projectRoot, record.gitCommonDir); + writeRepoFamilyIndex({ + ...index, + instances: index.instances.filter((candidate) => + !(candidate.worktreeRoot === record.worktreeRoot && candidate.runtimeScope === record.runtimeScope), + ), + }, record.projectRoot, record.gitCommonDir); +} + +export function listWorktreeDevInstances(tenantRoot: string) { + try { + return readdirSync(devInstanceDir(tenantRoot)) + .filter((entry) => entry.endsWith('.json')) + .map((entry) => readDevInstanceFile(resolve(devInstanceDir(tenantRoot), entry))) + .filter((entry): entry is DevInstanceRecord => Boolean(entry)); + } catch { + return []; + } +} + +export function listRepoFamilyDevInstances(tenantRoot: string) { + const git = resolveGitWorktreeInfo(tenantRoot); + const index = readRepoFamilyIndex(tenantRoot, git.gitCommonDir); + return index.instances + .map((entry) => readDevInstanceFile(entry.instancePath)) + .filter((entry): entry is DevInstanceRecord => Boolean(entry)); +} + +export function evaluateDevInstance( + record: DevInstanceRecord, + deps: Pick, +): DevInstanceRecord { + if (!record.pid || !deps.processIsAlive(record.pid)) { + return { ...record, status: 'stale', staleReason: record.pid ? `Process ${record.pid} is not running.` : 'No supervisor pid is recorded.' }; + } + return record; +} + +export function removeDevInstanceRecord(record: DevInstanceRecord) { + rmSync(record.instancePath, { force: true }); + rmSync(record.pidPath, { force: true }); + removeRepoFamilyIndexEntry(record); +} + +export function usedPortsFromInstances(records: readonly DevInstanceRecord[], processIsAlive: ProcessStatusChecker) { + const ports = new Set(); + for (const record of records) { + const evaluated = evaluateDevInstance(record, { processIsAlive }); + if (evaluated.status === 'stale') continue; + for (const port of Object.values(record.ports)) { + if (Number.isInteger(port) && port > 0) ports.add(port); + } + } + return ports; +} + +export function managedPortBlock(blockIndex: number) { + const offset = blockIndex * 10; + return { + web: DEFAULT_WEB_PORT + offset, + api: DEFAULT_API_PORT + offset, + postgres: DEFAULT_MARKET_POSTGRES_PORT + offset, + }; +} + +export function resolveManagedPortOverrides( + tenantRoot: string, + options: ManagedDevOptions, + deps: Pick, +) { + const existing = listWorktreeDevInstances(tenantRoot) + .map((record) => evaluateDevInstance(record, deps)) + .find((record) => record.status !== 'stale'); + if (existing && options.webPort == null && options.apiPort == null) { + return { + webPort: existing.ports.web, + apiPort: existing.ports.api, + env: { + TREESEED_MARKET_LOCAL_POSTGRES_PORT: existing.ports.postgres ? String(existing.ports.postgres) : undefined, + TREESEED_MARKET_LOCAL_POSTGRES_CONTAINER: `treeseed-market-local-postgres-${worktreeInstanceSuffix(tenantRoot)}`, + TREESEED_MARKET_LOCAL_POSTGRES_VOLUME: `treeseed-market-local-postgres-data-${worktreeInstanceSuffix(tenantRoot)}`, + } as NodeJS.ProcessEnv, + }; + } + + const repoInstances = listRepoFamilyDevInstances(tenantRoot); + const usedPorts = usedPortsFromInstances(repoInstances, deps.processIsAlive); + for (const owner of deps.inspectPortOwners([ + DEFAULT_WEB_PORT, + DEFAULT_API_PORT, + DEFAULT_MARKET_POSTGRES_PORT, + ])) { + if (owner.pid) usedPorts.add(owner.port); + } + + for (let block = 0; block < 100; block += 1) { + const candidate = managedPortBlock(block); + const requestedWeb = options.webPort ?? candidate.web; + const requestedApi = options.apiPort ?? candidate.api; + const candidatePorts = [requestedWeb, requestedApi, candidate.postgres]; + const liveOwners = deps.inspectPortOwners(candidatePorts).filter((owner) => owner.pid && owner.pid !== process.pid); + const blocked = candidatePorts.some((port) => usedPorts.has(port)) || liveOwners.length > 0; + if (!blocked || options.forceConflicts === true) { + return { + webPort: requestedWeb, + apiPort: requestedApi, + env: { + TREESEED_MARKET_LOCAL_POSTGRES_PORT: String(candidate.postgres), + TREESEED_MARKET_LOCAL_POSTGRES_CONTAINER: `treeseed-market-local-postgres-${worktreeInstanceSuffix(tenantRoot)}`, + TREESEED_MARKET_LOCAL_POSTGRES_VOLUME: `treeseed-market-local-postgres-data-${worktreeInstanceSuffix(tenantRoot)}`, + } as NodeJS.ProcessEnv, + }; + } + } + + throw new Error('Unable to allocate a free Treeseed dev port block for this worktree.'); +} + +export function renderManagedDevStatus(records: readonly DevInstanceRecord[]) { + if (records.length === 0) return 'No managed Treeseed dev instances found.'; + return records.map((record) => { + const url = record.urls.web ?? record.urls.api ?? '(no url)'; + const branch = record.branch ? ` ${record.branch}` : ''; + return `${record.status.padEnd(8)} pid ${record.pid ?? '-'}${branch} ${url} log ${record.logPath}`; + }).join('\n'); +} diff --git a/src/dev/runtime/dev-runtime-state.ts b/src/dev/runtime/dev-runtime-state.ts new file mode 100644 index 00000000..53965331 --- /dev/null +++ b/src/dev/runtime/dev-runtime-state.ts @@ -0,0 +1,258 @@ +import { mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { setTimeout as delay } from 'node:timers/promises'; +import { DEFAULT_KILL_GRACE_MS, DEFAULT_SHUTDOWN_GRACE_MS, DEV_RUNTIME_DIR, DEV_RUNTIME_LEGACY_FILE, type ProcessStatusChecker, type DevInstanceStatus, type IntegratedDevCommandId, type IntegratedDevOptions, type IntegratedDevPlan } from '../configuration/runtime-configuration.ts'; +import { ALL_COMMAND_IDS, CANONICAL_COMMAND_IDS, type DevPortOwner, type IntegratedDevDependencies } from './integrated-dev-dependencies.ts'; +import { createDevInstanceRecord, readDevInstanceFile, removeDevInstanceRecord, writeDevInstance } from './dev-pid-path.ts'; +import { devInstancePath, instanceRuntimeScope } from './default-kill-process.ts'; +import { emitEvent } from './prepare-dev-runtime-slots.ts'; + +export type DevRuntimeState = { + pid: number; + tenantRoot: string; + startedAt: string; + commandIds?: IntegratedDevCommandId[]; + statePath?: string; +}; + +export function devRuntimeStateDir(tenantRoot: string) { + return resolve(tenantRoot, DEV_RUNTIME_DIR); +} + +export function devRuntimeStatePath(tenantRoot: string, key: string) { + return resolve(devRuntimeStateDir(tenantRoot), `runtime-${key}.json`); +} + +export function legacyDevRuntimeStatePath(tenantRoot: string) { + return resolve(tenantRoot, DEV_RUNTIME_LEGACY_FILE); +} + +export function runtimeScopeKey(commandIds: readonly IntegratedDevCommandId[]) { + const selected = CANONICAL_COMMAND_IDS.filter((id) => commandIds.includes(id)); + return selected.length > 0 ? selected.join('-') : 'integrated'; +} + +export function readDevRuntimeStateFile(path: string): DevRuntimeState | null { + try { + const parsed = JSON.parse(readFileSync(path, 'utf8')) as Partial; + if (!Number.isInteger(parsed.pid) || typeof parsed.tenantRoot !== 'string' || typeof parsed.startedAt !== 'string') { + return null; + } + const commandIds = Array.isArray(parsed.commandIds) + ? parsed.commandIds.filter((id): id is IntegratedDevCommandId => ALL_COMMAND_IDS.includes(id as IntegratedDevCommandId)) + : undefined; + return { + pid: parsed.pid!, + tenantRoot: parsed.tenantRoot, + startedAt: parsed.startedAt, + ...(commandIds ? { commandIds } : {}), + statePath: path, + }; + } catch { + return null; + } +} + +export function listDevRuntimeStates(tenantRoot: string) { + const states: DevRuntimeState[] = []; + const legacy = readDevRuntimeStateFile(legacyDevRuntimeStatePath(tenantRoot)); + if (legacy) { + states.push(legacy); + } + try { + for (const entry of readdirSync(devRuntimeStateDir(tenantRoot))) { + if (!entry.startsWith('runtime-') || !entry.endsWith('.json')) { + continue; + } + const state = readDevRuntimeStateFile(resolve(devRuntimeStateDir(tenantRoot), entry)); + if (state) { + states.push(state); + } + } + } catch { + // No active dev state directory yet. + } + return states; +} + +export function runtimeStateOverlaps(state: DevRuntimeState, commandIds: readonly IntegratedDevCommandId[]) { + if (!state.commandIds || state.commandIds.length === 0) { + return true; + } + return state.commandIds.some((id) => commandIds.includes(id)); +} + +export function listLiveOverlappingDevRuntimeStates( + tenantRoot: string, + commandIds: readonly IntegratedDevCommandId[], + processIsAlive: ProcessStatusChecker, +) { + const live: DevRuntimeState[] = []; + for (const state of listDevRuntimeStates(tenantRoot)) { + const statePath = state.statePath; + if (!statePath || !runtimeStateOverlaps(state, commandIds)) { + continue; + } + if (state.pid === process.pid) { + continue; + } + if (!processIsAlive(state.pid)) { + rmSync(statePath, { force: true }); + continue; + } + live.push(state); + } + return live; +} + +export function parsePortFromUrl(value: string | undefined) { + if (!value) return null; + try { + const url = new URL(value); + const port = Number(url.port || (url.protocol === 'https:' ? 443 : 80)); + return Number.isInteger(port) && port > 0 ? port : null; + } catch { + return null; + } +} + +export function requiredDevPorts(plan: IntegratedDevPlan) { + const ports: number[] = []; + for (const command of plan.commands) { + if (command.id === 'web') { + const port = parsePortFromUrl(plan.webUrl ?? undefined); + if (port) ports.push(port); + } + if (command.id === 'api') { + const port = parsePortFromUrl(plan.apiBaseUrl); + if (port) ports.push(port); + } + } + return [...new Set(ports)]; +} + +export function formatPortOwner(owner: DevPortOwner) { + return `port ${owner.port}${owner.pid ? ` pid ${owner.pid}` : ''}${owner.processName ? ` (${owner.processName})` : ''}`; +} + +export function writeCurrentDevRuntimeState(plan: IntegratedDevPlan, status: DevInstanceStatus = 'starting') { + const tenantRoot = plan.tenantRoot; + const commandIds = plan.commands.map((command) => command.id); + const outputPath = devRuntimeStatePath(tenantRoot, runtimeScopeKey(commandIds)); + mkdirSync(dirname(outputPath), { recursive: true }); + writeFileSync( + outputPath, + `${JSON.stringify({ + pid: process.pid, + tenantRoot, + commandIds, + startedAt: new Date().toISOString(), + }, null, 2)}\n`, + 'utf8', + ); + const managedProcessGroupId = process.env.TREESEED_MANAGED_DEV_INSTANCE === '1' && process.platform !== 'win32' + ? process.pid + : null; + writeDevInstance(createDevInstanceRecord(plan, status, process.pid, managedProcessGroupId)); + return outputPath; +} + +export function updateCurrentDevRuntimeState(plan: IntegratedDevPlan, status: DevInstanceStatus, staleReason?: string) { + const existing = readDevInstanceFile(devInstancePath(plan.tenantRoot, instanceRuntimeScope(plan))); + if (!existing || existing.pid !== process.pid) { + return; + } + writeDevInstance({ ...existing, status, staleReason }); +} + +export function removeCurrentDevRuntimeState(plan: IntegratedDevPlan) { + const tenantRoot = plan.tenantRoot; + const commandIds = plan.commands.map((command) => command.id); + const statePath = devRuntimeStatePath(tenantRoot, runtimeScopeKey(commandIds)); + const state = readDevRuntimeStateFile(statePath); + if (!state || state.pid !== process.pid) { + // Still try the richer instance record; a legacy state race should not strand it. + } else { + rmSync(statePath, { force: true }); + } + const instance = readDevInstanceFile(devInstancePath(tenantRoot, instanceRuntimeScope(plan))); + if (instance?.pid === process.pid) { + removeDevInstanceRecord(instance); + } +} + +export async function waitForProcessExit(pid: number, processIsAlive: ProcessStatusChecker, timeoutMs: number) { + const startedAt = Date.now(); + while (Date.now() - startedAt < timeoutMs) { + if (!processIsAlive(pid)) { + return true; + } + await delay(100); + } + return !processIsAlive(pid); +} + +export async function stopPreviousDevRuntimes( + tenantRoot: string, + commandIds: readonly IntegratedDevCommandId[], + options: Pick, + deps: Pick, +) { + for (const state of listLiveOverlappingDevRuntimeStates(tenantRoot, commandIds, deps.processIsAlive)) { + const statePath = state.statePath; + if (!statePath) continue; + + emitEvent(options, deps.write, { + type: 'replace', + message: `Stopping previous Treeseed dev runtime (${state.pid}) before starting overlapping surfaces.`, + detail: { pid: state.pid, startedAt: state.startedAt, commandIds: state.commandIds ?? null }, + }); + + try { + deps.killProcess(state.pid, 'SIGTERM'); + } catch { + // The runtime may have exited after the liveness check. + } + if (await waitForProcessExit(state.pid, deps.processIsAlive, options.shutdownGraceMs ?? DEFAULT_SHUTDOWN_GRACE_MS)) { + rmSync(statePath, { force: true }); + continue; + } + + try { + deps.killProcess(state.pid, 'SIGKILL'); + } catch { + // Ignore shutdown races from already-exited supervisors. + } + await waitForProcessExit(state.pid, deps.processIsAlive, DEFAULT_KILL_GRACE_MS); + rmSync(statePath, { force: true }); + } +} + +export async function stopPortOwners( + owners: readonly DevPortOwner[], + options: Pick, + deps: Pick, +) { + const pids = [...new Set(owners.map((owner) => owner.pid).filter((pid): pid is number => Number.isInteger(pid) && pid > 0 && pid !== process.pid))]; + for (const pid of pids) { + emitEvent(options, deps.write, { + type: 'replace', + message: `Stopping service on required dev port (pid ${pid}).`, + detail: owners.filter((owner) => owner.pid === pid), + }); + try { + deps.killProcess(pid, 'SIGTERM'); + } catch { + // Ignore races with processes that exited after inspection. + } + if (await waitForProcessExit(pid, deps.processIsAlive, options.shutdownGraceMs ?? DEFAULT_SHUTDOWN_GRACE_MS)) { + continue; + } + try { + deps.killProcess(pid, 'SIGKILL'); + } catch { + // Ignore shutdown races. + } + await waitForProcessExit(pid, deps.processIsAlive, DEFAULT_KILL_GRACE_MS); + } +} diff --git a/src/dev/runtime/integrated-dev-dependencies.ts b/src/dev/runtime/integrated-dev-dependencies.ts new file mode 100644 index 00000000..025417e6 --- /dev/null +++ b/src/dev/runtime/integrated-dev-dependencies.ts @@ -0,0 +1,264 @@ +import { existsSync, readFileSync } from 'node:fs'; +import { spawn, spawnSync } from 'node:child_process'; +import { dirname, isAbsolute, resolve } from 'node:path'; +import { loadDeployConfigFromPath } from '../../runtime/platform/deploy-config.ts'; +import { packageRoot, require, type FetchLike, type ProcessKiller, type ProcessStatusChecker, type SignalRegistrar, type SpawnLike, type SpawnSyncLike, type IntegratedDevCommandId, type IntegratedDevFeedbackMode, type IntegratedDevOpenMode, type IntegratedDevSetupMode, type IntegratedDevSurface, type LocalRuntimeMode, type LocalRuntimeSelection, type WatchStarter } from '../configuration/runtime-configuration.ts'; +import { resetMarketPostgres, stopMarketPostgres } from '../support/attach-prefixed-log-reader.ts'; + +export type IntegratedDevDependencies = { + spawn: SpawnLike; + spawnSync: SpawnSyncLike; + onSignal: SignalRegistrar; + prepareEnvironment: (tenantRoot: string) => void; + fetch: FetchLike; + killProcess: ProcessKiller; + processIsAlive: ProcessStatusChecker; + write: (line: string, stream: 'stdout' | 'stderr') => void; + openBrowser: (url: string) => void | Promise; + startWatch: WatchStarter; + removePath: (path: string) => void; + stopMailpitContainers: () => boolean; + resetMarketPostgres: () => boolean; + stopMarketPostgres: () => boolean; + inspectPortOwners: (ports: readonly number[]) => DevPortOwner[]; +}; + +export type ManagedStartDependencies = Pick; + +export type DevPortOwner = { + port: number; + pid: number | null; + processName?: string; + detail: string; +}; + +export type DevEvent = { + type: + | 'plan' + | 'setup' + | 'spawn' + | 'log' + | 'ready' + | 'restart' + | 'reset' + | 'reload' + | 'open' + | 'error' + | 'replace' + | 'shutdown'; + surface?: string; + message?: string; + status?: string; + url?: string; + command?: string; + args?: string[]; + exitCode?: number; + signal?: string | null; + detail?: unknown; +}; + +export function resolvePackageRoot(packageName: string, tenantRoot: string) { + const paths = [tenantRoot, packageRoot, process.cwd()]; + let resolvedPath: string; + try { + resolvedPath = require.resolve(packageName, { paths }); + } catch (error) { + if (packageName !== '@treeseed/sdk') throw error; + resolvedPath = require.resolve('@treeseed/sdk/operator-contracts', { paths }); + } + let currentDir = dirname(resolvedPath); + while (!existsSync(resolve(currentDir, 'package.json'))) { + const parentDir = dirname(currentDir); + if (parentDir === currentDir) { + throw new Error(`Unable to resolve package root for "${packageName}".`); + } + currentDir = parentDir; + } + return currentDir; +} + +export function resolveOptionalPackageRoot(packageName: string, tenantRoot: string) { + try { + return resolvePackageRoot(packageName, tenantRoot); + } catch { + return null; + } +} + +export function resolvePackageRootEnvOverride(env: NodeJS.ProcessEnv, envName: string, tenantRoot: string) { + const value = env[envName]?.trim(); + if (!value) return null; + const root = isAbsolute(value) ? value : resolve(tenantRoot, value); + if (!existsSync(resolve(root, 'package.json'))) { + throw new Error(`${envName} must point to a package root containing package.json.`); + } + return root; +} + +export function resolveNodeEntrypoint(packageDir: string, sourceRelativePath: string, distRelativePath: string) { + const sourcePath = resolve(packageDir, sourceRelativePath); + + if (existsSync(sourcePath)) { + return { + command: 'tsx', + args: [sourcePath], + }; + } + + return { + command: process.execPath, + args: [resolve(packageDir, distRelativePath)], + }; +} + +export function resolveOptionalScriptEntrypoint(packageDir: string, sourceRelativePath: string, distRelativePath: string) { + const sourcePath = resolve(packageDir, sourceRelativePath); + if (existsSync(sourcePath)) { + return { + command: 'tsx', + args: [sourcePath], + }; + } + const distPath = resolve(packageDir, distRelativePath); + if (existsSync(distPath)) { + return { + command: process.execPath, + args: [distPath], + }; + } + return null; +} + +export function normalizePort(value: number | undefined, fallback: number) { + return Number.isInteger(value) && Number(value) > 0 ? Number(value) : fallback; +} + +export function normalizeSetupMode(value: IntegratedDevSetupMode | undefined) { + return value ?? 'auto'; +} + +export function normalizeFeedbackMode(value: IntegratedDevFeedbackMode | undefined) { + return value ?? 'live'; +} + +export function normalizeOpenMode(value: IntegratedDevOpenMode | undefined) { + return value ?? 'off'; +} + +export function normalizeLocalRuntimeMode(value: unknown): LocalRuntimeMode { + return value === 'provider' || value === 'local' ? value : 'auto'; +} + +export function normalizeProvider(value: unknown, fallback = 'local') { + return typeof value === 'string' && value.trim().length > 0 ? value.trim() : fallback; +} + +export function unsupportedProviderRuntimeMessage(kind: string, name: string, provider: string) { + return [ + `Local provider runtime is not supported for ${kind} "${name}" with provider "${provider}".`, + `Set ${kind === 'surface' ? 'surfaces' : 'services'}.${name}.local.runtime to "auto" or "local" in treeseed.site.yaml,`, + 'or add a provider-local adapter before requiring provider runtime.', + ].join(' '); +} + +export function fallbackWebProviderFromDeployConfig(deployConfig: unknown) { + const record = deployConfig && typeof deployConfig === 'object' + ? deployConfig as { providers?: { deploy?: unknown } } + : {}; + return normalizeProvider(record.providers?.deploy, 'local'); +} + +export function selectWebLocalRuntime( + surfaceConfig: unknown, + providerFallback = 'local', + overrideRuntime?: LocalRuntimeMode, +): LocalRuntimeSelection { + const record = surfaceConfig && typeof surfaceConfig === 'object' ? surfaceConfig as { + provider?: unknown; + local?: { runtime?: unknown }; + } : {}; + const provider = normalizeProvider(record.provider, providerFallback); + const requested = overrideRuntime ?? normalizeLocalRuntimeMode(record.local?.runtime); + if (provider === 'cloudflare' && requested !== 'local') { + return { + requested, + provider, + selected: 'cloudflare-wrangler-local', + reason: 'Cloudflare web surfaces support local Wrangler runtime.', + }; + } + if (requested === 'provider') { + throw new Error(unsupportedProviderRuntimeMessage('surface', 'web', provider)); + } + return { + requested, + provider, + selected: 'astro-local', + reason: overrideRuntime === 'local' + ? 'CLI override selected the full local Astro runtime for faster UI development.' + : requested === 'local' + ? 'Configured to use the full local Astro runtime.' + : `Provider "${provider}" has no provider-local web runtime; using Astro local.`, + }; +} + +export function loadDevDeployConfig(tenantRoot: string) { + try { + return loadDeployConfigFromPath(resolve(tenantRoot, 'treeseed.site.yaml')); + } catch { + return null; + } +} + +export function generatedLocalWranglerPath(tenantRoot: string) { + return resolve(tenantRoot, '.treeseed', 'generated', 'environments', 'local', 'wrangler.toml'); +} + +export function browserHost(host: string) { + return host === '0.0.0.0' || host === '::' || host === '[::]' ? '127.0.0.1' : host; +} + +export function webUrlFor(host: string, port: number) { + return `http://${browserHost(host)}:${port}`; +} + +export const CANONICAL_COMMAND_IDS: IntegratedDevCommandId[] = ['web', 'api', 'manager', 'worker', 'agents']; + +export const ALL_COMMAND_IDS: IntegratedDevCommandId[] = ['web', 'api', 'manager', 'worker', 'agents', 'operations-runner']; + +export const MARKET_DEV_COMMAND_IDS: IntegratedDevCommandId[] = ['web', 'api', 'operations-runner']; + +export function isMarketWorkspace(tenantRoot: string) { + try { + const pkg = JSON.parse(readFileSync(resolve(tenantRoot, 'package.json'), 'utf8')) as { name?: unknown }; + const apiPackageRoot = resolve(tenantRoot, 'packages/api'); + return pkg.name === '@treeseed/market' + && existsSync(resolve(apiPackageRoot, 'package.json')) + && existsSync(resolve(apiPackageRoot, 'src/api/server.ts')) + && existsSync(resolve(apiPackageRoot, 'src/operations-runner/entrypoint.ts')); + } catch { + return false; + } +} + +export function surfaceCommandIds(surface: IntegratedDevSurface): IntegratedDevCommandId[] { + switch (surface) { + case 'web': + return ['web']; + case 'api': + return ['api']; + case 'manager': + return ['manager']; + case 'worker': + return ['worker']; + case 'agents': + return ['agents']; + case 'services': + return ['api', 'manager', 'worker', 'agents']; + case 'all': + return ['web', 'api', 'manager', 'worker']; + case 'integrated': + default: + return ['web', 'api', 'manager', 'worker']; + } +} diff --git a/src/dev/runtime/prepare-dev-runtime-slots.ts b/src/dev/runtime/prepare-dev-runtime-slots.ts new file mode 100644 index 00000000..695126ce --- /dev/null +++ b/src/dev/runtime/prepare-dev-runtime-slots.ts @@ -0,0 +1,275 @@ +import { appendFileSync, existsSync, mkdirSync } from 'node:fs'; +import { dirname } from 'node:path'; +import type { IntegratedDevOptions, IntegratedDevPlan, IntegratedDevResetAction, IntegratedDevResetPlan } from '../configuration/runtime-configuration.ts'; +import type { DevEvent, IntegratedDevDependencies } from './integrated-dev-dependencies.ts'; +import { formatPortOwner, listLiveOverlappingDevRuntimeStates, requiredDevPorts, stopPortOwners, stopPreviousDevRuntimes } from './dev-runtime-state.ts'; +import { resetMarketPostgres } from '../support/attach-prefixed-log-reader.ts'; +import { serializeDevPlanForOutput, writeDevReloadStampPath } from './default-kill-process.ts'; + +export async function prepareDevRuntimeSlots( + plan: IntegratedDevPlan, + options: Pick, + deps: Pick, +) { + const commandIds = plan.commands.map((command) => command.id); + const liveRuntimeStates = listLiveOverlappingDevRuntimeStates(plan.tenantRoot, commandIds, deps.processIsAlive); + const ports = requiredDevPorts(plan); + const portOwners = deps.inspectPortOwners(ports) + .filter((owner) => owner.pid !== process.pid); + if (options.force !== true) { + if (liveRuntimeStates.length > 0 || portOwners.length > 0) { + emitEvent(options, deps.write, { + type: 'error', + status: 'existing-service', + message: [ + 'Treeseed dev found an existing runtime or service on a required port.', + 'Stop it first, or rerun with --force to terminate overlapping Treeseed dev services and port owners.', + ].join(' '), + detail: { + runtimes: liveRuntimeStates.map((state) => ({ + pid: state.pid, + startedAt: state.startedAt, + commandIds: state.commandIds ?? null, + statePath: state.statePath ?? null, + })), + ports: portOwners.map((owner) => ({ ...owner, label: formatPortOwner(owner) })), + }, + }); + return false; + } + return true; + } + + await stopPreviousDevRuntimes(plan.tenantRoot, commandIds, options, deps); + if (portOwners.length > 0) { + const ownersWithoutPid = portOwners.filter((owner) => owner.pid == null); + if (ownersWithoutPid.length > 0) { + emitEvent(options, deps.write, { + type: 'error', + status: 'existing-service', + message: `Cannot force-stop required dev ports because some listeners did not expose process ids: ${ownersWithoutPid.map(formatPortOwner).join(', ')}.`, + detail: ownersWithoutPid, + }); + return false; + } + await stopPortOwners(portOwners, options, deps); + } + const remainingPortOwners = deps.inspectPortOwners(ports) + .filter((owner) => owner.pid !== process.pid); + if (remainingPortOwners.length > 0) { + emitEvent(options, deps.write, { + type: 'error', + status: 'existing-service', + message: `Required dev ports are still occupied after --force: ${remainingPortOwners.map(formatPortOwner).join(', ')}.`, + detail: remainingPortOwners, + }); + return false; + } + return true; +} + +export function emitEvent( + options: Pick, + write: IntegratedDevDependencies['write'], + event: DevEvent, + stream: 'stdout' | 'stderr' = event.type === 'error' ? 'stderr' : 'stdout', +) { + if (options.json) { + write(`${JSON.stringify({ schemaVersion: 1, kind: 'treeseed.dev.event', ...event })}\n`, stream); + return; + } + const surface = event.surface ? `[${event.surface}]` : event.type === 'setup' ? '[setup]' : '[dev]'; + const message = event.message ?? event.detail ?? event.status ?? ''; + write(`${surface} ${String(message)}\n`, stream); +} + +export function createDevLogWrite( + baseWrite: IntegratedDevDependencies['write'], + logPath: string, +): IntegratedDevDependencies['write'] { + mkdirSync(dirname(logPath), { recursive: true }); + appendFileSync(logPath, `[dev] Log session started at ${new Date().toISOString()}.\n`, 'utf8'); + const suppressBaseWrite = process.env.TREESEED_MANAGED_DEV_SUPPRESS_STDIO === '1'; + return (line, stream) => { + if (!suppressBaseWrite) { + baseWrite(line, stream); + } + appendFileSync(logPath, line, 'utf8'); + }; +} + +export function runIntegratedDevReset( + reset: IntegratedDevResetPlan | null, + options: Pick, + deps: Pick, +) { + if (!reset?.enabled) { + return null; + } + const results = reset.actions.map((action) => { + if (action.status === 'skipped') { + emitEvent(options, deps.write, { + type: 'reset', + status: action.status, + message: `${action.label}: skipped`, + detail: action, + }); + return action; + } + if (action.kind === 'service') { + const stopped = action.id === 'market-postgres' + ? deps.resetMarketPostgres() + : deps.stopMailpitContainers(); + const result: IntegratedDevResetAction = { + ...action, + status: stopped ? 'removed' : 'failed', + detail: stopped + ? (action.id === 'market-postgres' ? 'Market PostgreSQL database state was reset.' : 'Mailpit container and inbox state were reset.') + : (action.id === 'market-postgres' + ? 'Unable to stop or remove the Treeseed-managed Market PostgreSQL container and volume.' + : 'Unable to remove the Treeseed-managed Mailpit container and inbox state.'), + }; + emitEvent(options, deps.write, { + type: 'reset', + status: result.status, + message: `${result.label}: ${result.status}`, + detail: result, + }, result.status === 'failed' ? 'stderr' : 'stdout'); + return result; + } + if (!action.path || !existsSync(action.path)) { + if (action.id === 'dev-reload' && action.path) { + try { + writeDevReloadStampPath(action.path); + const result: IntegratedDevResetAction = { + ...action, + status: 'refreshed', + detail: action.path, + }; + emitEvent(options, deps.write, { + type: 'reset', + status: result.status, + message: `${result.label}: refreshed`, + detail: result, + }); + return result; + } catch (error) { + const result: IntegratedDevResetAction = { + ...action, + status: 'failed', + detail: error instanceof Error ? error.message : String(error), + }; + emitEvent(options, deps.write, { + type: 'reset', + status: result.status, + message: `${result.label}: failed`, + detail: result, + }, 'stderr'); + return result; + } + } + const result: IntegratedDevResetAction = { + ...action, + status: 'skipped', + detail: action.detail ?? 'Path does not exist.', + }; + emitEvent(options, deps.write, { + type: 'reset', + status: result.status, + message: `${result.label}: skipped`, + detail: result, + }); + return result; + } + if (action.id === 'dev-reload') { + try { + writeDevReloadStampPath(action.path); + const result: IntegratedDevResetAction = { + ...action, + status: 'refreshed', + detail: action.path, + }; + emitEvent(options, deps.write, { + type: 'reset', + status: result.status, + message: `${result.label}: refreshed`, + detail: result, + }); + return result; + } catch (error) { + const result: IntegratedDevResetAction = { + ...action, + status: 'failed', + detail: error instanceof Error ? error.message : String(error), + }; + emitEvent(options, deps.write, { + type: 'reset', + status: result.status, + message: `${result.label}: failed`, + detail: result, + }, 'stderr'); + return result; + } + } + try { + deps.removePath(action.path); + const result: IntegratedDevResetAction = { + ...action, + status: 'removed', + detail: action.path, + }; + emitEvent(options, deps.write, { + type: 'reset', + status: result.status, + message: `${result.label}: removed`, + detail: result, + }); + return result; + } catch (error) { + const result: IntegratedDevResetAction = { + ...action, + status: 'failed', + detail: error instanceof Error ? error.message : String(error), + }; + emitEvent(options, deps.write, { + type: 'reset', + status: result.status, + message: `${result.label}: failed`, + detail: result, + }, 'stderr'); + return result; + } + }); + return { + ...reset, + actions: results, + }; +} + +export function writePlan(plan: IntegratedDevPlan, options: Pick, write: IntegratedDevDependencies['write']) { + if (options.json) { + write(`${JSON.stringify({ schemaVersion: 1, kind: 'treeseed.dev.plan', ok: true, payload: serializeDevPlanForOutput(plan) }, null, 2)}\n`, 'stdout'); + return; + } + write(`Treeseed dev plan\n`, 'stdout'); + write(`surface: ${plan.surface}\n`, 'stdout'); + write(`setup: ${plan.setupMode}\n`, 'stdout'); + write(`feedback: ${plan.feedbackMode}\n`, 'stdout'); + if (plan.reset) { + write(`reset: enabled\n`, 'stdout'); + for (const action of plan.reset.actions) { + write(`- reset ${action.id}: ${action.status}${action.path ? ` ${action.path}` : ''}\n`, 'stdout'); + } + } + if (plan.webUrl) { + write(`web: ${plan.webUrl}\n`, 'stdout'); + } + write(`api: ${plan.apiBaseUrl}\n`, 'stdout'); + write(`log: ${plan.logPath}\n`, 'stdout'); + for (const [name, runtime] of Object.entries(plan.localRuntimes)) { + write(`runtime ${name}: ${runtime.selected} (${runtime.provider}, requested ${runtime.requested})${runtime.reason ? ` - ${runtime.reason}` : ''}\n`, 'stdout'); + } + for (const command of plan.commands) { + write(`- ${command.id}: ${command.command} ${command.args.join(' ')}\n`, 'stdout'); + } +} diff --git a/src/dev/runtime/render-dev-log-json-event-for-human.ts b/src/dev/runtime/render-dev-log-json-event-for-human.ts new file mode 100644 index 00000000..28ad0375 --- /dev/null +++ b/src/dev/runtime/render-dev-log-json-event-for-human.ts @@ -0,0 +1,284 @@ +import { closeSync, existsSync, mkdirSync, openSync, readSync, statSync, writeFileSync } from 'node:fs'; +import { spawn } from 'node:child_process'; +import { dirname, resolve } from 'node:path'; +import { setTimeout as delay } from 'node:timers/promises'; +import { DEFAULT_KILL_GRACE_MS, DEFAULT_READINESS_TIMEOUT_MS, DEFAULT_SHUTDOWN_GRACE_MS, type DevInstanceRecord, type ManagedDevOptions } from '../configuration/runtime-configuration.ts'; +import type { ManagedStartDependencies, IntegratedDevDependencies } from './integrated-dev-dependencies.ts'; +import { createDevInstanceRecord, evaluateDevInstance, listRepoFamilyDevInstances, listWorktreeDevInstances, readDevInstanceFile, removeDevInstanceRecord, renderManagedDevStatus, resolveManagedPortOverrides, writeDevInstance } from './dev-pid-path.ts'; +import { waitForProcessExit } from './dev-runtime-state.ts'; +import { defaultInspectPortOwners, defaultKillProcess, defaultProcessIsAlive, defaultWrite, devInstancePath, instanceRuntimeScope } from './default-kill-process.ts'; +import { createIntegratedDevPlan } from '../support/create-api-command.ts'; +import { writePlan } from './prepare-dev-runtime-slots.ts'; +import { fetchOk } from './run-local-setup.ts'; + +export function renderDevLogJsonEventForHuman(parsed: Record) { + if (parsed.kind === 'treeseed.dev.log' && parsed.type === 'start') { + const startedAt = typeof parsed.startedAt === 'string' ? ` at ${parsed.startedAt}` : ''; + return `[dev] Log session started${startedAt}.`; + } + if (parsed.kind !== 'treeseed.dev.event') { + return null; + } + const surface = typeof parsed.surface === 'string' + ? `[${parsed.surface}]` + : parsed.type === 'setup' + ? '[setup]' + : '[dev]'; + const message = typeof parsed.message === 'string' + ? parsed.message + : typeof parsed.status === 'string' + ? parsed.status + : ''; + if (!message) { + return null; + } + if (surface === '[operations-runner]') { + try { + const runner = JSON.parse(message) as Record; + if (runner.ok === true && runner.claimed === false && runner.operation == null) { + return null; + } + } catch { + // Not a runner heartbeat payload. + } + } + return `${surface} ${message}`; +} + +export function renderDevLogForHuman(raw: string) { + const rendered: string[] = []; + for (const line of raw.split(/\r?\n/u)) { + if (!line) { + rendered.push(line); + continue; + } + if (line.trimStart().startsWith('{')) { + try { + const parsed = JSON.parse(line) as Record; + const human = renderDevLogJsonEventForHuman(parsed); + if (human) rendered.push(human); + continue; + } catch { + // Non-JSON despite the leading brace; keep it as-is. + } + } + rendered.push(line); + } + return rendered.join('\n'); +} + +export function readRecentDevLog(path: string, maxLines = 300, maxBytes = 256 * 1024) { + const stats = statSync(path); + const start = Math.max(0, stats.size - maxBytes); + const fd = openSync(path, 'r'); + try { + const buffer = Buffer.alloc(stats.size - start); + readSync(fd, buffer, 0, buffer.length, start); + const lines = buffer.toString('utf8').split(/\r?\n/u); + return lines.slice(Math.max(0, lines.length - maxLines)).join('\n'); + } finally { + closeSync(fd); + } +} + +export async function waitForManagedInstanceReady( + instancePath: string, + options: Pick, + deps: Pick, +) { + const startedAt = Date.now(); + const timeoutMs = options.readinessTimeoutMs ?? DEFAULT_READINESS_TIMEOUT_MS; + while (Date.now() - startedAt < timeoutMs) { + const record = readDevInstanceFile(instancePath); + if (record) { + const evaluated = evaluateDevInstance(record, deps); + if (evaluated.status === 'ready' || evaluated.status === 'degraded' || evaluated.status === 'stale') { + return evaluated; + } + } + await delay(500); + } + const record = readDevInstanceFile(instancePath); + return record ? { ...record, status: 'degraded' as const, staleReason: 'Timed out waiting for readiness.' } : null; +} + +export function managedDevResult(kind: string, ok: boolean, payload: unknown, options: Pick, write: IntegratedDevDependencies['write']) { + if (options.json) { + write(`${JSON.stringify({ schemaVersion: 1, kind, ok, payload }, null, 2)}\n`, 'stdout'); + } else if (typeof payload === 'string') { + write(`${payload}\n`, 'stdout'); + } else if (Array.isArray(payload)) { + write(`${renderManagedDevStatus(payload as DevInstanceRecord[])}\n`, 'stdout'); + } else { + write(`${renderManagedDevStatus([payload as DevInstanceRecord])}\n`, 'stdout'); + } + return ok ? 0 : 1; +} + +export async function stopDevInstance( + record: DevInstanceRecord, + options: Pick, + deps: Pick, +) { + if (!record.pid || !deps.processIsAlive(record.pid)) { + removeDevInstanceRecord(record); + return { ...record, status: 'stale' as const, staleReason: 'Process was not running.' }; + } + const targetPid = record.processGroupId && process.platform !== 'win32' ? -record.processGroupId : record.pid; + try { + deps.killProcess(targetPid, 'SIGTERM'); + } catch { + // Already stopped or not owned by this process table. + } + if (!await waitForProcessExit(record.pid, deps.processIsAlive, options.shutdownGraceMs ?? DEFAULT_SHUTDOWN_GRACE_MS)) { + try { + deps.killProcess(targetPid, 'SIGKILL'); + } catch { + // Ignore shutdown races. + } + await waitForProcessExit(record.pid, deps.processIsAlive, DEFAULT_KILL_GRACE_MS); + } + removeDevInstanceRecord(record); + return { ...record, status: 'stopped' as const, updatedAt: new Date().toISOString() }; +} + +export async function runManagedDev( + options: ManagedDevOptions, + deps: Partial & { + supervisorCommand?: string; + supervisorArgs?: string[]; + } = {}, +) { + const tenantRoot = resolve(options.cwd ?? process.cwd()); + const write = deps.write ?? defaultWrite; + const spawnProcess = deps.spawn ?? spawn; + const fetchFn = deps.fetch ?? globalThis.fetch.bind(globalThis); + const processIsAlive = deps.processIsAlive ?? defaultProcessIsAlive; + const killProcess = deps.killProcess ?? defaultKillProcess; + const inspectPortOwners = deps.inspectPortOwners ?? defaultInspectPortOwners; + const baseDeps = { processIsAlive, inspectPortOwners }; + + if (options.action === 'status') { + const records = (options.all ? listRepoFamilyDevInstances(tenantRoot) : listWorktreeDevInstances(tenantRoot)) + .map((record) => evaluateDevInstance(record, { processIsAlive })); + return managedDevResult('treeseed.dev.status', true, records, options, write); + } + + if (options.action === 'logs') { + const record = listWorktreeDevInstances(tenantRoot) + .map((entry) => evaluateDevInstance(entry, { processIsAlive })) + .find((entry) => entry.status !== 'stale') ?? listWorktreeDevInstances(tenantRoot)[0]; + if (!record) { + return managedDevResult('treeseed.dev.logs', false, 'No managed Treeseed dev instance found for this worktree.', options, write); + } + if (options.json) { + return managedDevResult('treeseed.dev.logs', true, { logPath: record.logPath, exists: existsSync(record.logPath) }, options, write); + } + if (!existsSync(record.logPath)) { + write(`Log file does not exist yet: ${record.logPath}\n`, 'stderr'); + return 1; + } + if (options.follow) { + const tail = spawnProcess('tail', ['-f', record.logPath], { cwd: record.projectRoot, stdio: 'inherit' }); + return await new Promise((resolvePromise) => { + tail.on('exit', (code) => resolvePromise(code ?? 0)); + }); + } + write(renderDevLogForHuman(readRecentDevLog(record.logPath)), 'stdout'); + return 0; + } + + if (options.action === 'stop') { + const records = options.all ? listRepoFamilyDevInstances(tenantRoot) : listWorktreeDevInstances(tenantRoot); + const stopped = []; + for (const record of records) { + stopped.push(await stopDevInstance(record, options, { killProcess, processIsAlive })); + } + return managedDevResult('treeseed.dev.stop', true, stopped, options, write); + } + + if (options.action === 'restart') { + await runManagedDev({ ...options, action: 'stop' }, { ...deps, write: () => {} }); + return runManagedDev({ ...options, action: 'start' }, deps); + } + + const allocated = resolveManagedPortOverrides(tenantRoot, options, baseDeps); + const effectiveEnv = { + ...(options.env ?? {}), + ...allocated.env, + }; + const plan = createIntegratedDevPlan({ + ...options, + cwd: tenantRoot, + webPort: allocated.webPort, + apiPort: allocated.apiPort, + env: effectiveEnv, + }); + const runtimeScope = instanceRuntimeScope(plan); + const instancePath = devInstancePath(tenantRoot, runtimeScope); + const logPath = plan.logPath; + if (options.plan) { + writePlan(plan, options, write); + return 0; + } + const existing = readDevInstanceFile(instancePath); + if (existing && evaluateDevInstance(existing, { processIsAlive }).status !== 'stale' && options.force !== true) { + return managedDevResult('treeseed.dev.start', true, evaluateDevInstance(existing, { processIsAlive }), options, write); + } + if (existing && options.force === true) { + await stopDevInstance(existing, options, { killProcess, processIsAlive }); + } + + mkdirSync(dirname(logPath), { recursive: true }); + writeFileSync(logPath, '', { flag: 'a' }); + const supervisorCommand = deps.supervisorCommand ?? process.execPath; + const supervisorArgs = [ + ...(deps.supervisorArgs ?? process.argv.slice(1).filter((arg) => !['start', 'restart', 'status', 'stop', 'logs'].includes(arg))), + '--port', + String(allocated.webPort), + '--api-port', + String(allocated.apiPort), + ...(options.webHost ? ['--host', options.webHost] : []), + ...(options.apiHost ? ['--api-host', options.apiHost] : []), + ...(options.webRuntime ? ['--web-runtime', options.webRuntime] : []), + ...(options.surfaces ? ['--surfaces', options.surfaces] : options.surface ? ['--surface', options.surface] : []), + ...(options.setupMode ? ['--setup', options.setupMode] : []), + ...(options.feedbackMode ? ['--feedback', options.feedbackMode] : []), + ...(options.openMode ? ['--open', options.openMode] : []), + ...(options.reset ? ['--reset'] : []), + ...(options.forceConflicts ? ['--force'] : []), + ...(options.projectId ? ['--project-id', options.projectId] : []), + ...(options.teamId ? ['--team-id', options.teamId] : []), + ]; + const logFd = openSync(logPath, 'a'); + const child = spawnProcess(supervisorCommand, supervisorArgs, { + cwd: tenantRoot, + env: { + ...process.env, + ...effectiveEnv, + TREESEED_MANAGED_DEV_INSTANCE: '1', + TREESEED_MANAGED_DEV_SUPPRESS_STDIO: '1', + }, + stdio: ['ignore', logFd, logFd], + detached: true, + }); + closeSync(logFd); + child.unref?.(); + const childPid = typeof child.pid === 'number' ? child.pid : null; + const starting = writeDevInstance(createDevInstanceRecord(plan, 'starting', childPid, childPid && process.platform !== 'win32' ? childPid : null)); + const ready = await waitForManagedInstanceReady(instancePath, options, { processIsAlive }); + if (!ready) { + return managedDevResult('treeseed.dev.start', false, { ...starting, status: 'degraded', staleReason: 'Supervisor did not publish an instance record.' }, options, write); + } + if (ready.status === 'stale') { + return managedDevResult('treeseed.dev.start', false, ready, options, write); + } + // Touch HTTP readiness once more so stale startup records do not look successful. + for (const check of ready.readyChecks.filter((entry) => entry.required && entry.strategy === 'http' && entry.url)) { + if (!await fetchOk(fetchFn, check.url!, 2_000)) { + const degraded = writeDevInstance({ ...ready, status: 'degraded', staleReason: `${check.label} is not reachable at ${check.url}.` }); + return managedDevResult('treeseed.dev.start', false, degraded, options, write); + } + } + return managedDevResult('treeseed.dev.start', ready.status === 'ready', ready, options, write); +} diff --git a/src/dev/runtime/run-integrated-dev.ts b/src/dev/runtime/run-integrated-dev.ts new file mode 100644 index 00000000..0db512b8 --- /dev/null +++ b/src/dev/runtime/run-integrated-dev.ts @@ -0,0 +1,482 @@ +import { spawn, spawnSync } from 'node:child_process'; +import { resolve } from 'node:path'; +import { setTimeout as delay } from 'node:timers/promises'; +import { startPollingWatch } from "../../runtime/dev-watch"; +import { DEFAULT_PROCESS_READY_GRACE_MS, DEFAULT_READINESS_TIMEOUT_MS, DEFAULT_SHUTDOWN_GRACE_MS, INITIAL_RESTART_BACKOFF_MS, MAX_RESTART_BACKOFF_MS, SETUP_RETRY_BACKOFF_MS, type IntegratedDevCommand, type IntegratedDevOptions, type WatchController } from '../configuration/runtime-configuration.ts'; +import type { IntegratedDevDependencies } from './integrated-dev-dependencies.ts'; +import { createManagedDevProcess, defaultInspectPortOwners, defaultKillProcess, defaultProcessIsAlive, defaultRemovePath, defaultResetMarketPostgres, defaultStopMarketPostgres, defaultWrite, stopManagedProcess, writeDevReloadStamp, type ManagedDevProcess } from './default-kill-process.ts'; +import { createIntegratedDevPlan, defaultPrepareEnvironment, defaultSignalRegistrar } from '../support/create-api-command.ts'; +import { defaultOpenBrowser, failedSetupMessage, runLocalSetup, shouldOpenBrowser, waitForHttpReady } from './run-local-setup.ts'; +import { attachPrefixedLogReader, resetMarketPostgres, stopMarketPostgres } from '../support/attach-prefixed-log-reader.ts'; +import { createDevLogWrite, emitEvent, prepareDevRuntimeSlots, runIntegratedDevReset, writePlan } from './prepare-dev-runtime-slots.ts'; +import { removeCurrentDevRuntimeState, updateCurrentDevRuntimeState, writeCurrentDevRuntimeState } from './dev-runtime-state.ts'; + +export async function runIntegratedDev( + options: IntegratedDevOptions = {}, + deps: Partial = {}, +) { + const tenantRoot = resolve(options.cwd ?? process.cwd()); + let write = deps.write ?? defaultWrite; + const spawnProcess = deps.spawn ?? spawn; + const spawnSyncProcess = deps.spawnSync ?? spawnSync; + const onSignal = deps.onSignal ?? defaultSignalRegistrar; + const fetchFn = deps.fetch ?? globalThis.fetch.bind(globalThis); + const killProcess = deps.killProcess ?? defaultKillProcess; + const processIsAlive = deps.processIsAlive ?? defaultProcessIsAlive; + const openBrowser = deps.openBrowser ?? defaultOpenBrowser; + const startWatch = deps.startWatch ?? startPollingWatch; + const prepareEnvironment = deps.prepareEnvironment ?? defaultPrepareEnvironment; + const removePath = deps.removePath ?? defaultRemovePath; + const stopMailpit = deps.stopMailpitContainers ?? (() => true); + const resetMarketPostgresContainer = deps.resetMarketPostgres ?? defaultResetMarketPostgres; + const stopMarketPostgresContainer = deps.stopMarketPostgres ?? defaultStopMarketPostgres; + const inspectPortOwners = deps.inspectPortOwners ?? defaultInspectPortOwners; + + prepareEnvironment(tenantRoot); + const plan = createIntegratedDevPlan({ + ...options, + cwd: tenantRoot, + env: { + ...process.env, + ...(options.env ?? {}), + }, + }); + + if (options.plan) { + writePlan(plan, options, write); + return 0; + } + + const commandIds = plan.commands.map((command) => command.id); + write = createDevLogWrite(write, plan.logPath); + emitEvent(options, write, { + type: 'log', + message: `Writing Treeseed dev logs to ${plan.logPath}.`, + detail: { logPath: plan.logPath }, + }); + if (!await prepareDevRuntimeSlots(plan, options, { write, killProcess, processIsAlive, inspectPortOwners })) { + return 1; + } + + const resetResults = runIntegratedDevReset(plan.reset, options, { + write, + removePath, + stopMailpitContainers: stopMailpit, + resetMarketPostgres: deps.resetMarketPostgres + ? resetMarketPostgresContainer + : () => resetMarketPostgres(plan.commands[0]?.env ?? process.env, { spawnSync: spawnSyncProcess }), + }); + const failedReset = resetResults?.actions.find((action) => action.status === 'failed'); + if (failedReset) { + emitEvent(options, write, { + type: 'error', + message: `${failedReset.label} failed during dev reset.`, + detail: failedReset, + }); + return 1; + } + + writeCurrentDevRuntimeState(plan, 'starting'); + + const children = new Map(); + const commandsById = new Map(plan.commands.map((command) => [command.id, command])); + const requiredSurfaceIds = new Set(plan.readyChecks.filter((check) => check.required).map((check) => check.id)); + const exited = new Map(); + const restartAttempts = new Map(); + const restartTimers = new Map(); + let setupRetryTimer: NodeJS.Timeout | null = null; + let readinessInProgress = false; + let watchController: WatchController | null = null; + let settled = false; + let readinessComplete = false; + let restartInProgress = false; + const shutdownGraceMs = options.shutdownGraceMs ?? DEFAULT_SHUTDOWN_GRACE_MS; + + return await new Promise((resolveExitCode) => { + const disposers = [ + onSignal('SIGINT', () => finalize(130)), + onSignal('SIGTERM', () => finalize(143)), + ]; + + function stopWatching() { + if (!watchController) { + return; + } + watchController.stop(); + watchController = null; + } + + function clearTimers() { + if (setupRetryTimer) { + clearTimeout(setupRetryTimer); + setupRetryTimer = null; + } + for (const timer of restartTimers.values()) { + clearTimeout(timer); + } + restartTimers.clear(); + } + + function finalize(exitCode: number) { + if (settled) { + return; + } + settled = true; + void finalizeAsync(exitCode); + } + + async function finalizeAsync(exitCode: number) { + stopWatching(); + clearTimers(); + await Promise.all( + [...children.values()].map((managed) => stopManagedProcess(managed, 'SIGTERM', killProcess, shutdownGraceMs)), + ); + const marketEnv = plan.commands[0]?.env ?? process.env; + const shouldStopMarketPostgres = marketEnv.TREESEED_MARKET_LOCAL_POSTGRES_MANAGED === 'true'; + if (shouldStopMarketPostgres) { + const stopped = deps.stopMarketPostgres + ? stopMarketPostgresContainer() + : stopMarketPostgres(marketEnv, { spawnSync: spawnSyncProcess }); + if (!stopped) { + emitEvent(options, write, { + type: 'shutdown', + status: 'degraded', + message: 'Unable to stop the managed local Market PostgreSQL container.', + }, 'stderr'); + } + } + children.clear(); + for (const dispose of disposers) { + dispose(); + } + removeCurrentDevRuntimeState(plan); + emitEvent( + options, + write, + { type: 'shutdown', exitCode, message: `Dev runtime stopped with exit code ${exitCode}.` }, + exitCode === 0 ? 'stdout' : 'stderr', + ); + resolveExitCode(exitCode); + } + + function restartDelayFor(id: IntegratedDevCommand['id']) { + const attempts = restartAttempts.get(id) ?? 0; + return Math.min(MAX_RESTART_BACKOFF_MS, INITIAL_RESTART_BACKOFF_MS * (2 ** attempts)); + } + + function markRestartAttempt(id: IntegratedDevCommand['id']) { + const attempts = restartAttempts.get(id) ?? 0; + restartAttempts.set(id, attempts + 1); + } + + function runSetupOnce() { + const setupResults = runLocalSetup(plan, options, { spawnSync: spawnSyncProcess, write }); + const failedSetup = setupResults.find((step) => step.status === 'failed' && step.required); + if (failedSetup) { + emitEvent(options, write, { type: 'error', message: failedSetupMessage(failedSetup), detail: failedSetup }); + return false; + } + return true; + } + + function scheduleSetupRetry(reason: string) { + if (setupRetryTimer || settled) { + return; + } + emitEvent(options, write, { + type: 'restart', + status: 'retrying', + message: `${reason} Retrying local setup in ${Math.round(SETUP_RETRY_BACKOFF_MS / 1000)}s.`, + }, 'stderr'); + setupRetryTimer = setTimeout(() => { + setupRetryTimer = null; + if (settled) return; + if (!runSetupOnce()) { + scheduleSetupRetry('Local setup is still failing.'); + return; + } + for (const command of plan.commands) { + if (!children.has(command.id)) { + spawnCommand(command); + } + } + void waitForReadiness(); + }, SETUP_RETRY_BACKOFF_MS); + } + + function spawnCommand(command: IntegratedDevCommand) { + emitEvent(options, write, { + type: 'spawn', + surface: command.id, + command: command.command, + args: command.args, + message: `Starting ${command.label}.`, + }); + const child = spawnProcess(command.command, command.args, { + cwd: command.cwd, + env: command.env, + stdio: options.stdio ?? ['ignore', 'pipe', 'pipe'], + detached: true, + }); + const managed = createManagedDevProcess(command, child); + children.set(command.id, managed); + attachPrefixedLogReader(child, command.id, options, write); + child.on('exit', (code, signal) => { + managed.exited = true; + managed.exitCode = code; + managed.exitSignal = signal; + managed.resolveExit(); + exited.set(command.id, { code, signal }); + if (managed.intentionalStop || settled) { + return; + } + const exitCode = signal === 'SIGINT' + ? 130 + : signal === 'SIGTERM' + ? 143 + : code ?? 0; + const required = requiredSurfaceIds.has(command.id); + if (required) { + emitEvent(options, write, { + type: 'error', + surface: command.id, + exitCode, + signal, + message: `${command.label} exited unexpectedly during ${readinessComplete ? 'supervision' : 'startup'} with ${signal ?? exitCode}; restarting.`, + }); + children.delete(command.id); + scheduleCommandRestart(command.id); + return; + } + const status = exitCode === 0 ? 'idle' : 'degraded'; + emitEvent(options, write, { + type: 'error', + surface: command.id, + exitCode, + signal, + status, + message: readinessComplete + ? `${command.label} exited with ${signal ?? exitCode}; continuing because it is not a required surface.` + : `${command.label} exited during startup with ${signal ?? exitCode}; continuing because it is not a required surface.`, + }, status === 'idle' ? 'stdout' : 'stderr'); + void stopManagedProcess(managed, 'SIGTERM', killProcess, 0).finally(() => { + children.delete(command.id); + }); + }); + return child; + } + + function scheduleCommandRestart(id: IntegratedDevCommand['id']) { + const command = commandsById.get(id); + if (!command || settled || restartTimers.has(id)) { + return; + } + const delayMs = restartDelayFor(id); + markRestartAttempt(id); + emitEvent(options, write, { + type: 'restart', + surface: id, + status: 'scheduled', + message: `Restarting ${command.label} in ${Math.round(delayMs / 1000)}s.`, + }, 'stderr'); + const timer = setTimeout(() => { + restartTimers.delete(id); + void restartCommand(id); + }, delayMs); + restartTimers.set(id, timer); + } + + async function restartCommand(id: IntegratedDevCommand['id']) { + const command = commandsById.get(id); + if (!command || settled) { + return; + } + const current = children.get(id); + if (current) { + await stopManagedProcess(current, 'SIGTERM', killProcess, Math.min(shutdownGraceMs, 500)); + } + children.delete(id); + exited.delete(id); + if (settled) { + return; + } + spawnCommand(command); + emitEvent(options, write, { type: 'restart', surface: id, message: `Restarted ${command.label}.` }); + void waitForReadiness(); + } + + function startLiveWatch() { + if (watchController || plan.watchEntries.length === 0 || plan.feedbackMode === 'off' || settled) { + return; + } + watchController = startWatch({ + watchEntries: plan.watchEntries, + onChange: async (change) => { + if (settled) { + return; + } + if (restartInProgress) { + watchController?.rebaseline(); + return; + } + restartInProgress = true; + try { + emitEvent(options, write, { + type: 'restart', + message: `Detected ${change.changedPaths.length} development change${change.changedPaths.length === 1 ? '' : 's'}.`, + detail: { + tenantChanged: change.tenantChanged, + tenantApiChanged: change.tenantApiChanged, + coreChanged: change.coreChanged, + sdkChanged: change.sdkChanged, + agentChanged: change.agentChanged, + cliChanged: change.cliChanged, + commandImplementationChanged: change.commandImplementationChanged, + }, + }); + if (change.commandImplementationChanged) { + emitEvent(options, write, { + type: 'replace', + status: 'restart-required', + message: 'The dev command implementation changed. Stop and rerun `npx trsd dev` to load the new supervisor.', + detail: change.changedPaths, + }, 'stderr'); + return; + } + if (change.agentChanged) { + emitEvent(options, write, { + type: 'restart', + status: 'deferred', + message: 'Agent service changes detected; running agent services will keep their current code until the next workday or a manual restart.', + detail: change.changedPaths, + }); + } + if (change.tenantChanged || change.tenantApiChanged || change.coreChanged || change.sdkChanged) { + if (!runSetupOnce()) { + scheduleSetupRetry('Local setup failed after a development change.'); + return; + } + } + const restartIds = new Set(); + if ((change.tenantChanged || change.coreChanged || change.sdkChanged) && commandsById.has('web')) { + restartIds.add('web'); + } + if ((change.tenantApiChanged || change.sdkChanged) && commandsById.has('api')) { + restartIds.add('api'); + } + if (change.sdkChanged) { + for (const id of commandsById.keys()) { + restartIds.add(id); + } + } + if ((change.tenantApiChanged || change.sdkChanged) && commandsById.has('operations-runner')) { + restartIds.add('operations-runner'); + } + for (const id of restartIds) { + await restartCommand(id); + } + if (plan.feedbackMode === 'live') { + writeDevReloadStamp(plan.tenantRoot); + emitEvent(options, write, { type: 'reload', message: 'Wrote browser reload stamp.' }); + } + } finally { + watchController?.rebaseline(); + restartInProgress = false; + } + }, + }); + watchController.rebaseline(); + } + + async function waitForReadiness() { + if (readinessInProgress) { + return; + } + readinessInProgress = true; + const readinessTimeoutMs = options.readinessTimeoutMs ?? DEFAULT_READINESS_TIMEOUT_MS; + const processReadyGraceMs = options.processReadyGraceMs ?? DEFAULT_PROCESS_READY_GRACE_MS; + let allRequiredReady = true; + for (const check of plan.readyChecks) { + if (settled) { + readinessInProgress = false; + return; + } + let ready = false; + if (check.strategy === 'http' && check.url) { + ready = await waitForHttpReady(fetchFn, check.url, readinessTimeoutMs); + } else { + const commandId = check.id as IntegratedDevCommand['id']; + await delay(processReadyGraceMs); + ready = !exited.has(commandId) && children.has(commandId); + } + if (settled) { + readinessInProgress = false; + return; + } + if (!ready && check.required) { + allRequiredReady = false; + emitEvent(options, write, { + type: 'error', + surface: check.id, + url: check.url, + message: `${check.label} did not become ready${check.url ? ` at ${check.url}` : ''}; keeping dev alive and retrying.`, + }); + scheduleCommandRestart(check.id); + continue; + } + if (ready) restartAttempts.set(check.id, 0); + emitEvent(options, write, { + type: 'ready', + surface: check.id, + status: ready ? 'ready' : 'degraded', + url: check.url, + message: `${check.label} is ${ready ? 'ready' : 'degraded'}${check.url ? ` at ${check.url}` : ''}.`, + }); + } + readinessInProgress = false; + if (!allRequiredReady) { + updateCurrentDevRuntimeState(plan, 'degraded', 'One or more required readiness checks failed.'); + startLiveWatch(); + return; + } + readinessComplete = true; + updateCurrentDevRuntimeState(plan, 'ready'); + if (plan.webUrl) { + emitEvent(options, write, { type: 'ready', url: plan.webUrl, message: `Treeseed dev ready at ${plan.webUrl}.` }); + } + if (shouldOpenBrowser(plan)) { + try { + await openBrowser(plan.webUrl!); + emitEvent(options, write, { type: 'open', url: plan.webUrl ?? undefined, message: `Opened ${plan.webUrl}.` }); + } catch (error) { + emitEvent(options, write, { + type: 'open', + status: 'degraded', + url: plan.webUrl ?? undefined, + message: `Could not open ${plan.webUrl}.`, + detail: error instanceof Error ? error.message : String(error), + }); + } + } + startLiveWatch(); + } + + startLiveWatch(); + if (runSetupOnce()) { + for (const command of plan.commands) { + spawnCommand(command); + } + void waitForReadiness().catch((error) => { + readinessInProgress = false; + emitEvent(options, write, { + type: 'error', + message: 'Dev readiness failed; keeping supervisor alive.', + detail: error instanceof Error ? error.message : String(error), + }); + scheduleSetupRetry('Readiness failed unexpectedly.'); + }); + } else { + scheduleSetupRetry('Initial local setup failed.'); + } + }); +} diff --git a/src/dev/runtime/run-local-setup.ts b/src/dev/runtime/run-local-setup.ts new file mode 100644 index 00000000..c3e91194 --- /dev/null +++ b/src/dev/runtime/run-local-setup.ts @@ -0,0 +1,168 @@ +import { spawn } from 'node:child_process'; +import { setTimeout as delay } from 'node:timers/promises'; +import { createPersistentDeployTarget, ensureGeneratedWranglerConfig, ensureLocalWorkspaceLinks, findNearestWorkspaceRoot, resolveToolBinary } from '../../runtime/platform/workflow-support.ts'; +import type { FetchLike, IntegratedDevOptions, IntegratedDevPlan, IntegratedDevSetupStep } from '../configuration/runtime-configuration.ts'; +import type { IntegratedDevDependencies } from './integrated-dev-dependencies.ts'; +import { emitEvent } from './prepare-dev-runtime-slots.ts'; +import { ensureMarketPostgres, runSetupStep } from '../support/attach-prefixed-log-reader.ts'; +import { writeDevReloadStamp } from './default-kill-process.ts'; + +export function runLocalSetup( + plan: IntegratedDevPlan, + options: Pick, + deps: Pick, +) { + const results: IntegratedDevSetupStep[] = []; + if (plan.setupMode === 'off') { + for (const step of plan.setupSteps) { + results.push(step); + emitEvent(options, deps.write, { type: 'setup', status: step.status, message: `${step.label}: ${step.status}`, detail: step.detail }); + } + return results; + } + + for (const step of plan.setupSteps) { + let result = step; + if (step.status === 'planned') { + emitEvent(options, deps.write, { + type: 'setup', + status: 'running', + message: `${step.label}: running`, + detail: step.detail, + }); + } + if (step.id === 'workspace-links') { + if (plan.setupMode === 'check') { + result = { ...step, status: 'skipped', detail: 'Workspace links were checked in non-mutating mode.' }; + } else { + const workspaceRoot = findNearestWorkspaceRoot(plan.tenantRoot); + if (workspaceRoot) { + const links = ensureLocalWorkspaceLinks(workspaceRoot, { + mode: 'auto', + env: { ...process.env, ...plan.commands[0]?.env }, + }); + result = { + ...step, + status: links.issues.length > 0 ? 'failed' : 'completed', + detail: links.issues.length > 0 + ? links.issues.join('; ') + : `Verified ${links.links.length} workspace link${links.links.length === 1 ? '' : 's'}.`, + }; + } else { + result = { ...step, status: 'skipped', detail: 'No Treeseed workspace root found.' }; + } + } + } else if (step.id === 'wrangler') { + const wrangler = resolveToolBinary('wrangler', { env: { ...process.env, ...plan.commands[0]?.env } }); + result = wrangler + ? { ...step, status: 'completed', detail: wrangler } + : { + ...step, + status: step.required ? 'failed' : 'degraded', + detail: 'Wrangler was not found. Run `npx trsd install --json` and retry `npx trsd dev`.', + }; + } else if (step.id === 'market-postgres') { + if (plan.setupMode === 'check') { + result = { ...step, status: 'skipped', detail: 'Local Market PostgreSQL startup was checked in non-mutating mode.' }; + } else if (step.status === 'failed') { + result = step; + } else { + try { + const detail = ensureMarketPostgres({ ...process.env, ...plan.commands[0]?.env }, deps); + result = { ...step, status: 'completed', detail }; + } catch (error) { + result = { + ...step, + status: 'failed', + detail: error instanceof Error ? error.message : String(error), + }; + } + } + } else if (step.id === 'wrangler-config') { + if (plan.setupMode === 'check') { + result = { ...step, status: 'skipped', detail: 'Local Wrangler config generation was checked in non-mutating mode.' }; + } else { + try { + const { wranglerPath } = ensureGeneratedWranglerConfig(plan.tenantRoot, { + target: createPersistentDeployTarget('local'), + env: plan.commands[0]?.env, + }); + result = { ...step, status: 'completed', detail: wranglerPath }; + } catch (error) { + result = { + ...step, + status: 'failed', + detail: error instanceof Error ? error.message : String(error), + }; + } + } + } else if (plan.setupMode === 'check') { + result = { ...step, status: step.status === 'failed' ? 'failed' : 'skipped', detail: step.detail ?? 'Skipped in setup check mode.' }; + } else { + result = runSetupStep(step, plan, deps); + } + results.push(result); + emitEvent(options, deps.write, { + type: 'setup', + status: result.status, + message: `${result.label}: ${result.status}`, + detail: result.detail, + }, result.status === 'failed' ? 'stderr' : 'stdout'); + } + const failedRequired = results.some((step) => step.required && step.status === 'failed'); + if (plan.feedbackMode === 'live' && plan.setupMode === 'auto' && !failedRequired) { + writeDevReloadStamp(plan.tenantRoot); + emitEvent(options, deps.write, { type: 'reload', message: 'Wrote initial browser reload stamp.' }); + } + return results; +} + +export async function fetchOk(fetchFn: FetchLike, url: string, timeoutMs: number) { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), timeoutMs); + try { + const response = await fetchFn(url, { signal: controller.signal }); + return response.ok; + } catch { + return false; + } finally { + clearTimeout(timeout); + } +} + +export async function waitForHttpReady(fetchFn: FetchLike, url: string, timeoutMs: number) { + const startedAt = Date.now(); + while (Date.now() - startedAt < timeoutMs) { + if (await fetchOk(fetchFn, url, 2_000)) { + return true; + } + await delay(500); + } + return false; +} + +export async function defaultOpenBrowser(url: string) { + const platform = process.platform; + const command = platform === 'darwin' ? 'open' : platform === 'win32' ? 'cmd' : 'xdg-open'; + const args = platform === 'win32' ? ['/c', 'start', '', url] : [url]; + const child = spawn(command, args, { stdio: 'ignore', detached: true }); + child.unref(); +} + +export function shouldOpenBrowser(plan: IntegratedDevPlan) { + if (!plan.webUrl || plan.openMode === 'off') { + return false; + } + if (plan.openMode === 'on') { + return true; + } + return process.stdout.isTTY === true && process.env.CI !== 'true'; +} + +export function failedSetupMessage(failed: IntegratedDevSetupStep) { + return [ + `${failed.label} failed.`, + failed.detail ? String(failed.detail) : null, + 'Run `npx trsd install --json` if a managed executable is missing, then retry `npx trsd dev --setup auto`.', + ].filter(Boolean).join(' '); +} diff --git a/src/dev/support/attach-prefixed-log-reader.ts b/src/dev/support/attach-prefixed-log-reader.ts new file mode 100644 index 00000000..cfc9901f --- /dev/null +++ b/src/dev/support/attach-prefixed-log-reader.ts @@ -0,0 +1,239 @@ +import { spawnSync } from 'node:child_process'; +import { resolveToolBinary } from '../../runtime/platform/workflow-support.ts'; +import { DEFAULT_SETUP_STEP_TIMEOUT_MS, DEFAULT_MARKET_POSTGRES_CONTAINER, DEFAULT_MARKET_POSTGRES_PORT, DEFAULT_MARKET_POSTGRES_VOLUME, type ProcessLike, type SpawnSyncLike, type IntegratedDevOptions, type IntegratedDevPlan, type IntegratedDevSetupStep } from '../configuration/runtime-configuration.ts'; +import type { IntegratedDevDependencies } from '../runtime/integrated-dev-dependencies.ts'; +import { emitEvent } from '../runtime/prepare-dev-runtime-slots.ts'; + +export function attachPrefixedLogReader( + child: ProcessLike, + surface: string, + options: Pick, + write: IntegratedDevDependencies['write'], +) { + const filterState: Record<'stdout' | 'stderr', { suppressWorkerdBrokenPipeBlock: boolean }> = { + stdout: { suppressWorkerdBrokenPipeBlock: false }, + stderr: { suppressWorkerdBrokenPipeBlock: false }, + }; + function shouldSuppressLogLine(line: string, name: 'stdout' | 'stderr') { + if (!options.json && surface === 'operations-runner' && name === 'stdout') { + try { + const parsed = JSON.parse(line) as Record; + if (parsed.ok === true && parsed.claimed === false && parsed.operation == null) { + return true; + } + } catch { + // Non-JSON runner output should flow through normally. + } + } + const state = filterState[name]; + if (state.suppressWorkerdBrokenPipeBlock) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith('stack:') || line.includes('/workerd@')) { + return true; + } + state.suppressWorkerdBrokenPipeBlock = false; + } + if (line.includes('kj::getCaughtExceptionAsKj() = kj/async-io-unix.c++:186: disconnected: ::write')) { + state.suppressWorkerdBrokenPipeBlock = true; + return true; + } + return false; + } + function attach(stream: NodeJS.ReadableStream | null | undefined, name: 'stdout' | 'stderr') { + if (!stream || typeof stream.on !== 'function') { + return; + } + let buffer = ''; + stream.on('data', (chunk: Buffer | string) => { + buffer += chunk.toString(); + for (;;) { + const newlineIndex = buffer.indexOf('\n'); + if (newlineIndex < 0) { + break; + } + const line = buffer.slice(0, newlineIndex); + buffer = buffer.slice(newlineIndex + 1); + if (shouldSuppressLogLine(line, name)) { + continue; + } + if (options.json) { + emitEvent(options, write, { type: 'log', surface, message: line, detail: { stream: name } }, name); + } else { + write(`[${surface}] ${line}\n`, name); + } + } + }); + stream.on('end', () => { + if (buffer.length > 0) { + if (shouldSuppressLogLine(buffer, name)) { + buffer = ''; + return; + } + if (options.json) { + emitEvent(options, write, { type: 'log', surface, message: buffer, detail: { stream: name } }, name); + } else { + write(`[${surface}] ${buffer}\n`, name); + } + buffer = ''; + } + }); + } + attach(child.stdout ?? null, 'stdout'); + attach(child.stderr ?? null, 'stderr'); +} + +export function runSetupStep( + step: IntegratedDevSetupStep, + plan: IntegratedDevPlan, + deps: Pick, +) { + if (!step.command || !step.args) { + return { + ...step, + status: step.status === 'failed' ? 'failed' : 'skipped', + } satisfies IntegratedDevSetupStep; + } + const result = deps.spawnSync(step.command, step.args, { + cwd: plan.tenantRoot, + env: { + ...process.env, + ...plan.commands[0]?.env, + LOCAL_DEV_MODE: 'cloudflare', + TREESEED_PUBLIC_DEV_WATCH_RELOAD: plan.feedbackMode === 'live' ? 'true' : process.env.TREESEED_PUBLIC_DEV_WATCH_RELOAD, + }, + encoding: 'utf8', + timeout: DEFAULT_SETUP_STEP_TIMEOUT_MS, + }); + if ((result.status ?? 1) === 0) { + return { + ...step, + status: 'completed', + detail: [result.stdout, result.stderr].filter(Boolean).join('\n').trim() || step.detail, + } satisfies IntegratedDevSetupStep; + } + const timedOut = result.error && 'code' in result.error && result.error.code === 'ETIMEDOUT'; + const timeoutDetail = timedOut + ? `${step.label} timed out after ${Math.round(DEFAULT_SETUP_STEP_TIMEOUT_MS / 1000)} seconds.` + : null; + return { + ...step, + status: step.required ? 'failed' : 'degraded', + detail: [timeoutDetail, result.stdout, result.stderr] + .filter(Boolean) + .join('\n') + .trim() || `Exited with ${result.status ?? 1}.`, + } satisfies IntegratedDevSetupStep; +} + +export function marketPostgresConfig(env: NodeJS.ProcessEnv) { + return { + container: env.TREESEED_MARKET_LOCAL_POSTGRES_CONTAINER?.trim() || DEFAULT_MARKET_POSTGRES_CONTAINER, + volume: env.TREESEED_MARKET_LOCAL_POSTGRES_VOLUME?.trim() || DEFAULT_MARKET_POSTGRES_VOLUME, + port: env.TREESEED_MARKET_LOCAL_POSTGRES_PORT?.trim() || String(DEFAULT_MARKET_POSTGRES_PORT), + user: 'treeseed', + password: 'treeseed', + database: 'market_local', + }; +} + +export function dockerBinary(env: NodeJS.ProcessEnv) { + return resolveToolBinary('docker', { env }) ?? 'docker'; +} + +export function spawnDocker( + args: string[], + env: NodeJS.ProcessEnv, + deps: Pick, + timeout = 30_000, +) { + return deps.spawnSync(dockerBinary(env), args, { + cwd: process.cwd(), + env, + encoding: 'utf8', + timeout, + }); +} + +export function dockerResultText(result: ReturnType) { + return [result.stdout, result.stderr] + .filter(Boolean) + .join('\n') + .trim(); +} + +export function dockerVolumeIsMissing(result: ReturnType) { + if (result.error) return false; + const text = dockerResultText(result).toLowerCase(); + return text.includes('no such volume') || text.includes('not found'); +} + +export function ensureMarketPostgres( + env: NodeJS.ProcessEnv, + deps: Pick, +) { + const config = marketPostgresConfig(env); + const inspect = spawnDocker(['inspect', config.container], env, deps); + if ((inspect.status ?? 1) !== 0) { + const run = spawnDocker([ + 'run', + '-d', + '--name', + config.container, + '-e', + `POSTGRES_USER=${config.user}`, + '-e', + `POSTGRES_PASSWORD=${config.password}`, + '-e', + `POSTGRES_DB=${config.database}`, + '-p', + `127.0.0.1:${config.port}:5432`, + '-v', + `${config.volume}:/var/lib/postgresql/data`, + 'postgres:16', + ], env, deps, 60_000); + if ((run.status ?? 1) !== 0) { + throw new Error(dockerResultText(run) || `Unable to start ${config.container}.`); + } + } else { + const start = spawnDocker(['start', config.container], env, deps); + if ((start.status ?? 1) !== 0) { + throw new Error(dockerResultText(start) || `Unable to start existing ${config.container}.`); + } + } + + const startedAt = Date.now(); + let last = ''; + while (Date.now() - startedAt < 45_000) { + const ready = spawnDocker(['exec', config.container, 'pg_isready', '-U', config.user, '-d', config.database], env, deps, 5_000); + last = dockerResultText(ready); + if ((ready.status ?? 1) === 0) { + const query = spawnDocker(['exec', config.container, 'psql', '-U', config.user, '-d', config.database, '-c', 'SELECT 1'], env, deps, 5_000); + last = dockerResultText(query) || last; + if ((query.status ?? 1) === 0) { + return `Market PostgreSQL is ready at 127.0.0.1:${config.port} (${config.container}).`; + } + } + Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 500); + } + throw new Error(last || `Timed out waiting for ${config.container} to accept connections.`); +} + +export function resetMarketPostgres(env: NodeJS.ProcessEnv, deps: Pick) { + const config = marketPostgresConfig(env); + spawnDocker(['rm', '-f', config.container], env, deps, 30_000); + const existingVolume = spawnDocker(['volume', 'inspect', config.volume], env, deps, 30_000); + if ((existingVolume.status ?? 1) !== 0) { + return dockerVolumeIsMissing(existingVolume); + } + const volume = spawnDocker(['volume', 'rm', config.volume], env, deps, 30_000); + return (volume.status ?? 1) === 0 || dockerVolumeIsMissing(volume); +} + +export function stopMarketPostgres(env: NodeJS.ProcessEnv, deps: Pick) { + if (env.TREESEED_MARKET_LOCAL_POSTGRES_MANAGED !== 'true') { + return true; + } + const config = marketPostgresConfig(env); + const result = spawnDocker(['rm', '-f', config.container], env, deps, 30_000); + return (result.status ?? 1) === 0; +} diff --git a/src/dev/support/create-api-command.ts b/src/dev/support/create-api-command.ts new file mode 100644 index 00000000..0267371a --- /dev/null +++ b/src/dev/support/create-api-command.ts @@ -0,0 +1,283 @@ +import { resolve } from 'node:path'; +import { applyEnvironmentToProcess, assertCommandEnvironment, resolveWranglerBin } from '../../runtime/platform/workflow-support.ts'; +import { INITIAL_RESTART_BACKOFF_MS, MAX_RESTART_BACKOFF_MS, SETUP_RETRY_BACKOFF_MS, DEFAULT_API_HOST, DEFAULT_API_PORT, DEFAULT_LOCAL_SMTP_HOST, DEFAULT_LOCAL_SMTP_PORT, DEFAULT_MAILPIT_UI_PORT, DEFAULT_MARKET_POSTGRES_CONTAINER, DEFAULT_MARKET_POSTGRES_PORT, DEFAULT_MARKET_POSTGRES_VOLUME, DEFAULT_WEB_HOST, DEFAULT_WEB_PORT, type IntegratedDevCommand, type IntegratedDevCommandId, type IntegratedDevOptions, type IntegratedDevPlan, type IntegratedDevReadinessCheck } from '../configuration/runtime-configuration.ts'; +import { createIntegratedDevResetPlan, isManagedMarketPostgresUrl, nodeLocalRuntime, resolveSeededLocalProjectId, resolveSeededLocalTeamId, selectedSurfaceCommandIds } from './parse-surface-value.ts'; +import { MARKET_DEV_COMMAND_IDS, fallbackWebProviderFromDeployConfig, generatedLocalWranglerPath, isMarketWorkspace, loadDevDeployConfig, normalizeFeedbackMode, normalizeOpenMode, normalizePort, normalizeSetupMode, resolveNodeEntrypoint, resolveOptionalPackageRoot, resolvePackageRoot, resolvePackageRootEnvOverride, selectWebLocalRuntime, webUrlFor } from '../runtime/integrated-dev-dependencies.ts'; +import { resolveLocalMachineEnv } from '../runtime/default-kill-process.ts'; +import { createAgentCommand, createSetupSteps, createWatchEntries } from './create-watch-entries.ts'; +import { runtimeScopeKey } from '../runtime/dev-runtime-state.ts'; + +export function createApiCommand( + tenantRoot: string, + sharedEnv: NodeJS.ProcessEnv, + apiHost: string, + apiPort: number, +): IntegratedDevCommand { + const apiPackageRoot = resolve(tenantRoot, 'packages/api'); + return { + id: 'api', + label: 'Treeseed API', + command: 'tsx', + args: [resolve(apiPackageRoot, 'src/api/server.ts')], + cwd: apiPackageRoot, + env: { + ...sharedEnv, + HOST: apiHost, + PORT: String(apiPort), + TREESEED_ENVIRONMENT: sharedEnv.TREESEED_ENVIRONMENT ?? 'local', + TREESEED_API_ENVIRONMENT: sharedEnv.TREESEED_API_ENVIRONMENT ?? 'local', + TREESEED_API_REQUEST_LOGS: sharedEnv.TREESEED_API_REQUEST_LOGS ?? 'true', + }, + localRuntime: nodeLocalRuntime('Treeseed API'), + }; +} + +export function createMarketOperationsRunnerCommand( + tenantRoot: string, + sharedEnv: NodeJS.ProcessEnv, +): IntegratedDevCommand { + const apiPackageRoot = resolve(tenantRoot, 'packages/api'); + return { + id: 'operations-runner', + label: 'Treeseed Operations Runner', + command: 'tsx', + args: [ + resolve(apiPackageRoot, 'src/operations-runner/entrypoint.ts'), + 'run', + '--watch', + '--market', + 'local', + '--operation', + 'project:web_deployment', + '--poll-interval-ms', + '5000', + ], + cwd: apiPackageRoot, + env: { + ...sharedEnv, + TREESEED_PLATFORM_RUNNER_ENVIRONMENT: sharedEnv.TREESEED_PLATFORM_RUNNER_ENVIRONMENT ?? 'local', + }, + localRuntime: nodeLocalRuntime('Treeseed Operations Runner'), + }; +} + +export function createIntegratedDevPlan(options: IntegratedDevOptions = {}): IntegratedDevPlan { + const tenantRoot = resolve(options.cwd ?? process.cwd()); + const surface = options.surface ?? 'integrated'; + const setupMode = normalizeSetupMode(options.setupMode); + const feedbackMode = normalizeFeedbackMode(options.feedbackMode); + const openMode = normalizeOpenMode(options.openMode); + const watch = feedbackMode !== 'off' || options.watch === true; + const webHost = options.webHost ?? DEFAULT_WEB_HOST; + const webPort = normalizePort(options.webPort, DEFAULT_WEB_PORT); + const apiHost = options.apiHost ?? DEFAULT_API_HOST; + const apiPort = normalizePort(options.apiPort, DEFAULT_API_PORT); + const machineEnv = resolveLocalMachineEnv(tenantRoot); + const mergedEnv = { ...process.env, ...machineEnv, ...(options.env ?? {}) }; + const teamId = options.teamId ?? mergedEnv.TREESEED_HOSTING_TEAM_ID; + const apiBaseUrl = options.apiHost != null || options.apiPort != null + ? `http://${apiHost}:${apiPort}` + : mergedEnv.TREESEED_API_BASE_URL?.trim() || `http://${apiHost}:${apiPort}`; + const selectedCommandIds = selectedSurfaceCommandIds(options); + const marketWorkspace = isMarketWorkspace(tenantRoot); + const effectiveCommandIds = marketWorkspace + ? MARKET_DEV_COMMAND_IDS.filter((id) => selectedCommandIds.includes(id) || (id === 'operations-runner' && selectedCommandIds.includes('api'))) + : selectedCommandIds; + const devResetId = options.reset ? String(Date.now()) : undefined; + const webUrl = effectiveCommandIds.includes('web') ? webUrlFor(webHost, webPort) : null; + const sdkPackageRoot = resolvePackageRoot('@treeseed/sdk', tenantRoot); + const agentPackageRoot = resolvePackageRootEnvOverride(mergedEnv, 'TREESEED_AGENT_PACKAGE_ROOT', tenantRoot) + ?? resolveOptionalPackageRoot('@treeseed/agent', tenantRoot); + const cliPackageRoot = resolveOptionalPackageRoot('@treeseed/cli', tenantRoot); + const deployConfig = loadDevDeployConfig(tenantRoot); + const webLocalRuntime = selectWebLocalRuntime(deployConfig?.surfaces?.web, fallbackWebProviderFromDeployConfig(deployConfig), options.webRuntime); + const usesCloudflareWebRuntime = webLocalRuntime.selected === 'cloudflare-wrangler-local'; + const usesGeneratedLocalD1State = usesCloudflareWebRuntime + || webLocalRuntime.provider === 'cloudflare' + || (!marketWorkspace && effectiveCommandIds.some((id) => id !== 'web')); + const localD1PersistTo = mergedEnv.TREESEED_API_D1_LOCAL_PERSIST_TO ?? ( + usesGeneratedLocalD1State + ? resolve(tenantRoot, '.treeseed', 'generated', 'environments', 'local', '.wrangler', 'state', 'v3', 'd1') + : resolve(tenantRoot, '.wrangler', 'state', 'v3', 'd1') + ); + const projectId = options.projectId + ?? mergedEnv.TREESEED_PROJECT_ID + ?? resolveSeededLocalProjectId(localD1PersistTo); + const resolvedHostingTeamId = teamId ?? mergedEnv.TREESEED_HOSTING_TEAM_ID; + const resolvedTeamId = mergedEnv.TREESEED_TEAM_ID + ?? resolvedHostingTeamId + ?? resolveSeededLocalTeamId(localD1PersistTo, projectId ?? null); + const marketPostgresPort = mergedEnv.TREESEED_MARKET_LOCAL_POSTGRES_PORT ?? String(DEFAULT_MARKET_POSTGRES_PORT); + const apiDatabaseUrl = mergedEnv.TREESEED_DATABASE_URL + ?? `postgres://treeseed:treeseed@127.0.0.1:${marketPostgresPort}/market_local`; + const managedMarketPostgres = marketWorkspace && isManagedMarketPostgresUrl(apiDatabaseUrl); + const mailpitSmtpPort = mergedEnv.TREESEED_MAILPIT_SMTP_PORT ?? String(DEFAULT_LOCAL_SMTP_PORT); + const mailpitUiPort = mergedEnv.TREESEED_MAILPIT_UI_PORT ?? String(DEFAULT_MAILPIT_UI_PORT); + const webEntrypoint = resolveNodeEntrypoint( + sdkPackageRoot, + 'scripts/tenant/tenant-astro-command.ts', + 'dist/scripts/tenant/tenant-astro-command.js', + ); + const wranglerEntrypoint = { + command: process.execPath, + args: [ + resolveWranglerBin(), + 'dev', + '--local', + '--config', + generatedLocalWranglerPath(tenantRoot), + '--ip', + webHost, + '--port', + String(webPort), + ], + }; + const watchEntries = watch ? createWatchEntries(tenantRoot, { sdkPackageRoot, agentPackageRoot, cliPackageRoot }) : []; + const resetRequested = options.reset === true; + + const sharedEnv: NodeJS.ProcessEnv = { + ...mergedEnv, + LOCAL_DEV_MODE: usesCloudflareWebRuntime ? (mergedEnv.LOCAL_DEV_MODE ?? 'cloudflare') : undefined, + TREESEED_SITE_URL: mergedEnv.TREESEED_SITE_URL ?? webUrl, + BETTER_AUTH_URL: mergedEnv.BETTER_AUTH_URL ?? webUrl, + TREESEED_API_BASE_URL: apiBaseUrl, + TREESEED_MARKET_API_BASE_URL: mergedEnv.TREESEED_MARKET_API_BASE_URL ?? apiBaseUrl, + TREESEED_API_REQUEST_LOGS: mergedEnv.TREESEED_API_REQUEST_LOGS ?? 'true', + ...(marketWorkspace ? { + TREESEED_DATABASE_URL: apiDatabaseUrl, + TREESEED_MARKET_LOCAL_POSTGRES_CONTAINER: mergedEnv.TREESEED_MARKET_LOCAL_POSTGRES_CONTAINER ?? DEFAULT_MARKET_POSTGRES_CONTAINER, + TREESEED_MARKET_LOCAL_POSTGRES_VOLUME: mergedEnv.TREESEED_MARKET_LOCAL_POSTGRES_VOLUME ?? DEFAULT_MARKET_POSTGRES_VOLUME, + TREESEED_MARKET_LOCAL_POSTGRES_PORT: marketPostgresPort, + TREESEED_MARKET_LOCAL_POSTGRES_MANAGED: managedMarketPostgres ? 'true' : 'false', + } : {}), + TREESEED_PROJECT_ID: projectId ?? mergedEnv.TREESEED_PROJECT_ID, + TREESEED_TEAM_ID: resolvedTeamId ?? mergedEnv.TREESEED_TEAM_ID, + TREESEED_HOSTING_TEAM_ID: resolvedHostingTeamId ?? mergedEnv.TREESEED_HOSTING_TEAM_ID, + TREESEED_API_D1_DATABASE_NAME: mergedEnv.TREESEED_API_D1_DATABASE_NAME ?? 'SITE_DATA_DB', + SITE_DATA_DB: mergedEnv.SITE_DATA_DB ?? 'SITE_DATA_DB', + TREESEED_API_D1_LOCAL_PERSIST_TO: localD1PersistTo, + TREESEED_WEB_SERVICE_ID: mergedEnv.TREESEED_WEB_SERVICE_ID ?? mergedEnv.TREESEED_API_WEB_SERVICE_ID ?? 'web', + TREESEED_WEB_SERVICE_SECRET: + mergedEnv.TREESEED_WEB_SERVICE_SECRET + ?? mergedEnv.TREESEED_API_WEB_SERVICE_SECRET + ?? 'treeseed-web-service-dev-secret', + TREESEED_API_WEB_SERVICE_ID: mergedEnv.TREESEED_API_WEB_SERVICE_ID ?? mergedEnv.TREESEED_WEB_SERVICE_ID ?? 'web', + TREESEED_API_WEB_SERVICE_SECRET: + mergedEnv.TREESEED_API_WEB_SERVICE_SECRET + ?? mergedEnv.TREESEED_WEB_SERVICE_SECRET + ?? 'treeseed-web-service-dev-secret', + TREESEED_PLATFORM_RUNNER_SECRET: mergedEnv.TREESEED_PLATFORM_RUNNER_SECRET ?? 'treeseed-platform-runner-dev-secret', + FORM_TOKEN_SECRET: mergedEnv.FORM_TOKEN_SECRET ?? 'treeseed-local-form-token-secret', + TREESEED_BETTER_AUTH_SECRET: mergedEnv.TREESEED_BETTER_AUTH_SECRET ?? 'treeseed-local-better-auth-secret-minimum-32-characters', + ...(devResetId ? { TREESEED_DEV_RESET_ID: devResetId } : {}), + SMTP_HOST: DEFAULT_LOCAL_SMTP_HOST, + SMTP_PORT: mailpitSmtpPort, + SMTP_USERNAME: '', + SMTP_PASSWORD: '', + TREESEED_MAILPIT_SMTP_HOST: DEFAULT_LOCAL_SMTP_HOST, + TREESEED_MAILPIT_SMTP_PORT: mailpitSmtpPort, + TREESEED_MAILPIT_UI_PORT: mailpitUiPort, + TREESEED_AUTH_EMAIL_FROM: mergedEnv.TREESEED_AUTH_EMAIL_FROM ?? 'Treeseed Market ', + }; + const reset = createIntegratedDevResetPlan({ + tenantRoot, + env: sharedEnv, + enabled: resetRequested, + }); + + if (watch && feedbackMode === 'live') { + sharedEnv.TREESEED_PUBLIC_DEV_WATCH_RELOAD = sharedEnv.TREESEED_PUBLIC_DEV_WATCH_RELOAD || 'true'; + } + + const commands: IntegratedDevCommand[] = []; + + if (effectiveCommandIds.includes('web')) { + commands.push({ + id: 'web', + label: usesCloudflareWebRuntime ? 'Cloudflare Wrangler UI' : 'Astro UI', + command: usesCloudflareWebRuntime ? wranglerEntrypoint.command : webEntrypoint.command, + args: usesCloudflareWebRuntime + ? wranglerEntrypoint.args + : [...webEntrypoint.args, 'dev', '--host', webHost, '--port', String(webPort)], + cwd: tenantRoot, + env: sharedEnv, + localRuntime: webLocalRuntime, + }); + } + if (!marketWorkspace && effectiveCommandIds.some((id) => id !== 'web') && !agentPackageRoot) { + throw new Error('Unable to resolve @treeseed/agent for local API or agent service surfaces.'); + } + for (const id of effectiveCommandIds) { + if (id === 'web') continue; + if (marketWorkspace && id === 'api') { + commands.push(createApiCommand(tenantRoot, sharedEnv, apiHost, apiPort)); + continue; + } + if (marketWorkspace && id === 'operations-runner') { + commands.push(createMarketOperationsRunnerCommand(tenantRoot, sharedEnv)); + continue; + } + commands.push(createAgentCommand(id as Extract, tenantRoot, agentPackageRoot!, sharedEnv, apiHost, apiPort)); + } + + const readyChecks: IntegratedDevReadinessCheck[] = commands.map((command) => { + if (command.id === 'web' || command.id === 'api') { + return { + id: command.id, + label: command.label, + required: true, + strategy: 'http', + url: command.id === 'web' ? webUrl ?? undefined : `${apiBaseUrl.replace(/\/+$/u, '')}/healthz`, + }; + } + return { + id: command.id, + label: command.label, + required: command.id === 'operations-runner', + strategy: 'process', + }; + }); + return { + surface, + setupMode, + feedbackMode, + openMode, + watch, + tenantRoot, + apiBaseUrl, + webUrl, + setupSteps: createSetupSteps(tenantRoot, setupMode, sdkPackageRoot, { commands }, sharedEnv, usesCloudflareWebRuntime), + readyChecks, + watchEntries, + commands, + logPath: resolve(tenantRoot, '.treeseed', 'logs', `dev-${runtimeScopeKey(commands.map((command) => command.id))}.jsonl`), + localRuntimes: { + ...(commands.some((command) => command.id === 'web') ? { web: webLocalRuntime } : {}), + ...(commands.some((command) => command.id === 'api') ? { api: nodeLocalRuntime('Treeseed API') } : {}), + ...(commands.some((command) => command.id === 'manager') ? { manager: nodeLocalRuntime('Manager') } : {}), + ...(commands.some((command) => command.id === 'worker') ? { worker: nodeLocalRuntime('Worker Runner') } : {}), + ...(commands.some((command) => command.id === 'agents') ? { agents: nodeLocalRuntime('Agents Loop') } : {}), + ...(commands.some((command) => command.id === 'operations-runner') ? { marketRunner: nodeLocalRuntime('Treeseed Operations Runner') } : {}), + }, + restartPolicy: { + initialBackoffMs: INITIAL_RESTART_BACKOFF_MS, + maxBackoffMs: MAX_RESTART_BACKOFF_MS, + setupRetryBackoffMs: SETUP_RETRY_BACKOFF_MS, + commandImplementationChangesRequireRestart: true, + agentChanges: 'defer', + }, + reset, + }; +} + +export function defaultSignalRegistrar(signal: NodeJS.Signals, handler: () => void) { + process.on(signal, handler); + return () => { + process.off(signal, handler); + }; +} + +export function defaultPrepareEnvironment(tenantRoot: string) { + applyEnvironmentToProcess({ tenantRoot, scope: 'local', override: true }); + assertCommandEnvironment({ tenantRoot, scope: 'local', purpose: 'dev' }); +} diff --git a/src/dev/support/create-watch-entries.ts b/src/dev/support/create-watch-entries.ts new file mode 100644 index 00000000..8b6ae65e --- /dev/null +++ b/src/dev/support/create-watch-entries.ts @@ -0,0 +1,271 @@ +import { existsSync } from 'node:fs'; +import { resolve, sep } from 'node:path'; +import { packageScriptPath, resolveToolBinary } from '../../runtime/platform/workflow-support.ts'; +import { discoverApplications } from '../../runtime/platform/hosting.ts'; +import { packageRoot, type IntegratedDevCommand, type IntegratedDevCommandId, type IntegratedDevPlan, type IntegratedDevSetupMode, type IntegratedDevSetupStep, type IntegratedDevWatchEntry } from '../configuration/runtime-configuration.ts'; +import { dockerIsAvailable, nodeLocalRuntime } from './parse-surface-value.ts'; +import { generatedLocalWranglerPath, resolveNodeEntrypoint, resolveOptionalScriptEntrypoint } from '../runtime/integrated-dev-dependencies.ts'; + +export function createWatchEntries(tenantRoot: string, roots: { + sdkPackageRoot: string; + agentPackageRoot?: string | null; + cliPackageRoot?: string | null; +}): IntegratedDevWatchEntry[] { + const entries: IntegratedDevWatchEntry[] = [ + { kind: 'tenant', root: resolve(tenantRoot, 'src') }, + { kind: 'tenant', root: resolve(tenantRoot, 'content') }, + { kind: 'tenant', root: resolve(tenantRoot, 'public') }, + { kind: 'tenant', root: resolve(tenantRoot, 'astro.config.ts') }, + { kind: 'tenant', root: resolve(tenantRoot, 'astro.config.ts') }, + { kind: 'tenant', root: resolve(tenantRoot, 'treeseed.site.yaml') }, + { kind: 'tenant', root: resolve(tenantRoot, 'treeseed.config.ts') }, + { kind: 'tenant', root: resolve(tenantRoot, 'package.json') }, + { kind: 'tenant', root: resolve(tenantRoot, 'tsconfig.json') }, + ]; + + if (!packageRoot.split(sep).includes('node_modules')) { + entries.push( + { kind: 'core', root: resolve(packageRoot, 'src') }, + { kind: 'core', root: resolve(packageRoot, 'scripts', 'build-tenant-worker.ts') }, + { kind: 'core', root: resolve(packageRoot, 'package.json') }, + { kind: 'core', root: resolve(packageRoot, 'src', 'dev.ts'), restartRequired: true }, + { kind: 'core', root: resolve(packageRoot, 'scripts', 'dev-platform.ts'), restartRequired: true }, + ); + } + if (!roots.sdkPackageRoot.split(sep).includes('node_modules')) { + entries.push( + { kind: 'sdk', root: resolve(roots.sdkPackageRoot, 'src') }, + { kind: 'sdk', root: resolve(roots.sdkPackageRoot, 'scripts', 'tenant-astro-command.ts') }, + { kind: 'sdk', root: resolve(roots.sdkPackageRoot, 'scripts', 'tenant-d1-migrate-local.ts') }, + { kind: 'sdk', root: resolve(roots.sdkPackageRoot, 'package.json') }, + ); + } + if (roots.agentPackageRoot && !roots.agentPackageRoot.split(sep).includes('node_modules')) { + entries.push( + { kind: 'agent', root: resolve(roots.agentPackageRoot, 'src') }, + { kind: 'agent', root: resolve(roots.agentPackageRoot, 'package.json') }, + ); + } + if (roots.cliPackageRoot && !roots.cliPackageRoot.split(sep).includes('node_modules')) { + entries.push( + { kind: 'cli', root: resolve(roots.cliPackageRoot, 'src', 'cli', 'handlers', 'dev.ts'), restartRequired: true }, + { kind: 'cli', root: resolve(roots.cliPackageRoot, 'dist', 'cli', 'handlers', 'dev.js'), restartRequired: true }, + ); + } + try { + for (const app of discoverApplications(tenantRoot)) { + if (app.relativeRoot === '.') continue; + entries.push({ kind: 'tenant', root: app.configPath, restartRequired: true }); + } + } catch { + // The root manifest watcher above still catches config edits while a manifest is mid-edit. + } + + return entries; +} + +export function createSetupSteps( + tenantRoot: string, + setupMode: IntegratedDevSetupMode, + sdkPackageRoot: string, + planLike: Pick, + env: NodeJS.ProcessEnv, + usesCloudflareWebRuntime: boolean, +): IntegratedDevSetupStep[] { + if (setupMode === 'off') { + return [ + { + id: 'setup-disabled', + label: 'Local setup disabled', + required: false, + status: 'skipped', + detail: 'Run without --setup off to prepare workspace links, local D1 state, and generated dev artifacts.', + }, + ]; + } + + const hasWebCommand = planLike.commands.some((command) => command.id === 'web'); + const hasLocalRuntimeCommand = planLike.commands.some((command) => + command.id !== 'web' + && command.id !== 'operations-runner' + && command.label !== 'Treeseed API' + ); + const hasApiCommand = planLike.commands.some((command) => command.label === 'Treeseed API'); + const managedMarketPostgres = env.TREESEED_MARKET_LOCAL_POSTGRES_MANAGED === 'true'; + const needsCloudflareLocalRuntime = usesCloudflareWebRuntime || hasLocalRuntimeCommand; + const coreScripts = [ + ['starlight-patch', 'Patch Starlight content path', 'scripts/content/patch-starlight-content-path.ts', 'dist/scripts/content/patch-starlight-content-path.js'], + ['worker-bundle', 'Generate local worker bundle', 'scripts/build/build-tenant-worker.ts', 'dist/scripts/build/build-tenant-worker.js'], + ] as const; + const tenantBuild = usesCloudflareWebRuntime + ? { + command: process.execPath, + args: [packageScriptPath('build/tenant-build')], + } + : null; + const dockerReady = dockerIsAvailable(env); + const apiPackageRoot = resolve(tenantRoot, 'packages/api'); + const marketMigrateScript = existsSync(resolve(apiPackageRoot, 'scripts/migrate-db.ts')) + ? { + command: 'tsx', + args: [resolve(apiPackageRoot, 'scripts/migrate-db.ts')], + } + : null; + const steps: IntegratedDevSetupStep[] = [ + { + id: 'workspace-links', + label: 'Ensure local workspace links', + required: setupMode === 'auto', + status: 'planned', + }, + ...(hasApiCommand && managedMarketPostgres ? [ + { + id: 'market-postgres', + label: 'Start local Market PostgreSQL', + required: true, + status: dockerReady ? 'planned' : 'failed', + detail: dockerReady + ? 'Treeseed will manage the local Market PostgreSQL container automatically.' + : 'Docker daemon is unavailable; local API requires managed PostgreSQL.', + } satisfies IntegratedDevSetupStep, + ] : []), + ...(hasApiCommand ? [ + { + id: 'market-migrations', + label: 'Apply local Market database migrations', + required: true, + command: marketMigrateScript?.command, + args: marketMigrateScript?.args, + status: marketMigrateScript ? 'planned' : 'failed', + detail: marketMigrateScript ? undefined : 'Unable to resolve packages/api/scripts/migrate-db.ts.', + } satisfies IntegratedDevSetupStep, + ] : []), + { + id: 'wrangler', + label: 'Verify Wrangler executable', + required: needsCloudflareLocalRuntime, + status: 'planned', + detail: resolveToolBinary('wrangler', { env }) ?? undefined, + }, + ...(needsCloudflareLocalRuntime ? [ + { + id: 'wrangler-config', + label: 'Generate local Wrangler config', + required: true, + status: 'planned', + detail: generatedLocalWranglerPath(tenantRoot), + } satisfies IntegratedDevSetupStep, + ] : []), + ...(usesCloudflareWebRuntime && hasWebCommand ? [ + { + id: 'web-build', + label: 'Build local Cloudflare web runtime', + required: true, + command: tenantBuild?.command, + args: tenantBuild?.args, + status: tenantBuild ? 'planned' : 'failed', + detail: tenantBuild ? undefined : 'Unable to resolve the tenant build script.', + } satisfies IntegratedDevSetupStep, + ] : (hasWebCommand ? coreScripts.map(([id, label, source, dist]) => { + const script = resolveOptionalScriptEntrypoint(packageRoot, source, dist); + return { + id, + label, + required: true, + command: script?.command, + args: script?.args, + status: script ? 'planned' : 'skipped', + detail: script ? undefined : `Script not found at ${source}.`, + } satisfies IntegratedDevSetupStep; + }) : [])), + ]; + + if (needsCloudflareLocalRuntime) { + const migrate = resolveOptionalScriptEntrypoint( + sdkPackageRoot, + 'scripts/tenant-d1-migrate-local.ts', + 'dist/scripts/tenant-d1-migrate-local.js', + ); + steps.push({ + id: 'd1-migrations', + label: 'Run local D1 migrations', + required: true, + command: migrate?.command, + args: migrate?.args, + status: migrate ? 'planned' : 'failed', + detail: migrate ? undefined : 'Unable to resolve the local D1 migration script.', + }); + } + + return steps; +} + +export function createAgentCommand( + id: Extract, + tenantRoot: string, + agentPackageRoot: string, + sharedEnv: NodeJS.ProcessEnv, + apiHost: string, + apiPort: number, +): IntegratedDevCommand { + const configs: Record, { + label: string; + source: string; + dist: string; + extraArgs?: string[]; + extraEnv: NodeJS.ProcessEnv; + }> = { + api: { + label: 'Treeseed API', + source: 'src/api/server.ts', + dist: 'dist/api/server.js', + extraEnv: { + HOST: apiHost, + PORT: String(apiPort), + TREESEED_API_REPO_ROOT: tenantRoot, + }, + }, + manager: { + label: 'Manager', + source: 'src/services/manager.ts', + dist: 'dist/services/manager.js', + extraArgs: ['--mode', 'loop'], + extraEnv: { + TREESEED_MANAGER_MODE: 'loop', + }, + }, + worker: { + label: 'Worker Runner', + source: 'src/services/worker.ts', + dist: 'dist/services/worker.js', + extraEnv: {}, + }, + agents: { + label: 'Agents Loop', + source: 'src/services/agents.ts', + dist: 'dist/services/agents.js', + extraEnv: {}, + }, + }; + const config = configs[id]; + const entrypoint = resolveNodeEntrypoint(agentPackageRoot, config.source, config.dist); + const { + TREESEED_AGENT_D1_DATABASE: _agentD1Database, + TREESEED_AGENT_D1_PERSIST_TO: _agentD1PersistTo, + ...agentSharedEnv + } = sharedEnv; + return { + id, + label: config.label, + command: entrypoint.command, + args: [...entrypoint.args, ...(config.extraArgs ?? [])], + cwd: tenantRoot, + env: { + ...agentSharedEnv, + TREESEED_AGENT_REPO_ROOT: tenantRoot, + TREESEED_ENVIRONMENT: sharedEnv.TREESEED_ENVIRONMENT ?? 'local', + ...config.extraEnv, + }, + localRuntime: nodeLocalRuntime(config.label), + }; +} diff --git a/src/dev/support/parse-surface-value.ts b/src/dev/support/parse-surface-value.ts new file mode 100644 index 00000000..1249eb22 --- /dev/null +++ b/src/dev/support/parse-surface-value.ts @@ -0,0 +1,264 @@ +import { existsSync, readdirSync, statSync } from 'node:fs'; +import { spawnSync } from 'node:child_process'; +import { isAbsolute, relative, resolve } from 'node:path'; +import { DatabaseSync } from 'node:sqlite'; +import { resolveToolBinary } from '../../runtime/platform/workflow-support.ts'; +import { DEV_RELOAD_FILE, DEFAULT_MARKET_POSTGRES_PORT, type IntegratedDevCommandId, type IntegratedDevOptions, type IntegratedDevResetAction, type IntegratedDevResetPlan, type IntegratedDevSurface, type LocalRuntimeSelection } from '../configuration/runtime-configuration.ts'; +import { CANONICAL_COMMAND_IDS, isMarketWorkspace, surfaceCommandIds } from '../runtime/integrated-dev-dependencies.ts'; + +export function parseSurfaceValue(value: string): IntegratedDevSurface | null { + return ( + value === 'web' || + value === 'api' || + value === 'manager' || + value === 'worker' || + value === 'agents' || + value === 'services' || + value === 'all' || + value === 'integrated' + ) ? value : null; +} + +export function selectedSurfaceCommandIds(options: Pick) { + const values = (options.surfaces?.trim() || options.surface || 'integrated') + .split(',') + .map((entry) => entry.trim()) + .filter(Boolean); + const selected = new Set(); + for (const value of values.length > 0 ? values : ['integrated']) { + const surface = parseSurfaceValue(value); + if (!surface) continue; + for (const id of surfaceCommandIds(surface)) { + selected.add(id); + } + } + const selectedIds = CANONICAL_COMMAND_IDS.filter((id) => selected.has(id)); + return selectedIds.length > 0 ? selectedIds : surfaceCommandIds('integrated'); +} + +export function nodeLocalRuntime(label: string): LocalRuntimeSelection { + return { + requested: 'local', + provider: 'local', + selected: 'node-local', + reason: `${label} runs as a local Node.js process.`, + }; +} + +export function dockerComposeIsAvailable(env: NodeJS.ProcessEnv) { + const docker = resolveToolBinary('docker', { env }); + if (!docker) return false; + const result = spawnSync(docker, ['compose', 'version'], { + encoding: 'utf8', + env, + }); + return (result.status ?? 1) === 0; +} + +export function dockerIsAvailable(env: NodeJS.ProcessEnv) { + const docker = resolveToolBinary('docker', { env }); + if (!docker) return false; + const result = spawnSync(docker, ['info'], { + encoding: 'utf8', + env, + }); + return (result.status ?? 1) === 0; +} + +export function resetActionForPath( + id: IntegratedDevResetAction['id'], + label: string, + path: string, +): IntegratedDevResetAction { + return { + id, + label, + kind: 'path', + path, + status: existsSync(path) ? 'planned' : 'skipped', + detail: existsSync(path) ? undefined : 'Path does not exist.', + }; +} + +export function uniqueResetActions(actions: IntegratedDevResetAction[]) { + const seen = new Set(); + return actions.filter((action) => { + const key = action.kind === 'path' ? `${action.kind}:${action.path}` : `${action.kind}:${action.id}`; + if (seen.has(key)) return false; + seen.add(key); + return true; + }); +} + +export function optionalResetActionForPath( + id: IntegratedDevResetAction['id'], + label: string, + path: string, +) { + return existsSync(path) ? resetActionForPath(id, label, path) : null; +} + +export function pathContains(parent: string, child: string) { + const diff = relative(parent, child); + return diff === '' || (diff.length > 0 && !diff.startsWith('..') && !isAbsolute(diff)); +} + +export function knownLocalRuntimeStateResetActions(tenantRoot: string, activePersistTo: string) { + const localGeneratedWranglerState = resolve(tenantRoot, '.treeseed', 'generated', 'environments', 'local', '.wrangler', 'state', 'v3'); + const rootWranglerState = resolve(tenantRoot, '.wrangler', 'state', 'v3'); + const stateRoots = [ + optionalResetActionForPath( + 'generated-wrangler-state', + 'Remove generated local Wrangler runtime state', + localGeneratedWranglerState, + ), + optionalResetActionForPath( + 'root-wrangler-state', + 'Remove root Wrangler runtime state', + rootWranglerState, + ), + ].filter((action): action is IntegratedDevResetAction => Boolean(action)); + const coveredByStateRoot = stateRoots.some((action) => action.path && pathContains(action.path, activePersistTo)); + return uniqueResetActions([ + ...(coveredByStateRoot ? [] : [resetActionForPath('d1-state', 'Remove active local D1 state', activePersistTo)]), + ...stateRoots, + optionalResetActionForPath( + 'legacy-local-sqlite', + 'Remove legacy local SQLite state', + resolve(tenantRoot, '.treeseed', 'generated', 'environments', 'local', 'site-data.sqlite'), + ), + ].filter((action): action is IntegratedDevResetAction => Boolean(action))); +} + +export function isManagedMarketPostgresUrl(value: string | undefined) { + if (!value?.trim()) return true; + try { + const url = new URL(value); + const port = url.port || (url.protocol === 'postgres:' || url.protocol === 'postgresql:' ? '5432' : ''); + return ['postgres:', 'postgresql:'].includes(url.protocol) + && ['127.0.0.1', 'localhost'].includes(url.hostname) + && port === String(DEFAULT_MARKET_POSTGRES_PORT) + && url.pathname === '/market_local' + && decodeURIComponent(url.username) === 'treeseed'; + } catch { + return false; + } +} + +export function resolveLocalD1SqlitePath(persistTo: string) { + if (/\.sqlite$/u.test(persistTo) && existsSync(persistTo)) { + return persistTo; + } + const miniflareRoot = resolve(persistTo, 'miniflare-D1DatabaseObject'); + if (existsSync(miniflareRoot)) { + const candidates = readdirSync(miniflareRoot) + .filter((entry) => /\.sqlite$/u.test(entry) && entry !== 'metadata.sqlite') + .map((entry) => { + const path = resolve(miniflareRoot, entry); + return { + path, + size: statSync(path).size, + }; + }) + .sort((left, right) => right.size - left.size || left.path.localeCompare(right.path)); + if (candidates[0]?.path) { + return candidates[0].path; + } + } + const siteDataPath = resolve(persistTo, 'site-data.sqlite'); + return existsSync(siteDataPath) ? siteDataPath : null; +} + +export function resolveSeededLocalProjectId(persistTo: string, projectSlug = 'market') { + const sqlitePath = resolveLocalD1SqlitePath(persistTo); + if (!sqlitePath) return null; + let db: DatabaseSync | null = null; + try { + db = new DatabaseSync(sqlitePath, { readOnly: true }); + const row = db.prepare( + `SELECT id FROM projects WHERE LOWER(slug) = LOWER(?) ORDER BY created_at ASC LIMIT 1`, + ).get(projectSlug) as { id?: unknown } | undefined; + return typeof row?.id === 'string' && row.id.trim() ? row.id.trim() : null; + } catch { + return null; + } finally { + db?.close(); + } +} + +export function resolveSeededLocalTeamId(persistTo: string, projectId: string | null, teamSlug = 'treeseed') { + const sqlitePath = resolveLocalD1SqlitePath(persistTo); + if (!sqlitePath) return null; + let db: DatabaseSync | null = null; + try { + db = new DatabaseSync(sqlitePath, { readOnly: true }); + if (projectId) { + const projectRow = db.prepare( + `SELECT team_id FROM projects WHERE id = ? LIMIT 1`, + ).get(projectId) as { team_id?: unknown } | undefined; + if (typeof projectRow?.team_id === 'string' && projectRow.team_id.trim()) { + return projectRow.team_id.trim(); + } + } + const teamRow = db.prepare( + `SELECT id FROM teams WHERE LOWER(slug) = LOWER(?) ORDER BY created_at ASC LIMIT 1`, + ).get(teamSlug) as { id?: unknown } | undefined; + return typeof teamRow?.id === 'string' && teamRow.id.trim() ? teamRow.id.trim() : null; + } catch { + return null; + } finally { + db?.close(); + } +} + +export function createIntegratedDevResetPlan(options: { + tenantRoot: string; + env: NodeJS.ProcessEnv; + enabled?: boolean; +}): IntegratedDevResetPlan | null { + if (!options.enabled) { + return null; + } + const tenantRoot = options.tenantRoot; + const d1PersistTo = options.env.TREESEED_API_D1_LOCAL_PERSIST_TO?.trim() || resolve(tenantRoot, '.wrangler', 'state', 'v3', 'd1'); + const marketWorkspace = isMarketWorkspace(tenantRoot); + const managedMarketPostgres = options.env.TREESEED_MARKET_LOCAL_POSTGRES_MANAGED === 'true'; + return { + enabled: true, + actions: [ + ...knownLocalRuntimeStateResetActions(tenantRoot, d1PersistTo), + ...(marketWorkspace ? [{ + id: 'market-postgres', + label: managedMarketPostgres ? 'Reset local Market PostgreSQL' : 'Skip external Market PostgreSQL', + kind: 'service', + status: managedMarketPostgres ? 'planned' : 'skipped', + detail: managedMarketPostgres + ? 'The Treeseed-managed Market PostgreSQL container, database, and volume will be removed and recreated on the next dev run.' + : 'TREESEED_DATABASE_URL points at an external database, so dev reset will not drop it.', + } satisfies IntegratedDevResetAction] : []), + resetActionForPath('wrangler-tmp', 'Remove Wrangler temporary output', resolve(tenantRoot, '.wrangler', 'tmp')), + resetActionForPath('worker-bundle', 'Remove generated local worker bundle', resolve(tenantRoot, '.treeseed', 'generated', 'worker')), + { + id: 'dev-reload', + label: 'Refresh browser reload marker', + kind: 'path', + path: resolve(tenantRoot, DEV_RELOAD_FILE), + status: 'planned', + detail: 'The browser reload marker will be recreated so open tabs do not poll a missing file after reset.', + }, + ], + preserved: [ + '.env*', + 'treeseed.site.yaml', + 'src/env.yaml', + '.treeseed/config', + '.treeseed/generated/environments', + '.treeseed/state', + '.treeseed/workflow', + '.treeseed/workspace-links.json', + 'migrations', + 'node_modules', + 'Treeseed-managed local service containers', + ], + }; +} diff --git a/src/index.ts b/src/index.ts index d62622e5..6707e36b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,39 +1,29 @@ export { - buildTreeseedSiteLayers, - resolveTreeseedPageEntrypoint, - resolveTreeseedSiteResource, - resolveTreeseedStyleEntrypoint, - TREESEED_SITE_RESOURCE_KINDS, -} from './site-resources'; + buildSiteLayers, + resolvePageEntrypoint, + resolveSiteResource, + resolveStyleEntrypoint, + SITE_RESOURCE_KINDS, +} from './support/site-resources'; export { - buildTreeseedPlatformLayers, - resolveTreeseedPlatformResource, - TREESEED_PLATFORM_RESOURCE_KINDS, -} from './platform-resources'; -export { parseSiteConfig } from './utils/site-config-schema.js'; + buildPlatformLayers, + resolvePlatformResource, + PLATFORM_RESOURCE_KINDS, +} from './support/platform-resources'; +export { parseSiteConfig } from './utils/configuration/site-config-schema.js'; export { - executeKnowledgeHubProviderLaunch, - validateKnowledgeHubProviderLaunchPrerequisites, -} from './launch'; -export { - createTreeseedIntegratedDevPlan, - runTreeseedManagedDev, - runTreeseedIntegratedDev, - type TreeseedIntegratedDevCommand, - type TreeseedIntegratedDevOptions, - type TreeseedIntegratedDevPlan, - type TreeseedIntegratedDevSurface, - type TreeseedManagedDevOptions, - type TreeseedDevInstanceRecord, -} from './dev'; + createIntegratedDevPlan, + runManagedDev, + runIntegratedDev, + type IntegratedDevCommand, + type IntegratedDevOptions, + type IntegratedDevPlan, + type IntegratedDevSurface, + type ManagedDevOptions, + type DevInstanceRecord, +} from './runtime/dev'; export { filterSiteRenderedModels, isSiteRenderedModel, siteModelRendered, -} from './utils/site-models.ts'; -export { - buildPrivateKnowledgeReaderViewModel, - type RuntimeReaderNavGroup, - type RuntimeReaderNavItem, - type RuntimeReaderViewModel, -} from './utils/runtime-reader.ts'; +} from './utils/support/site-models.ts'; diff --git a/src/launch.ts b/src/launch.ts deleted file mode 100644 index 1b6603ff..00000000 --- a/src/launch.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { - executeKnowledgeHubProviderLaunch, - validateKnowledgeHubProviderLaunchPrerequisites, - type KnowledgeHubProviderLaunchInput, - type KnowledgeHubProviderLaunchResult, - type KnowledgeHubProviderLaunchPreflightReport, -} from '@treeseed/sdk/operations/hub-provider-launch'; - -export { - executeKnowledgeHubProviderLaunch, - validateKnowledgeHubProviderLaunchPrerequisites, -}; - -export type { - KnowledgeHubProviderLaunchInput, - KnowledgeHubProviderLaunchResult, - KnowledgeHubProviderLaunchPreflightReport, -}; diff --git a/src/middleware.ts b/src/middleware.ts deleted file mode 100644 index 121a8cf0..00000000 --- a/src/middleware.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { defineMiddleware } from 'astro:middleware'; -import { resolveEditorialPreview } from './middleware/editorial-preview.js'; -import { applyTreeseedWebCacheHeaders } from './utils/web-cache.js'; - -export const onRequest = defineMiddleware(async (context, next) => { - resolveEditorialPreview(context); - const response = await next(); - return applyTreeseedWebCacheHeaders(context.request, context.url, response); -}); - diff --git a/src/middleware/editorial-preview.ts b/src/middleware/editorial-preview.ts index 6f64083e..6c4938c7 100644 --- a/src/middleware/editorial-preview.ts +++ b/src/middleware/editorial-preview.ts @@ -2,8 +2,8 @@ import { EDITORIAL_PREVIEW_COOKIE, verifyEditorialPreviewToken, type EditorialPreviewTokenPayload, -} from '@treeseed/sdk/platform/published-content'; -import type { CloudflareRuntime } from '@treeseed/sdk/types/cloudflare'; +} from '../runtime/platform/published-content.ts'; +import type { CloudflareRuntime } from '@treeseed/sdk/site-contracts/cloudflare'; type CookieValue = { value: string } | undefined; diff --git a/src/middleware/starlightRouteData.ts b/src/middleware/starlightRouteData.ts deleted file mode 100644 index 29ec87b1..00000000 --- a/src/middleware/starlightRouteData.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { defineRouteMiddleware } from '@astrojs/starlight/route-data'; -import type { StarlightRouteData } from '@astrojs/starlight/route-data'; -import type { TreeseedBookRuntime } from '@treeseed/sdk/platform/books-data'; -import type { TreeseedContentCollection } from '@treeseed/sdk/platform/contracts'; -import { TREESEED_LINKS, buildStarlightSidebarEntriesFromRuntime, normalizeHref } from '../utils/starlight-nav'; -import { loadHostedBookRuntime } from '../utils/published-content'; -import { RUNTIME_TENANT } from '../tenant/runtime-config'; - -type SidebarEntry = StarlightRouteData['sidebar'][number]; -type SidebarLink = Extract; -type SidebarGroup = Extract; -type PaginationLinks = StarlightRouteData['pagination']; - -const copyLink = (entry: SidebarLink): SidebarLink => ({ ...entry, attrs: { ...entry.attrs } }); - -const copyEntry = (entry: SidebarEntry): SidebarEntry => - entry.type === 'link' - ? copyLink(entry) - : { - ...entry, - entries: entry.entries.map(copyEntry), - }; - -const findTopLevelGroup = (sidebar: SidebarEntry[], label: string) => - sidebar.find((entry): entry is SidebarGroup => entry.type === 'group' && entry.label === label); - -const flattenLinks = (entries: SidebarEntry[]): SidebarLink[] => - entries.flatMap((entry) => (entry.type === 'link' ? [copyLink(entry)] : flattenLinks(entry.entries))); - -const findTopLevelGroupForPath = (sidebar: SidebarEntry[], currentPath: string) => - sidebar.find((entry): entry is SidebarGroup => - entry.type === 'group' && - flattenLinks(entry.entries).some((link) => normalizeHref(link.href) === normalizeHref(currentPath)), - ); - -const buildPagination = (entries: SidebarEntry[], currentHref: string): PaginationLinks => { - const flatLinks = flattenLinks(entries); - const currentIndex = flatLinks.findIndex((link) => normalizeHref(link.href) === normalizeHref(currentHref)); - - if (currentIndex === -1) { - return { prev: undefined, next: undefined }; - } - - return { - prev: currentIndex > 0 ? flatLinks[currentIndex - 1] : undefined, - next: currentIndex < flatLinks.length - 1 ? flatLinks[currentIndex + 1] : undefined, - }; -}; - -const setRouteSidebar = ( - route: StarlightRouteData, - currentPath: string, - sidebar: SidebarEntry[], - paginationSource: SidebarEntry[] | null, -) => { - route.sidebar = sidebar; - route.hasSidebar = sidebar.length > 0; - route.pagination = paginationSource ? buildPagination(paginationSource, currentPath) : { prev: undefined, next: undefined }; -}; - -function runtimeTenantModelRendered(modelName: TreeseedContentCollection) { - const featureValue = RUNTIME_TENANT.features?.[modelName as keyof typeof RUNTIME_TENANT.features]; - const siteValue = RUNTIME_TENANT.site?.[modelName as keyof typeof RUNTIME_TENANT.site]; - - return featureValue ?? siteValue ?? true; -} - -export const onRequest = defineRouteMiddleware(async (context) => { - const route = context.locals.starlightRoute; - const currentPath = normalizeHref(context.url.pathname); - if (!runtimeTenantModelRendered('books')) { - setRouteSidebar(route, currentPath, [], null); - return; - } - let runtime: TreeseedBookRuntime | null = null; - try { - runtime = await loadHostedBookRuntime(context.locals); - } catch { - runtime = null; - } - - if (!runtime) { - const bookGroup = findTopLevelGroupForPath(route.sidebar, currentPath); - if (bookGroup) { - setRouteSidebar(route, currentPath, [copyEntry(bookGroup)], bookGroup.entries); - return; - } - - if (currentPath === normalizeHref(TREESEED_LINKS.home)) { - setRouteSidebar(route, currentPath, [], null); - } - return; - } - - route.sidebar = buildStarlightSidebarEntriesFromRuntime(runtime, currentPath); - - const activeBook = runtime.BOOKS.find((book) => - currentPath.startsWith(normalizeHref(book.basePath)), - ); - if (activeBook) { - const bookGroup = findTopLevelGroup(route.sidebar, activeBook.sectionLabel); - if (!bookGroup) return; - - setRouteSidebar(route, currentPath, [copyEntry(bookGroup)], bookGroup.entries); - return; - } - - if (currentPath === normalizeHref(runtime.TREESEED_LINKS.home)) { - setRouteSidebar(route, currentPath, [], null); - return; - } -}); diff --git a/src/pages/[slug].astro b/src/pages/[slug].astro index d2290f57..c60c4fa2 100644 --- a/src/pages/[slug].astro +++ b/src/pages/[slug].astro @@ -4,7 +4,7 @@ import ContentLayout from '@treeseed/ui/components/astro/layouts/ContentLayout.a import BridgeLayout from '@treeseed/ui/components/astro/layouts/BridgeLayout.astro'; import PublishedContentBody from '@treeseed/ui/components/astro/site/PublishedContentBody.astro'; import RouteNotFound from '@treeseed/ui/components/astro/site/RouteNotFound.astro'; -import { isPublishedRuntimeContentMode, loadPublishedEntry } from '../utils/site-content-runtime'; +import { isPublishedRuntimeContentMode, loadPublishedEntry } from '../utils/content/site-content-runtime'; export const prerender = false; diff --git a/src/pages/agents/[slug].astro b/src/pages/agents/[slug].astro index c4783fe2..3b1b0461 100644 --- a/src/pages/agents/[slug].astro +++ b/src/pages/agents/[slug].astro @@ -1,10 +1,10 @@ --- import CoreStyleScope from '../../layouts/CoreStyleScope.astro'; import ProfileLayout from '@treeseed/ui/components/astro/layouts/ProfileLayout.astro'; -import { resolveReferences } from '../../utils/hub-content'; +import { resolveReferences } from '../../utils/content/hub-content'; import PublishedContentBody from '@treeseed/ui/components/astro/site/PublishedContentBody.astro'; import RouteNotFound from '@treeseed/ui/components/astro/site/RouteNotFound.astro'; -import { isPublishedRuntimeContentMode, loadPublishedEntry, metadataFromPublishedContent, resolvePublishedReferences } from '../../utils/site-content-runtime'; +import { isPublishedRuntimeContentMode, loadPublishedEntry, metadataFromPublishedContent, resolvePublishedReferences } from '../../utils/content/site-content-runtime'; export const prerender = false; diff --git a/src/pages/agents/index.astro b/src/pages/agents/index.astro index 78fa86c4..366861f0 100644 --- a/src/pages/agents/index.astro +++ b/src/pages/agents/index.astro @@ -3,7 +3,7 @@ import CoreStyleScope from '../../layouts/CoreStyleScope.astro'; import MainLayout from '@treeseed/ui/components/astro/layouts/MainLayout.astro'; import SectionIntro from '@treeseed/ui/components/astro/site/SectionIntro.astro'; import ProfileList from '@treeseed/ui/components/astro/site/ProfileList.astro'; -import { isPublishedRuntimeContentMode, loadPublishedCollection } from '../../utils/site-content-runtime'; +import { isPublishedRuntimeContentMode, loadPublishedCollection } from '../../utils/content/site-content-runtime'; const publishedRuntime = isPublishedRuntimeContentMode(); const agents = publishedRuntime @@ -26,7 +26,7 @@ const agents = publishedRuntime name: agent.data.name, summary: agent.data.summary, meta: `${agent.data.operator} · ${agent.data.runtimeStatus}`, - tags: agent.data.tags, + tags: agent.data.groupIds, }))} /> diff --git a/src/pages/api/feedback/submit.ts b/src/pages/api/feedback/submit.ts deleted file mode 100644 index 5df2d050..00000000 --- a/src/pages/api/feedback/submit.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { APIRoute } from 'astro'; - -export const prerender = false; - -function runtimeEnv(locals: App.Locals | Record | null | undefined) { - return (locals as App.Locals | undefined)?.runtime?.env as Record | undefined; -} - -function envValue(locals: App.Locals | Record | null | undefined, name: string) { - const runtimeValue = runtimeEnv(locals)?.[name]; - if (typeof runtimeValue === 'string' && runtimeValue.trim()) return runtimeValue.trim(); - const processValue = (globalThis as { process?: { env?: Record } }).process?.env?.[name]; - return typeof processValue === 'string' && processValue.trim() ? processValue.trim() : ''; -} - -function marketApiBaseUrl(locals: App.Locals | Record | null | undefined) { - return ( - envValue(locals, 'TREESEED_MARKET_API_BASE_URL') - || envValue(locals, 'TREESEED_CENTRAL_MARKET_API_BASE_URL') - || 'https://api.treeseed.dev' - ).replace(/\/+$/u, ''); -} - -export const POST: APIRoute = async (context) => { - const body = await context.request.text(); - const response = await fetch(`${marketApiBaseUrl(context.locals)}/v1/feedback`, { - method: 'POST', - headers: { - accept: 'application/json', - 'content-type': context.request.headers.get('content-type') ?? 'application/json', - }, - body, - }); - const text = await response.text(); - return new Response(text, { - status: response.status, - headers: { - 'content-type': response.headers.get('content-type') ?? 'application/json', - 'cache-control': 'no-store', - }, - }); -}; diff --git a/src/pages/api/form/submit.ts b/src/pages/api/form/submit.ts index 21ed5950..e349a285 100644 --- a/src/pages/api/form/submit.ts +++ b/src/pages/api/form/submit.ts @@ -1,5 +1,6 @@ import type { APIContext } from 'astro'; -import { handleFormSubmission, handleTokenRequest } from '../../../utils/forms/service'; +import { formSubmissionResponse } from '@treeseed/ui/forms'; +import { handleFormSubmission, handleTokenRequest } from '../../../utils/forms/service/service'; export async function GET(context: APIContext) { return handleTokenRequest(context); @@ -7,5 +8,10 @@ export async function GET(context: APIContext) { export async function POST(context: APIContext) { const result = await handleFormSubmission(context); - return context.redirect(result.redirectTo, 303); + return formSubmissionResponse(context.request, { + ok: result.ok, + code: result.code, + message: result.message, + reset: result.ok, + }, { fallbackRedirect: result.redirectTo }); } diff --git a/src/pages/books/[slug].astro b/src/pages/books/[slug].astro deleted file mode 100644 index 03725b1a..00000000 --- a/src/pages/books/[slug].astro +++ /dev/null @@ -1,35 +0,0 @@ ---- -import CoreStyleScope from '../../layouts/CoreStyleScope.astro'; -import BookLayout from '@treeseed/ui/components/astro/layouts/BookLayout.astro'; -import PublishedContentBody from '@treeseed/ui/components/astro/site/PublishedContentBody.astro'; -import RouteNotFound from '@treeseed/ui/components/astro/site/RouteNotFound.astro'; -import { isPublishedRuntimeContentMode, loadPublishedEntry } from '../../utils/site-content-runtime'; - -export const prerender = false; - -const slug = String(Astro.params.slug ?? ''); -const publishedRuntime = isPublishedRuntimeContentMode(); -const contentApi = publishedRuntime ? null : await import('astro:content'); -const books = contentApi ? (await contentApi.getCollection('books')).sort((a, b) => a.data.order - b.data.order) : []; -const localBook = books.find((candidate) => candidate.id === slug || candidate.data.slug === slug) ?? null; -const publishedBook = publishedRuntime ? await loadPublishedEntry(Astro.locals, 'books', slug) : null; -const book = publishedRuntime ? publishedBook?.entry ?? null : localBook; -if (!book) { - Astro.response.status = 404; -} -const rendered = contentApi && localBook ? await contentApi.render(localBook) : null; -const Content = rendered?.Content ?? null; ---- - - - -{ - !book || (!Content && !publishedBook?.html) ? ( - - ) : ( - - {publishedBook?.html ? : Content ? : null} - - ) -} - diff --git a/src/pages/books/index.astro b/src/pages/books/index.astro index b8997ccc..4837912f 100644 --- a/src/pages/books/index.astro +++ b/src/pages/books/index.astro @@ -3,33 +3,49 @@ import CoreStyleScope from '../../layouts/CoreStyleScope.astro'; import MainLayout from '@treeseed/ui/components/astro/layouts/MainLayout.astro'; import SectionIntro from '@treeseed/ui/components/astro/site/SectionIntro.astro'; import BookList from '@treeseed/ui/components/astro/site/BookList.astro'; -import { isPublishedRuntimeContentMode, loadPublishedCollection } from '../../utils/site-content-runtime'; +import { isPublishedRuntimeContentMode, loadPublishedCollection } from '../../utils/content/site-content-runtime'; +import { loadFederatedBookLibrary } from '../../utils/knowledge/reader-library'; const publishedRuntime = isPublishedRuntimeContentMode(); -const books = publishedRuntime +const federation = await loadFederatedBookLibrary(Astro.locals).catch(() => ({ available: false, unavailable: true, books: [] })); +const unavailable = 'unavailable' in federation && federation.unavailable; +if (unavailable) Astro.response.status = 503; +const books = unavailable ? [] : federation.available ? federation.books : publishedRuntime ? (await loadPublishedCollection(Astro.locals, 'books')).sort((a, b) => Number(a.data.order ?? 0) - Number(b.data.order ?? 0)) : (await (await import('astro:content')).getCollection('books')).sort((a, b) => a.data.order - b.data.order); +const knowledge = federation.available || unavailable ? [] : publishedRuntime + ? await loadPublishedCollection(Astro.locals, 'docs') + : await (await import('astro:content')).getCollection('docs').catch(() => []); --- - +
- +

Knowledge is temporarily unavailable

+

The published library could not be loaded. Retry this page after the knowledge runtime recovers.

+ + ) : ({ - href: `/books/${book.id}/`, + id: book.data.id, + slug: book.data.slug, title: book.data.title, summary: book.data.summary, - meta: book.data.sectionLabel, - landingPath: book.data.landingPath, - downloadHref: book.data.downloadHref, + status: book.data.status, + visibility: book.data.visibility, + topics: book.data.groupIds, + audience: book.data.audience, + pageCount: Number(book.data.pageCount ?? knowledge.filter((page: any) => page.data.bookId === book.data.id).length), + teamSlug: book.data.source?.teamSlug ?? 'treeseed', }))} - /> + />}
diff --git a/src/pages/contact.astro b/src/pages/contact.astro index cbfc8323..0c99dc14 100644 --- a/src/pages/contact.astro +++ b/src/pages/contact.astro @@ -3,8 +3,6 @@ import CoreStyleScope from '../layouts/CoreStyleScope.astro'; import MainLayout from '@treeseed/ui/components/astro/layouts/MainLayout.astro'; import ContactForm from '@treeseed/ui/components/astro/forms/ContactForm.astro'; -const status = Astro.url.searchParams.get('formStatus'); -const code = Astro.url.searchParams.get('formCode'); --- @@ -25,7 +23,7 @@ const code = Astro.url.searchParams.get('formCode');

- +
diff --git a/src/pages/decisions/[slug].astro b/src/pages/decisions/[slug].astro index 429f58f1..26ad4892 100644 --- a/src/pages/decisions/[slug].astro +++ b/src/pages/decisions/[slug].astro @@ -1,7 +1,7 @@ --- import CoreStyleScope from '../../layouts/CoreStyleScope.astro'; import AuthoredEntryLayout from '@treeseed/ui/components/astro/layouts/AuthoredEntryLayout.astro'; -import { resolveContributor, resolveReferences } from '../../utils/hub-content'; +import { resolveContributor, resolveReferences } from '../../utils/content/hub-content'; import PublishedContentBody from '@treeseed/ui/components/astro/site/PublishedContentBody.astro'; import RouteNotFound from '@treeseed/ui/components/astro/site/RouteNotFound.astro'; import { @@ -10,7 +10,7 @@ import { metadataFromPublishedContent, resolvePublishedContributor, resolvePublishedReferences, -} from '../../utils/site-content-runtime'; +} from '../../utils/content/site-content-runtime'; export const prerender = false; diff --git a/src/pages/decisions/index.astro b/src/pages/decisions/index.astro index 18d59efa..e1ed38c9 100644 --- a/src/pages/decisions/index.astro +++ b/src/pages/decisions/index.astro @@ -3,8 +3,8 @@ import CoreStyleScope from '../../layouts/CoreStyleScope.astro'; import MainLayout from '@treeseed/ui/components/astro/layouts/MainLayout.astro'; import SectionIntro from '@treeseed/ui/components/astro/site/SectionIntro.astro'; import ChronicleList from '@treeseed/ui/components/astro/site/ChronicleList.astro'; -import { getPublishedDecisions, resolveContributorsForEntries } from '../../utils/hub-content'; -import { isPublishedRuntimeContentMode, loadPublishedCollection, resolvePublishedContributor, metadataFromPublishedContent, loadPublishedEntry } from '../../utils/site-content-runtime'; +import { getPublishedDecisions, resolveContributorsForEntries } from '../../utils/content/hub-content'; +import { isPublishedRuntimeContentMode, loadPublishedCollection, resolvePublishedContributor, metadataFromPublishedContent, loadPublishedEntry } from '../../utils/content/site-content-runtime'; const publishedRuntime = isPublishedRuntimeContentMode(); const decisions = publishedRuntime @@ -43,7 +43,7 @@ const contributors = publishedRuntime status: decision.data.status, date: decision.data.date, meta: contributors.get(decision.id)?.data.name, - tags: decision.data.tags, + tags: decision.data.groupIds, }))} /> diff --git a/src/pages/docs-runtime/[...slug].astro b/src/pages/docs-runtime/[...slug].astro deleted file mode 100644 index 00d42dbb..00000000 --- a/src/pages/docs-runtime/[...slug].astro +++ /dev/null @@ -1,63 +0,0 @@ ---- -import CoreStyleScope from '../../layouts/CoreStyleScope.astro'; -import MainLayout from '@treeseed/ui/components/astro/layouts/MainLayout.astro'; -import PublishedContentBody from '@treeseed/ui/components/astro/site/PublishedContentBody.astro'; -import EmptyState from '@treeseed/ui/components/astro/surface/EmptyState.astro'; -import ReaderTemplate from '@treeseed/ui/components/astro/templates/ReaderTemplate.astro'; -import { isPublishedRuntimeContentMode } from '../../utils/site-content-runtime'; -import { buildPublicKnowledgeReaderViewModel } from '../../utils/runtime-reader'; - -export const prerender = false; - -const slug = String(Astro.params.slug ?? '').replace(/^\/+|\/+$/gu, ''); -const publishedRuntime = isPublishedRuntimeContentMode(); -const contentApi = publishedRuntime ? null : await import('astro:content'); -const localEntries = contentApi ? await contentApi.getCollection('docs').catch(() => []) : []; -const viewModel = await buildPublicKnowledgeReaderViewModel({ - locals: Astro.locals, - slug, - localDocuments: localEntries.map((entry: any) => ({ - id: entry.id, - slug: entry.slug, - data: entry.data, - })), -}); -const localEntry = contentApi && viewModel.localDocumentId - ? localEntries.find((entry: any) => entry.id === viewModel.localDocumentId) ?? null - : null; -const rendered = contentApi && localEntry ? await contentApi.render(localEntry) : null; -const Content = rendered?.Content ?? null; -if (viewModel.status === 'not_found') Astro.response.status = 404; -if (viewModel.status === 'unavailable') Astro.response.status = 503; -for (const [name, value] of Object.entries(viewModel.cache.headers)) { - Astro.response.headers.set(name, value); -} ---- - - - - - {viewModel.status === 'ready' ? ( - viewModel.publishedHtml ? ( - - ) : Content ? ( - - ) : ( - - ) - ) : ( - - )} - - - diff --git a/src/pages/docs-runtime/index.astro b/src/pages/docs-runtime/index.astro deleted file mode 100644 index 15c79031..00000000 --- a/src/pages/docs-runtime/index.astro +++ /dev/null @@ -1,62 +0,0 @@ ---- -import CoreStyleScope from '../../layouts/CoreStyleScope.astro'; -import MainLayout from '@treeseed/ui/components/astro/layouts/MainLayout.astro'; -import PublishedContentBody from '@treeseed/ui/components/astro/site/PublishedContentBody.astro'; -import EmptyState from '@treeseed/ui/components/astro/surface/EmptyState.astro'; -import ReaderTemplate from '@treeseed/ui/components/astro/templates/ReaderTemplate.astro'; -import { isPublishedRuntimeContentMode } from '../../utils/site-content-runtime'; -import { buildPublicKnowledgeReaderViewModel } from '../../utils/runtime-reader'; - -export const prerender = false; - -const publishedRuntime = isPublishedRuntimeContentMode(); -const contentApi = publishedRuntime ? null : await import('astro:content'); -const localEntries = contentApi ? await contentApi.getCollection('docs').catch(() => []) : []; -const viewModel = await buildPublicKnowledgeReaderViewModel({ - locals: Astro.locals, - slug: '', - localDocuments: localEntries.map((entry: any) => ({ - id: entry.id, - slug: entry.slug, - data: entry.data, - })), -}); -const localEntry = contentApi && viewModel.localDocumentId - ? localEntries.find((entry: any) => entry.id === viewModel.localDocumentId) ?? null - : null; -const rendered = contentApi && localEntry ? await contentApi.render(localEntry) : null; -const Content = rendered?.Content ?? null; -if (viewModel.status === 'not_found') Astro.response.status = 404; -if (viewModel.status === 'unavailable') Astro.response.status = 503; -for (const [name, value] of Object.entries(viewModel.cache.headers)) { - Astro.response.headers.set(name, value); -} ---- - - - - - {viewModel.status === 'ready' ? ( - viewModel.publishedHtml ? ( - - ) : Content ? ( - - ) : ( - - ) - ) : ( - - )} - - - diff --git a/src/pages/feed.xml.ts b/src/pages/feed.xml.ts index 658526ff..68b17db0 100644 --- a/src/pages/feed.xml.ts +++ b/src/pages/feed.xml.ts @@ -1,7 +1,7 @@ import type { APIContext } from 'astro'; -import { getPublishedNotes } from '../utils/hub-content'; -import { isPublishedRuntimeContentMode, loadPublishedCollection } from '../utils/site-content-runtime'; -import { siteModelRendered } from '../utils/site-models.ts'; +import { getPublishedNotes } from '../utils/content/hub-content'; +import { isPublishedRuntimeContentMode, loadPublishedCollection } from '../utils/content/site-content-runtime'; +import { siteModelRendered } from '../utils/support/site-models.ts'; export async function GET(context: APIContext) { if (!siteModelRendered('notes')) { diff --git a/src/pages/index.astro b/src/pages/index.astro index 3ce40a7b..432a357f 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -21,15 +21,15 @@ import { getPublishedProposals, getPublishedQuestions, resolveContributorsForEntries, -} from '../utils/hub-content'; +} from '../utils/content/hub-content'; import { isPublishedRuntimeContentMode, loadPublishedCollection, loadPublishedEntry, metadataFromPublishedContent, resolvePublishedContributor, -} from '../utils/site-content-runtime'; -import { siteModelRendered } from '../utils/site-models.ts'; +} from '../utils/content/site-content-runtime'; +import { siteModelRendered } from '../utils/support/site-models.ts'; const notesRendered = siteModelRendered('notes'); const questionsRendered = siteModelRendered('questions'); @@ -334,7 +334,7 @@ const ctaSecondaryLabel = booksRendered ? 'Explore books' : notesRendered ? 'Rea status: question.data.status, date: question.data.date, meta: questionContributors.get(question.id)?.data.name, - tags: question.data.tags, + tags: question.data.groupIds, }))} /> @@ -354,7 +354,7 @@ const ctaSecondaryLabel = booksRendered ? 'Explore books' : notesRendered ? 'Rea status: objective.data.status, date: objective.data.date, meta: objectiveContributors.get(objective.id)?.data.name, - tags: objective.data.tags, + tags: objective.data.groupIds, }))} /> @@ -376,7 +376,7 @@ const ctaSecondaryLabel = booksRendered ? 'Explore books' : notesRendered ? 'Rea status: proposal.data.status, date: proposal.data.date, meta: proposalContributors.get(proposal.id)?.data.name, - tags: proposal.data.tags, + tags: proposal.data.groupIds, }))} /> @@ -396,7 +396,7 @@ const ctaSecondaryLabel = booksRendered ? 'Explore books' : notesRendered ? 'Rea status: decision.data.status, date: decision.data.date, meta: decisionContributors.get(decision.id)?.data.name, - tags: decision.data.tags, + tags: decision.data.groupIds, }))} /> @@ -418,14 +418,14 @@ const ctaSecondaryLabel = booksRendered ? 'Explore books' : notesRendered ? 'Rea summary: person.data.summary, meta: `${person.data.role} · ${person.data.affiliation}`, status: person.data.status, - tags: person.data.tags, + tags: person.data.groupIds, })), ...agents.map((agent) => ({ href: `/agents/${agent.id}/`, name: agent.data.name, summary: agent.data.summary, meta: `${agent.data.operator} · ${agent.data.runtimeStatus}`, - tags: agent.data.tags, + tags: agent.data.groupIds, })), ]} /> @@ -440,12 +440,15 @@ const ctaSecondaryLabel = booksRendered ? 'Explore books' : notesRendered ? 'Rea /> ({ - href: `/books/${book.id}/`, + id: book.data.id, + slug: book.data.slug, title: book.data.title, summary: book.data.summary, - meta: book.data.sectionLabel, - landingPath: book.data.landingPath, - downloadHref: book.data.downloadHref, + status: book.data.status, + visibility: book.data.visibility, + topics: book.data.groupIds, + audience: book.data.audience, + teamSlug: 'treeseed', }))} /> diff --git a/src/pages/notes/[slug].astro b/src/pages/notes/[slug].astro index 567048eb..49b9db33 100644 --- a/src/pages/notes/[slug].astro +++ b/src/pages/notes/[slug].astro @@ -3,7 +3,7 @@ import CoreStyleScope from '../../layouts/CoreStyleScope.astro'; import AuthoredEntryLayout from '@treeseed/ui/components/astro/layouts/AuthoredEntryLayout.astro'; import PublishedContentBody from '@treeseed/ui/components/astro/site/PublishedContentBody.astro'; import RouteNotFound from '@treeseed/ui/components/astro/site/RouteNotFound.astro'; -import { isPublishedRuntimeContentMode, loadPublishedEntry, metadataFromPublishedContent } from '../../utils/site-content-runtime'; +import { isPublishedRuntimeContentMode, loadPublishedEntry, metadataFromPublishedContent } from '../../utils/content/site-content-runtime'; export const prerender = false; diff --git a/src/pages/notes/index.astro b/src/pages/notes/index.astro index de207e55..de2129e1 100644 --- a/src/pages/notes/index.astro +++ b/src/pages/notes/index.astro @@ -3,7 +3,7 @@ import CoreStyleScope from '../../layouts/CoreStyleScope.astro'; import MainLayout from '@treeseed/ui/components/astro/layouts/MainLayout.astro'; import SectionIntro from '@treeseed/ui/components/astro/site/SectionIntro.astro'; import NotesList from '@treeseed/ui/components/astro/site/NotesList.astro'; -import { isPublishedRuntimeContentMode, loadPublishedCollection } from '../../utils/site-content-runtime'; +import { isPublishedRuntimeContentMode, loadPublishedCollection } from '../../utils/content/site-content-runtime'; const publishedRuntime = isPublishedRuntimeContentMode(); const notes = publishedRuntime diff --git a/src/pages/objectives/[slug].astro b/src/pages/objectives/[slug].astro index 9d3dfde5..5e3e32cc 100644 --- a/src/pages/objectives/[slug].astro +++ b/src/pages/objectives/[slug].astro @@ -1,7 +1,7 @@ --- import CoreStyleScope from '../../layouts/CoreStyleScope.astro'; import AuthoredEntryLayout from '@treeseed/ui/components/astro/layouts/AuthoredEntryLayout.astro'; -import { resolveContributor, resolveReferences } from '../../utils/hub-content'; +import { resolveContributor, resolveReferences } from '../../utils/content/hub-content'; import PublishedContentBody from '@treeseed/ui/components/astro/site/PublishedContentBody.astro'; import RouteNotFound from '@treeseed/ui/components/astro/site/RouteNotFound.astro'; import { @@ -10,7 +10,7 @@ import { metadataFromPublishedContent, resolvePublishedContributor, resolvePublishedReferences, -} from '../../utils/site-content-runtime'; +} from '../../utils/content/site-content-runtime'; export const prerender = false; diff --git a/src/pages/objectives/index.astro b/src/pages/objectives/index.astro index 1b7aa1e6..be0695d9 100644 --- a/src/pages/objectives/index.astro +++ b/src/pages/objectives/index.astro @@ -3,8 +3,8 @@ import CoreStyleScope from '../../layouts/CoreStyleScope.astro'; import MainLayout from '@treeseed/ui/components/astro/layouts/MainLayout.astro'; import SectionIntro from '@treeseed/ui/components/astro/site/SectionIntro.astro'; import ChronicleList from '@treeseed/ui/components/astro/site/ChronicleList.astro'; -import { getPublishedObjectives, resolveContributorsForEntries } from '../../utils/hub-content'; -import { isPublishedRuntimeContentMode, loadPublishedCollection, resolvePublishedContributor, metadataFromPublishedContent, loadPublishedEntry } from '../../utils/site-content-runtime'; +import { getPublishedObjectives, resolveContributorsForEntries } from '../../utils/content/hub-content'; +import { isPublishedRuntimeContentMode, loadPublishedCollection, resolvePublishedContributor, metadataFromPublishedContent, loadPublishedEntry } from '../../utils/content/site-content-runtime'; const publishedRuntime = isPublishedRuntimeContentMode(); const objectives = publishedRuntime @@ -43,7 +43,7 @@ const contributors = publishedRuntime status: objective.data.status, date: objective.data.date, meta: contributors.get(objective.id)?.data.name, - tags: objective.data.tags, + tags: objective.data.groupIds, }))} /> diff --git a/src/pages/people/[slug].astro b/src/pages/people/[slug].astro index b5b77ae3..e0b62304 100644 --- a/src/pages/people/[slug].astro +++ b/src/pages/people/[slug].astro @@ -1,10 +1,10 @@ --- import CoreStyleScope from '../../layouts/CoreStyleScope.astro'; import ProfileLayout from '@treeseed/ui/components/astro/layouts/ProfileLayout.astro'; -import { resolveReferences } from '../../utils/hub-content'; +import { resolveReferences } from '../../utils/content/hub-content'; import PublishedContentBody from '@treeseed/ui/components/astro/site/PublishedContentBody.astro'; import RouteNotFound from '@treeseed/ui/components/astro/site/RouteNotFound.astro'; -import { isPublishedRuntimeContentMode, loadPublishedEntry, metadataFromPublishedContent, resolvePublishedReferences } from '../../utils/site-content-runtime'; +import { isPublishedRuntimeContentMode, loadPublishedEntry, metadataFromPublishedContent, resolvePublishedReferences } from '../../utils/content/site-content-runtime'; export const prerender = false; diff --git a/src/pages/people/index.astro b/src/pages/people/index.astro index c69b3f80..9d4f4bb7 100644 --- a/src/pages/people/index.astro +++ b/src/pages/people/index.astro @@ -3,7 +3,7 @@ import CoreStyleScope from '../../layouts/CoreStyleScope.astro'; import MainLayout from '@treeseed/ui/components/astro/layouts/MainLayout.astro'; import SectionIntro from '@treeseed/ui/components/astro/site/SectionIntro.astro'; import ProfileList from '@treeseed/ui/components/astro/site/ProfileList.astro'; -import { isPublishedRuntimeContentMode, loadPublishedCollection } from '../../utils/site-content-runtime'; +import { isPublishedRuntimeContentMode, loadPublishedCollection } from '../../utils/content/site-content-runtime'; const publishedRuntime = isPublishedRuntimeContentMode(); const people = publishedRuntime @@ -27,7 +27,7 @@ const people = publishedRuntime summary: person.data.summary, meta: `${person.data.role} · ${person.data.affiliation}`, status: person.data.status, - tags: person.data.tags, + tags: person.data.groupIds, }))} /> diff --git a/src/pages/proposals/[slug].astro b/src/pages/proposals/[slug].astro index 3487a62b..129198b5 100644 --- a/src/pages/proposals/[slug].astro +++ b/src/pages/proposals/[slug].astro @@ -1,7 +1,7 @@ --- import CoreStyleScope from '../../layouts/CoreStyleScope.astro'; import AuthoredEntryLayout from '@treeseed/ui/components/astro/layouts/AuthoredEntryLayout.astro'; -import { resolveContributor, resolveReferences } from '../../utils/hub-content'; +import { resolveContributor, resolveReferences } from '../../utils/content/hub-content'; import PublishedContentBody from '@treeseed/ui/components/astro/site/PublishedContentBody.astro'; import RouteNotFound from '@treeseed/ui/components/astro/site/RouteNotFound.astro'; import { @@ -10,7 +10,7 @@ import { metadataFromPublishedContent, resolvePublishedContributor, resolvePublishedReferences, -} from '../../utils/site-content-runtime'; +} from '../../utils/content/site-content-runtime'; export const prerender = false; diff --git a/src/pages/proposals/index.astro b/src/pages/proposals/index.astro index f7cb03de..5e22b635 100644 --- a/src/pages/proposals/index.astro +++ b/src/pages/proposals/index.astro @@ -3,8 +3,8 @@ import CoreStyleScope from '../../layouts/CoreStyleScope.astro'; import MainLayout from '@treeseed/ui/components/astro/layouts/MainLayout.astro'; import SectionIntro from '@treeseed/ui/components/astro/site/SectionIntro.astro'; import ChronicleList from '@treeseed/ui/components/astro/site/ChronicleList.astro'; -import { getPublishedProposals, resolveContributorsForEntries } from '../../utils/hub-content'; -import { isPublishedRuntimeContentMode, loadPublishedCollection, resolvePublishedContributor, metadataFromPublishedContent, loadPublishedEntry } from '../../utils/site-content-runtime'; +import { getPublishedProposals, resolveContributorsForEntries } from '../../utils/content/hub-content'; +import { isPublishedRuntimeContentMode, loadPublishedCollection, resolvePublishedContributor, metadataFromPublishedContent, loadPublishedEntry } from '../../utils/content/site-content-runtime'; const publishedRuntime = isPublishedRuntimeContentMode(); const proposals = publishedRuntime @@ -43,7 +43,7 @@ const contributors = publishedRuntime status: proposal.data.status, date: proposal.data.date, meta: contributors.get(proposal.id)?.data.name, - tags: proposal.data.tags, + tags: proposal.data.groupIds, }))} /> diff --git a/src/pages/questions/[slug].astro b/src/pages/questions/[slug].astro index 06ab7376..d0ad1504 100644 --- a/src/pages/questions/[slug].astro +++ b/src/pages/questions/[slug].astro @@ -1,7 +1,7 @@ --- import CoreStyleScope from '../../layouts/CoreStyleScope.astro'; import AuthoredEntryLayout from '@treeseed/ui/components/astro/layouts/AuthoredEntryLayout.astro'; -import { resolveContributor, resolveReferences } from '../../utils/hub-content'; +import { resolveContributor, resolveReferences } from '../../utils/content/hub-content'; import PublishedContentBody from '@treeseed/ui/components/astro/site/PublishedContentBody.astro'; import RouteNotFound from '@treeseed/ui/components/astro/site/RouteNotFound.astro'; import { @@ -10,7 +10,7 @@ import { metadataFromPublishedContent, resolvePublishedContributor, resolvePublishedReferences, -} from '../../utils/site-content-runtime'; +} from '../../utils/content/site-content-runtime'; export const prerender = false; diff --git a/src/pages/questions/index.astro b/src/pages/questions/index.astro index 8dd7f4b1..0b0f3231 100644 --- a/src/pages/questions/index.astro +++ b/src/pages/questions/index.astro @@ -3,8 +3,8 @@ import CoreStyleScope from '../../layouts/CoreStyleScope.astro'; import MainLayout from '@treeseed/ui/components/astro/layouts/MainLayout.astro'; import SectionIntro from '@treeseed/ui/components/astro/site/SectionIntro.astro'; import ChronicleList from '@treeseed/ui/components/astro/site/ChronicleList.astro'; -import { getPublishedQuestions, resolveContributorsForEntries } from '../../utils/hub-content'; -import { isPublishedRuntimeContentMode, loadPublishedCollection, resolvePublishedContributor, metadataFromPublishedContent, loadPublishedEntry } from '../../utils/site-content-runtime'; +import { getPublishedQuestions, resolveContributorsForEntries } from '../../utils/content/hub-content'; +import { isPublishedRuntimeContentMode, loadPublishedCollection, resolvePublishedContributor, metadataFromPublishedContent, loadPublishedEntry } from '../../utils/content/site-content-runtime'; const publishedRuntime = isPublishedRuntimeContentMode(); const questions = publishedRuntime @@ -43,7 +43,7 @@ const contributors = publishedRuntime status: question.data.status, date: question.data.date, meta: contributors.get(question.id)?.data.name, - tags: question.data.tags, + tags: question.data.groupIds, }))} /> diff --git a/src/pages/t/[teamSlug]/books/[bookSlug]/[...pageSlug].astro b/src/pages/t/[teamSlug]/books/[bookSlug]/[...pageSlug].astro new file mode 100644 index 00000000..db5803b1 --- /dev/null +++ b/src/pages/t/[teamSlug]/books/[bookSlug]/[...pageSlug].astro @@ -0,0 +1,43 @@ +--- +import StarlightPage from '@astrojs/starlight/components/StarlightPage.astro'; +import PublishedContentBody from '@treeseed/ui/components/astro/site/PublishedContentBody.astro'; +import RouteNotFound from '@treeseed/ui/components/astro/site/RouteNotFound.astro'; +import CoreStyleScope from '../../../../../layouts/CoreStyleScope.astro'; +import { loadPublishedEntry } from '../../../../../utils/content/site-content-runtime.ts'; +import { bookSidebar, canReadKnowledge, loadFederatedReader, loadReaderLibrary, pagesForBook, starlightKnowledgeFrontmatter } from '../../../../../utils/knowledge/reader-library.ts'; + +export const prerender = false; + +const teamSlug = String(Astro.params.teamSlug ?? ''); +const bookSlug = String(Astro.params.bookSlug ?? ''); +const pageSlug = String(Astro.params.pageSlug ?? '').replace(/^\/+|\/+$/gu, ''); +const federated = await loadFederatedReader(Astro.locals, { teamSlug, bookSlug, pageSlug }).catch(() => ({ unavailable: true })); +const useLocalLibrary = federated === null; +const library = federated?.book ? federated : useLocalLibrary ? await loadReaderLibrary(Astro.locals) : { published: false, books: [], pages: [] }; +const book = federated?.book ?? library.books.find((candidate) => candidate.data.slug === bookSlug && candidate.data.status === 'published' + && canReadKnowledge(Astro.locals, String(candidate.data.visibility))) ?? null; +const pages = federated?.book ? federated.pages : book ? pagesForBook(library.pages, String(book.data.id)).filter((candidate) => canReadKnowledge(Astro.locals, String(candidate.data.visibility))) : []; +const page = federated?.page ?? pages.find((candidate) => candidate.data.slug === pageSlug) ?? null; +const content = !federated?.book && !library.published && page ? await import('astro:content') : null; +const localEntry = content && page ? (await content.getCollection('docs')).find((entry: any) => entry.id === page.id) ?? null : null; +const rendered = localEntry ? await content?.render(localEntry) : null; +const Content = rendered?.Content ?? null; +const published = federated?.page ? { html: federated.page.data.bodyHtml } : library.published && page ? await loadPublishedEntry(Astro.locals, 'docs', page.id) : null; +if ((federated as any)?.unavailable) Astro.response.status = 503; +else if (!book || !page || (!federated?.book && teamSlug !== 'treeseed')) Astro.response.status = 404; +--- + + + +{!book || !page ? ( + +) : ( + + {published?.html ? : Content ? :

This page could not be rendered.

} +
+)} +
diff --git a/src/pages/t/[teamSlug]/books/[bookSlug]/index.astro b/src/pages/t/[teamSlug]/books/[bookSlug]/index.astro new file mode 100644 index 00000000..8eebb696 --- /dev/null +++ b/src/pages/t/[teamSlug]/books/[bookSlug]/index.astro @@ -0,0 +1,39 @@ +--- +import StarlightPage from '@astrojs/starlight/components/StarlightPage.astro'; +import RouteNotFound from '@treeseed/ui/components/astro/site/RouteNotFound.astro'; +import CoreStyleScope from '../../../../../layouts/CoreStyleScope.astro'; +import { bookSidebar, canReadKnowledge, loadFederatedReader, loadReaderLibrary, pagesForBook, starlightKnowledgeFrontmatter } from '../../../../../utils/knowledge/reader-library.ts'; + +export const prerender = false; + +const teamSlug = String(Astro.params.teamSlug ?? ''); +const bookSlug = String(Astro.params.bookSlug ?? ''); +const federated = await loadFederatedReader(Astro.locals, { teamSlug, bookSlug }).catch(() => ({ unavailable: true })); +const useLocalLibrary = federated === null; +const library = federated?.book ? federated : useLocalLibrary ? await loadReaderLibrary(Astro.locals) : { books: [], pages: [] }; +const book = federated?.book ?? library.books.find((candidate) => candidate.data.slug === bookSlug && candidate.data.status === 'published' + && canReadKnowledge(Astro.locals, String(candidate.data.visibility))) ?? null; +const pages = federated?.book ? federated.pages : book ? pagesForBook(library.pages, String(book.data.id)).filter((page) => canReadKnowledge(Astro.locals, String(page.data.visibility))) : []; +if ((federated as any)?.unavailable) Astro.response.status = 503; +else if (!book || (!federated?.book && teamSlug !== 'treeseed')) Astro.response.status = 404; +const overview = book ? { + title: book.data.title, summary: book.data.summary, description: book.data.description, +} : null; +--- + + + +{!book || !overview ? ( + +) : ( + +

{book.data.description}

+

In this book

+ {pages.length ? ( +
    + {pages.map((page) =>
  1. {page.data.title}

    {page.data.summary}

  2. )} +
+ ) :

This book does not have any published pages yet.

} +
+)} +
diff --git a/src/pages/ui/index.astro b/src/pages/ui/index.astro index e74de0c6..b566d71d 100644 --- a/src/pages/ui/index.astro +++ b/src/pages/ui/index.astro @@ -20,7 +20,7 @@ import ActionList from '@treeseed/ui/components/astro/data/ActionList.astro'; import KeyValueList from '@treeseed/ui/components/astro/data/KeyValueList.astro'; import DataTable from '@treeseed/ui/components/astro/data/DataTable.astro'; import PageHeader from '@treeseed/ui/components/astro/layout/PageHeader.astro'; -import { getBuiltInColorSchemes, normalizeThemePreference } from '../../utils/theme.ts'; +import { getBuiltInColorSchemes, normalizeThemePreference } from '../../utils/support/theme.ts'; const selected = normalizeThemePreference({ scheme: Astro.url.searchParams.get('scheme'), diff --git a/src/platform.ts b/src/platform.ts deleted file mode 100644 index 4c87a1ee..00000000 --- a/src/platform.ts +++ /dev/null @@ -1,17 +0,0 @@ -export { - buildTreeseedPlatformLayers, - resolveTreeseedPlatformResource, - TREESEED_PLATFORM_RESOURCE_KINDS, - type TreeseedPlatformLayer, -} from './platform-resources'; -export { - createTreeseedIntegratedDevPlan, - runTreeseedManagedDev, - runTreeseedIntegratedDev, - type TreeseedIntegratedDevCommand, - type TreeseedIntegratedDevOptions, - type TreeseedIntegratedDevPlan, - type TreeseedIntegratedDevSurface, - type TreeseedManagedDevOptions, - type TreeseedDevInstanceRecord, -} from './dev'; diff --git a/src/plugin-default.ts b/src/plugin-default.ts deleted file mode 100644 index 72a48c7a..00000000 --- a/src/plugin-default.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@treeseed/sdk/plugin-default'; diff --git a/src/dev-watch.ts b/src/runtime/dev-watch.ts similarity index 92% rename from src/dev-watch.ts rename to src/runtime/dev-watch.ts index 30caf410..ca99c90e 100644 --- a/src/dev-watch.ts +++ b/src/runtime/dev-watch.ts @@ -4,13 +4,13 @@ import { relative, resolve, sep } from 'node:path'; const WATCH_INTERVAL_MS = 900; const WATCH_DEBOUNCE_MS = 350; -export type TreeseedDevWatchEntry = { +export type DevWatchEntry = { kind: 'tenant' | 'core' | 'sdk' | 'agent' | 'cli'; root: string; restartRequired?: boolean; }; -export type TreeseedDevWatchChange = { +export type DevWatchChange = { changedPaths: string[]; tenantChanged: boolean; tenantApiChanged: boolean; @@ -21,17 +21,17 @@ export type TreeseedDevWatchChange = { commandImplementationChanged: boolean; }; -export type TreeseedDevWatchController = { +export type DevWatchController = { stop: () => void; rebaseline: () => void; }; -export type TreeseedDevWatchStarter = ( +export type DevWatchStarter = ( input: { - watchEntries: TreeseedDevWatchEntry[]; - onChange: (change: TreeseedDevWatchChange) => void | Promise; + watchEntries: DevWatchEntry[]; + onChange: (change: DevWatchChange) => void | Promise; }, -) => TreeseedDevWatchController; +) => DevWatchController; export function shouldIgnoreWatchPath(filePath: string, rootPath: string) { const rel = relative(rootPath, filePath); @@ -111,7 +111,7 @@ function collectDirectorySnapshot(directoryPath: string, rootPath: string, snaps } } -function collectSnapshot(entries: TreeseedDevWatchEntry[]) { +function collectSnapshot(entries: DevWatchEntry[]) { const snapshot = new Map(); for (const entry of entries) { collectRootSnapshot(entry.root, snapshot); @@ -134,8 +134,8 @@ function diffSnapshots(previousSnapshot: Map, nextSnapshot: Map< return [...changed]; } -export function classifyChanges(changedPaths: string[], watchEntries: TreeseedDevWatchEntry[]): TreeseedDevWatchChange { - function matchesEntry(filePath: string, entry: TreeseedDevWatchEntry) { +export function classifyChanges(changedPaths: string[], watchEntries: DevWatchEntry[]): DevWatchChange { + function matchesEntry(filePath: string, entry: DevWatchEntry) { return filePath === entry.root || filePath.startsWith(`${entry.root}${sep}`); } function isTenantApiInput(filePath: string) { @@ -175,7 +175,7 @@ export function classifyChanges(changedPaths: string[], watchEntries: TreeseedDe }; } -export function startPollingWatch({ watchEntries, onChange }: Parameters[0]) { +export function startPollingWatch({ watchEntries, onChange }: Parameters[0]) { let previousSnapshot = collectSnapshot(watchEntries); let queuedPaths: string[] = []; let debounceTimer: NodeJS.Timeout | null = null; diff --git a/src/runtime/dev.ts b/src/runtime/dev.ts new file mode 100644 index 00000000..6d0d6de8 --- /dev/null +++ b/src/runtime/dev.ts @@ -0,0 +1,13 @@ +export * from '../dev/configuration/runtime-configuration.ts'; +export * from '../dev/runtime/integrated-dev-dependencies.ts'; +export * from '../dev/support/parse-surface-value.ts'; +export * from '../dev/support/create-watch-entries.ts'; +export * from '../dev/support/create-api-command.ts'; +export * from '../dev/runtime/default-kill-process.ts'; +export * from '../dev/runtime/dev-pid-path.ts'; +export * from '../dev/runtime/render-dev-log-json-event-for-human.ts'; +export * from '../dev/runtime/dev-runtime-state.ts'; +export * from '../dev/runtime/prepare-dev-runtime-slots.ts'; +export * from '../dev/support/attach-prefixed-log-reader.ts'; +export * from '../dev/runtime/run-local-setup.ts'; +export * from '../dev/runtime/run-integrated-dev.ts'; diff --git a/src/runtime/platform/deploy-config.ts b/src/runtime/platform/deploy-config.ts new file mode 100644 index 00000000..52793fa9 --- /dev/null +++ b/src/runtime/platform/deploy-config.ts @@ -0,0 +1,36 @@ +import { existsSync, readFileSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { parse as parseYaml } from 'yaml'; +import type { DeployConfig } from '@treeseed/sdk/site-contracts/platform'; +import { resolveTenantRoot } from './tenant-config.ts'; + +export function parseDeployConfig(raw: string): DeployConfig { + const parsed = (parseYaml(raw) ?? {}) as DeployConfig; + if (!parsed || typeof parsed !== 'object') throw new Error('TreeSeed deploy config must be a mapping.'); + return parsed; +} + +export function resolveDeployConfigPathFromRoot(root: string, configPath = 'treeseed.site.yaml') { + const candidate = resolve(root, configPath); + if (!existsSync(candidate)) throw new Error(`Unable to resolve TreeSeed deploy config at "${candidate}".`); + return candidate; +} + +export function resolveDeployConfigPath(configPath = 'treeseed.site.yaml') { + return resolveDeployConfigPathFromRoot(resolveTenantRoot(), configPath); +} + +export function loadDeployConfigFromPath(path: string): DeployConfig { + const parsed = parseDeployConfig(readFileSync(path, 'utf8')); + const tenantRoot = dirname(path); + Object.defineProperties(parsed, { + __tenantRoot: { value: tenantRoot, enumerable: false }, + __projectRoot: { value: parsed.projectRoot ? resolve(tenantRoot, parsed.projectRoot) : tenantRoot, enumerable: false }, + __configPath: { value: path, enumerable: false }, + }); + return parsed; +} + +export function loadDeployConfig(configPath = 'treeseed.site.yaml') { + return loadDeployConfigFromPath(resolveDeployConfigPath(configPath)); +} diff --git a/src/runtime/platform/deploy-runtime.ts b/src/runtime/platform/deploy-runtime.ts new file mode 100644 index 00000000..e5565829 --- /dev/null +++ b/src/runtime/platform/deploy-runtime.ts @@ -0,0 +1,26 @@ +import type { DeployConfig } from '@treeseed/sdk/site-contracts/platform'; + +declare const __TREESEED_DEPLOY_CONFIG__: DeployConfig | undefined; +let cached: DeployConfig | null = null; + +const defaults = { + forms: 'store_only', operations: 'default', deploy: 'cloudflare', dns: 'cloudflare-dns', site: 'default', + content: { serving: 'local_collections', runtime: 'filesystem', publish: 'filesystem', docs: 'default' }, + agents: { execution: 'codex', mutation: 'local_branch', repository: 'git', verification: 'local', notification: 'sdk_message', research: 'project_graph' }, +}; + +export function getDeployConfig(): DeployConfig { + if (cached) return cached; + if (typeof __TREESEED_DEPLOY_CONFIG__ !== 'undefined' && __TREESEED_DEPLOY_CONFIG__) return (cached = __TREESEED_DEPLOY_CONFIG__); + return (cached = { name: 'TreeSeed Site', slug: 'treeseed-site', siteUrl: 'https://example.com', contactEmail: 'contact@example.com', providers: structuredClone(defaults), smtp: { enabled: false }, turnstile: { enabled: false } }); +} + +export function resetDeployConfigForTests() { cached = null } +export function getFormsProvider() { return getDeployConfig().providers?.forms ?? defaults.forms } +export function getContentServingMode(config: DeployConfig = getDeployConfig()) { + const override = globalThis.process?.env?.TREESEED_CONTENT_SERVING_MODE?.trim(); + if (override === 'local_collections' || override === 'published_runtime') return override; + return config.providers?.content?.serving ?? defaults.content.serving; +} +export function isSmtpEnabled() { return getDeployConfig().smtp?.enabled ?? false } +export function isTurnstileEnabled() { return getDeployConfig().turnstile?.enabled ?? false } diff --git a/src/runtime/platform/hosting.ts b/src/runtime/platform/hosting.ts new file mode 100644 index 00000000..dfdff45f --- /dev/null +++ b/src/runtime/platform/hosting.ts @@ -0,0 +1,14 @@ +import { existsSync, readdirSync } from 'node:fs'; +import { resolve } from 'node:path'; + +export interface DiscoveredApplication { relativeRoot: string; root: string; configPath: string } + +export function discoverApplications(root: string): DiscoveredApplication[] { + const candidates = ['.', 'docs']; + const packages = resolve(root, 'packages'); + if (existsSync(packages)) { + for (const entry of readdirSync(packages, { withFileTypes: true })) if (entry.isDirectory()) candidates.push(`packages/${entry.name}`); + } + return candidates.map((relativeRoot) => ({ relativeRoot, root: resolve(root, relativeRoot), configPath: resolve(root, relativeRoot, 'treeseed.site.yaml') })) + .filter((entry) => existsSync(entry.configPath)); +} diff --git a/src/runtime/platform/plugin-default.ts b/src/runtime/platform/plugin-default.ts new file mode 100644 index 00000000..95301761 --- /dev/null +++ b/src/runtime/platform/plugin-default.ts @@ -0,0 +1,10 @@ +export default { + id: 'treeseed-core-default', + provides: { + forms: ['store_only', 'notify_admin', 'full_email'], operations: ['default'], + agents: { execution: ['codex'], mutation: ['local_branch'], repository: ['git'], verification: ['local'], notification: ['sdk_message'], research: ['project_graph'] }, + deploy: ['cloudflare'], dns: ['cloudflare-dns'], + content: { runtime: ['filesystem', 'team_scoped_r2_overlay'], publish: ['filesystem', 'team_scoped_r2_overlay'], docs: ['default'] }, + site: ['default'], + }, +}; diff --git a/src/runtime/platform/plugins.ts b/src/runtime/platform/plugins.ts new file mode 100644 index 00000000..822392dc --- /dev/null +++ b/src/runtime/platform/plugins.ts @@ -0,0 +1,50 @@ +import { existsSync } from 'node:fs'; +import { createRequire } from 'node:module'; +import { dirname, resolve } from 'node:path'; +import type { DeployConfig } from '@treeseed/sdk/site-contracts/platform'; +import defaultPlugin from './plugin-default.ts'; +import { loadDeployConfig } from './deploy-config.ts'; +export { resetDeployConfigForTests } from './deploy-runtime.ts'; + +const require = createRequire(import.meta.url); + +export interface LoadedPluginRegistration { + package: string; + config: Record; + baseDir: string; + plugin: Record; +} + +function normalizePlugin(value: unknown, packageName: string) { + const plugin = (value as { default?: unknown } | undefined)?.default ?? value; + if (!plugin || typeof plugin !== 'object') throw new Error(`TreeSeed plugin "${packageName}" did not export a plugin object.`); + return plugin as Record; +} + +function loadConfiguredPlugin(packageName: string, tenantRoot: string) { + if (packageName === '@treeseed/sdk/plugin-default' || packageName === '@treeseed/core/plugin-default') { + return { plugin: defaultPlugin, baseDir: resolve(import.meta.dirname, '../..') }; + } + const candidate = packageName.startsWith('file:') ? packageName.slice(5) : packageName; + const resolvedPath = candidate.startsWith('.') || candidate.startsWith('/') + ? resolve(tenantRoot, candidate) + : require.resolve(packageName, { paths: [tenantRoot, process.cwd()] }); + return { plugin: normalizePlugin(require(resolvedPath), packageName), baseDir: dirname(resolvedPath) }; +} + +export function loadPlugins(config: DeployConfig = loadDeployConfig()): LoadedPluginRegistration[] { + const tenantRoot = (config as DeployConfig & { __tenantRoot?: string }).__tenantRoot ?? process.cwd(); + const references = config.plugins?.length ? config.plugins : [{ package: '@treeseed/core/plugin-default' }]; + return references.filter((entry: any) => entry?.enabled !== false).map((entry: any) => { + const loaded = loadConfiguredPlugin(entry.package, tenantRoot); + return { package: entry.package, config: entry.config ?? {}, baseDir: loaded.baseDir, plugin: loaded.plugin }; + }); +} + +export function loadPluginRuntime(config: DeployConfig = loadDeployConfig()) { + return { config, plugins: loadPlugins(config) }; +} + +export function getDeployConfig() { + return loadDeployConfig(); +} diff --git a/src/runtime/platform/published-content.ts b/src/runtime/platform/published-content.ts new file mode 100644 index 00000000..348d9978 --- /dev/null +++ b/src/runtime/platform/published-content.ts @@ -0,0 +1,89 @@ +import { createHmac, timingSafeEqual } from 'node:crypto'; +import type { DeployConfig } from '@treeseed/sdk/site-contracts/platform'; +import type { CloudflareRuntime, R2BucketLike } from '@treeseed/sdk/site-contracts/cloudflare'; + +export const EDITORIAL_PREVIEW_COOKIE = 'treeseed-content-preview'; + +export interface EditorialPreviewTokenPayload { teamId: string; previewId: string; expiresAt: string } +export interface PublishedContentObjectPointer { objectKey: string; sha256?: string } +export interface PublishedContentEntry { + id: string; model: string; slug: string; title?: string; summary?: string; status?: string; + publishedAt?: string; updatedAt?: string; content: PublishedContentObjectPointer; [key: string]: unknown; +} +export interface PublishedContentManifest { + entries: PublishedContentEntry[]; + runtime?: { docsTree?: PublishedContentObjectPointer; searchIndex?: PublishedContentObjectPointer; [key: string]: unknown }; + [key: string]: unknown; +} +export interface ContentRuntimeProvider { + getManifest(): Promise; + listCollection(model: string): Promise; + getEntry(model: string, slugOrId: string): Promise; + getObject(pointer: PublishedContentObjectPointer): Promise; +} + +function signature(payload: string, secret: string) { + return createHmac('sha256', secret).update(payload).digest('base64url'); +} + +export function signEditorialPreviewToken(payload: EditorialPreviewTokenPayload, secret: string) { + const encoded = Buffer.from(JSON.stringify(payload), 'utf8').toString('base64url'); + return `${encoded}.${signature(encoded, secret)}`; +} + +export function verifyEditorialPreviewToken(token: string, secret: string): EditorialPreviewTokenPayload | null { + try { + const [payload, provided] = token.split('.'); + if (!payload || !provided) return null; + const expected = signature(payload, secret); + if (provided.length !== expected.length || !timingSafeEqual(Buffer.from(provided), Buffer.from(expected))) return null; + const parsed = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8')) as EditorialPreviewTokenPayload; + if (!parsed.teamId || !parsed.previewId || !parsed.expiresAt || Date.parse(parsed.expiresAt) <= Date.now()) return null; + return parsed; + } catch { return null } +} + +export function isTeamScopedR2ContentEnabled(config: DeployConfig) { + return config.providers?.content?.runtime === 'team_scoped_r2_overlay'; +} + +export function resolvePublishedContentBucketBinding(config: DeployConfig) { + return process.env.TREESEED_CONTENT_BUCKET_BINDING?.trim() || config.cloudflare?.r2?.binding || 'TREESEED_CONTENT_BUCKET'; +} + +export function resolveCloudflareR2Bucket(runtime: CloudflareRuntime | null | undefined, binding: string): R2BucketLike | null { + const candidate = runtime?.env?.[binding]; + return candidate && typeof candidate === 'object' ? candidate as R2BucketLike : null; +} + +function expand(template: string, values: Record) { + return template.replace(/\{([A-Za-z]+)\}/gu, (_match, key: string) => values[key] ?? key); +} + +export function resolveTeamScopedContentLocator(config: DeployConfig, teamId: string, previewId?: string) { + const values = { teamId, projectId: config.slug ?? 'admin', environment: process.env.TREESEED_ENVIRONMENT ?? 'staging', previewId: previewId ?? '' }; + const manifestTemplate = config.cloudflare?.r2?.manifestKeyTemplate ?? 'teams/{teamId}/published/common.json'; + const previewTemplate = config.cloudflare?.r2?.previewRootTemplate ?? 'teams/{teamId}/previews'; + return { + manifestKey: expand(manifestTemplate, values), + previewRoot: expand(previewTemplate, values), + overlayKey: previewId ? `${expand(previewTemplate, values)}/${previewId}/overlay.json` : undefined, + }; +} + +export function createTeamScopedR2OverlayContentRuntimeProvider(input: { + bucket: R2BucketLike; + locator: { manifestKey: string; overlayKey?: string }; +}): ContentRuntimeProvider { + let manifestPromise: Promise | null = null; + const getManifest = () => manifestPromise ??= input.bucket.get(input.locator.manifestKey).then(async (object) => { + if (!object) throw new Error(`Published content manifest is unavailable at ${input.locator.manifestKey}.`); + return object.json(); + }); + return { + getManifest, + async listCollection(model) { return (await getManifest()).entries.filter((entry) => entry.model === model) }, + async getEntry(model, slugOrId) { return (await getManifest()).entries.find((entry) => entry.model === model && (entry.slug === slugOrId || entry.id === slugOrId)) ?? null }, + async getObject(pointer: PublishedContentObjectPointer) { const object = await input.bucket.get(pointer.objectKey); return object ? object.json() : null }, + }; +} diff --git a/src/runtime/platform/tenant-config.ts b/src/runtime/platform/tenant-config.ts new file mode 100644 index 00000000..54f181ab --- /dev/null +++ b/src/runtime/platform/tenant-config.ts @@ -0,0 +1,87 @@ +import { existsSync, readFileSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { parse as parseYaml } from 'yaml'; +import type { ContentCollection, TenantConfig } from '@treeseed/sdk/site-contracts/platform'; + +const CONTENT_ALIASES: Record = { + pages: ['page_root', 'pages_root'], notes: ['notes_root'], questions: ['questions_root'], + objectives: ['objectives_root'], proposals: ['proposals_root'], decisions: ['decisions_root'], + people: ['people_root'], agents: ['agents_root'], books: ['books_root'], + docs: ['knowledge', 'knowledge_root', 'docs_root'], groups: ['groups_root'], group_edges: ['group_edges_root'], +}; + +function tenantCandidates(start = process.cwd()) { + const candidates: string[] = []; + let current = resolve(start); + while (true) { + candidates.push(current, resolve(current, 'docs'), resolve(current, 'fixture')); + const parent = resolve(current, '..'); + if (parent === current) break; + current = parent; + } + return [...new Set(candidates)]; +} + +function resolveManifestPath(manifestPath: string) { + const explicitRoot = process.env.TREESEED_TENANT_ROOT?.trim(); + const candidates = [ + ...(explicitRoot ? [resolve(explicitRoot, manifestPath)] : []), + resolve(manifestPath), + ...tenantCandidates().map((root) => resolve(root, manifestPath)), + ]; + const found = candidates.find(existsSync); + if (!found) throw new Error(`Unable to resolve TreeSeed tenant manifest "${manifestPath}" from ${process.cwd()}.`); + return found; +} + +export function resolveTenantRoot() { + const explicitRoot = process.env.TREESEED_TENANT_ROOT?.trim(); + const roots = [...(explicitRoot ? [resolve(explicitRoot)] : []), ...tenantCandidates()]; + const found = roots.find((root) => existsSync(resolve(root, 'src/manifest.yaml'))); + if (!found) throw new Error(`Unable to resolve a TreeSeed tenant root from ${process.cwd()}.`); + return found; +} + +export function defineTenant(tenant: T): T { return tenant } + +export function loadManifest(manifestPath = './src/manifest.yaml'): TenantConfig { + const resolvedPath = resolveManifestPath(manifestPath); + const tenantRoot = resolve(dirname(resolvedPath), '..'); + const parsed = (parseYaml(readFileSync(resolvedPath, 'utf8')) ?? {}) as Record; + const rawContent = (parsed.content ?? {}) as Record; + for (const [key, aliases] of Object.entries(CONTENT_ALIASES)) { + if (rawContent[key] !== undefined) continue; + const alias = aliases.find((candidate) => rawContent[candidate] !== undefined); + if (alias) rawContent[key] = rawContent[alias]; + } + const tenant = { + ...parsed, + siteConfigPath: resolve(tenantRoot, String(parsed.siteConfigPath ?? './src/config.yaml')), + content: Object.fromEntries(Object.entries(rawContent).map(([key, value]) => [key, resolve(tenantRoot, String(value))])), + site: parsed.site ?? {}, + } as TenantConfig; + Object.defineProperty(tenant, '__tenantRoot', { value: tenantRoot, enumerable: false }); + return tenant; +} + +export const loadTenantManifest = loadManifest; + +export function getTenantContentRoot(tenant: Pick, collection: string) { + const root = tenant.content[collection]; + if (!root) throw new Error(`Unknown tenant content collection: ${collection}`); + return root; +} + +export function tenantFeatureEnabled(tenant: Pick, feature: string) { + return tenant.features?.[feature] !== false; +} + +const MODEL_FEATURES: Partial> = { + docs: 'docs', books: 'books', notes: 'notes', questions: 'questions', objectives: 'objectives', + proposals: 'proposals', decisions: 'decisions', agents: 'agents', +}; + +export function tenantModelRendered(tenant: Pick, model: ContentCollection) { + const feature = MODEL_FEATURES[model]; + return (!feature || tenantFeatureEnabled(tenant, feature)) && tenant.site?.models?.[model]?.rendered !== false; +} diff --git a/src/runtime/platform/trusted-assertion.ts b/src/runtime/platform/trusted-assertion.ts new file mode 100644 index 00000000..eae7174c --- /dev/null +++ b/src/runtime/platform/trusted-assertion.ts @@ -0,0 +1,19 @@ +import { createHmac, randomBytes } from 'node:crypto'; + +export function createSignedUserAssertion(input: { + secret: string; + userId: string; + sessionId: string | null; + identityId: string | null; + authTime: string; +}) { + const payload = Buffer.from(JSON.stringify({ + userId: input.userId, + sessionId: input.sessionId, + identityId: input.identityId, + authTime: input.authTime, + expiresAt: new Date(Date.now() + 5 * 60 * 1000).toISOString(), + nonce: randomBytes(16).toString('base64url'), + }), 'utf8').toString('base64url'); + return `${payload}.${createHmac('sha256', input.secret).update(payload).digest('base64url')}`; +} diff --git a/src/runtime/platform/workflow-support.ts b/src/runtime/platform/workflow-support.ts new file mode 100644 index 00000000..ea638602 --- /dev/null +++ b/src/runtime/platform/workflow-support.ts @@ -0,0 +1,76 @@ +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import { delimiter, dirname, resolve } from 'node:path'; +import { spawnSync } from 'node:child_process'; + +function executableCandidates(name: string, env: NodeJS.ProcessEnv) { + const override = env[`TREESEED_${name.toUpperCase().replace(/[^A-Z0-9]/gu, '_')}_BIN`]; + const suffixes = process.platform === 'win32' ? ['', '.cmd', '.exe'] : ['']; + return [ + ...(override ? [override] : []), + ...String(env.PATH ?? '').split(delimiter).flatMap((root) => suffixes.map((suffix) => resolve(root, `${name}${suffix}`))), + ...suffixes.map((suffix) => resolve(process.cwd(), 'node_modules', '.bin', `${name}${suffix}`)), + ]; +} + +export function resolveToolBinary(name: string, options: { env?: NodeJS.ProcessEnv } = {}) { + return executableCandidates(name, options.env ?? process.env).find(existsSync) ?? null; +} + +export function packageScriptPath(scriptName: string) { + const root = resolve(import.meta.dirname, '../../..'); + for (const candidate of [resolve(root, 'dist', 'scripts', `${scriptName}.js`), resolve(root, 'scripts', `${scriptName}.ts`)]) { + if (existsSync(candidate)) return candidate; + } + throw new Error(`Unable to resolve TreeSeed Core script ${scriptName}.`); +} + +export function resolveWranglerBin() { + const resolved = resolveToolBinary('wrangler'); + if (!resolved) throw new Error('Wrangler is unavailable.'); + return resolved; +} + +export function findNearestWorkspaceRoot(start = process.cwd()) { + let current = resolve(start); + while (true) { + const packagePath = resolve(current, 'package.json'); + if (existsSync(packagePath)) { + try { if (JSON.parse(readFileSync(packagePath, 'utf8')).workspaces) return current } catch { /* continue */ } + } + const parent = resolve(current, '..'); + if (parent === current) return null; + current = parent; + } +} + +export function ensureLocalWorkspaceLinks(_root?: string, _options?: { mode?: string; env?: NodeJS.ProcessEnv }) { + return { links: [] as string[], issues: [] as string[] }; +} + +export function createPersistentDeployTarget(scope = 'prod') { return { scope, kind: 'persistent' as const } } + +export function ensureGeneratedWranglerConfig(tenantRoot: string, _options?: { target?: unknown; env?: NodeJS.ProcessEnv }) { + const wranglerPath = resolve(tenantRoot, '.treeseed', 'generated', 'environments', 'local', 'wrangler.toml'); + mkdirSync(dirname(wranglerPath), { recursive: true }); + if (!existsSync(wranglerPath)) writeFileSync(wranglerPath, 'name = "treeseed-local"\ncompatibility_date = "2026-01-01"\n', 'utf8'); + return { wranglerPath }; +} + +export function resolveMachineEnvironmentValues(_tenantRoot: string, scope: string) { + const prefix = `TREESEED_${scope.toUpperCase()}_`; + return Object.fromEntries(Object.entries(process.env).filter(([key]) => key.startsWith(prefix) || key.startsWith('TREESEED_'))); +} + +export function applyEnvironmentToProcess(input: { tenantRoot: string; scope: string; override?: boolean }) { + const values = resolveMachineEnvironmentValues(input.tenantRoot, input.scope); + for (const [key, value] of Object.entries(values)) if (input.override || process.env[key] === undefined) process.env[key] = value; + return values; +} + +export function assertCommandEnvironment(_input?: { tenantRoot: string; scope: string; purpose: string }) { return true } + +export function runRepositoryGit(args: string[], options: { cwd: string; mode?: string; allowFailure?: boolean }) { + const result = spawnSync('git', args, { cwd: options.cwd, encoding: 'utf8' }); + if (!options.allowFailure && (result.status ?? 1) !== 0) throw new Error(String(result.stderr || `git ${args.join(' ')} failed`)); + return result; +} diff --git a/src/site-resources.ts b/src/site-resources.ts deleted file mode 100644 index a4e77e35..00000000 --- a/src/site-resources.ts +++ /dev/null @@ -1,10 +0,0 @@ -export { - buildTreeseedSiteLayers, - resolveTreeseedPageEntrypoint, - resolveTreeseedSiteResource, - resolveTreeseedStyleEntrypoint, - TREESEED_SITE_RESOURCE_KINDS, - type TreeseedSiteLayer, - type TreeseedSiteLayerDefinition, - type TreeseedSiteResourceKind, -} from './platform-resources'; diff --git a/src/site.ts b/src/site.ts deleted file mode 100644 index e5f8da68..00000000 --- a/src/site.ts +++ /dev/null @@ -1,545 +0,0 @@ -import { defineConfig, envField } from 'astro/config'; -import type { AstroUserConfig } from 'astro'; -import cloudflare from '@astrojs/cloudflare'; -import react from '@astrojs/react'; -import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'; -import { createRequire } from 'node:module'; -import { resolve, sep } from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { parse as parseYaml } from 'yaml'; -import rehypeKatex from 'rehype-katex'; -import remarkMath from 'remark-math'; -import tailwindcss from '@tailwindcss/vite'; -import type { TreeseedDeployConfig, TreeseedTenantConfig } from '@treeseed/sdk/platform/contracts'; -import type { TreeseedSiteExtensionContribution, TreeseedSiteRouteContribution } from '@treeseed/sdk/platform/plugin'; -import { parseSiteConfig } from './utils/site-config-schema.js'; -import { buildTenantBookRuntime } from '@treeseed/sdk/platform/books-data'; -import { getStarlightSidebarConfigFromRuntime } from './utils/starlight-nav'; -import { buildTreeseedThemeCss } from './utils/theme.ts'; -import { loadTreeseedDeployConfig } from '@treeseed/sdk/platform/deploy-config'; -import { getTreeseedContentServingMode } from '@treeseed/sdk/platform/deploy-runtime'; -import { getTenantContentRoot } from '@treeseed/sdk/platform/tenant-config'; -import { loadTreeseedPluginRuntime } from '@treeseed/sdk/platform/plugins'; -import type { TreeseedContentCollection } from '@treeseed/sdk/platform/contracts'; -import { - buildTreeseedSiteLayers, - resolveTreeseedPageEntrypoint, - resolveTreeseedSiteResource, - resolveTreeseedStyleEntrypoint, -} from './site-resources'; -import { deriveTreeseedAstroAllowedDomains } from './utils/astro-security'; -import { isSiteRenderedModel } from './utils/site-models'; - -const TENANT_THEME_VIRTUAL_ID = 'virtual:treeseed/tenant-theme.css'; -const RESOLVED_TENANT_THEME_VIRTUAL_ID = '\0treeseed:tenant-theme.css'; -const require = createRequire(import.meta.url); - -type SiteCreateDependencies = { - starlight: (config: Record) => unknown; -}; - -type SiteRoute = { - pattern: string; - entrypoint: string; - resourcePath?: string; - model?: TreeseedContentCollection; -}; - -type SiteExtensionContribution = TreeseedSiteExtensionContribution; - -function packageFile(relativePath: string) { - return fileURLToPath(new URL(relativePath, import.meta.url)); -} - -function packageModuleFile(relativeStem: string) { - for (const extension of ['.js', '.ts', '.ts']) { - const candidateUrl = new URL(`${relativeStem}${extension}`, import.meta.url); - const candidatePath = fileURLToPath(candidateUrl); - if (existsSync(candidatePath)) { - return candidatePath; - } - } - - throw new Error(`Unable to resolve package module for ${relativeStem}`); -} - -const PACKAGE_ROUTE_ENTRIES: Array<{ pattern: string; entrypoint?: string; resourcePath?: string; model?: TreeseedContentCollection }> = [ - { pattern: '/', resourcePath: 'pages/index.astro' }, - { pattern: '/404', resourcePath: 'pages/404.astro' }, - { pattern: '/contact', resourcePath: 'pages/contact.astro' }, - { pattern: '/api/feedback/submit', resourcePath: 'pages/api/feedback/submit.ts' }, - { pattern: '/feed.xml', resourcePath: 'pages/feed.xml', model: 'notes' }, - { pattern: '/ui', resourcePath: 'pages/ui/index.astro' }, - { pattern: '/agents', resourcePath: 'pages/agents/index.astro', model: 'agents' }, - { pattern: '/agents/[slug]', resourcePath: 'pages/agents/[slug].astro', model: 'agents' }, - { pattern: '/books', resourcePath: 'pages/books/index.astro', model: 'books' }, - { pattern: '/books/[slug]', resourcePath: 'pages/books/[slug].astro', model: 'books' }, - { pattern: '/notes', resourcePath: 'pages/notes/index.astro', model: 'notes' }, - { pattern: '/notes/[slug]', resourcePath: 'pages/notes/[slug].astro', model: 'notes' }, - { pattern: '/objectives', resourcePath: 'pages/objectives/index.astro', model: 'objectives' }, - { pattern: '/objectives/[slug]', resourcePath: 'pages/objectives/[slug].astro', model: 'objectives' }, - { pattern: '/proposals', resourcePath: 'pages/proposals/index.astro', model: 'proposals' }, - { pattern: '/proposals/[slug]', resourcePath: 'pages/proposals/[slug].astro', model: 'proposals' }, - { pattern: '/people', resourcePath: 'pages/people/index.astro', model: 'people' }, - { pattern: '/people/[slug]', resourcePath: 'pages/people/[slug].astro', model: 'people' }, - { pattern: '/decisions', resourcePath: 'pages/decisions/index.astro', model: 'decisions' }, - { pattern: '/decisions/[slug]', resourcePath: 'pages/decisions/[slug].astro', model: 'decisions' }, - { pattern: '/questions', resourcePath: 'pages/questions/index.astro', model: 'questions' }, - { pattern: '/questions/[slug]', resourcePath: 'pages/questions/[slug].astro', model: 'questions' }, -]; - -const DYNAMIC_PAGE_ROUTE_ENTRY = { pattern: '/[slug]', resourcePath: 'pages/[slug].astro', model: 'pages' as const }; - -function collectMarkdownFiles(rootPath: string): string[] { - if (!existsSync(rootPath)) { - return []; - } - - const stats = statSync(rootPath); - if (stats.isFile()) { - return /\.(md|mdx)$/iu.test(rootPath) ? [rootPath] : []; - } - - return readdirSync(rootPath, { withFileTypes: true }).flatMap((entry) => { - const fullPath = resolve(rootPath, entry.name); - if (entry.isDirectory()) { - return collectMarkdownFiles(fullPath); - } - - return entry.isFile() && /\.(md|mdx)$/iu.test(entry.name) ? [fullPath] : []; - }); -} - -function readFrontmatter(filePath: string): Record | null { - const raw = readFileSync(filePath, 'utf8'); - const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/); - if (!match) { - return null; - } - - return parseYaml(match[1]) as Record; -} - -function collectLocalPageRouteEntries(tenantConfig: TreeseedTenantConfig, projectRoot: string) { - const pagesRoot = resolve(projectRoot, getTenantContentRoot(tenantConfig, 'pages')); - const slugs = new Set(); - - for (const filePath of collectMarkdownFiles(pagesRoot)) { - const frontmatter = readFrontmatter(filePath); - const slug = typeof frontmatter?.slug === 'string' ? frontmatter.slug.replace(/^\/+|\/+$/g, '') : ''; - if (!slug || slug.includes('/')) { - continue; - } - slugs.add(slug); - } - - return [...slugs].sort().map((slug) => ({ - pattern: `/${slug}`, - resourcePath: 'pages/[slug].astro', - model: 'pages' as const, - })); -} - -function tenantPageRouteExists(projectRoot: string, pattern: string) { - if (pattern.includes('[') || pattern.includes(']')) return false; - const cleanPattern = pattern.replace(/\/+$/u, '') || '/'; - const routePath = cleanPattern === '/' - ? 'index' - : cleanPattern.replace(/^\/+/u, '').replace(/\/$/u, '/index'); - return ['astro', 'mdx', 'md', 'ts', 'js'].some((extension) => - existsSync(resolve(projectRoot, 'src', 'pages', `${routePath}.${extension}`)), - ); -} - -function createTreeseedRoutesIntegration(tenantConfig: TreeseedTenantConfig, projectRoot: string, routes: SiteRoute[] = []) { - return { - name: 'treeseed-routes', - hooks: { - 'astro:config:setup'({ injectRoute }: { injectRoute: (route: SiteRoute) => void }) { - for (const route of routes) { - if (route.model && !isSiteRenderedModel(tenantConfig, route.model)) continue; - if (tenantPageRouteExists(projectRoot, route.pattern)) continue; - injectRoute(route); - } - }, - }, - }; -} - -function normalizeSiteHookContribution(contribution: unknown): SiteExtensionContribution { - if (!contribution || typeof contribution !== 'object') { - return {}; - } - return contribution as SiteExtensionContribution; -} - -function resolveRouteEntry( - route: TreeseedSiteRouteContribution, - siteLayers: ReturnType, -): SiteRoute { - if (route.entrypoint) { - return { - pattern: route.pattern, - entrypoint: route.entrypoint, - resourcePath: route.resourcePath, - model: route.model, - }; - } - - if (route.resourcePath) { - return { - pattern: route.pattern, - entrypoint: resolveTreeseedPageEntrypoint(siteLayers, route.resourcePath), - resourcePath: route.resourcePath, - model: route.model, - }; - } - - throw new Error(`Treeseed route "${route.pattern}" must define either entrypoint or resourcePath.`); -} - -function resolveUiComponentEntrypoint( - siteLayers: ReturnType, - resourcePath: string, - uiEntrypoint: string, - projectRoot?: string, -) { - const tenantEntrypoint = resolveTreeseedSiteResource(siteLayers, 'components', resourcePath); - if (tenantEntrypoint) return tenantEntrypoint; - - if (projectRoot) { - try { - return createRequire(resolve(projectRoot, 'package.json')).resolve(uiEntrypoint); - } catch { - // Fall back to Core's package context for installed-package consumers. - } - } - - return require.resolve(uiEntrypoint); -} - -function resolveUiDistRoot(projectRoot: string) { - const probeEntrypoint = '@treeseed/ui/components/astro/layouts/MainLayout.astro'; - let resolvedProbe: string; - try { - resolvedProbe = createRequire(resolve(projectRoot, 'package.json')).resolve(probeEntrypoint); - } catch { - resolvedProbe = require.resolve(probeEntrypoint); - } - - const distMarker = `${sep}dist${sep}astro${sep}`; - const markerIndex = resolvedProbe.lastIndexOf(distMarker); - if (markerIndex < 0) { - return null; - } - return resolvedProbe.slice(0, markerIndex + `${sep}dist`.length); -} - -function createUiPackageAliases(projectRoot: string) { - const uiDistRoot = resolveUiDistRoot(projectRoot); - if (!uiDistRoot) return []; - - return [ - { find: /^@treeseed\/ui\/components\/astro\/(.*)$/, replacement: `${uiDistRoot}/astro/$1` }, - { find: /^@treeseed\/ui\/styles\/(.*)$/, replacement: `${uiDistRoot}/styles/$1` }, - { find: /^@treeseed\/ui\/lib\/(.*)$/, replacement: `${uiDistRoot}/lib/$1` }, - { find: /^@treeseed\/ui\/theme\/schemes\/(.*)$/, replacement: `${uiDistRoot}/theme/schemes/$1` }, - ]; -} - -function resolveSitePluginExtensions( - pluginRuntime: ReturnType, - context: { - projectRoot: string; - tenantConfig: TreeseedTenantConfig; - siteConfig: ReturnType; - deployConfig: TreeseedDeployConfig; - }, -) { - const selectedSiteProvider = pluginRuntime.config.providers.site; - const extensions: Required = { - routes: [], - starlightComponents: {}, - customCss: [], - remarkPlugins: [], - rehypePlugins: [], - envSchema: {}, - vitePlugins: [], - integrations: [], - routeMiddleware: [], - }; - - if (selectedSiteProvider !== 'default') { - let matched = false; - for (const { plugin, config } of pluginRuntime.plugins) { - const providerFactory = plugin.siteProviders?.[selectedSiteProvider]; - if (!providerFactory) { - continue; - } - matched = true; - Object.assign(extensions, normalizeSiteHookContribution(providerFactory({ ...context, pluginConfig: config ?? {} }))); - break; - } - if (!matched) { - throw new Error(`Treeseed site provider "${selectedSiteProvider}" is not registered.`); - } - } - - for (const { plugin, config } of pluginRuntime.plugins) { - const rawContribution = - typeof plugin.siteHooks === 'function' - ? plugin.siteHooks({ ...context, pluginConfig: config ?? {} }) - : plugin.siteHooks; - const contribution = normalizeSiteHookContribution(rawContribution); - extensions.routes.push(...(contribution.routes ?? [])); - Object.assign(extensions.starlightComponents, contribution.starlightComponents ?? {}); - extensions.customCss.push(...(contribution.customCss ?? [])); - extensions.remarkPlugins.push(...(contribution.remarkPlugins ?? [])); - extensions.rehypePlugins.push(...(contribution.rehypePlugins ?? [])); - Object.assign(extensions.envSchema, contribution.envSchema ?? {}); - extensions.vitePlugins.push(...(contribution.vitePlugins ?? [])); - extensions.integrations.push(...(contribution.integrations ?? [])); - extensions.routeMiddleware.push(...(contribution.routeMiddleware ?? [])); - } - - return extensions; -} - -function toStarlightLogoSrc(publicPath: string) { - return publicPath.startsWith('/') ? `./public${publicPath}` : publicPath; -} - -function createTenantThemeVitePlugin(themeCss: string) { - return { - name: 'treeseed-tenant-theme', - resolveId(id: string) { - return id === TENANT_THEME_VIRTUAL_ID ? RESOLVED_TENANT_THEME_VIRTUAL_ID : undefined; - }, - load(id: string) { - return id === RESOLVED_TENANT_THEME_VIRTUAL_ID ? themeCss : undefined; - }, - }; -} - -function normalizeEscapedMath(value: string) { - return value - .replace(/\\\\([A-Za-z]+)/g, '\\$1') - .replace(/\\([\[\]])/g, '$1') - .replace(/\\left\\([\[\]\(\)])/g, '\\left$1') - .replace(/\\right\\([\[\]\(\)])/g, '\\right$1') - .replace(/\\([_=+\-])/g, '$1'); -} - -function walkTree(node: any, visitor: (node: any) => void) { - visitor(node); - if (!node || !Array.isArray(node.children)) { - return; - } - - for (const child of node.children) { - walkTree(child, visitor); - } -} - -function remarkNormalizeEscapedMath() { - return (tree: any) => { - walkTree(tree, (node) => { - if ((node.type === 'math' || node.type === 'inlineMath') && typeof node.value === 'string') { - const normalizedValue = normalizeEscapedMath(node.value); - node.value = normalizedValue; - - if (node.data && Array.isArray(node.data.hChildren)) { - for (const child of node.data.hChildren) { - if (child && child.type === 'text' && typeof child.value === 'string') { - child.value = normalizedValue; - } - } - } - } - }); - }; -} - -function rehypeNormalizeEscapedMath() { - return (tree: any) => { - walkTree(tree, (node) => { - if (node?.type !== 'element' || node.tagName !== 'code') { - return; - } - - const classNames = Array.isArray(node.properties?.className) ? node.properties.className : []; - if (!classNames.includes('language-math')) { - return; - } - - if (!Array.isArray(node.children)) { - return; - } - - for (const child of node.children) { - if (child?.type === 'text' && typeof child.value === 'string') { - child.value = normalizeEscapedMath(child.value); - } - } - }); - }; -} - -export function createTreeseedSite( - tenantConfig: TreeseedTenantConfig, - { starlight }: SiteCreateDependencies, -): AstroUserConfig { - const projectRoot = process.cwd(); - const siteConfig = parseSiteConfig(readFileSync(resolve(projectRoot, tenantConfig.siteConfigPath), 'utf8')); - const deployConfig = loadTreeseedDeployConfig(); - const pluginRuntime = loadTreeseedPluginRuntime(deployConfig); - const bookRuntime = buildTenantBookRuntime(tenantConfig, { projectRoot }); - const docsRendered = isSiteRenderedModel(tenantConfig, 'docs'); - const booksRendered = isSiteRenderedModel(tenantConfig, 'books'); - const tenantThemeCss = buildTreeseedThemeCss(siteConfig.site.theme); - const siteLayers = buildTreeseedSiteLayers(pluginRuntime, { - coreRoot: fileURLToPath(new URL('.', import.meta.url)), - projectRoot, - tenantConfig, - siteConfig, - deployConfig, - }); - const siteExtensions = resolveSitePluginExtensions(pluginRuntime, { - projectRoot, - tenantConfig, - siteConfig, - deployConfig, - }); - const injectedTenantConfig = JSON.stringify(tenantConfig); - const injectedProjectRoot = JSON.stringify(projectRoot); - const injectedSiteConfig = JSON.stringify(siteConfig); - const injectedDeployConfig = JSON.stringify(deployConfig); - const injectedBookRuntime = JSON.stringify(bookRuntime); - const resolvedGlobalCss = resolveTreeseedStyleEntrypoint(siteLayers, 'styles/global.css'); - const serverRendered = - deployConfig.surfaces?.web?.provider === 'cloudflare' || deployConfig.providers.deploy === 'cloudflare'; - const allowedDomains = deriveTreeseedAstroAllowedDomains(deployConfig, { siteUrl: siteConfig.site.siteUrl }); - const publishedRuntime = getTreeseedContentServingMode() === 'published_runtime'; - const pageRoutes = publishedRuntime ? [DYNAMIC_PAGE_ROUTE_ENTRY] : collectLocalPageRouteEntries(tenantConfig, projectRoot); - const packageRoutes = [ - ...PACKAGE_ROUTE_ENTRIES, - ...pageRoutes, - ...(docsRendered - ? [ - { pattern: '/knowledge', resourcePath: 'pages/docs-runtime/index.astro', model: 'docs' as const }, - { pattern: '/knowledge/[...slug]', resourcePath: 'pages/docs-runtime/[...slug].astro', model: 'docs' as const }, - ] - : []), - ]; - - return defineConfig({ - adapter: serverRendered - ? cloudflare({ imageService: 'compile' }) - : undefined, - output: serverRendered - ? 'server' - : 'static', - session: serverRendered - ? { driver: 'null' } - : undefined, - security: { - checkOrigin: true, - allowedDomains, - }, - site: siteConfig.site.siteUrl, - image: { - service: { - entrypoint: 'astro/assets/services/sharp', - }, - }, - vite: { - resolve: { - alias: createUiPackageAliases(projectRoot), - }, - define: { - __TREESEED_TENANT_CONFIG__: injectedTenantConfig, - __TREESEED_PROJECT_ROOT__: injectedProjectRoot, - __TREESEED_SITE_CONFIG__: injectedSiteConfig, - __TREESEED_DEPLOY_CONFIG__: injectedDeployConfig, - __TREESEED_BOOK_RUNTIME__: injectedBookRuntime, - }, - optimizeDeps: { - exclude: ['libsodium-wrappers-sumo'], - }, - plugins: [ - createTenantThemeVitePlugin(tenantThemeCss), - tailwindcss() as any, - ...siteExtensions.vitePlugins, - ], - ssr: { - external: ['node:async_hooks', 'node:crypto', 'node:fs', 'node:module', 'node:path', 'node:url'], - }, - }, - markdown: { - syntaxHighlight: false, - remarkPlugins: [remarkMath, remarkNormalizeEscapedMath, ...siteExtensions.remarkPlugins], - rehypePlugins: [rehypeNormalizeEscapedMath, [rehypeKatex, { strict: 'ignore' }], ...siteExtensions.rehypePlugins], - }, - env: { - schema: { - TREESEED_PUBLIC_TURNSTILE_SITE_KEY: envField.string({ context: 'client', access: 'public', optional: true }), - TREESEED_PUBLIC_DEV_WATCH_RELOAD: envField.boolean({ context: 'client', access: 'public', optional: true }), - TREESEED_TURNSTILE_SECRET_KEY: envField.string({ context: 'server', access: 'secret', optional: true }), - TREESEED_SMTP_HOST: envField.string({ context: 'server', access: 'secret', optional: true }), - TREESEED_SMTP_PORT: envField.number({ context: 'server', access: 'secret', optional: true }), - TREESEED_SMTP_USERNAME: envField.string({ context: 'server', access: 'secret', optional: true }), - TREESEED_SMTP_PASSWORD: envField.string({ context: 'server', access: 'secret', optional: true }), - TREESEED_SMTP_FROM: envField.string({ context: 'server', access: 'secret', optional: true }), - TREESEED_SMTP_REPLY_TO: envField.string({ context: 'server', access: 'secret', optional: true }), - TREESEED_FORM_TOKEN_SECRET: envField.string({ context: 'server', access: 'secret', optional: true }), - TREESEED_EDITORIAL_PREVIEW_SECRET: envField.string({ context: 'server', access: 'secret', optional: true }), - TREESEED_EDITORIAL_PREVIEW_ROOT: envField.string({ context: 'server', access: 'secret', optional: true }), - TREESEED_EDITORIAL_PREVIEW_TTL_HOURS: envField.number({ context: 'server', access: 'secret', optional: true }), - TREESEED_CONTENT_BUCKET_NAME: envField.string({ context: 'server', access: 'secret', optional: true }), - TREESEED_CONTENT_DEFAULT_TEAM_ID: envField.string({ context: 'server', access: 'secret', optional: true }), - TREESEED_CONTENT_MANIFEST_KEY_TEMPLATE: envField.string({ context: 'server', access: 'secret', optional: true }), - TREESEED_CONTENT_PREVIEW_ROOT_TEMPLATE: envField.string({ context: 'server', access: 'secret', optional: true }), - TREESEED_LOCAL_DEV_MODE: envField.enum({ values: ['cloudflare'], context: 'server', access: 'secret', optional: true }), - TREESEED_FORMS_LOCAL_BYPASS_CLOUDFLARE_GUARDS: envField.boolean({ context: 'server', access: 'secret', optional: true }), - ...siteExtensions.envSchema, - }, - }, - integrations: [ - react(), - createTreeseedRoutesIntegration(tenantConfig, projectRoot, [ - ...packageRoutes.map((route) => resolveRouteEntry(route, siteLayers)), - ...siteExtensions.routes.map((route) => resolveRouteEntry(route, siteLayers)), - ]), - ...(docsRendered && !publishedRuntime ? [starlight({ - prerender: !serverRendered, - pagefind: !serverRendered, - disable404Route: true, - expressiveCode: false, - customCss: [resolvedGlobalCss, TENANT_THEME_VIRTUAL_ID, ...siteExtensions.customCss], - title: siteConfig.site.name, - logo: { - src: toStarlightLogoSrc(siteConfig.site.logo.src), - alt: siteConfig.site.logo.alt, - }, - social: [ - { icon: 'github', label: `${siteConfig.site.name} GitHub`, href: siteConfig.site.githubRepository }, - { icon: 'discord', label: `${siteConfig.site.name} Discord`, href: siteConfig.site.discordLink }, - ], - components: { - Footer: resolveUiComponentEntrypoint(siteLayers, 'components/docs/Footer.astro', '@treeseed/ui/components/astro/docs/Footer.astro', projectRoot), - Header: resolveUiComponentEntrypoint(siteLayers, 'components/docs/Header.astro', '@treeseed/ui/components/astro/docs/Header.astro', projectRoot), - PageTitle: resolveUiComponentEntrypoint(siteLayers, 'components/docs/PageTitle.astro', '@treeseed/ui/components/astro/docs/PageTitle.astro', projectRoot), - PageFrame: resolveUiComponentEntrypoint(siteLayers, 'components/docs/PageFrame.astro', '@treeseed/ui/components/astro/docs/PageFrame.astro', projectRoot), - PageSidebar: resolveUiComponentEntrypoint(siteLayers, 'components/docs/PageSidebar.astro', '@treeseed/ui/components/astro/docs/PageSidebar.astro', projectRoot), - Sidebar: resolveUiComponentEntrypoint(siteLayers, 'components/docs/Sidebar.astro', '@treeseed/ui/components/astro/docs/Sidebar.astro', projectRoot), - SiteTitle: resolveUiComponentEntrypoint(siteLayers, 'components/SiteTitle.astro', '@treeseed/ui/components/astro/core/SiteTitle.astro', projectRoot), - ThemeSelect: resolveUiComponentEntrypoint(siteLayers, 'components/docs/ThemeSelect.astro', '@treeseed/ui/components/astro/docs/ThemeSelect.astro', projectRoot), - ...siteExtensions.starlightComponents, - }, - sidebar: booksRendered ? getStarlightSidebarConfigFromRuntime(bookRuntime) : [], - routeMiddleware: [packageModuleFile('./middleware/starlightRouteData'), ...siteExtensions.routeMiddleware], - } as any)] : []), - ...siteExtensions.integrations, - ], - }); -} diff --git a/src/site/create-site.ts b/src/site/create-site.ts new file mode 100644 index 00000000..7c2fadb7 --- /dev/null +++ b/src/site/create-site.ts @@ -0,0 +1,192 @@ +import { defineConfig, envField } from 'astro/config'; +import type { AstroUserConfig } from 'astro'; +import cloudflare from '@astrojs/cloudflare'; +import node from '@astrojs/node'; +import react from '@astrojs/react'; +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import rehypeKatex from 'rehype-katex'; +import remarkMath from 'remark-math'; +import tailwindcss from '@tailwindcss/vite'; +import type { TenantConfig } from '@treeseed/sdk/site-contracts/platform'; +import { parseSiteConfig } from "../utils/configuration/site-config-schema.js"; +import { buildThemeCss } from "../utils/support/theme.ts"; +import { loadDeployConfig } from '../runtime/platform/deploy-config.ts'; +import { getContentServingMode } from '../runtime/platform/deploy-runtime.ts'; +import { loadPluginRuntime } from '../runtime/platform/plugins.ts'; +import { buildSiteLayers, resolveStyleEntrypoint } from "../support/site-resources"; +import { deriveAstroAllowedDomains } from "../utils/support/astro-security"; +import { isSiteRenderedModel } from "../utils/support/site-models"; +import { + DYNAMIC_PAGE_ROUTE_ENTRY, + PACKAGE_ROUTE_ENTRIES, + TENANT_THEME_VIRTUAL_ID, + collectLocalPageRouteEntries, + createRoutesIntegration, + createUiPackageAliases, + resolveRouteEntry, + resolveUiComponentEntrypoint, +} from './tenant-theme-virtual-id.ts'; +import type { SiteCreateDependencies } from './tenant-theme-virtual-id.ts'; +import { createTenantThemeVitePlugin, rehypeNormalizeEscapedMath, remarkNormalizeEscapedMath, resolveSitePluginExtensions, toStarlightLogoSrc } from './resolve-site-plugin-extensions.ts'; +import { createWorkspacePackageReloadPlugin } from './workspace-package-reload.ts'; + +export function createSite( + tenantConfig: TenantConfig, + { starlight }: SiteCreateDependencies, +): AstroUserConfig { + const projectRoot = process.cwd(); + const siteConfig = parseSiteConfig(readFileSync(resolve(projectRoot, tenantConfig.siteConfigPath), 'utf8')); + const deployConfig = loadDeployConfig(); + const pluginRuntime = loadPluginRuntime(deployConfig); + const docsRendered = isSiteRenderedModel(tenantConfig, 'docs'); + const tenantThemeCss = buildThemeCss(siteConfig.site.theme); + const siteLayers = buildSiteLayers(pluginRuntime, { + coreRoot: fileURLToPath(new URL('..', import.meta.url)), + projectRoot, + tenantConfig, + siteConfig, + deployConfig, + }); + const siteExtensions = resolveSitePluginExtensions(pluginRuntime, { + projectRoot, + tenantConfig, + siteConfig, + deployConfig, + }); + const injectedTenantConfig = JSON.stringify(tenantConfig); + const injectedProjectRoot = JSON.stringify(projectRoot); + const injectedSiteConfig = JSON.stringify(siteConfig); + const injectedDeployConfig = JSON.stringify(deployConfig); + const resolvedGlobalCss = resolveStyleEntrypoint(siteLayers, 'styles/global.css'); + const runtimeTarget = process.env.TREESEED_WEB_RUNTIME_TARGET; + if (runtimeTarget && !['managed-node', 'cloudflare'].includes(runtimeTarget)) { + throw new Error(`Unsupported TREESEED_WEB_RUNTIME_TARGET ${runtimeTarget}.`); + } + const managedNode = runtimeTarget === 'managed-node'; + const serverRendered = managedNode || + deployConfig.surfaces?.web?.provider === 'cloudflare' || deployConfig.providers.deploy === 'cloudflare'; + const allowedDomains = deriveAstroAllowedDomains(deployConfig, { siteUrl: siteConfig.site.siteUrl }); + const publishedRuntime = getContentServingMode(deployConfig) === 'published_runtime'; + const pageRoutes = publishedRuntime ? [DYNAMIC_PAGE_ROUTE_ENTRY] : collectLocalPageRouteEntries(tenantConfig, projectRoot); + const packageRoutes = [ + ...PACKAGE_ROUTE_ENTRIES, + ...pageRoutes, + ]; + + return defineConfig({ + legacy: { collections: false }, + adapter: managedNode + ? node({ mode: 'standalone' }) + : serverRendered ? cloudflare({ imageService: 'compile' }) + : undefined, + output: serverRendered + ? 'server' + : 'static', + session: serverRendered + ? { driver: 'null' } + : undefined, + security: { + checkOrigin: true, + allowedDomains, + }, + site: siteConfig.site.siteUrl, + image: { + service: { + entrypoint: 'astro/assets/services/sharp', + }, + }, + vite: { + resolve: { + alias: createUiPackageAliases(projectRoot), + dedupe: ['react', 'react-dom'], + }, + define: { + TENANT_CONFIG: injectedTenantConfig, + PROJECT_ROOT: injectedProjectRoot, + SITE_CONFIG: injectedSiteConfig, + DEPLOY_CONFIG: injectedDeployConfig, + }, + optimizeDeps: { + exclude: ['@treeseed/sdk', '@treeseed/ui', '@treeseed/core', '@treeseed/admin'], + }, + plugins: [ + createWorkspacePackageReloadPlugin(projectRoot), + createTenantThemeVitePlugin(tenantThemeCss), + tailwindcss() as any, + ...siteExtensions.vitePlugins, + ], + ssr: { + external: ['node:async_hooks', 'node:crypto', 'node:fs', 'node:module', 'node:path', 'node:url'], + }, + }, + markdown: { + syntaxHighlight: false, + remarkPlugins: [remarkMath, remarkNormalizeEscapedMath, ...siteExtensions.remarkPlugins], + rehypePlugins: [rehypeNormalizeEscapedMath, [rehypeKatex, { strict: 'ignore' }], ...siteExtensions.rehypePlugins], + }, + env: { + schema: { + PUBLIC_TURNSTILE_SITE_KEY: envField.string({ context: 'client', access: 'public', optional: true }), + TREESEED_PUBLIC_DEV_WATCH_RELOAD: envField.boolean({ context: 'client', access: 'public', optional: true }), + TURNSTILE_SECRET_KEY: envField.string({ context: 'server', access: 'secret', optional: true }), + SMTP_HOST: envField.string({ context: 'server', access: 'secret', optional: true }), + SMTP_PORT: envField.number({ context: 'server', access: 'secret', optional: true }), + SMTP_USERNAME: envField.string({ context: 'server', access: 'secret', optional: true }), + SMTP_PASSWORD: envField.string({ context: 'server', access: 'secret', optional: true }), + SMTP_FROM: envField.string({ context: 'server', access: 'secret', optional: true }), + SMTP_REPLY_TO: envField.string({ context: 'server', access: 'secret', optional: true }), + FORM_TOKEN_SECRET: envField.string({ context: 'server', access: 'secret', optional: true }), + TREESEED_EDITORIAL_PREVIEW_SECRET: envField.string({ context: 'server', access: 'secret', optional: true }), + TREESEED_EDITORIAL_PREVIEW_ROOT: envField.string({ context: 'server', access: 'secret', optional: true }), + TREESEED_EDITORIAL_PREVIEW_TTL_HOURS: envField.number({ context: 'server', access: 'secret', optional: true }), + CONTENT_BUCKET_NAME: envField.string({ context: 'server', access: 'secret', optional: true }), + TREESEED_CONTENT_DEFAULT_TEAM_ID: envField.string({ context: 'server', access: 'secret', optional: true }), + TREESEED_CONTENT_MANIFEST_KEY: envField.string({ context: 'server', access: 'secret', optional: true }), + TREESEED_CONTENT_MANIFEST_KEY_TEMPLATE: envField.string({ context: 'server', access: 'secret', optional: true }), + TREESEED_CONTENT_PREVIEW_ROOT_TEMPLATE: envField.string({ context: 'server', access: 'secret', optional: true }), + LOCAL_DEV_MODE: envField.enum({ values: ['cloudflare', 'managed-node'], context: 'server', access: 'secret', optional: true }), + FORMS_LOCAL_BYPASS_CLOUDFLARE_GUARDS: envField.boolean({ context: 'server', access: 'secret', optional: true }), + ...siteExtensions.envSchema, + }, + }, + integrations: [ + react(), + createRoutesIntegration(tenantConfig, projectRoot, [ + ...packageRoutes.map((route) => resolveRouteEntry(route, siteLayers)), + ...siteExtensions.routes.map((route) => resolveRouteEntry(route, siteLayers)), + ]), + ...(docsRendered ? [starlight({ + prerender: !serverRendered, + pagefind: !serverRendered, + disable404Route: true, + expressiveCode: false, + customCss: [resolvedGlobalCss, TENANT_THEME_VIRTUAL_ID, ...siteExtensions.customCss], + title: siteConfig.site.name, + logo: { + src: toStarlightLogoSrc(siteConfig.site.logo.src), + alt: siteConfig.site.logo.alt, + }, + social: [ + { icon: 'github', label: `${siteConfig.site.name} GitHub`, href: siteConfig.site.githubRepository }, + { icon: 'discord', label: `${siteConfig.site.name} Discord`, href: siteConfig.site.discordLink }, + ], + components: { + Footer: resolveUiComponentEntrypoint(siteLayers, 'components/docs/Footer.astro', '@treeseed/ui/components/astro/docs/Footer.astro', projectRoot), + Header: resolveUiComponentEntrypoint(siteLayers, 'components/docs/Header.astro', '@treeseed/ui/components/astro/docs/Header.astro', projectRoot), + PageTitle: resolveUiComponentEntrypoint(siteLayers, 'components/docs/PageTitle.astro', '@treeseed/ui/components/astro/docs/PageTitle.astro', projectRoot), + PageFrame: resolveUiComponentEntrypoint(siteLayers, 'components/docs/PageFrame.astro', '@treeseed/ui/components/astro/docs/PageFrame.astro', projectRoot), + PageSidebar: resolveUiComponentEntrypoint(siteLayers, 'components/docs/PageSidebar.astro', '@treeseed/ui/components/astro/docs/PageSidebar.astro', projectRoot), + Sidebar: resolveUiComponentEntrypoint(siteLayers, 'components/docs/Sidebar.astro', '@treeseed/ui/components/astro/docs/Sidebar.astro', projectRoot), + SiteTitle: resolveUiComponentEntrypoint(siteLayers, 'components/SiteTitle.astro', '@treeseed/ui/components/astro/core/SiteTitle.astro', projectRoot), + ThemeSelect: resolveUiComponentEntrypoint(siteLayers, 'components/docs/ThemeSelect.astro', '@treeseed/ui/components/astro/docs/ThemeSelect.astro', projectRoot), + ...siteExtensions.starlightComponents, + }, + sidebar: [], + routeMiddleware: siteExtensions.routeMiddleware, + } as any)] : []), + ...siteExtensions.integrations, + ], + }); +} diff --git a/src/site/resolve-site-plugin-extensions.ts b/src/site/resolve-site-plugin-extensions.ts new file mode 100644 index 00000000..1b11deb4 --- /dev/null +++ b/src/site/resolve-site-plugin-extensions.ts @@ -0,0 +1,147 @@ +import type { DeployConfig, TenantConfig } from '@treeseed/sdk/site-contracts/platform'; +import { parseSiteConfig } from "../utils/configuration/site-config-schema.js"; +import { loadPluginRuntime } from '../runtime/platform/plugins.ts'; +import { + RESOLVED_TENANT_THEME_VIRTUAL_ID, + TENANT_THEME_VIRTUAL_ID, + normalizeSiteHookContribution, +} from './tenant-theme-virtual-id.ts'; +import type { SiteExtensionContribution } from './tenant-theme-virtual-id.ts'; + +export function resolveSitePluginExtensions( + pluginRuntime: ReturnType, + context: { + projectRoot: string; + tenantConfig: TenantConfig; + siteConfig: ReturnType; + deployConfig: DeployConfig; + }, +) { + const selectedSiteProvider = pluginRuntime.config.providers.site; + const extensions: Required = { + routes: [], + starlightComponents: {}, + customCss: [], + remarkPlugins: [], + rehypePlugins: [], + envSchema: {}, + vitePlugins: [], + integrations: [], + routeMiddleware: [], + }; + + if (selectedSiteProvider !== 'default') { + let matched = false; + for (const { plugin, config } of pluginRuntime.plugins) { + const providerFactory = plugin.siteProviders?.[selectedSiteProvider]; + if (!providerFactory) { + continue; + } + matched = true; + Object.assign(extensions, normalizeSiteHookContribution(providerFactory({ ...context, pluginConfig: config ?? {} }))); + break; + } + if (!matched) { + throw new Error(`Treeseed site provider "${selectedSiteProvider}" is not registered.`); + } + } + + for (const { plugin, config } of pluginRuntime.plugins) { + const rawContribution = + typeof plugin.siteHooks === 'function' + ? plugin.siteHooks({ ...context, pluginConfig: config ?? {} }) + : plugin.siteHooks; + const contribution = normalizeSiteHookContribution(rawContribution); + extensions.routes.push(...(contribution.routes ?? [])); + Object.assign(extensions.starlightComponents, contribution.starlightComponents ?? {}); + extensions.customCss.push(...(contribution.customCss ?? [])); + extensions.remarkPlugins.push(...(contribution.remarkPlugins ?? [])); + extensions.rehypePlugins.push(...(contribution.rehypePlugins ?? [])); + Object.assign(extensions.envSchema, contribution.envSchema ?? {}); + extensions.vitePlugins.push(...(contribution.vitePlugins ?? [])); + extensions.integrations.push(...(contribution.integrations ?? [])); + extensions.routeMiddleware.push(...(contribution.routeMiddleware ?? [])); + } + + return extensions; +} + +export function toStarlightLogoSrc(publicPath: string) { + return publicPath.startsWith('/') ? `./public${publicPath}` : publicPath; +} + +export function createTenantThemeVitePlugin(themeCss: string) { + return { + name: 'treeseed-tenant-theme', + resolveId(id: string) { + return id === TENANT_THEME_VIRTUAL_ID ? RESOLVED_TENANT_THEME_VIRTUAL_ID : undefined; + }, + load(id: string) { + return id === RESOLVED_TENANT_THEME_VIRTUAL_ID ? themeCss : undefined; + }, + }; +} + +export function normalizeEscapedMath(value: string) { + return value + .replace(/\\\\([A-Za-z]+)/g, '\\$1') + .replace(/\\([\[\]])/g, '$1') + .replace(/\\left\\([\[\]\(\)])/g, '\\left$1') + .replace(/\\right\\([\[\]\(\)])/g, '\\right$1') + .replace(/\\([_=+\-])/g, '$1'); +} + +export function walkTree(node: any, visitor: (node: any) => void) { + visitor(node); + if (!node || !Array.isArray(node.children)) { + return; + } + + for (const child of node.children) { + walkTree(child, visitor); + } +} + +export function remarkNormalizeEscapedMath() { + return (tree: any) => { + walkTree(tree, (node) => { + if ((node.type === 'math' || node.type === 'inlineMath') && typeof node.value === 'string') { + const normalizedValue = normalizeEscapedMath(node.value); + node.value = normalizedValue; + + if (node.data && Array.isArray(node.data.hChildren)) { + for (const child of node.data.hChildren) { + if (child && child.type === 'text' && typeof child.value === 'string') { + child.value = normalizedValue; + } + } + } + } + }); + }; +} + +export function rehypeNormalizeEscapedMath() { + return (tree: any) => { + walkTree(tree, (node) => { + if (node?.type !== 'element' || node.tagName !== 'code') { + return; + } + + const classNames = Array.isArray(node.properties?.className) ? node.properties.className : []; + if (!classNames.includes('language-math')) { + return; + } + + if (!Array.isArray(node.children)) { + return; + } + + for (const child of node.children) { + if (child?.type === 'text' && typeof child.value === 'string') { + child.value = normalizeEscapedMath(child.value); + } + } + }); + }; +} diff --git a/src/site/tenant-theme-virtual-id.ts b/src/site/tenant-theme-virtual-id.ts new file mode 100644 index 00000000..63f84ae2 --- /dev/null +++ b/src/site/tenant-theme-virtual-id.ts @@ -0,0 +1,250 @@ +import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'; +import { createRequire } from 'node:module'; +import { resolve, sep } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { parse as parseYaml } from 'yaml'; +import type { TenantConfig } from '@treeseed/sdk/site-contracts/platform'; +import type { SiteExtensionContribution, SiteRouteContribution } from '@treeseed/sdk/site-contracts/plugin'; +import { getTenantContentRoot } from '../runtime/platform/tenant-config.ts'; +import type { ContentCollection } from '@treeseed/sdk/site-contracts/platform'; +import { buildSiteLayers, resolvePageEntrypoint, resolveSiteResource } from "../support/site-resources"; +import { isSiteRenderedModel } from "../utils/support/site-models"; + + +export const TENANT_THEME_VIRTUAL_ID = 'virtual:treeseed/tenant-theme.css'; + +export const RESOLVED_TENANT_THEME_VIRTUAL_ID = '\0treeseed:tenant-theme.css'; + +export const require = createRequire(import.meta.url); + +export type SiteCreateDependencies = { + starlight: (config: Record) => unknown; +}; + +export type SiteRoute = { + pattern: string; + entrypoint: string; + resourcePath?: string; + model?: ContentCollection; +}; + +export type SiteExtensionContribution = SiteExtensionContribution; + +const PACKAGE_ROOT_URL = new URL('../', import.meta.url); + +export function packageFile(relativePath: string) { + return fileURLToPath(new URL(relativePath.replace(/^\.\//u, ''), PACKAGE_ROOT_URL)); +} + +export function packageModuleFile(relativeStem: string) { + for (const extension of ['.js', '.ts', '.mjs']) { + const candidateUrl = new URL( + `${relativeStem.replace(/^\.\//u, '')}${extension}`, + PACKAGE_ROOT_URL, + ); + const candidatePath = fileURLToPath(candidateUrl); + if (existsSync(candidatePath)) { + return candidatePath; + } + } + + throw new Error(`Unable to resolve package module for ${relativeStem}`); +} + +export const PACKAGE_ROUTE_ENTRIES: Array<{ pattern: string; entrypoint?: string; resourcePath?: string; model?: ContentCollection }> = [ + { pattern: '/', resourcePath: 'pages/index.astro' }, + { pattern: '/404', resourcePath: 'pages/404.astro' }, + { pattern: '/contact', resourcePath: 'pages/contact.astro' }, + { pattern: '/feed.xml', resourcePath: 'pages/feed.xml', model: 'notes' }, + { pattern: '/ui', resourcePath: 'pages/ui/index.astro' }, + { pattern: '/agents', resourcePath: 'pages/agents/index.astro', model: 'agents' }, + { pattern: '/agents/[slug]', resourcePath: 'pages/agents/[slug].astro', model: 'agents' }, + { pattern: '/books', resourcePath: 'pages/books/index.astro', model: 'books' }, + { pattern: '/t/[teamSlug]/books/[bookSlug]', resourcePath: 'pages/t/[teamSlug]/books/[bookSlug]/index.astro', model: 'books' }, + { pattern: '/t/[teamSlug]/books/[bookSlug]/[...pageSlug]', resourcePath: 'pages/t/[teamSlug]/books/[bookSlug]/[...pageSlug].astro', model: 'docs' }, + { pattern: '/notes', resourcePath: 'pages/notes/index.astro', model: 'notes' }, + { pattern: '/notes/[slug]', resourcePath: 'pages/notes/[slug].astro', model: 'notes' }, + { pattern: '/objectives', resourcePath: 'pages/objectives/index.astro', model: 'objectives' }, + { pattern: '/objectives/[slug]', resourcePath: 'pages/objectives/[slug].astro', model: 'objectives' }, + { pattern: '/proposals', resourcePath: 'pages/proposals/index.astro', model: 'proposals' }, + { pattern: '/proposals/[slug]', resourcePath: 'pages/proposals/[slug].astro', model: 'proposals' }, + { pattern: '/people', resourcePath: 'pages/people/index.astro', model: 'people' }, + { pattern: '/people/[slug]', resourcePath: 'pages/people/[slug].astro', model: 'people' }, + { pattern: '/decisions', resourcePath: 'pages/decisions/index.astro', model: 'decisions' }, + { pattern: '/decisions/[slug]', resourcePath: 'pages/decisions/[slug].astro', model: 'decisions' }, + { pattern: '/questions', resourcePath: 'pages/questions/index.astro', model: 'questions' }, + { pattern: '/questions/[slug]', resourcePath: 'pages/questions/[slug].astro', model: 'questions' }, +]; + +export const DYNAMIC_PAGE_ROUTE_ENTRY = { pattern: '/[slug]', resourcePath: 'pages/[slug].astro', model: 'pages' as const }; + +export function collectMarkdownFiles(rootPath: string): string[] { + if (!existsSync(rootPath)) { + return []; + } + + const stats = statSync(rootPath); + if (stats.isFile()) { + return /\.(md|mdx)$/iu.test(rootPath) ? [rootPath] : []; + } + + return readdirSync(rootPath, { withFileTypes: true }).flatMap((entry) => { + const fullPath = resolve(rootPath, entry.name); + if (entry.isDirectory()) { + return collectMarkdownFiles(fullPath); + } + + return entry.isFile() && /\.(md|mdx)$/iu.test(entry.name) ? [fullPath] : []; + }); +} + +export function readFrontmatter(filePath: string): Record | null { + const raw = readFileSync(filePath, 'utf8'); + const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/); + if (!match) { + return null; + } + + return parseYaml(match[1]) as Record; +} + +export function collectLocalPageRouteEntries(tenantConfig: TenantConfig, projectRoot: string) { + const pagesRoot = resolve(projectRoot, getTenantContentRoot(tenantConfig, 'pages')); + const slugs = new Set(); + + for (const filePath of collectMarkdownFiles(pagesRoot)) { + const frontmatter = readFrontmatter(filePath); + const slug = typeof frontmatter?.slug === 'string' ? frontmatter.slug.replace(/^\/+|\/+$/g, '') : ''; + if (!slug || slug.includes('/')) { + continue; + } + slugs.add(slug); + } + + return [...slugs].sort().map((slug) => ({ + pattern: `/${slug}`, + resourcePath: 'pages/[slug].astro', + model: 'pages' as const, + })); +} + +export function tenantPageRouteExists(projectRoot: string, pattern: string) { + const cleanPattern = pattern.replace(/\/+$/u, '') || '/'; + const routePath = cleanPattern === '/' ? 'index' : cleanPattern.replace(/^\/+/u, ''); + const candidates = cleanPattern === '/' ? [routePath] : [routePath, `${routePath}/index`]; + return candidates.some((candidate) => ['astro', 'mdx', 'md', 'ts', 'js'].some((extension) => + existsSync(resolve(projectRoot, 'src', 'pages', `${candidate}.${extension}`)), + )); +} + +export function createRoutesIntegration(tenantConfig: TenantConfig, projectRoot: string, routes: SiteRoute[] = []) { + return { + name: 'treeseed-routes', + hooks: { + 'astro:config:setup'({ injectRoute }: { injectRoute: (route: SiteRoute) => void }) { + for (const route of routes) { + if (route.model && !isSiteRenderedModel(tenantConfig, route.model)) continue; + if (tenantPageRouteExists(projectRoot, route.pattern)) continue; + injectRoute(route); + } + }, + }, + }; +} + +export function normalizeSiteHookContribution(contribution: unknown): SiteExtensionContribution { + if (!contribution || typeof contribution !== 'object') { + return {}; + } + return contribution as SiteExtensionContribution; +} + +export function resolveRouteEntry( + route: SiteRouteContribution, + siteLayers: ReturnType, +): SiteRoute { + if (route.entrypoint) { + return { + pattern: route.pattern, + entrypoint: route.entrypoint, + resourcePath: route.resourcePath, + model: route.model, + }; + } + + if (route.resourcePath) { + return { + pattern: route.pattern, + entrypoint: resolvePageEntrypoint(siteLayers, route.resourcePath), + resourcePath: route.resourcePath, + model: route.model, + }; + } + + throw new Error(`Treeseed route "${route.pattern}" must define either entrypoint or resourcePath.`); +} + +export function resolveUiComponentEntrypoint( + siteLayers: ReturnType, + resourcePath: string, + uiEntrypoint: string, + projectRoot?: string, +) { + const tenantEntrypoint = resolveSiteResource(siteLayers, 'components', resourcePath); + if (tenantEntrypoint) return tenantEntrypoint; + + if (projectRoot) { + try { + return createRequire(resolve(projectRoot, 'package.json')).resolve(uiEntrypoint); + } catch { + // Fall back to Core's package context for installed-package consumers. + } + } + + return require.resolve(uiEntrypoint); +} + +export function resolveUiDistRoot(projectRoot: string) { + const probeEntrypoint = '@treeseed/ui/components/astro/layouts/MainLayout.astro'; + let resolvedProbe: string; + try { + resolvedProbe = createRequire(resolve(projectRoot, 'package.json')).resolve(probeEntrypoint); + } catch { + resolvedProbe = require.resolve(probeEntrypoint); + } + + const distMarker = `${sep}dist${sep}astro${sep}`; + const markerIndex = resolvedProbe.lastIndexOf(distMarker); + if (markerIndex < 0) { + return null; + } + return resolvedProbe.slice(0, markerIndex + `${sep}dist`.length); +} + +export function createUiPackageAliases(projectRoot: string) { + const uiDistRoot = resolveUiDistRoot(projectRoot); + if (!uiDistRoot) return []; + const uiPackageRoot = resolve(uiDistRoot, '..'); + const uiPackage = JSON.parse(readFileSync(resolve(uiPackageRoot, 'package.json'), 'utf8')) as { + exports?: Record; + }; + const componentExports = Object.entries(uiPackage.exports ?? {}).flatMap(([key, target]) => { + if (!key.startsWith('./components/astro/')) return []; + const relativeTarget = typeof target === 'string' ? target : target.default ?? target.import; + if (!relativeTarget) return []; + return [{ + find: `@treeseed/ui/${key.slice(2)}`, + replacement: resolve(uiPackageRoot, relativeTarget), + }]; + }); + + return [ + // Exact public exports win when stable paths differ from dist internals; + // the regex retains compatibility for nested component entrypoints. + ...componentExports, + { find: /^@treeseed\/ui\/components\/astro\/(.*)$/, replacement: `${uiDistRoot}/astro/$1` }, + { find: /^@treeseed\/ui\/styles\/(.*)$/, replacement: `${uiDistRoot}/styles/$1` }, + { find: /^@treeseed\/ui\/lib\/(.*)$/, replacement: `${uiDistRoot}/lib/$1` }, + { find: /^@treeseed\/ui\/theme\/schemes\/(.*)$/, replacement: `${uiDistRoot}/theme/schemes/$1` }, + ]; +} diff --git a/src/site/workspace-package-reload.ts b/src/site/workspace-package-reload.ts new file mode 100644 index 00000000..fdeee341 --- /dev/null +++ b/src/site/workspace-package-reload.ts @@ -0,0 +1,127 @@ +import { existsSync, watch, type FSWatcher } from 'node:fs'; +import { basename, dirname, relative, resolve, sep } from 'node:path'; + +const workspacePackages = ['sdk', 'ui', 'core', 'admin'] as const; +const readinessMarkers: Record<(typeof workspacePackages)[number], string[]> = { + sdk: ['index.js'], + ui: ['react.js', 'ui.css'], + core: ['support/site.js', 'pages/404.astro'], + admin: ['config.js', 'layouts/AppLayout.astro'], +}; + +export function workspacePackageOutputRoots(projectRoot: string) { + return workspacePackages + .map((packageName) => resolve(projectRoot, 'packages', packageName, 'dist')) + .filter((path) => existsSync(path)); +} + +export function isWorkspacePackageOutput(path: string, roots: string[]) { + return roots.some((root) => { + const candidate = relative(root, path); + return candidate === '' || (!candidate.startsWith(`..${sep}`) && candidate !== '..'); + }); +} + +export function workspacePackageOutputsReady(roots: string[]) { + return roots.every((root) => { + const packageName = basename(dirname(root)) as keyof typeof readinessMarkers; + return readinessMarkers[packageName]?.every((marker) => existsSync(resolve(root, marker))) ?? false; + }); +} + +export function createWorkspacePackageReloadCoordinator( + roots: string[], + send: () => void, + delayMs = 250, +) { + let timer: ReturnType | null = null; + const notify = (path: string) => { + if (!isWorkspacePackageOutput(path, roots)) return; + if (timer) clearTimeout(timer); + timer = setTimeout(() => { + timer = null; + if (workspacePackageOutputsReady(roots)) send(); + }, delayMs); + }; + const dispose = () => { + if (timer) clearTimeout(timer); + timer = null; + }; + return { notify, dispose }; +} + +export function reloadWorkspacePackageModules(server: { + moduleGraph: { invalidateAll(): void }; + ws: { send(payload: { type: 'full-reload'; path: string }): void }; +}) { + server.moduleGraph.invalidateAll(); + server.ws.send({ type: 'full-reload', path: '*' }); +} + +type WatchFactory = (path: string, listener: (event: string, filename: string | Buffer | null) => void) => FSWatcher; + +export function createRecoveringWorkspacePackageWatcher( + root: string, + notify: (path: string) => void, + watchFactory: WatchFactory = (path, listener) => watch(path, { recursive: true }, listener), + retryMs = 500, +) { + let watcher: FSWatcher | null = null; + let retry: ReturnType | null = null; + let stopped = false; + const scheduleRecovery = () => { + watcher?.close(); + watcher = null; + if (stopped || retry) return; + retry = setTimeout(() => { + retry = null; + attach(); + }, retryMs); + }; + const attach = () => { + if (stopped) return; + try { + watcher = watchFactory(dirname(root), (_event, filename) => { + if (filename) notify(resolve(dirname(root), String(filename))); + }); + watcher.once('error', scheduleRecovery); + notify(root); + } catch { + scheduleRecovery(); + } + }; + attach(); + return { + close() { + stopped = true; + if (retry) clearTimeout(retry); + retry = null; + watcher?.close(); + watcher = null; + }, + }; +} + +export function createWorkspacePackageReloadPlugin(projectRoot: string) { + return { + name: 'treeseed-workspace-package-reload', + configureServer(server: { + moduleGraph: { invalidateAll(): void }; + ws: { send(payload: { type: 'full-reload'; path: string }): void }; + httpServer?: { once(event: 'close', callback: () => void): void } | null; + }) { + const roots = workspacePackageOutputRoots(projectRoot); + if (!roots.length) return; + const coordinator = createWorkspacePackageReloadCoordinator( + roots, + () => reloadWorkspacePackageModules(server), + ); + const watchers = roots.map((root) => createRecoveringWorkspacePackageWatcher(root, coordinator.notify)); + const dispose = () => { + coordinator.dispose(); + watchers.forEach((watcher) => watcher.close()); + }; + server.httpServer?.once('close', dispose); + }, + }; +} diff --git a/src/styles/global.css b/src/styles/global.css index 4f359d51..cff01a7e 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -4,7 +4,9 @@ @import "@treeseed/ui/styles/theme.css"; @import "@treeseed/ui/styles/ui.css"; @import "@treeseed/ui/styles/forms.css"; +@import "@treeseed/ui/styles/knowledge.css"; @import "@treeseed/ui/styles/app-shell.css"; +@import "@treeseed/ui/styles/charts.css"; @import "./prose.css"; @theme { diff --git a/src/support/middleware.ts b/src/support/middleware.ts new file mode 100644 index 00000000..578585ca --- /dev/null +++ b/src/support/middleware.ts @@ -0,0 +1,10 @@ +import { defineMiddleware } from 'astro:middleware'; +import { resolveEditorialPreview } from '../middleware/editorial-preview.js'; +import { applyWebCacheHeaders } from '../utils/support/web-cache.js'; + +export const onRequest = defineMiddleware(async (context, next) => { + resolveEditorialPreview(context); + const response = await next(); + return applyWebCacheHeaders(context.request, context.url, response); +}); + diff --git a/src/platform-resources.ts b/src/support/platform-resources.ts similarity index 61% rename from src/platform-resources.ts rename to src/support/platform-resources.ts index 59d02fe6..1fddc6ea 100644 --- a/src/platform-resources.ts +++ b/src/support/platform-resources.ts @@ -1,15 +1,15 @@ import { existsSync } from 'node:fs'; import { resolve } from 'node:path'; import type { - TreeseedDeployConfig, - TreeseedPlatformLayerDefinition, - TreeseedPlatformResourceKind, - TreeseedPlatformSurfaceName, - TreeseedTenantConfig, -} from '@treeseed/sdk/platform/contracts'; -import type { LoadedTreeseedPluginEntry } from '@treeseed/sdk/platform/plugins'; - -export const TREESEED_PLATFORM_RESOURCE_KINDS = [ + DeployConfig, + PlatformLayerDefinition, + PlatformResourceKind, + PlatformSurfaceName, + TenantConfig, +} from '@treeseed/sdk/site-contracts/platform'; +import type { LoadedPluginRegistration } from '../runtime/platform/plugins.ts'; + +export const PLATFORM_RESOURCE_KINDS = [ 'pages', 'styles', 'components', @@ -19,34 +19,34 @@ export const TREESEED_PLATFORM_RESOURCE_KINDS = [ 'config', ] as const; -export const TREESEED_SITE_RESOURCE_KINDS = ['pages', 'styles', 'components'] as const; +export const SITE_RESOURCE_KINDS = ['pages', 'styles', 'components'] as const; -export type TreeseedSiteResourceKind = (typeof TREESEED_SITE_RESOURCE_KINDS)[number]; +export type SiteResourceKind = (typeof SITE_RESOURCE_KINDS)[number]; -export type TreeseedPlatformLayer = { +export type PlatformLayer = { owner: string; - surface: TreeseedPlatformSurfaceName; + surface: PlatformSurfaceName; root: string; - kinds: TreeseedPlatformResourceKind[]; + kinds: PlatformResourceKind[]; }; -export type TreeseedSiteLayerDefinition = TreeseedPlatformLayerDefinition & { - kinds?: TreeseedSiteResourceKind[]; +export type SiteLayerDefinition = PlatformLayerDefinition & { + kinds?: SiteResourceKind[]; }; -export type TreeseedSiteLayer = TreeseedPlatformLayer & { - kinds: TreeseedSiteResourceKind[]; +export type SiteLayer = PlatformLayer & { + kinds: SiteResourceKind[]; surface: 'web'; }; type PlatformLayerBuildContext = { projectRoot: string; - tenantConfig: TreeseedTenantConfig; + tenantConfig: TenantConfig; siteConfig?: unknown; - deployConfig?: TreeseedDeployConfig; + deployConfig?: DeployConfig; coreRoot: string; - surface: TreeseedPlatformSurfaceName; - defaultKinds: TreeseedPlatformResourceKind[]; + surface: PlatformSurfaceName; + defaultKinds: PlatformResourceKind[]; }; type PlatformLayerPluginContext = Omit & { @@ -55,20 +55,20 @@ type PlatformLayerPluginContext = Omit - | ((context: PlatformLayerPluginContext) => Array | undefined); + | Array + | ((context: PlatformLayerPluginContext) => Array | undefined); siteLayers?: - | TreeseedSiteLayerDefinition[] - | ((context: PlatformLayerPluginContext) => TreeseedSiteLayerDefinition[] | undefined); + | SiteLayerDefinition[] + | ((context: PlatformLayerPluginContext) => SiteLayerDefinition[] | undefined); }; type PlatformLayerRuntime = { - plugins: LoadedTreeseedPluginEntry[]; + plugins: LoadedPluginRegistration[]; }; -const PLATFORM_RESOURCE_KIND_SET = new Set(TREESEED_PLATFORM_RESOURCE_KINDS); +const PLATFORM_RESOURCE_KIND_SET = new Set(PLATFORM_RESOURCE_KINDS); -function normalizeKinds(kinds: TreeseedPlatformResourceKind[] | undefined, fallbackKinds: TreeseedPlatformResourceKind[]) { +function normalizeKinds(kinds: PlatformResourceKind[] | undefined, fallbackKinds: PlatformResourceKind[]) { const normalized = kinds?.length ? [...new Set(kinds)] : [...fallbackKinds]; for (const kind of normalized) { if (!PLATFORM_RESOURCE_KIND_SET.has(kind)) { @@ -78,25 +78,25 @@ function normalizeKinds(kinds: TreeseedPlatformResourceKind[] | undefined, fallb return normalized; } -function normalizeResourcePath(kind: TreeseedPlatformResourceKind, resourcePath: string) { +function normalizeResourcePath(kind: PlatformResourceKind, resourcePath: string) { const normalized = resourcePath.replace(/\\/g, '/').replace(/^\/+/, ''); return normalized.startsWith(`${kind}/`) ? normalized : `${kind}/${normalized}`; } -function isKindSupported(layer: TreeseedPlatformLayer, kind: TreeseedPlatformResourceKind) { +function isKindSupported(layer: PlatformLayer, kind: PlatformResourceKind) { return layer.kinds.includes(kind); } -function getTenantRoot(tenantConfig: TreeseedTenantConfig, projectRoot: string) { - return (tenantConfig as TreeseedTenantConfig & { __tenantRoot?: string }).__tenantRoot ?? projectRoot; +function getTenantRoot(tenantConfig: TenantConfig, projectRoot: string) { + return (tenantConfig as TenantConfig & { __tenantRoot?: string }).__tenantRoot ?? projectRoot; } function normalizeLayerDefinition( owner: string, baseRoot: string, - layer: TreeseedPlatformLayerDefinition, + layer: PlatformLayerDefinition, context: Pick, -): TreeseedPlatformLayer { +): PlatformLayer { return { owner, surface: context.surface, @@ -106,9 +106,9 @@ function normalizeLayerDefinition( } function getPluginLayers( - entry: LoadedTreeseedPluginEntry, + entry: LoadedPluginRegistration, context: PlatformLayerBuildContext, -): TreeseedPlatformLayer[] { +): PlatformLayer[] { const plugin = entry.plugin as PlatformLayerContributingPlugin; const platformLayers = typeof plugin.platformLayers === 'function' @@ -139,15 +139,15 @@ function getTenantLayers(context: PlatformLayerBuildContext) { const legacyWebLayers = context.surface === 'web' ? [ context.tenantConfig.overrides?.pagesRoot - ? { root: context.tenantConfig.overrides.pagesRoot, kinds: ['pages'] as TreeseedPlatformResourceKind[] } + ? { root: context.tenantConfig.overrides.pagesRoot, kinds: ['pages'] as PlatformResourceKind[] } : null, context.tenantConfig.overrides?.stylesRoot - ? { root: context.tenantConfig.overrides.stylesRoot, kinds: ['styles'] as TreeseedPlatformResourceKind[] } + ? { root: context.tenantConfig.overrides.stylesRoot, kinds: ['styles'] as PlatformResourceKind[] } : null, context.tenantConfig.overrides?.componentsRoot - ? { root: context.tenantConfig.overrides.componentsRoot, kinds: ['components'] as TreeseedPlatformResourceKind[] } + ? { root: context.tenantConfig.overrides.componentsRoot, kinds: ['components'] as PlatformResourceKind[] } : null, - ].filter(Boolean) as TreeseedPlatformLayerDefinition[] + ].filter(Boolean) as PlatformLayerDefinition[] : []; return [...(surfaceOverrides?.layers ?? []), ...legacyWebLayers].map((layer) => @@ -155,11 +155,11 @@ function getTenantLayers(context: PlatformLayerBuildContext) { ); } -export function buildTreeseedPlatformLayers( +export function buildPlatformLayers( pluginRuntime: PlatformLayerRuntime, context: PlatformLayerBuildContext, ) { - const layers: TreeseedPlatformLayer[] = [ + const layers: PlatformLayer[] = [ { owner: '@treeseed/core', surface: context.surface, @@ -176,9 +176,9 @@ export function buildTreeseedPlatformLayers( return layers; } -export function resolveTreeseedPlatformResource( - layers: TreeseedPlatformLayer[], - kind: TreeseedPlatformResourceKind, +export function resolvePlatformResource( + layers: PlatformLayer[], + kind: PlatformResourceKind, resourcePath: string, ) { const normalizedPath = normalizeResourcePath(kind, resourcePath); @@ -198,26 +198,26 @@ export function resolveTreeseedPlatformResource( return null; } -export function buildTreeseedSiteLayers( +export function buildSiteLayers( pluginRuntime: PlatformLayerRuntime, context: Omit, ) { - return buildTreeseedPlatformLayers(pluginRuntime, { + return buildPlatformLayers(pluginRuntime, { ...context, surface: 'web', - defaultKinds: [...TREESEED_SITE_RESOURCE_KINDS], - }) as TreeseedSiteLayer[]; + defaultKinds: [...SITE_RESOURCE_KINDS], + }) as SiteLayer[]; } -export function resolveTreeseedSiteResource( - layers: TreeseedSiteLayer[], - kind: TreeseedSiteResourceKind, +export function resolveSiteResource( + layers: SiteLayer[], + kind: SiteResourceKind, resourcePath: string, ) { - return resolveTreeseedPlatformResource(layers, kind, resourcePath); + return resolvePlatformResource(layers, kind, resourcePath); } -export function resolveTreeseedPageEntrypoint(layers: TreeseedSiteLayer[], resourcePath: string) { +export function resolvePageEntrypoint(layers: SiteLayer[], resourcePath: string) { const hasExplicitExtension = /\.[A-Za-z0-9]+$/u.test(resourcePath); const compiledCandidates = hasExplicitExtension && /\.[cm]?tsx?$/u.test(resourcePath) ? [ @@ -230,7 +230,7 @@ export function resolveTreeseedPageEntrypoint(layers: TreeseedSiteLayer[], resou : [resourcePath, `${resourcePath}.astro`, `${resourcePath}.ts`, `${resourcePath}.js`, `${resourcePath}.mjs`]; for (const candidate of candidates) { - const resolved = resolveTreeseedSiteResource(layers, 'pages', candidate); + const resolved = resolveSiteResource(layers, 'pages', candidate); if (resolved) { return resolved; } @@ -240,8 +240,8 @@ export function resolveTreeseedPageEntrypoint(layers: TreeseedSiteLayer[], resou throw new Error(`Unable to resolve Treeseed page resource "${normalized}".`); } -export function resolveTreeseedStyleEntrypoint(layers: TreeseedSiteLayer[], resourcePath: string) { - const resolved = resolveTreeseedSiteResource(layers, 'styles', resourcePath); +export function resolveStyleEntrypoint(layers: SiteLayer[], resourcePath: string) { + const resolved = resolveSiteResource(layers, 'styles', resourcePath); if (!resolved) { throw new Error(`Unable to resolve Treeseed style resource "${normalizeResourcePath('styles', resourcePath)}".`); } diff --git a/src/support/platform.ts b/src/support/platform.ts new file mode 100644 index 00000000..da7cab29 --- /dev/null +++ b/src/support/platform.ts @@ -0,0 +1,17 @@ +export { + buildPlatformLayers, + resolvePlatformResource, + PLATFORM_RESOURCE_KINDS, + type PlatformLayer, +} from './platform-resources'; +export { + createIntegratedDevPlan, + runManagedDev, + runIntegratedDev, + type IntegratedDevCommand, + type IntegratedDevOptions, + type IntegratedDevPlan, + type IntegratedDevSurface, + type ManagedDevOptions, + type DevInstanceRecord, +} from '../runtime/dev'; diff --git a/src/support/plugin-default.ts b/src/support/plugin-default.ts new file mode 100644 index 00000000..33e5b3ab --- /dev/null +++ b/src/support/plugin-default.ts @@ -0,0 +1 @@ +export { default } from '../runtime/platform/plugin-default.ts'; diff --git a/src/support/routes.ts b/src/support/routes.ts new file mode 100644 index 00000000..6cb9d72c --- /dev/null +++ b/src/support/routes.ts @@ -0,0 +1,55 @@ +import { defineRoute, validateRouteCapabilities, type RouteCapability, type SiteRouteContribution } from '@treeseed/sdk/site-contracts/plugin'; + +type CapabilityInput = Pick & Partial; + +function coreRoute(pattern: string, resourcePath: string, input: CapabilityInput): SiteRouteContribution { + const detail = pattern.includes('['); + const reader = pattern.startsWith('/books') || pattern.includes('/books/'); + return defineRoute({ pattern, resourcePath, capability: { + owner: 'core', + responseKind: 'page', + archetype: reader ? 'reader' : detail ? 'detail' : 'collection', + shell: reader ? 'CoreReaderLayout' : 'CoreContentLayout', + template: reader ? 'StarlightPage' : detail ? 'DetailTemplate' : 'CollectionTemplate', + surface: 'content', + resourceType: 'content-page', + accessPolicy: ['public read'], + viewModelDependencies: ['Core content runtime'], + navigation: pattern === '/404' || pattern === '/ui' ? 'hidden' : 'primary', + states: ['loading', 'empty', 'unavailable', 'not-found', 'success'], + selector: `route-${input.id.replaceAll('.', '-')}`, + status: 'active', + guarantees: [], + ...input, + } }); +} + +export const CORE_ROUTES: readonly SiteRouteContribution[] = validateRouteCapabilities([ + coreRoute('/', 'pages/index.astro', { id: 'core.home', description: 'Core homepage composed from enabled public content collections.', archetype: 'dashboard', template: 'DashboardTemplate' }), + coreRoute('/404', 'pages/404.astro', { id: 'core.not-found', description: 'Explicit public not-found and recovery page.', navigation: 'hidden' }), + coreRoute('/[slug]', 'pages/[slug].astro', { id: 'core.page.detail', description: 'Generic top-level content-page resolver.' }), + coreRoute('/agents', 'pages/agents/index.astro', { id: 'core.agent.collection', description: 'Public software-agent directory.', resourceType: 'agent' }), + coreRoute('/agents/[slug]', 'pages/agents/[slug].astro', { id: 'core.agent.detail', description: 'Public software-agent profile.', resourceType: 'agent' }), + coreRoute('/books', 'pages/books/index.astro', { id: 'core.book.collection', description: 'Authorized federated book catalog.', resourceType: 'book', accessPolicy: ['public books', 'authorized authenticated books'], guarantees: ['guarantee.project.book.search-books.068', 'guarantee.book.knowledge.production-readiness.567'] }), + coreRoute('/t/[teamSlug]/books/[bookSlug]', 'pages/t/[teamSlug]/books/[bookSlug]/index.astro', { id: 'core.book.detail', description: 'Canonical team book overview and ordered contents.', resourceType: 'book', accessPolicy: ['public book', 'authorized authenticated book'], guarantees: ['guarantee.project.book.search-books.068', 'guarantee.book.knowledge.production-readiness.567'] }), + coreRoute('/t/[teamSlug]/books/[bookSlug]/[...pageSlug]', 'pages/t/[teamSlug]/books/[bookSlug]/[...pageSlug].astro', { id: 'core.knowledge.reader', description: 'Canonical Starlight knowledge-page reader.', resourceType: 'knowledge-page', accessPolicy: ['public page', 'authorized authenticated page'], guarantees: ['guarantee.project.knowledge.review-backlinks.091', 'guarantee.book.knowledge.production-readiness.567'] }), + coreRoute('/contact', 'pages/contact.astro', { id: 'core.contact', description: 'Public contact and feedback form.', archetype: 'auth-form', template: 'FormTemplate', resourceType: 'contact' }), + coreRoute('/decisions', 'pages/decisions/index.astro', { id: 'core.decision.collection', description: 'Public decision index.', resourceType: 'decision' }), + coreRoute('/decisions/[slug]', 'pages/decisions/[slug].astro', { id: 'core.decision.detail', description: 'Public decision detail and related content.', resourceType: 'decision' }), + coreRoute('/notes', 'pages/notes/index.astro', { id: 'core.note.collection', description: 'Public working-note index.', resourceType: 'note' }), + coreRoute('/notes/[slug]', 'pages/notes/[slug].astro', { id: 'core.note.detail', description: 'Public working-note detail.', resourceType: 'note' }), + coreRoute('/objectives', 'pages/objectives/index.astro', { id: 'core.objective.collection', description: 'Public objective index.', resourceType: 'objective' }), + coreRoute('/objectives/[slug]', 'pages/objectives/[slug].astro', { id: 'core.objective.detail', description: 'Public objective detail.', resourceType: 'objective' }), + coreRoute('/people', 'pages/people/index.astro', { id: 'core.person.collection', description: 'Public contributor directory.', resourceType: 'person' }), + coreRoute('/people/[slug]', 'pages/people/[slug].astro', { id: 'core.person.detail', description: 'Public contributor profile.', resourceType: 'person' }), + coreRoute('/proposals', 'pages/proposals/index.astro', { id: 'core.proposal.collection', description: 'Public proposal index.', resourceType: 'proposal' }), + coreRoute('/proposals/[slug]', 'pages/proposals/[slug].astro', { id: 'core.proposal.detail', description: 'Public proposal detail.', resourceType: 'proposal' }), + coreRoute('/questions', 'pages/questions/index.astro', { id: 'core.question.collection', description: 'Public research-question index.', resourceType: 'question' }), + coreRoute('/questions/[slug]', 'pages/questions/[slug].astro', { id: 'core.question.detail', description: 'Public research-question detail.', resourceType: 'question' }), + coreRoute('/ui', 'pages/ui/index.astro', { id: 'core.ui.catalog', description: 'Public shared-component inspection catalog.', resourceType: 'ui-catalog', navigation: 'hidden' }), +]); + +export const CORE_SUPPORT_ROUTES: readonly SiteRouteContribution[] = validateRouteCapabilities([ + defineRoute({ pattern: '/api/form/submit', resourcePath: 'pages/api/form/submit.ts', capability: { owner: 'core', id: 'core.support.form', description: 'Configured generic form submission handler.', responseKind: 'action', archetype: 'action', shell: 'Standalone', template: 'Standalone', surface: 'system', resourceType: 'form-handler', accessPolicy: ['configured form policy'], viewModelDependencies: ['Core form runtime'], navigation: 'hidden', states: ['validation', 'forbidden', 'retry', 'success'], selector: 'route-core-support-form', status: 'active', guarantees: [] } }), + defineRoute({ pattern: '/feed.xml', resourcePath: 'pages/feed.xml.ts', capability: { owner: 'core', id: 'core.support.feed', description: 'Generated public content feed.', responseKind: 'feed', archetype: 'feed', shell: 'Standalone', template: 'Standalone', surface: 'content', resourceType: 'content-feed', accessPolicy: ['public read'], viewModelDependencies: ['Core content runtime'], navigation: 'hidden', states: ['success', 'unavailable'], selector: 'route-core-support-feed', status: 'active', guarantees: [] } }), +]); diff --git a/src/support/site-resources.ts b/src/support/site-resources.ts new file mode 100644 index 00000000..581bc6ee --- /dev/null +++ b/src/support/site-resources.ts @@ -0,0 +1,10 @@ +export { + buildSiteLayers, + resolvePageEntrypoint, + resolveSiteResource, + resolveStyleEntrypoint, + SITE_RESOURCE_KINDS, + type SiteLayer, + type SiteLayerDefinition, + type SiteResourceKind, +} from './platform-resources'; diff --git a/src/support/site.ts b/src/support/site.ts new file mode 100644 index 00000000..c5edaf83 --- /dev/null +++ b/src/support/site.ts @@ -0,0 +1,3 @@ +export * from '../site/tenant-theme-virtual-id.ts'; +export * from '../site/resolve-site-plugin-extensions.ts'; +export * from '../site/create-site.ts'; diff --git a/src/templates.ts b/src/support/templates.ts similarity index 90% rename from src/templates.ts rename to src/support/templates.ts index 20d46b89..d9120ca9 100644 --- a/src/templates.ts +++ b/src/support/templates.ts @@ -1,9 +1,7 @@ import { getCollection } from 'astro:content'; -import { normalizeTemplateLaunchRequirements } from '@treeseed/sdk/template-launch-requirements'; -import type { TemplateLaunchRequirements } from '@treeseed/sdk/template-launch-requirements'; -import type { CatalogItem, CommerceOfferMode } from '@treeseed/sdk/types'; -import { RUNTIME_TENANT } from './tenant/runtime-config.ts'; -import { siteModelRendered } from './utils/site-models.ts'; +import type { CatalogItem, CommerceOfferMode } from '@treeseed/sdk/site-contracts/catalog'; +import { RUNTIME_TENANT } from '../tenant/runtime-config.ts'; +import { siteModelRendered } from '../utils/support/site-models.ts'; export interface TemplateContentEntry { id: string; @@ -68,7 +66,6 @@ export interface TemplateSiteCard { templateVersion?: string; priceModel?: CommerceOfferMode; source: 'catalog' | 'content'; - launchRequirements?: TemplateLaunchRequirements; } export interface TemplateSiteDetail extends TemplateSiteCard { @@ -142,10 +139,6 @@ function catalogNumber(metadata: Record | undefined, key: strin return typeof value === 'number' ? value : undefined; } -function catalogLaunchRequirements(metadata: Record | undefined, label: string) { - return normalizeTemplateLaunchRequirements(metadata?.launchRequirements, `${label} launchRequirements`); -} - function contentCardFromEntry(entry: TemplateContentEntry): TemplateSiteCard | null { if (entry.data.status !== 'live') { return null; @@ -161,7 +154,6 @@ function contentCardFromEntry(entry: TemplateContentEntry): TemplateSiteCard | n templateVersion: entry.data.templateVersion, priceModel: entry.data.offer?.priceModel, source: 'content', - launchRequirements: undefined, }; } @@ -212,7 +204,6 @@ function cardFromCatalogItem(item: CatalogItem): TemplateSiteCard { templateVersion: catalogString(item.metadata, 'templateVersion'), priceModel: item.offerMode, source: 'catalog', - launchRequirements: catalogLaunchRequirements(item.metadata, `catalog item ${item.slug}`), }; } @@ -276,12 +267,7 @@ export async function listSiteTemplates(context: TemplateSourceOptions = {}): Pr for (const item of catalogItems) { const card = cardFromCatalogItem(item); const existing = cardsBySourceRef.get(card.sourceRef); - if (existing) { - cardsBySourceRef.set(card.sourceRef, { - ...existing, - launchRequirements: existing.launchRequirements ?? card.launchRequirements, - }); - } else { + if (!existing) { cardsBySourceRef.set(card.sourceRef, card); } } diff --git a/src/tenant/bridge.ts b/src/tenant/bridge.ts index df626770..7528bd84 100644 --- a/src/tenant/bridge.ts +++ b/src/tenant/bridge.ts @@ -1,3 +1,3 @@ -import { loadTreeseedManifest } from '@treeseed/sdk/platform/tenant-config'; +import { loadManifest } from '../runtime/platform/tenant-config.ts'; -export const PROJECT_TENANT = loadTreeseedManifest(); +export const PROJECT_TENANT = loadManifest(); diff --git a/src/tenant/runtime-config.ts b/src/tenant/runtime-config.ts index 888fb18d..c2fbe80b 100644 --- a/src/tenant/runtime-config.ts +++ b/src/tenant/runtime-config.ts @@ -1,16 +1,16 @@ -import type { TreeseedTenantConfig } from '@treeseed/sdk/platform/contracts'; -import { parseSiteConfig } from '../utils/site-config-schema.js'; +import type { TenantConfig } from '@treeseed/sdk/site-contracts/platform'; +import { parseSiteConfig } from '../utils/configuration/site-config-schema.js'; -declare const __TREESEED_TENANT_CONFIG__: TreeseedTenantConfig | undefined; -declare const __TREESEED_PROJECT_ROOT__: string | undefined; -declare const __TREESEED_SITE_CONFIG__: ReturnType | undefined; +declare const TENANT_CONFIG: TenantConfig | undefined; +declare const PROJECT_ROOT: string | undefined; +declare const SITE_CONFIG: ReturnType | undefined; const injectedTenantConfig = - typeof __TREESEED_TENANT_CONFIG__ !== 'undefined' ? __TREESEED_TENANT_CONFIG__ : null; + typeof TENANT_CONFIG !== 'undefined' ? TENANT_CONFIG : null; const injectedProjectRoot = - typeof __TREESEED_PROJECT_ROOT__ !== 'undefined' ? __TREESEED_PROJECT_ROOT__ : null; + typeof PROJECT_ROOT !== 'undefined' ? PROJECT_ROOT : null; const injectedSiteConfig = - typeof __TREESEED_SITE_CONFIG__ !== 'undefined' ? __TREESEED_SITE_CONFIG__ : null; + typeof SITE_CONFIG !== 'undefined' ? SITE_CONFIG : null; function getNodeBuiltin(name: string): T | null { const getBuiltinModule = (globalThis as { process?: { getBuiltinModule?: (name: string) => T } }).process @@ -33,7 +33,7 @@ function resolveRuntimePath(projectRoot: string, path: string) { export const RUNTIME_PROJECT_ROOT = injectedProjectRoot ?? getCwd(); -function fallbackTenantConfig(projectRoot: string): TreeseedTenantConfig { +function fallbackTenantConfig(projectRoot: string): TenantConfig { return { id: 'treeseed-runtime', siteConfigPath: resolveRuntimePath(projectRoot, 'treeseed.site.yaml'), @@ -46,10 +46,12 @@ function fallbackTenantConfig(projectRoot: string): TreeseedTenantConfig { decisions: resolveRuntimePath(projectRoot, 'src/content/decisions'), people: resolveRuntimePath(projectRoot, 'src/content/people'), agents: resolveRuntimePath(projectRoot, 'src/content/agents'), + discussions: resolveRuntimePath(projectRoot, 'src/content/discussions'), + discussion_messages: resolveRuntimePath(projectRoot, 'src/content/discussion-messages'), + discussion_events: resolveRuntimePath(projectRoot, 'src/content/discussion-events'), books: resolveRuntimePath(projectRoot, 'src/content/books'), docs: resolveRuntimePath(projectRoot, 'src/content/knowledge'), templates: resolveRuntimePath(projectRoot, 'src/content/templates'), - knowledge_packs: resolveRuntimePath(projectRoot, 'src/content/knowledge-packs'), workdays: resolveRuntimePath(projectRoot, 'src/content/workdays'), }, features: { diff --git a/src/types/astro-build.d.ts b/src/types/astro-build.d.ts index d18a36fb..38ef43a4 100644 --- a/src/types/astro-build.d.ts +++ b/src/types/astro-build.d.ts @@ -14,7 +14,8 @@ declare module 'astro:env/server' { declare namespace App { interface Locals { starlightRoute?: any; - runtime?: import('@treeseed/sdk/types/cloudflare').CloudflareRuntime; - contentPreview?: import('@treeseed/sdk/platform/published-content').EditorialPreviewTokenPayload | null; + runtime?: import('@treeseed/sdk/site-contracts/cloudflare').CloudflareRuntime; + contentPreview?: import('../runtime/platform/published-content.ts').EditorialPreviewTokenPayload | null; + auth?: { principal: import('@treeseed/sdk/site-contracts/catalog').ApiPrincipal } | null; } } diff --git a/src/types/cloudflare.ts b/src/types/cloudflare.ts index 423bdf51..f0d9064f 100644 --- a/src/types/cloudflare.ts +++ b/src/types/cloudflare.ts @@ -1 +1 @@ -export * from '@treeseed/sdk/types/cloudflare'; +export * from '@treeseed/sdk/site-contracts/cloudflare'; diff --git a/src/utils/color-schemes/shared.ts b/src/utils/color-schemes/shared.ts index e8a2ff71..4d28a672 100644 --- a/src/utils/color-schemes/shared.ts +++ b/src/utils/color-schemes/shared.ts @@ -1,10 +1,10 @@ import type { - TreeseedColorSchemeId, - TreeseedSchemeTokens, - TreeseedSemanticColorTokens, -} from '@treeseed/sdk/platform/contracts'; + ColorSchemeId, + SchemeTokens, + SemanticColorTokens, +} from '@treeseed/sdk/site-contracts/platform'; -export type RequiredSchemeTokenInput = Pick; export type BuiltInColorSchemeDefinition = { - id: TreeseedColorSchemeId; + id: ColorSchemeId; name: string; swatches: string[]; modeSwatches: { light: string[]; dark: string[]; }; - tokens: TreeseedSchemeTokens; + tokens: SchemeTokens; }; export function completeTokens( tokens: RequiredSchemeTokenInput, mode: 'light' | 'dark', -): TreeseedSemanticColorTokens { +): SemanticColorTokens { return { ...tokens, surfaceOverlay: mode === 'dark' ? 'rgba(7, 12, 8, 0.72)' : 'rgba(255, 255, 255, 0.88)', diff --git a/src/utils/configuration/site-config-schema.ts b/src/utils/configuration/site-config-schema.ts new file mode 100644 index 00000000..04be897c --- /dev/null +++ b/src/utils/configuration/site-config-schema.ts @@ -0,0 +1,2 @@ +export * from './site-config-schema/is-record.ts'; +export * from './site-config-schema/parse-access-policies.ts'; diff --git a/src/utils/configuration/site-config-schema/is-record.ts b/src/utils/configuration/site-config-schema/is-record.ts new file mode 100644 index 00000000..51e3bc69 --- /dev/null +++ b/src/utils/configuration/site-config-schema/is-record.ts @@ -0,0 +1,256 @@ +// @ts-nocheck + + +/** @typedef {import('../../../entrypoints/models/field-aliases.ts').FieldAliasRegistry} FieldAliasRegistry */ + +export function isRecord(value) { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +export function expectRecord(value, path) { + if (!isRecord(value)) { + throw new Error(`Expected ${path} to be an object.`); + } + + return value; +} + +export function expectString(value, path) { + if (typeof value !== 'string' || value.trim().length === 0) { + throw new Error(`Expected ${path} to be a non-empty string.`); + } + + return value.trim(); +} + +export function optionalString(value, path) { + if (value === undefined || value === null || value === '') { + return undefined; + } + + return expectString(value, path); +} + +export function optionalBoolean(value, path) { + if (value === undefined || value === null) { + return undefined; + } + + if (typeof value !== 'boolean') { + throw new Error(`Expected ${path} to be a boolean.`); + } + + return value; +} + +export function optionalRecord(value, path) { + if (value === undefined || value === null) { + return undefined; + } + + return expectRecord(value, path); +} + +export function optionalEnum(value, path, allowed) { + if (value === undefined || value === null || value === '') { + return undefined; + } + + const parsedValue = expectString(value, path); + if (!allowed.includes(parsedValue)) { + throw new Error(`Expected ${path} to be one of: ${allowed.join(', ')}.`); + } + + return parsedValue; +} + +export function stringArray(value, path) { + if (value === undefined || value === null) { + return []; + } + + if (!Array.isArray(value)) { + throw new Error(`Expected ${path} to be an array.`); + } + + return value.map((entry, index) => expectString(entry, `${path}[${index}]`)); +} + +export function parseMenuGroups(value, path) { + if (!Array.isArray(value)) { + throw new Error(`Expected ${path} to be an array.`); + } + + return value.map((group, groupIndex) => { + const parsedGroup = expectRecord(group, `${path}[${groupIndex}]`); + const items = parsedGroup.items; + if (!Array.isArray(items) || items.length === 0) { + throw new Error(`Expected ${path}[${groupIndex}].items to contain at least one menu item.`); + } + + return { + label: expectString(parsedGroup.label, `${path}[${groupIndex}].label`), + items: items.map((item, itemIndex) => { + const parsedItem = expectRecord(item, `${path}[${groupIndex}].items[${itemIndex}]`); + return { + label: expectString(parsedItem.label, `${path}[${groupIndex}].items[${itemIndex}].label`), + href: expectString(parsedItem.href, `${path}[${groupIndex}].items[${itemIndex}].href`), + }; + }), + }; + }); +} + +export function parseContactRouting(value, path) { + const parsedValue = expectRecord(value ?? {}, path); + const keys = ['default', 'question', 'feedback', 'collaboration', 'issue']; + + return Object.fromEntries( + keys.flatMap((key) => { + if (!(key in parsedValue)) { + return []; + } + + return [[key, stringArray(parsedValue[key], `${path}.${key}`)]]; + }), + ); +} + +export const BUILT_IN_THEME_SCHEMES = new Set(['fern', 'lichen', 'cedar', 'tidepool']); + +export const THEME_TOKEN_NAMES = new Set([ + 'canvas', + 'canvasSubtle', + 'surface', + 'surfaceMuted', + 'surfaceRaised', + 'surfaceOverlay', + 'text', + 'textMuted', + 'textSubtle', + 'textInverse', + 'link', + 'linkHover', + 'border', + 'borderMuted', + 'borderStrong', + 'focus', + 'accent', + 'accentHover', + 'accentStrong', + 'accentSoft', + 'accentText', + 'info', + 'infoSoft', + 'infoText', + 'infoBorder', + 'success', + 'successSoft', + 'successText', + 'successBorder', + 'warning', + 'warningSoft', + 'warningText', + 'warningBorder', + 'danger', + 'dangerSoft', + 'dangerText', + 'dangerBorder', + 'shadow', + 'grid', +]); + +export function parseThemeSchemeId(value, path) { + const schemeId = expectString(value, path); + if (!/^[a-z][a-z0-9-]*$/u.test(schemeId)) { + throw new Error(`Expected ${path} to be a stable lowercase slug.`); + } + return schemeId; +} + +export function parseThemeTokenOverrides(value, path) { + const record = optionalRecord(value, path); + if (!record) { + return undefined; + } + + return Object.fromEntries( + Object.entries(record).map(([tokenName, tokenValue]) => { + if (!THEME_TOKEN_NAMES.has(tokenName)) { + throw new Error(`Unknown theme token ${path}.${tokenName}.`); + } + return [tokenName, expectString(tokenValue, `${path}.${tokenName}`)]; + }), + ); +} + +export function parseThemeScheme(value, path) { + const scheme = expectRecord(value, path); + const allowedKeys = new Set(['light', 'dark']); + for (const key of Object.keys(scheme)) { + if (!allowedKeys.has(key)) { + throw new Error(`Unknown theme scheme key ${path}.${key}.`); + } + } + return { + light: parseThemeTokenOverrides(scheme.light, `${path}.light`), + dark: parseThemeTokenOverrides(scheme.dark, `${path}.dark`), + }; +} + +export function parseTheme(value, path) { + const theme = optionalRecord(value, path); + if (!theme) { + return undefined; + } + + const allowedKeys = new Set(['defaultScheme', 'defaultMode', 'schemes']); + for (const key of Object.keys(theme)) { + if (!allowedKeys.has(key)) { + throw new Error(`Unknown theme key ${path}.${key}.`); + } + } + + const schemes = optionalRecord(theme.schemes, `${path}.schemes`); + const parsedSchemes = schemes + ? Object.fromEntries( + Object.entries(schemes).map(([schemeId, scheme]) => [ + parseThemeSchemeId(schemeId, `${path}.schemes.${schemeId}`), + parseThemeScheme(scheme, `${path}.schemes.${schemeId}`), + ]), + ) + : undefined; + const defaultScheme = optionalString(theme.defaultScheme, `${path}.defaultScheme`); + if (defaultScheme) { + parseThemeSchemeId(defaultScheme, `${path}.defaultScheme`); + if (!BUILT_IN_THEME_SCHEMES.has(defaultScheme) && !(parsedSchemes && defaultScheme in parsedSchemes)) { + throw new Error(`Expected ${path}.defaultScheme to reference a built-in or configured scheme.`); + } + } + + return { + defaultScheme, + defaultMode: optionalEnum(theme.defaultMode, `${path}.defaultMode`, ['light', 'dark', 'system']), + schemes: parsedSchemes, + }; +} + +export function parseAccessRoles(value, path) { + const record = optionalRecord(value, path); + if (!record) { + return {}; + } + + return Object.fromEntries( + Object.entries(record).map(([roleId, rawRole]) => { + const parsedRole = expectRecord(rawRole, `${path}.${roleId}`); + return [ + roleId, + { + grants: stringArray(parsedRole.grants, `${path}.${roleId}.grants`), + }, + ]; + }), + ); +} + diff --git a/src/utils/configuration/site-config-schema/parse-access-policies.ts b/src/utils/configuration/site-config-schema/parse-access-policies.ts new file mode 100644 index 00000000..d6285582 --- /dev/null +++ b/src/utils/configuration/site-config-schema/parse-access-policies.ts @@ -0,0 +1,261 @@ +// @ts-nocheck +import { parse as parseYaml } from 'yaml'; +import { normalizeAliasedRecord } from '@treeseed/sdk/field-aliases'; +import { expectRecord,expectString,optionalBoolean,optionalRecord,optionalString,parseAccessRoles,parseContactRouting,parseMenuGroups,parseTheme,stringArray } from './is-record.ts'; + +export function parseAccessPolicies(value, path) { + const record = optionalRecord(value, path); + if (!record) { + return {}; + } + + return Object.fromEntries( + Object.entries(record).map(([policyId, rawPolicy]) => { + const parsedPolicy = expectRecord(rawPolicy, `${path}.${policyId}`); + return [ + policyId, + { + audience: optionalString(parsedPolicy.audience, `${path}.${policyId}.audience`), + entitlement: optionalString(parsedPolicy.entitlement, `${path}.${policyId}.entitlement`), + offer: optionalString(parsedPolicy.offer, `${path}.${policyId}.offer`), + visibility: optionalString(parsedPolicy.visibility, `${path}.${policyId}.visibility`), + }, + ]; + }), + ); +} + +export function parseAccessDefaults(value, path) { + const record = optionalRecord(value, path); + if (!record) { + return { models: {} }; + } + + const models = optionalRecord(record.models, `${path}.models`) ?? {}; + return { + models: Object.fromEntries( + Object.entries(models).map(([modelId, rawSurfaces]) => { + const parsedSurfaces = expectRecord(rawSurfaces, `${path}.models.${modelId}`); + return [ + modelId, + Object.fromEntries( + Object.entries(parsedSurfaces).map(([surfaceId, rawPolicy]) => [ + surfaceId, + expectString(rawPolicy, `${path}.models.${modelId}.${surfaceId}`), + ]), + ), + ]; + }), + ), + }; +} + +export function parseAccessBootstrap(value, path) { + const record = optionalRecord(value, path); + if (!record) { + return {}; + } + + const owners = optionalRecord(record.owners, `${path}.owners`); + return { + owners: owners + ? { + emails: stringArray(owners.emails, `${path}.owners.emails`), + roles: stringArray(owners.roles, `${path}.owners.roles`), + } + : undefined, + }; +} + +export function parseAccess(value, path) { + const record = optionalRecord(value, path); + if (!record) { + return { + roles: {}, + policies: {}, + defaults: { models: {} }, + bootstrap: {}, + }; + } + + return { + roles: parseAccessRoles(record.roles, `${path}.roles`), + policies: parseAccessPolicies(record.policies, `${path}.policies`), + defaults: parseAccessDefaults(record.defaults, `${path}.defaults`), + bootstrap: parseAccessBootstrap(record.bootstrap, `${path}.bootstrap`), + }; +} + +/** @type {TreeseedFieldAliasRegistry} */ +export const siteFieldAliases = { + siteUrl: { key: 'siteUrl', aliases: ['site_url'] }, + githubRepository: { key: 'githubRepository', aliases: ['github_repository'] }, + discordLink: { key: 'discordLink', aliases: ['discord_link'] }, + headerMenu: { key: 'headerMenu', aliases: ['header_menu'] }, + footerMenu: { key: 'footerMenu', aliases: ['footer_menu'] }, + emailNotifications: { key: 'emailNotifications', aliases: ['email_notifications'] }, + projectStage: { key: 'projectStage', aliases: ['project_stage'] }, + projectStageDetail: { key: 'projectStageDetail', aliases: ['project_stage_detail'] }, +}; + +/** @type {TreeseedFieldAliasRegistry} */ +export const pageDefaultsFieldAliases = { + pageLayout: { key: 'pageLayout', aliases: ['page_layout'] }, +}; + +/** @type {TreeseedFieldAliasRegistry} */ +export const agentDefaultsFieldAliases = { + runtimeStatus: { key: 'runtimeStatus', aliases: ['runtime_status'] }, +}; + +/** @type {TreeseedFieldAliasRegistry} */ +export const formsFieldAliases = { + apiBaseUrl: { key: 'apiBaseUrl', aliases: ['api_base_url'] }, +}; + +/** @type {TreeseedFieldAliasRegistry} */ +export const emailNotificationFieldAliases = { + contactRouting: { key: 'contactRouting', aliases: ['contact_routing'] }, + subscribeRecipients: { key: 'subscribeRecipients', aliases: ['subscribe_recipients'] }, +}; + +/** + * @param {string} source + */ +export function parseSiteConfig(source) { + const parsed = expectRecord(parseYaml(source), 'config'); + const site = normalizeAliasedRecord(siteFieldAliases, expectRecord(parsed.site, 'site')); + const models = expectRecord(parsed.models ?? {}, 'models'); + const pageModel = expectRecord(models.pages ?? {}, 'models.pages'); + const noteModel = expectRecord(models.notes ?? {}, 'models.notes'); + const questionModel = expectRecord(models.questions ?? {}, 'models.questions'); + const objectiveModel = expectRecord(models.objectives ?? {}, 'models.objectives'); + const proposalModel = expectRecord(models.proposals ?? {}, 'models.proposals'); + const decisionModel = expectRecord(models.decisions ?? {}, 'models.decisions'); + const peopleModel = expectRecord(models.people ?? {}, 'models.people'); + const agentModel = expectRecord(models.agents ?? {}, 'models.agents'); + const bookModel = expectRecord(models.books ?? {}, 'models.books'); + const docsModel = expectRecord(models.docs ?? {}, 'models.docs'); + const pageDefaults = normalizeAliasedRecord(pageDefaultsFieldAliases, expectRecord(pageModel.defaults ?? {}, 'models.pages.defaults')); + const noteDefaults = expectRecord(noteModel.defaults ?? {}, 'models.notes.defaults'); + const questionDefaults = expectRecord(questionModel.defaults ?? {}, 'models.questions.defaults'); + const objectiveDefaults = expectRecord(objectiveModel.defaults ?? {}, 'models.objectives.defaults'); + const proposalDefaults = expectRecord(proposalModel.defaults ?? {}, 'models.proposals.defaults'); + const decisionDefaults = expectRecord(decisionModel.defaults ?? {}, 'models.decisions.defaults'); + const peopleDefaults = expectRecord(peopleModel.defaults ?? {}, 'models.people.defaults'); + const agentDefaults = normalizeAliasedRecord(agentDefaultsFieldAliases, expectRecord(agentModel.defaults ?? {}, 'models.agents.defaults')); + const bookDefaults = expectRecord(bookModel.defaults ?? {}, 'models.books.defaults'); + const docsDefaults = expectRecord(docsModel.defaults ?? {}, 'models.docs.defaults'); + const logo = expectRecord(site.logo, 'site.logo'); + const forms = normalizeAliasedRecord(formsFieldAliases, expectRecord(site.forms ?? {}, 'site.forms')); + const emailNotifications = normalizeAliasedRecord( + emailNotificationFieldAliases, + expectRecord(site.emailNotifications, 'site.emailNotifications'), + ); + const access = parseAccess(parsed.access, 'access'); + + return { + site: { + logo: { + src: expectString(logo.src, 'site.logo.src'), + alt: expectString(logo.alt, 'site.logo.alt'), + }, + name: expectString(site.name, 'site.name'), + statement: expectString(site.statement, 'site.statement'), + siteUrl: expectString(site.siteUrl, 'site.siteUrl'), + githubRepository: expectString(site.githubRepository, 'site.githubRepository'), + discordLink: expectString(site.discordLink, 'site.discordLink'), + headerMenu: parseMenuGroups(site.headerMenu, 'site.headerMenu'), + footerMenu: parseMenuGroups(site.footerMenu, 'site.footerMenu'), + forms: { + apiBaseUrl: optionalString(forms.apiBaseUrl, 'site.forms.apiBaseUrl'), + }, + emailNotifications: { + contactRouting: parseContactRouting( + emailNotifications.contactRouting, + 'site.emailNotifications.contactRouting', + ), + subscribeRecipients: stringArray( + emailNotifications.subscribeRecipients, + 'site.emailNotifications.subscribeRecipients', + ), + }, + summary: expectString(site.summary, 'site.summary'), + projectStage: expectString(site.projectStage, 'site.projectStage'), + projectStageDetail: expectString(site.projectStageDetail, 'site.projectStageDetail'), + theme: parseTheme(site.theme, 'site.theme'), + }, + models: { + pages: { + defaults: { + pageLayout: optionalString(pageDefaults.pageLayout, 'models.pages.defaults.pageLayout'), + status: optionalString(pageDefaults.status, 'models.pages.defaults.status'), + stage: optionalString(pageDefaults.stage, 'models.pages.defaults.stage'), + audience: stringArray(pageDefaults.audience, 'models.pages.defaults.audience'), + }, + }, + notes: { + defaults: { + author: optionalString(noteDefaults.author, 'models.notes.defaults.author'), + draft: optionalBoolean(noteDefaults.draft, 'models.notes.defaults.draft'), + groupIds: stringArray(noteDefaults.groupIds, 'models.notes.defaults.groupIds'), + status: optionalString(noteDefaults.status, 'models.notes.defaults.status'), + }, + }, + questions: { + defaults: { + draft: optionalBoolean(questionDefaults.draft, 'models.questions.defaults.draft'), + groupIds: stringArray(questionDefaults.groupIds, 'models.questions.defaults.groupIds'), + status: optionalString(questionDefaults.status, 'models.questions.defaults.status'), + }, + }, + objectives: { + defaults: { + draft: optionalBoolean(objectiveDefaults.draft, 'models.objectives.defaults.draft'), + groupIds: stringArray(objectiveDefaults.groupIds, 'models.objectives.defaults.groupIds'), + status: optionalString(objectiveDefaults.status, 'models.objectives.defaults.status'), + }, + }, + proposals: { + defaults: { + draft: optionalBoolean(proposalDefaults.draft, 'models.proposals.defaults.draft'), + groupIds: stringArray(proposalDefaults.groupIds, 'models.proposals.defaults.groupIds'), + status: optionalString(proposalDefaults.status, 'models.proposals.defaults.status'), + }, + }, + decisions: { + defaults: { + draft: optionalBoolean(decisionDefaults.draft, 'models.decisions.defaults.draft'), + groupIds: stringArray(decisionDefaults.groupIds, 'models.decisions.defaults.groupIds'), + status: optionalString(decisionDefaults.status, 'models.decisions.defaults.status'), + }, + }, + people: { + defaults: { + status: optionalString(peopleDefaults.status, 'models.people.defaults.status'), + groupIds: stringArray(peopleDefaults.groupIds, 'models.people.defaults.groupIds'), + }, + }, + agents: { + defaults: { + groupIds: stringArray(agentDefaults.groupIds, 'models.agents.defaults.groupIds'), + runtimeStatus: optionalString( + agentDefaults.runtimeStatus, + 'models.agents.defaults.runtimeStatus', + ), + }, + }, + books: { + defaults: { + groupIds: stringArray(bookDefaults.groupIds, 'models.books.defaults.groupIds'), + }, + }, + docs: { + defaults: { + groupIds: stringArray(docsDefaults.groupIds, 'models.docs.defaults.groupIds'), + }, + }, + }, + access, + }; +} diff --git a/src/utils/site-config.ts b/src/utils/configuration/site-config.ts similarity index 74% rename from src/utils/site-config.ts rename to src/utils/configuration/site-config.ts index cd96fe84..5c02c080 100644 --- a/src/utils/site-config.ts +++ b/src/utils/configuration/site-config.ts @@ -1,5 +1,5 @@ -import { RUNTIME_SITE_CONFIG, RUNTIME_TENANT } from '../tenant/runtime-config'; -import { buildTreeseedThemeCss } from './theme.ts'; +import { RUNTIME_SITE_CONFIG, RUNTIME_TENANT } from '../../tenant/runtime-config'; +import { buildThemeCss } from '../support/theme.ts'; function requireRuntimeSiteConfig() { if (!RUNTIME_SITE_CONFIG) { @@ -35,7 +35,7 @@ export const SITE_HEADER_MENU = SITE.headerMenu; export const SITE_FOOTER_MENU = SITE.footerMenu; export const SITE_FORMS = SITE.forms; export const SITE_EMAIL_NOTIFICATIONS = SITE.emailNotifications; -export const SITE_THEME_CSS = buildTreeseedThemeCss(SITE.theme); +export const SITE_THEME_CSS = buildThemeCss(SITE.theme); export const PAGE_MODEL_DEFAULTS = SITE_CONFIG.models.pages.defaults; export const NOTE_MODEL_DEFAULTS = SITE_CONFIG.models.notes.defaults; @@ -46,7 +46,7 @@ export const DECISION_MODEL_DEFAULTS = SITE_CONFIG.models.decisions.defaults; export const PEOPLE_MODEL_DEFAULTS = SITE_CONFIG.models.people.defaults; export const AGENT_MODEL_DEFAULTS = SITE_CONFIG.models.agents.defaults; export const BOOK_MODEL_DEFAULTS = SITE_CONFIG.models.books.defaults; -export const TREESEED_MODEL_DEFAULTS = SITE_CONFIG.models.docs.defaults; +export const MODEL_DEFAULTS = SITE_CONFIG.models.docs.defaults; export function applyPageModelDefaults< T extends Partial<{ @@ -67,111 +67,111 @@ export function applyNoteModelDefaults< T extends Partial<{ author: string; draft: boolean; - tags: string[]; + groupIds: string[]; status: string; }>, >(value: T) { return { ...NOTE_MODEL_DEFAULTS, ...value, - tags: value.tags ?? NOTE_MODEL_DEFAULTS.tags ?? [], + groupIds: value.groupIds ?? NOTE_MODEL_DEFAULTS.groupIds ?? [], }; } export function applyQuestionModelDefaults< T extends Partial<{ draft: boolean; - tags: string[]; + groupIds: string[]; status: string; }>, >(value: T) { return { ...QUESTION_MODEL_DEFAULTS, ...value, - tags: value.tags ?? QUESTION_MODEL_DEFAULTS.tags ?? [], + groupIds: value.groupIds ?? QUESTION_MODEL_DEFAULTS.groupIds ?? [], }; } export function applyObjectiveModelDefaults< T extends Partial<{ draft: boolean; - tags: string[]; + groupIds: string[]; status: string; }>, >(value: T) { return { ...OBJECTIVE_MODEL_DEFAULTS, ...value, - tags: value.tags ?? OBJECTIVE_MODEL_DEFAULTS.tags ?? [], + groupIds: value.groupIds ?? OBJECTIVE_MODEL_DEFAULTS.groupIds ?? [], }; } export function applyProposalModelDefaults< T extends Partial<{ draft: boolean; - tags: string[]; + groupIds: string[]; status: string; }>, >(value: T) { return { ...PROPOSAL_MODEL_DEFAULTS, ...value, - tags: value.tags ?? PROPOSAL_MODEL_DEFAULTS.tags ?? [], + groupIds: value.groupIds ?? PROPOSAL_MODEL_DEFAULTS.groupIds ?? [], }; } export function applyDecisionModelDefaults< T extends Partial<{ draft: boolean; - tags: string[]; + groupIds: string[]; status: string; }>, >(value: T) { return { ...DECISION_MODEL_DEFAULTS, ...value, - tags: value.tags ?? DECISION_MODEL_DEFAULTS.tags ?? [], + groupIds: value.groupIds ?? DECISION_MODEL_DEFAULTS.groupIds ?? [], }; } export function applyPeopleModelDefaults< T extends Partial<{ status: string; - tags: string[]; + groupIds: string[]; }>, >(value: T) { return { ...PEOPLE_MODEL_DEFAULTS, ...value, - tags: value.tags ?? PEOPLE_MODEL_DEFAULTS.tags ?? [], + groupIds: value.groupIds ?? PEOPLE_MODEL_DEFAULTS.groupIds ?? [], }; } export function applyAgentModelDefaults< T extends Partial<{ runtimeStatus: string; - tags: string[]; + groupIds: string[]; }>, >(value: T) { return { ...AGENT_MODEL_DEFAULTS, ...value, - tags: value.tags ?? AGENT_MODEL_DEFAULTS.tags ?? [], + groupIds: value.groupIds ?? AGENT_MODEL_DEFAULTS.groupIds ?? [], }; } -export function applyBookModelDefaults>(value: T) { +export function applyBookModelDefaults>(value: T) { return { ...BOOK_MODEL_DEFAULTS, ...value, - tags: value.tags ?? BOOK_MODEL_DEFAULTS.tags ?? [], + groupIds: value.groupIds ?? BOOK_MODEL_DEFAULTS.groupIds ?? [], }; } -export function applyDocsModelDefaults>(value: T) { +export function applyDocsModelDefaults>(value: T) { return { - ...TREESEED_MODEL_DEFAULTS, + ...MODEL_DEFAULTS, ...value, - tags: value.tags ?? TREESEED_MODEL_DEFAULTS.tags ?? [], + groupIds: value.groupIds ?? MODEL_DEFAULTS.groupIds ?? [], }; } diff --git a/src/utils/content-status.ts b/src/utils/content/content-status.ts similarity index 96% rename from src/utils/content-status.ts rename to src/utils/content/content-status.ts index 78449393..cce09b99 100644 --- a/src/utils/content-status.ts +++ b/src/utils/content/content-status.ts @@ -1,4 +1,4 @@ -import { SITE } from './site-config'; +import { SITE } from '../configuration/site-config'; export const CONTENT_STATUSES = ['live', 'in progress', 'exploratory', 'planned', 'speculative'] as const; diff --git a/src/utils/hub-content.ts b/src/utils/content/hub-content.ts similarity index 98% rename from src/utils/hub-content.ts rename to src/utils/content/hub-content.ts index 1eeb682c..787d6b33 100644 --- a/src/utils/hub-content.ts +++ b/src/utils/content/hub-content.ts @@ -1,5 +1,5 @@ import type { CollectionEntry, getEntries } from 'astro:content'; -import { siteModelRendered } from './site-models.ts'; +import { siteModelRendered } from '../support/site-models.ts'; export type ContributorEntry = CollectionEntry<'people'> | CollectionEntry<'agents'>; export type HubQuestionEntry = CollectionEntry<'questions'>; diff --git a/src/utils/site-content-runtime.ts b/src/utils/content/site-content-runtime.ts similarity index 92% rename from src/utils/site-content-runtime.ts rename to src/utils/content/site-content-runtime.ts index 55c1b30a..0e78864d 100644 --- a/src/utils/site-content-runtime.ts +++ b/src/utils/content/site-content-runtime.ts @@ -1,10 +1,10 @@ import { createMarkdownProcessor, type MarkdownProcessor } from '@astrojs/markdown-remark'; import rehypeKatex from 'rehype-katex'; import remarkMath from 'remark-math'; -import type { TreeseedContentCollection } from '@treeseed/sdk/platform/contracts'; -import { getTreeseedContentServingMode } from '@treeseed/sdk/platform/deploy-runtime'; -import type { ContentRuntimeProvider, PublishedContentEntry } from '@treeseed/sdk/platform/published-content'; -import { resolveHostedContentRuntimeProvider } from './published-content.ts'; +import type { ContentCollection } from '@treeseed/sdk/site-contracts/platform'; +import { getContentServingMode } from '../../runtime/platform/deploy-runtime.ts'; +import type { ContentRuntimeProvider, PublishedContentEntry } from '../../runtime/platform/published-content.ts'; +import { resolveHostedContentRuntimeProvider } from '../packages/published-content.ts'; export type PublishedContentSourcePayload = { model: string; @@ -79,7 +79,7 @@ function dateValue(value: unknown) { return null; } -function normalizeTags(value: unknown) { +function normalizeGroupIds(value: unknown) { return Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === 'string' && entry.trim().length > 0) : []; @@ -110,7 +110,7 @@ export function normalizePublishedEntry>( summary: typeof frontmatter.summary === 'string' ? frontmatter.summary : entry.summary, status: typeof frontmatter.status === 'string' ? frontmatter.status : entry.status, slug: typeof frontmatter.slug === 'string' ? frontmatter.slug : entry.slug, - tags: normalizeTags(frontmatter.tags), + groupIds: normalizeGroupIds(frontmatter.groupIds), links: normalizeLinks(frontmatter.links), }; @@ -133,7 +133,7 @@ export function normalizePublishedEntry>( } export function isPublishedRuntimeContentMode() { - return getTreeseedContentServingMode() === 'published_runtime'; + return getContentServingMode() === 'published_runtime'; } export async function renderPublishedMarkdown(body: string) { @@ -145,7 +145,7 @@ export async function renderPublishedMarkdown(body: string) { export async function loadPublishedCollection( locals: App.Locals | Record | undefined | null, - model: TreeseedContentCollection, + model: ContentCollection, ) { const provider = providerForLocals(locals); if (!provider) { @@ -162,7 +162,7 @@ export async function loadPublishedCollection( export async function loadPublishedEntry( locals: App.Locals | Record | undefined | null, - model: TreeseedContentCollection, + model: ContentCollection, slugOrId: string, ) { const provider = providerForLocals(locals); @@ -190,7 +190,7 @@ export async function loadPublishedEntry( export async function resolvePublishedReferences( locals: App.Locals | Record | undefined | null, - model: TreeseedContentCollection, + model: ContentCollection, references: unknown, ) { const provider = providerForLocals(locals); diff --git a/src/utils/forms/session.ts b/src/utils/forms/accounts/session.ts similarity index 90% rename from src/utils/forms/session.ts rename to src/utils/forms/accounts/session.ts index 00a8ee46..2d766e7f 100644 --- a/src/utils/forms/session.ts +++ b/src/utils/forms/accounts/session.ts @@ -1,6 +1,6 @@ -import { createOpaqueId, signFormToken, verifyFormToken } from './crypto'; -import { FORM_SESSION_COOKIE } from './constants'; -import type { FormSubmitPayload } from '../../types/forms'; +import { createOpaqueId, signFormToken, verifyFormToken } from '../security/crypto'; +import { FORM_SESSION_COOKIE } from '../service/constants'; +import type { FormSubmitPayload } from '../../../types/forms'; export async function issueFormToken(formType: FormSubmitPayload['formType'], secret: string) { const sessionId = createOpaqueId(); diff --git a/src/utils/forms/provider-core.ts b/src/utils/forms/capacity/providers/provider-core.ts similarity index 83% rename from src/utils/forms/provider-core.ts rename to src/utils/forms/capacity/providers/provider-core.ts index caf2cd68..3d779e15 100644 --- a/src/utils/forms/provider-core.ts +++ b/src/utils/forms/capacity/providers/provider-core.ts @@ -3,13 +3,13 @@ import type { FormRuntimeCapabilities, GuardStore, SubscriberStore, -} from '../../types/forms'; -import type { D1DatabaseLike, KvNamespaceLike } from '../../types/cloudflare'; -import { assertNonceUnused, assertNonceUnusedLocal, applySubmissionRateLimit, applySubmissionRateLimitLocal } from './guard'; -import { createContactSubmission } from './contact-submissions'; -import { upsertSubscriber } from './subscribers'; -import { sendEmail as sendDefaultEmail } from './smtp'; -import { verifyTurnstileToken as verifyDefaultTurnstileToken } from './turnstile'; +} from '../../../../types/forms'; +import type { D1DatabaseLike, KvNamespaceLike } from '../../../../types/cloudflare'; +import { assertNonceUnused, assertNonceUnusedLocal, applySubmissionRateLimit, applySubmissionRateLimitLocal } from '../../security/guard'; +import { createContactSubmission } from '../../persistence/contact-submissions'; +import { upsertSubscriber } from '../../persistence/subscribers'; +import { sendEmail as sendDefaultEmail } from '../../transport/smtp'; +import { verifyTurnstileToken as verifyDefaultTurnstileToken } from '../../security/turnstile'; interface FormEmailOptions { smtp: { @@ -35,7 +35,7 @@ interface FormProviderBehavior { }; } -export interface TreeseedFormsProvider { +export interface FormsProvider { id: string; behavior: FormProviderBehavior; createGuardStore?(input: { runtime: FormRuntimeCapabilities; kv: KvNamespaceLike | null }): GuardStore; @@ -84,7 +84,7 @@ function createDefaultSubscriberStore(runtime: FormRuntimeCapabilities, db: D1Da return upsertSubscriber(db, input); } - const { upsertLocalSubscriber } = await import('./subscribers-local'); + const { upsertLocalSubscriber } = await import('../../runtime/subscribers-local'); return upsertLocalSubscriber(input); }, }; @@ -97,13 +97,13 @@ function createDefaultContactStore(runtime: FormRuntimeCapabilities, db: D1Datab return createContactSubmission(db, input); } - const { createLocalContactSubmission } = await import('./contact-submissions-local'); + const { createLocalContactSubmission } = await import('../../runtime/contact-submissions-local'); return createLocalContactSubmission(input); }, }; } -export const BUILTIN_FORMS_PROVIDERS: Record = { +export const BUILTIN_FORMS_PROVIDERS: Record = { store_only: { id: 'store_only', behavior: { @@ -127,7 +127,7 @@ export const BUILTIN_FORMS_PROVIDERS: Record = { }, }; -export function finalizeFormsProvider(provider: TreeseedFormsProvider): Required { +export function finalizeFormsProvider(provider: FormsProvider): Required { return { ...provider, createGuardStore: provider.createGuardStore ?? ((input) => createDefaultGuardStore(input.runtime, input.kv)), diff --git a/src/utils/forms/provider-runtime.ts b/src/utils/forms/capacity/providers/provider-runtime.ts similarity index 63% rename from src/utils/forms/provider-runtime.ts rename to src/utils/forms/capacity/providers/provider-runtime.ts index 67aba34d..fd94882d 100644 --- a/src/utils/forms/provider-runtime.ts +++ b/src/utils/forms/capacity/providers/provider-runtime.ts @@ -1,12 +1,12 @@ -import { getTreeseedFormsProvider } from '@treeseed/sdk/platform/deploy-runtime'; -import { loadTreeseedPluginRuntime } from '@treeseed/sdk/platform/plugins'; -import type { TreeseedFormsProvider } from './provider-core'; +import { getFormsProvider } from '../../../../runtime/platform/deploy-runtime.ts'; +import { loadPluginRuntime } from '../../../../runtime/platform/plugins.ts'; +import type { FormsProvider } from './provider-core'; import { BUILTIN_FORMS_PROVIDERS, finalizeFormsProvider } from './provider-core'; -type RuntimePluginEntry = ReturnType['plugins'][number]; +type RuntimePluginEntry = ReturnType['plugins'][number]; let cachedFormsRuntime: null | { - providers: Map; + providers: Map; } = null; function readPluginRecord(pluginEntry: RuntimePluginEntry, key: string): Record { @@ -20,21 +20,21 @@ function assertUniqueProvider(registry: Map, id: string, owner: } } -export function resetTreeseedFormsProviderRuntimeForTests() { +export function resetFormsProviderRuntimeForTests() { cachedFormsRuntime = null; } -export function resolveFormsProvider(providerId = getTreeseedFormsProvider()) { +export function resolveFormsProvider(providerId = getFormsProvider()) { if (!cachedFormsRuntime) { - const runtime = loadTreeseedPluginRuntime(); - const providers = new Map(); + const runtime = loadPluginRuntime(); + const providers = new Map(); for (const provider of Object.values(BUILTIN_FORMS_PROVIDERS)) { providers.set(provider.id, provider); } for (const pluginEntry of runtime.plugins) { - const contributedProviders = readPluginRecord(pluginEntry, 'formsProviders'); + const contributedProviders = readPluginRecord(pluginEntry, 'formsProviders'); for (const [id, provider] of Object.entries(contributedProviders)) { assertUniqueProvider(providers, id, pluginEntry.package); providers.set(id, provider); diff --git a/src/utils/forms/config.ts b/src/utils/forms/config.ts deleted file mode 100644 index d3ea45a8..00000000 --- a/src/utils/forms/config.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { - TREESEED_FORM_TOKEN_SECRET, - TREESEED_FORMS_LOCAL_BYPASS_CLOUDFLARE_GUARDS, - TREESEED_LOCAL_DEV_MODE, - TREESEED_SMTP_FROM, - TREESEED_SMTP_HOST, - TREESEED_SMTP_PASSWORD, - TREESEED_SMTP_PORT, - TREESEED_SMTP_REPLY_TO, - TREESEED_SMTP_USERNAME, - TREESEED_TURNSTILE_SECRET_KEY, -} from 'astro:env/server'; -import { SITE_EMAIL_NOTIFICATIONS } from '../site-config'; -import type { ContactRoutingMap, LocalDevMode } from '../../types/forms'; -import { - getTreeseedFormsProvider, - isTreeseedSmtpEnabled, - isTreeseedTurnstileEnabled, -} from '@treeseed/sdk/platform/deploy-runtime'; - -export function getFormSecret() { - return TREESEED_FORM_TOKEN_SECRET ?? ''; -} - -export function getTurnstileSecret() { - return TREESEED_TURNSTILE_SECRET_KEY ?? ''; -} - -export function getContactRoutingMap() { - return SITE_EMAIL_NOTIFICATIONS.contactRouting as ContactRoutingMap; -} - -export function getSubscribeRecipients() { - return SITE_EMAIL_NOTIFICATIONS.subscribeRecipients; -} - -export function getSmtpConfig() { - return { - host: TREESEED_SMTP_HOST ?? '', - port: TREESEED_SMTP_PORT ?? 465, - username: TREESEED_SMTP_USERNAME ?? '', - password: TREESEED_SMTP_PASSWORD ?? '', - from: TREESEED_SMTP_FROM ?? '', - replyTo: TREESEED_SMTP_REPLY_TO ?? '', - }; -} - -export function getFormsMode() { - return getTreeseedFormsProvider(); -} - -export function getLocalDevMode(): LocalDevMode | null { - if (TREESEED_LOCAL_DEV_MODE === 'cloudflare') { - return 'cloudflare'; - } - - return null; -} - -export function shouldBypassCloudflareGuardsByEnv() { - return TREESEED_FORMS_LOCAL_BYPASS_CLOUDFLARE_GUARDS; -} - -export function isSmtpConfigured() { - const smtp = getSmtpConfig(); - return Boolean(smtp.host && smtp.port && smtp.from); -} - -export function isSmtpEnabled() { - return isTreeseedSmtpEnabled() && isSmtpConfigured(); -} - -export function isTurnstileEnabled() { - return isTreeseedTurnstileEnabled() && Boolean(getTurnstileSecret()); -} diff --git a/src/utils/forms/configuration/config.ts b/src/utils/forms/configuration/config.ts new file mode 100644 index 00000000..288ec1c3 --- /dev/null +++ b/src/utils/forms/configuration/config.ts @@ -0,0 +1,75 @@ +import { + FORM_TOKEN_SECRET, + FORMS_LOCAL_BYPASS_CLOUDFLARE_GUARDS, + LOCAL_DEV_MODE, + SMTP_FROM, + SMTP_HOST, + SMTP_PASSWORD, + SMTP_PORT, + SMTP_REPLY_TO, + SMTP_USERNAME, + TURNSTILE_SECRET_KEY, +} from 'astro:env/server'; +import { SITE_EMAIL_NOTIFICATIONS } from '../../configuration/site-config'; +import type { ContactRoutingMap, LocalDevMode } from '../../../types/forms'; +import { + getFormsProvider, + isSmtpEnabled, + isTurnstileEnabled, +} from '../../../runtime/platform/deploy-runtime.ts'; + +export function getFormSecret() { + return FORM_TOKEN_SECRET ?? ''; +} + +export function getTurnstileSecret() { + return TURNSTILE_SECRET_KEY ?? ''; +} + +export function getContactRoutingMap() { + return SITE_EMAIL_NOTIFICATIONS.contactRouting as ContactRoutingMap; +} + +export function getSubscribeRecipients() { + return SITE_EMAIL_NOTIFICATIONS.subscribeRecipients; +} + +export function getSmtpConfig() { + return { + host: SMTP_HOST ?? '', + port: SMTP_PORT ?? 465, + username: SMTP_USERNAME ?? '', + password: SMTP_PASSWORD ?? '', + from: SMTP_FROM ?? '', + replyTo: SMTP_REPLY_TO ?? '', + }; +} + +export function getFormsMode() { + return getFormsProvider(); +} + +export function getLocalDevMode(): LocalDevMode | null { + if (LOCAL_DEV_MODE === 'cloudflare') { + return 'cloudflare'; + } + + return null; +} + +export function shouldBypassCloudflareGuardsByEnv() { + return FORMS_LOCAL_BYPASS_CLOUDFLARE_GUARDS; +} + +export function isSmtpConfigured() { + const smtp = getSmtpConfig(); + return Boolean(smtp.host && smtp.port && smtp.from); +} + +export function isSmtpEnabled() { + return isSmtpEnabled() && isSmtpConfigured(); +} + +export function isTurnstileEnabled() { + return isTurnstileEnabled() && Boolean(getTurnstileSecret()); +} diff --git a/src/utils/forms/smtp-cloudflare.ts b/src/utils/forms/hosting/smtp-cloudflare.ts similarity index 100% rename from src/utils/forms/smtp-cloudflare.ts rename to src/utils/forms/hosting/smtp-cloudflare.ts diff --git a/src/utils/forms/contact-submissions.ts b/src/utils/forms/persistence/contact-submissions.ts similarity index 89% rename from src/utils/forms/contact-submissions.ts rename to src/utils/forms/persistence/contact-submissions.ts index c7a0bc2b..d5f839f7 100644 --- a/src/utils/forms/contact-submissions.ts +++ b/src/utils/forms/persistence/contact-submissions.ts @@ -1,6 +1,6 @@ -import { hashValue } from './crypto'; -import type { D1DatabaseLike } from '../../types/cloudflare'; -import type { ContactRecordInput } from '../../types/forms'; +import { hashValue } from '../security/crypto'; +import type { D1DatabaseLike } from '../../../types/cloudflare'; +import type { ContactRecordInput } from '../../../types/forms'; async function tableColumns(db: D1DatabaseLike, tableName: 'contact_submissions') { const { results } = await db diff --git a/src/utils/forms/subscribers.ts b/src/utils/forms/persistence/subscribers.ts similarity index 86% rename from src/utils/forms/subscribers.ts rename to src/utils/forms/persistence/subscribers.ts index 88fecc41..a45bcafa 100644 --- a/src/utils/forms/subscribers.ts +++ b/src/utils/forms/persistence/subscribers.ts @@ -1,6 +1,6 @@ -import { hashValue } from './crypto'; -import type { D1DatabaseLike } from '../../types/cloudflare'; -import type { SubscriberRecordInput } from '../../types/forms'; +import { hashValue } from '../security/crypto'; +import type { D1DatabaseLike } from '../../../types/cloudflare'; +import type { SubscriberRecordInput } from '../../../types/forms'; async function hasTable(db: D1DatabaseLike, tableName: string) { const row = await db diff --git a/src/utils/forms/routing-core.ts b/src/utils/forms/routing/routing-core.ts similarity index 75% rename from src/utils/forms/routing-core.ts rename to src/utils/forms/routing/routing-core.ts index 6eed871e..c9695c76 100644 --- a/src/utils/forms/routing-core.ts +++ b/src/utils/forms/routing/routing-core.ts @@ -1,4 +1,4 @@ -import type { ContactRoutingMap, ContactType } from '../../types/forms'; +import type { ContactRoutingMap, ContactType } from '../../../types/forms'; export function resolveContactRecipientsFromMap(routingMap: ContactRoutingMap, contactType: ContactType) { const recipients = routingMap[contactType]?.length ? routingMap[contactType] : routingMap.default; diff --git a/src/utils/forms/routing.ts b/src/utils/forms/routing/routing.ts similarity index 69% rename from src/utils/forms/routing.ts rename to src/utils/forms/routing/routing.ts index 7428e4bf..7626ece0 100644 --- a/src/utils/forms/routing.ts +++ b/src/utils/forms/routing/routing.ts @@ -1,5 +1,5 @@ -import { getContactRoutingMap, getSubscribeRecipients } from './config'; -import type { ContactType } from '../../types/forms'; +import { getContactRoutingMap, getSubscribeRecipients } from '../configuration/config'; +import type { ContactType } from '../../../types/forms'; import { resolveContactRecipientsFromMap } from './routing-core'; export function resolveContactRecipients(contactType: ContactType) { diff --git a/src/utils/forms/contact-submissions-local.ts b/src/utils/forms/runtime/contact-submissions-local.ts similarity index 66% rename from src/utils/forms/contact-submissions-local.ts rename to src/utils/forms/runtime/contact-submissions-local.ts index 29027023..9f3dba76 100644 --- a/src/utils/forms/contact-submissions-local.ts +++ b/src/utils/forms/runtime/contact-submissions-local.ts @@ -1,5 +1,5 @@ -import { hashValue } from './crypto'; -import type { ContactRecordInput } from '../../types/forms'; +import { hashValue } from '../security/crypto'; +import type { ContactRecordInput } from '../../../types/forms'; interface LocalContactSubmissionRecord { name: string; @@ -14,10 +14,10 @@ interface LocalContactSubmissionRecord { } const localContactSubmissions = - (globalThis as { __treeseedFixtureContactSubmissions?: LocalContactSubmissionRecord[] }).__treeseedFixtureContactSubmissions + (globalThis as { FixtureContactSubmissions?: LocalContactSubmissionRecord[] }).FixtureContactSubmissions ?? []; -(globalThis as { __treeseedFixtureContactSubmissions?: LocalContactSubmissionRecord[] }).__treeseedFixtureContactSubmissions = +(globalThis as { FixtureContactSubmissions?: LocalContactSubmissionRecord[] }).FixtureContactSubmissions = localContactSubmissions; export async function createLocalContactSubmission(input: ContactRecordInput) { diff --git a/src/utils/forms/runtime-core.ts b/src/utils/forms/runtime/runtime-core.ts similarity index 97% rename from src/utils/forms/runtime-core.ts rename to src/utils/forms/runtime/runtime-core.ts index 705875d1..4c6fe005 100644 --- a/src/utils/forms/runtime-core.ts +++ b/src/utils/forms/runtime/runtime-core.ts @@ -1,4 +1,4 @@ -import type { FormRuntimeCapabilities, LocalDevMode } from '../../types/forms'; +import type { FormRuntimeCapabilities, LocalDevMode } from '../../../types/forms'; interface RuntimeInputs { isCloudflareRuntime: boolean; diff --git a/src/utils/forms/runtime.ts b/src/utils/forms/runtime/runtime.ts similarity index 81% rename from src/utils/forms/runtime.ts rename to src/utils/forms/runtime/runtime.ts index 5b6a3a66..ea2b0bd0 100644 --- a/src/utils/forms/runtime.ts +++ b/src/utils/forms/runtime/runtime.ts @@ -4,9 +4,9 @@ import { isSmtpEnabled, isTurnstileEnabled, shouldBypassCloudflareGuardsByEnv, -} from './config'; -import type { CloudflareRuntime } from '../../types/cloudflare'; -import type { FormRuntimeCapabilities } from '../../types/forms'; +} from '../configuration/config'; +import type { CloudflareRuntime } from '../../../types/cloudflare'; +import type { FormRuntimeCapabilities } from '../../../types/forms'; import { deriveFormRuntimeCapabilities } from './runtime-core'; export function resolveFormRuntimeCapabilities(locals: App.Locals): FormRuntimeCapabilities { diff --git a/src/utils/forms/subscribers-local.ts b/src/utils/forms/runtime/subscribers-local.ts similarity index 66% rename from src/utils/forms/subscribers-local.ts rename to src/utils/forms/runtime/subscribers-local.ts index a54bb3e0..158c3389 100644 --- a/src/utils/forms/subscribers-local.ts +++ b/src/utils/forms/runtime/subscribers-local.ts @@ -1,5 +1,5 @@ -import { hashValue } from './crypto'; -import type { SubscriberRecordInput } from '../../types/forms'; +import { hashValue } from '../security/crypto'; +import type { SubscriberRecordInput } from '../../../types/forms'; interface LocalSubscriberRecord { email: string; @@ -13,10 +13,10 @@ interface LocalSubscriberRecord { } const localSubscribers = - (globalThis as { __treeseedFixtureSubscribers?: Map }).__treeseedFixtureSubscribers + (globalThis as { FixtureSubscribers?: Map }).FixtureSubscribers ?? new Map(); -(globalThis as { __treeseedFixtureSubscribers?: Map }).__treeseedFixtureSubscribers = localSubscribers; +(globalThis as { FixtureSubscribers?: Map }).FixtureSubscribers = localSubscribers; export async function upsertLocalSubscriber(input: SubscriberRecordInput) { const now = new Date().toISOString(); diff --git a/src/utils/forms/crypto.ts b/src/utils/forms/security/crypto.ts similarity index 94% rename from src/utils/forms/crypto.ts rename to src/utils/forms/security/crypto.ts index 12e2ca50..b7c34a58 100644 --- a/src/utils/forms/crypto.ts +++ b/src/utils/forms/security/crypto.ts @@ -1,5 +1,5 @@ -import { FORM_TOKEN_TTL_MS } from './constants'; -import type { SignedFormTokenPayload } from '../../types/forms'; +import { FORM_TOKEN_TTL_MS } from '../service/constants'; +import type { SignedFormTokenPayload } from '../../../types/forms'; function base64UrlEncode(input: Uint8Array) { let binary = ''; diff --git a/src/utils/forms/guard.ts b/src/utils/forms/security/guard.ts similarity index 82% rename from src/utils/forms/guard.ts rename to src/utils/forms/security/guard.ts index 7b9f0d55..256a08a5 100644 --- a/src/utils/forms/guard.ts +++ b/src/utils/forms/security/guard.ts @@ -1,16 +1,16 @@ -import { NONCE_TTL_SECONDS, RATE_LIMIT_MAX_ATTEMPTS, RATE_LIMIT_TTL_SECONDS } from './constants'; +import { NONCE_TTL_SECONDS, RATE_LIMIT_MAX_ATTEMPTS, RATE_LIMIT_TTL_SECONDS } from '../service/constants'; import { hashValue } from './crypto'; -import type { KvNamespaceLike } from '../../types/cloudflare'; +import type { KvNamespaceLike } from '../../../types/cloudflare'; type CounterRecord = { count: number; expiresAt: number }; -const localNonceStore = (globalThis as { __treeseedFixtureNonceStore?: Map }).__treeseedFixtureNonceStore +const localNonceStore = (globalThis as { FixtureNonceStore?: Map }).FixtureNonceStore ?? new Map(); -const localRateStore = (globalThis as { __treeseedFixtureRateStore?: Map }).__treeseedFixtureRateStore +const localRateStore = (globalThis as { FixtureRateStore?: Map }).FixtureRateStore ?? new Map(); -(globalThis as { __treeseedFixtureNonceStore?: Map }).__treeseedFixtureNonceStore = localNonceStore; -(globalThis as { __treeseedFixtureRateStore?: Map }).__treeseedFixtureRateStore = localRateStore; +(globalThis as { FixtureNonceStore?: Map }).FixtureNonceStore = localNonceStore; +(globalThis as { FixtureRateStore?: Map }).FixtureRateStore = localRateStore; async function incrementCounter(kv: KvNamespaceLike, key: string, ttl: number) { const currentValue = await kv.get(key); diff --git a/src/utils/forms/turnstile.ts b/src/utils/forms/security/turnstile.ts similarity index 94% rename from src/utils/forms/turnstile.ts rename to src/utils/forms/security/turnstile.ts index acd99d64..bddea1eb 100644 --- a/src/utils/forms/turnstile.ts +++ b/src/utils/forms/security/turnstile.ts @@ -1,4 +1,4 @@ -import type { FormRuntimeCapabilities } from '../../types/forms'; +import type { FormRuntimeCapabilities } from '../../../types/forms'; interface TurnstileResponse { success: boolean; diff --git a/src/utils/forms/validation.ts b/src/utils/forms/security/validation.ts similarity index 96% rename from src/utils/forms/validation.ts rename to src/utils/forms/security/validation.ts index c21b6126..df345dc7 100644 --- a/src/utils/forms/validation.ts +++ b/src/utils/forms/security/validation.ts @@ -1,4 +1,4 @@ -import { CONTACT_TYPES, type ContactSubmission, type FormSubmitPayload, type SubscribeSubmission } from '../../types/forms'; +import { CONTACT_TYPES, type ContactSubmission, type FormSubmitPayload, type SubscribeSubmission } from '../../../types/forms'; function normalizeEmail(value: FormDataEntryValue | null) { return typeof value === 'string' ? value.trim().toLowerCase() : ''; diff --git a/src/utils/forms/constants.ts b/src/utils/forms/service/constants.ts similarity index 77% rename from src/utils/forms/constants.ts rename to src/utils/forms/service/constants.ts index 46938294..14b4066f 100644 --- a/src/utils/forms/constants.ts +++ b/src/utils/forms/service/constants.ts @@ -1,4 +1,4 @@ -import type { ContactType } from '../../types/forms'; +import type { ContactType } from '../../../types/forms'; export const FORM_SESSION_COOKIE = 'treeseed_form_session'; export const FORM_TOKEN_TTL_MS = 30 * 60 * 1000; @@ -6,8 +6,6 @@ export const NONCE_TTL_SECONDS = 60 * 60; export const RATE_LIMIT_TTL_SECONDS = 10 * 60; export const RATE_LIMIT_MAX_ATTEMPTS = 3; export const HONEYPOT_FIELD = 'website'; -export const FORM_SUCCESS_PARAM = 'formStatus'; -export const FORM_CODE_PARAM = 'formCode'; export const SUBSCRIBE_ANCHOR_ID = 'site-subscribe'; export const CONTACT_TYPE_LABELS: Record = { diff --git a/src/utils/forms/service-core.ts b/src/utils/forms/service/service-core.ts similarity index 96% rename from src/utils/forms/service-core.ts rename to src/utils/forms/service/service-core.ts index 0b190a3f..e0364010 100644 --- a/src/utils/forms/service-core.ts +++ b/src/utils/forms/service/service-core.ts @@ -1,10 +1,10 @@ import { CONTACT_TYPE_LABELS, FORM_SESSION_COOKIE, HONEYPOT_FIELD } from './constants'; -import { buildRedirectTarget, getRemoteIp, sanitizeRedirectTo } from './http'; -import { issueFormToken, verifyIssuedToken, createSessionCookie } from './session'; -import { parsePayload, validatePayload } from './validation'; -import { hashValue } from './crypto'; -import { resolveContactRecipientsFromMap } from './routing-core'; -import type { TreeseedFormsProvider } from './provider-core'; +import { buildRedirectTarget, getRemoteIp, sanitizeRedirectTo } from '../transport/http'; +import { issueFormToken, verifyIssuedToken, createSessionCookie } from '../accounts/session'; +import { parsePayload, validatePayload } from '../security/validation'; +import { hashValue } from '../security/crypto'; +import { resolveContactRecipientsFromMap } from '../routing/routing-core'; +import type { FormsProvider } from '../capacity/providers/provider-core'; import type { ContactSubmission, ContactSubmissionStore, @@ -14,7 +14,7 @@ import type { SubmitResult, SubscribeSubmission, SubscriberStore, -} from '../../types/forms'; +} from '../../../types/forms'; interface SmtpConfig { host: string; @@ -33,7 +33,7 @@ export interface FormServiceBindings { export interface FormServiceConfig { runtime: FormRuntimeCapabilities; bindings?: FormServiceBindings | null; - formsProvider: Required; + formsProvider: Required; formSecret: string; turnstileSecret?: string; contactRouting: ContactRoutingMap; @@ -55,7 +55,7 @@ function resultFor(formType: FormSubmitPayload['formType'], redirectTo: string, ok, code, message, - redirectTo: buildRedirectTarget(formType, redirectTo, ok, code), + redirectTo: buildRedirectTarget(formType, redirectTo), }; } diff --git a/src/utils/forms/service.ts b/src/utils/forms/service/service.ts similarity index 78% rename from src/utils/forms/service.ts rename to src/utils/forms/service/service.ts index 895c4c63..acfec16b 100644 --- a/src/utils/forms/service.ts +++ b/src/utils/forms/service/service.ts @@ -1,16 +1,16 @@ import type { APIContext } from 'astro'; -import { getTreeseedFormsProvider } from '@treeseed/sdk/platform/deploy-runtime'; -import { resolveBuiltinFormsProvider } from './provider-core'; +import { getFormsProvider } from '../../../runtime/platform/deploy-runtime.ts'; +import { resolveBuiltinFormsProvider } from '../capacity/providers/provider-core'; import { getContactRoutingMap, getFormSecret, getSmtpConfig, getSubscribeRecipients, getTurnstileSecret, -} from './config'; -import { resolveFormRuntimeCapabilities } from './runtime'; -import type { CloudflareRuntime } from '../../types/cloudflare'; -import { SITE } from '../site-config'; +} from '../configuration/config'; +import { resolveFormRuntimeCapabilities } from '../runtime/runtime'; +import type { CloudflareRuntime } from '../../../types/cloudflare'; +import { SITE } from '../../configuration/site-config'; import { handleFormSubmissionWithConfig, handleTokenRequestWithConfig } from './service-core'; function getBindings(locals: App.Locals) { @@ -39,7 +39,7 @@ function createAstroFormConfig(context: APIContext) { return { runtime: resolveFormRuntimeCapabilities(context.locals), bindings: getBindings(context.locals), - formsProvider: resolveBuiltinFormsProvider(getTreeseedFormsProvider()), + formsProvider: resolveBuiltinFormsProvider(getFormsProvider()), formSecret: getFormSecret(), turnstileSecret: getTurnstileSecret(), contactRouting: getContactRoutingMap(), diff --git a/src/utils/forms/http.ts b/src/utils/forms/transport/http.ts similarity index 85% rename from src/utils/forms/http.ts rename to src/utils/forms/transport/http.ts index c17807df..a374b88d 100644 --- a/src/utils/forms/http.ts +++ b/src/utils/forms/transport/http.ts @@ -1,4 +1,4 @@ -import { FORM_CODE_PARAM, FORM_SUCCESS_PARAM, SUBSCRIBE_ANCHOR_ID } from './constants'; +import { SUBSCRIBE_ANCHOR_ID } from '../service/constants'; export function getRemoteIp(request: Request) { return request.headers.get('CF-Connecting-IP') ?? request.headers.get('X-Forwarded-For') ?? ''; @@ -27,14 +27,10 @@ function resolveRedirectBase(requestUrl?: URL | string) { export function buildRedirectTarget( formType: 'contact' | 'subscribe', rawRedirectTo: string, - isSuccess: boolean, - code: string, requestUrl?: URL | string, ) { const fallback = fallbackPath(formType); const url = new URL(rawRedirectTo || fallback, resolveRedirectBase(requestUrl)); - url.searchParams.set(FORM_SUCCESS_PARAM, isSuccess ? 'success' : 'error'); - url.searchParams.set(FORM_CODE_PARAM, code); if (formType === 'subscribe') { url.hash = SUBSCRIBE_ANCHOR_ID; diff --git a/src/utils/forms/smtp-node.ts b/src/utils/forms/transport/smtp-node.ts similarity index 93% rename from src/utils/forms/smtp-node.ts rename to src/utils/forms/transport/smtp-node.ts index 2f681a36..bd38660d 100644 --- a/src/utils/forms/smtp-node.ts +++ b/src/utils/forms/transport/smtp-node.ts @@ -1,5 +1,5 @@ import nodemailer from 'nodemailer'; -import { getSmtpConfig } from './config'; +import { getSmtpConfig } from '../configuration/config'; interface EmailMessage { to: string[]; diff --git a/src/utils/forms/smtp.ts b/src/utils/forms/transport/smtp.ts similarity index 78% rename from src/utils/forms/smtp.ts rename to src/utils/forms/transport/smtp.ts index 56df1113..d4acb3c5 100644 --- a/src/utils/forms/smtp.ts +++ b/src/utils/forms/transport/smtp.ts @@ -1,4 +1,4 @@ -import type { FormRuntimeCapabilities } from '../../types/forms'; +import type { FormRuntimeCapabilities } from '../../../types/forms'; interface EmailMessage { to: string[]; @@ -25,7 +25,7 @@ export async function sendEmail( options: SendEmailOptions, ) { if (runtime.isCloudflareRuntime) { - const { sendEmailWithCloudflareSockets } = await import('./smtp-cloudflare'); + const { sendEmailWithCloudflareSockets } = await import('../hosting/smtp-cloudflare'); return sendEmailWithCloudflareSockets(message, options); } diff --git a/src/utils/knowledge/reader-library.ts b/src/utils/knowledge/reader-library.ts new file mode 100644 index 00000000..0a051aed --- /dev/null +++ b/src/utils/knowledge/reader-library.ts @@ -0,0 +1,173 @@ +import type { ContentCollection } from '@treeseed/sdk/site-contracts/platform'; +import { createSignedUserAssertion } from '../../runtime/platform/trusted-assertion.ts'; +import { REMOTE_CONTRACT_HEADER, REMOTE_CONTRACT_VERSION } from '@treeseed/sdk/site-contracts/catalog'; +import { isPublishedRuntimeContentMode, loadPublishedCollection } from '../content/site-content-runtime.ts'; + +export type RuntimeBookEntry = { id: string; data: Record }; +export type RuntimeKnowledgeEntry = { id: string; slug?: string; data: Record }; +type PublicKnowledgeCacheEntry = { loadedAt: number; etag?: string; payload: any; pending?: Promise }; + +const publicKnowledgeCache = new Map(); +const PUBLIC_KNOWLEDGE_CACHE_TTL_MS = 5_000; +const PUBLIC_KNOWLEDGE_CACHE_MAX_ENTRIES = 128; + +function apiBaseUrl(locals: App.Locals | Record) { + const runtime = (locals as any)?.runtime?.env ?? {}; + return String(runtime.TREESEED_MARKET_API_BASE_URL + ?? process.env.TREESEED_MARKET_API_BASE_URL ?? '').replace(/\/+$/u, ''); +} + +async function fetchKnowledge(baseUrl: string, path: string, headers: Headers, cached?: PublicKnowledgeCacheEntry) { + if (cached?.etag) headers.set('if-none-match', cached.etag); + const response = await fetch(`${baseUrl}${path}`, { headers }); + if (response.status === 304 && cached) return cached.payload; + if (response.status === 404) return { missing: true }; + if (!response.ok) throw new Error(`Knowledge API request failed with status ${response.status}.`); + const envelope = await response.json(); + return { payload: envelope.payload ?? envelope, etag: response.headers.get('etag') ?? undefined }; +} + +async function knowledgeRequest(locals: App.Locals | Record, path: string) { + const baseUrl = apiBaseUrl(locals); + if (!baseUrl) return null; + const runtime = (locals as any)?.runtime?.env ?? {}; + const principal = (locals as any)?.auth?.principal; + const session = (locals as any)?.auth?.session; + const assertionSecret = String(runtime.TREESEED_API_WEB_ASSERTION_SECRET ?? runtime.TREESEED_WEB_ASSERTION_SECRET + ?? process.env.TREESEED_API_WEB_ASSERTION_SECRET ?? process.env.TREESEED_WEB_ASSERTION_SECRET ?? '').trim(); + const serviceId = String(runtime.TREESEED_WEB_SERVICE_ID ?? runtime.TREESEED_API_WEB_SERVICE_ID + ?? process.env.TREESEED_WEB_SERVICE_ID ?? process.env.TREESEED_API_WEB_SERVICE_ID ?? 'web').trim(); + const serviceSecret = String(runtime.TREESEED_WEB_SERVICE_SECRET ?? runtime.TREESEED_API_WEB_SERVICE_SECRET + ?? process.env.TREESEED_WEB_SERVICE_SECRET ?? process.env.TREESEED_API_WEB_SERVICE_SECRET ?? '').trim(); + const headers = new Headers({ accept: 'application/json', [REMOTE_CONTRACT_HEADER]: String(REMOTE_CONTRACT_VERSION) }); + if (principal?.id && session?.id && assertionSecret && serviceSecret) { + headers.set('x-treeseed-service-id', serviceId); + headers.set('x-treeseed-service-secret', serviceSecret); + headers.set('x-treeseed-user-assertion', createSignedUserAssertion({ secret: assertionSecret, + userId: principal.id, sessionId: session.id, identityId: session.identityId ?? null, + authTime: session.authenticatedAt ?? new Date().toISOString() })); + } + if (principal?.id) { + const result = await fetchKnowledge(baseUrl, path, headers); + return result?.payload ?? result; + } + const key = `${baseUrl}${path}`; + const cached = publicKnowledgeCache.get(key); + if (cached && Date.now() - cached.loadedAt <= PUBLIC_KNOWLEDGE_CACHE_TTL_MS) return cached.payload; + if (cached?.pending) return cached.pending; + const pending = fetchKnowledge(baseUrl, path, headers, cached).then((result) => { + const entry = result?.payload === undefined + ? { loadedAt: Date.now(), payload: result } + : { loadedAt: Date.now(), payload: result.payload, etag: result.etag }; + publicKnowledgeCache.set(key, entry); + while (publicKnowledgeCache.size > PUBLIC_KNOWLEDGE_CACHE_MAX_ENTRIES) { + publicKnowledgeCache.delete(publicKnowledgeCache.keys().next().value!); + } + return entry.payload; + }).catch((error) => { + publicKnowledgeCache.delete(key); + throw error; + }); + publicKnowledgeCache.set(key, { loadedAt: cached?.loadedAt ?? 0, etag: cached?.etag, + payload: cached?.payload, pending }); + return pending; +} + +const runtimeBook = (book: any): RuntimeBookEntry => ({ id: book.id, data: book }); +const runtimePage = (page: any): RuntimeKnowledgeEntry => ({ id: page.id, slug: page.slug, data: page }); + +export async function loadFederatedBookLibrary(locals: App.Locals | Record) { + const result = await knowledgeRequest(locals, '/v1/knowledge/library'); + return result ? { available: true, books: (result.books ?? []).map(runtimeBook) } : { available: false, books: [] }; +} + +export async function loadFederatedReader(locals: App.Locals | Record, input: { + teamSlug: string; bookSlug: string; pageSlug?: string; +}) { + const query = new URLSearchParams({ teamSlug: input.teamSlug, bookSlug: input.bookSlug, + ...(input.pageSlug ? { pageSlug: input.pageSlug } : {}) }); + const result = await knowledgeRequest(locals, `/v1/knowledge/reader?${query}`); + if (!result || result.missing) return result; + return { book: runtimeBook(result.book), pages: (result.navigation ?? []).map(runtimePage), + page: result.page ? runtimePage(result.page) : null, published: true }; +} + +export async function loadReaderLibrary(locals: App.Locals | Record) { + if (isPublishedRuntimeContentMode()) { + return { + published: true, + books: await loadPublishedCollection(locals, 'books') as RuntimeBookEntry[], + pages: await loadPublishedCollection(locals, 'docs') as RuntimeKnowledgeEntry[], + }; + } + const content = await import('astro:content'); + return { + published: false, + books: await content.getCollection('books') as RuntimeBookEntry[], + pages: await content.getCollection('docs').catch(() => []) as RuntimeKnowledgeEntry[], + }; +} + +export function pagesForBook(pages: RuntimeKnowledgeEntry[], bookId: string) { + return pages + .filter((page) => page.data.bookId === bookId && page.data.status === 'published') + .sort((left, right) => Number(left.data.order ?? 0) - Number(right.data.order ?? 0) || String(left.data.title).localeCompare(String(right.data.title))); +} + +export function canReadKnowledge(locals: App.Locals | Record, visibility: string) { + if (visibility === 'public') return true; + const principal = (locals as any)?.auth?.principal; + if (!principal) return false; + if (visibility === 'authenticated') return true; + if (visibility === 'admin') return principal.roles?.includes?.('platform_admin') || principal.permissions?.includes?.('*:*:*'); + // Team/project authorization must be established by a policy-filtered published manifest. + // Local readers fail closed because a broad role does not identify the owning team or project. + return false; +} + +export function bookSidebar(teamSlug: string, book: RuntimeBookEntry, pages: RuntimeKnowledgeEntry[]) { + const base = `/t/${encodeURIComponent(teamSlug)}/books/${encodeURIComponent(String(book.data.slug))}`; + const hierarchy = buildReaderHierarchy(pages.map((entry) => ({ + id: String(entry.data.id ?? entry.id), parentId: entry.data.parentId ? String(entry.data.parentId) : undefined, + order: Number(entry.data.order ?? 0), title: String(entry.data.title), entry, + }))); + const items = (nodes: typeof hierarchy): any[] => nodes.map((node) => ({ + label: node.page.title, + link: `${base}/${String(node.page.entry.data.slug).split('/').map(encodeURIComponent).join('/')}`, + ...(node.children.length ? { items: items(node.children) } : {}), + })); + return [{ + label: String(book.data.title), + items: [ + { label: 'Overview', link: base }, + ...items(hierarchy), + ], + }]; +} + +type ReaderTreeItem = { id: string; parentId?: string; order: number; title: string; entry: RuntimeKnowledgeEntry }; +type ReaderTreeNode = { page: ReaderTreeItem; children: ReaderTreeNode[] }; + +function buildReaderHierarchy(pages: ReaderTreeItem[]): ReaderTreeNode[] { + const nodes = new Map(pages.map((page) => [page.id, { page, children: [] as ReaderTreeNode[] }])); + const roots: ReaderTreeNode[] = []; + for (const node of nodes.values()) { + const parent = node.page.parentId ? nodes.get(node.page.parentId) : undefined; + if (parent) parent.children.push(node); else roots.push(node); + } + const sort = (items: ReaderTreeNode[]) => { + items.sort((left, right) => left.page.order - right.page.order || left.page.title.localeCompare(right.page.title) || left.page.id.localeCompare(right.page.id)); + items.forEach((item) => sort(item.children)); + }; + sort(roots); + return roots; +} + +export function starlightKnowledgeFrontmatter(data: Record) { + return { + ...Object.fromEntries(Object.entries(data).filter(([, value]) => value !== null && value !== undefined)), + title: String(data.title), + description: String(data.summary ?? data.description ?? ''), + editUrl: false as const, + }; +} diff --git a/src/utils/published-content.ts b/src/utils/packages/published-content.ts similarity index 73% rename from src/utils/published-content.ts rename to src/utils/packages/published-content.ts index bb0d4eb6..9f07c3f7 100644 --- a/src/utils/published-content.ts +++ b/src/utils/packages/published-content.ts @@ -1,5 +1,4 @@ -import type { TreeseedBookRuntime } from '@treeseed/sdk/platform/books-data'; -import { getTreeseedDeployConfig } from '@treeseed/sdk/platform/deploy-runtime'; +import { getDeployConfig } from '../../runtime/platform/deploy-runtime.ts'; import { createTeamScopedR2OverlayContentRuntimeProvider, isTeamScopedR2ContentEnabled, @@ -8,8 +7,8 @@ import { resolveTeamScopedContentLocator, type ContentRuntimeProvider, type EditorialPreviewTokenPayload, -} from '@treeseed/sdk/platform/published-content'; -import type { CloudflareRuntime } from '../types/cloudflare'; +} from '../../runtime/platform/published-content.ts'; +import type { CloudflareRuntime } from '../../types/cloudflare'; function runtimeFromLocals(locals: App.Locals | Record | undefined | null) { return ((locals as App.Locals | undefined)?.runtime ?? null) as CloudflareRuntime | null; @@ -19,6 +18,11 @@ function previewFromLocals(locals: App.Locals | Record | undefi return ((locals as App.Locals | undefined)?.contentPreview ?? null) as EditorialPreviewTokenPayload | null; } +function runtimeText(runtime: CloudflareRuntime | null, key: string) { + const value = runtime?.env?.[key]; + return typeof value === 'string' && value.trim() ? value.trim() : null; +} + function defaultTeamIdForRuntime(locals: App.Locals | Record | undefined | null) { const runtime = runtimeFromLocals(locals); const configured = typeof runtime?.env?.TREESEED_CONTENT_DEFAULT_TEAM_ID === 'string' @@ -28,13 +32,13 @@ function defaultTeamIdForRuntime(locals: App.Locals | Record | return configured; } - return getTreeseedDeployConfig().slug; + return getDeployConfig().slug; } export function resolveHostedContentRuntimeProvider( locals: App.Locals | Record | undefined | null, ): ContentRuntimeProvider | null { - const deployConfig = getTreeseedDeployConfig(); + const deployConfig = getDeployConfig(); if (!isTeamScopedR2ContentEnabled(deployConfig)) { return null; } @@ -52,30 +56,22 @@ export function resolveHostedContentRuntimeProvider( defaultTeamId, preview?.teamId === defaultTeamId ? preview.previewId : undefined, ); + const manifestKey = runtimeText(runtime, 'TREESEED_CONTENT_MANIFEST_KEY'); + const previewRoot = runtimeText(runtime, 'TREESEED_EDITORIAL_PREVIEW_ROOT'); return createTeamScopedR2OverlayContentRuntimeProvider({ bucket, - locator, + locator: { + ...locator, + ...(manifestKey ? { manifestKey } : {}), + ...(previewRoot ? { + previewRoot, + overlayKey: preview?.previewId ? `${previewRoot}/${preview.previewId}/overlay.json` : undefined, + } : {}), + }, }); } -export async function loadHostedBookRuntime( - locals: App.Locals | Record | undefined | null, -): Promise { - const provider = resolveHostedContentRuntimeProvider(locals); - if (!provider) { - return null; - } - - const manifest = await provider.getManifest(); - const pointer = manifest.runtime?.booksRuntime; - if (!pointer) { - return null; - } - - return provider.getObject(pointer); -} - export type HostedDocsTreeEntry = { id: string; slug: string; diff --git a/src/utils/runtime-reader.ts b/src/utils/runtime-reader.ts deleted file mode 100644 index 470390a0..00000000 --- a/src/utils/runtime-reader.ts +++ /dev/null @@ -1,514 +0,0 @@ -import type { TreeseedBookRuntime } from '@treeseed/sdk/platform/books-data'; -import { getTreeseedContentServingMode, getTreeseedDeployConfig } from '@treeseed/sdk/platform/deploy-runtime'; -import type { FeedbackContext, HelpContext, HelpTopic, HelpTopicLink, ResolvedAction } from '@treeseed/ui/lib/foundation'; -import { - createTeamScopedR2OverlayContentRuntimeProvider, - resolveCloudflareR2Bucket, - resolvePublishedContentBucketBinding, - type ContentRuntimeProvider, -} from '@treeseed/sdk/platform/published-content'; -import { - loadHostedBookRuntime, - loadHostedDocsTree, - resolveHostedContentRuntimeProvider, - type HostedDocsTreeEntry, -} from './published-content.ts'; -import { - loadPublishedEntry, - normalizePublishedEntry, - renderPublishedMarkdown, - type PublishedContentSourcePayload, - type RuntimeHubEntry, -} from './site-content-runtime.ts'; -import type { CloudflareRuntime } from '../types/cloudflare'; - -export interface RuntimeReaderNavItem { - label: string; - href: string; - current?: boolean; -} - -export interface RuntimeReaderNavGroup { - label: string; - items: RuntimeReaderNavItem[]; -} - -export interface LocalRuntimeReaderDocument { - id: string; - slug?: string; - data: { - title?: string; - summary?: string; - description?: string; - slug?: string; - }; -} - -export interface RuntimeReaderViewModel { - status: 'ready' | 'not_found' | 'unavailable' | 'denied' | 'requires_sign_in'; - source: 'r2_published_manifest' | 'r2_private_manifest' | 'local_collections'; - title: string; - description: string; - currentPath: string; - publishedHtml: string | null; - localDocumentId: string | null; - navGroups: RuntimeReaderNavGroup[]; - actions: ResolvedAction[]; - help?: HelpContext; - feedback?: FeedbackContext; - cache: { - cdnEligible: boolean; - headers: Record; - }; - errorTitle?: string; - errorDescription?: string; -} - -interface RuntimeReaderInput { - locals: App.Locals | Record | undefined | null; - slug?: string; - localDocuments?: LocalRuntimeReaderDocument[]; -} - -interface PrivateRuntimeReaderInput { - locals: App.Locals | Record | undefined | null; - projectId: string; - teamId: string; - slug?: string; - access: 'allowed' | 'denied' | 'requires_sign_in'; -} - -function normalizePath(path: string) { - return path.endsWith('/') ? path : `${path}/`; -} - -function cleanSlug(slug: unknown) { - return String(slug ?? '').replace(/^\/+|\/+$/gu, ''); -} - -function currentPathFor(slug: string) { - return slug ? `/knowledge/${slug}/` : '/knowledge/'; -} - -function publicFeedbackContext(currentPath: string, title: string): FeedbackContext { - return { - url: currentPath, - canonicalPath: currentPath, - title, - capabilityId: 'core.public-knowledge-reader', - shell: 'public', - context: 'public', - resourceType: 'knowledge-page', - resourceId: currentPath.replace(/^\/knowledge\/?|\/$/gu, '') || 'knowledge:index', - environment: (process.env.TREESEED_ENVIRONMENT === 'production' || process.env.TREESEED_ENVIRONMENT === 'staging') - ? process.env.TREESEED_ENVIRONMENT - : 'local', - submissionEndpoint: '/api/feedback/submit', - allowAnonymous: true, - screenshotPolicy: 'optional', - attachmentStoragePolicy: 'public', - routePattern: currentPath === '/knowledge/' ? '/knowledge' : '/knowledge/[...slug]', - policy: 'public', - }; -} - -function publicHelpContext(input: { - currentPath: string; - title: string; - description: string; - navGroups?: RuntimeReaderNavGroup[]; - actions?: ResolvedAction[]; - source: 'r2_published_manifest' | 'local_collections'; -}): HelpContext { - const resourceId = input.currentPath.replace(/^\/knowledge\/?|\/$/gu, '') || 'knowledge:index'; - const topic: HelpTopic = { - id: `knowledge:${resourceId}`, - title: input.title, - summary: input.description, - href: input.currentPath, - visibility: 'public', - source: 'runtime-content', - }; - const relatedDocs: HelpTopicLink[] = (input.navGroups ?? []).flatMap((group) => group.items).slice(0, 6).map((item) => ({ - topicId: `knowledge:${item.href.replace(/^\/knowledge\/?|\/$/gu, '') || 'knowledge:index'}`, - title: item.label, - href: item.href, - visibility: 'public', - summary: item.current ? 'Current Knowledge Hub page.' : 'Related public Knowledge Hub page.', - source: 'runtime-content', - current: item.current, - })); - return { - capabilityId: 'core.public-knowledge-reader', - topicIds: ['knowledge-reader', topic.id], - shell: 'public', - context: 'public', - resourceType: 'knowledge-page', - resourceId, - routePattern: input.currentPath === '/knowledge/' ? '/knowledge' : '/knowledge/[...slug]', - canonicalPath: input.currentPath, - template: 'reader', - summary: 'Read public Knowledge Hub content from the runtime content source.', - topics: [topic], - relatedDocs, - relatedActions: input.actions ?? [], - searchScope: 'public', - searchPlaceholder: 'Search this Knowledge Hub context', - visibility: 'public', - feedbackType: 'content_issue', - }; -} - -function privateCurrentPathFor(projectId: string, slug: string) { - const encodedProjectId = encodeURIComponent(projectId); - const clean = cleanSlug(slug); - return clean ? `/app/projects/${encodedProjectId}/knowledge/${clean}/` : `/app/projects/${encodedProjectId}/knowledge/`; -} - -function publishedSlugFor(slug: string) { - return slug ? `knowledge/${slug}` : 'knowledge'; -} - -function privateSlugCandidates(slug: string) { - const clean = cleanSlug(slug); - const target = clean || 'index'; - return [...new Set([ - target, - target === 'index' ? 'knowledge' : `knowledge/${target}`, - ])]; -} - -function localSlugFor(document: LocalRuntimeReaderDocument) { - const dataSlug = typeof document.data.slug === 'string' ? document.data.slug : ''; - return cleanSlug(dataSlug || document.slug || document.id).replace(/\/index$/u, ''); -} - -function titleForDocument(document: LocalRuntimeReaderDocument | RuntimeHubEntry | null, fallback: string) { - const data = document?.data ?? {}; - return typeof data.title === 'string' && data.title.trim() ? data.title.trim() : fallback; -} - -function summaryForDocument(document: LocalRuntimeReaderDocument | RuntimeHubEntry | null, fallback: string) { - const data = document?.data ?? {}; - for (const value of [data.summary, data.description]) { - if (typeof value === 'string' && value.trim()) return value.trim(); - } - return fallback; -} - -function downloadActions(runtime: TreeseedBookRuntime | null, currentPath: string): ResolvedAction[] { - const normalized = normalizePath(currentPath); - const activeBook = runtime?.BOOKS.find((book) => normalized.startsWith(normalizePath(book.basePath))) ?? null; - const download = activeBook - ? { href: activeBook.downloadHref, label: activeBook.downloadTitle } - : runtime?.TREESEED_LIBRARY_DOWNLOAD - ? { href: runtime.TREESEED_LIBRARY_DOWNLOAD.downloadHref, label: runtime.TREESEED_LIBRARY_DOWNLOAD.downloadTitle } - : null; - return download?.href ? [{ - id: 'knowledge.download', - label: download.label, - state: 'allowed', - href: download.href, - method: 'GET', - confirmation: 'none', - auditSensitivity: 'normal', - }] : []; -} - -function privateDownloadActions(runtime: TreeseedBookRuntime | null, currentPath: string): ResolvedAction[] { - return downloadActions(runtime, currentPath).map((action) => ({ - ...action, - state: 'disabledWithReason', - href: undefined, - reason: 'Private downloads require the signed download flow in a later migration phase.', - auditSensitivity: 'sensitive', - })); -} - -function runtimeNavGroups(runtime: TreeseedBookRuntime | null, docsTree: HostedDocsTreeEntry[] | null, currentPath: string): RuntimeReaderNavGroup[] { - const entries = docsTree ?? []; - if (!runtime?.BOOKS.length) { - return [{ label: 'Knowledge', items: entries.map((entry) => navItem(entry.title ?? entry.slug, entry.path, currentPath)) }]; - } - return runtime.BOOKS.map((book) => ({ - label: book.sectionLabel, - items: entries - .filter((entry) => normalizePath(entry.path).startsWith(normalizePath(book.basePath))) - .map((entry) => navItem(entry.title ?? entry.slug, entry.path, currentPath)), - })).filter((group) => group.items.length > 0); -} - -function localNavGroups(documents: LocalRuntimeReaderDocument[] | undefined, currentPath: string): RuntimeReaderNavGroup[] { - const groups = new Map(); - for (const document of documents ?? []) { - const slug = localSlugFor(document); - const href = slug === 'knowledge' || slug === 'index' ? '/knowledge/' : `/knowledge/${slug}/`; - const group = slug.includes('/') ? slug.split('/')[0] || 'Knowledge' : 'Knowledge'; - const label = titleForDocument(document, slug); - groups.set(group, [...(groups.get(group) ?? []), navItem(label, href, currentPath)]); - } - return [...groups.entries()].map(([label, items]) => ({ label: titleCase(label), items })); -} - -function navItem(label: string, href: string, currentPath: string): RuntimeReaderNavItem { - return { label, href, current: normalizePath(href) === normalizePath(currentPath) }; -} - -function titleCase(value: string) { - return value.replace(/[-_]+/gu, ' ').replace(/\b\w/gu, (match) => match.toUpperCase()); -} - -function localDocumentFor(documents: LocalRuntimeReaderDocument[] | undefined, slug: string) { - const target = slug || 'index'; - return (documents ?? []).find((document) => { - const localSlug = localSlugFor(document); - return localSlug === target - || localSlug === `knowledge/${target}` - || (target === 'index' && (localSlug === 'knowledge' || localSlug === '')); - }) ?? null; -} - -function unavailable(source: RuntimeReaderViewModel['source'], currentPath: string, title = 'Knowledge unavailable', description = 'Published knowledge runtime content is not available right now.'): RuntimeReaderViewModel { - return { - status: 'unavailable', - source, - title, - description, - currentPath, - publishedHtml: null, - localDocumentId: null, - navGroups: [], - actions: [], - help: source === 'r2_private_manifest' ? undefined : publicHelpContext({ currentPath, title, description, source, actions: [] }), - feedback: source === 'r2_private_manifest' ? undefined : publicFeedbackContext(currentPath, title), - cache: { cdnEligible: source === 'r2_published_manifest', headers: { 'cache-control': 'public, max-age=60' } }, - errorTitle: title, - errorDescription: description, - }; -} - -function notFound(source: RuntimeReaderViewModel['source'], currentPath: string): RuntimeReaderViewModel { - const title = 'Knowledge page not found'; - return { - status: 'not_found', - source, - title, - description: 'The requested knowledge page is not available.', - currentPath, - publishedHtml: null, - localDocumentId: null, - navGroups: [], - actions: [], - help: source === 'r2_private_manifest' ? undefined : publicHelpContext({ currentPath, title, description: 'The requested knowledge page is not available.', source, actions: [] }), - feedback: source === 'r2_private_manifest' ? undefined : publicFeedbackContext(currentPath, title), - cache: { cdnEligible: source === 'r2_published_manifest', headers: { 'cache-control': 'public, max-age=60' } }, - errorTitle: title, - errorDescription: 'The requested knowledge page is not available.', - }; -} - -function denied(currentPath: string): RuntimeReaderViewModel { - return { - status: 'denied', - source: 'r2_private_manifest', - title: 'Private knowledge unavailable', - description: 'Your current account cannot access this private knowledge page.', - currentPath, - publishedHtml: null, - localDocumentId: null, - navGroups: [], - actions: [], - cache: { cdnEligible: false, headers: { 'cache-control': 'private, no-store' } }, - errorTitle: 'Private knowledge unavailable', - errorDescription: 'Your current account cannot access this private knowledge page.', - }; -} - -function requiresSignIn(currentPath: string): RuntimeReaderViewModel { - return { - status: 'requires_sign_in', - source: 'r2_private_manifest', - title: 'Sign in required', - description: 'Sign in with a Market account that can access this project.', - currentPath, - publishedHtml: null, - localDocumentId: null, - navGroups: [], - actions: [], - cache: { cdnEligible: false, headers: { 'cache-control': 'private, no-store' } }, - errorTitle: 'Sign in required', - errorDescription: 'Sign in with a Market account that can access this project.', - }; -} - -function runtimeFromLocals(locals: App.Locals | Record | undefined | null) { - return ((locals as App.Locals | undefined)?.runtime ?? null) as CloudflareRuntime | null; -} - -function privateManifestKey(teamId: string, projectId: string) { - const cleanTeamId = cleanSlug(teamId) || 'unknown-team'; - const cleanProjectId = cleanSlug(projectId) || 'unknown-project'; - return `teams/${cleanTeamId}/projects/${cleanProjectId}/private/common.json`; -} - -function resolvePrivateContentRuntimeProvider(input: Pick): ContentRuntimeProvider | null { - const deployConfig = getTreeseedDeployConfig(); - const runtime = runtimeFromLocals(input.locals); - const bucket = resolveCloudflareR2Bucket(runtime, resolvePublishedContentBucketBinding(deployConfig)); - if (!bucket) return null; - return createTeamScopedR2OverlayContentRuntimeProvider({ - bucket, - locator: { - teamId: input.teamId, - manifestKey: privateManifestKey(input.teamId, input.projectId), - previewRoot: `teams/${cleanSlug(input.teamId)}/projects/${cleanSlug(input.projectId)}/private/previews`, - mode: 'production', - }, - }); -} - -async function loadPrivateBookRuntime(provider: ContentRuntimeProvider): Promise { - const manifest = await provider.getManifest(); - const pointer = manifest.runtime?.booksRuntime; - return pointer ? provider.getObject(pointer) : null; -} - -async function loadPrivateDocsTree(provider: ContentRuntimeProvider): Promise { - const manifest = await provider.getManifest(); - const pointer = manifest.runtime?.docsTree; - return pointer ? provider.getObject(pointer) : null; -} - -async function loadPrivateEntry(provider: ContentRuntimeProvider, slug: string) { - for (const candidate of privateSlugCandidates(slug)) { - const entry = await provider.getEntry('docs', candidate); - if (!entry) continue; - const content = await provider.getObject(entry.content); - if (!content) return null; - return { - entry: normalizePublishedEntry(entry, content), - content, - html: await renderPublishedMarkdown(content.body ?? ''), - }; - } - return null; -} - -export async function buildPublicKnowledgeReaderViewModel(input: RuntimeReaderInput): Promise { - const slug = cleanSlug(input.slug); - const currentPath = currentPathFor(slug); - const publishedRuntime = getTreeseedContentServingMode() === 'published_runtime'; - if (publishedRuntime) { - const provider = resolveHostedContentRuntimeProvider(input.locals); - if (!provider) return unavailable('r2_published_manifest', currentPath); - try { - const [runtime, docsTree, document] = await Promise.all([ - loadHostedBookRuntime(input.locals), - loadHostedDocsTree(input.locals), - loadPublishedEntry(input.locals, 'docs', publishedSlugFor(slug)), - ]); - if (!document) return notFound('r2_published_manifest', currentPath); - const title = titleForDocument(document.entry, 'Knowledge page'); - const actions = downloadActions(runtime, currentPath); - const navGroups = runtimeNavGroups(runtime, docsTree, currentPath); - const description = summaryForDocument(document.entry, 'Published knowledge page'); - return { - status: 'ready', - source: 'r2_published_manifest', - title, - description, - currentPath, - publishedHtml: document.html, - localDocumentId: null, - navGroups, - actions, - help: publicHelpContext({ currentPath, title, description, navGroups, actions, source: 'r2_published_manifest' }), - feedback: publicFeedbackContext(currentPath, title), - cache: { - cdnEligible: true, - headers: { - 'cache-control': 'public, max-age=300, s-maxage=3600', - }, - }, - }; - } catch { - return unavailable('r2_published_manifest', currentPath); - } - } - - const localDocument = localDocumentFor(input.localDocuments, slug); - if (!localDocument) return notFound('local_collections', currentPath); - const title = titleForDocument(localDocument, 'Knowledge page'); - const description = summaryForDocument(localDocument, 'Local knowledge page'); - const navGroups = localNavGroups(input.localDocuments, currentPath); - return { - status: 'ready', - source: 'local_collections', - title, - description, - currentPath, - publishedHtml: null, - localDocumentId: localDocument.id, - navGroups, - actions: [], - help: publicHelpContext({ currentPath, title, description, navGroups, actions: [], source: 'local_collections' }), - feedback: publicFeedbackContext(currentPath, title), - cache: { - cdnEligible: false, - headers: { - 'cache-control': 'no-store', - }, - }, - }; -} - -export async function buildPrivateKnowledgeReaderViewModel(input: PrivateRuntimeReaderInput): Promise { - const slug = cleanSlug(input.slug); - const currentPath = privateCurrentPathFor(input.projectId, slug); - if (input.access === 'requires_sign_in') return requiresSignIn(currentPath); - if (input.access === 'denied') return denied(currentPath); - - const provider = resolvePrivateContentRuntimeProvider(input); - if (!provider) { - return { - ...unavailable('r2_private_manifest', currentPath, 'Private knowledge unavailable', 'Private knowledge runtime content is not available right now.'), - cache: { cdnEligible: false, headers: { 'cache-control': 'private, no-store' } }, - }; - } - - try { - const [runtime, docsTree, document] = await Promise.all([ - loadPrivateBookRuntime(provider), - loadPrivateDocsTree(provider), - loadPrivateEntry(provider, slug), - ]); - if (!document) { - return { - ...notFound('r2_private_manifest', currentPath), - cache: { cdnEligible: false, headers: { 'cache-control': 'private, no-store' } }, - }; - } - return { - status: 'ready', - source: 'r2_private_manifest', - title: titleForDocument(document.entry, 'Private knowledge page'), - description: summaryForDocument(document.entry, 'Private project knowledge page'), - currentPath, - publishedHtml: document.html, - localDocumentId: null, - navGroups: runtimeNavGroups(runtime, docsTree, currentPath), - actions: privateDownloadActions(runtime, currentPath), - cache: { - cdnEligible: false, - headers: { - 'cache-control': 'private, no-store', - }, - }, - }; - } catch { - return { - ...unavailable('r2_private_manifest', currentPath, 'Private knowledge unavailable', 'Private knowledge runtime content is not available right now.'), - cache: { cdnEligible: false, headers: { 'cache-control': 'private, no-store' } }, - }; - } -} diff --git a/src/utils/seo.ts b/src/utils/seo.ts deleted file mode 100644 index 0b0cf908..00000000 --- a/src/utils/seo.ts +++ /dev/null @@ -1 +0,0 @@ -export { SITE } from './site-config'; diff --git a/src/utils/site-config-schema.ts b/src/utils/site-config-schema.ts deleted file mode 100644 index 351e77eb..00000000 --- a/src/utils/site-config-schema.ts +++ /dev/null @@ -1,2 +0,0 @@ -// @ts-nocheck -export { parseSiteConfig } from '@treeseed/sdk/platform/site-config-schema'; diff --git a/src/utils/site-models.ts b/src/utils/site-models.ts deleted file mode 100644 index 837bef59..00000000 --- a/src/utils/site-models.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { TreeseedContentCollection, TreeseedTenantConfig } from '@treeseed/sdk/platform/contracts'; -import { RUNTIME_TENANT } from '../tenant/runtime-config.ts'; - -const MODEL_FEATURE_MAP: Partial>> = { - docs: 'docs', - books: 'books', - notes: 'notes', - questions: 'questions', - objectives: 'objectives', - proposals: 'proposals', - decisions: 'decisions', - agents: 'agents', -}; - -function tenantModelRendered( - tenantConfig: Pick, - modelName: TreeseedContentCollection, -) { - const featureName = MODEL_FEATURE_MAP[modelName]; - if (featureName && tenantConfig.features?.[featureName] === false) { - return false; - } - return tenantConfig.site?.models?.[modelName]?.rendered !== false; -} - -export function isSiteRenderedModel( - tenantConfig: Pick, - modelName: TreeseedContentCollection, -) { - return tenantModelRendered(tenantConfig, modelName); -} - -export function filterSiteRenderedModels( - tenantConfig: Pick, - entries: T[], -) { - return entries.filter((entry) => isSiteRenderedModel(tenantConfig, entry.model)); -} - -export function siteModelRendered(modelName: TreeseedContentCollection) { - return isSiteRenderedModel(RUNTIME_TENANT, modelName); -} diff --git a/src/utils/starlight-nav.ts b/src/utils/starlight-nav.ts deleted file mode 100644 index e32584bd..00000000 --- a/src/utils/starlight-nav.ts +++ /dev/null @@ -1,156 +0,0 @@ -import type { SidebarEntry, SidebarGroup, SidebarLink } from '@astrojs/starlight/route-data'; -import type { TreeseedBookDefinition } from '@treeseed/sdk/platform/contracts'; -import { - BOOKS as RUNTIME_BOOKS, - BOOKS_LINK as RUNTIME_BOOKS_LINK, - TREESEED_LIBRARY_DOWNLOAD as RUNTIME_TREESEED_LIBRARY_DOWNLOAD, - TREESEED_LINKS as RUNTIME_TREESEED_LINKS, - type TreeseedBookRuntime, -} from '@treeseed/sdk/platform/books-data'; - -interface DocsDownload { - downloadFileName: string; - downloadHref: string; - downloadTitle: string; -} - -type StarlightRuntime = TreeseedBookRuntime; - -declare const __TREESEED_BOOK_RUNTIME__: StarlightRuntime | undefined; - -export const normalizeHref = (href: string) => (href.endsWith('/') ? href : `${href}/`); - -function buildSidebarLink(href: string, label: string, currentPath: string): SidebarLink { - return { - type: 'link', - label, - href, - isCurrent: normalizeHref(href) === normalizeHref(currentPath), - badge: undefined, - attrs: {}, - }; -} - -function buildSidebarEntries( - items: TreeseedBookDefinition['sidebarItems'], - currentPath: string, -): SidebarEntry[] { - return items.map((item) => { - if (item.items?.length) { - return { - type: 'group', - label: item.label, - entries: buildSidebarEntries(item.items, currentPath), - collapsed: false, - badge: undefined, - } satisfies SidebarGroup; - } - - if (item.link) { - return buildSidebarLink(item.link, item.label, currentPath); - } - - return { - type: 'group', - label: item.label, - entries: [], - collapsed: false, - badge: undefined, - } satisfies SidebarGroup; - }); -} - -export function buildBookSidebarFromRuntime(runtime: StarlightRuntime, bookSlug: string) { - const book = runtime.BOOKS.find((candidate) => candidate.slug === bookSlug); - if (!book) { - throw new Error(`Unknown book slug: ${bookSlug}`); - } - - return { - label: book.sectionLabel, - items: book.sidebarItems, - }; -} - -export function getStarlightSidebarConfigFromRuntime(runtime: StarlightRuntime) { - return [runtime.BOOKS_LINK, ...runtime.BOOKS.map((book) => buildBookSidebarFromRuntime(runtime, book.slug))]; -} - -export function buildStarlightSidebarEntriesFromRuntime( - runtime: StarlightRuntime, - currentPath: string = runtime.TREESEED_LINKS.home, -): SidebarEntry[] { - return [ - buildSidebarLink(String(runtime.BOOKS_LINK.link ?? runtime.TREESEED_LINKS.home), String(runtime.BOOKS_LINK.label ?? 'Books'), currentPath), - ...runtime.BOOKS.map((book) => ({ - type: 'group', - label: book.sectionLabel, - entries: buildSidebarEntries(book.sidebarItems, currentPath), - collapsed: false, - badge: undefined, - } satisfies SidebarGroup)), - ]; -} - -export function getBookForPathFromRuntime(runtime: StarlightRuntime, pathname: string) { - const normalizedPath = normalizeHref(pathname); - return runtime.BOOKS.find((book) => normalizedPath.startsWith(normalizeHref(book.basePath))); -} - -export function getDocsDownloadForPathFromRuntime(runtime: StarlightRuntime, pathname: string) { - const normalizedPath = normalizeHref(pathname); - - if (normalizedPath === normalizeHref(runtime.TREESEED_LINKS.home)) { - return runtime.TREESEED_LIBRARY_DOWNLOAD; - } - - const book = getBookForPathFromRuntime(runtime, normalizedPath); - if (!book) { - return null; - } - - return { - downloadFileName: book.downloadFileName, - downloadHref: book.downloadHref, - downloadTitle: book.downloadTitle, - }; -} - -export const BOOKS: TreeseedBookRuntime['BOOKS'] = RUNTIME_BOOKS ?? []; -export const BOOKS_LINK: TreeseedBookRuntime['BOOKS_LINK'] = RUNTIME_BOOKS_LINK ?? { - label: 'Books', - link: '/books/', -}; -export const TREESEED_LIBRARY_DOWNLOAD: TreeseedBookRuntime['TREESEED_LIBRARY_DOWNLOAD'] = RUNTIME_TREESEED_LIBRARY_DOWNLOAD ?? { - downloadFileName: 'treeseed-knowledge.md', - downloadHref: '/books/treeseed-knowledge.md', - downloadTitle: 'TreeSeed Knowledge Library', -}; -export const TREESEED_LINKS: TreeseedBookRuntime['TREESEED_LINKS'] = RUNTIME_TREESEED_LINKS ?? { - home: '/books/', -}; - -const runtime: StarlightRuntime = - typeof __TREESEED_BOOK_RUNTIME__ !== 'undefined' - ? __TREESEED_BOOK_RUNTIME__ - : { BOOKS, BOOKS_LINK, TREESEED_LIBRARY_DOWNLOAD, TREESEED_LINKS }; - -export function buildBookSidebar(bookSlug: string) { - return buildBookSidebarFromRuntime(runtime, bookSlug); -} - -export function getStarlightSidebarConfig() { - return getStarlightSidebarConfigFromRuntime(runtime); -} - -export function getStarlightSidebarEntries(currentPath?: string) { - return buildStarlightSidebarEntriesFromRuntime(runtime, currentPath); -} - -export function getBookForPath(pathname: string) { - return getBookForPathFromRuntime(runtime, pathname); -} - -export function getDocsDownloadForPath(pathname: string) { - return getDocsDownloadForPathFromRuntime(runtime, pathname); -} diff --git a/src/utils/astro-security.ts b/src/utils/support/astro-security.ts similarity index 79% rename from src/utils/astro-security.ts rename to src/utils/support/astro-security.ts index d00ed9b2..beb2688a 100644 --- a/src/utils/astro-security.ts +++ b/src/utils/support/astro-security.ts @@ -1,6 +1,6 @@ -import type { TreeseedDeployConfig, TreeseedManagedServiceEnvironmentConfig } from '@treeseed/sdk/platform/contracts'; +import type { DeployConfig, ManagedServiceEnvironmentConfig } from '@treeseed/sdk/site-contracts/platform'; -export interface TreeseedAstroAllowedDomain { +export interface AstroAllowedDomain { hostname: string; protocol?: string; port?: string; @@ -29,17 +29,17 @@ function appendHostname(hostnames: string[], value: string | null | undefined) { } } -function appendEnvironmentHostnames(hostnames: string[], environments: Iterable) { +function appendEnvironmentHostnames(hostnames: string[], environments: Iterable) { for (const environment of environments) { appendHostname(hostnames, environment?.domain); appendHostname(hostnames, environment?.baseUrl); } } -export function deriveTreeseedAstroAllowedDomains( - deployConfig: Pick, +export function deriveAstroAllowedDomains( + deployConfig: Pick, options: { siteUrl?: string | null } = {}, -): TreeseedAstroAllowedDomain[] { +): AstroAllowedDomain[] { const hostnames: string[] = []; const webSurface = deployConfig.surfaces?.web; diff --git a/src/utils/routes.ts b/src/utils/support/routes.ts similarity index 88% rename from src/utils/routes.ts rename to src/utils/support/routes.ts index edbbe552..8b5055e8 100644 --- a/src/utils/routes.ts +++ b/src/utils/support/routes.ts @@ -1,4 +1,4 @@ -import { SITE_HEADER_MENU } from './site-config'; +import { SITE_HEADER_MENU } from '../configuration/site-config'; export const SITE_NAV_GROUPS = SITE_HEADER_MENU; diff --git a/src/utils/support/seo.ts b/src/utils/support/seo.ts new file mode 100644 index 00000000..ce715915 --- /dev/null +++ b/src/utils/support/seo.ts @@ -0,0 +1 @@ +export { SITE } from '../configuration/site-config'; diff --git a/src/utils/support/site-models.ts b/src/utils/support/site-models.ts new file mode 100644 index 00000000..dd4e80dd --- /dev/null +++ b/src/utils/support/site-models.ts @@ -0,0 +1,42 @@ +import type { ContentCollection, TenantConfig } from '@treeseed/sdk/site-contracts/platform'; +import { RUNTIME_TENANT } from '../../tenant/runtime-config.ts'; + +const MODEL_FEATURE_MAP: Partial>> = { + docs: 'docs', + books: 'books', + notes: 'notes', + questions: 'questions', + objectives: 'objectives', + proposals: 'proposals', + decisions: 'decisions', + agents: 'agents', +}; + +function tenantModelRendered( + tenantConfig: Pick, + modelName: ContentCollection, +) { + const featureName = MODEL_FEATURE_MAP[modelName]; + if (featureName && tenantConfig.features?.[featureName] === false) { + return false; + } + return tenantConfig.site?.models?.[modelName]?.rendered !== false; +} + +export function isSiteRenderedModel( + tenantConfig: Pick, + modelName: ContentCollection, +) { + return tenantModelRendered(tenantConfig, modelName); +} + +export function filterSiteRenderedModels( + tenantConfig: Pick, + entries: T[], +) { + return entries.filter((entry) => isSiteRenderedModel(tenantConfig, entry.model)); +} + +export function siteModelRendered(modelName: ContentCollection) { + return isSiteRenderedModel(RUNTIME_TENANT, modelName); +} diff --git a/src/utils/theme.ts b/src/utils/support/theme.ts similarity index 69% rename from src/utils/theme.ts rename to src/utils/support/theme.ts index 7530eb92..3469e5de 100644 --- a/src/utils/theme.ts +++ b/src/utils/support/theme.ts @@ -1,19 +1,19 @@ import type { - TreeseedColorSchemeId, - TreeseedSchemeTokens, - TreeseedSemanticColorTokens, - TreeseedThemeConfig, - TreeseedThemeMode, -} from '@treeseed/sdk/platform/contracts'; -import { BUILT_IN_COLOR_SCHEMES } from './color-schemes/index.ts'; + ColorSchemeId, + SchemeTokens, + SemanticColorTokens, + ThemeConfig, + ThemeMode, +} from '@treeseed/sdk/site-contracts/platform'; +import { BUILT_IN_COLOR_SCHEMES } from '../color-schemes/index.ts'; export type ThemePreference = { - scheme: TreeseedColorSchemeId; - mode: TreeseedThemeMode; + scheme: ColorSchemeId; + mode: ThemeMode; }; -export type TreeseedColorSchemeSummary = { - id: TreeseedColorSchemeId; +export type ColorSchemeSummary = { + id: ColorSchemeId; name: string; swatches: string[]; modeSwatches: { @@ -22,29 +22,29 @@ export type TreeseedColorSchemeSummary = { }; }; -export type ResolvedTreeseedThemeConfig = { - defaultScheme: TreeseedColorSchemeId; - defaultMode: TreeseedThemeMode; - schemes: Record; - summaries: TreeseedColorSchemeSummary[]; +export type ResolvedThemeConfig = { + defaultScheme: ColorSchemeId; + defaultMode: ThemeMode; + schemes: Record; + summaries: ColorSchemeSummary[]; }; -const DEFAULT_SCHEME: TreeseedColorSchemeId = 'fern'; -const DEFAULT_MODE: TreeseedThemeMode = 'system'; -const THEME_MODES = new Set(['light', 'dark', 'system']); +const DEFAULT_SCHEME: ColorSchemeId = 'fern'; +const DEFAULT_MODE: ThemeMode = 'system'; +const THEME_MODES = new Set(['light', 'dark', 'system']); -const BUILT_IN_SCHEME_SUMMARIES: TreeseedColorSchemeSummary[] = BUILT_IN_COLOR_SCHEMES.map((scheme) => ({ +const BUILT_IN_SCHEME_SUMMARIES: ColorSchemeSummary[] = BUILT_IN_COLOR_SCHEMES.map((scheme) => ({ id: scheme.id, name: scheme.name, swatches: scheme.swatches, modeSwatches: scheme.modeSwatches, })); -const BUILT_IN_SCHEMES: Record = Object.fromEntries( +const BUILT_IN_SCHEMES: Record = Object.fromEntries( BUILT_IN_COLOR_SCHEMES.map((scheme) => [scheme.id, scheme.tokens]), -) as Record; +) as Record; -function mergeTokens(base: TreeseedSemanticColorTokens, override?: Partial) { +function mergeTokens(base: SemanticColorTokens, override?: Partial) { return { ...base, ...(override ?? {}), @@ -52,24 +52,24 @@ function mergeTokens(base: TreeseedSemanticColorTokens, override?: Partial; dark: Partial }>, -): TreeseedSchemeTokens { + base: SchemeTokens, + override?: Partial<{ light: Partial; dark: Partial }>, +): SchemeTokens { return { light: mergeTokens(base.light, override?.light), dark: mergeTokens(base.dark, override?.dark), }; } -function normalizeSchemeId(value: unknown, fallback: TreeseedColorSchemeId) { - return typeof value === 'string' && /^[a-z][a-z0-9-]*$/u.test(value) ? value as TreeseedColorSchemeId : fallback; +function normalizeSchemeId(value: unknown, fallback: ColorSchemeId) { + return typeof value === 'string' && /^[a-z][a-z0-9-]*$/u.test(value) ? value as ColorSchemeId : fallback; } function cssVariableName(tokenName: string) { return `--ts-color-${tokenName.replace(/[A-Z]/g, (character) => `-${character.toLowerCase()}`)}`; } -function buildTokenDeclarations(tokens: TreeseedSemanticColorTokens) { +function buildTokenDeclarations(tokens: SemanticColorTokens) { return Object.entries(tokens) .map(([tokenName, value]) => `\t${cssVariableName(tokenName)}: ${value};`) .join('\n'); @@ -94,7 +94,7 @@ export function getBuiltInColorSchemes() { })); } -export function resolveTreeseedThemeConfig(input?: TreeseedThemeConfig): ResolvedTreeseedThemeConfig { +export function resolveThemeConfig(input?: ThemeConfig): ResolvedThemeConfig { const schemes = { ...BUILT_IN_SCHEMES }; for (const [schemeId, scheme] of Object.entries(input?.schemes ?? {})) { const base = schemes[schemeId] ?? schemes[DEFAULT_SCHEME]; @@ -107,7 +107,7 @@ export function resolveTreeseedThemeConfig(input?: TreeseedThemeConfig): Resolve const customSummaries = Object.keys(input?.schemes ?? {}) .filter((schemeId) => !BUILT_IN_SCHEME_SUMMARIES.some((summary) => summary.id === schemeId)) .map((schemeId) => ({ - id: schemeId as TreeseedColorSchemeId, + id: schemeId as ColorSchemeId, name: schemeId .split('-') .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) @@ -146,14 +146,14 @@ export function normalizeThemePreference(input: unknown): ThemePreference { const record = input && typeof input === 'object' ? input as Record : {}; return { scheme: normalizeSchemeId(record.scheme ?? record.colorScheme, DEFAULT_SCHEME), - mode: typeof (record.mode ?? record.themeMode) === 'string' && THEME_MODES.has((record.mode ?? record.themeMode) as TreeseedThemeMode) - ? (record.mode ?? record.themeMode) as TreeseedThemeMode + mode: typeof (record.mode ?? record.themeMode) === 'string' && THEME_MODES.has((record.mode ?? record.themeMode) as ThemeMode) + ? (record.mode ?? record.themeMode) as ThemeMode : DEFAULT_MODE, }; } -export function buildTreeseedThemeCss(input?: TreeseedThemeConfig) { - const resolved = resolveTreeseedThemeConfig(input); +export function buildThemeCss(input?: ThemeConfig) { + const resolved = resolveThemeConfig(input); const defaultTokens = resolved.schemes[resolved.defaultScheme][resolved.defaultMode === 'dark' ? 'dark' : 'light']; const darkDefaultTokens = resolved.schemes[resolved.defaultScheme].dark; const blocks = [ diff --git a/src/utils/web-cache.ts b/src/utils/support/web-cache.ts similarity index 90% rename from src/utils/web-cache.ts rename to src/utils/support/web-cache.ts index 3f404612..75efdc54 100644 --- a/src/utils/web-cache.ts +++ b/src/utils/support/web-cache.ts @@ -1,5 +1,5 @@ -import { EDITORIAL_PREVIEW_COOKIE } from '@treeseed/sdk/platform/published-content'; -import { getTreeseedDeployConfig } from '@treeseed/sdk/platform/plugins'; +import { EDITORIAL_PREVIEW_COOKIE } from '../../runtime/platform/published-content.ts'; +import { getDeployConfig } from '../../runtime/platform/plugins.ts'; const STATIC_FILE_PATTERN = /\.[a-z0-9]+$/i; const PRIVATE_PATH_PREFIXES = ['/api', '/auth', '/admin', '/app', '/internal']; @@ -21,7 +21,7 @@ const CONTENT_INDEX_PATHS = new Set([ '/questions', ]); -export type TreeseedWebRequestClassification = +export type WebRequestClassification = | { kind: 'static_asset' } | { kind: 'api_no_cache'; reason: string } | { kind: 'preview_no_cache'; reason: string } @@ -56,7 +56,7 @@ function normalizePath(pathname: string) { } function configuredSourcePagePaths() { - const configured = getTreeseedDeployConfig().surfaces?.web?.cache?.sourcePages?.paths; + const configured = getDeployConfig().surfaces?.web?.cache?.sourcePages?.paths; return new Set((configured && configured.length ? configured : DEFAULT_SOURCE_PAGE_PATHS).map((entry) => normalizePath(entry))); } @@ -73,7 +73,7 @@ function isContentHtmlPath(pathname: string) { return normalized !== '/' && !configuredSourcePagePaths().has(normalized); } -export function classifyTreeseedWebRequest(request: Request, url: URL): TreeseedWebRequestClassification { +export function classifyWebRequest(request: Request, url: URL): WebRequestClassification { if (!['GET', 'HEAD'].includes(request.method)) { return { kind: 'api_no_cache', reason: 'method' }; } @@ -131,7 +131,7 @@ function publicCdnCacheControl(edgeTtlSeconds: number, staleWhileRevalidateSecon } function resolveWebCachePolicy() { - const cache = getTreeseedDeployConfig().surfaces?.web?.cache ?? {}; + const cache = getDeployConfig().surfaces?.web?.cache ?? {}; return { sourcePages: { browserTtlSeconds: cache.sourcePages?.browserTtlSeconds ?? DEFAULT_LONG_LIVED_CACHE_POLICY.browserTtlSeconds, @@ -150,12 +150,12 @@ function resolveWebCachePolicy() { }; } -export function applyTreeseedWebCacheHeaders( +export function applyWebCacheHeaders( request: Request, url: URL, response: Response, ) { - const classification = classifyTreeseedWebRequest(request, url); + const classification = classifyWebRequest(request, url); if (classification.kind === 'preview_no_cache' || classification.kind === 'api_no_cache' || classification.kind === 'private_no_cache') { response.headers.set('Cache-Control', 'no-store'); response.headers.set('CDN-Cache-Control', 'no-store'); diff --git a/src/worker/forms-worker.ts b/src/worker/forms-worker.ts index cd1ca481..1fb75d70 100644 --- a/src/worker/forms-worker.ts +++ b/src/worker/forms-worker.ts @@ -1,11 +1,12 @@ -import { deriveFormRuntimeCapabilities } from '../utils/forms/runtime-core'; -import { resolveBuiltinFormsProvider } from '../utils/forms/provider-core'; -import { handleFormSubmissionWithConfig, handleTokenRequestWithConfig } from '../utils/forms/service-core'; -import type { TreeseedDeployConfig } from '@treeseed/sdk/platform/contracts'; +import { deriveFormRuntimeCapabilities } from '../utils/forms/runtime/runtime-core'; +import { resolveBuiltinFormsProvider } from '../utils/forms/capacity/providers/provider-core'; +import { handleFormSubmissionWithConfig, handleTokenRequestWithConfig } from '../utils/forms/service/service-core'; +import { formSubmissionResponse } from '@treeseed/ui/forms'; +import type { DeployConfig } from '@treeseed/sdk/site-contracts/platform'; import type { CloudflareRuntimeAssets, D1DatabaseLike, KvNamespaceLike } from '../types/cloudflare'; -declare const __TREESEED_DEPLOY_CONFIG__: TreeseedDeployConfig; -declare const __TREESEED_SITE_CONFIG__: { +declare const DEPLOY_CONFIG: DeployConfig; +declare const SITE_CONFIG: { site: { siteUrl: string; emailNotifications: { @@ -19,16 +20,16 @@ interface WorkerEnv { FORM_GUARD_KV: KvNamespaceLike; SITE_DATA_DB: D1DatabaseLike; ASSETS: CloudflareRuntimeAssets; - TREESEED_FORM_TOKEN_SECRET?: string; - TREESEED_TURNSTILE_SECRET_KEY?: string; - TREESEED_SMTP_HOST?: string; - TREESEED_SMTP_PORT?: string; - TREESEED_SMTP_USERNAME?: string; - TREESEED_SMTP_PASSWORD?: string; - TREESEED_SMTP_FROM?: string; - TREESEED_SMTP_REPLY_TO?: string; - TREESEED_LOCAL_DEV_MODE?: string; - TREESEED_FORMS_LOCAL_BYPASS_CLOUDFLARE_GUARDS?: string; + FORM_TOKEN_SECRET?: string; + TURNSTILE_SECRET_KEY?: string; + SMTP_HOST?: string; + SMTP_PORT?: string; + SMTP_USERNAME?: string; + SMTP_PASSWORD?: string; + SMTP_FROM?: string; + SMTP_REPLY_TO?: string; + LOCAL_DEV_MODE?: string; + FORMS_LOCAL_BYPASS_CLOUDFLARE_GUARDS?: string; } function envBoolean(value: unknown) { @@ -80,31 +81,31 @@ function serializeCookie(cookie: { name: string; value: string; options: Record< function buildSmtpConfig(env: WorkerEnv) { return { - host: env.TREESEED_SMTP_HOST ?? '', - port: Number(env.TREESEED_SMTP_PORT ?? '465'), - username: env.TREESEED_SMTP_USERNAME ?? '', - password: env.TREESEED_SMTP_PASSWORD ?? '', - from: env.TREESEED_SMTP_FROM ?? '', - replyTo: env.TREESEED_SMTP_REPLY_TO ?? '', + host: env.SMTP_HOST ?? '', + port: Number(env.SMTP_PORT ?? '465'), + username: env.SMTP_USERNAME ?? '', + password: env.SMTP_PASSWORD ?? '', + from: env.SMTP_FROM ?? '', + replyTo: env.SMTP_REPLY_TO ?? '', }; } function isSmtpEnabled(env: WorkerEnv) { const smtp = buildSmtpConfig(env); - return Boolean(__TREESEED_DEPLOY_CONFIG__.smtp?.enabled && smtp.host && smtp.port && smtp.from); + return Boolean(DEPLOY_CONFIG.smtp?.enabled && smtp.host && smtp.port && smtp.from); } function isTurnstileEnabled(env: WorkerEnv) { - return Boolean(env.TREESEED_TURNSTILE_SECRET_KEY); + return Boolean(env.TURNSTILE_SECRET_KEY); } function buildRuntime(env: WorkerEnv) { return deriveFormRuntimeCapabilities({ isCloudflareRuntime: true, - localDevMode: env.TREESEED_LOCAL_DEV_MODE === 'cloudflare' ? 'cloudflare' : null, + localDevMode: env.LOCAL_DEV_MODE === 'cloudflare' ? 'cloudflare' : null, isDevServer: false, - bypassCloudflareGuards: envBoolean(env.TREESEED_FORMS_LOCAL_BYPASS_CLOUDFLARE_GUARDS), - formsMode: __TREESEED_DEPLOY_CONFIG__.providers?.forms ?? 'store_only', + bypassCloudflareGuards: envBoolean(env.FORMS_LOCAL_BYPASS_CLOUDFLARE_GUARDS), + formsMode: DEPLOY_CONFIG.providers?.forms ?? 'store_only', smtpEnabled: isSmtpEnabled(env), turnstileEnabled: isTurnstileEnabled(env), }); @@ -113,17 +114,17 @@ function buildRuntime(env: WorkerEnv) { function buildFormConfig(env: WorkerEnv) { return { runtime: buildRuntime(env), - formsProvider: resolveBuiltinFormsProvider(__TREESEED_DEPLOY_CONFIG__.providers?.forms ?? 'store_only'), + formsProvider: resolveBuiltinFormsProvider(DEPLOY_CONFIG.providers?.forms ?? 'store_only'), bindings: { FORM_GUARD_KV: env.FORM_GUARD_KV, SITE_DATA_DB: env.SITE_DATA_DB, }, - formSecret: env.TREESEED_FORM_TOKEN_SECRET ?? '', - turnstileSecret: env.TREESEED_TURNSTILE_SECRET_KEY ?? '', - contactRouting: __TREESEED_SITE_CONFIG__.site.emailNotifications.contactRouting, - subscribeRecipients: __TREESEED_SITE_CONFIG__.site.emailNotifications.subscribeRecipients, + formSecret: env.FORM_TOKEN_SECRET ?? '', + turnstileSecret: env.TURNSTILE_SECRET_KEY ?? '', + contactRouting: SITE_CONFIG.site.emailNotifications.contactRouting, + subscribeRecipients: SITE_CONFIG.site.emailNotifications.subscribeRecipients, smtpConfig: buildSmtpConfig(env), - siteUrl: __TREESEED_SITE_CONFIG__.site.siteUrl, + siteUrl: SITE_CONFIG.site.siteUrl, }; } @@ -155,7 +156,12 @@ async function handleApiRequest(request: Request, env: WorkerEnv) { if (request.method === 'POST') { const result = await handleFormSubmissionWithConfig(context, config); - return context.redirect(result.redirectTo, 303); + return formSubmissionResponse(request, { + ok: result.ok, + code: result.code, + message: result.message, + reset: result.ok, + }, { fallbackRedirect: result.redirectTo, headers: responseHeaders }); } return new Response('Method Not Allowed', { diff --git a/templates/github/deploy-web.workflow.yml b/templates/github/deploy-web.workflow.yml index c84de2d9..64d41a90 100644 --- a/templates/github/deploy-web.workflow.yml +++ b/templates/github/deploy-web.workflow.yml @@ -67,9 +67,8 @@ __WORKING_DIRECTORY_BLOCK__ web: TREESEED_PROJECT_DOMAINS: ${{ vars.TREESEED_PROJECT_DOMAINS }} TREESEED_SITE_URL: ${{ vars.TREESEED_SITE_URL }} BETTER_AUTH_URL: ${{ vars.BETTER_AUTH_URL || vars.TREESEED_SITE_URL }} - TREESEED_CENTRAL_MARKET_API_BASE_URL: ${{ vars.TREESEED_CENTRAL_MARKET_API_BASE_URL || 'https://api.treeseed.dev' }} - TREESEED_MARKET_API_BASE_URL: ${{ vars.TREESEED_MARKET_API_BASE_URL || vars.TREESEED_CENTRAL_MARKET_API_BASE_URL || 'https://api.treeseed.dev' }} - TREESEED_CATALOG_MARKET_API_BASE_URLS: ${{ vars.TREESEED_CATALOG_MARKET_API_BASE_URLS || vars.TREESEED_MARKET_API_BASE_URL || vars.TREESEED_CENTRAL_MARKET_API_BASE_URL || 'https://api.treeseed.dev' }} + TREESEED_MARKET_API_BASE_URL: ${{ vars.TREESEED_MARKET_API_BASE_URL || 'https://api.treeseed.dev' }} + TREESEED_CATALOG_MARKET_API_BASE_URLS: ${{ vars.TREESEED_CATALOG_MARKET_API_BASE_URLS || vars.TREESEED_MARKET_API_BASE_URL || 'https://api.treeseed.dev' }} TREESEED_HOSTING_KIND: ${{ vars.TREESEED_HOSTING_KIND }} TREESEED_HOSTING_REGISTRATION: ${{ vars.TREESEED_HOSTING_REGISTRATION }} TREESEED_HOSTING_TEAM_ID: ${{ vars.TREESEED_HOSTING_TEAM_ID }} diff --git a/test/dev/integrated-dev.test.ts b/test/dev/integrated-dev.test.ts deleted file mode 100644 index 5f085e41..00000000 --- a/test/dev/integrated-dev.test.ts +++ /dev/null @@ -1,1679 +0,0 @@ -import { afterAll, describe, expect, it } from 'vitest'; -import type { SpawnOptions } from 'node:child_process'; -import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; -import { tmpdir } from 'node:os'; -import { resolve } from 'node:path'; -import { PassThrough } from 'node:stream'; -import { DatabaseSync } from 'node:sqlite'; -import { - createTreeseedIntegratedDevPlan, - runTreeseedManagedDev, - runTreeseedIntegratedDev, - runTreeseedIntegratedDevReset, -} from '../../src/dev.ts'; -import { classifyChanges, shouldIgnoreWatchPath } from '../../src/dev-watch.ts'; - -type FakeExitListener = (code: number | null, signal: NodeJS.Signals | null) => void; - -class FakeChildProcess { - readonly pid?: number; - readonly stdout = new PassThrough(); - readonly stderr = new PassThrough(); - kills: Array = []; - private readonly listeners: FakeExitListener[] = []; - - constructor(pid?: number) { - this.pid = pid; - } - - on(event: string, listener: FakeExitListener) { - if (event === 'exit') { - this.listeners.push(listener); - } - return this; - } - - kill(signal?: string | number) { - this.kills.push(signal); - return true; - } - - exit(code: number | null, signal: NodeJS.Signals | null = null) { - for (const listener of this.listeners) { - listener(code, signal); - } - } -} - -describe('Treeseed integrated dev orchestration', () => { - const tenantRoot = resolve(process.cwd(), '.fixtures/treeseed-fixtures/sites/working-site'); - let fakeAgentPackageRoot: string | null = null; - - afterAll(() => { - if (fakeAgentPackageRoot) { - rmSync(fakeAgentPackageRoot, { recursive: true, force: true }); - } - }); - - function getFakeAgentPackageRoot() { - if (fakeAgentPackageRoot) return fakeAgentPackageRoot; - const root = mkdtempSync(resolve(tmpdir(), 'treeseed-agent-package-')); - mkdirSync(resolve(root, 'scripts'), { recursive: true }); - mkdirSync(resolve(root, 'src/api'), { recursive: true }); - mkdirSync(resolve(root, 'src/services'), { recursive: true }); - writeFileSync(resolve(root, 'package.json'), JSON.stringify({ name: '@treeseed/agent', type: 'module' }, null, 2)); - writeFileSync(resolve(root, 'src/api/server.ts'), 'export {};\n'); - writeFileSync(resolve(root, 'src/services/manager.ts'), 'export {};\n'); - writeFileSync(resolve(root, 'src/services/workday-manager.ts'), 'export {};\n'); - writeFileSync(resolve(root, 'src/services/worker.ts'), 'export {};\n'); - writeFileSync(resolve(root, 'src/services/agents.ts'), 'export {};\n'); - fakeAgentPackageRoot = root; - return root; - } - - function withAgentPackageEnv(env: NodeJS.ProcessEnv = {}) { - return { - TREESEED_WEB_SERVICE_SECRET: undefined, - TREESEED_API_WEB_SERVICE_SECRET: undefined, - TREESEED_PLATFORM_RUNNER_SECRET: undefined, - ...env, - TREESEED_AGENT_PACKAGE_ROOT: getFakeAgentPackageRoot(), - }; - } - - function writeTempTenant(siteConfig: string) { - const root = mkdtempSync(resolve(tmpdir(), 'treeseed-dev-runtime-')); - mkdirSync(resolve(root, 'src'), { recursive: true }); - writeFileSync(resolve(root, 'src/manifest.yaml'), 'id: test\n'); - writeFileSync(resolve(root, 'treeseed.site.yaml'), siteConfig); - return root; - } - - function writeApiPackage(root: string) { - const apiRoot = resolve(root, 'packages/api'); - mkdirSync(resolve(apiRoot, 'src/api'), { recursive: true }); - mkdirSync(resolve(apiRoot, 'src/operations-runner'), { recursive: true }); - mkdirSync(resolve(apiRoot, 'scripts'), { recursive: true }); - writeFileSync(resolve(apiRoot, 'package.json'), JSON.stringify({ name: '@treeseed/api', type: 'module' }, null, 2)); - writeFileSync(resolve(apiRoot, 'src/api/server.ts'), 'export {};\n'); - writeFileSync(resolve(apiRoot, 'src/operations-runner/entrypoint.ts'), 'export {};\n'); - writeFileSync(resolve(apiRoot, 'scripts/migrate-db.ts'), 'export {};\n'); - return apiRoot; - } - - function writeLocalD1Project(root: string, id: string, slug = 'market') { - const d1Root = resolve(root, '.treeseed/generated/environments/local/.wrangler/state/v3/d1/miniflare-D1DatabaseObject'); - mkdirSync(d1Root, { recursive: true }); - const db = new DatabaseSync(resolve(d1Root, 'local-ui.sqlite')); - try { - db.exec(`CREATE TABLE projects ( - id TEXT PRIMARY KEY, - slug TEXT NOT NULL, - name TEXT NOT NULL, - created_at TEXT NOT NULL - )`); - db.prepare(`INSERT INTO projects (id, slug, name, created_at) VALUES (?, ?, ?, ?)`) - .run(id, slug, 'TreeSeed Market', '2026-05-15T00:00:00.000Z'); - } finally { - db.close(); - } - } - - it('creates an integrated full platform plan with local defaults', () => { - const plan = createTreeseedIntegratedDevPlan({ - cwd: tenantRoot, - env: withAgentPackageEnv({ - TREESEED_SMTP_HOST: 'smtp.mailgun.org', - TREESEED_SMTP_PORT: '587', - TREESEED_SMTP_USERNAME: 'hosted-user', - TREESEED_SMTP_PASSWORD: 'hosted-password', - }), - }); - - expect(plan.surface).toBe('integrated'); - expect(plan.setupMode).toBe('auto'); - expect(plan.feedbackMode).toBe('live'); - expect(plan.openMode).toBe('off'); - expect(plan.webUrl).toBe('http://127.0.0.1:4321'); - expect(plan.apiBaseUrl).toBe('http://127.0.0.1:3000'); - expect(plan.commands.map((command) => command.id)).toEqual(['web', 'api', 'manager', 'worker']); - expect(plan.localRuntimes.web.selected).toBe('cloudflare-wrangler-local'); - expect(plan.localRuntimes.api.selected).toBe('node-local'); - expect(plan.localRuntimes.manager.selected).toBe('node-local'); - expect(plan.localRuntimes.worker.selected).toBe('node-local'); - expect(plan.localRuntimes).not.toHaveProperty('agents'); - expect(plan.commands[0]?.localRuntime?.selected).toBe('cloudflare-wrangler-local'); - expect(plan.commands[0]?.args).toEqual(expect.arrayContaining(['dev', '--local', '--config'])); - expect(plan.setupSteps.map((step) => step.id)).toEqual( - expect.arrayContaining(['workspace-links', 'wrangler', 'wrangler-config', 'web-build']), - ); - expect(plan.setupSteps.map((step) => step.id)).not.toContain('mailpit'); - expect(plan.readyChecks.map((check) => check.id)).toEqual(['web', 'api', 'manager', 'worker']); - expect(plan.readyChecks.filter((check) => check.required).map((check) => check.id)).toEqual(['web', 'api']); - expect(plan.watchEntries.length).toBeGreaterThan(0); - expect(plan.commands[0]?.env.TREESEED_PUBLIC_DEV_WATCH_RELOAD).toBe('true'); - expect(plan.commands[0]?.env.TREESEED_SITE_URL).toBe('http://127.0.0.1:4321'); - expect(plan.commands[0]?.env.BETTER_AUTH_URL).toBe('http://127.0.0.1:4321'); - expect(plan.commands[0]?.env.TREESEED_API_BASE_URL).toBe('http://127.0.0.1:3000'); - expect(plan.commands[0]?.env.TREESEED_WEB_SERVICE_ID).toBe('web'); - expect(plan.commands[0]?.env.TREESEED_WEB_SERVICE_SECRET).toBe('treeseed-web-service-dev-secret'); - expect(plan.commands[0]?.env.TREESEED_API_WEB_SERVICE_ID).toBe('web'); - expect(plan.commands[0]?.env.TREESEED_API_WEB_SERVICE_SECRET).toBe('treeseed-web-service-dev-secret'); - expect(plan.commands[0]?.env.TREESEED_PLATFORM_RUNNER_SECRET).toBe('treeseed-platform-runner-dev-secret'); - expect(plan.commands[0]?.env.TREESEED_SMTP_HOST).toBe('127.0.0.1'); - expect(plan.commands[0]?.env.TREESEED_SMTP_PORT).toBe('1025'); - expect(plan.commands[0]?.env.TREESEED_SMTP_USERNAME).toBe(''); - expect(plan.commands[0]?.env.TREESEED_SMTP_PASSWORD).toBe(''); - expect(plan.commands[0]?.env.TREESEED_MAILPIT_SMTP_HOST).toBe('127.0.0.1'); - expect(plan.commands[0]?.env.TREESEED_MAILPIT_SMTP_PORT).toBe('1025'); - expect(plan.commands[1]?.label).toBe('Treeseed API'); - expect(plan.commands[1]?.env.PORT).toBe('3000'); - expect(plan.commands[1]?.env.TREESEED_API_REQUEST_LOGS).toBe('true'); - expect(plan.commands[2]?.label).toBe('Manager'); - expect(plan.commands[2]?.args).toEqual(expect.arrayContaining(['--mode', 'loop'])); - expect(plan.commands[2]?.env.TREESEED_MANAGER_MODE).toBe('loop'); - expect(plan.commands[2]?.env.TREESEED_AGENT_D1_PERSIST_TO).toBeUndefined(); - expect(plan.commands[3]?.env.TREESEED_AGENT_D1_PERSIST_TO).toBeUndefined(); - }, 10_000); - - it('turns the Market repo root dev plan into web API runner orchestration with managed local state', () => { - const root = mkdtempSync(resolve(tmpdir(), 'treeseed-market-dev-')); - try { - writeFileSync(resolve(root, 'package.json'), JSON.stringify({ name: '@treeseed/market', type: 'module' }, null, 2)); - writeFileSync(resolve(root, 'treeseed.site.yaml'), 'name: Market\nslug: market\n'); - const apiRoot = writeApiPackage(root); - - const plan = createTreeseedIntegratedDevPlan({ - cwd: root, - surfaces: 'web,api', - webRuntime: 'local', - env: { - TREESEED_DATABASE_URL: undefined, - TREESEED_PLATFORM_RUNNER_SECRET: undefined, - }, - }); - - expect(plan.commands.map((command) => command.id)).toEqual(['web', 'api', 'operations-runner']); - expect(plan.commands.find((command) => command.id === 'api')?.label).toBe('Treeseed API'); - expect(plan.commands.find((command) => command.id === 'api')?.command).toBe('tsx'); - expect(plan.commands.find((command) => command.id === 'api')?.args).toEqual([ - resolve(apiRoot, 'src/api/server.ts'), - ]); - expect(plan.commands.find((command) => command.id === 'api')?.cwd).toBe(apiRoot); - const runner = plan.commands.find((command) => command.id === 'operations-runner'); - expect(runner?.command).toBe('tsx'); - expect(runner?.args).toEqual(expect.arrayContaining([ - resolve(apiRoot, 'src/operations-runner/entrypoint.ts'), - 'run', - '--watch', - '--operation', - 'project:web_deployment', - ])); - expect(runner?.cwd).toBe(apiRoot); - expect(runner?.args).not.toContain('--mock-external'); - expect(runner?.env.TREESEED_DATABASE_URL).toBe('postgres://treeseed:treeseed@127.0.0.1:55432/market_local'); - expect(runner?.env.TREESEED_MARKET_DATABASE_URL).toBeUndefined(); - expect(runner?.env.TREESEED_PLATFORM_RUNNER_SECRET).toBe('treeseed-platform-runner-dev-secret'); - expect(plan.readyChecks.filter((check) => check.required).map((check) => check.id)).toEqual( - expect.arrayContaining(['web', 'api', 'operations-runner']), - ); - expect(plan.setupSteps.map((step) => step.id)).toEqual(expect.arrayContaining(['market-postgres', 'market-migrations'])); - } finally { - rmSync(root, { recursive: true, force: true }); - } - }); - - it('treats the default Market PostgreSQL URL as managed local reset state', () => { - const root = mkdtempSync(resolve(tmpdir(), 'treeseed-market-dev-default-db-')); - try { - writeFileSync(resolve(root, 'package.json'), JSON.stringify({ name: '@treeseed/market', type: 'module' }, null, 2)); - writeApiPackage(root); - - const plan = createTreeseedIntegratedDevPlan({ - cwd: root, - surfaces: 'web,api', - webRuntime: 'local', - reset: true, - env: { - TREESEED_DATABASE_URL: 'postgres://treeseed:treeseed@127.0.0.1:55432/market_local', - }, - }); - - expect(plan.commands.find((command) => command.id === 'api')?.env.TREESEED_MARKET_LOCAL_POSTGRES_MANAGED).toBe('true'); - expect(plan.setupSteps.map((step) => step.id)).toContain('market-postgres'); - expect(plan.reset?.actions.find((action) => action.id === 'market-postgres')).toMatchObject({ - status: 'planned', - label: 'Reset local Market PostgreSQL', - detail: expect.stringContaining('database'), - }); - } finally { - rmSync(root, { recursive: true, force: true }); - } - }); - - it('hides idle Market runner poll JSON from human dev logs', async () => { - const root = mkdtempSync(resolve(tmpdir(), 'treeseed-market-dev-logs-')); - const signalHandlers = new Map void>(); - const children: FakeChildProcess[] = []; - const output: string[] = []; - try { - writeFileSync(resolve(root, 'package.json'), JSON.stringify({ name: '@treeseed/market', type: 'module' }, null, 2)); - writeFileSync(resolve(root, 'treeseed.site.yaml'), 'name: Market\nslug: market\n'); - writeApiPackage(root); - - const promise = runTreeseedIntegratedDev( - { - cwd: root, - surfaces: 'api', - setupMode: 'off', - feedbackMode: 'off', - openMode: 'off', - shutdownGraceMs: 0, - env: { - TREESEED_DATABASE_URL: 'postgres://configured-market-db', - }, - }, - { - spawn() { - const child = new FakeChildProcess(); - children.push(child); - return child as never; - }, - onSignal(signal, handler) { - signalHandlers.set(signal, handler); - return () => signalHandlers.delete(signal); - }, - inspectPortOwners() { - return []; - }, - prepareEnvironment() {}, - fetch: async () => ({ ok: true }) as Response, - write(line) { - output.push(line); - }, - }, - ); - - await new Promise((resolvePromise) => setTimeout(resolvePromise, 25)); - const runner = children[1]; - runner?.stdout.write('{"ok":true,"claimed":false,"operation":null}\n'); - runner?.stdout.write('{"ok":true,"claimed":true,"operation":{"id":"op_1"}}\n'); - await new Promise((resolvePromise) => setTimeout(resolvePromise, 25)); - signalHandlers.get('SIGINT')?.(); - - await expect(promise).resolves.toBe(130); - const joined = output.join(''); - expect(joined).not.toContain('"claimed":false'); - expect(joined).toContain('"claimed":true'); - } finally { - rmSync(root, { recursive: true, force: true }); - } - }); - - it('uses the seeded local market project id for node services', () => { - const root = writeTempTenant('title: Test Site\n'); - writeLocalD1Project(root, 'project-market-local'); - try { - const plan = createTreeseedIntegratedDevPlan({ - cwd: root, - env: withAgentPackageEnv({ - TREESEED_LOCAL_DEV_MODE: 'cloudflare', - TREESEED_API_D1_LOCAL_PERSIST_TO: resolve(root, '.treeseed/generated/environments/local/.wrangler/state/v3/d1'), - }), - }); - - for (const id of ['api', 'manager', 'worker'] as const) { - expect(plan.commands.find((command) => command.id === id)?.env.TREESEED_PROJECT_ID).toBe('project-market-local'); - } - } finally { - rmSync(root, { recursive: true, force: true }); - } - }); - - it('keeps generated Wrangler D1 sqlite scoped to the web knowledge hub by default', () => { - const plan = createTreeseedIntegratedDevPlan({ - cwd: tenantRoot, - setupMode: 'off', - env: withAgentPackageEnv({ - TREESEED_API_D1_LOCAL_PERSIST_TO: '/tmp/treeseed-generated-d1', - }), - }); - - expect(plan.commands.find((command) => command.id === 'manager')?.env.TREESEED_AGENT_D1_PERSIST_TO).toBeUndefined(); - expect(plan.commands.find((command) => command.id === 'worker')?.env.TREESEED_AGENT_D1_PERSIST_TO).toBeUndefined(); - expect(plan.commands.find((command) => command.id === 'web')?.env.TREESEED_API_D1_LOCAL_PERSIST_TO).toBe('/tmp/treeseed-generated-d1'); - }); - - it('does not pass explicit D1 overrides to local agent services', () => { - const plan = createTreeseedIntegratedDevPlan({ - cwd: tenantRoot, - setupMode: 'off', - env: withAgentPackageEnv({ - TREESEED_AGENT_D1_PERSIST_TO: '/tmp/treeseed-agent.sqlite', - }), - }); - - expect(plan.commands.find((command) => command.id === 'manager')?.env.TREESEED_AGENT_D1_PERSIST_TO).toBeUndefined(); - expect(plan.commands.find((command) => command.id === 'worker')?.env.TREESEED_AGENT_D1_PERSIST_TO).toBeUndefined(); - }); - - it('selects provider-local Wrangler for Cloudflare web surfaces', () => { - const tempRoot = writeTempTenant(`name: Test -slug: test -siteUrl: https://example.com -contactEmail: hello@example.com -cloudflare: - accountId: account-123 -surfaces: - web: - provider: cloudflare - local: - runtime: provider -`); - try { - const plan = createTreeseedIntegratedDevPlan({ cwd: tempRoot, surface: 'web', setupMode: 'off', env: {} }); - - expect(plan.localRuntimes.web).toMatchObject({ - requested: 'provider', - selected: 'cloudflare-wrangler-local', - provider: 'cloudflare', - }); - expect(plan.commands[0]?.label).toBe('Cloudflare Wrangler UI'); - expect(plan.commands[0]?.args).toEqual(expect.arrayContaining([ - 'dev', - '--local', - '--config', - resolve(tempRoot, '.treeseed/generated/environments/local/wrangler.toml'), - ])); - } finally { - rmSync(tempRoot, { recursive: true, force: true }); - } - }); - - it('uses Astro local when Cloudflare web explicitly requests local runtime', () => { - const tempRoot = writeTempTenant(`name: Test -slug: test -siteUrl: https://example.com -contactEmail: hello@example.com -cloudflare: - accountId: account-123 -surfaces: - web: - provider: cloudflare - local: - runtime: local -`); - try { - const plan = createTreeseedIntegratedDevPlan({ cwd: tempRoot, surface: 'web', setupMode: 'off', env: {} }); - - expect(plan.localRuntimes.web.selected).toBe('astro-local'); - expect(plan.commands[0]?.label).toBe('Astro UI'); - expect(plan.commands[0]?.args).toEqual(expect.arrayContaining(['dev', '--host', '127.0.0.1', '--port', '4321'])); - } finally { - rmSync(tempRoot, { recursive: true, force: true }); - } - }); - - it('lets the CLI override Cloudflare provider web runtime with Astro local', () => { - const tempRoot = writeTempTenant(`name: Test -slug: test -siteUrl: https://example.com -contactEmail: hello@example.com -cloudflare: - accountId: account-123 -surfaces: - web: - provider: cloudflare - local: - runtime: provider -`); - try { - const plan = createTreeseedIntegratedDevPlan({ - cwd: tempRoot, - surface: 'web', - setupMode: 'off', - webRuntime: 'local', - env: { - TREESEED_LOCAL_DEV_MODE: 'cloudflare', - }, - }); - - expect(plan.localRuntimes.web).toMatchObject({ - requested: 'local', - selected: 'astro-local', - provider: 'cloudflare', - }); - expect(plan.commands[0]?.label).toBe('Astro UI'); - expect(plan.commands[0]?.args).toEqual(expect.arrayContaining(['dev', '--host', '127.0.0.1', '--port', '4321'])); - expect(plan.commands[0]?.env.TREESEED_LOCAL_DEV_MODE).toBeUndefined(); - expect(plan.commands[0]?.env.TREESEED_API_D1_LOCAL_PERSIST_TO).toBe( - resolve(tempRoot, '.treeseed/generated/environments/local/.wrangler/state/v3/d1'), - ); - } finally { - rmSync(tempRoot, { recursive: true, force: true }); - } - }); - - it('starts manager and worker but not agents from the default integrated runtime', () => { - const tempRoot = writeTempTenant(`name: Test -slug: test -siteUrl: https://example.com -contactEmail: hello@example.com -cloudflare: - accountId: account-123 -services: - api: - provider: railway - local: - runtime: auto -`); - try { - const plan = createTreeseedIntegratedDevPlan({ - cwd: tempRoot, - surface: 'integrated', - setupMode: 'off', - env: withAgentPackageEnv(), - }); - - expect(plan.commands.map((command) => command.id)).toEqual(['web', 'api', 'manager', 'worker']); - expect(plan.localRuntimes).toHaveProperty('api'); - expect(plan.localRuntimes).toHaveProperty('manager'); - expect(plan.localRuntimes).toHaveProperty('worker'); - expect(plan.localRuntimes).not.toHaveProperty('agents'); - } finally { - rmSync(tempRoot, { recursive: true, force: true }); - } - }); - - it('fails when provider runtime is required but unsupported', () => { - const tempRoot = writeTempTenant(`name: Test -slug: test -siteUrl: https://example.com -contactEmail: hello@example.com -cloudflare: - accountId: account-123 -surfaces: - web: - provider: static-host - local: - runtime: provider -`); - try { - expect(() => createTreeseedIntegratedDevPlan({ cwd: tempRoot, surface: 'web', setupMode: 'off', env: {} })) - .toThrow(/Local provider runtime is not supported/u); - } finally { - rmSync(tempRoot, { recursive: true, force: true }); - } - }); - - it('preserves explicit env values and lets the supervisor own live feedback', () => { - const plan = createTreeseedIntegratedDevPlan({ - cwd: tenantRoot, - watch: true, - env: withAgentPackageEnv({ - TREESEED_API_BASE_URL: 'https://override.example.com', - PORT: '4400', - }), - }); - - expect(plan.apiBaseUrl).toBe('https://override.example.com'); - expect(plan.commands[0]?.args).not.toContain('--watch-path'); - expect(plan.watchEntries.length).toBeGreaterThan(0); - expect(plan.commands[0]?.env.TREESEED_PUBLIC_DEV_WATCH_RELOAD).toBe('true'); - }); - - it('ignores generated and runtime files in watched development roots', () => { - const sourceRoot = resolve(tenantRoot, 'packages/core/src'); - const publicRoot = resolve(tenantRoot, 'public'); - - expect(shouldIgnoreWatchPath(resolve(sourceRoot, 'services/.ts-run-123.mjs'), sourceRoot)).toBe(true); - expect(shouldIgnoreWatchPath(resolve(sourceRoot, '.agent-worktrees/task/src/page.ts'), sourceRoot)).toBe(true); - expect(shouldIgnoreWatchPath(resolve(sourceRoot, 'services/manager.ts'), sourceRoot)).toBe(false); - expect(shouldIgnoreWatchPath(resolve(publicRoot, '__treeseed/dev-reload.json'), publicRoot)).toBe(true); - expect(shouldIgnoreWatchPath(resolve(publicRoot, 'books/example.json'), publicRoot)).toBe(true); - expect(shouldIgnoreWatchPath(resolve(publicRoot, 'images/logo.png'), publicRoot)).toBe(false); - }); - - it('classifies dev changes by restart scope', () => { - const root = mkdtempSync(resolve(tmpdir(), 'treeseed-dev-watch-')); - try { - const tenantSource = resolve(root, 'tenant/src/pages/index.astro'); - const tenantConfig = resolve(root, 'tenant/treeseed.site.yaml'); - const sdkSource = resolve(root, 'packages/sdk/src/index.ts'); - const agentSource = resolve(root, 'packages/agent/src/services/worker.ts'); - const cliDevHandler = resolve(root, 'packages/cli/src/cli/handlers/dev.ts'); - const entries = [ - { kind: 'tenant' as const, root: resolve(root, 'tenant/src') }, - { kind: 'tenant' as const, root: resolve(root, 'tenant/treeseed.site.yaml') }, - { kind: 'sdk' as const, root: resolve(root, 'packages/sdk/src') }, - { kind: 'agent' as const, root: resolve(root, 'packages/agent/src') }, - { kind: 'cli' as const, root: cliDevHandler, restartRequired: true }, - ]; - - expect(classifyChanges([tenantSource], entries)).toMatchObject({ - tenantChanged: true, - tenantApiChanged: false, - sdkChanged: false, - agentChanged: false, - commandImplementationChanged: false, - }); - expect(classifyChanges([tenantConfig], entries)).toMatchObject({ - tenantChanged: true, - tenantApiChanged: true, - }); - expect(classifyChanges([sdkSource, agentSource], entries)).toMatchObject({ - sdkChanged: true, - agentChanged: true, - commandImplementationChanged: false, - }); - expect(classifyChanges([cliDevHandler], entries)).toMatchObject({ - cliChanged: true, - commandImplementationChanged: true, - }); - } finally { - rmSync(root, { recursive: true, force: true }); - } - }); - - it('lets explicit API port override the shared web environment value', () => { - const plan = createTreeseedIntegratedDevPlan({ - cwd: tenantRoot, - apiPort: 4401, - env: withAgentPackageEnv({ - TREESEED_API_BASE_URL: 'https://override.example.com', - PORT: '4400', - }), - }); - - expect(plan.apiBaseUrl).toBe('http://127.0.0.1:4401'); - expect(plan.commands.map((command) => command.id)).toEqual(['web', 'api', 'manager', 'worker']); - expect(plan.commands[0]?.env.TREESEED_API_BASE_URL).toBe('http://127.0.0.1:4401'); - expect(plan.commands[1]?.env.PORT).toBe('4401'); - }); - - it('plans comma-separated surfaces in canonical order without duplicates', () => { - const plan = createTreeseedIntegratedDevPlan({ - cwd: tenantRoot, - surfaces: 'worker,web,integrated,agents,api', - setupMode: 'off', - env: withAgentPackageEnv(), - }); - - expect(plan.commands.map((command) => command.id)).toEqual(['web', 'api', 'manager', 'worker', 'agents']); - expect(plan.localRuntimes).toHaveProperty('agents'); - }); - - it('plans the all surface as the full task runtime without the legacy agents loop', () => { - const plan = createTreeseedIntegratedDevPlan({ - cwd: tenantRoot, - surface: 'all', - setupMode: 'off', - env: withAgentPackageEnv(), - }); - - expect(plan.surface).toBe('all'); - expect(plan.commands.map((command) => command.id)).toEqual(['web', 'api', 'manager', 'worker']); - expect(plan.localRuntimes).toHaveProperty('web'); - expect(plan.localRuntimes).toHaveProperty('api'); - expect(plan.localRuntimes).toHaveProperty('manager'); - expect(plan.localRuntimes).toHaveProperty('worker'); - expect(plan.localRuntimes).not.toHaveProperty('agents'); - }); - - it('plans explicit API and service surfaces with Node runtimes', () => { - const apiPlan = createTreeseedIntegratedDevPlan({ - cwd: tenantRoot, - surface: 'api', - setupMode: 'off', - env: withAgentPackageEnv(), - }); - expect(apiPlan.webUrl).toBeNull(); - expect(apiPlan.commands.map((command) => command.id)).toEqual(['api']); - expect(apiPlan.localRuntimes).not.toHaveProperty('web'); - expect(apiPlan.readyChecks.map((check) => check.id)).toEqual(['api']); - - const servicesPlan = createTreeseedIntegratedDevPlan({ - cwd: tenantRoot, - surface: 'services', - setupMode: 'off', - env: withAgentPackageEnv(), - }); - expect(servicesPlan.commands.map((command) => command.id)).toEqual(['api', 'manager', 'worker', 'agents']); - expect(servicesPlan.readyChecks.filter((check) => check.required).map((check) => check.id)).toEqual(['api']); - expect(servicesPlan.readyChecks.filter((check) => !check.required).map((check) => check.id)).toEqual([ - 'manager', - 'worker', - 'agents', - ]); - }); - - it('emits a structured plan and exits without spawning services', async () => { - const output: string[] = []; - const exitCode = await runTreeseedIntegratedDev( - { - cwd: tenantRoot, - plan: true, - json: true, - setupMode: 'off', - env: withAgentPackageEnv({ - GH_TOKEN: 'test-token', - TREESEED_API_AUTH_SECRET: 'test-secret', - }), - }, - { - spawn() { - throw new Error('plan mode should not spawn child processes'); - }, - prepareEnvironment() {}, - write(line) { - output.push(line); - }, - }, - ); - - expect(exitCode).toBe(0); - const parsed = JSON.parse(output.join('')); - expect(parsed.kind).toBe('treeseed.dev.plan'); - expect(parsed.payload.commands.map((command: { id: string }) => command.id)).toEqual(['web', 'api', 'manager', 'worker']); - expect(parsed.payload.restartPolicy.commandImplementationChangesRequireRestart).toBe(true); - expect(parsed.payload.restartPolicy.agentChanges).toBe('defer'); - expect(parsed.payload.commands[0].env.GH_TOKEN).toBe('[redacted]'); - expect(parsed.payload.commands[0].env.TREESEED_API_AUTH_SECRET).toBe('[redacted]'); - }); - - it('starts a managed worktree dev instance with durable state, logs, ports, and discovery metadata', async () => { - const tempRoot = writeTempTenant(`name: Test -slug: test -siteUrl: https://example.com -contactEmail: hello@example.com -surfaces: - web: - provider: cloudflare - local: - runtime: local -`); - const previousCacheHome = process.env.XDG_CACHE_HOME; - const childPid = 55_555; - const output: string[] = []; - const spawnCalls: Array<{ command: string; args: string[]; options: SpawnOptions }> = []; - - try { - process.env.XDG_CACHE_HOME = resolve(tempRoot, '.cache'); - const exitCode = await runTreeseedManagedDev( - { - action: 'start', - cwd: tempRoot, - surface: 'web', - setupMode: 'off', - feedbackMode: 'off', - openMode: 'off', - json: true, - readinessTimeoutMs: 1, - }, - { - supervisorCommand: 'node', - supervisorArgs: ['dev-platform.js'], - spawn(command, args, options) { - spawnCalls.push({ command, args, options }); - return new FakeChildProcess(childPid) as never; - }, - processIsAlive(pid) { - return pid === childPid; - }, - inspectPortOwners() { - return []; - }, - fetch: async () => ({ ok: false }) as Response, - write(line) { - output.push(line); - }, - }, - ); - - expect(exitCode).toBe(1); - expect(spawnCalls).toHaveLength(1); - expect(spawnCalls[0]?.command).toBe('node'); - expect(spawnCalls[0]?.args).toEqual(expect.arrayContaining(['dev-platform.js', '--port', '4321', '--api-port', '3000'])); - expect(spawnCalls[0]?.args).not.toContain('--json'); - expect(spawnCalls[0]?.options.detached).toBe(true); - expect(spawnCalls[0]?.options.stdio).toEqual(['ignore', expect.any(Number), expect.any(Number)]); - expect(spawnCalls[0]?.options.env).toMatchObject({ - TREESEED_MANAGED_DEV_INSTANCE: '1', - TREESEED_MANAGED_DEV_SUPPRESS_STDIO: '1', - }); - expect(Object.keys(spawnCalls[0]?.options.env ?? {}).some((key) => /MAILPIT_.*CONTAINER/u.test(key))).toBe(false); - - const parsed = JSON.parse(output.join('')); - expect(parsed.kind).toBe('treeseed.dev.start'); - expect(parsed.ok).toBe(false); - expect(parsed.payload).toMatchObject({ - status: 'degraded', - pid: childPid, - processGroupId: childPid, - runtimeScope: 'web', - ports: { web: 4321, api: 3000, postgres: 55432, mailpitSmtp: 1025 }, - urls: { web: 'http://127.0.0.1:4321' }, - }); - - const instancePath = resolve(tempRoot, '.treeseed/dev/instances/web.json'); - const pidPath = resolve(tempRoot, '.treeseed/dev/pids/web.pid'); - const logPath = resolve(tempRoot, '.treeseed/logs/dev-web.jsonl'); - expect(JSON.parse(readFileSync(instancePath, 'utf8'))).toMatchObject({ - kind: 'treeseed.dev.instance', - projectRoot: tempRoot, - worktreeRoot: tempRoot, - status: 'degraded', - pid: childPid, - processGroupId: childPid, - logPath, - }); - expect(readFileSync(pidPath, 'utf8').trim()).toBe(String(childPid)); - expect(existsSync(logPath)).toBe(true); - expect(existsSync(resolve(tempRoot, '.cache/treeseed/dev-instances'))).toBe(true); - } finally { - if (previousCacheHome === undefined) { - delete process.env.XDG_CACHE_HOME; - } else { - process.env.XDG_CACHE_HOME = previousCacheHome; - } - rmSync(tempRoot, { recursive: true, force: true }); - } - }); - - it('reports stale managed dev instances and stops only the owned process group', async () => { - const tempRoot = writeTempTenant(`name: Test -slug: test -siteUrl: https://example.com -contactEmail: hello@example.com -surfaces: - web: - provider: cloudflare - local: - runtime: local -`); - const previousCacheHome = process.env.XDG_CACHE_HOME; - const childPid = 55_556; - try { - process.env.XDG_CACHE_HOME = resolve(tempRoot, '.cache'); - const startOutput: string[] = []; - await runTreeseedManagedDev( - { - action: 'start', - cwd: tempRoot, - surface: 'web', - setupMode: 'off', - feedbackMode: 'off', - openMode: 'off', - json: true, - readinessTimeoutMs: 1, - }, - { - supervisorCommand: 'node', - supervisorArgs: ['dev-platform.js'], - spawn() { - return new FakeChildProcess(childPid) as never; - }, - processIsAlive(pid) { - return pid === childPid; - }, - inspectPortOwners() { - return []; - }, - fetch: async () => ({ ok: false }) as Response, - write(line) { - startOutput.push(line); - }, - }, - ); - - const statusOutput: string[] = []; - const statusExit = await runTreeseedManagedDev( - { action: 'status', cwd: tempRoot, json: true }, - { - processIsAlive() { - return false; - }, - write(line) { - statusOutput.push(line); - }, - }, - ); - expect(statusExit).toBe(0); - const status = JSON.parse(statusOutput.join('')); - expect(status.kind).toBe('treeseed.dev.status'); - expect(status.payload[0]).toMatchObject({ - status: 'stale', - pid: childPid, - staleReason: `Process ${childPid} is not running.`, - }); - - const livePids = new Set([childPid]); - const killCalls: Array<{ pid: number; signal: NodeJS.Signals }> = []; - const stopOutput: string[] = []; - const stopExit = await runTreeseedManagedDev( - { action: 'stop', cwd: tempRoot, json: true, shutdownGraceMs: 0 }, - { - processIsAlive(pid) { - return livePids.has(pid); - }, - killProcess(pid, signal) { - killCalls.push({ pid, signal }); - livePids.delete(Math.abs(pid)); - }, - write(line) { - stopOutput.push(line); - }, - }, - ); - - expect(stopExit).toBe(0); - expect(killCalls).toEqual([{ pid: process.platform === 'win32' ? childPid : -childPid, signal: 'SIGTERM' }]); - const stopped = JSON.parse(stopOutput.join('')); - expect(stopped.kind).toBe('treeseed.dev.stop'); - expect(stopped.payload[0].status).toBe('stopped'); - expect(existsSync(resolve(tempRoot, '.treeseed/dev/instances/web.json'))).toBe(false); - expect(existsSync(resolve(tempRoot, '.treeseed/dev/pids/web.pid'))).toBe(false); - } finally { - if (previousCacheHome === undefined) { - delete process.env.XDG_CACHE_HOME; - } else { - process.env.XDG_CACHE_HOME = previousCacheHome; - } - rmSync(tempRoot, { recursive: true, force: true }); - } - }); - - it('replaces an active previous dev runtime before spawning services', async () => { - const tempRoot = writeTempTenant(`name: Test -slug: test -siteUrl: https://example.com -contactEmail: hello@example.com -surfaces: - web: - provider: cloudflare - local: - runtime: local -`); - const previousPid = 98_765; - const runtimePath = resolve(tempRoot, '.treeseed/generated/dev/runtime-web.json'); - const legacyRuntimePath = resolve(tempRoot, '.treeseed/generated/dev/runtime.json'); - const signalHandlers = new Map void>(); - const livePids = new Set([previousPid]); - const killCalls: Array<{ pid: number; signal: NodeJS.Signals }> = []; - let spawnCount = 0; - - try { - mkdirSync(resolve(tempRoot, '.treeseed/generated/dev'), { recursive: true }); - writeFileSync(legacyRuntimePath, `${JSON.stringify({ - pid: previousPid, - tenantRoot: tempRoot, - startedAt: '2026-05-06T00:00:00.000Z', - }, null, 2)}\n`); - - const promise = runTreeseedIntegratedDev( - { - cwd: tempRoot, - surface: 'web', - setupMode: 'off', - feedbackMode: 'off', - openMode: 'off', - shutdownGraceMs: 0, - force: true, - }, - { - spawn() { - spawnCount += 1; - return new FakeChildProcess() as never; - }, - killProcess(pid, signal) { - killCalls.push({ pid, signal }); - livePids.delete(pid); - }, - processIsAlive(pid) { - return livePids.has(pid); - }, - inspectPortOwners() { - return []; - }, - onSignal(signal, handler) { - signalHandlers.set(signal, handler); - return () => signalHandlers.delete(signal); - }, - prepareEnvironment() {}, - fetch: async () => ({ ok: true }) as Response, - }, - ); - - await new Promise((resolvePromise) => setTimeout(resolvePromise, 25)); - expect(killCalls).toEqual([{ pid: previousPid, signal: 'SIGTERM' }]); - expect(spawnCount).toBe(1); - expect(JSON.parse(readFileSync(runtimePath, 'utf8'))).toMatchObject({ - pid: process.pid, - tenantRoot: tempRoot, - commandIds: ['web'], - }); - expect(existsSync(legacyRuntimePath)).toBe(false); - - signalHandlers.get('SIGINT')?.(); - await expect(promise).resolves.toBe(130); - expect(existsSync(runtimePath)).toBe(false); - } finally { - rmSync(tempRoot, { recursive: true, force: true }); - } - }); - - it('fails with an existing runtime warning unless force is requested', async () => { - const tempRoot = writeTempTenant(`name: Test -slug: test -siteUrl: https://example.com -contactEmail: hello@example.com -surfaces: - web: - provider: cloudflare - local: - runtime: local -`); - const previousPid = 98_765; - const legacyRuntimePath = resolve(tempRoot, '.treeseed/generated/dev/runtime.json'); - const output: string[] = []; - let spawnCount = 0; - - try { - mkdirSync(resolve(tempRoot, '.treeseed/generated/dev'), { recursive: true }); - writeFileSync(legacyRuntimePath, `${JSON.stringify({ - pid: previousPid, - tenantRoot: tempRoot, - startedAt: '2026-05-06T00:00:00.000Z', - }, null, 2)}\n`); - - const exitCode = await runTreeseedIntegratedDev( - { - cwd: tempRoot, - surface: 'web', - setupMode: 'off', - feedbackMode: 'off', - openMode: 'off', - json: true, - }, - { - spawn() { - spawnCount += 1; - return new FakeChildProcess() as never; - }, - processIsAlive(pid) { - return pid === previousPid; - }, - inspectPortOwners() { - return []; - }, - prepareEnvironment() {}, - write(line) { - output.push(line); - }, - }, - ); - - expect(exitCode).toBe(1); - expect(spawnCount).toBe(0); - expect(output.join('')).toContain('existing runtime or service'); - expect(existsSync(legacyRuntimePath)).toBe(true); - } finally { - rmSync(tempRoot, { recursive: true, force: true }); - } - }); - - it('fails with a port-owner warning unless force is requested', async () => { - const tempRoot = writeTempTenant(`name: Test -slug: test -siteUrl: https://example.com -contactEmail: hello@example.com -surfaces: - web: - provider: cloudflare - local: - runtime: local -`); - const output: string[] = []; - - try { - const exitCode = await runTreeseedIntegratedDev( - { - cwd: tempRoot, - surface: 'web', - setupMode: 'off', - feedbackMode: 'off', - openMode: 'off', - json: true, - }, - { - spawn() { - throw new Error('spawn should not be reached'); - }, - processIsAlive() { - return false; - }, - inspectPortOwners() { - return [{ port: 4321, pid: 22_222, processName: 'astro', detail: 'LISTEN 127.0.0.1:4321 pid=22222' }]; - }, - prepareEnvironment() {}, - write(line) { - output.push(line); - }, - }, - ); - - expect(exitCode).toBe(1); - expect(output.join('')).toContain('required port'); - expect(output.join('')).toContain('22222'); - } finally { - rmSync(tempRoot, { recursive: true, force: true }); - } - }); - - it('force-stops listeners on required ports before spawning services', async () => { - const tempRoot = writeTempTenant(`name: Test -slug: test -siteUrl: https://example.com -contactEmail: hello@example.com -surfaces: - web: - provider: cloudflare - local: - runtime: local -`); - const signalHandlers = new Map void>(); - const livePids = new Set([22_222]); - const killCalls: Array<{ pid: number; signal: NodeJS.Signals }> = []; - let spawnCount = 0; - let inspectCount = 0; - - try { - const promise = runTreeseedIntegratedDev( - { - cwd: tempRoot, - surface: 'web', - setupMode: 'off', - feedbackMode: 'off', - openMode: 'off', - shutdownGraceMs: 0, - force: true, - }, - { - spawn() { - spawnCount += 1; - return new FakeChildProcess() as never; - }, - killProcess(pid, signal) { - killCalls.push({ pid, signal }); - livePids.delete(pid); - }, - processIsAlive(pid) { - return livePids.has(pid); - }, - inspectPortOwners() { - inspectCount += 1; - return inspectCount === 1 - ? [{ port: 4321, pid: 22_222, processName: 'astro', detail: 'LISTEN 127.0.0.1:4321 pid=22222' }] - : []; - }, - onSignal(signal, handler) { - signalHandlers.set(signal, handler); - return () => signalHandlers.delete(signal); - }, - prepareEnvironment() {}, - fetch: async () => ({ ok: true }) as Response, - }, - ); - - await new Promise((resolvePromise) => setTimeout(resolvePromise, 25)); - expect(killCalls).toEqual([{ pid: 22_222, signal: 'SIGTERM' }]); - expect(spawnCount).toBe(1); - - signalHandlers.get('SIGINT')?.(); - await expect(promise).resolves.toBe(130); - } finally { - rmSync(tempRoot, { recursive: true, force: true }); - } - }); - - it('keeps disjoint dev runtimes alive when starting manager services beside web api', async () => { - const tempRoot = writeTempTenant(`name: Test -slug: test -siteUrl: https://example.com -contactEmail: hello@example.com -surfaces: - web: - provider: cloudflare - local: - runtime: local -`); - const webApiPid = 98_765; - const managerPid = 98_766; - const webApiRuntimePath = resolve(tempRoot, '.treeseed/generated/dev/runtime-web-api.json'); - const managerRuntimePath = resolve(tempRoot, '.treeseed/generated/dev/runtime-manager-worker.json'); - const signalHandlers = new Map void>(); - const livePids = new Set([webApiPid, managerPid]); - const killCalls: Array<{ pid: number; signal: NodeJS.Signals }> = []; - let spawnCount = 0; - - try { - mkdirSync(resolve(tempRoot, '.treeseed/generated/dev'), { recursive: true }); - writeFileSync(webApiRuntimePath, `${JSON.stringify({ - pid: webApiPid, - tenantRoot: tempRoot, - commandIds: ['web', 'api'], - startedAt: '2026-05-06T00:00:00.000Z', - }, null, 2)}\n`); - writeFileSync(managerRuntimePath, `${JSON.stringify({ - pid: managerPid, - tenantRoot: tempRoot, - commandIds: ['manager', 'worker'], - startedAt: '2026-05-06T00:01:00.000Z', - }, null, 2)}\n`); - - const promise = runTreeseedIntegratedDev( - { - cwd: tempRoot, - surfaces: 'manager,worker', - setupMode: 'off', - feedbackMode: 'off', - openMode: 'off', - shutdownGraceMs: 0, - force: true, - env: withAgentPackageEnv(), - }, - { - spawn() { - spawnCount += 1; - return new FakeChildProcess() as never; - }, - killProcess(pid, signal) { - killCalls.push({ pid, signal }); - livePids.delete(pid); - }, - processIsAlive(pid) { - return livePids.has(pid); - }, - onSignal(signal, handler) { - signalHandlers.set(signal, handler); - return () => signalHandlers.delete(signal); - }, - prepareEnvironment() {}, - fetch: async () => ({ ok: true }) as Response, - }, - ); - - await new Promise((resolvePromise) => setTimeout(resolvePromise, 25)); - expect(killCalls).toEqual([{ pid: managerPid, signal: 'SIGTERM' }]); - expect(spawnCount).toBe(2); - expect(JSON.parse(readFileSync(webApiRuntimePath, 'utf8'))).toMatchObject({ - pid: webApiPid, - commandIds: ['web', 'api'], - }); - expect(JSON.parse(readFileSync(managerRuntimePath, 'utf8'))).toMatchObject({ - pid: process.pid, - commandIds: ['manager', 'worker'], - }); - - signalHandlers.get('SIGINT')?.(); - await expect(promise).resolves.toBe(130); - expect(existsSync(webApiRuntimePath)).toBe(true); - expect(existsSync(managerRuntimePath)).toBe(false); - } finally { - rmSync(tempRoot, { recursive: true, force: true }); - } - }); - - it('plans dev reset against runtime state while preserving configuration paths', () => { - const tempRoot = mkdtempSync(resolve(tmpdir(), 'treeseed-dev-reset-plan-')); - const d1Path = resolve(tempRoot, '.wrangler/state/v3/d1'); - const envPath = resolve(tempRoot, '.treeseed/generated/environments/local'); - try { - mkdirSync(d1Path, { recursive: true }); - mkdirSync(envPath, { recursive: true }); - mkdirSync(resolve(tempRoot, '.treeseed/generated/worker'), { recursive: true }); - writeFileSync(resolve(tempRoot, '.treeseed/generated/worker/index.js'), 'export {};\n'); - writeFileSync(resolve(tempRoot, 'treeseed.site.yaml'), 'site: test\n'); - - const plan = createTreeseedIntegratedDevPlan({ - cwd: tempRoot, - reset: true, - surface: 'web', - setupMode: 'off', - env: {}, - }); - - expect(plan.reset?.enabled).toBe(true); - expect(plan.reset?.actions.map((action) => action.id)).toEqual([ - 'root-wrangler-state', - 'wrangler-tmp', - 'worker-bundle', - 'dev-reload', - ]); - expect(plan.reset?.actions.find((action) => action.id === 'root-wrangler-state')?.path).toBe(resolve(tempRoot, '.wrangler/state/v3')); - expect(plan.commands[0]?.env.TREESEED_DEV_RESET_ID).toEqual(expect.any(String)); - expect(plan.reset?.preserved).toEqual(expect.arrayContaining([ - 'treeseed.site.yaml', - '.treeseed/generated/environments', - '.treeseed/config', - '.treeseed/state', - '.treeseed/workflow', - ])); - expect(existsSync(envPath)).toBe(true); - } finally { - rmSync(tempRoot, { recursive: true, force: true }); - } - }); - - it('does not mutate reset targets when reset is only planned', async () => { - const tempRoot = mkdtempSync(resolve(tmpdir(), 'treeseed-dev-reset-dry-')); - const d1Path = resolve(tempRoot, '.wrangler/state/v3/d1'); - const workerPath = resolve(tempRoot, '.treeseed/generated/worker'); - try { - mkdirSync(d1Path, { recursive: true }); - mkdirSync(workerPath, { recursive: true }); - writeFileSync(resolve(workerPath, 'index.js'), 'export {};\n'); - - const output: string[] = []; - const exitCode = await runTreeseedIntegratedDev( - { cwd: tempRoot, reset: true, plan: true, json: true, setupMode: 'off', surface: 'web' }, - { - spawn() { - throw new Error('plan mode should not spawn child processes'); - }, - removePath() { - throw new Error('plan mode should not remove paths'); - }, - stopMailpitContainers() { - throw new Error('plan mode should not stop Mailpit'); - }, - prepareEnvironment() {}, - write(line) { - output.push(line); - }, - }, - ); - - expect(exitCode).toBe(0); - const parsed = JSON.parse(output.join('')); - expect(parsed.kind).toBe('treeseed.dev.plan'); - expect(parsed.payload.reset.actions.find((action: { id: string }) => action.id === 'root-wrangler-state')?.status).toBe('planned'); - expect(existsSync(d1Path)).toBe(true); - expect(existsSync(workerPath)).toBe(true); - } finally { - rmSync(tempRoot, { recursive: true, force: true }); - } - }); - - it('clears disposable reset targets and leaves configuration paths intact', () => { - const tempRoot = mkdtempSync(resolve(tmpdir(), 'treeseed-dev-reset-')); - const d1Path = resolve(tempRoot, '.wrangler/state/v3/d1'); - const rootKvPath = resolve(tempRoot, '.wrangler/state/v3/kv/miniflare-KVNamespaceObject'); - const generatedStatePath = resolve(tempRoot, '.treeseed/generated/environments/local/.wrangler/state/v3'); - const generatedD1Path = resolve(generatedStatePath, 'd1'); - const generatedKvPath = resolve(generatedStatePath, 'kv/miniflare-KVNamespaceObject'); - const generatedR2Path = resolve(generatedStatePath, 'r2/miniflare-R2BucketObject'); - const generatedCachePath = resolve(generatedStatePath, 'cache/miniflare-CacheObject'); - const legacySqlitePath = resolve(tempRoot, '.treeseed/generated/environments/local/site-data.sqlite'); - const tmpPath = resolve(tempRoot, '.wrangler/tmp'); - const workerPath = resolve(tempRoot, '.treeseed/generated/worker'); - const envPath = resolve(tempRoot, '.treeseed/generated/environments/local'); - const reloadPath = resolve(tempRoot, 'public/__treeseed/dev-reload.json'); - try { - for (const path of [d1Path, rootKvPath, generatedD1Path, generatedKvPath, generatedR2Path, generatedCachePath, tmpPath, workerPath, envPath]) { - mkdirSync(path, { recursive: true }); - } - writeFileSync(resolve(rootKvPath, 'metadata.sqlite'), 'kv\n'); - writeFileSync(resolve(generatedD1Path, 'site-data.sqlite'), 'd1\n'); - writeFileSync(resolve(generatedKvPath, 'metadata.sqlite'), 'kv\n'); - writeFileSync(resolve(generatedR2Path, 'bucket.sqlite'), 'r2\n'); - writeFileSync(resolve(generatedCachePath, 'metadata.sqlite'), 'cache\n'); - mkdirSync(resolve(tempRoot, 'public/__treeseed'), { recursive: true }); - writeFileSync(legacySqlitePath, 'sqlite\n'); - writeFileSync(reloadPath, '{}\n'); - writeFileSync(resolve(tempRoot, 'treeseed.site.yaml'), 'site: test\n'); - const plan = createTreeseedIntegratedDevPlan({ - cwd: tempRoot, - reset: true, - surface: 'web', - setupMode: 'off', - env: {}, - }); - const events: string[] = []; - const result = runTreeseedIntegratedDevReset(plan.reset, { json: true }, { - write(line) { - events.push(line); - }, - removePath(path) { - rmSync(path, { recursive: true, force: true }); - }, - stopMailpitContainers() { - return true; - }, - resetMarketPostgres() { - return true; - }, - }); - - expect(result?.actions.filter((action) => action.kind === 'path' && action.id !== 'dev-reload').every((action) => action.status === 'removed')).toBe(true); - expect(result?.actions.find((action) => action.id === 'mailpit')).toBeUndefined(); - expect(result?.actions.find((action) => action.id === 'dev-reload')?.status).toBe('refreshed'); - expect(existsSync(d1Path)).toBe(false); - expect(existsSync(rootKvPath)).toBe(false); - expect(existsSync(generatedStatePath)).toBe(false); - expect(existsSync(legacySqlitePath)).toBe(false); - expect(existsSync(tmpPath)).toBe(false); - expect(existsSync(workerPath)).toBe(false); - expect(JSON.parse(readFileSync(reloadPath, 'utf8'))).toMatchObject({ buildId: expect.any(String) }); - expect(existsSync(envPath)).toBe(true); - expect(existsSync(resolve(tempRoot, 'treeseed.site.yaml'))).toBe(true); - expect(events.some((line) => line.includes('"type":"reset"'))).toBe(true); - } finally { - rmSync(tempRoot, { recursive: true, force: true }); - } - }); - - it('restarts a crashed required child process instead of exiting', async () => { - const spawns: Array<{ command: string; args: string[]; options: SpawnOptions }> = []; - const children = Array.from({ length: 2 }, () => new FakeChildProcess()); - const signalHandlers = new Map void>(); - let settled = false; - - const promise = runTreeseedIntegratedDev( - { cwd: tenantRoot, surface: 'web', setupMode: 'off', feedbackMode: 'off', openMode: 'off', shutdownGraceMs: 0 }, - { - spawn(command, args, options) { - spawns.push({ command, args, options }); - return children[spawns.length - 1] as never; - }, - onSignal(signal, handler) { - signalHandlers.set(signal, handler); - return () => signalHandlers.delete(signal); - }, - inspectPortOwners() { - return []; - }, - prepareEnvironment() {}, - fetch: async () => ({ ok: true }) as Response, - }, - ); - promise.then(() => { - settled = true; - }); - - await new Promise((resolvePromise) => setTimeout(resolvePromise, 25)); - expect(spawns).toHaveLength(1); - children[0]?.exit(1); - await new Promise((resolvePromise) => setTimeout(resolvePromise, 1_100)); - expect(spawns).toHaveLength(2); - expect(settled).toBe(false); - signalHandlers.get('SIGINT')?.(); - await expect(promise).resolves.toBe(130); - expect(signalHandlers.size).toBe(0); - }); - - it('prints the ready URL without opening a browser by default', async () => { - const child = new FakeChildProcess(); - const signalHandlers = new Map void>(); - const output: string[] = []; - let openCalls = 0; - - const promise = runTreeseedIntegratedDev( - { cwd: tenantRoot, surface: 'web', setupMode: 'off', feedbackMode: 'off', shutdownGraceMs: 0 }, - { - spawn() { - return child as never; - }, - onSignal(signal, handler) { - signalHandlers.set(signal, handler); - return () => signalHandlers.delete(signal); - }, - inspectPortOwners() { - return []; - }, - prepareEnvironment() {}, - fetch: async () => ({ ok: true }) as Response, - openBrowser() { - openCalls += 1; - }, - write(line) { - output.push(line); - }, - }, - ); - - await new Promise((resolvePromise) => setTimeout(resolvePromise, 25)); - expect(openCalls).toBe(0); - expect(output.some((line) => line.includes('Treeseed dev ready at http://127.0.0.1:4321.'))).toBe(true); - signalHandlers.get('SIGINT')?.(); - await expect(promise).resolves.toBe(130); - }); - - it('shuts down children on parent signals', async () => { - const children = Array.from({ length: 1 }, () => { - const child = new FakeChildProcess(); - const kill = child.kill.bind(child); - child.kill = (signal?: string | number) => { - kill(signal); - child.exit(null, typeof signal === 'string' ? signal as NodeJS.Signals : null); - return true; - }; - return child; - }); - let spawnCount = 0; - const signalHandlers = new Map void>(); - - const promise = runTreeseedIntegratedDev( - { cwd: tenantRoot, surface: 'web', setupMode: 'off', feedbackMode: 'off', openMode: 'off', shutdownGraceMs: 0 }, - { - spawn() { - spawnCount += 1; - return children[spawnCount - 1] as never; - }, - onSignal(signal, handler) { - signalHandlers.set(signal, handler); - return () => signalHandlers.delete(signal); - }, - inspectPortOwners() { - return []; - }, - prepareEnvironment() {}, - fetch: async () => ({ ok: true }) as Response, - }, - ); - - await new Promise((resolvePromise) => setTimeout(resolvePromise, 25)); - signalHandlers.get('SIGINT')?.(); - await expect(promise).resolves.toBe(130); - for (const child of children) { - expect(child.kills).toEqual(['SIGTERM', 'SIGKILL']); - } - }); - - it('keeps supervising after readiness until a parent signal arrives', async () => { - const signalHandlers = new Map void>(); - let spawnCount = 0; - let settled = false; - - const promise = runTreeseedIntegratedDev( - { - cwd: tenantRoot, - surface: 'web', - setupMode: 'off', - feedbackMode: 'off', - openMode: 'off', - shutdownGraceMs: 0, - }, - { - spawn() { - spawnCount += 1; - return new FakeChildProcess() as never; - }, - onSignal(signal, handler) { - signalHandlers.set(signal, handler); - return () => signalHandlers.delete(signal); - }, - inspectPortOwners() { - return []; - }, - prepareEnvironment() {}, - fetch: async () => ({ ok: true }) as Response, - }, - ); - promise.then(() => { - settled = true; - }); - - await new Promise((resolvePromise) => setTimeout(resolvePromise, 25)); - expect(spawnCount).toBe(1); - expect(settled).toBe(false); - - signalHandlers.get('SIGINT')?.(); - await expect(promise).resolves.toBe(130); - }); - - it('suppresses transient local workerd broken pipe log blocks', async () => { - const signalHandlers = new Map void>(); - const child = new FakeChildProcess() as FakeChildProcess & { stderr: PassThrough }; - child.stderr = new PassThrough(); - const output: string[] = []; - - const promise = runTreeseedIntegratedDev( - { - cwd: tenantRoot, - surface: 'web', - setupMode: 'off', - feedbackMode: 'off', - openMode: 'off', - shutdownGraceMs: 0, - }, - { - spawn() { - return child as never; - }, - onSignal(signal, handler) { - signalHandlers.set(signal, handler); - return () => signalHandlers.delete(signal); - }, - inspectPortOwners() { - return []; - }, - prepareEnvironment() {}, - fetch: async () => ({ ok: true }) as Response, - write(line) { - output.push(line); - }, - }, - ); - - await new Promise((resolvePromise) => setTimeout(resolvePromise, 25)); - child.stderr.write('✘ [ERROR] kj::getCaughtExceptionAsKj() = kj/async-io-unix.c++:186: disconnected: ::write(fd, buffer.begin(), buffer.size()): Broken pipe\n'); - child.stderr.write('\n'); - child.stderr.write(' stack: /tmp/workerd@51faac1 /tmp/workerd@2027e07\n'); - child.stderr.write('\n'); - child.stderr.write('real local runtime error\n'); - await new Promise((resolvePromise) => setTimeout(resolvePromise, 25)); - signalHandlers.get('SIGINT')?.(); - await expect(promise).resolves.toBe(130); - const joined = output.join(''); - expect(joined).not.toContain('Broken pipe'); - expect(joined).not.toContain('workerd@'); - expect(joined).toContain('[web] real local runtime error'); - }); - - it('starts the watcher immediately and rebaselines before observing changes', async () => { - const signalHandlers = new Map void>(); - let releaseFetch: (() => void) | null = null; - let startWatchCount = 0; - let rebaselineCount = 0; - - const promise = runTreeseedIntegratedDev( - { - cwd: tenantRoot, - surface: 'web', - setupMode: 'off', - feedbackMode: 'live', - openMode: 'off', - shutdownGraceMs: 0, - }, - { - spawn() { - return new FakeChildProcess() as never; - }, - onSignal(signal, handler) { - signalHandlers.set(signal, handler); - return () => signalHandlers.delete(signal); - }, - inspectPortOwners() { - return []; - }, - prepareEnvironment() {}, - fetch: async () => new Promise((resolveResponse) => { - releaseFetch = () => resolveResponse({ ok: true } as Response); - }), - startWatch() { - startWatchCount += 1; - return { - stop() {}, - rebaseline() { - rebaselineCount += 1; - }, - }; - }, - }, - ); - - await new Promise((resolvePromise) => setTimeout(resolvePromise, 25)); - expect(startWatchCount).toBe(1); - expect(rebaselineCount).toBeGreaterThan(0); - releaseFetch?.(); - await new Promise((resolvePromise) => setTimeout(resolvePromise, 25)); - expect(startWatchCount).toBe(1); - - signalHandlers.get('SIGINT')?.(); - await expect(promise).resolves.toBe(130); - }); - - it('sends shutdown signals to child process groups when pids are available', async () => { - const signalHandlers = new Map void>(); - const killCalls: Array<{ pid: number; signal: NodeJS.Signals }> = []; - const child = new FakeChildProcess(12345); - - const promise = runTreeseedIntegratedDev( - { - cwd: tenantRoot, - surface: 'web', - setupMode: 'off', - feedbackMode: 'off', - openMode: 'off', - shutdownGraceMs: 0, - }, - { - spawn() { - return child as never; - }, - killProcess(pid, signal) { - killCalls.push({ pid, signal }); - child.exit(null, signal); - }, - onSignal(signal, handler) { - signalHandlers.set(signal, handler); - return () => signalHandlers.delete(signal); - }, - inspectPortOwners() { - return []; - }, - prepareEnvironment() {}, - fetch: async () => ({ ok: true }) as Response, - }, - ); - - await new Promise((resolvePromise) => setTimeout(resolvePromise, 25)); - signalHandlers.get('SIGINT')?.(); - await expect(promise).resolves.toBe(130); - expect(killCalls).toEqual([ - { pid: -12345, signal: 'SIGTERM' }, - { pid: -12345, signal: 'SIGKILL' }, - ]); - }); -}); diff --git a/test/ui/primitives.test.ts b/test/ui/primitives.test.ts deleted file mode 100644 index 4d58d6d3..00000000 --- a/test/ui/primitives.test.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { existsSync, readFileSync } from 'node:fs'; -import { dirname, resolve } from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { describe, expect, it } from 'vitest'; - -const testDir = dirname(fileURLToPath(import.meta.url)); -const packageRoot = resolve(testDir, '../..'); - -function source(path: string) { - return readFileSync(resolve(packageRoot, path), 'utf8'); -} - -function packageJson() { - return JSON.parse(source('package.json')) as { - dependencies?: Record; - exports: Record; - }; -} - -describe('core UI ownership boundary', () => { - it('depends on @treeseed/ui for reusable web components', () => { - expect(packageJson().dependencies?.['@treeseed/ui']).toBeDefined(); - }); - - it('does not publish reusable component or style entrypoints', () => { - const exports = packageJson().exports; - for (const entrypoint of Object.keys(exports)) { - expect(entrypoint, entrypoint).not.toMatch(/^\.\/components(?:\/|$)/u); - expect(entrypoint, entrypoint).not.toMatch(/^\.\/styles\/(?:theme|ui|forms|app-shell)\.css$/u); - } - }); - - it('resolves default docs chrome from @treeseed/ui while preserving tenant overrides', () => { - const site = source('src/site.ts'); - expect(site).toContain('resolveTreeseedSiteResource(siteLayers, \'components\', resourcePath)'); - expect(site).toContain('@treeseed/ui/components/astro/docs/Header.astro'); - expect(site).toContain('@treeseed/ui/components/astro/docs/Footer.astro'); - expect(site).toContain('@treeseed/ui/components/astro/core/SiteTitle.astro'); - expect(site).not.toContain('./components/docs/Header.astro'); - expect(site).not.toContain('./components/docs/Footer.astro'); - }); - - it('uses @treeseed/ui from Core pages and stylesheet composition', () => { - for (const path of [ - 'src/pages/index.astro', - 'src/pages/contact.astro', - 'src/pages/books/[slug].astro', - 'src/pages/docs-runtime/[...slug].astro', - 'src/styles/global.css', - ]) { - expect(source(path), path).toContain('@treeseed/ui'); - } - }); - - it('renders the public knowledge runtime reader through ReaderTemplate without page-local styling', () => { - for (const path of [ - 'src/pages/docs-runtime/index.astro', - 'src/pages/docs-runtime/[...slug].astro', - ]) { - const contents = source(path); - expect(contents, path).toContain('ReaderTemplate'); - expect(contents, path).toContain('buildPublicKnowledgeReaderViewModel'); - expect(contents, path).toContain('helpContext={viewModel.help}'); - expect(contents, path).toContain('feedbackContext={viewModel.feedback}'); - expect(contents, path).not.toContain(' { - const site = source('src/site.ts'); - expect(site).toContain("{ pattern: '/knowledge', resourcePath: 'pages/docs-runtime/index.astro'"); - expect(site).toContain("{ pattern: '/knowledge/[...slug]', resourcePath: 'pages/docs-runtime/[...slug].astro'"); - expect(site).toContain("{ pattern: '/api/feedback/submit', resourcePath: 'pages/api/feedback/submit.ts'"); - expect(site).not.toContain('docsRendered && publishedRuntime'); - }); - - it('routes Core Knowledge Hub feedback to Market/API without local persistence', () => { - const helper = source('src/utils/runtime-reader.ts'); - const endpoint = source('src/pages/api/feedback/submit.ts'); - expect(helper).toContain("submissionEndpoint: '/api/feedback/submit'"); - expect(helper).toContain("capabilityId: 'core.public-knowledge-reader'"); - expect(helper).toContain('publicHelpContext'); - expect(helper).toContain("source: 'runtime-content'"); - expect(endpoint).toContain('/v1/feedback'); - expect(endpoint).toContain('cache-control'); - expect(endpoint).not.toMatch(/recordAuditEvent|upsertTeamInboxItem|new MarketControlPlaneStore/iu); - }); - - it('keeps private reader helper server-only and no-leak', () => { - const helper = source('src/utils/runtime-reader.ts'); - expect(helper).toContain('buildPrivateKnowledgeReaderViewModel'); - expect(helper).toContain('r2_private_manifest'); - expect(helper).toContain('private, no-store'); - expect(helper).not.toMatch(/localDocuments[^)]*buildPrivateKnowledgeReaderViewModel/u); - expect(helper).not.toContain('r2://'); - }); - - it('keeps removed reusable source trees absent', () => { - for (const path of [ - 'src/components/ui', - 'src/components/docs', - 'src/components/forms', - 'src/components/site', - 'src/layouts/MainLayout.astro', - 'src/styles/theme.css', - 'src/styles/ui.css', - 'src/styles/forms.css', - 'src/styles/app-shell.css', - ]) { - expect(existsSync(resolve(packageRoot, path)), path).toBe(false); - } - }); -}); diff --git a/test/utils/runtime-reader.test.ts b/test/utils/runtime-reader.test.ts deleted file mode 100644 index 100c028e..00000000 --- a/test/utils/runtime-reader.test.ts +++ /dev/null @@ -1,358 +0,0 @@ -import { afterEach, describe, expect, it, vi } from 'vitest'; -import { resetTreeseedDeployConfigForTests } from '@treeseed/sdk/platform/plugins'; -import { buildPrivateKnowledgeReaderViewModel, buildPublicKnowledgeReaderViewModel } from '../../src/utils/runtime-reader'; - -const originalDeployConfig = (globalThis as { __TREESEED_DEPLOY_CONFIG__?: unknown }).__TREESEED_DEPLOY_CONFIG__; - -class MemoryR2Object { - constructor(private readonly value: unknown) {} - - async text() { - return JSON.stringify(this.value); - } - - async arrayBuffer() { - return new TextEncoder().encode(JSON.stringify(this.value)).buffer; - } - - async json() { - return this.value as T; - } -} - -class MemoryR2Bucket { - private readonly objects = new Map(); - - set(key: string, value: unknown) { - this.objects.set(key, value); - } - - async get(key: string) { - const value = this.objects.get(key); - return value === undefined ? null : new MemoryR2Object(value); - } -} - -afterEach(() => { - vi.stubGlobal('__TREESEED_DEPLOY_CONFIG__', originalDeployConfig); - vi.unstubAllEnvs(); - resetTreeseedDeployConfigForTests(); -}); - -function configurePublishedRuntime(bucket?: MemoryR2Bucket) { - vi.stubEnv('TREESEED_CONTENT_SERVING_MODE', 'published_runtime'); - vi.stubEnv('TREESEED_CONTENT_BUCKET_BINDING', 'TREESEED_CONTENT_BUCKET'); - vi.stubGlobal('__TREESEED_DEPLOY_CONFIG__', { - name: 'Example Site', - slug: 'example-site', - siteUrl: 'https://example.com', - contactEmail: 'hello@example.com', - cloudflare: { - accountId: 'account-123', - workerName: 'example-site', - r2: { - binding: 'TREESEED_CONTENT_BUCKET', - bucketName: 'example-site-content', - manifestKeyTemplate: 'teams/{teamId}/published/common.json', - previewRootTemplate: 'teams/{teamId}/previews', - }, - }, - providers: { - content: { - runtime: 'team_scoped_r2_overlay', - publish: 'team_scoped_r2_overlay', - serving: 'published_runtime', - }, - }, - plugins: [], - smtp: { enabled: false }, - turnstile: { enabled: false }, - }); - resetTreeseedDeployConfigForTests(); - return { - runtime: { - env: { - ...(bucket ? { TREESEED_CONTENT_BUCKET: bucket } : {}), - TREESEED_CONTENT_DEFAULT_TEAM_ID: 'example-site', - }, - }, - } as App.Locals; -} - -function publishedBucket() { - const bucket = new MemoryR2Bucket(); - bucket.set('teams/example-site/published/common.json', { - schemaVersion: 2, - siteSlug: 'example-site', - teamId: 'example-site', - revision: 'rev-1', - generatedAt: '2026-06-25T00:00:00.000Z', - entries: [{ - id: 'knowledge/operations', - model: 'docs', - slug: 'knowledge/operations', - title: 'Operations', - summary: 'Operational knowledge', - status: 'published', - visibility: 'public', - content: { objectKey: 'objects/docs/operations.json', sha256: 'doc-sha' }, - }], - runtime: { - booksRuntime: { objectKey: 'objects/books-runtime.json', sha256: 'books-sha' }, - docsTree: { objectKey: 'objects/docs-tree.json', sha256: 'tree-sha' }, - }, - }); - bucket.set('objects/docs/operations.json', { - model: 'docs', - id: 'knowledge/operations', - slug: 'knowledge/operations', - frontmatter: { - title: 'Operations', - summary: 'Operational knowledge', - }, - body: '## Runtime page\n\nPublished from R2.', - }); - bucket.set('objects/books-runtime.json', { - BOOKS: [{ - order: 1, - slug: 'operations', - title: 'Operations', - description: 'Operations book', - summary: 'Operations summary', - sectionLabel: 'Operations', - basePath: '/knowledge/operations/', - landingPath: '/books/operations/', - downloadFileName: 'operations.md', - downloadHref: '/books/operations.md', - downloadTitle: 'Download Operations', - sidebarItems: [{ label: 'Operations', link: '/knowledge/operations/' }], - }], - BOOKS_LINK: { label: 'Books', link: '/books/' }, - TREESEED_LINKS: { home: '/knowledge/' }, - TREESEED_LIBRARY_DOWNLOAD: { - downloadFileName: 'treeseed-knowledge.md', - downloadHref: '/books/treeseed-knowledge.md', - downloadTitle: 'Download Library', - }, - }); - bucket.set('objects/docs-tree.json', [{ - id: 'knowledge/operations', - slug: 'knowledge/operations', - title: 'Operations', - summary: 'Operational knowledge', - path: '/knowledge/operations/', - }]); - return bucket; -} - -function privateBucket() { - const bucket = new MemoryR2Bucket(); - bucket.set('teams/team-1/projects/project-1/private/common.json', { - schemaVersion: 2, - siteSlug: 'example-site', - teamId: 'team-1', - revision: 'private-rev-1', - generatedAt: '2026-06-25T00:00:00.000Z', - entries: [{ - id: 'knowledge/private-ops', - model: 'docs', - slug: 'knowledge/private-ops', - title: 'Private Operations', - summary: 'Team-only operational knowledge', - status: 'published', - visibility: 'team', - content: { objectKey: 'private/docs/private-ops.json', sha256: 'private-doc-sha' }, - }], - runtime: { - booksRuntime: { objectKey: 'private/books-runtime.json', sha256: 'private-books-sha' }, - docsTree: { objectKey: 'private/docs-tree.json', sha256: 'private-tree-sha' }, - }, - }); - bucket.set('private/docs/private-ops.json', { - model: 'docs', - id: 'knowledge/private-ops', - slug: 'knowledge/private-ops', - frontmatter: { - title: 'Private Operations', - summary: 'Team-only operational knowledge', - }, - body: '## Private page\n\nServed through the private reader.', - }); - bucket.set('private/books-runtime.json', { - BOOKS: [{ - order: 1, - slug: 'private-ops', - title: 'Private Operations', - description: 'Private operations book', - summary: 'Private operations summary', - sectionLabel: 'Private Operations', - basePath: '/app/projects/project-1/knowledge/private-ops/', - landingPath: '/app/projects/project-1/knowledge/', - downloadFileName: 'private-operations.md', - downloadHref: '/private/raw/download/not-used.md', - downloadTitle: 'Download Private Operations', - sidebarItems: [{ label: 'Private Operations', link: '/app/projects/project-1/knowledge/private-ops/' }], - }], - BOOKS_LINK: { label: 'Books', link: '/app/projects/project-1/knowledge/' }, - TREESEED_LINKS: { home: '/app/projects/project-1/knowledge/' }, - }); - bucket.set('private/docs-tree.json', [{ - id: 'knowledge/private-ops', - slug: 'knowledge/private-ops', - title: 'Private Operations', - summary: 'Team-only operational knowledge', - path: '/app/projects/project-1/knowledge/private-ops/', - }]); - return bucket; -} - -describe('public knowledge runtime reader', () => { - it('loads published reader content, navigation, and download metadata from R2 runtime objects', async () => { - const vm = await buildPublicKnowledgeReaderViewModel({ - locals: configurePublishedRuntime(publishedBucket()), - slug: 'operations', - localDocuments: [{ - id: 'operations', - data: { title: 'Local Operations', summary: 'Local fallback should not be used.' }, - }], - }); - - expect(vm.status).toBe('ready'); - expect(vm.source).toBe('r2_published_manifest'); - expect(vm.title).toBe('Operations'); - expect(vm.publishedHtml).toContain('Runtime page'); - expect(vm.localDocumentId).toBeNull(); - expect(vm.navGroups[0]).toMatchObject({ label: 'Operations' }); - expect(vm.actions[0]).toMatchObject({ id: 'knowledge.download', href: '/books/operations.md' }); - expect(vm.help).toMatchObject({ - capabilityId: 'core.public-knowledge-reader', - searchScope: 'public', - visibility: 'public', - feedbackType: 'content_issue', - }); - expect(vm.help?.topics?.[0]).toMatchObject({ source: 'runtime-content', title: 'Operations' }); - expect(vm.help?.relatedDocs.some((topic) => topic.current)).toBe(true); - expect(vm.cache.cdnEligible).toBe(true); - }); - - it('does not fall back to local collections when published runtime has no provider', async () => { - const vm = await buildPublicKnowledgeReaderViewModel({ - locals: configurePublishedRuntime(), - slug: 'operations', - localDocuments: [{ - id: 'operations', - data: { title: 'Local Operations', summary: 'Local fallback should not be used.' }, - }], - }); - - expect(vm.status).toBe('unavailable'); - expect(vm.source).toBe('r2_published_manifest'); - expect(vm.localDocumentId).toBeNull(); - }); - - it('uses local docs only outside published runtime', async () => { - vi.stubEnv('TREESEED_CONTENT_SERVING_MODE', 'local_collections'); - resetTreeseedDeployConfigForTests(); - const vm = await buildPublicKnowledgeReaderViewModel({ - locals: {}, - slug: 'operations', - localDocuments: [{ - id: 'operations', - data: { title: 'Local Operations', summary: 'Local development page.' }, - }], - }); - - expect(vm.status).toBe('ready'); - expect(vm.source).toBe('local_collections'); - expect(vm.title).toBe('Local Operations'); - expect(vm.localDocumentId).toBe('operations'); - expect(vm.help?.topics?.[0]).toMatchObject({ title: 'Local Operations', source: 'runtime-content' }); - expect(vm.cache.cdnEligible).toBe(false); - }); - - it('returns safe states for missing published documents and failed manifests', async () => { - const missing = await buildPublicKnowledgeReaderViewModel({ - locals: configurePublishedRuntime(publishedBucket()), - slug: 'missing', - }); - expect(missing.status).toBe('not_found'); - expect(missing.errorDescription).not.toContain('objects/'); - - const brokenBucket = new MemoryR2Bucket(); - const unavailable = await buildPublicKnowledgeReaderViewModel({ - locals: configurePublishedRuntime(brokenBucket), - slug: 'operations', - }); - expect(unavailable.status).toBe('unavailable'); - expect(unavailable.errorDescription).not.toContain('teams/example-site'); - }); -}); - -describe('private project knowledge runtime reader', () => { - it('loads private content from the project manifest with private cache headers and no download URL exposure', async () => { - const vm = await buildPrivateKnowledgeReaderViewModel({ - locals: configurePublishedRuntime(privateBucket()), - projectId: 'project-1', - teamId: 'team-1', - slug: 'private-ops', - access: 'allowed', - }); - - expect(vm.status).toBe('ready'); - expect(vm.source).toBe('r2_private_manifest'); - expect(vm.title).toBe('Private Operations'); - expect(vm.publishedHtml).toContain('Private page'); - expect(vm.localDocumentId).toBeNull(); - expect(vm.cache).toEqual({ cdnEligible: false, headers: { 'cache-control': 'private, no-store' } }); - expect(vm.actions[0]).toMatchObject({ id: 'knowledge.download', state: 'disabledWithReason', href: undefined }); - expect(JSON.stringify(vm)).not.toContain('private/docs/private-ops.json'); - }); - - it('returns safe private states for denied access, missing manifest, and missing document', async () => { - const denied = await buildPrivateKnowledgeReaderViewModel({ - locals: configurePublishedRuntime(privateBucket()), - projectId: 'project-1', - teamId: 'team-1', - slug: 'private-ops', - access: 'denied', - }); - expect(denied.status).toBe('denied'); - expect(JSON.stringify(denied)).not.toContain('private-ops.json'); - - const unavailable = await buildPrivateKnowledgeReaderViewModel({ - locals: configurePublishedRuntime(new MemoryR2Bucket()), - projectId: 'project-1', - teamId: 'team-1', - slug: 'private-ops', - access: 'allowed', - }); - expect(unavailable.status).toBe('unavailable'); - expect(unavailable.cache.headers['cache-control']).toBe('private, no-store'); - expect(JSON.stringify(unavailable)).not.toContain('teams/team-1/projects/project-1/private/common.json'); - - const missing = await buildPrivateKnowledgeReaderViewModel({ - locals: configurePublishedRuntime(privateBucket()), - projectId: 'project-1', - teamId: 'team-1', - slug: 'missing', - access: 'allowed', - }); - expect(missing.status).toBe('not_found'); - expect(JSON.stringify(missing)).not.toContain('private/docs'); - }); - - it('never uses local collection data for private reader resolution', async () => { - vi.stubEnv('TREESEED_CONTENT_SERVING_MODE', 'local_collections'); - resetTreeseedDeployConfigForTests(); - const vm = await buildPrivateKnowledgeReaderViewModel({ - locals: configurePublishedRuntime(), - projectId: 'project-1', - teamId: 'team-1', - slug: 'private-ops', - access: 'allowed', - }); - expect(vm.status).toBe('unavailable'); - expect(vm.source).toBe('r2_private_manifest'); - expect(vm.localDocumentId).toBeNull(); - }); -}); diff --git a/test/utils/site-config.test.ts b/test/utils/site-config.test.ts deleted file mode 100644 index d1bac230..00000000 --- a/test/utils/site-config.test.ts +++ /dev/null @@ -1,512 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { mkdtempSync, mkdirSync, writeFileSync } from 'node:fs'; -import { tmpdir } from 'node:os'; -import { join, resolve } from 'node:path'; -import { - applyAgentModelDefaults, - applyBookModelDefaults, - applyDecisionModelDefaults, - applyObjectiveModelDefaults, - SITE_CONFIG, - applyDocsModelDefaults, - applyNoteModelDefaults, - applyPeopleModelDefaults, - applyPageModelDefaults, - applyProposalModelDefaults, - applyQuestionModelDefaults, -} from '../../src/utils/site-config'; -import { loadTreeseedManifest } from '@treeseed/sdk/platform/tenant-config'; -import { tenantModelRendered } from '@treeseed/sdk/platform/tenant-config'; -import { parseSiteConfig } from '../../src/utils/site-config-schema.js'; -import { buildTreeseedThemeCss, getBuiltInColorSchemes, normalizeThemePreference, resolveTreeseedThemeConfig } from '../../src/utils/theme.ts'; - -describe('site config parsing', () => { - it('loads grouped header and footer menus from config.yaml', () => { - expect(SITE_CONFIG.site.headerMenu.length).toBeGreaterThan(0); - expect(SITE_CONFIG.site.headerMenu[0].label).toBe('Learn'); - expect(SITE_CONFIG.site.headerMenu[0].items).toContainEqual({ - label: 'Vision', - href: '/vision/', - }); - expect(SITE_CONFIG.site.footerMenu.length).toBeGreaterThan(0); - }); - - it('requires core site metadata fields', () => { - expect(() => - parseSiteConfig(` -site: - logo: - src: /logo.png - alt: Example logo - statement: Example statement - siteUrl: https://example.com - githubRepository: https://github.com/example/repo - discordLink: https://discord.gg/example - headerMenu: - - label: Explore - items: - - label: Home - href: / - footerMenu: - - label: Explore - items: - - label: Home - href: / - emailNotifications: - contactRouting: - default: - - hello@example.com - subscribeRecipients: - - hello@example.com - summary: Example summary - projectStage: Founding - projectStageDetail: Still early -models: {} - `), - ).toThrow('site.name'); - }); - - it('applies content model defaults as fallbacks', () => { - const page = applyPageModelDefaults({ - status: 'live', - }); - const note = applyNoteModelDefaults({ - status: 'live', - }); - const question = applyQuestionModelDefaults({ - status: 'exploratory', - }); - const objective = applyObjectiveModelDefaults({ - status: 'in progress', - }); - const proposal = applyProposalModelDefaults({ - status: 'planned', - }); - const decision = applyDecisionModelDefaults({ - status: 'live', - }); - const person = applyPeopleModelDefaults({}); - const agent = applyAgentModelDefaults({}); - const book = applyBookModelDefaults({}); - const docs = applyDocsModelDefaults({}); - - expect(page.pageLayout).toBe(SITE_CONFIG.models.pages.defaults.pageLayout); - expect(page.stage).toBe(SITE_CONFIG.models.pages.defaults.stage); - expect(page.audience).toEqual(SITE_CONFIG.models.pages.defaults.audience); - expect(note.author).toBe(SITE_CONFIG.models.notes.defaults.author); - expect(note.draft).toBe(SITE_CONFIG.models.notes.defaults.draft); - expect(note.tags).toEqual(SITE_CONFIG.models.notes.defaults.tags); - expect(question.draft).toBe(SITE_CONFIG.models.questions.defaults.draft); - expect(question.tags).toEqual(SITE_CONFIG.models.questions.defaults.tags); - expect(objective.draft).toBe(SITE_CONFIG.models.objectives.defaults.draft); - expect(objective.tags).toEqual(SITE_CONFIG.models.objectives.defaults.tags); - expect(proposal.draft).toBe(SITE_CONFIG.models.proposals.defaults.draft); - expect(proposal.tags).toEqual(SITE_CONFIG.models.proposals.defaults.tags); - expect(decision.draft).toBe(SITE_CONFIG.models.decisions.defaults.draft); - expect(decision.tags).toEqual(SITE_CONFIG.models.decisions.defaults.tags); - expect(person.status).toBe(SITE_CONFIG.models.people.defaults.status); - expect(person.tags).toEqual(SITE_CONFIG.models.people.defaults.tags); - expect(agent.tags).toEqual(SITE_CONFIG.models.agents.defaults.tags); - expect(book.tags).toEqual(SITE_CONFIG.models.books.defaults.tags); - expect(docs.tags).toEqual(SITE_CONFIG.models.docs.defaults.tags); - }); - - it('extracts email notification mappings from config.yaml', () => { - expect(SITE_CONFIG.site.emailNotifications.contactRouting.default).toEqual(['hello@treeseed.dev']); - expect(SITE_CONFIG.site.emailNotifications.contactRouting.issue).toEqual(['hello@treeseed.dev']); - expect(SITE_CONFIG.site.emailNotifications.subscribeRecipients).toEqual(['hello@treeseed.dev']); - }); - - it('parses optional tenant theme palette overrides', () => { - const parsed = parseSiteConfig(` -site: - logo: - src: /logo.png - alt: Example logo - name: Example - statement: Example statement - siteUrl: https://example.com - githubRepository: https://github.com/example/repo - discordLink: https://discord.gg/example - headerMenu: - - label: Explore - items: - - label: Home - href: / - footerMenu: - - label: Explore - items: - - label: Home - href: / - emailNotifications: - contactRouting: - default: - - hello@example.com - subscribeRecipients: - - hello@example.com - summary: Example summary - projectStage: Founding - projectStageDetail: Still early - theme: - defaultScheme: cedar - defaultMode: dark - schemes: - cedar: - light: - canvas: '#fefaf1' - accent: '#d4dfc8' - accentStrong: '#557255' - info: '#7aa4c2' - dark: - accent: '#d98c5f' -models: {} - `); - - expect(parsed.site.theme).toEqual({ - defaultScheme: 'cedar', - defaultMode: 'dark', - schemes: { - cedar: { - light: { - canvas: '#fefaf1', - accent: '#d4dfc8', - accentStrong: '#557255', - info: '#7aa4c2', - }, - dark: { - accent: '#d98c5f', - }, - }, - }, - }); - }); - - it('builds tenant theme css from palette overrides', () => { - const css = buildTreeseedThemeCss({ - defaultScheme: 'cedar', - defaultMode: 'system', - schemes: { - cedar: { - light: { - canvas: '#fefaf1', - accent: '#d4dfc8', - accentSoft: '#eef4e8', - infoText: '#35586d', - }, - }, - }, - }); - - expect(css).toContain('html[data-ts-scheme="cedar"][data-ts-mode="light"]'); - expect(css).toContain('@media (prefers-color-scheme: dark)'); - expect(css).toContain('--ts-color-canvas: #fefaf1;'); - expect(css).toContain('--ts-color-accent: #d4dfc8;'); - expect(css).toContain('--ts-color-accent-soft: #eef4e8;'); - expect(css).toContain('--ts-color-info-text: #35586d;'); - expect(css).toContain('--ts-color-text:'); - expect(css).not.toContain(`--${'site'}-`); - }); - - it('keeps built-in color schemes modular and complete', () => { - const summaries = getBuiltInColorSchemes(); - const resolved = resolveTreeseedThemeConfig(); - const requiredTokens = [ - 'canvas', - 'canvasSubtle', - 'surface', - 'surfaceMuted', - 'surfaceRaised', - 'surfaceOverlay', - 'text', - 'textMuted', - 'textSubtle', - 'textInverse', - 'link', - 'linkHover', - 'border', - 'borderMuted', - 'borderStrong', - 'focus', - 'accent', - 'accentHover', - 'accentStrong', - 'accentSoft', - 'accentText', - 'info', - 'infoSoft', - 'infoText', - 'infoBorder', - 'success', - 'successSoft', - 'successText', - 'successBorder', - 'warning', - 'warningSoft', - 'warningText', - 'warningBorder', - 'danger', - 'dangerSoft', - 'dangerText', - 'dangerBorder', - 'shadow', - 'grid', - ]; - - expect(summaries.map((scheme) => scheme.id)).toEqual(['fern', 'lichen', 'cedar', 'tidepool']); - for (const summary of summaries) { - expect(summary.modeSwatches.light).toHaveLength(4); - expect(summary.modeSwatches.dark).toHaveLength(4); - for (const mode of ['light', 'dark'] as const) { - for (const token of requiredTokens) { - expect(resolved.schemes[summary.id][mode], `${summary.id}.${mode}.${token}`).toHaveProperty(token); - } - } - } - }); - - it('normalizes anonymous theme preferences', () => { - expect(normalizeThemePreference({ colorScheme: 'tidepool', themeMode: 'dark' })).toEqual({ - scheme: 'tidepool', - mode: 'dark', - }); - expect(normalizeThemePreference({ scheme: 'Lichen Stone', mode: 'sepia' })).toEqual({ - scheme: 'fern', - mode: 'system', - }); - }); - - it('rejects invalid theme mode, scheme ids, and token names', () => { - const baseConfig = ` -site: - logo: - src: /logo.png - alt: Example logo - name: Example - statement: Example statement - siteUrl: https://example.com - githubRepository: https://github.com/example/repo - discordLink: https://discord.gg/example - headerMenu: - - label: Explore - items: - - label: Home - href: / - footerMenu: - - label: Explore - items: - - label: Home - href: / - emailNotifications: - contactRouting: - default: - - hello@example.com - subscribeRecipients: - - hello@example.com - summary: Example summary - projectStage: Founding - projectStageDetail: Still early -`; - expect(() => parseSiteConfig(`${baseConfig} theme:\n defaultMode: sepia\nmodels: {}\n`)).toThrow('defaultMode'); - expect(() => parseSiteConfig(`${baseConfig} theme:\n defaultScheme: Fern\nmodels: {}\n`)).toThrow('defaultScheme'); - expect(() => parseSiteConfig(`${baseConfig} theme:\n schemes:\n fern:\n light:\n madeUp: '#fff'\nmodels: {}\n`)).toThrow('Unknown theme token'); - }); - - it('accepts aliased snake_case site config keys', () => { - const parsed = parseSiteConfig(` -site: - logo: - src: /logo.png - alt: Example logo - name: Example - statement: Example statement - site_url: https://example.com - github_repository: https://github.com/example/repo - discord_link: https://discord.gg/example - header_menu: - - label: Explore - items: - - label: Home - href: / - footer_menu: - - label: Explore - items: - - label: Home - href: / - email_notifications: - contact_routing: - default: - - hello@example.com - subscribe_recipients: - - hello@example.com - summary: Example summary - project_stage: Founding - project_stage_detail: Still early -models: - pages: - defaults: - page_layout: bridge -`); - - expect(parsed.site.siteUrl).toBe('https://example.com'); - expect(parsed.site.githubRepository).toBe('https://github.com/example/repo'); - expect(parsed.site.projectStage).toBe('Founding'); - expect(parsed.models.pages.defaults.pageLayout).toBe('bridge'); - }); - - it('parses access roles, policies, defaults, and bootstrap owners', () => { - const parsed = parseSiteConfig(` -site: - logo: - src: /logo.png - alt: Example logo - name: Example - statement: Example statement - siteUrl: https://example.com - githubRepository: https://github.com/example/repo - discordLink: https://discord.gg/example - headerMenu: - - label: Explore - items: - - label: Home - href: / - footerMenu: - - label: Explore - items: - - label: Home - href: / - emailNotifications: - contactRouting: - default: - - hello@example.com - subscribeRecipients: - - hello@example.com - summary: Example summary - projectStage: Founding - projectStageDetail: Still early -models: {} -access: - roles: - site_admin: - grants: - - site:manage - - content:*:* - policies: - public_free: - audience: public - offer: free - paid_updates: - audience: entitlement - entitlement: subscription_updates - defaults: - models: - books: - page: public_free - updates: paid_updates - bootstrap: - owners: - emails: - - owner@example.com - roles: - - site_admin - `); - - expect(parsed.access.roles.site_admin.grants).toEqual(['site:manage', 'content:*:*']); - expect(parsed.access.policies.public_free.offer).toBe('free'); - expect(parsed.access.defaults.models.books.page).toBe('public_free'); - expect(parsed.access.bootstrap.owners.emails).toEqual(['owner@example.com']); - }); - - it('accepts aliased tenant manifest keys', () => { - const tenantRoot = mkdtempSync(join(tmpdir(), 'treeseed-core-tenant-')); - mkdirSync(resolve(tenantRoot, 'src'), { recursive: true }); - writeFileSync( - resolve(tenantRoot, 'src', 'manifest.yaml'), - `id: tenant -site_config_path: ./src/config.yaml -content: - page_root: ./src/content/pages - notes_root: ./src/content/notes - questions_root: ./src/content/questions - objectives_root: ./src/content/objectives - proposals_root: ./src/content/proposals - decisions_root: ./src/content/decisions - people_root: ./src/content/people - agents_root: ./src/content/agents - books_root: ./src/content/books - knowledge_root: ./src/content/knowledge -features: - docs: true -`, - 'utf8', - ); - - const previousRoot = process.env.TREESEED_TENANT_ROOT; - process.env.TREESEED_TENANT_ROOT = tenantRoot; - try { - const manifest = loadTreeseedManifest(resolve(tenantRoot, 'src', 'manifest.yaml')); - expect(manifest.siteConfigPath).toBe(resolve(tenantRoot, 'src', 'config.yaml')); - expect(manifest.content.pages).toBe(resolve(tenantRoot, 'src', 'content', 'pages')); - expect(manifest.content.docs).toBe(resolve(tenantRoot, 'src', 'content', 'knowledge')); - } finally { - if (previousRoot === undefined) { - delete process.env.TREESEED_TENANT_ROOT; - } else { - process.env.TREESEED_TENANT_ROOT = previousRoot; - } - } - }); - - it('loads manifest-owned site rendering flags and defaults omitted models to rendered', () => { - const tenantRoot = mkdtempSync(join(tmpdir(), 'treeseed-core-tenant-')); - mkdirSync(resolve(tenantRoot, 'src'), { recursive: true }); - writeFileSync( - resolve(tenantRoot, 'src', 'manifest.yaml'), - `id: tenant -siteConfigPath: ./src/config.yaml -content: - pages: ./src/content/pages - notes: ./src/content/notes - questions: ./src/content/questions - objectives: ./src/content/objectives - proposals: ./src/content/proposals - decisions: ./src/content/decisions - people: ./src/content/people - agents: ./src/content/agents - books: ./src/content/books - docs: ./src/content/knowledge - workdays: ./src/content/workdays -features: - docs: true - notes: true - proposals: true - decisions: true -site: - models: - workdays: - rendered: false - notes: - rendered: false - proposals: - rendered: false -`, - 'utf8', - ); - - const previousRoot = process.env.TREESEED_TENANT_ROOT; - process.env.TREESEED_TENANT_ROOT = tenantRoot; - try { - const manifest = loadTreeseedManifest(resolve(tenantRoot, 'src', 'manifest.yaml')); - expect(manifest.site?.models?.workdays?.rendered).toBe(false); - expect(tenantModelRendered(manifest, 'workdays')).toBe(false); - expect(tenantModelRendered(manifest, 'notes')).toBe(false); - expect(tenantModelRendered(manifest, 'proposals')).toBe(false); - expect(tenantModelRendered(manifest, 'people')).toBe(true); - } finally { - if (previousRoot === undefined) { - delete process.env.TREESEED_TENANT_ROOT; - } else { - process.env.TREESEED_TENANT_ROOT = previousRoot; - } - } - }); -}); diff --git a/test/fixtures/0007_site_web_sessions.sql b/tests/fixtures/0007_site_web_sessions.sql similarity index 100% rename from test/fixtures/0007_site_web_sessions.sql rename to tests/fixtures/0007_site_web_sessions.sql diff --git a/tests/fixtures/library/agent-tests/docs-reviewer-basic.mdx b/tests/fixtures/library/agent-tests/docs-reviewer-basic.mdx new file mode 100644 index 00000000..af5e1d91 --- /dev/null +++ b/tests/fixtures/library/agent-tests/docs-reviewer-basic.mdx @@ -0,0 +1,12 @@ +--- +id: agent-test:docs-reviewer-basic +agent: treeseed-docs-reviewer +kind: handler +fixture: packages/agent/test/fixtures/agent-tests/docs-reviewer/basic +trigger: + messageType: docs_mutation_completed +expect: + status: waiting + messages: [review_failed] +tags: [review, documentation] +--- diff --git a/tests/fixtures/library/templates/engineering.mdx b/tests/fixtures/library/templates/engineering.mdx new file mode 100644 index 00000000..9d9eef38 --- /dev/null +++ b/tests/fixtures/library/templates/engineering.mdx @@ -0,0 +1,38 @@ +--- +slug: engineering +sourceRef: engineering +title: TreeSeed Engineering Starter +description: First-party TreeSeed starter for software projects guided by research, architecture, implementation, review, and release agents. +summary: An engineering starter that can live as an integrated docs submodule while agents operate from the parent software repository root. +status: live +featured: true +category: starter +audience: [engineers, maintainers] +tags: [starter, engineering, software, docs, agents] +publisher: + id: treeseed + name: TreeSeed + url: https://treeseed.ai +publisherVerified: true +templateVersion: 1.0.0 +templateApiVersion: 1 +minCliVersion: 0.1.1 +minCoreVersion: 0.1.2 +fulfillment: + mode: git + source: + kind: git + repoUrl: https://github.com/treeseed-ai/engineering.git + directory: . + ref: main + integrity: pending-external-repo + hooksPolicy: builtin_only + supportsReconcile: true +offer: + priceModel: free + license: AGPL-3.0-only + support: community +relatedBooks: [books/market-architecture] +relatedKnowledge: [knowledge/market] +relatedObjectives: [objectives/launch-market-site] +--- diff --git a/test/utils/published-content.test.ts b/tests/integration/content/published-content.test.ts similarity index 56% rename from test/utils/published-content.test.ts rename to tests/integration/content/published-content.test.ts index fa01b52a..1078ce8f 100644 --- a/test/utils/published-content.test.ts +++ b/tests/integration/content/published-content.test.ts @@ -2,13 +2,13 @@ import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import { afterEach, describe, expect, it, vi } from 'vitest'; -import { resetTreeseedDeployConfigForTests } from '@treeseed/sdk/platform/plugins'; -import { loadTreeseedDeployConfig } from '@treeseed/sdk/platform/deploy-config'; -import { buildStarlightSidebarEntriesFromRuntime } from '../../src/utils/starlight-nav'; -import { loadHostedBookRuntime } from '../../src/utils/published-content'; +import { resetDeployConfigForTests } from '../../../src/runtime/platform/plugins.ts'; +import { loadDeployConfig } from '../../../src/runtime/platform/deploy-config.ts'; +import { loadHostedDocsTree } from '../../../src/utils/packages/published-content'; +import { loadPublishedCollection } from '../../../src/utils/content/site-content-runtime'; const originalCwd = process.cwd(); -const originalDeployConfig = (globalThis as { __TREESEED_DEPLOY_CONFIG__?: unknown }).__TREESEED_DEPLOY_CONFIG__; +const originalDeployConfig = (globalThis as { DEPLOY_CONFIG?: unknown }).DEPLOY_CONFIG; class MemoryR2Object { constructor(private readonly value: unknown) {} @@ -47,7 +47,7 @@ afterEach(() => { process.chdir(originalCwd); vi.stubGlobal('__TREESEED_DEPLOY_CONFIG__', originalDeployConfig); vi.unstubAllEnvs(); - resetTreeseedDeployConfigForTests(); + resetDeployConfigForTests(); }); async function createTenantFixture() { @@ -68,8 +68,8 @@ cloudflare: r2: binding: TREESEED_CONTENT_BUCKET bucketName: example-site-content - manifestKeyTemplate: teams/{teamId}/published/common.json - previewRootTemplate: teams/{teamId}/previews + manifestKeyTemplate: content/{teamId}/{projectId}/{environment}/channels/current.json + previewRootTemplate: content/{teamId}/{projectId}/previews previewTtlHours: 168 plugins: - package: '@treeseed/sdk/plugin-default' @@ -84,6 +84,7 @@ providers: research: project_graph deploy: cloudflare content: + serving: published_runtime runtime: team_scoped_r2_overlay publish: team_scoped_r2_overlay docs: default @@ -98,85 +99,66 @@ turnstile: } describe('published content helpers', () => { - it('loads a hosted book runtime from the team production manifest and converts it to Starlight sidebar entries', async () => { + it('loads the exact project-scoped R2 publication without a local content directory', async () => { const tenantRoot = await createTenantFixture(); const bucket = new MemoryR2Bucket(); - bucket.set('teams/example-site/published/common.json', { + const manifestKey = 'content/example-site/admin/staging/channels/current.json'; + bucket.set(manifestKey, { + contract: 'treeseed.content-publication/v3', schemaVersion: 2, - siteSlug: 'example-site', + siteSlug: 'admin', teamId: 'example-site', + projectId: 'admin', revision: 'rev-1', generatedAt: '2026-04-15T00:00:00.000Z', - entries: [], + entries: [{ + id: 'runtime-note', + model: 'notes', + slug: 'runtime-note', + title: 'Runtime note', + content: { objectKey: 'content/example-site/admin/runtime-note.json', sha256: 'note-sha' }, + }], runtime: { - booksRuntime: { - objectKey: 'teams/example-site/objects/books-runtime.json', - sha256: 'books-runtime-sha', + docsTree: { + objectKey: 'teams/example-site/objects/docs-tree.json', + sha256: 'docs-tree-sha', }, }, }); - bucket.set('teams/example-site/objects/books-runtime.json', { - BOOKS: [ - { - order: 1, - slug: 'operations', - title: 'Operations', - description: 'Operations book', - summary: 'Operations summary', - sectionLabel: 'Operations', - basePath: '/knowledge/operations/', - landingPath: '/books/operations/', - downloadFileName: 'operations.md', - downloadHref: '/books/operations.md', - downloadTitle: 'Operations', - sidebarItems: [ - { - label: 'Overview', - link: '/knowledge/operations/', - }, - ], - }, - ], - BOOKS_LINK: { - label: 'Books', - link: '/books/', - }, - TREESEED_LINKS: { - home: '/books/', - }, - TREESEED_LIBRARY_DOWNLOAD: { - downloadFileName: 'treeseed-knowledge.md', - downloadHref: '/books/treeseed-knowledge.md', - downloadTitle: 'Knowledge Library', - }, + bucket.set('content/example-site/admin/runtime-note.json', { + model: 'notes', id: 'runtime-note', slug: 'runtime-note', title: 'Runtime note', + frontmatter: { title: 'Runtime note' }, body: 'R2 only.\n', }); + bucket.set('teams/example-site/objects/docs-tree.json', [{ + id: 'operations.start', slug: 'operations/start', title: 'Start', summary: 'Begin here.', + path: '/t/example-site/books/operations/start', + }]); process.chdir(tenantRoot); vi.stubEnv('TREESEED_CONTENT_BUCKET_BINDING', 'TREESEED_CONTENT_BUCKET'); - vi.stubGlobal('__TREESEED_DEPLOY_CONFIG__', loadTreeseedDeployConfig('treeseed.site.yaml')); - resetTreeseedDeployConfigForTests(); + vi.stubGlobal('__TREESEED_DEPLOY_CONFIG__', loadDeployConfig('treeseed.site.yaml')); + resetDeployConfigForTests(); - const runtime = await loadHostedBookRuntime({ + const tree = await loadHostedDocsTree({ runtime: { env: { TREESEED_CONTENT_BUCKET: bucket, TREESEED_CONTENT_DEFAULT_TEAM_ID: 'example-site', + TREESEED_CONTENT_MANIFEST_KEY: manifestKey, }, }, } as App.Locals); - expect(runtime).not.toBeNull(); - const sidebar = buildStarlightSidebarEntriesFromRuntime(runtime!, '/knowledge/operations/'); - - expect(runtime?.BOOKS[0]?.title).toBe('Operations'); - expect(sidebar[0]).toMatchObject({ - type: 'link', - label: 'Books', - href: '/books/', - }); - expect(sidebar[1]).toMatchObject({ - type: 'group', - label: 'Operations', - }); + expect(tree).toEqual([{ id: 'operations.start', slug: 'operations/start', title: 'Start', + summary: 'Begin here.', path: '/t/example-site/books/operations/start' }]); + const notes = await loadPublishedCollection({ + runtime: { env: { + TREESEED_CONTENT_BUCKET: bucket, + TREESEED_CONTENT_DEFAULT_TEAM_ID: 'example-site', + TREESEED_CONTENT_MANIFEST_KEY: manifestKey, + } }, + } as App.Locals, 'notes'); + expect(notes).toHaveLength(1); + expect(notes[0]).toMatchObject({ id: 'runtime-note', data: { title: 'Runtime note' } }); await rm(tenantRoot, { recursive: true, force: true }); }); diff --git a/tests/integration/content/starlight-dependency-resolution.test.ts b/tests/integration/content/starlight-dependency-resolution.test.ts new file mode 100644 index 00000000..f692ab48 --- /dev/null +++ b/tests/integration/content/starlight-dependency-resolution.test.ts @@ -0,0 +1,28 @@ +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { pathToFileURL } from 'node:url'; +import { afterEach, describe, expect, it } from 'vitest'; +import { resolveStarlightRoot } from '../../../scripts/content/resolve-starlight-root.ts'; + +describe('Starlight dependency resolution', () => { + let root: string | null = null; + + afterEach(() => { + if (root) rmSync(root, { recursive: true, force: true }); + root = null; + }); + + it('finds a dependency hoisted above the invoking package', () => { + root = mkdtempSync(join(tmpdir(), 'core-starlight-resolution-')); + const packageRoot = join(root, 'node_modules/@astrojs/starlight'); + const invokingEntry = join(root, 'workspace/packages/core/scripts/entry.mjs'); + + mkdirSync(packageRoot, { recursive: true }); + mkdirSync(join(root, 'workspace/packages/core/scripts'), { recursive: true }); + writeFileSync(join(packageRoot, 'package.json'), '{"main":"index.js"}\n'); + writeFileSync(join(packageRoot, 'index.js'), 'module.exports = {};\n'); + + expect(resolveStarlightRoot(pathToFileURL(invokingEntry).href)).toBe(packageRoot); + }); +}); diff --git a/tests/integration/dev/integrated-dev.clears-disposable-reset-targets-and-leaves-configuration-paths-intact.test.ts b/tests/integration/dev/integrated-dev.clears-disposable-reset-targets-and-leaves-configuration-paths-intact.test.ts new file mode 100644 index 00000000..4e3bb116 --- /dev/null +++ b/tests/integration/dev/integrated-dev.clears-disposable-reset-targets-and-leaves-configuration-paths-intact.test.ts @@ -0,0 +1,314 @@ +import { afterAll, describe, expect, it } from 'vitest'; + +import type { SpawnOptions } from 'node:child_process'; + +import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; + +import { tmpdir } from 'node:os'; + +import { resolve } from 'node:path'; + +import { PassThrough } from 'node:stream'; + +import { DatabaseSync } from 'node:sqlite'; + +import { + createIntegratedDevPlan, + runManagedDev, + runIntegratedDev, + runIntegratedDevReset, +} from '../../../src/runtime/dev.ts'; + +import { classifyChanges, shouldIgnoreWatchPath } from '../../../src/runtime/dev-watch.ts'; + +type FakeExitListener = (code: number | null, signal: NodeJS.Signals | null) => void; + +class FakeChildProcess { + readonly pid?: number; + readonly stdout = new PassThrough(); + readonly stderr = new PassThrough(); + kills: Array = []; + private readonly listeners: FakeExitListener[] = []; + + constructor(pid?: number) { + this.pid = pid; + } + + on(event: string, listener: FakeExitListener) { + if (event === 'exit') { + this.listeners.push(listener); + } + return this; + } + + kill(signal?: string | number) { + this.kills.push(signal); + return true; + } + + exit(code: number | null, signal: NodeJS.Signals | null = null) { + for (const listener of this.listeners) { + listener(code, signal); + } + } +} +describe('Treeseed integrated dev orchestration', () => { +const tenantRoot = resolve(process.cwd(), '.fixtures/treeseed-fixtures/sites/working-site'); + +let fakeAgentPackageRoot: string | null = null; + +afterAll(() => { + if (fakeAgentPackageRoot) { + rmSync(fakeAgentPackageRoot, { recursive: true, force: true }); + } + }); + +function getFakeAgentPackageRoot() { + if (fakeAgentPackageRoot) return fakeAgentPackageRoot; + const root = mkdtempSync(resolve(tmpdir(), 'treeseed-agent-package-')); + mkdirSync(resolve(root, 'scripts'), { recursive: true }); + mkdirSync(resolve(root, 'src/api'), { recursive: true }); + mkdirSync(resolve(root, 'src/services'), { recursive: true }); + writeFileSync(resolve(root, 'package.json'), JSON.stringify({ name: '@treeseed/agent', type: 'module' }, null, 2)); + writeFileSync(resolve(root, 'src/api/server.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/manager.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/workday-manager.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/worker.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/agents.ts'), 'export {};\n'); + fakeAgentPackageRoot = root; + return root; + } + +function withAgentPackageEnv(env: NodeJS.ProcessEnv = {}) { + return { + TREESEED_WEB_SERVICE_SECRET: undefined, + TREESEED_API_WEB_SERVICE_SECRET: undefined, + TREESEED_PLATFORM_RUNNER_SECRET: undefined, + ...env, + TREESEED_AGENT_PACKAGE_ROOT: getFakeAgentPackageRoot(), + }; + } + +function writeTempTenant(siteConfig: string) { + const root = mkdtempSync(resolve(tmpdir(), 'treeseed-dev-runtime-')); + mkdirSync(resolve(root, 'src'), { recursive: true }); + writeFileSync(resolve(root, 'src/manifest.yaml'), 'id: test\n'); + writeFileSync(resolve(root, 'treeseed.site.yaml'), siteConfig); + return root; + } + +function writeApiPackage(root: string) { + const apiRoot = resolve(root, 'packages/api'); + mkdirSync(resolve(apiRoot, 'src/api'), { recursive: true }); + mkdirSync(resolve(apiRoot, 'src/operations-runner'), { recursive: true }); + mkdirSync(resolve(apiRoot, 'scripts'), { recursive: true }); + writeFileSync(resolve(apiRoot, 'package.json'), JSON.stringify({ name: '@treeseed/api', type: 'module' }, null, 2)); + writeFileSync(resolve(apiRoot, 'src/api/server.ts'), 'export {};\n'); + writeFileSync(resolve(apiRoot, 'src/operations-runner/entrypoint.ts'), 'export {};\n'); + writeFileSync(resolve(apiRoot, 'scripts/migrate-db.ts'), 'export {};\n'); + return apiRoot; + } + +function writeLocalD1Project(root: string, id: string, slug = 'market') { + const d1Root = resolve(root, '.treeseed/generated/environments/local/.wrangler/state/v3/d1/miniflare-D1DatabaseObject'); + mkdirSync(d1Root, { recursive: true }); + const db = new DatabaseSync(resolve(d1Root, 'local-ui.sqlite')); + try { + db.exec(`CREATE TABLE projects ( + id TEXT PRIMARY KEY, + slug TEXT NOT NULL, + name TEXT NOT NULL, + created_at TEXT NOT NULL + )`); + db.prepare(`INSERT INTO projects (id, slug, name, created_at) VALUES (?, ?, ?, ?)`) + .run(id, slug, 'TreeSeed Market', '2026-05-15T00:00:00.000Z'); + } finally { + db.close(); + } + } + +it('clears disposable reset targets and leaves configuration paths intact', () => { + const tempRoot = mkdtempSync(resolve(tmpdir(), 'treeseed-dev-reset-')); + const d1Path = resolve(tempRoot, '.wrangler/state/v3/d1'); + const rootKvPath = resolve(tempRoot, '.wrangler/state/v3/kv/miniflare-KVNamespaceObject'); + const generatedStatePath = resolve(tempRoot, '.treeseed/generated/environments/local/.wrangler/state/v3'); + const generatedD1Path = resolve(generatedStatePath, 'd1'); + const generatedKvPath = resolve(generatedStatePath, 'kv/miniflare-KVNamespaceObject'); + const generatedR2Path = resolve(generatedStatePath, 'r2/miniflare-R2BucketObject'); + const generatedCachePath = resolve(generatedStatePath, 'cache/miniflare-CacheObject'); + const legacySqlitePath = resolve(tempRoot, '.treeseed/generated/environments/local/site-data.sqlite'); + const tmpPath = resolve(tempRoot, '.wrangler/tmp'); + const workerPath = resolve(tempRoot, '.treeseed/generated/worker'); + const envPath = resolve(tempRoot, '.treeseed/generated/environments/local'); + const reloadPath = resolve(tempRoot, 'public/__treeseed/dev-reload.json'); + try { + for (const path of [d1Path, rootKvPath, generatedD1Path, generatedKvPath, generatedR2Path, generatedCachePath, tmpPath, workerPath, envPath]) { + mkdirSync(path, { recursive: true }); + } + writeFileSync(resolve(rootKvPath, 'metadata.sqlite'), 'kv\n'); + writeFileSync(resolve(generatedD1Path, 'site-data.sqlite'), 'd1\n'); + writeFileSync(resolve(generatedKvPath, 'metadata.sqlite'), 'kv\n'); + writeFileSync(resolve(generatedR2Path, 'bucket.sqlite'), 'r2\n'); + writeFileSync(resolve(generatedCachePath, 'metadata.sqlite'), 'cache\n'); + mkdirSync(resolve(tempRoot, 'public/__treeseed'), { recursive: true }); + writeFileSync(legacySqlitePath, 'sqlite\n'); + writeFileSync(reloadPath, '{}\n'); + writeFileSync(resolve(tempRoot, 'treeseed.site.yaml'), 'site: test\n'); + const plan = createIntegratedDevPlan({ + cwd: tempRoot, + reset: true, + surface: 'web', + setupMode: 'off', + env: {}, + }); + const events: string[] = []; + const result = runIntegratedDevReset(plan.reset, { json: true }, { + write(line) { + events.push(line); + }, + removePath(path) { + rmSync(path, { recursive: true, force: true }); + }, + stopMailpitContainers() { + return true; + }, + resetMarketPostgres() { + return true; + }, + }); + + expect(result?.actions.filter((action) => action.kind === 'path' && action.id !== 'dev-reload').every((action) => action.status === 'removed')).toBe(true); + expect(result?.actions.find((action) => action.id === 'mailpit')).toBeUndefined(); + expect(result?.actions.find((action) => action.id === 'dev-reload')?.status).toBe('refreshed'); + expect(existsSync(d1Path)).toBe(false); + expect(existsSync(rootKvPath)).toBe(false); + expect(existsSync(generatedStatePath)).toBe(false); + expect(existsSync(legacySqlitePath)).toBe(false); + expect(existsSync(tmpPath)).toBe(false); + expect(existsSync(workerPath)).toBe(false); + expect(JSON.parse(readFileSync(reloadPath, 'utf8'))).toMatchObject({ buildId: expect.any(String) }); + expect(existsSync(envPath)).toBe(true); + expect(existsSync(resolve(tempRoot, 'treeseed.site.yaml'))).toBe(true); + expect(events.some((line) => line.includes('"type":"reset"'))).toBe(true); + } finally { + rmSync(tempRoot, { recursive: true, force: true }); + } + }); + +it('restarts a crashed required child process instead of exiting', async () => { + const spawns: Array<{ command: string; args: string[]; options: SpawnOptions }> = []; + const children = Array.from({ length: 2 }, () => new FakeChildProcess()); + const signalHandlers = new Map void>(); + let settled = false; + + const promise = runIntegratedDev( + { cwd: tenantRoot, surface: 'web', setupMode: 'off', feedbackMode: 'off', openMode: 'off', shutdownGraceMs: 0 }, + { + spawn(command, args, options) { + spawns.push({ command, args, options }); + return children[spawns.length - 1] as never; + }, + onSignal(signal, handler) { + signalHandlers.set(signal, handler); + return () => signalHandlers.delete(signal); + }, + inspectPortOwners() { + return []; + }, + prepareEnvironment() {}, + fetch: async () => ({ ok: true }) as Response, + }, + ); + promise.then(() => { + settled = true; + }); + + await new Promise((resolvePromise) => setTimeout(resolvePromise, 25)); + expect(spawns).toHaveLength(1); + children[0]?.exit(1); + await new Promise((resolvePromise) => setTimeout(resolvePromise, 1_100)); + expect(spawns).toHaveLength(2); + expect(settled).toBe(false); + signalHandlers.get('SIGINT')?.(); + await expect(promise).resolves.toBe(130); + expect(signalHandlers.size).toBe(0); + }); + +it('prints the ready URL without opening a browser by default', async () => { + const child = new FakeChildProcess(); + const signalHandlers = new Map void>(); + const output: string[] = []; + let openCalls = 0; + + const promise = runIntegratedDev( + { cwd: tenantRoot, surface: 'web', setupMode: 'off', feedbackMode: 'off', shutdownGraceMs: 0 }, + { + spawn() { + return child as never; + }, + onSignal(signal, handler) { + signalHandlers.set(signal, handler); + return () => signalHandlers.delete(signal); + }, + inspectPortOwners() { + return []; + }, + prepareEnvironment() {}, + fetch: async () => ({ ok: true }) as Response, + openBrowser() { + openCalls += 1; + }, + write(line) { + output.push(line); + }, + }, + ); + + await new Promise((resolvePromise) => setTimeout(resolvePromise, 25)); + expect(openCalls).toBe(0); + expect(output.some((line) => line.includes('Treeseed dev ready at http://127.0.0.1:4321.'))).toBe(true); + signalHandlers.get('SIGINT')?.(); + await expect(promise).resolves.toBe(130); + }); + +it('shuts down children on parent signals', async () => { + const children = Array.from({ length: 1 }, () => { + const child = new FakeChildProcess(); + const kill = child.kill.bind(child); + child.kill = (signal?: string | number) => { + kill(signal); + child.exit(null, typeof signal === 'string' ? signal as NodeJS.Signals : null); + return true; + }; + return child; + }); + let spawnCount = 0; + const signalHandlers = new Map void>(); + + const promise = runIntegratedDev( + { cwd: tenantRoot, surface: 'web', setupMode: 'off', feedbackMode: 'off', openMode: 'off', shutdownGraceMs: 0 }, + { + spawn() { + spawnCount += 1; + return children[spawnCount - 1] as never; + }, + onSignal(signal, handler) { + signalHandlers.set(signal, handler); + return () => signalHandlers.delete(signal); + }, + inspectPortOwners() { + return []; + }, + prepareEnvironment() {}, + fetch: async () => ({ ok: true }) as Response, + }, + ); + + await new Promise((resolvePromise) => setTimeout(resolvePromise, 25)); + signalHandlers.get('SIGINT')?.(); + await expect(promise).resolves.toBe(130); + for (const child of children) { + expect(child.kills).toEqual(['SIGTERM', 'SIGKILL']); + } + }); +}); diff --git a/tests/integration/dev/integrated-dev.creates-an-integrated-full-platform-plan-with-local-defaults.test.ts b/tests/integration/dev/integrated-dev.creates-an-integrated-full-platform-plan-with-local-defaults.test.ts new file mode 100644 index 00000000..5d95923c --- /dev/null +++ b/tests/integration/dev/integrated-dev.creates-an-integrated-full-platform-plan-with-local-defaults.test.ts @@ -0,0 +1,320 @@ +import { afterAll, describe, expect, it } from 'vitest'; + +import type { SpawnOptions } from 'node:child_process'; + +import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; + +import { tmpdir } from 'node:os'; + +import { resolve } from 'node:path'; + +import { PassThrough } from 'node:stream'; + +import { DatabaseSync } from 'node:sqlite'; + +import { + createIntegratedDevPlan, + runManagedDev, + runIntegratedDev, + runIntegratedDevReset, +} from '../../../src/runtime/dev.ts'; + +import { classifyChanges, shouldIgnoreWatchPath } from '../../../src/runtime/dev-watch.ts'; + +type FakeExitListener = (code: number | null, signal: NodeJS.Signals | null) => void; + +class FakeChildProcess { + readonly pid?: number; + readonly stdout = new PassThrough(); + readonly stderr = new PassThrough(); + kills: Array = []; + private readonly listeners: FakeExitListener[] = []; + + constructor(pid?: number) { + this.pid = pid; + } + + on(event: string, listener: FakeExitListener) { + if (event === 'exit') { + this.listeners.push(listener); + } + return this; + } + + kill(signal?: string | number) { + this.kills.push(signal); + return true; + } + + exit(code: number | null, signal: NodeJS.Signals | null = null) { + for (const listener of this.listeners) { + listener(code, signal); + } + } +} +describe('Treeseed integrated dev orchestration', () => { +const tenantRoot = resolve(process.cwd(), '.fixtures/treeseed-fixtures/sites/working-site'); + +let fakeAgentPackageRoot: string | null = null; + +afterAll(() => { + if (fakeAgentPackageRoot) { + rmSync(fakeAgentPackageRoot, { recursive: true, force: true }); + } + }); + +function getFakeAgentPackageRoot() { + if (fakeAgentPackageRoot) return fakeAgentPackageRoot; + const root = mkdtempSync(resolve(tmpdir(), 'treeseed-agent-package-')); + mkdirSync(resolve(root, 'scripts'), { recursive: true }); + mkdirSync(resolve(root, 'src/api'), { recursive: true }); + mkdirSync(resolve(root, 'src/services'), { recursive: true }); + writeFileSync(resolve(root, 'package.json'), JSON.stringify({ name: '@treeseed/agent', type: 'module' }, null, 2)); + writeFileSync(resolve(root, 'src/api/server.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/manager.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/workday-manager.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/worker.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/agents.ts'), 'export {};\n'); + fakeAgentPackageRoot = root; + return root; + } + +function withAgentPackageEnv(env: NodeJS.ProcessEnv = {}) { + return { + TREESEED_WEB_SERVICE_SECRET: undefined, + TREESEED_API_WEB_SERVICE_SECRET: undefined, + TREESEED_PLATFORM_RUNNER_SECRET: undefined, + ...env, + TREESEED_AGENT_PACKAGE_ROOT: getFakeAgentPackageRoot(), + }; + } + +function writeTempTenant(siteConfig: string) { + const root = mkdtempSync(resolve(tmpdir(), 'treeseed-dev-runtime-')); + mkdirSync(resolve(root, 'src'), { recursive: true }); + writeFileSync(resolve(root, 'src/manifest.yaml'), 'id: test\n'); + writeFileSync(resolve(root, 'treeseed.site.yaml'), siteConfig); + return root; + } + +function writeApiPackage(root: string) { + const apiRoot = resolve(root, 'packages/api'); + mkdirSync(resolve(apiRoot, 'src/api'), { recursive: true }); + mkdirSync(resolve(apiRoot, 'src/operations-runner'), { recursive: true }); + mkdirSync(resolve(apiRoot, 'scripts'), { recursive: true }); + writeFileSync(resolve(apiRoot, 'package.json'), JSON.stringify({ name: '@treeseed/api', type: 'module' }, null, 2)); + writeFileSync(resolve(apiRoot, 'src/api/server.ts'), 'export {};\n'); + writeFileSync(resolve(apiRoot, 'src/operations-runner/entrypoint.ts'), 'export {};\n'); + writeFileSync(resolve(apiRoot, 'scripts/migrate-db.ts'), 'export {};\n'); + return apiRoot; + } + +function writeLocalD1Project(root: string, id: string, slug = 'market') { + const d1Root = resolve(root, '.treeseed/generated/environments/local/.wrangler/state/v3/d1/miniflare-D1DatabaseObject'); + mkdirSync(d1Root, { recursive: true }); + const db = new DatabaseSync(resolve(d1Root, 'local-ui.sqlite')); + try { + db.exec(`CREATE TABLE projects ( + id TEXT PRIMARY KEY, + slug TEXT NOT NULL, + name TEXT NOT NULL, + created_at TEXT NOT NULL + )`); + db.prepare(`INSERT INTO projects (id, slug, name, created_at) VALUES (?, ?, ?, ?)`) + .run(id, slug, 'TreeSeed Market', '2026-05-15T00:00:00.000Z'); + } finally { + db.close(); + } + } + +it('creates an integrated full platform plan with local defaults', () => { + const plan = createIntegratedDevPlan({ + cwd: tenantRoot, + env: withAgentPackageEnv({ + SMTP_HOST: 'smtp.mailgun.org', + SMTP_PORT: '587', + SMTP_USERNAME: 'hosted-user', + SMTP_PASSWORD: 'hosted-password', + }), + }); + + expect(plan.surface).toBe('integrated'); + expect(plan.setupMode).toBe('auto'); + expect(plan.feedbackMode).toBe('live'); + expect(plan.openMode).toBe('off'); + expect(plan.webUrl).toBe('http://127.0.0.1:4321'); + expect(plan.apiBaseUrl).toBe('http://127.0.0.1:3000'); + expect(plan.commands.map((command) => command.id)).toEqual(['web', 'api', 'manager', 'worker']); + expect(plan.localRuntimes.web.selected).toBe('cloudflare-wrangler-local'); + expect(plan.localRuntimes.api.selected).toBe('node-local'); + expect(plan.localRuntimes.manager.selected).toBe('node-local'); + expect(plan.localRuntimes.worker.selected).toBe('node-local'); + expect(plan.localRuntimes).not.toHaveProperty('agents'); + expect(plan.commands[0]?.localRuntime?.selected).toBe('cloudflare-wrangler-local'); + expect(plan.commands[0]?.args).toEqual(expect.arrayContaining(['dev', '--local', '--config'])); + expect(plan.setupSteps.map((step) => step.id)).toEqual( + expect.arrayContaining(['workspace-links', 'wrangler', 'wrangler-config', 'web-build']), + ); + expect(plan.setupSteps.map((step) => step.id)).not.toContain('mailpit'); + expect(plan.readyChecks.map((check) => check.id)).toEqual(['web', 'api', 'manager', 'worker']); + expect(plan.readyChecks.filter((check) => check.required).map((check) => check.id)).toEqual(['web', 'api']); + expect(plan.watchEntries.length).toBeGreaterThan(0); + expect(plan.commands[0]?.env.TREESEED_PUBLIC_DEV_WATCH_RELOAD).toBe('true'); + expect(plan.commands[0]?.env.TREESEED_SITE_URL).toBe('http://127.0.0.1:4321'); + expect(plan.commands[0]?.env.BETTER_AUTH_URL).toBe('http://127.0.0.1:4321'); + expect(plan.commands[0]?.env.TREESEED_API_BASE_URL).toBe('http://127.0.0.1:3000'); + expect(plan.commands[0]?.env.TREESEED_WEB_SERVICE_ID).toBe('web'); + expect(plan.commands[0]?.env.TREESEED_WEB_SERVICE_SECRET).toBe('treeseed-web-service-dev-secret'); + expect(plan.commands[0]?.env.TREESEED_API_WEB_SERVICE_ID).toBe('web'); + expect(plan.commands[0]?.env.TREESEED_API_WEB_SERVICE_SECRET).toBe('treeseed-web-service-dev-secret'); + expect(plan.commands[0]?.env.TREESEED_PLATFORM_RUNNER_SECRET).toBe('treeseed-platform-runner-dev-secret'); + expect(plan.commands[0]?.env.SMTP_HOST).toBe('127.0.0.1'); + expect(plan.commands[0]?.env.SMTP_PORT).toBe('1025'); + expect(plan.commands[0]?.env.SMTP_USERNAME).toBe(''); + expect(plan.commands[0]?.env.SMTP_PASSWORD).toBe(''); + expect(plan.commands[0]?.env.TREESEED_MAILPIT_SMTP_HOST).toBe('127.0.0.1'); + expect(plan.commands[0]?.env.TREESEED_MAILPIT_SMTP_PORT).toBe('1025'); + expect(plan.commands[1]?.label).toBe('Treeseed API'); + expect(plan.commands[1]?.env.PORT).toBe('3000'); + expect(plan.commands[1]?.env.TREESEED_API_REQUEST_LOGS).toBe('true'); + expect(plan.commands[2]?.label).toBe('Manager'); + expect(plan.commands[2]?.args).toEqual(expect.arrayContaining(['--mode', 'loop'])); + expect(plan.commands[2]?.env.TREESEED_MANAGER_MODE).toBe('loop'); + expect(plan.commands[2]?.env.TREESEED_AGENT_D1_PERSIST_TO).toBeUndefined(); + expect(plan.commands[3]?.env.TREESEED_AGENT_D1_PERSIST_TO).toBeUndefined(); + }, 10_000); + +it('turns the Market repo root dev plan into web API runner orchestration with managed local state', () => { + const root = mkdtempSync(resolve(tmpdir(), 'treeseed-market-dev-')); + try { + writeFileSync(resolve(root, 'package.json'), JSON.stringify({ name: '@treeseed/market', type: 'module' }, null, 2)); + writeFileSync(resolve(root, 'treeseed.site.yaml'), 'name: Market\nslug: market\n'); + const apiRoot = writeApiPackage(root); + + const plan = createIntegratedDevPlan({ + cwd: root, + surfaces: 'web,api', + webRuntime: 'local', + env: { + TREESEED_DATABASE_URL: undefined, + TREESEED_PLATFORM_RUNNER_SECRET: undefined, + }, + }); + + expect(plan.commands.map((command) => command.id)).toEqual(['web', 'api', 'operations-runner']); + expect(plan.commands.find((command) => command.id === 'api')?.label).toBe('Treeseed API'); + expect(plan.commands.find((command) => command.id === 'api')?.command).toBe('tsx'); + expect(plan.commands.find((command) => command.id === 'api')?.args).toEqual([ + resolve(apiRoot, 'src/api/server.ts'), + ]); + expect(plan.commands.find((command) => command.id === 'api')?.cwd).toBe(apiRoot); + const runner = plan.commands.find((command) => command.id === 'operations-runner'); + expect(runner?.command).toBe('tsx'); + expect(runner?.args).toEqual(expect.arrayContaining([ + resolve(apiRoot, 'src/operations-runner/entrypoint.ts'), + 'run', + '--watch', + '--operation', + 'project:web_deployment', + ])); + expect(runner?.cwd).toBe(apiRoot); + expect(runner?.args).not.toContain('--mock-external'); + expect(runner?.env.TREESEED_DATABASE_URL).toBe('postgres://treeseed:treeseed@127.0.0.1:55432/market_local'); + expect(runner?.env.TREESEED_MARKET_DATABASE_URL).toBeUndefined(); + expect(runner?.env.TREESEED_PLATFORM_RUNNER_SECRET).toBe('treeseed-platform-runner-dev-secret'); + expect(plan.readyChecks.filter((check) => check.required).map((check) => check.id)).toEqual( + expect.arrayContaining(['web', 'api', 'operations-runner']), + ); + expect(plan.setupSteps.map((step) => step.id)).toEqual(expect.arrayContaining(['market-postgres', 'market-migrations'])); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + +it('treats the default Market PostgreSQL URL as managed local reset state', () => { + const root = mkdtempSync(resolve(tmpdir(), 'treeseed-market-dev-default-db-')); + try { + writeFileSync(resolve(root, 'package.json'), JSON.stringify({ name: '@treeseed/market', type: 'module' }, null, 2)); + writeApiPackage(root); + + const plan = createIntegratedDevPlan({ + cwd: root, + surfaces: 'web,api', + webRuntime: 'local', + reset: true, + env: { + TREESEED_DATABASE_URL: 'postgres://treeseed:treeseed@127.0.0.1:55432/market_local', + }, + }); + + expect(plan.commands.find((command) => command.id === 'api')?.env.TREESEED_MARKET_LOCAL_POSTGRES_MANAGED).toBe('true'); + expect(plan.setupSteps.map((step) => step.id)).toContain('market-postgres'); + expect(plan.reset?.actions.find((action) => action.id === 'market-postgres')).toMatchObject({ + status: 'planned', + label: 'Reset local Market PostgreSQL', + detail: expect.stringContaining('database'), + }); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + +it('hides idle Market runner poll JSON from human dev logs', async () => { + const root = mkdtempSync(resolve(tmpdir(), 'treeseed-market-dev-logs-')); + const signalHandlers = new Map void>(); + const children: FakeChildProcess[] = []; + const output: string[] = []; + try { + writeFileSync(resolve(root, 'package.json'), JSON.stringify({ name: '@treeseed/market', type: 'module' }, null, 2)); + writeFileSync(resolve(root, 'treeseed.site.yaml'), 'name: Market\nslug: market\n'); + writeApiPackage(root); + + const promise = runIntegratedDev( + { + cwd: root, + surfaces: 'api', + setupMode: 'off', + feedbackMode: 'off', + openMode: 'off', + shutdownGraceMs: 0, + env: { + TREESEED_DATABASE_URL: 'postgres://configured-market-db', + }, + }, + { + spawn() { + const child = new FakeChildProcess(); + children.push(child); + return child as never; + }, + onSignal(signal, handler) { + signalHandlers.set(signal, handler); + return () => signalHandlers.delete(signal); + }, + inspectPortOwners() { + return []; + }, + prepareEnvironment() {}, + fetch: async () => ({ ok: true }) as Response, + write(line) { + output.push(line); + }, + }, + ); + + await new Promise((resolvePromise) => setTimeout(resolvePromise, 25)); + const runner = children[1]; + runner?.stdout.write('{"ok":true,"claimed":false,"operation":null}\n'); + runner?.stdout.write('{"ok":true,"claimed":true,"operation":{"id":"op_1"}}\n'); + await new Promise((resolvePromise) => setTimeout(resolvePromise, 25)); + signalHandlers.get('SIGINT')?.(); + + await expect(promise).resolves.toBe(130); + const joined = output.join(''); + expect(joined).not.toContain('"claimed":false'); + expect(joined).toContain('"claimed":true'); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); +}); diff --git a/tests/integration/dev/integrated-dev.emits-a-structured-plan-and-exits-without-spawning-services.test.ts b/tests/integration/dev/integrated-dev.emits-a-structured-plan-and-exits-without-spawning-services.test.ts new file mode 100644 index 00000000..82021675 --- /dev/null +++ b/tests/integration/dev/integrated-dev.emits-a-structured-plan-and-exits-without-spawning-services.test.ts @@ -0,0 +1,262 @@ +import { afterAll, describe, expect, it } from 'vitest'; + +import type { SpawnOptions } from 'node:child_process'; + +import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; + +import { tmpdir } from 'node:os'; + +import { resolve } from 'node:path'; + +import { PassThrough } from 'node:stream'; + +import { DatabaseSync } from 'node:sqlite'; + +import { + createIntegratedDevPlan, + runManagedDev, + runIntegratedDev, + runIntegratedDevReset, +} from '../../../src/runtime/dev.ts'; + +import { classifyChanges, shouldIgnoreWatchPath } from '../../../src/runtime/dev-watch.ts'; + +type FakeExitListener = (code: number | null, signal: NodeJS.Signals | null) => void; + +class FakeChildProcess { + readonly pid?: number; + readonly stdout = new PassThrough(); + readonly stderr = new PassThrough(); + kills: Array = []; + private readonly listeners: FakeExitListener[] = []; + + constructor(pid?: number) { + this.pid = pid; + } + + on(event: string, listener: FakeExitListener) { + if (event === 'exit') { + this.listeners.push(listener); + } + return this; + } + + kill(signal?: string | number) { + this.kills.push(signal); + return true; + } + + exit(code: number | null, signal: NodeJS.Signals | null = null) { + for (const listener of this.listeners) { + listener(code, signal); + } + } +} +describe('Treeseed integrated dev orchestration', () => { +const tenantRoot = resolve(process.cwd(), '.fixtures/treeseed-fixtures/sites/working-site'); + +let fakeAgentPackageRoot: string | null = null; + +afterAll(() => { + if (fakeAgentPackageRoot) { + rmSync(fakeAgentPackageRoot, { recursive: true, force: true }); + } + }); + +function getFakeAgentPackageRoot() { + if (fakeAgentPackageRoot) return fakeAgentPackageRoot; + const root = mkdtempSync(resolve(tmpdir(), 'treeseed-agent-package-')); + mkdirSync(resolve(root, 'scripts'), { recursive: true }); + mkdirSync(resolve(root, 'src/api'), { recursive: true }); + mkdirSync(resolve(root, 'src/services'), { recursive: true }); + writeFileSync(resolve(root, 'package.json'), JSON.stringify({ name: '@treeseed/agent', type: 'module' }, null, 2)); + writeFileSync(resolve(root, 'src/api/server.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/manager.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/workday-manager.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/worker.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/agents.ts'), 'export {};\n'); + fakeAgentPackageRoot = root; + return root; + } + +function withAgentPackageEnv(env: NodeJS.ProcessEnv = {}) { + return { + TREESEED_WEB_SERVICE_SECRET: undefined, + TREESEED_API_WEB_SERVICE_SECRET: undefined, + TREESEED_PLATFORM_RUNNER_SECRET: undefined, + ...env, + TREESEED_AGENT_PACKAGE_ROOT: getFakeAgentPackageRoot(), + }; + } + +function writeTempTenant(siteConfig: string) { + const root = mkdtempSync(resolve(tmpdir(), 'treeseed-dev-runtime-')); + mkdirSync(resolve(root, 'src'), { recursive: true }); + writeFileSync(resolve(root, 'src/manifest.yaml'), 'id: test\n'); + writeFileSync(resolve(root, 'treeseed.site.yaml'), siteConfig); + return root; + } + +function writeApiPackage(root: string) { + const apiRoot = resolve(root, 'packages/api'); + mkdirSync(resolve(apiRoot, 'src/api'), { recursive: true }); + mkdirSync(resolve(apiRoot, 'src/operations-runner'), { recursive: true }); + mkdirSync(resolve(apiRoot, 'scripts'), { recursive: true }); + writeFileSync(resolve(apiRoot, 'package.json'), JSON.stringify({ name: '@treeseed/api', type: 'module' }, null, 2)); + writeFileSync(resolve(apiRoot, 'src/api/server.ts'), 'export {};\n'); + writeFileSync(resolve(apiRoot, 'src/operations-runner/entrypoint.ts'), 'export {};\n'); + writeFileSync(resolve(apiRoot, 'scripts/migrate-db.ts'), 'export {};\n'); + return apiRoot; + } + +function writeLocalD1Project(root: string, id: string, slug = 'market') { + const d1Root = resolve(root, '.treeseed/generated/environments/local/.wrangler/state/v3/d1/miniflare-D1DatabaseObject'); + mkdirSync(d1Root, { recursive: true }); + const db = new DatabaseSync(resolve(d1Root, 'local-ui.sqlite')); + try { + db.exec(`CREATE TABLE projects ( + id TEXT PRIMARY KEY, + slug TEXT NOT NULL, + name TEXT NOT NULL, + created_at TEXT NOT NULL + )`); + db.prepare(`INSERT INTO projects (id, slug, name, created_at) VALUES (?, ?, ?, ?)`) + .run(id, slug, 'TreeSeed Market', '2026-05-15T00:00:00.000Z'); + } finally { + db.close(); + } + } + +it('emits a structured plan and exits without spawning services', async () => { + const output: string[] = []; + const exitCode = await runIntegratedDev( + { + cwd: tenantRoot, + plan: true, + json: true, + setupMode: 'off', + env: withAgentPackageEnv({ + GH_TOKEN: 'test-token', + TREESEED_API_AUTH_SECRET: 'test-secret', + }), + }, + { + spawn() { + throw new Error('plan mode should not spawn child processes'); + }, + prepareEnvironment() {}, + write(line) { + output.push(line); + }, + }, + ); + + expect(exitCode).toBe(0); + const parsed = JSON.parse(output.join('')); + expect(parsed.kind).toBe('treeseed.dev.plan'); + expect(parsed.payload.commands.map((command: { id: string }) => command.id)).toEqual(['web', 'api', 'manager', 'worker']); + expect(parsed.payload.restartPolicy.commandImplementationChangesRequireRestart).toBe(true); + expect(parsed.payload.restartPolicy.agentChanges).toBe('defer'); + expect(parsed.payload.commands[0].env.GH_TOKEN).toBe('[redacted]'); + expect(parsed.payload.commands[0].env.TREESEED_API_AUTH_SECRET).toBe('[redacted]'); + }); + +it('starts a managed worktree dev instance with durable state, logs, ports, and discovery metadata', async () => { + const tempRoot = writeTempTenant(`name: Test +slug: test +siteUrl: https://example.com +contactEmail: hello@example.com +surfaces: + web: + provider: cloudflare + local: + runtime: local +`); + const previousCacheHome = process.env.XDG_CACHE_HOME; + const childPid = 55_555; + const output: string[] = []; + const spawnCalls: Array<{ command: string; args: string[]; options: SpawnOptions }> = []; + + try { + process.env.XDG_CACHE_HOME = resolve(tempRoot, '.cache'); + const exitCode = await runManagedDev( + { + action: 'start', + cwd: tempRoot, + surface: 'web', + setupMode: 'off', + feedbackMode: 'off', + openMode: 'off', + json: true, + readinessTimeoutMs: 1, + }, + { + supervisorCommand: 'node', + supervisorArgs: ['dev-platform.js'], + spawn(command, args, options) { + spawnCalls.push({ command, args, options }); + return new FakeChildProcess(childPid) as never; + }, + processIsAlive(pid) { + return pid === childPid; + }, + inspectPortOwners() { + return []; + }, + fetch: async () => ({ ok: false }) as Response, + write(line) { + output.push(line); + }, + }, + ); + + expect(exitCode).toBe(1); + expect(spawnCalls).toHaveLength(1); + expect(spawnCalls[0]?.command).toBe('node'); + expect(spawnCalls[0]?.args).toEqual(expect.arrayContaining(['dev-platform.js', '--port', '4321', '--api-port', '3000'])); + expect(spawnCalls[0]?.args).not.toContain('--json'); + expect(spawnCalls[0]?.options.detached).toBe(true); + expect(spawnCalls[0]?.options.stdio).toEqual(['ignore', expect.any(Number), expect.any(Number)]); + expect(spawnCalls[0]?.options.env).toMatchObject({ + TREESEED_MANAGED_DEV_INSTANCE: '1', + TREESEED_MANAGED_DEV_SUPPRESS_STDIO: '1', + }); + expect(Object.keys(spawnCalls[0]?.options.env ?? {}).some((key) => /MAILPIT_.*CONTAINER/u.test(key))).toBe(false); + + const parsed = JSON.parse(output.join('')); + expect(parsed.kind).toBe('treeseed.dev.start'); + expect(parsed.ok).toBe(false); + expect(parsed.payload).toMatchObject({ + status: 'degraded', + pid: childPid, + processGroupId: childPid, + runtimeScope: 'web', + ports: { web: 4321, api: 3000, postgres: 55432, mailpitSmtp: 1025 }, + urls: { web: 'http://127.0.0.1:4321' }, + }); + + const instancePath = resolve(tempRoot, '.treeseed/dev/instances/web.json'); + const pidPath = resolve(tempRoot, '.treeseed/dev/pids/web.pid'); + const logPath = resolve(tempRoot, '.treeseed/logs/dev-web.jsonl'); + expect(JSON.parse(readFileSync(instancePath, 'utf8'))).toMatchObject({ + kind: 'treeseed.dev.instance', + projectRoot: tempRoot, + worktreeRoot: tempRoot, + status: 'degraded', + pid: childPid, + processGroupId: childPid, + logPath, + }); + expect(readFileSync(pidPath, 'utf8').trim()).toBe(String(childPid)); + expect(existsSync(logPath)).toBe(true); + expect(existsSync(resolve(tempRoot, '.cache/treeseed/dev-instances'))).toBe(true); + } finally { + if (previousCacheHome === undefined) { + delete process.env.XDG_CACHE_HOME; + } else { + process.env.XDG_CACHE_HOME = previousCacheHome; + } + rmSync(tempRoot, { recursive: true, force: true }); + } + }); +}); diff --git a/tests/integration/dev/integrated-dev.fails-when-provider-runtime-is-required-but-unsupported.test.ts b/tests/integration/dev/integrated-dev.fails-when-provider-runtime-is-required-but-unsupported.test.ts new file mode 100644 index 00000000..d59767c5 --- /dev/null +++ b/tests/integration/dev/integrated-dev.fails-when-provider-runtime-is-required-but-unsupported.test.ts @@ -0,0 +1,291 @@ +import { afterAll, describe, expect, it } from 'vitest'; + +import type { SpawnOptions } from 'node:child_process'; + +import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; + +import { tmpdir } from 'node:os'; + +import { resolve } from 'node:path'; + +import { PassThrough } from 'node:stream'; + +import { DatabaseSync } from 'node:sqlite'; + +import { + createIntegratedDevPlan, + runManagedDev, + runIntegratedDev, + runIntegratedDevReset, +} from '../../../src/runtime/dev.ts'; + +import { classifyChanges, shouldIgnoreWatchPath } from '../../../src/runtime/dev-watch.ts'; + +type FakeExitListener = (code: number | null, signal: NodeJS.Signals | null) => void; + +class FakeChildProcess { + readonly pid?: number; + readonly stdout = new PassThrough(); + readonly stderr = new PassThrough(); + kills: Array = []; + private readonly listeners: FakeExitListener[] = []; + + constructor(pid?: number) { + this.pid = pid; + } + + on(event: string, listener: FakeExitListener) { + if (event === 'exit') { + this.listeners.push(listener); + } + return this; + } + + kill(signal?: string | number) { + this.kills.push(signal); + return true; + } + + exit(code: number | null, signal: NodeJS.Signals | null = null) { + for (const listener of this.listeners) { + listener(code, signal); + } + } +} +describe('Treeseed integrated dev orchestration', () => { +const tenantRoot = resolve(process.cwd(), '.fixtures/treeseed-fixtures/sites/working-site'); + +let fakeAgentPackageRoot: string | null = null; + +afterAll(() => { + if (fakeAgentPackageRoot) { + rmSync(fakeAgentPackageRoot, { recursive: true, force: true }); + } + }); + +function getFakeAgentPackageRoot() { + if (fakeAgentPackageRoot) return fakeAgentPackageRoot; + const root = mkdtempSync(resolve(tmpdir(), 'treeseed-agent-package-')); + mkdirSync(resolve(root, 'scripts'), { recursive: true }); + mkdirSync(resolve(root, 'src/api'), { recursive: true }); + mkdirSync(resolve(root, 'src/services'), { recursive: true }); + writeFileSync(resolve(root, 'package.json'), JSON.stringify({ name: '@treeseed/agent', type: 'module' }, null, 2)); + writeFileSync(resolve(root, 'src/api/server.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/manager.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/workday-manager.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/worker.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/agents.ts'), 'export {};\n'); + fakeAgentPackageRoot = root; + return root; + } + +function withAgentPackageEnv(env: NodeJS.ProcessEnv = {}) { + return { + TREESEED_WEB_SERVICE_SECRET: undefined, + TREESEED_API_WEB_SERVICE_SECRET: undefined, + TREESEED_PLATFORM_RUNNER_SECRET: undefined, + ...env, + TREESEED_AGENT_PACKAGE_ROOT: getFakeAgentPackageRoot(), + }; + } + +function writeTempTenant(siteConfig: string) { + const root = mkdtempSync(resolve(tmpdir(), 'treeseed-dev-runtime-')); + mkdirSync(resolve(root, 'src'), { recursive: true }); + writeFileSync(resolve(root, 'src/manifest.yaml'), 'id: test\n'); + writeFileSync(resolve(root, 'treeseed.site.yaml'), siteConfig); + return root; + } + +function writeApiPackage(root: string) { + const apiRoot = resolve(root, 'packages/api'); + mkdirSync(resolve(apiRoot, 'src/api'), { recursive: true }); + mkdirSync(resolve(apiRoot, 'src/operations-runner'), { recursive: true }); + mkdirSync(resolve(apiRoot, 'scripts'), { recursive: true }); + writeFileSync(resolve(apiRoot, 'package.json'), JSON.stringify({ name: '@treeseed/api', type: 'module' }, null, 2)); + writeFileSync(resolve(apiRoot, 'src/api/server.ts'), 'export {};\n'); + writeFileSync(resolve(apiRoot, 'src/operations-runner/entrypoint.ts'), 'export {};\n'); + writeFileSync(resolve(apiRoot, 'scripts/migrate-db.ts'), 'export {};\n'); + return apiRoot; + } + +function writeLocalD1Project(root: string, id: string, slug = 'market') { + const d1Root = resolve(root, '.treeseed/generated/environments/local/.wrangler/state/v3/d1/miniflare-D1DatabaseObject'); + mkdirSync(d1Root, { recursive: true }); + const db = new DatabaseSync(resolve(d1Root, 'local-ui.sqlite')); + try { + db.exec(`CREATE TABLE projects ( + id TEXT PRIMARY KEY, + slug TEXT NOT NULL, + name TEXT NOT NULL, + created_at TEXT NOT NULL + )`); + db.prepare(`INSERT INTO projects (id, slug, name, created_at) VALUES (?, ?, ?, ?)`) + .run(id, slug, 'TreeSeed Market', '2026-05-15T00:00:00.000Z'); + } finally { + db.close(); + } + } + +it('fails when provider runtime is required but unsupported', () => { + const tempRoot = writeTempTenant(`name: Test +slug: test +siteUrl: https://example.com +contactEmail: hello@example.com +cloudflare: + accountId: account-123 +surfaces: + web: + provider: static-host + local: + runtime: provider +`); + try { + expect(() => createIntegratedDevPlan({ cwd: tempRoot, surface: 'web', setupMode: 'off', env: {} })) + .toThrow(/Local provider runtime is not supported/u); + } finally { + rmSync(tempRoot, { recursive: true, force: true }); + } + }); + +it('preserves explicit env values and lets the supervisor own live feedback', () => { + const plan = createIntegratedDevPlan({ + cwd: tenantRoot, + watch: true, + env: withAgentPackageEnv({ + TREESEED_API_BASE_URL: 'https://override.example.com', + PORT: '4400', + }), + }); + + expect(plan.apiBaseUrl).toBe('https://override.example.com'); + expect(plan.commands[0]?.args).not.toContain('--watch-path'); + expect(plan.watchEntries.length).toBeGreaterThan(0); + expect(plan.commands[0]?.env.TREESEED_PUBLIC_DEV_WATCH_RELOAD).toBe('true'); + }); + +it('ignores generated and runtime files in watched development roots', () => { + const sourceRoot = resolve(tenantRoot, 'packages/core/src'); + const publicRoot = resolve(tenantRoot, 'public'); + + expect(shouldIgnoreWatchPath(resolve(sourceRoot, 'services/.ts-run-123.mjs'), sourceRoot)).toBe(true); + expect(shouldIgnoreWatchPath(resolve(sourceRoot, '.agent-worktrees/task/src/page.ts'), sourceRoot)).toBe(true); + expect(shouldIgnoreWatchPath(resolve(sourceRoot, 'services/manager.ts'), sourceRoot)).toBe(false); + expect(shouldIgnoreWatchPath(resolve(publicRoot, '__treeseed/dev-reload.json'), publicRoot)).toBe(true); + expect(shouldIgnoreWatchPath(resolve(publicRoot, 'books/example.json'), publicRoot)).toBe(true); + expect(shouldIgnoreWatchPath(resolve(publicRoot, 'images/logo.png'), publicRoot)).toBe(false); + }); + +it('classifies dev changes by restart scope', () => { + const root = mkdtempSync(resolve(tmpdir(), 'treeseed-dev-watch-')); + try { + const tenantSource = resolve(root, 'tenant/src/pages/index.astro'); + const tenantConfig = resolve(root, 'tenant/treeseed.site.yaml'); + const sdkSource = resolve(root, 'packages/sdk/src/index.ts'); + const agentSource = resolve(root, 'packages/agent/src/services/worker.ts'); + const cliDevHandler = resolve(root, 'packages/cli/src/cli/handlers/dev.ts'); + const entries = [ + { kind: 'tenant' as const, root: resolve(root, 'tenant/src') }, + { kind: 'tenant' as const, root: resolve(root, 'tenant/treeseed.site.yaml') }, + { kind: 'sdk' as const, root: resolve(root, 'packages/sdk/src') }, + { kind: 'agent' as const, root: resolve(root, 'packages/agent/src') }, + { kind: 'cli' as const, root: cliDevHandler, restartRequired: true }, + ]; + + expect(classifyChanges([tenantSource], entries)).toMatchObject({ + tenantChanged: true, + tenantApiChanged: false, + sdkChanged: false, + agentChanged: false, + commandImplementationChanged: false, + }); + expect(classifyChanges([tenantConfig], entries)).toMatchObject({ + tenantChanged: true, + tenantApiChanged: true, + }); + expect(classifyChanges([sdkSource, agentSource], entries)).toMatchObject({ + sdkChanged: true, + agentChanged: true, + commandImplementationChanged: false, + }); + expect(classifyChanges([cliDevHandler], entries)).toMatchObject({ + cliChanged: true, + commandImplementationChanged: true, + }); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + +it('lets explicit API port override the shared web environment value', () => { + const plan = createIntegratedDevPlan({ + cwd: tenantRoot, + apiPort: 4401, + env: withAgentPackageEnv({ + TREESEED_API_BASE_URL: 'https://override.example.com', + PORT: '4400', + }), + }); + + expect(plan.apiBaseUrl).toBe('http://127.0.0.1:4401'); + expect(plan.commands.map((command) => command.id)).toEqual(['web', 'api', 'manager', 'worker']); + expect(plan.commands[0]?.env.TREESEED_API_BASE_URL).toBe('http://127.0.0.1:4401'); + expect(plan.commands[1]?.env.PORT).toBe('4401'); + }); + +it('plans comma-separated surfaces in canonical order without duplicates', () => { + const plan = createIntegratedDevPlan({ + cwd: tenantRoot, + surfaces: 'worker,web,integrated,agents,api', + setupMode: 'off', + env: withAgentPackageEnv(), + }); + + expect(plan.commands.map((command) => command.id)).toEqual(['web', 'api', 'manager', 'worker', 'agents']); + expect(plan.localRuntimes).toHaveProperty('agents'); + }); + +it('plans the all surface as the full task runtime without the legacy agents loop', () => { + const plan = createIntegratedDevPlan({ + cwd: tenantRoot, + surface: 'all', + setupMode: 'off', + env: withAgentPackageEnv(), + }); + + expect(plan.surface).toBe('all'); + expect(plan.commands.map((command) => command.id)).toEqual(['web', 'api', 'manager', 'worker']); + expect(plan.localRuntimes).toHaveProperty('web'); + expect(plan.localRuntimes).toHaveProperty('api'); + expect(plan.localRuntimes).toHaveProperty('manager'); + expect(plan.localRuntimes).toHaveProperty('worker'); + expect(plan.localRuntimes).not.toHaveProperty('agents'); + }); + +it('plans explicit API and service surfaces with Node runtimes', () => { + const apiPlan = createIntegratedDevPlan({ + cwd: tenantRoot, + surface: 'api', + setupMode: 'off', + env: withAgentPackageEnv(), + }); + expect(apiPlan.webUrl).toBeNull(); + expect(apiPlan.commands.map((command) => command.id)).toEqual(['api']); + expect(apiPlan.localRuntimes).not.toHaveProperty('web'); + expect(apiPlan.readyChecks.map((check) => check.id)).toEqual(['api']); + + const servicesPlan = createIntegratedDevPlan({ + cwd: tenantRoot, + surface: 'services', + setupMode: 'off', + env: withAgentPackageEnv(), + }); + expect(servicesPlan.commands.map((command) => command.id)).toEqual(['api', 'manager', 'worker', 'agents']); + expect(servicesPlan.readyChecks.filter((check) => check.required).map((check) => check.id)).toEqual(['api']); + expect(servicesPlan.readyChecks.filter((check) => !check.required).map((check) => check.id)).toEqual([ + 'manager', + 'worker', + 'agents', + ]); + }); +}); diff --git a/tests/integration/dev/integrated-dev.fails-with-an-existing-runtime-warning-unless-force-is-requested.test.ts b/tests/integration/dev/integrated-dev.fails-with-an-existing-runtime-warning-unless-force-is-requested.test.ts new file mode 100644 index 00000000..139d6769 --- /dev/null +++ b/tests/integration/dev/integrated-dev.fails-with-an-existing-runtime-warning-unless-force-is-requested.test.ts @@ -0,0 +1,303 @@ +import { afterAll, describe, expect, it } from 'vitest'; + +import type { SpawnOptions } from 'node:child_process'; + +import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; + +import { tmpdir } from 'node:os'; + +import { resolve } from 'node:path'; + +import { PassThrough } from 'node:stream'; + +import { DatabaseSync } from 'node:sqlite'; + +import { + createIntegratedDevPlan, + runManagedDev, + runIntegratedDev, + runIntegratedDevReset, +} from '../../../src/runtime/dev.ts'; + +import { classifyChanges, shouldIgnoreWatchPath } from '../../../src/runtime/dev-watch.ts'; + +type FakeExitListener = (code: number | null, signal: NodeJS.Signals | null) => void; + +class FakeChildProcess { + readonly pid?: number; + readonly stdout = new PassThrough(); + readonly stderr = new PassThrough(); + kills: Array = []; + private readonly listeners: FakeExitListener[] = []; + + constructor(pid?: number) { + this.pid = pid; + } + + on(event: string, listener: FakeExitListener) { + if (event === 'exit') { + this.listeners.push(listener); + } + return this; + } + + kill(signal?: string | number) { + this.kills.push(signal); + return true; + } + + exit(code: number | null, signal: NodeJS.Signals | null = null) { + for (const listener of this.listeners) { + listener(code, signal); + } + } +} +describe('Treeseed integrated dev orchestration', () => { +const tenantRoot = resolve(process.cwd(), '.fixtures/treeseed-fixtures/sites/working-site'); + +let fakeAgentPackageRoot: string | null = null; + +afterAll(() => { + if (fakeAgentPackageRoot) { + rmSync(fakeAgentPackageRoot, { recursive: true, force: true }); + } + }); + +function getFakeAgentPackageRoot() { + if (fakeAgentPackageRoot) return fakeAgentPackageRoot; + const root = mkdtempSync(resolve(tmpdir(), 'treeseed-agent-package-')); + mkdirSync(resolve(root, 'scripts'), { recursive: true }); + mkdirSync(resolve(root, 'src/api'), { recursive: true }); + mkdirSync(resolve(root, 'src/services'), { recursive: true }); + writeFileSync(resolve(root, 'package.json'), JSON.stringify({ name: '@treeseed/agent', type: 'module' }, null, 2)); + writeFileSync(resolve(root, 'src/api/server.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/manager.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/workday-manager.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/worker.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/agents.ts'), 'export {};\n'); + fakeAgentPackageRoot = root; + return root; + } + +function withAgentPackageEnv(env: NodeJS.ProcessEnv = {}) { + return { + TREESEED_WEB_SERVICE_SECRET: undefined, + TREESEED_API_WEB_SERVICE_SECRET: undefined, + TREESEED_PLATFORM_RUNNER_SECRET: undefined, + ...env, + TREESEED_AGENT_PACKAGE_ROOT: getFakeAgentPackageRoot(), + }; + } + +function writeTempTenant(siteConfig: string) { + const root = mkdtempSync(resolve(tmpdir(), 'treeseed-dev-runtime-')); + mkdirSync(resolve(root, 'src'), { recursive: true }); + writeFileSync(resolve(root, 'src/manifest.yaml'), 'id: test\n'); + writeFileSync(resolve(root, 'treeseed.site.yaml'), siteConfig); + return root; + } + +function writeApiPackage(root: string) { + const apiRoot = resolve(root, 'packages/api'); + mkdirSync(resolve(apiRoot, 'src/api'), { recursive: true }); + mkdirSync(resolve(apiRoot, 'src/operations-runner'), { recursive: true }); + mkdirSync(resolve(apiRoot, 'scripts'), { recursive: true }); + writeFileSync(resolve(apiRoot, 'package.json'), JSON.stringify({ name: '@treeseed/api', type: 'module' }, null, 2)); + writeFileSync(resolve(apiRoot, 'src/api/server.ts'), 'export {};\n'); + writeFileSync(resolve(apiRoot, 'src/operations-runner/entrypoint.ts'), 'export {};\n'); + writeFileSync(resolve(apiRoot, 'scripts/migrate-db.ts'), 'export {};\n'); + return apiRoot; + } + +function writeLocalD1Project(root: string, id: string, slug = 'market') { + const d1Root = resolve(root, '.treeseed/generated/environments/local/.wrangler/state/v3/d1/miniflare-D1DatabaseObject'); + mkdirSync(d1Root, { recursive: true }); + const db = new DatabaseSync(resolve(d1Root, 'local-ui.sqlite')); + try { + db.exec(`CREATE TABLE projects ( + id TEXT PRIMARY KEY, + slug TEXT NOT NULL, + name TEXT NOT NULL, + created_at TEXT NOT NULL + )`); + db.prepare(`INSERT INTO projects (id, slug, name, created_at) VALUES (?, ?, ?, ?)`) + .run(id, slug, 'TreeSeed Market', '2026-05-15T00:00:00.000Z'); + } finally { + db.close(); + } + } + +it('fails with an existing runtime warning unless force is requested', async () => { + const tempRoot = writeTempTenant(`name: Test +slug: test +siteUrl: https://example.com +contactEmail: hello@example.com +surfaces: + web: + provider: cloudflare + local: + runtime: local +`); + const previousPid = 98_765; + const legacyRuntimePath = resolve(tempRoot, '.treeseed/generated/dev/runtime.json'); + const output: string[] = []; + let spawnCount = 0; + + try { + mkdirSync(resolve(tempRoot, '.treeseed/generated/dev'), { recursive: true }); + writeFileSync(legacyRuntimePath, `${JSON.stringify({ + pid: previousPid, + tenantRoot: tempRoot, + startedAt: '2026-05-06T00:00:00.000Z', + }, null, 2)}\n`); + + const exitCode = await runIntegratedDev( + { + cwd: tempRoot, + surface: 'web', + setupMode: 'off', + feedbackMode: 'off', + openMode: 'off', + json: true, + }, + { + spawn() { + spawnCount += 1; + return new FakeChildProcess() as never; + }, + processIsAlive(pid) { + return pid === previousPid; + }, + inspectPortOwners() { + return []; + }, + prepareEnvironment() {}, + write(line) { + output.push(line); + }, + }, + ); + + expect(exitCode).toBe(1); + expect(spawnCount).toBe(0); + expect(output.join('')).toContain('existing runtime or service'); + expect(existsSync(legacyRuntimePath)).toBe(true); + } finally { + rmSync(tempRoot, { recursive: true, force: true }); + } + }); + +it('fails with a port-owner warning unless force is requested', async () => { + const tempRoot = writeTempTenant(`name: Test +slug: test +siteUrl: https://example.com +contactEmail: hello@example.com +surfaces: + web: + provider: cloudflare + local: + runtime: local +`); + const output: string[] = []; + + try { + const exitCode = await runIntegratedDev( + { + cwd: tempRoot, + surface: 'web', + setupMode: 'off', + feedbackMode: 'off', + openMode: 'off', + json: true, + }, + { + spawn() { + throw new Error('spawn should not be reached'); + }, + processIsAlive() { + return false; + }, + inspectPortOwners() { + return [{ port: 4321, pid: 22_222, processName: 'astro', detail: 'LISTEN 127.0.0.1:4321 pid=22222' }]; + }, + prepareEnvironment() {}, + write(line) { + output.push(line); + }, + }, + ); + + expect(exitCode).toBe(1); + expect(output.join('')).toContain('required port'); + expect(output.join('')).toContain('22222'); + } finally { + rmSync(tempRoot, { recursive: true, force: true }); + } + }); + +it('force-stops listeners on required ports before spawning services', async () => { + const tempRoot = writeTempTenant(`name: Test +slug: test +siteUrl: https://example.com +contactEmail: hello@example.com +surfaces: + web: + provider: cloudflare + local: + runtime: local +`); + const signalHandlers = new Map void>(); + const livePids = new Set([22_222]); + const killCalls: Array<{ pid: number; signal: NodeJS.Signals }> = []; + let spawnCount = 0; + let inspectCount = 0; + + try { + const promise = runIntegratedDev( + { + cwd: tempRoot, + surface: 'web', + setupMode: 'off', + feedbackMode: 'off', + openMode: 'off', + shutdownGraceMs: 0, + force: true, + }, + { + spawn() { + spawnCount += 1; + return new FakeChildProcess() as never; + }, + killProcess(pid, signal) { + killCalls.push({ pid, signal }); + livePids.delete(pid); + }, + processIsAlive(pid) { + return livePids.has(pid); + }, + inspectPortOwners() { + inspectCount += 1; + return inspectCount === 1 + ? [{ port: 4321, pid: 22_222, processName: 'astro', detail: 'LISTEN 127.0.0.1:4321 pid=22222' }] + : []; + }, + onSignal(signal, handler) { + signalHandlers.set(signal, handler); + return () => signalHandlers.delete(signal); + }, + prepareEnvironment() {}, + fetch: async () => ({ ok: true }) as Response, + }, + ); + + await new Promise((resolvePromise) => setTimeout(resolvePromise, 25)); + expect(killCalls).toEqual([{ pid: 22_222, signal: 'SIGTERM' }]); + expect(spawnCount).toBe(1); + + signalHandlers.get('SIGINT')?.(); + await expect(promise).resolves.toBe(130); + } finally { + rmSync(tempRoot, { recursive: true, force: true }); + } + }); +}); diff --git a/tests/integration/dev/integrated-dev.keeps-disjoint-dev-runtimes-alive-when-starting-manager-services-beside-web-api.test.ts b/tests/integration/dev/integrated-dev.keeps-disjoint-dev-runtimes-alive-when-starting-manager-services-beside-web-api.test.ts new file mode 100644 index 00000000..3399ace4 --- /dev/null +++ b/tests/integration/dev/integrated-dev.keeps-disjoint-dev-runtimes-alive-when-starting-manager-services-beside-web-api.test.ts @@ -0,0 +1,298 @@ +import { afterAll, describe, expect, it } from 'vitest'; + +import type { SpawnOptions } from 'node:child_process'; + +import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; + +import { tmpdir } from 'node:os'; + +import { resolve } from 'node:path'; + +import { PassThrough } from 'node:stream'; + +import { DatabaseSync } from 'node:sqlite'; + +import { + createIntegratedDevPlan, + runManagedDev, + runIntegratedDev, + runIntegratedDevReset, +} from '../../../src/runtime/dev.ts'; + +import { classifyChanges, shouldIgnoreWatchPath } from '../../../src/runtime/dev-watch.ts'; + +type FakeExitListener = (code: number | null, signal: NodeJS.Signals | null) => void; + +class FakeChildProcess { + readonly pid?: number; + readonly stdout = new PassThrough(); + readonly stderr = new PassThrough(); + kills: Array = []; + private readonly listeners: FakeExitListener[] = []; + + constructor(pid?: number) { + this.pid = pid; + } + + on(event: string, listener: FakeExitListener) { + if (event === 'exit') { + this.listeners.push(listener); + } + return this; + } + + kill(signal?: string | number) { + this.kills.push(signal); + return true; + } + + exit(code: number | null, signal: NodeJS.Signals | null = null) { + for (const listener of this.listeners) { + listener(code, signal); + } + } +} +describe('Treeseed integrated dev orchestration', () => { +const tenantRoot = resolve(process.cwd(), '.fixtures/treeseed-fixtures/sites/working-site'); + +let fakeAgentPackageRoot: string | null = null; + +afterAll(() => { + if (fakeAgentPackageRoot) { + rmSync(fakeAgentPackageRoot, { recursive: true, force: true }); + } + }); + +function getFakeAgentPackageRoot() { + if (fakeAgentPackageRoot) return fakeAgentPackageRoot; + const root = mkdtempSync(resolve(tmpdir(), 'treeseed-agent-package-')); + mkdirSync(resolve(root, 'scripts'), { recursive: true }); + mkdirSync(resolve(root, 'src/api'), { recursive: true }); + mkdirSync(resolve(root, 'src/services'), { recursive: true }); + writeFileSync(resolve(root, 'package.json'), JSON.stringify({ name: '@treeseed/agent', type: 'module' }, null, 2)); + writeFileSync(resolve(root, 'src/api/server.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/manager.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/workday-manager.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/worker.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/agents.ts'), 'export {};\n'); + fakeAgentPackageRoot = root; + return root; + } + +function withAgentPackageEnv(env: NodeJS.ProcessEnv = {}) { + return { + TREESEED_WEB_SERVICE_SECRET: undefined, + TREESEED_API_WEB_SERVICE_SECRET: undefined, + TREESEED_PLATFORM_RUNNER_SECRET: undefined, + ...env, + TREESEED_AGENT_PACKAGE_ROOT: getFakeAgentPackageRoot(), + }; + } + +function writeTempTenant(siteConfig: string) { + const root = mkdtempSync(resolve(tmpdir(), 'treeseed-dev-runtime-')); + mkdirSync(resolve(root, 'src'), { recursive: true }); + writeFileSync(resolve(root, 'src/manifest.yaml'), 'id: test\n'); + writeFileSync(resolve(root, 'treeseed.site.yaml'), siteConfig); + return root; + } + +function writeApiPackage(root: string) { + const apiRoot = resolve(root, 'packages/api'); + mkdirSync(resolve(apiRoot, 'src/api'), { recursive: true }); + mkdirSync(resolve(apiRoot, 'src/operations-runner'), { recursive: true }); + mkdirSync(resolve(apiRoot, 'scripts'), { recursive: true }); + writeFileSync(resolve(apiRoot, 'package.json'), JSON.stringify({ name: '@treeseed/api', type: 'module' }, null, 2)); + writeFileSync(resolve(apiRoot, 'src/api/server.ts'), 'export {};\n'); + writeFileSync(resolve(apiRoot, 'src/operations-runner/entrypoint.ts'), 'export {};\n'); + writeFileSync(resolve(apiRoot, 'scripts/migrate-db.ts'), 'export {};\n'); + return apiRoot; + } + +function writeLocalD1Project(root: string, id: string, slug = 'market') { + const d1Root = resolve(root, '.treeseed/generated/environments/local/.wrangler/state/v3/d1/miniflare-D1DatabaseObject'); + mkdirSync(d1Root, { recursive: true }); + const db = new DatabaseSync(resolve(d1Root, 'local-ui.sqlite')); + try { + db.exec(`CREATE TABLE projects ( + id TEXT PRIMARY KEY, + slug TEXT NOT NULL, + name TEXT NOT NULL, + created_at TEXT NOT NULL + )`); + db.prepare(`INSERT INTO projects (id, slug, name, created_at) VALUES (?, ?, ?, ?)`) + .run(id, slug, 'TreeSeed Market', '2026-05-15T00:00:00.000Z'); + } finally { + db.close(); + } + } + +it('keeps disjoint dev runtimes alive when starting manager services beside web api', async () => { + const tempRoot = writeTempTenant(`name: Test +slug: test +siteUrl: https://example.com +contactEmail: hello@example.com +surfaces: + web: + provider: cloudflare + local: + runtime: local +`); + const webApiPid = 98_765; + const managerPid = 98_766; + const webApiRuntimePath = resolve(tempRoot, '.treeseed/generated/dev/runtime-web-api.json'); + const managerRuntimePath = resolve(tempRoot, '.treeseed/generated/dev/runtime-manager-worker.json'); + const signalHandlers = new Map void>(); + const livePids = new Set([webApiPid, managerPid]); + const killCalls: Array<{ pid: number; signal: NodeJS.Signals }> = []; + let spawnCount = 0; + + try { + mkdirSync(resolve(tempRoot, '.treeseed/generated/dev'), { recursive: true }); + writeFileSync(webApiRuntimePath, `${JSON.stringify({ + pid: webApiPid, + tenantRoot: tempRoot, + commandIds: ['web', 'api'], + startedAt: '2026-05-06T00:00:00.000Z', + }, null, 2)}\n`); + writeFileSync(managerRuntimePath, `${JSON.stringify({ + pid: managerPid, + tenantRoot: tempRoot, + commandIds: ['manager', 'worker'], + startedAt: '2026-05-06T00:01:00.000Z', + }, null, 2)}\n`); + + const promise = runIntegratedDev( + { + cwd: tempRoot, + surfaces: 'manager,worker', + setupMode: 'off', + feedbackMode: 'off', + openMode: 'off', + shutdownGraceMs: 0, + force: true, + env: withAgentPackageEnv(), + }, + { + spawn() { + spawnCount += 1; + return new FakeChildProcess() as never; + }, + killProcess(pid, signal) { + killCalls.push({ pid, signal }); + livePids.delete(pid); + }, + processIsAlive(pid) { + return livePids.has(pid); + }, + onSignal(signal, handler) { + signalHandlers.set(signal, handler); + return () => signalHandlers.delete(signal); + }, + prepareEnvironment() {}, + fetch: async () => ({ ok: true }) as Response, + }, + ); + + await new Promise((resolvePromise) => setTimeout(resolvePromise, 25)); + expect(killCalls).toEqual([{ pid: managerPid, signal: 'SIGTERM' }]); + expect(spawnCount).toBe(2); + expect(JSON.parse(readFileSync(webApiRuntimePath, 'utf8'))).toMatchObject({ + pid: webApiPid, + commandIds: ['web', 'api'], + }); + expect(JSON.parse(readFileSync(managerRuntimePath, 'utf8'))).toMatchObject({ + pid: process.pid, + commandIds: ['manager', 'worker'], + }); + + signalHandlers.get('SIGINT')?.(); + await expect(promise).resolves.toBe(130); + expect(existsSync(webApiRuntimePath)).toBe(true); + expect(existsSync(managerRuntimePath)).toBe(false); + } finally { + rmSync(tempRoot, { recursive: true, force: true }); + } + }); + +it('plans dev reset against runtime state while preserving configuration paths', () => { + const tempRoot = mkdtempSync(resolve(tmpdir(), 'treeseed-dev-reset-plan-')); + const d1Path = resolve(tempRoot, '.wrangler/state/v3/d1'); + const envPath = resolve(tempRoot, '.treeseed/generated/environments/local'); + try { + mkdirSync(d1Path, { recursive: true }); + mkdirSync(envPath, { recursive: true }); + mkdirSync(resolve(tempRoot, '.treeseed/generated/worker'), { recursive: true }); + writeFileSync(resolve(tempRoot, '.treeseed/generated/worker/index.js'), 'export {};\n'); + writeFileSync(resolve(tempRoot, 'treeseed.site.yaml'), 'site: test\n'); + + const plan = createIntegratedDevPlan({ + cwd: tempRoot, + reset: true, + surface: 'web', + setupMode: 'off', + env: {}, + }); + + expect(plan.reset?.enabled).toBe(true); + expect(plan.reset?.actions.map((action) => action.id)).toEqual([ + 'root-wrangler-state', + 'wrangler-tmp', + 'worker-bundle', + 'dev-reload', + ]); + expect(plan.reset?.actions.find((action) => action.id === 'root-wrangler-state')?.path).toBe(resolve(tempRoot, '.wrangler/state/v3')); + expect(plan.commands[0]?.env.TREESEED_DEV_RESET_ID).toEqual(expect.any(String)); + expect(plan.reset?.preserved).toEqual(expect.arrayContaining([ + 'treeseed.site.yaml', + '.treeseed/generated/environments', + '.treeseed/config', + '.treeseed/state', + '.treeseed/workflow', + ])); + expect(existsSync(envPath)).toBe(true); + } finally { + rmSync(tempRoot, { recursive: true, force: true }); + } + }); + +it('does not mutate reset targets when reset is only planned', async () => { + const tempRoot = mkdtempSync(resolve(tmpdir(), 'treeseed-dev-reset-dry-')); + const d1Path = resolve(tempRoot, '.wrangler/state/v3/d1'); + const workerPath = resolve(tempRoot, '.treeseed/generated/worker'); + try { + mkdirSync(d1Path, { recursive: true }); + mkdirSync(workerPath, { recursive: true }); + writeFileSync(resolve(workerPath, 'index.js'), 'export {};\n'); + + const output: string[] = []; + const exitCode = await runIntegratedDev( + { cwd: tempRoot, reset: true, plan: true, json: true, setupMode: 'off', surface: 'web' }, + { + spawn() { + throw new Error('plan mode should not spawn child processes'); + }, + removePath() { + throw new Error('plan mode should not remove paths'); + }, + stopMailpitContainers() { + throw new Error('plan mode should not stop Mailpit'); + }, + prepareEnvironment() {}, + write(line) { + output.push(line); + }, + }, + ); + + expect(exitCode).toBe(0); + const parsed = JSON.parse(output.join('')); + expect(parsed.kind).toBe('treeseed.dev.plan'); + expect(parsed.payload.reset.actions.find((action: { id: string }) => action.id === 'root-wrangler-state')?.status).toBe('planned'); + expect(existsSync(d1Path)).toBe(true); + expect(existsSync(workerPath)).toBe(true); + } finally { + rmSync(tempRoot, { recursive: true, force: true }); + } + }); +}); diff --git a/tests/integration/dev/integrated-dev.keeps-supervising-after-readiness-until-a-parent-signal-arrives.test.ts b/tests/integration/dev/integrated-dev.keeps-supervising-after-readiness-until-a-parent-signal-arrives.test.ts new file mode 100644 index 00000000..407ffb96 --- /dev/null +++ b/tests/integration/dev/integrated-dev.keeps-supervising-after-readiness-until-a-parent-signal-arrives.test.ts @@ -0,0 +1,316 @@ +import { afterAll, describe, expect, it } from 'vitest'; + +import type { SpawnOptions } from 'node:child_process'; + +import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; + +import { tmpdir } from 'node:os'; + +import { resolve } from 'node:path'; + +import { PassThrough } from 'node:stream'; + +import { DatabaseSync } from 'node:sqlite'; + +import { + createIntegratedDevPlan, + runManagedDev, + runIntegratedDev, + runIntegratedDevReset, +} from '../../../src/runtime/dev.ts'; + +import { classifyChanges, shouldIgnoreWatchPath } from '../../../src/runtime/dev-watch.ts'; + +type FakeExitListener = (code: number | null, signal: NodeJS.Signals | null) => void; + +class FakeChildProcess { + readonly pid?: number; + readonly stdout = new PassThrough(); + readonly stderr = new PassThrough(); + kills: Array = []; + private readonly listeners: FakeExitListener[] = []; + + constructor(pid?: number) { + this.pid = pid; + } + + on(event: string, listener: FakeExitListener) { + if (event === 'exit') { + this.listeners.push(listener); + } + return this; + } + + kill(signal?: string | number) { + this.kills.push(signal); + return true; + } + + exit(code: number | null, signal: NodeJS.Signals | null = null) { + for (const listener of this.listeners) { + listener(code, signal); + } + } +} +describe('Treeseed integrated dev orchestration', () => { +const tenantRoot = resolve(process.cwd(), '.fixtures/treeseed-fixtures/sites/working-site'); + +let fakeAgentPackageRoot: string | null = null; + +afterAll(() => { + if (fakeAgentPackageRoot) { + rmSync(fakeAgentPackageRoot, { recursive: true, force: true }); + } + }); + +function getFakeAgentPackageRoot() { + if (fakeAgentPackageRoot) return fakeAgentPackageRoot; + const root = mkdtempSync(resolve(tmpdir(), 'treeseed-agent-package-')); + mkdirSync(resolve(root, 'scripts'), { recursive: true }); + mkdirSync(resolve(root, 'src/api'), { recursive: true }); + mkdirSync(resolve(root, 'src/services'), { recursive: true }); + writeFileSync(resolve(root, 'package.json'), JSON.stringify({ name: '@treeseed/agent', type: 'module' }, null, 2)); + writeFileSync(resolve(root, 'src/api/server.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/manager.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/workday-manager.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/worker.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/agents.ts'), 'export {};\n'); + fakeAgentPackageRoot = root; + return root; + } + +function withAgentPackageEnv(env: NodeJS.ProcessEnv = {}) { + return { + TREESEED_WEB_SERVICE_SECRET: undefined, + TREESEED_API_WEB_SERVICE_SECRET: undefined, + TREESEED_PLATFORM_RUNNER_SECRET: undefined, + ...env, + TREESEED_AGENT_PACKAGE_ROOT: getFakeAgentPackageRoot(), + }; + } + +function writeTempTenant(siteConfig: string) { + const root = mkdtempSync(resolve(tmpdir(), 'treeseed-dev-runtime-')); + mkdirSync(resolve(root, 'src'), { recursive: true }); + writeFileSync(resolve(root, 'src/manifest.yaml'), 'id: test\n'); + writeFileSync(resolve(root, 'treeseed.site.yaml'), siteConfig); + return root; + } + +function writeApiPackage(root: string) { + const apiRoot = resolve(root, 'packages/api'); + mkdirSync(resolve(apiRoot, 'src/api'), { recursive: true }); + mkdirSync(resolve(apiRoot, 'src/operations-runner'), { recursive: true }); + mkdirSync(resolve(apiRoot, 'scripts'), { recursive: true }); + writeFileSync(resolve(apiRoot, 'package.json'), JSON.stringify({ name: '@treeseed/api', type: 'module' }, null, 2)); + writeFileSync(resolve(apiRoot, 'src/api/server.ts'), 'export {};\n'); + writeFileSync(resolve(apiRoot, 'src/operations-runner/entrypoint.ts'), 'export {};\n'); + writeFileSync(resolve(apiRoot, 'scripts/migrate-db.ts'), 'export {};\n'); + return apiRoot; + } + +function writeLocalD1Project(root: string, id: string, slug = 'market') { + const d1Root = resolve(root, '.treeseed/generated/environments/local/.wrangler/state/v3/d1/miniflare-D1DatabaseObject'); + mkdirSync(d1Root, { recursive: true }); + const db = new DatabaseSync(resolve(d1Root, 'local-ui.sqlite')); + try { + db.exec(`CREATE TABLE projects ( + id TEXT PRIMARY KEY, + slug TEXT NOT NULL, + name TEXT NOT NULL, + created_at TEXT NOT NULL + )`); + db.prepare(`INSERT INTO projects (id, slug, name, created_at) VALUES (?, ?, ?, ?)`) + .run(id, slug, 'TreeSeed Market', '2026-05-15T00:00:00.000Z'); + } finally { + db.close(); + } + } + +it('keeps supervising after readiness until a parent signal arrives', async () => { + const signalHandlers = new Map void>(); + let spawnCount = 0; + let settled = false; + + const promise = runIntegratedDev( + { + cwd: tenantRoot, + surface: 'web', + setupMode: 'off', + feedbackMode: 'off', + openMode: 'off', + shutdownGraceMs: 0, + }, + { + spawn() { + spawnCount += 1; + return new FakeChildProcess() as never; + }, + onSignal(signal, handler) { + signalHandlers.set(signal, handler); + return () => signalHandlers.delete(signal); + }, + inspectPortOwners() { + return []; + }, + prepareEnvironment() {}, + fetch: async () => ({ ok: true }) as Response, + }, + ); + promise.then(() => { + settled = true; + }); + + await new Promise((resolvePromise) => setTimeout(resolvePromise, 25)); + expect(spawnCount).toBe(1); + expect(settled).toBe(false); + + signalHandlers.get('SIGINT')?.(); + await expect(promise).resolves.toBe(130); + }); + +it('suppresses transient local workerd broken pipe log blocks', async () => { + const signalHandlers = new Map void>(); + const child = new FakeChildProcess() as FakeChildProcess & { stderr: PassThrough }; + child.stderr = new PassThrough(); + const output: string[] = []; + + const promise = runIntegratedDev( + { + cwd: tenantRoot, + surface: 'web', + setupMode: 'off', + feedbackMode: 'off', + openMode: 'off', + shutdownGraceMs: 0, + }, + { + spawn() { + return child as never; + }, + onSignal(signal, handler) { + signalHandlers.set(signal, handler); + return () => signalHandlers.delete(signal); + }, + inspectPortOwners() { + return []; + }, + prepareEnvironment() {}, + fetch: async () => ({ ok: true }) as Response, + write(line) { + output.push(line); + }, + }, + ); + + await new Promise((resolvePromise) => setTimeout(resolvePromise, 25)); + child.stderr.write('✘ [ERROR] kj::getCaughtExceptionAsKj() = kj/async-io-unix.c++:186: disconnected: ::write(fd, buffer.begin(), buffer.size()): Broken pipe\n'); + child.stderr.write('\n'); + child.stderr.write(' stack: /tmp/workerd@51faac1 /tmp/workerd@2027e07\n'); + child.stderr.write('\n'); + child.stderr.write('real local runtime error\n'); + await new Promise((resolvePromise) => setTimeout(resolvePromise, 25)); + signalHandlers.get('SIGINT')?.(); + await expect(promise).resolves.toBe(130); + const joined = output.join(''); + expect(joined).not.toContain('Broken pipe'); + expect(joined).not.toContain('workerd@'); + expect(joined).toContain('[web] real local runtime error'); + }); + +it('starts the watcher immediately and rebaselines before observing changes', async () => { + const signalHandlers = new Map void>(); + let releaseFetch: (() => void) | null = null; + let startWatchCount = 0; + let rebaselineCount = 0; + + const promise = runIntegratedDev( + { + cwd: tenantRoot, + surface: 'web', + setupMode: 'off', + feedbackMode: 'live', + openMode: 'off', + shutdownGraceMs: 0, + }, + { + spawn() { + return new FakeChildProcess() as never; + }, + onSignal(signal, handler) { + signalHandlers.set(signal, handler); + return () => signalHandlers.delete(signal); + }, + inspectPortOwners() { + return []; + }, + prepareEnvironment() {}, + fetch: async () => new Promise((resolveResponse) => { + releaseFetch = () => resolveResponse({ ok: true } as Response); + }), + startWatch() { + startWatchCount += 1; + return { + stop() {}, + rebaseline() { + rebaselineCount += 1; + }, + }; + }, + }, + ); + + await new Promise((resolvePromise) => setTimeout(resolvePromise, 25)); + expect(startWatchCount).toBe(1); + expect(rebaselineCount).toBeGreaterThan(0); + releaseFetch?.(); + await new Promise((resolvePromise) => setTimeout(resolvePromise, 25)); + expect(startWatchCount).toBe(1); + + signalHandlers.get('SIGINT')?.(); + await expect(promise).resolves.toBe(130); + }); + +it('sends shutdown signals to child process groups when pids are available', async () => { + const signalHandlers = new Map void>(); + const killCalls: Array<{ pid: number; signal: NodeJS.Signals }> = []; + const child = new FakeChildProcess(12345); + + const promise = runIntegratedDev( + { + cwd: tenantRoot, + surface: 'web', + setupMode: 'off', + feedbackMode: 'off', + openMode: 'off', + shutdownGraceMs: 0, + }, + { + spawn() { + return child as never; + }, + killProcess(pid, signal) { + killCalls.push({ pid, signal }); + child.exit(null, signal); + }, + onSignal(signal, handler) { + signalHandlers.set(signal, handler); + return () => signalHandlers.delete(signal); + }, + inspectPortOwners() { + return []; + }, + prepareEnvironment() {}, + fetch: async () => ({ ok: true }) as Response, + }, + ); + + await new Promise((resolvePromise) => setTimeout(resolvePromise, 25)); + signalHandlers.get('SIGINT')?.(); + await expect(promise).resolves.toBe(130); + expect(killCalls).toEqual([ + { pid: -12345, signal: 'SIGTERM' }, + { pid: -12345, signal: 'SIGKILL' }, + ]); + }); +}); diff --git a/tests/integration/dev/integrated-dev.reports-stale-managed-dev-instances-and-stops-only-the-owned-process-group.test.ts b/tests/integration/dev/integrated-dev.reports-stale-managed-dev-instances-and-stops-only-the-owned-process-group.test.ts new file mode 100644 index 00000000..449bc79a --- /dev/null +++ b/tests/integration/dev/integrated-dev.reports-stale-managed-dev-instances-and-stops-only-the-owned-process-group.test.ts @@ -0,0 +1,311 @@ +import { afterAll, describe, expect, it } from 'vitest'; + +import type { SpawnOptions } from 'node:child_process'; + +import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; + +import { tmpdir } from 'node:os'; + +import { resolve } from 'node:path'; + +import { PassThrough } from 'node:stream'; + +import { DatabaseSync } from 'node:sqlite'; + +import { + createIntegratedDevPlan, + runManagedDev, + runIntegratedDev, + runIntegratedDevReset, +} from '../../../src/runtime/dev.ts'; + +import { classifyChanges, shouldIgnoreWatchPath } from '../../../src/runtime/dev-watch.ts'; + +type FakeExitListener = (code: number | null, signal: NodeJS.Signals | null) => void; + +class FakeChildProcess { + readonly pid?: number; + readonly stdout = new PassThrough(); + readonly stderr = new PassThrough(); + kills: Array = []; + private readonly listeners: FakeExitListener[] = []; + + constructor(pid?: number) { + this.pid = pid; + } + + on(event: string, listener: FakeExitListener) { + if (event === 'exit') { + this.listeners.push(listener); + } + return this; + } + + kill(signal?: string | number) { + this.kills.push(signal); + return true; + } + + exit(code: number | null, signal: NodeJS.Signals | null = null) { + for (const listener of this.listeners) { + listener(code, signal); + } + } +} +describe('Treeseed integrated dev orchestration', () => { +const tenantRoot = resolve(process.cwd(), '.fixtures/treeseed-fixtures/sites/working-site'); + +let fakeAgentPackageRoot: string | null = null; + +afterAll(() => { + if (fakeAgentPackageRoot) { + rmSync(fakeAgentPackageRoot, { recursive: true, force: true }); + } + }); + +function getFakeAgentPackageRoot() { + if (fakeAgentPackageRoot) return fakeAgentPackageRoot; + const root = mkdtempSync(resolve(tmpdir(), 'treeseed-agent-package-')); + mkdirSync(resolve(root, 'scripts'), { recursive: true }); + mkdirSync(resolve(root, 'src/api'), { recursive: true }); + mkdirSync(resolve(root, 'src/services'), { recursive: true }); + writeFileSync(resolve(root, 'package.json'), JSON.stringify({ name: '@treeseed/agent', type: 'module' }, null, 2)); + writeFileSync(resolve(root, 'src/api/server.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/manager.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/workday-manager.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/worker.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/agents.ts'), 'export {};\n'); + fakeAgentPackageRoot = root; + return root; + } + +function withAgentPackageEnv(env: NodeJS.ProcessEnv = {}) { + return { + TREESEED_WEB_SERVICE_SECRET: undefined, + TREESEED_API_WEB_SERVICE_SECRET: undefined, + TREESEED_PLATFORM_RUNNER_SECRET: undefined, + ...env, + TREESEED_AGENT_PACKAGE_ROOT: getFakeAgentPackageRoot(), + }; + } + +function writeTempTenant(siteConfig: string) { + const root = mkdtempSync(resolve(tmpdir(), 'treeseed-dev-runtime-')); + mkdirSync(resolve(root, 'src'), { recursive: true }); + writeFileSync(resolve(root, 'src/manifest.yaml'), 'id: test\n'); + writeFileSync(resolve(root, 'treeseed.site.yaml'), siteConfig); + return root; + } + +function writeApiPackage(root: string) { + const apiRoot = resolve(root, 'packages/api'); + mkdirSync(resolve(apiRoot, 'src/api'), { recursive: true }); + mkdirSync(resolve(apiRoot, 'src/operations-runner'), { recursive: true }); + mkdirSync(resolve(apiRoot, 'scripts'), { recursive: true }); + writeFileSync(resolve(apiRoot, 'package.json'), JSON.stringify({ name: '@treeseed/api', type: 'module' }, null, 2)); + writeFileSync(resolve(apiRoot, 'src/api/server.ts'), 'export {};\n'); + writeFileSync(resolve(apiRoot, 'src/operations-runner/entrypoint.ts'), 'export {};\n'); + writeFileSync(resolve(apiRoot, 'scripts/migrate-db.ts'), 'export {};\n'); + return apiRoot; + } + +function writeLocalD1Project(root: string, id: string, slug = 'market') { + const d1Root = resolve(root, '.treeseed/generated/environments/local/.wrangler/state/v3/d1/miniflare-D1DatabaseObject'); + mkdirSync(d1Root, { recursive: true }); + const db = new DatabaseSync(resolve(d1Root, 'local-ui.sqlite')); + try { + db.exec(`CREATE TABLE projects ( + id TEXT PRIMARY KEY, + slug TEXT NOT NULL, + name TEXT NOT NULL, + created_at TEXT NOT NULL + )`); + db.prepare(`INSERT INTO projects (id, slug, name, created_at) VALUES (?, ?, ?, ?)`) + .run(id, slug, 'TreeSeed Market', '2026-05-15T00:00:00.000Z'); + } finally { + db.close(); + } + } + +it('reports stale managed dev instances and stops only the owned process group', async () => { + const tempRoot = writeTempTenant(`name: Test +slug: test +siteUrl: https://example.com +contactEmail: hello@example.com +surfaces: + web: + provider: cloudflare + local: + runtime: local +`); + const previousCacheHome = process.env.XDG_CACHE_HOME; + const childPid = 55_556; + try { + process.env.XDG_CACHE_HOME = resolve(tempRoot, '.cache'); + const startOutput: string[] = []; + await runManagedDev( + { + action: 'start', + cwd: tempRoot, + surface: 'web', + setupMode: 'off', + feedbackMode: 'off', + openMode: 'off', + json: true, + readinessTimeoutMs: 1, + }, + { + supervisorCommand: 'node', + supervisorArgs: ['dev-platform.js'], + spawn() { + return new FakeChildProcess(childPid) as never; + }, + processIsAlive(pid) { + return pid === childPid; + }, + inspectPortOwners() { + return []; + }, + fetch: async () => ({ ok: false }) as Response, + write(line) { + startOutput.push(line); + }, + }, + ); + + const statusOutput: string[] = []; + const statusExit = await runManagedDev( + { action: 'status', cwd: tempRoot, json: true }, + { + processIsAlive() { + return false; + }, + write(line) { + statusOutput.push(line); + }, + }, + ); + expect(statusExit).toBe(0); + const status = JSON.parse(statusOutput.join('')); + expect(status.kind).toBe('treeseed.dev.status'); + expect(status.payload[0]).toMatchObject({ + status: 'stale', + pid: childPid, + staleReason: `Process ${childPid} is not running.`, + }); + + const livePids = new Set([childPid]); + const killCalls: Array<{ pid: number; signal: NodeJS.Signals }> = []; + const stopOutput: string[] = []; + const stopExit = await runManagedDev( + { action: 'stop', cwd: tempRoot, json: true, shutdownGraceMs: 0 }, + { + processIsAlive(pid) { + return livePids.has(pid); + }, + killProcess(pid, signal) { + killCalls.push({ pid, signal }); + livePids.delete(Math.abs(pid)); + }, + write(line) { + stopOutput.push(line); + }, + }, + ); + + expect(stopExit).toBe(0); + expect(killCalls).toEqual([{ pid: process.platform === 'win32' ? childPid : -childPid, signal: 'SIGTERM' }]); + const stopped = JSON.parse(stopOutput.join('')); + expect(stopped.kind).toBe('treeseed.dev.stop'); + expect(stopped.payload[0].status).toBe('stopped'); + expect(existsSync(resolve(tempRoot, '.treeseed/dev/instances/web.json'))).toBe(false); + expect(existsSync(resolve(tempRoot, '.treeseed/dev/pids/web.pid'))).toBe(false); + } finally { + if (previousCacheHome === undefined) { + delete process.env.XDG_CACHE_HOME; + } else { + process.env.XDG_CACHE_HOME = previousCacheHome; + } + rmSync(tempRoot, { recursive: true, force: true }); + } + }); + +it('replaces an active previous dev runtime before spawning services', async () => { + const tempRoot = writeTempTenant(`name: Test +slug: test +siteUrl: https://example.com +contactEmail: hello@example.com +surfaces: + web: + provider: cloudflare + local: + runtime: local +`); + const previousPid = 98_765; + const runtimePath = resolve(tempRoot, '.treeseed/generated/dev/runtime-web.json'); + const legacyRuntimePath = resolve(tempRoot, '.treeseed/generated/dev/runtime.json'); + const signalHandlers = new Map void>(); + const livePids = new Set([previousPid]); + const killCalls: Array<{ pid: number; signal: NodeJS.Signals }> = []; + let spawnCount = 0; + + try { + mkdirSync(resolve(tempRoot, '.treeseed/generated/dev'), { recursive: true }); + writeFileSync(legacyRuntimePath, `${JSON.stringify({ + pid: previousPid, + tenantRoot: tempRoot, + startedAt: '2026-05-06T00:00:00.000Z', + }, null, 2)}\n`); + + const promise = runIntegratedDev( + { + cwd: tempRoot, + surface: 'web', + setupMode: 'off', + feedbackMode: 'off', + openMode: 'off', + shutdownGraceMs: 0, + force: true, + }, + { + spawn() { + spawnCount += 1; + return new FakeChildProcess() as never; + }, + killProcess(pid, signal) { + killCalls.push({ pid, signal }); + livePids.delete(pid); + }, + processIsAlive(pid) { + return livePids.has(pid); + }, + inspectPortOwners() { + return []; + }, + onSignal(signal, handler) { + signalHandlers.set(signal, handler); + return () => signalHandlers.delete(signal); + }, + prepareEnvironment() {}, + fetch: async () => ({ ok: true }) as Response, + }, + ); + + await new Promise((resolvePromise) => setTimeout(resolvePromise, 25)); + expect(killCalls).toEqual([{ pid: previousPid, signal: 'SIGTERM' }]); + expect(spawnCount).toBe(1); + expect(JSON.parse(readFileSync(runtimePath, 'utf8'))).toMatchObject({ + pid: process.pid, + tenantRoot: tempRoot, + commandIds: ['web'], + }); + expect(existsSync(legacyRuntimePath)).toBe(false); + + signalHandlers.get('SIGINT')?.(); + await expect(promise).resolves.toBe(130); + expect(existsSync(runtimePath)).toBe(false); + } finally { + rmSync(tempRoot, { recursive: true, force: true }); + } + }); +}); diff --git a/tests/integration/dev/integrated-dev.uses-the-seeded-local-market-project-id-for-node-services.test.ts b/tests/integration/dev/integrated-dev.uses-the-seeded-local-market-project-id-for-node-services.test.ts new file mode 100644 index 00000000..fe22352c --- /dev/null +++ b/tests/integration/dev/integrated-dev.uses-the-seeded-local-market-project-id-for-node-services.test.ts @@ -0,0 +1,304 @@ +import { afterAll, describe, expect, it } from 'vitest'; + +import type { SpawnOptions } from 'node:child_process'; + +import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; + +import { tmpdir } from 'node:os'; + +import { resolve } from 'node:path'; + +import { PassThrough } from 'node:stream'; + +import { DatabaseSync } from 'node:sqlite'; + +import { + createIntegratedDevPlan, + runManagedDev, + runIntegratedDev, + runIntegratedDevReset, +} from '../../../src/runtime/dev.ts'; + +import { classifyChanges, shouldIgnoreWatchPath } from '../../../src/runtime/dev-watch.ts'; + +type FakeExitListener = (code: number | null, signal: NodeJS.Signals | null) => void; + +class FakeChildProcess { + readonly pid?: number; + readonly stdout = new PassThrough(); + readonly stderr = new PassThrough(); + kills: Array = []; + private readonly listeners: FakeExitListener[] = []; + + constructor(pid?: number) { + this.pid = pid; + } + + on(event: string, listener: FakeExitListener) { + if (event === 'exit') { + this.listeners.push(listener); + } + return this; + } + + kill(signal?: string | number) { + this.kills.push(signal); + return true; + } + + exit(code: number | null, signal: NodeJS.Signals | null = null) { + for (const listener of this.listeners) { + listener(code, signal); + } + } +} +describe('Treeseed integrated dev orchestration', () => { +const tenantRoot = resolve(process.cwd(), '.fixtures/treeseed-fixtures/sites/working-site'); + +let fakeAgentPackageRoot: string | null = null; + +afterAll(() => { + if (fakeAgentPackageRoot) { + rmSync(fakeAgentPackageRoot, { recursive: true, force: true }); + } + }); + +function getFakeAgentPackageRoot() { + if (fakeAgentPackageRoot) return fakeAgentPackageRoot; + const root = mkdtempSync(resolve(tmpdir(), 'treeseed-agent-package-')); + mkdirSync(resolve(root, 'scripts'), { recursive: true }); + mkdirSync(resolve(root, 'src/api'), { recursive: true }); + mkdirSync(resolve(root, 'src/services'), { recursive: true }); + writeFileSync(resolve(root, 'package.json'), JSON.stringify({ name: '@treeseed/agent', type: 'module' }, null, 2)); + writeFileSync(resolve(root, 'src/api/server.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/manager.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/workday-manager.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/worker.ts'), 'export {};\n'); + writeFileSync(resolve(root, 'src/services/agents.ts'), 'export {};\n'); + fakeAgentPackageRoot = root; + return root; + } + +function withAgentPackageEnv(env: NodeJS.ProcessEnv = {}) { + return { + TREESEED_WEB_SERVICE_SECRET: undefined, + TREESEED_API_WEB_SERVICE_SECRET: undefined, + TREESEED_PLATFORM_RUNNER_SECRET: undefined, + ...env, + TREESEED_AGENT_PACKAGE_ROOT: getFakeAgentPackageRoot(), + }; + } + +function writeTempTenant(siteConfig: string) { + const root = mkdtempSync(resolve(tmpdir(), 'treeseed-dev-runtime-')); + mkdirSync(resolve(root, 'src'), { recursive: true }); + writeFileSync(resolve(root, 'src/manifest.yaml'), 'id: test\n'); + writeFileSync(resolve(root, 'treeseed.site.yaml'), siteConfig); + return root; + } + +function writeApiPackage(root: string) { + const apiRoot = resolve(root, 'packages/api'); + mkdirSync(resolve(apiRoot, 'src/api'), { recursive: true }); + mkdirSync(resolve(apiRoot, 'src/operations-runner'), { recursive: true }); + mkdirSync(resolve(apiRoot, 'scripts'), { recursive: true }); + writeFileSync(resolve(apiRoot, 'package.json'), JSON.stringify({ name: '@treeseed/api', type: 'module' }, null, 2)); + writeFileSync(resolve(apiRoot, 'src/api/server.ts'), 'export {};\n'); + writeFileSync(resolve(apiRoot, 'src/operations-runner/entrypoint.ts'), 'export {};\n'); + writeFileSync(resolve(apiRoot, 'scripts/migrate-db.ts'), 'export {};\n'); + return apiRoot; + } + +function writeLocalD1Project(root: string, id: string, slug = 'market') { + const d1Root = resolve(root, '.treeseed/generated/environments/local/.wrangler/state/v3/d1/miniflare-D1DatabaseObject'); + mkdirSync(d1Root, { recursive: true }); + const db = new DatabaseSync(resolve(d1Root, 'local-ui.sqlite')); + try { + db.exec(`CREATE TABLE projects ( + id TEXT PRIMARY KEY, + slug TEXT NOT NULL, + name TEXT NOT NULL, + created_at TEXT NOT NULL + )`); + db.prepare(`INSERT INTO projects (id, slug, name, created_at) VALUES (?, ?, ?, ?)`) + .run(id, slug, 'TreeSeed Market', '2026-05-15T00:00:00.000Z'); + } finally { + db.close(); + } + } + +it('uses the seeded local market project id for node services', () => { + const root = writeTempTenant('title: Test Site\n'); + writeLocalD1Project(root, 'project-market-local'); + try { + const plan = createIntegratedDevPlan({ + cwd: root, + env: withAgentPackageEnv({ + LOCAL_DEV_MODE: 'cloudflare', + TREESEED_API_D1_LOCAL_PERSIST_TO: resolve(root, '.treeseed/generated/environments/local/.wrangler/state/v3/d1'), + }), + }); + + for (const id of ['api', 'manager', 'worker'] as const) { + expect(plan.commands.find((command) => command.id === id)?.env.TREESEED_PROJECT_ID).toBe('project-market-local'); + } + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + +it('keeps generated Wrangler D1 sqlite scoped to the web knowledge hub by default', () => { + const plan = createIntegratedDevPlan({ + cwd: tenantRoot, + setupMode: 'off', + env: withAgentPackageEnv({ + TREESEED_API_D1_LOCAL_PERSIST_TO: '/tmp/treeseed-generated-d1', + }), + }); + + expect(plan.commands.find((command) => command.id === 'manager')?.env.TREESEED_AGENT_D1_PERSIST_TO).toBeUndefined(); + expect(plan.commands.find((command) => command.id === 'worker')?.env.TREESEED_AGENT_D1_PERSIST_TO).toBeUndefined(); + expect(plan.commands.find((command) => command.id === 'web')?.env.TREESEED_API_D1_LOCAL_PERSIST_TO).toBe('/tmp/treeseed-generated-d1'); + }); + +it('does not pass explicit D1 overrides to local agent services', () => { + const plan = createIntegratedDevPlan({ + cwd: tenantRoot, + setupMode: 'off', + env: withAgentPackageEnv({ + TREESEED_AGENT_D1_PERSIST_TO: '/tmp/treeseed-agent.sqlite', + }), + }); + + expect(plan.commands.find((command) => command.id === 'manager')?.env.TREESEED_AGENT_D1_PERSIST_TO).toBeUndefined(); + expect(plan.commands.find((command) => command.id === 'worker')?.env.TREESEED_AGENT_D1_PERSIST_TO).toBeUndefined(); + }); + +it('selects provider-local Wrangler for Cloudflare web surfaces', () => { + const tempRoot = writeTempTenant(`name: Test +slug: test +siteUrl: https://example.com +contactEmail: hello@example.com +cloudflare: + accountId: account-123 +surfaces: + web: + provider: cloudflare + local: + runtime: provider +`); + try { + const plan = createIntegratedDevPlan({ cwd: tempRoot, surface: 'web', setupMode: 'off', env: {} }); + + expect(plan.localRuntimes.web).toMatchObject({ + requested: 'provider', + selected: 'cloudflare-wrangler-local', + provider: 'cloudflare', + }); + expect(plan.commands[0]?.label).toBe('Cloudflare Wrangler UI'); + expect(plan.commands[0]?.args).toEqual(expect.arrayContaining([ + 'dev', + '--local', + '--config', + resolve(tempRoot, '.treeseed/generated/environments/local/wrangler.toml'), + ])); + } finally { + rmSync(tempRoot, { recursive: true, force: true }); + } + }); + +it('uses Astro local when Cloudflare web explicitly requests local runtime', () => { + const tempRoot = writeTempTenant(`name: Test +slug: test +siteUrl: https://example.com +contactEmail: hello@example.com +cloudflare: + accountId: account-123 +surfaces: + web: + provider: cloudflare + local: + runtime: local +`); + try { + const plan = createIntegratedDevPlan({ cwd: tempRoot, surface: 'web', setupMode: 'off', env: {} }); + + expect(plan.localRuntimes.web.selected).toBe('astro-local'); + expect(plan.commands[0]?.label).toBe('Astro UI'); + expect(plan.commands[0]?.args).toEqual(expect.arrayContaining(['dev', '--host', '127.0.0.1', '--port', '4321'])); + } finally { + rmSync(tempRoot, { recursive: true, force: true }); + } + }); + +it('lets the CLI override Cloudflare provider web runtime with Astro local', () => { + const tempRoot = writeTempTenant(`name: Test +slug: test +siteUrl: https://example.com +contactEmail: hello@example.com +cloudflare: + accountId: account-123 +surfaces: + web: + provider: cloudflare + local: + runtime: provider +`); + try { + const plan = createIntegratedDevPlan({ + cwd: tempRoot, + surface: 'web', + setupMode: 'off', + webRuntime: 'local', + env: { + LOCAL_DEV_MODE: 'cloudflare', + }, + }); + + expect(plan.localRuntimes.web).toMatchObject({ + requested: 'local', + selected: 'astro-local', + provider: 'cloudflare', + }); + expect(plan.commands[0]?.label).toBe('Astro UI'); + expect(plan.commands[0]?.args).toEqual(expect.arrayContaining(['dev', '--host', '127.0.0.1', '--port', '4321'])); + expect(plan.commands[0]?.env.LOCAL_DEV_MODE).toBeUndefined(); + expect(plan.commands[0]?.env.TREESEED_API_D1_LOCAL_PERSIST_TO).toBe( + resolve(tempRoot, '.treeseed/generated/environments/local/.wrangler/state/v3/d1'), + ); + } finally { + rmSync(tempRoot, { recursive: true, force: true }); + } + }); + +it('starts manager and worker but not agents from the default integrated runtime', () => { + const tempRoot = writeTempTenant(`name: Test +slug: test +siteUrl: https://example.com +contactEmail: hello@example.com +cloudflare: + accountId: account-123 +services: + api: + provider: railway + local: + runtime: auto +`); + try { + const plan = createIntegratedDevPlan({ + cwd: tempRoot, + surface: 'integrated', + setupMode: 'off', + env: withAgentPackageEnv(), + }); + + expect(plan.commands.map((command) => command.id)).toEqual(['web', 'api', 'manager', 'worker']); + expect(plan.localRuntimes).toHaveProperty('api'); + expect(plan.localRuntimes).toHaveProperty('manager'); + expect(plan.localRuntimes).toHaveProperty('worker'); + expect(plan.localRuntimes).not.toHaveProperty('agents'); + } finally { + rmSync(tempRoot, { recursive: true, force: true }); + } + }); +}); diff --git a/test/setup-runtime.ts b/tests/support/setup-runtime.ts similarity index 51% rename from test/setup-runtime.ts rename to tests/support/setup-runtime.ts index b651684b..59303083 100644 --- a/test/setup-runtime.ts +++ b/tests/support/setup-runtime.ts @@ -1,28 +1,28 @@ import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; -import { loadTreeseedManifest } from '@treeseed/sdk/platform/tenant-config'; -import { loadTreeseedDeployConfig } from '@treeseed/sdk/platform/deploy-config'; -import { parseSiteConfig } from '../src/utils/site-config-schema.js'; +import { loadManifest } from '../../src/runtime/platform/tenant-config.ts'; +import { loadDeployConfigFromPath } from '../../src/runtime/platform/deploy-config.ts'; +import { parseSiteConfig } from '../../src/utils/configuration/site-config-schema.js'; const fixtureRoot = resolve(process.cwd(), '.fixtures/treeseed-fixtures/sites/working-site'); -const tenantConfig = loadTreeseedManifest(resolve(fixtureRoot, 'src/manifest.yaml')); +const tenantConfig = loadManifest(resolve(fixtureRoot, 'src/manifest.yaml')); const siteConfig = parseSiteConfig(readFileSync(tenantConfig.siteConfigPath, 'utf8')); -const deployConfig = loadTreeseedDeployConfig(resolve(fixtureRoot, 'treeseed.site.yaml')); +const deployConfig = loadDeployConfigFromPath(resolve(fixtureRoot, 'treeseed.site.yaml')); Object.defineProperties(globalThis, { - __TREESEED_PROJECT_ROOT__: { + PROJECT_ROOT: { configurable: true, value: fixtureRoot, }, - __TREESEED_TENANT_CONFIG__: { + TENANT_CONFIG: { configurable: true, value: tenantConfig, }, - __TREESEED_SITE_CONFIG__: { + SITE_CONFIG: { configurable: true, value: siteConfig, }, - __TREESEED_DEPLOY_CONFIG__: { + DEPLOY_CONFIG: { configurable: true, value: deployConfig, }, diff --git a/tests/unit/content/governance-content-schemas.test.ts b/tests/unit/content/governance-content-schemas.test.ts new file mode 100644 index 00000000..335cf863 --- /dev/null +++ b/tests/unit/content/governance-content-schemas.test.ts @@ -0,0 +1,43 @@ +import { z } from 'zod'; +import { describe, expect, it, vi } from 'vitest'; + +vi.mock('astro:content', () => ({ reference: () => z.string() })); + +const exactRef = { + store: 'treedx', model: 'objective', id: 'sdk-objective', repository: 'treeseed-ai/sdk-library', + commit: 'a'.repeat(40), path: 'objectives/sdk-objective.yaml', revision: 1, digest: `sha256:${'b'.repeat(64)}`, +}; + +describe('governance content schemas', () => { + it('does not expose retired mode-run authority in parsed discussion events', async () => { + const { createDiscussionCollectionSchemas } = await import('../../../src/content/discussion-schemas.ts'); + const event = createDiscussionCollectionSchemas().discussionEventSchema.parse({ + title: 'Assignment completed', discussionId: 'discussion-sdk', phase: 'acting', sequence: 1, + assignmentId: 'assignment-sdk', occurredAt: '2026-09-30T14:00:00.000Z', modeRunId: 'retired-run', + }); + expect(event.assignmentId).toBe('assignment-sdk'); + expect(event).not.toHaveProperty('modeRunId'); + }); + it('uses the canonical SDK proposal contract without a Core-owned compatibility shape', async () => { + const { createGovernanceCollectionSchemas } = await import('../../../src/content/governance-schemas.ts'); + const { proposalSchema } = createGovernanceCollectionSchemas(); + const proposal = { + schemaVersion: 'treeseed.proposal/v1', id: 'portable-proposal', projectId: 'sdk', + title: 'Portable proposal', request: 'Use one governed proposal contract.', status: 'draft', objectiveRefs: [exactRef], + }; + expect(proposalSchema.safeParse(proposal).success).toBe(true); + expect(proposalSchema.safeParse({ ...proposal, proposalType: 'retired-core-shape' }).success).toBe(false); + }); + + it('reports canonical SDK field diagnostics before Astro consumes content', async () => { + const { createGovernanceCollectionSchemas } = await import('../../../src/content/governance-schemas.ts'); + const invalid = createGovernanceCollectionSchemas().questionSchema.safeParse({ + schemaVersion: 'treeseed.question/v1', id: 'portable-question', projectId: 'sdk', subjectRef: exactRef, + question: '', status: 'open', askedAt: '2026-09-21T04:00:00.000Z', + }); + expect(invalid.success).toBe(false); + if (!invalid.success) expect(invalid.error.issues).toContainEqual(expect.objectContaining({ + path: ['question'], params: expect.objectContaining({ code: 'content_zod_too_small', model: 'question' }), + })); + }); +}); diff --git a/tests/unit/content/group-content-schemas.test.ts b/tests/unit/content/group-content-schemas.test.ts new file mode 100644 index 00000000..b5422ab5 --- /dev/null +++ b/tests/unit/content/group-content-schemas.test.ts @@ -0,0 +1,22 @@ +import { existsSync, readFileSync, readdirSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { parse } from 'yaml'; +import { describe, expect, it } from 'vitest'; +import { createGroupCollectionSchemas } from '../../../src/content/group-schemas.ts'; + +function frontmatter(path: string) { + const raw = readFileSync(path, 'utf8'); + const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/u); + if (!match) throw new Error(`Missing frontmatter in ${path}.`); + return parse(match[1]!); +} + +describe('group content schemas', () => { + it('keeps group definitions while agent authority remains exclusively in TreeDX', () => { + const fixture = resolve('.fixtures/treeseed-fixtures/sites/working-site/src/content'); + const group = frontmatter(resolve(fixture, 'groups/architecture.md')); + const agentDirectory = resolve(fixture, 'agents'); + expect(existsSync(agentDirectory) ? readdirSync(agentDirectory) : []).toEqual([]); + expect(createGroupCollectionSchemas().groupSchema.safeParse(group).success).toBe(true); + }); +}); diff --git a/tests/unit/content/portable-auxiliary-content-schemas.test.ts b/tests/unit/content/portable-auxiliary-content-schemas.test.ts new file mode 100644 index 00000000..b00c260f --- /dev/null +++ b/tests/unit/content/portable-auxiliary-content-schemas.test.ts @@ -0,0 +1,39 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { parse } from 'yaml'; +import { z } from 'zod'; +import { describe, expect, it, vi } from 'vitest'; +import { createAgentCollectionSchemas } from '../../../src/content/agent-schemas.ts'; +import { createCatalogCollectionSchemas } from '../../../src/content/catalog-schemas.ts'; +import { createWorkdayCollectionSchemas } from '../../../src/content/workday-schemas.ts'; + +vi.mock('astro:content', () => ({ reference: () => z.string() })); + +function frontmatter(path: string) { + const match = readFileSync(path, 'utf8').match(/^---\r?\n([\s\S]*?)\r?\n---/u); + if (!match) throw new Error(`Missing frontmatter in ${path}.`); + return parse(match[1]!); +} + +describe('portable auxiliary content schemas', () => { + it('validates live template and agent-test content through SDK-owned Zod schemas', () => { + const root = resolve('tests/fixtures/library'); + expect(createCatalogCollectionSchemas().templateProductSchema.safeParse( + frontmatter(resolve(root, 'templates/engineering.mdx')), + ).success).toBe(true); + expect(createAgentCollectionSchemas().agentTestSchema.safeParse( + frontmatter(resolve(root, 'agent-tests/docs-reviewer-basic.mdx')), + ).success).toBe(true); + }); + + it('preserves the Astro workday collection contract with portable field diagnostics', () => { + const result = createWorkdayCollectionSchemas().workdaySchema.safeParse({ + title: 'Broken workday', slug: 'broken', workDayId: '', reportVersion: 'v1', projectId: 'project-1', + environment: 'local', workdayState: 'running', startedAt: 'not-a-date', generatedAt: new Date(), summary: 'Invalid input.', + }); + expect(result.success).toBe(false); + if (!result.success) expect(result.error.issues.map((issue) => issue.path.join('.'))).toEqual(expect.arrayContaining([ + 'workDayId', 'startedAt', + ])); + }); +}); diff --git a/tests/unit/knowledge/reader-delivery.test.ts b/tests/unit/knowledge/reader-delivery.test.ts new file mode 100644 index 00000000..6ff9d1e6 --- /dev/null +++ b/tests/unit/knowledge/reader-delivery.test.ts @@ -0,0 +1,37 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { loadFederatedReader } from '../../../src/utils/knowledge/reader-library.ts'; + +afterEach(() => vi.unstubAllGlobals()); + +function response() { + return new Response(JSON.stringify({ payload: { + book: { id: 'book-a', slug: 'guide', title: 'Guide' }, + navigation: [{ id: 'page-a', bookId: 'book-a', slug: 'start', title: 'Start', order: 1 }], + page: { id: 'page-a', bookId: 'book-a', slug: 'start', title: 'Start', bodyMarkdown: 'Body' }, + revision: 'revision-a', + } }), { status: 200, headers: { 'content-type': 'application/json', etag: '"revision-a"' } }); +} + +describe('reader delivery', () => { + it('coalesces concurrent anonymous reader requests and consumes compact navigation', async () => { + const fetch = vi.fn(async () => response()); + vi.stubGlobal('fetch', fetch); + const locals = { runtime: { env: { TREESEED_MARKET_API_BASE_URL: 'https://reader-coalesce.example' } } } as any; + const readers = await Promise.all(Array.from({ length: 12 }, () => loadFederatedReader(locals, { + teamSlug: 'team-a', bookSlug: 'guide', pageSlug: 'start', + }))); + expect(fetch).toHaveBeenCalledTimes(1); + expect(readers[0]?.pages.map((page) => page.id)).toEqual(['page-a']); + expect(readers[0]?.page?.data.bodyMarkdown).toBe('Body'); + }); + + it('does not share authenticated reader responses', async () => { + const fetch = vi.fn(async () => response()); + vi.stubGlobal('fetch', fetch); + const locals = { runtime: { env: { TREESEED_MARKET_API_BASE_URL: 'https://reader-private.example' } }, + auth: { principal: { id: 'user-a' }, session: { id: 'session-a' } } } as any; + await loadFederatedReader(locals, { teamSlug: 'team-a', bookSlug: 'guide' }); + await loadFederatedReader(locals, { teamSlug: 'team-a', bookSlug: 'guide' }); + expect(fetch).toHaveBeenCalledTimes(2); + }); +}); diff --git a/tests/unit/knowledge/starlight-frontmatter.test.ts b/tests/unit/knowledge/starlight-frontmatter.test.ts new file mode 100644 index 00000000..61d8b3eb --- /dev/null +++ b/tests/unit/knowledge/starlight-frontmatter.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from 'vitest'; +import { bookSidebar, starlightKnowledgeFrontmatter } from '../../../src/utils/knowledge/reader-library'; + +describe('Starlight knowledge frontmatter', () => { + it('omits nullable optional repository metadata before Starlight validation', () => { + const frontmatter = starlightKnowledgeFrontmatter({ + title: 'Getting started', + summary: 'Published from an exact revision.', + parentId: null, + cover: undefined, + order: 0, + }); + + expect(frontmatter).toMatchObject({ + title: 'Getting started', + description: 'Published from an exact revision.', + order: 0, + editUrl: false, + }); + expect(frontmatter).not.toHaveProperty('parentId'); + expect(frontmatter).not.toHaveProperty('cover'); + }); + + it('renders parent and child pages as nested Starlight navigation', () => { + const sidebar = bookSidebar('treeseed', { id: 'guide', data: { id: 'guide', slug: 'guide', title: 'Guide' } }, [ + { id: 'child', data: { id: 'child', parentId: 'parent', slug: 'parent/child', title: 'Child', order: 1 } }, + { id: 'parent', data: { id: 'parent', slug: 'parent', title: 'Parent', order: 1 } }, + ]); + expect(sidebar[0].items[1]).toMatchObject({ label: 'Parent', items: [{ label: 'Child' }] }); + }); +}); diff --git a/tests/unit/ui/primitives.test.ts b/tests/unit/ui/primitives.test.ts new file mode 100644 index 00000000..e69a41c2 --- /dev/null +++ b/tests/unit/ui/primitives.test.ts @@ -0,0 +1,123 @@ +import { existsSync, readFileSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { describe, expect, it } from 'vitest'; + +const testDir = dirname(fileURLToPath(import.meta.url)); +const packageRoot = resolve(testDir, '../../..'); + +function source(path: string) { + const entry = resolve(packageRoot, path); + const visited = new Set(); + const readModule = (modulePath: string): string[] => { + if (visited.has(modulePath)) return []; + visited.add(modulePath); + const contents = readFileSync(modulePath, 'utf8'); + const dependencies = [...contents.matchAll(/export\s+(?:\*|\{[^}]*\})\s+from\s+['"](\.{1,2}\/[^'"]+)['"]/gu)] + .map((match) => resolve(dirname(modulePath), match[1]!.replace(/\.js$/u, '.ts'))) + .filter(existsSync); + return [contents, ...dependencies.flatMap(readModule)]; + }; + return readModule(entry).join('\n'); +} + +function packageJson() { + return JSON.parse(source('package.json')) as { + dependencies?: Record; + exports: Record; + }; +} + +describe('core UI ownership boundary', () => { + it('depends on @treeseed/ui for reusable web components', () => { + expect(packageJson().dependencies?.['@treeseed/ui']).toBeDefined(); + }); + + it('does not publish reusable component or style entrypoints', () => { + const exports = packageJson().exports; + for (const entrypoint of Object.keys(exports)) { + expect(entrypoint, entrypoint).not.toMatch(/^\.\/components(?:\/|$)/u); + expect(entrypoint, entrypoint).not.toMatch(/^\.\/styles\/(?:theme|ui|forms|app-shell)\.css$/u); + } + }); + + it('resolves default docs chrome from @treeseed/ui while preserving tenant overrides', () => { + const site = source('src/support/site.ts'); + expect(site).toContain('resolveSiteResource(siteLayers, \'components\', resourcePath)'); + expect(site).toContain('@treeseed/ui/components/astro/docs/Header.astro'); + expect(site).toContain('@treeseed/ui/components/astro/docs/Footer.astro'); + expect(site).toContain('@treeseed/ui/components/astro/core/SiteTitle.astro'); + expect(site).not.toContain('./components/docs/Header.astro'); + expect(site).not.toContain('./components/docs/Footer.astro'); + }); + + it('uses @treeseed/ui from Core pages and stylesheet composition', () => { + for (const path of [ + 'src/pages/index.astro', + 'src/pages/contact.astro', + 'src/pages/books/index.astro', + 'src/pages/t/[teamSlug]/books/[bookSlug]/[...pageSlug].astro', + 'src/styles/global.css', + ]) { + expect(source(path), path).toContain('@treeseed/ui'); + } + }); + + it('renders book knowledge through the public Starlight page component without page-local styling', () => { + for (const path of [ + 'src/pages/t/[teamSlug]/books/[bookSlug]/index.astro', + 'src/pages/t/[teamSlug]/books/[bookSlug]/[...pageSlug].astro', + ]) { + const contents = source(path); + expect(contents, path).toContain("@astrojs/starlight/components/StarlightPage.astro"); + expect(contents, path).toContain('canReadKnowledge'); + expect(contents, path).not.toContain(' { + const routes = source('src/support/routes.ts'); + expect(routes).toContain("coreRoute('/books'"); + expect(routes).toContain("coreRoute('/t/[teamSlug]/books/[bookSlug]'"); + expect(routes).toContain("coreRoute('/t/[teamSlug]/books/[bookSlug]/[...pageSlug]'"); + expect(routes).not.toContain("'/knowledge'"); + expect(routes).not.toContain('ReaderTemplate'); + }); + + it('negotiates enhanced form JSON and progressive POST/303 responses identically in Astro and Worker runtimes', () => { + const astroEndpoint = source('src/pages/api/form/submit.ts'); + const workerEndpoint = source('src/worker/forms-worker.ts'); + for (const contents of [astroEndpoint, workerEndpoint]) { + expect(contents).toContain("from '@treeseed/ui/forms'"); + expect(contents).toContain('formSubmissionResponse'); + expect(contents).toContain('reset: result.ok'); + expect(contents).toContain('fallbackRedirect: result.redirectTo'); + } + expect(astroEndpoint).not.toContain('Astro.redirect'); + expect(workerEndpoint).not.toMatch(/request\.method === 'POST'[\s\S]*new Response\(null,\s*\{\s*status:\s*303/u); + }); + + it('fails closed for local team and project visibility without policy-filtered manifests', () => { + const helper = source('src/utils/knowledge/reader-library.ts'); + expect(helper).toContain("if (visibility === 'public') return true"); + expect(helper).toContain('Local readers fail closed'); + expect(helper).not.toContain('r2://'); + }); + + it('keeps removed reusable source trees absent', () => { + for (const path of [ + 'src/components/ui', + 'src/components/docs', + 'src/components/forms', + 'src/components/site', + 'src/layouts/MainLayout.astro', + 'src/styles/theme.css', + 'src/styles/ui.css', + 'src/styles/forms.css', + 'src/styles/app-shell.css', + ]) { + expect(existsSync(resolve(packageRoot, path)), path).toBe(false); + } + }); +}); diff --git a/test/ui/style-scope.test.ts b/tests/unit/ui/style-scope.test.ts similarity index 88% rename from test/ui/style-scope.test.ts rename to tests/unit/ui/style-scope.test.ts index 32160b35..3f19b91b 100644 --- a/test/ui/style-scope.test.ts +++ b/tests/unit/ui/style-scope.test.ts @@ -41,7 +41,7 @@ describe('core public style scope', () => { }); it('wraps core public pages that render UI package public layouts or site components', () => { - const uiPublicImports = /@treeseed\/ui\/components\/astro\/(?:layouts\/(?:MainLayout|BookLayout|ContentLayout|BridgeLayout|AuthoredEntryLayout|ProfileLayout)|site\/(?:Hero|SectionIntro|BookList|ChronicleList|ProfileList|NotesList|PathCard|CTASection|TrustCallout|RouteNotFound)|forms\/ContactForm)\.astro/u; + const uiPublicImports = /@treeseed\/ui\/components\/astro\/(?:layouts\/(?:MainLayout|ContentLayout|BridgeLayout|AuthoredEntryLayout|ProfileLayout)|site\/(?:Hero|SectionIntro|BookList|ChronicleList|ProfileList|NotesList|PathCard|CTASection|TrustCallout|RouteNotFound)|forms\/ContactForm)\.astro/u; const offenders = filesUnder('src/pages') .filter((path) => extname(path) === '.astro') .filter((path) => path !== 'src/pages/ui/index.astro') diff --git a/tests/unit/ui/workspace-package-reload.test.ts b/tests/unit/ui/workspace-package-reload.test.ts new file mode 100644 index 00000000..978cd126 --- /dev/null +++ b/tests/unit/ui/workspace-package-reload.test.ts @@ -0,0 +1,57 @@ +import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { resolve } from 'node:path'; +import { EventEmitter } from 'node:events'; +import { describe, expect, it, vi } from 'vitest'; +import { createRecoveringWorkspacePackageWatcher, createWorkspacePackageReloadCoordinator, reloadWorkspacePackageModules, workspacePackageOutputRoots } from '../../../src/site/workspace-package-reload.ts'; + +describe('workspace package live reload', () => { + it('batches linked package output and waits for complete runtime markers', () => { + vi.useFakeTimers(); + const root = mkdtempSync(resolve(tmpdir(), 'treeseed-package-reload-')); + const output = resolve(root, 'packages/ui/dist'); + mkdirSync(output, { recursive: true }); + const send = vi.fn(); + const coordinator = createWorkspacePackageReloadCoordinator(workspacePackageOutputRoots(root), send); + coordinator.notify(resolve(output, 'react/monitor.js')); + vi.advanceTimersByTime(250); + expect(send).not.toHaveBeenCalled(); + writeFileSync(resolve(output, 'react.js'), 'export {};'); + writeFileSync(resolve(output, 'ui.css'), ':root {}'); + coordinator.notify(resolve(output, 'ui.css')); + vi.advanceTimersByTime(250); + expect(send).toHaveBeenCalledOnce(); + coordinator.dispose(); + vi.useRealTimers(); + }); + + it('invalidates transformed modules before refreshing connected browsers', () => { + const order: string[] = []; + reloadWorkspacePackageModules({ + moduleGraph: { invalidateAll: () => order.push('invalidate') }, + ws: { send: () => order.push('reload') }, + }); + expect(order).toEqual(['invalidate', 'reload']); + }); + + it('recovers from transient filesystem watcher errors during package builds', () => { + vi.useFakeTimers(); + const watchers: Array }> = []; + const notify = vi.fn(); + const watchFactory = vi.fn((_path: string, _listener: (event: string, filename: string | Buffer | null) => void) => { + const watcher = Object.assign(new EventEmitter(), { close: vi.fn() }); + watchers.push(watcher); + return watcher as never; + }); + const controller = createRecoveringWorkspacePackageWatcher('/workspace/packages/ui/dist', notify, watchFactory, 500); + + expect(watchFactory).toHaveBeenCalledOnce(); + watchers[0]?.emit('error', Object.assign(new Error('transient access failure'), { code: 'EACCES' })); + expect(watchers[0]?.close).toHaveBeenCalledOnce(); + vi.advanceTimersByTime(500); + expect(watchFactory).toHaveBeenCalledTimes(2); + expect(notify).toHaveBeenLastCalledWith('/workspace/packages/ui/dist'); + controller.close(); + vi.useRealTimers(); + }); +}); diff --git a/test/utils/editorial-preview.test.ts b/tests/unit/utils/editorial-preview.test.ts similarity index 90% rename from test/utils/editorial-preview.test.ts rename to tests/unit/utils/editorial-preview.test.ts index 1cc200ad..1c4031a8 100644 --- a/test/utils/editorial-preview.test.ts +++ b/tests/unit/utils/editorial-preview.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { signEditorialPreviewToken } from '@treeseed/sdk/platform/published-content'; -import { resolveEditorialPreview } from '../../src/middleware/editorial-preview.ts'; +import { signEditorialPreviewToken } from '../../../src/runtime/platform/published-content.ts'; +import { resolveEditorialPreview } from '../../../src/middleware/editorial-preview.ts'; function createContext(url: string) { const cookies = new Map(); diff --git a/test/utils/forms/crypto.test.ts b/tests/unit/utils/forms/crypto.test.ts similarity index 90% rename from test/utils/forms/crypto.test.ts rename to tests/unit/utils/forms/crypto.test.ts index 48f7c328..18442b8d 100644 --- a/test/utils/forms/crypto.test.ts +++ b/tests/unit/utils/forms/crypto.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { signFormToken, verifyFormToken } from '../../../src/utils/forms/crypto'; +import { signFormToken, verifyFormToken } from '../../../../src/utils/forms/security/crypto'; describe('form token signing', () => { it('round-trips a signed token payload', async () => { diff --git a/test/utils/forms/guard.test.ts b/tests/unit/utils/forms/guard.test.ts similarity index 90% rename from test/utils/forms/guard.test.ts rename to tests/unit/utils/forms/guard.test.ts index f1808606..1abf12e1 100644 --- a/test/utils/forms/guard.test.ts +++ b/tests/unit/utils/forms/guard.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { applySubmissionRateLimit, assertNonceUnused } from '../../../src/utils/forms/guard'; -import type { KvNamespaceLike } from '../../../src/types/cloudflare'; +import { applySubmissionRateLimit, assertNonceUnused } from '../../../../src/utils/forms/security/guard'; +import type { KvNamespaceLike } from '../../../../src/types/cloudflare'; class MemoryKv implements KvNamespaceLike { private store = new Map(); diff --git a/test/utils/forms/persistence.test.ts b/tests/unit/utils/forms/persistence.test.ts similarity index 93% rename from test/utils/forms/persistence.test.ts rename to tests/unit/utils/forms/persistence.test.ts index 370474c2..30fbe9dd 100644 --- a/test/utils/forms/persistence.test.ts +++ b/tests/unit/utils/forms/persistence.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; -import type { D1DatabaseLike, D1PreparedStatementLike } from '../../../src/types/cloudflare'; -import { createContactSubmission } from '../../../src/utils/forms/contact-submissions'; -import { upsertSubscriber } from '../../../src/utils/forms/subscribers'; +import type { D1DatabaseLike, D1PreparedStatementLike } from '../../../../src/types/cloudflare'; +import { createContactSubmission } from '../../../../src/utils/forms/persistence/contact-submissions'; +import { upsertSubscriber } from '../../../../src/utils/forms/persistence/subscribers'; interface RecordedStatement { query: string; diff --git a/test/utils/forms/routing.test.ts b/tests/unit/utils/forms/routing.test.ts similarity index 81% rename from test/utils/forms/routing.test.ts rename to tests/unit/utils/forms/routing.test.ts index 30377272..6b2f5fad 100644 --- a/test/utils/forms/routing.test.ts +++ b/tests/unit/utils/forms/routing.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { resolveContactRecipientsFromMap } from '../../../src/utils/forms/routing-core'; -import { SITE_EMAIL_NOTIFICATIONS } from '../../../src/utils/site-config'; +import { resolveContactRecipientsFromMap } from '../../../../src/utils/forms/routing/routing-core'; +import { SITE_EMAIL_NOTIFICATIONS } from '../../../../src/utils/configuration/site-config'; describe('contact routing resolution', () => { it('prefers a specific route over the default recipients', () => { diff --git a/test/utils/forms/runtime.test.ts b/tests/unit/utils/forms/runtime.test.ts similarity index 95% rename from test/utils/forms/runtime.test.ts rename to tests/unit/utils/forms/runtime.test.ts index 226e3061..4dbd00e4 100644 --- a/test/utils/forms/runtime.test.ts +++ b/tests/unit/utils/forms/runtime.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { deriveFormRuntimeCapabilities } from '../../../src/utils/forms/runtime-core'; +import { deriveFormRuntimeCapabilities } from '../../../../src/utils/forms/runtime/runtime-core'; describe('form runtime capabilities', () => { it('always bypasses Turnstile in Cloudflare local mode', () => { diff --git a/test/utils/forms/validation.test.ts b/tests/unit/utils/forms/validation.test.ts similarity index 92% rename from test/utils/forms/validation.test.ts rename to tests/unit/utils/forms/validation.test.ts index 17830a93..dde252c5 100644 --- a/test/utils/forms/validation.test.ts +++ b/tests/unit/utils/forms/validation.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { parsePayload, validatePayload } from '../../../src/utils/forms/validation'; +import { parsePayload, validatePayload } from '../../../../src/utils/forms/security/validation'; describe('form payload validation', () => { it('parses and validates a contact submission', () => { diff --git a/tests/unit/utils/site-config.loads-grouped-header-and-footer-menus-from-config-yaml.test.ts b/tests/unit/utils/site-config.loads-grouped-header-and-footer-menus-from-config-yaml.test.ts new file mode 100644 index 00000000..18b4e02f --- /dev/null +++ b/tests/unit/utils/site-config.loads-grouped-header-and-footer-menus-from-config-yaml.test.ts @@ -0,0 +1,284 @@ +import { describe, expect, it } from 'vitest'; + +import { mkdtempSync, mkdirSync, writeFileSync } from 'node:fs'; + +import { tmpdir } from 'node:os'; + +import { join, resolve } from 'node:path'; + +import { + applyAgentModelDefaults, + applyBookModelDefaults, + applyDecisionModelDefaults, + applyObjectiveModelDefaults, + SITE_CONFIG, + applyDocsModelDefaults, + applyNoteModelDefaults, + applyPeopleModelDefaults, + applyPageModelDefaults, + applyProposalModelDefaults, + applyQuestionModelDefaults, +} from '../../../src/utils/configuration/site-config'; + +import { loadManifest } from '../../../src/runtime/platform/tenant-config.ts'; + +import { tenantModelRendered } from '../../../src/runtime/platform/tenant-config.ts'; + +import { parseSiteConfig } from '../../../src/utils/configuration/site-config-schema.js'; + +import { buildThemeCss, getBuiltInColorSchemes, normalizeThemePreference, resolveThemeConfig } from '../../../src/utils/support/theme.ts'; +describe('site config parsing', () => { +it('loads grouped header and footer menus from config.yaml', () => { + expect(SITE_CONFIG.site.headerMenu.length).toBeGreaterThan(0); + expect(SITE_CONFIG.site.headerMenu[0].label).toBe('Learn'); + expect(SITE_CONFIG.site.headerMenu[0].items).toContainEqual({ + label: 'Vision', + href: '/vision/', + }); + expect(SITE_CONFIG.site.footerMenu.length).toBeGreaterThan(0); + }); + +it('requires core site metadata fields', () => { + expect(() => + parseSiteConfig(` +site: + logo: + src: /logo.png + alt: Example logo + statement: Example statement + siteUrl: https://example.com + githubRepository: https://github.com/example/repo + discordLink: https://discord.gg/example + headerMenu: + - label: Explore + items: + - label: Home + href: / + footerMenu: + - label: Explore + items: + - label: Home + href: / + emailNotifications: + contactRouting: + default: + - hello@example.com + subscribeRecipients: + - hello@example.com + summary: Example summary + projectStage: Founding + projectStageDetail: Still early +models: {} + `), + ).toThrow('site.name'); + }); + +it('applies content model defaults as fallbacks', () => { + const page = applyPageModelDefaults({ + status: 'live', + }); + const note = applyNoteModelDefaults({ + status: 'live', + }); + const question = applyQuestionModelDefaults({ + status: 'exploratory', + }); + const objective = applyObjectiveModelDefaults({ + status: 'in progress', + }); + const proposal = applyProposalModelDefaults({ + status: 'planned', + }); + const decision = applyDecisionModelDefaults({ + status: 'live', + }); + const person = applyPeopleModelDefaults({}); + const agent = applyAgentModelDefaults({}); + const book = applyBookModelDefaults({}); + const docs = applyDocsModelDefaults({}); + + expect(page.pageLayout).toBe(SITE_CONFIG.models.pages.defaults.pageLayout); + expect(page.stage).toBe(SITE_CONFIG.models.pages.defaults.stage); + expect(page.audience).toEqual(SITE_CONFIG.models.pages.defaults.audience); + expect(note.author).toBe(SITE_CONFIG.models.notes.defaults.author); + expect(note.draft).toBe(SITE_CONFIG.models.notes.defaults.draft); + expect(note.tags).toEqual(SITE_CONFIG.models.notes.defaults.tags); + expect(question.draft).toBe(SITE_CONFIG.models.questions.defaults.draft); + expect(question.tags).toEqual(SITE_CONFIG.models.questions.defaults.tags); + expect(objective.draft).toBe(SITE_CONFIG.models.objectives.defaults.draft); + expect(objective.tags).toEqual(SITE_CONFIG.models.objectives.defaults.tags); + expect(proposal.draft).toBe(SITE_CONFIG.models.proposals.defaults.draft); + expect(proposal.tags).toEqual(SITE_CONFIG.models.proposals.defaults.tags); + expect(decision.draft).toBe(SITE_CONFIG.models.decisions.defaults.draft); + expect(decision.tags).toEqual(SITE_CONFIG.models.decisions.defaults.tags); + expect(person.status).toBe(SITE_CONFIG.models.people.defaults.status); + expect(person.tags).toEqual(SITE_CONFIG.models.people.defaults.tags); + expect(agent.tags).toEqual(SITE_CONFIG.models.agents.defaults.tags); + expect(book.tags).toEqual(SITE_CONFIG.models.books.defaults.tags); + expect(docs.tags).toEqual(SITE_CONFIG.models.docs.defaults.tags); + }); + +it('extracts email notification mappings from config.yaml', () => { + expect(SITE_CONFIG.site.emailNotifications.contactRouting.default).toEqual(['hello@treeseed.dev']); + expect(SITE_CONFIG.site.emailNotifications.contactRouting.issue).toEqual(['hello@treeseed.dev']); + expect(SITE_CONFIG.site.emailNotifications.subscribeRecipients).toEqual(['hello@treeseed.dev']); + }); + +it('parses optional tenant theme palette overrides', () => { + const parsed = parseSiteConfig(` +site: + logo: + src: /logo.png + alt: Example logo + name: Example + statement: Example statement + siteUrl: https://example.com + githubRepository: https://github.com/example/repo + discordLink: https://discord.gg/example + headerMenu: + - label: Explore + items: + - label: Home + href: / + footerMenu: + - label: Explore + items: + - label: Home + href: / + emailNotifications: + contactRouting: + default: + - hello@example.com + subscribeRecipients: + - hello@example.com + summary: Example summary + projectStage: Founding + projectStageDetail: Still early + theme: + defaultScheme: cedar + defaultMode: dark + schemes: + cedar: + light: + canvas: '#fefaf1' + accent: '#d4dfc8' + accentStrong: '#557255' + info: '#7aa4c2' + dark: + accent: '#d98c5f' +models: {} + `); + + expect(parsed.site.theme).toEqual({ + defaultScheme: 'cedar', + defaultMode: 'dark', + schemes: { + cedar: { + light: { + canvas: '#fefaf1', + accent: '#d4dfc8', + accentStrong: '#557255', + info: '#7aa4c2', + }, + dark: { + accent: '#d98c5f', + }, + }, + }, + }); + }); + +it('builds tenant theme css from palette overrides', () => { + const css = buildThemeCss({ + defaultScheme: 'cedar', + defaultMode: 'system', + schemes: { + cedar: { + light: { + canvas: '#fefaf1', + accent: '#d4dfc8', + accentSoft: '#eef4e8', + infoText: '#35586d', + }, + }, + }, + }); + + expect(css).toContain('html[data-ts-scheme="cedar"][data-ts-mode="light"]'); + expect(css).toContain('@media (prefers-color-scheme: dark)'); + expect(css).toContain('--ts-color-canvas: #fefaf1;'); + expect(css).toContain('--ts-color-accent: #d4dfc8;'); + expect(css).toContain('--ts-color-accent-soft: #eef4e8;'); + expect(css).toContain('--ts-color-info-text: #35586d;'); + expect(css).toContain('--ts-color-text:'); + expect(css).not.toContain(`--${'site'}-`); + }); + +it('keeps built-in color schemes modular and complete', () => { + const summaries = getBuiltInColorSchemes(); + const resolved = resolveThemeConfig(); + const requiredTokens = [ + 'canvas', + 'canvasSubtle', + 'surface', + 'surfaceMuted', + 'surfaceRaised', + 'surfaceOverlay', + 'text', + 'textMuted', + 'textSubtle', + 'textInverse', + 'link', + 'linkHover', + 'border', + 'borderMuted', + 'borderStrong', + 'focus', + 'accent', + 'accentHover', + 'accentStrong', + 'accentSoft', + 'accentText', + 'info', + 'infoSoft', + 'infoText', + 'infoBorder', + 'success', + 'successSoft', + 'successText', + 'successBorder', + 'warning', + 'warningSoft', + 'warningText', + 'warningBorder', + 'danger', + 'dangerSoft', + 'dangerText', + 'dangerBorder', + 'shadow', + 'grid', + ]; + + expect(summaries.map((scheme) => scheme.id)).toEqual(['fern', 'lichen', 'cedar', 'tidepool']); + for (const summary of summaries) { + expect(summary.modeSwatches.light).toHaveLength(4); + expect(summary.modeSwatches.dark).toHaveLength(4); + for (const mode of ['light', 'dark'] as const) { + for (const token of requiredTokens) { + expect(resolved.schemes[summary.id][mode], `${summary.id}.${mode}.${token}`).toHaveProperty(token); + } + } + } + }); + +it('normalizes anonymous theme preferences', () => { + expect(normalizeThemePreference({ colorScheme: 'tidepool', themeMode: 'dark' })).toEqual({ + scheme: 'tidepool', + mode: 'dark', + }); + expect(normalizeThemePreference({ scheme: 'Lichen Stone', mode: 'sepia' })).toEqual({ + scheme: 'fern', + mode: 'system', + }); + }); +}); diff --git a/tests/unit/utils/site-config.rejects-invalid-theme-mode-scheme-ids-and-token-names.test.ts b/tests/unit/utils/site-config.rejects-invalid-theme-mode-scheme-ids-and-token-names.test.ts new file mode 100644 index 00000000..d8262998 --- /dev/null +++ b/tests/unit/utils/site-config.rejects-invalid-theme-mode-scheme-ids-and-token-names.test.ts @@ -0,0 +1,265 @@ +import { describe, expect, it } from 'vitest'; + +import { mkdtempSync, mkdirSync, writeFileSync } from 'node:fs'; + +import { tmpdir } from 'node:os'; + +import { join, resolve } from 'node:path'; + +import { + applyAgentModelDefaults, + applyBookModelDefaults, + applyDecisionModelDefaults, + applyObjectiveModelDefaults, + SITE_CONFIG, + applyDocsModelDefaults, + applyNoteModelDefaults, + applyPeopleModelDefaults, + applyPageModelDefaults, + applyProposalModelDefaults, + applyQuestionModelDefaults, +} from '../../../src/utils/configuration/site-config'; + +import { loadManifest } from '../../../src/runtime/platform/tenant-config.ts'; + +import { tenantModelRendered } from '../../../src/runtime/platform/tenant-config.ts'; + +import { parseSiteConfig } from '../../../src/utils/configuration/site-config-schema.js'; + +import { buildThemeCss, getBuiltInColorSchemes, normalizeThemePreference, resolveThemeConfig } from '../../../src/utils/support/theme.ts'; +describe('site config parsing', () => { +it('rejects invalid theme mode, scheme ids, and token names', () => { + const baseConfig = ` +site: + logo: + src: /logo.png + alt: Example logo + name: Example + statement: Example statement + siteUrl: https://example.com + githubRepository: https://github.com/example/repo + discordLink: https://discord.gg/example + headerMenu: + - label: Explore + items: + - label: Home + href: / + footerMenu: + - label: Explore + items: + - label: Home + href: / + emailNotifications: + contactRouting: + default: + - hello@example.com + subscribeRecipients: + - hello@example.com + summary: Example summary + projectStage: Founding + projectStageDetail: Still early +`; + expect(() => parseSiteConfig(`${baseConfig} theme:\n defaultMode: sepia\nmodels: {}\n`)).toThrow('defaultMode'); + expect(() => parseSiteConfig(`${baseConfig} theme:\n defaultScheme: Fern\nmodels: {}\n`)).toThrow('defaultScheme'); + expect(() => parseSiteConfig(`${baseConfig} theme:\n schemes:\n fern:\n light:\n madeUp: '#fff'\nmodels: {}\n`)).toThrow('Unknown theme token'); + }); + +it('accepts aliased snake_case site config keys', () => { + const parsed = parseSiteConfig(` +site: + logo: + src: /logo.png + alt: Example logo + name: Example + statement: Example statement + site_url: https://example.com + github_repository: https://github.com/example/repo + discord_link: https://discord.gg/example + header_menu: + - label: Explore + items: + - label: Home + href: / + footer_menu: + - label: Explore + items: + - label: Home + href: / + email_notifications: + contact_routing: + default: + - hello@example.com + subscribe_recipients: + - hello@example.com + summary: Example summary + project_stage: Founding + project_stage_detail: Still early +models: + pages: + defaults: + page_layout: bridge +`); + + expect(parsed.site.siteUrl).toBe('https://example.com'); + expect(parsed.site.githubRepository).toBe('https://github.com/example/repo'); + expect(parsed.site.projectStage).toBe('Founding'); + expect(parsed.models.pages.defaults.pageLayout).toBe('bridge'); + }); + +it('parses access roles, policies, defaults, and bootstrap owners', () => { + const parsed = parseSiteConfig(` +site: + logo: + src: /logo.png + alt: Example logo + name: Example + statement: Example statement + siteUrl: https://example.com + githubRepository: https://github.com/example/repo + discordLink: https://discord.gg/example + headerMenu: + - label: Explore + items: + - label: Home + href: / + footerMenu: + - label: Explore + items: + - label: Home + href: / + emailNotifications: + contactRouting: + default: + - hello@example.com + subscribeRecipients: + - hello@example.com + summary: Example summary + projectStage: Founding + projectStageDetail: Still early +models: {} +access: + roles: + site_admin: + grants: + - site:manage + - content:*:* + policies: + public_free: + audience: public + offer: free + paid_updates: + audience: entitlement + entitlement: subscription_updates + defaults: + models: + books: + page: public_free + updates: paid_updates + bootstrap: + owners: + emails: + - owner@example.com + roles: + - site_admin + `); + + expect(parsed.access.roles.site_admin.grants).toEqual(['site:manage', 'content:*:*']); + expect(parsed.access.policies.public_free.offer).toBe('free'); + expect(parsed.access.defaults.models.books.page).toBe('public_free'); + expect(parsed.access.bootstrap.owners.emails).toEqual(['owner@example.com']); + }); + +it('accepts aliased tenant manifest keys', () => { + const tenantRoot = mkdtempSync(join(tmpdir(), 'treeseed-core-tenant-')); + mkdirSync(resolve(tenantRoot, 'src'), { recursive: true }); + writeFileSync( + resolve(tenantRoot, 'src', 'manifest.yaml'), + `id: tenant +site_config_path: ./src/config.yaml +content: + page_root: ./src/content/pages + notes_root: ./src/content/notes + questions_root: ./src/content/questions + objectives_root: ./src/content/objectives + proposals_root: ./src/content/proposals + decisions_root: ./src/content/decisions + people_root: ./src/content/people + agents_root: ./src/content/agents + books_root: ./src/content/books + knowledge_root: ./src/content/knowledge +features: + docs: true +`, + 'utf8', + ); + + const previousRoot = process.env.TREESEED_TENANT_ROOT; + process.env.TREESEED_TENANT_ROOT = tenantRoot; + try { + const manifest = loadManifest(resolve(tenantRoot, 'src', 'manifest.yaml')); + expect(manifest.siteConfigPath).toBe(resolve(tenantRoot, 'src', 'config.yaml')); + expect(manifest.content.pages).toBe(resolve(tenantRoot, 'src', 'content', 'pages')); + expect(manifest.content.docs).toBe(resolve(tenantRoot, 'src', 'content', 'knowledge')); + } finally { + if (previousRoot === undefined) { + delete process.env.TREESEED_TENANT_ROOT; + } else { + process.env.TREESEED_TENANT_ROOT = previousRoot; + } + } + }); + +it('loads manifest-owned site rendering flags and defaults omitted models to rendered', () => { + const tenantRoot = mkdtempSync(join(tmpdir(), 'treeseed-core-tenant-')); + mkdirSync(resolve(tenantRoot, 'src'), { recursive: true }); + writeFileSync( + resolve(tenantRoot, 'src', 'manifest.yaml'), + `id: tenant +siteConfigPath: ./src/config.yaml +content: + pages: ./src/content/pages + notes: ./src/content/notes + questions: ./src/content/questions + objectives: ./src/content/objectives + proposals: ./src/content/proposals + decisions: ./src/content/decisions + people: ./src/content/people + agents: ./src/content/agents + books: ./src/content/books + docs: ./src/content/knowledge + workdays: ./src/content/workdays +features: + docs: true + notes: true + proposals: true + decisions: true +site: + models: + workdays: + rendered: false + notes: + rendered: false + proposals: + rendered: false +`, + 'utf8', + ); + + const previousRoot = process.env.TREESEED_TENANT_ROOT; + process.env.TREESEED_TENANT_ROOT = tenantRoot; + try { + const manifest = loadManifest(resolve(tenantRoot, 'src', 'manifest.yaml')); + expect(manifest.site?.models?.workdays?.rendered).toBe(false); + expect(tenantModelRendered(manifest, 'workdays')).toBe(false); + expect(tenantModelRendered(manifest, 'notes')).toBe(false); + expect(tenantModelRendered(manifest, 'proposals')).toBe(false); + expect(tenantModelRendered(manifest, 'people')).toBe(true); + } finally { + if (previousRoot === undefined) { + delete process.env.TREESEED_TENANT_ROOT; + } else { + process.env.TREESEED_TENANT_ROOT = previousRoot; + } + } + }); +}); diff --git a/test/utils/site-resources.test.ts b/tests/unit/utils/site-resources.test.ts similarity index 64% rename from test/utils/site-resources.test.ts rename to tests/unit/utils/site-resources.test.ts index 382abc95..76b2341e 100644 --- a/test/utils/site-resources.test.ts +++ b/tests/unit/utils/site-resources.test.ts @@ -1,15 +1,16 @@ import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises'; import { dirname, join } from 'node:path'; import { tmpdir } from 'node:os'; -import type { TreeseedTenantConfig } from '@treeseed/sdk/platform/contracts'; -import type { LoadedTreeseedPluginEntry } from '@treeseed/sdk/platform/plugins'; +import type { TenantConfig } from '@treeseed/sdk/site-contracts/platform'; +import type { LoadedPluginRegistration } from '../../../src/runtime/platform/plugins.ts'; import { afterEach, describe, expect, it } from 'vitest'; import { - buildTreeseedSiteLayers, - resolveTreeseedPageEntrypoint, - resolveTreeseedSiteResource, - resolveTreeseedStyleEntrypoint, -} from '../../src/site-resources'; + buildSiteLayers, + resolvePageEntrypoint, + resolveSiteResource, + resolveStyleEntrypoint, +} from '../../../src/support/site-resources'; +import { tenantPageRouteExists } from '../../../src/site/tenant-theme-virtual-id'; const tempRoots: string[] = []; @@ -30,7 +31,7 @@ async function writeFixtureFile(root: string, relativePath: string, contents: st return fullPath; } -function createTenantConfig(overrides?: TreeseedTenantConfig['overrides']): TreeseedTenantConfig { +function createTenantConfig(overrides?: TenantConfig['overrides']): TenantConfig { return { id: 'test-site', siteConfigPath: '/tmp/site-config.yaml', @@ -92,17 +93,17 @@ describe('site resources', () => { }, }, ], - } satisfies { plugins: LoadedTreeseedPluginEntry[] }; + } satisfies { plugins: LoadedPluginRegistration[] }; - const layers = buildTreeseedSiteLayers(pluginRuntime, { + const layers = buildSiteLayers(pluginRuntime, { coreRoot, projectRoot: coreRoot, tenantConfig: createTenantConfig(), }); - expect(resolveTreeseedPageEntrypoint(layers, 'pages/contact.astro')).toBe(join(teamRoot, 'layers/pages/contact.astro')); - expect(resolveTreeseedStyleEntrypoint(layers, 'styles/tokens.css')).toBe(join(userRoot, 'layers/styles/tokens.css')); - expect(resolveTreeseedStyleEntrypoint(layers, 'styles/global.css')).toBe(join(coreRoot, 'styles/global.css')); + expect(resolvePageEntrypoint(layers, 'pages/contact.astro')).toBe(join(teamRoot, 'layers/pages/contact.astro')); + expect(resolveStyleEntrypoint(layers, 'styles/tokens.css')).toBe(join(userRoot, 'layers/styles/tokens.css')); + expect(resolveStyleEntrypoint(layers, 'styles/global.css')).toBe(join(coreRoot, 'styles/global.css')); }); it('lets tenant overrides beat package layers for pages and styles', async () => { @@ -135,23 +136,23 @@ describe('site resources', () => { }, }, ], - } satisfies { plugins: LoadedTreeseedPluginEntry[] }; + } satisfies { plugins: LoadedPluginRegistration[] }; - const layers = buildTreeseedSiteLayers(pluginRuntime, { + const layers = buildSiteLayers(pluginRuntime, { coreRoot, projectRoot: tenantRoot, tenantConfig, }); - expect(resolveTreeseedPageEntrypoint(layers, 'pages/index.astro')).toBe(join(tenantRoot, 'src/overrides/pages/index.astro')); - expect(resolveTreeseedStyleEntrypoint(layers, 'styles/global.css')).toBe(join(tenantRoot, 'src/overrides/styles/global.css')); + expect(resolvePageEntrypoint(layers, 'pages/index.astro')).toBe(join(tenantRoot, 'src/overrides/pages/index.astro')); + expect(resolveStyleEntrypoint(layers, 'styles/global.css')).toBe(join(tenantRoot, 'src/overrides/styles/global.css')); }); it('returns null for missing resources and throws for required page or style entrypoints', async () => { const coreRoot = await createFixtureRoot('treeseed-site-core-'); await writeFixtureFile(coreRoot, 'styles/global.css', 'core global'); - const layers = buildTreeseedSiteLayers( + const layers = buildSiteLayers( { plugins: [] }, { coreRoot, @@ -160,8 +161,20 @@ describe('site resources', () => { }, ); - expect(resolveTreeseedSiteResource(layers, 'pages', 'pages/contact.astro')).toBeNull(); - expect(() => resolveTreeseedPageEntrypoint(layers, 'pages/contact.astro')).toThrow(/pages\/contact\.astro/); - expect(() => resolveTreeseedStyleEntrypoint(layers, 'styles/tokens.css')).toThrow(/styles\/tokens\.css/); + expect(resolveSiteResource(layers, 'pages', 'pages/contact.astro')).toBeNull(); + expect(() => resolvePageEntrypoint(layers, 'pages/contact.astro')).toThrow(/pages\/contact\.astro/); + expect(() => resolveStyleEntrypoint(layers, 'styles/tokens.css')).toThrow(/styles\/tokens\.css/); + }); + + it('recognizes tenant index and dynamic routes before plugin injection', async () => { + const tenantRoot = await createFixtureRoot('treeseed-site-routes-'); + await writeFixtureFile(tenantRoot, 'src/pages/app/account/index.astro', 'account'); + await writeFixtureFile(tenantRoot, 'src/pages/app/projects/[projectId]/index.astro', 'project'); + await writeFixtureFile(tenantRoot, 'src/pages/v1/[...all].ts', 'api'); + + expect(tenantPageRouteExists(tenantRoot, '/app/account')).toBe(true); + expect(tenantPageRouteExists(tenantRoot, '/app/projects/[projectId]')).toBe(true); + expect(tenantPageRouteExists(tenantRoot, '/v1/[...all]')).toBe(true); + expect(tenantPageRouteExists(tenantRoot, '/app/missing')).toBe(false); }); }); diff --git a/test/utils/template-site.test.ts b/tests/unit/utils/template-site.test.ts similarity index 92% rename from test/utils/template-site.test.ts rename to tests/unit/utils/template-site.test.ts index a17f8a12..d59cde5a 100644 --- a/test/utils/template-site.test.ts +++ b/tests/unit/utils/template-site.test.ts @@ -28,7 +28,6 @@ content: books: ./src/content/books docs: ./src/content/knowledge templates: ./src/content/templates - knowledge_packs: ./src/content/knowledge-packs workdays: ./src/content/workdays features: docs: true @@ -58,7 +57,7 @@ describe('template site resolution', () => { const tenantRoot = await createTenantRoot(''); process.env.TREESEED_TENANT_ROOT = tenantRoot; vi.resetModules(); - const { listSiteTemplates } = await import('../../src/templates.ts'); + const { listSiteTemplates } = await import('../../../src/support/templates.ts'); const listing = await listSiteTemplates({ catalogProvider: { @@ -138,7 +137,7 @@ describe('template site resolution', () => { const tenantRoot = await createTenantRoot(''); process.env.TREESEED_TENANT_ROOT = tenantRoot; vi.resetModules(); - const { resolveSiteTemplate } = await import('../../src/templates.ts'); + const { resolveSiteTemplate } = await import('../../../src/support/templates.ts'); const resolved = await resolveSiteTemplate('starter', { listLocalEntries: async () => [ @@ -192,14 +191,14 @@ describe('template site resolution', () => { const tenantRoot = await createTenantRoot(''); process.env.TREESEED_TENANT_ROOT = tenantRoot; vi.resetModules(); - vi.doMock('../../src/utils/site-models.ts', async () => { - const actual = await vi.importActual('../../src/utils/site-models.ts'); + vi.doMock('../../../src/utils/support/site-models.ts', async () => { + const actual = await vi.importActual('../../../src/utils/support/site-models.ts'); return { ...actual, siteModelRendered: () => false, }; }); - const { listSiteTemplates } = await import('../../src/templates.ts'); + const { listSiteTemplates } = await import('../../../src/support/templates.ts'); const listing = await listSiteTemplates(); expect(listing.rendered).toBe(false); diff --git a/test/utils/web-cache.test.ts b/tests/unit/utils/web-cache.test.ts similarity index 80% rename from test/utils/web-cache.test.ts rename to tests/unit/utils/web-cache.test.ts index 7162454b..8f1fba47 100644 --- a/test/utils/web-cache.test.ts +++ b/tests/unit/utils/web-cache.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it, vi } from 'vitest'; -vi.mock('@treeseed/sdk/platform/plugins', () => ({ - getTreeseedDeployConfig() { +vi.mock('../../../src/runtime/platform/plugins.ts', () => ({ + getDeployConfig() { return { name: 'Test Site', slug: 'test-site', @@ -57,17 +57,17 @@ vi.mock('@treeseed/sdk/platform/plugins', () => ({ }, })); -import { applyTreeseedWebCacheHeaders, classifyTreeseedWebRequest } from '../../src/utils/web-cache.js'; +import { applyWebCacheHeaders, classifyWebRequest } from '../../../src/utils/support/web-cache.js'; describe('web cache policy', () => { it('classifies public content routes as cacheable html requests', () => { const request = new Request('https://example.com/books/test-book'); - expect(classifyTreeseedWebRequest(request, new URL(request.url))).toEqual({ kind: 'content_page_html' }); + expect(classifyWebRequest(request, new URL(request.url))).toEqual({ kind: 'content_page_html' }); }); it('classifies source-only pages separately', () => { const request = new Request('https://example.com/contact'); - expect(classifyTreeseedWebRequest(request, new URL(request.url))).toEqual({ kind: 'source_page_html' }); + expect(classifyWebRequest(request, new URL(request.url))).toEqual({ kind: 'source_page_html' }); }); it('classifies preview-cookie requests as preview-sensitive', () => { @@ -76,7 +76,7 @@ describe('web cache policy', () => { cookie: 'treeseed-content-preview=preview-token', }, }); - expect(classifyTreeseedWebRequest(request, new URL(request.url))).toEqual({ + expect(classifyWebRequest(request, new URL(request.url))).toEqual({ kind: 'preview_no_cache', reason: 'preview_cookie', }); @@ -84,7 +84,7 @@ describe('web cache policy', () => { it('classifies form endpoints as dynamic non-cacheable', () => { const request = new Request('https://example.com/api/form/submit'); - expect(classifyTreeseedWebRequest(request, new URL(request.url))).toEqual({ + expect(classifyWebRequest(request, new URL(request.url))).toEqual({ kind: 'api_no_cache', reason: 'api_path', }); @@ -99,7 +99,7 @@ describe('web cache policy', () => { }, }); - const updated = applyTreeseedWebCacheHeaders(request, new URL(request.url), response); + const updated = applyWebCacheHeaders(request, new URL(request.url), response); expect(updated.headers.get('Cache-Control')).toBe('public, max-age=0, must-revalidate'); expect(updated.headers.get('CDN-Cache-Control')).toBe( 'public, s-maxage=31536000, stale-while-revalidate=86400, stale-if-error=86400', @@ -116,7 +116,7 @@ describe('web cache policy', () => { }, }); - const updated = applyTreeseedWebCacheHeaders(request, new URL(request.url), response); + const updated = applyWebCacheHeaders(request, new URL(request.url), response); expect(updated.headers.get('CDN-Cache-Control')).toBeNull(); }); @@ -129,7 +129,7 @@ describe('web cache policy', () => { }, }); - const updated = applyTreeseedWebCacheHeaders(request, new URL(request.url), response); + const updated = applyWebCacheHeaders(request, new URL(request.url), response); expect(updated.headers.get('Cache-Control')).toBe('no-store'); expect(updated.headers.get('CDN-Cache-Control')).toBe('no-store'); }); diff --git a/test/utils/workspace-bootstrap.test.ts b/tests/unit/utils/workspace-bootstrap.test.ts similarity index 81% rename from test/utils/workspace-bootstrap.test.ts rename to tests/unit/utils/workspace-bootstrap.test.ts index 4277aa61..6a881d67 100644 --- a/test/utils/workspace-bootstrap.test.ts +++ b/tests/unit/utils/workspace-bootstrap.test.ts @@ -3,7 +3,8 @@ import { mkdtempSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; import { afterEach, describe, expect, it, vi } from 'vitest'; -import { detectTreeseedBootstrapMode } from '../../scripts/workspace-bootstrap.ts'; +import { detectBootstrapMode } from '../../../scripts/treedx/workspaces/workspace-bootstrap.ts'; +import { packageRoot } from '../../../scripts/support/paths.ts'; const packageDirs = ['sdk', 'ui', 'core', 'admin', 'cli', 'agent']; @@ -27,7 +28,7 @@ describe('Treeseed workspace bootstrap mode detection', () => { it('uses registry mode when no package submodules are checked out', () => { const root = makeRoot(); - const state = detectTreeseedBootstrapMode(root); + const state = detectBootstrapMode(root); expect(state.mode).toBe('registry'); expect(state.missing.map((entry) => entry.relativeDir)).toEqual([ @@ -46,7 +47,7 @@ describe('Treeseed workspace bootstrap mode detection', () => { writePackage(root, dirName); } - const state = detectTreeseedBootstrapMode(root); + const state = detectBootstrapMode(root); expect(state.mode).toBe('workspace'); expect(state.missing).toEqual([]); @@ -57,7 +58,7 @@ describe('Treeseed workspace bootstrap mode detection', () => { writePackage(root, 'sdk'); writePackage(root, 'core'); - const state = detectTreeseedBootstrapMode(root); + const state = detectBootstrapMode(root); expect(state.mode).toBe('partial'); expect(state.missing.map((entry) => entry.relativeDir)).toContain('packages/cli'); @@ -72,8 +73,12 @@ describe('Treeseed workspace bootstrap mode detection', () => { } vi.stubEnv('TREESEED_BOOTSTRAP_MODE', 'auto'); - const state = detectTreeseedBootstrapMode(root); + const state = detectBootstrapMode(root); expect(state.mode).toBe('workspace'); }); + + it('resolves support paths from the package root after functional script grouping', () => { + expect(packageRoot).toBe(resolve(process.cwd())); + }); }); diff --git a/treeseed.package.yaml b/treeseed.package.yaml index bd9b16cd..f9e3d872 100644 --- a/treeseed.package.yaml +++ b/treeseed.package.yaml @@ -19,13 +19,50 @@ artifacts: name: "@treeseed/core" releaseGate: workflow: verify.yml -projectArchitecture: - topology: single_repository_site - rootPath: . - sitePath: docs - contentPath: docs/src/content - contentRuntimeSource: r2_published_manifest - localContentMaterialization: existing_path - contentPublishTarget: - kind: cloudflare_r2 - prefix: packages/core +development: + schemaVersion: treeseed.development-runtime/v1 + project: { id: core, repository: treeseed-ai/core } + defaults: { leaseSeconds: 14400, restoreOnFailure: true } + targets: + - id: package + kind: package-watch + platforms: [linux-amd64, linux-arm64, darwin-arm64, darwin-amd64] + runtimeRequirements: [node>=22] + sourceRoots: [src, scripts] + ignoredPaths: [dist, node_modules] + operations: + watch: { command: node, args: [--watch-path=src, --watch-path=package.json, --import, tsx, scripts/development/build-watch.ts], environment: {}, timeoutSeconds: 86400 } + build: { command: npm, args: [run, build:dist], environment: {}, timeoutSeconds: 900 } + verify: { command: npm, args: [run, test:unit], environment: {}, timeoutSeconds: 900 } + ready: { kind: marker, path: dist/.treeseed-build-complete.json, timeoutSeconds: 120 } + outputs: [{ path: dist, mediaType: application/vnd.treeseed.node-package, completionMarker: dist/.treeseed-build-complete.json, digestAlgorithm: sha256 }] + endpoints: [] + dependencies: + - { id: sdk, target: package, locality: local, reaction: rebuild } + - { id: ui, target: package, locality: local, reaction: rebuild } + statePolicy: stateless + migrationPolicy: none + secretRefs: {} + shutdown: { graceSeconds: 10, activeWorkPolicy: block } + resources: {} + logs: [.treeseed/development/package.log] + forbiddenOperations: [publish, production-tag] + freeze: + kind: npm-package + operation: { command: npm, args: [pack, --json], environment: {}, timeoutSeconds: 900 } + artifacts: [treeseed-core-*.tgz] + contractOperations: [] + promotion: { liveAdmissible: false, candidateRequiresVerification: true } +standards: + schemaVersion: 1 + workflow: { enabled: true } + acceptedBaseline: { packageName: "@treeseed/core", packageVersion: "0.12.59" } + produced: + - { id: "@treeseed/core/site-plugin-runtime", family: behavioral, version: "1.0.0", semanticRange: "^1.0.0", source: dist, artifact: .treeseed/standards/site-plugin-runtime.json, verifier: scripts/packages/release-verify.ts } + consumed: + - { id: "@treeseed/sdk/typescript-public-api", family: typescript, version: "1.0.0", semanticRange: "^1.0.0", source: node_modules/@treeseed/sdk, verifier: scripts/packages/release-verify.ts } + - { id: "@treeseed/ui/presentation-catalog", family: behavioral, version: "1.0.0", semanticRange: "^1.0.0", source: node_modules/@treeseed/ui, verifier: scripts/packages/release-verify.ts } + guarantees: [managed-node-fail-closed, hosted-cloudflare-retained, packaged-route-composition] + deprecations: [] + runtimes: [node>=22, cloudflare-workers] + rollbackOperations: [remove-prerelease-dist-tag, restore-accepted-composition] diff --git a/tsconfig.json b/tsconfig.json index 11cd1a93..7805a58d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,7 @@ "include": [ "src/**/*.ts", "src/**/*.d.ts", - "test/**/*.ts", + "tests/**/*.ts", "scripts/**/*.ts", "vitest.config.ts" ], diff --git a/vitest.config.ts b/vitest.config.ts index 8329351b..1dd5be93 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,28 +1,11 @@ -import { existsSync } from 'node:fs'; -import { resolve } from 'node:path'; import { defineConfig } from 'vitest/config'; -const workspaceSdkRoot = resolve(process.cwd(), '../sdk'); -const useWorkspaceSdk = existsSync(resolve(workspaceSdkRoot, 'src/index.ts')); - export default defineConfig({ - resolve: useWorkspaceSdk - ? { - alias: [ - { - find: /^@treeseed\/sdk$/, - replacement: resolve(workspaceSdkRoot, 'src/index.ts'), - }, - { - find: /^@treeseed\/sdk\/(.*)$/, - replacement: resolve(workspaceSdkRoot, 'src/$1'), - }, - ], - } - : undefined, test: { - include: ['test/**/*.test.ts'], - exclude: ['test/utils/agents/e2e/**'], - setupFiles: ['test/setup-runtime.ts'], + // Managed-dev integration tests coordinate fixed local ports and process state. + fileParallelism: false, + include: ['tests/{unit,integration,contract}/**/*.test.ts'], + exclude: ['tests/e2e/**'], + setupFiles: ['tests/support/setup-runtime.ts'], }, });