diff --git a/.github/ISSUE_TEMPLATE/00-general.md b/.github/ISSUE_TEMPLATE/00-general.md index 64285b6..6b2b549 100644 --- a/.github/ISSUE_TEMPLATE/00-general.md +++ b/.github/ISSUE_TEMPLATE/00-general.md @@ -45,14 +45,14 @@ Include any relevant information: -| Component | Version | -|-----------|---------| -| OS | Ubuntu 22.04 / Windows 11 | -| Python | 3.10.x / 3.11.x | -| Terraform | 1.9.x | -| Azure CLI | 2.x | -| Isaac Sim | 4.5 / 5.0 | -| GPU | NVIDIA RTX / A100 | +| Component | Version | +|-----------|---------------------------| +| OS | Ubuntu 22.04 / Windows 11 | +| Python | 3.10.x / 3.11.x | +| Terraform | 1.9.x | +| Azure CLI | 2.x | +| Isaac Sim | 4.5 / 5.0 | +| GPU | NVIDIA RTX / A100 | ## Additional Notes @@ -63,4 +63,4 @@ Include any relevant information: **Before submitting:** - [ ] I have searched [existing issues](https://github.com/Azure-Samples/azure-nvidia-robotics-reference-architecture/issues) for duplicates -- [ ] I have reviewed the [README](README.md) and [documentation](docs/) +- [ ] I have reviewed the [README](../../README.md) and [documentation](../../docs/) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 0b57ad3..9dfd0e9 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,4 +1,7 @@ +# Pull Request + ## Description + Closes # @@ -34,8 +37,8 @@ Closes # ## Checklist -- [ ] My code follows the [project conventions](.github/copilot-instructions.md) -- [ ] Commit messages follow [conventional commit format](.github/instructions/commit-message.instructions.md) +- [ ] My code follows the [project conventions](copilot-instructions.md) +- [ ] Commit messages follow [conventional commit format](instructions/commit-message.instructions.md) - [ ] I have performed a self-review - [ ] Documentation updated as needed - [ ] No new linting warnings introduced diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 352449d..26c757f 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -13,14 +13,17 @@ Items in **HIGHEST PRIORITY** sections from attached instructions files override **Artifacts:** Do not create or modify tests, scripts, or one-off markdown docs unless explicitly requested. **Comment policy:** Never include thought processes, step-by-step reasoning, or narrative comments in code. + * Keep comments brief and factual; describe **behavior/intent, invariants, edge cases**. * Remove or update comments that contradict the current behavior. Do not restate obvious functionality. * Do NOT add temporal or plan-phase markers (e.g. "Phase 1 cleanup", "... after migration", dates, or task references) to code files. When editing or updating any code files, always remove or replace these types of comments. **Conventions and Styling:** Always follow conventions and styling in this codebase FIRST for all changes, edits, updates, and new files. + * Conventions and styling are in instruction files and must be read in with the `read_file` tool if not already added as an ``. **Proactive fixes:** Always fix problems and errors you encounter, even if unrelated to the original request. Prefer root-cause, constructive fixes over symptom-only patches. + * Always correct all incorrect or problematic conventions, styling, and redundant and/or misleading comments. **Deleting files and folders:** Use `rm` with the run_in_terminal tool when needing to delete files or folders. @@ -28,5 +31,6 @@ Items in **HIGHEST PRIORITY** sections from attached instructions files override **Edit tools:** Never use `insert_edit_into_file` tool when other edit and file modification tools are available. **Memory and tracking work**: Always track work in Beads instead of Markdown. + * All upcoming work, tracked work, issues, plans, todos, phases, tasks, and memory must always use the mcp_beads tools. * Don't ever use git commands for anything related to the mcp_beads tools and beads in general, its at the user's discretion when to use git commands and tools. diff --git a/.github/instructions/commit-message.instructions.md b/.github/instructions/commit-message.instructions.md index 4a31950..753fd9b 100644 --- a/.github/instructions/commit-message.instructions.md +++ b/.github/instructions/commit-message.instructions.md @@ -38,7 +38,6 @@ Scopes MUST be one of the following: - `(src)` - `(deploy)` - ## Description - Description MUST be short and LESS THAN 100 bytes @@ -62,7 +61,7 @@ For larger changes only: - Footer MUST start with a blank line - Must include an emoji that represents the change -- Must end with ` - Generated by Copilot` +- Must end with `- Generated by Copilot` ## Example Complete Commit Message - Large diff --git a/.github/instructions/docs-style-and-conventions.instructions.md b/.github/instructions/docs-style-and-conventions.instructions.md index 1eff384..1be2e4d 100644 --- a/.github/instructions/docs-style-and-conventions.instructions.md +++ b/.github/instructions/docs-style-and-conventions.instructions.md @@ -39,12 +39,12 @@ Documents follow this section order when applicable: ### Heading Levels -| Level | Usage | -|-------|-------| -| H1 (`#`) | Document title only, one per file | -| H2 (`##`) | Major sections | -| H3 (`###`) | Subsections within H2 | -| H4+ | Avoid; restructure content instead | +| Level | Usage | +|------------|------------------------------------| +| H1 (`#`) | Document title only, one per file | +| H2 (`##`) | Major sections | +| H3 (`###`) | Subsections within H2 | +| H4+ | Avoid; restructure content instead | ### README Section Emojis @@ -98,9 +98,9 @@ Use tables for structured information. Tables are scannable and align related da - Prerequisites with versions ```markdown -| Script | Purpose | -|--------|---------| -| `01-deploy-robotics-charts.sh` | GPU Operator, KAI Scheduler | +| Script | Purpose | +|----------------------------------|---------------------------------------| +| `01-deploy-robotics-charts.sh` | GPU Operator, KAI Scheduler | | `02-deploy-azureml-extension.sh` | AzureML K8s extension, compute attach | ``` @@ -280,10 +280,10 @@ This pattern appears frequently in AI-generated content: **Use tables when structure matters:** ```markdown -| Component | Requirement | -|-----------|-------------| -| Storage | Blob containers for checkpoints | -| Compute | GPU nodes with sufficient memory | +| Component | Requirement | +|------------|---------------------------------------| +| Storage | Blob containers for checkpoints | +| Compute | GPU nodes with sufficient memory | | Networking | Private endpoints with DNS resolution | ``` @@ -329,10 +329,10 @@ Required fields: `title`, `description`. Add `ms.date` for versioned content. ## File Naming -| Type | Convention | Example | -|------|------------|---------| -| README | `README.md` (uppercase) | `deploy/README.md` | -| Guides | kebab-case | `mlflow-integration.md` | +| Type | Convention | Example | +|------------|-----------------------------|---------------------------------------| +| README | `README.md` (uppercase) | `deploy/README.md` | +| Guides | kebab-case | `mlflow-integration.md` | | References | kebab-case with type suffix | `azureml-validation-job-debugging.md` | ## Checklist diff --git a/.github/instructions/shell-scripts.instructions.md b/.github/instructions/shell-scripts.instructions.md index 664d222..26a3739 100644 --- a/.github/instructions/shell-scripts.instructions.md +++ b/.github/instructions/shell-scripts.instructions.md @@ -100,16 +100,19 @@ info "Operation complete" **Arguments:** + - Short: `-h`, `-t` | Long: `--help`, `--tf-dir` - Value options: `shift 2` | Flags: `shift` - Unknown options: `fatal "Unknown option: $1"` **Variables:** + - Always quote: `"$var"`, `"${array[@]}"` - Defaults: `var="${ENV_VAR:-default}"` - Booleans: `true`/`false` strings, test with `[[ "$var" == "true" ]]` **Output:** + - Progress: `info "message"` - Warnings: `warn "message"` - Fatal errors: `fatal "message"` @@ -117,17 +120,20 @@ info "Operation complete" - Summaries: `print_kv "Key" "$value"` **Idempotent operations:** + ```bash kubectl create ... --dry-run=client -o yaml | kubectl apply -f - helm repo add name url 2>/dev/null || true ``` **Conditional output:** + ```bash print_kv "Status" "$([[ $skip == true ]] && echo 'Skipped' || echo "$version")" ``` **Array building:** + ```bash args=(--version "$ver" --namespace "$ns") [[ -n "$extra" ]] && args+=(--set "$extra") @@ -138,14 +144,14 @@ command "${args[@]}" ## Library Functions (lib/common.sh) -| Function | Purpose | -|----------|---------| -| `info`, `warn`, `error`, `fatal` | Colored logging | -| `require_tools tool1 tool2` | Validate CLI tools exist | -| `read_terraform_outputs "$dir"` | Read terraform JSON | -| `tf_get "$json" "path" "default"` | Extract optional value | -| `tf_require "$json" "path" "desc"` | Extract required value | -| `connect_aks "$rg" "$cluster"` | Get AKS credentials | -| `ensure_namespace "$ns"` | Create namespace idempotently | -| `section "Title"` | Print section header | -| `print_kv "Key" "$val"` | Print key-value pair | +| Function | Purpose | +|------------------------------------|-------------------------------| +| `info`, `warn`, `error`, `fatal` | Colored logging | +| `require_tools tool1 tool2` | Validate CLI tools exist | +| `read_terraform_outputs "$dir"` | Read terraform JSON | +| `tf_get "$json" "path" "default"` | Extract optional value | +| `tf_require "$json" "path" "desc"` | Extract required value | +| `connect_aks "$rg" "$cluster"` | Get AKS credentials | +| `ensure_namespace "$ns"` | Create namespace idempotently | +| `section "Title"` | Print section header | +| `print_kv "Key" "$val"` | Print key-value pair | diff --git a/.github/prompts/chatlog.prompt.md b/.github/prompts/chatlog.prompt.md index c3667f4..9facb89 100644 --- a/.github/prompts/chatlog.prompt.md +++ b/.github/prompts/chatlog.prompt.md @@ -37,7 +37,7 @@ Manage conversation details by creating and maintaining structured chatlog files * Create the `.copilot-tracking/chatlogs/` directory if it doesn't exist * Generate a new chatlog file with the following structure: -```markdown +````markdown # [Descriptive Title] **Date**: YYYY-MM-DD (e.g., November 19, 2025) @@ -83,7 +83,7 @@ command here ## Related Documentation -- [Link Title](URL) +* [Link Title](URL) ## Follow-up Issues @@ -96,7 +96,8 @@ command here 1. [Key takeaway with brief explanation] 2. [Another key takeaway] -``` + +```` * Populate the chatlog with details from the current conversation context * Follow markdown linting rules strictly: @@ -165,6 +166,7 @@ Ready to continue. What would you like to work on? ### Initial Response (mode=create) Format: + ```text βœ… Created chatlog: .copilot-tracking/chatlogs/YYYYMMDD-brief-description-chatlog.md @@ -191,6 +193,7 @@ Use the format specified in Phase 3 above, then proceed with the conversation. ### Final Summary (optional, at conversation end) Format: + ```text πŸ“‹ Chatlog Summary @@ -201,7 +204,8 @@ Format: - [Section 2]: [brief description] **Key Additions**: -- [Most important new insight/solution] +* [Most important new insight/solution] + ``` --- diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..30e2476 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,54 @@ +name: CodeQL Security Analysis + +on: + schedule: + # Weekly scan: Sundays at 04:00 UTC + - cron: '0 4 * * 0' + workflow_call: + +permissions: + contents: read + security-events: write + +jobs: + analyze: + name: CodeQL Analysis + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + actions: read + + strategy: + fail-fast: false + matrix: + language: ['python'] + + steps: + - name: Checkout repository + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v4.2.2 + with: + persist-credentials: false + + - name: Initialize CodeQL + uses: github/codeql-action/init@ce729e4d353d580e6cacd6a8cf2921b72e5e310a # v3.27.0 + with: + languages: ${{ matrix.language }} + queries: security-extended,security-and-quality + + - name: Autobuild + uses: github/codeql-action/autobuild@ce729e4d353d580e6cacd6a8cf2921b72e5e310a # v3.27.0 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@ce729e4d353d580e6cacd6a8cf2921b72e5e310a # v3.27.0 + with: + category: "/language:${{ matrix.language }}" + + - name: Add job summary + if: always() + run: | + echo "## CodeQL Security Analysis Complete" >> $GITHUB_STEP_SUMMARY + echo "**Language:** ${{ matrix.language }}" >> $GITHUB_STEP_SUMMARY + echo "**Queries:** security-extended, security-and-quality" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "πŸ“Š View results in the Security tab under Code Scanning" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000..1ef69d9 --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,33 @@ +name: Dependency Review + +on: + workflow_call: + +permissions: + contents: read + pull-requests: write + +jobs: + dependency-review: + name: Review Dependencies + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + + steps: + - name: Harden Runner + uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.10.2 + with: + egress-policy: audit + + - name: Checkout code + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v4.2.2 + with: + persist-credentials: false + + - name: Dependency Review + uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.3.4 + with: + fail-on-severity: moderate + comment-summary-in-pr: always diff --git a/.github/workflows/link-lang-check.yml b/.github/workflows/link-lang-check.yml new file mode 100644 index 0000000..0732075 --- /dev/null +++ b/.github/workflows/link-lang-check.yml @@ -0,0 +1,48 @@ +name: Link Language Check + +on: + workflow_call: + inputs: + soft-fail: + description: 'Whether to continue on language link violations' + required: false + type: boolean + default: false + +permissions: + contents: read + +jobs: + link-lang-check: + name: Link Language Check + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Harden Runner + uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.10.2 + with: + egress-policy: audit + + - name: Checkout code + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v4.2.2 + with: + persist-credentials: false + + - name: Create logs directory + shell: pwsh + run: | + New-Item -ItemType Directory -Force -Path logs | Out-Null + + - name: Run Link Language Check + shell: pwsh + run: scripts/linting/Invoke-LinkLanguageCheck.ps1 + continue-on-error: ${{ inputs.soft-fail }} + + - name: Upload link language check results + if: always() + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v4.4.3 + with: + name: link-lang-check-results + path: logs/link-lang-check-results.json + retention-days: 30 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..3c7220f --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,54 @@ +name: CI + +on: + push: + branches: + - main + +permissions: + contents: read + +jobs: + # Spell checking using cspell + spell-check: + name: Spell Check + uses: ./.github/workflows/spell-check.yml + permissions: + contents: read + + # Markdown linting using markdownlint-cli2 + markdown-lint: + name: Markdown Lint + uses: ./.github/workflows/markdown-lint.yml + permissions: + contents: read + + # Markdown table formatting check + table-format: + name: Table Format + uses: ./.github/workflows/table-format.yml + permissions: + contents: read + + # PowerShell script analysis + psscriptanalyzer: + name: PSScriptAnalyzer + uses: ./.github/workflows/ps-script-analyzer.yml + with: + changed-files-only: false + permissions: + contents: read + + # Link language locale check + link-lang-check: + name: Link Language Check + uses: ./.github/workflows/link-lang-check.yml + permissions: + contents: read + + # Markdown link validation + markdown-link-check: + name: Markdown Link Check + uses: ./.github/workflows/markdown-link-check.yml + permissions: + contents: read diff --git a/.github/workflows/markdown-link-check.yml b/.github/workflows/markdown-link-check.yml new file mode 100644 index 0000000..a94ea36 --- /dev/null +++ b/.github/workflows/markdown-link-check.yml @@ -0,0 +1,92 @@ +name: Markdown Link Check + +on: + workflow_call: + inputs: + soft-fail: + description: 'Whether to continue on broken links' + required: false + type: boolean + default: false + +permissions: + contents: read + +jobs: + markdown-link-check: + name: Check Markdown Links + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Harden Runner + uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.10.2 + with: + egress-policy: audit + + - name: Checkout code + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v4.2.2 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Get changed markdown files + id: changed-files + shell: bash + run: | + if [ "${{ github.event_name }}" == "pull_request" ]; then + CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRT ${{ github.event.pull_request.base.sha }} ${{ github.sha }} -- '*.md' | tr '\n' ' ') + else + CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRT HEAD~1 HEAD -- '*.md' | tr '\n' ' ') + fi + echo "files=$CHANGED_FILES" >> $GITHUB_OUTPUT + if [ -z "$CHANGED_FILES" ]; then + echo "has_changes=false" >> $GITHUB_OUTPUT + else + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "Changed markdown files: $CHANGED_FILES" + fi + + - name: Setup Node.js + uses: actions/setup-node@b9b25d45f70a5d94d88496aa4896bf9ed8f49b67 # v4.1.0 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + if: steps.changed-files.outputs.has_changes == 'true' + run: npm ci + + - name: Create logs directory + if: steps.changed-files.outputs.has_changes == 'true' + shell: pwsh + run: | + New-Item -ItemType Directory -Force -Path logs | Out-Null + + - name: Run markdown link check + id: link-check + if: steps.changed-files.outputs.has_changes == 'true' + shell: pwsh + run: | + $changedFiles = "${{ steps.changed-files.outputs.files }}" -split '\s+' | Where-Object { $_ } + & scripts/linting/Markdown-Link-Check.ps1 -Path $changedFiles + continue-on-error: ${{ inputs.soft-fail }} + + - name: Skip notification + if: steps.changed-files.outputs.has_changes != 'true' + run: echo "No markdown files changed, skipping link check." + + - name: Upload markdown link check results + if: steps.changed-files.outputs.has_changes == 'true' + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v4.4.3 + with: + name: markdown-link-check-results + path: logs/markdown-link-check-results.json + retention-days: 30 + + - name: Check results and fail if needed + if: steps.changed-files.outputs.has_changes == 'true' && !inputs.soft-fail && steps.link-check.outcome == 'failure' + shell: pwsh + run: | + Write-Host "Markdown link check failed and soft-fail is false. Failing the job." + exit 1 diff --git a/.github/workflows/markdown-lint.yml b/.github/workflows/markdown-lint.yml new file mode 100644 index 0000000..bc5a870 --- /dev/null +++ b/.github/workflows/markdown-lint.yml @@ -0,0 +1,44 @@ +name: Markdown Lint + +on: + workflow_call: + inputs: + soft-fail: + description: 'Whether to continue on markdown lint violations' + required: false + type: boolean + default: false + +permissions: + contents: read + +jobs: + markdown-lint: + name: Markdown Lint + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Harden Runner + uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.10.2 + with: + egress-policy: audit + + - name: Checkout code + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v4.2.2 + with: + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@b9b25d45f70a5d94d88496aa4896bf9ed8f49b67 # v4.1.0 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run markdown lint + id: markdown-lint + run: npm run lint:md + continue-on-error: ${{ inputs.soft-fail }} diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml new file mode 100644 index 0000000..bd9a656 --- /dev/null +++ b/.github/workflows/pr-validation.yml @@ -0,0 +1,67 @@ +name: PR Validation + +on: + pull_request: + branches: + - main + types: + - opened + - synchronize + - reopened + +permissions: + contents: read + pull-requests: write + +jobs: + # Spell checking using cspell + spell-check: + name: Spell Check + uses: ./.github/workflows/spell-check.yml + permissions: + contents: read + + # Markdown linting using markdownlint-cli2 + markdown-lint: + name: Markdown Lint + uses: ./.github/workflows/markdown-lint.yml + permissions: + contents: read + + # Markdown table formatting check + table-format: + name: Table Format + uses: ./.github/workflows/table-format.yml + permissions: + contents: read + + # PowerShell script analysis + psscriptanalyzer: + name: PSScriptAnalyzer + uses: ./.github/workflows/ps-script-analyzer.yml + with: + changed-files-only: true + permissions: + contents: read + + # Link language locale check + link-lang-check: + name: Link Language Check + uses: ./.github/workflows/link-lang-check.yml + permissions: + contents: read + + # Markdown link validation + markdown-link-check: + name: Markdown Link Check + uses: ./.github/workflows/markdown-link-check.yml + permissions: + contents: read + + # Dependency review for security vulnerabilities + dependency-review: + name: Dependency Review + uses: ./.github/workflows/dependency-review.yml + permissions: + contents: read + pull-requests: write diff --git a/.github/workflows/ps-script-analyzer.yml b/.github/workflows/ps-script-analyzer.yml new file mode 100644 index 0000000..fb95a58 --- /dev/null +++ b/.github/workflows/ps-script-analyzer.yml @@ -0,0 +1,63 @@ +name: PowerShell Lint + +on: + workflow_call: + inputs: + soft-fail: + description: 'Whether to continue on PSScriptAnalyzer violations' + required: false + type: boolean + default: false + changed-files-only: + description: 'Only analyze changed PowerShell files' + required: false + type: boolean + default: true + +permissions: + contents: read + +jobs: + psscriptanalyzer: + name: PSScriptAnalyzer + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Harden Runner + uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.10.2 + with: + egress-policy: audit + + - name: Checkout code + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v4.2.2 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Create logs directory + shell: pwsh + run: | + New-Item -ItemType Directory -Force -Path logs | Out-Null + + - name: Run PSScriptAnalyzer + shell: pwsh + run: | + $params = @{} + + if ('${{ inputs.changed-files-only }}' -eq 'true') { + $params['ChangedFilesOnly'] = $true + } + if ('${{ inputs.soft-fail }}' -eq 'true') { + $params['SoftFail'] = $true + } + + & scripts/linting/Invoke-PSScriptAnalyzer.ps1 @params + + - name: Upload PSScriptAnalyzer results + if: always() + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v4.4.3 + with: + name: psscriptanalyzer-results + path: logs/ + retention-days: 30 diff --git a/.github/workflows/spell-check.yml b/.github/workflows/spell-check.yml new file mode 100644 index 0000000..7787f83 --- /dev/null +++ b/.github/workflows/spell-check.yml @@ -0,0 +1,87 @@ +name: Spell Check + +on: + workflow_call: + inputs: + soft-fail: + description: 'Whether to continue on spell check errors' + required: false + type: boolean + default: false + +permissions: + contents: read + +jobs: + spell-check: + name: Spell Check + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Harden Runner + uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.10.2 + with: + egress-policy: audit + + - name: Checkout code + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v4.2.2 + with: + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@b9b25d45f70a5d94d88496aa4896bf9ed8f49b67 # v4.1.0 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Create logs directory + run: mkdir -p logs + + - name: Run spell check + id: spell-check + run: | + npm run spell-check > logs/spell-check-results.txt 2>&1 + EXIT_CODE=$? + if [ $EXIT_CODE -ne 0 ]; then + echo "SPELL_CHECK_FAILED=true" >> $GITHUB_ENV + fi + cat logs/spell-check-results.txt + exit $EXIT_CODE + continue-on-error: true + + - name: Create annotations + if: env.SPELL_CHECK_FAILED == 'true' + run: | + echo "::warning::Spell check found issues. Review the spell-check-results artifact for details." + + - name: Upload spell check results + if: always() + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v4.4.3 + with: + name: spell-check-results + path: logs/spell-check-results.txt + retention-days: 30 + + - name: Add job summary + if: always() + run: | + echo "## Spell Check Results" >> $GITHUB_STEP_SUMMARY + if [ "${{ env.SPELL_CHECK_FAILED }}" == "true" ]; then + echo "❌ **Status**: Failed" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Spelling errors were detected. Please review the artifact for details." >> $GITHUB_STEP_SUMMARY + else + echo "βœ… **Status**: Passed" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "No spelling errors detected." >> $GITHUB_STEP_SUMMARY + fi + + - name: Fail job if errors found + if: env.SPELL_CHECK_FAILED == 'true' && !inputs.soft-fail + run: | + echo "Spell check failed" + exit 1 diff --git a/.github/workflows/table-format.yml b/.github/workflows/table-format.yml new file mode 100644 index 0000000..65a06f1 --- /dev/null +++ b/.github/workflows/table-format.yml @@ -0,0 +1,84 @@ +name: Table Format Check + +on: + workflow_call: + inputs: + soft-fail: + description: 'Whether to continue on table format issues' + required: false + type: boolean + default: false + +permissions: + contents: read + +jobs: + table-format: + name: Table Format Check + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Harden Runner + uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.10.2 + with: + egress-policy: audit + + - name: Checkout code + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v4.2.2 + with: + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@b9b25d45f70a5d94d88496aa4896bf9ed8f49b67 # v4.1.0 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Check table formatting + id: table-format + run: | + npm run format:tables > table-format-output.txt 2>&1 || echo "TABLE_FORMAT_FAILED=true" >> $GITHUB_ENV + cat table-format-output.txt + continue-on-error: true + + - name: Create annotations + if: env.TABLE_FORMAT_FAILED == 'true' + run: | + echo "::warning::Table formatting issues found. This check does NOT auto-fix. Please manually format tables or run 'npm run format:tables' locally." + + - name: Upload table format results + if: always() + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v4.4.3 + with: + name: table-format-results + path: table-format-output.txt + retention-days: 30 + + - name: Add job summary + if: always() + run: | + echo "## Table Format Check Results" >> $GITHUB_STEP_SUMMARY + if [ "${{ env.TABLE_FORMAT_FAILED }}" == "true" ]; then + echo "❌ **Status**: Failed" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "⚠️ **Note**: This check does NOT auto-fix tables." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**To fix manually:**" >> $GITHUB_STEP_SUMMARY + echo "1. Run \`npm run format:tables\` locally" >> $GITHUB_STEP_SUMMARY + echo "2. Review and commit the changes" >> $GITHUB_STEP_SUMMARY + echo "3. Push to update this PR" >> $GITHUB_STEP_SUMMARY + else + echo "βœ… **Status**: Passed" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "All tables are properly formatted." >> $GITHUB_STEP_SUMMARY + fi + + - name: Fail job if issues found + if: env.TABLE_FORMAT_FAILED == 'true' && !inputs.soft-fail + run: | + echo "Table format check failed" + exit 1 diff --git a/.markdownlint-cli2.jsonc b/.markdownlint-cli2.jsonc new file mode 100644 index 0000000..d12414f --- /dev/null +++ b/.markdownlint-cli2.jsonc @@ -0,0 +1,78 @@ +{ + "ignores": [ + "**/node_modules/**", + ".copilot-tracking/**", + "venv/**" + ], + "config": { + "default": true, + "extends": null, + "MD001": true, + "MD003": { "style": "consistent" }, + "MD004": { "style": "consistent" }, + "MD005": true, + "MD007": { "indent": 2, "start_indented": false, "start_indent": 2 }, + "MD009": { "br_spaces": 2, "list_item_empty_lines": false, "strict": false }, + "MD010": { "code_blocks": true, "ignore_code_languages": [], "spaces_per_tab": 1 }, + "MD011": true, + "MD012": { "maximum": 1 }, + "MD013": { + "line_length": 500, + "heading_line_length": 80, + "code_block_line_length": 1200, + "code_blocks": true, + "tables": false, + "headings": true, + "strict": false, + "stern": false + }, + "MD014": true, + "MD018": true, + "MD019": true, + "MD020": true, + "MD021": true, + "MD022": { "lines_above": 1, "lines_below": 1 }, + "MD023": true, + "MD024": { "siblings_only": false }, + "MD025": { "level": 1, "front_matter_title": "^\\s*title\\s*[:=]" }, + "MD026": { "punctuation": ".,;:!γ€‚οΌŒοΌ›οΌšοΌ" }, + "MD027": true, + "MD028": true, + "MD029": { "style": "one_or_ordered" }, + "MD030": { "ul_single": 1, "ol_single": 1, "ul_multi": 1, "ol_multi": 1 }, + "MD031": { "list_items": true }, + "MD032": true, + "MD033": { "allowed_elements": ["details", "summary"] }, + "MD034": true, + "MD035": { "style": "consistent" }, + "MD036": { "punctuation": ".,;:!?γ€‚οΌŒοΌ›οΌšοΌοΌŸ" }, + "MD037": true, + "MD038": true, + "MD039": true, + "MD040": { "allowed_languages": [], "language_only": false }, + "MD041": { "level": 1, "front_matter_title": "^\\s*title\\s*[:=]" }, + "MD042": true, + "MD043": false, + "MD044": { "names": [], "code_blocks": true, "html_elements": true }, + "MD045": true, + "MD046": { "style": "consistent" }, + "MD047": true, + "MD048": { "style": "consistent" }, + "MD049": { "style": "consistent" }, + "MD050": { "style": "consistent" }, + "MD051": { "ignore_case": false }, + "MD052": { "shortcut_syntax": false }, + "MD053": { "ignored_definitions": ["//"] }, + "MD054": { + "autolink": true, + "inline": true, + "full": true, + "collapsed": true, + "shortcut": true, + "url_inline": true + }, + "MD055": { "style": "consistent" }, + "MD056": true, + "MD058": true + } +} diff --git a/.markdownlint.json b/.markdownlint.json deleted file mode 100644 index cd5394c..0000000 --- a/.markdownlint.json +++ /dev/null @@ -1,145 +0,0 @@ -{ - "default": true, - "extends": null, - "MD001": true, - "MD003": { - "style": "consistent" - }, - "MD004": { - "style": "consistent" - }, - "MD005": true, - "MD007": { - "indent": 2, - "start_indented": false, - "start_indent": 2 - }, - "MD009": { - "br_spaces": 2, - "list_item_empty_lines": false, - "strict": false - }, - "MD010": { - "code_blocks": true, - "ignore_code_languages": [], - "spaces_per_tab": 1 - }, - "MD011": true, - "MD012": { - "maximum": 1 - }, - "MD013": { - "line_length": 500, - "heading_line_length": 80, - "code_block_line_length": 1200, - "code_blocks": true, - "tables": false, - "headings": true, - "strict": false, - "stern": false - }, - "MD014": true, - "MD018": true, - "MD019": true, - "MD020": true, - "MD021": true, - "MD022": { - "lines_above": 1, - "lines_below": 1 - }, - "MD023": true, - "MD024": { - "siblings_only": false - }, - "MD025": { - "level": 1, - "front_matter_title": "^\\s*title\\s*[:=]" - }, - "MD026": { - "punctuation": ".,;:!γ€‚οΌŒοΌ›οΌšοΌ" - }, - "MD027": true, - "MD028": true, - "MD029": { - "style": "one_or_ordered" - }, - "MD030": { - "ul_single": 1, - "ol_single": 1, - "ul_multi": 1, - "ol_multi": 1 - }, - "MD031": { - "list_items": true - }, - "MD032": true, - "MD033": { - "allowed_elements": [ - "details", - "summary" - ] - }, - "MD034": true, - "MD035": { - "style": "consistent" - }, - "MD036": { - "punctuation": ".,;:!?γ€‚οΌŒοΌ›οΌšοΌοΌŸ" - }, - "MD037": true, - "MD038": true, - "MD039": true, - "MD040": { - "allowed_languages": [], - "language_only": false - }, - "MD041": { - "level": 1, - "front_matter_title": "^\\s*title\\s*[:=]" - }, - "MD042": true, - "MD043": false, - "MD044": { - "names": [], - "code_blocks": true, - "html_elements": true - }, - "MD045": true, - "MD046": { - "style": "consistent" - }, - "MD047": true, - "MD048": { - "style": "consistent" - }, - "MD049": { - "style": "consistent" - }, - "MD050": { - "style": "consistent" - }, - "MD051": { - "ignore_case": false - }, - "MD052": { - "shortcut_syntax": false - }, - "MD053": { - "ignored_definitions": [ - "//" - ] - }, - "MD054": { - "autolink": true, - "inline": true, - "full": true, - "collapsed": true, - "shortcut": true, - "url_inline": true - }, - "MD055": { - "style": "consistent" - }, - "MD056": true, - "MD058": true -} diff --git a/LICENSE.md b/LICENSE similarity index 98% rename from LICENSE.md rename to LICENSE index 7965606..9e841e7 100644 --- a/LICENSE.md +++ b/LICENSE @@ -18,4 +18,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE \ No newline at end of file + SOFTWARE diff --git a/README.md b/README.md index 5f57937..c26a4a1 100644 --- a/README.md +++ b/README.md @@ -1,293 +1,129 @@ ---- -title: Azure Robotics Reference Architecture with NVIDIA OSMO -description: Production-ready framework for orchestrating robotics and AI workloads on Microsoft Azure using NVIDIA Isaac Lab, Isaac Sim, and OSMO -author: Microsoft -ms.date: 2025-12-03 -ms.topic: overview -keywords: - - azure - - nvidia - - osmo - - isaac lab - - isaac sim - - robotics - - reinforcement learning - - mlflow -estimated_reading_time: 5 ---- - -# Azure NVIDIA Robotics Reference Architecture - -Production-ready framework for orchestrating robotics and AI workloads on [Azure](https://azure.microsoft.com/) using [NVIDIA Isaac Lab](https://developer.nvidia.com/isaac/lab), [Isaac Sim](https://developer.nvidia.com/isaac/sim), and [OSMO](https://developer.nvidia.com/osmo). - -## Features - -| Capability | Description | -|------------|-------------| -| Infrastructure as Code | [Terraform modules](deploy/001-iac/) for reproducible Azure deployments | -| Dual Orchestration | Submit jobs via [AzureML](workflows/azureml/) or [OSMO](workflows/osmo/) | -| Workload Identity | Key-less auth via Azure AD ([setup guide](deploy/002-setup/README.md#scenario-2-workload-identity)) | -| Private Networking | Services on private VNet with [VPN gateway](deploy/001-iac/vpn/) for cluster access ([client setup](deploy/001-iac/vpn/README.md#-vpn-client-setup)) | -| MLflow Integration | Experiment tracking with Azure ML ([details](docs/mlflow-integration.md)) | -| GPU Scheduling | [KAI Scheduler](deploy/002-setup/values/kai-scheduler.yaml) for efficient utilization | -| Auto-scaling | Pay-per-use GPU compute on AKS Spot nodes | - -## Architecture - -The infrastructure deploys an AKS cluster with GPU node pools running the NVIDIA GPU Operator and KAI Scheduler. Training workloads can be submitted via OSMO workflows (control plane and backend operator) and AzureML jobs (ML extension). Both platforms share common infrastructure: Azure Storage for checkpoints and data, Key Vault for secrets, and Azure Container Registry for container images. OSMO additionally uses PostgreSQL for workflow state and Redis for caching. - -![Architecture Diagram](docs/images/Architecture.drawio.svg) - -**Azure Infrastructure** (deployed by [Terraform](deploy/001-iac/)): - -| Component | Purpose | -|-----------|--------| -| Virtual Network | Private networking with NAT Gateway and DNS Resolver | -| Private Endpoints | Secure access to Azure services (7 endpoints, 11+ DNS zones) | -| AKS Cluster | Kubernetes with GPU Spot node pools and Workload Identity | -| Key Vault | Secrets management with RBAC authorization | -| Azure ML Workspace | Experiment tracking, model registry | -| Storage Account | Training data, checkpoints, and workflow artifacts | -| Container Registry | Training and OSMO container images | -| Azure Monitor | Log Analytics, Prometheus metrics, Managed Grafana | -| PostgreSQL | OSMO workflow state persistence | -| Redis | OSMO job queue and caching | -| VPN Gateway | Point-to-Site and Site-to-Site connectivity (required for private cluster access) | - -**Kubernetes Components** (deployed by [setup scripts](deploy/002-setup/)): - -| Component | Purpose | -|-----------|--------| -| NVIDIA GPU Operator | GPU drivers, device plugin, DCGM metrics exporter | -| KAI Scheduler | GPU-aware scheduling with bin-packing | -| AzureML Extension | ML training and inference job submission | -| OSMO Control Plane | Workflow API, router, and web interface | -| OSMO Backend Operator | Workflow execution on cluster | - -> [!NOTE] -> Running both AzureML and OSMO on the same cluster? Create **separate GPU node pools** for each platform. AzureML uses [Volcano](https://volcano.sh/) while OSMO uses [KAI Scheduler](https://github.com/NVIDIA/KAI-Scheduler)β€”these schedulers don't share resource visibility. Without dedicated pools, jobs from one platform may fail when the other is using GPU resources. Configure node selectors and taints to isolate workloads. - -## Real World Examples - -OSMO orchestration on Azure enables production-scale robotics training across industries: - -| Use Case | Training Scenario | -|----------|-------------------| -| Warehouse AMRs | Navigation policies with 1000+ parallel environments, checkpointing to Azure Storage | -| Manufacturing Arms | Manipulation strategies with physics-accurate simulation on pay-per-use GPU | -| Legged Robots | Locomotion optimization with MLflow tracking for sim-to-real transfer | -| Collaborative Robots | Safe interaction policies with Azure Monitor logging for compliance | - -## Prerequisites +# πŸ€– Azure Robotics Reference Architecture with NVIDIA OSMO -### Required Tools - -| Tool | Version | Installation | -|------|---------|--------------| -| [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) | 2.50+ | `brew install azure-cli` | -| [Terraform](https://www.terraform.io/downloads) | 1.9.8+ | `brew install terraform` | -| [kubectl](https://kubernetes.io/docs/tasks/tools/) | 1.28+ | `brew install kubectl` | -| [Helm](https://helm.sh/docs/intro/install/) | 3.x | `brew install helm` | -| [jq](https://stedolan.github.io/jq/) | latest | `brew install jq` | -| [OSMO CLI](https://developer.nvidia.com/osmo) | latest | See NVIDIA docs | - -### Azure Requirements - -- Azure subscription with **Contributor** + **Role Based Access Control Administrator** - - Scope: Subscription (if creating new resource group) or Resource Group (if using existing) - - Terraform creates role assignments for managed identities - - Alternative: **Owner** (grants more permissions than required) -- GPU VM quota for your target region (e.g., `Standard_NV36ads_A10_v5`) - -## Quick Start - -### 1. Deploy Infrastructure - -```bash -cd deploy/001-iac -source ../000-prerequisites/az-sub-init.sh -cp terraform.tfvars.example terraform.tfvars -# Edit terraform.tfvars with your values -terraform init && terraform apply -var-file=terraform.tfvars -``` +This reference architecture provides a production-ready framework for orchestrating robotics and AI workloads on [Microsoft Azure](https://azure.microsoft.com/) using NVIDIA technologies such as [Isaac Lab](https://developer.nvidia.com/isaac/lab), [Isaac Sim](https://developer.nvidia.com/isaac/sim), and [OSMO](https://developer.nvidia.com/osmo). +It demonstrates end-to-end reinforcement learning workflows, scalable training pipelines, and deployment processes with Azure-native authentication, storage, and ML services. -For automation and additional configuration, see [deploy/001-iac/README.md](deploy/001-iac/README.md). +## πŸš€ Key Features -### 2. Deploy VPN Gateway +OSMO handles workflow orchestration and job scheduling while Azure provides elastic GPU compute, persistent checkpointing, MLflow experiment tracking, and enterprise grade security. -The default configuration creates a private AKS cluster. Deploy the VPN Gateway to access the cluster: +- **Infrastructure as Code** - Terraform modules referencing [microsoft/edge-ai](https://github.com/microsoft/edge-ai) components for reproducible deployments +- **Containerized Workflows** - Docker-based Isaac Lab training with NVIDIA GPU support +- **CI/CD Integration** - Automated deployment pipelines with GitHub Actions +- **MLflow Integration** - Automatic experiment tracking and model versioning + - Automatic metric logging from SKRL agents to Azure ML + - Comprehensive tracking of episode statistics, losses, optimization metrics, and timing data + - Configurable logging intervals and metric filtering + - See [MLflow Integration Guide](docs/mlflow-integration.md) for details +- **Scalable Compute** - Auto-scaling GPU nodes based on workload demands +- **Cost Optimization** - Pay-per-use compute with automatic scaling +- **Enterprise Security** - Entra ID integration +- **Global Deployment** - Multi-region support for worldwide teams -```bash -cd vpn -cp terraform.tfvars.example terraform.tfvars -# Edit terraform.tfvars - must match parent deployment values -terraform init && terraform apply -var-file=terraform.tfvars -``` +## πŸ—Ό Architecture Overview -See [VPN client setup](deploy/001-iac/vpn/README.md#-vpn-client-setup) for connecting from your local machine. +This reference architecture integrates: -> [!NOTE] -> Skip this step if you set `should_enable_private_aks_cluster = false` for a public AKS control plane. See [Network Configuration Modes](deploy/001-iac/README.md#network-configuration-modes) for hybrid options that keep Azure services private while allowing public cluster access. +- **NVIDIA OSMO** - Workflow orchestration and job scheduling +- **Azure Machine Learning** - Experiment tracking and model management +- **Azure Kubernetes Service** - Software in the Loop (SIL) training +- **Azure Arc for Kubernetes** - Software in the Loop (SIL) and Hardware in the Loop (HIL) training +- **Azure Storage** - Persistent data and checkpoint storage +- **Azure Key Vault** - Secure credential management +- **Azure Monitor** - Comprehensive logging and metrics -### 3. Configure Cluster + -```bash -cd ../../002-setup +## 🌍 Real World Examples -# Get cluster credentials (resource group and cluster name from terraform output) -az aks get-credentials --resource-group --name +**OSMO orchestration** on Azure enables production-scale robotics training across industries. Some examples include: -# Verify connectivity (requires VPN for private clusters) -kubectl cluster-info +- **Warehouse AMRs** - Train navigation policies with 1000+ parallel environments on auto-scaling AKS GPU nodes, checkpoint to Azure Storage, track experiments in Azure ML +- **Manufacturing Arms** - Develop manipulation strategies with physics-accurate simulation, leveraging Azure's global regions for distributed teams and pay-per-use GPU compute +- **Legged Robots** - Optimize locomotion policies with MLflow experiment tracking for sim-to-real transfer +- **Collaborative Robots** - Create safe interaction policies with Azure Monitor logging and metrics, enabling compliance auditing and performance diagnostics at scale -# Deploy GPU infrastructure -./01-deploy-robotics-charts.sh +See [OSMO workflow examples](workflows/osmo/) for job configuration templates. -# Deploy AzureML extension -./02-deploy-azureml-extension.sh +## πŸ§‘πŸ½β€πŸ’» Prerequisites and Requirements -# Deploy OSMO -./03-deploy-osmo-control-plane.sh -./04-deploy-osmo-backend.sh -``` - -See [Accessing OSMO](deploy/002-setup/README.md#-accessing-osmo) for port-forwarding and login instructions. +### Required Tools -### 4. Submit Workloads +- [pyenv](https://github.com/pyenv/pyenv) +- Python 3.11 (required by Isaac Sim 5.X) +- [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli) (v2.50+) +- [Terraform](https://www.terraform.io/downloads) (v1.5+) +- [NVIDIA OSMO CLI](https://developer.nvidia.com/osmo) (latest) +- [Docker](https://docs.docker.com/get-docker/) with NVIDIA Container Toolkit -**OSMO Training** – Submits to NVIDIA OSMO orchestrator: +### Azure Requirements -```bash -# Quick training run (100 iterations for testing) -./scripts/submit-osmo-training.sh --task Isaac-Velocity-Rough-Anymal-C-v0 --max-iterations 100 +- Azure subscription with contributor access +- Sufficient quota for GPU VMs (Standard_NC6s_v3 or higher) +- Azure Machine Learning workspace (or permissions to create one) -# Full training with custom environments -./scripts/submit-osmo-training.sh --task Isaac-Velocity-Rough-Anymal-D-v0 --num-envs 4096 +### NVIDIA Requirements -# Resume from checkpoint -./scripts/submit-osmo-training.sh --task Isaac-Velocity-Rough-Anymal-C-v0 \ - --checkpoint-uri "runs://checkpoints" --checkpoint-mode resume -``` +- NVIDIA Developer account with OSMO access +- NGC API key for container registry access -**AzureML Training** – Submits to Azure Machine Learning: +## πŸƒβ€βž‘οΈ Quick Start ```bash -# Quick training run -./scripts/submit-azureml-training.sh --task Isaac-Velocity-Rough-Anymal-C-v0 --max-iterations 100 - -# Full training with log streaming -./scripts/submit-azureml-training.sh --task Isaac-Velocity-Rough-Anymal-D-v0 --num-envs 4096 --stream - -# Resume training from registered model -./scripts/submit-azureml-training.sh --task Isaac-Velocity-Rough-Anymal-C-v0 \ - --checkpoint-uri "azureml://models/isaac-velocity-rough-anymal-c-v0/versions/1" \ - --checkpoint-mode resume +./setup-dev.sh ``` -**AzureML Validation** – Validates a trained model: +The setup script installs Python 3.11 via pyenv, creates a virtual environment at `.venv/`, and installs training dependencies. -```bash -# Validate latest model version (model name derived from task) -./scripts/submit-azureml-validation.sh --task Isaac-Velocity-Rough-Anymal-C-v0 +### VS Code Configuration -# Validate specific model version with custom episodes -./scripts/submit-azureml-validation.sh --model-name isaac-velocity-rough-anymal-c-v0 \ - --model-version 2 --eval-episodes 200 +The workspace is configured with `python.analysis.extraPaths` pointing to `src/`, enabling imports like: -# Validate with streaming logs -./scripts/submit-azureml-validation.sh --model-name my-policy --stream +```python +from training.utils import AzureMLContext, bootstrap_azure_ml ``` -> **Tip**: Run any script with `--help` for all available options. - -## Deployment Scenarios +Select the `.venv/bin/python` interpreter in VS Code for IntelliSense support -| Scenario | Storage Auth | Registry | Use Case | -|----------|--------------|----------|----------| -| Access Keys | Keys | nvcr.io | Development | -| Workload Identity | Federated | nvcr.io | Production | -| Workload Identity + ACR | Federated | Private ACR | Air-gapped | - -See [002-setup/README.md](deploy/002-setup/README.md) for detailed instructions. - -## Repository Structure +## 🧱 Repository Structure ```text . β”œβ”€β”€ deploy/ -β”‚ β”œβ”€β”€ 000-prerequisites/ # Azure CLI and provider setup -β”‚ β”œβ”€β”€ 001-iac/ # Terraform infrastructure -β”‚ └── 002-setup/ # Cluster configuration scripts -β”œβ”€β”€ scripts/ -β”‚ β”œβ”€β”€ submit-azureml-*.sh # AzureML job submission -β”‚ └── submit-osmo-*.sh # OSMO workflow submission -β”œβ”€β”€ workflows/ -β”‚ β”œβ”€β”€ azureml/ # AzureML job templates -β”‚ └── osmo/ # OSMO workflow templates -β”œβ”€β”€ src/training/ # Training code -└── docs/ # Additional documentation +β”‚ β”œβ”€β”€ 000-prerequisites/ # Prerequisites validation and setup +β”‚ β”œβ”€β”€ 001-iac/ # Infrastructure as Code deployment +β”‚ β”œβ”€β”€ 002-setup/ # Post-infrastructure setup +β”‚ β”œβ”€β”€ 003-data/ # Data preparation and upload +β”‚ └── 004-workflow/ # Training workflow execution +β”‚ β”œβ”€β”€ job-templates/ # Job configuration templates +β”‚ └── osmo/ # OSMO inline workflow submission (see osmo/README.md) +β”œβ”€β”€ src/ +β”‚ β”œβ”€β”€ terraform/ # Infrastructure as Code +β”‚ β”‚ └── modules/ # Reusable Terraform modules +β”‚ └── training/ # Training code and tasks +β”‚ β”œβ”€β”€ common/ # Shared utilities +β”‚ β”œβ”€β”€ scripts/ # Framework-specific training scripts configured for Azure services +β”‚ β”‚ β”œβ”€β”€ rsl_rl/ # RSL_RL training scripts +β”‚ β”‚ β”œβ”€β”€ skrl/ # SKRL training scripts +β”‚ └── tasks/ # Placeholder for Isaac Lab training tasks ``` -## Documentation - -| Guide | Description | -|-------|-------------| -| [Deploy Overview](deploy/README.md) | Deployment order and quick path | -| [Infrastructure](deploy/001-iac/README.md) | Terraform configuration and modules | -| [Cluster Setup](deploy/002-setup/README.md) | Scripts and deployment scenarios | -| [Scripts](scripts/README.md) | Training and validation submission | -| [Workflows](workflows/README.md) | Job and workflow templates | -| [MLflow Integration](docs/mlflow-integration.md) | Experiment tracking setup | - -## Cost Estimation - -Use the [Azure Pricing Calculator](https://azure.microsoft.com/pricing/calculator/) to estimate costs. Add these services based on the architecture: - -| Service | Configuration | Notes | -|---------|---------------|-------| -| Azure Kubernetes Service (AKS) | System pool: Standard_D4s_v3 (3 nodes) | Always-on control plane | -| Virtual Machines (Spot) | Standard_NV36ads_A10_v5 or NC-series | GPU nodes scale to zero when idle | -| Azure Database for PostgreSQL | Flexible Server, Burstable B1ms | OSMO workflow state | -| Azure Cache for Redis | Basic C0 or Standard C1 | OSMO job queue | -| Azure Machine Learning | Basic workspace | No additional compute costs (uses AKS) | -| Storage Account | Standard LRS, ~100GB | Checkpoints and datasets | -| Container Registry | Basic or Standard | Image storage | -| Log Analytics | ~5GB/day ingestion | Monitoring data | -| Azure Managed Grafana | Essential tier | Dashboards (optional) | -| VPN Gateway | VpnGw1 | Point-to-site access (optional) | - -GPU Spot VMs provide significant savings (60-90%) compared to on-demand pricing. Actual costs depend on training frequency, job duration, and data volumes. - -## Contributing - -We appreciate contributions! Check out [open issues](https://github.com/Azure-Samples/azure-nvidia-robotics-reference-architecture/issues) to get started. - -## Acknowledgments - -- [microsoft/edge-ai](https://github.com/microsoft/edge-ai) – Infrastructure components -- [NVIDIA Isaac Lab](https://github.com/isaac-sim/IsaacLab) – RL framework -- [NVIDIA OSMO](https://github.com/NVIDIA/OSMO) – Workflow orchestration - -## Responsible AI - -Microsoft encourages customers to review its Responsible AI Standard when developing AI-enabled systems to ensure ethical, safe, and inclusive AI practices. Learn more at [Microsoft's Responsible AI](https://www.microsoft.com/ai/responsible-ai). +## πŸͺͺ License -## Legal +This project is licensed under the MIT License. See [LICENSE](LICENSE) for details. -This project is licensed under the [MIT License](./LICENSE.md). +## 🀝 Support -**Security:** See [SECURITY.md](./SECURITY.md) for security policy and reporting vulnerabilities. +For issues and questions: -## Trademark Notice +- Review [microsoft/edge-ai](https://github.com/microsoft/edge-ai) documentation -> This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft -> trademarks or logos is subject to and must follow Microsoft's Trademark & Brand Guidelines. Use of Microsoft trademarks or logos in -> modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or -> logos are subject to those third-party's policies. +## πŸ™ Acknowledgments ---- +This reference architecture builds upon: - -*πŸ€– Crafted with precision by ✨Copilot following brilliant human instruction, -then carefully refined by our team of discerning human reviewers.* - +- [microsoft/edge-ai](https://github.com/microsoft/edge-ai) - Edge AI infrastructure components +- [NVIDIA Isaac Lab](https://github.com/isaac-sim/IsaacLab) - RL task framework +- [NVIDIA Isaac Sim](https://developer.nvidia.com/isaac-sim) - Physics simulation +- [NVIDIA OSMO](https://developer.nvidia.com/osmo) - Workflow orchestration +- [NVIDIA OSMO GitHub](https://github.com/NVIDIA/OSMO) - Workflow orchestration diff --git a/SECURITY.md b/SECURITY.md index 28b2488..7449a0b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,6 +1,6 @@ - +# Security -## Security + Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet) and [Xamarin](https://github.com/xamarin). diff --git a/SUPPORT.md b/SUPPORT.md index 1c94f25..09b04a9 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -8,12 +8,12 @@ Thank you for using the Azure NVIDIA Robotics Reference Architecture! This docum This is an open-source reference architecture maintained by Microsoft and community contributors. We provide community support through GitHub issue tracking with the following response commitments: -| Priority Level | Description | Response Time Target | -|----------------|-------------|----------------------| -| Security Vulnerability | Security issues (see [SECURITY.md](SECURITY.md)) | 24 hours (via MSRC) | -| Critical | Blocking issues preventing deployment | 1-2 business days | -| Major | Significant issues affecting functionality | 3-5 business days | -| General Issues & Questions | Bug reports, feature requests, questions | Best effort | +| Priority Level | Description | Response Time Target | +|----------------------------|--------------------------------------------------|----------------------| +| Security Vulnerability | Security issues (see [SECURITY.md](SECURITY.md)) | 24 hours (via MSRC) | +| Critical | Blocking issues preventing deployment | 1-2 business days | +| Major | Significant issues affecting functionality | 3-5 business days | +| General Issues & Questions | Bug reports, feature requests, questions | Best effort | **Note**: Response times indicate initial acknowledgment. Resolution time varies based on issue complexity and community availability. diff --git a/deploy/000-prerequisites/README.md b/deploy/000-prerequisites/README.md index 7a0ec20..abf8c2d 100644 --- a/deploy/000-prerequisites/README.md +++ b/deploy/000-prerequisites/README.md @@ -4,10 +4,10 @@ Azure CLI initialization and subscription setup for Terraform deployments. ## πŸ“œ Scripts -| Script | Purpose | -|--------|---------| -| `az-sub-init.sh` | Azure login and `ARM_SUBSCRIPTION_ID` export | -| `register-azure-providers.sh` | Register required Azure resource providers | +| Script | Purpose | +|-------------------------------|----------------------------------------------| +| `az-sub-init.sh` | Azure login and `ARM_SUBSCRIPTION_ID` export | +| `register-azure-providers.sh` | Register required Azure resource providers | ## πŸš€ Usage diff --git a/deploy/001-iac/README.md b/deploy/001-iac/README.md index f9d9037..f3a26d8 100644 --- a/deploy/001-iac/README.md +++ b/deploy/001-iac/README.md @@ -4,17 +4,17 @@ Terraform configuration for the robotics reference architecture. Deploys Azure r ## πŸ“‹ Prerequisites -| Tool | Version | Installation | -|------|---------|--------------| -| Azure CLI | Latest | `az login` | -| Terraform | 1.5+ | `terraform version` | +| Tool | Version | Installation | +|--------------|-----------------|---------------------------------| +| Azure CLI | Latest | `az login` | +| Terraform | 1.5+ | `terraform version` | | GPU VM quota | Region-specific | e.g., `Standard_NV36ads_A10_v5` | ### Azure RBAC Permissions -| Role | Scope | -|------|-------| -| Contributor | Subscription (new RG) or Resource Group (existing RG) | +| Role | Scope | +|-----------------------------------------|-------------------------------------------------------| +| Contributor | Subscription (new RG) or Resource Group (existing RG) | | Role Based Access Control Administrator | Subscription (new RG) or Resource Group (existing RG) | Terraform creates role assignments for managed identities, requiring `Microsoft.Authorization/roleAssignments/write` permission. The Contributor role explicitly blocks this action; the RBAC Administrator role provides it. @@ -40,35 +40,35 @@ terraform init && terraform apply -var-file=terraform.tfvars ### Core Variables -| Variable | Description | Required | -|----------|-------------|----------| -| `environment` | Deployment environment (dev, test, prod) | Yes | -| `resource_prefix` | Resource naming prefix | Yes | -| `location` | Azure region | Yes | -| `instance` | Instance identifier | No (default: "001") | -| `tags` | Resource group tags | No (default: {}) | +| Variable | Description | Required | +|-------------------|------------------------------------------|---------------------| +| `environment` | Deployment environment (dev, test, prod) | Yes | +| `resource_prefix` | Resource naming prefix | Yes | +| `location` | Azure region | Yes | +| `instance` | Instance identifier | No (default: "001") | +| `tags` | Resource group tags | No (default: {}) | ### AKS System Node Pool -| Variable | Description | Default | -|----------|-------------|---------| -| `system_node_pool_vm_size` | VM size for AKS system node pool | `Standard_D8ds_v5` | -| `system_node_pool_node_count` | Number of nodes for AKS system node pool | `1` | -| `system_node_pool_zones` | Availability zones for system node pool | `null` | -| `system_node_pool_enable_auto_scaling` | Enable auto-scaling for system node pool | `false` | -| `system_node_pool_min_count` | Minimum nodes when auto-scaling enabled | `null` | -| `system_node_pool_max_count` | Maximum nodes when auto-scaling enabled | `null` | +| Variable | Description | Default | +|----------------------------------------|------------------------------------------|--------------------| +| `system_node_pool_vm_size` | VM size for AKS system node pool | `Standard_D8ds_v5` | +| `system_node_pool_node_count` | Number of nodes for AKS system node pool | `1` | +| `system_node_pool_zones` | Availability zones for system node pool | `null` | +| `system_node_pool_enable_auto_scaling` | Enable auto-scaling for system node pool | `false` | +| `system_node_pool_min_count` | Minimum nodes when auto-scaling enabled | `null` | +| `system_node_pool_max_count` | Maximum nodes when auto-scaling enabled | `null` | ### Feature Flags -| Variable | Description | Default | -|----------|-------------|---------| -| `should_enable_nat_gateway` | Deploy NAT Gateway for outbound connectivity | `true` | -| `should_enable_private_endpoint` | Deploy private endpoints and DNS zones for Azure services | `true` | -| `should_enable_private_aks_cluster` | Make AKS API endpoint private (requires VPN for kubectl) | `true` | -| `should_enable_public_network_access` | Allow public access to resources | `true` | -| `should_deploy_postgresql` | Deploy PostgreSQL Flexible Server for OSMO | `true` | -| `should_deploy_redis` | Deploy Azure Managed Redis for OSMO | `true` | +| Variable | Description | Default | +|---------------------------------------|-----------------------------------------------------------|---------| +| `should_enable_nat_gateway` | Deploy NAT Gateway for outbound connectivity | `true` | +| `should_enable_private_endpoint` | Deploy private endpoints and DNS zones for Azure services | `true` | +| `should_enable_private_aks_cluster` | Make AKS API endpoint private (requires VPN for kubectl) | `true` | +| `should_enable_public_network_access` | Allow public access to resources | `true` | +| `should_deploy_postgresql` | Deploy PostgreSQL Flexible Server for OSMO | `true` | +| `should_deploy_redis` | Deploy Azure Managed Redis for OSMO | `true` | ### Network Configuration Modes @@ -185,33 +185,33 @@ Root Module (001-iac/) ### Resources by Category -| Category | Resources | -|----------|-----------| -| Networking | VNet, subnets (main, PE, AKS, GPU pools), NSG, NAT Gateway, DNS Private Resolver | -| Security | Key Vault (RBAC mode), ML identity, OSMO identity | -| Observability | Log Analytics, App Insights, Monitor Workspace, Grafana, DCE, AMPLS | -| Storage | Storage Account (blob/file), Container Registry (Premium) | -| Machine Learning | AzureML Workspace | -| AKS | Cluster with Azure CNI Overlay, system pool, GPU node pools | -| Private DNS | 11 core zones (Key Vault, Storage, ACR, ML, AKS, Monitor) | -| OSMO Services | PostgreSQL Flexible Server (HA), Azure Managed Redis | +| Category | Resources | +|------------------|----------------------------------------------------------------------------------| +| Networking | VNet, subnets (main, PE, AKS, GPU pools), NSG, NAT Gateway, DNS Private Resolver | +| Security | Key Vault (RBAC mode), ML identity, OSMO identity | +| Observability | Log Analytics, App Insights, Monitor Workspace, Grafana, DCE, AMPLS | +| Storage | Storage Account (blob/file), Container Registry (Premium) | +| Machine Learning | AzureML Workspace | +| AKS | Cluster with Azure CNI Overlay, system pool, GPU node pools | +| Private DNS | 11 core zones (Key Vault, Storage, ACR, ML, AKS, Monitor) | +| OSMO Services | PostgreSQL Flexible Server (HA), Azure Managed Redis | ### Conditional Resources -| Condition | Resources Created | -|-----------|-------------------| -| `should_enable_private_endpoint` | Private endpoints, 11+ DNS zones, DNS resolver, AMPLS | -| `should_enable_nat_gateway` | NAT Gateway, Public IP, subnet associations | -| `should_deploy_postgresql` | PostgreSQL server, databases, delegated subnet, DNS zone | -| `should_deploy_redis` | Redis cache, private endpoint (if PE enabled), DNS zone | +| Condition | Resources Created | +|----------------------------------|----------------------------------------------------------| +| `should_enable_private_endpoint` | Private endpoints, 11+ DNS zones, DNS resolver, AMPLS | +| `should_enable_nat_gateway` | NAT Gateway, Public IP, subnet associations | +| `should_deploy_postgresql` | PostgreSQL server, databases, delegated subnet, DNS zone | +| `should_deploy_redis` | Redis cache, private endpoint (if PE enabled), DNS zone | ## πŸ“¦ Modules -| Module | Purpose | -|--------|---------| +| Module | Purpose | +|-------------------------------|-----------------------------------------------------------------| | [platform](modules/platform/) | Networking, storage, Key Vault, ML workspace, PostgreSQL, Redis | -| [sil](modules/sil/) | AKS cluster with GPU node pools | -| [vpn](modules/vpn/) | VPN Gateway module (used by vpn/ standalone deployment) | +| [sil](modules/sil/) | AKS cluster with GPU node pools | +| [vpn](modules/vpn/) | VPN Gateway module (used by vpn/ standalone deployment) | ## πŸ“€ Outputs @@ -338,12 +338,12 @@ az resource list --resource-group --query "[].{name:name, type: Azure retains certain deleted resources in a soft-deleted state. Redeployment fails when Terraform attempts to create a resource with the same name as a soft-deleted one. -| Resource | Soft Delete | Retention Period | Blocks Redeployment | -|----------|-------------|------------------|---------------------| -| Key Vault | Mandatory | 7-90 days (configurable) | Yes | -| Azure ML Workspace | Mandatory | 14 days (fixed) | Yes | -| Container Registry | Opt-in (preview) | 1-90 days (configurable) | No (disabled by default) | -| Storage Account | Recovery only | 14 days | No (same-name creation allowed) | +| Resource | Soft Delete | Retention Period | Blocks Redeployment | +|--------------------|------------------|--------------------------|---------------------------------| +| Key Vault | Mandatory | 7-90 days (configurable) | Yes | +| Azure ML Workspace | Mandatory | 14 days (fixed) | Yes | +| Container Registry | Opt-in (preview) | 1-90 days (configurable) | No (disabled by default) | +| Storage Account | Recovery only | 14 days | No (same-name creation allowed) | #### Purge Soft-Deleted Key Vault diff --git a/deploy/001-iac/dns/README.md b/deploy/001-iac/dns/README.md index e171a7a..e8f03f0 100644 --- a/deploy/001-iac/dns/README.md +++ b/deploy/001-iac/dns/README.md @@ -26,11 +26,11 @@ terraform apply -var="osmo_loadbalancer_ip=10.0.x.x" ## βš™οΈ Configuration -| Variable | Description | Default | -|----------|-------------|---------| -| `osmo_loadbalancer_ip` | Internal LoadBalancer IP | (required) | -| `osmo_private_dns_zone_name` | DNS zone name | `osmo.local` | -| `osmo_hostname` | Hostname within zone | `dev` | +| Variable | Description | Default | +|------------------------------|--------------------------|--------------| +| `osmo_loadbalancer_ip` | Internal LoadBalancer IP | (required) | +| `osmo_private_dns_zone_name` | DNS zone name | `osmo.local` | +| `osmo_hostname` | Hostname within zone | `dev` | ## πŸ’‘ How It Works diff --git a/deploy/001-iac/vpn/README.md b/deploy/001-iac/vpn/README.md index 6029212..36ff777 100644 --- a/deploy/001-iac/vpn/README.md +++ b/deploy/001-iac/vpn/README.md @@ -29,12 +29,12 @@ Deployment takes 20-30 minutes for the VPN Gateway. ## βš™οΈ Configuration -| Variable | Description | Default | -|----------|-------------|---------| -| `gateway_subnet_address_prefix` | GatewaySubnet CIDR (min /27) | `10.0.3.0/27` | -| `vpn_gateway_config.sku` | Gateway SKU | `VpnGw1` | -| `vpn_gateway_config.client_address_pool` | P2S client IP range | `["192.168.200.0/24"]` | -| `aad_auth_config.enabled` | Enable Azure AD auth | `true` | +| Variable | Description | Default | +|------------------------------------------|------------------------------|------------------------| +| `gateway_subnet_address_prefix` | GatewaySubnet CIDR (min /27) | `10.0.3.0/27` | +| `vpn_gateway_config.sku` | Gateway SKU | `VpnGw1` | +| `vpn_gateway_config.client_address_pool` | P2S client IP range | `["192.168.200.0/24"]` | +| `aad_auth_config.enabled` | Enable Azure AD auth | `true` | ## πŸ” Authentication Options @@ -63,11 +63,11 @@ root_certificate_public_data = "MIIC5jCCAc6g..." # Base64-encoded cert ### Install Azure VPN Client -| Platform | Installation | -|----------|--------------| -| Windows | [Microsoft Store](https://apps.microsoft.com/detail/9NP355QT2SQB) | -| macOS | [App Store](https://apps.apple.com/us/app/azure-vpn-client/id1553936137) | -| Ubuntu 20.04/22.04 | [Microsoft Docs](https://learn.microsoft.com/en-us/azure/vpn-gateway/point-to-site-entra-vpn-client-linux#install-the-azure-vpn-client) | +| Platform | Installation | +|--------------------|-----------------------------------------------------------------------------------------------------------------------------------| +| Windows | [Microsoft Store](https://apps.microsoft.com/detail/9NP355QT2SQB) | +| macOS | [App Store](https://apps.apple.com/us/app/azure-vpn-client/id1553936137) | +| Ubuntu 20.04/22.04 | [Microsoft Docs](https://learn.microsoft.com/azure/vpn-gateway/point-to-site-entra-vpn-client-linux#install-the-azure-vpn-client) | ### Download VPN Configuration diff --git a/deploy/002-setup/README.md b/deploy/002-setup/README.md index 3f29d37..78ad999 100644 --- a/deploy/002-setup/README.md +++ b/deploy/002-setup/README.md @@ -10,6 +10,7 @@ AKS cluster configuration for robotics workloads with AzureML and NVIDIA OSMO. - kubectl, Helm 3.x, jq installed - OSMO CLI (`osmo`) for backend deployment + > [!NOTE] > Scripts automatically install required Azure CLI extensions (`k8s-extension`, `ml`) if missing. @@ -17,17 +18,18 @@ AKS cluster configuration for robotics workloads with AzureML and NVIDIA OSMO. > The default infrastructure deploys a **private AKS cluster**. You must deploy the VPN Gateway and connect before running these scripts. See [VPN setup](../001-iac/vpn/README.md#-vpn-client-setup) for instructions. Without VPN, `kubectl` commands fail with `no such host` errors. > > To skip VPN, set `should_enable_private_aks_cluster = false` in your Terraform configuration. See [Network Configuration Modes](../001-iac/README.md#network-configuration-modes). + ### Azure RBAC Permissions For least-privilege access: -| Role | Scope | Purpose | -|------|-------|---------| -| Azure Kubernetes Service Cluster User Role | AKS Cluster | Get cluster credentials | -| Contributor | Resource Group | Extension and FIC creation | -| Key Vault Secrets User | Key Vault | Read PostgreSQL/Redis credentials | -| Storage Blob Data Contributor | Storage Account | Create workflow containers | +| Role | Scope | Purpose | +|--------------------------------------------|-----------------|-----------------------------------| +| Azure Kubernetes Service Cluster User Role | AKS Cluster | Get cluster credentials | +| Contributor | Resource Group | Extension and FIC creation | +| Key Vault Secrets User | Key Vault | Read PostgreSQL/Redis credentials | +| Storage Blob Data Contributor | Storage Account | Create workflow containers | ## πŸš€ Quick Start @@ -143,11 +145,11 @@ cd ../002-setup ## βš–οΈ Scenario Comparison -| | Access Keys | Workload Identity | Workload Identity + ACR | -|---|:---:|:---:|:---:| -| Storage Auth | Access Keys | Workload Identity | Workload Identity | -| Registry | nvcr.io | nvcr.io | Private ACR | -| Air-Gap | βœ— | βœ— | βœ“ | +| | Access Keys | Workload Identity | Workload Identity + ACR | +|--------------|:-----------:|:-----------------:|:-----------------------:| +| Storage Auth | Access Keys | Workload Identity | Workload Identity | +| Registry | nvcr.io | nvcr.io | Private ACR | +| Air-Gap | βœ— | βœ— | βœ“ | ## πŸ”’ Security Considerations for Public Deployments @@ -174,31 +176,31 @@ See [OSMO Keycloak configuration](https://nvidia.github.io/OSMO/main/deployment_ ## πŸ“œ Scripts -| Script | Purpose | -|--------|---------| -| `01-deploy-robotics-charts.sh` | GPU Operator, KAI Scheduler | -| `02-deploy-azureml-extension.sh` | AzureML K8s extension, compute attach | -| `03-deploy-osmo-control-plane.sh` | OSMO service, router, web-ui | -| `04-deploy-osmo-backend.sh` | Backend operator, workflow storage | +| Script | Purpose | +|-----------------------------------|---------------------------------------| +| `01-deploy-robotics-charts.sh` | GPU Operator, KAI Scheduler | +| `02-deploy-azureml-extension.sh` | AzureML K8s extension, compute attach | +| `03-deploy-osmo-control-plane.sh` | OSMO service, router, web-ui | +| `04-deploy-osmo-backend.sh` | Backend operator, workflow storage | ## 🚩 Script Flags -| Flag | Scripts | Description | -|------|---------|-------------| -| `--use-access-keys` | `04-deploy-osmo-backend.sh` | Storage account keys instead of workload identity | -| `--use-acr` | `03-deploy-osmo-control-plane.sh`, `04-deploy-osmo-backend.sh` | Pull from Terraform-deployed ACR | -| `--acr-name NAME` | `03-deploy-osmo-control-plane.sh`, `04-deploy-osmo-backend.sh` | Specify alternate ACR | -| `--config-preview` | All | Print config and exit | +| Flag | Scripts | Description | +|---------------------|----------------------------------------------------------------|---------------------------------------------------| +| `--use-access-keys` | `04-deploy-osmo-backend.sh` | Storage account keys instead of workload identity | +| `--use-acr` | `03-deploy-osmo-control-plane.sh`, `04-deploy-osmo-backend.sh` | Pull from Terraform-deployed ACR | +| `--acr-name NAME` | `03-deploy-osmo-control-plane.sh`, `04-deploy-osmo-backend.sh` | Specify alternate ACR | +| `--config-preview` | All | Print config and exit | ## βš™οΈ Configuration Scripts read from Terraform outputs in `../001-iac/`. Override with environment variables: -| Variable | Description | -|----------|-------------| +| Variable | Description | +|-------------------------|--------------------| | `AZURE_SUBSCRIPTION_ID` | Azure subscription | -| `AZURE_RESOURCE_GROUP` | Resource group | -| `AKS_CLUSTER_NAME` | Cluster name | +| `AZURE_RESOURCE_GROUP` | Resource group | +| `AKS_CLUSTER_NAME` | Cluster name | ## βœ… Verification @@ -221,10 +223,10 @@ OSMO services are deployed to the `osmo-control-plane` namespace. Access method When connected to VPN, OSMO services are accessible via the internal load balancer: -| Service | URL | -|---------|-----| -| UI Dashboard | http://10.0.5.7 | -| API Service | http://10.0.5.7/api | +| Service | URL | +|--------------|-----------------------| +| UI Dashboard | `http://10.0.5.7` | +| API Service | `http://10.0.5.7/api` | ```bash # Login to OSMO via internal load balancer @@ -242,11 +244,11 @@ osmo backend list If `should_enable_private_aks_cluster = false` and you are not using VPN, use `kubectl port-forward`: -| Service | Command | Local URL | -|---------|---------|-----------| -| UI Dashboard | `kubectl port-forward svc/osmo-ui 3000:80 -n osmo-control-plane` | http://localhost:3000 | -| API Service | `kubectl port-forward svc/osmo-service 9000:80 -n osmo-control-plane` | http://localhost:9000 | -| Router | `kubectl port-forward svc/osmo-router 8080:80 -n osmo-control-plane` | http://localhost:8080 | +| Service | Command | Local URL | +|--------------|-----------------------------------------------------------------------|-------------------------| +| UI Dashboard | `kubectl port-forward svc/osmo-ui 3000:80 -n osmo-control-plane` | `http://localhost:3000` | +| API Service | `kubectl port-forward svc/osmo-service 9000:80 -n osmo-control-plane` | `http://localhost:9000` | +| Router | `kubectl port-forward svc/osmo-router 8080:80 -n osmo-control-plane` | `http://localhost:8080` | ```bash # Terminal 1: Start port-forward for API service @@ -322,7 +324,7 @@ kubectl describe sa osmo-service -n osmo-control-plane ## πŸ“ Directory Structure -``` +```text 002-setup/ β”œβ”€β”€ 01-deploy-robotics-charts.sh β”œβ”€β”€ 02-deploy-azureml-extension.sh @@ -338,11 +340,11 @@ kubectl describe sa osmo-service -n osmo-control-plane ## 🧩 Optional Scripts -| Script | Purpose | -|--------|---------| -| `optional/deploy-volcano-scheduler.sh` | Volcano (alternative to KAI) | -| `optional/uninstall-volcano-scheduler.sh` | Remove Volcano scheduler | -| `optional/add-user-to-platform.sh` | Add user to OSMO platform | +| Script | Purpose | +|-------------------------------------------|------------------------------| +| `optional/deploy-volcano-scheduler.sh` | Volcano (alternative to KAI) | +| `optional/uninstall-volcano-scheduler.sh` | Remove Volcano scheduler | +| `optional/add-user-to-platform.sh` | Add user to OSMO platform | ## πŸ—‘οΈ Cleanup @@ -350,12 +352,12 @@ Uninstall scripts in `cleanup/` remove cluster components in reverse deployment ### Cleanup Scripts -| Script | Removes | -|--------|---------| -| `cleanup/uninstall-osmo-backend.sh` | Backend operator, workflow namespaces | -| `cleanup/uninstall-osmo-control-plane.sh` | OSMO service, router, web-ui | -| `cleanup/uninstall-azureml-extension.sh` | ML extension, compute target, FICs | -| `cleanup/uninstall-robotics-charts.sh` | GPU Operator, KAI Scheduler | +| Script | Removes | +|-------------------------------------------|---------------------------------------| +| `cleanup/uninstall-osmo-backend.sh` | Backend operator, workflow namespaces | +| `cleanup/uninstall-osmo-control-plane.sh` | OSMO service, router, web-ui | +| `cleanup/uninstall-azureml-extension.sh` | ML extension, compute target, FICs | +| `cleanup/uninstall-robotics-charts.sh` | GPU Operator, KAI Scheduler | ### Uninstall Order @@ -381,14 +383,14 @@ cd cleanup By default, uninstall scripts preserve data. Use flags for complete removal: -| Script | Preservation Flag | Description | -|--------|-------------------|-------------| -| `uninstall-osmo-backend.sh` | `--delete-container` | Deletes blob container with workflow artifacts | -| `uninstall-osmo-control-plane.sh` | `--delete-mek` | Removes encryption key ConfigMap | -| `uninstall-osmo-control-plane.sh` | `--purge-postgres` | Drops OSMO tables from PostgreSQL | -| `uninstall-osmo-control-plane.sh` | `--purge-redis` | Flushes OSMO keys from Redis | -| `uninstall-robotics-charts.sh` | `--delete-namespaces` | Removes gpu-operator, kai-scheduler namespaces | -| `uninstall-robotics-charts.sh` | `--delete-crds` | Removes GPU Operator CRDs | +| Script | Preservation Flag | Description | +|-----------------------------------|-----------------------|------------------------------------------------| +| `uninstall-osmo-backend.sh` | `--delete-container` | Deletes blob container with workflow artifacts | +| `uninstall-osmo-control-plane.sh` | `--delete-mek` | Removes encryption key ConfigMap | +| `uninstall-osmo-control-plane.sh` | `--purge-postgres` | Drops OSMO tables from PostgreSQL | +| `uninstall-osmo-control-plane.sh` | `--purge-redis` | Flushes OSMO keys from Redis | +| `uninstall-robotics-charts.sh` | `--delete-namespaces` | Removes gpu-operator, kai-scheduler namespaces | +| `uninstall-robotics-charts.sh` | `--delete-crds` | Removes GPU Operator CRDs | ### Full Component Cleanup diff --git a/deploy/README.md b/deploy/README.md index 963d0ba..fc10709 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -4,12 +4,12 @@ Infrastructure deployment and cluster configuration for the robotics reference a ## πŸ“‹ Deployment Order -| Step | Folder | Description | Time | -|:----:|--------|-------------|------| -| 1 | [000-prerequisites](000-prerequisites/) | Azure CLI login, subscription setup | 2 min | -| 2 | [001-iac](001-iac/) | Terraform: AKS, ML workspace, storage, PostgreSQL, Redis | 30-40 min | -| 3 | [001-iac/vpn](001-iac/vpn/) | VPN Gateway for private cluster access | 20-30 min | -| 4 | [002-setup](002-setup/) | Cluster config: GPU Operator, OSMO, AzureML extension | 30 min | +| Step | Folder | Description | Time | +|:----:|-----------------------------------------|----------------------------------------------------------|-----------| +| 1 | [000-prerequisites](000-prerequisites/) | Azure CLI login, subscription setup | 2 min | +| 2 | [001-iac](001-iac/) | Terraform: AKS, ML workspace, storage, PostgreSQL, Redis | 30-40 min | +| 3 | [001-iac/vpn](001-iac/vpn/) | VPN Gateway for private cluster access | 20-30 min | +| 4 | [002-setup](002-setup/) | Cluster config: GPU Operator, OSMO, AzureML extension | 30 min | > [!IMPORTANT] > The default configuration deploys a **private AKS cluster**. The cluster API endpoint is not publicly accessible. You must deploy the VPN Gateway (step 3) and connect before running cluster setup scripts (step 4). @@ -85,11 +85,11 @@ See the [root README](../README.md) for architecture details. Remove deployed components in reverse order. Cluster components must be removed before infrastructure. -| Step | Folder | Description | Time | -|:----:|--------|-------------|------| -| 1 | [002-setup/cleanup](002-setup/cleanup/) | Uninstall Helm charts, extensions, namespaces | 10-15 min | -| 2 | [001-iac/vpn](001-iac/vpn/) | Destroy VPN Gateway | 10-15 min | -| 3 | [001-iac](001-iac/) | Terraform destroy or resource group deletion | 20-30 min | +| Step | Folder | Description | Time | +|:----:|-----------------------------------------|-----------------------------------------------|-----------| +| 1 | [002-setup/cleanup](002-setup/cleanup/) | Uninstall Helm charts, extensions, namespaces | 10-15 min | +| 2 | [001-iac/vpn](001-iac/vpn/) | Destroy VPN Gateway | 10-15 min | +| 3 | [001-iac](001-iac/) | Terraform destroy or resource group deletion | 20-30 min | ### Partial Cleanup (Cluster Components Only) @@ -137,6 +137,7 @@ az group delete --name --yes --no-wait ``` This deletes all resources in the group immediately. Use when: + - Terraform created the resource group - You want to remove everything without preserving state - Terraform state is corrupted or unavailable diff --git a/docs/azureml-validation-job-debugging.md b/docs/azureml-validation-job-debugging.md index 322a86e..1459f57 100644 --- a/docs/azureml-validation-job-debugging.md +++ b/docs/azureml-validation-job-debugging.md @@ -20,7 +20,7 @@ After successfully training an IsaacLab policy using `osmorobo-submit.sh`, the u When first submitting the validation job, AzureML rejected the job YAML with schema validation errors: -``` +```text ValidationError: The value 'string' of input type is not valid for Command job. Supported types are ['uri_file', 'uri_folder', 'mlflow_model', 'custom_model', 'mltable', 'triton_model'] ``` @@ -31,7 +31,7 @@ Supported types are ['uri_file', 'uri_folder', 'mlflow_model', 'custom_model', ' After fixing the input types, a second error occurred: -``` +```text ValidationError: Empty string is not allowed for input value ``` @@ -41,7 +41,7 @@ ValidationError: Empty string is not allowed for input value After fixing the YAML schema, the job submitted successfully but failed at runtime: -``` +```text Error Code: ScriptExecution.StreamAccess.Authentication permission denied when access stream ``` @@ -190,7 +190,7 @@ kubectl logs -n azureml $POD -c data-capability **Key Finding**: -``` +```text Failed to get symmetric key for getting AML token: 'failed to load certificate: stat /tmp/azureml/cr/j/.../sha1-.pfx: no such file or directory' ``` @@ -231,16 +231,16 @@ azureml_config = { ### 2. AzureML Job YAML Schema Fixes -#### [deploy/004-workflow/azureml/jobs/isaaclab-validate.yaml](../deploy/004-workflow/azureml/jobs/isaaclab-validate.yaml) +#### [workflows/azureml/validate.yaml](../workflows/azureml/validate.yaml) Fixed input schema to comply with AzureML command job requirements: -| Change | Before | After | -|--------|--------|-------| -| Model input type | `type: mlflow_model` | `type: custom_model` | -| Literal inputs | Had `type: string`, `type: integer` | Removed type declarations (simple key-value) | -| Empty strings | `task: ""` | `task: auto` (sentinel value) | -| Mount mode | `mode: ro_mount` | `mode: download` (attempted workaround) | +| Change | Before | After | +|------------------|-------------------------------------|----------------------------------------------| +| Model input type | `type: mlflow_model` | `type: custom_model` | +| Literal inputs | Had `type: string`, `type: integer` | Removed type declarations (simple key-value) | +| Empty strings | `task: ""` | `task: auto` (sentinel value) | +| Mount mode | `mode: ro_mount` | `mode: download` (attempted workaround) | **Rationale**: AzureML command jobs don't support typed literal inputs like pipeline jobs do. @@ -294,7 +294,7 @@ kubectl label serviceaccount -n azureml default \ **Error Code**: `ScriptExecution.StreamAccess.Authentication` -``` +```text permission denied when access stream. Reason: None PermissionDenied(None) Error Message: Authentication failed when trying to access the stream. @@ -302,19 +302,19 @@ Error Message: Authentication failed when trying to access the stream. **Root Cause Analysis**: -| Factor | Status | -|--------|--------| -| Storage account `allowSharedKeyAccess` | `false` (security best practice) | -| ML Identity has `Storage Blob Data Contributor` | βœ… Verified | -| Federated Identity Credentials exist | βœ… Created | -| AzureML Extension has `identityType=UserAssigned` | βœ… Configured | -| Data-capability using workload identity | ❌ **Not working** | +| Factor | Status | +|---------------------------------------------------|----------------------------------| +| Storage account `allowSharedKeyAccess` | `false` (security best practice) | +| ML Identity has `Storage Blob Data Contributor` | βœ… Verified | +| Federated Identity Credentials exist | βœ… Created | +| AzureML Extension has `identityType=UserAssigned` | βœ… Configured | +| Data-capability using workload identity | ❌ **Not working** | The AzureML extension's `data-capability` sidecar container is not properly authenticating to Azure Blob Storage using workload identity federation. Despite all the correct configurations being in place, the token exchange isn't happening. ### Evidence from Pod Logs -``` +```text Failed to get symmetric key for getting AML token: 'failed to load certificate: stat /tmp/azureml/cr/j/.../sha1-.pfx: no such file or directory' ``` @@ -323,13 +323,13 @@ This suggests the data-capability container is still trying to use certificate-b ## Jobs Submitted (All Failed) -| Job Name | Status | Error | -|----------|--------|-------| -| `sharp_pen_l66lnkmpfy` | Failed | Permission denied (before FIC creation) | -| `tough_brick_y9qw8npw1m` | Failed | Permission denied (after FIC creation) | -| `olden_table_61c5q9vx4k` | Failed | Permission denied (after SA annotation) | -| `frosty_arch_jtzh3fky15` | Failed | Permission denied (after extension update) | -| `blue_cabbage_qjqy4kv8y9` | Failed | Permission denied (with download mode) | +| Job Name | Status | Error | +|---------------------------|--------|--------------------------------------------| +| `sharp_pen_l66lnkmpfy` | Failed | Permission denied (before FIC creation) | +| `tough_brick_y9qw8npw1m` | Failed | Permission denied (after FIC creation) | +| `olden_table_61c5q9vx4k` | Failed | Permission denied (after SA annotation) | +| `frosty_arch_jtzh3fky15` | Failed | Permission denied (after extension update) | +| `blue_cabbage_qjqy4kv8y9` | Failed | Permission denied (with download mode) | ## Options to Resolve @@ -390,13 +390,13 @@ Submit jobs to AzureML managed compute (e.g., `gpu-cluster`) instead of the atta ## Files Changed Summary -| File | Change Type | -|------|-------------| -| `deploy/001-iac/main.tf` | Added `should_install_extension`, `should_federate_ml_identity` | -| `deploy/004-workflow/azureml/jobs/isaaclab-validate.yaml` | Fixed input schema, changed mount to download | +| File | Change Type | +|-----------------------------------|-----------------------------------------------------------------| +| `deploy/001-iac/main.tf` | Added `should_install_extension`, `should_federate_ml_identity` | +| `workflows/azureml/validate.yaml` | Fixed input schema, changed mount to download | ## Related Resources -- [AzureML Kubernetes Compute Troubleshooting](https://learn.microsoft.com/en-us/azure/machine-learning/how-to-attach-kubernetes-anywhere) -- [Workload Identity Federation](https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview) -- [Storage Account Shared Key Disabled](https://learn.microsoft.com/en-us/azure/storage/common/shared-key-authorization-prevent) +- [AzureML Kubernetes Compute Troubleshooting](https://learn.microsoft.com/azure/machine-learning/how-to-attach-kubernetes-anywhere) +- [Workload Identity Federation](https://learn.microsoft.com/azure/aks/workload-identity-overview) +- [Storage Account Shared Key Disabled](https://learn.microsoft.com/azure/storage/common/shared-key-authorization-prevent) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..5eb03d7 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3926 @@ +{ + "name": "azure-nvidia-robotics-reference-architecture", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "azure-nvidia-robotics-reference-architecture", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "cspell": "9.4.0", + "markdown-link-check": "3.14.2", + "markdown-table-formatter": "1.6.0", + "markdownlint-cli2": "0.19.1" + } + }, + "node_modules/@cspell/cspell-bundled-dicts": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-9.4.0.tgz", + "integrity": "sha512-Hm2gpMg/lRv4fKtiO2NfBiaJdFZVVb1V1a+IVhlD9qCuObLhCt60Oze2kD1dQzhbaIX756cs/eyxa5bQ5jihhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/dict-ada": "^4.1.1", + "@cspell/dict-al": "^1.1.1", + "@cspell/dict-aws": "^4.0.16", + "@cspell/dict-bash": "^4.2.2", + "@cspell/dict-companies": "^3.2.7", + "@cspell/dict-cpp": "^6.0.15", + "@cspell/dict-cryptocurrencies": "^5.0.5", + "@cspell/dict-csharp": "^4.0.7", + "@cspell/dict-css": "^4.0.18", + "@cspell/dict-dart": "^2.3.1", + "@cspell/dict-data-science": "^2.0.12", + "@cspell/dict-django": "^4.1.5", + "@cspell/dict-docker": "^1.1.16", + "@cspell/dict-dotnet": "^5.0.10", + "@cspell/dict-elixir": "^4.0.8", + "@cspell/dict-en_us": "^4.4.24", + "@cspell/dict-en-common-misspellings": "^2.1.8", + "@cspell/dict-en-gb-mit": "^3.1.14", + "@cspell/dict-filetypes": "^3.0.14", + "@cspell/dict-flutter": "^1.1.1", + "@cspell/dict-fonts": "^4.0.5", + "@cspell/dict-fsharp": "^1.1.1", + "@cspell/dict-fullstack": "^3.2.7", + "@cspell/dict-gaming-terms": "^1.1.2", + "@cspell/dict-git": "^3.0.7", + "@cspell/dict-golang": "^6.0.24", + "@cspell/dict-google": "^1.0.9", + "@cspell/dict-haskell": "^4.0.6", + "@cspell/dict-html": "^4.0.13", + "@cspell/dict-html-symbol-entities": "^4.0.4", + "@cspell/dict-java": "^5.0.12", + "@cspell/dict-julia": "^1.1.1", + "@cspell/dict-k8s": "^1.0.12", + "@cspell/dict-kotlin": "^1.1.1", + "@cspell/dict-latex": "^4.0.4", + "@cspell/dict-lorem-ipsum": "^4.0.5", + "@cspell/dict-lua": "^4.0.8", + "@cspell/dict-makefile": "^1.0.5", + "@cspell/dict-markdown": "^2.0.13", + "@cspell/dict-monkeyc": "^1.0.11", + "@cspell/dict-node": "^5.0.8", + "@cspell/dict-npm": "^5.2.25", + "@cspell/dict-php": "^4.1.0", + "@cspell/dict-powershell": "^5.0.15", + "@cspell/dict-public-licenses": "^2.0.15", + "@cspell/dict-python": "^4.2.23", + "@cspell/dict-r": "^2.1.1", + "@cspell/dict-ruby": "^5.0.9", + "@cspell/dict-rust": "^4.0.12", + "@cspell/dict-scala": "^5.0.8", + "@cspell/dict-shell": "^1.1.2", + "@cspell/dict-software-terms": "^5.1.15", + "@cspell/dict-sql": "^2.2.1", + "@cspell/dict-svelte": "^1.0.7", + "@cspell/dict-swift": "^2.0.6", + "@cspell/dict-terraform": "^1.1.3", + "@cspell/dict-typescript": "^3.2.3", + "@cspell/dict-vue": "^3.0.5", + "@cspell/dict-zig": "^1.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@cspell/cspell-json-reporter": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-json-reporter/-/cspell-json-reporter-9.4.0.tgz", + "integrity": "sha512-TpHY7t13xNhcZF9bwOfgVIhcyPDamMnxU/TBYhf4mPtXPLrZ5gBTg3UZh0/9Zn3naMjmJtngdsLvB2wai9xBlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-types": "9.4.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@cspell/cspell-pipe": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-pipe/-/cspell-pipe-9.4.0.tgz", + "integrity": "sha512-cI0sUe7SB99hJB1T6PhH/MpSrnml1kOekTCE+VH3Eb7zkVP5/mwJXs8BlufdvwBona+Cgkx6jeWlhFpxLc39Yg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@cspell/cspell-resolver": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-resolver/-/cspell-resolver-9.4.0.tgz", + "integrity": "sha512-o9gbbdXlhxG2rqtGqQ7xZ8MGDDsPLbskBnTeuA++ix4Ch/HdjrBNmKReIGAEqJPfP+JGgoEKqFISHUDKAJ/ygQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-directory": "^4.0.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@cspell/cspell-service-bus": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-service-bus/-/cspell-service-bus-9.4.0.tgz", + "integrity": "sha512-UottRlFPN6FGUfojx5HtUPZTeYXg2rf2HvO/HLh0KicirVYO16vFxTevg9MyOvw1EXSsDRz8ECANjiE7fnzBCQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@cspell/cspell-types": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-types/-/cspell-types-9.4.0.tgz", + "integrity": "sha512-vSpd50OfmthBH0aRFRLA2zJFtwli3ntHA0WAOJ8tIMLUCJgF3udooRXFeX3wR8ri69C9mc3864LC4inyRC/E9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@cspell/dict-ada": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-ada/-/dict-ada-4.1.1.tgz", + "integrity": "sha512-E+0YW9RhZod/9Qy2gxfNZiHJjCYFlCdI69br1eviQQWB8yOTJX0JHXLs79kOYhSW0kINPVUdvddEBe6Lu6CjGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-al": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-al/-/dict-al-1.1.1.tgz", + "integrity": "sha512-sD8GCaZetgQL4+MaJLXqbzWcRjfKVp8x+px3HuCaaiATAAtvjwUQ5/Iubiqwfd1boIh2Y1/3EgM3TLQ7Q8e0wQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-aws": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@cspell/dict-aws/-/dict-aws-4.0.17.tgz", + "integrity": "sha512-ORcblTWcdlGjIbWrgKF+8CNEBQiLVKdUOFoTn0KPNkAYnFcdPP0muT4892h7H4Xafh3j72wqB4/loQ6Nti9E/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-bash": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-bash/-/dict-bash-4.2.2.tgz", + "integrity": "sha512-kyWbwtX3TsCf5l49gGQIZkRLaB/P8g73GDRm41Zu8Mv51kjl2H7Au0TsEvHv7jzcsRLS6aUYaZv6Zsvk1fOz+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/dict-shell": "1.1.2" + } + }, + "node_modules/@cspell/dict-companies": { + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/@cspell/dict-companies/-/dict-companies-3.2.10.tgz", + "integrity": "sha512-bJ1qnO1DkTn7JYGXvxp8FRQc4yq6tRXnrII+jbP8hHmq5TX5o1Wu+rdfpoUQaMWTl6balRvcMYiINDesnpR9Bw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-cpp": { + "version": "6.0.15", + "resolved": "https://registry.npmjs.org/@cspell/dict-cpp/-/dict-cpp-6.0.15.tgz", + "integrity": "sha512-N7MKK3llRNoBncygvrnLaGvmjo4xzVr5FbtAc9+MFGHK6/LeSySBupr1FM72XDaVSIsmBEe7sDYCHHwlI9Jb2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-cryptocurrencies": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-cryptocurrencies/-/dict-cryptocurrencies-5.0.5.tgz", + "integrity": "sha512-R68hYYF/rtlE6T/dsObStzN5QZw+0aQBinAXuWCVqwdS7YZo0X33vGMfChkHaiCo3Z2+bkegqHlqxZF4TD3rUA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-csharp": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-csharp/-/dict-csharp-4.0.8.tgz", + "integrity": "sha512-qmk45pKFHSxckl5mSlbHxmDitSsGMlk/XzFgt7emeTJWLNSTUK//MbYAkBNRtfzB4uD7pAFiKgpKgtJrTMRnrQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-css": { + "version": "4.0.19", + "resolved": "https://registry.npmjs.org/@cspell/dict-css/-/dict-css-4.0.19.tgz", + "integrity": "sha512-VYHtPnZt/Zd/ATbW3rtexWpBnHUohUrQOHff/2JBhsVgxOrksAxJnLAO43Q1ayLJBJUUwNVo+RU0sx0aaysZfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-dart": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-dart/-/dict-dart-2.3.2.tgz", + "integrity": "sha512-sUiLW56t9gfZcu8iR/5EUg+KYyRD83Cjl3yjDEA2ApVuJvK1HhX+vn4e4k4YfjpUQMag8XO2AaRhARE09+/rqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-data-science": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@cspell/dict-data-science/-/dict-data-science-2.0.13.tgz", + "integrity": "sha512-l1HMEhBJkPmw4I2YGVu2eBSKM89K9pVF+N6qIr5Uo5H3O979jVodtuwP8I7LyPrJnC6nz28oxeGRCLh9xC5CVA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-django": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@cspell/dict-django/-/dict-django-4.1.6.tgz", + "integrity": "sha512-SdbSFDGy9ulETqNz15oWv2+kpWLlk8DJYd573xhIkeRdcXOjskRuxjSZPKfW7O3NxN/KEf3gm3IevVOiNuFS+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-docker": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@cspell/dict-docker/-/dict-docker-1.1.17.tgz", + "integrity": "sha512-OcnVTIpHIYYKhztNTyK8ShAnXTfnqs43hVH6p0py0wlcwRIXe5uj4f12n7zPf2CeBI7JAlPjEsV0Rlf4hbz/xQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-dotnet": { + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/@cspell/dict-dotnet/-/dict-dotnet-5.0.11.tgz", + "integrity": "sha512-LSVKhpFf/ASTWJcfYeS0Sykcl1gVMsv2Z5Eo0TnTMSTLV3738HH+66pIsjUTChqU6SF3gKPuCe6EOaRYqb/evA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-elixir": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-elixir/-/dict-elixir-4.0.8.tgz", + "integrity": "sha512-CyfphrbMyl4Ms55Vzuj+mNmd693HjBFr9hvU+B2YbFEZprE5AG+EXLYTMRWrXbpds4AuZcvN3deM2XVB80BN/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-en_us": { + "version": "4.4.27", + "resolved": "https://registry.npmjs.org/@cspell/dict-en_us/-/dict-en_us-4.4.27.tgz", + "integrity": "sha512-0y4vH2i5cFmi8sxkc4OlD2IlnqDznOtKczm4h6jA288g5VVrm3bhkYK6vcB8b0CoRKtYWKet4VEmHBP1yI+Qfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-en-common-misspellings": { + "version": "2.1.12", + "resolved": "https://registry.npmjs.org/@cspell/dict-en-common-misspellings/-/dict-en-common-misspellings-2.1.12.tgz", + "integrity": "sha512-14Eu6QGqyksqOd4fYPuRb58lK1Va7FQK9XxFsRKnZU8LhL3N+kj7YKDW+7aIaAN/0WGEqslGP6lGbQzNti8Akw==", + "dev": true, + "license": "CC BY-SA 4.0" + }, + "node_modules/@cspell/dict-en-gb-mit": { + "version": "3.1.16", + "resolved": "https://registry.npmjs.org/@cspell/dict-en-gb-mit/-/dict-en-gb-mit-3.1.16.tgz", + "integrity": "sha512-4PPdapCJslytxAVJu35Mv97qDyGmAQxtDE790T2bWNhcqN6gvRVAc/eTRaXkUIf21q1xCxxNNqpH4VfMup69rQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-filetypes": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@cspell/dict-filetypes/-/dict-filetypes-3.0.15.tgz", + "integrity": "sha512-uDMeqYlLlK476w/muEFQGBy9BdQWS0mQ7BJiy/iQv5XUWZxE2O54ZQd9nW8GyQMzAgoyg5SG4hf9l039Qt66oA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-flutter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-flutter/-/dict-flutter-1.1.1.tgz", + "integrity": "sha512-UlOzRcH2tNbFhZmHJN48Za/2/MEdRHl2BMkCWZBYs+30b91mWvBfzaN4IJQU7dUZtowKayVIF9FzvLZtZokc5A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-fonts": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-fonts/-/dict-fonts-4.0.5.tgz", + "integrity": "sha512-BbpkX10DUX/xzHs6lb7yzDf/LPjwYIBJHJlUXSBXDtK/1HaeS+Wqol4Mlm2+NAgZ7ikIE5DQMViTgBUY3ezNoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-fsharp": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-fsharp/-/dict-fsharp-1.1.1.tgz", + "integrity": "sha512-imhs0u87wEA4/cYjgzS0tAyaJpwG7vwtC8UyMFbwpmtw+/bgss+osNfyqhYRyS/ehVCWL17Ewx2UPkexjKyaBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-fullstack": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-fullstack/-/dict-fullstack-3.2.8.tgz", + "integrity": "sha512-J6EeoeThvx/DFrcA2rJiCA6vfqwJMbkG0IcXhlsmRZmasIpanmxgt90OEaUazbZahFiuJT8wrhgQ1QgD1MsqBw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-gaming-terms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-gaming-terms/-/dict-gaming-terms-1.1.2.tgz", + "integrity": "sha512-9XnOvaoTBscq0xuD6KTEIkk9hhdfBkkvJAIsvw3JMcnp1214OCGW8+kako5RqQ2vTZR3Tnf3pc57o7VgkM0q1Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-git": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-git/-/dict-git-3.1.0.tgz", + "integrity": "sha512-KEt9zGkxqGy2q1nwH4CbyqTSv5nadpn8BAlDnzlRcnL0Xb3LX9xTgSGShKvzb0bw35lHoYyLWN2ZKAqbC4pgGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-golang": { + "version": "6.0.26", + "resolved": "https://registry.npmjs.org/@cspell/dict-golang/-/dict-golang-6.0.26.tgz", + "integrity": "sha512-YKA7Xm5KeOd14v5SQ4ll6afe9VSy3a2DWM7L9uBq4u3lXToRBQ1W5PRa+/Q9udd+DTURyVVnQ+7b9cnOlNxaRg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-google": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@cspell/dict-google/-/dict-google-1.0.9.tgz", + "integrity": "sha512-biL65POqialY0i4g6crj7pR6JnBkbsPovB2WDYkj3H4TuC/QXv7Pu5pdPxeUJA6TSCHI7T5twsO4VSVyRxD9CA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-haskell": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@cspell/dict-haskell/-/dict-haskell-4.0.6.tgz", + "integrity": "sha512-ib8SA5qgftExpYNjWhpYIgvDsZ/0wvKKxSP+kuSkkak520iPvTJumEpIE+qPcmJQo4NzdKMN8nEfaeci4OcFAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-html": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@cspell/dict-html/-/dict-html-4.0.14.tgz", + "integrity": "sha512-2bf7n+kS92g+cMKV0wr9o/Oq9n8JzU7CcrB96gIh2GHgnF+0xDOqO2W/1KeFAqOfqosoOVE48t+4dnEMkkoJ2Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-html-symbol-entities": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-html-symbol-entities/-/dict-html-symbol-entities-4.0.5.tgz", + "integrity": "sha512-429alTD4cE0FIwpMucvSN35Ld87HCyuM8mF731KU5Rm4Je2SG6hmVx7nkBsLyrmH3sQukTcr1GaiZsiEg8svPA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-java": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@cspell/dict-java/-/dict-java-5.0.12.tgz", + "integrity": "sha512-qPSNhTcl7LGJ5Qp6VN71H8zqvRQK04S08T67knMq9hTA8U7G1sTKzLmBaDOFhq17vNX/+rT+rbRYp+B5Nwza1A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-julia": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-julia/-/dict-julia-1.1.1.tgz", + "integrity": "sha512-WylJR9TQ2cgwd5BWEOfdO3zvDB+L7kYFm0I9u0s9jKHWQ6yKmfKeMjU9oXxTBxIufhCXm92SKwwVNAC7gjv+yA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-k8s": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@cspell/dict-k8s/-/dict-k8s-1.0.12.tgz", + "integrity": "sha512-2LcllTWgaTfYC7DmkMPOn9GsBWsA4DZdlun4po8s2ysTP7CPEnZc1ZfK6pZ2eI4TsZemlUQQ+NZxMe9/QutQxg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-kotlin": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-kotlin/-/dict-kotlin-1.1.1.tgz", + "integrity": "sha512-J3NzzfgmxRvEeOe3qUXnSJQCd38i/dpF9/t3quuWh6gXM+krsAXP75dY1CzDmS8mrJAlBdVBeAW5eAZTD8g86Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-latex": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-latex/-/dict-latex-4.0.4.tgz", + "integrity": "sha512-YdTQhnTINEEm/LZgTzr9Voz4mzdOXH7YX+bSFs3hnkUHCUUtX/mhKgf1CFvZ0YNM2afjhQcmLaR9bDQVyYBvpA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-lorem-ipsum": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-lorem-ipsum/-/dict-lorem-ipsum-4.0.5.tgz", + "integrity": "sha512-9a4TJYRcPWPBKkQAJ/whCu4uCAEgv/O2xAaZEI0n4y1/l18Yyx8pBKoIX5QuVXjjmKEkK7hi5SxyIsH7pFEK9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-lua": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-lua/-/dict-lua-4.0.8.tgz", + "integrity": "sha512-N4PkgNDMu9JVsRu7JBS/3E/dvfItRgk9w5ga2dKq+JupP2Y3lojNaAVFhXISh4Y0a6qXDn2clA6nvnavQ/jjLA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-makefile": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-makefile/-/dict-makefile-1.0.5.tgz", + "integrity": "sha512-4vrVt7bGiK8Rx98tfRbYo42Xo2IstJkAF4tLLDMNQLkQ86msDlYSKG1ZCk8Abg+EdNcFAjNhXIiNO+w4KflGAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-markdown": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@cspell/dict-markdown/-/dict-markdown-2.0.14.tgz", + "integrity": "sha512-uLKPNJsUcumMQTsZZgAK9RgDLyQhUz/uvbQTEkvF/Q4XfC1i/BnA8XrOrd0+Vp6+tPOKyA+omI5LRWfMu5K/Lw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@cspell/dict-css": "^4.0.19", + "@cspell/dict-html": "^4.0.14", + "@cspell/dict-html-symbol-entities": "^4.0.5", + "@cspell/dict-typescript": "^3.2.3" + } + }, + "node_modules/@cspell/dict-monkeyc": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@cspell/dict-monkeyc/-/dict-monkeyc-1.0.12.tgz", + "integrity": "sha512-MN7Vs11TdP5mbdNFQP5x2Ac8zOBm97ARg6zM5Sb53YQt/eMvXOMvrep7+/+8NJXs0jkp70bBzjqU4APcqBFNAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-node": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@cspell/dict-node/-/dict-node-5.0.9.tgz", + "integrity": "sha512-hO+ga+uYZ/WA4OtiMEyKt5rDUlUyu3nXMf8KVEeqq2msYvAPdldKBGH7lGONg6R/rPhv53Rb+0Y1SLdoK1+7wQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-npm": { + "version": "5.2.31", + "resolved": "https://registry.npmjs.org/@cspell/dict-npm/-/dict-npm-5.2.31.tgz", + "integrity": "sha512-+HoFoFe53pL0wDuSHRs5L+CcDMaG5sLfjKLPT4H0VdwNzho3HLOohTCZr6cYt7OEbXf3xi4YXBkamCy38xOpjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-php": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-php/-/dict-php-4.1.1.tgz", + "integrity": "sha512-EXelI+4AftmdIGtA8HL8kr4WlUE11OqCSVlnIgZekmTkEGSZdYnkFdiJ5IANSALtlQ1mghKjz+OFqVs6yowgWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-powershell": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/@cspell/dict-powershell/-/dict-powershell-5.0.15.tgz", + "integrity": "sha512-l4S5PAcvCFcVDMJShrYD0X6Huv9dcsQPlsVsBGbH38wvuN7gS7+GxZFAjTNxDmTY1wrNi1cCatSg6Pu2BW4rgg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-public-licenses": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@cspell/dict-public-licenses/-/dict-public-licenses-2.0.15.tgz", + "integrity": "sha512-cJEOs901H13Pfy0fl4dCD1U+xpWIMaEPq8MeYU83FfDZvellAuSo4GqWCripfIqlhns/L6+UZEIJSOZnjgy7Wg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-python": { + "version": "4.2.25", + "resolved": "https://registry.npmjs.org/@cspell/dict-python/-/dict-python-4.2.25.tgz", + "integrity": "sha512-hDdN0YhKgpbtZVRjQ2c8jk+n0wQdidAKj1Fk8w7KEHb3YlY5uPJ0mAKJk7AJKPNLOlILoUmN+HAVJz+cfSbWYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/dict-data-science": "^2.0.13" + } + }, + "node_modules/@cspell/dict-r": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-r/-/dict-r-2.1.1.tgz", + "integrity": "sha512-71Ka+yKfG4ZHEMEmDxc6+blFkeTTvgKbKAbwiwQAuKl3zpqs1Y0vUtwW2N4b3LgmSPhV3ODVY0y4m5ofqDuKMw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-ruby": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-ruby/-/dict-ruby-5.1.0.tgz", + "integrity": "sha512-9PJQB3cfkBULrMLp5kSAcFPpzf8oz9vFN+QYZABhQwWkGbuzCIXSorHrmWSASlx4yejt3brjaWS57zZ/YL5ZQQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-rust": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-rust/-/dict-rust-4.1.1.tgz", + "integrity": "sha512-fXiXnZH0wOaEVTKFRNaz6TsUGhuB8dAT0ubYkDNzRQCaV5JGSOebGb1v2x5ZrOSVp+moxWM/vdBfiNU6KOEaFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-scala": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@cspell/dict-scala/-/dict-scala-5.0.9.tgz", + "integrity": "sha512-AjVcVAELgllybr1zk93CJ5wSUNu/Zb5kIubymR/GAYkMyBdYFCZ3Zbwn4Zz8GJlFFAbazABGOu0JPVbeY59vGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-shell": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-shell/-/dict-shell-1.1.2.tgz", + "integrity": "sha512-WqOUvnwcHK1X61wAfwyXq04cn7KYyskg90j4lLg3sGGKMW9Sq13hs91pqrjC44Q+lQLgCobrTkMDw9Wyl9nRFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-software-terms": { + "version": "5.1.20", + "resolved": "https://registry.npmjs.org/@cspell/dict-software-terms/-/dict-software-terms-5.1.20.tgz", + "integrity": "sha512-TEk1xHvetTI4pv7Vzje1D322m6QEjaH2P6ucOOf6q7EJCppQIdC0lZSXkgHJAFU5HGSvEXSzvnVeW2RHW86ziQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-sql": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-sql/-/dict-sql-2.2.1.tgz", + "integrity": "sha512-qDHF8MpAYCf4pWU8NKbnVGzkoxMNrFqBHyG/dgrlic5EQiKANCLELYtGlX5auIMDLmTf1inA0eNtv74tyRJ/vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-svelte": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@cspell/dict-svelte/-/dict-svelte-1.0.7.tgz", + "integrity": "sha512-hGZsGqP0WdzKkdpeVLBivRuSNzOTvN036EBmpOwxH+FTY2DuUH7ecW+cSaMwOgmq5JFSdTcbTNFlNC8HN8lhaQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-swift": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@cspell/dict-swift/-/dict-swift-2.0.6.tgz", + "integrity": "sha512-PnpNbrIbex2aqU1kMgwEKvCzgbkHtj3dlFLPMqW1vSniop7YxaDTtvTUO4zA++ugYAEL+UK8vYrBwDPTjjvSnA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-terraform": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-terraform/-/dict-terraform-1.1.3.tgz", + "integrity": "sha512-gr6wxCydwSFyyBKhBA2xkENXtVFToheqYYGFvlMZXWjviynXmh+NK/JTvTCk/VHk3+lzbO9EEQKee6VjrAUSbA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-typescript": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-typescript/-/dict-typescript-3.2.3.tgz", + "integrity": "sha512-zXh1wYsNljQZfWWdSPYwQhpwiuW0KPW1dSd8idjMRvSD0aSvWWHoWlrMsmZeRl4qM4QCEAjua8+cjflm41cQBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-vue": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-vue/-/dict-vue-3.0.5.tgz", + "integrity": "sha512-Mqutb8jbM+kIcywuPQCCaK5qQHTdaByoEO2J9LKFy3sqAdiBogNkrplqUK0HyyRFgCfbJUgjz3N85iCMcWH0JA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-zig": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-zig/-/dict-zig-1.0.0.tgz", + "integrity": "sha512-XibBIxBlVosU06+M6uHWkFeT0/pW5WajDRYdXG2CgHnq85b0TI/Ks0FuBJykmsgi2CAD3Qtx8UHFEtl/DSFnAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dynamic-import": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@cspell/dynamic-import/-/dynamic-import-9.4.0.tgz", + "integrity": "sha512-d2fjLjzrKGUIn5hWK8gMuyAh2pqXSxBqOHpU1jR3jxbrO3MilunKNijaSstv7CZn067Jpc36VfaKQodaXNZzUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/url": "9.4.0", + "import-meta-resolve": "^4.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@cspell/filetypes": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@cspell/filetypes/-/filetypes-9.4.0.tgz", + "integrity": "sha512-RMrYHkvPF0tHVFM+T4voEhX9sfYQrd/mnNbf6+O4CWUyLCz4NQ5H9yOgEIJwEcLu4y3NESGXFef/Jn5xo0CUfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@cspell/strong-weak-map": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@cspell/strong-weak-map/-/strong-weak-map-9.4.0.tgz", + "integrity": "sha512-ui7mlXYmqElS/SmRubPBNWdkQVWgWbB6rjCurc+0owYXlnweItAMHTxC8mCWM/Au22SF1dB/JR8QBELFXLkTjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@cspell/url": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@cspell/url/-/url-9.4.0.tgz", + "integrity": "sha512-nt88P6m20AaVbqMxsyPf8KqyWPaFEW2UANi0ijBxc2xTkD2KiUovxfZUYW6NMU9XBYZlovT5LztkEhst2yBcSA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@oozcitak/dom": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-2.0.2.tgz", + "integrity": "sha512-GjpKhkSYC3Mj4+lfwEyI1dqnsKTgwGy48ytZEhm4A/xnH/8z9M3ZVXKr/YGQi3uCLs1AEBS+x5T2JPiueEDW8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oozcitak/infra": "^2.0.2", + "@oozcitak/url": "^3.0.0", + "@oozcitak/util": "^10.0.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@oozcitak/infra": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@oozcitak/infra/-/infra-2.0.2.tgz", + "integrity": "sha512-2g+E7hoE2dgCz/APPOEK5s3rMhJvNxSMBrP+U+j1OWsIbtSpWxxlUjq1lU8RIsFJNYv7NMlnVsCuHcUzJW+8vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oozcitak/util": "^10.0.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@oozcitak/url": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@oozcitak/url/-/url-3.0.0.tgz", + "integrity": "sha512-ZKfET8Ak1wsLAiLWNfFkZc/BraDccuTJKR6svTYc7sVjbR+Iu0vtXdiDMY4o6jaFl5TW2TlS7jbLl4VovtAJWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oozcitak/infra": "^2.0.2", + "@oozcitak/util": "^10.0.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@oozcitak/util": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-10.0.0.tgz", + "integrity": "sha512-hAX0pT/73190NLqBPPWSdBVGtbY6VOhWYK3qqHqtXQ1gK7kS2yz4+ivsN07hpJ6I3aeMtKP6J6npsEKOAzuTLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/katex": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.8.tgz", + "integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-timsort": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "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/basic-ftp": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.1.0.tgz", + "integrity": "sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "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" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "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" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk-template": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-1.1.2.tgz", + "integrity": "sha512-2bxTP2yUH7AJj/VAXfcA+4IcWGdQ87HwBANLt5XxGTeomo8yG0y95N1um9i5StvhT/Bl0/2cARA5v1PpPXUxUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.2.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/cheerio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", + "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.1.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.19.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/clear-module": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/clear-module/-/clear-module-4.1.2.tgz", + "integrity": "sha512-LWAxzHqdHsAZlPlEyJ2Poz6AIs384mPeqLVCru2p0BrP9G/kVGuhNyZYClLO6cXlnuJjzC8xtsJIuMjKqLXoAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^2.0.0", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/comment-json": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.5.1.tgz", + "integrity": "sha512-taEtr3ozUmOB7it68Jll7s0Pwm+aoiHyXKrEC8SEodL4rNpdfDLqa7PfBlrgFoCNNdR8ImL+muti5IGvktJAAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-timsort": "^1.0.3", + "core-util-is": "^1.0.3", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cspell": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/cspell/-/cspell-9.4.0.tgz", + "integrity": "sha512-ZvXO+EY/G0/msu7jwRiVk0sXL/zB7DMJLBvjSUrK82uVbDoDxHwXxUuOz2UVnk2+J61//ldIZrjxVK8KMvaJlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-json-reporter": "9.4.0", + "@cspell/cspell-pipe": "9.4.0", + "@cspell/cspell-types": "9.4.0", + "@cspell/dynamic-import": "9.4.0", + "@cspell/url": "9.4.0", + "ansi-regex": "^6.2.2", + "chalk": "^5.6.2", + "chalk-template": "^1.1.2", + "commander": "^14.0.2", + "cspell-config-lib": "9.4.0", + "cspell-dictionary": "9.4.0", + "cspell-gitignore": "9.4.0", + "cspell-glob": "9.4.0", + "cspell-io": "9.4.0", + "cspell-lib": "9.4.0", + "fast-json-stable-stringify": "^2.1.0", + "flatted": "^3.3.3", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15" + }, + "bin": { + "cspell": "bin.mjs", + "cspell-esm": "bin.mjs" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/streetsidesoftware/cspell?sponsor=1" + } + }, + "node_modules/cspell-config-lib": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/cspell-config-lib/-/cspell-config-lib-9.4.0.tgz", + "integrity": "sha512-CvQKSmK/DRIf3LpNx2sZth65pHW2AHngZqLkH3DTwnAPbiCAsE0XvCrVhvDfCNu/6uJIaa+NVHSs8GOf//DHBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-types": "9.4.0", + "comment-json": "^4.4.1", + "smol-toml": "^1.5.2", + "yaml": "^2.8.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cspell-dictionary": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/cspell-dictionary/-/cspell-dictionary-9.4.0.tgz", + "integrity": "sha512-c2qscanRZChoHZFYI7KpvBMdy8i6wNwl2EflcNRrFiFOq67t9CgxLe54PafaqhrHGpBc8nElaZKciLvjj6Uscw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-pipe": "9.4.0", + "@cspell/cspell-types": "9.4.0", + "cspell-trie-lib": "9.4.0", + "fast-equals": "^5.3.3" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cspell-gitignore": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/cspell-gitignore/-/cspell-gitignore-9.4.0.tgz", + "integrity": "sha512-HMrzLmJBUMSpaMMkltlTAz/aVOrHxixyhKfg5WbFCJ5JYZO6Qu3/JU3wRoOFoud9449wRjLkvrGmbbL2+vO6Lw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/url": "9.4.0", + "cspell-glob": "9.4.0", + "cspell-io": "9.4.0" + }, + "bin": { + "cspell-gitignore": "bin.mjs" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cspell-glob": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-9.4.0.tgz", + "integrity": "sha512-Q87Suj9oXrhoKck15qWorCizBjMNxG/k3NjnhKIAMrF+PdUa1Mpl0MOD+hqV1Wvwh1UHcIMYCP3bR3XpBbNx+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/url": "9.4.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cspell-grammar": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/cspell-grammar/-/cspell-grammar-9.4.0.tgz", + "integrity": "sha512-ie7OQ4Neflo+61bMzoLR7GtlZfMBAm2KL1U4iNqh15wUE5fDbvXeN15H5lu+gcO8BwYvC5wxZknw1x62/J8+3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-pipe": "9.4.0", + "@cspell/cspell-types": "9.4.0" + }, + "bin": { + "cspell-grammar": "bin.mjs" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cspell-io": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-9.4.0.tgz", + "integrity": "sha512-8w30dqlO54H9w6WGlvZhHI5kytVbF3bYPqKJAZLWKEO36L2mdpf6/abx/FA4yVLJ56wmH1x0N0ZK32wNRl5C6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-service-bus": "9.4.0", + "@cspell/url": "9.4.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cspell-lib": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-9.4.0.tgz", + "integrity": "sha512-ajjioE59IEDNUPawfaBpiMfGC32iKPkuYd4T9ftguuef8VvyKRifniiUi1nxwGgAhzSfxHvWs7qdT+29Pp5TMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-bundled-dicts": "9.4.0", + "@cspell/cspell-pipe": "9.4.0", + "@cspell/cspell-resolver": "9.4.0", + "@cspell/cspell-types": "9.4.0", + "@cspell/dynamic-import": "9.4.0", + "@cspell/filetypes": "9.4.0", + "@cspell/strong-weak-map": "9.4.0", + "@cspell/url": "9.4.0", + "clear-module": "^4.1.2", + "cspell-config-lib": "9.4.0", + "cspell-dictionary": "9.4.0", + "cspell-glob": "9.4.0", + "cspell-grammar": "9.4.0", + "cspell-io": "9.4.0", + "cspell-trie-lib": "9.4.0", + "env-paths": "^3.0.0", + "gensequence": "^8.0.8", + "import-fresh": "^3.3.1", + "resolve-from": "^5.0.0", + "vscode-languageserver-textdocument": "^1.0.12", + "vscode-uri": "^3.1.0", + "xdg-basedir": "^5.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cspell-trie-lib": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-9.4.0.tgz", + "integrity": "sha512-bySJTm8XDiJAoC1MDo4lE/KpSNxydo13ZETC8TF7Hb3rbWI1c6o5eZ4+i/tkG3M94OvKV91+MeAvoMCe7GGgAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-pipe": "9.4.0", + "@cspell/cspell-types": "9.4.0", + "gensequence": "^8.0.8" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/deep-is": { + "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/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "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" + } + }, + "node_modules/esutils": { + "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" + } + }, + "node_modules/fast-equals": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", + "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "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/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-package-json": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/find-package-json/-/find-package-json-1.2.0.tgz", + "integrity": "sha512-+SOGcLGYDJHtyqHd87ysBhmaeQ95oWspDKnMXBrnQ9Eq4OkLNqejgoaD8xVWu6GPa0B6roa6KinCMEMcVeqONw==", + "dev": true, + "license": "MIT" + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fs-extra": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", + "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/gensequence": { + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/gensequence/-/gensequence-8.0.8.tgz", + "integrity": "sha512-omMVniXEXpdx/vKxGnPRoO2394Otlze28TyxECbFVyoSpZ9H3EO7lemjcB12OpQJzRW4e5tt/dL1rOxry6aMHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/global-directory": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", + "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "4.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-15.0.0.tgz", + "integrity": "sha512-oB4vkQGqlMl682wL1IlWd02tXCbquGWM4voPEI85QmNKCaw8zGTm1f1rubFgkg3Eli2PtKlFgrnmUqasbQWlkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.5", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/html-link-extractor": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/html-link-extractor/-/html-link-extractor-1.0.5.tgz", + "integrity": "sha512-ADd49pudM157uWHwHQPUSX4ssMsvR/yHIswOR5CUfBdK9g9ZYGMhVSE6KZVHJ6kCkR0gH4htsfzU6zECDNVwyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cheerio": "^1.0.0-rc.10" + } + }, + "node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "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", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/parent-module": { + "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" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "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" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-absolute-url": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-4.0.1.tgz", + "integrity": "sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-extglob": { + "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" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-relative-url": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-relative-url/-/is-relative-url-4.1.0.tgz", + "integrity": "sha512-vhIXKasjAuxS7n+sdv7pJQykEAgS+YU8VBQOENXwo/VZpOHDgBBsIbHo7zFKaWBjYWF4qxERdhbPRRtFAeJKfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-absolute-url": "^4.0.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "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/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-yaml": { + "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" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/katex": { + "version": "0.16.28", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.28.tgz", + "integrity": "sha512-YHzO7721WbmAL6Ov1uzN/l5mY5WWWhJBSW+jq4tkfZfsxmo1hu6frS0EOswvjBUnWE6NtjEs48SFn5CQESRLZg==", + "dev": true, + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/levn": { + "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", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/link-check": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/link-check/-/link-check-5.5.1.tgz", + "integrity": "sha512-GrtE4Zp/FBduvElmad375NrPeMYnKwNt9rH/TDG/rbQbHL0QVC4S/cEPVKZ0CkhXlVuiK+/5flGpRxQzoLbjEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-relative-url": "^4.1.0", + "ms": "^2.1.3", + "needle": "^3.3.1", + "node-email-verifier": "^3.4.1", + "proxy-agent": "^6.5.0" + } + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-link-check": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/markdown-link-check/-/markdown-link-check-3.14.2.tgz", + "integrity": "sha512-DPJ+itd3D5fcfXD5s1i53lugH0Z/h80kkQxlYCBh8tFwEZGhyVgDcLl0rnKlWssAVDAmSmcbePpHpMEY+JcMMQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "async": "^3.2.6", + "chalk": "^5.6.2", + "commander": "^14.0.2", + "link-check": "^5.5.1", + "markdown-link-extractor": "^4.0.3", + "needle": "^3.3.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "xmlbuilder2": "^4.0.0" + }, + "bin": { + "markdown-link-check": "markdown-link-check" + } + }, + "node_modules/markdown-link-extractor": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/markdown-link-extractor/-/markdown-link-extractor-4.0.3.tgz", + "integrity": "sha512-aEltJiQ4/oC0h6Jbw/uuATGSHZPkcH8DIunNH1A0e+GSFkvZ6BbBkdvBTVfIV8r6HapCU3yTd0eFdi3ZeM1eAQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "html-link-extractor": "^1.0.5", + "marked": "^17.0.0" + } + }, + "node_modules/markdown-table-formatter": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/markdown-table-formatter/-/markdown-table-formatter-1.6.0.tgz", + "integrity": "sha512-TiqVZJB09VXGB7cInio5QDViAmY+lOzG+pDAib5klBmzbg0kRy3MEqn/dBtMsgk8RRNLtZbW1Vlm/UWgY1KyMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "find-package-json": "^1.2.0", + "fs-extra": "^11.1.1", + "glob": "^10.3.10", + "markdown-table-prettify": "^3.6.0", + "optionator": "^0.9.3" + }, + "bin": { + "markdown-table-formatter": "lib/index.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/markdown-table-prettify": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/markdown-table-prettify/-/markdown-table-prettify-3.7.0.tgz", + "integrity": "sha512-woZ1X+u0HsTygXL5kcptMSDwnjU//3UKTOH6fGdaABSSLOxTdWjr2P6i7dVrru5t/pxyEOT48/skv/8m8/VqdA==", + "dev": true, + "license": "MIT", + "bin": { + "markdown-table-prettify": "cli/index.js" + }, + "engines": { + "vscode": "^1.103.0" + } + }, + "node_modules/markdownlint": { + "version": "0.39.0", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.39.0.tgz", + "integrity": "sha512-Xt/oY7bAiHwukL1iru2np5LIkhwD19Y7frlsiDILK62v3jucXCD6JXlZlwMG12HZOR+roHIVuJZrfCkOhp6k3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark": "4.0.2", + "micromark-core-commonmark": "2.0.3", + "micromark-extension-directive": "4.0.0", + "micromark-extension-gfm-autolink-literal": "2.1.0", + "micromark-extension-gfm-footnote": "2.1.0", + "micromark-extension-gfm-table": "2.1.1", + "micromark-extension-math": "3.1.0", + "micromark-util-types": "2.0.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/DavidAnson" + } + }, + "node_modules/markdownlint-cli2": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.19.1.tgz", + "integrity": "sha512-p3JTemJJbkiMjXEMiFwgm0v6ym5g8K+b2oDny+6xdl300tUKySxvilJQLSea48C6OaYNmO30kH9KxpiAg5bWJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "globby": "15.0.0", + "js-yaml": "4.1.1", + "jsonc-parser": "3.3.1", + "markdown-it": "14.1.0", + "markdownlint": "0.39.0", + "markdownlint-cli2-formatter-default": "0.0.6", + "micromatch": "4.0.8" + }, + "bin": { + "markdownlint-cli2": "markdownlint-cli2-bin.mjs" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/DavidAnson" + } + }, + "node_modules/markdownlint-cli2-formatter-default": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/markdownlint-cli2-formatter-default/-/markdownlint-cli2-formatter-default-0.0.6.tgz", + "integrity": "sha512-VVDGKsq9sgzu378swJ0fcHfSicUnMxnL8gnLm/Q4J/xsNJ4e5bA6lvAz7PCzIl0/No0lHyaWdqVD2jotxOSFMQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/DavidAnson" + }, + "peerDependencies": { + "markdownlint-cli2": ">=0.0.4" + } + }, + "node_modules/marked": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.1.tgz", + "integrity": "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-4.0.0.tgz", + "integrity": "sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/minimatch": { + "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" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "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" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/needle": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz", + "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/node-email-verifier": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/node-email-verifier/-/node-email-verifier-3.4.1.tgz", + "integrity": "sha512-69JMeWgEUrCji+dOLULirdSoosRxgAq2y+imfmHHBGvgTwyTKqvm65Ls3+W30DCIWMrYj5kKVb/DHTQDK7OVwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3", + "validator": "^13.15.15" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/optionator": { + "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", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-2.0.0.tgz", + "integrity": "sha512-uo0Z9JJeWzv8BG+tRcapBKNJ0dro9cLyczGzulS6EfeyAdeC9sbojtW6XwvYxJkEne9En+J2XEl4zyglVeIwFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/picomatch": { + "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" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "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" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", + "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/smol-toml": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.0.tgz", + "integrity": "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-check": { + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici": { + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.19.2.tgz", + "integrity": "sha512-4VQSpGEGsWzk0VYxyB/wVX/Q7qf9t5znLRgs0dzszr9w9Fej/8RVNQ+S20vdXSAyra/bJ7ZQfGv6ZMj7UEbzSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/validator": { + "version": "13.15.26", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz", + "integrity": "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "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" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "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" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/xdg-basedir": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", + "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xmlbuilder2": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-4.0.3.tgz", + "integrity": "sha512-bx8Q1STctnNaaDymWnkfQLKofs0mGNN7rLLapJlGuV3VlvegD7Ls4ggMjE3aUSWItCCzU0PEv45lI87iSigiCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oozcitak/dom": "^2.0.2", + "@oozcitak/infra": "^2.0.2", + "@oozcitak/util": "^10.0.0", + "js-yaml": "^4.1.1" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..f797238 --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "azure-nvidia-robotics-reference-architecture", + "version": "1.0.0", + "private": true, + "description": "Azure NVIDIA Robotics Reference Architecture", + "scripts": { + "spell-check": "cspell \"**/*.{md,ts,js,json,yaml,yml,py,sh}\"", + "spell-check:fix": "cspell \"**/*.{md,ts,js,json,yaml,yml,py,sh}\" --show-suggestions", + "lint:md": "markdownlint-cli2 \"**/*.md\"", + "lint:md:fix": "markdownlint-cli2 \"**/*.md\" --fix", + "lint:ps": "pwsh -File scripts/linting/Invoke-PSScriptAnalyzer.ps1", + "lint:links": "pwsh -File scripts/linting/Invoke-LinkLanguageCheck.ps1", + "lint:all": "npm run lint:md && npm run lint:ps && npm run lint:links", + "format:tables": "markdown-table-formatter \"**/*.md\"" + }, + "devDependencies": { + "cspell": "9.4.0", + "markdown-link-check": "3.14.2", + "markdown-table-formatter": "1.6.0", + "markdownlint-cli2": "0.19.1" + }, + "repository": { + "type": "git", + "url": "https://github.com/Azure-Samples/azure-nvidia-robotics-reference-architecture.git" + }, + "author": "Microsoft", + "license": "MIT" +} diff --git a/scripts/README.md b/scripts/README.md index b948b59..a3979e0 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -6,18 +6,16 @@ ms.date: 2025-12-14 ms.topic: reference --- -# Scripts - Submission scripts for training and validation workflows on Azure ML and OSMO platforms. ## πŸ“œ Submission Scripts -| Script | Purpose | Platform | -|--------|---------|----------| -| `submit-azureml-training.sh` | Package code and submit Azure ML training job | Azure ML | -| `submit-azureml-validation.sh` | Submit model validation job | Azure ML | -| `submit-osmo-training.sh` | Package code and submit OSMO workflow (base64) | OSMO | -| `submit-osmo-dataset-training.sh` | Submit OSMO workflow using dataset folder injection | OSMO | +| Script | Purpose | Platform | +|-----------------------------------|-----------------------------------------------------|----------| +| `submit-azureml-training.sh` | Package code and submit Azure ML training job | Azure ML | +| `submit-azureml-validation.sh` | Submit model validation job | Azure ML | +| `submit-osmo-training.sh` | Package code and submit OSMO workflow (base64) | OSMO | +| `submit-osmo-dataset-training.sh` | Submit OSMO workflow using dataset folder injection | OSMO | ## πŸš€ Quick Start @@ -62,11 +60,11 @@ The `submit-osmo-dataset-training.sh` script uploads `src/training/` as a versio ### Dataset Parameters -| Parameter | Default | Description | -|-----------|---------|-------------| -| `--dataset-bucket` | `training` | OSMO bucket for training code | -| `--dataset-name` | `training-code` | Dataset name (auto-versioned) | -| `--training-path` | `src/training` | Local folder to upload | +| Parameter | Default | Description | +|--------------------|-----------------|-------------------------------| +| `--dataset-bucket` | `training` | OSMO bucket for training code | +| `--dataset-name` | `training-code` | Dataset name (auto-versioned) | +| `--training-path` | `src/training` | Local folder to upload | The script stages files to exclude `__pycache__` and build artifacts via `.amlignore` patterns before upload. @@ -74,20 +72,20 @@ The script stages files to exclude `__pycache__` and build artifacts via `.amlig Scripts resolve values in order: CLI arguments β†’ environment variables β†’ Terraform outputs. -| Variable | Description | -|----------|-------------| -| `AZURE_SUBSCRIPTION_ID` | Azure subscription | -| `AZURE_RESOURCE_GROUP` | Resource group name | -| `AZUREML_WORKSPACE_NAME` | ML workspace name | -| `TASK` | IsaacLab task name | -| `NUM_ENVS` | Number of parallel environments | -| `OSMO_DATASET_BUCKET` | Dataset bucket for OSMO training | -| `OSMO_DATASET_NAME` | Dataset name for OSMO training | +| Variable | Description | +|--------------------------|----------------------------------| +| `AZURE_SUBSCRIPTION_ID` | Azure subscription | +| `AZURE_RESOURCE_GROUP` | Resource group name | +| `AZUREML_WORKSPACE_NAME` | ML workspace name | +| `TASK` | IsaacLab task name | +| `NUM_ENVS` | Number of parallel environments | +| `OSMO_DATASET_BUCKET` | Dataset bucket for OSMO training | +| `OSMO_DATASET_NAME` | Dataset name for OSMO training | ## πŸ“š Library -| File | Purpose | -|------|---------| +| File | Purpose | +|----------------------------|------------------------------------------------| | `lib/terraform-outputs.sh` | Shared functions for reading Terraform outputs | Source the library to use helper functions: @@ -101,8 +99,8 @@ get_azureml_workspace # Returns ML workspace name ## πŸ”— Related Documentation -| Resource | Description | -|----------|-------------| -| [workflows/](../workflows/) | YAML templates for training and validation jobs | -| [workflows/osmo/](../workflows/osmo/) | OSMO workflow templates including dataset training | -| [deploy/002-setup/](../deploy/002-setup/) | Cluster configuration and OSMO deployment | +| Resource | Description | +|-------------------------------------------|----------------------------------------------------| +| [workflows/](../workflows/) | YAML templates for training and validation jobs | +| [workflows/osmo/](../workflows/osmo/) | OSMO workflow templates including dataset training | +| [deploy/002-setup/](../deploy/002-setup/) | Cluster configuration and OSMO deployment | diff --git a/scripts/linting/Invoke-LinkLanguageCheck.ps1 b/scripts/linting/Invoke-LinkLanguageCheck.ps1 new file mode 100644 index 0000000..ac63ba9 --- /dev/null +++ b/scripts/linting/Invoke-LinkLanguageCheck.ps1 @@ -0,0 +1,116 @@ +#!/usr/bin/env pwsh +# +# Invoke-LinkLanguageCheck.ps1 +# +# Purpose: Wrapper for Link-Lang-Check.ps1 with GitHub Actions integration + +[CmdletBinding()] +param( + [string[]]$Files +) + +Import-Module (Join-Path $PSScriptRoot "Modules/LintingHelpers.psm1") -Force + +$repoRoot = git rev-parse --show-toplevel 2>$null +if ($LASTEXITCODE -ne 0) { + Write-Error "Not in a git repository" + exit 1 +} + +$logsDir = Join-Path $repoRoot "logs" +if (-not (Test-Path $logsDir)) { + New-Item -ItemType Directory -Path $logsDir -Force | Out-Null +} + +Write-Host "πŸ” Checking for URLs with language paths..." -ForegroundColor Cyan + +if ($Files -and $Files.Count -gt 0) { + $jsonOutput = & (Join-Path $PSScriptRoot "Link-Lang-Check.ps1") -Files $Files 2>&1 +} +else { + $jsonOutput = & (Join-Path $PSScriptRoot "Link-Lang-Check.ps1") 2>&1 +} + +try { + $results = $jsonOutput | ConvertFrom-Json + + if ($results -and $results.Count -gt 0) { + Write-Host "Found $($results.Count) URLs with 'en-us' language paths`n" -ForegroundColor Yellow + + foreach ($item in $results) { + Write-GitHubAnnotation ` + -Type 'warning' ` + -Message "URL contains language path: $($item.original_url)" ` + -File $item.file ` + -Line $item.line_number + } + + $outputData = @{ + timestamp = (Get-Date).ToUniversalTime().ToString("o") + script = "link-lang-check" + summary = @{ + total_issues = $results.Count + files_affected = ($results | Select-Object -ExpandProperty file -Unique).Count + } + issues = $results + } + $outputData | ConvertTo-Json -Depth 3 | Out-File (Join-Path $logsDir "link-lang-check-results.json") -Encoding utf8 + + Set-GitHubOutput -Name "issues" -Value $results.Count + Set-GitHubEnv -Name "LINK_LANG_FAILED" -Value "true" + + $uniqueFiles = $results | Select-Object -ExpandProperty file -Unique + + Write-GitHubStepSummary -Content @" +## Link Language Path Check Results + +⚠️ **Status**: Issues Found + +Found $($results.Count) URL(s) containing language path 'en-us'. + +**Why this matters:** +Language-specific URLs don't adapt to user preferences and may break for non-English users. + +**To fix locally:** +``````powershell +scripts/linting/Link-Lang-Check.ps1 -Fix +`````` + +**Files affected:** +$(($uniqueFiles | ForEach-Object { $count = ($results | Where-Object file -eq $_).Count; "- $_ ($count occurrence(s))" }) -join "`n") +"@ + + exit 1 + } + else { + Write-Host "βœ… No URLs with language paths found" -ForegroundColor Green + + $emptyResults = @{ + timestamp = (Get-Date).ToUniversalTime().ToString("o") + script = "link-lang-check" + summary = @{ + total_issues = 0 + files_affected = 0 + } + issues = @() + } + $emptyResults | ConvertTo-Json -Depth 3 | Out-File (Join-Path $logsDir "link-lang-check-results.json") -Encoding utf8 + + Set-GitHubOutput -Name "issues" -Value "0" + + Write-GitHubStepSummary -Content @" +## Link Language Path Check Results + +βœ… **Status**: Passed + +No URLs with language-specific paths detected. +"@ + + exit 0 + } +} +catch { + Write-Error "Error parsing results: $_" + Write-Host "Raw output: $jsonOutput" + exit 1 +} diff --git a/scripts/linting/Invoke-PSScriptAnalyzer.ps1 b/scripts/linting/Invoke-PSScriptAnalyzer.ps1 new file mode 100644 index 0000000..5cebf23 --- /dev/null +++ b/scripts/linting/Invoke-PSScriptAnalyzer.ps1 @@ -0,0 +1,229 @@ +#!/usr/bin/env pwsh +<# +.SYNOPSIS + Runs PSScriptAnalyzer on PowerShell files in the repository. + +.DESCRIPTION + Wrapper script for PSScriptAnalyzer that provides GitHub Actions integration, + automatic module installation, and configurable file targeting. + +.PARAMETER ChangedFilesOnly + When specified, only analyze PowerShell files that have changed compared to the base branch. + +.PARAMETER BaseBranch + The base branch to compare against when using ChangedFilesOnly. Defaults to 'origin/main'. + +.PARAMETER ConfigPath + Path to the PSScriptAnalyzer settings file. Defaults to 'scripts/linting/PSScriptAnalyzer.psd1'. + +.PARAMETER OutputPath + Path for JSON results output. When specified, results are exported to this file. + +.PARAMETER SoftFail + When specified, the script exits with code 0 even if violations are found. + +.EXAMPLE + ./Invoke-PSScriptAnalyzer.ps1 + Analyzes all PowerShell files in the repository. + +.EXAMPLE + ./Invoke-PSScriptAnalyzer.ps1 -ChangedFilesOnly -BaseBranch 'origin/main' + Analyzes only changed PowerShell files compared to origin/main. +#> +[CmdletBinding()] +param( + [switch]$ChangedFilesOnly, + [string]$BaseBranch = 'origin/main', + [string]$ConfigPath = '', + [string]$OutputPath = '', + [switch]$SoftFail +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +# Import shared helper functions +$modulePath = Join-Path $PSScriptRoot 'Modules/LintingHelpers.psm1' +if (Test-Path $modulePath) { + Import-Module $modulePath -Force +} + +# Determine repository root +$repoRoot = git rev-parse --show-toplevel 2>$null +if (-not $repoRoot) { + $repoRoot = (Get-Item $PSScriptRoot).Parent.Parent.FullName +} + +# Set default config path if not specified +if (-not $ConfigPath) { + $ConfigPath = Join-Path $PSScriptRoot 'PSScriptAnalyzer.psd1' +} + +# Ensure PSScriptAnalyzer module is installed +Write-Host '::group::PSScriptAnalyzer Setup' +if (-not (Get-Module -ListAvailable -Name PSScriptAnalyzer)) { + Write-Host 'Installing PSScriptAnalyzer module...' + Install-Module -Name PSScriptAnalyzer -Force -Scope CurrentUser -Repository PSGallery +} +Import-Module PSScriptAnalyzer -Force +Write-Host "PSScriptAnalyzer version: $((Get-Module PSScriptAnalyzer).Version)" +Write-Host '::endgroup::' + +# Collect files to analyze +Write-Host '::group::Collecting Files' +$filesToAnalyze = @() + +if ($ChangedFilesOnly) { + Write-Host "Analyzing changed files only (base: $BaseBranch)" + if (Get-Command -Name 'Get-ChangedFilesFromGit' -ErrorAction SilentlyContinue) { + $filesToAnalyze = Get-ChangedFilesFromGit -BaseBranch $BaseBranch -FileExtensions @('*.ps1', '*.psm1', '*.psd1') + } else { + # Fallback if module not available + $mergeBase = git merge-base HEAD $BaseBranch 2>$null + if (-not $mergeBase) { + $mergeBase = $BaseBranch + } + $changedFiles = git diff --name-only --diff-filter=d $mergeBase HEAD 2>$null + if ($changedFiles) { + $filesToAnalyze = $changedFiles | Where-Object { $_ -match '\.(ps1|psm1|psd1)$' } | ForEach-Object { + Join-Path $repoRoot $_ + } | Where-Object { Test-Path $_ } + } + } +} else { + Write-Host 'Analyzing all PowerShell files in repository' + $filesToAnalyze = Get-ChildItem -Path $repoRoot -Include '*.ps1', '*.psm1', '*.psd1' -Recurse -File | + Where-Object { $_.FullName -notmatch '[\\/](\.git|node_modules|vendor)[\\/]' } | + Select-Object -ExpandProperty FullName +} + +Write-Host "Found $($filesToAnalyze.Count) file(s) to analyze" +if ($filesToAnalyze.Count -gt 0) { + $filesToAnalyze | ForEach-Object { Write-Host " - $_" } +} +Write-Host '::endgroup::' + +# Run analysis +$allResults = @() +$errorCount = 0 +$warningCount = 0 + +if ($filesToAnalyze.Count -gt 0) { + Write-Host '::group::PSScriptAnalyzer Results' + + foreach ($file in $filesToAnalyze) { + $relativePath = $file + if ($file.StartsWith($repoRoot)) { + $relativePath = $file.Substring($repoRoot.Length).TrimStart('\', '/') + } + + $analyzerParams = @{ + Path = $file + Recurse = $false + } + + if ((Test-Path $ConfigPath)) { + $analyzerParams['Settings'] = $ConfigPath + } + + $results = Invoke-ScriptAnalyzer @analyzerParams + + if ($results) { + $allResults += $results + + foreach ($result in $results) { + $severity = $result.Severity.ToString().ToLower() + $message = "$($result.RuleName): $($result.Message)" + + # Map PSScriptAnalyzer severity to GitHub annotation type + $annotationType = switch ($severity) { + 'error' { 'error' } + 'warning' { 'warning' } + default { 'notice' } + } + + if ($severity -eq 'error') { $errorCount++ } + if ($severity -eq 'warning') { $warningCount++ } + + # Write GitHub Actions annotation + if (Get-Command -Name 'Write-GitHubAnnotation' -ErrorAction SilentlyContinue) { + Write-GitHubAnnotation -Type $annotationType -Message $message -File $relativePath -Line $result.Line -Column $result.Column + } else { + Write-Host "::$annotationType file=$relativePath,line=$($result.Line),col=$($result.Column)::$message" + } + } + } + } + + Write-Host '::endgroup::' +} + +# Export results if OutputPath specified +if ($OutputPath) { + $outputDir = Split-Path $OutputPath -Parent + if ($outputDir -and -not (Test-Path $outputDir)) { + New-Item -ItemType Directory -Path $outputDir -Force | Out-Null + } + + $exportData = @{ + timestamp = (Get-Date).ToString('o') + totalFiles = $filesToAnalyze.Count + errorCount = $errorCount + warningCount = $warningCount + results = $allResults | ForEach-Object { + @{ + file = $_.ScriptPath + line = $_.Line + column = $_.Column + severity = $_.Severity.ToString() + rule = $_.RuleName + message = $_.Message + } + } + } + + $exportData | ConvertTo-Json -Depth 10 | Set-Content -Path $OutputPath -Encoding UTF8 + Write-Host "Results exported to: $OutputPath" +} + +# Summary +Write-Host '' +Write-Host "PSScriptAnalyzer Summary:" +Write-Host " Files analyzed: $($filesToAnalyze.Count)" +Write-Host " Errors: $errorCount" +Write-Host " Warnings: $warningCount" + +# Set GitHub outputs if available +if (Get-Command -Name 'Set-GitHubOutput' -ErrorAction SilentlyContinue) { + Set-GitHubOutput -Name 'error-count' -Value $errorCount + Set-GitHubOutput -Name 'warning-count' -Value $warningCount + Set-GitHubOutput -Name 'files-analyzed' -Value $filesToAnalyze.Count +} + +# Write step summary if in GitHub Actions +if ($env:GITHUB_STEP_SUMMARY) { + $summary = @" +## PSScriptAnalyzer Results + +| Metric | Count | +|--------|-------| +| Files Analyzed | $($filesToAnalyze.Count) | +| Errors | $errorCount | +| Warnings | $warningCount | + +"@ + if (Get-Command -Name 'Write-GitHubStepSummary' -ErrorAction SilentlyContinue) { + Write-GitHubStepSummary -Content $summary + } else { + Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value $summary + } +} + +# Exit with appropriate code +if ($errorCount -gt 0 -and -not $SoftFail) { + Write-Host '' + Write-Host "::error::PSScriptAnalyzer found $errorCount error(s). Fix the issues above." + exit 1 +} + +exit 0 diff --git a/scripts/linting/Link-Lang-Check.ps1 b/scripts/linting/Link-Lang-Check.ps1 new file mode 100644 index 0000000..4e3d339 --- /dev/null +++ b/scripts/linting/Link-Lang-Check.ps1 @@ -0,0 +1,257 @@ +#!/usr/bin/env pwsh +<# +.SYNOPSIS + Language Path Link Checker and Fixer + +.DESCRIPTION + Finds and optionally fixes URLs in git-tracked text files that contain + the language path segment 'en-us'. Helps maintain links that work regardless + of user language settings by removing unnecessary language path segments. + +.PARAMETER Fix + Fix URLs by removing "en-us/" instead of just reporting them + +.EXAMPLE + .\Link-Lang-Check.ps1 + +.EXAMPLE + .\Link-Lang-Check.ps1 -Fix -Verbose +#> + +[CmdletBinding()] +param( + [switch]$Fix, + [string[]]$Files +) + +function Get-GitTextFile { + <# + .SYNOPSIS + Get list of all text files under git source control, excluding binary files. + #> + + try { + $result = & git grep -I --name-only -e '' 2>&1 + + if ($LASTEXITCODE -gt 1) { + Write-Error "Error executing git grep: $result" + return @() + } + + if ($result -and $result.Count -gt 0) { + return $result | Where-Object { $_ -is [string] -and $_.Trim() -ne '' } + } + + return @() + } + catch { + Write-Error "Error getting git text files: $_" + return @() + } +} + +function Find-LinksInFile { + <# + .SYNOPSIS + Find links with 'en-us' in them and return details. + #> + + [CmdletBinding()] + param( + [string]$FilePath + ) + + $linksFound = @() + + try { + $lines = Get-Content -Path $FilePath -Encoding UTF8 -ErrorAction Stop + } + catch { + Write-Verbose "Could not read $FilePath`: $_" + return $linksFound + } + + $urlPattern = 'https?://[^\s<>"'']+?en-us/[^\s<>"'']+' + + for ($i = 0; $i -lt $lines.Count; $i++) { + $line = $lines[$i] + $urlMatches = [regex]::Matches($line, $urlPattern) + + foreach ($match in $urlMatches) { + $linksFound += [PSCustomObject]@{ + File = $FilePath + LineNumber = $i + 1 + OriginalUrl = $match.Value + FixedUrl = $match.Value -replace 'en-us/', '' + } + } + } + + return $linksFound +} + +function Repair-LinksInFile { + <# + .SYNOPSIS + Fix links in a single file by removing 'en-us/' from URLs. + #> + + [CmdletBinding()] + param( + [string]$FilePath, + [PSCustomObject[]]$Links + ) + + try { + $content = Get-Content -Path $FilePath -Raw -Encoding UTF8 -ErrorAction Stop + } + catch { + Write-Verbose "Could not read $FilePath`: $_" + return $false + } + + $modifiedContent = $content + foreach ($link in $Links) { + $modifiedContent = $modifiedContent -replace [regex]::Escape($link.OriginalUrl), $link.FixedUrl + } + + if ($modifiedContent -ne $content) { + try { + Set-Content -Path $FilePath -Value $modifiedContent -Encoding UTF8 -NoNewline -ErrorAction Stop + return $true + } + catch { + Write-Verbose "Could not write to $FilePath`: $_" + return $false + } + } + return $false +} + +function Repair-AllLink { + <# + .SYNOPSIS + Fix all links in their respective files. + #> + + [CmdletBinding()] + param( + [PSCustomObject[]]$AllLinks + ) + + $linksByFile = $AllLinks | Group-Object -Property File + $filesModified = 0 + + foreach ($fileGroup in $linksByFile) { + $filePath = $fileGroup.Name + $links = $fileGroup.Group + + Write-Verbose "Fixing links in $filePath..." + + if (Repair-LinksInFile -FilePath $filePath -Links $links) { + $filesModified++ + } + } + + return $filesModified +} + +function ConvertTo-JsonOutput { + <# + .SYNOPSIS + Prepare links for JSON output. + #> + + [CmdletBinding()] + param( + [PSCustomObject[]]$Links + ) + + $jsonData = @() + foreach ($link in $Links) { + $jsonData += [PSCustomObject]@{ + file = $link.File + line_number = $link.LineNumber + original_url = $link.OriginalUrl + } + } + return $jsonData +} + +# Main script execution +try { + if ($Verbose) { + Write-Information "Getting list of git-tracked text files..." -InformationAction Continue + } + + if ($Files -and $Files.Count -gt 0) { + $files = $Files + } + else { + $files = Get-GitTextFile + } + + if ($Verbose) { + Write-Information "Found $($files.Count) git-tracked text files" -InformationAction Continue + } + + $allLinks = @() + + foreach ($filePath in $files) { + if (-not (Test-Path -Path $filePath -PathType Leaf)) { + if ($Verbose) { + Write-Warning "Skipping $filePath`: not a regular file" + } + continue + } + + if ($Verbose) { + Write-Verbose "Processing $filePath..." + } + + $links = Find-LinksInFile -FilePath $filePath + $allLinks += $links + } + + if ($allLinks.Count -gt 0) { + if ($Fix) { + if ($Verbose) { + Write-Information "`nFound $($allLinks.Count) URLs containing 'en-us':`n" -InformationAction Continue + foreach ($linkInfo in $allLinks) { + Write-Information "File: $($linkInfo.File), Line: $($linkInfo.LineNumber)" -InformationAction Continue + Write-Information " URL: $($linkInfo.OriginalUrl)" -InformationAction Continue + Write-Information "" -InformationAction Continue + } + } + + $filesModified = Repair-AllLink -AllLinks $allLinks + Write-Output "Fixed $($allLinks.Count) URLs in $filesModified files." + + if ($Verbose) { + Write-Information "`nDetails of fixes:" -InformationAction Continue + foreach ($linkInfo in $allLinks) { + Write-Information "File: $($linkInfo.File), Line: $($linkInfo.LineNumber)" -InformationAction Continue + Write-Information " Original: $($linkInfo.OriginalUrl)" -InformationAction Continue + Write-Information " Fixed: $($linkInfo.FixedUrl)" -InformationAction Continue + Write-Information "" -InformationAction Continue + } + } + } + else { + $jsonOutput = ConvertTo-JsonOutput -Links $allLinks + Write-Output ($jsonOutput | ConvertTo-Json -Depth 3) + } + } + else { + if (-not $Fix) { + Write-Output "[]" + } + else { + Write-Output "No URLs containing 'en-us' were found." + } + } +} +catch { + Write-Error "An error occurred: $_" + exit 1 +} diff --git a/scripts/linting/Markdown-Link-Check.ps1 b/scripts/linting/Markdown-Link-Check.ps1 new file mode 100644 index 0000000..9628c2f --- /dev/null +++ b/scripts/linting/Markdown-Link-Check.ps1 @@ -0,0 +1,356 @@ +#!/usr/bin/env pwsh +#Requires -Version 7.0 + + +<# +.SYNOPSIS + Repository-aware wrapper for markdown-link-check. + +.DESCRIPTION + Runs markdown-link-check with the repo-specific configuration to validate + all markdown links across the repository. Only checks files that are tracked + by git (respects .gitignore and only includes committed/staged files). + +.PARAMETER Path + One or more files or directories to scan. Directories are searched + recursively for Markdown files. Defaults to the documentation sources. + +.PARAMETER ConfigPath + Path to the shared markdown-link-check configuration file. + +.PARAMETER Quiet + Suppress non-error output from markdown-link-check. + +.EXAMPLE + # Validate all markdown files in default paths + ./Markdown-Link-Check.ps1 + +.EXAMPLE + # Validate specific path with verbose output + ./Markdown-Link-Check.ps1 -Path ".github" -Quiet:$false +#> + +[CmdletBinding()] +param( + [string[]]$Path = @( + ".", + ".github", + "docs" + ), + + [string]$ConfigPath = (Join-Path -Path $PSScriptRoot -ChildPath 'markdown-link-check.config.json'), + + [switch]$Quiet +) + +# Import LintingHelpers module +Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'Modules/LintingHelpers.psm1') -Force + +function Get-MarkdownTarget { + <# + .SYNOPSIS + Resolves Markdown files to validate from provided path arguments. + + .DESCRIPTION + Accepts files or directories, expanding directories to all git-tracked + Markdown files discovered recursively, and returns a sorted, unique list + of absolute file paths for downstream validation. Only checks files that + are tracked by git (respects .gitignore). + + .PARAMETER InputPath + Files or directories that may contain Markdown content. + + .OUTPUTS + System.String[] + #> + param( + [string[]]$InputPath + ) + + $targets = @() + $repoRoot = git rev-parse --show-toplevel 2>$null + + if ($LASTEXITCODE -ne 0) { + Write-Warning "Not in a git repository, falling back to file system search" + foreach ($item in $InputPath) { + if ([string]::IsNullOrWhiteSpace($item)) { + continue + } + + $resolved = Resolve-Path -LiteralPath $item -ErrorAction SilentlyContinue + if (-not $resolved) { + Write-Warning "Unable to resolve path: $item" + continue + } + + foreach ($resolvedPath in $resolved) { + if (Test-Path -LiteralPath $resolvedPath -PathType Container) { + $targets += Get-ChildItem -LiteralPath $resolvedPath -Recurse -Include *.md | + Where-Object { -not $_.PSIsContainer } | + Select-Object -ExpandProperty FullName + } + else { + $targets += $resolvedPath.ProviderPath + } + } + } + return ($targets | Sort-Object -Unique) + } + + Write-Verbose "Searching for git-tracked markdown files..." + Write-Verbose "Repository root: $repoRoot" + + foreach ($item in $InputPath) { + if ([string]::IsNullOrWhiteSpace($item)) { + continue + } + + if (Test-Path -Path $item -PathType Leaf) { + $absolutePath = (Resolve-Path $item).Path + $relativePath = [System.IO.Path]::GetRelativePath($repoRoot, $absolutePath) + $tracked = git ls-files $relativePath 2>$null + + if ($tracked -and $item -like "*.md") { + $targets += $absolutePath + } + elseif (-not $tracked) { + Write-Warning "File not tracked by git: $item" + } + } + elseif (Test-Path -Path $item -PathType Container) { + $absolutePath = (Resolve-Path $item).Path + $relativePath = [System.IO.Path]::GetRelativePath($repoRoot, $absolutePath) + $searchPath = if ($relativePath -eq '.') { '*.md' } else { "$relativePath/**/*.md" } + + Write-Verbose "Searching in: $searchPath" + $trackedFiles = git ls-files $searchPath 2>$null + + if ($trackedFiles) { + foreach ($file in $trackedFiles) { + $fullPath = Join-Path $repoRoot $file + if (Test-Path $fullPath) { + $targets += $fullPath + } + } + } + } + else { + Write-Warning "Unable to resolve path: $item" + } + } + + Write-Verbose "Found $($targets.Count) git-tracked markdown files" + return ($targets | Sort-Object -Unique) +} + +function Get-RelativePrefix { + <# + .SYNOPSIS + Builds a normalized relative prefix between two paths. + + .DESCRIPTION + Computes the relative path from a source directory to a destination and + enforces forward-slash separators with a trailing slash when required to + produce consistent link prefixes. + + .PARAMETER FromPath + The directory from which the relative path should be calculated. + + .PARAMETER ToPath + The target path that should be expressed relative to the source. + + .OUTPUTS + System.String + #> + param( + [string]$FromPath, + [string]$ToPath + ) + + $relative = [System.IO.Path]::GetRelativePath($FromPath, $ToPath) + if ([string]::IsNullOrWhiteSpace($relative) -or $relative -eq '.') { + return '' + } + + $normalized = $relative -replace '\\', '/' + if (-not $normalized.EndsWith('/')) { + $normalized += '/' + } + + return $normalized +} + + +$scriptRootParent = Split-Path -Path $PSScriptRoot -Parent +$repoRootPath = Split-Path -Path $scriptRootParent -Parent +$repoRoot = Resolve-Path -LiteralPath $repoRootPath +$config = Resolve-Path -LiteralPath $ConfigPath -ErrorAction Stop +$filesToCheck = Get-MarkdownTarget -InputPath $Path + +if (-not $filesToCheck -or $filesToCheck.Count -eq 0) { + Write-Error 'No markdown files were found to validate.' + exit 1 +} + +$cli = Join-Path -Path $repoRoot.Path -ChildPath 'node_modules/.bin/markdown-link-check' +if ($IsWindows) { + $cli += '.cmd' +} + +if (-not (Test-Path -LiteralPath $cli)) { + Write-Error 'markdown-link-check is not installed. Run "npm install --save-dev markdown-link-check" first.' + exit 1 +} + +$baseArguments = @('-c', $config.Path) +if ($Quiet) { + $baseArguments += '-q' +} + +$failedFiles = @() +$brokenLinks = @() +$totalLinks = 0 +$totalFiles = $filesToCheck.Count + +Push-Location $repoRoot.Path +try { + foreach ($file in $filesToCheck) { + $absolute = Resolve-Path -LiteralPath $file + $relative = [System.IO.Path]::GetRelativePath($repoRoot.Path, $absolute) + Write-Output "Checking $relative" + + $xmlFile = [System.IO.Path]::GetTempFileName() + '.xml' + try { + $commandArgs = $baseArguments + @($relative, '--reporters', 'default,junit', '--junit-output', $xmlFile) + $output = & $cli @commandArgs 2>&1 + $exitCode = $LASTEXITCODE + + if ($VerbosePreference -eq 'Continue' -or $exitCode -ne 0) { + Write-Host $output + } + + if (Test-Path $xmlFile) { + [xml]$xml = Get-Content $xmlFile -Raw -Encoding utf8 + + foreach ($testsuite in $xml.testsuites.testsuite) { + foreach ($testcase in $testsuite.testcase) { + $totalLinks++ + + $url = ($testcase.properties.property | Where-Object { $_.name -eq 'url' }).value + $status = ($testcase.properties.property | Where-Object { $_.name -eq 'status' }).value + $statusCode = ($testcase.properties.property | Where-Object { $_.name -eq 'statusCode' }).value + + if (-not $Quiet) { + if ($status -eq 'alive') { + Write-Host " βœ“ $url" -ForegroundColor Green + } + elseif ($status -eq 'ignored') { + Write-Host " / $url (ignored)" -ForegroundColor Yellow + } + elseif ($status -eq 'dead') { + Write-Host " βœ– $url β†’ Status: $statusCode" -ForegroundColor Red + } + } + + if ($status -eq 'dead') { + $brokenLinks += @{ + File = $relative + Link = $url + Status = "$statusCode" + } + Write-GitHubAnnotation -Type 'error' -Message "Broken link: $url (Status: $statusCode)" -File $relative + } + } + } + } + + if ($exitCode -ne 0) { + $failedFiles += $relative + } + } + catch { + Write-Warning "Failed to parse XML output for $relative : $_" + if ($exitCode -ne 0) { + $failedFiles += $relative + } + } + finally { + if (Test-Path $xmlFile) { + Remove-Item $xmlFile -Force + } + } + } +} +finally { + Pop-Location +} + +$logsDir = Join-Path -Path $repoRoot.Path -ChildPath 'logs' +if (-not (Test-Path $logsDir)) { + New-Item -ItemType Directory -Path $logsDir -Force | Out-Null +} + +$results = @{ + timestamp = (Get-Date).ToUniversalTime().ToString('o') + script = 'markdown-link-check' + summary = @{ + total_files = $totalFiles + files_with_broken_links = $failedFiles.Count + total_links_checked = $totalLinks + total_broken_links = $brokenLinks.Count + } + broken_links = $brokenLinks +} + +$resultsPath = Join-Path -Path $logsDir -ChildPath 'markdown-link-check-results.json' +$results | ConvertTo-Json -Depth 10 | Set-Content -Path $resultsPath -Encoding UTF8 + +if ($failedFiles.Count -gt 0) { + $summaryContent = @" +## ❌ Markdown Link Check Failed + +**Files with broken links:** $($failedFiles.Count) / $totalFiles +**Total broken links:** $($brokenLinks.Count) + +### Broken Links + +| File | Broken Link | +|------|-------------| +"@ + + foreach ($link in $brokenLinks) { + $summaryContent += "`n| ``$($link.File)`` | ``$($link.Link)`` |" + } + + $summaryContent += @" + + +### How to Fix + +1. Review the broken links listed above +2. Update or remove invalid links +3. Re-run the link check to verify fixes + +For more information, see the [markdown-link-check documentation](https://github.com/tcort/markdown-link-check). +"@ + + Write-GitHubStepSummary -Content $summaryContent + Set-GitHubEnv -Name "MARKDOWN_LINK_CHECK_FAILED" -Value "true" + + Write-Error ("markdown-link-check reported failures for: {0}" -f ($failedFiles -join ', ')) + exit 1 +} +else { + $summaryContent = @" +## βœ… Markdown Link Check Passed + +**Files checked:** $totalFiles +**Total links checked:** $totalLinks +**Broken links:** 0 + +Great job! All markdown links are valid. πŸŽ‰ +"@ + + Write-GitHubStepSummary -Content $summaryContent + Write-Output 'markdown-link-check completed successfully.' +} diff --git a/scripts/linting/Modules/LintingHelpers.psm1 b/scripts/linting/Modules/LintingHelpers.psm1 new file mode 100644 index 0000000..2087361 --- /dev/null +++ b/scripts/linting/Modules/LintingHelpers.psm1 @@ -0,0 +1,346 @@ +# LintingHelpers.psm1 +# +# Purpose: Shared helper functions for linting scripts and workflows +# Author: HVE Core Team +# Created: 2025-11-05 + +function Get-ChangedFilesFromGit { + <# + .SYNOPSIS + Gets changed files from git with intelligent fallback strategies. + + .DESCRIPTION + Attempts to detect changed files using merge-base, with fallbacks for different scenarios. + + .PARAMETER BaseBranch + The base branch to compare against (default: origin/main). + + .PARAMETER FileExtensions + Array of file extensions to filter (e.g., @('*.ps1', '*.md')). + + .OUTPUTS + Array of changed file paths. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $false)] + [string]$BaseBranch = "origin/main", + + [Parameter(Mandatory = $false)] + [string[]]$FileExtensions = @('*') + ) + + $changedFiles = @() + + try { + # Try merge-base first (best for PRs) + $mergeBase = git merge-base HEAD $BaseBranch 2>$null + if ($LASTEXITCODE -eq 0 -and $mergeBase) { + $changedFiles = git diff --name-only $mergeBase HEAD 2>$null + } + else { + # Fallback: compare directly with base branch + $changedFiles = git diff --name-only $BaseBranch HEAD 2>$null + if ($LASTEXITCODE -ne 0) { + # Final fallback: get all tracked files + Write-Warning "Could not determine changed files, analyzing all files" + $changedFiles = git ls-files 2>$null + } + } + } + catch { + Write-Warning "Git operation failed: $_" + $changedFiles = git ls-files 2>$null + } + + # Filter by extensions + if ($FileExtensions -and $FileExtensions[0] -ne '*') { + $filteredFiles = @() + foreach ($file in $changedFiles) { + foreach ($ext in $FileExtensions) { + $pattern = $ext.TrimStart('*') + if ($file -like "*$pattern") { + $filteredFiles += $file + break + } + } + } + $changedFiles = $filteredFiles + } + + # Return only existing files + $existingFiles = @() + foreach ($file in $changedFiles) { + if (Test-Path $file) { + $existingFiles += $file + } + } + + return $existingFiles +} + +function Get-FilesRecursive { + <# + .SYNOPSIS + Gets files recursively with gitignore pattern support. + + .DESCRIPTION + Retrieves files matching specified patterns while respecting gitignore rules. + + .PARAMETER Path + Root path to search from. + + .PARAMETER Include + Array of file patterns to include (e.g., @('*.ps1', '*.psm1')). + + .PARAMETER GitIgnorePath + Path to .gitignore file for exclusion patterns. + + .OUTPUTS + Array of file paths. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Path, + + [Parameter(Mandatory = $false)] + [string[]]$Include = @('*'), + + [Parameter(Mandatory = $false)] + [string]$GitIgnorePath + ) + + $files = @() + $excludePatterns = @() + + # Load gitignore patterns if provided + if ($GitIgnorePath -and (Test-Path $GitIgnorePath)) { + $excludePatterns = Get-GitIgnorePatterns -GitIgnorePath $GitIgnorePath + } + + # Get all files matching include patterns + foreach ($pattern in $Include) { + $matchingFiles = Get-ChildItem -Path $Path -Filter $pattern -Recurse -File -ErrorAction SilentlyContinue + foreach ($file in $matchingFiles) { + $relativePath = $file.FullName.Substring((Get-Location).Path.Length + 1) + + # Check against exclude patterns + $excluded = $false + foreach ($excludePattern in $excludePatterns) { + if ($relativePath -like $excludePattern) { + $excluded = $true + break + } + } + + if (-not $excluded) { + $files += $file + } + } + } + + return $files | Select-Object -Unique +} + +function Get-GitIgnorePatterns { + <# + .SYNOPSIS + Converts gitignore patterns to PowerShell wildcard patterns. + + .PARAMETER GitIgnorePath + Path to the .gitignore file. + + .OUTPUTS + Array of PowerShell wildcard patterns. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$GitIgnorePath + ) + + $patterns = @() + + if (-not (Test-Path $GitIgnorePath)) { + return $patterns + } + + $lines = Get-Content $GitIgnorePath -ErrorAction SilentlyContinue + foreach ($line in $lines) { + $line = $line.Trim() + + # Skip comments and empty lines + if ([string]::IsNullOrWhiteSpace($line) -or $line.StartsWith('#')) { + continue + } + + # Convert to PowerShell pattern + $pattern = if ($line.StartsWith('/')) { + $line.Substring(1).Replace('/', '\') + } + elseif ($line.Contains('/')) { + "*\$($line.Replace('/', '\'))*" + } + else { + "*\$line\*" + } + + $patterns += $pattern + } + + return $patterns +} + +function Write-GitHubAnnotation { + <# + .SYNOPSIS + Writes GitHub Actions annotations for errors, warnings, or notices. + + .PARAMETER Type + Annotation type: 'error', 'warning', or 'notice'. + + .PARAMETER Message + The annotation message. + + .PARAMETER File + Optional file path. + + .PARAMETER Line + Optional line number. + + .PARAMETER Column + Optional column number. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [ValidateSet('error', 'warning', 'notice')] + [string]$Type, + + [Parameter(Mandatory = $true)] + [string]$Message, + + [Parameter(Mandatory = $false)] + [string]$File, + + [Parameter(Mandatory = $false)] + [int]$Line, + + [Parameter(Mandatory = $false)] + [int]$Column + ) + + if ($env:GITHUB_ACTIONS) { + $annotation = "::$Type" + $params = @() + + if ($File) { $params += "file=$File" } + if ($Line) { $params += "line=$Line" } + if ($Column) { $params += "col=$Column" } + + if ($params.Count -gt 0) { + $annotation += " $($params -join ',')" + } + + $annotation += "::$Message" + Write-Host $annotation + } + else { + $prefix = switch ($Type) { + 'error' { '❌' } + 'warning' { '⚠️' } + 'notice' { 'ℹ️' } + } + Write-Host "$prefix $Message" + } +} + +function Set-GitHubOutput { + <# + .SYNOPSIS + Sets a GitHub Actions output variable. + + .PARAMETER Name + Output variable name. + + .PARAMETER Value + Output variable value. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Name, + + [Parameter(Mandatory = $true)] + [string]$Value + ) + + if ($env:GITHUB_OUTPUT) { + "$Name=$Value" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 + } + else { + Write-Verbose "GitHub output: $Name=$Value" + } +} + +function Set-GitHubEnv { + <# + .SYNOPSIS + Sets a GitHub Actions environment variable. + + .PARAMETER Name + Environment variable name. + + .PARAMETER Value + Environment variable value. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Name, + + [Parameter(Mandatory = $true)] + [string]$Value + ) + + if ($env:GITHUB_ENV) { + "$Name=$Value" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 + } + else { + Write-Verbose "GitHub env: $Name=$Value" + } +} + +function Write-GitHubStepSummary { + <# + .SYNOPSIS + Appends content to GitHub Actions step summary. + + .PARAMETER Content + Markdown content to append. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Content + ) + + if ($env:GITHUB_STEP_SUMMARY) { + $Content | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8 + } + else { + Write-Verbose "Not in GitHub Actions environment - summary content: $Content" + } +} + +# Export functions +Export-ModuleMember -Function @( + 'Get-ChangedFilesFromGit', + 'Get-FilesRecursive', + 'Get-GitIgnorePatterns', + 'Write-GitHubAnnotation', + 'Set-GitHubOutput', + 'Set-GitHubEnv', + 'Write-GitHubStepSummary' +) diff --git a/scripts/linting/PSScriptAnalyzer.psd1 b/scripts/linting/PSScriptAnalyzer.psd1 new file mode 100644 index 0000000..f1c9eec --- /dev/null +++ b/scripts/linting/PSScriptAnalyzer.psd1 @@ -0,0 +1,66 @@ +# PSScriptAnalyzer Settings +# Purpose: Enforce PowerShell best practices and coding standards + +@{ + # Severity levels: Error, Warning, Information + # Only include rules that should fail the build + Severity = @('Error', 'Warning') + + # Include all default rules + IncludeDefaultRules = $true + + # Exclude specific rules that may not apply + ExcludeRules = @( + # Allow Write-Host for user-facing scripts (console output is intentional) + 'PSAvoidUsingWriteHost', + # Allow plural nouns for functions that semantically return multiple items + 'PSUseSingularNouns', + # Skip ShouldProcess for simple helper functions that only append to files + 'PSUseShouldProcessForStateChangingFunctions', + # Skip false positive for error redirection operator (2>$null) + 'PSPossibleIncorrectUsageOfRedirectionOperator' + ) + + # Custom rule configurations + Rules = @{ + # Enforce cmdlet alias avoidance (use full cmdlet names) + PSAvoidUsingCmdletAliases = @{ + Enable = $true + } + + # Require explicit parameter types + PSUseCompatibleSyntax = @{ + Enable = $true + TargetVersions = @('5.1', '7.0', '7.2') + } + + # Enforce proper comment-based help + PSProvideCommentHelp = @{ + Enable = $true + ExportedOnly = $false + BlockComment = $true + VSCodeSnippetCorrection = $true + Placement = 'before' + } + + # Enforce proper OutputType declarations + PSUseOutputTypeCorrectly = @{ + Enable = $true + } + + # Enforce proper verb usage + PSUseApprovedVerbs = @{ + Enable = $true + } + + # Require BOM for UTF-8 encoded files + PSUseBOMForUnicodeEncodedFile = @{ + Enable = $true + } + + # Avoid using positional parameters + PSAvoidUsingPositionalParameters = @{ + Enable = $false # Disabled - common in scripts + } + } +} diff --git a/scripts/linting/markdown-link-check.config.json b/scripts/linting/markdown-link-check.config.json new file mode 100644 index 0000000..c486605 --- /dev/null +++ b/scripts/linting/markdown-link-check.config.json @@ -0,0 +1,14 @@ +{ + "projectBaseUrl": ".", + "timeout": "20s", + "retryOn429": true, + "ignorePatterns": [ + { "pattern": "\\{\\{[A-Z0-9_]+\\}\\}" }, + { "pattern": "^mailto:" }, + { "pattern": "^tel:" }, + { + "pattern": "^https://github\\.com/Azure-Samples/azure-nvidia-robotics-reference-architecture" + } + ], + "replacementPatterns": [] +} diff --git a/workflows/README.md b/workflows/README.md index 397f7d0..63c00f0 100644 --- a/workflows/README.md +++ b/workflows/README.md @@ -6,8 +6,6 @@ ms.date: 2025-12-14 ms.topic: reference --- -# Workflows - Workflow templates for submitting robotics training and validation jobs to Azure infrastructure. ## πŸ“ Directory Structure @@ -27,13 +25,13 @@ workflows/ ## βš–οΈ Platform Comparison -| Feature | AzureML | OSMO | -|---------|---------|------| -| Orchestration | Azure ML Job Service | OSMO Workflow Engine | -| Scheduling | Azure ML Compute | KAI Scheduler / Volcano | -| Multi-node | Azure ML distributed jobs | OSMO workflow DAGs | -| Checkpointing | MLflow integration | MLflow + custom handlers | -| Monitoring | Azure ML Studio | OSMO UI Dashboard | +| Feature | AzureML | OSMO | +|---------------|---------------------------|--------------------------| +| Orchestration | Azure ML Job Service | OSMO Workflow Engine | +| Scheduling | Azure ML Compute | KAI Scheduler / Volcano | +| Multi-node | Azure ML distributed jobs | OSMO workflow DAGs | +| Checkpointing | MLflow integration | MLflow + custom handlers | +| Monitoring | Azure ML Studio | OSMO UI Dashboard | ## πŸš€ Quick Start @@ -61,12 +59,12 @@ workflows/ The `train-dataset.yaml` template uploads `src/training/` as a versioned OSMO dataset instead of base64-encoding it inline. -| Aspect | train.yaml | train-dataset.yaml | -|--------|------------|--------------------| +| Aspect | train.yaml | train-dataset.yaml | +|----------------|------------------------|-----------------------| | Payload method | Base64-encoded archive | Dataset folder upload | -| Size limit | ~1MB | Unlimited | -| Versioning | None | Automatic | -| Reusability | Per-run | Across runs | +| Size limit | ~1MB | Unlimited | +| Versioning | None | Automatic | +| Reusability | Per-run | Across runs | ### Dataset Submission @@ -83,31 +81,31 @@ The `train-dataset.yaml` template uploads `src/training/` as a versioned OSMO da ### Dataset Parameters -| Parameter | Default | Description | -|-----------|---------|-------------| -| `--dataset-bucket` | `training` | OSMO bucket for training code | -| `--dataset-name` | `training-code` | Dataset name (auto-versioned) | -| `--training-path` | `src/training` | Local folder to upload | +| Parameter | Default | Description | +|--------------------|-----------------|-------------------------------| +| `--dataset-bucket` | `training` | OSMO bucket for training code | +| `--dataset-name` | `training-code` | Dataset name (auto-versioned) | +| `--training-path` | `src/training` | Local folder to upload | The training folder mounts at `/data//training` inside the container. ## πŸ“‹ Prerequisites -| Requirement | Setup | -|-------------|-------| -| Infrastructure deployed | `deploy/001-iac/` | -| Setup scripts completed | `deploy/002-setup/` | -| Azure CLI authenticated | `az login` | +| Requirement | Setup | +|-------------------------------|--------------------------| +| Infrastructure deployed | `deploy/001-iac/` | +| Setup scripts completed | `deploy/002-setup/` | +| Azure CLI authenticated | `az login` | | OSMO CLI (for OSMO workflows) | Installed and configured | ## βš™οΈ Configuration Scripts resolve values in order: -| Precedence | Source | Example | -|------------|--------|---------| -| 1 (highest) | CLI arguments | `--resource-group rg-custom` | -| 2 | Environment variables | `AZURE_RESOURCE_GROUP=rg-custom` | -| 3 (default) | Terraform outputs | `deploy/001-iac/` | +| Precedence | Source | Example | +|-------------|-----------------------|----------------------------------| +| 1 (highest) | CLI arguments | `--resource-group rg-custom` | +| 2 | Environment variables | `AZURE_RESOURCE_GROUP=rg-custom` | +| 3 (default) | Terraform outputs | `deploy/001-iac/` | See individual workflow READMEs for detailed configuration options. diff --git a/workflows/azureml/README.md b/workflows/azureml/README.md index e0ae3e0..dd1d4d0 100644 --- a/workflows/azureml/README.md +++ b/workflows/azureml/README.md @@ -6,16 +6,14 @@ ms.date: 2025-12-04 ms.topic: reference --- -# AzureML Workflows - Azure Machine Learning job templates for Isaac Lab training and validation workloads. ## πŸ“œ Available Templates -| Template | Purpose | Submission Script | -|----------|---------|-------------------| -| [train.yaml](train.yaml) | Training jobs with checkpoint support | `scripts/submit-azureml-training.sh` | -| [validate.yaml](validate.yaml) | Policy validation and inference | `scripts/submit-azureml-validation.sh` | +| Template | Purpose | Submission Script | +|--------------------------------|---------------------------------------|----------------------------------------| +| [train.yaml](train.yaml) | Training jobs with checkpoint support | `scripts/submit-azureml-training.sh` | +| [validate.yaml](validate.yaml) | Policy validation and inference | `scripts/submit-azureml-validation.sh` | ## πŸ‹οΈ Training Job (`train.yaml`) @@ -23,16 +21,16 @@ Submits Isaac Lab reinforcement learning training to AKS GPU nodes via Azure ML. ### Key Parameters -| Input | Description | Default | -|-------|-------------|---------| -| `mode` | Execution mode | `train` | -| `checkpoint_mode` | Checkpoint loading strategy | `from-scratch` | -| `task` | Isaac Lab task name | `Isaac-Velocity-Rough-Anymal-C-v0` | -| `num_envs` | Number of parallel environments | `4096` | -| `headless` | Run without rendering | `true` | -| `max_iterations` | Training iterations | `4500` | +| Input | Description | Default | +|-------------------|---------------------------------|------------------------------------| +| `mode` | Execution mode | `train` | +| `checkpoint_mode` | Checkpoint loading strategy | `from-scratch` | +| `task` | Isaac Lab task name | `Isaac-Velocity-Rough-Anymal-C-v0` | +| `num_envs` | Number of parallel environments | `4096` | +| `headless` | Run without rendering | `true` | +| `max_iterations` | Training iterations | `4500` | -### Usage +### Training Usage ```bash # Default configuration from Terraform outputs @@ -48,16 +46,16 @@ Submits Isaac Lab reinforcement learning training to AKS GPU nodes via Azure ML. Runs trained policy validation and generates inference metrics. -### Key Parameters +### Validation Parameters -| Input | Description | Default | -|-------|-------------|---------| -| `mode` | Execution mode | `play` | -| `checkpoint_mode` | Must use trained checkpoint | `from-trained` | -| `task` | Isaac Lab task name | `Isaac-Velocity-Rough-Anymal-C-v0` | -| `num_envs` | Environments for validation | `1024` | +| Input | Description | Default | +|-------------------|-----------------------------|------------------------------------| +| `mode` | Execution mode | `play` | +| `checkpoint_mode` | Must use trained checkpoint | `from-trained` | +| `task` | Isaac Lab task name | `Isaac-Velocity-Rough-Anymal-C-v0` | +| `num_envs` | Environments for validation | `1024` | -### Usage +### Validation Usage ```bash # Default configuration @@ -72,12 +70,12 @@ Runs trained policy validation and generates inference metrics. All scripts support environment variable configuration: -| Variable | Description | -|----------|-------------| -| `AZURE_SUBSCRIPTION_ID` | Azure subscription ID | -| `AZURE_RESOURCE_GROUP` | Resource group name | +| Variable | Description | +|--------------------------|-------------------------| +| `AZURE_SUBSCRIPTION_ID` | Azure subscription ID | +| `AZURE_RESOURCE_GROUP` | Resource group name | | `AZUREML_WORKSPACE_NAME` | Azure ML workspace name | -| `AZUREML_COMPUTE` | Compute target name | +| `AZUREML_COMPUTE` | Compute target name | ## πŸ“‹ Prerequisites diff --git a/workflows/osmo/README.md b/workflows/osmo/README.md index e2a481a..261e920 100644 --- a/workflows/osmo/README.md +++ b/workflows/osmo/README.md @@ -6,32 +6,30 @@ ms.date: 2025-12-04 ms.topic: reference --- -# OSMO Workflows - NVIDIA OSMO workflow templates for distributed Isaac Lab training on Azure Kubernetes Service. ## πŸ“œ Available Templates -| Template | Purpose | Submission Script | -|----------|---------|-------------------| -| [train.yaml](train.yaml) | Distributed training (base64 inline) | `scripts/submit-osmo-training.sh` | +| Template | Purpose | Submission Script | +|------------------------------------------|---------------------------------------|-------------------------------------------| +| [train.yaml](train.yaml) | Distributed training (base64 inline) | `scripts/submit-osmo-training.sh` | | [train-dataset.yaml](train-dataset.yaml) | Distributed training (dataset upload) | `scripts/submit-osmo-dataset-training.sh` | ## βš–οΈ Workflow Comparison -| Aspect | train.yaml | train-dataset.yaml | -|--------|------------|--------------------| -| Payload | Base64-encoded archive | Dataset folder upload | -| Size limit | ~1MB | Unlimited | -| Versioning | None | Automatic | -| Reusability | Per-run | Across runs | -| Setup | None | Bucket configured | +| Aspect | train.yaml | train-dataset.yaml | +|-------------|------------------------|-----------------------| +| Payload | Base64-encoded archive | Dataset folder upload | +| Size limit | ~1MB | Unlimited | +| Versioning | None | Automatic | +| Reusability | Per-run | Across runs | +| Setup | None | Bucket configured | ## πŸ‹οΈ Training Workflow (`train.yaml`) Submits Isaac Lab distributed training through OSMO's workflow orchestration engine. -### Features +### Training Features * Multi-GPU distributed training coordination * KAI Scheduler / Volcano integration @@ -42,14 +40,14 @@ Submits Isaac Lab distributed training through OSMO's workflow orchestration eng Parameters are passed as key=value pairs through the submission script: -| Parameter | Description | -|-----------|-------------| +| Parameter | Description | +|-------------------------|-----------------------| | `azure_subscription_id` | Azure subscription ID | -| `azure_resource_group` | Resource group name | -| `azure_workspace_name` | ML workspace name | -| `task` | Isaac Lab task name | -| `num_envs` | Parallel environments | -| `max_iterations` | Training iterations | +| `azure_resource_group` | Resource group name | +| `azure_workspace_name` | ML workspace name | +| `task` | Isaac Lab task name | +| `num_envs` | Parallel environments | +| `max_iterations` | Training iterations | ### Usage @@ -67,7 +65,7 @@ Parameters are passed as key=value pairs through the submission script: Submits Isaac Lab training using OSMO dataset folder injection instead of base64-encoded archives. -### Features +### Dataset Features * Dataset versioning and reusability * No payload size limits @@ -76,13 +74,13 @@ Submits Isaac Lab training using OSMO dataset folder injection instead of base64 ### Dataset Parameters -| Parameter | Default | Description | -|-----------|---------|-------------| -| `dataset_bucket` | `training` | OSMO bucket for training code | -| `dataset_name` | `training-code` | Dataset name in bucket | -| `training_localpath` | (required) | Local path to src/training relative to workflow | +| Parameter | Default | Description | +|----------------------|-----------------|-------------------------------------------------| +| `dataset_bucket` | `training` | OSMO bucket for training code | +| `dataset_name` | `training-code` | Dataset name in bucket | +| `training_localpath` | (required) | Local path to src/training relative to workflow | -### Usage +### Dataset Usage ```bash # Default configuration @@ -96,14 +94,14 @@ Submits Isaac Lab training using OSMO dataset folder injection instead of base64 ## βš™οΈ Environment Variables -| Variable | Description | -|----------|-------------| -| `AZURE_SUBSCRIPTION_ID` | Azure subscription ID | -| `AZURE_RESOURCE_GROUP` | Resource group name | -| `WORKFLOW_TEMPLATE` | Path to workflow template | -| `OSMO_CONFIG_DIR` | OSMO configuration directory | -| `OSMO_DATASET_BUCKET` | Dataset bucket name (default: training) | -| `OSMO_DATASET_NAME` | Dataset name (default: training-code) | +| Variable | Description | +|-------------------------|-----------------------------------------| +| `AZURE_SUBSCRIPTION_ID` | Azure subscription ID | +| `AZURE_RESOURCE_GROUP` | Resource group name | +| `WORKFLOW_TEMPLATE` | Path to workflow template | +| `OSMO_CONFIG_DIR` | OSMO configuration directory | +| `OSMO_DATASET_BUCKET` | Dataset bucket name (default: training) | +| `OSMO_DATASET_NAME` | Dataset name (default: training-code) | ## πŸ“‹ Prerequisites @@ -120,10 +118,10 @@ OSMO services are deployed to the `osmo-control-plane` namespace. Access method When connected to VPN, OSMO is accessible via the internal load balancer: -| Service | URL | -|---------|-----| -| UI Dashboard | http://10.0.5.7 | -| API Service | http://10.0.5.7/api | +| Service | URL | +|--------------|-----------------------| +| UI Dashboard | `http://10.0.5.7` | +| API Service | `http://10.0.5.7/api` | ```bash osmo login http://10.0.5.7 --method=dev --username=testuser @@ -137,11 +135,11 @@ osmo info If `should_enable_private_aks_cluster = false` and not using VPN: -| Service | Port-Forward Command | Local URL | -|---------|---------------------|----------| -| UI Dashboard | `kubectl port-forward svc/osmo-ui 3000:80 -n osmo-control-plane` | http://localhost:3000 | -| API Service | `kubectl port-forward svc/osmo-service 9000:80 -n osmo-control-plane` | http://localhost:9000 | -| Router | `kubectl port-forward svc/osmo-router 8080:80 -n osmo-control-plane` | http://localhost:8080 | +| Service | Port-Forward Command | Local URL | +|--------------|-----------------------------------------------------------------------|-------------------------| +| UI Dashboard | `kubectl port-forward svc/osmo-ui 3000:80 -n osmo-control-plane` | `http://localhost:3000` | +| API Service | `kubectl port-forward svc/osmo-service 9000:80 -n osmo-control-plane` | `http://localhost:9000` | +| Router | `kubectl port-forward svc/osmo-router 8080:80 -n osmo-control-plane` | `http://localhost:8080` | ```bash # Start port-forward in background (or separate terminal) @@ -162,5 +160,5 @@ osmo backend list Access the OSMO UI dashboard: -- **VPN**: Open http://10.0.5.7 in your browser -- **Port-forward**: Run `kubectl port-forward svc/osmo-ui 3000:80 -n osmo-control-plane` then open http://localhost:3000 +* **VPN**: Open `http://10.0.5.7` in your browser +* **Port-forward**: Run `kubectl port-forward svc/osmo-ui 3000:80 -n osmo-control-plane` then open `http://localhost:3000`