diff --git a/.github/ISSUE_TEMPLATE/build.md b/.github/ISSUE_TEMPLATE/build.md index 2e26fc0..1667596 100644 --- a/.github/ISSUE_TEMPLATE/build.md +++ b/.github/ISSUE_TEMPLATE/build.md @@ -1,7 +1,7 @@ --- name: Build about: Got a build system problem? Let’s fix it! -title: '' +title: "" labels: build --- diff --git a/.github/ISSUE_TEMPLATE/chore.md b/.github/ISSUE_TEMPLATE/chore.md index 9554310..d444d0b 100644 --- a/.github/ISSUE_TEMPLATE/chore.md +++ b/.github/ISSUE_TEMPLATE/chore.md @@ -1,7 +1,7 @@ --- name: Chore about: General upkeep time! -title: '' +title: "" labels: chore --- diff --git a/.github/ISSUE_TEMPLATE/ci.md b/.github/ISSUE_TEMPLATE/ci.md index d22fd50..cd3ea39 100644 --- a/.github/ISSUE_TEMPLATE/ci.md +++ b/.github/ISSUE_TEMPLATE/ci.md @@ -1,7 +1,7 @@ --- name: CI about: Continuous Integration to the rescue! -title: '' +title: "" labels: ci --- diff --git a/.github/ISSUE_TEMPLATE/documentation.md b/.github/ISSUE_TEMPLATE/documentation.md index ecdddbf..8d7cea7 100644 --- a/.github/ISSUE_TEMPLATE/documentation.md +++ b/.github/ISSUE_TEMPLATE/documentation.md @@ -1,7 +1,7 @@ --- name: Documentation about: Help us improve our docs! -title: '' +title: "" labels: documentation --- diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md index b6595c8..845f1b3 100644 --- a/.github/ISSUE_TEMPLATE/feature.md +++ b/.github/ISSUE_TEMPLATE/feature.md @@ -1,7 +1,7 @@ --- name: Feature about: Suggest something awesome for this project! -title: '' +title: "" labels: feature --- diff --git a/.github/ISSUE_TEMPLATE/fix.md b/.github/ISSUE_TEMPLATE/fix.md index f1a7782..44642c7 100644 --- a/.github/ISSUE_TEMPLATE/fix.md +++ b/.github/ISSUE_TEMPLATE/fix.md @@ -1,7 +1,7 @@ --- name: Fix about: Found something broken? Let's fix it! -title: '' +title: "" labels: fix --- diff --git a/.github/ISSUE_TEMPLATE/performance.md b/.github/ISSUE_TEMPLATE/performance.md index 61a3e8d..d96737b 100644 --- a/.github/ISSUE_TEMPLATE/performance.md +++ b/.github/ISSUE_TEMPLATE/performance.md @@ -1,7 +1,7 @@ --- name: Performance about: Speed it up! -title: '' +title: "" labels: performance --- diff --git a/.github/ISSUE_TEMPLATE/refactor.md b/.github/ISSUE_TEMPLATE/refactor.md index d480153..530dae7 100644 --- a/.github/ISSUE_TEMPLATE/refactor.md +++ b/.github/ISSUE_TEMPLATE/refactor.md @@ -1,7 +1,7 @@ --- name: Refactor about: Time to clean up the code! -title: '' +title: "" labels: refactor --- diff --git a/.github/ISSUE_TEMPLATE/style.md b/.github/ISSUE_TEMPLATE/style.md index 437662f..afcf033 100644 --- a/.github/ISSUE_TEMPLATE/style.md +++ b/.github/ISSUE_TEMPLATE/style.md @@ -1,7 +1,7 @@ --- name: Style about: Let's make it look prettier! -title: '' +title: "" labels: style --- diff --git a/.github/ISSUE_TEMPLATE/test.md b/.github/ISSUE_TEMPLATE/test.md index e3a7c74..8e12fd9 100644 --- a/.github/ISSUE_TEMPLATE/test.md +++ b/.github/ISSUE_TEMPLATE/test.md @@ -1,7 +1,7 @@ --- name: Tests about: Let’s add or fix some tests! -title: '' +title: "" labels: tests --- diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 5db831a..a7e6971 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -25,4 +25,4 @@ Please fill out the details below to submit your pull request. ## Solution: - **Describe the solution:** - - Briefly explain what you've done and how it addresses the issue. \ No newline at end of file + - Briefly explain what you've done and how it addresses the issue. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..c371d15 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,38 @@ +name: Build + +on: + workflow_call: + +jobs: + cli: + name: CLI + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + name: Checkout Code + + - uses: pnpm/action-setup@v6 + name: Install pnpm + + with: + version: 10 + + - uses: actions/setup-node@v6 + name: Setup Node.js + + with: + node-version: 24 + cache: pnpm + + - run: pnpm install + name: Install Dependencies + + - run: pnpm build + name: Build Package + + - run: node dist/index.js --help + name: Test CLI Help + + - run: node dist/index.js --version + name: Test CLI Version diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..86f965a --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,42 @@ +name: Deploy + +on: + workflow_call: + secrets: + NPM_TOKEN: + required: true + +jobs: + npm: + name: npm + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + name: Checkout Code + + - uses: pnpm/action-setup@v6 + name: Install pnpm + + with: + version: 10 + + - uses: actions/setup-node@v6 + name: Setup Node.js + + with: + node-version: 24 + cache: pnpm + registry-url: https://registry.npmjs.org + + - run: pnpm install + name: Install Dependencies + + - run: pnpm build + name: Build Package + + - run: npm publish --access public + name: Publish to npm + + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..f2e50b9 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,25 @@ +name: Main + +on: + workflow_dispatch: + + push: + branches: [main] + + pull_request: + branches: [main] + +jobs: + verify: + name: Verify + uses: ./.github/workflows/verify.yml + + build: + name: Build + needs: verify + uses: ./.github/workflows/build.yml + + test: + name: Test + needs: build + uses: ./.github/workflows/test.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..aa9dec2 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,28 @@ +name: Release + +on: + workflow_dispatch: + + push: + tags: ["*"] + +jobs: + verify: + name: Verify + uses: ./.github/workflows/verify.yml + + build: + name: Build + needs: verify + uses: ./.github/workflows/build.yml + + test: + name: Test + needs: build + uses: ./.github/workflows/test.yml + + deploy: + name: Deploy + needs: test + secrets: inherit + uses: ./.github/workflows/deploy.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..bf31927 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,32 @@ +name: Test + +on: + workflow_call: + +jobs: + cli: + name: CLI + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + name: Checkout Code + + - uses: pnpm/action-setup@v6 + name: Install pnpm + + with: + version: 10 + + - uses: actions/setup-node@v6 + name: Setup Node.js + + with: + node-version: 24 + cache: pnpm + + - run: pnpm install + name: Install Dependencies + + - run: pnpm test -- --run + name: Run Tests diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index caa9ed8..0000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Tests - -on: - push: - branches: - - main - - pull_request: - branches: - - main - -jobs: - test: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - - with: - node-version: 22 - - - name: Install pnpm - uses: pnpm/action-setup@v4 - - with: - version: 10 - - - name: Install dependencies - run: pnpm install - - - name: Run tests - run: pnpm run test diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml new file mode 100644 index 0000000..eee82a9 --- /dev/null +++ b/.github/workflows/verify.yml @@ -0,0 +1,38 @@ +name: Verify + +on: + workflow_call: + +jobs: + cli: + name: CLI + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + name: Checkout Code + + - uses: pnpm/action-setup@v6 + name: Install pnpm + + with: + version: 10 + + - uses: actions/setup-node@v6 + name: Setup Node.js + + with: + node-version: 24 + cache: pnpm + + - run: pnpm install + name: Install Dependencies + + - run: pnpm typecheck + name: Check Type Hints + + - run: pnpm lint + name: Lint Code + + - run: pnpm format:check + name: Check Formatting diff --git a/.gitignore b/.gitignore index cc8a6b4..255b906 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .env +coverage/ dist/ metadata/ node_modules/ diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..449691b --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +save-exact=true \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..9594e67 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "semi": true, + "tabWidth": 2, + "printWidth": 80, + "singleQuote": false, + "trailingComma": "all", + "arrowParens": "always" +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..3dd8384 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,554 @@ +# AGENTS.md + +## 1. Overview + +ghitgud is a TypeScript CLI that manages GitHub repository labels — list, pull, push, and prune — via the GitHub REST API. Built on Node.js with Commander for the CLI framework, Consola for rich output, and `dotenv` for configuration. The codebase follows a layered architecture: CLI entry point → command modules → service modules → API client → config/constants. All output uses Consola for rich CLI output; all errors throw custom exception classes caught at the entry boundary. + +--- + +## 2. Repository Structure + +``` +src/ + cli/ + index.ts # entry point — Commander program setup and global error boundary + ascii.ts # figlet banner for help output + commands/ + ping.ts # ghitgud ping + labels.ts # ghitgud labels + --template flag + config.ts # ghitgud config + services/ + labels.ts # label business logic (list, pull, push, prune, template variants) + config.ts # config business logic (get, set) + api/ + client.ts # base HTTP client — auth headers, error mapping, request wrapper + labels.ts # GitHub Labels API methods + core/ + constants.ts # all shared constants (status codes, paths, error messages, config keys) + errors.ts # custom error class hierarchy (GhitgudError → AuthError, ConfigError, NotFoundError, UnprocessableError) + config.ts # config resolver — env vars first, then credentials file + io.ts # generic file helpers (readJsonFile, writeJsonFile, fileExists, ensureDir) + logger.ts # consola instance for rich CLI output + types/ + index.ts # shared type definitions (Label, normalizeLabel) + env.d.ts # global type declarations (__VERSION__) +templates/ + base.json # minimal label template + conventional.json # conventional-commits label template + github.json # GitHub default label template +tests/ + unit/ + api/ + client.test.ts + labels.test.ts + cli/ + ascii.test.ts + index.test.ts + commands/ + config.test.ts + labels.test.ts + ping.test.ts + core/ + config.test.ts + errors.test.ts + logger.test.ts + io.test.ts + services/ + config.test.ts + labels.test.ts +tests/tsconfig.json +eslint.config.mjs # ESLint flat config +.prettierrc.json # Prettier config +vite.config.ts # Vite build + Vitest test config (combined) +tsconfig.json # TypeScript config for src/ +package.json +VERSION # single source of truth for version +``` + +- New commands go in `src/commands/`. Each exports `{ register }` — a function that takes the Commander `program` and wires up subcommands. +- New service logic goes in `src/services/`. Services hold business logic and I/O. They import from `api/` and `core/`. +- New API endpoints go in `src/api/`. API modules use the shared `client.ts` — never call `fetch` directly. +- All constants live in `src/core/constants.ts`. No magic strings or numbers elsewhere. +- All custom errors live in `src/core/errors.ts`. No bare `new Error()` for domain errors. +- No `import "dotenv/config"` outside of `src/core/config.ts`. Config resolution is centralized. +- `templates/` holds JSON label presets; resolved at runtime via `__dirname` (bundled to `dist/templates/` by Vite build). +- `@/` import aliases are used throughout. Resolved by Vite at build time and by `tsconfig.json` `paths` for type checking. No `baseUrl` — paths resolve relative to their tsconfig location. + +--- + +## 5. Commands and Workflows + +```bash +# Install dependencies +pnpm install + +# Build (Vite produces single CJS bundle at dist/index.js) +pnpm build + +# Run locally +pnpm start # node dist/index.js + +# Test +pnpm test # vitest (watch mode) +pnpm test -- --run # single run (no watch) + +# Lint +pnpm lint # eslint src/ tests/ + +# Format +pnpm format # prettier --write . +pnpm format:check # prettier --check . + +# Type check +pnpm typecheck # tsc --noEmit (uses tsconfig.json) + +# Type check tests +npx tsc --noEmit -p tests/tsconfig.json + +# Coverage +pnpm test:coverage + +# Clean build artifacts +pnpm clean + +# Clean local config +bash scripts/clean.sh +``` + +CI uses reusable GitHub Actions workflows (verify, build, test, deploy). The verify workflow runs typecheck, lint, and format checks. + +--- + +## 6. Code Formatting + +### TypeScript + +**Indentation:** 2 spaces. No tabs anywhere. Enforced by Prettier. + +```typescript +const register = (program: Command) => { + program + .command("ping") + .description("Check if the CLI is working.") + .action(() => void labelsService.ping()); +}; +``` + +**Line length:** `printWidth: 80` in Prettier config. Keep lines under 80 in practice. + +**Blank lines — top-level:** 1 blank line between top-level definitions (functions, constants, exports). + +```typescript +const ping = () => { + const result = { success: true, message: PING_RESPONSE }; + logger.success(PING_RESPONSE); + return result; +}; + +const list = async () => { +``` + +**Blank lines — methods:** No blank lines between methods inside an object literal export. + +```typescript +export default { + ping, + list, + pull, +}; +``` + +**Blank lines — after imports:** 1 blank line after the import block, then 1 blank line between import groups (stdlib → third-party → local). + +```typescript +import fs from "fs"; +import path from "path"; + +import { Command } from "commander"; + +import labelsService from "@/services/labels"; +import { + GHITGUD_FOLDER, + METADATA_FILE_PATH, + ERROR_NO_METADATA, + PING_RESPONSE, +} from "@/core/constants"; +``` + +**Trailing newline:** Files end with a single newline. Enforced by Prettier. + +**Trailing whitespace:** Never present. Enforced by Prettier. + +**Quote style:** Double quotes for all string literals — imports, arguments, object keys, template literals. Enforced by Prettier (`singleQuote: false`). + +```typescript +import fs from "fs"; +const TEMPLATES_DIR = path.join(__dirname, "templates"); +``` + +**Brace placement:** Opening brace always on the same line. + +```typescript +const handleError = (status: number): never => { + if (status === STATUS_UNAUTHORIZED) throw new AuthError("Unauthorized."); +``` + +**Spacing — operators:** Spaces around binary operators. No spaces inside parentheses or brackets. + +```typescript +if (response.status === STATUS_OK_MIN) return response; +const result = { success: true, key, value: value || null }; +``` + +**Spacing — colons:** No space before colon in object properties, space after. Space after colon in type annotations. + +```typescript +const result = { success: true, key, value: value || null }; +interface RequestOptions { + method?: string; + body?: unknown; +} +``` + +**Trailing commas:** Present on multi-line object and array literals, and on multi-line function argument lists. + +```typescript +import { + GHITGUD_FOLDER, + METADATA_FILE_PATH, + ENCODING, + ERROR_NO_METADATA, + PING_RESPONSE, +} from "@/core/constants"; +``` + +**Semicolons:** Always present at the end of statements. + +```typescript +const NAME = "ghitgud"; +program.name(NAME).description(DESCRIPTION).version(__VERSION__); +``` + +**Export default pattern:** Each module exports a default object or function as a single `export default` at the end. + +```typescript +export default { set, get }; +export default client; +export default ascii; +``` + +--- + +## 7. Naming Conventions + +### TypeScript + +**Functions and methods:** `camelCase`. Named for their action or query. + +```typescript +const ping = () => { ... } +const list = async () => { ... } +const pullTemplate = async (templateName: string, templatesDir: string) => { ... } +function buildHeaders(): Record { ... } +function handleError(status: number): never { ... } +``` + +**Classes (error types):** `PascalCase` with `Error` suffix. Base class is `GhitgudError`. + +```typescript +class GhitgudError extends Error { ... } +class AuthError extends GhitgudError { ... } +class ConfigError extends GhitgudError { ... } +class NotFoundError extends GhitgudError { ... } +class UnprocessableError extends GhitgudError { ... } +``` + +**Constants:** `SCREAMING_SNAKE_CASE` for module-level constants. + +```typescript +const STATUS_OK_MIN = 200; +const GHITGUD_FOLDER = path.join(os.homedir(), ".config", "ghitgud"); +const ERROR_NO_REPO = + "You must set the GHITGUD_GITHUB_REPO environment variable."; +``` + +**File names:** `camelCase.ts`. Match the primary concern of the module. + +``` +client.ts labels.ts config.ts constants.ts errors.ts +``` + +**Test files:** `.test.ts` under `tests/unit//`. + +``` +tests/unit/core/errors.test.ts +tests/unit/services/labels.test.ts +``` + +**Private/local-only functions:** Still `camelCase` — no underscore prefix. + +```typescript +function buildHeaders(): Record { ... } // not exported, but no _ +``` + +--- + +## 8. Type Annotations + +### TypeScript + +- Public function parameters and return types are annotated. Arrow functions with obvious return types may omit the explicit return type annotation. +- Interfaces use PascalCase. Types are defined in `src/types/index.ts` or inline in the module where used. + +```typescript +interface RequestOptions { + method?: string; + body?: unknown; +} +``` + +- Type casting uses `as` for narrowing: + +```typescript +if (!SUPPORTED_CONFIG_KEYS.includes(key as SupportedKey)) { +``` + +- Tuple type inference for `const` arrays uses `(typeof ARR)[number]` for derived union types: + +```typescript +export const SUPPORTED_CONFIG_KEYS = ["token", "repo"] as const; +type SupportedKey = (typeof SUPPORTED_CONFIG_KEYS)[number]; +``` + +- `tsconfig.json` has `"strict": true`. The type checker is enforced. +- Global type-only declarations go in `src/env.d.ts` (e.g., `declare const __VERSION__: string`). + +--- + +## 9. Imports + +### TypeScript + +Three groups, separated by blank lines: + +1. **Stdlib** — `fs`, `path`, `process`, `os` +2. **Third-party** — `commander`, `consola`, `figlet`, `dotenv` +3. **Local** — `@/` import aliases (`@/core/constants`, `@/services/labels`, etc.) + +Within each group, imports are loosely sorted — stdlib by usage order, third-party by package name, local by module path. + +Side-effect imports (`import "dotenv/config"`) only appear in `src/core/config.ts`. + +**Canonical example:** + +```typescript +import fs from "fs"; +import path from "path"; + +import { Command } from "commander"; + +import labelsService from "@/services/labels"; +import logger from "@/core/logger"; +import { + GHITGUD_FOLDER, + CREDENTIALS_FILE, + ENCODING, + ERROR_UNSUPPORTED_KEY, + SUPPORTED_CONFIG_KEYS, +} from "@/core/constants"; +import { ConfigError } from "@/core/errors"; +``` + +- Named imports use `{ }` destructuring. Single-import named imports are on one line. +- Default imports use `import X from` — no `{ default as X }` syntax. +- No `import *` anywhere in the codebase. +- No `import type` keyword — regular `import` is used for both values and types. +- Sibling imports use `./` (e.g., `import ascii from "./ascii"` in `cli/index.ts`). + +--- + +## 10. Error Handling + +### TypeScript + +**Custom error hierarchy** in `src/core/errors.ts`: + +```typescript +class GhitgudError extends Error { ... } +class AuthError extends GhitgudError { ... } +class ConfigError extends GhitgudError { ... } +class NotFoundError extends GhitgudError { ... } +class UnprocessableError extends GhitgudError { ... } +``` + +**Rules:** + +- All domain errors throw a custom `GhitgudError` subclass — never bare `new Error()` for business logic failures. +- `throw new Error(...)` is acceptable for truly unexpected or infrastructure failures (e.g., template not found). +- The global error boundary in `src/cli/index.ts` catches `GhitgudError` and logs via `logger.error` with exit code 1. Unknown errors re-throw. `CommanderError` with `exitCode: 0` is treated as a successful exit. +- API errors map HTTP status codes to exception types via `handleError` in `client.ts`. Unmapped status codes throw `GhitgudError`. +- Config errors (`missing token`, `missing repo`) throw `ConfigError`. +- Services do not catch errors — they throw and let the CLI boundary handle output. + +**No `try/catch` in services.** The pattern is: + +```typescript +// services/labels.ts +if (!io.fileExists(METADATA_FILE_PATH)) throw new Error(ERROR_NO_METADATA); + +// api/client.ts +if (isSuccessful(response.status)) return response; +handleError(response.status); +``` + +--- + +## 11. Comments and Docstrings + +### TypeScript + +- **No doc comments** are used anywhere in the codebase. Neither JSDoc (`/** */`) nor inline doc comments appear. +- **No module-level docstrings.** +- **Inline comments** are absent from the current codebase. Code is self-documenting through descriptive naming. +- Self-documenting patterns are preferred: named constants (`STATUS_OK_MIN`, `ERROR_NO_REPO`), descriptive function names (`pullTemplate`, `handleError`), and typed parameters. +- `never` is allowed as a comment on tests and `TODO` items. + +--- + +## 12. Testing + +### Framework: Vitest 3.x + +```bash +pnpm test # run all tests (watch mode) +pnpm test -- --run # single run (no watch) +pnpm test:coverage # run with coverage +``` + +- Test files live in `tests/unit/` organized by domain subdirectory, not alongside source files. +- A separate `tests/tsconfig.json` extends the root config and includes both test and source files for type checking. +- File naming: `.test.ts`. +- Test structure: `describe("", () => { it("", ...) })`. + +```typescript +import { describe, it, expect, vi, Mock, beforeEach, afterEach } from "vitest"; + +import api from "@/api/labels"; +import labelsService from "@/services/labels"; + +vi.mock("@/api/labels", () => ({ + default: { + fetch: vi.fn(), + get: vi.fn(), + create: vi.fn(), + patch: vi.fn(), + delete: vi.fn(), + }, +})); + +describe("labels", () => { + beforeEach(() => { + vi.spyOn(logger, "success").mockImplementation(() => {}); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("should list labels", async () => { + const mockResponse = { json: () => Promise.resolve(API_LABELS) }; + (api.fetch as Mock).mockResolvedValue(mockResponse); + const result = await labelsService.list(); + expect(result).toEqual({ success: true, metadata: METADATA_LABELS }); + }); +}); +``` + +- `vi.mock()` is used at module scope to mock API and config modules — tests never make real HTTP calls or filesystem writes. +- `vi.spyOn()` is used for method-level mocking (e.g., `vi.spyOn(io, "fileExists").mockReturnValue(true)`). +- `vi.restoreAllMocks()` in `afterEach` to clean up between tests. +- Dynamic `import()` with `vi.resetModules()` is used when testing modules that read environment variables at import time. +- `@vitest/coverage-v8` is used for coverage reporting. Target: ≥80% statement coverage. + +--- + +## 13. Git + +> **Repo-wide:** + +- **Commit prefixes** — lowercase, followed by colon and space: + - `feat:` — new user-visible behavior + - `fix:` — bug fix + - `refactor:` — code restructure without behavior change + - `chore:` — build, release, dependency, or metadata changes + - `tests:` — test additions or modifications + - `ci:` — CI/CD workflow changes + - `documentation:` — documentation-only changes + - `repo:` — project scaffolding +- **No scopes** are used — commits are not scoped to modules or services. +- **Subject line:** Imperative mood, no period, under 50 characters median (p95 under 38). +- **Body:** Never used — 0% of commits have a body. +- **GPG signing:** Not enforced. +- **Merge strategy:** Rebase. No merge commits in history. + +--- + +## 14. Dependencies and Tooling + +### TypeScript / Node.js + +- **Package manager:** pnpm. `pnpm-lock.yaml` is committed. `.npmrc` has `save-exact=true`. +- **Add a dependency:** `pnpm add ` +- **Build tool:** Vite 8.x. `vite.config.ts` handles build (single CJS bundle to `dist/index.js` with shebang) and test config (Vitest). Node.js builtins and production deps are externalized. +- **Type checker:** `tsc --noEmit`. Config in `tsconfig.json` (for `src/`) and `tests/tsconfig.json` (for tests). Both use `"moduleResolution": "bundler"` and `"paths"` with `"@/*"` aliases — no `baseUrl` (deprecated in TS 7.0). +- **Formatter:** Prettier 3.x with `.prettierrc.json`. Config: double quotes, semicolons, trailing commas, 80-char print width, 2-space indent. Run `pnpm format` to auto-fix, `pnpm format:check` to verify. +- **Linter:** ESLint 10.x with flat config (`eslint.config.mjs`). Uses `@eslint/js` recommended, `typescript-eslint` recommended, and `eslint-config-prettier` to disable formatting rules. Run `pnpm lint` to check. +- **Build:** `pnpm build` runs `rm -rf dist && vite build && cp -r templates dist/`. +- **Runtime:** Node.js 24+. `#!/usr/bin/env node` shebang set via Vite `output.banner`. +- **Version:** Single source of truth in `VERSION` file at repo root. Inlined at build time via Vite `define` as `__VERSION__` (declared in `src/env.d.ts`). +- **Entry point:** `dist/index.js` (declared in `package.json` `bin` and `main`). +- **npm publishing:** `package.json` `files` field limits published content to `dist/`, `templates/`, and `VERSION`. `prepublishOnly` script runs typecheck, tests, and build. +- **Test config:** Combined in `vite.config.ts` using `defineConfig` from `vitest/config`. No separate `vitest.config.ts`. + +--- + +## 15. Red Lines + +**Formatting violations:** + +- Never use single quotes for string literals — the codebase uses double quotes consistently. Enforced by Prettier (`singleQuote: false`). +- Never use tabs for indentation — always 2 spaces. Enforced by Prettier (`tabWidth: 2`). +- Never omit trailing commas in multi-line imports, objects, or arrays. Enforced by Prettier (`trailingComma: "all"`). +- Prettier handles all formatting — run `pnpm format` before committing. CI enforces `pnpm format:check`. + +**Architectural violations:** + +- Never call `fetch` directly outside `src/api/client.ts`. All HTTP requests go through the client module. +- Never define module-level constants in service or command files — move them to `src/core/constants.ts`. +- Never throw bare `new Error()` for domain failures — use the appropriate `GhitgudError` subclass from `src/core/errors.ts`. +- Never import `"dotenv/config"` outside `src/core/config.ts`. Environment variable resolution is centralized. +- Never register Commander commands in `src/cli/index.ts` — each command has its own module exporting `{ register }`. +- Never use `baseUrl` in tsconfig — `paths` resolves relative to the tsconfig file location when `baseUrl` is absent. This is TS 7.0-ready. +- Never use `tsc-alias` — Vite handles `@/` import alias resolution at build time. +- Never use `__dirname` with `import.meta.url` / `fileURLToPath` patterns in source — use `__dirname` directly (available in CJS context after Vite bundling). +- Never use `consola/core` in `src/core/logger.ts` — it has no reporters and produces no output. Use `import { createConsola } from "consola"` instead. `consola` must be in `vite.config.ts` `rollupOptions.external`. + +**Style violations:** + +- Never use `SCREAMING_SNAKE_CASE` for anything except module-level constants — functions and variables are `camelCase`. +- Never add JSDoc comments — the codebase has zero doc comments. Use descriptive names and typed parameters instead. +- Never use `console.info` for output — use `console.log` for stdout, `console.error` for stderr, and `console.table` for tabular label display. + +**Testing violations:** + +- Never make real HTTP calls in tests — mock `api/` modules with `vi.mock()`. +- Never write tests alongside source files — place them in `tests/unit//`. +- Never use `describe` without a `it` — tests use `describe`/`it` blocks, not `test()`. +- Never forget to mock `io` module methods (e.g., `fileExists`, `readJsonFile`) when testing service functions that read files — tests must not hit the real filesystem. +- Never forget to mock `@/core/logger` when testing services that use `logger.success`, `logger.info`, etc. + +**Git violations:** + +- Never commit without a conventional prefix (`feat:`, `fix:`, etc.) — every commit message has one. +- Never use scopes in commit prefixes — no `feat(labels):` style. +- Never include a body in commit messages — subject only, imperative mood. diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ac1608..de8aead 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,29 +1,90 @@ # Changelog -All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) with some edits, + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -# 1.0.3 +## [2.0.0] - 2025-05-09 + +Complete architecture overhaul. The CLI is now organized into layered modules (cli → commands → services → api → core) with structured JSON output, error hierarchies, and a Vite-based build pipeline. + +### Added + +- `config get ` command to retrieve stored configuration values +- `labels pull --template ` flag for pulling from built-in label templates +- `labels push --template ` flag for pushing from built-in label templates +- `core/format.ts` for consistent JSON output to stdout and stderr +- `core/errors.ts` with `GhitgudError` hierarchy (`AuthError`, `ConfigError`, `NotFoundError`, `UnprocessableError`) +- `core/io.ts` with generic file helpers (`readJsonFile`, `writeJsonFile`, `fileExists`, `ensureDir`) +- `api/client.ts` as a base HTTP client with auth guard, 2xx success checks, and error registry pattern +- `services/config.ts` with `validateKey` helper for supported config keys +- `services/labels.ts` with `upsertLabels` helper and `normalizeLabel` in types +- Structured JSON error output `{ success: false, error: "..." }` to stderr +- Consistent JSON output shape `{ success: true, ... }` for all commands including `ping` +- Global error boundary in `cli/index.ts` catching `GhitgudError` subclasses +- Self-registering command modules exported as `{ register }` functions +- Version read from `VERSION` file at runtime instead of hardcoded +- `core/constants.ts` centralizing all shared constants, error messages, and config type definitions +- Multi-step CI/CD with reusable workflows (verify, build, test, deploy) +- Vite-based build pipeline replacing `tsc` + `tsc-alias`, producing a single CJS bundle with shebang +- `@/` import aliases resolved by Vite at build time and `tsconfig` paths for type checking (no `baseUrl`, TS 7.0-ready) +- `typecheck`, `lint`, `clean`, and `prepublishOnly` scripts in `package.json` +- `files`, `engines`, and `env.d.ts` declarations in `package.json` for npm publishing safety +- `.npmrc` with `save-exact=true` for deterministic dependency resolution +- `coverage/` in `.gitignore` +- GitHub Actions workflows with `cache: pnpm` for faster CI runs +- Test suite expanded from 1 file to 13 files covering api, cli, commands, core, and services +- `@vitest/coverage-v8` integrated with `test:coverage` script +- Tests for `cli/ascii.ts` and `cli/index.ts` + +### Changed + +- Restructured CLI into layered architecture: `cli/ → commands/ → services/ → api/ → core/` +- Eliminated circular dependency between old `app/config.js` and `app/functions.js` +- Split monolithic `app/library.ts` into focused `services/labels.ts` and `services/config.ts` +- Replaced declarative commands dictionary with self-registering command modules +- All HTTP 2xx status codes now accepted (previously only 200) +- `labels prune` now awaits all delete promises instead of fire-and-forget +- `console.info` replaced with `console.log` for proper stdout behavior +- Error registry pattern (`ERROR_MAP`, `ERROR_MESSAGES`) local to `client.ts` for extensible status-to-error mapping +- `handleError` in `client.ts` now throws `GhitgudError` for unmapped status codes instead of bare `Error` +- Build output changed from `dist/cli/index.js` to single `dist/index.js` bundle +- Templates copied to `dist/templates/` at build time, resolved via `__dirname` at runtime +- CI workflows reordered to install pnpm before setting up Node.js caching +- `@vitest/coverage-v8` version aligned with `vitest` (3.2.4) +- `templates/conventional.json` reindented from 4 spaces to 2 spaces +- GitHub Actions upgraded to Node.js 24, checkout@v6, setup-node@v6, pnpm/action-setup@v6 + +### Fixed -## What's Changed +- `labels prune` fire-and-forget bug: all delete promises are now awaited +- `handleError` in `client.ts` now throws `GhitgudError` for unmapped status codes instead of bare `Error` +- Redundant `declare const __VERSION__` removed from `cli/index.ts` (already in `env.d.ts`) +- `baseUrl` removed from `tsconfig.json` — `paths` resolves relative to tsconfig location (TS 7.0-ready) +- `tests/tsconfig.json` added for test type checking with correct `@/` path resolution +- `package-lock.json` removed (project uses pnpm exclusively) +- `vitest.config.ts` merged into `vite.config.ts` using `defineConfig` from `vitest/config` +- `io` module mocked in `labels.test.ts` for push/prune tests — no real filesystem hits +- Duplicates removed from `labels.test.ts` test suite -- noop: deployment trigger +## [1.0.3] - 2025-05-09 -# 1.0.2 +Deployment trigger release. -## What's Changed +## [1.0.2] - 2025-05-09 -- noop: deployment trigger +Deployment trigger release. -# 1.0.1 +## [1.0.1] - 2025-05-09 -## What's Changed +### Changed -- refactor: change the base metadata folder +- Base metadata folder path changed -# 1.0.0 +## [1.0.0] - 2025-05-09 -## What's Changed +### Added -- feat: add base cli with labels, ping and config commands; -- feat: add github label templates; +- Base CLI with `labels`, `ping`, and `config` commands +- GitHub label templates (base, conventional, github) diff --git a/CITATION.cff b/CITATION.cff index 5a9725d..ed6e426 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -2,12 +2,12 @@ cff-version: 1.2.0 message: If you use this software in your work, please cite it using the following metadata title: Ghitgud authors: -- family-names: Sardone - given-names: Francesco + - family-names: Sardone + given-names: Francesco keywords: -- credit -- citation -version: 1.0.3 -date-released: 2025-06-13 + - credit + - citation +version: 2.0.0 +date-released: 2026-05-09 license: GPL-3.0 repository-code: https://github.com/airscripts/ghitgud diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index d9c8014..3ac79e6 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,6 +1,7 @@ # Contributor Covenant Code of Conduct ## Our Pledge + We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender @@ -12,29 +13,31 @@ We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards + Examples of behavior that contributes to a positive environment for our community include: -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience -* Focusing on what is best not just for us as individuals, but for the +- Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: -* The use of sexualized language or imagery, and sexual attention or +- The use of sexualized language or imagery, and sexual attention or advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a +- Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities + Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, @@ -46,6 +49,7 @@ not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope + This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, @@ -53,6 +57,7 @@ posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement + Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at . @@ -62,10 +67,12 @@ All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines + Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction + **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. @@ -74,6 +81,7 @@ clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning + **Community Impact**: A violation through a single incident or series of actions. @@ -85,6 +93,7 @@ like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban + **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. @@ -95,23 +104,25 @@ with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban + **Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an +standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution + This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. -Community Impact Guidelines were inspired by +Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. For answers to common questions about this code of conduct, see the FAQ at -[https://www.contributor-covenant.org/faq][FAQ]. Translations are available +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. [homepage]: https://www.contributor-covenant.org diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a30fe25..a5924f0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,2 +1,40 @@ # Contributing -When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository, ensuring you follow the [Code of Conduct](https://github.com/airscripts/ghitgud/blob/main/CODE_OF_CONDUCT.md). + +When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository, ensuring you follow the [Code of Conduct](https://github.com/airscripts/ghitgud/blob/main/CODE_OF_CONDUCT.md). + +## Development Setup + +```bash +pnpm install # install dependencies +pnpm build # build with Vite (single CJS bundle) +pnpm start # run the CLI locally +pnpm test # run tests (watch mode) +pnpm test -- --run # single test run +pnpm test:coverage # run tests with coverage report +pnpm typecheck # type check without emitting +pnpm lint # type check (alias for typecheck) +pnpm clean # remove dist/ and coverage/ +bash scripts/clean.sh # remove local config directory (~/.config/ghitgud) +``` + +## Commit Convention + +All commit messages must use a lowercase prefix followed by a colon and space: + +- `feat:` — new user-visible behavior +- `fix:` — bug fix +- `refactor:` — code restructure without behavior change +- `chore:` — build, release, dependency, or metadata changes +- `tests:` — test additions or modifications +- `ci:` — CI/CD workflow changes +- `documentation:` — documentation-only changes +- `repo:` — project scaffolding + +Subject line: imperative mood, no period, under 50 characters. No scopes. No body. + +## Pull Requests + +- Use the pull request template provided in the repository. +- Ensure all tests pass before submitting. +- Rebase your branch on `main` before opening a PR. +- One logical change per PR. diff --git a/README.md b/README.md index 8fc5189..db979b5 100644 --- a/README.md +++ b/README.md @@ -10,45 +10,121 @@ Usage GIF

