From 5cbe299221b31b857c8c8b9e047c69c9eb4ddcfd Mon Sep 17 00:00:00 2001 From: Christopher Burns Date: Mon, 1 Dec 2025 16:25:40 +0000 Subject: [PATCH 1/8] Update package dependencies and enhance documentation workflows - Added new dependencies for remark-related packages in package.json to improve documentation formatting and linting. - Updated the GitHub Actions workflow for bundle analysis to use a maintained action, ensuring better compatibility and performance. - Refined documentation files by standardizing bullet points and improving readability across various sections. - Removed outdated bundle analysis action files to streamline the repository and reduce maintenance overhead. --- .github/workflows/bundle-analysis.yml | 3 +- .github/workflows/deploy-docs-main.yml | 15 +- docs/architecture.mdx | 195 +- docs/frameworks/hono/advanced.mdx | 7 +- docs/frameworks/hono/configuration.mdx | 5 +- docs/frameworks/hono/deployment.mdx | 5 +- docs/frameworks/hono/orchestration.mdx | 14 +- docs/frameworks/hono/quickstart.mdx | 14 +- docs/frameworks/hono/writing-runners.mdx | 13 +- docs/frameworks/http/advanced.mdx | 7 +- docs/frameworks/http/deployment.mdx | 3 +- docs/frameworks/http/orchestration.mdx | 14 +- docs/frameworks/http/quickstart.mdx | 15 +- docs/frameworks/http/writing-runners.mdx | 11 +- docs/frameworks/index.mdx | 2 - docs/frameworks/nitro/advanced.mdx | 7 +- docs/frameworks/nitro/configuration.mdx | 26 +- docs/frameworks/nitro/deployment.mdx | 8 +- docs/frameworks/nitro/orchestration.mdx | 14 +- docs/frameworks/nitro/quickstart.mdx | 15 +- docs/frameworks/nitro/writing-runners.mdx | 13 +- docs/frameworks/shared/advanced.mdx | 313 ++- docs/frameworks/shared/orchestration.mdx | 499 ++-- docs/frameworks/shared/writing-runners.mdx | 500 ++-- docs/getting-started.mdx | 23 +- docs/introduction.mdx | 57 +- docs/oss/contributing.mdx | 59 +- docs/oss/license.mdx | 1 - docs/reference/api.mdx | 80 +- docs/reference/cli.mdx | 18 +- docs/reference/index.mdx | 5 +- internals/bundle-analysis-action/README.md | 58 - internals/bundle-analysis-action/action.yml | 66 - internals/bundle-analysis-action/package.json | 30 - .../src/analyze/bundle-analysis.test.ts | 611 ----- .../src/analyze/bundle-analysis.ts | 400 --- .../src/config/inputs.ts | 73 - .../src/github/pr-comment.test.ts | 298 -- .../src/github/pr-comment.ts | 121 - .../bundle-analysis-action/src/main.test.ts | 294 -- internals/bundle-analysis-action/src/main.ts | 84 - .../bundle-analysis-action/tsconfig.json | 19 - .../bundle-analysis-action/vitest.config.ts | 23 - internals/c15t-github-action/README.md | 19 - .../__tests__/ascii-art.test.ts | 19 - .../__tests__/assets/result | 1 - .../__tests__/assets/result2 | 1 - .../__tests__/changes.test.ts | 154 -- .../__tests__/comment.test.ts | 356 --- .../__tests__/comments.test.ts | 93 - .../__tests__/config.test.ts | 433 --- .../__tests__/deployment.test.ts | 149 - .../__tests__/errors.test.ts | 43 - .../__tests__/improvements.test.ts | 27 - .../__tests__/logger.test.ts | 27 - .../__tests__/push-comment.test.ts | 101 - .../__tests__/render-comment.test.ts | 87 - .../__tests__/validate.test.ts | 41 - .../__tests__/vercel-client.test.ts | 212 -- internals/c15t-github-action/action.yml | 215 -- internals/c15t-github-action/package.json | 31 - .../c15t-github-action/src/config/inputs.ts | 203 -- .../src/deploy/vercel-client.ts | 386 --- .../src/github/pr-comment.ts | 200 -- internals/c15t-github-action/src/main.ts | 182 -- .../c15t-github-action/src/steps/ascii-art.ts | 176 -- .../c15t-github-action/src/steps/changes.ts | 113 - .../c15t-github-action/src/steps/comments.ts | 141 - .../src/steps/deployment.ts | 283 -- .../src/steps/first-commit.ts | 23 - .../src/steps/github-app-auth.ts | 72 - .../src/steps/push-comment.ts | 57 - .../src/steps/render-comment.ts | 205 -- .../src/steps/setup-docs.ts | 41 - .../src/steps/template-tracking.ts | 89 - .../c15t-github-action/src/utils/errors.ts | 176 -- .../c15t-github-action/src/utils/logger.ts | 114 - internals/c15t-github-action/tsconfig.json | 19 - internals/c15t-github-action/vitest.config.ts | 11 - package.json | 8 +- pnpm-lock.yaml | 2407 +++++++++++++---- 81 files changed, 2945 insertions(+), 8008 deletions(-) delete mode 100644 internals/bundle-analysis-action/README.md delete mode 100644 internals/bundle-analysis-action/action.yml delete mode 100644 internals/bundle-analysis-action/package.json delete mode 100644 internals/bundle-analysis-action/src/analyze/bundle-analysis.test.ts delete mode 100644 internals/bundle-analysis-action/src/analyze/bundle-analysis.ts delete mode 100644 internals/bundle-analysis-action/src/config/inputs.ts delete mode 100644 internals/bundle-analysis-action/src/github/pr-comment.test.ts delete mode 100644 internals/bundle-analysis-action/src/github/pr-comment.ts delete mode 100644 internals/bundle-analysis-action/src/main.test.ts delete mode 100644 internals/bundle-analysis-action/src/main.ts delete mode 100644 internals/bundle-analysis-action/tsconfig.json delete mode 100644 internals/bundle-analysis-action/vitest.config.ts delete mode 100644 internals/c15t-github-action/README.md delete mode 100644 internals/c15t-github-action/__tests__/ascii-art.test.ts delete mode 100644 internals/c15t-github-action/__tests__/assets/result delete mode 100644 internals/c15t-github-action/__tests__/assets/result2 delete mode 100644 internals/c15t-github-action/__tests__/changes.test.ts delete mode 100644 internals/c15t-github-action/__tests__/comment.test.ts delete mode 100644 internals/c15t-github-action/__tests__/comments.test.ts delete mode 100644 internals/c15t-github-action/__tests__/config.test.ts delete mode 100644 internals/c15t-github-action/__tests__/deployment.test.ts delete mode 100644 internals/c15t-github-action/__tests__/errors.test.ts delete mode 100644 internals/c15t-github-action/__tests__/improvements.test.ts delete mode 100644 internals/c15t-github-action/__tests__/logger.test.ts delete mode 100644 internals/c15t-github-action/__tests__/push-comment.test.ts delete mode 100644 internals/c15t-github-action/__tests__/render-comment.test.ts delete mode 100644 internals/c15t-github-action/__tests__/validate.test.ts delete mode 100644 internals/c15t-github-action/__tests__/vercel-client.test.ts delete mode 100644 internals/c15t-github-action/action.yml delete mode 100644 internals/c15t-github-action/package.json delete mode 100644 internals/c15t-github-action/src/config/inputs.ts delete mode 100644 internals/c15t-github-action/src/deploy/vercel-client.ts delete mode 100644 internals/c15t-github-action/src/github/pr-comment.ts delete mode 100644 internals/c15t-github-action/src/main.ts delete mode 100644 internals/c15t-github-action/src/steps/ascii-art.ts delete mode 100644 internals/c15t-github-action/src/steps/changes.ts delete mode 100644 internals/c15t-github-action/src/steps/comments.ts delete mode 100644 internals/c15t-github-action/src/steps/deployment.ts delete mode 100644 internals/c15t-github-action/src/steps/first-commit.ts delete mode 100644 internals/c15t-github-action/src/steps/github-app-auth.ts delete mode 100644 internals/c15t-github-action/src/steps/push-comment.ts delete mode 100644 internals/c15t-github-action/src/steps/render-comment.ts delete mode 100644 internals/c15t-github-action/src/steps/setup-docs.ts delete mode 100644 internals/c15t-github-action/src/steps/template-tracking.ts delete mode 100644 internals/c15t-github-action/src/utils/errors.ts delete mode 100644 internals/c15t-github-action/src/utils/logger.ts delete mode 100644 internals/c15t-github-action/tsconfig.json delete mode 100644 internals/c15t-github-action/vitest.config.ts diff --git a/.github/workflows/bundle-analysis.yml b/.github/workflows/bundle-analysis.yml index affe25e..359ae39 100644 --- a/.github/workflows/bundle-analysis.yml +++ b/.github/workflows/bundle-analysis.yml @@ -78,7 +78,7 @@ jobs: WITH_RSDOCTOR=1 pnpm turbo run build --filter="./packages/*" - name: ๐Ÿ“Š Analyze bundle differences - uses: ./internals/bundle-analysis-action + uses: consentdotio/github-actions/bundle-analysis-action@main with: base_dir: .bundle-base current_dir: . @@ -86,6 +86,7 @@ jobs: pr_number: ${{ github.event.pull_request.number }} skip_comment: false fail_on_increase: false + header: "bundle-analysis" - name: ๐Ÿ“ค Upload bundle diff report if: always() diff --git a/.github/workflows/deploy-docs-main.yml b/.github/workflows/deploy-docs-main.yml index 344eb88..b8c0b32 100644 --- a/.github/workflows/deploy-docs-main.yml +++ b/.github/workflows/deploy-docs-main.yml @@ -1,5 +1,5 @@ -# ๐Ÿš€ c15t main docs scheduled deployment workflow -name: Deploy docs (Canary) +# ๐Ÿš€ Runner main docs scheduled deployment workflow +name: Deploy docs (Main) on: # ๐Ÿ”„ Allow manual trigger as well @@ -39,9 +39,9 @@ jobs: - name: ๐Ÿ“ฅ Install workspace deps run: pnpm install --frozen-lockfile - - name: โ–ฒ C15T Canary Deployment - id: c15t - uses: ./internals/c15t-github-action + - name: โ–ฒ Runner Docs Deployment + id: runner-docs + uses: consentdotio/github-actions/docs-preview-action@main with: # Auth GITHUB_TOKEN: ${{ github.token }} @@ -69,10 +69,15 @@ jobs: # Orchestration & gating consent_git_token: ${{ secrets.CONSENT_GIT_TOKEN }} + docs_template_repo: consentdotio/runner-docs only_if_changed: "false" change_globs: | docs/** + changelog/** packages/*/src/** packages/*/package.json check_template_changes: "true" + # Branding customization + comment_marker_prefix: "runner" + diff --git a/docs/architecture.mdx b/docs/architecture.mdx index f099cf8..fd12534 100644 --- a/docs/architecture.mdx +++ b/docs/architecture.mdx @@ -15,117 +15,148 @@ Runners is a distributed test execution framework that allows you to write simpl ### Core Packages #### `@runners/core` + The core execution engine that: -- Discovers runners from your codebase -- Executes runners with proper context -- Handles errors and timeouts -- Normalizes results + +* Discovers runners from your codebase +* Executes runners with proper context +* Handles errors and timeouts +* Normalizes results **Key Files:** -- `src/runner.ts` - Runner execution logic -- `src/schema-discovery.ts` - Discovers runners using directives -- `src/types.ts` - Core type definitions + +* `src/runner.ts` - Runner execution logic +* `src/schema-discovery.ts` - Discovers runners using directives +* `src/types.ts` - Core type definitions #### `@runners/playwright` + Optional Playwright integration that: -- Launches browser instances -- Provides page objects -- Handles browser lifecycle -- Manages browser contexts + +* Launches browser instances +* Provides page objects +* Handles browser lifecycle +* Manages browser contexts **Key Files:** -- `src/index.ts` - `withPlaywright()` helper function + +* `src/index.ts` - `withPlaywright()` helper function #### `@runners/http` + HTTP handler for exposing runners as an API: -- Creates HTTP endpoints (`/api/runner/execute`, `/api/runner/info`) -- Validates requests using Zod schemas -- Returns normalized JSON responses -- Serves OpenAPI documentation + +* Creates HTTP endpoints (`/api/runner/execute`, `/api/runner/info`) +* Validates requests using Zod schemas +* Returns normalized JSON responses +* Serves OpenAPI documentation **Key Files:** -- `src/handler.ts` - HTTP request handler -- `src/schema-loader.ts` - Loads pre-extracted schemas + +* `src/handler.ts` - HTTP request handler +* `src/schema-loader.ts` - Loads pre-extracted schemas #### `@runners/nitro` + Nitro framework integration: -- Registers Nitro module -- Auto-discovers runners -- Exposes API routes -- Integrates with Nitro's build system + +* Registers Nitro module +* Auto-discovers runners +* Exposes API routes +* Integrates with Nitro's build system #### `@runners/orchestrator` + Multi-region orchestration service: -- Accepts run requests with multiple runners and regions -- Fans out jobs to different regions -- Executes jobs in parallel (with concurrency control) -- Aggregates results from all regions + +* Accepts run requests with multiple runners and regions +* Fans out jobs to different regions +* Executes jobs in parallel (with concurrency control) +* Aggregates results from all regions **Key Components:** -- `fanoutJobs()` - Groups runners by URL/region into jobs -- `runWorkflow()` - Main orchestration workflow -- `runRemoteStep()` - Executes jobs on remote runners -- `runLocalStep()` - Executes jobs locally -- `aggregateResults()` - Combines results from multiple jobs + +* `fanoutJobs()` - Groups runners by URL/region into jobs +* `runWorkflow()` - Main orchestration workflow +* `runRemoteStep()` - Executes jobs on remote runners +* `runLocalStep()` - Executes jobs locally +* `aggregateResults()` - Combines results from multiple jobs #### `@runners/nitro-orchestrator` + Nitro integration for the orchestrator: -- Registers orchestrator as Nitro module -- Exposes orchestrator API routes -- Integrates with Nitro's build system + +* Registers orchestrator as Nitro module +* Exposes orchestrator API routes +* Integrates with Nitro's build system ### Shared Packages #### `@runners/cli` + Command-line interface: -- Parses commands and flags -- Discovers runners -- Executes runners locally -- Provides formatted output + +* Parses commands and flags +* Discovers runners +* Executes runners locally +* Provides formatted output **Structure:** -- `src/commands/` - Individual command implementations -- `src/context/` - CLI context creation -- `src/utils/` - Shared utilities (logger, etc.) + +* `src/commands/` - Individual command implementations +* `src/context/` - CLI context creation +* `src/utils/` - Shared utilities (logger, etc.) #### `@runners/config` + Configuration management: -- `defineConfig()` helper for TypeScript config files -- Validates configuration -- Provides type-safe config + +* `defineConfig()` helper for TypeScript config files +* Validates configuration +* Provides type-safe config #### `@runners/contracts` + Shared API contracts using oRPC: -- `runnerContract` - Runner API contract -- `orchestratorContract` - Orchestrator API contract -- Zod schemas for validation -- Type-safe client/server types + +* `runnerContract` - Runner API contract +* `orchestratorContract` - Orchestrator API contract +* Zod schemas for validation +* Type-safe client/server types #### `@runners/errors` + Error handling utilities: -- Custom error types -- Error formatting -- Error serialization + +* Custom error types +* Error formatting +* Error serialization ### Build Tools #### `@runners/schema-extractor` + Rust-based tool that: -- Extracts runner schemas at build time -- Generates metadata JSON files -- Used by build plugins to pre-extract schemas + +* Extracts runner schemas at build time +* Generates metadata JSON files +* Used by build plugins to pre-extract schemas #### `@runners/swc-plugin-runners` + SWC plugin that: -- Removes `"use runner"` directives during compilation -- Integrates with schema extraction -- Optimizes runner discovery + +* Removes `"use runner"` directives during compilation +* Integrates with schema extraction +* Optimizes runner discovery #### `@runners/typescript-plugin` + TypeScript plugin that: -- Provides type checking for runners -- Validates runner signatures -- Provides IDE autocomplete + +* Provides type checking for runners +* Validates runner signatures +* Provides IDE autocomplete ## Data Flow @@ -186,6 +217,7 @@ Run Summary Response Runners are discovered using the `"use runner"` directive: ### Module-Level Directive + ```typescript "use runner"; @@ -194,6 +226,7 @@ export const runner2: Runner = async (ctx) => { /* ... */ }; ``` ### Function-Level Directive + ```typescript export const runner: Runner = async (ctx) => { "use runner"; @@ -202,6 +235,7 @@ export const runner: Runner = async (ctx) => { ``` The discovery process: + 1. Scans `src/**/*.ts` and `runners/**/*.ts` (configurable) 2. Parses AST to find `"use runner"` directives 3. Extracts exported async functions with directives @@ -231,14 +265,17 @@ type Runner = ( ``` Runners support both input and output validation: -- **Input schemas** (`TInput`) - Validated at runtime before execution -- **Output schemas** (`TOutput`) - Provide TypeScript typing for the `details` field + +* **Input schemas** (`TInput`) - Validated at runtime before execution +* **Output schemas** (`TOutput`) - Provide TypeScript typing for the `details` field Runners can use: -- **Zod schemas** - Most common, full TypeScript inference and runtime validation -- **Standard Schema** - Compatible with other validators + +* **Zod schemas** - Most common, full TypeScript inference and runtime validation +* **Standard Schema** - Compatible with other validators **Example:** + ```typescript const InputSchema = z.object({ url: z.string().url() }); const OutputSchema = z.object({ title: z.string() }); @@ -300,18 +337,21 @@ All errors are normalized into `RunnerResult` with `status: "error"`. ## Concurrency Model ### Local Execution -- Runners execute sequentially by default -- Can be parallelized by orchestrator + +* Runners execute sequentially by default +* Can be parallelized by orchestrator ### Orchestrated Execution -- Jobs execute in parallel (configurable concurrency) -- Default: unlimited parallelism -- Can limit with `concurrency` option + +* Jobs execute in parallel (configurable concurrency) +* Default: unlimited parallelism +* Can limit with `concurrency` option ### Playwright -- Each runner gets its own browser context -- Contexts are isolated -- Browsers are reused when possible + +* Each runner gets its own browser context +* Contexts are isolated +* Browsers are reused when possible ## Security Considerations @@ -343,9 +383,8 @@ The architecture supports extension through: Potential architectural improvements: -- [ ] Runner caching and memoization -- [ ] Distributed runner discovery -- [ ] Streaming results -- [ ] Custom result aggregators -- [ ] Plugin system for extensions - +* \[ ] Runner caching and memoization +* \[ ] Distributed runner discovery +* \[ ] Streaming results +* \[ ] Custom result aggregators +* \[ ] Plugin system for extensions diff --git a/docs/frameworks/hono/advanced.mdx b/docs/frameworks/hono/advanced.mdx index b1b53c4..d47dcfa 100644 --- a/docs/frameworks/hono/advanced.mdx +++ b/docs/frameworks/hono/advanced.mdx @@ -26,7 +26,6 @@ availableIn: ## See Also -- [Writing Runners](/docs/frameworks/hono/writing-runners) - Runner authoring guide -- [API Reference](/docs/reference/api) - Complete API documentation -- [Deployment](/docs/frameworks/hono/deployment) - Production deployment guide - +* [Writing Runners](/docs/frameworks/hono/writing-runners) - Runner authoring guide +* [API Reference](/docs/reference/api) - Complete API documentation +* [Deployment](/docs/frameworks/hono/deployment) - Production deployment guide diff --git a/docs/frameworks/hono/configuration.mdx b/docs/frameworks/hono/configuration.mdx index 25753c0..a2eb572 100644 --- a/docs/frameworks/hono/configuration.mdx +++ b/docs/frameworks/hono/configuration.mdx @@ -49,6 +49,5 @@ See [Nitro Configuration](/docs/frameworks/nitro/configuration) for all availabl ## Next Steps -- Learn about [Deployment](/docs/frameworks/hono/deployment) -- Check [Quickstart](/docs/frameworks/hono/quickstart) for setup guide - +* Learn about [Deployment](/docs/frameworks/hono/deployment) +* Check [Quickstart](/docs/frameworks/hono/quickstart) for setup guide diff --git a/docs/frameworks/hono/deployment.mdx b/docs/frameworks/hono/deployment.mdx index 5db3014..c9b8d0c 100644 --- a/docs/frameworks/hono/deployment.mdx +++ b/docs/frameworks/hono/deployment.mdx @@ -94,6 +94,5 @@ docker run -p 3000:3000 -e RUNNER_REGION=us-east-1 runners-hono ## Next Steps -- Learn about [Configuration](/docs/frameworks/hono/configuration) -- Check [Quickstart](/docs/frameworks/hono/quickstart) for setup guide - +* Learn about [Configuration](/docs/frameworks/hono/configuration) +* Check [Quickstart](/docs/frameworks/hono/quickstart) for setup guide diff --git a/docs/frameworks/hono/orchestration.mdx b/docs/frameworks/hono/orchestration.mdx index b8b8c26..d41becd 100644 --- a/docs/frameworks/hono/orchestration.mdx +++ b/docs/frameworks/hono/orchestration.mdx @@ -82,11 +82,11 @@ export default defineConfig({ The orchestrator automatically provides: -- `POST /api/orchestrator` - Submit a new run request -- `GET /api/orchestrator/{runId}/status` - Get run status -- `GET /api/orchestrator/{runId}` - Get run results -- `GET /api/orchestrator/docs` - Interactive API documentation -- `GET /api/orchestrator/spec.json` - OpenAPI specification +* `POST /api/orchestrator` - Submit a new run request +* `GET /api/orchestrator/{runId}/status` - Get run status +* `GET /api/orchestrator/{runId}` - Get run results +* `GET /api/orchestrator/docs` - Interactive API documentation +* `GET /api/orchestrator/spec.json` - OpenAPI specification ../shared/orchestration.mdx#modes @@ -98,5 +98,5 @@ The orchestrator automatically provides: ## See Also -- [API Reference](/docs/reference/api) - Complete API documentation -- [Deployment](/docs/frameworks/hono/deployment) - Production deployment guide +* [API Reference](/docs/reference/api) - Complete API documentation +* [Deployment](/docs/frameworks/hono/deployment) - Production deployment guide diff --git a/docs/frameworks/hono/quickstart.mdx b/docs/frameworks/hono/quickstart.mdx index ba5fa88..1a58973 100644 --- a/docs/frameworks/hono/quickstart.mdx +++ b/docs/frameworks/hono/quickstart.mdx @@ -34,9 +34,10 @@ export default defineConfig({ ``` The Runners module will automatically: -- Discover runners from `src/**/*.ts` and `runners/**/*.ts` -- Expose runner endpoints at `/api/runner/*` -- Leave your Hono routes untouched + +* Discover runners from `src/**/*.ts` and `runners/**/*.ts` +* Expose runner endpoints at `/api/runner/*` +* Leave your Hono routes untouched ## Create Your First Runner @@ -172,7 +173,6 @@ node .output/server/index.mjs ## Next Steps -- Learn about [Writing Runners](/docs/frameworks/hono/writing-runners) -- Check out [Deployment](/docs/frameworks/hono/deployment) for production setup -- Explore [API Reference](/docs/reference/api) for detailed API docs - +* Learn about [Writing Runners](/docs/frameworks/hono/writing-runners) +* Check out [Deployment](/docs/frameworks/hono/deployment) for production setup +* Explore [API Reference](/docs/reference/api) for detailed API docs diff --git a/docs/frameworks/hono/writing-runners.mdx b/docs/frameworks/hono/writing-runners.mdx index 9abbe08..7a54feb 100644 --- a/docs/frameworks/hono/writing-runners.mdx +++ b/docs/frameworks/hono/writing-runners.mdx @@ -34,13 +34,12 @@ availableIn: When writing runners for Hono (via Nitro): -- Runners work alongside your Hono routes -- Runner endpoints are automatically exposed at `/api/runner/*` -- Your Hono app continues to work normally +* Runners work alongside your Hono routes +* Runner endpoints are automatically exposed at `/api/runner/*` +* Your Hono app continues to work normally ## See Also -- [Configuration](/docs/frameworks/hono/configuration) - Configure runner discovery -- [Deployment](/docs/frameworks/hono/deployment) - Deploy Hono applications -- [API Reference](/docs/reference/api) - Complete API documentation - +* [Configuration](/docs/frameworks/hono/configuration) - Configure runner discovery +* [Deployment](/docs/frameworks/hono/deployment) - Deploy Hono applications +* [API Reference](/docs/reference/api) - Complete API documentation diff --git a/docs/frameworks/http/advanced.mdx b/docs/frameworks/http/advanced.mdx index ec66c9c..8db5f21 100644 --- a/docs/frameworks/http/advanced.mdx +++ b/docs/frameworks/http/advanced.mdx @@ -26,7 +26,6 @@ availableIn: ## See Also -- [Writing Runners](/docs/frameworks/http/writing-runners) - Runner authoring guide -- [API Reference](/docs/reference/api) - Complete API documentation -- [Deployment](/docs/frameworks/http/deployment) - Production deployment guide - +* [Writing Runners](/docs/frameworks/http/writing-runners) - Runner authoring guide +* [API Reference](/docs/reference/api) - Complete API documentation +* [Deployment](/docs/frameworks/http/deployment) - Production deployment guide diff --git a/docs/frameworks/http/deployment.mdx b/docs/frameworks/http/deployment.mdx index aa56355..aa7d98c 100644 --- a/docs/frameworks/http/deployment.mdx +++ b/docs/frameworks/http/deployment.mdx @@ -129,5 +129,4 @@ spec: ## Next Steps -- Check [Quickstart](/docs/frameworks/http/quickstart) for setup guide - +* Check [Quickstart](/docs/frameworks/http/quickstart) for setup guide diff --git a/docs/frameworks/http/orchestration.mdx b/docs/frameworks/http/orchestration.mdx index 5054c70..69847a6 100644 --- a/docs/frameworks/http/orchestration.mdx +++ b/docs/frameworks/http/orchestration.mdx @@ -59,11 +59,11 @@ export PLAYWRIGHT_RUNNERS='{ The orchestrator provides: -- `POST /api/orchestrator` - Submit a new run request -- `GET /api/orchestrator/{runId}/status` - Get run status -- `GET /api/orchestrator/{runId}` - Get run results -- `GET /api/orchestrator/docs` - Interactive API documentation -- `GET /api/orchestrator/spec.json` - OpenAPI specification +* `POST /api/orchestrator` - Submit a new run request +* `GET /api/orchestrator/{runId}/status` - Get run status +* `GET /api/orchestrator/{runId}` - Get run results +* `GET /api/orchestrator/docs` - Interactive API documentation +* `GET /api/orchestrator/spec.json` - OpenAPI specification ../shared/orchestration.mdx#modes @@ -75,5 +75,5 @@ The orchestrator provides: ## See Also -- [API Reference](/docs/reference/api) - Complete API documentation -- [Deployment](/docs/frameworks/http/deployment) - Production deployment guide +* [API Reference](/docs/reference/api) - Complete API documentation +* [Deployment](/docs/frameworks/http/deployment) - Production deployment guide diff --git a/docs/frameworks/http/quickstart.mdx b/docs/frameworks/http/quickstart.mdx index 919d6ea..2769241 100644 --- a/docs/frameworks/http/quickstart.mdx +++ b/docs/frameworks/http/quickstart.mdx @@ -160,10 +160,10 @@ Open `http://localhost:3000/api/runner/docs` in your browser for interactive API The handler provides: -- `POST /api/runner/execute` - Execute runners -- `GET /api/runner/info` - Get runner information -- `GET /api/runner/docs` - Interactive API documentation (Scalar UI) -- `GET /api/runner/spec.json` - OpenAPI specification +* `POST /api/runner/execute` - Execute runners +* `GET /api/runner/info` - Get runner information +* `GET /api/runner/docs` - Interactive API documentation (Scalar UI) +* `GET /api/runner/spec.json` - OpenAPI specification ## Deployment @@ -216,7 +216,6 @@ CMD ["node", "dist/server.js"] ## Next Steps -- Learn about [Writing Runners](/docs/frameworks/http/writing-runners) -- Check out [Deployment](/docs/frameworks/http/deployment) for production setup -- Explore [API Reference](/docs/reference/api) for detailed API docs - +* Learn about [Writing Runners](/docs/frameworks/http/writing-runners) +* Check out [Deployment](/docs/frameworks/http/deployment) for production setup +* Explore [API Reference](/docs/reference/api) for detailed API docs diff --git a/docs/frameworks/http/writing-runners.mdx b/docs/frameworks/http/writing-runners.mdx index 26a37b7..a6499d4 100644 --- a/docs/frameworks/http/writing-runners.mdx +++ b/docs/frameworks/http/writing-runners.mdx @@ -34,12 +34,11 @@ availableIn: When writing runners for standalone HTTP API: -- Import runners from your `runners/` directory -- Use `createHttpRunner()` to create the handler -- Runners are executed via HTTP requests +* Import runners from your `runners/` directory +* Use `createHttpRunner()` to create the handler +* Runners are executed via HTTP requests ## See Also -- [Deployment](/docs/frameworks/http/deployment) - Deploy HTTP API servers -- [API Reference](/docs/reference/api) - Complete API documentation - +* [Deployment](/docs/frameworks/http/deployment) - Deploy HTTP API servers +* [API Reference](/docs/reference/api) - Complete API documentation diff --git a/docs/frameworks/index.mdx b/docs/frameworks/index.mdx index 1218c73..faed955 100644 --- a/docs/frameworks/index.mdx +++ b/docs/frameworks/index.mdx @@ -11,6 +11,4 @@ full: true } href="/docs/frameworks/hono/quickstart" /> } href="/docs/frameworks/http/quickstart" /> - - diff --git a/docs/frameworks/nitro/advanced.mdx b/docs/frameworks/nitro/advanced.mdx index 20704b7..0512ceb 100644 --- a/docs/frameworks/nitro/advanced.mdx +++ b/docs/frameworks/nitro/advanced.mdx @@ -26,7 +26,6 @@ availableIn: ## See Also -- [Writing Runners](/docs/frameworks/nitro/writing-runners) - Runner authoring guide -- [API Reference](/docs/reference/api) - Complete API documentation -- [Deployment](/docs/frameworks/nitro/deployment) - Production deployment guide - +* [Writing Runners](/docs/frameworks/nitro/writing-runners) - Runner authoring guide +* [API Reference](/docs/reference/api) - Complete API documentation +* [Deployment](/docs/frameworks/nitro/deployment) - Production deployment guide diff --git a/docs/frameworks/nitro/configuration.mdx b/docs/frameworks/nitro/configuration.mdx index a50228d..9d31fbc 100644 --- a/docs/frameworks/nitro/configuration.mdx +++ b/docs/frameworks/nitro/configuration.mdx @@ -63,9 +63,10 @@ export default defineConfig({ ## Build Configuration The module automatically: -- Extracts schemas at build time -- Bundles runners for optimal performance -- Creates virtual handlers for API routes + +* Extracts schemas at build time +* Bundles runners for optimal performance +* Creates virtual handlers for API routes ### Custom Build Directory @@ -79,19 +80,20 @@ export default defineConfig({ ## Development Mode In development, the module: -- Watches for runner file changes -- Rebuilds incrementally -- Externalizes runner bundles to prevent reloads + +* Watches for runner file changes +* Rebuilds incrementally +* Externalizes runner bundles to prevent reloads ## Production Mode In production, the module: -- Performs one-time build -- Optimizes bundles -- Pre-extracts schemas -## Next Steps +* Performs one-time build +* Optimizes bundles +* Pre-extracts schemas -- Learn about [Deployment](/docs/frameworks/nitro/deployment) -- Check [Quickstart](/docs/frameworks/nitro/quickstart) for setup guide +## Next Steps +* Learn about [Deployment](/docs/frameworks/nitro/deployment) +* Check [Quickstart](/docs/frameworks/nitro/quickstart) for setup guide diff --git a/docs/frameworks/nitro/deployment.mdx b/docs/frameworks/nitro/deployment.mdx index 125c5c6..98db14d 100644 --- a/docs/frameworks/nitro/deployment.mdx +++ b/docs/frameworks/nitro/deployment.mdx @@ -33,7 +33,8 @@ npx vercel --prebuilt ### Environment Variables Set in Vercel dashboard: -- `RUNNER_REGION` - Region identifier (optional, defaults to Vercel region) + +* `RUNNER_REGION` - Region identifier (optional, defaults to Vercel region) ## Cloudflare Pages @@ -159,6 +160,5 @@ spec: ## Next Steps -- Learn about [Configuration](/docs/frameworks/nitro/configuration) -- Check [Orchestration](/docs/frameworks/nitro/orchestration) for multi-region setup - +* Learn about [Configuration](/docs/frameworks/nitro/configuration) +* Check [Orchestration](/docs/frameworks/nitro/orchestration) for multi-region setup diff --git a/docs/frameworks/nitro/orchestration.mdx b/docs/frameworks/nitro/orchestration.mdx index 6f17c8c..2c16ed3 100644 --- a/docs/frameworks/nitro/orchestration.mdx +++ b/docs/frameworks/nitro/orchestration.mdx @@ -76,11 +76,11 @@ export default defineConfig({ The orchestrator automatically provides: -- `POST /api/orchestrator` - Submit a new run request -- `GET /api/orchestrator/{runId}/status` - Get run status -- `GET /api/orchestrator/{runId}` - Get run results -- `GET /api/orchestrator/docs` - Interactive API documentation -- `GET /api/orchestrator/spec.json` - OpenAPI specification +* `POST /api/orchestrator` - Submit a new run request +* `GET /api/orchestrator/{runId}/status` - Get run status +* `GET /api/orchestrator/{runId}` - Get run results +* `GET /api/orchestrator/docs` - Interactive API documentation +* `GET /api/orchestrator/spec.json` - OpenAPI specification ../shared/orchestration.mdx#modes @@ -92,5 +92,5 @@ The orchestrator automatically provides: ## See Also -- [API Reference](/docs/reference/api) - Complete API documentation -- [Deployment](/docs/frameworks/nitro/deployment) - Production deployment guide +* [API Reference](/docs/reference/api) - Complete API documentation +* [Deployment](/docs/frameworks/nitro/deployment) - Production deployment guide diff --git a/docs/frameworks/nitro/quickstart.mdx b/docs/frameworks/nitro/quickstart.mdx index b7f1a2d..d95a647 100644 --- a/docs/frameworks/nitro/quickstart.mdx +++ b/docs/frameworks/nitro/quickstart.mdx @@ -121,14 +121,13 @@ export default defineConfig({ The module automatically creates: -- `POST /api/runner/execute` - Execute runners -- `GET /api/runner/info` - Get runner information -- `GET /api/runner/docs` - Interactive API documentation (Scalar UI) -- `GET /api/runner/spec.json` - OpenAPI specification +* `POST /api/runner/execute` - Execute runners +* `GET /api/runner/info` - Get runner information +* `GET /api/runner/docs` - Interactive API documentation (Scalar UI) +* `GET /api/runner/spec.json` - OpenAPI specification ## Next Steps -- Learn about [Writing Runners](/docs/frameworks/nitro/writing-runners) -- Check out [Deployment](/docs/frameworks/nitro/deployment) for production setup -- Explore [API Reference](/docs/reference/api) for detailed API docs - +* Learn about [Writing Runners](/docs/frameworks/nitro/writing-runners) +* Check out [Deployment](/docs/frameworks/nitro/deployment) for production setup +* Explore [API Reference](/docs/reference/api) for detailed API docs diff --git a/docs/frameworks/nitro/writing-runners.mdx b/docs/frameworks/nitro/writing-runners.mdx index f372aa4..84ea40e 100644 --- a/docs/frameworks/nitro/writing-runners.mdx +++ b/docs/frameworks/nitro/writing-runners.mdx @@ -34,13 +34,12 @@ availableIn: When writing runners for Nitro: -- Runners are automatically discovered from `src/**/*.ts` and `runners/**/*.ts` -- Schemas are extracted at build time for optimal performance -- Runners are bundled automatically during Nitro build +* Runners are automatically discovered from `src/**/*.ts` and `runners/**/*.ts` +* Schemas are extracted at build time for optimal performance +* Runners are bundled automatically during Nitro build ## See Also -- [Configuration](/docs/frameworks/nitro/configuration) - Configure runner discovery -- [Deployment](/docs/frameworks/nitro/deployment) - Deploy Nitro applications -- [API Reference](/docs/reference/api) - Complete API documentation - +* [Configuration](/docs/frameworks/nitro/configuration) - Configure runner discovery +* [Deployment](/docs/frameworks/nitro/deployment) - Deploy Nitro applications +* [API Reference](/docs/reference/api) - Complete API documentation diff --git a/docs/frameworks/shared/advanced.mdx b/docs/frameworks/shared/advanced.mdx index 0f21fef..73bc149 100644 --- a/docs/frameworks/shared/advanced.mdx +++ b/docs/frameworks/shared/advanced.mdx @@ -15,199 +15,198 @@ availableIn: ---
-## Custom Schema Validators - -Runners support Standard Schema-compatible validators, not just Zod: - -```typescript -import { StandardSchemaV1 } from "@standard-schema/spec"; -import type { Runner } from "runners"; - -// Using a custom validator -const CustomInputSchema: StandardSchemaV1 = { - "~standard": { - validate: (value) => { - if (typeof value !== "string") { - return { issues: [{ message: "Must be string" }] }; - } - return { value }; + ## Custom Schema Validators + + Runners support Standard Schema-compatible validators, not just Zod: + + ```typescript + import { StandardSchemaV1 } from "@standard-schema/spec"; + import type { Runner } from "runners"; + + // Using a custom validator + const CustomInputSchema: StandardSchemaV1 = { + "~standard": { + validate: (value) => { + if (typeof value !== "string") { + return { issues: [{ message: "Must be string" }] }; + } + return { value }; + }, }, - }, -}; - -export const customRunner: Runner = async ( - ctx, - input -) => { - "use runner"; - // input is typed as string - return { name: "custom", status: "pass" }; -}; -``` + }; + + export const customRunner: Runner = async ( + ctx, + input + ) => { + "use runner"; + // input is typed as string + return { name: "custom", status: "pass" }; + }; + ```
-## Performance Optimization + ## Performance Optimization -### 1. Schema Pre-extraction + ### 1. Schema Pre-extraction -Schemas are extracted at build time for performance: + Schemas are extracted at build time for performance: -```bash -# Build extracts schemas automatically -npm run build -``` + ```bash + # Build extracts schemas automatically + npm run build + ``` -### 2. Parallel Execution + ### 2. Parallel Execution -Use orchestrator for parallel execution: + Use orchestrator for parallel execution: -```typescript -const runRequest = { - runners: [/* ... */], - mode: "remote", - concurrency: 10, // Run 10 jobs in parallel -}; -``` + ```typescript + const runRequest = { + runners: [/* ... */], + mode: "remote", + concurrency: 10, // Run 10 jobs in parallel + }; + ``` -### 3. Browser Reuse + ### 3. Browser Reuse -Playwright contexts are reused when possible: + Playwright contexts are reused when possible: -```typescript -// Multiple runners can share browser context -const { page } = await withPlaywright(ctx, url); -// Browser is reused for subsequent runners -``` + ```typescript + // Multiple runners can share browser context + const { page } = await withPlaywright(ctx, url); + // Browser is reused for subsequent runners + ``` -### 4. Lazy Loading + ### 4. Lazy Loading -Runners are loaded only when needed: + Runners are loaded only when needed: -```typescript -// Runners loaded on-demand -const runner = await import(`./runners/${runnerName}`); -``` + ```typescript + // Runners loaded on-demand + const runner = await import(`./runners/${runnerName}`); + ```
-## Error Recovery - -Implement retry logic: - -```typescript -async function runWithRetry( - runner: Runner, - context: RunnerContext, - input: unknown, - maxRetries = 3 -) { - for (let attempt = 1; attempt <= maxRetries; attempt++) { - try { - return await runner(context, input); - } catch (error) { - if (attempt === maxRetries) { - throw error; + ## Error Recovery + + Implement retry logic: + + ```typescript + async function runWithRetry( + runner: Runner, + context: RunnerContext, + input: unknown, + maxRetries = 3 + ) { + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + return await runner(context, input); + } catch (error) { + if (attempt === maxRetries) { + throw error; + } + + // Exponential backoff + await new Promise(resolve => + setTimeout(resolve, Math.pow(2, attempt) * 1000) + ); } - - // Exponential backoff - await new Promise(resolve => - setTimeout(resolve, Math.pow(2, attempt) * 1000) - ); } } -} -``` + ```
-## Testing Runners + ## Testing Runners -### Unit Testing + ### Unit Testing -```typescript -import { describe, it, expect } from "vitest"; -import { cookieBannerTest } from "./runners"; + ```typescript + import { describe, it, expect } from "vitest"; + import { cookieBannerTest } from "./runners"; -describe("cookieBannerTest", () => { - it("should pass when banner is visible", async () => { - const context = { - log: () => {}, - region: "us-east-1", - }; - - const result = await cookieBannerTest(context, { - url: "https://example.com", + describe("cookieBannerTest", () => { + it("should pass when banner is visible", async () => { + const context = { + log: () => {}, + region: "us-east-1", + }; + + const result = await cookieBannerTest(context, { + url: "https://example.com", + }); + + expect(result.status).toBe("pass"); }); - - expect(result.status).toBe("pass"); }); -}); -``` + ``` -### Integration Testing + ### Integration Testing -```typescript -import { runRunners } from "runners"; -import { cookieBannerTest } from "./runners"; + ```typescript + import { runRunners } from "runners"; + import { cookieBannerTest } from "./runners"; -describe("Runner Integration", () => { - it("should execute runners", async () => { - const result = await runRunners({ - runners: [cookieBannerTest], - region: "us-east-1", + describe("Runner Integration", () => { + it("should execute runners", async () => { + const result = await runRunners({ + runners: [cookieBannerTest], + region: "us-east-1", + }); + + expect(result.results).toHaveLength(1); + expect(result.results[0].status).toBe("pass"); }); - - expect(result.results).toHaveLength(1); - expect(result.results[0].status).toBe("pass"); }); -}); -``` + ```
-## Advanced Playwright Usage - -### Custom Browser Options - -```typescript -import { withPlaywright } from "runners/playwright"; - -export const customBrowserRunner: Runner = async (ctx, input) => { - "use runner"; - - const { browser } = await withPlaywright(ctx, input.url); - - // Access browser directly - const context = await browser.newContext({ - viewport: { width: 1920, height: 1080 }, - userAgent: "Custom User Agent", - }); - - const page = await context.newPage(); - // ... use custom page -}; -``` - -### Multiple Pages - -```typescript -export const multiPageRunner: Runner = async (ctx, input) => { - "use runner"; - - const { browser } = await withPlaywright(ctx, input.url); - - const pages = await Promise.all([ - browser.newPage(), - browser.newPage(), - browser.newPage(), - ]); - - // Use multiple pages - await Promise.all(pages.map(page => page.goto(input.url))); - - // ... rest of logic -}; -``` -
+ ## Advanced Playwright Usage + ### Custom Browser Options + + ```typescript + import { withPlaywright } from "runners/playwright"; + + export const customBrowserRunner: Runner = async (ctx, input) => { + "use runner"; + + const { browser } = await withPlaywright(ctx, input.url); + + // Access browser directly + const context = await browser.newContext({ + viewport: { width: 1920, height: 1080 }, + userAgent: "Custom User Agent", + }); + + const page = await context.newPage(); + // ... use custom page + }; + ``` + + ### Multiple Pages + + ```typescript + export const multiPageRunner: Runner = async (ctx, input) => { + "use runner"; + + const { browser } = await withPlaywright(ctx, input.url); + + const pages = await Promise.all([ + browser.newPage(), + browser.newPage(), + browser.newPage(), + ]); + + // Use multiple pages + await Promise.all(pages.map(page => page.goto(input.url))); + + // ... rest of logic + }; + ``` + diff --git a/docs/frameworks/shared/orchestration.mdx b/docs/frameworks/shared/orchestration.mdx index 24cd16a..aa91dec 100644 --- a/docs/frameworks/shared/orchestration.mdx +++ b/docs/frameworks/shared/orchestration.mdx @@ -15,315 +15,320 @@ availableIn: ---
-## Overview + ## Overview -The orchestrator allows you to: -- Run the same tests across multiple regions -- Execute tests in parallel -- Aggregate results from distributed runs -- Coordinate execution across multiple runner endpoints + The orchestrator allows you to: + + * Run the same tests across multiple regions + * Execute tests in parallel + * Aggregate results from distributed runs + * Coordinate execution across multiple runner endpoints
-## Architecture - -``` -Client - โ†“ -Orchestrator (single request) - โ†“ -fanoutJobs() - Groups runners by URL/region - โ†“ -Jobs Created (one per URL-region combination) - โ†“ -Parallel Execution - โ”œโ”€โ†’ Remote Runner (us-east-1) - โ”œโ”€โ†’ Remote Runner (eu-west-1) - โ””โ”€โ†’ Remote Runner (ap-southeast-1) - โ†“ -Results Aggregated - โ†“ -Single Run Summary -``` + ## Architecture + + ``` + Client + โ†“ + Orchestrator (single request) + โ†“ + fanoutJobs() - Groups runners by URL/region + โ†“ + Jobs Created (one per URL-region combination) + โ†“ + Parallel Execution + โ”œโ”€โ†’ Remote Runner (us-east-1) + โ”œโ”€โ†’ Remote Runner (eu-west-1) + โ””โ”€โ†’ Remote Runner (ap-southeast-1) + โ†“ + Results Aggregated + โ†“ + Single Run Summary + ```
-## Setup + ## Setup -### 1. Install Package + ### 1. Install Package -```bash -pnpm add runners -``` + ```bash + pnpm add runners + ``` -### 2. Configure Runner Endpoints + ### 2. Configure Runner Endpoints -Set the `PLAYWRIGHT_RUNNERS` environment variable: + Set the `PLAYWRIGHT_RUNNERS` environment variable: -```bash -export PLAYWRIGHT_RUNNERS='{ - "us-east-1": "https://runner-us-east-1.example.com/api/runner", - "eu-west-1": "https://runner-eu-west-1.example.com/api/runner", - "ap-southeast-1": "https://runner-ap-southeast-1.example.com/api/runner" -}' -``` + ```bash + export PLAYWRIGHT_RUNNERS='{ + "us-east-1": "https://runner-us-east-1.example.com/api/runner", + "eu-west-1": "https://runner-eu-west-1.example.com/api/runner", + "ap-southeast-1": "https://runner-ap-southeast-1.example.com/api/runner" + }' + ``` -### 3. Create Orchestrator Handler + ### 3. Create Orchestrator Handler -```typescript -// server.ts -import { createOrchestratorHandler } from "runners/orchestrator"; + ```typescript + // server.ts + import { createOrchestratorHandler } from "runners/orchestrator"; -export default createOrchestratorHandler(); -``` + export default createOrchestratorHandler(); + ``` -### 4. Deploy Runner Endpoints + ### 4. Deploy Runner Endpoints -Deploy runner endpoints in each region: + Deploy runner endpoints in each region: -```typescript -// runner-server.ts (deploy to each region) -import { createHttpRunner } from "runners/http"; -import * as runners from "./runners"; + ```typescript + // runner-server.ts (deploy to each region) + import { createHttpRunner } from "runners/http"; + import * as runners from "./runners"; -export default createHttpRunner({ - runners, - region: process.env.RUNNER_REGION, // e.g., "us-east-1" -}); -``` + export default createHttpRunner({ + runners, + region: process.env.RUNNER_REGION, // e.g., "us-east-1" + }); + ```
-## Usage + ## Usage -### Submit a Run Request + ### Submit a Run Request -```bash -curl -X POST http://orchestrator.example.com/api/orchestrator \ - -H "Content-Type: application/json" \ - -d '{ - "runners": [ + ```bash + curl -X POST http://orchestrator.example.com/api/orchestrator \ + -H "Content-Type: application/json" \ + -d '{ + "runners": [ + { + "name": "cookieBannerTest", + "region": "us-east-1", + "input": { + "url": "https://example.com" + } + }, + { + "name": "cookieBannerTest", + "region": "eu-west-1", + "input": { + "url": "https://example.com" + } + } + ], + "mode": "remote" + }' + ``` + + Response: + + ```json + { + "runId": "run_abc123" + } + ``` + + ### Check Run Status + + ```bash + curl http://orchestrator.example.com/api/orchestrator/run_abc123/status + ``` + + Response: + + ```json + { + "runId": "run_abc123", + "state": "running", + "totalJobs": 2, + "completedJobs": 1, + "failedJobs": 0, + "createdAt": "2024-01-01T00:00:00Z", + "updatedAt": "2024-01-01T00:01:00Z" + } + ``` + + ### Get Run Results + + ```bash + curl http://orchestrator.example.com/api/orchestrator/run_abc123 + ``` + + Response: + + ```json + { + "runId": "run_abc123", + "state": "completed", + "jobs": [ { - "name": "cookieBannerTest", + "jobId": "job_123", "region": "us-east-1", - "input": { - "url": "https://example.com" - } - }, + "state": "completed", + "results": [ + { + "name": "cookieBannerTest", + "status": "pass", + "durationMs": 1234 + } + ], + "startedAt": "2024-01-01T00:00:00Z", + "completedAt": "2024-01-01T00:01:00Z", + "durationMs": 60000 + } + ], + "summary": { + "total": 2, + "passed": 2, + "failed": 0, + "errored": 0 + }, + "createdAt": "2024-01-01T00:00:00Z", + "completedAt": "2024-01-01T00:01:00Z", + "durationMs": 62000 + } + ``` +
+ +
+ ## Run Modes + + ### Remote Mode + + Execute runners on remote endpoints: + + ```json + { + "runners": [ { "name": "cookieBannerTest", - "region": "eu-west-1", - "input": { - "url": "https://example.com" - } + "region": "us-east-1", + "input": { "url": "https://example.com" } } ], "mode": "remote" - }' -``` - -Response: -```json -{ - "runId": "run_abc123" -} -``` - -### Check Run Status - -```bash -curl http://orchestrator.example.com/api/orchestrator/run_abc123/status -``` - -Response: -```json -{ - "runId": "run_abc123", - "state": "running", - "totalJobs": 2, - "completedJobs": 1, - "failedJobs": 0, - "createdAt": "2024-01-01T00:00:00Z", - "updatedAt": "2024-01-01T00:01:00Z" -} -``` - -### Get Run Results - -```bash -curl http://orchestrator.example.com/api/orchestrator/run_abc123 -``` - -Response: -```json -{ - "runId": "run_abc123", - "state": "completed", - "jobs": [ - { - "jobId": "job_123", - "region": "us-east-1", - "state": "completed", - "results": [ - { - "name": "cookieBannerTest", - "status": "pass", - "durationMs": 1234 - } - ], - "startedAt": "2024-01-01T00:00:00Z", - "completedAt": "2024-01-01T00:01:00Z", - "durationMs": 60000 - } - ], - "summary": { - "total": 2, - "passed": 2, - "failed": 0, - "errored": 0 - }, - "createdAt": "2024-01-01T00:00:00Z", - "completedAt": "2024-01-01T00:01:00Z", - "durationMs": 62000 -} -``` -
+ } + ``` -
-## Run Modes + **Requirements:** -### Remote Mode + * Each runner must specify a `region` + * `PLAYWRIGHT_RUNNERS` must be configured + * Runner endpoints must be accessible -Execute runners on remote endpoints: + ### Local Mode -```json -{ - "runners": [ - { - "name": "cookieBannerTest", - "region": "us-east-1", - "input": { "url": "https://example.com" } - } - ], - "mode": "remote" -} -``` - -**Requirements:** -- Each runner must specify a `region` -- `PLAYWRIGHT_RUNNERS` must be configured -- Runner endpoints must be accessible - -### Local Mode - -Execute runners locally (for testing): - -```json -{ - "runners": [ - { - "name": "cookieBannerTest", - "input": { "url": "https://example.com" } - } - ], - "mode": "local" -} -``` - -**Requirements:** -- Runners must be available locally -- Each runner must specify a `url` in input + Execute runners locally (for testing): + + ```json + { + "runners": [ + { + "name": "cookieBannerTest", + "input": { "url": "https://example.com" } + } + ], + "mode": "local" + } + ``` + + **Requirements:** + + * Runners must be available locally + * Each runner must specify a `url` in input
-## Concurrency Control + ## Concurrency Control -Control how many jobs run in parallel: + Control how many jobs run in parallel: -```json -{ - "runners": [ /* ... */ ], - "mode": "remote", - "concurrency": 3 -} -``` + ```json + { + "runners": [ /* ... */ ], + "mode": "remote", + "concurrency": 3 + } + ``` -- **Default**: Unlimited (all jobs run in parallel) -- **Limited**: Set `concurrency` to limit parallel jobs -- **Sequential**: Set `concurrency: 1` to run one at a time + * **Default**: Unlimited (all jobs run in parallel) + * **Limited**: Set `concurrency` to limit parallel jobs + * **Sequential**: Set `concurrency: 1` to run one at a time
-## Timeouts + ## Timeouts -Set timeout for entire run: + Set timeout for entire run: -```json -{ - "runners": [ /* ... */ ], - "mode": "remote", - "timeout": 60000 -} -``` + ```json + { + "runners": [ /* ... */ ], + "mode": "remote", + "timeout": 60000 + } + ``` -- Timeout applies to entire run (all jobs) -- Individual job timeouts are handled by runner endpoints -- Run status becomes `"timed_out"` if timeout exceeded + * Timeout applies to entire run (all jobs) + * Individual job timeouts are handled by runner endpoints + * Run status becomes `"timed_out"` if timeout exceeded
-## Best Practices + ## Best Practices -### 1. Use Consistent URLs + ### 1. Use Consistent URLs -Group runners by URL to minimize job count: + Group runners by URL to minimize job count: -```json -{ - "runners": [ - { "name": "test1", "region": "us-east-1", "input": { "url": "https://example.com" } }, - { "name": "test2", "region": "us-east-1", "input": { "url": "https://example.com" } } - ] -} -``` + ```json + { + "runners": [ + { "name": "test1", "region": "us-east-1", "input": { "url": "https://example.com" } }, + { "name": "test2", "region": "us-east-1", "input": { "url": "https://example.com" } } + ] + } + ``` -### 2. Set Appropriate Timeouts + ### 2. Set Appropriate Timeouts -```json -{ - "timeout": 300000 // 5 minutes for slow tests -} -``` + ```json + { + "timeout": 300000 // 5 minutes for slow tests + } + ``` -### 3. Monitor Run Status + ### 3. Monitor Run Status -Poll status endpoint for long-running runs: + Poll status endpoint for long-running runs: -```typescript -async function waitForCompletion(runId: string) { - while (true) { - const status = await getRunStatus(runId); - if (status.state === "completed" || status.state === "failed") { - return status; + ```typescript + async function waitForCompletion(runId: string) { + while (true) { + const status = await getRunStatus(runId); + if (status.state === "completed" || status.state === "failed") { + return status; + } + await sleep(5000); // Poll every 5 seconds } - await sleep(5000); // Poll every 5 seconds } -} -``` + ``` -### 4. Handle Failures Gracefully + ### 4. Handle Failures Gracefully -```typescript -const summary = await getRunResults(runId); + ```typescript + const summary = await getRunResults(runId); -if (summary.state === "failed") { - const failedJobs = summary.jobs.filter(job => job.state === "failed"); - console.error(`Failed jobs: ${failedJobs.length}`); - - for (const job of failedJobs) { - console.error(`Region ${job.region}: ${job.error}`); + if (summary.state === "failed") { + const failedJobs = summary.jobs.filter(job => job.state === "failed"); + console.error(`Failed jobs: ${failedJobs.length}`); + + for (const job of failedJobs) { + console.error(`Region ${job.region}: ${job.error}`); + } } -} -``` + ```
- diff --git a/docs/frameworks/shared/writing-runners.mdx b/docs/frameworks/shared/writing-runners.mdx index 9de2c6c..0091d2e 100644 --- a/docs/frameworks/shared/writing-runners.mdx +++ b/docs/frameworks/shared/writing-runners.mdx @@ -15,316 +15,320 @@ availableIn: ---
-## Overview + ## Overview -This guide covers everything you need to know about writing effective runners. + This guide covers everything you need to know about writing effective runners.
-## Basic Runner Structure + ## Basic Runner Structure -A runner is an async function that: -1. Has the `"use runner"` directive -2. Accepts `RunnerContext` and optional input -3. Returns a `RunnerResult` + A runner is an async function that: -```typescript -import type { Runner } from "runners"; + 1. Has the `"use runner"` directive + 2. Accepts `RunnerContext` and optional input + 3. Returns a `RunnerResult` -export const myRunner: Runner = async (ctx, input) => { - "use runner"; - - // Your logic here - - return { - name: "my_runner", - status: "pass", // or "fail" or "error" - details: { /* optional */ }, + ```typescript + import type { Runner } from "runners"; + + export const myRunner: Runner = async (ctx, input) => { + "use runner"; + + // Your logic here + + return { + name: "my_runner", + status: "pass", // or "fail" or "error" + details: { /* optional */ }, + }; }; -}; -``` + ```
-## The "use runner" Directive + ## The "use runner" Directive -The directive tells the discovery system that this function is a runner. It can be: + The directive tells the discovery system that this function is a runner. It can be: -### Function-Level (Recommended) -```typescript -export const runner: Runner = async (ctx) => { - "use runner"; - // Only this function is a runner -}; -``` + ### Function-Level (Recommended) -### Module-Level -```typescript -"use runner"; + ```typescript + export const runner: Runner = async (ctx) => { + "use runner"; + // Only this function is a runner + }; + ``` -export const runner1: Runner = async (ctx) => { - // All exported async functions are runners -}; + ### Module-Level -export const runner2: Runner = async (ctx) => { - // This is also a runner -}; -``` + ```typescript + "use runner"; -**Best Practice**: Use function-level directives for clarity and to avoid accidentally marking helper functions as runners. + export const runner1: Runner = async (ctx) => { + // All exported async functions are runners + }; + + export const runner2: Runner = async (ctx) => { + // This is also a runner + }; + ``` + + **Best Practice**: Use function-level directives for clarity and to avoid accidentally marking helper functions as runners.
-## Input and Output Validation + ## Input and Output Validation -Runners support validation for both input and output using Zod or Standard Schema. Input schemas are validated at runtime, while output schemas provide TypeScript typing for the `details` field. + Runners support validation for both input and output using Zod or Standard Schema. Input schemas are validated at runtime, while output schemas provide TypeScript typing for the `details` field. -### Input Validation + ### Input Validation -Use Zod schemas to validate and type your input: + Use Zod schemas to validate and type your input: -```typescript -import { z } from "zod"; + ```typescript + import { z } from "zod"; -const MyInputSchema = z.object({ - url: z.string().url(), - timeout: z.number().positive().optional(), -}); + const MyInputSchema = z.object({ + url: z.string().url(), + timeout: z.number().positive().optional(), + }); -export const validatedRunner: Runner< - z.infer -> = async (ctx, input) => { - "use runner"; - - // input is typed and validated - const url = input.url; // string - const timeout = input.timeout; // number | undefined -}; -``` + export const validatedRunner: Runner< + z.infer + > = async (ctx, input) => { + "use runner"; + + // input is typed and validated + const url = input.url; // string + const timeout = input.timeout; // number | undefined + }; + ``` -The runner harness will automatically validate input against your schema before execution. + The runner harness will automatically validate input against your schema before execution. -### Output Validation + ### Output Validation -Define output schemas to type the `details` field in your results: + Define output schemas to type the `details` field in your results: -```typescript -import { z } from "zod"; + ```typescript + import { z } from "zod"; -const MyInputSchema = z.object({ - url: z.string().url(), -}); + const MyInputSchema = z.object({ + url: z.string().url(), + }); -const MyOutputSchema = z.object({ - title: z.string(), - description: z.string().optional(), -}); + const MyOutputSchema = z.object({ + title: z.string(), + description: z.string().optional(), + }); -export const typedRunner: Runner< - z.infer, - z.infer -> = async (ctx, input) => { - "use runner"; - - return { - name: "typed_runner", - status: "pass", - details: { - title: "Example", // Typed by MyOutputSchema - description: "Optional", // Typed as string | undefined - }, + export const typedRunner: Runner< + z.infer, + z.infer + > = async (ctx, input) => { + "use runner"; + + return { + name: "typed_runner", + status: "pass", + details: { + title: "Example", // Typed by MyOutputSchema + description: "Optional", // Typed as string | undefined + }, + }; }; -}; -``` + ```
-## Using Playwright - -For browser automation, use the `withPlaywright()` helper: + ## Using Playwright + + For browser automation, use the `withPlaywright()` helper: + + ```typescript + import { withPlaywright } from "runners/playwright"; + + export const browserRunner: Runner = async (ctx, input) => { + "use runner"; + + const { page, log } = await withPlaywright(ctx, input.url); + + await page.goto(input.url); + const title = await page.title(); + + log("Page loaded", { title }); + + return { + name: "browser_check", + status: "pass", + details: { title }, + }; + }; + ``` -```typescript -import { withPlaywright } from "runners/playwright"; + The `withPlaywright()` helper: -export const browserRunner: Runner = async (ctx, input) => { - "use runner"; - - const { page, log } = await withPlaywright(ctx, input.url); - - await page.goto(input.url); - const title = await page.title(); - - log("Page loaded", { title }); - - return { - name: "browser_check", - status: "pass", - details: { title }, - }; -}; -``` - -The `withPlaywright()` helper: -- Launches a browser instance -- Provides a `page` object -- Handles browser lifecycle -- Manages browser contexts + * Launches a browser instance + * Provides a `page` object + * Handles browser lifecycle + * Manages browser contexts
-## Runner Context + ## Runner Context -The `RunnerContext` provides: + The `RunnerContext` provides: -```typescript -type RunnerContext = { - region?: string; - runId?: string; - log: (message: string, meta?: Record) => void; -}; -``` - -### Logging - -Use `ctx.log()` for structured logging: - -```typescript -export const loggedRunner: Runner = async (ctx, input) => { - "use runner"; - - ctx.log("Starting check", { url: input.url }); - - // ... do work ... - - ctx.log("Check complete", { result: "pass" }); - - return { name: "logged", status: "pass" }; -}; -``` + ```typescript + type RunnerContext = { + region?: string; + runId?: string; + log: (message: string, meta?: Record) => void; + }; + ``` + + ### Logging + + Use `ctx.log()` for structured logging: + + ```typescript + export const loggedRunner: Runner = async (ctx, input) => { + "use runner"; + + ctx.log("Starting check", { url: input.url }); + + // ... do work ... + + ctx.log("Check complete", { result: "pass" }); + + return { name: "logged", status: "pass" }; + }; + ``` -### Region and Run ID + ### Region and Run ID -Access region and run ID from context: + Access region and run ID from context: -```typescript -export const contextualRunner: Runner = async (ctx, input) => { - "use runner"; - - const region = ctx.region || "unknown"; - const runId = ctx.runId || "local"; - - return { - name: "contextual", - status: "pass", - details: { region, runId }, + ```typescript + export const contextualRunner: Runner = async (ctx, input) => { + "use runner"; + + const region = ctx.region || "unknown"; + const runId = ctx.runId || "local"; + + return { + name: "contextual", + status: "pass", + details: { region, runId }, + }; }; -}; -``` + ```
-## Error Handling + ## Error Handling + + Runners can throw errors, which are automatically caught and converted to error results: + + ```typescript + export const errorHandlingRunner: Runner = async (ctx, input) => { + "use runner"; + + try { + // Your logic + return { name: "success", status: "pass" }; + } catch (error) { + // Errors are caught and converted to error status + throw error; + } + }; + ``` -Runners can throw errors, which are automatically caught and converted to error results: + Errors result in: -```typescript -export const errorHandlingRunner: Runner = async (ctx, input) => { - "use runner"; - - try { - // Your logic - return { name: "success", status: "pass" }; - } catch (error) { - // Errors are caught and converted to error status - throw error; + ```json + { + "name": "runner_name", + "status": "error", + "errorMessage": "Error message here" } -}; -``` - -Errors result in: -```json -{ - "name": "runner_name", - "status": "error", - "errorMessage": "Error message here" -} -``` + ```
-## Best Practices + ## Best Practices -### 1. Use Descriptive Names + ### 1. Use Descriptive Names -```typescript -// Good -export const cookieBannerVisibleTest: Runner = async (ctx, input) => { - "use runner"; - // ... -}; + ```typescript + // Good + export const cookieBannerVisibleTest: Runner = async (ctx, input) => { + "use runner"; + // ... + }; -// Bad -export const test1: Runner = async (ctx, input) => { - "use runner"; - // ... -}; -``` + // Bad + export const test1: Runner = async (ctx, input) => { + "use runner"; + // ... + }; + ``` -### 2. Validate Input + ### 2. Validate Input -Always define input schemas: + Always define input schemas: -```typescript -const InputSchema = z.object({ - url: z.string().url(), -}); + ```typescript + const InputSchema = z.object({ + url: z.string().url(), + }); -export const validatedRunner: Runner> = async (ctx, input) => { - "use runner"; - // input is validated -}; -``` - -### 3. Return Meaningful Details - -```typescript -return { - name: "check", - status: "pass", - details: { - // Include useful information - title: "Page Title", - url: input.url, - timestamp: Date.now(), - }, -}; -``` - -### 4. Use Logging - -Log important events: - -```typescript -ctx.log("Starting check", { url: input.url }); -ctx.log("Check complete", { result: "pass", duration: 1234 }); -``` - -### 5. Handle Edge Cases - -```typescript -export const robustRunner: Runner = async (ctx, input) => { - "use runner"; - - if (!input?.url) { - return { - name: "robust", - status: "fail", - errorMessage: "URL is required", - }; - } - - // Continue with logic -}; -``` -
+ export const validatedRunner: Runner> = async (ctx, input) => { + "use runner"; + // input is validated + }; + ``` + + ### 3. Return Meaningful Details + + ```typescript + return { + name: "check", + status: "pass", + details: { + // Include useful information + title: "Page Title", + url: input.url, + timestamp: Date.now(), + }, + }; + ``` + + ### 4. Use Logging + Log important events: + + ```typescript + ctx.log("Starting check", { url: input.url }); + ctx.log("Check complete", { result: "pass", duration: 1234 }); + ``` + + ### 5. Handle Edge Cases + + ```typescript + export const robustRunner: Runner = async (ctx, input) => { + "use runner"; + + if (!input?.url) { + return { + name: "robust", + status: "fail", + errorMessage: "URL is required", + }; + } + + // Continue with logic + }; + ``` + diff --git a/docs/getting-started.mdx b/docs/getting-started.mdx index 64dddac..37136c5 100644 --- a/docs/getting-started.mdx +++ b/docs/getting-started.mdx @@ -78,12 +78,13 @@ export const exampleTitleTestOutputSchema = ExampleTitleOutputSchema; ``` Key points: -- The `"use runner"` directive marks this function as a runner -- Use `withPlaywright()` to get browser access -- Return a `RunnerResult` with `name`, `status`, and optional `details` -- **Input schemas** validate input at runtime -- **Output schemas** provide TypeScript typing for the `details` field -- Export schemas following naming conventions for automatic discovery + +* The `"use runner"` directive marks this function as a runner +* Use `withPlaywright()` to get browser access +* Return a `RunnerResult` with `name`, `status`, and optional `details` +* **Input schemas** validate input at runtime +* **Output schemas** provide TypeScript typing for the `details` field +* Export schemas following naming conventions for automatic discovery ## Running Locally @@ -96,6 +97,7 @@ npx runners run \ ``` The CLI will: + 1. Discover runners from `src/**/*.ts` and `runners/**/*.ts` 2. Execute the specified runner(s) 3. Print formatted results @@ -269,8 +271,7 @@ export const apiCheck: Runner = async (ctx, input) => { ## Next Steps -- Read [Writing Runners](/docs/frameworks/nitro/writing-runners) for detailed runner authoring guide -- Check [CLI Usage](/docs/reference/cli) for advanced CLI features -- Explore [Orchestration](/docs/frameworks/nitro/orchestration) for multi-region execution -- See [Examples](/examples/) for complete working examples - +* Read [Writing Runners](/docs/frameworks/nitro/writing-runners) for detailed runner authoring guide +* Check [CLI Usage](/docs/reference/cli) for advanced CLI features +* Explore [Orchestration](/docs/frameworks/nitro/orchestration) for multi-region execution +* See [Examples](/examples/) for complete working examples diff --git a/docs/introduction.mdx b/docs/introduction.mdx index 3a73a6f..1dfa699 100644 --- a/docs/introduction.mdx +++ b/docs/introduction.mdx @@ -8,19 +8,19 @@ lastModified: 2025-11-26 Runners is an open-source SDK that transforms TypeScript functions into executable, distributable test functions. Built for modern development teams, it provides a unified solution for: -- Writing simple test functions -- Running tests locally or in CI -- Distributing tests across multiple regions -- Executing tests via HTTP API -- Orchestrating multi-region test runs +* Writing simple test functions +* Running tests locally or in CI +* Distributing tests across multiple regions +* Executing tests via HTTP API +* Orchestrating multi-region test runs Gone are the days of: -- Complex test infrastructure setup -- Worrying about browser management -- Manual container orchestration -- Region-specific deployment complexity -- Inconsistent test execution environments +* Complex test infrastructure setup +* Worrying about browser management +* Manual container orchestration +* Region-specific deployment complexity +* Inconsistent test execution environments ## Core Principles @@ -28,39 +28,39 @@ Gone are the days of: Runners is built with developer experience in mind: -- Write simple async functions with `"use runner"` directive -- TypeScript-first with full type safety -- Zod schemas for runtime validation -- Zero configuration for local execution -- Framework integrations for seamless deployment +* Write simple async functions with `"use runner"` directive +* TypeScript-first with full type safety +* Zod schemas for runtime validation +* Zero configuration for local execution +* Framework integrations for seamless deployment ### 2. Run Anywhere The same runner function can run in multiple environments: -- **Locally**: Via CLI for development and debugging -- **CI/CD**: Integrated into your build pipeline -- **HTTP API**: Exposed as an API endpoint -- **Distributed**: Orchestrated across multiple regions globally +* **Locally**: Via CLI for development and debugging +* **CI/CD**: Integrated into your build pipeline +* **HTTP API**: Exposed as an API endpoint +* **Distributed**: Orchestrated across multiple regions globally ### 3. Performance & Reliability Built with production in mind: -- Schema pre-extraction for fast startup -- Parallel execution with concurrency control -- Automatic error handling and timeout protection -- Normalized results across all execution modes -- Browser reuse for Playwright-based runners +* Schema pre-extraction for fast startup +* Parallel execution with concurrency control +* Automatic error handling and timeout protection +* Normalized results across all execution modes +* Browser reuse for Playwright-based runners ### 4. Open Source Building in public for transparency and collaboration: -- Inspect and understand the execution engine -- Contribute improvements and fixes -- Self-host for complete control -- Trust through transparency +* Inspect and understand the execution engine +* Contribute improvements and fixes +* Self-host for complete control +* Trust through transparency ## Get Started @@ -73,4 +73,3 @@ Ready to start running functions anywhere? Choose your path: - diff --git a/docs/oss/contributing.mdx b/docs/oss/contributing.mdx index e6d3cbd..ae0a38c 100644 --- a/docs/oss/contributing.mdx +++ b/docs/oss/contributing.mdx @@ -10,9 +10,9 @@ Thank you for your interest in contributing to Runners! This guide will help you ### Prerequisites -- Node.js 18+ (or 20+, 22+, 24+) -- pnpm 10.20.0+ -- Rust (for schema-extractor and swc-plugin) +* Node.js 18+ (or 20+, 22+, 24+) +* pnpm 10.20.0+ +* Rust (for schema-extractor and swc-plugin) ### Clone and Install @@ -89,17 +89,17 @@ pnpm lint --fix ### TypeScript -- Use TypeScript strict mode -- Prefer explicit types over `any` -- Use Zod for runtime validation -- Export types from `types.ts` files +* Use TypeScript strict mode +* Prefer explicit types over `any` +* Use Zod for runtime validation +* Export types from `types.ts` files ### Naming Conventions -- **Files**: `kebab-case.ts` -- **Functions**: `camelCase` -- **Types**: `PascalCase` -- **Constants**: `UPPER_SNAKE_CASE` +* **Files**: `kebab-case.ts` +* **Functions**: `camelCase` +* **Types**: `PascalCase` +* **Constants**: `UPPER_SNAKE_CASE` ## Adding Features @@ -111,10 +111,10 @@ git checkout -b feature/your-feature-name ### 2. Make Changes -- Write code following project conventions -- Add tests for new functionality -- Update documentation -- Update types if needed +* Write code following project conventions +* Add tests for new functionality +* Update documentation +* Update types if needed ### 3. Test Your Changes @@ -142,10 +142,10 @@ git commit -m "docs: update documentation" ### 5. Create Pull Request -- Write a clear description -- Reference related issues -- Include screenshots if UI changes -- Ensure CI passes +* Write a clear description +* Reference related issues +* Include screenshots if UI changes +* Ensure CI passes ## Writing Tests @@ -303,16 +303,16 @@ pnpm --filter runners publish ## Getting Help -- **Issues**: Open an issue on GitHub -- **Discussions**: Use GitHub Discussions -- **Discord**: Join our Discord server (if available) +* **Issues**: Open an issue on GitHub +* **Discussions**: Use GitHub Discussions +* **Discord**: Join our Discord server (if available) ## Code of Conduct -- Be respectful and inclusive -- Welcome newcomers -- Focus on constructive feedback -- Follow project conventions +* Be respectful and inclusive +* Welcome newcomers +* Focus on constructive feedback +* Follow project conventions ## License @@ -320,7 +320,6 @@ By contributing, you agree that your contributions will be licensed under the MI ## See Also -- [API Reference](/docs/reference/api) - API documentation -- [Getting Started](/docs/getting-started) - Quick start guide -- [Writing Runners](/docs/frameworks/nitro/writing-runners) - Runner authoring guide - +* [API Reference](/docs/reference/api) - API documentation +* [Getting Started](/docs/getting-started) - Quick start guide +* [Writing Runners](/docs/frameworks/nitro/writing-runners) - Runner authoring guide diff --git a/docs/oss/license.mdx b/docs/oss/license.mdx index 6de853a..edd00d7 100644 --- a/docs/oss/license.mdx +++ b/docs/oss/license.mdx @@ -25,4 +25,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/docs/reference/api.mdx b/docs/reference/api.mdx index fceed3e..58bccbd 100644 --- a/docs/reference/api.mdx +++ b/docs/reference/api.mdx @@ -4,8 +4,6 @@ description: Complete API documentation for the Runners SDK lastModified: 2025-11-26 --- - - ## Core Types ### Runner @@ -20,8 +18,9 @@ type Runner = ( A runner function that accepts context and optional input, returns a result. **Type Parameters:** -- `TInput`: Input type (usually inferred from Zod schema) - validated at runtime -- `TOutput`: Output details type (usually inferred from Zod schema) - provides TypeScript typing for `details` field + +* `TInput`: Input type (usually inferred from Zod schema) - validated at runtime +* `TOutput`: Output details type (usually inferred from Zod schema) - provides TypeScript typing for `details` field **Example with both schemas:** @@ -58,9 +57,10 @@ type RunnerContext = { Context provided to all runners. **Properties:** -- `region?: string` - Region identifier (e.g., "us-east-1") -- `runId?: string` - Unique run identifier -- `log(message, meta?)` - Logging function + +* `region?: string` - Region identifier (e.g., "us-east-1") +* `runId?: string` - Unique run identifier +* `log(message, meta?)` - Logging function ### RunnerResult @@ -77,11 +77,12 @@ type RunnerResult> = { Result returned by runners. **Properties:** -- `name: string` - Unique runner identifier -- `status: "pass" | "fail" | "error"` - Execution status -- `details?: TDetails` - Optional custom data -- `errorMessage?: string` - Error message if status is "error" -- `durationMs?: number` - Execution duration in milliseconds + +* `name: string` - Unique runner identifier +* `status: "pass" | "fail" | "error"` - Execution status +* `details?: TDetails` - Optional custom data +* `errorMessage?: string` - Error message if status is "error" +* `durationMs?: number` - Execution duration in milliseconds ### RunStatus @@ -109,12 +110,14 @@ const result = await runRunners({ ``` **Parameters:** -- `runners: Runner[]` - Array of runner functions to execute -- `region?: string` - Optional region identifier -- `runId?: string` - Optional run identifier -- `timeout?: number` - Optional timeout in milliseconds + +* `runners: Runner[]` - Array of runner functions to execute +* `region?: string` - Optional region identifier +* `runId?: string` - Optional run identifier +* `timeout?: number` - Optional timeout in milliseconds **Returns:** + ```typescript type RunRunnersResult = { region?: string; @@ -124,6 +127,7 @@ type RunRunnersResult = { ``` **Example:** + ```typescript import { runRunners } from "runners"; import { cookieBannerTest } from "./runners"; @@ -152,12 +156,14 @@ const handler = createHttpRunner({ ``` **Parameters:** -- `runners: Record` - Object mapping runner names to functions -- `region?: string` - Optional region identifier + +* `runners: Record` - Object mapping runner names to functions +* `region?: string` - Optional region identifier **Returns:** HTTP request handler (framework-agnostic) **Example:** + ```typescript import { createHttpRunner } from "runners/http"; import * as runners from "./runners"; @@ -175,6 +181,7 @@ export default createHttpRunner({ Execute one or more runners. **Request:** + ```json { "url": "https://example.com", @@ -190,6 +197,7 @@ Execute one or more runners. ``` **Response:** + ```json { "region": "us-east-1", @@ -210,6 +218,7 @@ Execute one or more runners. Get information about available runners. **Response:** + ```json { "runners": ["runner1", "runner2"], @@ -266,6 +275,7 @@ PLAYWRIGHT_RUNNERS='{ Submit a new run request. **Request:** + ```json { "runners": [ @@ -284,17 +294,19 @@ Submit a new run request. ``` **Response:** + ```json { "runId": "generated-run-id" } ``` -#### GET /api/orchestrator/{runId}/status +#### GET /api/orchestrator/\{runId}/status Get run status. **Response:** + ```json { "runId": "run-id", @@ -307,11 +319,12 @@ Get run status. } ``` -#### GET /api/orchestrator/{runId} +#### GET /api/orchestrator/\{runId} Get run results. **Response:** + ```json { "runId": "run-id", @@ -416,13 +429,15 @@ npx runners run [options] [runner-names...] ``` **Options:** -- `--url ` - URL to pass to runners -- `--config ` - Path to config file -- `--region ` - Region identifier -- `--timeout ` - Timeout in milliseconds -- `--help` - Show help + +* `--url ` - URL to pass to runners +* `--config ` - Path to config file +* `--region ` - Region identifier +* `--timeout ` - Timeout in milliseconds +* `--help` - Show help **Examples:** + ```bash # Run specific runners npx runners run --url https://example.com runner1 runner2 @@ -460,10 +475,12 @@ const { page, url, region, log } = await withPlaywright(ctx, url); ``` **Parameters:** -- `ctx: RunnerContext` - Runner context -- `url: string` - URL to navigate to + +* `ctx: RunnerContext` - Runner context +* `url: string` - URL to navigate to **Returns:** + ```typescript type PlaywrightContext = { page: Page; @@ -476,6 +493,7 @@ type PlaywrightContext = { ``` **Example:** + ```typescript export const browserRunner: Runner = async (ctx, input) => { "use runner"; @@ -509,9 +527,10 @@ export default defineConfig({ ``` Automatically: -- Discovers runners from `src/**/*.ts` and `runners/**/*.ts` -- Exposes `/api/runner/execute` endpoint -- Serves OpenAPI docs at `/api/runner/docs` + +* Discovers runners from `src/**/*.ts` and `runners/**/*.ts` +* Exposes `/api/runner/execute` endpoint +* Serves OpenAPI docs at `/api/runner/docs` ### Nitro Orchestrator Module @@ -588,4 +607,3 @@ import type { RunStatus, } from "runners"; ``` - diff --git a/docs/reference/cli.mdx b/docs/reference/cli.mdx index e2ebe8a..b4db84b 100644 --- a/docs/reference/cli.mdx +++ b/docs/reference/cli.mdx @@ -41,6 +41,7 @@ npx runners run --config runners.config.ts Execute runners locally. **Syntax:** + ```bash npx runners run [options] [runner-names...] ``` @@ -114,8 +115,8 @@ export default defineConfig({ The CLI automatically discovers runners from: -- `src/**/*.ts` -- `runners/**/*.ts` +* `src/**/*.ts` +* `runners/**/*.ts` Runners must have the `"use runner"` directive to be discovered. @@ -191,9 +192,9 @@ export const runner: Runner = async (ctx) => { ## Exit Codes -- `0` - All runners passed -- `1` - One or more runners failed or errored -- `2` - Invalid command or configuration +* `0` - All runners passed +* `1` - One or more runners failed or errored +* `2` - Invalid command or configuration ## Advanced Usage @@ -313,7 +314,6 @@ pnpm exec tsc --noEmit ## See Also -- [Writing Runners](/docs/frameworks/nitro/writing-runners) - Guide to creating runners -- [Getting Started](/docs/getting-started) - Quick start guide -- [API Reference](/docs/reference/api) - Complete API documentation - +* [Writing Runners](/docs/frameworks/nitro/writing-runners) - Guide to creating runners +* [Getting Started](/docs/getting-started) - Quick start guide +* [API Reference](/docs/reference/api) - Complete API documentation diff --git a/docs/reference/index.mdx b/docs/reference/index.mdx index 31e54f3..73ab339 100644 --- a/docs/reference/index.mdx +++ b/docs/reference/index.mdx @@ -8,6 +8,5 @@ lastModified: 2025-11-26 Complete documentation for the Runners SDK: -- [API Reference](./api) - Complete API documentation -- [CLI Reference](./cli) - Command-line interface documentation - +* [API Reference](./api) - Complete API documentation +* [CLI Reference](./cli) - Command-line interface documentation diff --git a/internals/bundle-analysis-action/README.md b/internals/bundle-analysis-action/README.md deleted file mode 100644 index d73f02a..0000000 --- a/internals/bundle-analysis-action/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# Bundle Analysis Action - -A GitHub Action for analyzing bundle size differences using rsdoctor data. - -## Features - -- Analyzes bundle differences between base and current branches -- Generates markdown reports with detailed bundle statistics -- Automatically comments on pull requests with analysis results -- Supports sticky comments (updates existing comments) - -## Usage - -```yaml -- uses: ./internals/bundle-analysis-action - with: - base_dir: .bundle-base # Directory with base branch data - current_dir: . # Directory with current branch data - github_token: ${{ secrets.GITHUB_TOKEN }} - fail_on_increase: false # Fail action on >10% increases -``` - -## Inputs - -| Input | Description | Default | Required | -|-------|-------------|---------|----------| -| `base_dir` | Directory containing base branch rsdoctor data | `.bundle-base` | No | -| `current_dir` | Directory containing current branch rsdoctor data | `.` | No | -| `github_token` | GitHub token for posting comments | - | Yes | -| `header` | Header identifier for sticky comments | `bundle-analysis` | No | -| `pr_number` | Pull request number (auto-detected) | - | No | -| `skip_comment` | Skip posting comment | `false` | No | -| `fail_on_increase` | Fail if bundle increases >10% | `false` | No | - -## Outputs - -| Output | Description | -|--------|-------------| -| `report_path` | Path to the generated bundle diff report | -| `has_changes` | Whether bundle changes were detected | -| `total_diff_percent` | Total bundle size change percentage | - -## Development - -```bash -# Install dependencies -pnpm install - -# Build action -pnpm build - -# Type check -pnpm check-types - -# Lint -pnpm lint -``` - diff --git a/internals/bundle-analysis-action/action.yml b/internals/bundle-analysis-action/action.yml deleted file mode 100644 index eddb03e..0000000 --- a/internals/bundle-analysis-action/action.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: "Bundle Analysis" -description: "Analyze bundle size differences using rsdoctor and comment on PRs" -author: "c15t" - -inputs: - base_dir: - description: "Directory containing base branch rsdoctor data" - default: ".bundle-base" - required: false - current_dir: - description: "Directory containing current branch rsdoctor data" - default: "." - required: false - github_token: - description: "GitHub token for posting comments" - required: true - header: - description: "Header to identify the comment (for sticky comments)" - default: "bundle-analysis" - required: false - pr_number: - description: "Pull request number (auto-detected if not provided)" - required: false - skip_comment: - description: "Skip posting comment if true" - default: "false" - required: false - packages_dir: - description: "Directory containing packages to analyze" - default: "packages" - required: false - fail_on_increase: - description: "Fail the action if bundle size increases significantly" - default: "false" - required: false - threshold: - description: "Percentage threshold for significant bundle size increase" - default: "10" - required: false - -outputs: - report_path: - description: "Path to the generated bundle diff report" - has_changes: - description: "Whether bundle changes were detected" - total_diff_percent: - description: "Total bundle size change percentage" - -runs: - using: "composite" - steps: - - name: Run bundle analysis - shell: bash - working-directory: ${{ github.action_path }} - run: pnpm -w exec tsx "$GITHUB_ACTION_PATH/src/main.ts" - env: - INPUT_BASE_DIR: ${{ inputs.base_dir }} - INPUT_CURRENT_DIR: ${{ inputs.current_dir }} - INPUT_GITHUB_TOKEN: ${{ inputs.github_token }} - INPUT_HEADER: ${{ inputs.header }} - INPUT_PR_NUMBER: ${{ inputs.pr_number }} - INPUT_SKIP_COMMENT: ${{ inputs.skip_comment }} - INPUT_FAIL_ON_INCREASE: ${{ inputs.fail_on_increase }} - INPUT_PACKAGES_DIR: ${{ inputs.packages_dir }} - INPUT_THRESHOLD: ${{ inputs.threshold }} - diff --git a/internals/bundle-analysis-action/package.json b/internals/bundle-analysis-action/package.json deleted file mode 100644 index 978ab24..0000000 --- a/internals/bundle-analysis-action/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "@c15t/bundle-analysis-action", - "version": "0.1.0", - "private": true, - "description": "Analyze bundle size differences using rsdoctor and comment on PRs", - "author": "c15t", - "license": "MIT", - "main": "dist/index.js", - "engines": { - "node": ">=18" - }, - "scripts": { - "build": "ncc build src/main.ts -o dist -m", - "check-types": "tsc --noEmit", - "test": "vitest run", - "fmt": "pnpm biome format --write . && pnpm biome check --formatter-enabled=false --linter-enabled=false --write", - "lint": "pnpm biome lint ./src" - }, - "dependencies": { - "@actions/core": "^1.11.1", - "@actions/github": "^6.0.1", - "@actions/glob": "^0.5.0" - }, - "devDependencies": { - "@types/node": "^24.3.0", - "@vercel/ncc": "^0.38.3", - "tsx": "^4.19.3", - "vitest": "^4.0.8" - } -} diff --git a/internals/bundle-analysis-action/src/analyze/bundle-analysis.test.ts b/internals/bundle-analysis-action/src/analyze/bundle-analysis.test.ts deleted file mode 100644 index c79b3ff..0000000 --- a/internals/bundle-analysis-action/src/analyze/bundle-analysis.test.ts +++ /dev/null @@ -1,611 +0,0 @@ -import { - existsSync, - promises as fs, - type PathLike, - type PathOrFileDescriptor, - readdirSync, - readFileSync, - statSync, - writeFileSync, -} from "node:fs"; -import { beforeEach, describe, expect, it, vi } from "vitest"; -import { - analyzeBundles, - type BundleStats, - calculateTotalDiffPercent, - compareBundles, - extractBundleSizes, - formatBytes, - generateMarkdownReport, - type PackageBundleData, - writeReport, -} from "./bundle-analysis"; - -// Mock fs module -vi.mock("node:fs", async () => { - const actual = await vi.importActual("node:fs"); - return { - ...actual, - existsSync: vi.fn(), - readdirSync: vi.fn(), - readFileSync: vi.fn(), - statSync: vi.fn(), - writeFileSync: vi.fn(), - promises: { - readdir: vi.fn(), - }, - }; -}); - -describe("bundle-analysis", () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - describe("extractBundleSizes", () => { - it("should extract bundles from chunks", () => { - const mockData = { - data: { - chunkGraph: { - chunks: [ - { - name: "main.js", - id: "chunk-1", - size: 1024, - assets: ["asset-1"], - }, - { - name: "vendor.js", - id: "chunk-2", - size: 2048, - }, - ], - assets: [ - { - id: "asset-1", - gzipSize: 512, - }, - ], - }, - }, - }; - - vi.mocked(readFileSync).mockReturnValue(JSON.stringify(mockData)); - - const result = extractBundleSizes("/test/rsdoctor-data.json"); - - expect(result).toHaveLength(2); - expect(result[0]).toEqual({ - name: "main.js", - path: "/test/rsdoctor-data.json", - size: 1024, - gzipSize: 512, - }); - expect(result[1]).toEqual({ - name: "vendor.js", - path: "/test/rsdoctor-data.json", - size: 2048, - gzipSize: undefined, - }); - }); - - it("should fallback to assets if chunks not available", () => { - const mockData = { - data: { - chunkGraph: { - assets: [ - { - id: "asset-1", - path: "main.js", - size: 1024, - gzipSize: 512, - }, - ], - }, - }, - }; - - vi.mocked(readFileSync).mockReturnValue(JSON.stringify(mockData)); - - const result = extractBundleSizes("/test/rsdoctor-data.json"); - - expect(result).toHaveLength(1); - expect(result[0]).toEqual({ - name: "main.js", - path: "/test/rsdoctor-data.json", - size: 1024, - gzipSize: 512, - }); - }); - - it("should fallback to modules if chunks and assets not available", () => { - const mockData = { - data: { - modules: [ - { - chunks: ["chunk-1"], - size: { - transformedSize: 512, - }, - }, - { - chunks: ["chunk-1", "chunk-2"], - size: { - sourceSize: 256, - }, - }, - { - chunks: ["chunk-2"], - size: { - transformedSize: 128, - }, - }, - ], - }, - }; - - vi.mocked(readFileSync).mockReturnValue(JSON.stringify(mockData)); - - const result = extractBundleSizes("/test/rsdoctor-data.json"); - - expect(result).toHaveLength(2); - expect(result.find((b) => b.name === "chunk-1")).toEqual({ - name: "chunk-1", - path: "/test/rsdoctor-data.json", - size: 768, // 512 + 256 - }); - expect(result.find((b) => b.name === "chunk-2")).toEqual({ - name: "chunk-2", - path: "/test/rsdoctor-data.json", - size: 384, // 256 + 128 - }); - }); - - it("should return empty array on error", () => { - vi.mocked(readFileSync).mockImplementation(() => { - throw new Error("File not found"); - }); - - const consoleSpy = vi - .spyOn(console, "error") - .mockImplementation(() => {}); - - const result = extractBundleSizes("/test/invalid.json"); - - expect(result).toEqual([]); - expect(consoleSpy).toHaveBeenCalled(); - - consoleSpy.mockRestore(); - }); - }); - - describe("compareBundles", () => { - const baseBundles: BundleStats[] = [ - { name: "main.js", path: "/base", size: 1000 }, - { name: "vendor.js", path: "/base", size: 2000 }, - { name: "removed.js", path: "/base", size: 500 }, - ]; - - const currentBundles: BundleStats[] = [ - { name: "main.js", path: "/current", size: 1100 }, // changed - { name: "vendor.js", path: "/current", size: 2000 }, // unchanged - { name: "new.js", path: "/current", size: 300 }, // added - ]; - - it("should identify added bundles", () => { - const result = compareBundles(baseBundles, currentBundles); - expect(result.added).toHaveLength(1); - expect(result.added[0].name).toBe("new.js"); - }); - - it("should identify removed bundles", () => { - const result = compareBundles(baseBundles, currentBundles); - expect(result.removed).toHaveLength(1); - expect(result.removed[0].name).toBe("removed.js"); - }); - - it("should identify changed bundles", () => { - const result = compareBundles(baseBundles, currentBundles); - expect(result.changed).toHaveLength(1); - expect(result.changed[0]).toEqual({ - name: "main.js", - baseSize: 1000, - currentSize: 1100, - diff: 100, - diffPercent: 10, - }); - }); - - it("should handle zero base size", () => { - const base: BundleStats[] = [{ name: "test.js", path: "/base", size: 0 }]; - const current: BundleStats[] = [ - { name: "test.js", path: "/current", size: 100 }, - ]; - - const result = compareBundles(base, current); - expect(result.changed[0].diffPercent).toBe(0); - }); - - it("should return empty arrays when bundles are identical", () => { - const bundles: BundleStats[] = [ - { name: "main.js", path: "/base", size: 1000 }, - ]; - const result = compareBundles(bundles, bundles); - expect(result.added).toHaveLength(0); - expect(result.removed).toHaveLength(0); - expect(result.changed).toHaveLength(0); - }); - }); - - describe("formatBytes", () => { - it("should format zero bytes correctly", () => { - expect(formatBytes(0)).toBe("0 B"); - }); - - it("should format bytes correctly", () => { - expect(formatBytes(1)).toBe("1.00 B"); - expect(formatBytes(500)).toBe("500.00 B"); - expect(formatBytes(1023)).toBe("1023.00 B"); - }); - - it("should format kilobytes correctly", () => { - expect(formatBytes(1024)).toBe("1.00 KB"); - expect(formatBytes(1536)).toBe("1.50 KB"); - expect(formatBytes(5120)).toBe("5.00 KB"); - }); - - it("should format megabytes correctly", () => { - expect(formatBytes(1024 * 1024)).toBe("1.00 MB"); - expect(formatBytes(2.5 * 1024 * 1024)).toBe("2.50 MB"); - }); - - it("should format gigabytes correctly", () => { - expect(formatBytes(1024 * 1024 * 1024)).toBe("1.00 GB"); - }); - - it("should handle negative bytes", () => { - expect(formatBytes(-1024)).toBe("-1.00 KB"); - expect(formatBytes(-512)).toBe("-512.00 B"); - }); - }); - - describe("generateMarkdownReport", () => { - it("should generate empty report when no packages", () => { - const result = generateMarkdownReport([]); - expect(result).toBe( - "# ๐Ÿ“ฆ Bundle Size Analysis\n\nNo bundle changes detected.\n" - ); - }); - - it("should generate summary table", () => { - const packages: PackageBundleData[] = [ - { - packageName: "test-package", - baseBundles: [], - currentBundles: [], - diffs: { - added: [], - removed: [], - changed: [], - }, - totalBaseSize: 1024, - totalCurrentSize: 1100, - totalDiff: 100, - totalDiffPercent: 10, - }, - ]; - - const result = generateMarkdownReport(packages); - expect(result).toContain("## Summary"); - expect(result).toContain("test-package"); - expect(result).toContain("1.00 KB"); - expect(result).toContain("10.00%"); - expect(result).toContain( - "*This analysis was generated automatically by [rsdoctor](https://rsdoctor.rs/).*" - ); - }); - - it("should include added bundles section", () => { - const packages: PackageBundleData[] = [ - { - packageName: "test-package", - baseBundles: [], - currentBundles: [], - diffs: { - added: [{ name: "new.js", path: "/test", size: 500 }], - removed: [], - changed: [], - }, - totalBaseSize: 0, - totalCurrentSize: 500, - totalDiff: 500, - totalDiffPercent: 0, - }, - ]; - - const result = generateMarkdownReport(packages); - expect(result).toContain("### โž• Added Bundles"); - expect(result).toContain("new.js"); - expect(result).toContain("500.00 B"); - }); - - it("should include removed bundles section", () => { - const packages: PackageBundleData[] = [ - { - packageName: "test-package", - baseBundles: [], - currentBundles: [], - diffs: { - added: [], - removed: [{ name: "old.js", path: "/test", size: 300 }], - changed: [], - }, - totalBaseSize: 300, - totalCurrentSize: 0, - totalDiff: -300, - totalDiffPercent: -100, - }, - ]; - - const result = generateMarkdownReport(packages); - expect(result).toContain("### โž– Removed Bundles"); - expect(result).toContain("old.js"); - expect(result).toContain("300.00 B"); - }); - - it("should include changed bundles section", () => { - const packages: PackageBundleData[] = [ - { - packageName: "test-package", - baseBundles: [], - currentBundles: [], - diffs: { - added: [], - removed: [], - changed: [ - { - name: "main.js", - baseSize: 1024, - currentSize: 1200, - diff: 200, - diffPercent: 20, - }, - ], - }, - totalBaseSize: 1024, - totalCurrentSize: 1200, - totalDiff: 200, - totalDiffPercent: 20, - }, - ]; - - const result = generateMarkdownReport(packages); - expect(result).toContain("### ๐Ÿ“Š Changed Bundles"); - expect(result).toContain("main.js"); - expect(result).toContain("1.00 KB"); - expect(result).toContain("20.00%"); - }); - - it("should skip packages with no changes", () => { - const packages: PackageBundleData[] = [ - { - packageName: "no-changes", - baseBundles: [], - currentBundles: [], - diffs: { - added: [], - removed: [], - changed: [], - }, - totalBaseSize: 1000, - totalCurrentSize: 1000, - totalDiff: 0, - totalDiffPercent: 0, - }, - { - packageName: "with-changes", - baseBundles: [], - currentBundles: [], - diffs: { - added: [{ name: "new.js", path: "/test", size: 500 }], - removed: [], - changed: [], - }, - totalBaseSize: 0, - totalCurrentSize: 500, - totalDiff: 500, - totalDiffPercent: 0, - }, - ]; - - const result = generateMarkdownReport(packages); - expect(result).toContain("no-changes"); // Should appear in summary - // no-changes package should not have a details section since it has no diffs - // Check that there's no details section specifically for no-changes - const noChangesInDetails = result.match( - /
[\s\S]*?no-changes[\s\S]*?<\/details>/ - ); - expect(noChangesInDetails).toBeNull(); // Should not find no-changes inside details - // with-changes package should have a details section - expect(result).toContain("
"); // Should have details section for with-changes - expect(result).toContain("with-changes"); // Should appear in details - }); - }); - - describe("analyzeBundles", () => { - it("should return empty array when packages directory does not exist", async () => { - vi.mocked(existsSync).mockReturnValue(false); - const result = await analyzeBundles("/base", "/current", "/nonexistent"); - expect(result).toEqual([]); - }); - - it("should analyze packages", async () => { - const baseData = { - data: { - chunkGraph: { - chunks: [{ name: "main.js", size: 1000 }], - }, - }, - }; - - const currentData = { - data: { - chunkGraph: { - chunks: [{ name: "main.js", size: 1100 }], - }, - }, - }; - - vi.mocked(existsSync).mockImplementation((path: PathLike) => { - return String(path) === "packages" || String(path).includes("dist"); - }); - - vi.mocked(readdirSync).mockReturnValue([ - "package1", - "package2", - ] as unknown as ReturnType); - - vi.mocked(statSync).mockReturnValue({ - isDirectory: () => true, - } as unknown as ReturnType); - - vi.mocked(fs.readdir).mockImplementation(async (path: PathLike) => { - if (String(path).includes("dist")) { - return [ - { - name: "rsdoctor-data.json", - isDirectory: () => false, - isSymbolicLink: () => false, - isFile: () => true, - }, - ] as unknown as Awaited>; - } - return [] as unknown as Awaited>; - }); - - vi.mocked(readFileSync).mockImplementation( - (path: PathOrFileDescriptor) => { - if (String(path).includes("base")) { - return JSON.stringify(baseData); - } - return JSON.stringify(currentData); - } - ); - - const result = await analyzeBundles("/base", "/current", "packages"); - - expect(result).toHaveLength(2); - expect(result[0].packageName).toBe("package1"); - expect(result[1].packageName).toBe("package2"); - }); - - it("should skip packages with zero size", async () => { - vi.mocked(existsSync).mockReturnValue(true); - vi.mocked(readdirSync).mockReturnValue([ - "package1", - ] as unknown as ReturnType); - vi.mocked(statSync).mockReturnValue({ - isDirectory: () => true, - } as unknown as ReturnType); - - vi.mocked(fs.readdir).mockResolvedValue( - [] as unknown as Awaited> - ); - - const result = await analyzeBundles("/base", "/current", "packages"); - expect(result).toEqual([]); - }); - }); - - describe("writeReport", () => { - it("should write report to file", () => { - const packages: PackageBundleData[] = [ - { - packageName: "test-package", - baseBundles: [], - currentBundles: [], - diffs: { - added: [], - removed: [], - changed: [], - }, - totalBaseSize: 1000, - totalCurrentSize: 1100, - totalDiff: 100, - totalDiffPercent: 10, - }, - ]; - - writeReport(packages, "/test/output.md"); - - expect(writeFileSync).toHaveBeenCalledWith( - "/test/output.md", - expect.stringContaining("# ๐Ÿ“ฆ Bundle Size Analysis"), - "utf-8" - ); - }); - - it("should throw error on file write failure", () => { - vi.mocked(writeFileSync).mockImplementation(() => { - throw new Error("Permission denied"); - }); - - const packages: PackageBundleData[] = []; - - expect(() => { - writeReport(packages, "/test/output.md"); - }).toThrow("Failed to write bundle analysis report"); - }); - }); - - describe("calculateTotalDiffPercent", () => { - it("should return 0 for empty packages", () => { - expect(calculateTotalDiffPercent([])).toBe(0); - }); - - it("should calculate total diff percent correctly", () => { - const packages: PackageBundleData[] = [ - { - packageName: "pkg1", - baseBundles: [], - currentBundles: [], - diffs: { added: [], removed: [], changed: [] }, - totalBaseSize: 1000, - totalCurrentSize: 1100, - totalDiff: 100, - totalDiffPercent: 10, - }, - { - packageName: "pkg2", - baseBundles: [], - currentBundles: [], - diffs: { added: [], removed: [], changed: [] }, - totalBaseSize: 2000, - totalCurrentSize: 2200, - totalDiff: 200, - totalDiffPercent: 10, - }, - ]; - - // Total: 3000 base, 3300 current, 300 diff = 10% - expect(calculateTotalDiffPercent(packages)).toBe(10); - }); - - it("should handle zero base size", () => { - const packages: PackageBundleData[] = [ - { - packageName: "pkg1", - baseBundles: [], - currentBundles: [], - diffs: { added: [], removed: [], changed: [] }, - totalBaseSize: 0, - totalCurrentSize: 1000, - totalDiff: 1000, - totalDiffPercent: 0, - }, - ]; - - expect(calculateTotalDiffPercent(packages)).toBe(0); - }); - }); -}); diff --git a/internals/bundle-analysis-action/src/analyze/bundle-analysis.ts b/internals/bundle-analysis-action/src/analyze/bundle-analysis.ts deleted file mode 100644 index 5de98f1..0000000 --- a/internals/bundle-analysis-action/src/analyze/bundle-analysis.ts +++ /dev/null @@ -1,400 +0,0 @@ -/** - * @packageDocumentation - * Bundle analysis logic for comparing rsdoctor outputs. - */ -import { - existsSync, - promises as fs, - readdirSync, - readFileSync, - statSync, - writeFileSync, -} from "node:fs"; -import { basename, join } from "node:path"; - -export interface BundleStats { - name: string; - path: string; - size: number; - gzipSize?: number; -} - -export interface PackageBundleData { - packageName: string; - baseBundles: BundleStats[]; - currentBundles: BundleStats[]; - diffs: { - added: BundleStats[]; - removed: BundleStats[]; - changed: Array<{ - name: string; - baseSize: number; - currentSize: number; - diff: number; - diffPercent: number; - }>; - }; - totalBaseSize: number; - totalCurrentSize: number; - totalDiff: number; - totalDiffPercent: number; -} - -async function findRsdoctorDataFiles(dir: string): Promise { - const files: string[] = []; - if (!existsSync(dir)) { - return files; - } - - async function walk(currentDir: string): Promise { - const entries = await fs.readdir(currentDir, { withFileTypes: true }); - const promises: Promise[] = []; - - for (const entry of entries) { - const fullPath = join(currentDir, entry.name); - if (entry.isSymbolicLink()) { - // Skip symlinks to avoid circular dependencies - continue; - } - if (entry.isDirectory()) { - promises.push(walk(fullPath)); - } else if (entry.name === "rsdoctor-data.json") { - files.push(fullPath); - } - } - - await Promise.all(promises); - } - - await walk(dir); - return files; -} - -export function extractBundleSizes(jsonPath: string): BundleStats[] { - try { - const content = readFileSync(jsonPath, "utf-8"); - const data = JSON.parse(content); - const bundles: BundleStats[] = []; - - // Extract bundle information from rsdoctor data structure - if (data?.data?.chunkGraph?.chunks) { - for (const chunk of data.data.chunkGraph.chunks || []) { - const chunkName = chunk.name || chunk.id || "unknown"; - const chunkSize = chunk.size || 0; - - // Try to find corresponding asset for gzip size - let gzipSize: number | undefined; - if (data?.data?.chunkGraph?.assets && chunk.assets) { - for (const assetId of chunk.assets) { - const asset = data.data.chunkGraph.assets.find( - (a: { id: string | number }) => String(a.id) === String(assetId) - ); - if (asset?.gzipSize) { - gzipSize = asset.gzipSize; - break; - } - } - } - - bundles.push({ - name: chunkName, - path: jsonPath, - size: chunkSize, - gzipSize, - }); - } - } - - // Fallback: extract from assets if chunks not available - if (bundles.length === 0 && data?.data?.chunkGraph?.assets) { - for (const asset of data.data.chunkGraph.assets || []) { - bundles.push({ - name: asset.path || asset.id || "unknown", - path: jsonPath, - size: asset.size || 0, - gzipSize: asset.gzipSize, - }); - } - } - - // Last resort: try to get from modules - if (bundles.length === 0 && data?.data?.modules) { - const chunkMap = new Map(); - - for (const module of data.data.modules || []) { - const chunkNames = module.chunks || []; - const size = - module.size?.transformedSize || module.size?.sourceSize || 0; - - for (const chunkName of chunkNames) { - if (!chunkMap.has(chunkName)) { - chunkMap.set(chunkName, { - name: chunkName, - path: jsonPath, - size: 0, - }); - } - const bundle = chunkMap.get(chunkName); - if (bundle) { - bundle.size += size; - } - } - } - - bundles.push(...Array.from(chunkMap.values())); - } - - return bundles; - } catch (error) { - console.error(`Error reading ${jsonPath}:`, error); - return []; - } -} - -export function compareBundles( - baseBundles: BundleStats[], - currentBundles: BundleStats[] -): PackageBundleData["diffs"] { - const baseMap = new Map(baseBundles.map((b) => [b.name, b])); - const currentMap = new Map(currentBundles.map((b) => [b.name, b])); - - const added: BundleStats[] = []; - const removed: BundleStats[] = []; - const changed: Array<{ - name: string; - baseSize: number; - currentSize: number; - diff: number; - diffPercent: number; - }> = []; - - // Find added bundles - for (const [name, bundle] of currentMap) { - if (!baseMap.has(name)) { - added.push(bundle); - } - } - - // Find removed bundles - for (const [name, bundle] of baseMap) { - if (!currentMap.has(name)) { - removed.push(bundle); - } - } - - // Find changed bundles - for (const [name, baseBundle] of baseMap) { - const currentBundle = currentMap.get(name); - if (currentBundle && baseBundle.size !== currentBundle.size) { - const diff = currentBundle.size - baseBundle.size; - const diffPercent = - baseBundle.size > 0 ? (diff / baseBundle.size) * 100 : null; - changed.push({ - name, - baseSize: baseBundle.size, - currentSize: currentBundle.size, - diff, - diffPercent: diffPercent ?? 0, - }); - } - } - - return { added, removed, changed }; -} - -async function analyzePackage( - packageDir: string, - baseDir: string, - currentDir: string -): Promise { - const packageName = basename(packageDir); - const baseDistPath = join(baseDir, packageDir, "dist"); - const currentDistPath = join(currentDir, packageDir, "dist"); - - const baseFiles = await findRsdoctorDataFiles(baseDistPath); - const currentFiles = await findRsdoctorDataFiles(currentDistPath); - - if (baseFiles.length === 0 && currentFiles.length === 0) { - return null; - } - - const baseBundles: BundleStats[] = []; - const currentBundles: BundleStats[] = []; - - for (const file of baseFiles) { - baseBundles.push(...extractBundleSizes(file)); - } - - for (const file of currentFiles) { - currentBundles.push(...extractBundleSizes(file)); - } - - const diffs = compareBundles(baseBundles, currentBundles); - const totalBaseSize = baseBundles.reduce((sum, b) => sum + b.size, 0); - const totalCurrentSize = currentBundles.reduce((sum, b) => sum + b.size, 0); - const totalDiff = totalCurrentSize - totalBaseSize; - const totalDiffPercent = - totalBaseSize > 0 ? (totalDiff / totalBaseSize) * 100 : 0; - - return { - packageName, - baseBundles, - currentBundles, - diffs, - totalBaseSize, - totalCurrentSize, - totalDiff, - totalDiffPercent, - }; -} - -export function formatBytes(bytes: number): string { - if (bytes === 0) return "0 B"; - const k = 1024; - const sizes = ["B", "KB", "MB", "GB"]; - const i = Math.floor(Math.log(Math.abs(bytes)) / Math.log(k)); - return `${(bytes / k ** i).toFixed(2)} ${sizes[i]}`; -} - -function getChangeEmoji(diffPercent: number): string { - if (diffPercent > 5) return "๐Ÿ”ด"; - if (diffPercent > 0) return "๐ŸŸก"; - if (diffPercent < -5) return "๐ŸŸข"; - return "โšช"; -} - -export function generateMarkdownReport(packages: PackageBundleData[]): string { - let markdown = "# ๐Ÿ“ฆ Bundle Size Analysis\n\n"; - - if (packages.length === 0) { - markdown += "No bundle changes detected.\n"; - return markdown; - } - - // Summary table - markdown += "## Summary\n\n"; - markdown += "| Package | Base Size | Current Size | Change | % Change |\n"; - markdown += "|---------|-----------|--------------|--------|----------|\n"; - - for (const pkg of packages) { - const sign = pkg.totalDiff >= 0 ? "+" : ""; - const emoji = getChangeEmoji(pkg.totalDiffPercent); - markdown += `| ${emoji} \`${pkg.packageName}\` | ${formatBytes(pkg.totalBaseSize)} | ${formatBytes(pkg.totalCurrentSize)} | ${sign}${formatBytes(pkg.totalDiff)} | ${sign}${pkg.totalDiffPercent.toFixed(2)}% |\n`; - } - - // Detailed changes per package (collapsible) - for (const pkg of packages) { - if ( - pkg.diffs.added.length === 0 && - pkg.diffs.removed.length === 0 && - pkg.diffs.changed.length === 0 - ) { - continue; - } - - const sign = pkg.totalDiff >= 0 ? "+" : ""; - const emoji = getChangeEmoji(pkg.totalDiffPercent); - const summaryText = `${emoji} \`${pkg.packageName}\`: ${sign}${formatBytes(pkg.totalDiff)} (${sign}${pkg.totalDiffPercent.toFixed(2)}%)`; - - markdown += `\n
\n${summaryText}\n\n`; - - if (pkg.diffs.added.length > 0) { - markdown += "### โž• Added Bundles\n\n"; - for (const bundle of pkg.diffs.added) { - markdown += `- \`${bundle.name}\`: ${formatBytes(bundle.size)}\n`; - } - markdown += "\n"; - } - - if (pkg.diffs.removed.length > 0) { - markdown += "### โž– Removed Bundles\n\n"; - for (const bundle of pkg.diffs.removed) { - markdown += `- \`${bundle.name}\`: ${formatBytes(bundle.size)}\n`; - } - markdown += "\n"; - } - - if (pkg.diffs.changed.length > 0) { - markdown += "### ๐Ÿ“Š Changed Bundles\n\n"; - markdown += "| Bundle | Base Size | Current Size | Change | % Change |\n"; - markdown += "|--------|-----------|--------------|--------|----------|\n"; - for (const change of pkg.diffs.changed) { - const sign = change.diff >= 0 ? "+" : ""; - const emoji = getChangeEmoji(change.diffPercent); - markdown += `| ${emoji} \`${change.name}\` | ${formatBytes(change.baseSize)} | ${formatBytes(change.currentSize)} | ${sign}${formatBytes(change.diff)} | ${sign}${change.diffPercent.toFixed(2)}% |\n`; - } - markdown += "\n"; - } - - markdown += "
\n"; - } - - markdown += - "\n---\n*This analysis was generated automatically by [rsdoctor](https://rsdoctor.rs/).*"; - - return markdown; -} - -/** - * Analyzes bundle differences between base and current branches. - */ -export async function analyzeBundles( - baseDir: string, - currentDir: string, - packagesDir = "packages" -): Promise { - const packages: string[] = []; - - if (existsSync(packagesDir)) { - const entries = readdirSync(packagesDir); - for (const entry of entries) { - const fullPath = join(packagesDir, entry); - if (statSync(fullPath).isDirectory()) { - packages.push(join(packagesDir, entry)); - } - } - } - - const results: PackageBundleData[] = []; - for (const pkg of packages) { - const result = await analyzePackage(pkg, baseDir, currentDir); - if (result && (result.totalBaseSize > 0 || result.totalCurrentSize > 0)) { - results.push(result); - } - } - - return results; -} - -/** - * Writes the bundle analysis report to a file. - */ -export function writeReport( - packages: PackageBundleData[], - outputPath: string -): void { - try { - const report = generateMarkdownReport(packages); - writeFileSync(outputPath, report, "utf-8"); - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - const errorStack = - error instanceof Error && error.stack ? error.stack : errorMessage; - throw new Error( - `Failed to write bundle analysis report to ${outputPath}: ${errorStack}` - ); - } -} - -/** - * Calculates total bundle size change percentage across all packages. - */ -export function calculateTotalDiffPercent( - packages: PackageBundleData[] -): number { - if (packages.length === 0) return 0; - const totalBase = packages.reduce((sum, p) => sum + p.totalBaseSize, 0); - const totalCurrent = packages.reduce((sum, p) => sum + p.totalCurrentSize, 0); - const totalDiff = totalCurrent - totalBase; - return totalBase > 0 ? (totalDiff / totalBase) * 100 : 0; -} diff --git a/internals/bundle-analysis-action/src/config/inputs.ts b/internals/bundle-analysis-action/src/config/inputs.ts deleted file mode 100644 index 86619a7..0000000 --- a/internals/bundle-analysis-action/src/config/inputs.ts +++ /dev/null @@ -1,73 +0,0 @@ -/** - * @packageDocumentation - * Configuration and input resolution for the bundle analysis GitHub Action. - */ -import * as core from "@actions/core"; -import { context } from "@actions/github"; - -/** - * Directory containing base branch rsdoctor data files - */ -export const baseDir = - core.getInput("base_dir", { required: false }) || ".bundle-base"; - -/** - * Directory containing current branch rsdoctor data files - */ -export const currentDir = - core.getInput("current_dir", { required: false }) || "."; - -/** - * GitHub token for API requests - */ -export const githubToken = core.getInput("github_token", { required: true }); - -/** - * Header identifier for sticky comments - */ -export const header = - core.getInput("header", { required: false }) || "bundle-analysis"; - -/** - * Pull request number (auto-detected if not provided) - */ -const inputPrNumber = core.getInput("pr_number", { required: false }); -export const prNumber = - context?.payload?.pull_request?.number ?? - (inputPrNumber ? Number(inputPrNumber) : undefined); - -/** - * Whether to skip posting a comment - */ -export const skipComment = core.getBooleanInput("skip_comment", { - required: false, -}); - -/** - * Whether to fail the action on significant bundle increases - */ -export const failOnIncrease = core.getBooleanInput("fail_on_increase", { - required: false, -}); - -/** - * Directory containing packages to analyze - */ -export const packagesDir = - core.getInput("packages_dir", { required: false }) || "packages"; - -/** - * Percentage threshold for significant bundle size increase - */ -const thresholdInput = core.getInput("threshold", { required: false }) || "10"; -const parsedThreshold = parseFloat(thresholdInput); -export const threshold = - isNaN(parsedThreshold) || parsedThreshold < 0 ? 10 : parsedThreshold; - -/** - * Repository descriptor where the action will run - */ -export const repo = { - owner: context.repo.owner, - repo: context.repo.repo, -}; diff --git a/internals/bundle-analysis-action/src/github/pr-comment.test.ts b/internals/bundle-analysis-action/src/github/pr-comment.test.ts deleted file mode 100644 index 91e9aab..0000000 --- a/internals/bundle-analysis-action/src/github/pr-comment.test.ts +++ /dev/null @@ -1,298 +0,0 @@ -import * as core from "@actions/core"; -import { beforeEach, describe, expect, it, vi } from "vitest"; -import { - createComment, - ensureComment, - findPreviousComment, - updateComment, -} from "../github/pr-comment"; - -// Mock @actions/core -vi.mock("@actions/core", () => ({ - default: { - setFailed: vi.fn(), - setOutput: vi.fn(), - }, - setFailed: vi.fn(), - setOutput: vi.fn(), -})); - -// Mock @actions/github -const mockOctokit = { - rest: { - issues: { - listComments: vi.fn(), - createComment: vi.fn(), - updateComment: vi.fn(), - }, - }, -}; - -describe("pr-comment", () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - describe("findPreviousComment", () => { - it("should find previous comment with header", async () => { - const comments = [ - { - id: 1, - body: "\nComment content\n", - }, - { id: 2, body: "Other comment" }, - ]; - - mockOctokit.rest.issues.listComments.mockResolvedValue({ - data: comments, - }); - - const result = await findPreviousComment( - mockOctokit as any, - { owner: "test", repo: "repo" }, - 123, - "bundle-analysis" - ); - - expect(result).toEqual({ - id: 1, - body: comments[0].body, - }); - }); - - it("should return undefined when no comment found", async () => { - mockOctokit.rest.issues.listComments.mockResolvedValue({ - data: [{ id: 1, body: "Other comment" }], - }); - - const result = await findPreviousComment( - mockOctokit as any, - { owner: "test", repo: "repo" }, - 123, - "bundle-analysis" - ); - - expect(result).toBeUndefined(); - }); - - it("should paginate through comments", async () => { - const firstPage = Array.from({ length: 100 }, (_, i) => ({ - id: i + 1, - body: `Comment ${i + 1}`, - })); - - const secondPage = [ - { - id: 101, - body: "\nFound\n", - }, - ]; - - mockOctokit.rest.issues.listComments - .mockResolvedValueOnce({ data: firstPage }) - .mockResolvedValueOnce({ data: secondPage }); - - const result = await findPreviousComment( - mockOctokit as any, - { owner: "test", repo: "repo" }, - 123, - "bundle-analysis" - ); - - expect(result).toEqual({ - id: 101, - body: secondPage[0].body, - }); - expect(mockOctokit.rest.issues.listComments).toHaveBeenCalledTimes(2); - }); - - it("should stop pagination when fewer than perPage results", async () => { - const comments = [ - { id: 1, body: "Comment 1" }, - { id: 2, body: "Comment 2" }, - ]; - - mockOctokit.rest.issues.listComments.mockResolvedValue({ - data: comments, - }); - - await findPreviousComment( - mockOctokit as any, - { owner: "test", repo: "repo" }, - 123, - "bundle-analysis" - ); - - expect(mockOctokit.rest.issues.listComments).toHaveBeenCalledTimes(1); - }); - }); - - describe("createComment", () => { - it("should create comment with header", async () => { - mockOctokit.rest.issues.createComment.mockResolvedValue({ - data: { id: 123 }, - }); - - const result = await createComment( - mockOctokit as any, - { owner: "test", repo: "repo" }, - 456, - "Comment body", - "bundle-analysis" - ); - - expect(result).toEqual({ id: 123 }); - expect(mockOctokit.rest.issues.createComment).toHaveBeenCalledWith({ - owner: "test", - repo: "repo", - issue_number: 456, - body: "\nComment body\n", - }); - }); - - it("should handle creation errors", async () => { - mockOctokit.rest.issues.createComment.mockRejectedValue( - new Error("API Error") - ); - - const result = await createComment( - mockOctokit as any, - { owner: "test", repo: "repo" }, - 456, - "Comment body", - "bundle-analysis" - ); - - expect(result).toBeUndefined(); - expect(core.setFailed).toHaveBeenCalledWith( - "Failed to create comment: API Error" - ); - }); - - it("should handle custom header", async () => { - mockOctokit.rest.issues.createComment.mockResolvedValue({ - data: { id: 123 }, - }); - - await createComment( - mockOctokit as any, - { owner: "test", repo: "repo" }, - 456, - "Comment body", - "custom-header" - ); - - expect(mockOctokit.rest.issues.createComment).toHaveBeenCalledWith( - expect.objectContaining({ - body: expect.stringContaining(""), - }) - ); - }); - }); - - describe("updateComment", () => { - it("should update comment with header", async () => { - mockOctokit.rest.issues.updateComment.mockResolvedValue({}); - - await updateComment( - mockOctokit as any, - { owner: "test", repo: "repo" }, - 789, - "Updated body", - "bundle-analysis" - ); - - expect(mockOctokit.rest.issues.updateComment).toHaveBeenCalledWith({ - owner: "test", - repo: "repo", - comment_id: 789, - body: "\nUpdated body\n", - }); - }); - - it("should handle update errors", async () => { - mockOctokit.rest.issues.updateComment.mockRejectedValue( - new Error("Update failed") - ); - - await updateComment( - mockOctokit as any, - { owner: "test", repo: "repo" }, - 789, - "Updated body", - "bundle-analysis" - ); - - expect(core.setFailed).toHaveBeenCalledWith( - "Failed to update comment: Update failed" - ); - }); - }); - - describe("ensureComment", () => { - it("should update existing comment", async () => { - const existingComment = { - id: 123, - body: "\nOld\n", - }; - - mockOctokit.rest.issues.listComments.mockResolvedValue({ - data: [existingComment], - }); - mockOctokit.rest.issues.updateComment.mockResolvedValue({}); - - await ensureComment( - mockOctokit as any, - { owner: "test", repo: "repo" }, - 456, - "New body", - "bundle-analysis" - ); - - expect(mockOctokit.rest.issues.updateComment).toHaveBeenCalled(); - expect(mockOctokit.rest.issues.createComment).not.toHaveBeenCalled(); - expect(core.setOutput).toHaveBeenCalledWith("updated_comment_id", 123); - }); - - it("should create new comment when none exists", async () => { - mockOctokit.rest.issues.listComments.mockResolvedValue({ - data: [], - }); - mockOctokit.rest.issues.createComment.mockResolvedValue({ - data: { id: 789 }, - }); - - await ensureComment( - mockOctokit as any, - { owner: "test", repo: "repo" }, - 456, - "New body", - "bundle-analysis" - ); - - expect(mockOctokit.rest.issues.createComment).toHaveBeenCalled(); - expect(mockOctokit.rest.issues.updateComment).not.toHaveBeenCalled(); - expect(core.setOutput).toHaveBeenCalledWith("created_comment_id", 789); - }); - - it("should handle create failure gracefully", async () => { - mockOctokit.rest.issues.listComments.mockResolvedValue({ - data: [], - }); - mockOctokit.rest.issues.createComment.mockResolvedValue(undefined); - - await ensureComment( - mockOctokit as any, - { owner: "test", repo: "repo" }, - 456, - "New body", - "bundle-analysis" - ); - - expect(core.setOutput).not.toHaveBeenCalledWith( - "created_comment_id", - expect.anything() - ); - }); - }); -}); diff --git a/internals/bundle-analysis-action/src/github/pr-comment.ts b/internals/bundle-analysis-action/src/github/pr-comment.ts deleted file mode 100644 index 3257522..0000000 --- a/internals/bundle-analysis-action/src/github/pr-comment.ts +++ /dev/null @@ -1,121 +0,0 @@ -/** - * @packageDocumentation - * Utilities for posting bundle analysis comments on pull requests. - */ -import * as core from "@actions/core"; -import type { GitHub } from "@actions/github/lib/utils"; - -function autoStart(header: string): string { - const key = (header || "bundle-analysis").trim() || "bundle-analysis"; - return ``; -} - -function autoEnd(header: string): string { - const key = (header || "bundle-analysis").trim() || "bundle-analysis"; - return ``; -} - -function bodyWithHeader(body: string, header: string): string { - return [autoStart(header), body, autoEnd(header)].join("\n"); -} - -export async function findPreviousComment( - octokit: InstanceType, - repo: { owner: string; repo: string }, - number: number, - header: string -): Promise<{ id: number; body: string } | undefined> { - const start = autoStart(header); - let page = 1; - const perPage = 100; - - while (true) { - const { data } = await octokit.rest.issues.listComments({ - ...repo, - issue_number: number, - per_page: perPage, - page, - }); - - for (const comment of data) { - if (comment.body?.includes(start)) { - return { - id: comment.id, - body: comment.body || "", - }; - } - } - - if (data.length < perPage) { - break; - } - - page++; - } - - return undefined; -} - -export async function createComment( - octokit: InstanceType, - repo: { owner: string; repo: string }, - number: number, - body: string, - header: string -): Promise<{ id: number } | undefined> { - const bodyWithHeaderText = bodyWithHeader(body, header); - try { - const { data } = await octokit.rest.issues.createComment({ - ...repo, - issue_number: number, - body: bodyWithHeaderText, - }); - return { id: data.id }; - } catch (error) { - if (error instanceof Error) { - core.setFailed(`Failed to create comment: ${error.message}`); - } - return undefined; - } -} - -export async function updateComment( - octokit: InstanceType, - repo: { owner: string; repo: string }, - commentId: number, - body: string, - header: string -): Promise { - const bodyWithHeaderText = bodyWithHeader(body, header); - try { - await octokit.rest.issues.updateComment({ - ...repo, - comment_id: commentId, - body: bodyWithHeaderText, - }); - } catch (error) { - if (error instanceof Error) { - core.setFailed(`Failed to update comment: ${error.message}`); - } - } -} - -export async function ensureComment( - octokit: InstanceType, - repo: { owner: string; repo: string }, - number: number, - body: string, - header: string -): Promise { - const previous = await findPreviousComment(octokit, repo, number, header); - - if (previous) { - await updateComment(octokit, repo, previous.id, body, header); - core.setOutput("updated_comment_id", previous.id); - } else { - const created = await createComment(octokit, repo, number, body, header); - if (created) { - core.setOutput("created_comment_id", created.id); - } - } -} diff --git a/internals/bundle-analysis-action/src/main.test.ts b/internals/bundle-analysis-action/src/main.test.ts deleted file mode 100644 index 53088fc..0000000 --- a/internals/bundle-analysis-action/src/main.test.ts +++ /dev/null @@ -1,294 +0,0 @@ -import { readFileSync } from "node:fs"; -import * as core from "@actions/core"; -import * as github from "@actions/github"; -import { beforeEach, describe, expect, it, vi } from "vitest"; -import type { PackageBundleData } from "./analyze/bundle-analysis"; -import { - analyzeBundles, - calculateTotalDiffPercent, - writeReport, -} from "./analyze/bundle-analysis"; -import { - baseDir, - currentDir, - failOnIncrease, - githubToken, - header, - packagesDir, - prNumber, - repo, - skipComment, - threshold, -} from "./config/inputs"; -import { ensureComment } from "./github/pr-comment"; - -// Mock dependencies -vi.mock("@actions/core"); -vi.mock("@actions/github"); -vi.mock("node:fs", () => ({ - readFileSync: vi.fn(), -})); -vi.mock("./analyze/bundle-analysis", () => ({ - analyzeBundles: vi.fn(), - calculateTotalDiffPercent: vi.fn(), - writeReport: vi.fn(), -})); -vi.mock("./config/inputs", () => ({ - baseDir: ".bundle-base", - currentDir: ".", - githubToken: "test-token", - header: "bundle-analysis", - packagesDir: "packages", - prNumber: 123, - repo: { owner: "test", repo: "test-repo" }, - skipComment: false, - failOnIncrease: false, - threshold: 10, -})); -vi.mock("./github/pr-comment", () => ({ - ensureComment: vi.fn(), -})); - -// Import the run function - we'll need to test it indirectly -// since main.ts calls run() immediately -describe("main", () => { - const mockPackages: PackageBundleData[] = [ - { - packageName: "test-package", - baseBundles: [], - currentBundles: [], - diffs: { - added: [], - removed: [], - changed: [], - }, - totalBaseSize: 1000, - totalCurrentSize: 1100, - totalDiff: 100, - totalDiffPercent: 10, - }, - ]; - - const mockOctokit = { - rest: { - issues: { - listComments: vi.fn(), - createComment: vi.fn(), - updateComment: vi.fn(), - }, - }, - }; - - beforeEach(() => { - vi.clearAllMocks(); - vi.mocked(github.getOctokit).mockReturnValue(mockOctokit as any); - }); - - // Test the main logic by testing the individual functions - // Since main.ts calls run() immediately, we test the logic separately - describe("bundle analysis workflow", () => { - it("should analyze bundles and generate report", async () => { - vi.mocked(analyzeBundles).mockResolvedValue(mockPackages); - vi.mocked(calculateTotalDiffPercent).mockReturnValue(10); - - await analyzeBundles(baseDir, currentDir, packagesDir); - const totalDiffPercent = calculateTotalDiffPercent(mockPackages); - writeReport(mockPackages, "bundle-diff.md"); - - expect(analyzeBundles).toHaveBeenCalledWith( - baseDir, - currentDir, - packagesDir - ); - expect(calculateTotalDiffPercent).toHaveBeenCalledWith(mockPackages); - expect(writeReport).toHaveBeenCalledWith(mockPackages, "bundle-diff.md"); - expect(totalDiffPercent).toBe(10); - }); - - it("should set outputs correctly", async () => { - vi.mocked(analyzeBundles).mockResolvedValue(mockPackages); - vi.mocked(calculateTotalDiffPercent).mockReturnValue(10); - - const packages = await analyzeBundles(baseDir, currentDir, packagesDir); - const totalDiffPercent = calculateTotalDiffPercent(packages); - - // Simulate what main.ts does - core.setOutput("report_path", "bundle-diff.md"); - core.setOutput("has_changes", packages.length > 0); - core.setOutput("total_diff_percent", totalDiffPercent.toFixed(2)); - - expect(core.setOutput).toHaveBeenCalledWith( - "report_path", - "bundle-diff.md" - ); - expect(core.setOutput).toHaveBeenCalledWith("has_changes", true); - expect(core.setOutput).toHaveBeenCalledWith( - "total_diff_percent", - "10.00" - ); - }); - - it("should post comment when PR number is available", async () => { - vi.mocked(analyzeBundles).mockResolvedValue(mockPackages); - vi.mocked(calculateTotalDiffPercent).mockReturnValue(10); - vi.mocked(readFileSync).mockReturnValue("# Report content"); - - // Simulate the main.ts logic - if (!skipComment && prNumber) { - const report = readFileSync("bundle-diff.md", "utf-8"); - const octokit = github.getOctokit(githubToken); - await ensureComment(octokit, repo, prNumber, report, header); - } - - expect(ensureComment).toHaveBeenCalledWith( - mockOctokit, - repo, - 123, - "# Report content", - "bundle-analysis" - ); - }); - - it("should skip comment when skipComment is true", async () => { - const skipCommentTrue = true; - - if (skipCommentTrue) { - // Should not call ensureComment - } else if (prNumber) { - await ensureComment( - mockOctokit as any, - repo, - prNumber, - "report", - header - ); - } - - expect(ensureComment).not.toHaveBeenCalled(); - }); - - it("should skip comment when PR number is not available", async () => { - const noPrNumber = undefined; - - if (!skipComment && noPrNumber) { - await ensureComment( - mockOctokit as any, - repo, - noPrNumber, - "report", - header - ); - } - - expect(ensureComment).not.toHaveBeenCalled(); - }); - - it("should fail when bundle increase exceeds threshold", async () => { - const packagesWithIncrease: PackageBundleData[] = [ - { - ...mockPackages[0], - totalDiffPercent: 15, // Exceeds threshold of 10 - }, - ]; - - const failOnIncreaseTrue = true; - const thresholdValue = 10; - - if (failOnIncreaseTrue) { - const hasSignificantIncrease = packagesWithIncrease.some( - (p) => p.totalDiffPercent > thresholdValue - ); - if (hasSignificantIncrease) { - core.setFailed( - `Bundle size increased significantly (>${thresholdValue}%). Review the changes above.` - ); - } - } - - expect(core.setFailed).toHaveBeenCalledWith( - "Bundle size increased significantly (>10%). Review the changes above." - ); - }); - - it("should not fail when bundle increase is below threshold", async () => { - const packagesBelowThreshold: PackageBundleData[] = [ - { - ...mockPackages[0], - totalDiffPercent: 5, // Below threshold of 10 - }, - ]; - - const failOnIncreaseTrue = true; - const thresholdValue = 10; - - if (failOnIncreaseTrue) { - const hasSignificantIncrease = packagesBelowThreshold.some( - (p) => p.totalDiffPercent > thresholdValue - ); - if (hasSignificantIncrease) { - core.setFailed("Should not fail"); - } - } - - expect(core.setFailed).not.toHaveBeenCalled(); - }); - - it("should not fail when failOnIncrease is false", async () => { - const packagesWithIncrease: PackageBundleData[] = [ - { - ...mockPackages[0], - totalDiffPercent: 15, - }, - ]; - - const failOnIncreaseFalse = false; - - if (failOnIncreaseFalse) { - // Should not check or fail - } else { - const hasSignificantIncrease = packagesWithIncrease.some( - (p) => p.totalDiffPercent > threshold - ); - if (hasSignificantIncrease) { - core.setFailed("Should fail"); - } - } - - // When failOnIncrease is false, we shouldn't reach the setFailed call - // This test verifies the logic flow - expect(true).toBe(true); - }); - - it("should handle errors gracefully", async () => { - const error = new Error("Test error"); - - try { - throw error; - } catch (caughtError) { - if (caughtError instanceof Error) { - core.setFailed(caughtError.message); - } else { - core.setFailed("Unknown error occurred"); - } - } - - expect(core.setFailed).toHaveBeenCalledWith("Test error"); - }); - - it("should handle non-Error objects", async () => { - const nonError = "String error"; - - try { - throw nonError; - } catch (caughtError) { - if (caughtError instanceof Error) { - core.setFailed(caughtError.message); - } else { - core.setFailed("Unknown error occurred"); - } - } - - expect(core.setFailed).toHaveBeenCalledWith("Unknown error occurred"); - }); - }); -}); diff --git a/internals/bundle-analysis-action/src/main.ts b/internals/bundle-analysis-action/src/main.ts deleted file mode 100644 index 57bb8e9..0000000 --- a/internals/bundle-analysis-action/src/main.ts +++ /dev/null @@ -1,84 +0,0 @@ -/** - * @packageDocumentation - * Entry point for the bundle analysis GitHub Action. - */ -import { readFileSync } from "node:fs"; -import * as core from "@actions/core"; -import * as github from "@actions/github"; -import { - analyzeBundles, - calculateTotalDiffPercent, - writeReport, -} from "./analyze/bundle-analysis"; -import { - baseDir, - currentDir, - failOnIncrease, - githubToken, - header, - packagesDir, - prNumber, - repo, - skipComment, - threshold, -} from "./config/inputs"; -import { ensureComment } from "./github/pr-comment"; - -async function run(): Promise { - try { - core.info("Starting bundle analysis..."); - - // Analyze bundles - const packages = await analyzeBundles(baseDir, currentDir, packagesDir); - core.info(`Analyzed ${packages.length} packages`); - - // Calculate total diff - const totalDiffPercent = calculateTotalDiffPercent(packages); - - // Generate report - const reportPath = "bundle-diff.md"; - writeReport(packages, reportPath); - - // Set outputs - core.setOutput("report_path", reportPath); - core.setOutput("has_changes", packages.length > 0); - core.setOutput("total_diff_percent", totalDiffPercent.toFixed(2)); - - // Post comment if enabled and PR is available - if (!skipComment && prNumber) { - const report = readFileSync(reportPath, "utf-8"); - - core.info(`Posting comment on PR #${prNumber}`); - const octokit = github.getOctokit(githubToken); - await ensureComment(octokit, repo, prNumber, report, header); - core.info("Comment posted successfully"); - } else if (skipComment) { - core.info("Skipping comment posting (skip_comment=true)"); - } else if (!prNumber) { - core.info("No PR number available, skipping comment"); - } - - // Fail if significant increase detected - if (failOnIncrease) { - core.info(`Using threshold: ${threshold}%`); - const hasSignificantIncrease = packages.some( - (p) => p.totalDiffPercent > threshold - ); - if (hasSignificantIncrease) { - core.setFailed( - `Bundle size increased significantly (>${threshold}%). Review the changes above.` - ); - } - } - - core.info("Bundle analysis complete"); - } catch (error) { - if (error instanceof Error) { - core.setFailed(error.message); - } else { - core.setFailed("Unknown error occurred"); - } - } -} - -run(); diff --git a/internals/bundle-analysis-action/tsconfig.json b/internals/bundle-analysis-action/tsconfig.json deleted file mode 100644 index 2b3ca37..0000000 --- a/internals/bundle-analysis-action/tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "module": "ESNext", - "moduleResolution": "Bundler", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "resolveJsonModule": true, - "types": ["node"], - "noEmit": true, - "forceConsistentCasingInFileNames": true, - "isolatedModules": true, - "verbatimModuleSyntax": true, - "lib": ["es2022"] - }, - "include": ["src"], - "exclude": ["dist", "node_modules", "**/__tests__/**"] -} diff --git a/internals/bundle-analysis-action/vitest.config.ts b/internals/bundle-analysis-action/vitest.config.ts deleted file mode 100644 index 26fb6b9..0000000 --- a/internals/bundle-analysis-action/vitest.config.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - test: { - environment: "node", - include: ["src/**/*.test.ts"], - exclude: [ - "**/node_modules/**", - "**/dist/**", - "**/build/**", - "**/.cache/**", - "**/coverage/**", - ], - coverage: { - provider: "istanbul", - reporter: ["text", "json-summary", "json", "html"], - reportOnFailure: true, - enabled: true, - reportsDirectory: "./coverage", - include: ["src/**/*.ts", "!**/*.d.ts", "!**/node_modules/**"], - }, - }, -}); diff --git a/internals/c15t-github-action/README.md b/internals/c15t-github-action/README.md deleted file mode 100644 index d1326d3..0000000 --- a/internals/c15t-github-action/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# C15T Sticky Pull Request Comment - -Create a comment on a pull request, if it exists update that comment. Forked from `marocchino/sticky-pull-request-comment`. - -## Usage - -```yaml -permissions: - pull-requests: write - -steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/c15t-github-action - with: - message: | - Release ${{ github.sha }} to https://pr-${{ github.event.number }}.example.com -``` - -All inputs/outputs are the same as the original action. See `action.yml` for details. diff --git a/internals/c15t-github-action/__tests__/ascii-art.test.ts b/internals/c15t-github-action/__tests__/ascii-art.test.ts deleted file mode 100644 index e361ada..0000000 --- a/internals/c15t-github-action/__tests__/ascii-art.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { ASCII_SET, BRAILLE_SPACE, LEFT_PAD } from "../src/steps/ascii-art"; - -describe("ascii-art constants", () => { - it("ASCII_SET is non-empty and entries have art and weight", () => { - expect(Array.isArray(ASCII_SET)).toBe(true); - expect(ASCII_SET.length).toBeGreaterThan(0); - for (const entry of ASCII_SET) { - expect(typeof entry.art).toBe("string"); - expect(typeof entry.weight).toBe("number"); - } - }); - - it("constants exported", () => { - expect(BRAILLE_SPACE).toBeTypeOf("string"); - expect(LEFT_PAD).toBeTypeOf("string"); - }); -}); diff --git a/internals/c15t-github-action/__tests__/assets/result b/internals/c15t-github-action/__tests__/assets/result deleted file mode 100644 index 37d4e6c..0000000 --- a/internals/c15t-github-action/__tests__/assets/result +++ /dev/null @@ -1 +0,0 @@ -hi there diff --git a/internals/c15t-github-action/__tests__/assets/result2 b/internals/c15t-github-action/__tests__/assets/result2 deleted file mode 100644 index a823312..0000000 --- a/internals/c15t-github-action/__tests__/assets/result2 +++ /dev/null @@ -1 +0,0 @@ -hey there diff --git a/internals/c15t-github-action/__tests__/changes.test.ts b/internals/c15t-github-action/__tests__/changes.test.ts deleted file mode 100644 index 1ac4ae9..0000000 --- a/internals/c15t-github-action/__tests__/changes.test.ts +++ /dev/null @@ -1,154 +0,0 @@ -import * as github from "@actions/github"; -import { describe, expect, it, vi } from "vitest"; - -describe("change detection", () => { - describe("shouldDeployByPolicy", () => { - it("should allow main branch for push events", async () => { - const ctx = github.context as unknown as { - eventName: string; - ref: string; - }; - ctx.eventName = "push"; - ctx.ref = "refs/heads/main"; - - vi.resetModules(); - const { shouldDeployByPolicy } = await import("../src/steps/changes"); - - const result = shouldDeployByPolicy("main,canary", "main,canary"); - expect(result).toBe(true); - }); - - it("should allow canary branch for push events", async () => { - const ctx = github.context as unknown as { - eventName: string; - ref: string; - }; - ctx.eventName = "push"; - ctx.ref = "refs/heads/canary"; - - vi.resetModules(); - const { shouldDeployByPolicy } = await import("../src/steps/changes"); - - const result = shouldDeployByPolicy("main,canary", "main,canary"); - expect(result).toBe(true); - }); - - it("should reject feature branch for push events", async () => { - const ctx = github.context as unknown as { - eventName: string; - ref: string; - }; - ctx.eventName = "push"; - ctx.ref = "refs/heads/feature/new-feature"; - - vi.resetModules(); - const { shouldDeployByPolicy } = await import("../src/steps/changes"); - - const result = shouldDeployByPolicy("main,canary", "main,canary"); - expect(result).toBe(false); - }); - - it("should allow main branch for PR events", async () => { - const ctx = github.context as unknown as { - eventName: string; - payload: { - pull_request: { - base: { ref: string }; - }; - }; - }; - ctx.eventName = "pull_request"; - ctx.payload = { - pull_request: { - base: { ref: "main" }, - }, - }; - - vi.resetModules(); - const { shouldDeployByPolicy } = await import("../src/steps/changes"); - - const result = shouldDeployByPolicy("main,canary", "main,canary"); - expect(result).toBe(true); - }); - - it("should allow canary branch for PR events", async () => { - const ctx = github.context as unknown as { - eventName: string; - payload: { - pull_request: { - base: { ref: string }; - }; - }; - }; - ctx.eventName = "pull_request"; - ctx.payload = { - pull_request: { - base: { ref: "canary" }, - }, - }; - - vi.resetModules(); - const { shouldDeployByPolicy } = await import("../src/steps/changes"); - - const result = shouldDeployByPolicy("main,canary", "main,canary"); - expect(result).toBe(true); - }); - - it("should reject feature branch for PR events", async () => { - const ctx = github.context as unknown as { - eventName: string; - payload: { - pull_request: { - base: { ref: string }; - }; - }; - }; - ctx.eventName = "pull_request"; - ctx.payload = { - pull_request: { - base: { ref: "feature/new-feature" }, - }, - }; - - vi.resetModules(); - const { shouldDeployByPolicy } = await import("../src/steps/changes"); - - const result = shouldDeployByPolicy("main,canary", "main,canary"); - expect(result).toBe(false); - }); - }); - - describe("parseCsv", () => { - it("should parse comma-separated values", async () => { - vi.resetModules(); - const { parseCsv } = await import("../src/steps/changes"); - - const result = parseCsv("main,canary,develop", ["default"]); - expect(result).toEqual(["main", "canary", "develop"]); - }); - - it("should use fallback when input is undefined", async () => { - vi.resetModules(); - const { parseCsv } = await import("../src/steps/changes"); - - const result = parseCsv(undefined, ["main", "canary"]); - expect(result).toEqual(["main", "canary"]); - }); - - it("should filter out empty values", async () => { - vi.resetModules(); - const { parseCsv } = await import("../src/steps/changes"); - - const result = parseCsv("main,,canary,", ["default"]); - expect(result).toEqual(["main", "canary"]); - }); - - it("should trim whitespace", async () => { - vi.resetModules(); - const { parseCsv } = await import("../src/steps/changes"); - - const result = parseCsv(" main , canary ", ["default"]); - expect(result).toEqual(["main", "canary"]); - }); - }); -}); diff --git a/internals/c15t-github-action/__tests__/comment.test.ts b/internals/c15t-github-action/__tests__/comment.test.ts deleted file mode 100644 index b3d3ea0..0000000 --- a/internals/c15t-github-action/__tests__/comment.test.ts +++ /dev/null @@ -1,356 +0,0 @@ -import * as core from "@actions/core"; -import { getOctokit } from "@actions/github"; -import { beforeEach, describe, expect, it, vi } from "vitest"; - -import { - commentsEqual, - createComment, - deleteComment, - findPreviousComment, - getBodyOf, - updateComment, -} from "../src/github/pr-comment"; - -vi.mock("@actions/core", () => ({ - warning: vi.fn(), -})); - -const repo = { - owner: "c15t", - repo: "c15t", -}; -beforeEach(() => { - vi.clearAllMocks(); -}); -it("findPreviousComment", async () => { - const authenticatedBotUser = { login: "github-actions[bot]" }; - const authenticatedUser = { login: "github-actions" }; - const otherUser = { login: "some-user" }; - const comment = { - id: "1", - author: authenticatedUser, - isMinimized: false, - body: "previous message", - }; - const commentWithCustomHeader = { - id: "2", - author: authenticatedUser, - isMinimized: false, - body: "previous message", - }; - const headerFirstComment = { - id: "3", - author: authenticatedUser, - isMinimized: false, - body: "header first message", - }; - const otherUserComment = { - id: "4", - author: otherUser, - isMinimized: false, - body: "Fake previous message", - }; - const otherComments = [ - { id: "5", author: otherUser, isMinimized: false, body: "lgtm" }, - { - id: "6", - author: authenticatedUser, - isMinimized: false, - body: "previous message", - }, - ]; - const octokit = getOctokit("github-token"); - vi.spyOn(octokit, "graphql").mockResolvedValue({ - viewer: authenticatedBotUser, - repository: { - pullRequest: { - comments: { - nodes: [ - commentWithCustomHeader, - otherUserComment, - comment, - headerFirstComment, - ...otherComments, - ], - pageInfo: { hasNextPage: false, endCursor: "6" }, - }, - }, - }, - } as unknown); - - expect(await findPreviousComment(octokit, repo, 123, "")).toBe(comment); - expect(await findPreviousComment(octokit, repo, 123, "TypeA")).toBe( - commentWithCustomHeader - ); - expect(await findPreviousComment(octokit, repo, 123, "LegacyComment")).toBe( - headerFirstComment - ); - expect(octokit.graphql).toBeCalledWith(expect.any(String), { - after: null, - number: 123, - owner: repo.owner, - repo: repo.repo, - }); -}); - -it("findPreviousComment with explicit authorLogin", async () => { - const octokit = getOctokit("github-token"); - const consentComment = { - id: "7", - author: { login: "consentdotio" }, - isMinimized: false, - body: "previous message", - }; - vi.spyOn(octokit, "graphql").mockResolvedValue({ - viewer: { login: "github-actions[bot]" }, - repository: { - pullRequest: { - comments: { - nodes: [consentComment], - pageInfo: { hasNextPage: false, endCursor: "7" }, - }, - }, - }, - } as unknown); - - const found = await findPreviousComment( - octokit, - repo, - 123, - "", - "consentdotio" - ); - expect(found).toMatchObject({ id: "7" }); -}); - -describe("updateComment", () => { - const octokit = getOctokit("github-token"); - beforeEach(() => { - vi.spyOn(octokit, "graphql").mockResolvedValue(""); - }); - it("with comment body", async () => { - expect( - await updateComment(octokit, "456", "hello there", "") - ).toBeUndefined(); - expect(octokit.graphql).toBeCalledWith(expect.any(String), { - input: { - id: "456", - body: "\nhello there\n", - }, - }); - expect( - await updateComment(octokit, "456", "hello there", "TypeA") - ).toBeUndefined(); - expect(octokit.graphql).toBeCalledWith(expect.any(String), { - input: { - id: "456", - body: "\nhello there\n", - }, - }); - expect( - await updateComment( - octokit, - "456", - "hello there", - "TypeA", - "\nhello there\n" - ) - ).toBeUndefined(); - expect(octokit.graphql).toBeCalledWith(expect.any(String), { - input: { - id: "456", - body: "\nhello there\nhello there\n", - }, - }); - }); - it("without comment body and previous body", async () => { - expect(await updateComment(octokit, "456", "", "")).toBeUndefined(); - expect(octokit.graphql).not.toBeCalled(); - expect(core.warning).toBeCalledWith("Comment body cannot be blank"); - }); -}); - -describe("createComment", () => { - const octokit = getOctokit("github-token"); - beforeEach(() => { - vi.spyOn(octokit.rest.issues, "createComment").mockResolvedValue({ - status: 201, - url: "https://api.github.local", - headers: {} as Record, - data: { id: 1 } as unknown, - } as Awaited>); - }); - it("with comment body or previousBody", async () => { - await expect( - createComment(octokit, repo, 456, "hello there", "") - ).resolves.toBeDefined(); - expect(octokit.rest.issues.createComment).toBeCalledWith({ - issue_number: 456, - owner: repo.owner, - repo: repo.repo, - body: "\nhello there\n", - }); - await expect( - createComment(octokit, repo, 456, "hello there", "TypeA") - ).resolves.toBeDefined(); - expect(octokit.rest.issues.createComment).toBeCalledWith({ - issue_number: 456, - owner: repo.owner, - repo: repo.repo, - body: "\nhello there\n", - }); - }); - it("with previousBody unwraps and re-wraps content", async () => { - await expect( - createComment(octokit, repo, 456, "hello", "TypeA", "prev") - ).resolves.toBeDefined(); - expect(octokit.rest.issues.createComment).toBeCalledWith({ - issue_number: 456, - owner: repo.owner, - repo: repo.repo, - body: "\n\nhello\n", - }); - }); - it("with previousBody containing headers unwraps inner content", async () => { - const previousBodyWithHeaders = - "\nprevious content\n"; - await expect( - createComment( - octokit, - repo, - 456, - "new content", - "TypeA", - previousBodyWithHeaders - ) - ).resolves.toBeDefined(); - expect(octokit.rest.issues.createComment).toBeCalledWith({ - issue_number: 456, - owner: repo.owner, - repo: repo.repo, - body: "\nprevious content\nnew content\n", - }); - }); - it("without comment body and previousBody", async () => { - expect(await createComment(octokit, repo, 456, "", "")).toBeUndefined(); - expect(octokit.rest.issues.createComment).not.toBeCalled(); - expect(core.warning).toBeCalledWith("Comment body cannot be blank"); - }); - it("handles previousBody with malformed headers gracefully", async () => { - const malformedPreviousBody = - "\npartial content without end"; - await expect( - createComment( - octokit, - repo, - 456, - "new content", - "TypeA", - malformedPreviousBody - ) - ).resolves.toBeDefined(); - expect(octokit.rest.issues.createComment).toBeCalledWith({ - issue_number: 456, - owner: repo.owner, - repo: repo.repo, - body: "\n\nnew content\n", - }); - }); -}); - -it("deleteComment", async () => { - const octokit = getOctokit("github-token"); - vi.spyOn(octokit, "graphql").mockResolvedValue(""); - expect(await deleteComment(octokit, "456")).toBeUndefined(); - expect(octokit.graphql).toBeCalledWith(expect.any(String), { id: "456" }); -}); - -describe("getBodyOf", () => { - const nullPrevious = {}; - const simplePrevious = { - body: "\nhello there\n", - }; - const detailsPrevious = { - body: ` -
- title - - content -
- - - `, - }; - const replaced = ` -
- title - - content -
- - - `; - it.each` - append | hideDetails | previous | expected - ${false} | ${false} | ${detailsPrevious} | ${undefined} - ${true} | ${false} | ${nullPrevious} | ${undefined} - ${true} | ${false} | ${detailsPrevious} | ${detailsPrevious.body} - ${true} | ${true} | ${nullPrevious} | ${undefined} - ${true} | ${true} | ${simplePrevious} | ${simplePrevious.body} - ${true} | ${true} | ${detailsPrevious} | ${replaced} - `( - "receive $previous, $append, $hideDetails and returns $expected", - ({ - append, - hideDetails, - previous, - expected, - }: { - append: boolean; - hideDetails: boolean; - previous: { body?: string }; - expected: string | undefined; - }) => { - expect(getBodyOf(previous, append, hideDetails)).toEqual(expected); - } - ); -}); - -describe("commentsEqual", () => { - it.each([ - { - body: "body", - previous: "\nbody\n", - header: "header", - expected: true, - }, - { - body: "body", - previous: - "\nbody\n", - header: "", - expected: true, - }, - { - body: "body", - previous: "\nbody\n", - header: "header", - expected: false, - }, - { body: "body", previous: "body", header: "header", expected: true }, - { body: "body", previous: "", header: "header", expected: false }, - { body: "", previous: "body", header: "header", expected: false }, - ])("commentsEqual(%s, %s, %s)", ({ - body, - previous, - header, - expected, - }: { - body: string; - previous: string; - header: string; - expected: boolean; - }) => { - expect(commentsEqual(body, previous, header)).toEqual(expected); - }); -}); diff --git a/internals/c15t-github-action/__tests__/comments.test.ts b/internals/c15t-github-action/__tests__/comments.test.ts deleted file mode 100644 index ea81e25..0000000 --- a/internals/c15t-github-action/__tests__/comments.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -import * as core from "@actions/core"; -import { getOctokit } from "@actions/github"; -import { beforeEach, describe, expect, it, vi } from "vitest"; - -vi.mock("@actions/core", () => ({ - warning: vi.fn(), - info: vi.fn(), - setOutput: vi.fn(), - getInput: () => "", - getBooleanInput: () => false, - getMultilineInput: () => [], -})); - -vi.mock("../src/config/inputs", async () => { - return { - repo: { owner: "o", repo: "r" }, - append: false, - header: "", - hideDetails: false, - hideOldComment: false, - hideAndRecreate: false, - hideClassify: "OUTDATED", - deleteOldComment: false, - ignoreEmpty: false, - onlyCreateComment: false, - onlyUpdateComment: false, - recreate: false, - skipUnchanged: false, - pullRequestNumber: 123, - authorLogin: "c15t", - }; -}); - -// Import within tests after mocks are set up -let ensureComment: ( - octokit: ReturnType, - effectiveBody: string, - options?: { appendOverride?: boolean; hideDetailsOverride?: boolean } -) => Promise; - -describe("comments.ensureComment", () => { - const octokit = getOctokit("token"); - - beforeEach(() => { - vi.resetModules(); - }); - - it("does nothing when body empty and ignoreEmpty true", async () => { - process.env.GITHUB_REPOSITORY = "owner/repo"; - vi.doMock("../src/config/inputs", async () => ({ - ...(await vi.importActual("../src/config/inputs")), - ignoreEmpty: true, - })); - ({ ensureComment } = await import("../src/steps/comments")); - await ensureComment( - octokit as unknown as ReturnType, - "" - ); - expect(core.info).toBeCalled(); - }); - - it("replaces inner block on skip (no append)", async () => { - process.env.GITHUB_REPOSITORY = "owner/repo"; - const previousBody = [ - "", - "old-content", - "", - ].join("\n"); - vi.doMock("../src/github/pr-comment", async () => { - return { - findPreviousComment: vi - .fn() - .mockResolvedValue({ id: "id1", body: previousBody }), - getBodyOf: (p: { body?: string }, append: boolean) => - append ? p.body : undefined, - updateComment: vi.fn().mockResolvedValue(undefined), - createComment: vi.fn().mockResolvedValue(undefined), - }; - }); - ({ ensureComment } = await import("../src/steps/comments")); - const update = (await import("../src/github/pr-comment")) - .updateComment as unknown as vi.Mock; - await ensureComment( - octokit as unknown as ReturnType, - "new-rendered", - { appendOverride: false } - ); - expect(update).toBeCalled(); - const args = update.mock.calls.at(-1)?.[2] as string; - expect(args).toContain("new-rendered"); - expect(args).not.toContain("old-content"); - }); -}); diff --git a/internals/c15t-github-action/__tests__/config.test.ts b/internals/c15t-github-action/__tests__/config.test.ts deleted file mode 100644 index 7baac20..0000000 --- a/internals/c15t-github-action/__tests__/config.test.ts +++ /dev/null @@ -1,433 +0,0 @@ -import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; - -const mockConfig = { - pullRequestNumber: 123, - repo: { owner: "marocchino", repo: "stick-pull-request-comment" }, - header: "", - append: false, - recreate: false, - deleteOldComment: false, - hideOldComment: false, - hideAndRecreate: false, - hideClassify: "OUTDATED", - hideDetails: false, - githubToken: "some-token", - ignoreEmpty: false, - skipUnchanged: false, - getBody: vi.fn().mockResolvedValue(""), - authorLogin: "consentdotio", -}; - -vi.mock("../src/config/inputs", () => { - return mockConfig; -}); - -beforeEach(() => { - process.env["GITHUB_REPOSITORY"] = "marocchino/stick-pull-request-comment"; - process.env["INPUT_NUMBER"] = "123"; - process.env["INPUT_APPEND"] = "false"; - process.env["INPUT_RECREATE"] = "false"; - process.env["INPUT_DELETE"] = "false"; - process.env["INPUT_ONLY_CREATE"] = "false"; - process.env["INPUT_ONLY_UPDATE"] = "false"; - process.env["INPUT_HIDE"] = "false"; - process.env["INPUT_HIDE_AND_RECREATE"] = "false"; - process.env["INPUT_HIDE_CLASSIFY"] = "OUTDATED"; - process.env["INPUT_HIDE_DETAILS"] = "false"; - process.env["INPUT_GITHUB_TOKEN"] = "some-token"; - process.env["INPUT_IGNORE_EMPTY"] = "false"; - process.env["INPUT_SKIP_UNCHANGED"] = "false"; - process.env["INPUT_FOLLOW_SYMBOLIC_LINKS"] = "false"; - process.env["INPUT_AUTHOR_LOGIN"] = "consentdotio"; - - mockConfig.pullRequestNumber = 123; - mockConfig.repo = { owner: "marocchino", repo: "stick-pull-request-comment" }; - mockConfig.header = ""; - mockConfig.append = false; - mockConfig.recreate = false; - mockConfig.deleteOldComment = false; - mockConfig.hideOldComment = false; - mockConfig.hideAndRecreate = false; - mockConfig.hideClassify = "OUTDATED"; - mockConfig.hideDetails = false; - mockConfig.githubToken = "some-token"; - mockConfig.ignoreEmpty = false; - mockConfig.skipUnchanged = false; - // author login default - mockConfig.authorLogin = "consentdotio"; - mockConfig.getBody.mockResolvedValue(""); -}); - -afterEach(() => { - vi.resetModules(); - delete process.env["GITHUB_REPOSITORY"]; - delete process.env["INPUT_OWNER"]; - delete process.env["INPUT_REPO"]; - delete process.env["INPUT_HEADER"]; - delete process.env["INPUT_MESSAGE"]; - delete process.env["INPUT_NUMBER"]; - delete process.env["INPUT_APPEND"]; - delete process.env["INPUT_RECREATE"]; - delete process.env["INPUT_DELETE"]; - delete process.env["INPUT_ONLY_CREATE"]; - delete process.env["INPUT_ONLY_UPDATE"]; - delete process.env["INPUT_HIDE"]; - delete process.env["INPUT_HIDE_AND_RECREATE"]; - delete process.env["INPUT_HIDE_CLASSIFY"]; - delete process.env["INPUT_HIDE_DETAILS"]; - delete process.env["INPUT_GITHUB_TOKEN"]; - delete process.env["INPUT_PATH"]; - delete process.env["INPUT_IGNORE_EMPTY"]; - delete process.env["INPUT_SKIP_UNCHANGED"]; - delete process.env["INPUT_FOLLOW_SYMBOLIC_LINKS"]; - delete process.env["INPUT_AUTHOR_LOGIN"]; -}); - -test("repo", async () => { - process.env["INPUT_OWNER"] = "jin"; - process.env["INPUT_REPO"] = "other"; - - mockConfig.repo = { owner: "jin", repo: "other" }; - - const config = await import("../src/config/inputs"); - expect(config).toMatchObject({ - pullRequestNumber: expect.any(Number), - repo: { owner: "jin", repo: "other" }, - header: "", - append: false, - recreate: false, - deleteOldComment: false, - hideOldComment: false, - hideAndRecreate: false, - hideClassify: "OUTDATED", - hideDetails: false, - githubToken: "some-token", - ignoreEmpty: false, - skipUnchanged: false, - }); - expect(await config.getBody()).toEqual(""); -}); - -test("header", async () => { - process.env["INPUT_HEADER"] = "header"; - mockConfig.header = "header"; - - const config = await import("../src/config/inputs"); - expect(config).toMatchObject({ - pullRequestNumber: expect.any(Number), - repo: { owner: "marocchino", repo: "stick-pull-request-comment" }, - header: "header", - append: false, - recreate: false, - deleteOldComment: false, - hideOldComment: false, - hideAndRecreate: false, - hideClassify: "OUTDATED", - hideDetails: false, - githubToken: "some-token", - ignoreEmpty: false, - skipUnchanged: false, - }); - expect(await config.getBody()).toEqual(""); -}); - -test("append", async () => { - process.env["INPUT_APPEND"] = "true"; - mockConfig.append = true; - - const config = await import("../src/config/inputs"); - expect(config).toMatchObject({ - pullRequestNumber: expect.any(Number), - repo: { owner: "marocchino", repo: "stick-pull-request-comment" }, - header: "", - append: true, - recreate: false, - deleteOldComment: false, - hideOldComment: false, - hideAndRecreate: false, - hideClassify: "OUTDATED", - hideDetails: false, - githubToken: "some-token", - ignoreEmpty: false, - skipUnchanged: false, - }); - expect(await config.getBody()).toEqual(""); -}); - -test("recreate", async () => { - process.env["INPUT_RECREATE"] = "true"; - mockConfig.recreate = true; - - const config = await import("../src/config/inputs"); - expect(config).toMatchObject({ - pullRequestNumber: expect.any(Number), - repo: { owner: "marocchino", repo: "stick-pull-request-comment" }, - header: "", - append: false, - recreate: true, - deleteOldComment: false, - hideOldComment: false, - hideAndRecreate: false, - hideClassify: "OUTDATED", - hideDetails: false, - githubToken: "some-token", - ignoreEmpty: false, - skipUnchanged: false, - }); - expect(await config.getBody()).toEqual(""); -}); - -test("delete", async () => { - process.env["INPUT_DELETE"] = "true"; - mockConfig.deleteOldComment = true; - - const config = await import("../src/config/inputs"); - expect(config).toMatchObject({ - pullRequestNumber: expect.any(Number), - repo: { owner: "marocchino", repo: "stick-pull-request-comment" }, - header: "", - append: false, - recreate: false, - deleteOldComment: true, - hideOldComment: false, - hideAndRecreate: false, - hideClassify: "OUTDATED", - hideDetails: false, - githubToken: "some-token", - ignoreEmpty: false, - skipUnchanged: false, - }); - expect(await config.getBody()).toEqual(""); -}); - -test("hideOldComment", async () => { - process.env["INPUT_HIDE"] = "true"; - mockConfig.hideOldComment = true; - - const config = await import("../src/config/inputs"); - expect(config).toMatchObject({ - pullRequestNumber: expect.any(Number), - repo: { owner: "marocchino", repo: "stick-pull-request-comment" }, - header: "", - append: false, - recreate: false, - deleteOldComment: false, - hideOldComment: true, - hideAndRecreate: false, - hideClassify: "OUTDATED", - hideDetails: false, - githubToken: "some-token", - ignoreEmpty: false, - skipUnchanged: false, - }); - expect(await config.getBody()).toEqual(""); -}); - -test("hideAndRecreate", async () => { - process.env["INPUT_HIDE_AND_RECREATE"] = "true"; - mockConfig.hideAndRecreate = true; - - const config = await import("../src/config/inputs"); - expect(config).toMatchObject({ - pullRequestNumber: expect.any(Number), - repo: { owner: "marocchino", repo: "stick-pull-request-comment" }, - header: "", - append: false, - recreate: false, - deleteOldComment: false, - hideOldComment: false, - hideAndRecreate: true, - hideClassify: "OUTDATED", - hideDetails: false, - githubToken: "some-token", - ignoreEmpty: false, - skipUnchanged: false, - }); - expect(await config.getBody()).toEqual(""); -}); - -test("hideClassify", async () => { - process.env["INPUT_HIDE_CLASSIFY"] = "OFF_TOPIC"; - mockConfig.hideClassify = "OFF_TOPIC"; - - const config = await import("../src/config/inputs"); - expect(config).toMatchObject({ - pullRequestNumber: expect.any(Number), - repo: { owner: "marocchino", repo: "stick-pull-request-comment" }, - header: "", - append: false, - recreate: false, - deleteOldComment: false, - hideOldComment: false, - hideAndRecreate: false, - hideClassify: "OFF_TOPIC", - hideDetails: false, - githubToken: "some-token", - ignoreEmpty: false, - skipUnchanged: false, - }); - expect(await config.getBody()).toEqual(""); -}); - -test("hideDetails", async () => { - process.env["INPUT_HIDE_DETAILS"] = "true"; - mockConfig.hideDetails = true; - - const config = await import("../src/config/inputs"); - expect(config).toMatchObject({ - pullRequestNumber: expect.any(Number), - repo: { owner: "marocchino", repo: "stick-pull-request-comment" }, - header: "", - append: false, - recreate: false, - deleteOldComment: false, - hideOldComment: false, - hideAndRecreate: false, - hideClassify: "OUTDATED", - hideDetails: true, - githubToken: "some-token", - ignoreEmpty: false, - skipUnchanged: false, - }); - expect(await config.getBody()).toEqual(""); -}); - -describe("path", () => { - test("when exists return content of a file", async () => { - process.env["INPUT_PATH"] = "./__tests__/assets/result"; - mockConfig.getBody.mockResolvedValue("hi there\n"); - - const config = await import("../src/config/inputs"); - expect(config).toMatchObject({ - pullRequestNumber: expect.any(Number), - repo: { owner: "marocchino", repo: "stick-pull-request-comment" }, - header: "", - append: false, - recreate: false, - deleteOldComment: false, - hideOldComment: false, - hideAndRecreate: false, - hideClassify: "OUTDATED", - hideDetails: false, - githubToken: "some-token", - ignoreEmpty: false, - skipUnchanged: false, - }); - expect(await config.getBody()).toEqual("hi there\n"); - }); - - test("glob match files", async () => { - process.env["INPUT_PATH"] = "./__tests__/assets/*"; - mockConfig.getBody.mockResolvedValue("hi there\n\nhey there\n"); - - const config = await import("../src/config/inputs"); - expect(config).toMatchObject({ - pullRequestNumber: expect.any(Number), - repo: { owner: "marocchino", repo: "stick-pull-request-comment" }, - header: "", - append: false, - recreate: false, - deleteOldComment: false, - hideOldComment: false, - hideAndRecreate: false, - hideClassify: "OUTDATED", - hideDetails: false, - githubToken: "some-token", - ignoreEmpty: false, - skipUnchanged: false, - }); - expect(await config.getBody()).toEqual("hi there\n\nhey there\n"); - }); - - test("when not exists return null string", async () => { - process.env["INPUT_PATH"] = "./__tests__/assets/not_exists"; - mockConfig.getBody.mockResolvedValue(""); - - const config = await import("../src/config/inputs"); - expect(config).toMatchObject({ - pullRequestNumber: expect.any(Number), - repo: { owner: "marocchino", repo: "stick-pull-request-comment" }, - header: "", - append: false, - recreate: false, - deleteOldComment: false, - hideOldComment: false, - hideAndRecreate: false, - hideClassify: "OUTDATED", - hideDetails: false, - githubToken: "some-token", - ignoreEmpty: false, - skipUnchanged: false, - }); - expect(await config.getBody()).toEqual(""); - }); -}); - -test("message", async () => { - process.env["INPUT_MESSAGE"] = "hello there"; - mockConfig.getBody.mockResolvedValue("hello there"); - - const config = await import("../src/config/inputs"); - expect(config).toMatchObject({ - pullRequestNumber: expect.any(Number), - repo: { owner: "marocchino", repo: "stick-pull-request-comment" }, - header: "", - append: false, - recreate: false, - deleteOldComment: false, - hideOldComment: false, - hideAndRecreate: false, - hideClassify: "OUTDATED", - hideDetails: false, - githubToken: "some-token", - ignoreEmpty: false, - skipUnchanged: false, - }); - expect(await config.getBody()).toEqual("hello there"); -}); - -test("ignore_empty", async () => { - process.env["INPUT_IGNORE_EMPTY"] = "true"; - mockConfig.ignoreEmpty = true; - - const config = await import("../src/config/inputs"); - expect(config).toMatchObject({ - pullRequestNumber: expect.any(Number), - repo: { owner: "marocchino", repo: "stick-pull-request-comment" }, - header: "", - append: false, - recreate: false, - deleteOldComment: false, - hideOldComment: false, - hideAndRecreate: false, - hideClassify: "OUTDATED", - hideDetails: false, - githubToken: "some-token", - ignoreEmpty: true, - skipUnchanged: false, - }); - expect(await config.getBody()).toEqual(""); -}); - -test("skip_unchanged", async () => { - process.env["INPUT_SKIP_UNCHANGED"] = "true"; - mockConfig.skipUnchanged = true; - - const config = await import("../src/config/inputs"); - expect(config).toMatchObject({ - pullRequestNumber: expect.any(Number), - repo: { owner: "marocchino", repo: "stick-pull-request-comment" }, - header: "", - append: false, - recreate: false, - deleteOldComment: false, - hideOldComment: false, - hideAndRecreate: false, - hideClassify: "OUTDATED", - hideDetails: false, - githubToken: "some-token", - ignoreEmpty: false, - skipUnchanged: true, - }); - expect(await config.getBody()).toEqual(""); -}); diff --git a/internals/c15t-github-action/__tests__/deployment.test.ts b/internals/c15t-github-action/__tests__/deployment.test.ts deleted file mode 100644 index 79ba498..0000000 --- a/internals/c15t-github-action/__tests__/deployment.test.ts +++ /dev/null @@ -1,149 +0,0 @@ -import * as core from "@actions/core"; -import * as github from "@actions/github"; -import { describe, expect, it, vi } from "vitest"; - -// Import lazily inside tests to allow env setup before module eval - -describe("deployment helpers", () => { - it("resolveBranch prefers head_ref", async () => { - const ctx = github.context as unknown as { - ref?: string; - head_ref?: string; - }; - process.env.GITHUB_REPOSITORY = "owner/repo"; - // stub required inputs - vi.spyOn(core, "getBooleanInput").mockImplementation(() => false); - vi.spyOn(core, "getInput").mockImplementation(() => ""); - ctx.ref = "refs/heads/feature/x"; - ctx.head_ref = "pull/123/head"; - vi.resetModules(); - const { resolveBranch } = await import("../src/steps/deployment"); - expect(resolveBranch()).toBe("pull/123/head"); - }); - - it("computeEnvironmentName", async () => { - process.env.GITHUB_REPOSITORY = "owner/repo"; - vi.spyOn(core, "getBooleanInput").mockImplementation(() => false); - vi.spyOn(core, "getInput").mockImplementation(() => ""); - vi.resetModules(); - const { computeEnvironmentName } = await import("../src/steps/deployment"); - expect(computeEnvironmentName("production", "any")).toBe("production"); - expect(computeEnvironmentName(undefined, "main")).toBe("production"); - expect(computeEnvironmentName(undefined, "feat")).toBe("preview/feat"); - }); - - it("resolveBranch falls back to branch from ref when head_ref absent", async () => { - const ctx = github.context as unknown as { - ref?: string; - head_ref?: string; - }; - process.env.GITHUB_REPOSITORY = "owner/repo"; - vi.spyOn(core, "getBooleanInput").mockImplementation(() => false); - vi.spyOn(core, "getInput").mockImplementation(() => ""); - ctx.head_ref = ""; - ctx.ref = "refs/heads/feature/xyz"; - vi.resetModules(); - const { resolveBranch } = await import("../src/steps/deployment"); - expect(resolveBranch()).toBe("feature/xyz"); - }); - - it("resolveBranch returns raw ref for non-branch refs (document current behavior)", async () => { - const ctx = github.context as unknown as { - ref?: string; - head_ref?: string; - }; - process.env.GITHUB_REPOSITORY = "owner/repo"; - vi.spyOn(core, "getBooleanInput").mockImplementation(() => false); - vi.spyOn(core, "getInput").mockImplementation(() => ""); - ctx.head_ref = ""; - ctx.ref = "refs/tags/v1.2.3"; - vi.resetModules(); - const { resolveBranch } = await import("../src/steps/deployment"); - expect(resolveBranch()).toBe("refs/tags/v1.2.3"); - }); - - describe("target resolution logic", () => { - it("should resolve target to production for main branch when no explicit target provided", async () => { - const ctx = github.context as unknown as { - ref?: string; - head_ref?: string; - }; - process.env.GITHUB_REPOSITORY = "owner/repo"; - vi.spyOn(core, "getBooleanInput").mockImplementation(() => false); - vi.spyOn(core, "getInput").mockImplementation(() => ""); - ctx.ref = "refs/heads/main"; - ctx.head_ref = ""; - vi.resetModules(); - - // No explicit target provided in inputs - - // Test the target resolution logic indirectly through branch resolution - // by testing the branch resolution and target hint calculation - const { resolveBranch } = await import("../src/steps/deployment"); - expect(resolveBranch()).toBe("main"); - - // The target hint logic should set targetHint to 'production' for main branch - // when no explicit target is provided - }); - - it("should resolve target to staging for canary branch when no explicit target provided", async () => { - const ctx = github.context as unknown as { - ref?: string; - head_ref?: string; - }; - process.env.GITHUB_REPOSITORY = "owner/repo"; - vi.spyOn(core, "getBooleanInput").mockImplementation(() => false); - vi.spyOn(core, "getInput").mockImplementation(() => ""); - ctx.ref = "refs/heads/canary"; - ctx.head_ref = ""; - vi.resetModules(); - - const { resolveBranch } = await import("../src/steps/deployment"); - expect(resolveBranch()).toBe("canary"); - - // The target hint logic should set targetHint to 'staging' for canary branch - // when no explicit target is provided - }); - - it("should resolve target to staging for feature branch when no explicit target provided", async () => { - const ctx = github.context as unknown as { - ref?: string; - head_ref?: string; - }; - process.env.GITHUB_REPOSITORY = "owner/repo"; - vi.spyOn(core, "getBooleanInput").mockImplementation(() => false); - vi.spyOn(core, "getInput").mockImplementation(() => ""); - ctx.ref = "refs/heads/feature/new-feature"; - ctx.head_ref = ""; - vi.resetModules(); - - const { resolveBranch } = await import("../src/steps/deployment"); - expect(resolveBranch()).toBe("feature/new-feature"); - - // The target hint logic should set targetHint to 'staging' for feature branches - // when no explicit target is provided - }); - - it("should use explicit target when provided", async () => { - const ctx = github.context as unknown as { - ref?: string; - head_ref?: string; - }; - process.env.GITHUB_REPOSITORY = "owner/repo"; - vi.spyOn(core, "getBooleanInput").mockImplementation(() => false); - // Mock getInput to return 'production' for target - vi.spyOn(core, "getInput").mockImplementation((name) => { - if (name === "target") return "production"; - return ""; - }); - ctx.ref = "refs/heads/canary"; - ctx.head_ref = ""; - vi.resetModules(); - - const { resolveBranch } = await import("../src/steps/deployment"); - expect(resolveBranch()).toBe("canary"); - - // Even though branch is canary, explicit target should be used - }); - }); -}); diff --git a/internals/c15t-github-action/__tests__/errors.test.ts b/internals/c15t-github-action/__tests__/errors.test.ts deleted file mode 100644 index c066d11..0000000 --- a/internals/c15t-github-action/__tests__/errors.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { describe, expect, it, vi } from "vitest"; -import { ErrorHandler, executeWithRetry } from "../src/utils/errors"; - -vi.mock("@actions/core"); - -describe("Error utilities", () => { - it("maps Vercel project not found", () => { - const err = ErrorHandler.handleVercel(new Error("Project not found")); - expect(err.type).toBe("configuration"); - expect(err.retryable).toBe(false); - }); - - it("maps API rate limit", () => { - const err = ErrorHandler.handleVercel(new Error("Rate limit exceeded")); - expect(err.type).toBe("api_limit"); - expect(err.retryable).toBe(true); - }); - - it("retries transient errors and eventually succeeds (no real sleep)", async () => { - const op = vi - .fn() - .mockRejectedValueOnce(new Error("transient")) - .mockRejectedValueOnce(new Error("still transient")) - .mockResolvedValueOnce("ok"); - - const res = await executeWithRetry(() => op(), ErrorHandler.handleVercel, { - maxRetries: 3, - backoffBaseMs: 0, - maxDelayMs: 0, - sleep: async () => {}, - }); - expect(res).toBe("ok"); - expect(op).toHaveBeenCalledTimes(3); - }); - - it("stops retrying on non-retryable errors", async () => { - const op = vi.fn().mockRejectedValue(new Error("Project not found")); - await expect( - executeWithRetry(() => op(), ErrorHandler.handleVercel, 3) - ).rejects.toThrow("Project not found"); - expect(op).toHaveBeenCalledTimes(1); - }); -}); diff --git a/internals/c15t-github-action/__tests__/improvements.test.ts b/internals/c15t-github-action/__tests__/improvements.test.ts deleted file mode 100644 index 7b06215..0000000 --- a/internals/c15t-github-action/__tests__/improvements.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { describe, expect, it } from "vitest"; - -describe("Deployment improvements", () => { - it("demonstrates canonical alias selection logic", () => { - // Simulate the canonical alias logic we improved - const aliases = [ - "first.example.com", - "second.example.com", - "third.example.com", - ]; - let canonicalSet = false; - let canonicalUrl = ""; - - for (const domain of aliases) { - // Simulate successful alias assignment - const success = true; // All succeed in this test - - if (success && !canonicalSet && domain.includes(".")) { - canonicalUrl = `https://${domain}`; - canonicalSet = true; - } - } - - // Should use first alias as canonical - expect(canonicalUrl).toBe("https://first.example.com"); - }); -}); diff --git a/internals/c15t-github-action/__tests__/logger.test.ts b/internals/c15t-github-action/__tests__/logger.test.ts deleted file mode 100644 index a5cc8ee..0000000 --- a/internals/c15t-github-action/__tests__/logger.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import * as core from "@actions/core"; -import { describe, expect, it, vi } from "vitest"; -import { createLogger } from "../src/utils/logger"; - -vi.mock("@actions/core"); - -describe("logger", () => { - it("respects debug flag", () => { - const infoSpy = vi.spyOn(core, "info"); - const logger = createLogger(true); - logger.debug("hello", { k: "v" }); - expect(infoSpy).toHaveBeenCalled(); - }); - - it("logs info/warn/error with metadata", () => { - const infoSpy = vi.spyOn(core, "info"); - const warnSpy = vi.spyOn(core, "warning"); - const errSpy = vi.spyOn(core, "error"); - const logger = createLogger(false); - logger.info("i", { a: 1 }); - logger.warn("w", { b: 2 }); - logger.error("e", { c: 3 }); - expect(infoSpy).toHaveBeenCalled(); - expect(warnSpy).toHaveBeenCalled(); - expect(errSpy).toHaveBeenCalled(); - }); -}); diff --git a/internals/c15t-github-action/__tests__/push-comment.test.ts b/internals/c15t-github-action/__tests__/push-comment.test.ts deleted file mode 100644 index 9b9c740..0000000 --- a/internals/c15t-github-action/__tests__/push-comment.test.ts +++ /dev/null @@ -1,101 +0,0 @@ -import * as core from "@actions/core"; -import * as github from "@actions/github"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; - -// note: inputs module is mocked per-test with vi.doMock + dynamic import - -vi.mock("@actions/core", () => ({ - info: vi.fn(), - warning: vi.fn(), - getBooleanInput: (name: string) => { - if (name === "comment_on_push") { - return true; - } - return false; - }, - getInput: () => "", -})); - -describe("maybeCommentOnPush", () => { - const octokit = github.getOctokit("token"); - - beforeEach(() => { - process.env.GITHUB_REPOSITORY = "owner/repo"; - vi.spyOn(octokit.rest.repos, "createCommitComment").mockResolvedValue({ - status: 201, - url: "https://api.github.local", - headers: {} as Record, - data: { id: 1 } as unknown, - } as Awaited>); - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); - - it("skips when running on a PR (pullRequestNumber is set)", async () => { - vi.resetModules(); - vi.doMock("../src/config/inputs", () => ({ - commentOnPush: true, - pullRequestNumber: 123, - })); - const { maybeCommentOnPush } = await import("../src/steps/push-comment"); - const result = await maybeCommentOnPush( - octokit as unknown as ReturnType, - "body", - "url" - ); - expect(result).toBe(false); - expect(octokit.rest.repos.createCommitComment).not.toHaveBeenCalled(); - }); - - it("returns true if no deployment url", async () => { - vi.resetModules(); - vi.doMock("../src/config/inputs", () => ({ - commentOnPush: true, - pullRequestNumber: Number.NaN, - })); - const { maybeCommentOnPush } = await import("../src/steps/push-comment"); - const ok = await maybeCommentOnPush( - octokit as unknown as ReturnType, - "body" - ); - expect(ok).toBe(true); - expect(core.info).toBeCalled(); - expect(octokit.rest.repos.createCommitComment).not.toHaveBeenCalled(); - }); - - it("includes share/tips sections in auto-rendered body on push", async () => { - vi.resetModules(); - vi.doMock("../src/config/inputs", () => ({ - commentOnPush: true, - pullRequestNumber: Number.NaN, - })); - const { maybeCommentOnPush } = await import("../src/steps/push-comment"); - // Capture body passed to commit comment - const bodySpy = vi - .spyOn(octokit.rest.repos, "createCommitComment") - .mockResolvedValue({ - status: 201, - url: "https://api.github.local", - headers: {} as Record, - data: { id: 2 } as unknown, - } as Awaited>); - await maybeCommentOnPush( - octokit as unknown as ReturnType, - undefined, - "https://preview.example.com" - ); - expect(bodySpy).toHaveBeenCalled(); - const args = bodySpy.mock.calls[0]?.[0] as { body?: string }; - expect( - (args?.body || "").match(/
/g)?.length || 0 - ).toBeGreaterThanOrEqual(2); - expect(args?.body || "").toContain( - "๐Ÿ’™ Share your contribution on social media" - ); - expect(args?.body || "").toContain( - "๐Ÿชง Documentation and Community" - ); - }); -}); diff --git a/internals/c15t-github-action/__tests__/render-comment.test.ts b/internals/c15t-github-action/__tests__/render-comment.test.ts deleted file mode 100644 index 66bfefd..0000000 --- a/internals/c15t-github-action/__tests__/render-comment.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { describe, expect, it, vi } from "vitest"; - -import { renderCommentMarkdown } from "../src/steps/render-comment"; - -const PREVIEW_TABLE_REGEX = - /\| \[Open Preview\]\(https:\/\/example\.com\) \| Skipped \|/; - -describe("renderCommentMarkdown", () => { - it("includes preview table and footer", () => { - const url = "https://example.com"; - const out = renderCommentMarkdown(url); - expect(out).toContain("### Docs Preview"); - expect(out).toContain("[Open Preview](https://example.com)"); - expect(out).toContain("Baked with"); - }); - - it("is deterministic for a given seed/url", () => { - vi.useFakeTimers(); - vi.setSystemTime(new Date("2024-01-01T00:00:00Z")); - const url = "https://deterministic.com"; - const a = renderCommentMarkdown(url, { seed: "seed-123" }); - const b = renderCommentMarkdown(url, { seed: "seed-123" }); - expect(a).toEqual(b); - vi.useRealTimers(); - }); - - it("includes first time contributor message when flagged", () => { - const url = "https://example.com"; - const out = renderCommentMarkdown(url, { firstContribution: true }); - expect(out).toContain("Your first c15t commit"); - }); - - it("debug renders multiple ascii blocks", () => { - const url = "https://example.com"; - const out = renderCommentMarkdown(url, { debug: true }); - // Expect multiple fenced blocks - const fences = out.split("```").length - 1; - expect(fences).toBeGreaterThanOrEqual(4); - }); - - it("renders status field in preview table", () => { - const md = renderCommentMarkdown("https://example.com", { - status: "Skipped", - }); - expect(md).toMatch(PREVIEW_TABLE_REGEX); - }); - - it("uses deterministic seed selection without ternary", () => { - vi.useFakeTimers(); - vi.setSystemTime(new Date("2024-01-01T00:00:00Z")); - - const withSeed = renderCommentMarkdown("https://example.com", { - seed: "test-seed", - }); - const withoutSeed = renderCommentMarkdown("https://example.com"); - - // Both should work without ternary expressions - expect(typeof withSeed).toBe("string"); - expect(typeof withoutSeed).toBe("string"); - expect(withSeed).toContain("### Docs Preview"); - expect(withoutSeed).toContain("### Docs Preview"); - - vi.useRealTimers(); - }); - - it("handles status assignment without ternary", () => { - const withStatus = renderCommentMarkdown("https://example.com", { - status: "Custom Status", - }); - const withoutStatus = renderCommentMarkdown("https://example.com"); - - expect(withStatus).toContain("Custom Status"); - expect(withoutStatus).toContain("Ready"); // default - }); - - it("handles first contribution without ternary expressions", () => { - const firstTime = renderCommentMarkdown("https://example.com", { - firstContribution: true, - }); - const regular = renderCommentMarkdown("https://example.com", { - firstContribution: false, - }); - - expect(firstTime).toContain("Your first c15t commit"); - expect(regular).not.toContain("Your first c15t commit"); - }); -}); diff --git a/internals/c15t-github-action/__tests__/validate.test.ts b/internals/c15t-github-action/__tests__/validate.test.ts deleted file mode 100644 index b884cf2..0000000 --- a/internals/c15t-github-action/__tests__/validate.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { describe, expect, it } from "vitest"; - -describe("Template SHA validation", () => { - it("validates repo format correctly", () => { - const validFormats = ["owner/repo", "org/project", "user/name"]; - const invalidFormats = ["invalid", "", "owner/", "/repo", "owner//repo"]; - - for (const format of validFormats) { - expect(format.includes("/")).toBe(true); - const [owner, name] = format.split("/"); - expect(owner).toBeTruthy(); - expect(name).toBeTruthy(); - } - - for (const format of invalidFormats) { - if (format.includes("/")) { - const [owner, name] = format.split("/"); - expect(!owner || !name).toBe(true); - } else { - expect(format.includes("/")).toBe(false); - } - } - }); -}); - -describe("Installation ID validation", () => { - it("validates installation IDs correctly", () => { - const validIds = ["12345", "67890", "1"]; - const invalidIds = ["0", "-1", "abc", "12.5", "", "invalid"]; - - for (const id of validIds) { - const parsed = Number(id); - expect(Number.isInteger(parsed) && parsed > 0).toBe(true); - } - - for (const id of invalidIds) { - const parsed = Number(id); - expect(Number.isInteger(parsed) && parsed > 0).toBe(false); - } - }); -}); diff --git a/internals/c15t-github-action/__tests__/vercel-client.test.ts b/internals/c15t-github-action/__tests__/vercel-client.test.ts deleted file mode 100644 index ecf57fe..0000000 --- a/internals/c15t-github-action/__tests__/vercel-client.test.ts +++ /dev/null @@ -1,212 +0,0 @@ -import { describe, expect, it, vi } from "vitest"; - -describe("vercel-client target resolution", () => { - describe("resolveTarget", () => { - it("should return production for main branch", async () => { - vi.resetModules(); - const { resolveTarget } = await import("../src/deploy/vercel-client"); - - const env = { - GITHUB_REF: "refs/heads/main", - }; - - expect(resolveTarget(env)).toBe("production"); - }); - - it("should return staging for canary branch", async () => { - vi.resetModules(); - const { resolveTarget } = await import("../src/deploy/vercel-client"); - - const env = { - GITHUB_REF: "refs/heads/canary", - }; - - expect(resolveTarget(env)).toBe("staging"); - }); - - it("should return staging for feature branches", async () => { - vi.resetModules(); - const { resolveTarget } = await import("../src/deploy/vercel-client"); - - const env = { - GITHUB_REF: "refs/heads/feature/new-feature", - }; - - expect(resolveTarget(env)).toBe("staging"); - }); - - it("should return staging for pull request refs", async () => { - vi.resetModules(); - const { resolveTarget } = await import("../src/deploy/vercel-client"); - - const env = { - GITHUB_REF: "refs/pull/123/merge", - }; - - expect(resolveTarget(env)).toBe("staging"); - }); - - it("should return staging when GITHUB_REF is missing", async () => { - vi.resetModules(); - const { resolveTarget } = await import("../src/deploy/vercel-client"); - - const env = {}; - - expect(resolveTarget(env)).toBe("staging"); - }); - }); - - describe("getBranch", () => { - it("should return branch name from GITHUB_REF", async () => { - vi.resetModules(); - const { getBranch } = await import("../src/deploy/vercel-client"); - - const env = { - GITHUB_REF: "refs/heads/feature/branch-name", - }; - - expect(getBranch(env)).toBe("feature/branch-name"); - }); - - it("should prefer GITHUB_HEAD_REF over GITHUB_REF", async () => { - vi.resetModules(); - const { getBranch } = await import("../src/deploy/vercel-client"); - - const env = { - GITHUB_REF: "refs/heads/main", - GITHUB_HEAD_REF: "feature/branch-name", - }; - - expect(getBranch(env)).toBe("feature/branch-name"); - }); - - it("should handle tag refs", async () => { - vi.resetModules(); - const { getBranch } = await import("../src/deploy/vercel-client"); - - const env = { - GITHUB_REF: "refs/tags/v1.2.3", - }; - - expect(getBranch(env)).toBe("v1.2.3"); - }); - - it("should return unknown for unrecognized ref format", async () => { - vi.resetModules(); - const { getBranch } = await import("../src/deploy/vercel-client"); - - const env = { - GITHUB_REF: "refs/unknown/type", - }; - - expect(getBranch(env)).toBe("unknown"); - }); - - it("should return unknown when GITHUB_REF is missing", async () => { - vi.resetModules(); - const { getBranch } = await import("../src/deploy/vercel-client"); - - const env = {}; - - expect(getBranch(env)).toBe("unknown"); - }); - }); - - describe("deployToVercel target handling", () => { - it("should use explicit target when provided", async () => { - vi.resetModules(); - - // Mock the https module to avoid actual network calls - const mockHttpsRequest = vi.fn(); - const mockHttps = { - request: mockHttpsRequest, - }; - - // Mock the fs module - const mockReadFileSync = vi.fn().mockReturnValue('{"name": "test"}'); - const mockExistsSync = vi.fn().mockReturnValue(true); - const mockReaddirSync = vi.fn().mockReturnValue([]); - const mockFs = { - readFileSync: mockReadFileSync, - existsSync: mockExistsSync, - readdirSync: mockReaddirSync, - }; - - // Mock path module - const mockPath = { - resolve: vi.fn().mockReturnValue("/tmp/test"), - join: vi.fn().mockReturnValue("/tmp/test/package.json"), - relative: vi.fn().mockReturnValue("package.json"), - }; - - // Set up environment variables - process.env.GITHUB_REF = "refs/heads/canary"; - process.env.GITHUB_SHA = "abc123"; - process.env.GITHUB_REPOSITORY = "owner/repo"; - process.env.GITHUB_REPOSITORY_OWNER = "owner"; - - // Import after setting up mocks - vi.doMock("node:https", () => mockHttps); - vi.doMock("node:fs", () => mockFs); - vi.doMock("node:path", () => mockPath); - - // no direct import needed here - - // (network layer mocked via request stub) - const mockReq = { - write: vi.fn(), - end: vi.fn(), - on: vi.fn(), - }; - mockHttpsRequest.mockReturnValue(mockReq); - - // Test with explicit target - const options = { - token: "test-token", - projectId: "test-project", - orgId: "test-org", - workingDirectory: "/tmp/test", - target: "production" as const, - }; - - // We can't easily test the full function due to mocking complexity, - // but the target resolution logic should work correctly - expect(options.target).toBe("production"); - }); - - it("should handle empty string target correctly", async () => { - vi.resetModules(); - - // Test the target resolution logic for empty strings - const env = { - GITHUB_REF: "refs/heads/canary", - }; - - const { resolveTarget } = await import("../src/deploy/vercel-client"); - - // Test that resolveTarget works for canary branch - expect(resolveTarget(env)).toBe("staging"); - - // Test that empty string handling works - const target = resolveTarget(env); - expect(target).toBe("staging"); - }); - - it("should handle undefined target correctly", async () => { - vi.resetModules(); - - const env = { - GITHUB_REF: "refs/heads/canary", - }; - - const { resolveTarget } = await import("../src/deploy/vercel-client"); - - // Test that resolveTarget works for canary branch - expect(resolveTarget(env)).toBe("staging"); - - // Test that undefined handling works - const target = resolveTarget(env); - expect(target).toBe("staging"); - }); - }); -}); diff --git a/internals/c15t-github-action/action.yml b/internals/c15t-github-action/action.yml deleted file mode 100644 index 735064b..0000000 --- a/internals/c15t-github-action/action.yml +++ /dev/null @@ -1,215 +0,0 @@ -name: "C15T Docs Preview (Deploy + PR Comment)" -description: "Deploy docs to Vercel and comment PR. Requires 'pull-requests: write' permission to create or update PR comments." -author: "c15t" - -inputs: - # --- Orchestration & gating (to enable minimal workflows) --- - setup_docs: - description: "Fetch and prepare docs template into working_directory (.docs)" - default: "true" - required: false - consent_git_token: - description: "Token to access private docs template repository" - required: false - docs_template_repo: - description: "Owner/repo of docs template to fetch" - default: "consentdotio/runner-docs" - required: false - docs_template_ref: - description: "Branch or ref of docs template" - default: "main" - required: false - only_if_changed: - description: "Skip deploy when no relevant files changed" - default: "false" - required: false - change_globs: - description: "Newline-separated globs to detect relevant changes" - required: false - check_template_changes: - description: "Also deploy when upstream template changed since last deploy" - default: "false" - required: false - template_repo: - description: "Owner/repo to check for template changes (defaults to docs_template_repo)" - required: false - template_ref: - description: "Branch/ref to check for template changes (defaults to docs_template_ref)" - required: false - post_skip_comment: - description: "Post a sticky comment explaining why deploy was skipped" - default: "true" - required: false - skip_message: - description: "Custom message body when deployment is skipped" - required: false - deploy_on_push_branches: - description: "Comma-separated list of branches to deploy on push" - default: "main,canary" - required: false - deploy_on_pr_base_branches: - description: "Comma-separated list of PR base branches to deploy previews" - default: "main,canary" - required: false - - # --- Optional GitHub App authentication (in-action) --- - github_app_id: - description: "GitHub App ID (if set, action will mint an installation token)" - required: false - github_app_private_key: - description: "GitHub App private key (PEM). Use GitHub Actions secrets." - required: false - github_app_installation_id: - description: "Installation ID. If omitted, the action will discover installation for this repo." - required: false - header: - description: "Header to determine if the comment is to be updated, not shown on screen. It can be used when you want to add multiple comments independently at the same time." - default: "" - required: false - append: - description: "Indicate if new comment messages should be appended to previous comment message. Only `true` is allowed. Just skip this item when you don't need it." - default: "false" - required: false - hide_details: - description: "hide summary tags in the previously created comment. Only `true` is allowed. Just skip this item when you don't need it." - default: "false" - required: false - hide: - description: "Minimize the existing comment instead of updating it" - default: "false" - required: false - message: - description: "comment message" - required: false - path: - description: "glob path to file(s) containing comment message" - required: false - # Vercel deployment inputs (optional). When provided, the action will deploy before commenting. - vercel_token: - description: "Vercel API token" - required: false - vercel_project_id: - description: "Vercel Project ID" - required: false - vercel_org_id: - description: "Vercel Org (Team) ID" - required: false - working_directory: - description: "Directory to deploy (docs working directory)" - required: false - default: ".docs" - framework: - description: "Framework for Vercel project settings" - required: false - default: "nextjs" - target: - description: "Deployment target: production|staging" - required: false - canary_alias: - description: "Alias to assign when deploying from a configured branch" - required: false - assign_alias_on_branch: - description: "Branch name on which to assign the alias (empty to disable)" - required: false - alias_domains: - description: "Newline-separated list of alias domains. Supports {{PR_NUMBER}} and {{BRANCH}} templating." - required: false - vercel_args: - description: "Optional vercel CLI-like args. Currently supports -m/--meta key=value pairs to add to meta." - required: false - vercel_scope: - description: "Vercel scope/team slug. If provided, overrides org/team id for API calls." - required: false - comment_on_push: - description: "When true, also create/update a commit comment on push events." - default: "true" - required: true - debug_mode: - description: "Enable verbose debug logging from the action" - default: "false" - required: false - ignore_empty: - description: "Indicates whether to ignore missing or empty messages" - default: "false" - required: false - skip_unchanged: - description: "only update or recreate if message is different from previous. Only `true` is allowed. Just skip this item when you don't need it." - default: "false" - required: false - author_login: - description: "Explicit GitHub login to match when locating the existing sticky comment (e.g. consentdotio). If omitted, the authenticated actor is used." - required: false - follow_symbolic_links: - description: "Indicates whether to follow symbolic links for path" - default: "false" - required: false - number: - description: "pull request number for push event" - required: false - owner: - description: "Another repo owner, If not set, the current repo owner is used by default. Note that when you trying changing a repo, be aware that GITHUB_TOKEN should also have permission for that repository." - required: false - repo: - description: "Another repo name limited use on github enterprise. If not set, the current repo is used by default. Note that When you trying changing a repo, be aware that GITHUB_TOKEN should also use that repo's." - required: false - GITHUB_TOKEN: - description: "The GitHub access token used to create or update the comment (e.g. secrets.GITHUB_TOKEN or a PAT)." - required: true -outputs: - previous_comment_id: - description: "ID of previous comment, if found" - created_comment_id: - description: "ID of newly created comment, if any" - deployment_url: - description: "Vercel deployment URL, when deployment inputs are provided" -runs: - using: "composite" - steps: - - name: Run c15t action (tsx) - shell: bash - working-directory: ${{ github.action_path }} - run: pnpm -w exec tsx "$GITHUB_ACTION_PATH/src/main.ts" - env: - INPUT_SETUP_DOCS: ${{ inputs.setup_docs }} - INPUT_CONSENT_GIT_TOKEN: ${{ inputs.consent_git_token }} - INPUT_DOCS_TEMPLATE_REPO: ${{ inputs.docs_template_repo }} - INPUT_DOCS_TEMPLATE_REF: ${{ inputs.docs_template_ref }} - INPUT_ONLY_IF_CHANGED: ${{ inputs.only_if_changed }} - INPUT_CHANGE_GLOBS: ${{ inputs.change_globs }} - INPUT_CHECK_TEMPLATE_CHANGES: ${{ inputs.check_template_changes }} - INPUT_TEMPLATE_REPO: ${{ inputs.template_repo }} - INPUT_TEMPLATE_REF: ${{ inputs.template_ref }} - INPUT_POST_SKIP_COMMENT: ${{ inputs.post_skip_comment }} - INPUT_SKIP_MESSAGE: ${{ inputs.skip_message }} - INPUT_DEPLOY_ON_PUSH_BRANCHES: ${{ inputs.deploy_on_push_branches }} - INPUT_DEPLOY_ON_PR_BASE_BRANCHES: ${{ inputs.deploy_on_pr_base_branches }} - INPUT_GITHUB_APP_ID: ${{ inputs.github_app_id }} - INPUT_GITHUB_APP_PRIVATE_KEY: ${{ inputs.github_app_private_key }} - INPUT_GITHUB_APP_INSTALLATION_ID: ${{ inputs.github_app_installation_id }} - INPUT_HEADER: ${{ inputs.header }} - INPUT_APPEND: ${{ inputs.append }} - INPUT_HIDE_DETAILS: ${{ inputs.hide_details }} - INPUT_HIDE: ${{ inputs.hide }} - INPUT_MESSAGE: ${{ inputs.message }} - INPUT_PATH: ${{ inputs.path }} - INPUT_VERCEL_TOKEN: ${{ inputs.vercel_token }} - INPUT_VERCEL_PROJECT_ID: ${{ inputs.vercel_project_id }} - INPUT_VERCEL_ORG_ID: ${{ inputs.vercel_org_id }} - INPUT_WORKING_DIRECTORY: ${{ inputs.working_directory }} - INPUT_FRAMEWORK: ${{ inputs.framework }} - INPUT_TARGET: ${{ inputs.target }} - INPUT_CANARY_ALIAS: ${{ inputs.canary_alias }} - INPUT_ASSIGN_ALIAS_ON_BRANCH: ${{ inputs.assign_alias_on_branch }} - INPUT_ALIAS_DOMAINS: ${{ inputs.alias_domains }} - INPUT_VERCEL_ARGS: ${{ inputs.vercel_args }} - INPUT_VERCEL_SCOPE: ${{ inputs.vercel_scope }} - INPUT_COMMENT_ON_PUSH: ${{ inputs.comment_on_push }} - INPUT_DEBUG_MODE: ${{ inputs.debug_mode }} - INPUT_IGNORE_EMPTY: ${{ inputs.ignore_empty }} - INPUT_SKIP_UNCHANGED: ${{ inputs.skip_unchanged }} - INPUT_AUTHOR_LOGIN: ${{ inputs.author_login }} - INPUT_FOLLOW_SYMBOLIC_LINKS: ${{ inputs.follow_symbolic_links }} - INPUT_NUMBER: ${{ inputs.number }} - INPUT_OWNER: ${{ inputs.owner }} - INPUT_REPO: ${{ inputs.repo }} - INPUT_GITHUB_TOKEN: ${{ inputs.GITHUB_TOKEN }} diff --git a/internals/c15t-github-action/package.json b/internals/c15t-github-action/package.json deleted file mode 100644 index f944a76..0000000 --- a/internals/c15t-github-action/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "@c15t/github-action", - "version": "0.1.0", - "private": true, - "description": "C15T Docs Preview: setup, change detection, Vercel deploy, GitHub deployments/status, and sticky PR comments with GitHub App auth.", - "author": "c15t", - "license": "MIT", - "main": "dist/index.js", - "engines": { - "node": ">=18" - }, - "scripts": { - "build": "ncc build src/main.ts -o dist -m", - "check-types": "tsc --noEmit", - "test": "vitest run", - "fmt": "pnpm biome format --write . && pnpm biome check --formatter-enabled=false --linter-enabled=false --write", - "lint": "pnpm biome lint ./src" - }, - "dependencies": { - "@actions/core": "^1.11.1", - "@actions/github": "^6.0.1", - "@actions/glob": "^0.5.0", - "@octokit/auth-app": "^6.1.2", - "@octokit/graphql-schema": "^15.26.0" - }, - "devDependencies": { - "@types/node": "^24.3.0", - "@vercel/ncc": "^0.38.3", - "tsx": "^4.19.3" - } -} diff --git a/internals/c15t-github-action/src/config/inputs.ts b/internals/c15t-github-action/src/config/inputs.ts deleted file mode 100644 index 6ed9c01..0000000 --- a/internals/c15t-github-action/src/config/inputs.ts +++ /dev/null @@ -1,203 +0,0 @@ -/** - * Configuration and input resolution for the c15t GitHub Action. - * - * This module centralizes access to all action inputs (with defaults where - * applicable) and exposes helpers for building the repository target and - * resolving the comment body from either a literal message or file paths. - */ -import { readFileSync } from "node:fs"; -import * as core from "@actions/core"; -import { context } from "@actions/github"; -import { create } from "@actions/glob"; - -// import type { ReportedContentClassifiers } from '@octokit/graphql-schema'; - -/** - * Pull request number to operate on. - * - * Resolved from the GitHub context or the optional `number` input. - */ -const inputNumber = core.getInput("number", { required: false }); -export const pullRequestNumber = - context?.payload?.pull_request?.number ?? - (inputNumber ? Number(inputNumber) : undefined); - -/** True when the PR author appears to be a first-time contributor. */ -export const isFirstTimeContributor = (() => { - const assoc = context?.payload?.pull_request?.author_association || ""; - return assoc === "FIRST_TIMER" || assoc === "FIRST_TIME_CONTRIBUTOR"; -})(); - -/** - * Repository descriptor where the action will run. - */ -export const repo = buildRepo(); -/** Header text appended to the sticky comment marker. */ -export const header = core.getInput("header", { required: false }); -/** Whether to append to the previous body if present. */ -export const append = core.getBooleanInput("append", { required: true }); -/** Whether to close any open
blocks when appending. */ -export const hideDetails = core.getBooleanInput("hide_details", { - required: true, -}); -/** Whether to delete the previous sticky comment before posting. */ -// pruned: recreate/hide/hide_and_recreate/delete/only_* flows for internal use -/** Skip updating when the computed body is unchanged. */ -export const skipUnchanged = core.getBooleanInput("skip_unchanged", { - required: true, -}); -/** GitHub token used to authenticate API requests. */ -export const githubToken = core.getInput("GITHUB_TOKEN", { required: true }); -/** When true, do nothing if the resolved body is empty. */ -export const ignoreEmpty = core.getBooleanInput("ignore_empty", { - required: true, -}); -/** - * Optional explicit author login used to identify the sticky comment author. - * Defaults to the authenticated actor. Set to a fixed value like - * "consentdotio" to ensure we always match that user's comments. - */ -export const authorLogin = core.getInput("author_login", { required: false }); - -/** Vercel token to authenticate API requests. */ -export const vercelToken = core.getInput("vercel_token", { required: false }); -/** Vercel project ID. */ -export const vercelProjectId = core.getInput("vercel_project_id", { - required: false, -}); -/** Vercel org/team ID. */ -export const vercelOrgId = core.getInput("vercel_org_id", { required: false }); -/** Directory to deploy (relative to repo root). */ -export const vercelWorkingDirectory = - core.getInput("working_directory", { required: false }) || ".docs"; -/** Vercel framework name. */ -export const vercelFramework = - core.getInput("framework", { required: false }) || "nextjs"; -/** Explicit target override: production|staging. */ -export const vercelTarget = core.getInput("target", { required: false }) as - | "production" - | "staging" - | ""; -/** Optional alias to assign on matching branch. */ -export const canaryAlias = core.getInput("canary_alias", { required: false }); -/** Branch name which triggers alias assignment. */ -export const aliasOnBranch = core.getInput("assign_alias_on_branch", { - required: false, -}); - -/** Newline-separated alias domains to assign (supports templating). */ -export const aliasDomains = core - .getMultilineInput("alias_domains", { required: false }) - .filter(Boolean); -/** Pass-through vercel args (currently supports -m/--meta pairs). */ -export const vercelArgs = core.getInput("vercel_args", { required: false }); -/** Vercel scope/team slug override. */ -export const vercelScope = core.getInput("vercel_scope", { required: false }); - -/** Also comment on push (commit) events. */ -export const commentOnPush = core.getBooleanInput("comment_on_push", { - required: false, -}); - -/** Enable verbose debug logging */ -export const debugMode = core.getBooleanInput("debug_mode", { - required: false, -}); - -// --- Orchestration & gating (for minimal workflows) -export const setupDocs = core.getBooleanInput("setup_docs", { - required: false, -}); -export const consentGitToken = core.getInput("consent_git_token", { - required: false, -}); -export const docsTemplateRepo = - core.getInput("docs_template_repo", { required: false }) || - "consentdotio/runner-docs"; -export const docsTemplateRef = - core.getInput("docs_template_ref", { required: false }) || "main"; -export const onlyIfChanged = core.getBooleanInput("only_if_changed", { - required: false, -}); -export const changeGlobs = core - .getMultilineInput("change_globs", { required: false }) - .filter(Boolean); -export const checkTemplateChanges = core.getBooleanInput( - "check_template_changes", - { required: false } -); -export const templateRepo = core.getInput("template_repo", { required: false }); -export const postSkipComment = core.getBooleanInput("post_skip_comment", { - required: false, -}); -export const skipMessage = core.getInput("skip_message", { required: false }); -export const deployOnPushBranches = core.getInput("deploy_on_push_branches", { - required: false, -}); -export const deployOnPrBaseBranches = core.getInput( - "deploy_on_pr_base_branches", - { required: false } -); - -// --- GitHub App authentication (optional) -export const githubAppId = core.getInput("github_app_id", { required: false }); -export const githubAppPrivateKey = core.getInput("github_app_private_key", { - required: false, -}); -export const githubAppInstallationId = core.getInput( - "github_app_installation_id", - { required: false } -); - -/** - * Builds the repository descriptor from action inputs and context. - * - * @returns The `{ owner, repo }` tuple used by the GitHub API - */ -function buildRepo(): { repo: string; owner: string } { - return { - owner: core.getInput("owner", { required: false }) || context.repo.owner, - repo: core.getInput("repo", { required: false }) || context.repo.repo, - }; -} - -/** - * Resolves the comment body to post. When `path` inputs are provided, this - * reads and concatenates all matched files; otherwise it returns the `message` - * input value. - * - * @returns The body text to post. Returns an empty string on failure when - * reading files, after logging the failure via `core.setFailed`. - * - * @throws {Error} Underlying filesystem errors are caught and converted into - * a failure via `core.setFailed`, and an empty string is returned instead. - * - * @example - * // In action.yml configuration, provide either: - * // - inputs.message: a literal string, or - * // - inputs.path: one or more globs to read files from - */ -export async function getBody(): Promise { - const pathInput = core.getMultilineInput("path", { required: false }); - const followSymbolicLinks = core.getBooleanInput("follow_symbolic_links", { - required: true, - }); - if (pathInput && pathInput.length > 0) { - try { - const globber = await create(pathInput.join("\n"), { - followSymbolicLinks, - matchDirectories: false, - }); - return (await globber.glob()) - .map((path) => readFileSync(path, "utf-8")) - .join("\n"); - } catch (error) { - if (error instanceof Error) { - core.setFailed(error.message); - } - return ""; - } - } else { - return core.getInput("message", { required: false }); - } -} diff --git a/internals/c15t-github-action/src/deploy/vercel-client.ts b/internals/c15t-github-action/src/deploy/vercel-client.ts deleted file mode 100644 index 204004c..0000000 --- a/internals/c15t-github-action/src/deploy/vercel-client.ts +++ /dev/null @@ -1,386 +0,0 @@ -import { appendFileSync, existsSync, readdirSync, readFileSync } from "node:fs"; -import https from "node:https"; -import path from "node:path"; - -// Precompiled regex for performance -const TOKEN_SPLIT_RE = /'([^']*)'|"([^"]*)"|[^\s]+/gm; -const BRANCH_TPL_RE = /\{\{\s*BRANCH\s*\}\}/g; -const PR_TPL_RE = /\{\{\s*PR_NUMBER\s*\}\}/g; -// Hoisted slugify regexes for performance -const RE_UNDERSCORE_OR_SPACE = /[_\s]+/g; -const RE_NON_WORD_EXCEPT_DASH = /[^\w-]+/g; -const RE_MULTI_DASH = /--+/g; -const RE_LEADING_DASH = /^-+/; -const RE_TRAILING_DASH = /-+$/; - -/** - * @internal - * Deploy to Vercel v13 API with optional alias assignment via v2 API. - * This module is bundled inside the GitHub Action to avoid external scripts. - */ - -export type DeployTarget = "production" | "staging"; - -interface VercelDeployOptions { - token: string; - projectId: string; - orgId: string; - workingDirectory: string; - framework?: string; - /** - * Target can be provided explicitly. If omitted, it will be resolved as - * production when branch is `main`, otherwise staging. - */ - target?: DeployTarget; - /** Optional alias domain to assign (e.g. canary.c15t.com). */ - aliasDomain?: string; - /** Branch name that must match to assign the alias (e.g. canary). */ - aliasBranch?: string; - /** Multiple alias domains (templating supported, e.g. {{PR_NUMBER}}, {{BRANCH}}). */ - aliasDomains?: string[]; - /** Raw vercel-like args string to parse for -m/--meta key=value pairs. */ - vercelArgs?: string; - /** Optional Vercel scope/team slug to include in metadata. */ - vercelScope?: string; -} - -interface VercelDeployResult { - url: string; - id?: string; -} - -/** - * Determine the current branch from GitHub env vars. - * @param env - Environment variables - */ -export function getBranch(env: NodeJS.ProcessEnv): string { - const refEnv = env.GITHUB_REF || ""; - const headRef = env.GITHUB_HEAD_REF || ""; - if (headRef) { - return headRef; - } - if (refEnv.startsWith("refs/heads/")) { - return refEnv.replace("refs/heads/", ""); - } - if (refEnv.startsWith("refs/tags/")) { - return refEnv.replace("refs/tags/", ""); - } - return "unknown"; -} - -function chooseLockfile(cwd: string): string | undefined { - const candidates = ["pnpm-lock.yaml", "yarn.lock", "package-lock.json"]; - return candidates.find((f) => existsSync(path.join(cwd, f))); -} - -function fileShouldBeIgnored( - fileName: string, - chosenLockfile: string | undefined, - ignoreFiles: Set -): boolean { - if (chosenLockfile && fileName === chosenLockfile) { - return false; - } - return ignoreFiles.has(fileName); -} - -function walkFiles(cwd: string): string[] { - const ignoreDirs = new Set([ - "node_modules", - ".git", - ".next", - ".vercel", - "out", - "dist", - "build", - ".cache", - ".turbo", - ]); - const ignoreFiles = new Set([ - "pnpm-lock.yaml", - "yarn.lock", - "package-lock.json", - ]); - const chosenLockfile = chooseLockfile(cwd); - - function walk(dir: string): string[] { - const entries = readdirSync(dir, { withFileTypes: true }); - const out: string[] = []; - for (const entry of entries) { - if (entry.name.startsWith(".git")) { - continue; - } - const fullPath = path.join(dir, entry.name); - const relativePath = path.relative(cwd, fullPath); - if (entry.isDirectory()) { - if (ignoreDirs.has(entry.name)) { - continue; - } - out.push(...walk(fullPath)); - } else if (entry.isFile()) { - if (fileShouldBeIgnored(entry.name, chosenLockfile, ignoreFiles)) { - continue; - } - out.push(relativePath); - } - } - return out; - } - - const filesList = walk(cwd); - if ( - !filesList.includes("package.json") && - existsSync(path.join(cwd, "package.json")) - ) { - filesList.push("package.json"); - } - if ( - chosenLockfile && - !filesList.includes(chosenLockfile) && - existsSync(path.join(cwd, chosenLockfile)) - ) { - filesList.push(chosenLockfile); - } - return filesList; -} - -export function resolveTarget(env: NodeJS.ProcessEnv): DeployTarget { - if (env.GITHUB_REF === "refs/heads/main") { - return "production"; - } - return "staging"; -} - -function slugify(input: string): string { - return input - .toString() - .trim() - .toLowerCase() - .replace(RE_UNDERSCORE_OR_SPACE, "-") - .replace(RE_NON_WORD_EXCEPT_DASH, "") - .replace(RE_MULTI_DASH, "-") - .replace(RE_LEADING_DASH, "") - .replace(RE_TRAILING_DASH, ""); -} - -function parseMetaArgs(raw: string | undefined): Record { - if (!raw) { - return {}; - } - const meta: Record = {}; - const tokens = raw.match(TOKEN_SPLIT_RE) ?? []; - for (let i = 0; i < tokens.length; i++) { - const token = tokens[i]; - if (token === "-m" || token === "--meta") { - const kv = tokens[i + 1] ?? ""; - i++; - const eq = kv.indexOf("="); - if (eq > 0) { - const k = kv.slice(0, eq); - const v = kv.slice(eq + 1).replace(/^"|"$/g, ""); - meta[k] = v; - } - } - } - return meta; -} - -function templateAliasDomains(domains: string[] | undefined): string[] { - if (!domains || domains.length === 0) { - return []; - } - const prNumber = process.env.GITHUB_PR_NUMBER || ""; - const isPr = Boolean(prNumber); - const ref = process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF || ""; - const branch = slugify(ref.replace("refs/heads/", "")); - return domains - .map((d) => d.replace(BRANCH_TPL_RE, branch)) - .map((d) => (isPr ? d.replace(PR_TPL_RE, prNumber) : d)) - .filter((d) => !d.includes("{{")); -} - -/** - * Deploys the directory at `workingDirectory` to Vercel using the v13 API. - * Optionally assigns an alias domain if `aliasDomain` and `aliasBranch` match. - * - * @param options - Deployment parameters - * @returns Deployment result including public URL - * @throws {Error} When HTTP requests fail or URL is missing in the response - */ -export async function deployToVercel( - options: VercelDeployOptions -): Promise { - const cwd = path.resolve(options.workingDirectory || "."); - const branch = getBranch(process.env); - let target: DeployTarget; - if (typeof options.target === "string" && options.target.trim() !== "") { - target = options.target; - } else { - target = resolveTarget(process.env); - } - - const repo = process.env.GITHUB_REPOSITORY || ""; - const owner = process.env.GITHUB_REPOSITORY_OWNER || ""; - const sha = process.env.GITHUB_SHA || ""; - - const commitMessage = process.env.GITHUB_COMMIT_MESSAGE || ""; - const commitAuthorName = process.env.GITHUB_COMMIT_AUTHOR_NAME || ""; - const commitAuthorLogin = - process.env.GITHUB_COMMIT_AUTHOR_LOGIN || process.env.GITHUB_ACTOR || ""; - // Email is intentionally omitted from metadata to avoid PII leakage - const prNumber = process.env.GITHUB_PR_NUMBER || ""; - const prHeadRef = - process.env.GITHUB_PR_HEAD_REF || process.env.GITHUB_HEAD_REF || ""; - const prBaseRef = - process.env.GITHUB_PR_BASE_REF || process.env.GITHUB_BASE_REF || ""; - const includePrMeta = prNumber.trim().length > 0; - - const filesList = walkFiles(cwd); - const files = filesList.map((file) => { - const data = readFileSync(path.join(cwd, file)); - return { - file: file.replace(/\\/g, "/"), - data: data.toString("base64"), - encoding: "base64" as const, - }; - }); - - const additionalMeta = parseMetaArgs(options.vercelArgs); - const body = JSON.stringify({ - name: "c15t", - project: options.projectId, - target, - files, - meta: { - githubCommitRef: branch, - githubCommitSha: sha, - githubRepo: repo.split("/")[1] || "", - githubOrg: owner || "", - githubCommitMessage: commitMessage, - githubCommitAuthorName: commitAuthorName, - githubCommitAuthorLogin: commitAuthorLogin, - // githubCommitAuthorEmail intentionally omitted - source: "github", - ...(options.vercelScope ? { githubScope: options.vercelScope } : {}), - ...(includePrMeta - ? { - githubPrNumber: prNumber, - ...(prHeadRef ? { githubPrHeadRef: prHeadRef } : {}), - ...(prBaseRef ? { githubPrBaseRef: prBaseRef } : {}), - } - : {}), - ...additionalMeta, - }, - projectSettings: { - framework: options.framework || "nextjs", - }, - }); - - const requestPath = `/v13/deployments?teamId=${encodeURIComponent(options.orgId)}`; - - const resText: string = await new Promise((resolve, reject) => { - const req = https.request( - { - hostname: "api.vercel.com", - path: requestPath, - method: "POST", - headers: { - Authorization: `Bearer ${options.token}`, - "Content-Type": "application/json", - "Content-Length": Buffer.byteLength(body), - }, - }, - (res) => { - const chunks: Buffer[] = []; - res.on("data", (d) => chunks.push(d)); - res.on("end", () => { - const txt = Buffer.concat(chunks).toString("utf8"); - if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) { - return resolve(txt); - } - return reject( - new Error(`Vercel API error: ${res.statusCode}\n${txt}`) - ); - }); - } - ); - req.on("error", (err) => reject(err)); - req.write(body); - req.end(); - }); - - const json = JSON.parse(resText) as { url?: string; id?: string }; - let url = json.url ? `https://${json.url}` : ""; - if (!url) { - throw new Error("Vercel did not return a deployment URL."); - } - - // Assign alias if configured and branch matches. Support multiple domains with templating. - const allAliases = [ - ...(options.aliasDomain ? [options.aliasDomain] : []), - ...templateAliasDomains(options.aliasDomains), - ]; - if ( - allAliases.length && - options.aliasBranch && - getBranch(process.env) === options.aliasBranch && - json.id - ) { - let canonicalSet = false; - for (const domain of allAliases) { - try { - await new Promise((resolve, reject) => { - const aliasBody = JSON.stringify({ alias: domain }); - const req = https.request( - { - hostname: "api.vercel.com", - path: `/v2/deployments/${encodeURIComponent(json.id as string)}/aliases?teamId=${encodeURIComponent(options.orgId)}`, - method: "POST", - headers: { - Authorization: `Bearer ${options.token}`, - "Content-Type": "application/json", - "Content-Length": Buffer.byteLength(aliasBody), - }, - }, - (res) => { - const chunks: Buffer[] = []; - res.on("data", (d) => chunks.push(d)); - res.on("end", () => { - const txt = Buffer.concat(chunks).toString("utf8"); - if ( - res.statusCode && - res.statusCode >= 200 && - res.statusCode < 300 - ) { - resolve(); - return; - } - reject( - new Error( - `Failed to alias domain ${domain}: ${res.statusCode}\n${txt}` - ) - ); - }); - } - ); - req.on("error", (err) => reject(err)); - req.write(aliasBody); - req.end(); - }); - // Prefer the first alias as the canonical URL - if (!canonicalSet && domain?.includes(".")) { - url = `https://${domain}`; - canonicalSet = true; - } - } catch { - // ignore alias errors and continue - } - } - } - - if (process.env.GITHUB_OUTPUT) { - appendFileSync(process.env.GITHUB_OUTPUT, `url=${url}\n`); - appendFileSync(process.env.GITHUB_OUTPUT, `deployment_url=${url}\n`); - } - return { url, id: json.id }; -} diff --git a/internals/c15t-github-action/src/github/pr-comment.ts b/internals/c15t-github-action/src/github/pr-comment.ts deleted file mode 100644 index 8134fd9..0000000 --- a/internals/c15t-github-action/src/github/pr-comment.ts +++ /dev/null @@ -1,200 +0,0 @@ -/** - * Utilities for discovering and managing a sticky pull request comment. - * - * This module encapsulates all direct interactions with GitHub's GraphQL - * and REST APIs that are needed by the action. Operations include finding an - * existing comment, creating, updating, deleting, minimizing, and extracting - * or transforming bodies with a persistent header marker so we can detect the - * correct comment in subsequent runs. - */ -import * as core from "@actions/core"; -import type { GitHub } from "@actions/github/lib/utils"; -import type { IssueComment, Repository, User } from "@octokit/graphql-schema"; - -// Precompiled regexes -const RE_BOT_SUFFIX = /\[bot\]$/i; -const RE_DETAILS_OPEN = /()/g; - -type CreateCommentResponse = Awaited< - ReturnType["rest"]["issues"]["createComment"]> ->; - -function autoStart(header: string): string { - const key = (header || "runner-docs-preview").trim() || "runner-docs-preview"; - return ``; -} - -function autoEnd(header: string): string { - const key = (header || "runner-docs-preview").trim() || "runner-docs-preview"; - return ``; -} - -function bodyWithHeader(body: string, header: string): string { - return [autoStart(header), body, autoEnd(header)].join("\n"); -} - -function bodyWithoutHeader(body: string, header: string): string { - const start = autoStart(header); - const end = autoEnd(header); - const i = body.indexOf(start); - const j = body.indexOf(end); - if (i !== -1 && j !== -1) { - // Return inner content only, markers excluded - return body.substring(i + start.length, j).trim(); - } - return ""; -} - -export async function findPreviousComment( - octokit: InstanceType, - repo: { owner: string; repo: string }, - number: number, - header: string, - authorLogin?: string -): Promise { - let after: string | null = null; - let hasNextPage = true; - const start = autoStart(header); - while (hasNextPage) { - const data = await octokit.graphql<{ - repository: Repository; - viewer: User; - }>( - ` - query($repo: String! $owner: String! $number: Int! $after: String) { - viewer { login } - repository(name: $repo owner: $owner) { - pullRequest(number: $number) { - comments(first: 100 after: $after) { - nodes { id author { login } isMinimized body } - pageInfo { endCursor hasNextPage } - } - } - } - } - `, - { ...repo, after, number } - ); - - const viewer = data.viewer as User; - const repository = data.repository as Repository; - const normalizeLogin = (login: string | null | undefined): string => - (login ?? "").replace(RE_BOT_SUFFIX, "").trim().toLowerCase(); - const expectedLogin = normalizeLogin(authorLogin ?? viewer.login); - const target = repository.pullRequest?.comments?.nodes?.find( - (node: IssueComment | null | undefined) => - normalizeLogin(node?.author?.login) === expectedLogin && - !node?.isMinimized && - Boolean(node?.body?.includes(start)) - ); - if (target) { - return target; - } - after = repository.pullRequest?.comments?.pageInfo?.endCursor ?? null; - hasNextPage = - repository.pullRequest?.comments?.pageInfo?.hasNextPage ?? false; - } - return undefined; -} - -export async function updateComment( - octokit: InstanceType, - id: string, - body: string, - header: string, - previousBody?: string -): Promise { - if (!body && !previousBody) { - return core.warning("Comment body cannot be blank"); - } - let rawPreviousBody = ""; - if (previousBody) { - rawPreviousBody = bodyWithoutHeader(previousBody, header); - } - await octokit.graphql( - ` - mutation($input: UpdateIssueCommentInput!) { - updateIssueComment(input: $input) { issueComment { id body } } - } - `, - { - input: { - id, - body: previousBody - ? bodyWithHeader(`${rawPreviousBody}\n${body}`, header) - : bodyWithHeader(body, header), - }, - } - ); -} - -export async function createComment( - octokit: InstanceType, - repo: { owner: string; repo: string }, - issue_number: number, - body: string, - header: string, - previousBody?: string -): Promise { - if (!body && !previousBody) { - core.warning("Comment body cannot be blank"); - return; - } - let rawPreviousBody = ""; - if (previousBody) { - rawPreviousBody = bodyWithoutHeader(previousBody, header); - } - let composed = bodyWithHeader(body, header); - if (previousBody) { - composed = bodyWithHeader(`${rawPreviousBody}\n${body}`, header); - } - return await octokit.rest.issues.createComment({ - ...repo, - issue_number, - body: composed, - }); -} - -export async function deleteComment( - octokit: InstanceType, - id: string -): Promise { - await octokit.graphql( - ` - mutation($id: ID!) { deleteIssueComment(input: { id: $id }) { clientMutationId } } - `, - { id } - ); -} - -export function getBodyOf( - previous: { body?: string }, - append: boolean, - hideDetails: boolean -): string | undefined { - if (!append) { - return undefined; - } - if (!hideDetails || !previous.body) { - return previous.body; - } - return previous.body.replace(RE_DETAILS_OPEN, "$1$2"); -} - -export function commentsEqual( - body: string, - previous: string | undefined, - header: string -): boolean { - const start = autoStart(header); - const end = autoEnd(header); - const normalize = (s: string): string => { - const i = s.indexOf(start); - const j = s.indexOf(end); - if (i !== -1 && j !== -1) { - return s.substring(i + start.length, j).trim(); - } - return s; - }; - return normalize(body) === normalize(previous || ""); -} diff --git a/internals/c15t-github-action/src/main.ts b/internals/c15t-github-action/src/main.ts deleted file mode 100644 index e80db8a..0000000 --- a/internals/c15t-github-action/src/main.ts +++ /dev/null @@ -1,182 +0,0 @@ -/** - * @packageDocumentation - * Entry point for the c15t GitHub Action that manages the docs-preview - * lifecycle: orchestrates a Vercel deploy (or gated skip), renders the - * preview body, and ensures a sticky PR comment with append semantics. - * - * The behavior is configured via inputs read in `config/inputs` and the - * PR comment operations are implemented in `steps/comments`. - * - * @see `./config/inputs` - * @see `./steps/comments` - */ -import * as core from "@actions/core"; -import * as github from "@actions/github"; -import { - commentOnPush, - debugMode, - getBody, - githubAppId, - githubAppInstallationId, - githubAppPrivateKey, - githubToken, - isFirstTimeContributor, - postSkipComment, - pullRequestNumber, - skipMessage, -} from "./config/inputs"; -import { ensureComment } from "./steps/comments"; -import { performVercelDeployment } from "./steps/deployment"; -import { getAuthToken } from "./steps/github-app-auth"; -import { maybeCommentOnPush } from "./steps/push-comment"; -import { renderCommentMarkdown } from "./steps/render-comment"; -import { ErrorHandler, executeWithRetry } from "./utils/errors"; -import { createLogger } from "./utils/logger"; - -function computeEffectiveBody( - deploymentUrl: string | undefined, - body: string -): string { - let base = body; - if (deploymentUrl && !body) { - base = renderCommentMarkdown(deploymentUrl, { - firstContribution: isFirstTimeContributor, - }); - } - return base; -} - -/** - * Runs the action's main workflow. - * - * The workflow is: - * - Validate configuration (mutually exclusive options and required inputs). - * - Resolve the comment body from message or files. - * - Find an existing sticky comment on the PR (if any). - * - Perform the requested operation (create/update/minimize/delete/recreate). - * - * It sets the following outputs when applicable: - * - `previous_comment_id`: ID of the found previous comment (if any) - * - `created_comment_id`: ID of a newly created comment (when created) - * - * @returns A promise that resolves with `undefined` when the workflow - * finishes. The function uses `@actions/core` to signal failures. - * - * @throws {Error} When invalid combinations of options are provided, - * such as `delete` with `recreate`, `only_create` with `only_update`, - * or `hide` with `hide_and_recreate`. - * - * @example - * // Typical execution is handled by the GitHub Actions runtime. For - * // local reasoning/testing, just call run(): - * await (async () => { await run(); })(); - */ - -async function run(): Promise { - try { - const logger = createLogger(Boolean(debugMode)); - logger.info("start", { - event: github.context.eventName, - ref: github.context.ref, - sha: github.context.sha, - }); - const token = await getAuthToken( - githubToken, - githubAppId, - githubAppPrivateKey, - githubAppInstallationId - ); - let authKind = "default-token"; - if (githubAppId && githubAppPrivateKey) { - authKind = "github-app"; - } - logger.info("auth", { kind: authKind }); - const octokit = github.getOctokit(token); - logger.info("orchestrating deploy"); - const deploymentUrl = await executeWithRetry( - () => performVercelDeployment(octokit), - ErrorHandler.handleVercel, - 3 - ); - if (deploymentUrl) { - logger.info("deployment ready", { url: deploymentUrl }); - } - if (!deploymentUrl) { - // Deployment was skipped by policy/gating. Optionally post a sticky skip comment. - if (postSkipComment) { - // Try to find the most recent successful deployment URL from repo deployments - let lastUrl = ""; - try { - const { data } = await octokit.rest.repos.listDeployments({ - ...github.context.repo, - per_page: 10, - }); - for (const d of data) { - const statuses = await octokit.rest.repos.listDeploymentStatuses({ - ...github.context.repo, - deployment_id: d.id, - per_page: 1, - }); - const envUrl = statuses.data[0]?.environment_url || ""; - if (statuses.data[0]?.state === "success" && envUrl) { - lastUrl = envUrl; - break; - } - } - } catch { - // ignore lookup errors - } - logger.info("deployment skipped", { lastUrl: lastUrl || "n/a" }); - const rendered = renderCommentMarkdown( - lastUrl || "https://vercel.com", - { - firstContribution: isFirstTimeContributor, - status: "Skipped", - } - ); - const body = skipMessage || rendered; - // Post as PR sticky comment when running in a PR; otherwise, if enabled, post a commit comment on push - if (typeof pullRequestNumber === "number" && pullRequestNumber >= 1) { - logger.info("posting sticky PR skip comment"); - await ensureComment(octokit, body, { appendOverride: false }); - } else if (commentOnPush) { - logger.info("posting commit skip comment on push"); - try { - await octokit.rest.repos.createCommitComment({ - ...github.context.repo, - commit_sha: github.context.sha, - body, - }); - } catch (e) { - core.warning( - `Could not post commit skip comment: ${ - e instanceof Error ? e.message : String(e) - }` - ); - } - } - } - return; - } - const body = await getBody(); - const effectiveBody = computeEffectiveBody(deploymentUrl, body); - const handled = await maybeCommentOnPush( - octokit, - effectiveBody, - deploymentUrl - ); - if (handled) { - logger.info("handled push commit comment; exiting"); - return; - } - logger.info("validating options"); - await ensureComment(octokit, effectiveBody, { appendOverride: true }); - logger.info("ensured PR sticky comment with deployment link"); - } catch (error) { - if (error instanceof Error) { - core.setFailed(error.message); - } - } -} - -run(); diff --git a/internals/c15t-github-action/src/steps/ascii-art.ts b/internals/c15t-github-action/src/steps/ascii-art.ts deleted file mode 100644 index cb3c0d8..0000000 --- a/internals/c15t-github-action/src/steps/ascii-art.ts +++ /dev/null @@ -1,176 +0,0 @@ -/** - * Weighted ASCII-art entry used by the PR comment UI. - * - * The `art` string is a fully composed block (lines joined by `\n`). - * `weight` controls selection probability when sampling from the set. - * Use small positive integers (>= 1); a default of `1` is typical. - * - * @see ASCII_SET - * @example - * // Access the first entry and print its art - * console.log(ASCII_SET[0].art); - */ -export interface WeightedAsciiArt { - art: string; - weight: number; -} - -/** - * Catalog of weighted ASCII art used in docs preview comments. - * - * - Each entry is immutable and satisfies `WeightedAsciiArt`. - * - Selection is weighted by `weight` (non-negative integers recommended). - * - The array is readonly; consumers must not mutate it. - * - * @see WeightedAsciiArt - */ -export const ASCII_SET = [ - { - art: [ - "โ €โ €โ €โ €โ €โ €_____โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €_____", - "โ €โ €---'โ €โ €โ €__\\______โ €โ €โ €โ €โ €โ €______/__โ €โ €โ €`---", - "โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €______)โ €โ €โ €โ €(______", - "โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €__)โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €(__", - "โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €__)โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €(__", - "โ €โ €---.______)โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €(______.---", - "", - "Allโ €signsโ €pointโ €toโ€ฆโ €yourโ €docsโ €updateโ €isโ €ready", - ].join("\n"), - weight: 1, - }, - // { - // art: [ - // 'โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €_โ €โ €โ €โ €โ €โ €โ €โ €โ €', - // 'โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €_(โ €โ €)โ €โ €โ €โ €โ €โ €I just served up', - // 'โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €(โ €โ €โ €)โ €)โ €โ €โ €โ €โ €your docs,', - // 'โ €โ €_โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €(โ €โ €โ €โ €_)โ €โ €โ €โ €โ €โ €โ €โ €', - // 'โ €(โ €โ €)โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €(โ €(โ €โ €)โ €โ €โ €โ €โ €chefโ€™s kiss.', - // 'โ €(โ €โ €โ €)โ €โ €โ €โ €โ €โ €_โ €,-.โ €_โ €โ €โ €โ €โ €)โ €โ €)โ €โ €โ €โ €โ €(โ €โ €โ €โ €โ €โ €', - // "โ €โ €(โ €(โ €โ €,-'''โ €โ €)_(โ €โ €```-.,'โ €โ €โ €โ €โ €โ €โ €โ €โ €)โ €โ €", - // 'โ €โ €โ €โ €`-(โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €)โ €โ €โ €โ €โ €โ €โ €โ €)โ €(โ €โ €โ €', - // "โ €โ €โ €โ €__|`-..._______...-'|__โ €โ €โ €โ €โ €โ €โ €(โ €โ €)โ €โ €", - // 'โ €โ €โ €[===โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €|==]โ €โ €โ €โ €____(___โ €โ €โ €โ €โ €โ €โ €โ €__,.--.', - // "โ €โ €โ €โ €โ €โ €|โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €|__,-_''.------.``_-.-''__,.--'", - // "โ €โ €โ €โ €โ €โ €|โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €|โ €((โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €))-'โ €/โ €|โ €โ €โ €", - // "โ €โ €โ €โ €โ €โ €|โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €|โ €โ €\\`-...______...-'/โ €/โ €/โ €|โ €โ €โ €", - // "โ €โ €โ €โ €โ €โ €`-..._______...-'โ €โ €โ €โ €`-...______...-'โ €/โ €/โ €|โ €โ €โ €", - // "โ €โ €โ €โ €โ €/โ €/`-โ €,-----.โ €-'โ €โ €โ €โ €โ €โ €โ €โ €`-โ €,----.โ €-'โ €โ €/โ €/โ €|โ €โ €โ €", - // 'โ €โ €โ €โ €โ €/โ €/___________________________________/โ €/โ €โ €|โ €โ €โ €', - // "โ €โ €โ €โ €`---------------------------------------'โ €โ €โ €|โ €โ €โ €", - // 'โ €โ €โ €โ €|โ €โ €โ €__________________________________|โ €|โ €โ €โ €', - // 'โ €โ €โ €โ €|โ €โ €|โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €|โ €โ €|โ €โ €โ €โ €โ €|โ €โ €โ €', - // 'โ €โ €โ €โ €|โ €โ €|___-_-___-___-_-___-_-_____-_-___|โ €โ €|โ €โ €โ €โ €โ €|โ €โ €โ €', - // 'โ €โ €โ €โ €|โ €(___________________________________)โ €|โ €โ €โ €โ €โ €', - // 'โ €โ €โ €โ €|โ €โ €|โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-|โ €โ €|โ €โ €โ €โ €โ €', - // 'โ €โ €โ €โ €|โ €โ €|โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-โ €-|โ €โ €|โ €โ €โ €โ €โ €', - // ].join('\n'), - // weight: 1, - // }, - - { - art: [ - "โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €150xโ €โ €โ €โ €โ €300x", - "โ €โ €โ €โ €โ €100xโ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €600x", - "", - "โ €โ €โ €80xโ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €1200x", - "", - "โ €60xโ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €2400x", - 'โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €(")', - "40xโ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €\\โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €5000x", - "โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €\\", - "โ €20xโ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €\\โ €โ €โ €โ €โ €โ €โ €โ €5000x", - "โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €vibesโ €โ €\\", - "โ €โ €โ €โ €0xโ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €\\โ €โ €โ €โ €โ €1000000x", - "", - "โ €โ €weโ €cantโ €keepโ €upโ €withโ €theseโ €vibes", - ].join("\n"), - weight: 1, - }, - { - art: [ - "โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €_โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €", - "โ €โ €โ €โ €โ €โ €โ €|โ €โ €โ €(_)โ €โ €โ €โ €|โ €โ €โ €โ €โ €โ €", - "โ €โ €โ €โ €โ €โ €|โ €โ €โ €โ €_)โ €โ €|โ €โ €โ €โ €โ €โ €โ €โ €โ €", - "โ €โ €(โ €โ €โ €โ €โ €โ €โ €(โ €โ €โ €โ €โ €|โ €โ €โ €โ €โ €โ €โ €โ €", - 'โ €โ €โ €(โ €โ €_.-"""-._โ €โ €โ €โ €)โ €โ €โ €โ €โ €', - "โ €โ €(โ €.'โ €โ €โ €โ €โ €โ €โ €โ €โ €`.โ €โ €โ €)โ €โ €โ €โ €", - "โ €โ €โ €/โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €\\โ €โ €)โ €โ €โ €โ €", - "โ €โ €|_=_=_=_=_=_=_=_|โ €โ €โ €โ €โ €โ €", - "โ €โ €โ €\\โ €โ €'โ €โ €โ €โ €โ €โ €โ €'โ €โ €/โ €โ €โ €โ €โ €โ €โ €", - "โ €โ €โ €โ €\\โ €`:โ €โ €โ €โ €โ €:'โ €/โ €โ €โ €โ €โ €โ €โ €โ €", - "โ €โ €โ €โ €โ €\\โ €`:โ €โ €โ €:'โ €/โ €โ €โ €", - "โ €โ €โ €โ €โ €โ €\\โ €`:โ €:'โ €/โ €โ €", - "โ €โ €โ €โ €โ €โ €โ €\\โ €`:'โ €/โ €โ €", - "โ €โ €โ €โ €โ €โ €โ €โ €\\`'/โ €โ €'โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €", - "โ €โ €โ €โ €โ €โ €โ €โ €__||__โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €", - "โ €โ €โ €โ €โ €โ €โ €|โ €docsโ €|", - "โ €โ €โ €โ €โ €โ €โ €'------'", - "yourโ €docsโ €areโ €droppingโ €hot", - ].join("\n"), - weight: 1, - }, - { - art: [ - "โ €โ €__", - "โ €(`/\\", - "โ €`=\\/\\โ €__...--~~~~~-._โ €โ €โ €_.-~~~~~--...__", - "โ €โ €`=\\/\\โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €\\โ €/โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €\\\\", - "โ €โ €โ €`=\\/โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ € โ €โ €Vโ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €\\\\", - "โ €โ €โ €//_\\___--~~~~~~-._โ €โ €|โ €โ €_.-~~~~~~--...__\\\\", - "โ €โ €//โ €โ €)โ €(..----~~~~._\\โ €|โ €/_.~~~~----.....__\\\\", - "โ €===================\\\\|//====================", - "", - "Iโ €justโ €readโ €yourโ €latestโ €docsโ €update,โ €youโ€™reโ €aโ €realโ €pageโ €turner.", - ].join("\n"), - weight: 1, - }, - { - art: [ - "โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €/โ €\\", - "โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €/โ €_โ €\\", - "โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €|.oโ €'.|", - "โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €|'._.'|", - "โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €|โ €โ €โ €โ €โ €|", - "โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €,'|โ €โ €|โ €โ €|`.", - "โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €/โ €โ €|โ €โ €|โ €โ €|โ €โ €\\", - "โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €|,-'--|--'-.|", - "I just launched your docs update into orbit.", - ].join("\n"), - weight: 1, - }, - { - art: [ - "โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €___", - "โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €(โ €((", - "โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €)โ €))โ €โ €โ €โ €โ €โ €โ €Yourโ €docsโ €updateโ €isโ €sharp", - "โ €โ €.::.โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €/โ €/(", - "โ €'Mโ €.-;-.-.-.-.-.-.-.-.-/|โ €((::::::::::::::::::::::::::::::::::::::::::::::.._", - "(Jโ €(โ €(โ €(โ €(โ €(โ €(โ €(โ €(โ €(โ €(โ €(โ €|โ €โ €))โ €โ €โ €-====================================-โ €โ €โ €โ €โ €โ €_.>", - "โ €`Pโ €`-;-`-`-`-`-`-`-`-`-\\|โ €((::::::::::::::::::::::::::::::::::::::::::::::''", - "โ €โ €`::'โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €\\โ €\\(", - "โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €)โ €))โ €โ €โ €โ €โ €โ €โ €andโ €readyโ €forโ €battle", - "โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €โ €(_((", - ].join("\n"), - weight: 1, - }, -] as const satisfies _AsciiSet; - -/** - * Readonly view of the ASCII-art catalog entries. - * - * Used to validate the shape of `ASCII_SET` and to keep its elements - * readonly at the type level. - * - * @internal - */ -type _AsciiSet = readonly Readonly[]; -/** - * U+2800 (Braille Pattern Blank). GitHub Markdown collapses ASCII spaces in - * some contexts; this character keeps visual alignment stable. - */ -export const BRAILLE_SPACE = "โ €"; -/** - * Left indentation applied to each ASCII-art line to improve readability and - * avoid overflowing the quote/code block boundary. - */ -export const LEFT_PAD = " "; diff --git a/internals/c15t-github-action/src/steps/changes.ts b/internals/c15t-github-action/src/steps/changes.ts deleted file mode 100644 index e7e5d3d..0000000 --- a/internals/c15t-github-action/src/steps/changes.ts +++ /dev/null @@ -1,113 +0,0 @@ -import * as core from "@actions/core"; -import * as github from "@actions/github"; - -export function parseCsv( - input: string | undefined, - fallback: string[] -): string[] { - if (!input) { - return fallback; - } - return input - .split(",") - .map((s) => s.trim()) - .filter(Boolean); -} - -export function shouldDeployByPolicy( - pushBranchesCsv: string | undefined, - prBaseBranchesCsv: string | undefined -): boolean { - const ctx = github.context; - const isPR = - ctx.eventName === "pull_request" || ctx.eventName === "pull_request_target"; - const pushBranches = parseCsv(pushBranchesCsv, ["main", "canary"]); - const prBaseBranches = parseCsv(prBaseBranchesCsv, ["main", "canary"]); - - if (!isPR) { - const ref = ctx.ref?.replace("refs/heads/", "") || ""; - return pushBranches.includes(ref); - } - const baseRef = - (ctx.payload as unknown as { pull_request?: { base?: { ref?: string } } }) - ?.pull_request?.base?.ref || ""; - return prBaseBranches.includes(baseRef); -} - -export async function detectRelevantChanges( - octokit: ReturnType, - globs: string[] -): Promise { - const ctx = github.context; - try { - if ( - ctx.eventName === "pull_request" || - ctx.eventName === "pull_request_target" - ) { - const pr = ( - ctx.payload as unknown as { pull_request?: { number?: number } } - )?.pull_request; - const number = pr?.number ?? 0; - if (!number) { - return true; // be safe: deploy - } - const files = await octokit.paginate( - octokit.rest.pulls.listFiles, - { - ...ctx.repo, - pull_number: number, - per_page: 100, - }, - // flatten items - (res) => res.data - ); - const paths = files.map((f) => f.filename); - return paths.some((p) => globs.some((g) => minimatch(p, g))); - } - // push event: compare last commit range when possible - const payload = ctx.payload as unknown as { before?: string }; - const beforeSha = payload?.before; - const base = - beforeSha && beforeSha !== "0000000000000000000000000000000000000000" - ? beforeSha - : `${ctx.sha}~1`; - const head = ctx.sha; - const res = await octokit.rest.repos.compareCommitsWithBasehead({ - ...ctx.repo, - basehead: `${base}...${head}`, - }); - const paths = res.data.files?.map((f) => f.filename || "") || []; - return paths.some((p) => globs.some((g) => minimatch(p, g))); - } catch (e) { - core.warning( - `change detection failed, proceeding with deploy: ${e instanceof Error ? e.message : String(e)}` - ); - return true; - } -} - -// Lightweight minimatch impl for ** and * patterns (subset sufficient for our globs) -function escapeRegex(s: string): string { - return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); -} -function globToRegex(glob: string): RegExp { - // Validate glob length and complexity to prevent ReDoS - if (glob.length > 1000) { - throw new Error("Glob pattern too long"); - } - if ((glob.match(/\*/g) || []).length > 20) { - throw new Error("Too many wildcards in glob pattern"); - } - const escaped = escapeRegex(glob) - .replace(/\\\*\\\*/g, ".*") - .replace(/\\\*/g, "[^/]*"); - const re = `^${escaped}$`; - return new RegExp(re, "u"); -} -function minimatch(path: string, glob: string): boolean { - try { - return globToRegex(glob).test(path); - } catch { - return false; - } -} diff --git a/internals/c15t-github-action/src/steps/comments.ts b/internals/c15t-github-action/src/steps/comments.ts deleted file mode 100644 index e0706d3..0000000 --- a/internals/c15t-github-action/src/steps/comments.ts +++ /dev/null @@ -1,141 +0,0 @@ -import * as core from "@actions/core"; -import type * as github from "@actions/github"; -import { - append, - authorLogin, - header, - hideDetails, - ignoreEmpty, - pullRequestNumber, - repo, - skipUnchanged, -} from "../config/inputs"; -import { - commentsEqual, - createComment, - findPreviousComment, - getBodyOf, - updateComment, -} from "../github/pr-comment"; - -// removed no-op ensureFirstTimerBanner - -function requireEffectiveBodyOrThrow(effectiveBody: string): void { - if (!effectiveBody) { - throw new Error( - "Either message/path input is required or Vercel inputs must be set" - ); - } -} - -async function createInitialCommentWhenMissing( - octokit: ReturnType, - previous: unknown, - effectiveBody: string, - prNumber: number -): Promise { - if (previous) { - return false; - } - const created = await createComment( - octokit, - repo, - prNumber, - effectiveBody, - header - ); - core.setOutput("created_comment_id", created?.data.id); - return true; -} - -function handleSkipUnchanged( - effectiveBody: string, - previousBodyRaw: string | undefined -): boolean { - return ( - skipUnchanged && - commentsEqual(effectiveBody || "", previousBodyRaw || "", header) - ); -} - -async function updateExistingComment( - octokit: ReturnType, - previousId: string, - effectiveBody: string, - previousBody: string | undefined -): Promise { - await updateComment(octokit, previousId, effectiveBody, header, previousBody); -} - -const AUTO_COMMENT_START = - ""; -const AUTO_COMMENT_END = - ""; - -export async function ensureComment( - octokit: ReturnType, - effectiveBody: string, - options?: { appendOverride?: boolean; hideDetailsOverride?: boolean } -): Promise { - if (!effectiveBody && ignoreEmpty) { - core.info("no body given: skip step by ignoreEmpty"); - return; - } - requireEffectiveBodyOrThrow(effectiveBody); - - const prNumber = - typeof pullRequestNumber === "number" ? pullRequestNumber : 0; - - const previous = await findPreviousComment( - octokit, - repo, - prNumber, - header, - authorLogin || undefined - ); - core.setOutput("previous_comment_id", previous?.id); - if ( - await createInitialCommentWhenMissing( - octokit, - previous, - effectiveBody, - prNumber - ) - ) { - return; - } - if (handleSkipUnchanged(effectiveBody, previous?.body || "")) { - return; - } - let shouldAppend = - typeof options?.appendOverride === "boolean" - ? options.appendOverride - : append; - const shouldHideDetails = - typeof options?.hideDetailsOverride === "boolean" - ? options.hideDetailsOverride - : hideDetails; - - // Migration: if previous body doesn't have the new auto-generated block, replace instead of append - const previousHasAutoBlock = Boolean( - previous?.body?.includes(AUTO_COMMENT_START) && - previous?.body?.includes(AUTO_COMMENT_END) - ); - if (shouldAppend && !previousHasAutoBlock) { - shouldAppend = false; - } - const previousBody = getBodyOf( - { body: previous?.body || "" }, - shouldAppend, - shouldHideDetails - ); - if (!previous?.id) { - return; - } - await updateExistingComment( - octokit, - previous.id, - effectiveBody, - previousBody - ); -} diff --git a/internals/c15t-github-action/src/steps/deployment.ts b/internals/c15t-github-action/src/steps/deployment.ts deleted file mode 100644 index 4113ab2..0000000 --- a/internals/c15t-github-action/src/steps/deployment.ts +++ /dev/null @@ -1,283 +0,0 @@ -import * as core from "@actions/core"; -import * as github from "@actions/github"; -import { - aliasDomains, - aliasOnBranch, - canaryAlias, - changeGlobs, - checkTemplateChanges, - consentGitToken, - deployOnPrBaseBranches, - deployOnPushBranches, - onlyIfChanged, - setupDocs, - vercelArgs, - vercelFramework, - vercelOrgId, - vercelProjectId, - vercelScope, - vercelTarget, - vercelToken, - vercelWorkingDirectory, -} from "../config/inputs"; -import { type DeployTarget, deployToVercel } from "../deploy/vercel-client"; -import { ErrorHandler, executeWithRetry } from "../utils/errors"; -import { detectRelevantChanges, shouldDeployByPolicy } from "./changes"; -import { setupDocsWithScript } from "./setup-docs"; - -async function createGitHubDeployment( - octokit: ReturnType, - environmentName: string, - payload?: Record -): Promise { - try { - const ghDeployment = await octokit.rest.repos.createDeployment({ - ...github.context.repo, - ref: github.context.sha, - required_contexts: [], - environment: environmentName, - transient_environment: environmentName !== "production", - production_environment: environmentName === "production", - auto_merge: false, - auto_inactive: false, - description: "Vercel deployment", - ...(payload ? { payload: JSON.stringify(payload) } : {}), - }); - const id = (ghDeployment as unknown as { data?: { id?: number } }).data?.id; - return typeof id === "number" ? id : undefined; - } catch (e) { - core.warning( - `Could not create GitHub Deployment: ${e instanceof Error ? e.message : String(e)}` - ); - return undefined; - } -} - -async function setGitHubDeploymentStatus( - octokit: ReturnType, - deploymentId: number, - state: "in_progress" | "success" | "failure", - description: string, - environmentUrl?: string -): Promise { - try { - await octokit.rest.repos.createDeploymentStatus({ - ...github.context.repo, - deployment_id: deploymentId, - state, - description, - ...(environmentUrl ? { environment_url: environmentUrl } : {}), - }); - } catch (e) { - core.warning( - `Could not set deployment ${state}: ${e instanceof Error ? e.message : String(e)}` - ); - } -} - -export function resolveBranch(): string { - const ref = github.context.ref || ""; - const headRef = - (github.context as unknown as { head_ref?: string }).head_ref || ""; - if (headRef) { - return headRef; - } - if (ref.startsWith("refs/heads/")) { - return ref.replace("refs/heads/", ""); - } - return ref; -} - -export function computeEnvironmentName( - target: DeployTarget | undefined, - branch: string -): string { - if (target === "production") { - return "production"; - } - if (branch === "main") { - return "production"; - } - return `preview/${branch}`; -} - -async function ensureGitMetaEnv( - octokit: ReturnType -): Promise { - const env = process.env; - if ( - env.GITHUB_COMMIT_MESSAGE && - env.GITHUB_COMMIT_AUTHOR_NAME && - env.GITHUB_COMMIT_AUTHOR_LOGIN - ) { - return; - } - try { - const sha = github.context.sha; - const { data: commit } = await octokit.rest.repos.getCommit({ - ...github.context.repo, - ref: sha, - }); - if (!env.GITHUB_COMMIT_MESSAGE) { - process.env.GITHUB_COMMIT_MESSAGE = commit.commit.message || ""; - } - if (!env.GITHUB_COMMIT_AUTHOR_NAME) { - process.env.GITHUB_COMMIT_AUTHOR_NAME = commit.commit.author?.name || ""; - } - if (!env.GITHUB_COMMIT_AUTHOR_LOGIN) { - process.env.GITHUB_COMMIT_AUTHOR_LOGIN = github.context.actor || ""; - } - if (!env.GITHUB_PR_NUMBER) { - const prNum = ( - github.context.payload as unknown as { - pull_request?: { number?: number }; - } - )?.pull_request?.number; - if (typeof prNum === "number") { - process.env.GITHUB_PR_NUMBER = String(prNum); - } - } - } catch (e) { - core.debug( - `ensureGitMetaEnv failed: ${e instanceof Error ? e.message : String(e)}` - ); - } -} - -export async function performVercelDeployment( - octokit: ReturnType -): Promise { - if (!vercelToken || !vercelProjectId || !vercelOrgId) { - return undefined; - } - - // Optional docs setup inline - if (setupDocs) { - try { - setupDocsWithScript(consentGitToken); - } catch (e) { - core.setFailed( - `docs setup failed: ${e instanceof Error ? e.message : String(e)}` - ); - return undefined; - } - } - - // Policy: only deploy on allowed branches/PR bases - const allowedByPolicy = shouldDeployByPolicy( - deployOnPushBranches, - deployOnPrBaseBranches - ); - if (!allowedByPolicy) { - core.info("Policy prevents deploy on this ref; skipping deployment"); - return undefined; - } - - // Change detection gating - if (onlyIfChanged) { - const globs = changeGlobs.length - ? changeGlobs - : ["docs/**", "packages/*/src/**", "packages/*/package.json"]; - const relevant = await detectRelevantChanges(octokit, globs); - if (!relevant && !checkTemplateChanges) { - core.info("No relevant file changes detected; skipping deployment"); - return undefined; - } - } - - const branch = resolveBranch(); - let targetHint: DeployTarget | undefined = - vercelTarget && vercelTarget.trim() !== "" - ? (vercelTarget as DeployTarget) - : undefined; - if (!targetHint) { - if (branch === "main") { - targetHint = "production" as DeployTarget; - } else { - targetHint = "staging" as DeployTarget; - } - } - - // Template change tracking (best effort) - DISABLED - let latestTemplateSha: string | undefined; - // if (checkTemplateChanges) { - // const tplRepo = docsTemplateRepo || 'consentdotio/runner-docs'; - // const tplRef = docsTemplateRef || 'main'; - // latestTemplateSha = await fetchLatestTemplateSha( - // octokit, - // tplRepo, - // tplRef, - // consentGitToken || process.env.CONSENT_GIT_TOKEN - // ); - // const envName = computeEnvironmentName(targetHint, branch); - // const previous = await readLastTemplateShaFromDeployments(octokit, envName); - // if (latestTemplateSha && previous && latestTemplateSha === previous) { - // core.info('Template unchanged; skipping deployment'); - // return undefined; - // } - // } - - const environmentName = computeEnvironmentName(targetHint, branch); - const deploymentId = await createGitHubDeployment( - octokit, - environmentName, - latestTemplateSha ? { template_sha: latestTemplateSha } : undefined - ); - if (typeof deploymentId === "number") { - await setGitHubDeploymentStatus( - octokit, - deploymentId, - "in_progress", - "Starting Vercel deploy" - ); - } - - // Ensure Git metadata envs if not already present - await ensureGitMetaEnv(octokit); - - try { - const result = await executeWithRetry( - () => - deployToVercel({ - token: vercelToken, - projectId: vercelProjectId, - orgId: vercelOrgId, - workingDirectory: vercelWorkingDirectory, - framework: vercelFramework, - target: targetHint, - aliasDomain: canaryAlias || undefined, - aliasBranch: aliasOnBranch || undefined, - aliasDomains, - vercelArgs, - vercelScope, - }), - ErrorHandler.handleVercel, - 3 - ); - const url = result.url; - core.setOutput("deployment_url", url); - if (typeof deploymentId === "number") { - await setGitHubDeploymentStatus( - octokit, - deploymentId, - "success", - `Preview ready${environmentName ? `: ${environmentName}` : ""}`, - url - ); - } - return url; - } catch (e) { - core.setFailed( - `vercel deployment failed: ${e instanceof Error ? e.message : String(e)}` - ); - if (typeof deploymentId === "number") { - await setGitHubDeploymentStatus( - octokit, - deploymentId, - "failure", - "Vercel deployment failed" - ); - } - return undefined; - } -} diff --git a/internals/c15t-github-action/src/steps/first-commit.ts b/internals/c15t-github-action/src/steps/first-commit.ts deleted file mode 100644 index 311ffca..0000000 --- a/internals/c15t-github-action/src/steps/first-commit.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Special ASCII art used to celebrate first-time contributors. - * Intended to be rendered inside a fenced code block to preserve spacing - * in GitHub markdown. - */ -export const FIRST_TIME_CONTRIBUTOR_ASCII = [ - "โ €_______________", - "|@@@@|โ €โ €โ €โ €โ €|####|", - "|@@@@|โ €โ €โ €โ €โ €|####|", - "|@@@@|โ €โ €โ €โ €โ €|####|", - "\\@@@@|โ €โ €โ €โ €โ €|####/", - "โ €\\@@@|โ €โ €โ €โ €โ €|###/", - "โ €โ €`@@|_____|##'", - "โ €โ €โ €โ €โ €โ €โ €(O)", - "โ €โ €โ €โ €.-'''''-.", - "โ €โ €.'โ €โ €*โ €*โ €*โ €โ €`.", - "โ €:โ €โ €*โ €โ €โ €โ €โ €โ €โ €*โ €โ €:", - ":โ €~โ €Fโ €Iโ €Rโ €Sโ €Tโ €~โ €:", - ":โ €โ €Cโ €Oโ €Mโ €Mโ €Iโ €Tโ €โ €:", - "โ €:โ €โ €*โ €โ €โ €โ €โ €โ €โ €*โ €โ €:", - "โ €โ €`.โ €โ €*โ €*โ €*โ €โ €.'", - "โ €โ €โ €โ €`-.....-'", -].join("\n"); diff --git a/internals/c15t-github-action/src/steps/github-app-auth.ts b/internals/c15t-github-action/src/steps/github-app-auth.ts deleted file mode 100644 index e8890eb..0000000 --- a/internals/c15t-github-action/src/steps/github-app-auth.ts +++ /dev/null @@ -1,72 +0,0 @@ -import * as core from "@actions/core"; -import * as github from "@actions/github"; -import { createAppAuth } from "@octokit/auth-app"; - -/** - * Resolve the auth token to use for API calls. - * - * If `appId`/`privateKey` are missing or resolution fails, returns `defaultToken`. - * When `installationIdInput` is omitted, discovers the repo installation via the - * GitHub App. Falls back to `defaultToken` if discovery fails. - * - * @param defaultToken - Fallback token (GITHUB_TOKEN or PAT). - * @param appId - GitHub App ID, if using App auth. - * @param privateKey - GitHub App private key (PEM). Multiline secrets supported. - * @param installationIdInput - Optional installation ID to skip discovery. - * @returns A bearer token string for authenticated requests. - * @throws Never throws; logs warnings and returns `defaultToken` on failures. - */ -export async function getAuthToken( - defaultToken: string, - appId: string | undefined, - privateKey: string | undefined, - installationIdInput: string | undefined -): Promise { - if (!appId || !privateKey) { - return defaultToken; - } - try { - const appAuth = createAppAuth({ - appId, - privateKey, - }); - let installationId: number | undefined; - if (installationIdInput) { - const parsed = Number(installationIdInput); - if (Number.isInteger(parsed) && parsed > 0) { - installationId = parsed; - } else { - core.info( - "Invalid github_app_installation_id provided; falling back to default token" - ); - return defaultToken; - } - } else { - const appOctokit = github.getOctokit( - (await appAuth({ type: "app" })).token - ); - const { owner, repo } = github.context.repo; - const { data } = await appOctokit.rest.apps.getRepoInstallation({ - owner, - repo, - }); - installationId = data.id; - } - if (!Number.isInteger(installationId) || (installationId as number) <= 0) { - core.info( - "Could not resolve GitHub App installation for this repo; falling back to default token" - ); - return defaultToken; - } - const installationAuth = await appAuth({ - type: "installation", - installationId: installationId as number, - }); - return installationAuth.token; - } catch (e) { - core.warning( - `GitHub App token generation failed: ${e instanceof Error ? e.message : String(e)}` - ); - return defaultToken; - } -} diff --git a/internals/c15t-github-action/src/steps/push-comment.ts b/internals/c15t-github-action/src/steps/push-comment.ts deleted file mode 100644 index a5b03b9..0000000 --- a/internals/c15t-github-action/src/steps/push-comment.ts +++ /dev/null @@ -1,57 +0,0 @@ -import * as core from "@actions/core"; -import * as github from "@actions/github"; -import { commentOnPush, pullRequestNumber } from "../config/inputs"; -import { renderCommentMarkdown } from "./render-comment"; - -/** - * Optionally posts a commit comment with a docs preview link on push events. - * - * Skips when the run is associated with a pull request or when disabled via - * inputs. Returns a boolean indicating whether the step completed without - * error (including intentional skips). - * - * @param octokit - Preconfigured Octokit instance. - * @param effectiveBody - Comment body to post; falls back to a generated - * preview comment when absent. - * @param deploymentUrl - Public deployment URL; required to post a comment. - * @returns True when posting succeeded or the step was intentionally skipped; - * false when a PR run was detected or posting failed. - * @see renderCommentMarkdown - */ - -export async function maybeCommentOnPush( - octokit: ReturnType, - effectiveBody?: string, - deploymentUrl?: string -): Promise { - if (typeof pullRequestNumber === "number" && pullRequestNumber >= 1) { - return false; - } - if (!commentOnPush) { - core.info("comment_on_push=false: skipping commit comment on push"); - return true; - } - if (!deploymentUrl) { - core.info("no deployment URL provided; skipping commit comment"); - return true; - } - try { - await octokit.rest.repos.createCommitComment({ - ...github.context.repo, - commit_sha: github.context.sha, - body: - effectiveBody || - renderCommentMarkdown(deploymentUrl, { - seed: github.context.sha, - }), - }); - } catch (e) { - core.warning( - `Could not post commit comment: ${ - e instanceof Error ? e.message : String(e) - }` - ); - return false; - } - return true; -} diff --git a/internals/c15t-github-action/src/steps/render-comment.ts b/internals/c15t-github-action/src/steps/render-comment.ts deleted file mode 100644 index de9da7c..0000000 --- a/internals/c15t-github-action/src/steps/render-comment.ts +++ /dev/null @@ -1,205 +0,0 @@ -import { - ASCII_SET, - BRAILLE_SPACE, - LEFT_PAD, - type WeightedAsciiArt, -} from "./ascii-art"; -import { FIRST_TIME_CONTRIBUTOR_ASCII } from "./first-commit"; - -export type RenderCommentOptions = { - debug?: boolean; - seed?: string; - firstContribution?: boolean; - status?: string; -}; - -function pickWeightedAscii( - choices: readonly WeightedAsciiArt[], - seed?: string -): string { - let total = 0; - for (const c of choices) { - const w = Math.max(0, c.weight); - total += w; - } - if (total <= 0) { - if (choices[0]?.art) { - return choices[0].art; - } - return ""; - } - // Deterministic fallback when a seed is provided (FNV-1a style hash) - let r: number; - if (seed) { - let h = 2166136261 >>> 0; - for (let i = 0; i < seed.length; i++) { - h ^= seed.charCodeAt(i); - // h *= 16777619 (using shifts to avoid bigint) - h = (h + (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24)) >>> 0; - } - // Map uniformly to [0, total) using 32-bit range (avoids modulo bias) - r = (h / 0x100000000) * total; - } else { - r = Math.random() * total; - } - let acc = 0; - for (const c of choices) { - const w = Math.max(0, c.weight); - acc += w; - if (r < acc) { - return c.art; - } - } - const lastChoice = choices.at(-1); - if (lastChoice?.art) { - return lastChoice.art; - } - return ""; -} - -/** - * Render a deterministic, branded Markdown block for docs-preview comments. - * - * - When `firstContribution` is true, a special ASCII art banner is shown. - * - When `debug` is true, renders all available ASCII variants. - * - `seed` ensures deterministic ASCII selection for the same input. - * - * @param url - The preview URL to include in the comment. - * @param options - Rendering options. - * @returns The complete Markdown string. - * @internal - * @example - * renderCommentMarkdown('https://example.vercel.app', { seed: 'abc123' }); - */ -export function renderCommentMarkdown( - url: string, - options?: RenderCommentOptions -): string { - const updated = new Date().toUTCString(); - let status = "Ready"; - if (options?.status) { - status = options.status; - } - - const formatArt = (ascii: string) => { - const asciiWithBrailleSpaces = ascii.replace(/ /g, BRAILLE_SPACE); - const pad = LEFT_PAD; - - return asciiWithBrailleSpaces - .split("\n") - .map((l) => `${pad}${l}`) - .join("\n"); - }; - - const firstTimeContributorMessage = [ - "
", - "> ๐ŸŽ‰ **Your first c15t commit!**", - "> ", - "> This is your first contribution to c15t, and I just wanted to say " + - "thank you. Youโ€™re helping us build the best developer-first consent " + - "infrastructure. Hereโ€™s to many more commits ahead! ๐Ÿš€ ", - "> ", - "> Christopher, Author of c15t, [@burnedchris](https://x.com/burnedchris)", - "", - ]; - - const previewMessage = [ - "### Docs Preview", - "| Preview | Status | Updated (UTC) |", - "| - | - | - |", - `| [Open Preview](${url}) | ${status} | ${updated} |`, - ]; - - const messageTemplate = ({ - art, - url, - updated, - firstContribution, - }: { - art: string; - url?: string; - updated?: string; - firstContribution?: boolean; - }) => { - const lines: string[] = []; - lines.push("```"); - let artBlock = art; - if (firstContribution) { - artBlock = FIRST_TIME_CONTRIBUTOR_ASCII; - } - lines.push(formatArt(artBlock)); - lines.push("```"); - lines.push(""); - if (firstContribution) { - lines.push(firstTimeContributorMessage.join("\n")); - } - if (url && updated) { - lines.push(previewMessage.join("\n")); - } - // Share section (inspired by CodeRabbit share block) - lines.push("
"); - lines.push("๐Ÿ’™ Share your contribution on social media"); - lines.push(""); - const shareBase = - "I just contributed to c15t.com, the fastest open-source cookie " + - "banner on the web. Fully developer-first, beating every major CMP " + - "in benchmarks and free for everyone to use or self-host. Check it " + - "out: github.com/c15t/c15t"; - const shareTextEncoded = encodeURIComponent( - url ? `${shareBase} ${url}` : shareBase - ); - const shareUrlParam = encodeURIComponent(url ?? "https://c15t.com"); - lines.push( - `- [X](https://twitter.com/intent/tweet?text=${shareTextEncoded})` - ); - lines.push( - `- [Mastodon](https://mastodon.social/share?text=${shareTextEncoded})` - ); - lines.push( - `- [Reddit](https://www.reddit.com/submit?title=Fastest%20open-source%20cookie%20banner%20-%20c15t&text=${shareTextEncoded})` - ); - lines.push( - `- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=${shareUrlParam}&mini=true&title=Fastest%20open%20source%20cookie%20banner%20-%20c15t&summary=${shareTextEncoded})` - ); - lines.push(""); - lines.push("
"); - lines.push(""); - lines.push("
"); - lines.push("๐Ÿชง Documentation and Community"); - lines.push(""); - lines.push( - "- Visit our [Documentation](https://c15t.com/docs) for detailed information on how to use c15t." - ); - lines.push( - "- Join our [Discord Community](https://c15t.link/discord) to get help, request features, and share feedback." - ); - lines.push( - "- Follow us on [X](https://twitter.com/consentdotio) for updates and announcements." - ); - lines.push(""); - lines.push("
"); - lines.push(""); - lines.push("---"); - lines.push( - "***Baked with ๐Ÿ’™ by [Consent](https://consent.io?ref=c15t-github-comment), powered by COOKIE***" - ); - lines.push("(Consent Oversees Our Key Integration Events)"); - lines.push("because every CI needs a snack."); - lines.push(""); - return lines.join("\n"); - }; - - if (options?.debug) { - return ASCII_SET.map((a) => - messageTemplate({ art: a.art, url, updated }) - ).join("\n\n"); - } - - const inner = messageTemplate({ - art: pickWeightedAscii(ASCII_SET, options?.seed ?? url), - url, - updated, - firstContribution: options?.firstContribution, - }); - return inner; -} diff --git a/internals/c15t-github-action/src/steps/setup-docs.ts b/internals/c15t-github-action/src/steps/setup-docs.ts deleted file mode 100644 index b2b21e4..0000000 --- a/internals/c15t-github-action/src/steps/setup-docs.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { spawnSync } from "node:child_process"; -import * as core from "@actions/core"; -import * as github from "@actions/github"; - -function isForkPullRequest(): boolean { - const pr = ( - github.context?.payload as unknown as { - pull_request?: { head?: { repo?: { full_name?: string } } }; - } - )?.pull_request; - if (!pr) { - return false; - } - const headRepo = pr.head?.repo?.full_name || ""; - const thisRepo = `${github.context.repo.owner}/${github.context.repo.repo}`; - return headRepo.toLowerCase() !== thisRepo.toLowerCase(); -} - -export function setupDocsWithScript(consentGitToken?: string): void { - const isPrFromFork = isForkPullRequest(); - if (isPrFromFork) { - core.info("PR from fork detected: skipping docs setup"); - return; - } - const env = { - ...process.env, - CONSENT_GIT_TOKEN: consentGitToken || process.env.CONSENT_GIT_TOKEN || "", - }; - core.info("Running docs setup script via pnpm tsx scripts/setup-docs.ts"); - const result = spawnSync( - "pnpm", - ["tsx", "scripts/setup-docs.ts", "--vercel"], - { stdio: "inherit", env } - ); - if (result.error) { - throw result.error; - } - if (typeof result.status === "number" && result.status !== 0) { - throw new Error(`setup-docs script failed with exit code ${result.status}`); - } -} diff --git a/internals/c15t-github-action/src/steps/template-tracking.ts b/internals/c15t-github-action/src/steps/template-tracking.ts deleted file mode 100644 index bcfd9a4..0000000 --- a/internals/c15t-github-action/src/steps/template-tracking.ts +++ /dev/null @@ -1,89 +0,0 @@ -import * as core from "@actions/core"; -import * as github from "@actions/github"; - -export async function fetchLatestTemplateSha( - octokit: ReturnType, - repo: string, - ref: string, - authToken?: string -): Promise { - try { - if (!repo.includes("/")) { - throw new Error( - `Invalid repo format: "${repo}". Expected "owner/name" format.` - ); - } - const [owner, name] = repo.split("/"); - if (!owner || !name) { - throw new Error( - `Invalid repo format: "${repo}". Both owner and name are required.` - ); - } - let client: ReturnType = octokit; - if (authToken) { - client = github.getOctokit(authToken); - } - const { data: commits } = await client.rest.repos.listCommits({ - owner, - repo: name, - sha: ref, - per_page: 1, - }); - return commits[0]?.sha; - } catch (e) { - core.warning( - `Could not fetch latest template sha: ${e instanceof Error ? e.message : String(e)}` - ); - return undefined; - } -} - -export async function readLastTemplateShaFromDeployments( - octokit: ReturnType, - environment: string -): Promise { - try { - const { data } = await octokit.rest.repos.listDeployments({ - ...github.context.repo, - environment, - per_page: 30, - }); - for (const d of data) { - const statuses = await octokit.rest.repos.listDeploymentStatuses({ - ...github.context.repo, - deployment_id: d.id, - per_page: 1, - }); - if (statuses.data[0]?.state === "success") { - const payload: unknown = (d as unknown as { payload?: unknown }) - .payload; - const sha = extractTemplateShaFromPayload(payload); - if (sha) { - return sha; - } - } - } - return undefined; - } catch { - core.info("No previous deployments found for template sha lookup"); - return undefined; - } -} - -function extractTemplateShaFromPayload(payload: unknown): string | undefined { - if (typeof payload === "string") { - try { - const parsed = JSON.parse(payload) as { template_sha?: string }; - return parsed.template_sha; - } catch (e) { - core.warning( - `Could not parse payload: ${e instanceof Error ? e.message : String(e)}` - ); - return undefined; - } - } - if (payload && typeof payload === "object") { - return (payload as { template_sha?: string }).template_sha; - } - return undefined; -} diff --git a/internals/c15t-github-action/src/utils/errors.ts b/internals/c15t-github-action/src/utils/errors.ts deleted file mode 100644 index 9e4349e..0000000 --- a/internals/c15t-github-action/src/utils/errors.ts +++ /dev/null @@ -1,176 +0,0 @@ -import * as core from "@actions/core"; - -/** - * Rich, actionable error descriptor used to communicate failures with guidance. - * - * @remarks - * Produced by mapping raw provider errors to user-friendly diagnostics with - * concrete next steps. - */ -export type ActionableError = { - type: - | "deployment" - | "authentication" - | "configuration" - | "api_limit" - | "unknown"; - message: string; - suggestion: string; - actionItems: string[]; - helpUrl?: string; - retryable: boolean; -}; - -/** - * Helpers to translate provider-specific errors into actionable guidance. - */ -export const ErrorHandler = { - /** Map Vercel-related errors to an actionable form. */ - handleVercel(error: unknown): ActionableError { - const message = error instanceof Error ? error.message : String(error); - if (message.toLowerCase().includes("project not found")) { - return { - type: "configuration", - message, - suggestion: - "Check VERCEL_PROJECT_ID and VERCEL_ORG_ID secrets match your Vercel project.", - actionItems: [ - "Verify VERCEL_PROJECT_ID exists in Vercel dashboard.", - "Confirm VERCEL_ORG_ID is the correct team/org.", - "Ensure VERCEL_TOKEN has access to the project.", - ], - helpUrl: - "https://vercel.com/docs/concepts/projects/overview#project-id", - retryable: false, - }; - } - if (message.toLowerCase().includes("rate limit")) { - return { - type: "api_limit", - message, - suggestion: "Wait before retrying or reduce deployment frequency.", - actionItems: [ - "Wait 10-15 minutes and retry.", - "Enable only_if_changed gating to avoid unnecessary deploys.", - ], - retryable: true, - }; - } - if (message.toLowerCase().includes("build failed")) { - return { - type: "deployment", - message, - suggestion: - "Inspect Vercel build logs and validate your build locally.", - actionItems: [ - "Check Vercel build logs for errors.", - "Run local build: `pnpm -C .docs run build` or project-specific build.", - "Ensure required env vars are present in Vercel.", - ], - retryable: true, - }; - } - return { - type: "unknown", - message, - suggestion: "Review error details and verify deployment configuration.", - actionItems: ["Check repository secrets", "Re-run with debug_mode: true"], - retryable: true, - }; - }, - /** Map GitHub API failures to actionable guidance. */ - handleGitHub(error: unknown): ActionableError { - const any = error as { status?: number; message?: string }; - const message = - any?.message || (error instanceof Error ? error.message : String(error)); - if (any?.status === 403) { - return { - type: "authentication", - message, - suggestion: - "Ensure token has required permissions (pull-requests: write, deployments: write).", - actionItems: [ - "Use GITHUB_TOKEN with proper permissions in workflow.", - "If using GitHub App, verify installation permissions.", - ], - retryable: true, - }; - } - if (any?.status === 404) { - return { - type: "configuration", - message, - suggestion: - "Verify repository/PR/deployment resource exists and is accessible.", - actionItems: [ - "Check repo owner/name and PR number.", - "Ensure token/app has access to the repository.", - ], - retryable: false, - }; - } - return { - type: "unknown", - message, - suggestion: "Check GitHub API status and token permissions.", - actionItems: ["Verify https://www.githubstatus.com/"], - retryable: true, - }; - }, -}; - -/** Retry configuration for `executeWithRetry`. */ -export type RetryOptions = { - maxRetries?: number; - backoffBaseMs?: number; // base multiplier for exponential backoff - maxDelayMs?: number; - sleep?: (ms: number) => Promise; -}; - -/** - * Execute an async operation with exponential backoff and actionable error - * reporting. - * - * @typeParam ResultType - Operation resolution type - * @param operation - Function that performs the async work - * @param mapError - Maps raw errors to `ActionableError` - * @param options - Retry configuration or a number representing max retries - * @returns The resolved operation result - * @throws Error When the final attempt fails. Non-retryable errors abort ASAP. - */ -export async function executeWithRetry( - operation: () => Promise, - mapError: (error: unknown) => ActionableError, - options: number | RetryOptions = 3 -): Promise { - const opts: RetryOptions = - typeof options === "number" ? { maxRetries: options } : options || {}; - const maxRetries = opts.maxRetries ?? 3; - const base = opts.backoffBaseMs ?? 1000; - const maxDelay = opts.maxDelayMs ?? 30000; - const sleeper = - opts.sleep ?? ((ms: number) => new Promise((r) => setTimeout(r, ms))); - let last: ActionableError | undefined; - for (let attempt = 1; attempt <= maxRetries; attempt++) { - try { - return await operation(); - } catch (e) { - last = mapError(e); - const isLast = attempt === maxRetries; - if (!last.retryable || isLast) { - core.error( - `Failure after ${attempt}/${maxRetries} attempts: ${last.message}` - ); - core.error(`Suggestion: ${last.suggestion}`); - for (const item of last.actionItems) { - core.error(`- ${item}`); - } - throw e; - } - const delayMs = Math.min(maxDelay, 2 ** attempt * base); - core.info(`Retrying in ${delayMs}ms (attempt ${attempt}/${maxRetries})`); - await sleeper(delayMs); - } - } - throw last ? new Error(last.message) : new Error("Unknown error"); -} diff --git a/internals/c15t-github-action/src/utils/logger.ts b/internals/c15t-github-action/src/utils/logger.ts deleted file mode 100644 index daf2361..0000000 --- a/internals/c15t-github-action/src/utils/logger.ts +++ /dev/null @@ -1,114 +0,0 @@ -import * as core from "@actions/core"; -import * as github from "@actions/github"; - -/** - * Arbitrary structured logging fields for contextual metadata. - * - * @remarks - * Values should be JSON-serializable. Non-serializable values will be omitted - * when rendering the log line. - */ -export type LogFields = Record; - -/** - * Minimal logger interface used across the action. - * - * @remarks - * This interface intentionally mirrors common logger APIs and routes messages - * through `@actions/core` sinks. `debug` messages are emitted only when - * `debug_mode` is enabled via inputs. - */ -export type Logger = { - /** Log a verbose diagnostic message (hidden unless debug is enabled). */ - debug: (message: string, fields?: LogFields) => void; - /** Log an informational message. */ - info: (message: string, fields?: LogFields) => void; - /** Log a warning message. */ - warn: (message: string, fields?: LogFields) => void; - /** Log an error message. */ - error: (message: string, fields?: LogFields) => void; - /** - * Create a derived logger that always includes the provided fields. - * - * @param fields - Additional fields to merge into subsequent log calls - */ - child: (fields: LogFields) => Logger; -}; - -/** - * Render structured fields as a JSON suffix for a single-line log entry. - * - * @param fields - Optional fields to render - * @returns A string beginning with a leading space or an empty string when no - * fields are provided or serialization fails - * @internal - */ -function formatFields(fields?: LogFields): string { - if (!fields || Object.keys(fields).length === 0) { - return ""; - } - try { - return ` ${JSON.stringify(fields)}`; - } catch { - return ""; - } -} - -/** - * Create a logger that writes to GitHub Actions logging streams. - * - * @param debugEnabled - When true, `debug` logs are emitted via `core.info` - * @param base - Optional base fields included with every log line - * @returns A `Logger` instance - * - * @example - * ```ts - * const logger = createLogger(true, { component: 'deploy' }); - * logger.info('starting'); - * logger.debug('payload', { size: 123 }); - * ``` - */ -export function createLogger( - debugEnabled: boolean, - base: LogFields = {} -): Logger { - const baseMeta = { - event: github.context.eventName, - ref: github.context.ref, - sha: github.context.sha, - actor: github.context.actor, - ...base, - }; - - function log( - level: "debug" | "info" | "warn" | "error", - message: string, - fields?: LogFields - ): void { - const line = `[c15t] ${message}${formatFields({ ...baseMeta, ...(fields || {}) })}`; - if (level === "debug") { - if (debugEnabled) { - core.info(line); - } - return; - } - if (level === "info") { - core.info(line); - return; - } - if (level === "warn") { - core.warning(line); - return; - } - core.error(line); - } - - return { - debug: (m, f) => log("debug", m, f), - info: (m, f) => log("info", m, f), - warn: (m, f) => log("warn", m, f), - error: (m, f) => log("error", m, f), - child: (childFields: LogFields) => - createLogger(debugEnabled, { ...baseMeta, ...childFields }), - }; -} diff --git a/internals/c15t-github-action/tsconfig.json b/internals/c15t-github-action/tsconfig.json deleted file mode 100644 index 2b3ca37..0000000 --- a/internals/c15t-github-action/tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "module": "ESNext", - "moduleResolution": "Bundler", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "resolveJsonModule": true, - "types": ["node"], - "noEmit": true, - "forceConsistentCasingInFileNames": true, - "isolatedModules": true, - "verbatimModuleSyntax": true, - "lib": ["es2022"] - }, - "include": ["src"], - "exclude": ["dist", "node_modules", "**/__tests__/**"] -} diff --git a/internals/c15t-github-action/vitest.config.ts b/internals/c15t-github-action/vitest.config.ts deleted file mode 100644 index 424ea78..0000000 --- a/internals/c15t-github-action/vitest.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { baseConfig } from "@c15t/vitest-config/base"; -import { defineConfig, mergeConfig } from "vitest/config"; - -export default mergeConfig( - baseConfig, - defineConfig({ - test: { - environment: "node", - }, - }) -); diff --git a/package.json b/package.json index fdfccc8..1a8bd6e 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "build": "turbo build --filter='./packages/**/*'", "clean": "turbo clean", "fmt": "turbo fmt", + "fmt:docs": "pnpm remark ./docs --ext mdx --output --use remark-mdx --use remark-frontmatter --use remark-preset-lint-recommended --use remark-preset-lint-consistent", "lint": "biome check", "runners": "runners", "test": "turbo test", @@ -18,7 +19,12 @@ "runners": "workspace:*", "turbo": "^2.5.4", "typescript": "^5.9.3", - "ultracite": "6.3.4" + "ultracite": "6.3.4", + "remark-cli": "^12.0.1", + "remark-frontmatter": "^5.0.0", + "remark-mdx": "^3.1.0", + "remark-preset-lint-consistent": "^6.0.1", + "remark-preset-lint-recommended": "^7.0.1" }, "packageManager": "pnpm@10.20.0+sha512.cf9998222162dd85864d0a8102e7892e7ba4ceadebbf5a31f9c2fce48dfce317a9c53b9f6464d1ef9042cba2e02ae02a9f7c143a2b438cd93c91840f0192b9dd", "engines": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e81973c..4db3550 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,21 @@ importers: '@rslib/core': specifier: ^0.18.0 version: 0.18.0(typescript@5.9.3) + remark-cli: + specifier: ^12.0.1 + version: 12.0.1 + remark-frontmatter: + specifier: ^5.0.0 + version: 5.0.0 + remark-mdx: + specifier: ^3.1.0 + version: 3.1.1 + remark-preset-lint-consistent: + specifier: ^6.0.1 + version: 6.0.1 + remark-preset-lint-recommended: + specifier: ^7.0.1 + version: 7.0.1 runners: specifier: workspace:* version: link:packages/export @@ -84,7 +99,7 @@ importers: version: 4.10.6 nitro: specifier: 'catalog:' - version: 3.0.1-alpha.1(@vercel/functions@3.3.3(@aws-sdk/credential-provider-web-identity@3.936.0))(chokidar@4.0.3)(lru-cache@11.2.2)(rollup@4.53.3)(vite@7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)) + version: 3.0.1-alpha.1(@vercel/functions@3.3.3(@aws-sdk/credential-provider-web-identity@3.936.0))(chokidar@4.0.3)(lru-cache@11.2.2)(rollup@4.53.3)(vite@7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.2)) examples/hono-orchestrator: dependencies: @@ -106,7 +121,7 @@ importers: version: 4.10.6 nitro: specifier: 'catalog:' - version: 3.0.1-alpha.1(@vercel/functions@3.3.3(@aws-sdk/credential-provider-web-identity@3.936.0))(chokidar@4.0.3)(lru-cache@11.2.2)(rollup@4.53.3)(vite@7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)) + version: 3.0.1-alpha.1(@vercel/functions@3.3.3(@aws-sdk/credential-provider-web-identity@3.936.0))(chokidar@4.0.3)(lru-cache@11.2.2)(rollup@4.53.3)(vite@7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.2)) examples/http-api: dependencies: @@ -141,7 +156,7 @@ importers: version: 22.19.0 nitro: specifier: 'catalog:' - version: 3.0.1-alpha.1(@vercel/functions@3.3.3(@aws-sdk/credential-provider-web-identity@3.936.0))(chokidar@4.0.3)(lru-cache@11.2.2)(rollup@4.53.3)(vite@7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)) + version: 3.0.1-alpha.1(@vercel/functions@3.3.3(@aws-sdk/credential-provider-web-identity@3.936.0))(chokidar@4.0.3)(lru-cache@11.2.2)(rollup@4.53.3)(vite@7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.2)) examples/swc-plugin: dependencies: @@ -165,59 +180,6 @@ importers: specifier: ^5.9.3 version: 5.9.3 - internals/bundle-analysis-action: - dependencies: - '@actions/core': - specifier: ^1.11.1 - version: 1.11.1 - '@actions/github': - specifier: ^6.0.1 - version: 6.0.1 - '@actions/glob': - specifier: ^0.5.0 - version: 0.5.0 - devDependencies: - '@types/node': - specifier: ^24.3.0 - version: 24.10.1 - '@vercel/ncc': - specifier: ^0.38.3 - version: 0.38.4 - tsx: - specifier: ^4.19.3 - version: 4.20.6 - vitest: - specifier: ^4.0.8 - version: 4.0.13(@types/debug@4.1.12)(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6) - - internals/c15t-github-action: - dependencies: - '@actions/core': - specifier: ^1.11.1 - version: 1.11.1 - '@actions/github': - specifier: ^6.0.1 - version: 6.0.1 - '@actions/glob': - specifier: ^0.5.0 - version: 0.5.0 - '@octokit/auth-app': - specifier: ^6.1.2 - version: 6.1.4 - '@octokit/graphql-schema': - specifier: ^15.26.0 - version: 15.26.1 - devDependencies: - '@types/node': - specifier: ^24.3.0 - version: 24.10.1 - '@vercel/ncc': - specifier: ^0.38.3 - version: 0.38.4 - tsx: - specifier: ^4.19.3 - version: 4.20.6 - packages/export: dependencies: '@runners/cli': @@ -265,7 +227,7 @@ importers: version: link:../orchestrator nitro: specifier: ^3.0.0 - version: 3.0.0(@vercel/functions@3.3.3(@aws-sdk/credential-provider-web-identity@3.936.0))(chokidar@4.0.3)(lru-cache@11.2.2)(vite@7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)) + version: 3.0.0(@vercel/functions@3.3.3(@aws-sdk/credential-provider-web-identity@3.936.0))(chokidar@4.0.3)(lru-cache@11.2.2)(vite@7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.2)) devDependencies: '@rslib/core': specifier: ^0.18.0 @@ -435,7 +397,7 @@ importers: version: link:../http nitro: specifier: ^3.0.0 - version: 3.0.0(@vercel/functions@3.3.3(@aws-sdk/credential-provider-web-identity@3.936.0))(chokidar@4.0.3)(lru-cache@11.2.2)(vite@7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)) + version: 3.0.0(@vercel/functions@3.3.3(@aws-sdk/credential-provider-web-identity@3.936.0))(chokidar@4.0.3)(lru-cache@11.2.2)(vite@7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.2)) devDependencies: '@rslib/core': specifier: ^0.18.0 @@ -490,7 +452,7 @@ importers: version: 5.9.3 vitest: specifier: ^4.0.13 - version: 4.0.13(@types/debug@4.1.12)(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6) + version: 4.0.13(@types/debug@4.1.12)(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.2) packages/shared/cli: dependencies: @@ -530,7 +492,7 @@ importers: version: 5.9.3 vitest: specifier: 'catalog:' - version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6) + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.2) packages/shared/config: dependencies: @@ -592,24 +554,6 @@ importers: packages: - '@actions/core@1.11.1': - resolution: {integrity: sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==} - - '@actions/exec@1.1.1': - resolution: {integrity: sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==} - - '@actions/github@6.0.1': - resolution: {integrity: sha512-xbZVcaqD4XnQAe35qSQqskb3SqIAfRyLBrHMd/8TuL7hJSz2QtbDwnNM8zWx4zO5l2fnGtseNE3MbEvD7BxVMw==} - - '@actions/glob@0.5.0': - resolution: {integrity: sha512-tST2rjPvJLRZLuT9NMUtyBjvj9Yo0MiJS3ow004slMvm8GFM+Zv9HvMJ7HWzfUyJnGrJvDsYkWBaaG3YKXRtCw==} - - '@actions/http-client@2.2.3': - resolution: {integrity: sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==} - - '@actions/io@1.1.3': - resolution: {integrity: sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==} - '@ast-grep/napi-darwin-arm64@0.37.0': resolution: {integrity: sha512-QAiIiaAbLvMEg/yBbyKn+p1gX2/FuaC0SMf7D7capm/oG4xGMzdeaQIcSosF4TCxxV+hIH4Bz9e4/u7w6Bnk3Q==} engines: {node: '>= 10'} @@ -745,6 +689,14 @@ packages: resolution: {integrity: sha512-sIyFcoPZkTtNu9xFeEoynMef3bPJIAbOfUh+ueYcfhVl6xm2VRtMcMclSxmZCMnHHd4hlYKJeq/aggmBEWynww==} engines: {node: '>=18.0.0'} + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + '@biomejs/biome@2.3.7': resolution: {integrity: sha512-CTbAS/jNAiUc6rcq94BrTB8z83O9+BsgWj2sBCQg9rD6Wkh2gjfR87usjx0Ncx0zGXP1NKgT7JNglay5Zfs9jw==} engines: {node: '>=14.21.3'} @@ -1128,10 +1080,6 @@ packages: cpu: [x64] os: [win32] - '@fastify/busboy@2.1.1': - resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} - engines: {node: '>=14'} - '@isaacs/balanced-match@4.0.1': resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} engines: {node: 20 || >=22} @@ -1274,80 +1222,29 @@ packages: '@napi-rs/wasm-runtime@1.0.7': resolution: {integrity: sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==} - '@octokit/auth-app@6.1.4': - resolution: {integrity: sha512-QkXkSOHZK4dA5oUqY5Dk3S+5pN2s1igPjEASNQV8/vgJgW034fQWR16u7VsNOK/EljA00eyjYF5mWNxWKWhHRQ==} - engines: {node: '>= 18'} - - '@octokit/auth-oauth-app@7.1.0': - resolution: {integrity: sha512-w+SyJN/b0l/HEb4EOPRudo7uUOSW51jcK1jwLa+4r7PA8FPFpoxEnHBHMITqCsc/3Vo2qqFjgQfz/xUUvsSQnA==} - engines: {node: '>= 18'} - - '@octokit/auth-oauth-device@6.1.0': - resolution: {integrity: sha512-FNQ7cb8kASufd6Ej4gnJ3f1QB5vJitkoV1O0/g6e6lUsQ7+VsSNRHRmFScN2tV4IgKA12frrr/cegUs0t+0/Lw==} - engines: {node: '>= 18'} - - '@octokit/auth-oauth-user@4.1.0': - resolution: {integrity: sha512-FrEp8mtFuS/BrJyjpur+4GARteUCrPeR/tZJzD8YourzoVhRics7u7we/aDcKv+yywRNwNi/P4fRi631rG/OyQ==} - engines: {node: '>= 18'} - - '@octokit/auth-token@4.0.0': - resolution: {integrity: sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==} - engines: {node: '>= 18'} - - '@octokit/core@5.2.2': - resolution: {integrity: sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==} - engines: {node: '>= 18'} - - '@octokit/endpoint@9.0.6': - resolution: {integrity: sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==} - engines: {node: '>= 18'} - - '@octokit/graphql-schema@15.26.1': - resolution: {integrity: sha512-RFDC2MpRBd4AxSRvUeBIVeBU7ojN/SxDfALUd7iVYOSeEK3gZaqR2MGOysj4Zh2xj2RY5fQAUT+Oqq7hWTraMA==} + '@npmcli/config@8.3.4': + resolution: {integrity: sha512-01rtHedemDNhUXdicU7s+QYz/3JyV5Naj84cvdXGH4mgCdL+agmSYaLF4LUG4vMCLzhBO8YtS0gPpH1FGvbgAw==} + engines: {node: ^16.14.0 || >=18.0.0} - '@octokit/graphql@7.1.1': - resolution: {integrity: sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==} - engines: {node: '>= 18'} - - '@octokit/oauth-authorization-url@6.0.2': - resolution: {integrity: sha512-CdoJukjXXxqLNK4y/VOiVzQVjibqoj/xHgInekviUJV73y/BSIcwvJ/4aNHPBPKcPWFnd4/lO9uqRV65jXhcLA==} - engines: {node: '>= 18'} - - '@octokit/oauth-methods@4.1.0': - resolution: {integrity: sha512-4tuKnCRecJ6CG6gr0XcEXdZtkTDbfbnD5oaHBmLERTjTMZNi2CbfEHZxPU41xXLDG4DfKf+sonu00zvKI9NSbw==} - engines: {node: '>= 18'} - - '@octokit/openapi-types@20.0.0': - resolution: {integrity: sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==} - - '@octokit/openapi-types@24.2.0': - resolution: {integrity: sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==} - - '@octokit/plugin-paginate-rest@9.2.2': - resolution: {integrity: sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==} - engines: {node: '>= 18'} - peerDependencies: - '@octokit/core': '5' - - '@octokit/plugin-rest-endpoint-methods@10.4.1': - resolution: {integrity: sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==} - engines: {node: '>= 18'} - peerDependencies: - '@octokit/core': '5' + '@npmcli/git@5.0.8': + resolution: {integrity: sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ==} + engines: {node: ^16.14.0 || >=18.0.0} - '@octokit/request-error@5.1.1': - resolution: {integrity: sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==} - engines: {node: '>= 18'} + '@npmcli/map-workspaces@3.0.6': + resolution: {integrity: sha512-tkYs0OYnzQm6iIRdfy+LcLBjcKuQCeE5YLb8KnrIlutJfheNaPvPpgoFEyEFgbjzl5PLZ3IA/BWAwRU0eHuQDA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - '@octokit/request@8.4.1': - resolution: {integrity: sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==} - engines: {node: '>= 18'} + '@npmcli/name-from-folder@2.0.0': + resolution: {integrity: sha512-pwK+BfEBZJbKdNYpHHRTNBwBoqrN/iIMO0AiGvYsp3Hoaq0WbgGSWQR6SCldZovoDpY3yje5lkFUe6gsDgJ2vg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - '@octokit/types@12.6.0': - resolution: {integrity: sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==} + '@npmcli/package-json@5.2.1': + resolution: {integrity: sha512-f7zYC6kQautXHvNbLEWgD/uGu1+xCn9izgqBfgItWSx22U0ZDekxN08A1vM8cTxj/cRVe0Q94Ode+tdoYmIOOQ==} + engines: {node: ^16.14.0 || >=18.0.0} - '@octokit/types@13.10.0': - resolution: {integrity: sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==} + '@npmcli/promise-spawn@7.0.2': + resolution: {integrity: sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==} + engines: {node: ^16.14.0 || >=18.0.0} '@orpc/client@1.11.3': resolution: {integrity: sha512-USuUOvG07odUzrn3/xGE0V+JbK6DV+eYqURa98kMelSoGRLP0ceqomu49s1+paKYgT1fefRDMaCKxo04hgRNhg==} @@ -1600,6 +1497,10 @@ packages: cpu: [x64] os: [win32] + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + '@rollup/rollup-android-arm-eabi@4.53.3': resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} cpu: [arm] @@ -2079,32 +1980,41 @@ packages: '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} - '@types/btoa-lite@1.0.2': - resolution: {integrity: sha512-ZYbcE2x7yrvNFJiU7xJGrpF/ihpkM7zKgw8bha3LNJSesvTtUNxbpzaT7WXBIryf6jovisrxTBvymxMeLLj1Mg==} - '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/concat-stream@2.0.3': + resolution: {integrity: sha512-3qe4oQAPNwVNwK4C9c8u+VJqv9kez+2MR4qJpoPFfXtgxxif1QbFusvXzK0/Wra2VX07smostI2VMmJNSpZjuQ==} + '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} '@types/fs-extra@11.0.4': resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/http-cache-semantics@4.0.4': resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} + '@types/is-empty@1.2.3': + resolution: {integrity: sha512-4J1l5d79hoIvsrKh5VUKVRA1aIdsOb10Hu5j3J2VfP/msDnfTdGPmNp2E1Wg+vs97Bktzo+MZePFFXSGoykYJw==} + '@types/jsonfile@6.1.4': resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} - '@types/jsonwebtoken@9.0.10': - resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} @@ -2115,6 +2025,18 @@ packages: '@types/node@24.10.1': resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} + '@types/supports-color@8.1.3': + resolution: {integrity: sha512-Hy6UMpxhE3j1tLpl27exp1XqHD7n8chAiNPzWfz16LPZoMMoSc4dzLl6w9qijkEb/r5O1ozdu1CWGA2L83ZeZg==} + + '@types/text-table@0.2.5': + resolution: {integrity: sha512-hcZhlNvMkQG/k1vcZ6yHOl6WAYftQ2MLfTHcYRZ2xYZFD8tGVnE3qFV0lj1smQeDSR7/yY0PyuUalauf33bJeA==} + + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@vercel/functions@3.3.3': resolution: {integrity: sha512-Gf+Nc/h7YjTpIhVk9UqGqKUcOIlnuSTqEKr7aApEeYjPUeqr/C4UddU6FyCyrFM0tnefKcOXVA6m7op+1JSZBg==} engines: {node: '>= 20'} @@ -2124,10 +2046,6 @@ packages: '@aws-sdk/credential-provider-web-identity': optional: true - '@vercel/ncc@0.38.4': - resolution: {integrity: sha512-8LwjnlP39s08C08J5NstzriPvW1SP8Zfpp1BvC2sI35kPeZnHfxVkCwu4/+Wodgnd60UtT1n8K8zw+Mp7J9JmQ==} - hasBin: true - '@vercel/oidc@3.0.5': resolution: {integrity: sha512-fnYhv671l+eTTp48gB4zEsTW/YtRgRPnkI2nT7x6qw5rkI1Lq2hTmQIpHPgyThI0znLK+vX2n9XxKdXZ7BUbbw==} engines: {node: '>= 20'} @@ -2190,10 +2108,6 @@ packages: '@vitest/utils@4.0.13': resolution: {integrity: sha512-ydozWyQ4LZuu8rLp47xFUWis5VOKMdHjXCWhs1LuJsTNKww+pTHQNK4e0assIB9K80TxFyskENL6vCu3j34EYA==} - '@wolfy1339/lru-cache@11.0.2-patch.1': - resolution: {integrity: sha512-BgYZfL2ADCXKOw2wJtkM3slhHotawWkgIRRxq4wEybnZQPjvAp71SPX35xepMykTw8gXlzWcWPTY31hlbnRsDA==} - engines: {node: 18 >=18.20 || 20 || >=22} - '@xhmikosr/archive-type@7.1.0': resolution: {integrity: sha512-xZEpnGplg1sNPyEgFh0zbHxqlw5dtYg6viplmWSxUj12+QjU9SKu3U/2G73a15pEjLaOqTefNSZ1fOPUOT4Xgg==} engines: {node: '>=18'} @@ -2234,6 +2148,20 @@ packages: resolution: {integrity: sha512-siPY6BD5dQ2SZPl3I0OZBHL27ZqZvLEosObsZRQ1NUB8qcxegwt0T9eKtV96JMFQpIz1elhkzqOg4c/Ri6Dp9A==} engines: {node: ^14.14.0 || >=16.0.0} + abbrev@2.0.0: + resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -2250,6 +2178,10 @@ packages: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + arch@3.0.0: resolution: {integrity: sha512-AmIAC+Wtm2AU8lGfTtHsw0Y9Qtftx2YXEEtiBP10xFUtMOA+sHHx6OAddyL52mUKh1vsXQ6/w1mVDptZCyUt4Q==} @@ -2268,6 +2200,9 @@ packages: react-native-b4a: optional: true + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -2282,9 +2217,6 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - before-after-hook@2.2.3: - resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} - bin-version-check@5.1.0: resolution: {integrity: sha512-bYsvMqJ8yNGILLz1KP9zKLzQ6YpljV3ln1gqhuLkUtyfGi3qXKGuK2p+U4NAvjVFzDFiBBtOpCOSFNuYYEGZ5g==} engines: {node: '>=12'} @@ -2293,23 +2225,25 @@ packages: resolution: {integrity: sha512-nk5wEsP4RiKjG+vF+uG8lFsEn4d7Y6FVDamzzftSunXOoOcOOkzcWdKVlGgFFwlUQCj63SgnUkLLGF8v7lufhw==} engines: {node: '>=12'} + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + bowser@2.12.1: resolution: {integrity: sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==} - brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - btoa-lite@1.0.0: - resolution: {integrity: sha512-gvW7InbIyF8AicrqWoptdW08pUxuhq8BEgowNajy9RhiE86fmGAGl+bLKo6oB8QP0CkqHLowfN0oJdKC/J6LbA==} + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} - buffer-equal-constant-time@1.0.1: - resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} @@ -2330,6 +2264,9 @@ packages: resolution: {integrity: sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==} engines: {node: '>=14.16'} + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@5.3.3: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} engines: {node: '>=18'} @@ -2342,17 +2279,40 @@ packages: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + check-error@2.1.1: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} engines: {node: '>= 16'} + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} + ci-info@4.3.1: + resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==} + engines: {node: '>=8'} + citty@0.1.6: resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + collapse-white-space@2.1.0: + resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -2360,6 +2320,9 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@14.0.2: resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==} engines: {node: '>=20'} @@ -2376,8 +2339,9 @@ packages: resolution: {integrity: sha512-r1To31BQD5060QdkC+Iheai7gHwoSZobzunqkf2/kQ6xIAfJyrKNAFUwdKvkK7Qgu7pVTKQEa7ok7Ed3ycAJgg==} engines: {node: '>= 6'} - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + concat-stream@2.0.0: + resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} + engines: {'0': node >= 6.0} confbox@0.2.2: resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} @@ -2447,6 +2411,9 @@ packages: supports-color: optional: true + decode-named-character-reference@1.2.0: + resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} + decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -2470,8 +2437,9 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} - deprecation@2.3.1: - resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} destr@2.0.5: resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} @@ -2480,11 +2448,14 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - ecdsa-sig-formatter@1.0.11: - resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -2496,6 +2467,12 @@ packages: resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} engines: {node: '>=10.13.0'} + err-code@2.0.3: + resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} @@ -2518,6 +2495,12 @@ packages: engines: {node: '>=4'} hasBin: true + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + + estree-util-visit@2.0.0: + resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -2543,6 +2526,9 @@ packages: resolution: {integrity: sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==} engines: {node: '>=4'} + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-fifo@1.3.2: resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} @@ -2550,6 +2536,9 @@ packages: resolution: {integrity: sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==} hasBin: true + fault@2.0.1: + resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -2577,6 +2566,10 @@ packages: resolution: {integrity: sha512-vqIlNogKeyD3yzrm0yhRMQg8hOVwYcYRfjEoODd49iCprMn4HL85gK3HcykQE53EPIpX3HcAbGA5ELQv216dAQ==} engines: {node: '>=16'} + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + find-up@8.0.0: resolution: {integrity: sha512-JGG8pvDi2C+JxidYdIwQDyS/CgcrIdh18cvgxcBge3wSHRQOrooMD3GlFBcmMJAN9M42SAZjDp5zv1dglJjwww==} engines: {node: '>=20'} @@ -2593,6 +2586,10 @@ packages: resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==} engines: {node: '>= 14.17'} + format@0.2.2: + resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} + engines: {node: '>=0.4.x'} + fs-extra@11.3.2: resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==} engines: {node: '>=14.14'} @@ -2614,6 +2611,14 @@ packages: get-tsconfig@4.13.0: resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + hasBin: true + glob@12.0.0: resolution: {integrity: sha512-5Qcll1z7IKgHr5g485ePDdHcNQY0k2dtv/bjYy0iuyGxQw2qSOiiXUXJ+AYQpg3HNoUMHqAruX478Jeev7UULw==} engines: {node: 20 || >=22} @@ -2630,16 +2635,6 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - graphql-tag@2.12.6: - resolution: {integrity: sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==} - engines: {node: '>=10'} - peerDependencies: - graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 - - graphql@16.12.0: - resolution: {integrity: sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==} - engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} - h3@2.0.1-rc.2: resolution: {integrity: sha512-2vS7OETzPDzGQxmmcs6ttu7p0NW25zAdkPXYOr43dn4GZf81uUljJvupa158mcpUGpsQUqIy4O4THWUQT1yVeA==} engines: {node: '>=20.11.1'} @@ -2662,6 +2657,10 @@ packages: resolution: {integrity: sha512-BIdolzGpDO9MQ4nu3AUuDwHZZ+KViNm+EZ75Ae55eMXMqLVhDFqEMXxtUe9Qh8hjL+pIna/frs2j6Y2yD5Ua/g==} engines: {node: '>=16.9.0'} + hosted-git-info@7.0.2: + resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} + engines: {node: ^16.14.0 || >=18.0.0} + http-cache-semantics@4.2.0: resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} @@ -2676,17 +2675,69 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@6.0.2: + resolution: {integrity: sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==} + engines: {node: '>= 4'} + + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@4.1.3: + resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + inspect-with-kind@1.0.5: resolution: {integrity: sha512-MAQUJuIo7Xqk8EVNP+6d3CKq9c80hi4tjIbIAT6lmGW9W6WzlHiu9PS8uSuUYU+Do+j1baiFp3H25XEVxDIG2g==} + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + + is-empty@1.2.0: + resolution: {integrity: sha512-F2FnH/otLNJv0J6wc73A5Xo7oHLNnqplYqZhUu01tD54DIPvxIRSTSLkrUB/M0nHO4vo1O9PDfN4KoTxCzLh/w==} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + is-plain-obj@1.1.0: resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} engines: {node: '>=0.10.0'} + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -2694,6 +2745,13 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isexe@3.1.1: + resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} + engines: {node: '>=16'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jackspeak@4.1.1: resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} engines: {node: 20 || >=22} @@ -2702,28 +2760,30 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + json-parse-even-better-errors@3.0.2: + resolution: {integrity: sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + jsonc-parser@3.3.1: resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} jsonfile@6.2.0: resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} - jsonwebtoken@9.0.2: - resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} - engines: {node: '>=12', npm: '>=6'} - - jwa@1.4.2: - resolution: {integrity: sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==} - - jws@3.2.2: - resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} - keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -2801,30 +2861,19 @@ packages: resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} engines: {node: '>= 12.0.0'} + lines-and-columns@2.0.4: + resolution: {integrity: sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + load-plugin@6.0.3: + resolution: {integrity: sha512-kc0X2FEUZr145odl68frm+lMJuQ23+rTXYmR6TImqPtbpmXC4vVXbWKDQ9IzndA0HfyQamWfKLhzsqGSTxE63w==} + locate-path@8.0.0: resolution: {integrity: sha512-XT9ewWAC43tiAV7xDAPflMkG0qOPn2QjHqlgX8FOqmWa/rxnyYDulF9T0F7tRy1u+TVTmK/M//6VIOye+2zDXg==} engines: {node: '>=20'} - lodash.includes@4.3.0: - resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} - - lodash.isboolean@3.0.3: - resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} - - lodash.isinteger@4.0.4: - resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} - - lodash.isnumber@3.0.3: - resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} - - lodash.isplainobject@4.0.6: - resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} - - lodash.isstring@4.0.1: - resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} - - lodash.once@4.1.1: - resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} loupe@3.2.1: resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} @@ -2833,6 +2882,9 @@ packages: resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.2.2: resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==} engines: {node: 20 || >=22} @@ -2840,9 +2892,136 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + markdown-extensions@2.0.0: + resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} + engines: {node: '>=16'} + + mdast-comment-marker@3.0.0: + resolution: {integrity: sha512-bt08sLmTNg00/UtVDiqZKocxqvQqqyQZAg1uaRuO/4ysXV5motg7RolF5o5yy/sY1rG0v2XgZEqFWho1+2UquA==} + + mdast-util-directive@3.1.0: + resolution: {integrity: sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==} + + mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + + mdast-util-frontmatter@2.0.1: + resolution: {integrity: sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==} + + mdast-util-heading-style@3.0.0: + resolution: {integrity: sha512-tsUfM9Kj9msjlemA/38Z3pvraQay880E3zP2NgIthMoGcpU9bcPX9oSM6QC/+eFXGGB4ba+VCB1dKAPHB7Veug==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdx@3.0.0: + resolution: {integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-frontmatter@2.0.0: + resolution: {integrity: sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==} + + micromark-extension-mdx-expression@3.0.1: + resolution: {integrity: sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==} + + micromark-extension-mdx-jsx@3.0.2: + resolution: {integrity: sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==} + + micromark-extension-mdx-md@2.0.0: + resolution: {integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==} + + micromark-extension-mdxjs-esm@3.0.0: + resolution: {integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==} + + micromark-extension-mdxjs@3.0.0: + resolution: {integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-mdx-expression@2.0.3: + resolution: {integrity: sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-events-to-acorn@2.0.3: + resolution: {integrity: sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + mime-db@1.54.0: resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} engines: {node: '>= 0.6'} @@ -2863,13 +3042,13 @@ packages: resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} engines: {node: 20 || >=22} - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} @@ -2924,10 +3103,39 @@ packages: node-fetch-native@1.6.7: resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + nopt@7.2.1: + resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + hasBin: true + + normalize-package-data@6.0.2: + resolution: {integrity: sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==} + engines: {node: ^16.14.0 || >=18.0.0} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + normalize-url@8.1.0: resolution: {integrity: sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==} engines: {node: '>=14.16'} + npm-install-checks@6.3.0: + resolution: {integrity: sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + npm-normalize-package-bin@3.0.1: + resolution: {integrity: sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + npm-package-arg@11.0.3: + resolution: {integrity: sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==} + engines: {node: ^16.14.0 || >=18.0.0} + + npm-pick-manifest@9.1.0: + resolution: {integrity: sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==} + engines: {node: ^16.14.0 || >=18.0.0} + npm-run-path@4.0.1: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} @@ -2946,9 +3154,6 @@ packages: ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} @@ -2982,10 +3187,21 @@ packages: package-manager-detector@1.5.0: resolution: {integrity: sha512-uBj69dVlYe/+wxj8JOpr97XfsxH/eumMt6HqjNTmJDf/6NO9s+0uxeOneIz3AsPt2m6y9PqzDzd3ATcU17MNfw==} + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + + parse-json@7.1.1: + resolution: {integrity: sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==} + engines: {node: '>=16'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + path-scurry@2.0.1: resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} engines: {node: 20 || >=22} @@ -3003,6 +3219,10 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + picomatch@4.0.3: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} @@ -3023,10 +3243,30 @@ packages: engines: {node: '>=18'} hasBin: true + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} + proc-log@4.2.0: + resolution: {integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + promise-inflight@1.0.1: + resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} + peerDependencies: + bluebird: '*' + peerDependenciesMeta: + bluebird: + optional: true + + promise-retry@2.0.1: + resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} + engines: {node: '>=10'} + quick-lru@5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} @@ -3035,10 +3275,128 @@ packages: resolution: {integrity: sha512-h36JMxKRqrAxVD8201FrCpyeNuUY9Y5zZwujr20fFO77tpUtGa6EZzfKw/3WaiBX95fq7+MpsuMLNdSnORAwSA==} engines: {node: '>=14.18.0'} + read-package-json-fast@3.0.2: + resolution: {integrity: sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + remark-cli@12.0.1: + resolution: {integrity: sha512-2NAEOACoTgo+e+YAaCTODqbrWyhMVmlUyjxNCkTrDRHHQvH6+NbrnqVvQaLH/Q8Ket3v90A43dgAJmXv8y5Tkw==} + hasBin: true + + remark-frontmatter@5.0.0: + resolution: {integrity: sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==} + + remark-lint-blockquote-indentation@4.0.1: + resolution: {integrity: sha512-7BhOsImFgTD7IIliu2tt+yJbx5gbMbXCOspc3VdYf/87iLJdWKqJoMy2V6DZG7kBjBlBsIZi38fDDngJttXt4w==} + + remark-lint-checkbox-character-style@5.0.1: + resolution: {integrity: sha512-6qilm7XQXOcTvjFEqqNY57Ki7md9rkSdpMIfIzVXdEnI4Npl2BnUff6ANrGRM7qTgJTrloaf8H0eQ91urcU6Og==} + + remark-lint-code-block-style@4.0.1: + resolution: {integrity: sha512-d4mHsEpv1yqXWl2dd+28tGRX0Lzk5qw7cfxAQVkOXPUONhsMFwXJEBeeqZokeG4lOKtkKdIJR7ezScDfWR0X4w==} + + remark-lint-emphasis-marker@4.0.1: + resolution: {integrity: sha512-BF1WWsAxai3XoKk48sfiqT3L8m02AZLj3BnipWkHDRXuLfz6VwsHVaHWyNvvE0p6b2B3A5dSYbcfJu5RmPx4tQ==} + + remark-lint-fenced-code-marker@4.0.1: + resolution: {integrity: sha512-uI91OcVPKjNxV+vpjDW9T64hkE0a/CRn3JhwdMxUAJYpVsKnA7PFPSFJOx/abNsVZHNSe7ZFGgGdaH/lqgSizA==} + + remark-lint-final-newline@3.0.1: + resolution: {integrity: sha512-q5diKHD6BMbzqWqgvYPOB8AJgLrMzEMBAprNXjcpKoZ/uCRqly+gxjco+qVUMtMWSd+P+KXZZEqoa7Y6QiOudw==} + + remark-lint-hard-break-spaces@4.1.1: + resolution: {integrity: sha512-AKDPDt39fvmr3yk38OKZEWJxxCOOUBE+96AsBfs+ExS5LW6oLa9041X5ahFDQHvHGzdoremEIaaElursaPEkNg==} + + remark-lint-heading-style@4.0.1: + resolution: {integrity: sha512-+rUpJ/N2CGC5xPgZ18XgsCsUBtadgEhdTi0BJPrsFmHPzL22BUHajeg9im8Y7zphUcbi1qFiKuxZd2nzDgZSXQ==} + + remark-lint-link-title-style@4.0.1: + resolution: {integrity: sha512-MtmnYrhjhRXR0zeiyYf/7GBlUF5KAPypJb345KjyDluOhI4Wj4VAXvVQuov/MFc3y8p/1yVwv3QDYv6yue8/wQ==} + + remark-lint-list-item-bullet-indent@5.0.1: + resolution: {integrity: sha512-LKuTxkw5aYChzZoF3BkfaBheSCHs0T8n8dPHLQEuOLo6iC5wy98iyryz0KZ61GD8stlZgQO2KdWSdnP6vr40Iw==} + + remark-lint-list-item-content-indent@4.0.1: + resolution: {integrity: sha512-KSopxxp64O6dLuTQ2sWaTqgjKWr1+AoB1QCTektMJ3mfHfn0QyZzC2CZbBU22KGzBhiYXv9cIxlJlxUtq2NqHg==} + + remark-lint-list-item-indent@4.0.1: + resolution: {integrity: sha512-gJd1Q+jOAeTgmGRsdMpnRh01DUrAm0O5PCQxE8ttv1QZOV015p/qJH+B4N6QSmcUuPokHLAh9USuq05C73qpiA==} + + remark-lint-no-blockquote-without-marker@6.0.1: + resolution: {integrity: sha512-b4IOkNcG7C16HYAdKUeAhO7qPt45m+v7SeYbVrqvbSFtlD3EUBL8fgHRgLK1mdujFXDP1VguOEMx+Txv8JOT4w==} + + remark-lint-no-duplicate-definitions@4.0.1: + resolution: {integrity: sha512-Ek+A/xDkv5Nn+BXCFmf+uOrFSajCHj6CjhsHjtROgVUeEPj726yYekDBoDRA0Y3+z+U30AsJoHgf/9Jj1IFSug==} + + remark-lint-no-heading-content-indent@5.0.1: + resolution: {integrity: sha512-YIWktnZo7M9aw7PGnHdshvetSH3Y0qW+Fm143R66zsk5lLzn1XA5NEd/MtDzP8tSxxV+gcv+bDd5St1QUI4oSQ==} + + remark-lint-no-literal-urls@4.0.1: + resolution: {integrity: sha512-RhTANFkFFXE6bM+WxWcPo2TTPEfkWG3lJZU50ycW7tJJmxUzDNzRed/z80EVJIdGwFa0NntVooLUJp3xrogalQ==} + + remark-lint-no-shortcut-reference-image@4.0.1: + resolution: {integrity: sha512-hQhJ3Dr8ZWRdj7qm6+9vcPpqtGchhENA2UHOmcTraLf6dN1cFATCgY/HbTbRIN6NkG/EEClTgRC1QCokWR2Mmw==} + + remark-lint-no-shortcut-reference-link@4.0.1: + resolution: {integrity: sha512-YxciuUZc90QaJYhayGO80lS3zxEOBgwwLW1MKYB7AfUdkrLcLVlS+DFloiq0MZ7EDVXuuGUEnIzyjyLSbI5BUA==} + + remark-lint-no-undefined-references@5.0.2: + resolution: {integrity: sha512-5prkVb1tKwJwr5+kct/UjsLjvMdEDO7uClPeGfrxfAcN59+pWU8OUSYiqYmpSKWJPIdyxPRS8Oyf1HtaYvg8VQ==} + + remark-lint-no-unused-definitions@4.0.2: + resolution: {integrity: sha512-KRzPmvfq6b3LSEcAQZobAn+5eDfPTle0dPyDEywgPSc3E7MIdRZQenL9UL8iIqHQWK4FvdUD0GX8FXGqu5EuCw==} + + remark-lint-ordered-list-marker-style@4.0.1: + resolution: {integrity: sha512-vZTAbstcBPbGwJacwldGzdGmKwy5/4r29SZ9nQkME4alEl5B1ReSBlYa8t7QnTSW7+tqvA9Sg71RPadgAKWa4w==} + + remark-lint-ordered-list-marker-value@4.0.1: + resolution: {integrity: sha512-HQb1MrArvApREC1/I6bkiFlZVDjngsuII29n8E8StnAaHOMN3hVYy6wJ9Uk+O3+X9O8v7fDsZPqFUHSfJhERXQ==} + + remark-lint-rule-style@4.0.1: + resolution: {integrity: sha512-gl1Ft13oTS3dJUCsWZzxD/5dAwI1HON67KU7uNfODD5gXJ8Y11deOWbun190ma7XbYdD7P0l8VT2HeRtEQzrWg==} + + remark-lint-strong-marker@4.0.1: + resolution: {integrity: sha512-KaGtj/OWEP4eoafevnlp3NsEVwC7yGEjBJ6uFMzfjNoXyjATdfZ2euB/AfKVt/A/FdZeeMeVoAUFH4DL+hScLQ==} + + remark-lint-table-cell-padding@5.1.1: + resolution: {integrity: sha512-6fgVA1iINBoAJaZMOnSsxrF9Qj9+hmCqrsrqZqgJJETjT1ODGH64iAN1/6vHR7dIwmy73d6ysB2WrGyKhVlK3A==} + + remark-lint@10.0.1: + resolution: {integrity: sha512-1+PYGFziOg4pH7DDf1uMd4AR3YuO2EMnds/SdIWMPGT7CAfDRSnAmpxPsJD0Ds3IKpn97h3d5KPGf1WFOg6hXQ==} + + remark-mdx@3.1.1: + resolution: {integrity: sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==} + + remark-message-control@8.0.0: + resolution: {integrity: sha512-brpzOO+jdyE/mLqvqqvbogmhGxKygjpCUCG/PwSCU43+JZQ+RM+sSzkCWBcYvgF3KIAVNIoPsvXjBkzO7EdsYQ==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-preset-lint-consistent@6.0.1: + resolution: {integrity: sha512-SOLdA36UOU1hiGFm6HAqN9+DORGJPVWxU/EvPVkknTr9V4ULhlzHEJ8OVRMVX3jqoy4lrwb4IqiboVz0YLA7+Q==} + + remark-preset-lint-recommended@7.0.1: + resolution: {integrity: sha512-j1CY5u48PtZl872BQ40uWSQMT3R4gXKp0FUgevMu5gW7hFMtvaCiDq+BfhzeR8XKKiW9nIMZGfIMZHostz5X4g==} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + + remark@15.0.1: + resolution: {integrity: sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==} + rendu@0.0.6: resolution: {integrity: sha512-nZ512Dw0MxKiIYfCVv8DPe6ig4m0Qt3FOYBJEXrammjIYBBPuHaudc0AGfYx+iyOw2q0itAtPywiVZXtTFCsig==} hasBin: true @@ -3053,6 +3411,10 @@ packages: resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==} engines: {node: '>=14.16'} + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + rollup@4.53.3: resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -3138,6 +3500,21 @@ packages: resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} engines: {node: '>= 12'} + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + + spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + + spdx-license-ids@3.0.22: + resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==} + srvx@0.8.16: resolution: {integrity: sha512-hmcGW4CgroeSmzgF1Ihwgl+Ths0JqAJ7HwjP2X7e3JzY7u4IydLMcdnlqGQiQGUswz+PO9oh/KtCpOISIvs9QQ==} engines: {node: '>=20.16.0'} @@ -3165,6 +3542,16 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string-width@6.1.0: + resolution: {integrity: sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==} + engines: {node: '>=16'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -3190,6 +3577,10 @@ packages: resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==} engines: {node: '>=18'} + supports-color@9.4.0: + resolution: {integrity: sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==} + engines: {node: '>=12'} + tagged-tag@1.0.0: resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} engines: {node: '>=20'} @@ -3204,6 +3595,9 @@ packages: text-decoder@1.2.3: resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} @@ -3237,10 +3631,17 @@ packages: resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} engines: {node: '>=14.0.0'} + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + token-types@6.1.1: resolution: {integrity: sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==} engines: {node: '>=14.16'} + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + trpc-cli@0.12.1: resolution: {integrity: sha512-/D/mIQf3tUrS7ZKJZ1gmSPJn2psAABJfkC5Eevm55SZ4s6KwANOUNlwhAGXN9HT4VSJVfoF2jettevE9vHPQlg==} engines: {node: '>=18'} @@ -3274,10 +3675,6 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - tunnel@0.0.6: - resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} - engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} - turbo-darwin-64@2.6.1: resolution: {integrity: sha512-Dm0HwhyZF4J0uLqkhUyCVJvKM9Rw7M03v3J9A7drHDQW0qAbIGBrUijQ8g4Q9Cciw/BXRRd8Uzkc3oue+qn+ZQ==} cpu: [x64] @@ -3312,10 +3709,17 @@ packages: resolution: {integrity: sha512-qBwXXuDT3rA53kbNafGbT5r++BrhRgx3sAo0cHoDAeG9g1ItTmUMgltz3Hy7Hazy1ODqNpR+C7QwqL6DYB52yA==} hasBin: true + type-fest@3.13.1: + resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} + engines: {node: '>=14.16'} + type-fest@5.2.0: resolution: {integrity: sha512-xxCJm+Bckc6kQBknN7i9fnP/xobQRsRQxR01CztFkp/h++yfVxUUcmMgfR2HttJx/dpWjS9ubVuyspJv24Q9DA==} engines: {node: '>=20'} + typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -3341,10 +3745,6 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - undici@5.29.0: - resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==} - engines: {node: '>=14.0'} - undici@7.16.0: resolution: {integrity: sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==} engines: {node: '>=20.18.1'} @@ -3359,11 +3759,41 @@ packages: resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} engines: {node: '>=18'} - universal-github-app-jwt@1.2.0: - resolution: {integrity: sha512-dncpMpnsKBk0eetwfN8D8OUHGfiDhhJ+mtsbMl+7PfW7mYjiH8LIcqRmYMtzYLgSh47HjfdBtrBwIQ/gizKR3g==} + unified-args@11.0.1: + resolution: {integrity: sha512-WEQghE91+0s3xPVs0YW6a5zUduNLjmANswX7YbBfksHNDGMjHxaWCql4SR7c9q0yov/XiIEdk6r/LqfPjaYGcw==} + + unified-engine@11.2.2: + resolution: {integrity: sha512-15g/gWE7qQl9tQ3nAEbMd5h9HV1EACtFs6N9xaRBZICoCwnNGbal1kOs++ICf4aiTdItZxU2s/kYWhW7htlqJg==} + + unified-lint-rule@3.0.1: + resolution: {integrity: sha512-HxIeQOmwL19DGsxHXbeyzKHBsoSCFO7UtRVUvT2v61ptw/G+GbysWcrpHdfs5jqbIFDA11MoKngIhQK0BeTVjA==} + + unified-message-control@5.0.0: + resolution: {integrity: sha512-B2cSAkpuMVVmPP90KCfKdBhm1e9KYJ+zK3x5BCa0N65zpq1Ybkc9C77+M5qwR8FWO7RF3LM5QRRPZtgjW6DUCw==} + + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} - universal-user-agent@6.0.1: - resolution: {integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==} + unist-util-inspect@8.1.0: + resolution: {integrity: sha512-mOlg8Mp33pR0eeFpo5d2902ojqFFOKMMG2hF8bmH7ZlhnmjFgh0NI3/ZDwdaBJNbvrS7LZFVrBVtIE9KZ9s7vQ==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position-from-estree@2.0.0: + resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} @@ -3517,6 +3947,34 @@ packages: uploadthing: optional: true + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + + validate-npm-package-name@5.0.1: + resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile-reporter@8.1.1: + resolution: {integrity: sha512-qxRZcnFSQt6pWKn3PAk81yLK2rO2i7CDXpy8v8ZquiEOMLSnPw6BMSi9Y1sUCwGGl7a9b3CJT1CKpnRF7pp66g==} + + vfile-sort@4.0.0: + resolution: {integrity: sha512-lffPI1JrbHDTToJwcq0rl6rBmkjQmMuXkAxsZPRS9DXbaJQvc642eCg6EGxcX2i1L+esbuhq+2l9tBll5v8AeQ==} + + vfile-statistics@3.0.0: + resolution: {integrity: sha512-/qlwqwWBWFOmpXujL/20P+Iuydil0rZZNglR+VNm6J0gpLHwuVM5s7g2TfVoswbXjZ4HuIhLMySEyIw5i7/D8w==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + vite-node@3.2.4: resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -3627,11 +4085,19 @@ packages: jsdom: optional: true + walk-up-path@3.0.1: + resolution: {integrity: sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true + which@4.0.0: + resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} + engines: {node: ^16.13.0 || >=18.0.0} + hasBin: true + why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} @@ -3648,8 +4114,10 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + yaml@2.8.2: + resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} + engines: {node: '>= 14.6'} + hasBin: true yauzl@3.2.0: resolution: {integrity: sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==} @@ -3662,38 +4130,10 @@ packages: zod@4.1.12: resolution: {integrity: sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==} -snapshots: - - '@actions/core@1.11.1': - dependencies: - '@actions/exec': 1.1.1 - '@actions/http-client': 2.2.3 + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} - '@actions/exec@1.1.1': - dependencies: - '@actions/io': 1.1.3 - - '@actions/github@6.0.1': - dependencies: - '@actions/http-client': 2.2.3 - '@octokit/core': 5.2.2 - '@octokit/plugin-paginate-rest': 9.2.2(@octokit/core@5.2.2) - '@octokit/plugin-rest-endpoint-methods': 10.4.1(@octokit/core@5.2.2) - '@octokit/request': 8.4.1 - '@octokit/request-error': 5.1.1 - undici: 5.29.0 - - '@actions/glob@0.5.0': - dependencies: - '@actions/core': 1.11.1 - minimatch: 3.1.2 - - '@actions/http-client@2.2.3': - dependencies: - tunnel: 0.0.6 - undici: 5.29.0 - - '@actions/io@1.1.3': {} +snapshots: '@ast-grep/napi-darwin-arm64@0.37.0': optional: true @@ -3929,6 +4369,14 @@ snapshots: '@aws/lambda-invoke-store@0.2.1': optional: true + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-validator-identifier@7.28.5': {} + '@biomejs/biome@2.3.7': optionalDependencies: '@biomejs/cli-darwin-arm64': 2.3.7 @@ -4149,8 +4597,6 @@ snapshots: '@esbuild/win32-x64@0.27.0': optional: true - '@fastify/busboy@2.1.1': {} - '@isaacs/balanced-match@4.0.1': {} '@isaacs/brace-expansion@5.0.0': @@ -4272,116 +4718,57 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@octokit/auth-app@6.1.4': + '@npmcli/config@8.3.4': dependencies: - '@octokit/auth-oauth-app': 7.1.0 - '@octokit/auth-oauth-user': 4.1.0 - '@octokit/request': 8.4.1 - '@octokit/request-error': 5.1.1 - '@octokit/types': 13.10.0 - deprecation: 2.3.1 - lru-cache: '@wolfy1339/lru-cache@11.0.2-patch.1' - universal-github-app-jwt: 1.2.0 - universal-user-agent: 6.0.1 - - '@octokit/auth-oauth-app@7.1.0': - dependencies: - '@octokit/auth-oauth-device': 6.1.0 - '@octokit/auth-oauth-user': 4.1.0 - '@octokit/request': 8.4.1 - '@octokit/types': 13.10.0 - '@types/btoa-lite': 1.0.2 - btoa-lite: 1.0.0 - universal-user-agent: 6.0.1 - - '@octokit/auth-oauth-device@6.1.0': - dependencies: - '@octokit/oauth-methods': 4.1.0 - '@octokit/request': 8.4.1 - '@octokit/types': 13.10.0 - universal-user-agent: 6.0.1 - - '@octokit/auth-oauth-user@4.1.0': - dependencies: - '@octokit/auth-oauth-device': 6.1.0 - '@octokit/oauth-methods': 4.1.0 - '@octokit/request': 8.4.1 - '@octokit/types': 13.10.0 - btoa-lite: 1.0.0 - universal-user-agent: 6.0.1 - - '@octokit/auth-token@4.0.0': {} - - '@octokit/core@5.2.2': - dependencies: - '@octokit/auth-token': 4.0.0 - '@octokit/graphql': 7.1.1 - '@octokit/request': 8.4.1 - '@octokit/request-error': 5.1.1 - '@octokit/types': 13.10.0 - before-after-hook: 2.2.3 - universal-user-agent: 6.0.1 - - '@octokit/endpoint@9.0.6': - dependencies: - '@octokit/types': 13.10.0 - universal-user-agent: 6.0.1 - - '@octokit/graphql-schema@15.26.1': - dependencies: - graphql: 16.12.0 - graphql-tag: 2.12.6(graphql@16.12.0) - - '@octokit/graphql@7.1.1': - dependencies: - '@octokit/request': 8.4.1 - '@octokit/types': 13.10.0 - universal-user-agent: 6.0.1 - - '@octokit/oauth-authorization-url@6.0.2': {} - - '@octokit/oauth-methods@4.1.0': - dependencies: - '@octokit/oauth-authorization-url': 6.0.2 - '@octokit/request': 8.4.1 - '@octokit/request-error': 5.1.1 - '@octokit/types': 13.10.0 - btoa-lite: 1.0.0 - - '@octokit/openapi-types@20.0.0': {} - - '@octokit/openapi-types@24.2.0': {} - - '@octokit/plugin-paginate-rest@9.2.2(@octokit/core@5.2.2)': - dependencies: - '@octokit/core': 5.2.2 - '@octokit/types': 12.6.0 + '@npmcli/map-workspaces': 3.0.6 + '@npmcli/package-json': 5.2.1 + ci-info: 4.3.1 + ini: 4.1.3 + nopt: 7.2.1 + proc-log: 4.2.0 + semver: 7.7.3 + walk-up-path: 3.0.1 + transitivePeerDependencies: + - bluebird - '@octokit/plugin-rest-endpoint-methods@10.4.1(@octokit/core@5.2.2)': + '@npmcli/git@5.0.8': dependencies: - '@octokit/core': 5.2.2 - '@octokit/types': 12.6.0 + '@npmcli/promise-spawn': 7.0.2 + ini: 4.1.3 + lru-cache: 10.4.3 + npm-pick-manifest: 9.1.0 + proc-log: 4.2.0 + promise-inflight: 1.0.1 + promise-retry: 2.0.1 + semver: 7.7.3 + which: 4.0.0 + transitivePeerDependencies: + - bluebird - '@octokit/request-error@5.1.1': + '@npmcli/map-workspaces@3.0.6': dependencies: - '@octokit/types': 13.10.0 - deprecation: 2.3.1 - once: 1.4.0 + '@npmcli/name-from-folder': 2.0.0 + glob: 10.5.0 + minimatch: 9.0.5 + read-package-json-fast: 3.0.2 - '@octokit/request@8.4.1': - dependencies: - '@octokit/endpoint': 9.0.6 - '@octokit/request-error': 5.1.1 - '@octokit/types': 13.10.0 - universal-user-agent: 6.0.1 + '@npmcli/name-from-folder@2.0.0': {} - '@octokit/types@12.6.0': + '@npmcli/package-json@5.2.1': dependencies: - '@octokit/openapi-types': 20.0.0 + '@npmcli/git': 5.0.8 + glob: 10.5.0 + hosted-git-info: 7.0.2 + json-parse-even-better-errors: 3.0.2 + normalize-package-data: 6.0.2 + proc-log: 4.2.0 + semver: 7.7.3 + transitivePeerDependencies: + - bluebird - '@octokit/types@13.10.0': + '@npmcli/promise-spawn@7.0.2': dependencies: - '@octokit/openapi-types': 24.2.0 + which: 4.0.0 '@orpc/client@1.11.3': dependencies: @@ -4622,6 +5009,9 @@ snapshots: '@oxc-transform/binding-win32-x64-msvc@0.96.0': optional: true + '@pkgjs/parseargs@0.11.0': + optional: true + '@rollup/rollup-android-arm-eabi@4.53.3': optional: true @@ -5180,20 +5570,25 @@ snapshots: tslib: 2.8.1 optional: true - '@types/btoa-lite@1.0.2': {} - '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 + '@types/concat-stream@2.0.3': + dependencies: + '@types/node': 24.10.1 + '@types/debug@4.1.12': dependencies: '@types/ms': 2.1.0 - optional: true '@types/deep-eql@4.0.2': {} + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.8 + '@types/estree@1.0.8': {} '@types/fs-extra@11.0.4': @@ -5201,16 +5596,21 @@ snapshots: '@types/jsonfile': 6.1.4 '@types/node': 24.10.1 + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + '@types/http-cache-semantics@4.0.4': {} + '@types/is-empty@1.2.3': {} + '@types/jsonfile@6.1.4': dependencies: '@types/node': 24.10.1 - '@types/jsonwebtoken@9.0.10': + '@types/mdast@4.0.4': dependencies: - '@types/ms': 2.1.0 - '@types/node': 24.10.1 + '@types/unist': 3.0.3 '@types/ms@2.1.0': {} @@ -5222,6 +5622,14 @@ snapshots: dependencies: undici-types: 7.16.0 + '@types/supports-color@8.1.3': {} + + '@types/text-table@0.2.5': {} + + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + '@vercel/functions@3.3.3(@aws-sdk/credential-provider-web-identity@3.936.0)': dependencies: '@vercel/oidc': 3.0.5 @@ -5229,8 +5637,6 @@ snapshots: '@aws-sdk/credential-provider-web-identity': 3.936.0 optional: true - '@vercel/ncc@0.38.4': {} - '@vercel/oidc@3.0.5': optional: true @@ -5251,21 +5657,21 @@ snapshots: chai: 6.2.1 tinyrainbow: 3.0.3 - '@vitest/mocker@3.2.4(vite@7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6))': + '@vitest/mocker@3.2.4(vite@7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.2))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6) + vite: 7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.2) - '@vitest/mocker@4.0.13(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6))': + '@vitest/mocker@4.0.13(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.2))': dependencies: '@vitest/spy': 4.0.13 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6) + vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.2) '@vitest/pretty-format@3.2.4': dependencies: @@ -5315,8 +5721,6 @@ snapshots: '@vitest/pretty-format': 4.0.13 tinyrainbow: 3.0.3 - '@wolfy1339/lru-cache@11.0.2-patch.1': {} - '@xhmikosr/archive-type@7.1.0': dependencies: file-type: 20.5.0 @@ -5412,6 +5816,14 @@ snapshots: dependencies: arch: 3.0.0 + abbrev@2.0.0: {} + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + ansi-regex@5.0.1: {} ansi-regex@6.2.2: {} @@ -5422,6 +5834,11 @@ snapshots: ansi-styles@6.2.3: {} + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + arch@3.0.0: {} array-timsort@1.0.3: {} @@ -5430,14 +5847,14 @@ snapshots: b4a@1.7.3: {} + bail@2.0.2: {} + balanced-match@1.0.2: {} bare-events@2.8.2: {} base64-js@1.5.1: {} - before-after-hook@2.2.3: {} - bin-version-check@5.1.0: dependencies: bin-version: 6.0.0 @@ -5449,23 +5866,22 @@ snapshots: execa: 5.1.1 find-versions: 5.1.0 + binary-extensions@2.3.0: {} + bowser@2.12.1: optional: true - brace-expansion@1.1.12: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - brace-expansion@2.0.2: dependencies: balanced-match: 1.0.2 - btoa-lite@1.0.0: {} + braces@3.0.3: + dependencies: + fill-range: 7.1.1 buffer-crc32@0.2.13: {} - buffer-equal-constant-time@1.0.1: {} + buffer-from@1.1.2: {} buffer@5.7.1: dependencies: @@ -5488,6 +5904,8 @@ snapshots: normalize-url: 8.1.0 responselike: 3.0.0 + ccount@2.0.1: {} + chai@5.3.3: dependencies: assertion-error: 2.0.1 @@ -5500,23 +5918,49 @@ snapshots: chalk@5.6.2: {} + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + check-error@2.1.1: {} + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + chokidar@4.0.3: dependencies: readdirp: 4.1.2 optional: true + ci-info@4.3.1: {} + citty@0.1.6: dependencies: consola: 3.4.2 + collapse-white-space@2.1.0: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 color-name@1.1.4: {} + comma-separated-tokens@2.0.3: {} + commander@14.0.2: {} commander@6.2.1: {} @@ -5529,7 +5973,12 @@ snapshots: core-util-is: 1.0.3 esprima: 4.0.1 - concat-map@0.0.1: {} + concat-stream@2.0.0: + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.2 + typedarray: 0.0.6 confbox@0.2.2: {} @@ -5567,6 +6016,10 @@ snapshots: dependencies: ms: 2.1.3 + decode-named-character-reference@1.2.0: + dependencies: + character-entities: 2.0.2 + decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 @@ -5581,18 +6034,20 @@ snapshots: defu@6.1.4: {} - deprecation@2.3.1: {} + dequal@2.0.3: {} destr@2.0.5: {} detect-libc@2.1.2: optional: true + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + eastasianwidth@0.2.0: {} - ecdsa-sig-formatter@1.0.11: - dependencies: - safe-buffer: 5.2.1 + emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} @@ -5603,6 +6058,12 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.0 + err-code@2.0.3: {} + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + es-module-lexer@1.7.0: {} esbuild@0.25.12: @@ -5667,6 +6128,13 @@ snapshots: esprima@4.0.1: {} + estree-util-is-identifier-name@3.0.0: {} + + estree-util-visit@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/unist': 3.0.3 + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.8 @@ -5702,6 +6170,8 @@ snapshots: ext-list: 2.2.2 sort-keys-length: 1.0.1 + extend@3.0.2: {} + fast-fifo@1.3.2: {} fast-xml-parser@5.2.5: @@ -5709,6 +6179,10 @@ snapshots: strnum: 2.1.1 optional: true + fault@2.0.1: + dependencies: + format: 0.2.2 + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -5732,6 +6206,10 @@ snapshots: dependencies: filename-reserved-regex: 3.0.0 + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + find-up@8.0.0: dependencies: locate-path: 8.0.0 @@ -5748,6 +6226,8 @@ snapshots: form-data-encoder@2.1.4: {} + format@0.2.2: {} + fs-extra@11.3.2: dependencies: graceful-fs: 4.2.11 @@ -5766,6 +6246,19 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + glob@12.0.0: dependencies: foreground-child: 3.3.1 @@ -5797,13 +6290,6 @@ snapshots: graceful-fs@4.2.11: {} - graphql-tag@2.12.6(graphql@16.12.0): - dependencies: - graphql: 16.12.0 - tslib: 2.8.1 - - graphql@16.12.0: {} - h3@2.0.1-rc.2(crossws@0.4.1(srvx@0.8.16)): dependencies: cookie-es: 2.0.0 @@ -5822,6 +6308,10 @@ snapshots: hono@4.10.6: {} + hosted-git-info@7.0.2: + dependencies: + lru-cache: 10.4.3 + http-cache-semantics@4.2.0: {} http2-wrapper@2.2.1: @@ -5833,28 +6323,79 @@ snapshots: ieee754@1.2.1: {} + ignore@6.0.2: {} + + import-meta-resolve@4.2.0: {} + + inherits@2.0.4: {} + + ini@4.1.3: {} + inspect-with-kind@1.0.5: dependencies: kind-of: 6.0.3 + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + + is-arrayish@0.2.1: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-decimal@2.0.1: {} + + is-empty@1.2.0: {} + + is-extglob@2.1.1: {} + is-fullwidth-code-point@3.0.0: {} + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-hexadecimal@2.0.1: {} + + is-number@7.0.0: {} + is-plain-obj@1.1.0: {} + is-plain-obj@4.1.0: {} + is-stream@2.0.1: {} isexe@2.0.0: {} + isexe@3.1.1: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + jackspeak@4.1.1: dependencies: '@isaacs/cliui': 8.0.2 jiti@2.6.1: {} + js-tokens@4.0.0: {} + js-tokens@9.0.1: {} json-buffer@3.0.1: {} + json-parse-even-better-errors@3.0.2: {} + + json5@2.2.3: {} + jsonc-parser@3.3.1: {} jsonfile@6.2.0: @@ -5863,30 +6404,6 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 - jsonwebtoken@9.0.2: - dependencies: - jws: 3.2.2 - lodash.includes: 4.3.0 - lodash.isboolean: 3.0.3 - lodash.isinteger: 4.0.4 - lodash.isnumber: 3.0.3 - lodash.isplainobject: 4.0.6 - lodash.isstring: 4.0.1 - lodash.once: 4.1.1 - ms: 2.1.3 - semver: 7.7.3 - - jwa@1.4.2: - dependencies: - buffer-equal-constant-time: 1.0.1 - ecdsa-sig-formatter: 1.0.11 - safe-buffer: 5.2.1 - - jws@3.2.2: - dependencies: - jwa: 1.4.2 - safe-buffer: 5.2.1 - keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -5943,36 +6460,373 @@ snapshots: lightningcss-win32-x64-msvc: 1.30.2 optional: true + lines-and-columns@2.0.4: {} + + load-plugin@6.0.3: + dependencies: + '@npmcli/config': 8.3.4 + import-meta-resolve: 4.2.0 + transitivePeerDependencies: + - bluebird + locate-path@8.0.0: dependencies: p-locate: 6.0.0 - lodash.includes@4.3.0: {} + longest-streak@3.1.0: {} - lodash.isboolean@3.0.3: {} + loupe@3.2.1: {} - lodash.isinteger@4.0.4: {} + lowercase-keys@3.0.0: {} - lodash.isnumber@3.0.3: {} + lru-cache@10.4.3: {} - lodash.isplainobject@4.0.6: {} + lru-cache@11.2.2: {} - lodash.isstring@4.0.1: {} + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 - lodash.once@4.1.1: {} + markdown-extensions@2.0.0: {} - loupe@3.2.1: {} + mdast-comment-marker@3.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-mdx-expression: 2.0.1 + transitivePeerDependencies: + - supports-color - lowercase-keys@3.0.0: {} + mdast-util-directive@3.1.0: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-visit-parents: 6.0.2 + transitivePeerDependencies: + - supports-color - lru-cache@11.2.2: {} + mdast-util-from-markdown@2.0.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color - magic-string@0.30.21: + mdast-util-frontmatter@2.0.1: dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + escape-string-regexp: 5.0.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-extension-frontmatter: 2.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-heading-style@3.0.0: + dependencies: + '@types/mdast': 4.0.4 + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx@3.0.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 merge-stream@2.0.0: {} + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-frontmatter@2.0.0: + dependencies: + fault: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-expression@3.0.1: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-jsx@3.0.2: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + + micromark-extension-mdx-md@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-mdxjs-esm@3.0.0: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-extension-mdxjs@3.0.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + micromark-extension-mdx-expression: 3.0.1 + micromark-extension-mdx-jsx: 3.0.2 + micromark-extension-mdx-md: 2.0.0 + micromark-extension-mdxjs-esm: 3.0.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-mdx-expression@2.0.3: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.2.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-events-to-acorn@2.0.3: + dependencies: + '@types/estree': 1.0.8 + '@types/unist': 3.0.3 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.12 + debug: 4.4.3 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + mime-db@1.54.0: {} mimic-fn@2.1.0: {} @@ -5985,14 +6839,12 @@ snapshots: dependencies: '@isaacs/brace-expansion': 5.0.0 - minimatch@3.1.2: - dependencies: - brace-expansion: 1.1.12 - minimatch@9.0.5: dependencies: brace-expansion: 2.0.2 + minimist@1.2.8: {} + minipass@7.1.2: {} ms@2.1.3: {} @@ -6001,7 +6853,7 @@ snapshots: nf3@0.1.12: {} - nitro@3.0.0(@vercel/functions@3.3.3(@aws-sdk/credential-provider-web-identity@3.936.0))(chokidar@4.0.3)(lru-cache@11.2.2)(vite@7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)): + nitro@3.0.0(@vercel/functions@3.3.3(@aws-sdk/credential-provider-web-identity@3.936.0))(chokidar@4.0.3)(lru-cache@11.2.2)(vite@7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.2)): dependencies: consola: 3.4.2 cookie-es: 2.0.0 @@ -6021,7 +6873,7 @@ snapshots: unenv: 2.0.0-rc.21 unstorage: 2.0.0-alpha.3(@vercel/functions@3.3.3(@aws-sdk/credential-provider-web-identity@3.936.0))(chokidar@4.0.3)(db0@0.3.4)(lru-cache@11.2.2)(ofetch@1.5.1) optionalDependencies: - vite: 7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6) + vite: 7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.2) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -6051,7 +6903,7 @@ snapshots: - sqlite3 - uploadthing - nitro@3.0.1-alpha.1(@vercel/functions@3.3.3(@aws-sdk/credential-provider-web-identity@3.936.0))(chokidar@4.0.3)(lru-cache@11.2.2)(rollup@4.53.3)(vite@7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)): + nitro@3.0.1-alpha.1(@vercel/functions@3.3.3(@aws-sdk/credential-provider-web-identity@3.936.0))(chokidar@4.0.3)(lru-cache@11.2.2)(rollup@4.53.3)(vite@7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.2)): dependencies: consola: 3.4.2 crossws: 0.4.1(srvx@0.9.6) @@ -6069,7 +6921,7 @@ snapshots: unstorage: 2.0.0-alpha.4(@vercel/functions@3.3.3(@aws-sdk/credential-provider-web-identity@3.936.0))(chokidar@4.0.3)(db0@0.3.4)(lru-cache@11.2.2)(ofetch@2.0.0-alpha.3) optionalDependencies: rollup: 4.53.3 - vite: 7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6) + vite: 7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.2) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -6101,8 +6953,40 @@ snapshots: node-fetch-native@1.6.7: {} + nopt@7.2.1: + dependencies: + abbrev: 2.0.0 + + normalize-package-data@6.0.2: + dependencies: + hosted-git-info: 7.0.2 + semver: 7.7.3 + validate-npm-package-license: 3.0.4 + + normalize-path@3.0.0: {} + normalize-url@8.1.0: {} + npm-install-checks@6.3.0: + dependencies: + semver: 7.7.3 + + npm-normalize-package-bin@3.0.1: {} + + npm-package-arg@11.0.3: + dependencies: + hosted-git-info: 7.0.2 + proc-log: 4.2.0 + semver: 7.7.3 + validate-npm-package-name: 5.0.1 + + npm-pick-manifest@9.1.0: + dependencies: + npm-install-checks: 6.3.0 + npm-normalize-package-bin: 3.0.1 + npm-package-arg: 11.0.3 + semver: 7.7.3 + npm-run-path@4.0.1: dependencies: path-key: 3.1.1 @@ -6125,10 +7009,6 @@ snapshots: ohash@2.0.11: {} - once@1.4.0: - dependencies: - wrappy: 1.0.2 - onetime@5.1.2: dependencies: mimic-fn: 2.1.0 @@ -6185,8 +7065,31 @@ snapshots: package-manager-detector@1.5.0: {} + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.2.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + + parse-json@7.1.1: + dependencies: + '@babel/code-frame': 7.27.1 + error-ex: 1.3.4 + json-parse-even-better-errors: 3.0.2 + lines-and-columns: 2.0.4 + type-fest: 3.13.1 + path-key@3.1.1: {} + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + path-scurry@2.0.1: dependencies: lru-cache: 11.2.2 @@ -6200,6 +7103,8 @@ snapshots: picocolors@1.1.1: {} + picomatch@2.3.1: {} + picomatch@4.0.3: {} piscina@4.9.2: @@ -6220,19 +7125,375 @@ snapshots: optionalDependencies: fsevents: 2.3.2 + pluralize@8.0.0: {} + postcss@8.5.6: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 + proc-log@4.2.0: {} + + promise-inflight@1.0.1: {} + + promise-retry@2.0.1: + dependencies: + err-code: 2.0.3 + retry: 0.12.0 + quick-lru@5.1.1: {} radash@12.1.1: {} + read-package-json-fast@3.0.2: + dependencies: + json-parse-even-better-errors: 3.0.2 + npm-normalize-package-bin: 3.0.1 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + readdirp@4.1.2: optional: true + remark-cli@12.0.1: + dependencies: + import-meta-resolve: 4.2.0 + markdown-extensions: 2.0.0 + remark: 15.0.1 + unified-args: 11.0.1 + transitivePeerDependencies: + - bluebird + - supports-color + + remark-frontmatter@5.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-frontmatter: 2.0.1 + micromark-extension-frontmatter: 2.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-lint-blockquote-indentation@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-phrasing: 4.1.0 + pluralize: 8.0.0 + unified-lint-rule: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit-parents: 6.0.2 + + remark-lint-checkbox-character-style@5.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-phrasing: 4.1.0 + unified-lint-rule: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit-parents: 6.0.2 + vfile-message: 4.0.3 + + remark-lint-code-block-style@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-phrasing: 4.1.0 + unified-lint-rule: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit-parents: 6.0.2 + vfile-message: 4.0.3 + + remark-lint-emphasis-marker@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + unified-lint-rule: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit-parents: 6.0.2 + vfile-message: 4.0.3 + + remark-lint-fenced-code-marker@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-phrasing: 4.1.0 + unified-lint-rule: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit-parents: 6.0.2 + vfile-message: 4.0.3 + + remark-lint-final-newline@3.0.1: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + unified-lint-rule: 3.0.1 + vfile-location: 5.0.3 + + remark-lint-hard-break-spaces@4.1.1: + dependencies: + '@types/mdast': 4.0.4 + unified-lint-rule: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + + remark-lint-heading-style@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-heading-style: 3.0.0 + mdast-util-phrasing: 4.1.0 + unified-lint-rule: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit-parents: 6.0.2 + vfile-message: 4.0.3 + + remark-lint-link-title-style@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + unified-lint-rule: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit-parents: 6.0.2 + vfile-message: 4.0.3 + + remark-lint-list-item-bullet-indent@5.0.1: + dependencies: + '@types/mdast': 4.0.4 + pluralize: 8.0.0 + unified-lint-rule: 3.0.1 + unist-util-position: 5.0.0 + + remark-lint-list-item-content-indent@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-phrasing: 4.1.0 + pluralize: 8.0.0 + unified-lint-rule: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit-parents: 6.0.2 + vfile-message: 4.0.3 + + remark-lint-list-item-indent@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-phrasing: 4.1.0 + pluralize: 8.0.0 + unified-lint-rule: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit-parents: 6.0.2 + + remark-lint-no-blockquote-without-marker@6.0.1: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-directive: 3.1.0 + mdast-util-phrasing: 4.1.0 + pluralize: 8.0.0 + unified-lint-rule: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit-parents: 6.0.2 + vfile-location: 5.0.3 + transitivePeerDependencies: + - supports-color + + remark-lint-no-duplicate-definitions@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-phrasing: 4.1.0 + unified-lint-rule: 3.0.1 + unist-util-visit-parents: 6.0.2 + vfile-message: 4.0.3 + + remark-lint-no-heading-content-indent@5.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-phrasing: 4.1.0 + pluralize: 8.0.0 + unified-lint-rule: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit-parents: 6.0.2 + + remark-lint-no-literal-urls@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-string: 4.0.0 + micromark-util-character: 2.1.1 + unified-lint-rule: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit-parents: 6.0.2 + + remark-lint-no-shortcut-reference-image@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + unified-lint-rule: 3.0.1 + unist-util-visit-parents: 6.0.2 + + remark-lint-no-shortcut-reference-link@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + unified-lint-rule: 3.0.1 + unist-util-visit-parents: 6.0.2 + + remark-lint-no-undefined-references@5.0.2: + dependencies: + '@types/mdast': 4.0.4 + collapse-white-space: 2.1.0 + devlop: 1.1.0 + micromark-util-normalize-identifier: 2.0.1 + unified-lint-rule: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit-parents: 6.0.2 + vfile-location: 5.0.3 + + remark-lint-no-unused-definitions@4.0.2: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + unified-lint-rule: 3.0.1 + unist-util-visit-parents: 6.0.2 + + remark-lint-ordered-list-marker-style@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-phrasing: 4.1.0 + micromark-util-character: 2.1.1 + unified-lint-rule: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit-parents: 6.0.2 + vfile-message: 4.0.3 + + remark-lint-ordered-list-marker-value@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-phrasing: 4.1.0 + micromark-util-character: 2.1.1 + unified-lint-rule: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit-parents: 6.0.2 + vfile-message: 4.0.3 + + remark-lint-rule-style@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-phrasing: 4.1.0 + unified-lint-rule: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit-parents: 6.0.2 + vfile-message: 4.0.3 + + remark-lint-strong-marker@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + unified-lint-rule: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit-parents: 6.0.2 + vfile-message: 4.0.3 + + remark-lint-table-cell-padding@5.1.1: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + devlop: 1.1.0 + mdast-util-phrasing: 4.1.0 + pluralize: 8.0.0 + unified-lint-rule: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit-parents: 6.0.2 + vfile-message: 4.0.3 + + remark-lint@10.0.1: + dependencies: + '@types/mdast': 4.0.4 + remark-message-control: 8.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-mdx@3.1.1: + dependencies: + mdast-util-mdx: 3.0.0 + micromark-extension-mdxjs: 3.0.0 + transitivePeerDependencies: + - supports-color + + remark-message-control@8.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-comment-marker: 3.0.0 + unified-message-control: 5.0.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-preset-lint-consistent@6.0.1: + dependencies: + remark-lint: 10.0.1 + remark-lint-blockquote-indentation: 4.0.1 + remark-lint-checkbox-character-style: 5.0.1 + remark-lint-code-block-style: 4.0.1 + remark-lint-emphasis-marker: 4.0.1 + remark-lint-fenced-code-marker: 4.0.1 + remark-lint-heading-style: 4.0.1 + remark-lint-link-title-style: 4.0.1 + remark-lint-list-item-content-indent: 4.0.1 + remark-lint-ordered-list-marker-style: 4.0.1 + remark-lint-ordered-list-marker-value: 4.0.1 + remark-lint-rule-style: 4.0.1 + remark-lint-strong-marker: 4.0.1 + remark-lint-table-cell-padding: 5.1.1 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-preset-lint-recommended@7.0.1: + dependencies: + remark-lint: 10.0.1 + remark-lint-final-newline: 3.0.1 + remark-lint-hard-break-spaces: 4.1.1 + remark-lint-list-item-bullet-indent: 5.0.1 + remark-lint-list-item-indent: 4.0.1 + remark-lint-no-blockquote-without-marker: 6.0.1 + remark-lint-no-duplicate-definitions: 4.0.1 + remark-lint-no-heading-content-indent: 5.0.1 + remark-lint-no-literal-urls: 4.0.1 + remark-lint-no-shortcut-reference-image: 4.0.1 + remark-lint-no-shortcut-reference-link: 4.0.1 + remark-lint-no-undefined-references: 5.0.2 + remark-lint-no-unused-definitions: 4.0.2 + remark-lint-ordered-list-marker-style: 4.0.1 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + + remark@15.0.1: + dependencies: + '@types/mdast': 4.0.4 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + rendu@0.0.6: dependencies: cookie-es: 2.0.0 @@ -6246,6 +7507,8 @@ snapshots: dependencies: lowercase-keys: 3.0.0 + retry@0.12.0: {} + rollup@4.53.3: dependencies: '@types/estree': 1.0.8 @@ -6325,6 +7588,22 @@ snapshots: source-map@0.7.6: {} + space-separated-tokens@2.0.2: {} + + spdx-correct@3.2.0: + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.22 + + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@3.0.1: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.22 + + spdx-license-ids@3.0.22: {} + srvx@0.8.16: {} srvx@0.9.6: {} @@ -6354,6 +7633,21 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.2 + string-width@6.1.0: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 10.6.0 + strip-ansi: 7.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -6380,6 +7674,8 @@ snapshots: dependencies: '@tokenizer/token': 0.3.0 + supports-color@9.4.0: {} + tagged-tag@1.0.0: {} tapable@2.3.0: {} @@ -6399,6 +7695,8 @@ snapshots: transitivePeerDependencies: - react-native-b4a + text-table@0.2.0: {} + through@2.3.8: {} tinybench@2.9.0: {} @@ -6420,12 +7718,18 @@ snapshots: tinyspy@4.0.4: {} + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + token-types@6.1.1: dependencies: '@borewit/text-codec': 0.1.1 '@tokenizer/token': 0.3.0 ieee754: 1.2.1 + trough@2.2.0: {} + trpc-cli@0.12.1(@orpc/server@1.11.3(crossws@0.4.1(srvx@0.9.6)))(@trpc/server@11.7.1(typescript@5.9.3))(zod@4.1.12): dependencies: commander: 14.0.2 @@ -6443,8 +7747,6 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - tunnel@0.0.6: {} - turbo-darwin-64@2.6.1: optional: true @@ -6472,10 +7774,14 @@ snapshots: turbo-windows-64: 2.6.1 turbo-windows-arm64: 2.6.1 + type-fest@3.13.1: {} + type-fest@5.2.0: dependencies: tagged-tag: 1.0.0 + typedarray@0.0.6: {} + typescript@5.9.3: {} ufo@1.6.1: {} @@ -6508,10 +7814,6 @@ snapshots: undici-types@7.16.0: {} - undici@5.29.0: - dependencies: - '@fastify/busboy': 2.1.1 - undici@7.16.0: {} unenv@2.0.0-rc.21: @@ -6528,12 +7830,106 @@ snapshots: unicorn-magic@0.3.0: {} - universal-github-app-jwt@1.2.0: + unified-args@11.0.1: + dependencies: + '@types/text-table': 0.2.5 + chalk: 5.6.2 + chokidar: 3.6.0 + comma-separated-tokens: 2.0.3 + json5: 2.2.3 + minimist: 1.2.8 + strip-ansi: 7.1.2 + text-table: 0.2.0 + unified-engine: 11.2.2 + transitivePeerDependencies: + - bluebird + - supports-color + + unified-engine@11.2.2: + dependencies: + '@types/concat-stream': 2.0.3 + '@types/debug': 4.1.12 + '@types/is-empty': 1.2.3 + '@types/node': 22.19.0 + '@types/unist': 3.0.3 + concat-stream: 2.0.0 + debug: 4.4.3 + extend: 3.0.2 + glob: 10.5.0 + ignore: 6.0.2 + is-empty: 1.2.0 + is-plain-obj: 4.1.0 + load-plugin: 6.0.3 + parse-json: 7.1.1 + trough: 2.2.0 + unist-util-inspect: 8.1.0 + vfile: 6.0.3 + vfile-message: 4.0.3 + vfile-reporter: 8.1.1 + vfile-statistics: 3.0.0 + yaml: 2.8.2 + transitivePeerDependencies: + - bluebird + - supports-color + + unified-lint-rule@3.0.1: + dependencies: + '@types/unist': 3.0.3 + trough: 2.2.0 + unified: 11.0.5 + vfile: 6.0.3 + + unified-message-control@5.0.0: + dependencies: + '@types/unist': 3.0.3 + devlop: 1.1.0 + space-separated-tokens: 2.0.2 + unist-util-is: 6.0.1 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + vfile-message: 4.0.3 + + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unist-util-inspect@8.1.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-is@6.0.1: dependencies: - '@types/jsonwebtoken': 9.0.10 - jsonwebtoken: 9.0.2 + '@types/unist': 3.0.3 - universal-user-agent@6.0.1: {} + unist-util-position-from-estree@2.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 universalify@2.0.1: {} @@ -6553,13 +7949,58 @@ snapshots: lru-cache: 11.2.2 ofetch: 2.0.0-alpha.3 - vite-node@3.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6): + util-deprecate@1.0.2: {} + + validate-npm-package-license@3.0.4: + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + + validate-npm-package-name@5.0.1: {} + + vfile-location@5.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile: 6.0.3 + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile-reporter@8.1.1: + dependencies: + '@types/supports-color': 8.1.3 + string-width: 6.1.0 + supports-color: 9.4.0 + unist-util-stringify-position: 4.0.0 + vfile: 6.0.3 + vfile-message: 4.0.3 + vfile-sort: 4.0.0 + vfile-statistics: 3.0.0 + + vfile-sort@4.0.0: + dependencies: + vfile: 6.0.3 + vfile-message: 4.0.3 + + vfile-statistics@3.0.0: + dependencies: + vfile: 6.0.3 + vfile-message: 4.0.3 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + vite-node@3.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.2): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6) + vite: 7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.2) transitivePeerDependencies: - '@types/node' - jiti @@ -6574,7 +8015,7 @@ snapshots: - tsx - yaml - vite@7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6): + vite@7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.2): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -6588,8 +8029,9 @@ snapshots: jiti: 2.6.1 lightningcss: 1.30.2 tsx: 4.20.6 + yaml: 2.8.2 - vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6): + vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.2): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -6603,12 +8045,13 @@ snapshots: jiti: 2.6.1 lightningcss: 1.30.2 tsx: 4.20.6 + yaml: 2.8.2 - vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.2): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)) + '@vitest/mocker': 3.2.4(vite@7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.2)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -6626,8 +8069,8 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6) - vite-node: 3.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6) + vite: 7.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.2) + vite-node: 3.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 @@ -6646,10 +8089,10 @@ snapshots: - tsx - yaml - vitest@4.0.13(@types/debug@4.1.12)(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6): + vitest@4.0.13(@types/debug@4.1.12)(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.2): dependencies: '@vitest/expect': 4.0.13 - '@vitest/mocker': 4.0.13(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)) + '@vitest/mocker': 4.0.13(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.2)) '@vitest/pretty-format': 4.0.13 '@vitest/runner': 4.0.13 '@vitest/snapshot': 4.0.13 @@ -6666,7 +8109,7 @@ snapshots: tinyexec: 0.3.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6) + vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 @@ -6685,10 +8128,16 @@ snapshots: - tsx - yaml + walk-up-path@3.0.1: {} + which@2.0.2: dependencies: isexe: 2.0.0 + which@4.0.0: + dependencies: + isexe: 3.1.1 + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 @@ -6708,7 +8157,7 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.2 - wrappy@1.0.2: {} + yaml@2.8.2: {} yauzl@3.2.0: dependencies: @@ -6718,3 +8167,5 @@ snapshots: yocto-queue@1.2.2: {} zod@4.1.12: {} + + zwitch@2.0.4: {} From 8ff93e7ee493b7e3b3e5eb4240dcf8dadc8e06c5 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 16:29:06 +0000 Subject: [PATCH 2/8] [autofix.ci] apply automated fixes --- package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 1a8bd6e..259c287 100644 --- a/package.json +++ b/package.json @@ -16,15 +16,15 @@ "devDependencies": { "@biomejs/biome": "^2.3.7", "@rslib/core": "^0.18.0", + "remark-cli": "^12.0.1", + "remark-frontmatter": "^5.0.0", + "remark-mdx": "^3.1.0", + "remark-preset-lint-consistent": "^6.0.1", + "remark-preset-lint-recommended": "^7.0.1", "runners": "workspace:*", "turbo": "^2.5.4", "typescript": "^5.9.3", - "ultracite": "6.3.4", - "remark-cli": "^12.0.1", - "remark-frontmatter": "^5.0.0", - "remark-mdx": "^3.1.0", - "remark-preset-lint-consistent": "^6.0.1", - "remark-preset-lint-recommended": "^7.0.1" + "ultracite": "6.3.4" }, "packageManager": "pnpm@10.20.0+sha512.cf9998222162dd85864d0a8102e7892e7ba4ceadebbf5a31f9c2fce48dfce317a9c53b9f6464d1ef9042cba2e02ae02a9f7c143a2b438cd93c91840f0192b9dd", "engines": { From 8c145415bb349482d1f6e64af379e68b6cfec775 Mon Sep 17 00:00:00 2001 From: Christopher Burns Date: Mon, 1 Dec 2025 16:31:41 +0000 Subject: [PATCH 3/8] Enhance GitHub Actions workflows for code and documentation formatting - Added a step to format documentation using pnpm fmt:docs in the autofix workflow. - Updated the type checking step in the CI workflow to use the more descriptive command 'typecheck' instead of 'check-types'. --- .github/workflows/autofix.yml | 4 ++++ .github/workflows/ci.yml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 2a615d2..d82ddcc 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -40,6 +40,10 @@ jobs: - name: โœจ Format code run: pnpm fmt + - name: ๐Ÿ“š Format Documentation + run: pnpm fmt:docs + + - name: ๐Ÿ“ฆ Format package.json run: npx --yes sort-package-json "package.json" "packages/*/package.json" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d611f99..10e4a39 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: run: pnpm install --frozen-lockfile - name: ๐Ÿ‘€ Type Check - run: pnpm turbo run check-types + run: pnpm turbo run typecheck - name: ๐Ÿงน Run Biome with Reviewdog uses: mongolyy/reviewdog-action-biome@v2.1.0 From a890025697ab9fc56931aa4c8feaefd463c97fdc Mon Sep 17 00:00:00 2001 From: Christopher Burns Date: Mon, 1 Dec 2025 16:49:29 +0000 Subject: [PATCH 4/8] Add new GitHub Actions workflows for bundle analysis and documentation deployment - Introduced a new workflow for bundle size analysis, triggered on pull requests and pushes to main and canary branches, ensuring better visibility of bundle size changes. - Added a unified documentation deployment workflow that supports PR previews, production, and staging deployments, enhancing the deployment process with improved alias handling and caching. - Removed the outdated documentation deployment workflow to streamline the repository. --- ...undle-analysis.yml => analysis-bundle.yml} | 0 .../workflows/{autofix.yml => ci-autofix.yml} | 1 - .github/workflows/{ci.yml => ci-main.yml} | 0 .github/workflows/deploy-docs-main.yml | 83 ------------ .github/workflows/deploy-docs.yml | 122 ++++++++++++++++++ 5 files changed, 122 insertions(+), 84 deletions(-) rename .github/workflows/{bundle-analysis.yml => analysis-bundle.yml} (100%) rename .github/workflows/{autofix.yml => ci-autofix.yml} (99%) rename .github/workflows/{ci.yml => ci-main.yml} (100%) delete mode 100644 .github/workflows/deploy-docs-main.yml create mode 100644 .github/workflows/deploy-docs.yml diff --git a/.github/workflows/bundle-analysis.yml b/.github/workflows/analysis-bundle.yml similarity index 100% rename from .github/workflows/bundle-analysis.yml rename to .github/workflows/analysis-bundle.yml diff --git a/.github/workflows/autofix.yml b/.github/workflows/ci-autofix.yml similarity index 99% rename from .github/workflows/autofix.yml rename to .github/workflows/ci-autofix.yml index d82ddcc..9e29043 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/ci-autofix.yml @@ -43,7 +43,6 @@ jobs: - name: ๐Ÿ“š Format Documentation run: pnpm fmt:docs - - name: ๐Ÿ“ฆ Format package.json run: npx --yes sort-package-json "package.json" "packages/*/package.json" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci-main.yml similarity index 100% rename from .github/workflows/ci.yml rename to .github/workflows/ci-main.yml diff --git a/.github/workflows/deploy-docs-main.yml b/.github/workflows/deploy-docs-main.yml deleted file mode 100644 index b8c0b32..0000000 --- a/.github/workflows/deploy-docs-main.yml +++ /dev/null @@ -1,83 +0,0 @@ -# ๐Ÿš€ Runner main docs scheduled deployment workflow -name: Deploy docs (Main) - -on: - # ๐Ÿ”„ Allow manual trigger as well - workflow_dispatch: - -permissions: - contents: read - issues: write - pull-requests: write - deployments: write - -jobs: - deploy: - concurrency: - group: docs-main - cancel-in-progress: true - runs-on: ubuntu-latest - steps: - - name: ๐Ÿ“ฅ Checkout repository - uses: actions/checkout@v4 - with: - ref: main - fetch-depth: 0 - - # ๐Ÿ“ฆ Setup pnpm (used by docs template/scripts) - - name: ๐Ÿ“ฆ Setup pnpm - uses: pnpm/action-setup@v4 - with: - run_install: false - - - name: โ™ป๏ธ Setup pnpm cache - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: 'pnpm' - - - name: ๐Ÿ“ฅ Install workspace deps - run: pnpm install --frozen-lockfile - - - name: โ–ฒ Runner Docs Deployment - id: runner-docs - uses: consentdotio/github-actions/docs-preview-action@main - with: - # Auth - GITHUB_TOKEN: ${{ github.token }} - github_app_id: ${{ secrets.CONSENT_APP_ID }} - github_app_private_key: ${{ secrets.CONSENT_APP_PRIVATE_KEY }} - github_app_installation_id: 81013186 - - # Production target - target: production - - # Comment behavior - header: runner-docs-main - append: "true" - hide_details: "true" - - # Vercel - vercel_token: ${{ secrets.VERCEL_TOKEN }} - vercel_project_id: ${{ secrets.VERCEL_PROJECT_ID }} - vercel_org_id: ${{ secrets.VERCEL_ORG_ID }} - - assign_alias_on_branch: main - - # Branch deployment policies - deploy_on_push_branches: main - - # Orchestration & gating - consent_git_token: ${{ secrets.CONSENT_GIT_TOKEN }} - docs_template_repo: consentdotio/runner-docs - only_if_changed: "false" - change_globs: | - docs/** - changelog/** - packages/*/src/** - packages/*/package.json - check_template_changes: "true" - - # Branding customization - comment_marker_prefix: "runner" - diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 0000000..cdecd37 --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,122 @@ +# ๐Ÿš€ Runner docs deployment workflow (Unified) +name: Deploy docs + +on: + # ๐Ÿ“Œ Trigger on PRs that modify docs (build previews for reviews) + pull_request: + branches: + - "**" + # ๐Ÿ“Œ Trigger on pushes to main (production) and other branches (staging) + push: + branches: + - "**" + # ๐Ÿ“Œ Manual trigger for production deployments + workflow_dispatch: + inputs: + branch: + description: "Branch to deploy (main for production, or any branch for staging)" + required: true + type: string + +permissions: + contents: read + issues: write + pull-requests: write + deployments: write + +jobs: + setup: + runs-on: ubuntu-latest + outputs: + alias_domains: ${{ steps.set-vars.outputs.alias_domains }} + target: ${{ steps.set-vars.outputs.target }} + header: ${{ steps.set-vars.outputs.header }} + steps: + - id: set-vars + run: | + if [ "${{ github.event_name }}" == "pull_request" ]; then + echo "alias_domains=pr-${{ github.event.pull_request.number }}.runner.dev" >> $GITHUB_OUTPUT + echo "target=" >> $GITHUB_OUTPUT + echo "header=runner-docs-preview" >> $GITHUB_OUTPUT + elif [ "${{ github.ref }}" == "refs/heads/main" ] || [ "${{ github.event.inputs.branch }}" == "main" ]; then + echo "alias_domains=runner.com" >> $GITHUB_OUTPUT + echo "target=production" >> $GITHUB_OUTPUT + echo "header=runner-docs-production" >> $GITHUB_OUTPUT + else + BRANCH_NAME="${{ github.ref_name || github.event.inputs.branch }}" + echo "alias_domains=${BRANCH_NAME}.runner.dev" >> $GITHUB_OUTPUT + echo "target=" >> $GITHUB_OUTPUT + echo "header=runner-docs-staging" >> $GITHUB_OUTPUT + fi + deploy: + needs: setup + concurrency: + group: ${{ github.event_name == 'pull_request' && 'docs-preview-' || github.ref == 'refs/heads/main' && 'docs-production-' || 'docs-staging-' }}${{ github.event_name == 'pull_request' && github.ref || github.ref_name || github.event.inputs.branch }} + cancel-in-progress: true + runs-on: ubuntu-latest + steps: + - name: ๐Ÿ“ฅ Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ github.event_name == 'pull_request' && '' || github.event.inputs.branch || github.ref_name }} + fetch-depth: 0 + + # ๐Ÿ“ฆ Setup pnpm (used by docs template/scripts) + - name: ๐Ÿ“ฆ Setup pnpm + uses: pnpm/action-setup@v4 + with: + run_install: false + + - name: โ™ป๏ธ Setup pnpm cache + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'pnpm' + + - name: ๐Ÿ“ฅ Install workspace deps + run: pnpm install --frozen-lockfile + + - name: โ–ฒ Runner Docs Deployment + id: runner-docs + uses: consentdotio/github-actions/docs-preview-action@main + with: + # Auth + GITHUB_TOKEN: ${{ github.token }} + github_app_id: ${{ secrets.CONSENT_APP_ID }} + github_app_private_key: ${{ secrets.CONSENT_APP_PRIVATE_KEY }} + github_app_installation_id: 81013186 + + # Production target (only for main branch) + target: ${{ needs.setup.outputs.target }} + + # Comment behavior + header: ${{ needs.setup.outputs.header }} + append: "true" + hide_details: "true" + + # Vercel + vercel_token: ${{ secrets.VERCEL_TOKEN }} + vercel_project_id: ${{ secrets.VERCEL_PROJECT_ID }} + vercel_org_id: ${{ secrets.VERCEL_ORG_ID }} + + # Alias assignment + assign_alias_on_branch: ${{ github.ref == 'refs/heads/main' && 'main' || '' }} + alias_domains: ${{ needs.setup.outputs.alias_domains }} + + # Branch deployment policies + deploy_on_push_branches: ${{ github.event_name == 'push' && github.ref_name || '' }} + deploy_on_pr_base_branches: ${{ github.event_name == 'pull_request' && 'main,**' || '' }} + + # Orchestration & gating + consent_git_token: ${{ secrets.CONSENT_GIT_TOKEN }} + docs_template_repo: consentdotio/runner-docs + only_if_changed: ${{ github.event_name == 'pull_request' && 'true' || 'false' }} + change_globs: | + docs/** + changelog/** + packages/*/src/** + packages/*/package.json + check_template_changes: "true" + + # Branding customization + comment_marker_prefix: "runner" From 98f84409f2fb9d61f1ac8ec1f7f0d6f288a1534d Mon Sep 17 00:00:00 2001 From: Christopher Burns Date: Mon, 1 Dec 2025 17:10:17 +0000 Subject: [PATCH 5/8] Fix GitHub Actions workflow to correctly reference pull request head SHA for checkout - Updated the checkout step in the documentation deployment workflow to use the pull request head SHA when the event is a pull request, ensuring the correct branch is checked out for deployments. --- .github/workflows/deploy-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index cdecd37..19cd32c 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -58,7 +58,7 @@ jobs: - name: ๐Ÿ“ฅ Checkout repository uses: actions/checkout@v4 with: - ref: ${{ github.event_name == 'pull_request' && '' || github.event.inputs.branch || github.ref_name }} + ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.event.inputs.branch || github.ref_name }} fetch-depth: 0 # ๐Ÿ“ฆ Setup pnpm (used by docs template/scripts) From 4fa4c3fc87aaf3b034857ad590dc80d7cd9c1fc2 Mon Sep 17 00:00:00 2001 From: Christopher Burns Date: Mon, 1 Dec 2025 17:13:08 +0000 Subject: [PATCH 6/8] Add reusable GitHub Actions workflow for documentation deployment - Created a new reusable workflow for deploying documentation, allowing for more flexible configurations such as branch-specific alias assignments and change detection. - Updated the existing documentation deployment workflow to utilize the new reusable workflow, streamlining the deployment process and improving maintainability. --- .github/workflows/deploy-docs-reusable.yml | 132 +++++++++++++++++++++ .github/workflows/deploy-docs.yml | 99 +++++----------- 2 files changed, 163 insertions(+), 68 deletions(-) create mode 100644 .github/workflows/deploy-docs-reusable.yml diff --git a/.github/workflows/deploy-docs-reusable.yml b/.github/workflows/deploy-docs-reusable.yml new file mode 100644 index 0000000..01b32f3 --- /dev/null +++ b/.github/workflows/deploy-docs-reusable.yml @@ -0,0 +1,132 @@ +name: Reusable Deploy Docs + +on: + workflow_call: + inputs: + ref: + description: "Git ref to checkout" + required: false + type: string + target: + description: "Deployment target (production/staging)" + required: false + type: string + header: + description: "Comment header identifier" + required: true + type: string + assign_alias_on_branch: + description: "Branch to assign alias on" + required: false + type: string + alias_domains: + description: "Alias domains (newline-separated)" + required: false + type: string + deploy_on_push_branches: + description: "Branches to deploy on push" + required: false + type: string + deploy_on_pr_base_branches: + description: "PR base branches to deploy previews" + required: false + type: string + only_if_changed: + description: "Only deploy if changed" + required: false + type: string + default: "false" + change_globs: + description: "Change detection globs (newline-separated)" + required: false + type: string + check_template_changes: + description: "Check template changes" + required: false + type: string + default: "false" + secrets: + CONSENT_APP_ID: + required: true + CONSENT_APP_PRIVATE_KEY: + required: true + VERCEL_TOKEN: + required: true + VERCEL_PROJECT_ID: + required: true + VERCEL_ORG_ID: + required: true + CONSENT_GIT_TOKEN: + required: true + +permissions: + contents: read + issues: write + pull-requests: write + deployments: write + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: ๐Ÿ“ฅ Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref }} + fetch-depth: 0 + + # ๐Ÿ“ฆ Setup pnpm (used by docs template/scripts) + - name: ๐Ÿ“ฆ Setup pnpm + uses: pnpm/action-setup@v4 + with: + run_install: false + + - name: โ™ป๏ธ Setup pnpm cache + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: "pnpm" + + - name: ๐Ÿ“ฅ Install workspace deps + run: pnpm install --frozen-lockfile + + - name: โ–ฒ Runner Deployment + id: runner-docs + uses: consentdotio/github-actions/docs-preview-action@main + with: + # Auth + GITHUB_TOKEN: ${{ github.token }} + github_app_id: ${{ secrets.CONSENT_APP_ID }} + github_app_private_key: ${{ secrets.CONSENT_APP_PRIVATE_KEY }} + github_app_installation_id: 81013186 + + # Production target + target: ${{ inputs.target }} + + # Comment behavior + header: ${{ inputs.header }} + append: "true" + hide_details: "true" + + # Vercel + vercel_token: ${{ secrets.VERCEL_TOKEN }} + vercel_project_id: ${{ secrets.VERCEL_PROJECT_ID }} + vercel_org_id: ${{ secrets.VERCEL_ORG_ID }} + + assign_alias_on_branch: ${{ inputs.assign_alias_on_branch }} + alias_domains: ${{ inputs.alias_domains }} + + # Branch deployment policies + deploy_on_push_branches: ${{ inputs.deploy_on_push_branches }} + deploy_on_pr_base_branches: ${{ inputs.deploy_on_pr_base_branches }} + + # Orchestration & gating + consent_git_token: ${{ secrets.CONSENT_GIT_TOKEN }} + docs_template_repo: consentdotio/runner-docs + only_if_changed: ${{ inputs.only_if_changed }} + change_globs: ${{ inputs.change_globs }} + check_template_changes: ${{ inputs.check_template_changes }} + + # Branding customization + comment_marker_prefix: "runner" + diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 19cd32c..8cca478 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -51,72 +51,35 @@ jobs: deploy: needs: setup concurrency: - group: ${{ github.event_name == 'pull_request' && 'docs-preview-' || github.ref == 'refs/heads/main' && 'docs-production-' || 'docs-staging-' }}${{ github.event_name == 'pull_request' && github.ref || github.ref_name || github.event.inputs.branch }} + group: ${{ github.event_name == 'pull_request' && 'docs-preview-' || github.ref == 'refs/heads/main' && 'docs-production-' || 'docs-staging-' }}${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref_name || github.event.inputs.branch }} cancel-in-progress: true - runs-on: ubuntu-latest - steps: - - name: ๐Ÿ“ฅ Checkout repository - uses: actions/checkout@v4 - with: - ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.event.inputs.branch || github.ref_name }} - fetch-depth: 0 - - # ๐Ÿ“ฆ Setup pnpm (used by docs template/scripts) - - name: ๐Ÿ“ฆ Setup pnpm - uses: pnpm/action-setup@v4 - with: - run_install: false - - - name: โ™ป๏ธ Setup pnpm cache - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: 'pnpm' - - - name: ๐Ÿ“ฅ Install workspace deps - run: pnpm install --frozen-lockfile - - - name: โ–ฒ Runner Docs Deployment - id: runner-docs - uses: consentdotio/github-actions/docs-preview-action@main - with: - # Auth - GITHUB_TOKEN: ${{ github.token }} - github_app_id: ${{ secrets.CONSENT_APP_ID }} - github_app_private_key: ${{ secrets.CONSENT_APP_PRIVATE_KEY }} - github_app_installation_id: 81013186 - - # Production target (only for main branch) - target: ${{ needs.setup.outputs.target }} - - # Comment behavior - header: ${{ needs.setup.outputs.header }} - append: "true" - hide_details: "true" - - # Vercel - vercel_token: ${{ secrets.VERCEL_TOKEN }} - vercel_project_id: ${{ secrets.VERCEL_PROJECT_ID }} - vercel_org_id: ${{ secrets.VERCEL_ORG_ID }} - - # Alias assignment - assign_alias_on_branch: ${{ github.ref == 'refs/heads/main' && 'main' || '' }} - alias_domains: ${{ needs.setup.outputs.alias_domains }} - - # Branch deployment policies - deploy_on_push_branches: ${{ github.event_name == 'push' && github.ref_name || '' }} - deploy_on_pr_base_branches: ${{ github.event_name == 'pull_request' && 'main,**' || '' }} - - # Orchestration & gating - consent_git_token: ${{ secrets.CONSENT_GIT_TOKEN }} - docs_template_repo: consentdotio/runner-docs - only_if_changed: ${{ github.event_name == 'pull_request' && 'true' || 'false' }} - change_globs: | - docs/** - changelog/** - packages/*/src/** - packages/*/package.json - check_template_changes: "true" - - # Branding customization - comment_marker_prefix: "runner" + uses: ./.github/workflows/deploy-docs-reusable.yml + with: + # Determine ref based on event type + ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.event.inputs.branch || github.ref_name }} + # Production for main branch, staging for others + target: ${{ needs.setup.outputs.target }} + # Header based on event type + header: ${{ needs.setup.outputs.header }} + # Alias assignment + assign_alias_on_branch: ${{ github.ref == 'refs/heads/main' && 'main' || '' }} + # Alias domains + alias_domains: ${{ needs.setup.outputs.alias_domains }} + # Branch deployment policies + deploy_on_push_branches: ${{ github.event_name == 'push' && github.ref_name || '' }} + deploy_on_pr_base_branches: ${{ github.event_name == 'pull_request' && 'main,**' || '' }} + # Change detection + only_if_changed: ${{ github.event_name == 'pull_request' && 'true' || 'false' }} + change_globs: | + docs/** + changelog/** + packages/*/src/** + packages/*/package.json + check_template_changes: "true" + secrets: + CONSENT_APP_ID: ${{ secrets.CONSENT_APP_ID }} + CONSENT_APP_PRIVATE_KEY: ${{ secrets.CONSENT_APP_PRIVATE_KEY }} + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + CONSENT_GIT_TOKEN: ${{ secrets.CONSENT_GIT_TOKEN }} From 5710e5ef41aef859a91ca7f9dc473b402ba67008 Mon Sep 17 00:00:00 2001 From: Christopher Burns Date: Mon, 1 Dec 2025 17:17:54 +0000 Subject: [PATCH 7/8] Add initial release changelog for runners SDK v1.0.0 - Created a new changelog file detailing the initial release of the runners SDK, including features such as local and remote execution, CLI support, HTTP API orchestration, Playwright integration, TypeScript plugin, and schema extraction. - Highlighted core capabilities and provided example code snippets for better understanding and usage. --- changelog/2025-12-01-v1.0.0.mdx | 133 ++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 changelog/2025-12-01-v1.0.0.mdx diff --git a/changelog/2025-12-01-v1.0.0.mdx b/changelog/2025-12-01-v1.0.0.mdx new file mode 100644 index 0000000..3bd6141 --- /dev/null +++ b/changelog/2025-12-01-v1.0.0.mdx @@ -0,0 +1,133 @@ +--- +title: "v1.0.0 - Initial Release" +version: "1.0.0" +date: 2025-12-01 +description: "Initial release of runners SDK - a small SDK for writing runners that can be executed by an API orchestrator or a local CLI, without worrying about browsers, containers, or regions." +tags: + - release + - stable + - cli + - http + - nitro + - hono +type: release +breaking: false +authors: [BurnedChris] +packages: + - "runners@1.0.0" +--- + +## โœจ New Features + +### Runner SDK + +The runners SDK provides a simple way to write test runners that work both locally and remotely. Write your runner once, run it anywhere. + +```ts +import { z } from "zod"; +import type { Runner } from "runners"; +import { withPlaywright } from "runners/playwright"; + +const CookieBannerInputSchema = z.object({ + url: z.string(), +}); + +export const cookieBannerVisibleTest: Runner< + z.infer +> = async (ctx, input) => { + "use runner"; + + const { page, url, region, log } = await withPlaywright(ctx, input.url); + + log("Checking cookie banner", { url, region }); + + const banner = page + .locator("[data-cookie-banner], .cookie-banner, #cookie-banner") + .first(); + const visible = await banner.isVisible(); + + return { + name: "cookie_banner_visible", + status: visible ? "pass" : "fail", + details: { visible }, + }; +}; +``` + +### CLI Support + +Run your runners locally or in CI with the built-in CLI: + +```bash +runners run ./runners/cookie-banner-visible.ts --input '{"url":"https://example.com"}' +``` + +The CLI handles timeouts, error handling, and result normalization automatically. + +### HTTP API Orchestrator + +Deploy runners as HTTP endpoints for remote execution. The SDK provides built-in HTTP handlers for popular frameworks: + +**Nitro (Vercel/Netlify)** + +```ts +import { createNitroOrchestrator } from "runners/nitro-orchestrator"; + +export default createNitroOrchestrator({ + runners: "./runners", +}); +``` + +**Hono** + +```ts +import { Hono } from "hono"; +import { createHttpOrchestrator } from "runners/http"; + +const app = new Hono(); +app.use("/runners/*", createHttpOrchestrator({ runners: "./runners" })); +``` + +### Playwright Integration + +Opt-in browser automation with Playwright. Use `withPlaywright()` when you need browser functionality: + +```ts +import { withPlaywright } from "runners/playwright"; + +export const myTest: Runner = async (ctx, input) => { + "use runner"; + + const { page, browser, log } = await withPlaywright(ctx, input.url); + + // Your browser automation code here + await page.goto(input.url); + const title = await page.title(); + + return { + name: "page_title_check", + status: "pass", + details: { title }, + }; +}; +``` + +### TypeScript Plugin + +Get full type safety and IntelliSense support with the TypeScript plugin. The plugin validates runner structure and provides helpful autocomplete. + +### Schema Extraction + +Automatically extract runner schemas for validation and documentation. The schema extractor analyzes your runner functions and generates Zod schemas. + +## ๐ŸŽฏ Core Capabilities + +* **Write once, run anywhere**: Runners work locally via CLI and remotely via HTTP API +* **Automatic error handling**: Timeouts, retries, and error normalization built-in +* **Type-safe**: Full TypeScript support with validation +* **Framework agnostic**: Works with Nitro, Hono, Express, and more +* **Browser automation**: Optional Playwright integration when needed +* **Zero configuration**: Sensible defaults, configure only what you need + + + From 4c2e180049eb0decbbce9aa14a990b54b98d1403 Mon Sep 17 00:00:00 2001 From: Christopher Burns Date: Mon, 1 Dec 2025 17:27:53 +0000 Subject: [PATCH 8/8] Refactor GitHub Actions workflow for documentation deployment - Removed push trigger for main and other branches, focusing on pull requests and manual dispatch for deployments. - Simplified alias domain assignments and adjusted target and header outputs based on event types. - Enhanced concurrency handling for deployment jobs, ensuring clearer management of deployment groups. --- .github/workflows/deploy-docs.yml | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 8cca478..192250c 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -6,10 +6,6 @@ on: pull_request: branches: - "**" - # ๐Ÿ“Œ Trigger on pushes to main (production) and other branches (staging) - push: - branches: - - "**" # ๐Ÿ“Œ Manual trigger for production deployments workflow_dispatch: inputs: @@ -29,44 +25,35 @@ jobs: runs-on: ubuntu-latest outputs: alias_domains: ${{ steps.set-vars.outputs.alias_domains }} - target: ${{ steps.set-vars.outputs.target }} - header: ${{ steps.set-vars.outputs.header }} steps: - id: set-vars run: | if [ "${{ github.event_name }}" == "pull_request" ]; then echo "alias_domains=pr-${{ github.event.pull_request.number }}.runner.dev" >> $GITHUB_OUTPUT - echo "target=" >> $GITHUB_OUTPUT - echo "header=runner-docs-preview" >> $GITHUB_OUTPUT - elif [ "${{ github.ref }}" == "refs/heads/main" ] || [ "${{ github.event.inputs.branch }}" == "main" ]; then + elif [ "${{ github.event.inputs.branch }}" == "main" ]; then echo "alias_domains=runner.com" >> $GITHUB_OUTPUT - echo "target=production" >> $GITHUB_OUTPUT - echo "header=runner-docs-production" >> $GITHUB_OUTPUT else - BRANCH_NAME="${{ github.ref_name || github.event.inputs.branch }}" - echo "alias_domains=${BRANCH_NAME}.runner.dev" >> $GITHUB_OUTPUT - echo "target=" >> $GITHUB_OUTPUT - echo "header=runner-docs-staging" >> $GITHUB_OUTPUT + echo "alias_domains=${{ github.event.inputs.branch }}.runner.dev" >> $GITHUB_OUTPUT fi deploy: needs: setup concurrency: - group: ${{ github.event_name == 'pull_request' && 'docs-preview-' || github.ref == 'refs/heads/main' && 'docs-production-' || 'docs-staging-' }}${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref_name || github.event.inputs.branch }} + group: ${{ github.event_name == 'pull_request' && 'docs-preview-' || 'docs-production-' }}${{ github.event_name == 'pull_request' && github.ref || github.event.inputs.branch }} cancel-in-progress: true uses: ./.github/workflows/deploy-docs-reusable.yml with: # Determine ref based on event type - ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.event.inputs.branch || github.ref_name }} - # Production for main branch, staging for others - target: ${{ needs.setup.outputs.target }} + ref: ${{ github.event_name == 'pull_request' && '' || github.event.inputs.branch }} + # Production for manual dispatch, staging for PRs + target: ${{ github.event_name == 'workflow_dispatch' && 'production' || '' }} # Header based on event type - header: ${{ needs.setup.outputs.header }} + header: ${{ github.event_name == 'pull_request' && 'runner-docs-preview' || 'runner-docs-production' }} # Alias assignment - assign_alias_on_branch: ${{ github.ref == 'refs/heads/main' && 'main' || '' }} + assign_alias_on_branch: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.branch || '' }} # Alias domains alias_domains: ${{ needs.setup.outputs.alias_domains }} # Branch deployment policies - deploy_on_push_branches: ${{ github.event_name == 'push' && github.ref_name || '' }} + deploy_on_push_branches: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.branch || '' }} deploy_on_pr_base_branches: ${{ github.event_name == 'pull_request' && 'main,**' || '' }} # Change detection only_if_changed: ${{ github.event_name == 'pull_request' && 'true' || 'false' }}