diff --git a/.github/workflows/compare-builds.yml b/.github/workflows/compare-builds.yml deleted file mode 100644 index 71ce30da..00000000 --- a/.github/workflows/compare-builds.yml +++ /dev/null @@ -1,130 +0,0 @@ -name: Compare Build Outputs - -on: - workflow_run: - workflows: ['Generate Docs'] - types: [completed] - -permissions: - contents: read - actions: read - pull-requests: write - -jobs: - get-comparators: - name: Get Comparators - runs-on: ubuntu-latest - if: github.event.workflow_run.event == 'pull_request' - outputs: - comparators: ${{ steps.get-comparators.outputs.comparators }} - steps: - - name: Harden Runner - uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 - with: - egress-policy: audit - - - name: Checkout Code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - - name: List comparators - id: get-comparators - run: | - # List all .mjs files in scripts/compare-builds/ and remove the .mjs extension - COMPARATORS=$(ls scripts/compare-builds/*.mjs | xargs -n1 basename | sed 's/\.mjs$//' | jq -R -s -c 'split("\n")[:-1]') - echo "comparators=$COMPARATORS" >> $GITHUB_OUTPUT - - compare: - name: Run ${{ matrix.comparator }} comparator - runs-on: ubuntu-latest - needs: get-comparators - strategy: - matrix: - comparator: ${{ fromJSON(needs.get-comparators.outputs.comparators) }} - - steps: - - name: Harden Runner - uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 - with: - egress-policy: audit - - - name: Checkout Code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - - name: Download Output (HEAD) - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 - with: - name: ${{ matrix.comparator }} - path: out/head - run-id: ${{ github.event.workflow_run.id }} - github-token: ${{ secrets.GITHUB_TOKEN }} - - - name: Get Run ID from BASE - id: base-run - env: - WORKFLOW_ID: ${{ github.event.workflow_run.workflow_id }} - GH_TOKEN: ${{ github.token }} - run: | - ID=$(gh run list -c $GITHUB_SHA -w $WORKFLOW_ID -L 1 --json databaseId --jq ".[].databaseId") - echo "run_id=$ID" >> $GITHUB_OUTPUT - - - name: Download Output (BASE) - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 - with: - name: ${{ matrix.comparator }} - path: out/base - run-id: ${{ steps.base-run.outputs.run_id }} - github-token: ${{ secrets.GITHUB_TOKEN }} - - - name: Compare Bundle Size - id: compare - run: | - node scripts/compare-builds/${{ matrix.comparator }}.mjs > result.txt - if [ -s result.txt ]; then - echo "has_output=true" >> "$GITHUB_OUTPUT" - else - echo "has_output=false" >> "$GITHUB_OUTPUT" - fi - - - name: Upload comparison artifact - if: steps.compare.outputs.has_output == 'true' - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 - with: - name: ${{ matrix.comparator }} - path: result.txt - - aggregate: - name: Aggregate Comparison Results - runs-on: ubuntu-latest - needs: compare - steps: - - name: Harden Runner - uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 - with: - egress-policy: audit - - - name: Download all comparison artifacts - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 - with: - path: results - - - name: Combine results - id: combine - run: | - shopt -s nullglob - result_files=(results/*.txt) - - if ((${#result_files[@]})); then - { - echo "combined<> "$GITHUB_OUTPUT" - fi - - - name: Add Comment to PR - if: steps.combine.outputs.combined - uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b # v3.0.1 - with: - comment-tag: compared - message: ${{ steps.combine.outputs.combined }} - pr-number: ${{ github.event.workflow_run.pull_requests[0].number }} diff --git a/.github/workflows/generate.yml b/.github/workflows/generate.yml index 750ee278..2c9aae59 100644 --- a/.github/workflows/generate.yml +++ b/.github/workflows/generate.yml @@ -1,4 +1,4 @@ -name: Generate Docs +name: Generate and Compare Docs on: push: @@ -15,8 +15,64 @@ permissions: contents: read jobs: + prepare: + runs-on: ubuntu-latest + outputs: + sha: ${{ steps.push.outputs.sha || steps.pr.outputs.sha }} + base-run: ${{ steps.main.outputs.run_id }} + steps: + - name: Harden Runner + uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 + with: + egress-policy: audit + + # If we are running from the main branch (a non-pull_request event), we + # want the latest SHA from nodejs/node + - id: push + if: ${{ github.event_name != 'pull_request' }} + run: | + SHA=$(git ls-remote https://github.com/nodejs/node.git HEAD | awk '{print $1}') + echo "$SHA" > commit + echo "sha=$SHA" >> "$GITHUB_OUTPUT" + + - name: Upload metadata artifact + if: ${{ github.event_name != 'pull_request' }} + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + with: + name: commit + path: commit + + # If we are running from a PR (a pull_request event), we + # want the SHA used by the most recent `push` run + - name: Get latest `main` run + if: ${{ github.event_name == 'pull_request' }} + id: main + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + GH_TOKEN: ${{ github.token }} + run: | + # `174604400` refers to `generate.yml`'s workflow ID, available at https://api.github.com/repos/nodejs/doc-kit/actions/workflows/generate.yml + # The `databaseId` is a given runs run ID (Ref: https://docs.github.com/en/enterprise-cloud@latest/graphql/reference/objects#workflowrun) + ID=$(gh run list --repo $GITHUB_REPOSITORY -c $BASE_SHA -w 174604400 -L 1 --json databaseId --jq ".[].databaseId") + echo "run_id=$ID" >> $GITHUB_OUTPUT + + - name: Download metadata artifact + if: ${{ github.event_name == 'pull_request' }} + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + with: + name: commit + run-id: ${{ steps.main.outputs.run_id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - id: pr + if: ${{ github.event_name == 'pull_request' }} + run: | + SHA=$(cat commit) + echo "sha=$SHA" >> "$GITHUB_OUTPUT" + generate: runs-on: ubuntu-latest + needs: prepare strategy: matrix: include: @@ -32,10 +88,12 @@ jobs: input: './node/doc/api/*.md' - target: legacy-json input: './node/doc/api/*.md' + compare: true - target: legacy-html input: './node/doc/api/*.md' - target: web input: './node/doc/api/*.md' + compare: true - target: llms-txt input: './node/doc/api/*.md' fail-fast: false @@ -56,6 +114,7 @@ jobs: with: persist-credentials: false repository: nodejs/node + ref: ${{ needs.prepare.outputs.sha }} sparse-checkout: | doc/api lib @@ -79,13 +138,27 @@ jobs: node bin/cli.mjs generate \ -t ${{ matrix.target }} \ -i "${{ matrix.input }}" \ - -o "out/${{ matrix.target }}" \ + -o out \ -c ./node/CHANGELOG.md \ --index ./node/doc/api/index.md \ --log-level debug + - name: Download base branch artifact + if: ${{ matrix.compare && needs.prepare.outputs.base-run }} + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + with: + name: ${{ matrix.target }} + path: base + run-id: ${{ needs.prepare.outputs.base-run }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Compare to base branch + if: ${{ matrix.compare && needs.prepare.outputs.base-run }} + run: | + node scripts/compare-builds/${{ matrix.target }}.mjs > out/comparison.txt + - name: Upload ${{ matrix.target }} artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: ${{ matrix.target }} - path: out/${{ matrix.target }} + path: out diff --git a/.github/workflows/leave-comment.yml b/.github/workflows/leave-comment.yml new file mode 100644 index 00000000..610c4007 --- /dev/null +++ b/.github/workflows/leave-comment.yml @@ -0,0 +1,47 @@ +name: Leave a comment + +on: + workflow_run: + workflows: ['Generate and Compare Docs'] + types: [completed] + +permissions: + contents: read + actions: read + pull-requests: write + +jobs: + aggregate: + name: Aggregate Comparison Results + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 + with: + egress-policy: audit + + - name: Download all comparison artifacts + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + with: + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + path: results + + - name: Combine results + id: combine + # Even if the cat fails (no files found), we don't want to fail the workflow + continue-on-error: true + run: | + { + echo "combined<> "$GITHUB_OUTPUT" + + - name: Add Comment to PR + if: steps.combine.outputs.combined + uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b # v3.0.1 + with: + comment-tag: compared + message: ${{ steps.combine.outputs.combined }} + pr-number: ${{ github.event.workflow_run.pull_requests[0].number }} diff --git a/.gitignore b/.gitignore index 4ef2b790..c86ab85e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,9 @@ node_modules npm-debug.log -# Default Output Directory +# Default Output and Comparison Directories out +base # Tests coverage diff --git a/docs/commands.md b/docs/commands.md new file mode 100644 index 00000000..9a78f7af --- /dev/null +++ b/docs/commands.md @@ -0,0 +1,223 @@ +# Creating Commands + +## Command Structure + +Commands in `doc-kit` are defined as modules that export a command object conforming to the `Command` interface: + +```typescript +interface Command { + name: string; + description: string; + options: { [key: string]: Option }; + action: (options: any) => Promise; +} +``` + +Each command consists of: + +- **name**: The command name used in the CLI (e.g., `generate`, `interactive`) +- **description**: A short description shown in help text +- **options**: An object mapping option names to their definitions +- **action**: The async function that executes when the command is run + +## Creating a New Command + +### Step 1: Create the Command File + +Create a new file in `bin/commands/` with your command name: + +```javascript +// bin/commands/my-command.mjs +import logger from '../../src/logger/index.mjs'; + +/** + * @type {import('./types').Command} + */ +export default { + name: 'my-command', + description: 'Does something useful', + + options: { + // Define your options here (see next section) + }, + + async action(opts) { + logger.info('Starting my-command', opts); + + // Your command logic here + + logger.info('Completed my-command'); + }, +}; +``` + +### Step 2: Register the Command + +Add your command to the exports in `bin/commands/index.mjs`: + +```javascript +import generate from './generate.mjs'; +import interactive from './interactive.mjs'; +import myCommand from './my-command.mjs'; // Add this + +export default [ + generate, + interactive, + myCommand, // Add this +]; +``` + +### Step 3: Update CLI Entry Point + +The CLI in `bin/cli.mjs` automatically loads commands from `bin/commands/index.mjs`, so no changes are needed there if you followed step 2. + +## Command Options + +Options define the flags and parameters your command accepts. Each option has: + +```typescript +interface Option { + flags: string[]; // CLI flags (e.g., ['-i', '--input ']) + desc: string; // Description for help text + prompt?: PromptConfig; // Interactive mode configuration +} +``` + +### Defining Options + +```javascript +options: { + input: { + flags: ['-i', '--input '], + desc: 'Input file patterns (glob)', + prompt: { + type: 'text', + message: 'Enter input glob patterns', + variadic: true, + required: true, + }, + }, + + force: { + flags: ['-f', '--force'], + desc: 'Force overwrite existing files', + prompt: { + type: 'confirm', + message: 'Overwrite existing files?', + initialValue: false, + }, + }, + + mode: { + flags: ['-m', '--mode '], + desc: 'Operation mode', + prompt: { + type: 'select', + message: 'Choose operation mode', + options: [ + { label: 'Fast', value: 'fast' }, + { label: 'Thorough', value: 'thorough' }, + ], + }, + }, +} +``` + +### Flag Syntax + +- `` - Required argument +- `[value]` - Optional argument +- `` - Variadic (multiple values) +- `[values...]` - Optional variadic + +### Option Types + +#### `text` + +Single-line text input. + +```javascript +prompt: { + type: 'text', + message: 'Enter a value', + initialValue: 'default', + required: true, +} +``` + +#### `confirm` + +Yes/no confirmation. + +```javascript +prompt: { + type: 'confirm', + message: 'Are you sure?', + initialValue: false, +} +``` + +#### `select` + +Single choice from a list. + +```javascript +prompt: { + type: 'select', + message: 'Choose one', + options: [ + { label: 'Option 1', value: 'opt1' }, + { label: 'Option 2', value: 'opt2' }, + ], +} +``` + +#### `multiselect` + +Multiple choices from a list. + +```javascript +prompt: { + type: 'multiselect', + message: 'Choose multiple', + options: [ + { label: 'Choice A', value: 'a' }, + { label: 'Choice B', value: 'b' }, + ], +} +``` + +## Interactive Prompts + +The `interactive` command automatically uses the `prompt` configuration from your options. When users run: + +```bash +doc-kit interactive +``` + +They'll be prompted to select a command, then guided through all required options. + +### Prompt Configuration + +- **message**: Question to ask the user +- **type**: Input type (`text`, `confirm`, `select`, `multiselect`) +- **required**: Whether the field must have a value +- **initialValue**: Default value +- **variadic**: Whether multiple values can be entered (for `text` type) +- **options**: Choices for `select`/`multiselect` types + +### Making Options Interactive-Friendly + +Always provide helpful messages and sensible defaults: + +```javascript +threads: { + flags: ['-p', '--threads '], + desc: 'Number of threads to use (minimum: 1)', + prompt: { + type: 'text', + message: 'How many threads to allow', + initialValue: String(cpus().length), // Smart default + }, +}, +``` diff --git a/docs/comparators.md b/docs/comparators.md new file mode 100644 index 00000000..d74c4b83 --- /dev/null +++ b/docs/comparators.md @@ -0,0 +1,165 @@ +# Creating Comparators + +This guide explains how to create build comparison scripts for `@nodejs/doc-kit`. Comparators help identify differences between documentation builds, useful for CI/CD and regression testing. + +## Comparator Concepts + +Comparators are scripts that: + +1. **Compare** generated documentation between two builds (base vs. head) +2. **Identify differences** in content, structure, or file size +3. **Report results** in a format suitable for CI/CD systems +4. **Help catch regressions** before merging changes + +### When to Use Comparators + +- **Verify backward compatibility** - Ensure new code produces same output +- **Track file size changes** - Monitor bundle size growth +- **Validate transformations** - Check that refactors don't alter output +- **Debug generation issues** - Understand what changed between versions + +## Comparator Structure + +Comparators are standalone ESM scripts located in `scripts/compare-builds/`: + +``` +scripts/compare-builds/ +├── utils.mjs # Shared utilities (BASE, HEAD paths) +├── legacy-json.mjs # Compare legacy JSON output +├── web.mjs # Compare web bundle sizes +└── your-comparator.mjs # Your new comparator +``` + +### Naming Convention + +**Each comparator must have the same name as the generator it compares.** For example: + +- `web.mjs` compares output from the `web` generator +- `legacy-json.mjs` compares output from the `legacy-json` generator +- `my-format.mjs` would compare output from a `my-format` generator + +## Creating a Comparator + +### Step 1: Create the Comparator File + +Create a new file in `scripts/compare-builds/` with the same name as your generator: + +```javascript +// scripts/compare-builds/my-format.mjs +import { readdir, readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { BASE, HEAD } from './utils.mjs'; + +// Fetch files from both directories +const [baseFiles, headFiles] = await Promise.all([BASE, HEAD].map(() => await readdir(dir))); + +// Find all unique files across both builds +const allFiles = [...new Set([...baseFiles, ...headFiles])]; + +/** + * Compare a single file between base and head + * @param {string} file - Filename to compare + * @returns {Promise} Difference object or null if identical + */ +const compareFile = async file => { + const basePath = join(BASE, file); + const headPath = join(HEAD, file); + + try { + const baseContent = await readFile(basePath, 'utf-8'); + const headContent = await readFile(headPath, 'utf-8'); + + if (baseContent !== headContent) { + return { + file, + type: 'modified', + baseSize: baseContent.length, + headSize: headContent.length, + }; + } + + return null; + } catch (error) { + // File missing in one of the builds + const exists = await Promise.all([ + readFile(basePath, 'utf-8').then(() => true).catch(() => false), + readFile(headPath, 'utf-8').then(() => true).catch(() => false), + ]); + + if (exists[0] && !exists[1]) { + return { file, type: 'removed' }; + } + if (!exists[0] && exists[1]) { + return { file, type: 'added' }; + } + + return { file, type: 'error', error: error.message }; + } +}; + +// Compare all files in parallel +const results = await Promise.all(allFiles.map(compareFile)); + +// Filter out null results (identical files) +const differences = results.filter(Boolean); + +// Output markdown results +if (differences.length > 0) { + console.log('## `my-format` Generator'); + console.log(''); + console.log(`Found ${differences.length} difference(s):`); + console.log(''); + + // Group by type + const added = differences.filter(d => d.type === 'added'); + const removed = differences.filter(d => d.type === 'removed'); + const modified = differences.filter(d => d.type === 'modified'); + + if (added.length) { + console.log('### Added Files'); + console.log(''); + added.forEach(d => console.log(`- \`${d.file}\``)); + console.log(''); + } + + if (removed.length) { + console.log('### Removed Files'); + console.log(''); + removed.forEach(d => console.log(`- \`${d.file}\``)); + console.log(''); + } + + if (modified.length) { + console.log('### Modified Files'); + console.log(''); + console.log('| File | Base Size | Head Size | Diff |'); + console.log('|-|-|-|-|'); + modified.forEach(({ file, baseSize, headSize }) => { + const diff = headSize - baseSize; + const sign = diff > 0 ? '+' : ''; + console.log(`| \`${file}\` | ${baseSize} | ${headSize} | ${sign}${diff} |`); + }); + console.log(''); + } +} +``` + +### Step 2: Test Locally + +Run your comparator locally to verify it works: + +```bash +# Set up BASE and HEAD directories +export BASE=path/to/base/output +export HEAD=path/to/head/output + +# Run the comparator +node scripts/compare-builds/my-format.mjs +``` + +### Step 3: Integrate with CI/CD + +The comparator will automatically run in GitHub Actions when: + +1. Your generator is configured with `compare: true` in the workflow +2. The comparator filename matches the generator name diff --git a/docs/generators.md b/docs/generators.md new file mode 100644 index 00000000..42836f4c --- /dev/null +++ b/docs/generators.md @@ -0,0 +1,427 @@ +# Creating Generators + +This guide explains how to create new documentation generators for `@nodejs/doc-kit`. + +## Generator Concepts + +Generators in `doc-kit` transform API documentation through a pipeline. Each generator: + +1. **Takes input** from a previous generator or raw files +2. **Processes the data** into a different format +3. **Yields output** for the next generator or final output + +### Generator Pipeline + +``` +Raw Markdown Files + ↓ + [ast] - Parse to MDAST + ↓ + [metadata] - Extract structured metadata + ↓ + [jsx-ast] - Convert to JSX AST + ↓ + [web] - Generate HTML/CSS/JS bundles +``` + +Each generator declares its dependency using the `dependsOn` field, allowing automatic pipeline construction. + +## Generator Structure + +A generator is defined as a module exporting an object conforming to the `GeneratorMetadata` interface: + +```typescript +interface GeneratorMetadata { + name: string; + version: string; + description: string; + dependsOn?: string; + + // Core generation function + generate( + input: Input, + options: Partial + ): Promise | AsyncGenerator; + + // Optional: for parallel processing + processChunk?( + fullInput: any, + itemIndices: number[], + deps: any + ): Promise; +} +``` + +## Creating a Basic Generator + +### Step 1: Create the Generator File + +Create a new directory in `src/generators/`: + +``` +src/generators/my-format/ +├── index.mjs # Main generator file +├── constants.mjs # Constants (optional) +├── types.d.ts # TypeScript types (optional) +└── utils/ # Utility functions (optional) + └── formatter.mjs +``` + +### Step 2: Implement the Generator + +```javascript +// src/generators/my-format/index.mjs +import { writeFile } from 'node:fs/promises'; +import { join } from 'node:path'; + +/** + * Generates output in MyFormat. + * + * @typedef {Array} Input + * @typedef {string} Output + * + * @type {GeneratorMetadata} + */ +export default { + name: 'my-format', + + version: '1.0.0', + + description: 'Generates documentation in MyFormat', + + // This generator depends on the metadata generator + dependsOn: 'metadata', + + /** + * Main generation function + * + * @param {Input} input - Metadata entries from previous generator + * @param {Partial} options - Configuration + * @returns {Promise} + */ + async generate(input, { output, version }) { + // Transform input to your format + const result = transformToMyFormat(input, version); + + // Write to file if output directory specified + if (output) { + await writeFile(join(output, 'documentation.myformat'), result, 'utf-8'); + } + + return result; + }, +}; + +/** + * Transform metadata entries to MyFormat + * @param {Input} entries + * @param {import('semver').SemVer} version + * @returns {string} + */ +function transformToMyFormat(entries, version) { + // Your transformation logic here + return entries + .map(entry => `${entry.api}: ${entry.heading.data.name}`) + .join('\n'); +} +``` + +### Step 3: Register the Generator + +Add your generator to the exports in `src/generators/index.mjs`: + +```javascript +// For public generators (available via CLI) +import myFormat from './my-format/index.mjs'; + +export const publicGenerators = { + 'json-simple': jsonSimple, + 'my-format': myFormat, // Add this + // ... other generators +}; + +// For internal generators (used only as dependencies) +const internalGenerators = { + ast, + metadata, + // ... internal generators +}; +``` + +## Parallel Processing with Workers + +For generators processing large datasets, implement parallel processing using worker threads. + +### Implementing Worker-Based Processing + +```javascript +export default { + name: 'parallel-generator', + version: '1.0.0', + description: 'Processes data in parallel', + dependsOn: 'metadata', + + /** + * Process a chunk of items in a worker thread. + * This function runs in isolated worker threads. + * + * @param {Array} fullInput - Complete input array + * @param {number[]} itemIndices - Indices of items to process + * @param {Object} deps - Serializable dependencies + * @returns {Promise>} + */ + async processChunk(fullInput, itemIndices, deps) { + const results = []; + + // Process only the items at specified indices + for (const idx of itemIndices) { + const item = fullInput[idx]; + const result = await processItem(item, deps); + results.push(result); + } + + return results; + }, + + /** + * Main generation function that orchestrates worker threads + * + * @param {Input} input + * @param {Partial} options + */ + async *generate(input, { worker, output }) { + // Prepare serializable dependencies + const deps = { + version: options.version, + ...someConfig, + }; + + // Stream chunks as they complete + for await (const chunkResult of worker.stream(input, input, deps)) { + // Process chunk result if needed + yield chunkResult; + } + }, +}; +``` + +### Key Points for Worker Processing + +1. **`processChunk` executes in worker threads** - No access to main thread state +2. **Only serializable data** can be passed to workers (no functions, classes, etc.) +3. **`fullInput` and `itemIndices`** - Workers receive full input but only process specified indices +4. **`deps` must be serializable** - Pass only JSON-compatible data + +### When to Use Workers + +Use parallel processing when: + +- Processing many independent items (files, modules, entries) +- Each item takes significant time to process +- Operations are CPU-intensive + +Don't use workers when: + +- Items have dependencies on each other +- Output must be in specific order +- Operation is I/O bound rather than CPU bound + +## Streaming Results + +Generators can yield results as they're produced using async generators: + +```javascript +export default { + name: 'streaming-generator', + version: '1.0.0', + description: 'Streams results as they are ready', + dependsOn: 'metadata', + + async processChunk(fullInput, itemIndices, deps) { + // Process chunk + return results; + }, + + /** + * Generator function that yields results incrementally + */ + async *generate(input, options) { + const { worker } = options; + + // Stream results as workers complete chunks + for await (const chunkResult of worker.stream(input, input, {})) { + // Yield immediately - downstream can start processing + yield chunkResult; + } + }, +}; +``` + +### Benefits of Streaming + +- **Reduced memory usage** - Process data in chunks +- **Earlier downstream starts** - Next generator can begin before this one finishes +- **Better parallelism** - Multiple generators can work simultaneously + +### Non-Streaming Generators + +Some generators must collect all input before processing: + +```javascript +export default { + name: 'batch-generator', + version: '1.0.0', + description: 'Requires all input at once', + dependsOn: 'jsx-ast', + + /** + * Non-streaming - returns Promise instead of AsyncGenerator + */ + async generate(input, options) { + // Collect all input (if dependency is streaming, this waits for completion) + const allData = await collectAll(input); + + // Process everything together + const result = processBatch(allData); + + return result; + }, +}; +``` + +Use non-streaming when: + +- You need all data to make decisions (e.g., code splitting, global analysis) +- Output format requires complete dataset +- Cross-references between items need resolution + +## Generator Dependencies + +### Declaring Dependencies + +```javascript +export default { + name: 'my-generator', + dependsOn: 'metadata', // This generator requires metadata output + + async generate(input, options) { + // input contains the output from 'metadata' generator + }, +}; +``` + +### Dependency Chain Example + +```javascript +// Step 1: Parse markdown to AST +export default { + name: 'ast', + dependsOn: undefined, // No dependency + // Processes raw markdown files +}; + +// Step 2: Extract metadata from AST +export default { + name: 'metadata', + dependsOn: 'ast', // Depends on AST + // Processes AST output +}; + +// Step 3: Generate HTML from metadata +export default { + name: 'html-generator', + dependsOn: 'metadata', // Depends on metadata + // Processes metadata output +}; +``` + +### Multiple Consumers + +Multiple generators can depend on the same generator: + +``` + metadata + ↙ ↓ ↘ + html json man-page +``` + +The framework ensures `metadata` runs once and its output is cached for all consumers. + +## File Output + +### Writing Output Files + +```javascript +import { writeFile, mkdir } from 'node:fs/promises'; +import { join } from 'node:path'; + +async generate(input, options) { + const { output } = options; + + if (!output) { + // Return data without writing + return result; + } + + // Ensure directory exists + await mkdir(output, { recursive: true }); + + // Write single file + await writeFile( + join(output, 'output.txt'), + content, + 'utf-8' + ); + + // Write multiple files + for (const item of items) { + await writeFile( + join(output, `${item.name}.txt`), + item.content, + 'utf-8' + ); + } + + return result; +} +``` + +### Copying Assets + +```javascript +import { cp } from 'node:fs/promises'; +import { join } from 'node:path'; + +async generate(input, options) { + const { output } = options; + + if (output) { + // Copy asset directory + await cp( + new URL('./assets', import.meta.url), + join(output, 'assets'), + { recursive: true } + ); + } + + return result; +} +``` + +### Output Structure + +Organize output clearly: + +``` +output/ +├── index.html +├── api/ +│ ├── fs.html +│ ├── http.html +│ └── path.html +├── assets/ +│ ├── style.css +│ └── script.js +└── data/ + └── search-index.json +``` diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index aa327fb6..44c4badc 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -18,11 +18,9 @@ "acorn": "^8.15.0", "commander": "^14.0.2", "dedent": "^1.7.0", - "eslint-plugin-react-x": "^2.3.12", "estree-util-to-js": "^2.0.0", "estree-util-visit": "^2.0.0", "github-slugger": "^2.0.0", - "glob": "^13.0.0", "globals": "^16.5.0", "hast-util-to-string": "^3.0.1", "hastscript": "^9.0.1", @@ -43,7 +41,7 @@ "rolldown": "^1.0.0-beta.53", "semver": "^7.7.3", "shiki": "^3.19.0", - "to-vfile": "^8.0.0", + "tinyglobby": "^0.2.15", "unified": "^11.0.5", "unist-builder": "^4.0.0", "unist-util-find-after": "^5.0.0", @@ -67,6 +65,7 @@ "eslint-import-resolver-node": "^0.3.9", "eslint-plugin-import-x": "^4.16.1", "eslint-plugin-jsdoc": "^61.4.1", + "eslint-plugin-react-x": "^2.3.13", "husky": "^9.1.7", "lint-staged": "^16.2.7", "prettier": "3.7.4" @@ -212,6 +211,7 @@ "version": "4.9.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" @@ -230,6 +230,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -242,21 +243,23 @@ "version": "4.12.1", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint-react/ast": { - "version": "2.3.12", - "resolved": "https://registry.npmjs.org/@eslint-react/ast/-/ast-2.3.12.tgz", - "integrity": "sha512-2wlRvqS4dxleGlL4Gp3Bh5PNb47wnAEa99CsGppzWCFXSPvm3d/bM5nJPvOwQOF53+PGa6xq1ZqwGh70zL7+zw==", + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@eslint-react/ast/-/ast-2.3.13.tgz", + "integrity": "sha512-OP2rOhHYLx2nfd9uA9uACKZJN9z9rX9uuAMx4PjT75JNOdYr1GgqWQZcYCepyJ+gmVNCyiXcLXuyhavqxCSM8Q==", + "dev": true, "license": "MIT", "dependencies": { - "@eslint-react/eff": "2.3.12", - "@typescript-eslint/types": "^8.48.1", - "@typescript-eslint/typescript-estree": "^8.48.1", - "@typescript-eslint/utils": "^8.48.1", + "@eslint-react/eff": "2.3.13", + "@typescript-eslint/types": "^8.49.0", + "@typescript-eslint/typescript-estree": "^8.49.0", + "@typescript-eslint/utils": "^8.49.0", "string-ts": "^2.3.1" }, "engines": { @@ -268,18 +271,19 @@ } }, "node_modules/@eslint-react/core": { - "version": "2.3.12", - "resolved": "https://registry.npmjs.org/@eslint-react/core/-/core-2.3.12.tgz", - "integrity": "sha512-Q3w6f0WfVyIJriJa+tYHS4rmVQ3nwnubCH7o/VYlBCR3qczpvpvkCi2XK4clU/7vpVwHbbaXGICAbJu7tNZqoQ==", - "license": "MIT", - "dependencies": { - "@eslint-react/ast": "2.3.12", - "@eslint-react/eff": "2.3.12", - "@eslint-react/shared": "2.3.12", - "@eslint-react/var": "2.3.12", - "@typescript-eslint/scope-manager": "^8.48.1", - "@typescript-eslint/types": "^8.48.1", - "@typescript-eslint/utils": "^8.48.1", + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@eslint-react/core/-/core-2.3.13.tgz", + "integrity": "sha512-4bWBE+1kApuxJKIrLJH2FuFtCbM4fXfDs6Ou8MNamGoX6hdynlntssvaMZTd/lk/L8dt01H/3btr7xBX4+4BNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/ast": "2.3.13", + "@eslint-react/eff": "2.3.13", + "@eslint-react/shared": "2.3.13", + "@eslint-react/var": "2.3.13", + "@typescript-eslint/scope-manager": "^8.49.0", + "@typescript-eslint/types": "^8.49.0", + "@typescript-eslint/utils": "^8.49.0", "birecord": "^0.1.1", "ts-pattern": "^5.9.0" }, @@ -292,22 +296,24 @@ } }, "node_modules/@eslint-react/eff": { - "version": "2.3.12", - "resolved": "https://registry.npmjs.org/@eslint-react/eff/-/eff-2.3.12.tgz", - "integrity": "sha512-QjFENG1VGVrD67YFc0yiLm9zef2kTeXZGux4hlMjGLnxTHnn0tPx4T/xGzh5C1WRmolcNeIzjVWMqSngFrTphQ==", + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@eslint-react/eff/-/eff-2.3.13.tgz", + "integrity": "sha512-byXsssozwh3VaiqcOonAKQgLXgpMVNSxBWFjdfbNhW7+NttorSt950qtiw+P7A9JoRab1OuGYk4MDY5UVBno8Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=20.19.0" } }, "node_modules/@eslint-react/shared": { - "version": "2.3.12", - "resolved": "https://registry.npmjs.org/@eslint-react/shared/-/shared-2.3.12.tgz", - "integrity": "sha512-mIgxjEwKOknJabbQs/bxvkEhKitJnET0QDc0a89pFx36DBLJIEvdcGMCDEXFgtgjDV/WwMxIava/+coE6T3Dyw==", + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@eslint-react/shared/-/shared-2.3.13.tgz", + "integrity": "sha512-ESE7dVeOXtem3K6BD6k2wJaFt35kPtTT9SWCL99LFk7pym4OEGoMxPcyB2R7PMWiVudwl63BmiOgQOdaFYPONg==", + "dev": true, "license": "MIT", "dependencies": { - "@eslint-react/eff": "2.3.12", - "@typescript-eslint/utils": "^8.48.1", + "@eslint-react/eff": "2.3.13", + "@typescript-eslint/utils": "^8.49.0", "ts-pattern": "^5.9.0", "zod": "^4.1.13" }, @@ -320,25 +326,27 @@ } }, "node_modules/@eslint-react/shared/node_modules/zod": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", - "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.2.1.tgz", + "integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, "node_modules/@eslint-react/var": { - "version": "2.3.12", - "resolved": "https://registry.npmjs.org/@eslint-react/var/-/var-2.3.12.tgz", - "integrity": "sha512-jjgeRcop74NTzWzCF8rBN1H5avdSDLEOALJjwmYWOdxoSUNGO7OIeM/pZvHZ7G36kHDuD619P2JauCVM2/c+7A==", + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@eslint-react/var/-/var-2.3.13.tgz", + "integrity": "sha512-BozBfUZkzzobD6x/M8XERAnZQ3UvZPsD49zTGFKKU9M/bgsM78HwzxAPLkiu88W55v3sO/Kqf8fQTXT4VEeZ/g==", + "dev": true, "license": "MIT", "dependencies": { - "@eslint-react/ast": "2.3.12", - "@eslint-react/eff": "2.3.12", - "@typescript-eslint/scope-manager": "^8.48.1", - "@typescript-eslint/types": "^8.48.1", - "@typescript-eslint/utils": "^8.48.1", + "@eslint-react/ast": "2.3.13", + "@eslint-react/eff": "2.3.13", + "@typescript-eslint/scope-manager": "^8.49.0", + "@typescript-eslint/types": "^8.49.0", + "@typescript-eslint/utils": "^8.49.0", "ts-pattern": "^5.9.0" }, "engines": { @@ -353,6 +361,7 @@ "version": "0.21.1", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.7", @@ -367,6 +376,7 @@ "version": "0.4.2", "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/core": "^0.17.0" @@ -379,6 +389,7 @@ "version": "0.17.0", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" @@ -391,6 +402,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, "license": "MIT", "dependencies": { "ajv": "^6.12.4", @@ -414,6 +426,7 @@ "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -426,6 +439,7 @@ "version": "9.39.1", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -438,6 +452,7 @@ "version": "2.1.7", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -447,6 +462,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/core": "^0.17.0", @@ -516,6 +532,7 @@ "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18.0" @@ -525,6 +542,7 @@ "version": "0.16.6", "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.1", @@ -538,6 +556,7 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18" @@ -551,6 +570,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=12.22" @@ -564,6 +584,7 @@ "version": "0.4.2", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18" @@ -573,27 +594,6 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1068,7 +1068,6 @@ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "license": "MIT", - "peer": true, "engines": { "node": "^14.21.3 || >=16" }, @@ -1169,7 +1168,6 @@ "resolved": "https://registry.npmjs.org/@orama/cuid2/-/cuid2-2.2.3.tgz", "integrity": "sha512-Lcak3chblMejdlSHgYU2lS2cdOhDpU6vkfIJH4m+YKvqQyLqs1bB8+w6NT1MG5bO12NUK2GFc34Mn2xshMIQ1g==", "license": "MIT", - "peer": true, "dependencies": { "@noble/hashes": "^1.1.5" } @@ -1187,8 +1185,7 @@ "version": "0.0.5", "resolved": "https://registry.npmjs.org/@orama/oramacore-events-parser/-/oramacore-events-parser-0.0.5.tgz", "integrity": "sha512-yAuSwog+HQBAXgZ60TNKEwu04y81/09mpbYBCmz1RCxnr4ObNY2JnPZI7HmALbjAhLJ8t5p+wc2JHRK93ubO4w==", - "license": "AGPL-3.0", - "peer": true + "license": "AGPL-3.0" }, "node_modules/@orama/stopwords": { "version": "3.1.16", @@ -2917,6 +2914,7 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, "license": "MIT" }, "node_modules/@types/mdast": { @@ -2961,13 +2959,14 @@ "license": "MIT" }, "node_modules/@typescript-eslint/project-service": { - "version": "8.48.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.48.1.tgz", - "integrity": "sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.0.tgz", + "integrity": "sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ==", + "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.48.1", - "@typescript-eslint/types": "^8.48.1", + "@typescript-eslint/tsconfig-utils": "^8.50.0", + "@typescript-eslint/types": "^8.50.0", "debug": "^4.3.4" }, "engines": { @@ -2982,13 +2981,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.48.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.1.tgz", - "integrity": "sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.0.tgz", + "integrity": "sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A==", + "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.48.1", - "@typescript-eslint/visitor-keys": "8.48.1" + "@typescript-eslint/types": "8.50.0", + "@typescript-eslint/visitor-keys": "8.50.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2999,9 +2999,10 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.48.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.1.tgz", - "integrity": "sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.0.tgz", + "integrity": "sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w==", + "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3015,14 +3016,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.48.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.48.1.tgz", - "integrity": "sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.50.0.tgz", + "integrity": "sha512-7OciHT2lKCewR0mFoBrvZJ4AXTMe/sYOe87289WAViOocEmDjjv8MvIOT2XESuKj9jp8u3SZYUSh89QA4S1kQw==", + "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.48.1", - "@typescript-eslint/typescript-estree": "8.48.1", - "@typescript-eslint/utils": "8.48.1", + "@typescript-eslint/types": "8.50.0", + "@typescript-eslint/typescript-estree": "8.50.0", + "@typescript-eslint/utils": "8.50.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -3039,9 +3041,10 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.48.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.1.tgz", - "integrity": "sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.0.tgz", + "integrity": "sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w==", + "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3052,15 +3055,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.48.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.1.tgz", - "integrity": "sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.0.tgz", + "integrity": "sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ==", + "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.48.1", - "@typescript-eslint/tsconfig-utils": "8.48.1", - "@typescript-eslint/types": "8.48.1", - "@typescript-eslint/visitor-keys": "8.48.1", + "@typescript-eslint/project-service": "8.50.0", + "@typescript-eslint/tsconfig-utils": "8.50.0", + "@typescript-eslint/types": "8.50.0", + "@typescript-eslint/visitor-keys": "8.50.0", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", @@ -3082,6 +3086,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -3091,6 +3096,7 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -3103,15 +3109,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.48.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.1.tgz", - "integrity": "sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.50.0.tgz", + "integrity": "sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.48.1", - "@typescript-eslint/types": "8.48.1", - "@typescript-eslint/typescript-estree": "8.48.1" + "@typescript-eslint/scope-manager": "8.50.0", + "@typescript-eslint/types": "8.50.0", + "@typescript-eslint/typescript-estree": "8.50.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3126,12 +3134,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.48.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.1.tgz", - "integrity": "sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.0.tgz", + "integrity": "sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==", + "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.48.1", + "@typescript-eslint/types": "8.50.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -3495,6 +3504,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3515,6 +3525,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -3560,6 +3571,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -3585,6 +3597,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, "license": "Python-2.0" }, "node_modules/aria-hidden": { @@ -3622,12 +3635,14 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, "license": "MIT" }, "node_modules/birecord": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/birecord/-/birecord-0.1.1.tgz", "integrity": "sha512-VUpsf/qykW0heRlC8LooCq28Kxn3mAqKohhDG/49rrsQ1dT1CXyj/pgXS+5BSRzFTR/3DyIBOqQOrGyZOh71Aw==", + "dev": true, "license": "(MIT OR Apache-2.0)" }, "node_modules/boolbase": { @@ -3640,6 +3655,7 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -3697,6 +3713,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -3716,6 +3733,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -3916,6 +3934,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -3928,6 +3947,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, "license": "MIT" }, "node_modules/colorette": { @@ -3970,12 +3990,14 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", + "dev": true, "license": "MIT" }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, "license": "MIT" }, "node_modules/convert-source-map": { @@ -3989,6 +4011,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -4030,8 +4053,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/debug": { "version": "4.4.3", @@ -4081,6 +4103,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, "license": "MIT" }, "node_modules/dequal": { @@ -4218,6 +4241,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -4230,7 +4254,9 @@ "version": "9.39.1", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4423,20 +4449,21 @@ } }, "node_modules/eslint-plugin-react-x": { - "version": "2.3.12", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-x/-/eslint-plugin-react-x-2.3.12.tgz", - "integrity": "sha512-G9ThX5LZQun3243JN/UchMbGPra9ZL1D7Wi4dwaIgqh26nRK8W6LBqRTJC+jlrmOanosg+flcxpUyFS/N+Ch7A==", - "license": "MIT", - "dependencies": { - "@eslint-react/ast": "2.3.12", - "@eslint-react/core": "2.3.12", - "@eslint-react/eff": "2.3.12", - "@eslint-react/shared": "2.3.12", - "@eslint-react/var": "2.3.12", - "@typescript-eslint/scope-manager": "^8.48.1", - "@typescript-eslint/type-utils": "^8.48.1", - "@typescript-eslint/types": "^8.48.1", - "@typescript-eslint/utils": "^8.48.1", + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-x/-/eslint-plugin-react-x-2.3.13.tgz", + "integrity": "sha512-+m+V/5VLMxgx0VsFUUyflMNLQG0WFYspsfv0XJFqx7me3A2b3P20QatNDHQCYswz0PRbRFqinTPukPRhZh68ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/ast": "2.3.13", + "@eslint-react/core": "2.3.13", + "@eslint-react/eff": "2.3.13", + "@eslint-react/shared": "2.3.13", + "@eslint-react/var": "2.3.13", + "@typescript-eslint/scope-manager": "^8.49.0", + "@typescript-eslint/type-utils": "^8.49.0", + "@typescript-eslint/types": "^8.49.0", + "@typescript-eslint/utils": "^8.49.0", "compare-versions": "^6.1.1", "is-immutable-type": "^5.0.1", "string-ts": "^2.3.1", @@ -4455,6 +4482,7 @@ "version": "8.4.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -4470,6 +4498,7 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -4481,6 +4510,7 @@ "version": "10.4.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", @@ -4497,6 +4527,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" @@ -4509,6 +4540,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -4520,6 +4552,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -4590,6 +4623,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" @@ -4612,24 +4646,28 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, "license": "MIT" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, "license": "MIT" }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, "license": "MIT", "dependencies": { "flat-cache": "^4.0.0" @@ -4655,6 +4693,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, "license": "MIT", "dependencies": { "locate-path": "^6.0.0", @@ -4671,6 +4710,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", @@ -4684,6 +4724,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true, "license": "ISC" }, "node_modules/foreground-child": { @@ -4763,27 +4804,11 @@ "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", "license": "ISC" }, - "node_modules/glob": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz", - "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==", - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.1.1", - "minipass": "^7.1.2", - "path-scurry": "^2.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -4792,21 +4817,6 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/globals": { "version": "16.5.0", "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", @@ -4829,6 +4839,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5117,6 +5128,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -5126,6 +5138,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -5142,6 +5155,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -5207,6 +5221,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -5232,6 +5247,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -5254,6 +5270,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/is-immutable-type/-/is-immutable-type-5.0.1.tgz", "integrity": "sha512-LkHEOGVZZXxGl8vDs+10k3DvP++SEoYEAJLRk6buTFi6kD7QekThV7xHS0j6gpnUCQ0zpud/gMDGiV4dQneLTg==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "@typescript-eslint/type-utils": "^8.0.0", @@ -5291,6 +5308,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { @@ -5345,6 +5363,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -5367,24 +5386,28 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, "license": "MIT" }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, "license": "MIT", "dependencies": { "json-buffer": "3.0.1" @@ -5394,6 +5417,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", @@ -5755,6 +5779,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, "license": "MIT", "dependencies": { "p-locate": "^5.0.0" @@ -5770,6 +5795,7 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, "license": "MIT" }, "node_modules/log-update": { @@ -5858,15 +5884,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/lru-cache": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", - "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", - "license": "ISC", - "engines": { - "node": "20 || >=22" - } - }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -6783,6 +6800,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -6795,6 +6813,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" @@ -6855,6 +6874,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, "license": "MIT" }, "node_modules/nth-check": { @@ -6913,6 +6933,7 @@ "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, "license": "MIT", "dependencies": { "deep-is": "^0.1.3", @@ -6930,6 +6951,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -6945,6 +6967,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, "license": "MIT", "dependencies": { "p-limit": "^3.0.2" @@ -6967,6 +6990,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -7033,6 +7057,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7042,6 +7067,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7054,22 +7080,6 @@ "dev": true, "license": "MIT" }, - "node_modules/path-scurry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", - "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -7133,6 +7143,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -7182,6 +7193,7 @@ "resolved": "https://registry.npmjs.org/preact/-/preact-11.0.0-beta.0.tgz", "integrity": "sha512-IcODoASASYwJ9kxz7+MJeiJhvLriwSb4y4mHIyxdgaRZp6kPUud7xytrk/6GZw8U3y6EFJaRb5wi9SrEK+8+lg==", "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -7200,6 +7212,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8.0" @@ -7244,6 +7257,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -7608,6 +7622,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -7681,8 +7696,7 @@ "version": "0.26.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/semver": { "version": "7.7.3", @@ -7700,6 +7714,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -7712,6 +7727,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7915,6 +7931,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/string-ts/-/string-ts-2.3.1.tgz", "integrity": "sha512-xSJq+BS52SaFFAVxuStmx6n5aYZU571uYUnUrPXkPFCfdHyZMMlbP2v2Wx5sNBnAVzq/2+0+mcBLBa3Xa5ubYw==", + "dev": true, "license": "MIT" }, "node_modules/string-width": { @@ -8049,6 +8066,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8079,6 +8097,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -8271,6 +8290,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -8308,19 +8328,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/to-vfile": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/to-vfile/-/to-vfile-8.0.0.tgz", - "integrity": "sha512-IcmH1xB5576MJc9qcfEC/m/nQCFt3fzMHz45sSlgJyTWjRbKW1HAkJpuf3DgE57YzIlZcwcBZA5ENQbBo4aLkg==", - "license": "MIT", - "dependencies": { - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -8345,6 +8352,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=18.12" @@ -8357,6 +8365,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/ts-declaration-location/-/ts-declaration-location-1.0.7.tgz", "integrity": "sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA==", + "dev": true, "funding": [ { "type": "ko-fi", @@ -8379,6 +8388,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -8391,6 +8401,7 @@ "version": "5.9.0", "resolved": "https://registry.npmjs.org/ts-pattern/-/ts-pattern-5.9.0.tgz", "integrity": "sha512-6s5V71mX8qBUmlgbrfL33xDUwO0fq48rxAu2LBE11WBeGdpCPOsXksQbZJHvHwhrd3QjUusd3mAOM5Gg0mFBLg==", + "dev": true, "license": "MIT" }, "node_modules/tslib": { @@ -8430,6 +8441,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" @@ -8636,6 +8648,7 @@ "integrity": "sha512-VUyWiTNQD7itdiMuJy+EuLEErLj3uwX/EpHQF8EOf33Dq3Ju6VW1GXm+swk6+1h7a49uv9fKZ+dft9jU7esdLA==", "dev": true, "hasInstallScript": true, + "peer": true, "dependencies": { "napi-postinstall": "^0.2.4" }, @@ -8668,6 +8681,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -8801,6 +8815,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -8816,6 +8831,7 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -9039,6 +9055,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -9062,7 +9079,6 @@ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", "license": "ISC", - "peer": true, "peerDependencies": { "zod": "^3.24.1" } diff --git a/package.json b/package.json index 86b48ed0..197fcaa6 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "eslint-import-resolver-node": "^0.3.9", "eslint-plugin-import-x": "^4.16.1", "eslint-plugin-jsdoc": "^61.4.1", + "eslint-plugin-react-x": "^2.3.13", "husky": "^9.1.7", "lint-staged": "^16.2.7", "prettier": "3.7.4" @@ -50,11 +51,9 @@ "acorn": "^8.15.0", "commander": "^14.0.2", "dedent": "^1.7.0", - "eslint-plugin-react-x": "^2.3.12", "estree-util-to-js": "^2.0.0", "estree-util-visit": "^2.0.0", "github-slugger": "^2.0.0", - "glob": "^13.0.0", "globals": "^16.5.0", "hast-util-to-string": "^3.0.1", "hastscript": "^9.0.1", @@ -75,7 +74,7 @@ "rolldown": "^1.0.0-beta.53", "semver": "^7.7.3", "shiki": "^3.19.0", - "to-vfile": "^8.0.0", + "tinyglobby": "^0.2.15", "unified": "^11.0.5", "unist-builder": "^4.0.0", "unist-util-find-after": "^5.0.0", diff --git a/scripts/compare-builds/legacy-json.mjs b/scripts/compare-builds/legacy-json.mjs new file mode 100644 index 00000000..f221e814 --- /dev/null +++ b/scripts/compare-builds/legacy-json.mjs @@ -0,0 +1,34 @@ +import assert from 'node:assert'; +import { readdir, readFile } from 'node:fs/promises'; +import { join } from 'node:path'; + +import { BASE, HEAD } from './utils.mjs'; + +const files = await readdir(BASE); + +export const details = (summary, diff) => + `
\n${summary}\n\n\`\`\`diff\n${diff}\n\`\`\`\n\n
`; + +const getFileDiff = async file => { + const basePath = join(BASE, file); + const headPath = join(HEAD, file); + + const baseContent = JSON.parse(await readFile(basePath, 'utf-8')); + const headContent = JSON.parse(await readFile(headPath, 'utf-8')); + + try { + assert.deepStrictEqual(baseContent, headContent); + return null; + } catch ({ message }) { + return details(file, message); + } +}; + +const results = await Promise.all(files.map(getFileDiff)); + +const filteredResults = results.filter(Boolean); + +if (filteredResults.length) { + console.log('## `legacy-json` generator'); + console.log(filteredResults.join('\n')); +} diff --git a/scripts/compare-builds/utils.mjs b/scripts/compare-builds/utils.mjs new file mode 100644 index 00000000..5c1d4079 --- /dev/null +++ b/scripts/compare-builds/utils.mjs @@ -0,0 +1,4 @@ +import { fileURLToPath } from 'node:url'; + +export const BASE = fileURLToPath(import.meta.resolve('../../base')); +export const HEAD = fileURLToPath(import.meta.resolve('../../out')); diff --git a/scripts/compare-builds/web.mjs b/scripts/compare-builds/web.mjs index f8ce2a3a..9081d7b9 100644 --- a/scripts/compare-builds/web.mjs +++ b/scripts/compare-builds/web.mjs @@ -1,9 +1,8 @@ import { stat, readdir } from 'node:fs/promises'; import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -const BASE = fileURLToPath(import.meta.resolve('../../out/base')); -const HEAD = fileURLToPath(import.meta.resolve('../../out/head')); +import { BASE, HEAD } from './utils.mjs'; + const UNITS = ['B', 'KB', 'MB', 'GB']; /** @@ -21,131 +20,51 @@ const formatBytes = bytes => { }; /** - * Formats the difference between base and head sizes - * @param {number} base - Base file size in bytes - * @param {number} head - Head file size in bytes - * @returns {string} Formatted diff string (e.g., "+1.50 KB (+10.00%)") - */ -const formatDiff = (base, head) => { - const diff = head - base; - const sign = diff > 0 ? '+' : ''; - const percent = base ? `${sign}${((diff / base) * 100).toFixed(2)}%` : 'N/A'; - return `${sign}${formatBytes(diff)} (${percent})`; -}; - -/** - * Gets all files in a directory with their stats - * @param {string} dir - Directory path to search - * @returns {Promise>} Map of filename to size + * Gets all files in a directory with their sizes + * @param {string} dir - Directory path to scan + * @returns {Promise>} Map of filename to size in bytes */ -const getDirectoryStats = async dir => { +const getStats = async dir => { const files = await readdir(dir); - const entries = await Promise.all( - files.map(async file => [file, (await stat(path.join(dir, file))).size]) - ); - return new Map(entries); -}; - -/** - * Generates a table row for a file - * @param {string} file - Filename - * @param {number} baseSize - Base size in bytes - * @param {number} headSize - Head size in bytes - * @returns {string} Markdown table row - */ -const generateRow = (file, baseSize, headSize) => { - const baseCol = formatBytes(baseSize); - const headCol = formatBytes(headSize); - const diffCol = formatDiff(baseSize, headSize); - - return `| \`${file}\` | ${baseCol} | ${headCol} | ${diffCol} |`; -}; - -/** - * Generates a markdown table - * @param {string[]} files - List of files - * @param {Map} baseStats - Base stats map - * @param {Map} headStats - Head stats map - * @returns {string} Markdown table - */ -const generateTable = (files, baseStats, headStats) => { - const header = '| File | Base | Head | Diff |\n|------|------|------|------|'; - const rows = files.map(f => - generateRow(f, baseStats.get(f), headStats.get(f)) + return new Map( + await Promise.all( + files.map(async f => [f, (await stat(path.join(dir, f))).size]) + ) ); - return `${header}\n${rows.join('\n')}`; }; -/** - * Wraps content in a details/summary element - * @param {string} summary - Summary text - * @param {string} content - Content to wrap - * @returns {string} Markdown details element - */ -const details = (summary, content) => - `
\n${summary}\n\n${content}\n\n
`; - -const [baseStats, headStats] = await Promise.all( - [BASE, HEAD].map(getDirectoryStats) -); - -const allFiles = Array.from( - new Set([...baseStats.keys(), ...headStats.keys()]) -); - -// Filter to only changed files (exist in both and have different sizes) -const changedFiles = allFiles.filter( - f => - baseStats.has(f) && - headStats.has(f) && - baseStats.get(f) !== headStats.get(f) -); - -if (changedFiles.length) { - // Separate HTML files and their matching JS files from other files - const pages = []; - const other = []; - - // Get all HTML base names - const htmlBaseNames = new Set( - changedFiles - .filter(f => path.extname(f) === '.html') - .map(f => path.basename(f, '.html')) - ); - - for (const file of changedFiles) { - const ext = path.extname(file); - const basename = path.basename(file, ext); - - // All HTML files go to pages - if (ext === '.html') { - pages.push(file); - } - // JS files go to pages only if they have a matching HTML file - else if (ext === '.js' && htmlBaseNames.has(basename)) { - pages.push(file); - } - // Everything else goes to other - else { - other.push(file); - } - } - - pages.sort(); - other.sort(); - - console.log('## Web Generator\n'); - - if (other.length) { - console.log(generateTable(other, baseStats, headStats)); - } - - if (pages.length) { - console.log( - details( - `Pages (${pages.filter(f => path.extname(f) === '.html').length})`, - generateTable(pages, baseStats, headStats) - ) - ); - } +// Fetch stats for both directories in parallel +const [baseStats, headStats] = await Promise.all([BASE, HEAD].map(getStats)); + +const didChange = f => + baseStats.has(f) && headStats.has(f) && baseStats.get(f) !== headStats.get(f); + +const toDiffObject = f => ({ + file: f, + base: baseStats.get(f), + head: headStats.get(f), + diff: headStats.get(f) - baseStats.get(f), +}); + +// Find files that exist in both directories but have different sizes, +// then sort by absolute diff (largest changes first) +const changed = [...new Set([...baseStats.keys(), ...headStats.keys()])] + .filter(didChange) + .map(toDiffObject) + .sort((a, b) => Math.abs(b.diff) - Math.abs(a.diff)); + +// Output markdown table if there are changes +if (changed.length) { + const rows = changed.map(({ file, base, head, diff }) => { + const sign = diff > 0 ? '+' : ''; + const percent = `${sign}${((diff / base) * 100).toFixed(2)}%`; + const diffFormatted = `${sign}${formatBytes(diff)} (${percent})`; + + return `| \`${file}\` | ${formatBytes(base)} | ${formatBytes(head)} | ${diffFormatted} |`; + }); + + console.log('## Web Generator'); + console.log('| File | Base | Head | Diff |'); + console.log('|-|-|-|-|'); + console.log(rows.join('\n')); } diff --git a/src/generators/ast-js/index.mjs b/src/generators/ast-js/index.mjs index b4a42483..bb075f17 100644 --- a/src/generators/ast-js/index.mjs +++ b/src/generators/ast-js/index.mjs @@ -1,9 +1,8 @@ +import { readFile } from 'node:fs/promises'; import { extname } from 'node:path'; -import { globSync } from 'glob'; -import { read } from 'to-vfile'; - -import { parseJsSource } from '../../parsers/javascript.mjs'; +import { parse } from 'acorn'; +import { globSync } from 'tinyglobby'; /** * This generator parses Javascript sources passed into the generator's input @@ -39,11 +38,17 @@ export default { const results = []; for (const path of filePaths) { - const vfile = await read(path, 'utf-8'); + const value = await readFile(path, 'utf-8'); + + const parsed = parse(value, { + allowReturnOutsideFunction: true, + ecmaVersion: 'latest', + locations: true, + }); - const parsedJS = await parseJsSource(vfile); + parsed.path = path; - results.push(parsedJS); + results.push(parsed); } return results; diff --git a/src/generators/ast/index.mjs b/src/generators/ast/index.mjs index 2bb0ded6..b1bc1ce3 100644 --- a/src/generators/ast/index.mjs +++ b/src/generators/ast/index.mjs @@ -1,9 +1,10 @@ 'use strict'; +import { readFile } from 'node:fs/promises'; import { extname } from 'node:path'; -import { globSync } from 'glob'; -import { read } from 'to-vfile'; +import { globSync } from 'tinyglobby'; +import { VFile } from 'vfile'; import createQueries from '../../utils/queries/index.mjs'; import { getRemark } from '../../utils/remark.mjs'; @@ -41,7 +42,7 @@ export default { const results = []; for (const path of filePaths) { - const vfile = await read(path, 'utf-8'); + const vfile = new VFile({ path, value: await readFile(path, 'utf-8') }); updateStabilityPrefixToLink(vfile); diff --git a/src/generators/jsx-ast/constants.mjs b/src/generators/jsx-ast/constants.mjs index 5e9b0a1f..ba6bc1c5 100644 --- a/src/generators/jsx-ast/constants.mjs +++ b/src/generators/jsx-ast/constants.mjs @@ -5,11 +5,21 @@ import { JSX_IMPORTS } from '../web/constants.mjs'; * * @see https://nodejs.org/api/documentation.html#stability-index */ + +// Alert level constants — prefer importing from UI library if available +// but not available for now +export const ALERT_LEVELS = { + DANGER: 'danger', + WARNING: 'warning', + INFO: 'info', + SUCCESS: 'success', +}; + export const STABILITY_LEVELS = [ - 'danger', // (0) Deprecated - 'warning', // (1) Experimental - 'success', // (2) Stable - 'info', // (3) Legacy + ALERT_LEVELS.DANGER, // (0) Deprecated + ALERT_LEVELS.WARNING, // (1) Experimental + ALERT_LEVELS.SUCCESS, // (2) Stable + ALERT_LEVELS.INFO, // (3) Legacy ]; // How deep should the Table of Contents go? @@ -150,3 +160,10 @@ export const TYPES_WITH_METHOD_SIGNATURES = [ 'method', 'classMethod', ]; + +// Patterns to map deprecation "Type" text to AlertBox levels. +// Order matters: first match wins. +export const DEPRECATION_TYPE_PATTERNS = [ + { pattern: /^(Documentation|Compilation)/i, level: ALERT_LEVELS.INFO }, + { pattern: /^(Runtime|Application)/i, level: ALERT_LEVELS.WARNING }, +]; diff --git a/src/generators/jsx-ast/utils/__tests__/buildBarProps.test.mjs b/src/generators/jsx-ast/utils/__tests__/buildBarProps.test.mjs index 0c1d8f89..145777b3 100644 --- a/src/generators/jsx-ast/utils/__tests__/buildBarProps.test.mjs +++ b/src/generators/jsx-ast/utils/__tests__/buildBarProps.test.mjs @@ -13,6 +13,7 @@ mock.module('../../../../utils/generators.mjs', { { version: '18.0.0', isLts: true, isCurrent: false }, { version: '19.0.0', isLts: false, isCurrent: true }, ], + leftHandAssign: Object.assign, getVersionFromSemVer: version => version.split('.')[0], getVersionURL: (version, api) => `/api/${version}/${api}`, }, diff --git a/src/generators/jsx-ast/utils/__tests__/buildContent.test.mjs b/src/generators/jsx-ast/utils/__tests__/buildContent.test.mjs new file mode 100644 index 00000000..ccc86999 --- /dev/null +++ b/src/generators/jsx-ast/utils/__tests__/buildContent.test.mjs @@ -0,0 +1,65 @@ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; + +import { transformHeadingNode } from '../buildContent.mjs'; + +const heading = { + type: 'heading', + depth: 3, + data: { type: 'misc', slug: 's', text: 'Heading' }, + children: [{ type: 'text', value: 'Heading' }], +}; + +const makeParent = typeText => ({ + children: [ + heading, + { + type: 'paragraph', + children: [{ type: 'text', value: `Type: ${typeText}` }], + }, + ], +}); + +describe('transformHeadingNode (deprecation Type -> AlertBox level)', () => { + it('maps documentation/compilation to info', () => { + const entry = { api: 'deprecations' }; + const parent = makeParent('Documentation'); + const node = parent.children[0]; + + transformHeadingNode(entry, {}, node, 0, parent); + + const alert = parent.children[1]; + const levelAttr = alert.attributes.find(a => a.name === 'level'); + + assert.equal(alert.name, 'AlertBox'); + assert.equal(levelAttr.value, 'info'); + }); + + it('maps runtime/application to warning', () => { + const entry = { api: 'deprecations' }; + const parent = makeParent('Runtime'); + const node = parent.children[0]; + + transformHeadingNode(entry, {}, node, 0, parent); + + const alert = parent.children[1]; + const levelAttr = alert.attributes.find(a => a.name === 'level'); + + assert.equal(alert.name, 'AlertBox'); + assert.equal(levelAttr.value, 'warning'); + }); + + it('falls back to danger for unknown types', () => { + const entry = { api: 'deprecations' }; + const parent = makeParent('SomeOtherThing'); + const node = parent.children[0]; + + transformHeadingNode(entry, {}, node, 0, parent); + + const alert = parent.children[1]; + const levelAttr = alert.attributes.find(a => a.name === 'level'); + + assert.equal(alert.name, 'AlertBox'); + assert.equal(levelAttr.value, 'danger'); + }); +}); diff --git a/src/generators/jsx-ast/utils/buildContent.mjs b/src/generators/jsx-ast/utils/buildContent.mjs index 5d92801c..adaed7aa 100644 --- a/src/generators/jsx-ast/utils/buildContent.mjs +++ b/src/generators/jsx-ast/utils/buildContent.mjs @@ -16,6 +16,8 @@ import { LIFECYCLE_LABELS, INTERNATIONALIZABLE, STABILITY_PREFIX_LENGTH, + DEPRECATION_TYPE_PATTERNS, + ALERT_LEVELS, TYPES_WITH_METHOD_SIGNATURES, TYPE_PREFIX_LENGTH, } from '../constants.mjs'; @@ -58,7 +60,7 @@ export const createChangeElement = (entry, remark) => { } // Sort changes by versions and reverse for newest first - const sortedChanges = sortChanges(changeEntries, 'versions').reverse(); + const sortedChanges = sortChanges(changeEntries, 'versions'); return createJSXElement(JSX_IMPORTS.ChangeHistory.name, { changes: sortedChanges, @@ -163,6 +165,18 @@ export const transformStabilityNode = (node, index, parent) => { return [SKIP]; }; +/** + * Maps deprecation type text to AlertBox level + * + * @param {string} typeText - The deprecation type text + * @returns {string} The corresponding AlertBox level + */ +const getLevelFromDeprecationType = typeText => { + const match = DEPRECATION_TYPE_PATTERNS.find(p => p.pattern.test(typeText)); + + return match ? match.level : ALERT_LEVELS.DANGER; +}; + /** * Transforms a heading node by injecting metadata, source links, and signatures. * @param {ApiDocMetadataEntry} entry - The API metadata entry @@ -180,16 +194,17 @@ export const transformHeadingNode = (entry, remark, node, index, parent) => { if (entry.api === 'deprecations' && node.depth === 3) { // On the 'deprecations.md' page, "Type: " turns into an AlertBox + // Extract the nodes representing the type text + const { + node: { children: sliced }, + } = slice(parent.children[index + 1], TYPE_PREFIX_LENGTH, undefined, { + textHandling: { boundaries: 'preserve' }, + }); + parent.children[index + 1] = createJSXElement(JSX_IMPORTS.AlertBox.name, { - children: slice( - parent.children[index + 1], - TYPE_PREFIX_LENGTH, - undefined, - { - textHandling: { boundaries: 'preserve' }, - } - ).node.children, - level: 'danger', + children: sliced, + // we assume sliced[0] is a text node here that contains the type text + level: getLevelFromDeprecationType(sliced[0].value), title: 'Type', }); } diff --git a/src/generators/jsx-ast/utils/transformer.mjs b/src/generators/jsx-ast/utils/transformer.mjs index e2f2c973..b5c732ff 100644 --- a/src/generators/jsx-ast/utils/transformer.mjs +++ b/src/generators/jsx-ast/utils/transformer.mjs @@ -29,6 +29,8 @@ const transformer = tree => { const thead = node.children.find(el => el.tagName === 'thead'); if (thead) { + // TODO(@avivkeller): These are only strings afaict, so a `toString` dependency + // might not actually be needed. const headers = thead.children[0].children.map(toString); const tbody = node.children.find(el => el.tagName === 'tbody'); diff --git a/src/generators/legacy-json/utils/parseList.mjs b/src/generators/legacy-json/utils/parseList.mjs index 73c36237..6f62bf66 100644 --- a/src/generators/legacy-json/utils/parseList.mjs +++ b/src/generators/legacy-json/utils/parseList.mjs @@ -5,6 +5,7 @@ import { TYPE_EXPRESSION, } from '../constants.mjs'; import parseSignature from './parseSignature.mjs'; +import { leftHandAssign } from '../../../utils/generators.mjs'; import createQueries from '../../../utils/queries/index.mjs'; import { transformNodesToString } from '../../../utils/unist.mjs'; @@ -95,6 +96,10 @@ export function parseList(section, nodes) { // Update the section based on its type and parsed values switch (section.type) { case 'ctor': + // Constructors are their own signatures + leftHandAssign(section, parseSignature(section.textRaw, values)); + break; + case 'classMethod': case 'method': // For methods and constructors, parse and attach signatures @@ -104,10 +109,7 @@ export function parseList(section, nodes) { case 'property': // For properties, update type and other details if values exist if (values.length) { - const { type, ...rest } = values[0]; - section.type = type; - Object.assign(section, rest); - section.textRaw = `\`${section.name}\` ${section.textRaw}`; + leftHandAssign(section, values[0]); } break; diff --git a/src/parsers/javascript.mjs b/src/parsers/javascript.mjs deleted file mode 100644 index a964415d..00000000 --- a/src/parsers/javascript.mjs +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -import * as acorn from 'acorn'; - -/** - * Parses a given JavaScript file into an ESTree AST representation of it - * - * @param {import('vfile').VFile} sourceFile - * @returns {Promise} - */ -export const parseJsSource = async sourceFile => { - if (typeof sourceFile.value !== 'string') { - throw new TypeError( - `expected sourceFile.value to be string but got ${typeof sourceFile.value}` - ); - } - - const res = acorn.parse(sourceFile.value, { - allowReturnOutsideFunction: true, - ecmaVersion: 'latest', - locations: true, - }); - - return { ...res, path: sourceFile.path }; -}; diff --git a/src/utils/__tests__/generators.test.mjs b/src/utils/__tests__/generators.test.mjs index e904874a..2a64b6c6 100644 --- a/src/utils/__tests__/generators.test.mjs +++ b/src/utils/__tests__/generators.test.mjs @@ -90,9 +90,9 @@ describe('sortChanges', () => { ]; const result = sortChanges(changes); - assert.equal(result[0].version, '16.2.0'); + assert.equal(result[0].version, '20.1.0'); assert.equal(result[1].version, '18.5.0'); - assert.equal(result[2].version, '20.1.0'); + assert.equal(result[2].version, '16.2.0'); }); it('handles array versions', () => { @@ -102,15 +102,15 @@ describe('sortChanges', () => { ]; const result = sortChanges(changes); - assert.equal(result[0].version[0], '16.2.0'); - assert.equal(result[1].version[0], '18.5.0'); + assert.equal(result[0].version[0], '18.5.0'); + assert.equal(result[1].version[0], '16.2.0'); }); it('sorts by custom key', () => { const changes = [{ customVersion: '18.0.0' }, { customVersion: '16.0.0' }]; const result = sortChanges(changes, 'customVersion'); - assert.equal(result[0].customVersion, '16.0.0'); - assert.equal(result[1].customVersion, '18.0.0'); + assert.equal(result[0].customVersion, '18.0.0'); + assert.equal(result[1].customVersion, '16.0.0'); }); }); diff --git a/src/utils/generators.mjs b/src/utils/generators.mjs index f169a9fc..ed0c89de 100644 --- a/src/utils/generators.mjs +++ b/src/utils/generators.mjs @@ -93,6 +93,19 @@ export const sortChanges = (changes, key = 'version') => { const aVersion = Array.isArray(a[key]) ? a[key][0] : a[key]; const bVersion = Array.isArray(b[key]) ? b[key][0] : b[key]; - return compare(coerceSemVer(aVersion), coerceSemVer(bVersion)); + return compare(coerceSemVer(bVersion), coerceSemVer(aVersion)); }); }; + +/** + * Assigns properties from one or more source objects to the target object + * **without overwriting existing keys** in the target. + * + * Similar to `Object.assign`, but preserves the target's existing keys. + * The target object is mutated in place. + * + * @param {Object} target - The object to assign properties to. + * @param {Object} source - The source object + */ +export const leftHandAssign = (target, source) => + Object.keys(source).forEach(k => k in target || (target[k] = source[k])); diff --git a/src/utils/highlighter.mjs b/src/utils/highlighter.mjs index 6cb2c0ef..722388a8 100644 --- a/src/utils/highlighter.mjs +++ b/src/utils/highlighter.mjs @@ -1,7 +1,6 @@ 'use strict'; import createHighlighter from '@node-core/rehype-shiki'; -import { toString } from 'hast-util-to-string'; import { h as createElement } from 'hastscript'; import { SKIP, visit } from 'unist-util-visit'; @@ -84,18 +83,18 @@ export default function rehypeShikiji() { return; } - // Retrieve the whole
 contents as a parsed DOM string
-      const preElementContents = toString(preElement);
-
       // Grabs the relevant alias/name of the language
       const languageId = codeLanguage.slice(languagePrefix.length);
 
       // Parses the 
 contents and returns a HAST tree with the highlighted code
-      const { children } = highlighter.shiki.codeToHast(preElementContents, {
-        lang: languageId,
-        // Allows support for dual themes (light, dark) for Shiki
-        themes: { light: shikiConfig.themes[0], dark: shikiConfig.themes[1] },
-      });
+      const { children } = highlighter.shiki.codeToHast(
+        preElement.children[0].value,
+        {
+          lang: languageId,
+          // Allows support for dual themes (light, dark) for Shiki
+          themes: { light: shikiConfig.themes[0], dark: shikiConfig.themes[1] },
+        }
+      );
 
       // Adds the original language back to the 
 element
       children[0].properties.class = `${children[0].properties.class} ${codeLanguage}`;