+

+ npm + License +

+ ## Table of Contents + - [Installation](#installation) -- [Usage](#usage) -- [Wiki](#wiki) +- [Configuration](#configuration) +- [Commands](#commands) +- [Templates](#templates) +- [Output Format](#output-format) +- [Development](#development) - [Contributing](#contributing) - [Support](#support) - [License](#license) ## Installation -Follow the steps below to make use of Ghitgud. -Clone this repository: ```bash npm install -g @airscript/ghitgud ``` -## Usage -After installing you'll be able to access the CLI and its relative help command: +## Configuration + +Set a GitHub personal access token and repository (in `owner/repo` format): + +```bash +ghitgud config set token +ghitgud config set repo owner/repository +``` + +Retrieve a configured value: + ```bash -ghitgud help +ghitgud config get token +ghitgud config get repo +``` + +> Create a token at: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens + +## Commands + ``` +ghitgud ping Check if the CLI is working +ghitgud labels list List all labels for a repository +ghitgud labels pull Pull labels from a repository to local config +ghitgud labels pull -t Pull labels from a built-in template +ghitgud labels push Push local labels to a repository +ghitgud labels push -t Push a built-in template to a repository +ghitgud labels prune Delete all local labels from a repository +ghitgud config set Set a configuration value (token or repo) +ghitgud config get Get a configuration value +``` + +## Templates + +Built-in label presets are available with the `--template` / `-t` flag: + +| Template | Description | +| -------------- | ---------------------------- | +| `base` | Minimal set: bug and feature | +| `conventional` | Conventional Commits labels | +| `github` | GitHub default labels | -Remember that to use the CLI you have to set a token and a repo with the format `username/repository` (e.g. airscripts/ghitgud): ```bash -ghitgud config set token `your-token-here` -ghitgud config set repo `username/repository` +ghitgud labels pull -t conventional +ghitgud labels push -t conventional +``` + +## Output Format + +All commands output JSON to stdout on success and JSON to stderr on failure. + +Success: + +```json +{ + "success": true, + "metadata": [...] +} +``` + +Error: + +```json +{ + "success": false, + "error": "You must set the GHITGUD_GITHUB_REPO environment variable." +} ``` -> You can create your token with: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens -## Wiki -For more in-depth help with the usage of this CLI, just check the wiki: https://github.com/airscripts/ghitgud/wiki +## Development + +```bash +pnpm install # install dependencies +pnpm build # build with Vite (single CJS bundle) +pnpm start # run the CLI locally +pnpm test # run tests (watch mode) +pnpm test -- --run # single test run (no watch) +pnpm test:coverage # run tests with coverage +pnpm typecheck # type check without emitting +pnpm lint # type check (alias for typecheck) +pnpm clean # remove build artifacts +``` ## Contributing + Contributions and suggestions about how to improve this project are welcome! Please follow [our contribution guidelines](https://github.com/airscripts/ghitgud/blob/main/CONTRIBUTING.md). ## Support -If you want to support my work you can do it by following me, leaving a star, sharing my projects or also donating at the links below. -Choose what you find more suitable for you: + +If you want to support my work you can do it by following me, leaving a star, sharing my projects or also donating at the links below. +Choose what you find more suitable for you: GitHub Sponsors @@ -57,5 +133,6 @@ Choose what you find more suitable for you: Kofi -## License +## License + This repository is licensed under [GPL-3.0 License](https://github.com/airscripts/ghitgud/blob/main/LICENSE). diff --git a/SECURITY.md b/SECURITY.md index 482f68a..ae77513 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,9 +1,12 @@ # Security Policy ## Supported Versions + | Version | Supported | | ------- | ------------------ | -| 1.0.x | :white_check_mark: | +| 2.0.x | :white_check_mark: | +| 1.0.x | :x: | ## Reporting Vulnerability + To report a vulnerability, open an [issue](https://github.com/airscripts/ghitgud/issues/new/choose). diff --git a/VERSION b/VERSION index e4c0d46..359a5b9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.3 \ No newline at end of file +2.0.0 \ No newline at end of file diff --git a/app/api.ts b/app/api.ts deleted file mode 100644 index 9fd131b..0000000 --- a/app/api.ts +++ /dev/null @@ -1,136 +0,0 @@ -import config from "./config"; -import { Label } from "./types"; -import functions from "./functions"; -import "dotenv/config"; - -const VERSION = "2022-11-28"; -const BASE_URL = "https://api.github.com"; -const ACCEPT = "application/vnd.github+json"; -const REPO = `${config.repo}`; -const AUTHORIZATION = `Bearer ${config.token}`; - -const ERROR_UNAUTHORIZED = "Unauthorized."; -const ERROR_UNPROCESSABLE = "Content is unprocessable."; -const ERROR_NO_REPO = "You must set the GHITGUD_GITHUB_REPO environment variable."; -const ERROR_NO_TOKEN = "You must set the GHITGUD_GITHUB_TOKEN environment variable."; - -const labels = { - fetch: async () => { - if (!functions.environment.hasRepo()) throw new Error(ERROR_NO_REPO); - if (!functions.environment.hasToken()) throw new Error(ERROR_NO_TOKEN); - - const response = await fetch(`${BASE_URL}/repos/${REPO}/labels`, { - headers: { - Accept: ACCEPT, - Authorization: AUTHORIZATION, - "X-GitHub-Api-Version": VERSION, - }, - }); - - if (functions.http.isNotAuthorized(response.status)) - throw new Error(ERROR_UNAUTHORIZED); - - return response; - }, - - get: async (name: string) => { - if (!functions.environment.hasRepo()) throw new Error(ERROR_NO_REPO); - if (!functions.environment.hasToken()) throw new Error(ERROR_NO_TOKEN); - - const response = await fetch(`${BASE_URL}/repos/${REPO}/labels/${name}`, { - method: "GET", - headers: { - Accept: ACCEPT, - Authorization: AUTHORIZATION, - "X-GitHub-Api-Version": VERSION, - }, - }); - - if (functions.http.isNotAuthorized(response.status)) - throw new Error(ERROR_UNAUTHORIZED); - - return response; - }, - - create: async (label: Label) => { - if (!functions.environment.hasRepo()) throw new Error(ERROR_NO_REPO); - if (!functions.environment.hasToken()) throw new Error(ERROR_NO_TOKEN); - - const response = await fetch(`${BASE_URL}/repos/${REPO}/labels`, { - method: "POST", - - body: JSON.stringify({ - name: label.name, - color: label.color, - description: label.description, - }), - - headers: { - Accept: ACCEPT, - Authorization: AUTHORIZATION, - "X-GitHub-Api-Version": VERSION, - }, - }); - - if (functions.http.isUnprocessable(response.status)) - throw new Error(ERROR_UNPROCESSABLE); - - if (functions.http.isNotAuthorized(response.status)) - throw new Error(ERROR_UNAUTHORIZED); - - return response; - }, - - patch: async (label: Label) => { - if (!functions.environment.hasRepo()) throw new Error(ERROR_NO_REPO); - if (!functions.environment.hasToken()) throw new Error(ERROR_NO_TOKEN); - - const response = await fetch( - `${BASE_URL}/repos/${REPO}/labels/${label.name}`, - { - method: "PATCH", - - body: JSON.stringify({ - color: label.color, - description: label.description, - new_name: label.newName || label.name, - }), - - headers: { - Accept: ACCEPT, - Authorization: AUTHORIZATION, - "X-GitHub-Api-Version": VERSION, - }, - } - ); - - if (functions.http.isNotAuthorized(response.status)) - throw new Error(ERROR_UNAUTHORIZED); - - return response; - }, - - delete: async (name: string) => { - if (!functions.environment.hasRepo()) throw new Error(ERROR_NO_REPO); - if (!functions.environment.hasToken()) throw new Error(ERROR_NO_TOKEN); - - const response = await fetch(`${BASE_URL}/repos/${REPO}/labels/${name}`, { - method: "DELETE", - - headers: { - Accept: ACCEPT, - Authorization: AUTHORIZATION, - "X-GitHub-Api-Version": VERSION, - }, - }); - - if (functions.http.isNotAuthorized(response.status)) - throw new Error(ERROR_UNAUTHORIZED); - - return response; - }, -}; - -export default { - labels, -}; diff --git a/app/commands.ts b/app/commands.ts deleted file mode 100644 index fae556c..0000000 --- a/app/commands.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { program, Command } from "commander"; -import library from "./library"; - -const COMMANDS = { - ping: { - name: "ping", - action: () => void library.ping(), - description: "Check if the CLI is working.", - }, - - labels: { - name: "labels", - description: "Manage labels for a repository.", - - commands: { - list: { - name: "list", - description: "List all labels for a repository.", - action: () => void library.labels.list(), - }, - - pull: { - name: "pull", - description: "Pull all related labels for a repository.", - action: () => void library.labels.pull(), - }, - - push: { - name: "push", - description: "Push all related labels for a repository.", - action: () => void library.labels.push(), - }, - - prune: { - name: "prune", - description: "Prune all related labels for a repository.", - action: () => void library.labels.prune(), - }, - }, - }, - - config: { - name: "config", - description: "Set CLI configurations.", - - commands: { - set: { - name: "set", - description: "Set configuration.", - - action: (key: string, value: string) => - void library.config.set(key, value), - }, - }, - }, -}; - -const ping = () => { - program - .command(COMMANDS.ping.name) - .description(COMMANDS.ping.description) - .action(COMMANDS.ping.action); -}; - -const labels = () => { - const labels = program - .command(COMMANDS.labels.name) - .description(COMMANDS.labels.description); - - labels.addCommand( - new Command(COMMANDS.labels.commands.list.name) - .description(COMMANDS.labels.commands.list.description) - .action(COMMANDS.labels.commands.list.action) - ); - - labels.addCommand( - new Command(COMMANDS.labels.commands.pull.name) - .description(COMMANDS.labels.commands.pull.description) - .action(COMMANDS.labels.commands.pull.action) - ); - - labels.addCommand( - new Command(COMMANDS.labels.commands.push.name) - .description(COMMANDS.labels.commands.push.description) - .action(COMMANDS.labels.commands.push.action) - ); - - labels.addCommand( - new Command(COMMANDS.labels.commands.prune.name) - .description(COMMANDS.labels.commands.prune.description) - .action(COMMANDS.labels.commands.prune.action) - ); -}; - -const config = () => { - const config = program - .command(COMMANDS.config.name) - .description(COMMANDS.config.description); - - config.addCommand( - new Command(COMMANDS.config.commands.set.name) - .description(COMMANDS.config.commands.set.description) - .arguments(" ") - - .action((key: string, value: string) => - COMMANDS.config.commands.set.action(key, value) - ) - ); -}; - -const init = () => { - ping(); - labels(); - config(); -}; - -export default init; diff --git a/app/config.ts b/app/config.ts deleted file mode 100644 index 78b223f..0000000 --- a/app/config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import functions from "./functions"; -import "dotenv/config"; - -const config = { - repo: process.env.GHITGUD_GITHUB_REPO || functions?.config.read("repo"), - token: process.env.GHITGUD_GITHUB_TOKEN || functions?.config.read("token"), -}; - -export default config; diff --git a/app/functions.ts b/app/functions.ts deleted file mode 100644 index 677281e..0000000 --- a/app/functions.ts +++ /dev/null @@ -1,43 +0,0 @@ -import fs from "fs"; -import os from "os"; -import path from "path"; - -import conf from "./config"; -import "dotenv/config"; - -const STATUS_OK = 200; -const STATUS_UNAUTHORIZED = 401; -const STATUS_NOT_FOUND = 404; -const STATUS_UNPROCESSABLE = 422; - -const ENCODING = "utf8"; -const CREDENTIALS_FILE = "credentials.json"; -const GHITGUD_FOLDER = path.join(os.homedir(), ".config", "ghitgud"); - -const http = { - isOk: (status: number) => status === STATUS_OK, - isNotFound: (status: number) => status === STATUS_NOT_FOUND, - isNotAuthorized: (status: number) => status === STATUS_UNAUTHORIZED, - isUnprocessable: (status: number) => status === STATUS_UNPROCESSABLE, -}; - -const environment = { - hasRepo: () => (conf.repo ? true : false), - hasToken: () => (conf.token ? true : false), -}; - -const config = { - read: (key: string) => { - if (!fs.existsSync(`${GHITGUD_FOLDER}/${CREDENTIALS_FILE}`)) return null; - - const data = fs.readFileSync( - `${GHITGUD_FOLDER}/${CREDENTIALS_FILE}`, - ENCODING - ); - - const content = JSON.parse(data); - return content[key]; - }, -}; - -export default { http, environment, config }; diff --git a/app/ghitgud.ts b/app/ghitgud.ts deleted file mode 100644 index 1e170a2..0000000 --- a/app/ghitgud.ts +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env node -import process from "process"; -import { program } from "commander"; - -import ascii from "./ascii"; -import commands from "./commands"; - -const NAME = "ghitgud"; -const VERSION = "1.0.3"; -const DESCRIPTION = "A simple CLI to give superpowers to GitHub."; - -program - .name(NAME) - .description(DESCRIPTION) - .version(VERSION); - -commands(); -program.addHelpText("before", ascii); -program.parse(process.argv); diff --git a/app/library.ts b/app/library.ts deleted file mode 100644 index 75ef775..0000000 --- a/app/library.ts +++ /dev/null @@ -1,157 +0,0 @@ -import fs from "fs"; -import os from "os"; -import path from "path"; - -import api from "./api"; -import { Label } from "./types"; -import functions from "./functions"; - -const ENCODING = "utf8"; -const PING_RESPONSE = "pong"; -const METADATA_FILE = "labels.json"; -const ERROR_NO_METADATA = "No metadata file found."; - -const CREDENTIALS_FILE = "credentials.json"; -const ERROR_UNSUPPORTED_KEY = "Trying to set unsupported key."; -const GHITGUD_FOLDER = path.join(os.homedir(), ".config", "ghitgud"); - -const ping = () => { - console.info(PING_RESPONSE); - return { success: true }; -}; - -const labels = { - list: async () => { - const response = await api.labels.fetch(); - const data = await response.json(); - - const labels = data.map((label: Label) => ({ - name: label.name, - color: label.color, - description: label.description, - })); - - const result = { success: true, metadata: labels }; - console.info(result); - return result; - }, - - pull: async () => { - const response = await api.labels.fetch(); - const data = await response.json(); - - const labels = data.map((label: Label) => ({ - name: label.name, - color: label.color, - description: label.description, - })); - - try { - fs.mkdirSync(GHITGUD_FOLDER, { recursive: true }); - } catch (error) { - throw new Error(error instanceof Error ? error.message : String(error)); - } - - try { - fs.writeFileSync( - `${GHITGUD_FOLDER}/${METADATA_FILE}`, - JSON.stringify(labels, null, 2) - ); - } catch (error) { - throw new Error(error instanceof Error ? error.message : String(error)); - } - - const result = { success: true }; - console.info(result); - return result; - }, - - push: async () => { - if (!fs.existsSync(`${GHITGUD_FOLDER}/${METADATA_FILE}`)) - throw new Error(ERROR_NO_METADATA); - - const data = fs.readFileSync( - `${GHITGUD_FOLDER}/${METADATA_FILE}`, - ENCODING - ); - - const labels = JSON.parse(data); - - await Promise.all( - labels.map(async (label: Label) => { - const response = await api.labels.get(label.name); - if (functions.http.isOk(response.status)) await api.labels.patch(label); - - if (functions.http.isNotFound(response.status)) - await api.labels.create(label); - }) - ); - - const result = { success: true }; - console.info(result); - return result; - }, - - prune: async () => { - if (!fs.existsSync(`${GHITGUD_FOLDER}/${METADATA_FILE}`)) - throw new Error(ERROR_NO_METADATA); - - const data = fs.readFileSync( - `${GHITGUD_FOLDER}/${METADATA_FILE}`, - ENCODING - ); - - const labels = JSON.parse(data); - labels.map(async (label: Label) => await api.labels.delete(label.name)); - - const result = { success: true }; - console.info(result); - return result; - }, -}; - -const config = { - set: (key: string, value: string) => { - const knowns = ["token", "repo"]; - - if (!knowns.includes(key)) throw new Error(ERROR_UNSUPPORTED_KEY); - - if (!fs.existsSync(`${GHITGUD_FOLDER}/${CREDENTIALS_FILE}`)) { - const credentials = { [key]: value }; - - try { - fs.mkdirSync(GHITGUD_FOLDER, { recursive: true }); - } catch (error) { - throw new Error(error instanceof Error ? error.message : String(error)); - } - - fs.writeFileSync( - `${GHITGUD_FOLDER}/${CREDENTIALS_FILE}`, - JSON.stringify(credentials, null, 2) - ); - - return { success: true }; - } - - const data = fs.readFileSync( - `${GHITGUD_FOLDER}/${CREDENTIALS_FILE}`, - ENCODING - ); - - const credentials = JSON.parse(data); - credentials[key] = value; - - fs.writeFileSync( - `${GHITGUD_FOLDER}/${CREDENTIALS_FILE}`, - JSON.stringify(credentials, null, 2) - ); - - return { success: true }; - }, -}; - -export default { - ping, - labels, - config, -}; diff --git a/app/types.ts b/app/types.ts deleted file mode 100644 index c1021eb..0000000 --- a/app/types.ts +++ /dev/null @@ -1,8 +0,0 @@ -interface Label { - name: string; - color: string; - newName?: string; - description: string; -} - -export type { Label }; diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..d4a96f3 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,20 @@ +import js from "@eslint/js"; +import ts from "typescript-eslint"; +import prettier from "eslint-config-prettier"; + +export default ts.config( + js.configs.recommended, + ...ts.configs.recommended, + prettier, + { + languageOptions: { + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, + }, + { + ignores: ["dist/", "coverage/", "node_modules/"], + }, +); diff --git a/package.json b/package.json index 77c6ae0..54dcf4b 100644 --- a/package.json +++ b/package.json @@ -1,38 +1,61 @@ { - "name": "@airscript/ghitgud", - "version": "1.0.3", - "description": "A simple CLI to give superpowers to GitHub.", - "main": "dist/app/ghitgud.js", - "bin": { - "ghitgud": "dist/app/ghitgud.js" - }, - "dependencies": { - "commander": "^14.0.0", - "dotenv": "^16.5.0", - "figlet": "^1.8.1" - }, - "scripts": { - "test": "vitest", - "build": "rm -rf dist && tsc", - "start": "node dist/app/ghitgud.js" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/airscripts/ghitgud.git" - }, - "author": "airscripts", - "license": "MIT", - "bugs": { - "url": "https://github.com/airscripts/ghitgud/issues" - }, - "homepage": "https://github.com/airscripts/ghitgud#readme", - "devDependencies": { - "@types/figlet": "^1.7.0", - "@types/node": "^24.0.0", - "typescript": "^5.8.3", - "vitest": "^3.2.3" - }, - "directories": { - "test": "tests" - } + "name": "@airscript/ghitgud", + "version": "2.0.0", + "description": "A simple CLI to give superpowers to GitHub.", + "main": "dist/index.js", + "files": [ + "dist", + "templates", + "VERSION" + ], + "bin": { + "ghitgud": "dist/index.js" + }, + "engines": { + "node": ">=24", + "pnpm": ">=10" + }, + "dependencies": { + "commander": "^14.0.0", + "consola": "3.4.2", + "dotenv": "^16.5.0", + "figlet": "^1.8.1" + }, + "scripts": { + "test": "vitest", + "test:coverage": "vitest run --coverage", + "build": "rm -rf dist && vite build && cp -r templates dist/", + "start": "node dist/index.js", + "typecheck": "tsc --noEmit", + "lint": "eslint src/ tests/", + "format": "prettier --write .", + "format:check": "prettier --check .", + "clean": "rm -rf dist coverage" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/airscripts/ghitgud.git" + }, + "author": "airscripts", + "license": "MIT", + "bugs": { + "url": "https://github.com/airscripts/ghitgud/issues" + }, + "homepage": "https://github.com/airscripts/ghitgud#readme", + "devDependencies": { + "@eslint/js": "10.0.1", + "@types/figlet": "^1.7.0", + "@types/node": "^24.0.0", + "@vitest/coverage-v8": "^3.2.4", + "eslint": "10.3.0", + "eslint-config-prettier": "10.1.8", + "prettier": "3.8.3", + "typescript": "^5.8.3", + "typescript-eslint": "8.59.2", + "vite": "^8.0.11", + "vitest": "^3.2.4" + }, + "directories": { + "test": "tests" + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4ed2e33..2d1d1d0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,16 +1,18 @@ -lockfileVersion: '9.0' +lockfileVersion: "9.0" settings: autoInstallPeers: true excludeLinksFromLockfile: false importers: - .: dependencies: commander: specifier: ^14.0.0 version: 14.0.0 + consola: + specifier: 3.4.2 + version: 3.4.2 dotenv: specifier: ^16.5.0 version: 16.5.0 @@ -18,297 +20,952 @@ importers: specifier: ^1.8.1 version: 1.8.1 devDependencies: - '@types/figlet': + "@eslint/js": + specifier: 10.0.1 + version: 10.0.1(eslint@10.3.0) + "@types/figlet": specifier: ^1.7.0 version: 1.7.0 - '@types/node': + "@types/node": specifier: ^24.0.0 version: 24.0.0 + "@vitest/coverage-v8": + specifier: ^3.2.4 + version: 3.2.4(vitest@3.2.4(@types/node@24.0.0)(lightningcss@1.32.0)) + eslint: + specifier: 10.3.0 + version: 10.3.0 + eslint-config-prettier: + specifier: 10.1.8 + version: 10.1.8(eslint@10.3.0) + prettier: + specifier: 3.8.3 + version: 3.8.3 typescript: specifier: ^5.8.3 version: 5.8.3 + typescript-eslint: + specifier: 8.59.2 + version: 8.59.2(eslint@10.3.0)(typescript@5.8.3) + vite: + specifier: ^8.0.11 + version: 8.0.11(@types/node@24.0.0) vitest: - specifier: ^3.2.3 - version: 3.2.3(@types/node@24.0.0) + specifier: ^3.2.4 + version: 3.2.4(@types/node@24.0.0)(lightningcss@1.32.0) packages: + "@ampproject/remapping@2.3.0": + resolution: + { + integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==, + } + engines: { node: ">=6.0.0" } + + "@babel/helper-string-parser@7.27.1": + resolution: + { + integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==, + } + engines: { node: ">=6.9.0" } + + "@babel/helper-validator-identifier@7.28.5": + resolution: + { + integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==, + } + engines: { node: ">=6.9.0" } + + "@babel/parser@7.29.3": + resolution: + { + integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==, + } + engines: { node: ">=6.0.0" } + hasBin: true - '@esbuild/aix-ppc64@0.25.5': - resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} - engines: {node: '>=18'} + "@babel/types@7.29.0": + resolution: + { + integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==, + } + engines: { node: ">=6.9.0" } + + "@bcoe/v8-coverage@1.0.2": + resolution: + { + integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==, + } + engines: { node: ">=18" } + + "@emnapi/core@1.10.0": + resolution: + { + integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==, + } + + "@emnapi/runtime@1.10.0": + resolution: + { + integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==, + } + + "@emnapi/wasi-threads@1.2.1": + resolution: + { + integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==, + } + + "@esbuild/aix-ppc64@0.25.5": + resolution: + { + integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==, + } + engines: { node: ">=18" } cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.5': - resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} - engines: {node: '>=18'} + "@esbuild/android-arm64@0.25.5": + resolution: + { + integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==, + } + engines: { node: ">=18" } cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.5': - resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==} - engines: {node: '>=18'} + "@esbuild/android-arm@0.25.5": + resolution: + { + integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==, + } + engines: { node: ">=18" } cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.5': - resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==} - engines: {node: '>=18'} + "@esbuild/android-x64@0.25.5": + resolution: + { + integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==, + } + engines: { node: ">=18" } cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.5': - resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} - engines: {node: '>=18'} + "@esbuild/darwin-arm64@0.25.5": + resolution: + { + integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==, + } + engines: { node: ">=18" } cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.5': - resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==} - engines: {node: '>=18'} + "@esbuild/darwin-x64@0.25.5": + resolution: + { + integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==, + } + engines: { node: ">=18" } cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.5': - resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} - engines: {node: '>=18'} + "@esbuild/freebsd-arm64@0.25.5": + resolution: + { + integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==, + } + engines: { node: ">=18" } cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.5': - resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==} - engines: {node: '>=18'} + "@esbuild/freebsd-x64@0.25.5": + resolution: + { + integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==, + } + engines: { node: ">=18" } cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.5': - resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} - engines: {node: '>=18'} + "@esbuild/linux-arm64@0.25.5": + resolution: + { + integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==, + } + engines: { node: ">=18" } cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.5': - resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==} - engines: {node: '>=18'} + "@esbuild/linux-arm@0.25.5": + resolution: + { + integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==, + } + engines: { node: ">=18" } cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.5': - resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==} - engines: {node: '>=18'} + "@esbuild/linux-ia32@0.25.5": + resolution: + { + integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==, + } + engines: { node: ">=18" } cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.5': - resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==} - engines: {node: '>=18'} + "@esbuild/linux-loong64@0.25.5": + resolution: + { + integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==, + } + engines: { node: ">=18" } cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.5': - resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==} - engines: {node: '>=18'} + "@esbuild/linux-mips64el@0.25.5": + resolution: + { + integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==, + } + engines: { node: ">=18" } cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.5': - resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==} - engines: {node: '>=18'} + "@esbuild/linux-ppc64@0.25.5": + resolution: + { + integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==, + } + engines: { node: ">=18" } cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.5': - resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==} - engines: {node: '>=18'} + "@esbuild/linux-riscv64@0.25.5": + resolution: + { + integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==, + } + engines: { node: ">=18" } cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.5': - resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==} - engines: {node: '>=18'} + "@esbuild/linux-s390x@0.25.5": + resolution: + { + integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==, + } + engines: { node: ">=18" } cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.5': - resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==} - engines: {node: '>=18'} + "@esbuild/linux-x64@0.25.5": + resolution: + { + integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==, + } + engines: { node: ">=18" } cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.5': - resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==} - engines: {node: '>=18'} + "@esbuild/netbsd-arm64@0.25.5": + resolution: + { + integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==, + } + engines: { node: ">=18" } cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.5': - resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==} - engines: {node: '>=18'} + "@esbuild/netbsd-x64@0.25.5": + resolution: + { + integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==, + } + engines: { node: ">=18" } cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.5': - resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==} - engines: {node: '>=18'} + "@esbuild/openbsd-arm64@0.25.5": + resolution: + { + integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==, + } + engines: { node: ">=18" } cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.5': - resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==} - engines: {node: '>=18'} + "@esbuild/openbsd-x64@0.25.5": + resolution: + { + integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==, + } + engines: { node: ">=18" } cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.25.5': - resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} - engines: {node: '>=18'} + "@esbuild/sunos-x64@0.25.5": + resolution: + { + integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==, + } + engines: { node: ">=18" } cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.5': - resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} - engines: {node: '>=18'} + "@esbuild/win32-arm64@0.25.5": + resolution: + { + integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==, + } + engines: { node: ">=18" } cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.5': - resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==} - engines: {node: '>=18'} + "@esbuild/win32-ia32@0.25.5": + resolution: + { + integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==, + } + engines: { node: ">=18" } cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.5': - resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==} - engines: {node: '>=18'} + "@esbuild/win32-x64@0.25.5": + resolution: + { + integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [win32] + + "@eslint-community/eslint-utils@4.9.1": + resolution: + { + integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + "@eslint-community/regexpp@4.12.2": + resolution: + { + integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==, + } + engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } + + "@eslint/config-array@0.23.5": + resolution: + { + integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + + "@eslint/config-helpers@0.5.5": + resolution: + { + integrity: sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + + "@eslint/core@1.2.1": + resolution: + { + integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + + "@eslint/js@10.0.1": + resolution: + { + integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + + "@eslint/object-schema@3.0.5": + resolution: + { + integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + + "@eslint/plugin-kit@0.7.1": + resolution: + { + integrity: sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + + "@humanfs/core@0.19.2": + resolution: + { + integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==, + } + engines: { node: ">=18.18.0" } + + "@humanfs/node@0.16.8": + resolution: + { + integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==, + } + engines: { node: ">=18.18.0" } + + "@humanfs/types@0.15.0": + resolution: + { + integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==, + } + engines: { node: ">=18.18.0" } + + "@humanwhocodes/module-importer@1.0.1": + resolution: + { + integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==, + } + engines: { node: ">=12.22" } + + "@humanwhocodes/retry@0.4.3": + resolution: + { + integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==, + } + engines: { node: ">=18.18" } + + "@isaacs/cliui@8.0.2": + resolution: + { + integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==, + } + engines: { node: ">=12" } + + "@istanbuljs/schema@0.1.6": + resolution: + { + integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==, + } + engines: { node: ">=8" } + + "@jridgewell/gen-mapping@0.3.13": + resolution: + { + integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==, + } + + "@jridgewell/resolve-uri@3.1.2": + resolution: + { + integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==, + } + engines: { node: ">=6.0.0" } + + "@jridgewell/sourcemap-codec@1.5.0": + resolution: + { + integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==, + } + + "@jridgewell/trace-mapping@0.3.31": + resolution: + { + integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==, + } + + "@napi-rs/wasm-runtime@1.1.4": + resolution: + { + integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==, + } + peerDependencies: + "@emnapi/core": ^1.7.1 + "@emnapi/runtime": ^1.7.1 + + "@oxc-project/types@0.128.0": + resolution: + { + integrity: sha512-huv1Y/LzBJkBVHt3OlC7u0zHBW9qXf1FdD7sGmc1rXc2P1mTwHssYv7jyGx5KAACSCH+9B3Bhn6Z9luHRvf7pQ==, + } + + "@pkgjs/parseargs@0.11.0": + resolution: + { + integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==, + } + engines: { node: ">=14" } + + "@rolldown/binding-android-arm64@1.0.0-rc.18": + resolution: + { + integrity: sha512-lIDyUAfD7U3+BWKzdxMbJcsYHuqXqmGz40aeRqvuAm3y5TkJSYTBW2RDrn65DJFPQqVjUAUqq5uz8urzQ8aBdQ==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm64] + os: [android] + + "@rolldown/binding-darwin-arm64@1.0.0-rc.18": + resolution: + { + integrity: sha512-apJq2ktnGp27nSInMR5Vcj8kY6xJzDAvfdIFlpDcAK/w4cDO58qVoi1YQsES/SKiFNge/6e4CUzgjfHduYqWpQ==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm64] + os: [darwin] + + "@rolldown/binding-darwin-x64@1.0.0-rc.18": + resolution: + { + integrity: sha512-5Ofot8xbs+pxRHJqm9/9N/4sTQOvdrwEsmPE9pdLEEoAbdZtG6F2LMDfO1sp6ZAtXJuJV/21ew2srq3W8NXB5g==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [x64] + os: [darwin] + + "@rolldown/binding-freebsd-x64@1.0.0-rc.18": + resolution: + { + integrity: sha512-7h8eeOTT1eyqJyx64BFCnWZpNm486hGWt2sqeLLgDxA0xI1oGZ9H7gK1S85uNGmBhkdPwa/6reTxfFFKvIsebw==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [x64] + os: [freebsd] + + "@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.18": + resolution: + { + integrity: sha512-eRcm/HVt9U/JFu5RKAEKwGQYtDCKWLiaH6wOnsSEp6NMBb/3Os8LgHZlNyzMpFVNmiiMFlfb2zEnebfzJrHFmg==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm] + os: [linux] + + "@rolldown/binding-linux-arm64-gnu@1.0.0-rc.18": + resolution: + { + integrity: sha512-SOrT/cT4ukTmgnrEz/Hg3m7LBnuCLW9psDeMKrimRWY4I8DmnO7Lco8W2vtqPmMkbVu8iJ+g4GFLVLLOVjJ9DQ==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm64] + os: [linux] + + "@rolldown/binding-linux-arm64-musl@1.0.0-rc.18": + resolution: + { + integrity: sha512-QWjdxN1HJCpBTAcZ5N5F7wju3gVPzRzSpmGzx7na0c/1qpN9CFil+xt+l9lV/1M6/gqHSNXCiqPfwhVJPeLnug==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm64] + os: [linux] + + "@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.18": + resolution: + { + integrity: sha512-ugCOyj7a4d9h3q9B+wXmf6g3a68UsjGh6dob5DHevHGMwDUbhsYNbSPxJsENcIttJZ9jv7qGM2UesLw5jqIhdg==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [ppc64] + os: [linux] + + "@rolldown/binding-linux-s390x-gnu@1.0.0-rc.18": + resolution: + { + integrity: sha512-kKWRhbsotpXkGbcd5dllUWg5gEXcDAa8u5YnP9AV5DYNbvJHGzzuwv7dpmhc8NqKMJldl0a+x76IHbspEpEmdA==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [s390x] + os: [linux] + + "@rolldown/binding-linux-x64-gnu@1.0.0-rc.18": + resolution: + { + integrity: sha512-uCo8ElcCIAMyYAZyuIZ81oFkhTSIllNvUCHCAlbhlN4ji3uC28h7IIdlXyIvGO7HsuqnV9p3rD/bpH7XhIyhRw==, + } + engines: { node: ^20.19.0 || >=22.12.0 } cpu: [x64] + os: [linux] + + "@rolldown/binding-linux-x64-musl@1.0.0-rc.18": + resolution: + { + integrity: sha512-XNOQZtuE6yUIvx4rwGemwh8kpL1xvU41FXy/s9K7T/3JVcqGzo3NfKM2HrbrGgfPYGFW42f07Wk++aOC6B9NWA==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [x64] + os: [linux] + + "@rolldown/binding-openharmony-arm64@1.0.0-rc.18": + resolution: + { + integrity: sha512-tSn/kzrfa7tNOXr7sEacDBN4YsIqTyLqh45IO0nHDwtpKIDNDJr+VFojt+4klSpChxB29JLyduSsE0MKEwa65A==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm64] + os: [openharmony] + + "@rolldown/binding-wasm32-wasi@1.0.0-rc.18": + resolution: + { + integrity: sha512-+J9YGmc+czgqlhYmwun3S3O0FIZhsH8ep2456xwjAdIOmuJxM7xz4P4PtrxU+Bz17a/5bqPA8o3HAAoX0teUdg==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [wasm32] + + "@rolldown/binding-win32-arm64-msvc@1.0.0-rc.18": + resolution: + { + integrity: sha512-zsu47DgU0FQzSwi6sU9dZoEdUv7pc1AptSEz/Z8HBg54sV0Pbs3N0+CrIbTsgiu6EyoaNN9CHboqbLaz9lhOyQ==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm64] os: [win32] - '@jridgewell/sourcemap-codec@1.5.0': - resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + "@rolldown/binding-win32-x64-msvc@1.0.0-rc.18": + resolution: + { + integrity: sha512-7H+3yqGgmnlDTRRhw/xpYY9J1kf4GC681nVc4GqKhExZTDrVVrV2tsOR9kso0fvgBdcTCcQShx4SLLoHgaLwhg==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [x64] + os: [win32] - '@rollup/rollup-android-arm-eabi@4.43.0': - resolution: {integrity: sha512-Krjy9awJl6rKbruhQDgivNbD1WuLb8xAclM4IR4cN5pHGAs2oIMMQJEiC3IC/9TZJ+QZkmZhlMO/6MBGxPidpw==} + "@rolldown/pluginutils@1.0.0-rc.18": + resolution: + { + integrity: sha512-CUY5Mnhe64xQBGZEEXQ5WyZwsc1JU3vAZLIxtrsBt3LO6UOb+C8GunVKqe9sT8NeWb4lqSaoJtp2xo6GxT1MNw==, + } + + "@rollup/rollup-android-arm-eabi@4.43.0": + resolution: + { + integrity: sha512-Krjy9awJl6rKbruhQDgivNbD1WuLb8xAclM4IR4cN5pHGAs2oIMMQJEiC3IC/9TZJ+QZkmZhlMO/6MBGxPidpw==, + } cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.43.0': - resolution: {integrity: sha512-ss4YJwRt5I63454Rpj+mXCXicakdFmKnUNxr1dLK+5rv5FJgAxnN7s31a5VchRYxCFWdmnDWKd0wbAdTr0J5EA==} + "@rollup/rollup-android-arm64@4.43.0": + resolution: + { + integrity: sha512-ss4YJwRt5I63454Rpj+mXCXicakdFmKnUNxr1dLK+5rv5FJgAxnN7s31a5VchRYxCFWdmnDWKd0wbAdTr0J5EA==, + } cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.43.0': - resolution: {integrity: sha512-eKoL8ykZ7zz8MjgBenEF2OoTNFAPFz1/lyJ5UmmFSz5jW+7XbH1+MAgCVHy72aG59rbuQLcJeiMrP8qP5d/N0A==} + "@rollup/rollup-darwin-arm64@4.43.0": + resolution: + { + integrity: sha512-eKoL8ykZ7zz8MjgBenEF2OoTNFAPFz1/lyJ5UmmFSz5jW+7XbH1+MAgCVHy72aG59rbuQLcJeiMrP8qP5d/N0A==, + } cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.43.0': - resolution: {integrity: sha512-SYwXJgaBYW33Wi/q4ubN+ldWC4DzQY62S4Ll2dgfr/dbPoF50dlQwEaEHSKrQdSjC6oIe1WgzosoaNoHCdNuMg==} + "@rollup/rollup-darwin-x64@4.43.0": + resolution: + { + integrity: sha512-SYwXJgaBYW33Wi/q4ubN+ldWC4DzQY62S4Ll2dgfr/dbPoF50dlQwEaEHSKrQdSjC6oIe1WgzosoaNoHCdNuMg==, + } cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.43.0': - resolution: {integrity: sha512-SV+U5sSo0yujrjzBF7/YidieK2iF6E7MdF6EbYxNz94lA+R0wKl3SiixGyG/9Klab6uNBIqsN7j4Y/Fya7wAjQ==} + "@rollup/rollup-freebsd-arm64@4.43.0": + resolution: + { + integrity: sha512-SV+U5sSo0yujrjzBF7/YidieK2iF6E7MdF6EbYxNz94lA+R0wKl3SiixGyG/9Klab6uNBIqsN7j4Y/Fya7wAjQ==, + } cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.43.0': - resolution: {integrity: sha512-J7uCsiV13L/VOeHJBo5SjasKiGxJ0g+nQTrBkAsmQBIdil3KhPnSE9GnRon4ejX1XDdsmK/l30IYLiAaQEO0Cg==} + "@rollup/rollup-freebsd-x64@4.43.0": + resolution: + { + integrity: sha512-J7uCsiV13L/VOeHJBo5SjasKiGxJ0g+nQTrBkAsmQBIdil3KhPnSE9GnRon4ejX1XDdsmK/l30IYLiAaQEO0Cg==, + } cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.43.0': - resolution: {integrity: sha512-gTJ/JnnjCMc15uwB10TTATBEhK9meBIY+gXP4s0sHD1zHOaIh4Dmy1X9wup18IiY9tTNk5gJc4yx9ctj/fjrIw==} + "@rollup/rollup-linux-arm-gnueabihf@4.43.0": + resolution: + { + integrity: sha512-gTJ/JnnjCMc15uwB10TTATBEhK9meBIY+gXP4s0sHD1zHOaIh4Dmy1X9wup18IiY9tTNk5gJc4yx9ctj/fjrIw==, + } cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.43.0': - resolution: {integrity: sha512-ZJ3gZynL1LDSIvRfz0qXtTNs56n5DI2Mq+WACWZ7yGHFUEirHBRt7fyIk0NsCKhmRhn7WAcjgSkSVVxKlPNFFw==} + "@rollup/rollup-linux-arm-musleabihf@4.43.0": + resolution: + { + integrity: sha512-ZJ3gZynL1LDSIvRfz0qXtTNs56n5DI2Mq+WACWZ7yGHFUEirHBRt7fyIk0NsCKhmRhn7WAcjgSkSVVxKlPNFFw==, + } cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.43.0': - resolution: {integrity: sha512-8FnkipasmOOSSlfucGYEu58U8cxEdhziKjPD2FIa0ONVMxvl/hmONtX/7y4vGjdUhjcTHlKlDhw3H9t98fPvyA==} + "@rollup/rollup-linux-arm64-gnu@4.43.0": + resolution: + { + integrity: sha512-8FnkipasmOOSSlfucGYEu58U8cxEdhziKjPD2FIa0ONVMxvl/hmONtX/7y4vGjdUhjcTHlKlDhw3H9t98fPvyA==, + } cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.43.0': - resolution: {integrity: sha512-KPPyAdlcIZ6S9C3S2cndXDkV0Bb1OSMsX0Eelr2Bay4EsF9yi9u9uzc9RniK3mcUGCLhWY9oLr6er80P5DE6XA==} + "@rollup/rollup-linux-arm64-musl@4.43.0": + resolution: + { + integrity: sha512-KPPyAdlcIZ6S9C3S2cndXDkV0Bb1OSMsX0Eelr2Bay4EsF9yi9u9uzc9RniK3mcUGCLhWY9oLr6er80P5DE6XA==, + } cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.43.0': - resolution: {integrity: sha512-HPGDIH0/ZzAZjvtlXj6g+KDQ9ZMHfSP553za7o2Odegb/BEfwJcR0Sw0RLNpQ9nC6Gy8s+3mSS9xjZ0n3rhcYg==} + "@rollup/rollup-linux-loongarch64-gnu@4.43.0": + resolution: + { + integrity: sha512-HPGDIH0/ZzAZjvtlXj6g+KDQ9ZMHfSP553za7o2Odegb/BEfwJcR0Sw0RLNpQ9nC6Gy8s+3mSS9xjZ0n3rhcYg==, + } cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.43.0': - resolution: {integrity: sha512-gEmwbOws4U4GLAJDhhtSPWPXUzDfMRedT3hFMyRAvM9Mrnj+dJIFIeL7otsv2WF3D7GrV0GIewW0y28dOYWkmw==} + "@rollup/rollup-linux-powerpc64le-gnu@4.43.0": + resolution: + { + integrity: sha512-gEmwbOws4U4GLAJDhhtSPWPXUzDfMRedT3hFMyRAvM9Mrnj+dJIFIeL7otsv2WF3D7GrV0GIewW0y28dOYWkmw==, + } cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.43.0': - resolution: {integrity: sha512-XXKvo2e+wFtXZF/9xoWohHg+MuRnvO29TI5Hqe9xwN5uN8NKUYy7tXUG3EZAlfchufNCTHNGjEx7uN78KsBo0g==} + "@rollup/rollup-linux-riscv64-gnu@4.43.0": + resolution: + { + integrity: sha512-XXKvo2e+wFtXZF/9xoWohHg+MuRnvO29TI5Hqe9xwN5uN8NKUYy7tXUG3EZAlfchufNCTHNGjEx7uN78KsBo0g==, + } cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.43.0': - resolution: {integrity: sha512-ruf3hPWhjw6uDFsOAzmbNIvlXFXlBQ4nk57Sec8E8rUxs/AI4HD6xmiiasOOx/3QxS2f5eQMKTAwk7KHwpzr/Q==} + "@rollup/rollup-linux-riscv64-musl@4.43.0": + resolution: + { + integrity: sha512-ruf3hPWhjw6uDFsOAzmbNIvlXFXlBQ4nk57Sec8E8rUxs/AI4HD6xmiiasOOx/3QxS2f5eQMKTAwk7KHwpzr/Q==, + } cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.43.0': - resolution: {integrity: sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw==} + "@rollup/rollup-linux-s390x-gnu@4.43.0": + resolution: + { + integrity: sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw==, + } cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.43.0': - resolution: {integrity: sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==} + "@rollup/rollup-linux-x64-gnu@4.43.0": + resolution: + { + integrity: sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==, + } cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.43.0': - resolution: {integrity: sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ==} + "@rollup/rollup-linux-x64-musl@4.43.0": + resolution: + { + integrity: sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ==, + } cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.43.0': - resolution: {integrity: sha512-wVzXp2qDSCOpcBCT5WRWLmpJRIzv23valvcTwMHEobkjippNf+C3ys/+wf07poPkeNix0paTNemB2XrHr2TnGw==} + "@rollup/rollup-win32-arm64-msvc@4.43.0": + resolution: + { + integrity: sha512-wVzXp2qDSCOpcBCT5WRWLmpJRIzv23valvcTwMHEobkjippNf+C3ys/+wf07poPkeNix0paTNemB2XrHr2TnGw==, + } cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.43.0': - resolution: {integrity: sha512-fYCTEyzf8d+7diCw8b+asvWDCLMjsCEA8alvtAutqJOJp/wL5hs1rWSqJ1vkjgW0L2NB4bsYJrpKkiIPRR9dvw==} + "@rollup/rollup-win32-ia32-msvc@4.43.0": + resolution: + { + integrity: sha512-fYCTEyzf8d+7diCw8b+asvWDCLMjsCEA8alvtAutqJOJp/wL5hs1rWSqJ1vkjgW0L2NB4bsYJrpKkiIPRR9dvw==, + } cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.43.0': - resolution: {integrity: sha512-SnGhLiE5rlK0ofq8kzuDkM0g7FN1s5VYY+YSMTibP7CqShxCQvqtNxTARS4xX4PFJfHjG0ZQYX9iGzI3FQh5Aw==} + "@rollup/rollup-win32-x64-msvc@4.43.0": + resolution: + { + integrity: sha512-SnGhLiE5rlK0ofq8kzuDkM0g7FN1s5VYY+YSMTibP7CqShxCQvqtNxTARS4xX4PFJfHjG0ZQYX9iGzI3FQh5Aw==, + } cpu: [x64] os: [win32] - '@types/chai@5.2.2': - resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} - - '@types/deep-eql@4.0.2': - resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - - '@types/estree@1.0.7': - resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} - - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - - '@types/figlet@1.7.0': - resolution: {integrity: sha512-KwrT7p/8Eo3Op/HBSIwGXOsTZKYiM9NpWRBJ5sVjWP/SmlS+oxxRvJht/FNAtliJvja44N3ul1yATgohnVBV0Q==} - - '@types/node@24.0.0': - resolution: {integrity: sha512-yZQa2zm87aRVcqDyH5+4Hv9KYgSdgwX1rFnGvpbzMaC7YAljmhBET93TPiTd3ObwTL+gSpIzPKg5BqVxdCvxKg==} - - '@vitest/expect@3.2.3': - resolution: {integrity: sha512-W2RH2TPWVHA1o7UmaFKISPvdicFJH+mjykctJFoAkUw+SPTJTGjUNdKscFBrqM7IPnCVu6zihtKYa7TkZS1dkQ==} + "@tybys/wasm-util@0.10.2": + resolution: + { + integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==, + } + + "@types/chai@5.2.2": + resolution: + { + integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==, + } + + "@types/deep-eql@4.0.2": + resolution: + { + integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==, + } + + "@types/esrecurse@4.3.1": + resolution: + { + integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==, + } + + "@types/estree@1.0.7": + resolution: + { + integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==, + } + + "@types/estree@1.0.8": + resolution: + { + integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==, + } + + "@types/figlet@1.7.0": + resolution: + { + integrity: sha512-KwrT7p/8Eo3Op/HBSIwGXOsTZKYiM9NpWRBJ5sVjWP/SmlS+oxxRvJht/FNAtliJvja44N3ul1yATgohnVBV0Q==, + } + + "@types/json-schema@7.0.15": + resolution: + { + integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==, + } + + "@types/node@24.0.0": + resolution: + { + integrity: sha512-yZQa2zm87aRVcqDyH5+4Hv9KYgSdgwX1rFnGvpbzMaC7YAljmhBET93TPiTd3ObwTL+gSpIzPKg5BqVxdCvxKg==, + } + + "@typescript-eslint/eslint-plugin@8.59.2": + resolution: + { + integrity: sha512-j/bwmkBvHUtPNxzuWe5z6BEk3q54YRyGlBXkSsmfoih7zNrBvl5A9A98anlp/7JbyZcWIJ8KXo/3Tq/DjFLtuQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + "@typescript-eslint/parser": ^8.59.2 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + + "@typescript-eslint/parser@8.59.2": + resolution: + { + integrity: sha512-plR3pp6D+SSUn1HM7xvSkx12/DhoHInI2YF35KAcVFNZvlC0gtrWqx7Qq1oH2Ssgi0vlFRCTbP+DZc7B9+TtsQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + + "@typescript-eslint/project-service@8.59.2": + resolution: + { + integrity: sha512-+2hqvEkeyf/0FBor67duF0Ll7Ot8jyKzDQOSrxazF/danillRq2DwR9dLptsXpoZQqxE1UisSmoZewrlPas9Vw==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + typescript: ">=4.8.4 <6.1.0" + + "@typescript-eslint/scope-manager@8.59.2": + resolution: + { + integrity: sha512-JzfyEpEtOU89CcFSwyNS3mu4MLvLSXqnmX05+aKBDM+TdR5jzcGOEBwxwGNxrEQ7p/z6kK2WyioCGBf2zZBnvg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@typescript-eslint/tsconfig-utils@8.59.2": + resolution: + { + integrity: sha512-BKK4alN7oi4C/zv4VqHQ+uRU+lTa6JGIZ7s1juw7b3RHo9OfKB+bKX3u0iVZetdsUCBBkSbdWbarJbmN0fTeSw==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + typescript: ">=4.8.4 <6.1.0" + + "@typescript-eslint/type-utils@8.59.2": + resolution: + { + integrity: sha512-nhqaj1nmTdVVl/BP5omXNRGO38jn5iosis2vbdmupF2txCf8ylWT8lx+JlvMYYVqzGVKtjojUFoQ3JRWK+mfzQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + + "@typescript-eslint/types@8.59.2": + resolution: + { + integrity: sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@typescript-eslint/typescript-estree@8.59.2": + resolution: + { + integrity: sha512-o0XPGNwcWw+FIwStOWn+BwBuEmL6QXP0rsvAFg7ET1dey1Nr6Wb1ac8p5HEsK0ygO/6mUxlk+YWQD9xcb/nnXg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + typescript: ">=4.8.4 <6.1.0" + + "@typescript-eslint/utils@8.59.2": + resolution: + { + integrity: sha512-Juw3EinkXqjaffxz6roowvV7GZT/kET5vSKKZT6upl5TXdWkLkYmNPXwDDL2Vkt2DPn0nODIS4egC/0AGxKo/Q==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + + "@typescript-eslint/visitor-keys@8.59.2": + resolution: + { + integrity: sha512-NwjLUnGy8/Zfx23fl50tRC8rYaYnM52xNRYFAXvmiil9yh1+K6aRVQMnzW6gQB/1DLgWt977lYQn7C+wtgXZiA==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@vitest/coverage-v8@3.2.4": + resolution: + { + integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==, + } + peerDependencies: + "@vitest/browser": 3.2.4 + vitest: 3.2.4 + peerDependenciesMeta: + "@vitest/browser": + optional: true - '@vitest/mocker@3.2.3': - resolution: {integrity: sha512-cP6fIun+Zx8he4rbWvi+Oya6goKQDZK+Yq4hhlggwQBbrlOQ4qtZ+G4nxB6ZnzI9lyIb+JnvyiJnPC2AGbKSPA==} + "@vitest/expect@3.2.4": + resolution: + { + integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==, + } + + "@vitest/mocker@3.2.4": + resolution: + { + integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==, + } peerDependencies: msw: ^2.4.9 vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 @@ -318,75 +975,385 @@ packages: vite: optional: true - '@vitest/pretty-format@3.2.3': - resolution: {integrity: sha512-yFglXGkr9hW/yEXngO+IKMhP0jxyFw2/qys/CK4fFUZnSltD+MU7dVYGrH8rvPcK/O6feXQA+EU33gjaBBbAng==} - - '@vitest/runner@3.2.3': - resolution: {integrity: sha512-83HWYisT3IpMaU9LN+VN+/nLHVBCSIUKJzGxC5RWUOsK1h3USg7ojL+UXQR3b4o4UBIWCYdD2fxuzM7PQQ1u8w==} - - '@vitest/snapshot@3.2.3': - resolution: {integrity: sha512-9gIVWx2+tysDqUmmM1L0hwadyumqssOL1r8KJipwLx5JVYyxvVRfxvMq7DaWbZZsCqZnu/dZedaZQh4iYTtneA==} - - '@vitest/spy@3.2.3': - resolution: {integrity: sha512-JHu9Wl+7bf6FEejTCREy+DmgWe+rQKbK+y32C/k5f4TBIAlijhJbRBIRIOCEpVevgRsCQR2iHRUH2/qKVM/plw==} + "@vitest/pretty-format@3.2.4": + resolution: + { + integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==, + } + + "@vitest/runner@3.2.4": + resolution: + { + integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==, + } + + "@vitest/snapshot@3.2.4": + resolution: + { + integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==, + } + + "@vitest/spy@3.2.4": + resolution: + { + integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==, + } + + "@vitest/utils@3.2.4": + resolution: + { + integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==, + } + + acorn-jsx@5.3.2: + resolution: + { + integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==, + } + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: + { + integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==, + } + engines: { node: ">=0.4.0" } + hasBin: true - '@vitest/utils@3.2.3': - resolution: {integrity: sha512-4zFBCU5Pf+4Z6v+rwnZ1HU1yzOKKvDkMXZrymE2PBlbjKJRlrOxbvpfPSvJTGRIwGoahaOGvp+kbCoxifhzJ1Q==} + ajv@6.15.0: + resolution: + { + integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==, + } + + ansi-regex@5.0.1: + resolution: + { + integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==, + } + engines: { node: ">=8" } + + ansi-regex@6.2.2: + resolution: + { + integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==, + } + engines: { node: ">=12" } + + ansi-styles@4.3.0: + resolution: + { + integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==, + } + engines: { node: ">=8" } + + ansi-styles@6.2.3: + resolution: + { + integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==, + } + engines: { node: ">=12" } assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} + resolution: + { + integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==, + } + engines: { node: ">=12" } + + ast-v8-to-istanbul@0.3.12: + resolution: + { + integrity: sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==, + } + + balanced-match@1.0.2: + resolution: + { + integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==, + } + + balanced-match@4.0.4: + resolution: + { + integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==, + } + engines: { node: 18 || 20 || >=22 } + + brace-expansion@2.1.0: + resolution: + { + integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==, + } + + brace-expansion@5.0.6: + resolution: + { + integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==, + } + engines: { node: 18 || 20 || >=22 } cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==, + } + engines: { node: ">=8" } chai@5.2.0: - resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} - engines: {node: '>=12'} + resolution: + { + integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==, + } + engines: { node: ">=12" } check-error@2.1.1: - resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} - engines: {node: '>= 16'} + resolution: + { + integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==, + } + engines: { node: ">= 16" } + + color-convert@2.0.1: + resolution: + { + integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==, + } + engines: { node: ">=7.0.0" } + + color-name@1.1.4: + resolution: + { + integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, + } commander@14.0.0: - resolution: {integrity: sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==} - engines: {node: '>=20'} + resolution: + { + integrity: sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==, + } + engines: { node: ">=20" } + + consola@3.4.2: + resolution: + { + integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==, + } + engines: { node: ^14.18.0 || >=16.10.0 } + + cross-spawn@7.0.6: + resolution: + { + integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==, + } + engines: { node: ">= 8" } debug@4.4.1: - resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} - engines: {node: '>=6.0'} + resolution: + { + integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==, + } + engines: { node: ">=6.0" } + peerDependencies: + supports-color: "*" + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: + { + integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==, + } + engines: { node: ">=6.0" } peerDependencies: - supports-color: '*' + supports-color: "*" peerDependenciesMeta: supports-color: optional: true deep-eql@5.0.2: - resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==, + } + engines: { node: ">=6" } + + deep-is@0.1.4: + resolution: + { + integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==, + } + + detect-libc@2.1.2: + resolution: + { + integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==, + } + engines: { node: ">=8" } dotenv@16.5.0: - resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==} - engines: {node: '>=12'} + resolution: + { + integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==, + } + engines: { node: ">=12" } + + eastasianwidth@0.2.0: + resolution: + { + integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==, + } + + emoji-regex@8.0.0: + resolution: + { + integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==, + } + + emoji-regex@9.2.2: + resolution: + { + integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==, + } es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + resolution: + { + integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==, + } esbuild@0.25.5: - resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==, + } + engines: { node: ">=18" } hasBin: true + escape-string-regexp@4.0.0: + resolution: + { + integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==, + } + engines: { node: ">=10" } + + eslint-config-prettier@10.1.8: + resolution: + { + integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==, + } + hasBin: true + peerDependencies: + eslint: ">=7.0.0" + + eslint-scope@9.1.2: + resolution: + { + integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + + eslint-visitor-keys@3.4.3: + resolution: + { + integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + + eslint-visitor-keys@5.0.1: + resolution: + { + integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + + eslint@10.3.0: + resolution: + { + integrity: sha512-XbEXaRva5cF0ZQB8w6MluHA0kZZfV2DuCMJ3ozyEOHLwDpZX2Lmm/7Pp0xdJmI0GL1W05VH5VwIFHEm1Vcw2gw==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + hasBin: true + peerDependencies: + jiti: "*" + peerDependenciesMeta: + jiti: + optional: true + + espree@11.2.0: + resolution: + { + integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + + esquery@1.7.0: + resolution: + { + integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==, + } + engines: { node: ">=0.10" } + + esrecurse@4.3.0: + resolution: + { + integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==, + } + engines: { node: ">=4.0" } + + estraverse@5.3.0: + resolution: + { + integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==, + } + engines: { node: ">=4.0" } + estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + resolution: + { + integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==, + } + + esutils@2.0.3: + resolution: + { + integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==, + } + engines: { node: ">=0.10.0" } expect-type@1.2.1: - resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} - engines: {node: '>=12.0.0'} - - fdir@6.4.6: - resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} + resolution: + { + integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==, + } + engines: { node: ">=12.0.0" } + + fast-deep-equal@3.1.3: + resolution: + { + integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, + } + + fast-json-stable-stringify@2.1.0: + resolution: + { + integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==, + } + + fast-levenshtein@2.0.6: + resolution: + { + integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==, + } + + fdir@6.5.0: + resolution: + { + integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==, + } + engines: { node: ">=12.0.0" } peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: @@ -394,124 +1361,741 @@ packages: optional: true figlet@1.8.1: - resolution: {integrity: sha512-kEC3Sme+YvA8Hkibv0NR1oClGcWia0VB2fC1SlMy027cwe795Xx40Xiv/nw/iFAwQLupymWh+uhAAErn/7hwPg==} - engines: {node: '>= 0.4.0'} + resolution: + { + integrity: sha512-kEC3Sme+YvA8Hkibv0NR1oClGcWia0VB2fC1SlMy027cwe795Xx40Xiv/nw/iFAwQLupymWh+uhAAErn/7hwPg==, + } + engines: { node: ">= 0.4.0" } hasBin: true + file-entry-cache@8.0.0: + resolution: + { + integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==, + } + engines: { node: ">=16.0.0" } + + find-up@5.0.0: + resolution: + { + integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==, + } + engines: { node: ">=10" } + + flat-cache@4.0.1: + resolution: + { + integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==, + } + engines: { node: ">=16" } + + flatted@3.4.2: + resolution: + { + integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==, + } + + foreground-child@3.3.1: + resolution: + { + integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==, + } + engines: { node: ">=14" } + fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + resolution: + { + integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, + } + engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } os: [darwin] + glob-parent@6.0.2: + resolution: + { + integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==, + } + engines: { node: ">=10.13.0" } + + glob@10.5.0: + resolution: + { + integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==, + } + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + + has-flag@4.0.0: + resolution: + { + integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==, + } + engines: { node: ">=8" } + + html-escaper@2.0.2: + resolution: + { + integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==, + } + + ignore@5.3.2: + resolution: + { + integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==, + } + engines: { node: ">= 4" } + + ignore@7.0.5: + resolution: + { + integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==, + } + engines: { node: ">= 4" } + + imurmurhash@0.1.4: + resolution: + { + integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==, + } + engines: { node: ">=0.8.19" } + + 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" } + + isexe@2.0.0: + resolution: + { + integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, + } + + istanbul-lib-coverage@3.2.2: + resolution: + { + integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==, + } + engines: { node: ">=8" } + + istanbul-lib-report@3.0.1: + resolution: + { + integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==, + } + engines: { node: ">=10" } + + istanbul-lib-source-maps@5.0.6: + resolution: + { + integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==, + } + engines: { node: ">=10" } + + istanbul-reports@3.2.0: + resolution: + { + integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==, + } + engines: { node: ">=8" } + + jackspeak@3.4.3: + resolution: + { + integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==, + } + + js-tokens@10.0.0: + resolution: + { + integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==, + } + js-tokens@9.0.1: - resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + resolution: + { + integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==, + } + + json-buffer@3.0.1: + resolution: + { + integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==, + } + + json-schema-traverse@0.4.1: + resolution: + { + integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==, + } + + json-stable-stringify-without-jsonify@1.0.1: + resolution: + { + integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==, + } + + keyv@4.5.4: + resolution: + { + integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==, + } + + levn@0.4.1: + resolution: + { + integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==, + } + engines: { node: ">= 0.8.0" } + + lightningcss-android-arm64@1.32.0: + resolution: + { + integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==, + } + engines: { node: ">= 12.0.0" } + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: + { + integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==, + } + engines: { node: ">= 12.0.0" } + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: + { + integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==, + } + engines: { node: ">= 12.0.0" } + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: + { + integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==, + } + engines: { node: ">= 12.0.0" } + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: + { + integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==, + } + engines: { node: ">= 12.0.0" } + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: + { + integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==, + } + engines: { node: ">= 12.0.0" } + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: + { + integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==, + } + engines: { node: ">= 12.0.0" } + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: + { + integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==, + } + engines: { node: ">= 12.0.0" } + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.32.0: + resolution: + { + integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==, + } + engines: { node: ">= 12.0.0" } + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: + { + integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==, + } + engines: { node: ">= 12.0.0" } + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: + { + integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==, + } + engines: { node: ">= 12.0.0" } + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: + { + integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==, + } + engines: { node: ">= 12.0.0" } + + locate-path@6.0.0: + resolution: + { + integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==, + } + engines: { node: ">=10" } loupe@3.1.3: - resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} + resolution: + { + integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==, + } + + loupe@3.2.1: + resolution: + { + integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==, + } + + lru-cache@10.4.3: + resolution: + { + integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==, + } magic-string@0.30.17: - resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + resolution: + { + integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==, + } + + magicast@0.3.5: + resolution: + { + integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==, + } + + make-dir@4.0.0: + resolution: + { + integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==, + } + engines: { node: ">=10" } + + minimatch@10.2.5: + resolution: + { + integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==, + } + engines: { node: 18 || 20 || >=22 } + + minimatch@9.0.9: + resolution: + { + integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==, + } + engines: { node: ">=16 || 14 >=14.17" } + + minipass@7.1.3: + resolution: + { + integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==, + } + engines: { node: ">=16 || 14 >=14.17" } ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + resolution: + { + integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==, + } nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + resolution: + { + integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==, + } + engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } hasBin: true + natural-compare@1.4.0: + resolution: + { + integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==, + } + + optionator@0.9.4: + resolution: + { + integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==, + } + engines: { node: ">= 0.8.0" } + + p-limit@3.1.0: + resolution: + { + integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==, + } + engines: { node: ">=10" } + + p-locate@5.0.0: + resolution: + { + integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==, + } + engines: { node: ">=10" } + + package-json-from-dist@1.0.1: + resolution: + { + integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==, + } + + path-exists@4.0.0: + resolution: + { + integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==, + } + engines: { node: ">=8" } + + 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" } + pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + resolution: + { + integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==, + } pathval@2.0.0: - resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} - engines: {node: '>= 14.16'} + resolution: + { + integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==, + } + engines: { node: ">= 14.16" } picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@4.0.2: - resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} - engines: {node: '>=12'} + resolution: + { + integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==, + } + + picomatch@4.0.4: + resolution: + { + integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==, + } + engines: { node: ">=12" } + + postcss@8.5.14: + resolution: + { + integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==, + } + engines: { node: ^10 || ^12 || >=14 } + + prelude-ls@1.2.1: + resolution: + { + integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==, + } + engines: { node: ">= 0.8.0" } + + prettier@3.8.3: + resolution: + { + integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==, + } + engines: { node: ">=14" } + hasBin: true - postcss@8.5.4: - resolution: {integrity: sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==} - engines: {node: ^10 || ^12 || >=14} + punycode@2.3.1: + resolution: + { + integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==, + } + engines: { node: ">=6" } + + rolldown@1.0.0-rc.18: + resolution: + { + integrity: sha512-phmyKBpuBdRYDf4hgyynGAYn/rDDe+iZXKVJ7WX5b1zQzpLkP5oJRPGsfJuHdzPMlyyEO/4sPW6yfSx2gf7lVg==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + hasBin: true rollup@4.43.0: - resolution: {integrity: sha512-wdN2Kd3Twh8MAEOEJZsuxuLKCsBEo4PVNLK6tQWAn10VhsVewQLzcucMgLolRlhFybGxfclbPeEYBaP6RvUFGg==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} + resolution: + { + integrity: sha512-wdN2Kd3Twh8MAEOEJZsuxuLKCsBEo4PVNLK6tQWAn10VhsVewQLzcucMgLolRlhFybGxfclbPeEYBaP6RvUFGg==, + } + engines: { node: ">=18.0.0", npm: ">=8.0.0" } + hasBin: true + + semver@7.8.0: + resolution: + { + integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==, + } + engines: { node: ">=10" } hasBin: true + shebang-command@2.0.0: + resolution: + { + integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==, + } + engines: { node: ">=8" } + + shebang-regex@3.0.0: + resolution: + { + integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==, + } + engines: { node: ">=8" } + siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + resolution: + { + integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==, + } + + signal-exit@4.1.0: + resolution: + { + integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==, + } + engines: { node: ">=14" } source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==, + } + engines: { node: ">=0.10.0" } stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + resolution: + { + integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==, + } std-env@3.9.0: - resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} + resolution: + { + integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==, + } + + string-width@4.2.3: + resolution: + { + integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==, + } + engines: { node: ">=8" } + + string-width@5.1.2: + resolution: + { + integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==, + } + engines: { node: ">=12" } + + strip-ansi@6.0.1: + resolution: + { + integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==, + } + engines: { node: ">=8" } + + strip-ansi@7.2.0: + resolution: + { + integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==, + } + engines: { node: ">=12" } strip-literal@3.0.0: - resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} + resolution: + { + integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==, + } + + supports-color@7.2.0: + resolution: + { + integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==, + } + engines: { node: ">=8" } + + test-exclude@7.0.2: + resolution: + { + integrity: sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==, + } + engines: { node: ">=18" } tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + resolution: + { + integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==, + } tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - - tinyglobby@0.2.14: - resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} - engines: {node: '>=12.0.0'} - - tinypool@1.1.0: - resolution: {integrity: sha512-7CotroY9a8DKsKprEy/a14aCCm8jYVmR7aFy4fpkZM8sdpNJbKkixuNjgM50yCmip2ezc8z4N7k3oe2+rfRJCQ==} - engines: {node: ^18.0.0 || >=20.0.0} + resolution: + { + integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==, + } + + tinyglobby@0.2.16: + resolution: + { + integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==, + } + engines: { node: ">=12.0.0" } + + tinypool@1.1.1: + resolution: + { + integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==, + } + engines: { node: ^18.0.0 || >=20.0.0 } tinyrainbow@2.0.0: - resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} - engines: {node: '>=14.0.0'} + resolution: + { + integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==, + } + engines: { node: ">=14.0.0" } tinyspy@4.0.3: - resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} - engines: {node: '>=14.0.0'} + resolution: + { + integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==, + } + engines: { node: ">=14.0.0" } + + ts-api-utils@2.5.0: + resolution: + { + integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==, + } + engines: { node: ">=18.12" } + peerDependencies: + typescript: ">=4.8.4" + + tslib@2.8.1: + resolution: + { + integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==, + } + + type-check@0.4.0: + resolution: + { + integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==, + } + engines: { node: ">= 0.8.0" } + + typescript-eslint@8.59.2: + resolution: + { + integrity: sha512-pJw051uomb3ZeCzGTpRb8RbEqB5Y4WWet8gl/GcTlU35BSx0PVdZ86/bqkQCyKKuraVQEK7r6kBHQXF+fBhkoQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" typescript@5.8.3: - resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} - engines: {node: '>=14.17'} + resolution: + { + integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==, + } + engines: { node: ">=14.17" } hasBin: true undici-types@7.8.0: - resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} - - vite-node@3.2.3: - resolution: {integrity: sha512-gc8aAifGuDIpZHrPjuHyP4dpQmYXqWw7D1GmDnWeNWP654UEXzVfQ5IHPSK5HaHkwB/+p1atpYpSdw/2kOv8iQ==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + resolution: + { + integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==, + } + + uri-js@4.4.1: + resolution: + { + integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==, + } + + vite-node@3.2.4: + resolution: + { + integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==, + } + engines: { node: ^18.0.0 || ^20.0.0 || >=22.0.0 } hasBin: true vite@6.3.5: - resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + resolution: + { + integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==, + } + engines: { node: ^18.0.0 || ^20.0.0 || >=22.0.0 } hasBin: true peerDependencies: - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - jiti: '>=1.21.0' - less: '*' + "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: ">=1.21.0" + less: "*" lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' + sass: "*" + sass-embedded: "*" + stylus: "*" + sugarss: "*" terser: ^5.16.0 tsx: ^4.8.1 yaml: ^2.4.2 peerDependenciesMeta: - '@types/node': + "@types/node": optional: true jiti: optional: true @@ -534,238 +2118,657 @@ packages: yaml: optional: true - vitest@3.2.3: - resolution: {integrity: sha512-E6U2ZFXe3N/t4f5BwUaVCKRLHqUpk1CBWeMh78UT4VaTPH/2dyvH6ALl29JTovEPu9dVKr/K/J4PkXgrMbw4Ww==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + vite@8.0.11: + resolution: + { + integrity: sha512-Jz1mxtUBR5xTT65VOdJZUUeoyLtqljmFkiUXhPTLZka3RDc9vpi/xXkyrnsdRcm2lIi3l3GPMnAidTsEGIj3Ow==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + hasBin: true + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + "@vitejs/devtools": ^0.1.18 + esbuild: ^0.27.0 || ^0.28.0 + jiti: ">=1.21.0" + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: ">=0.54.8" + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + "@types/node": + optional: true + "@vitejs/devtools": + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@3.2.4: + resolution: + { + integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==, + } + engines: { node: ^18.0.0 || ^20.0.0 || >=22.0.0 } hasBin: true peerDependencies: - '@edge-runtime/vm': '*' - '@types/debug': ^4.1.12 - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - '@vitest/browser': 3.2.3 - '@vitest/ui': 3.2.3 - happy-dom: '*' - jsdom: '*' + "@edge-runtime/vm": "*" + "@types/debug": ^4.1.12 + "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 + "@vitest/browser": 3.2.4 + "@vitest/ui": 3.2.4 + happy-dom: "*" + jsdom: "*" peerDependenciesMeta: - '@edge-runtime/vm': + "@edge-runtime/vm": optional: true - '@types/debug': + "@types/debug": optional: true - '@types/node': + "@types/node": optional: true - '@vitest/browser': + "@vitest/browser": optional: true - '@vitest/ui': + "@vitest/ui": optional: true happy-dom: optional: true jsdom: optional: true + which@2.0.2: + resolution: + { + integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==, + } + engines: { node: ">= 8" } + hasBin: true + why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==, + } + engines: { node: ">=8" } hasBin: true + word-wrap@1.2.5: + resolution: + { + integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==, + } + engines: { node: ">=0.10.0" } + + wrap-ansi@7.0.0: + resolution: + { + integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==, + } + engines: { node: ">=10" } + + wrap-ansi@8.1.0: + resolution: + { + integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==, + } + engines: { node: ">=12" } + + yocto-queue@0.1.0: + resolution: + { + integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==, + } + engines: { node: ">=10" } + snapshots: + "@ampproject/remapping@2.3.0": + dependencies: + "@jridgewell/gen-mapping": 0.3.13 + "@jridgewell/trace-mapping": 0.3.31 + + "@babel/helper-string-parser@7.27.1": {} + + "@babel/helper-validator-identifier@7.28.5": {} + + "@babel/parser@7.29.3": + dependencies: + "@babel/types": 7.29.0 + + "@babel/types@7.29.0": + dependencies: + "@babel/helper-string-parser": 7.27.1 + "@babel/helper-validator-identifier": 7.28.5 + + "@bcoe/v8-coverage@1.0.2": {} + + "@emnapi/core@1.10.0": + dependencies: + "@emnapi/wasi-threads": 1.2.1 + tslib: 2.8.1 + optional: true + + "@emnapi/runtime@1.10.0": + dependencies: + tslib: 2.8.1 + optional: true + + "@emnapi/wasi-threads@1.2.1": + dependencies: + tslib: 2.8.1 + optional: true + + "@esbuild/aix-ppc64@0.25.5": + optional: true - '@esbuild/aix-ppc64@0.25.5': + "@esbuild/android-arm64@0.25.5": optional: true - '@esbuild/android-arm64@0.25.5': + "@esbuild/android-arm@0.25.5": optional: true - '@esbuild/android-arm@0.25.5': + "@esbuild/android-x64@0.25.5": optional: true - '@esbuild/android-x64@0.25.5': + "@esbuild/darwin-arm64@0.25.5": optional: true - '@esbuild/darwin-arm64@0.25.5': + "@esbuild/darwin-x64@0.25.5": optional: true - '@esbuild/darwin-x64@0.25.5': + "@esbuild/freebsd-arm64@0.25.5": optional: true - '@esbuild/freebsd-arm64@0.25.5': + "@esbuild/freebsd-x64@0.25.5": optional: true - '@esbuild/freebsd-x64@0.25.5': + "@esbuild/linux-arm64@0.25.5": optional: true - '@esbuild/linux-arm64@0.25.5': + "@esbuild/linux-arm@0.25.5": + optional: true + + "@esbuild/linux-ia32@0.25.5": + optional: true + + "@esbuild/linux-loong64@0.25.5": + optional: true + + "@esbuild/linux-mips64el@0.25.5": + optional: true + + "@esbuild/linux-ppc64@0.25.5": + optional: true + + "@esbuild/linux-riscv64@0.25.5": + optional: true + + "@esbuild/linux-s390x@0.25.5": + optional: true + + "@esbuild/linux-x64@0.25.5": + optional: true + + "@esbuild/netbsd-arm64@0.25.5": + optional: true + + "@esbuild/netbsd-x64@0.25.5": + optional: true + + "@esbuild/openbsd-arm64@0.25.5": + optional: true + + "@esbuild/openbsd-x64@0.25.5": + optional: true + + "@esbuild/sunos-x64@0.25.5": + optional: true + + "@esbuild/win32-arm64@0.25.5": + optional: true + + "@esbuild/win32-ia32@0.25.5": + optional: true + + "@esbuild/win32-x64@0.25.5": + optional: true + + "@eslint-community/eslint-utils@4.9.1(eslint@10.3.0)": + dependencies: + eslint: 10.3.0 + eslint-visitor-keys: 3.4.3 + + "@eslint-community/regexpp@4.12.2": {} + + "@eslint/config-array@0.23.5": + dependencies: + "@eslint/object-schema": 3.0.5 + debug: 4.4.1 + minimatch: 10.2.5 + transitivePeerDependencies: + - supports-color + + "@eslint/config-helpers@0.5.5": + dependencies: + "@eslint/core": 1.2.1 + + "@eslint/core@1.2.1": + dependencies: + "@types/json-schema": 7.0.15 + + "@eslint/js@10.0.1(eslint@10.3.0)": + optionalDependencies: + eslint: 10.3.0 + + "@eslint/object-schema@3.0.5": {} + + "@eslint/plugin-kit@0.7.1": + dependencies: + "@eslint/core": 1.2.1 + levn: 0.4.1 + + "@humanfs/core@0.19.2": + dependencies: + "@humanfs/types": 0.15.0 + + "@humanfs/node@0.16.8": + dependencies: + "@humanfs/core": 0.19.2 + "@humanfs/types": 0.15.0 + "@humanwhocodes/retry": 0.4.3 + + "@humanfs/types@0.15.0": {} + + "@humanwhocodes/module-importer@1.0.1": {} + + "@humanwhocodes/retry@0.4.3": {} + + "@isaacs/cliui@8.0.2": + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.2.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + "@istanbuljs/schema@0.1.6": {} + + "@jridgewell/gen-mapping@0.3.13": + dependencies: + "@jridgewell/sourcemap-codec": 1.5.0 + "@jridgewell/trace-mapping": 0.3.31 + + "@jridgewell/resolve-uri@3.1.2": {} + + "@jridgewell/sourcemap-codec@1.5.0": {} + + "@jridgewell/trace-mapping@0.3.31": + dependencies: + "@jridgewell/resolve-uri": 3.1.2 + "@jridgewell/sourcemap-codec": 1.5.0 + + "@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)": + dependencies: + "@emnapi/core": 1.10.0 + "@emnapi/runtime": 1.10.0 + "@tybys/wasm-util": 0.10.2 optional: true - '@esbuild/linux-arm@0.25.5': + "@oxc-project/types@0.128.0": {} + + "@pkgjs/parseargs@0.11.0": optional: true - '@esbuild/linux-ia32@0.25.5': + "@rolldown/binding-android-arm64@1.0.0-rc.18": optional: true - '@esbuild/linux-loong64@0.25.5': + "@rolldown/binding-darwin-arm64@1.0.0-rc.18": optional: true - '@esbuild/linux-mips64el@0.25.5': + "@rolldown/binding-darwin-x64@1.0.0-rc.18": optional: true - '@esbuild/linux-ppc64@0.25.5': + "@rolldown/binding-freebsd-x64@1.0.0-rc.18": optional: true - '@esbuild/linux-riscv64@0.25.5': + "@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.18": optional: true - '@esbuild/linux-s390x@0.25.5': + "@rolldown/binding-linux-arm64-gnu@1.0.0-rc.18": optional: true - '@esbuild/linux-x64@0.25.5': + "@rolldown/binding-linux-arm64-musl@1.0.0-rc.18": optional: true - '@esbuild/netbsd-arm64@0.25.5': + "@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.18": optional: true - '@esbuild/netbsd-x64@0.25.5': + "@rolldown/binding-linux-s390x-gnu@1.0.0-rc.18": optional: true - '@esbuild/openbsd-arm64@0.25.5': + "@rolldown/binding-linux-x64-gnu@1.0.0-rc.18": optional: true - '@esbuild/openbsd-x64@0.25.5': + "@rolldown/binding-linux-x64-musl@1.0.0-rc.18": optional: true - '@esbuild/sunos-x64@0.25.5': + "@rolldown/binding-openharmony-arm64@1.0.0-rc.18": optional: true - '@esbuild/win32-arm64@0.25.5': + "@rolldown/binding-wasm32-wasi@1.0.0-rc.18": + dependencies: + "@emnapi/core": 1.10.0 + "@emnapi/runtime": 1.10.0 + "@napi-rs/wasm-runtime": 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true - '@esbuild/win32-ia32@0.25.5': + "@rolldown/binding-win32-arm64-msvc@1.0.0-rc.18": optional: true - '@esbuild/win32-x64@0.25.5': + "@rolldown/binding-win32-x64-msvc@1.0.0-rc.18": optional: true - '@jridgewell/sourcemap-codec@1.5.0': {} + "@rolldown/pluginutils@1.0.0-rc.18": {} + + "@rollup/rollup-android-arm-eabi@4.43.0": + optional: true - '@rollup/rollup-android-arm-eabi@4.43.0': + "@rollup/rollup-android-arm64@4.43.0": optional: true - '@rollup/rollup-android-arm64@4.43.0': + "@rollup/rollup-darwin-arm64@4.43.0": optional: true - '@rollup/rollup-darwin-arm64@4.43.0': + "@rollup/rollup-darwin-x64@4.43.0": optional: true - '@rollup/rollup-darwin-x64@4.43.0': + "@rollup/rollup-freebsd-arm64@4.43.0": optional: true - '@rollup/rollup-freebsd-arm64@4.43.0': + "@rollup/rollup-freebsd-x64@4.43.0": optional: true - '@rollup/rollup-freebsd-x64@4.43.0': + "@rollup/rollup-linux-arm-gnueabihf@4.43.0": optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.43.0': + "@rollup/rollup-linux-arm-musleabihf@4.43.0": optional: true - '@rollup/rollup-linux-arm-musleabihf@4.43.0': + "@rollup/rollup-linux-arm64-gnu@4.43.0": optional: true - '@rollup/rollup-linux-arm64-gnu@4.43.0': + "@rollup/rollup-linux-arm64-musl@4.43.0": optional: true - '@rollup/rollup-linux-arm64-musl@4.43.0': + "@rollup/rollup-linux-loongarch64-gnu@4.43.0": optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.43.0': + "@rollup/rollup-linux-powerpc64le-gnu@4.43.0": optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.43.0': + "@rollup/rollup-linux-riscv64-gnu@4.43.0": optional: true - '@rollup/rollup-linux-riscv64-gnu@4.43.0': + "@rollup/rollup-linux-riscv64-musl@4.43.0": optional: true - '@rollup/rollup-linux-riscv64-musl@4.43.0': + "@rollup/rollup-linux-s390x-gnu@4.43.0": optional: true - '@rollup/rollup-linux-s390x-gnu@4.43.0': + "@rollup/rollup-linux-x64-gnu@4.43.0": optional: true - '@rollup/rollup-linux-x64-gnu@4.43.0': + "@rollup/rollup-linux-x64-musl@4.43.0": optional: true - '@rollup/rollup-linux-x64-musl@4.43.0': + "@rollup/rollup-win32-arm64-msvc@4.43.0": optional: true - '@rollup/rollup-win32-arm64-msvc@4.43.0': + "@rollup/rollup-win32-ia32-msvc@4.43.0": optional: true - '@rollup/rollup-win32-ia32-msvc@4.43.0': + "@rollup/rollup-win32-x64-msvc@4.43.0": optional: true - '@rollup/rollup-win32-x64-msvc@4.43.0': + "@tybys/wasm-util@0.10.2": + dependencies: + tslib: 2.8.1 optional: true - '@types/chai@5.2.2': + "@types/chai@5.2.2": dependencies: - '@types/deep-eql': 4.0.2 + "@types/deep-eql": 4.0.2 + + "@types/deep-eql@4.0.2": {} + + "@types/esrecurse@4.3.1": {} - '@types/deep-eql@4.0.2': {} + "@types/estree@1.0.7": {} - '@types/estree@1.0.7': {} + "@types/estree@1.0.8": {} - '@types/estree@1.0.8': {} + "@types/figlet@1.7.0": {} - '@types/figlet@1.7.0': {} + "@types/json-schema@7.0.15": {} - '@types/node@24.0.0': + "@types/node@24.0.0": dependencies: undici-types: 7.8.0 - '@vitest/expect@3.2.3': + "@typescript-eslint/eslint-plugin@8.59.2(@typescript-eslint/parser@8.59.2(eslint@10.3.0)(typescript@5.8.3))(eslint@10.3.0)(typescript@5.8.3)": + dependencies: + "@eslint-community/regexpp": 4.12.2 + "@typescript-eslint/parser": 8.59.2(eslint@10.3.0)(typescript@5.8.3) + "@typescript-eslint/scope-manager": 8.59.2 + "@typescript-eslint/type-utils": 8.59.2(eslint@10.3.0)(typescript@5.8.3) + "@typescript-eslint/utils": 8.59.2(eslint@10.3.0)(typescript@5.8.3) + "@typescript-eslint/visitor-keys": 8.59.2 + eslint: 10.3.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/parser@8.59.2(eslint@10.3.0)(typescript@5.8.3)": + dependencies: + "@typescript-eslint/scope-manager": 8.59.2 + "@typescript-eslint/types": 8.59.2 + "@typescript-eslint/typescript-estree": 8.59.2(typescript@5.8.3) + "@typescript-eslint/visitor-keys": 8.59.2 + debug: 4.4.3 + eslint: 10.3.0 + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/project-service@8.59.2(typescript@5.8.3)": + dependencies: + "@typescript-eslint/tsconfig-utils": 8.59.2(typescript@5.8.3) + "@typescript-eslint/types": 8.59.2 + debug: 4.4.3 + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/scope-manager@8.59.2": + dependencies: + "@typescript-eslint/types": 8.59.2 + "@typescript-eslint/visitor-keys": 8.59.2 + + "@typescript-eslint/tsconfig-utils@8.59.2(typescript@5.8.3)": + dependencies: + typescript: 5.8.3 + + "@typescript-eslint/type-utils@8.59.2(eslint@10.3.0)(typescript@5.8.3)": + dependencies: + "@typescript-eslint/types": 8.59.2 + "@typescript-eslint/typescript-estree": 8.59.2(typescript@5.8.3) + "@typescript-eslint/utils": 8.59.2(eslint@10.3.0)(typescript@5.8.3) + debug: 4.4.3 + eslint: 10.3.0 + ts-api-utils: 2.5.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/types@8.59.2": {} + + "@typescript-eslint/typescript-estree@8.59.2(typescript@5.8.3)": + dependencies: + "@typescript-eslint/project-service": 8.59.2(typescript@5.8.3) + "@typescript-eslint/tsconfig-utils": 8.59.2(typescript@5.8.3) + "@typescript-eslint/types": 8.59.2 + "@typescript-eslint/visitor-keys": 8.59.2 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.8.0 + tinyglobby: 0.2.16 + ts-api-utils: 2.5.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/utils@8.59.2(eslint@10.3.0)(typescript@5.8.3)": + dependencies: + "@eslint-community/eslint-utils": 4.9.1(eslint@10.3.0) + "@typescript-eslint/scope-manager": 8.59.2 + "@typescript-eslint/types": 8.59.2 + "@typescript-eslint/typescript-estree": 8.59.2(typescript@5.8.3) + eslint: 10.3.0 + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/visitor-keys@8.59.2": + dependencies: + "@typescript-eslint/types": 8.59.2 + eslint-visitor-keys: 5.0.1 + + "@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/node@24.0.0)(lightningcss@1.32.0))": dependencies: - '@types/chai': 5.2.2 - '@vitest/spy': 3.2.3 - '@vitest/utils': 3.2.3 + "@ampproject/remapping": 2.3.0 + "@bcoe/v8-coverage": 1.0.2 + ast-v8-to-istanbul: 0.3.12 + debug: 4.4.1 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + magic-string: 0.30.17 + magicast: 0.3.5 + std-env: 3.9.0 + test-exclude: 7.0.2 + tinyrainbow: 2.0.0 + vitest: 3.2.4(@types/node@24.0.0)(lightningcss@1.32.0) + transitivePeerDependencies: + - supports-color + + "@vitest/expect@3.2.4": + dependencies: + "@types/chai": 5.2.2 + "@vitest/spy": 3.2.4 + "@vitest/utils": 3.2.4 chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.3(vite@6.3.5(@types/node@24.0.0))': + "@vitest/mocker@3.2.4(vite@6.3.5(@types/node@24.0.0)(lightningcss@1.32.0))": dependencies: - '@vitest/spy': 3.2.3 + "@vitest/spy": 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 6.3.5(@types/node@24.0.0) + vite: 6.3.5(@types/node@24.0.0)(lightningcss@1.32.0) - '@vitest/pretty-format@3.2.3': + "@vitest/pretty-format@3.2.4": dependencies: tinyrainbow: 2.0.0 - '@vitest/runner@3.2.3': + "@vitest/runner@3.2.4": dependencies: - '@vitest/utils': 3.2.3 + "@vitest/utils": 3.2.4 pathe: 2.0.3 strip-literal: 3.0.0 - '@vitest/snapshot@3.2.3': + "@vitest/snapshot@3.2.4": dependencies: - '@vitest/pretty-format': 3.2.3 + "@vitest/pretty-format": 3.2.4 magic-string: 0.30.17 pathe: 2.0.3 - '@vitest/spy@3.2.3': + "@vitest/spy@3.2.4": dependencies: tinyspy: 4.0.3 - '@vitest/utils@3.2.3': + "@vitest/utils@3.2.4": dependencies: - '@vitest/pretty-format': 3.2.3 - loupe: 3.1.3 + "@vitest/pretty-format": 3.2.4 + loupe: 3.2.1 tinyrainbow: 2.0.0 + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + assertion-error@2.0.1: {} + ast-v8-to-istanbul@0.3.12: + dependencies: + "@jridgewell/trace-mapping": 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 + + balanced-match@1.0.2: {} + + balanced-match@4.0.4: {} + + brace-expansion@2.1.0: + dependencies: + balanced-match: 1.0.2 + + brace-expansion@5.0.6: + dependencies: + balanced-match: 4.0.4 + cac@6.7.14: {} chai@5.2.0: @@ -778,153 +2781,555 @@ snapshots: check-error@2.1.1: {} + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + commander@14.0.0: {} + consola@3.4.2: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + debug@4.4.1: dependencies: ms: 2.1.3 + debug@4.4.3: + dependencies: + ms: 2.1.3 + deep-eql@5.0.2: {} + deep-is@0.1.4: {} + + detect-libc@2.1.2: {} + dotenv@16.5.0: {} + eastasianwidth@0.2.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + es-module-lexer@1.7.0: {} esbuild@0.25.5: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.5 - '@esbuild/android-arm': 0.25.5 - '@esbuild/android-arm64': 0.25.5 - '@esbuild/android-x64': 0.25.5 - '@esbuild/darwin-arm64': 0.25.5 - '@esbuild/darwin-x64': 0.25.5 - '@esbuild/freebsd-arm64': 0.25.5 - '@esbuild/freebsd-x64': 0.25.5 - '@esbuild/linux-arm': 0.25.5 - '@esbuild/linux-arm64': 0.25.5 - '@esbuild/linux-ia32': 0.25.5 - '@esbuild/linux-loong64': 0.25.5 - '@esbuild/linux-mips64el': 0.25.5 - '@esbuild/linux-ppc64': 0.25.5 - '@esbuild/linux-riscv64': 0.25.5 - '@esbuild/linux-s390x': 0.25.5 - '@esbuild/linux-x64': 0.25.5 - '@esbuild/netbsd-arm64': 0.25.5 - '@esbuild/netbsd-x64': 0.25.5 - '@esbuild/openbsd-arm64': 0.25.5 - '@esbuild/openbsd-x64': 0.25.5 - '@esbuild/sunos-x64': 0.25.5 - '@esbuild/win32-arm64': 0.25.5 - '@esbuild/win32-ia32': 0.25.5 - '@esbuild/win32-x64': 0.25.5 + "@esbuild/aix-ppc64": 0.25.5 + "@esbuild/android-arm": 0.25.5 + "@esbuild/android-arm64": 0.25.5 + "@esbuild/android-x64": 0.25.5 + "@esbuild/darwin-arm64": 0.25.5 + "@esbuild/darwin-x64": 0.25.5 + "@esbuild/freebsd-arm64": 0.25.5 + "@esbuild/freebsd-x64": 0.25.5 + "@esbuild/linux-arm": 0.25.5 + "@esbuild/linux-arm64": 0.25.5 + "@esbuild/linux-ia32": 0.25.5 + "@esbuild/linux-loong64": 0.25.5 + "@esbuild/linux-mips64el": 0.25.5 + "@esbuild/linux-ppc64": 0.25.5 + "@esbuild/linux-riscv64": 0.25.5 + "@esbuild/linux-s390x": 0.25.5 + "@esbuild/linux-x64": 0.25.5 + "@esbuild/netbsd-arm64": 0.25.5 + "@esbuild/netbsd-x64": 0.25.5 + "@esbuild/openbsd-arm64": 0.25.5 + "@esbuild/openbsd-x64": 0.25.5 + "@esbuild/sunos-x64": 0.25.5 + "@esbuild/win32-arm64": 0.25.5 + "@esbuild/win32-ia32": 0.25.5 + "@esbuild/win32-x64": 0.25.5 + + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@10.1.8(eslint@10.3.0): + dependencies: + eslint: 10.3.0 + + eslint-scope@9.1.2: + dependencies: + "@types/esrecurse": 4.3.1 + "@types/estree": 1.0.8 + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@10.3.0: + dependencies: + "@eslint-community/eslint-utils": 4.9.1(eslint@10.3.0) + "@eslint-community/regexpp": 4.12.2 + "@eslint/config-array": 0.23.5 + "@eslint/config-helpers": 0.5.5 + "@eslint/core": 1.2.1 + "@eslint/plugin-kit": 0.7.1 + "@humanfs/node": 0.16.8 + "@humanwhocodes/module-importer": 1.0.1 + "@humanwhocodes/retry": 0.4.3 + "@types/estree": 1.0.8 + ajv: 6.15.0 + cross-spawn: 7.0.6 + debug: 4.4.1 + escape-string-regexp: 4.0.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + minimatch: 10.2.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@11.2.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 5.0.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.8 + "@types/estree": 1.0.8 + + esutils@2.0.3: {} expect-type@1.2.1: {} - fdir@6.4.6(picomatch@4.0.2): + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fdir@6.5.0(picomatch@4.0.4): optionalDependencies: - picomatch: 4.0.2 + picomatch: 4.0.4 figlet@1.8.1: {} + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + + flatted@3.4.2: {} + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + fsevents@2.3.3: optional: true + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + has-flag@4.0.0: {} + + html-escaper@2.0.2: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + imurmurhash@0.1.4: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + isexe@2.0.0: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + "@jridgewell/trace-mapping": 0.3.31 + debug: 4.4.1 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jackspeak@3.4.3: + dependencies: + "@isaacs/cliui": 8.0.2 + optionalDependencies: + "@pkgjs/parseargs": 0.11.0 + + js-tokens@10.0.0: {} + js-tokens@9.0.1: {} + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + loupe@3.1.3: {} + loupe@3.2.1: {} + + lru-cache@10.4.3: {} + magic-string@0.30.17: dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 + "@jridgewell/sourcemap-codec": 1.5.0 + + magicast@0.3.5: + dependencies: + "@babel/parser": 7.29.3 + "@babel/types": 7.29.0 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.8.0 + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.6 + + minimatch@9.0.9: + dependencies: + brace-expansion: 2.1.0 + + minipass@7.1.3: {} ms@2.1.3: {} nanoid@3.3.11: {} + natural-compare@1.4.0: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + package-json-from-dist@1.0.1: {} + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + pathe@2.0.3: {} pathval@2.0.0: {} picocolors@1.1.1: {} - picomatch@4.0.2: {} + picomatch@4.0.4: {} - postcss@8.5.4: + postcss@8.5.14: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 + prelude-ls@1.2.1: {} + + prettier@3.8.3: {} + + punycode@2.3.1: {} + + rolldown@1.0.0-rc.18: + dependencies: + "@oxc-project/types": 0.128.0 + "@rolldown/pluginutils": 1.0.0-rc.18 + optionalDependencies: + "@rolldown/binding-android-arm64": 1.0.0-rc.18 + "@rolldown/binding-darwin-arm64": 1.0.0-rc.18 + "@rolldown/binding-darwin-x64": 1.0.0-rc.18 + "@rolldown/binding-freebsd-x64": 1.0.0-rc.18 + "@rolldown/binding-linux-arm-gnueabihf": 1.0.0-rc.18 + "@rolldown/binding-linux-arm64-gnu": 1.0.0-rc.18 + "@rolldown/binding-linux-arm64-musl": 1.0.0-rc.18 + "@rolldown/binding-linux-ppc64-gnu": 1.0.0-rc.18 + "@rolldown/binding-linux-s390x-gnu": 1.0.0-rc.18 + "@rolldown/binding-linux-x64-gnu": 1.0.0-rc.18 + "@rolldown/binding-linux-x64-musl": 1.0.0-rc.18 + "@rolldown/binding-openharmony-arm64": 1.0.0-rc.18 + "@rolldown/binding-wasm32-wasi": 1.0.0-rc.18 + "@rolldown/binding-win32-arm64-msvc": 1.0.0-rc.18 + "@rolldown/binding-win32-x64-msvc": 1.0.0-rc.18 + rollup@4.43.0: dependencies: - '@types/estree': 1.0.7 + "@types/estree": 1.0.7 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.43.0 - '@rollup/rollup-android-arm64': 4.43.0 - '@rollup/rollup-darwin-arm64': 4.43.0 - '@rollup/rollup-darwin-x64': 4.43.0 - '@rollup/rollup-freebsd-arm64': 4.43.0 - '@rollup/rollup-freebsd-x64': 4.43.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.43.0 - '@rollup/rollup-linux-arm-musleabihf': 4.43.0 - '@rollup/rollup-linux-arm64-gnu': 4.43.0 - '@rollup/rollup-linux-arm64-musl': 4.43.0 - '@rollup/rollup-linux-loongarch64-gnu': 4.43.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.43.0 - '@rollup/rollup-linux-riscv64-gnu': 4.43.0 - '@rollup/rollup-linux-riscv64-musl': 4.43.0 - '@rollup/rollup-linux-s390x-gnu': 4.43.0 - '@rollup/rollup-linux-x64-gnu': 4.43.0 - '@rollup/rollup-linux-x64-musl': 4.43.0 - '@rollup/rollup-win32-arm64-msvc': 4.43.0 - '@rollup/rollup-win32-ia32-msvc': 4.43.0 - '@rollup/rollup-win32-x64-msvc': 4.43.0 + "@rollup/rollup-android-arm-eabi": 4.43.0 + "@rollup/rollup-android-arm64": 4.43.0 + "@rollup/rollup-darwin-arm64": 4.43.0 + "@rollup/rollup-darwin-x64": 4.43.0 + "@rollup/rollup-freebsd-arm64": 4.43.0 + "@rollup/rollup-freebsd-x64": 4.43.0 + "@rollup/rollup-linux-arm-gnueabihf": 4.43.0 + "@rollup/rollup-linux-arm-musleabihf": 4.43.0 + "@rollup/rollup-linux-arm64-gnu": 4.43.0 + "@rollup/rollup-linux-arm64-musl": 4.43.0 + "@rollup/rollup-linux-loongarch64-gnu": 4.43.0 + "@rollup/rollup-linux-powerpc64le-gnu": 4.43.0 + "@rollup/rollup-linux-riscv64-gnu": 4.43.0 + "@rollup/rollup-linux-riscv64-musl": 4.43.0 + "@rollup/rollup-linux-s390x-gnu": 4.43.0 + "@rollup/rollup-linux-x64-gnu": 4.43.0 + "@rollup/rollup-linux-x64-musl": 4.43.0 + "@rollup/rollup-win32-arm64-msvc": 4.43.0 + "@rollup/rollup-win32-ia32-msvc": 4.43.0 + "@rollup/rollup-win32-x64-msvc": 4.43.0 fsevents: 2.3.3 + semver@7.8.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + siginfo@2.0.0: {} + signal-exit@4.1.0: {} + source-map-js@1.2.1: {} stackback@0.0.2: {} std-env@3.9.0: {} + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.2.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + strip-literal@3.0.0: dependencies: js-tokens: 9.0.1 + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + test-exclude@7.0.2: + dependencies: + "@istanbuljs/schema": 0.1.6 + glob: 10.5.0 + minimatch: 10.2.5 + tinybench@2.9.0: {} tinyexec@0.3.2: {} - tinyglobby@0.2.14: + tinyglobby@0.2.16: dependencies: - fdir: 6.4.6(picomatch@4.0.2) - picomatch: 4.0.2 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 - tinypool@1.1.0: {} + tinypool@1.1.1: {} tinyrainbow@2.0.0: {} tinyspy@4.0.3: {} + ts-api-utils@2.5.0(typescript@5.8.3): + dependencies: + typescript: 5.8.3 + + tslib@2.8.1: + optional: true + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.59.2(eslint@10.3.0)(typescript@5.8.3): + dependencies: + "@typescript-eslint/eslint-plugin": 8.59.2(@typescript-eslint/parser@8.59.2(eslint@10.3.0)(typescript@5.8.3))(eslint@10.3.0)(typescript@5.8.3) + "@typescript-eslint/parser": 8.59.2(eslint@10.3.0)(typescript@5.8.3) + "@typescript-eslint/typescript-estree": 8.59.2(typescript@5.8.3) + "@typescript-eslint/utils": 8.59.2(eslint@10.3.0)(typescript@5.8.3) + eslint: 10.3.0 + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + typescript@5.8.3: {} undici-types@7.8.0: {} - vite-node@3.2.3(@types/node@24.0.0): + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + vite-node@3.2.4(@types/node@24.0.0)(lightningcss@1.32.0): dependencies: cac: 6.7.14 debug: 4.4.1 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 6.3.5(@types/node@24.0.0) + vite: 6.3.5(@types/node@24.0.0)(lightningcss@1.32.0) transitivePeerDependencies: - - '@types/node' + - "@types/node" - jiti - less - lightningcss @@ -937,45 +3342,57 @@ snapshots: - tsx - yaml - vite@6.3.5(@types/node@24.0.0): + vite@6.3.5(@types/node@24.0.0)(lightningcss@1.32.0): dependencies: esbuild: 0.25.5 - fdir: 6.4.6(picomatch@4.0.2) - picomatch: 4.0.2 - postcss: 8.5.4 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.14 rollup: 4.43.0 - tinyglobby: 0.2.14 + tinyglobby: 0.2.16 + optionalDependencies: + "@types/node": 24.0.0 + fsevents: 2.3.3 + lightningcss: 1.32.0 + + vite@8.0.11(@types/node@24.0.0): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.14 + rolldown: 1.0.0-rc.18 + tinyglobby: 0.2.16 optionalDependencies: - '@types/node': 24.0.0 + "@types/node": 24.0.0 fsevents: 2.3.3 - vitest@3.2.3(@types/node@24.0.0): + vitest@3.2.4(@types/node@24.0.0)(lightningcss@1.32.0): dependencies: - '@types/chai': 5.2.2 - '@vitest/expect': 3.2.3 - '@vitest/mocker': 3.2.3(vite@6.3.5(@types/node@24.0.0)) - '@vitest/pretty-format': 3.2.3 - '@vitest/runner': 3.2.3 - '@vitest/snapshot': 3.2.3 - '@vitest/spy': 3.2.3 - '@vitest/utils': 3.2.3 + "@types/chai": 5.2.2 + "@vitest/expect": 3.2.4 + "@vitest/mocker": 3.2.4(vite@6.3.5(@types/node@24.0.0)(lightningcss@1.32.0)) + "@vitest/pretty-format": 3.2.4 + "@vitest/runner": 3.2.4 + "@vitest/snapshot": 3.2.4 + "@vitest/spy": 3.2.4 + "@vitest/utils": 3.2.4 chai: 5.2.0 debug: 4.4.1 expect-type: 1.2.1 magic-string: 0.30.17 pathe: 2.0.3 - picomatch: 4.0.2 + picomatch: 4.0.4 std-env: 3.9.0 tinybench: 2.9.0 tinyexec: 0.3.2 - tinyglobby: 0.2.14 - tinypool: 1.1.0 + tinyglobby: 0.2.16 + tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 6.3.5(@types/node@24.0.0) - vite-node: 3.2.3(@types/node@24.0.0) + vite: 6.3.5(@types/node@24.0.0)(lightningcss@1.32.0) + vite-node: 3.2.4(@types/node@24.0.0)(lightningcss@1.32.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 24.0.0 + "@types/node": 24.0.0 transitivePeerDependencies: - jiti - less @@ -990,7 +3407,27 @@ snapshots: - tsx - yaml + which@2.0.2: + dependencies: + isexe: 2.0.0 + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 stackback: 0.0.2 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 + + yocto-queue@0.1.0: {} diff --git a/src/api/client.ts b/src/api/client.ts new file mode 100644 index 0000000..6952532 --- /dev/null +++ b/src/api/client.ts @@ -0,0 +1,97 @@ +import config from "@/core/config"; + +import { + AuthError, + GhitgudError, + NotFoundError, + UnprocessableError, +} from "@/core/errors"; + +import { + STATUS_OK_MIN, + STATUS_OK_MAX, + ERROR_NOT_FOUND, + ERROR_UNEXPECTED, + STATUS_NOT_FOUND, + GITHUB_API_ACCEPT, + GITHUB_API_VERSION, + ERROR_UNAUTHORIZED, + GITHUB_API_BASE_URL, + ERROR_UNPROCESSABLE, + STATUS_UNAUTHORIZED, + STATUS_UNPROCESSABLE, +} from "@/core/constants"; + +interface RequestOptions { + method?: string; + body?: unknown; +} + +const ERROR_MAP: Record = { + [STATUS_UNAUTHORIZED]: AuthError, + [STATUS_NOT_FOUND]: NotFoundError, + [STATUS_UNPROCESSABLE]: UnprocessableError, +}; + +const ERROR_MESSAGES: Record = { + [STATUS_UNAUTHORIZED]: ERROR_UNAUTHORIZED, + [STATUS_NOT_FOUND]: ERROR_NOT_FOUND, + [STATUS_UNPROCESSABLE]: ERROR_UNPROCESSABLE, +}; + +function buildHeaders(): Record { + return { + Accept: GITHUB_API_ACCEPT, + Authorization: `Bearer ${config.getToken()}`, + "X-GitHub-Api-Version": GITHUB_API_VERSION, + }; +} + +function handleError(status: number): never { + const ErrorClass = ERROR_MAP[status]; + if (ErrorClass) throw new ErrorClass(ERROR_MESSAGES[status]); + throw new GhitgudError(`${ERROR_UNEXPECTED}: ${status}`); +} + +function isSuccessful(status: number): boolean { + return status >= STATUS_OK_MIN && status <= STATUS_OK_MAX; +} + +async function request( + endpoint: string, + options: RequestOptions = {}, +): Promise { + const url = `${GITHUB_API_BASE_URL}${endpoint}`; + const headers = buildHeaders(); + + const fetchOptions: RequestInit = { + method: options.method || "GET", + headers, + }; + + if (options.body) { + fetchOptions.body = JSON.stringify(options.body); + } + + const response = await fetch(url, fetchOptions); + + if (isSuccessful(response.status)) return response; + handleError(response.status); +} + +const client = { + get: (endpoint: string) => request(endpoint), + + post: (endpoint: string, body: unknown) => + request(endpoint, { method: "POST", body }), + + patch: (endpoint: string, body: unknown) => + request(endpoint, { method: "PATCH", body }), + + getRepo: () => config.getRepo(), + isOk: (status: number) => isSuccessful(status), + isNotFound: (status: number) => status === STATUS_NOT_FOUND, + delete: (endpoint: string) => request(endpoint, { method: "DELETE" }), +}; + +export default client; diff --git a/src/api/labels.ts b/src/api/labels.ts new file mode 100644 index 0000000..187b7f9 --- /dev/null +++ b/src/api/labels.ts @@ -0,0 +1,41 @@ +import client from "./client"; +import { Label } from "@/types"; + +const labels = { + fetch: async (): Promise => { + const repo = client.getRepo(); + return client.get(`/repos/${repo}/labels`); + }, + + get: async (name: string): Promise => { + const repo = client.getRepo(); + return client.get(`/repos/${repo}/labels/${name}`); + }, + + create: async (label: Label): Promise => { + const repo = client.getRepo(); + + return client.post(`/repos/${repo}/labels`, { + name: label.name, + color: label.color, + description: label.description, + }); + }, + + patch: async (label: Label): Promise => { + const repo = client.getRepo(); + + return client.patch(`/repos/${repo}/labels/${label.name}`, { + color: label.color, + description: label.description, + new_name: label.newName || label.name, + }); + }, + + delete: async (name: string): Promise => { + const repo = client.getRepo(); + return client.delete(`/repos/${repo}/labels/${name}`); + }, +}; + +export default labels; diff --git a/app/ascii.ts b/src/cli/ascii.ts similarity index 100% rename from app/ascii.ts rename to src/cli/ascii.ts diff --git a/src/cli/index.ts b/src/cli/index.ts new file mode 100644 index 0000000..213245f --- /dev/null +++ b/src/cli/index.ts @@ -0,0 +1,46 @@ +import process from "process"; +import { program } from "commander"; + +import ascii from "./ascii"; +import logger from "@/core/logger"; +import pingCommand from "@/commands/ping"; +import labelsCommand from "@/commands/labels"; +import configCommand from "@/commands/config"; +import { GhitgudError } from "@/core/errors"; + +const NAME = "ghitgud"; +const DESCRIPTION = "A simple CLI to give superpowers to GitHub."; + +program.name(NAME).description(DESCRIPTION).version(__VERSION__); + +pingCommand.register(program); +labelsCommand.register(program); +configCommand.register(program); + +program.addHelpText("before", ascii); +program.exitOverride(); + +try { + program.parse(process.argv); +} catch (error) { + if (error instanceof GhitgudError) { + logger.error(error.message); + process.exit(1); + } + + const commanderError = error as { code?: string; exitCode?: number }; + if (commanderError.exitCode === 0) { + process.exit(0); + } + + throw error; +} + +process.on("unhandledRejection", (error: unknown) => { + if (error instanceof GhitgudError) { + logger.error((error as GhitgudError).message); + process.exit(1); + } + + throw error; +}); diff --git a/src/commands/config.ts b/src/commands/config.ts new file mode 100644 index 0000000..eb102bc --- /dev/null +++ b/src/commands/config.ts @@ -0,0 +1,26 @@ +import { Command } from "commander"; +import configService from "@/services/config"; + +const register = (program: Command) => { + const config = program + .command("config") + .description("Set CLI configurations."); + + config + .command("set") + .description("Set configuration.") + .arguments(" ") + .action((key: string, value: string) => { + configService.set(key, value); + }); + + config + .command("get") + .description("Get configuration value.") + .arguments("") + .action((key: string) => { + configService.get(key); + }); +}; + +export default { register }; diff --git a/src/commands/labels.ts b/src/commands/labels.ts new file mode 100644 index 0000000..a321a72 --- /dev/null +++ b/src/commands/labels.ts @@ -0,0 +1,51 @@ +import { Command } from "commander"; +import labelsService from "@/services/labels"; +import { TEMPLATES_DIR } from "@/core/constants"; + +const register = (program: Command) => { + const labels = program + .command("labels") + .description("Manage labels for a repository."); + + labels + .command("list") + .description("List all labels for a repository.") + .action(() => void labelsService.list()); + + labels + .command("pull") + .description("Pull all related labels for a repository.") + .option( + "-t, --template ", + "Pull from a built-in template instead of the remote repository", + ) + .action(async (options) => { + if (options.template) { + await labelsService.pullTemplate(options.template, TEMPLATES_DIR); + } else { + await labelsService.pull(); + } + }); + + labels + .command("push") + .description("Push all related labels for a repository.") + .option( + "-t, --template ", + "Push from a built-in template instead of the local metadata file", + ) + .action(async (options) => { + if (options.template) { + await labelsService.pushTemplate(options.template, TEMPLATES_DIR); + } else { + await labelsService.push(); + } + }); + + labels + .command("prune") + .description("Prune all related labels for a repository.") + .action(() => void labelsService.prune()); +}; + +export default { register }; diff --git a/src/commands/ping.ts b/src/commands/ping.ts new file mode 100644 index 0000000..14bb35b --- /dev/null +++ b/src/commands/ping.ts @@ -0,0 +1,11 @@ +import { Command } from "commander"; +import labelsService from "@/services/labels"; + +const register = (program: Command) => { + program + .command("ping") + .description("Check if the CLI is working.") + .action(() => void labelsService.ping()); +}; + +export default { register }; diff --git a/src/core/config.ts b/src/core/config.ts new file mode 100644 index 0000000..be9ac28 --- /dev/null +++ b/src/core/config.ts @@ -0,0 +1,84 @@ +import fs from "fs"; +import "dotenv/config"; +import process from "process"; +import { ConfigError } from "@/core/errors"; + +import { + ENCODING, + ERROR_NO_REPO, + GHITGUD_FOLDER, + ERROR_NO_TOKEN, + CREDENTIALS_PATH, +} from "@/core/constants"; + +function readCredentialsFile(): Record | null { + if (!fs.existsSync(CREDENTIALS_PATH)) return null; + const data = fs.readFileSync(CREDENTIALS_PATH, ENCODING); + return JSON.parse(data); +} + +function resolve(key: string, envVar: string): string { + const envValue = process.env[envVar]; + if (envValue) return envValue; + + const credentials = readCredentialsFile(); + if (credentials && credentials[key]) return credentials[key]; + + throw new ConfigError(key === "repo" ? ERROR_NO_REPO : ERROR_NO_TOKEN); +} + +function read(key: string): string | null { + const credentials = readCredentialsFile(); + if (credentials && credentials[key]) return credentials[key]; + return null; +} + +function has(key: string): boolean { + const isEnvVarSet = + !!process.env[ + key === "repo" ? "GHITGUD_GITHUB_REPO" : "GHITGUD_GITHUB_TOKEN" + ]; + + if (isEnvVarSet) { + return true; + } + + const credentials = readCredentialsFile(); + return !!credentials?.[key]; +} + +function write(key: string, value: string): void { + let credentials: Record = {}; + + if (fs.existsSync(CREDENTIALS_PATH)) { + const data = fs.readFileSync(CREDENTIALS_PATH, ENCODING); + credentials = JSON.parse(data); + } else { + fs.mkdirSync(GHITGUD_FOLDER, { recursive: true }); + } + + credentials[key] = value; + fs.writeFileSync( + CREDENTIALS_PATH, + JSON.stringify(credentials, null, 2), + ENCODING, + ); +} + +function getRepo(): string { + return resolve("repo", "GHITGUD_GITHUB_REPO"); +} + +function getToken(): string { + return resolve("token", "GHITGUD_GITHUB_TOKEN"); +} + +const config = { + getRepo, + getToken, + read, + write, + has, +}; + +export default config; diff --git a/src/core/constants.ts b/src/core/constants.ts new file mode 100644 index 0000000..d1d6d33 --- /dev/null +++ b/src/core/constants.ts @@ -0,0 +1,37 @@ +import os from "os"; +import path from "path"; + +export const GHITGUD_FOLDER = path.join(os.homedir(), ".config", "ghitgud"); +export const CREDENTIALS_FILE = "credentials.json"; +export const METADATA_FILE = "labels.json"; +export const ENCODING = "utf8"; + +export const CREDENTIALS_PATH = path.join(GHITGUD_FOLDER, CREDENTIALS_FILE); +export const METADATA_FILE_PATH = path.join(GHITGUD_FOLDER, METADATA_FILE); +export const TEMPLATES_DIR = path.join(__dirname, "templates"); + +export const GITHUB_API_VERSION = "2022-11-28"; +export const GITHUB_API_BASE_URL = "https://api.github.com"; +export const GITHUB_API_ACCEPT = "application/vnd.github+json"; + +export const STATUS_OK_MIN = 200; +export const STATUS_OK_MAX = 299; +export const STATUS_UNAUTHORIZED = 401; +export const STATUS_NOT_FOUND = 404; +export const STATUS_UNPROCESSABLE = 422; + +export const ERROR_UNAUTHORIZED = "Unauthorized."; +export const ERROR_NOT_FOUND = "Resource not found."; +export const ERROR_UNPROCESSABLE = "Content is unprocessable."; +export const ERROR_UNEXPECTED = "Unexpected status code."; +export const ERROR_NO_REPO = + "Repository not configured. Set it with: ghitgud config set repo owner/repo."; +export const ERROR_NO_TOKEN = + "Token not configured. Set it with: ghitgud config set token ."; +export const ERROR_UNSUPPORTED_KEY = "Trying to set unsupported key."; +export const ERROR_NO_METADATA = "No metadata file found."; + +export const PING_RESPONSE = "pong"; + +export const SUPPORTED_CONFIG_KEYS = ["token", "repo"] as const; +export type SupportedKey = (typeof SUPPORTED_CONFIG_KEYS)[number]; diff --git a/src/core/errors.ts b/src/core/errors.ts new file mode 100644 index 0000000..5d333cc --- /dev/null +++ b/src/core/errors.ts @@ -0,0 +1,34 @@ +export class GhitgudError extends Error { + constructor(message: string) { + super(message); + this.name = "GhitgudError"; + } +} + +export class AuthError extends GhitgudError { + constructor(message: string) { + super(message); + this.name = "AuthError"; + } +} + +export class ConfigError extends GhitgudError { + constructor(message: string) { + super(message); + this.name = "ConfigError"; + } +} + +export class NotFoundError extends GhitgudError { + constructor(message: string) { + super(message); + this.name = "NotFoundError"; + } +} + +export class UnprocessableError extends GhitgudError { + constructor(message: string) { + super(message); + this.name = "UnprocessableError"; + } +} diff --git a/src/core/io.ts b/src/core/io.ts new file mode 100644 index 0000000..852623e --- /dev/null +++ b/src/core/io.ts @@ -0,0 +1,21 @@ +import fs from "fs"; +import { ENCODING } from "@/core/constants"; + +const readJsonFile = (filePath: string): T => { + const data = fs.readFileSync(filePath, ENCODING); + return JSON.parse(data) as T; +}; + +const writeJsonFile = (filePath: string, data: unknown): void => { + fs.writeFileSync(filePath, JSON.stringify(data, null, 2), ENCODING); +}; + +const fileExists = (filePath: string): boolean => { + return fs.existsSync(filePath); +}; + +const ensureDir = (dirPath: string): void => { + fs.mkdirSync(dirPath, { recursive: true }); +}; + +export default { readJsonFile, writeJsonFile, fileExists, ensureDir }; diff --git a/src/core/logger.ts b/src/core/logger.ts new file mode 100644 index 0000000..5ec6a2a --- /dev/null +++ b/src/core/logger.ts @@ -0,0 +1,5 @@ +import { createConsola } from "consola"; + +const logger = createConsola({ defaults: { tag: "ghitgud" } }); + +export default logger; diff --git a/src/env.d.ts b/src/env.d.ts new file mode 100644 index 0000000..415c2c8 --- /dev/null +++ b/src/env.d.ts @@ -0,0 +1 @@ +declare const __VERSION__: string; diff --git a/src/services/config.ts b/src/services/config.ts new file mode 100644 index 0000000..cab13cd --- /dev/null +++ b/src/services/config.ts @@ -0,0 +1,31 @@ +import config from "@/core/config"; +import logger from "@/core/logger"; +import { ConfigError } from "@/core/errors"; +import type { SupportedKey } from "@/core/constants"; + +import { ERROR_UNSUPPORTED_KEY, SUPPORTED_CONFIG_KEYS } from "@/core/constants"; + +const validateKey = (key: string): SupportedKey => { + if (!SUPPORTED_CONFIG_KEYS.includes(key as SupportedKey)) { + throw new ConfigError(ERROR_UNSUPPORTED_KEY); + } + + return key as SupportedKey; +}; + +const set = (key: string, value: string) => { + validateKey(key); + logger.info(`Setting config "${key}".`); + config.write(key, value); + logger.success(`Config "${key}" set successfully.`); + return { success: true }; +}; + +const get = (key: string) => { + validateKey(key); + const value = config.read(key); + logger.info(`${key}: ${value ?? "(not set)"}.`); + return { success: true, key, value: value || null }; +}; + +export default { set, get }; diff --git a/src/services/labels.ts b/src/services/labels.ts new file mode 100644 index 0000000..cbf63ee --- /dev/null +++ b/src/services/labels.ts @@ -0,0 +1,138 @@ +import path from "path"; +import io from "@/core/io"; +import api from "@/api/labels"; +import logger from "@/core/logger"; +import { NotFoundError } from "@/core/errors"; +import { Label, normalizeLabel } from "@/types"; + +import { + PING_RESPONSE, + GHITGUD_FOLDER, + ERROR_NO_METADATA, + METADATA_FILE_PATH, +} from "@/core/constants"; + +const formatLabels = (labels: Label[]) => { + const rows = labels.map((label) => ({ + name: label.name, + color: label.color, + description: label.description, + })); + + console.log(); + console.table(rows); +}; + +const ping = () => { + logger.success(PING_RESPONSE + "."); + return { success: true, message: PING_RESPONSE }; +}; + +const list = async () => { + logger.info("Fetching labels from repository."); + const response = await api.fetch(); + const data = await response.json(); + const labels = data.map((label: Label) => normalizeLabel(label)); + + formatLabels(labels); + return { success: true, metadata: labels }; +}; + +const pull = async () => { + logger.info("Pulling labels from repository."); + const response = await api.fetch(); + const data = await response.json(); + const labels = data.map((label: Label) => normalizeLabel(label)); + + io.ensureDir(GHITGUD_FOLDER); + io.writeJsonFile(METADATA_FILE_PATH, labels); + + logger.success("Labels pulled successfully."); + return { success: true, metadata: labels }; +}; + +const pullTemplate = async (templateName: string, templatesDir: string) => { + logger.info(`Pulling labels from template "${templateName}".`); + const templatePath = path.join(templatesDir, `${templateName}.json`); + + if (!io.fileExists(templatePath)) { + throw new Error(`Template "${templateName}" not found at ${templatePath}.`); + } + + const labels: Label[] = io.readJsonFile(templatePath); + io.ensureDir(GHITGUD_FOLDER); + io.writeJsonFile(METADATA_FILE_PATH, labels); + + formatLabels(labels); + logger.success(`Labels pulled from template "${templateName}".`); + return { success: true, metadata: labels }; +}; + +const upsertLabels = async (labels: Label[]) => { + logger.info(`Upserting ${labels.length} label(s).`); + + await Promise.all( + labels.map(async (label) => { + try { + await api.get(label.name); + await api.patch(label); + } catch (error) { + if (error instanceof NotFoundError) { + await api.create(label); + } else { + throw error; + } + } + }), + ); +}; + +const push = async () => { + if (!io.fileExists(METADATA_FILE_PATH)) throw new Error(ERROR_NO_METADATA); + logger.info("Pushing labels to repository."); + const labels: Label[] = io.readJsonFile(METADATA_FILE_PATH); + await upsertLabels(labels); + + logger.success("Labels pushed successfully."); + return { success: true }; +}; + +const pushTemplate = async (templateName: string, templatesDir: string) => { + logger.info(`Pushing labels from template "${templateName}".`); + const templatePath = path.join(templatesDir, `${templateName}.json`); + + if (!io.fileExists(templatePath)) { + throw new Error(`Template "${templateName}" not found at ${templatePath}.`); + } + + const labels: Label[] = io.readJsonFile(templatePath); + await upsertLabels(labels); + + logger.success(`Labels pushed from template "${templateName}".`); + return { success: true }; +}; + +const prune = async () => { + if (!io.fileExists(METADATA_FILE_PATH)) throw new Error(ERROR_NO_METADATA); + const labels: Label[] = io.readJsonFile(METADATA_FILE_PATH); + logger.info(`Pruning ${labels.length} label(s) from repository.`); + + await Promise.all( + labels.map(async (label) => { + await api.delete(label.name); + }), + ); + + logger.success("Labels pruned successfully."); + return { success: true }; +}; + +export default { + ping, + list, + pull, + pullTemplate, + push, + pushTemplate, + prune, +}; diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..e3c2961 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,15 @@ +interface Label { + name: string; + color: string; + newName?: string; + description: string; +} + +const normalizeLabel = (label: Label) => ({ + name: label.name, + color: label.color, + description: label.description, +}); + +export type { Label }; +export { normalizeLabel }; diff --git a/templates/base.json b/templates/base.json index 38259b4..6baec4f 100644 --- a/templates/base.json +++ b/templates/base.json @@ -9,4 +9,4 @@ "color": "a2eeef", "description": "New feature or request" } -] \ No newline at end of file +] diff --git a/templates/conventional.json b/templates/conventional.json index fa0dab7..32846ae 100644 --- a/templates/conventional.json +++ b/templates/conventional.json @@ -1,53 +1,52 @@ [ - { - "name": "build", - "color": "0052cc", - "description": "Changes that affect the build system or external dependencies." - }, - { - "name": "chore", - "color": "8c8c8c", - "description": "General maintenance such as dependency updates." - }, - { - "name": "ci", - "color": "6a3d1c", - "description": "Continuous integration changes." - }, - { - "name": "documentation", - "color": "0e8a16", - "description": "Improvements or additions to documentation." - }, - { - "name": "feature", - "color": "1d7a1d", - "description": "New feature or request." - }, - { - "name": "fix", - "color": "d73a49", - "description": "Something isn't working." - }, - { - "name": "performance", - "color": "b60205", - "description": "Code changes that improve performance." - }, - { - "name": "refactor", - "color": "fbca04", - "description": "Changes that neither fix a bug nor add a feature but improve the code." - }, - { - "name": "style", - "color": "fef2c0", - "description": "Changes related to code style, like formatting." - }, - { - "name": "test", - "color": "d4c5f9", - "description": "Adding or updating tests." - } - ] - \ No newline at end of file + { + "name": "build", + "color": "0052cc", + "description": "Changes that affect the build system or external dependencies." + }, + { + "name": "chore", + "color": "8c8c8c", + "description": "General maintenance such as dependency updates." + }, + { + "name": "ci", + "color": "6a3d1c", + "description": "Continuous integration changes." + }, + { + "name": "documentation", + "color": "0e8a16", + "description": "Improvements or additions to documentation." + }, + { + "name": "feature", + "color": "1d7a1d", + "description": "New feature or request." + }, + { + "name": "fix", + "color": "d73a49", + "description": "Something isn't working." + }, + { + "name": "performance", + "color": "b60205", + "description": "Code changes that improve performance." + }, + { + "name": "refactor", + "color": "fbca04", + "description": "Changes that neither fix a bug nor add a feature but improve the code." + }, + { + "name": "style", + "color": "fef2c0", + "description": "Changes related to code style, like formatting." + }, + { + "name": "test", + "color": "d4c5f9", + "description": "Adding or updating tests." + } +] diff --git a/templates/github.json b/templates/github.json index b2fe0d1..2b44b7b 100644 --- a/templates/github.json +++ b/templates/github.json @@ -44,4 +44,4 @@ "color": "ffffff", "description": "This will not be worked on" } -] \ No newline at end of file +] diff --git a/tests/integration/.gitkeep b/tests/integration/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/library.test.ts b/tests/library.test.ts deleted file mode 100644 index 172dd96..0000000 --- a/tests/library.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { describe, it, expect, vi, Mock } from "vitest"; - -import api from "../app/api"; -import library from "../app/library"; - -vi.mock("../app/api", () => ({ - default: { - labels: { - get: vi.fn(), - fetch: vi.fn(), - patch: vi.fn(), - create: vi.fn(), - delete: vi.fn(), - }, - }, -})); - -const API_LABELS = [ - { - id: 1, - name: "feature", - color: "ffffff", - description: "This is a feature.", - }, -]; - -const METADATA_LABELS = [ - { - name: "feature", - color: "ffffff", - description: "This is a feature.", - }, -]; - -describe("ping", () => { - it("should return a pong", () => { - const spy = vi.spyOn(console, "info"); - library.ping(); - expect(spy).toHaveBeenCalledWith("pong"); - expect(library.ping()).toEqual({ success: true }); - }); -}); - -describe("labels", () => { - it("should list labels", async () => { - const mockResponse = { json: () => Promise.resolve(API_LABELS) }; - (api.labels.fetch as Mock).mockResolvedValue(mockResponse); - const result = await library.labels.list(); - expect(result).toEqual({ success: true, metadata: METADATA_LABELS }); - }); - - it("should pull labels", async () => { - const mockResponse = { json: () => Promise.resolve(API_LABELS) }; - (api.labels.fetch as Mock).mockResolvedValue(mockResponse); - const result = await library.labels.pull(); - expect(result).toEqual({ success: true }); - }); - - it("should push labels", async () => { - const mockResponse = { status: 200 }; - (api.labels.get as Mock).mockResolvedValue(mockResponse); - const result = await library.labels.push(); - expect(result).toEqual({ success: true }); - }); - - it("should prune labels", async () => { - const result = await library.labels.prune(); - expect(result).toEqual({ success: true }); - }); -}); - -describe("config", () => { - it("should set a config", () => { - const result = library.config.set("token", "test"); - expect(result).toEqual({ success: true }); - }); -}); \ No newline at end of file diff --git a/tests/tsconfig.json b/tests/tsconfig.json new file mode 100644 index 0000000..794138b --- /dev/null +++ b/tests/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "rootDir": "..", + "paths": { + "@/*": ["../src/*"] + } + }, + "include": ["./**/*.ts", "../src/**/*.ts"], + "exclude": ["../node_modules", "../dist"] +} diff --git a/tests/unit/api/client.test.ts b/tests/unit/api/client.test.ts new file mode 100644 index 0000000..d4d62ae --- /dev/null +++ b/tests/unit/api/client.test.ts @@ -0,0 +1,168 @@ +import client from "@/api/client"; +import { GhitgudError } from "@/core/errors"; +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; + +vi.mock("@/core/config", () => ({ + default: { + has: vi.fn(), + read: vi.fn(), + write: vi.fn(), + getRepo: vi.fn(() => "owner/repo"), + getToken: vi.fn(() => "test-token"), + }, +})); + +const ORIGINAL_FETCH = global.fetch; + +describe("client", () => { + beforeEach(() => { + global.fetch = vi.fn(); + }); + + afterEach(() => { + global.fetch = ORIGINAL_FETCH; + vi.restoreAllMocks(); + }); + + describe("request", () => { + it("should make a successful GET request", async () => { + (global.fetch as ReturnType).mockResolvedValue({ + status: 200, + }); + + const result = await client.get("/repos/owner/repo/labels"); + expect(result.status).toBe(200); + }); + + it("should accept 201 Created response", async () => { + (global.fetch as ReturnType).mockResolvedValue({ + status: 201, + }); + + const result = await client.post("/repos/owner/repo/labels", { + name: "bug", + }); + + expect(result.status).toBe(201); + expect(global.fetch).toHaveBeenCalledWith( + "https://api.github.com/repos/owner/repo/labels", + expect.objectContaining({ method: "POST" }), + ); + }); + + it("should accept 204 No Content response", async () => { + (global.fetch as ReturnType).mockResolvedValue({ + status: 204, + }); + + const result = await client.delete("/repos/owner/repo/labels/bug"); + expect(result.status).toBe(204); + + expect(global.fetch).toHaveBeenCalledWith( + "https://api.github.com/repos/owner/repo/labels/bug", + expect.objectContaining({ method: "DELETE" }), + ); + }); + + it("should make a PATCH request", async () => { + (global.fetch as ReturnType).mockResolvedValue({ + status: 200, + }); + + await client.patch("/repos/owner/repo/labels/bug", { color: "fff" }); + expect(global.fetch).toHaveBeenCalledWith( + "https://api.github.com/repos/owner/repo/labels/bug", + expect.objectContaining({ method: "PATCH" }), + ); + }); + + it("should throw AuthError on 401", async () => { + (global.fetch as ReturnType).mockResolvedValue({ + status: 401, + }); + + await expect(client.get("/test")).rejects.toThrow("Unauthorized."); + }); + + it("should throw NotFoundError on 404", async () => { + (global.fetch as ReturnType).mockResolvedValue({ + status: 404, + }); + + await expect(client.get("/test")).rejects.toThrow("Resource not found."); + }); + + it("should throw UnprocessableError on 422", async () => { + (global.fetch as ReturnType).mockResolvedValue({ + status: 422, + }); + + await expect(client.get("/test")).rejects.toThrow( + "Content is unprocessable.", + ); + }); + + it("should throw GhitgudError on unexpected status", async () => { + (global.fetch as ReturnType).mockResolvedValue({ + status: 500, + }); + + await expect(client.get("/test")).rejects.toThrow( + "Unexpected status code.: 500", + ); + + await expect(client.get("/test")).rejects.toThrow(GhitgudError); + }); + + it("should include auth and api headers", async () => { + (global.fetch as ReturnType).mockResolvedValue({ + status: 200, + }); + + await client.get("/test"); + expect(global.fetch).toHaveBeenCalledWith( + "https://api.github.com/test", + expect.objectContaining({ + headers: expect.objectContaining({ + Authorization: "Bearer test-token", + Accept: "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + }), + }), + ); + }); + + it("should send JSON body when provided", async () => { + (global.fetch as ReturnType).mockResolvedValue({ + status: 201, + }); + + await client.post("/test", { name: "bug" }); + const call = (global.fetch as ReturnType).mock.calls[0]; + expect(call[1].body).toBe(JSON.stringify({ name: "bug" })); + }); + }); + + describe("isOk", () => { + it("should return true for 2xx status codes", () => { + expect(client.isOk(200)).toBe(true); + expect(client.isOk(201)).toBe(true); + expect(client.isOk(204)).toBe(true); + expect(client.isOk(404)).toBe(false); + expect(client.isOk(500)).toBe(false); + }); + }); + + describe("isNotFound", () => { + it("should return true only for 404", () => { + expect(client.isNotFound(404)).toBe(true); + expect(client.isNotFound(200)).toBe(false); + }); + }); + + describe("getRepo", () => { + it("should return the configured repo", () => { + expect(client.getRepo()).toBe("owner/repo"); + }); + }); +}); diff --git a/tests/unit/api/labels.test.ts b/tests/unit/api/labels.test.ts new file mode 100644 index 0000000..5418d0a --- /dev/null +++ b/tests/unit/api/labels.test.ts @@ -0,0 +1,66 @@ +import client from "@/api/client"; +import labels from "@/api/labels"; +import { describe, it, expect, vi, Mock } from "vitest"; + +vi.mock("@/api/client", () => ({ + default: { + get: vi.fn(), + post: vi.fn(), + patch: vi.fn(), + delete: vi.fn(), + getRepo: vi.fn(() => "owner/repo"), + }, +})); + +describe("labels api", () => { + it("should call client.get for fetch", async () => { + (client.get as Mock).mockResolvedValue({ status: 200 }); + await labels.fetch(); + expect(client.get).toHaveBeenCalledWith("/repos/owner/repo/labels"); + }); + + it("should call client.get for get with name", async () => { + (client.get as Mock).mockResolvedValue({ status: 200 }); + await labels.get("bug"); + expect(client.get).toHaveBeenCalledWith("/repos/owner/repo/labels/bug"); + }); + + it("should call client.post for create", async () => { + (client.post as Mock).mockResolvedValue({ status: 201 }); + const label = { + name: "bug", + color: "d73a4a", + description: "Something isn't working", + }; + + await labels.create(label); + expect(client.post).toHaveBeenCalledWith("/repos/owner/repo/labels", { + name: "bug", + color: "d73a4a", + description: "Something isn't working", + }); + }); + + it("should call client.patch for patch", async () => { + (client.patch as Mock).mockResolvedValue({ status: 200 }); + const label = { + name: "bug", + color: "d73a4a", + description: "Bug fix", + newName: "defect", + }; + + await labels.patch(label); + expect(client.patch).toHaveBeenCalledWith("/repos/owner/repo/labels/bug", { + color: "d73a4a", + new_name: "defect", + description: "Bug fix", + }); + }); + + it("should call client.delete for delete", async () => { + (client.delete as Mock).mockResolvedValue({ status: 204 }); + await labels.delete("bug"); + expect(client.delete).toHaveBeenCalledWith("/repos/owner/repo/labels/bug"); + }); +}); diff --git a/tests/unit/cli/ascii.test.ts b/tests/unit/cli/ascii.test.ts new file mode 100644 index 0000000..9a3371a --- /dev/null +++ b/tests/unit/cli/ascii.test.ts @@ -0,0 +1,14 @@ +import ascii from "@/cli/ascii"; +import { describe, it, expect } from "vitest"; + +describe("ascii", () => { + it("should contain the figlet-rendered title", () => { + expect(ascii).toContain("____"); + expect(ascii).toContain("|___"); + }); + + it("should be a non-empty string", () => { + expect(typeof ascii).toBe("string"); + expect(ascii.length).toBeGreaterThan(0); + }); +}); diff --git a/tests/unit/cli/index.test.ts b/tests/unit/cli/index.test.ts new file mode 100644 index 0000000..76e17ba --- /dev/null +++ b/tests/unit/cli/index.test.ts @@ -0,0 +1,52 @@ +import logger from "@/core/logger"; +import { GhitgudError } from "@/core/errors"; +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; + +vi.mock("@/core/logger", () => ({ + default: { + success: vi.fn(), + error: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + debug: vi.fn(), + }, +})); + +vi.mock("@/services/labels", () => ({ + default: { + ping: vi.fn(), + list: vi.fn(), + pull: vi.fn(), + push: vi.fn(), + prune: vi.fn(), + }, +})); + +vi.mock("@/services/config", () => ({ + default: { set: vi.fn(), get: vi.fn() }, +})); + +describe("cli index", () => { + beforeEach(() => { + vi.spyOn(logger, "error").mockImplementation(() => {}); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("should catch GhitgudError and log to stderr", () => { + const error = new GhitgudError("test error"); + logger.error(error.message); + expect(logger.error).toHaveBeenCalledWith("test error"); + }); + + it("should format GhitgudError message consistently", () => { + const messages = ["Unauthorized.", "Config error.", "Not found."]; + messages.forEach((msg) => { + logger.error(msg); + }); + + expect(logger.error).toHaveBeenCalledTimes(messages.length); + }); +}); diff --git a/tests/unit/commands/config.test.ts b/tests/unit/commands/config.test.ts new file mode 100644 index 0000000..edda2a4 --- /dev/null +++ b/tests/unit/commands/config.test.ts @@ -0,0 +1,16 @@ +import { Command } from "commander"; +import { describe, it, expect } from "vitest"; +import configCommand from "@/commands/config"; + +describe("config command", () => { + it("should register config command with subcommands", () => { + const program = new Command(); + configCommand.register(program); + const config = program.commands.find((c) => c.name() === "config"); + + expect(config).toBeDefined(); + const subcommands = config!.commands.map((c) => c.name()); + expect(subcommands).toContain("set"); + expect(subcommands).toContain("get"); + }); +}); diff --git a/tests/unit/commands/labels.test.ts b/tests/unit/commands/labels.test.ts new file mode 100644 index 0000000..d6774b6 --- /dev/null +++ b/tests/unit/commands/labels.test.ts @@ -0,0 +1,19 @@ +import { Command } from "commander"; +import { describe, it, expect } from "vitest"; +import labelsCommand from "@/commands/labels"; + +describe("labels command", () => { + it("should register labels command with subcommands", () => { + const program = new Command(); + labelsCommand.register(program); + + const labels = program.commands.find((c) => c.name() === "labels"); + expect(labels).toBeDefined(); + const subcommands = labels!.commands.map((c) => c.name()); + + expect(subcommands).toContain("list"); + expect(subcommands).toContain("pull"); + expect(subcommands).toContain("push"); + expect(subcommands).toContain("prune"); + }); +}); diff --git a/tests/unit/commands/ping.test.ts b/tests/unit/commands/ping.test.ts new file mode 100644 index 0000000..4a0f4f0 --- /dev/null +++ b/tests/unit/commands/ping.test.ts @@ -0,0 +1,24 @@ +import { Command } from "commander"; +import pingCommand from "@/commands/ping"; +import { describe, it, expect, vi } from "vitest"; + +vi.mock("@/services/labels", () => ({ + default: { + list: vi.fn(), + pull: vi.fn(), + push: vi.fn(), + prune: vi.fn(), + pullTemplate: vi.fn(), + pushTemplate: vi.fn(), + ping: vi.fn(() => ({ success: true, message: "pong" })), + }, +})); + +describe("ping command", () => { + it("should register ping command on program", () => { + const program = new Command(); + pingCommand.register(program); + const commands = program.commands.map((c) => c.name()); + expect(commands).toContain("ping"); + }); +}); diff --git a/tests/unit/core/config.test.ts b/tests/unit/core/config.test.ts new file mode 100644 index 0000000..097fe33 --- /dev/null +++ b/tests/unit/core/config.test.ts @@ -0,0 +1,105 @@ +import fs from "fs"; +import path from "path"; +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; + +import { + ERROR_NO_REPO, + GHITGUD_FOLDER, + ERROR_NO_TOKEN, + CREDENTIALS_FILE, +} from "@/core/constants"; + +const originalEnv = { ...process.env }; +const credentialsPath = path.join(GHITGUD_FOLDER, CREDENTIALS_FILE); + +describe("config", () => { + beforeEach(() => { + delete process.env.GHITGUD_GITHUB_REPO; + delete process.env.GHITGUD_GITHUB_TOKEN; + + if (fs.existsSync(GHITGUD_FOLDER)) { + fs.rmSync(GHITGUD_FOLDER, { recursive: true }); + } + }); + + afterEach(() => { + process.env = { ...originalEnv }; + if (fs.existsSync(GHITGUD_FOLDER)) { + fs.rmSync(GHITGUD_FOLDER, { recursive: true }); + } + }); + + describe("getRepo", () => { + it("should throw when not set", async () => { + vi.resetModules(); + const { default: config } = await import("@/core/config"); + expect(() => config.getRepo()).toThrow(ERROR_NO_REPO); + }); + + it("should return value from environment variable", async () => { + process.env.GHITGUD_GITHUB_REPO = "owner/repo"; + vi.resetModules(); + const { default: config } = await import("@/core/config"); + expect(config.getRepo()).toBe("owner/repo"); + }); + + it("should return value from credentials file", async () => { + fs.mkdirSync(GHITGUD_FOLDER, { recursive: true }); + fs.writeFileSync(credentialsPath, JSON.stringify({ repo: "owner/repo" })); + vi.resetModules(); + const { default: config } = await import("@/core/config"); + expect(config.getRepo()).toBe("owner/repo"); + }); + }); + + describe("getToken", () => { + it("should throw when not set", async () => { + vi.resetModules(); + const { default: config } = await import("@/core/config"); + expect(() => config.getToken()).toThrow(ERROR_NO_TOKEN); + }); + + it("should return value from environment variable", async () => { + process.env.GHITGUD_GITHUB_TOKEN = "my-token"; + vi.resetModules(); + const { default: config } = await import("@/core/config"); + expect(config.getToken()).toBe("my-token"); + }); + }); + + describe("write and read", () => { + it("should write and read a config value", async () => { + vi.resetModules(); + const { default: config } = await import("@/core/config"); + + config.write("token", "test-token"); + vi.resetModules(); + + const { default: config2 } = await import("@/core/config"); + const value = config2.read("token"); + expect(value).toBe("test-token"); + }); + + it("should return null for non-existent key", async () => { + vi.resetModules(); + const { default: config } = await import("@/core/config"); + const value = config.read("nonexistent"); + expect(value).toBeNull(); + }); + }); + + describe("has", () => { + it("should return true when env var is set", async () => { + process.env.GHITGUD_GITHUB_REPO = "owner/repo"; + vi.resetModules(); + const { default: config } = await import("@/core/config"); + expect(config.has("repo")).toBe(true); + }); + + it("should return false when not set anywhere", async () => { + vi.resetModules(); + const { default: config } = await import("@/core/config"); + expect(config.has("repo")).toBe(false); + }); + }); +}); diff --git a/tests/unit/core/errors.test.ts b/tests/unit/core/errors.test.ts new file mode 100644 index 0000000..7fc36b2 --- /dev/null +++ b/tests/unit/core/errors.test.ts @@ -0,0 +1,46 @@ +import { describe, it, expect } from "vitest"; + +import { + GhitgudError, + AuthError, + ConfigError, + NotFoundError, + UnprocessableError, +} from "@/core/errors"; + +describe("errors", () => { + it("GhitgudError should have correct name and message", () => { + const error = new GhitgudError("test"); + expect(error.name).toBe("GhitgudError"); + expect(error.message).toBe("test"); + expect(error).toBeInstanceOf(Error); + }); + + it("AuthError should extend GhitgudError", () => { + const error = new AuthError("unauthorized"); + expect(error.name).toBe("AuthError"); + expect(error.message).toBe("unauthorized"); + expect(error).toBeInstanceOf(GhitgudError); + }); + + it("ConfigError should extend GhitgudError", () => { + const error = new ConfigError("missing config"); + expect(error.name).toBe("ConfigError"); + expect(error.message).toBe("missing config"); + expect(error).toBeInstanceOf(GhitgudError); + }); + + it("NotFoundError should extend GhitgudError", () => { + const error = new NotFoundError("not found"); + expect(error.name).toBe("NotFoundError"); + expect(error.message).toBe("not found"); + expect(error).toBeInstanceOf(GhitgudError); + }); + + it("UnprocessableError should extend GhitgudError", () => { + const error = new UnprocessableError("unprocessable"); + expect(error.name).toBe("UnprocessableError"); + expect(error.message).toBe("unprocessable"); + expect(error).toBeInstanceOf(GhitgudError); + }); +}); diff --git a/tests/unit/core/io.test.ts b/tests/unit/core/io.test.ts new file mode 100644 index 0000000..7fd1bc5 --- /dev/null +++ b/tests/unit/core/io.test.ts @@ -0,0 +1,73 @@ +import os from "os"; +import fs from "fs"; +import path from "path"; +import io from "@/core/io"; +import { ENCODING } from "@/core/constants"; +import { describe, it, expect, beforeEach, afterEach } from "vitest"; + +describe("io", () => { + const testDir = path.join(os.tmpdir(), "ghitgud-test-io"); + const testFile = path.join(testDir, "test.json"); + + beforeEach(() => { + if (fs.existsSync(testDir)) fs.rmSync(testDir, { recursive: true }); + fs.mkdirSync(testDir, { recursive: true }); + }); + + afterEach(() => { + if (fs.existsSync(testDir)) fs.rmSync(testDir, { recursive: true }); + }); + + describe("readJsonFile", () => { + it("should read and parse a JSON file", () => { + fs.writeFileSync(testFile, JSON.stringify({ name: "test" }), ENCODING); + const result = io.readJsonFile<{ name: string }>(testFile); + expect(result).toEqual({ name: "test" }); + }); + + it("should read an array from a JSON file", () => { + const data = [{ name: "bug", color: "fff" }]; + fs.writeFileSync(testFile, JSON.stringify(data), ENCODING); + const result = io.readJsonFile>(testFile); + expect(result).toEqual(data); + }); + }); + + describe("writeJsonFile", () => { + it("should write data as formatted JSON", () => { + io.writeJsonFile(testFile, { name: "test" }); + const content = fs.readFileSync(testFile, ENCODING); + expect(JSON.parse(content)).toEqual({ name: "test" }); + }); + + it("should format JSON with 2-space indentation", () => { + io.writeJsonFile(testFile, { a: 1 }); + const content = fs.readFileSync(testFile, ENCODING); + expect(content).toBe('{\n "a": 1\n}'); + }); + }); + + describe("fileExists", () => { + it("should return true for existing file", () => { + fs.writeFileSync(testFile, "{}", ENCODING); + expect(io.fileExists(testFile)).toBe(true); + }); + + it("should return false for non-existent file", () => { + expect(io.fileExists("/nonexistent/path.json")).toBe(false); + }); + }); + + describe("ensureDir", () => { + it("should create directory if it does not exist", () => { + const newDir = path.join(testDir, "subdir"); + io.ensureDir(newDir); + expect(fs.existsSync(newDir)).toBe(true); + }); + + it("should not throw if directory already exists", () => { + io.ensureDir(testDir); + expect(fs.existsSync(testDir)).toBe(true); + }); + }); +}); diff --git a/tests/unit/core/logger.test.ts b/tests/unit/core/logger.test.ts new file mode 100644 index 0000000..d1db11d --- /dev/null +++ b/tests/unit/core/logger.test.ts @@ -0,0 +1,12 @@ +import logger from "@/core/logger"; +import { describe, it, expect } from "vitest"; + +describe("logger", () => { + it("should have standard log methods", () => { + expect(typeof logger.success).toBe("function"); + expect(typeof logger.error).toBe("function"); + expect(typeof logger.info).toBe("function"); + expect(typeof logger.warn).toBe("function"); + expect(typeof logger.debug).toBe("function"); + }); +}); diff --git a/tests/unit/services/config.test.ts b/tests/unit/services/config.test.ts new file mode 100644 index 0000000..e3a8d04 --- /dev/null +++ b/tests/unit/services/config.test.ts @@ -0,0 +1,74 @@ +import config from "@/core/config"; +import logger from "@/core/logger"; +import { ConfigError } from "@/core/errors"; +import configService from "@/services/config"; +import { ERROR_UNSUPPORTED_KEY } from "@/core/constants"; +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; + +vi.mock("@/core/config", () => ({ + default: { + write: vi.fn(), + read: vi.fn(), + }, +})); + +describe("config service", () => { + beforeEach(() => { + vi.spyOn(logger, "success").mockImplementation(() => {}); + vi.spyOn(logger, "info").mockImplementation(() => {}); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe("set", () => { + it("should set a valid config key", () => { + const result = configService.set("token", "my-token"); + expect(result).toEqual({ success: true }); + expect(config.write).toHaveBeenCalledWith("token", "my-token"); + expect(logger.success).toHaveBeenCalledWith( + 'Config "token" set successfully.', + ); + }); + + it("should set repo config key", () => { + const result = configService.set("repo", "owner/repo"); + expect(result).toEqual({ success: true }); + expect(config.write).toHaveBeenCalledWith("repo", "owner/repo"); + }); + + it("should throw ConfigError for unsupported key", () => { + expect(() => configService.set("invalid", "value")).toThrow(ConfigError); + expect(() => configService.set("invalid", "value")).toThrow( + ERROR_UNSUPPORTED_KEY, + ); + }); + }); + + describe("get", () => { + it("should get a config key with value", () => { + (config.read as ReturnType).mockReturnValue("my-token"); + const result = configService.get("token"); + + expect(result).toEqual({ + success: true, + key: "token", + value: "my-token", + }); + + expect(logger.info).toHaveBeenCalledWith("token: my-token."); + }); + + it("should return null for missing value", () => { + (config.read as ReturnType).mockReturnValue(null); + const result = configService.get("token"); + expect(result).toEqual({ success: true, key: "token", value: null }); + expect(logger.info).toHaveBeenCalledWith("token: (not set)."); + }); + + it("should throw ConfigError for unsupported key", () => { + expect(() => configService.get("invalid")).toThrow(ConfigError); + }); + }); +}); diff --git a/tests/unit/services/labels.test.ts b/tests/unit/services/labels.test.ts new file mode 100644 index 0000000..feff2bc --- /dev/null +++ b/tests/unit/services/labels.test.ts @@ -0,0 +1,183 @@ +import io from "@/core/io"; +import api from "@/api/labels"; +import logger from "@/core/logger"; +import labelsService from "@/services/labels"; +import { NotFoundError } from "@/core/errors"; +import { describe, it, expect, vi, Mock, beforeEach, afterEach } from "vitest"; + +vi.mock("@/api/labels", () => ({ + default: { + get: vi.fn(), + fetch: vi.fn(), + patch: vi.fn(), + create: vi.fn(), + delete: vi.fn(), + }, +})); + +vi.mock("@/core/logger", () => ({ + default: { + info: vi.fn(), + error: vi.fn(), + success: vi.fn(), + }, +})); + +vi.mock("@/core/io", () => ({ + default: { + ensureDir: vi.fn(), + fileExists: vi.fn(), + readJsonFile: vi.fn(), + writeJsonFile: vi.fn(), + }, +})); + +const API_LABELS = [ + { + id: 1, + name: "feature", + color: "ffffff", + description: "This is a feature.", + }, +]; + +const METADATA_LABELS = [ + { name: "bug", color: "d73a4a", description: "Something isn't working" }, +]; + +describe("labels", () => { + beforeEach(() => { + vi.spyOn(logger, "success").mockImplementation(() => {}); + vi.spyOn(logger, "info").mockImplementation(() => {}); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("should return pong for ping", () => { + const result = labelsService.ping(); + expect(result).toEqual({ success: true, message: "pong" }); + expect(logger.success).toHaveBeenCalledWith("pong."); + }); + + it("should list labels", async () => { + const mockResponse = { json: () => Promise.resolve(API_LABELS) }; + (api.fetch as Mock).mockResolvedValue(mockResponse); + const result = await labelsService.list(); + + expect(result).toEqual({ + success: true, + metadata: [ + { name: "feature", color: "ffffff", description: "This is a feature." }, + ], + }); + }); + + it("should pull labels", async () => { + const mockResponse = { json: () => Promise.resolve(API_LABELS) }; + (api.fetch as Mock).mockResolvedValue(mockResponse); + const result = await labelsService.pull(); + + expect(result).toEqual({ + success: true, + metadata: [ + { name: "feature", color: "ffffff", description: "This is a feature." }, + ], + }); + + expect(logger.success).toHaveBeenCalledWith("Labels pulled successfully."); + }); + + it("should push labels", async () => { + vi.spyOn(io, "fileExists").mockReturnValue(true); + vi.spyOn(io, "readJsonFile").mockReturnValue(METADATA_LABELS); + (api.get as Mock).mockResolvedValue({ status: 200 }); + (api.patch as Mock).mockResolvedValue({ status: 200 }); + const result = await labelsService.push(); + expect(result).toEqual({ success: true }); + expect(logger.success).toHaveBeenCalledWith("Labels pushed successfully."); + }); + + it("should push labels creating new ones when not found", async () => { + vi.spyOn(io, "fileExists").mockReturnValue(true); + vi.spyOn(io, "readJsonFile").mockReturnValue(METADATA_LABELS); + + (api.get as Mock).mockRejectedValue( + new NotFoundError("Resource not found."), + ); + + (api.create as Mock).mockResolvedValue({ status: 201 }); + const result = await labelsService.push(); + expect(result).toEqual({ success: true }); + expect(api.create).toHaveBeenCalled(); + }); + + it("should prune labels", async () => { + vi.spyOn(io, "fileExists").mockReturnValue(true); + vi.spyOn(io, "readJsonFile").mockReturnValue(METADATA_LABELS); + (api.delete as Mock).mockResolvedValue({ status: 204 }); + const result = await labelsService.prune(); + expect(result).toEqual({ success: true }); + expect(logger.success).toHaveBeenCalledWith("Labels pruned successfully."); + }); + + it("should throw when no metadata file for push", async () => { + vi.spyOn(io, "fileExists").mockReturnValue(false); + await expect(labelsService.push()).rejects.toThrow( + "No metadata file found.", + ); + }); + + it("should throw when no metadata file for prune", async () => { + vi.spyOn(io, "fileExists").mockReturnValue(false); + await expect(labelsService.prune()).rejects.toThrow( + "No metadata file found.", + ); + }); + + it("should pull from template", async () => { + vi.spyOn(io, "fileExists").mockReturnValue(true); + vi.spyOn(io, "readJsonFile").mockReturnValue(METADATA_LABELS); + vi.spyOn(io, "ensureDir").mockImplementation(() => {}); + vi.spyOn(io, "writeJsonFile").mockImplementation(() => {}); + const result = await labelsService.pullTemplate("base", "/mock/templates"); + expect(result.success).toBe(true); + expect(result.metadata).toBeDefined(); + expect(result.metadata.length).toBeGreaterThan(0); + }); + + it("should throw for nonexistent template", async () => { + vi.spyOn(io, "fileExists").mockReturnValue(false); + + await expect( + labelsService.pullTemplate("nonexistent", "/mock/templates"), + ).rejects.toThrow( + 'Template "nonexistent" not found at /mock/templates/nonexistent.json.', + ); + }); + + it("should push from template", async () => { + vi.spyOn(io, "fileExists").mockReturnValue(true); + vi.spyOn(io, "readJsonFile").mockReturnValue(METADATA_LABELS); + + (api.get as Mock).mockRejectedValue( + new NotFoundError("Resource not found."), + ); + + (api.create as Mock).mockResolvedValue({ status: 201 }); + const result = await labelsService.pushTemplate("base", "/mock/templates"); + expect(result).toEqual({ success: true }); + }); + + it("should throw for nonexistent template on push", async () => { + vi.spyOn(io, "fileExists").mockReturnValue(false); + (api.get as Mock).mockResolvedValue({ status: 200 }); + + await expect( + labelsService.pushTemplate("nonexistent", "/mock/templates"), + ).rejects.toThrow( + 'Template "nonexistent" not found at /mock/templates/nonexistent.json.', + ); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index b69da1f..ceca921 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,113 +1,20 @@ { "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ - - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - - /* Language and Environment */ - "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "libReplacement": true, /* Enable lib replacement. */ - // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - - /* Modules */ - "module": "commonjs", /* Specify what module code is generated. */ - // "rootDir": "./", /* Specify the root folder within your source files. */ - // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ - // "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */ - // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ - // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ - // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ - // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - - /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - - /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - "outDir": "./dist", /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ - // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ - // "erasableSyntaxOnly": true, /* Do not allow runtime constructs that are not part of ECMAScript. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ - - /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - } + "target": "es2022", + "module": "es2022", + "moduleResolution": "bundler", + "rootDir": "./src", + "paths": { + "@/*": ["./src/*"] + }, + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "types": ["node"], + "declaration": false + }, + "include": ["src"], + "exclude": ["node_modules", "dist", "tests"] } diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..4b8f2f7 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,49 @@ +import path from "path"; +import { readFileSync } from "fs"; +import { builtinModules } from "module"; +import { defineConfig } from "vitest/config"; + +const VERSION = readFileSync(path.resolve(__dirname, "VERSION"), "utf8").trim(); + +export default defineConfig({ + build: { + lib: { + entry: path.resolve(__dirname, "src/cli/index.ts"), + formats: ["cjs"], + fileName: () => "index.js", + }, + + outDir: path.resolve(__dirname, "dist"), + rollupOptions: { + external: [ + "commander", + "consola", + "dotenv", + "figlet", + ...builtinModules, + ...builtinModules.map((m) => `node:${m}`), + ], + + output: { + banner: "#!/usr/bin/env node", + }, + }, + + minify: false, + target: "node24", + }, + + resolve: { + alias: { + "@": path.resolve(__dirname, "src"), + }, + }, + + define: { + __VERSION__: JSON.stringify(VERSION), + }, + + test: { + include: ["tests/**/*.test.ts"], + }, +});