diff --git a/.github/workflows/zai-agents-init-example.yml b/.github/workflows/zai-agents-init-example.yml new file mode 100644 index 0000000..ee00359 --- /dev/null +++ b/.github/workflows/zai-agents-init-example.yml @@ -0,0 +1,77 @@ +name: Zai AGENTS.md Init (Example) + +# Example workflow that runs on Mondays to initialize/update AGENTS.md files +# This demonstrates the scheduled tasks feature for AGENTS.md management + +on: + schedule: + # Run every Monday at midnight UTC + - cron: "0 0 * * 1" + + # Allow manual triggering + workflow_dispatch: + +# Required permissions for creating branches and PRs +permissions: + contents: write + pull-requests: write + +jobs: + agents-init: + name: Initialize/Update AGENTS.md + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + # Full history needed for branch operations + fetch-depth: 0 + + - name: Run zai-code-bot with scheduled task + uses: ./ + with: + # REQUIRED: Your Z.ai API key + # Store this in repository secrets: Settings > Secrets > Actions > New repository secret + ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }} + + # Optional: Model to use (defaults to glm-5.2) + ZAI_MODEL: ${{ vars.ZAI_MODEL || 'glm-5.2' }} + + # REQUIRED: URL to your Gist file with AGENTS.md content + # This is the "agents init command" - the gist contains the AGENTS.md template + # Can be set via: + # 1. secrets.ZAI_AGENTS_GIST_URL (recommended for production) + # 2. vars.ZAI_AGENTS_GIST_URL (for organization-wide) + # 3. In .zai-scheduled.yml configuration file + ZAI_AGENTS_GIST_URL: ${{ secrets.ZAI_AGENTS_GIST_URL }} + + # Enable scheduled tasks processing + ZAI_SCHEDULED_ENABLED: "true" + + # Path to scheduled config (defaults to .zai-scheduled.yml) + ZAI_SCHEDULED_CONFIG_PATH: ".zai-scheduled.yml" + + # GitHub token is automatically provided by GitHub Actions + env: + GITHUB_TOKEN: ${{ github.token }} + + # Optional job to verify the update worked + verify: + name: Verify AGENTS.md Update + runs-on: ubuntu-latest + needs: agents-init + if: success() + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check for changes + run: | + echo "AGENTS.md update workflow completed successfully" + # Add verification logic here if needed + # For example, check if files were modified: + # git diff --name-only HEAD~1 | grep -q "AGENTS.md" && echo "Files updated" || echo "No changes" diff --git a/.github/workflows/zai-agents-update.yml b/.github/workflows/zai-agents-update.yml new file mode 100644 index 0000000..ad65a30 --- /dev/null +++ b/.github/workflows/zai-agents-update.yml @@ -0,0 +1,64 @@ +name: Zai AGENTS.md Auto-Update + +# This workflow runs every Monday to update AGENTS.md files from a Gist URL +# It uses the scheduled tasks feature of zai-code-bot + +on: + schedule: + # Run “At 06:00.” UTC + - cron: "0 6 * * *" + workflow_dispatch: + # Allow manual triggering from GitHub UI + inputs: + dry-run: + description: "Dry run (no PR creation)" + required: false + default: "false" + +permissions: + contents: write # Required to create branches and update files + pull-requests: write # Required to create PRs + issues: write # Required for comments + +jobs: + update-agents: + name: Update AGENTS.md Files + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Required for full git history + + - name: Run zai-code-bot scheduled tasks + uses: ./ + with: + # Required: Z.ai API key (from repository secrets) + ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }} + + # Optional: Z.ai model to use + ZAI_MODEL: ${{ vars.ZAI_MODEL || 'glm-5.2' }} + + # Required: URL to raw gist file containing AGENTS.md content + # This can also be configured in .zai-scheduled.yml + ZAI_AGENTS_GIST_URL: ${{ secrets.ZAI_AGENTS_GIST_URL || vars.ZAI_AGENTS_GIST_URL }} + + # Enable scheduled tasks + ZAI_SCHEDULED_ENABLED: "true" + + # Optional: Path to scheduled configuration file + ZAI_SCHEDULED_CONFIG_PATH: ".zai-scheduled.yml" + env: + # GitHub token for API access (automatically provided by GitHub Actions) + GITHUB_TOKEN: ${{ github.token }} + + # Optional: Notify on failure + notify-failure: + name: Notify on Failure + runs-on: ubuntu-latest + needs: update-agents + if: failure() && needs.update-agents.result == 'failure' + steps: + - name: Send notification + run: echo "AGENTS.md update failed. Check workflow logs." + # Replace with actual notification (Slack, email, etc.) diff --git a/.zai-scheduled.yml b/.zai-scheduled.yml new file mode 100644 index 0000000..4ead134 --- /dev/null +++ b/.zai-scheduled.yml @@ -0,0 +1,66 @@ +# Zai Code Bot - Scheduled Tasks Configuration +# This file configures automated tasks that run on a schedule. +# +# For this repository (zai-code-bot), this configures: +# - Weekly AGENTS.md file updates by executing a command from a Gist URL +# +# IMPORTANT: The Gist URL must contain a command that generates/updates AGENTS.md files. +# The bot will: +# 1. Fetch the command text from the Gist +# 2. Execute the command to generate/update AGENTS.md files +# 3. The command MUST scan the repo and return JSON with all AGENTS.md files to create/update +# +# Gist URL Configuration (priority order): +# 1. ZAI_AGENTS_GIST_URL environment variable (secrets/vars) +# 2. gist_url in task config (below) +# 3. gist_url in defaults (below) - currently set to AndreiDrang's gist +# +# The Gist at the default URL should contain a prompt-command like: +# /init-agentsmd +# This command MUST autonomously scan the repo and return JSON with AGENTS.md tree + +version: 1 + +# Default settings applied to all tasks unless overridden +defaults: + # Target branch for PRs created by scheduled tasks + branch: main + + # Default cron schedule (if not specified per-task) + # Format: minute hour day-of-month month day-of-week + # "0 0 * * 1" = every Monday at midnight UTC + schedule: "0 0 * * 1" + + # Default Gist URL containing the command to generate AGENTS.md files + # The command in this Gist will be executed to generate the content + # Can be overridden via ZAI_AGENTS_GIST_URL environment variable + gist_url: https://gist.githubusercontent.com/AndreiDrang/1580ae796fe56074b600cee6352a5f14/raw + +# Individual scheduled tasks +tasks: + # Update AGENTS.md files weekly on Monday + - id: weekly-agents-update + name: "Weekly AGENTS.md Update" + enabled: true + schedule: "0 0 * * 1" # Every Monday at midnight UTC + command: update-agents + config: + # Branch to update - will create PR targeting this branch + branch: main + + # PR configuration + pr_title: "chore: update AGENTS.md files" + pr_body: | + Automated weekly update of AGENTS.md files from Gist. + + This PR was automatically generated by zai-code-bot scheduled task. + The command was fetched from the Gist URL configured in ZAI_AGENTS_GIST_URL + and executed to discover and update all AGENTS.md files in the repository. + + The agent autonomously scanned the repository and generated updated AGENTS.md files. + + To modify the Gist URL: + 1. Go to Settings > Secrets > Actions + 2. Update the ZAI_AGENTS_GIST_URL secret + 3. Or update this configuration file + commit_message: "docs: update AGENTS.md from scheduled task" diff --git a/.zai-scheduled.yml.template b/.zai-scheduled.yml.template new file mode 100644 index 0000000..b18036f --- /dev/null +++ b/.zai-scheduled.yml.template @@ -0,0 +1,148 @@ +# Zai Code Bot - Scheduled Tasks Configuration +# +# This file configures automated tasks that run on a schedule. +# Place this file in the root of your repository as .zai-scheduled.yml +# +# Documentation: https://github.com/AndreiDrang/zai-code-bot#scheduled-tasks +# +# ============================================================================ +# CONFIGURATION EXAMPLE +# ============================================================================ + +version: 1 + +# Default settings applied to all tasks unless overridden +defaults: + # Target branch for PRs created by scheduled tasks + branch: main + + # Default cron schedule (if not specified per-task) + # Format: minute hour day-of-month month day-of-week + # Example: "0 0 * * 0" = every Sunday at midnight UTC + # See: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule + schedule: "0 0 * * 0" + + # Default Gist URL for commands + # This is used by update-agents task to fetch content + # Can also be set via ZAI_AGENTS_GIST_URL environment variable + gist_url: https://gist.githubusercontent.com/AndreiDrang/1580ae796fe56074b600cee6352a5f14/raw + +# Individual scheduled tasks +tasks: + # Update AGENTS.md files weekly + # The command will scan the repo and find all AGENTS.md files to create/update + - id: weekly-agents-update + name: "Weekly AGENTS.md Update" + enabled: true + schedule: "0 0 * * 0" # Weekly on Sunday at midnight UTC + command: update-agents + config: + # Branch to update (defaults to defaults.branch) + branch: main + + # URL to fetch content from + gist_url: https://gist.githubusercontent.com/AndreiDrang/1580ae796fe56074b600cee6352a5f14/raw + + # PR configuration + pr_title: "chore: update AGENTS.md files" + pr_body: | + Automated weekly update of AGENTS.md files from gist. + + The agent autonomously scanned the repository and updated all AGENTS.md files. + Generated by zai-code-bot scheduled task. + commit_message: "docs: update AGENTS.md from scheduled task" + + # Example: Additional task (disabled by default) + # Uncomment and configure to enable + # - id: daily-docs-sync + # name: "Daily Documentation Sync" + # enabled: false + # schedule: "0 12 * * *" # Daily at noon UTC + # command: sync-docs + # config: + # source_repo: my-org/docs-repo + # target_path: docs/ + # pr_title: "docs: sync documentation" + # pr_body: "Automated daily documentation sync" + # commit_message: "docs: sync from upstream" + +# ============================================================================ +# WORKFLOW EXAMPLE +# ============================================================================ +# +# Create a file at: .github/workflows/zai-scheduled.yml +# +# name: Zai Scheduled Tasks +# +# on: +# schedule: +# - cron: "0 0 * * 0" # Weekly on Sunday +# workflow_dispatch: # Allow manual triggering +# +# permissions: +# contents: write # Required to create branches and PRs +# pull-requests: write +# +# jobs: +# zai-scheduled: +# name: Run Scheduled Tasks +# runs-on: ubuntu-latest +# steps: +# - name: Checkout +# uses: actions/checkout@v4 +# with: +# fetch-depth: 0 # Required for git history +# +# - name: Run scheduled tasks +# uses: AndreiDrang/zai-code-bot@main +# with: +# ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }} +# ZAI_MODEL: ${{ vars.ZAI_MODEL }} +# ZAI_SCHEDULED_ENABLED: "true" +# ZAI_AGENTS_GIST_URL: ${{ vars.ZAI_AGENTS_GIST_URL }} # Or use secrets.ZAI_AGENTS_GIST_URL +# +# ============================================================================ +# AVAILABLE COMMANDS +# ============================================================================ +# +# update-agents: Fetches and executes a command from a Gist URL to update files +# Gist URL can be configured via: gist_url (config) or ZAI_AGENTS_GIST_URL (env var) +# +# The command in the Gist MUST return a JSON response with file updates. +# The command scans the repo and determines which AGENTS.md files to create/update. +# +# Required config: branch (optional, defaults to main) +# Optional config: gist_url, pr_title, pr_body, commit_message +# +# Example command for auto-discovery mode (should be in Gist): +# /init-agentsmd +# This command must return JSON like: +# { +# "summary": "Updated 3 AGENTS.md files", +# "files": [ +# {"path": "AGENTS.md", "content": "...", "action": "updated"}, +# {"path": "src/lib/AGENTS.md", "content": "...", "action": "created"} +# ] +# } +# +# ============================================================================ +# CRON SCHEDULE REFERENCE +# ============================================================================ +# +# ┌───────────── minute (0 - 59) +# │ ┌───────────── hour (0 - 23) +# │ │ ┌───────────── day of the month (1 - 31) +# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC) +# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT) +# │ │ │ │ │ +# │ │ │ │ │ +# * * * * * +# +# Examples: +# "0 0 * * *" - Every day at midnight UTC +# "0 0 * * 0" - Every Sunday at midnight UTC +# "0 9 * * 1-5" - Every weekday at 9 AM UTC +# "0 */6 * * *" - Every 6 hours +# +# Note: GitHub Actions uses UTC timezone +# See: https://crontab.guru/ for help with cron expressions diff --git a/action.yml b/action.yml index 2db2afb..4da953f 100644 --- a/action.yml +++ b/action.yml @@ -36,6 +36,18 @@ inputs: description: "Maximum diff characters per file chunk before splitting a large patch across review parts" required: false default: "18000" + ZAI_SCHEDULED_ENABLED: + description: "Enable scheduled tasks execution" + required: false + default: "true" + ZAI_SCHEDULED_CONFIG_PATH: + description: "Path to scheduled tasks configuration file" + required: false + default: ".zai-scheduled.yml" + ZAI_AGENTS_GIST_URL: + description: "URL to raw gist file for AGENTS.md updates" + required: false + default: "" runs: using: "node20" main: "dist/index.js" diff --git a/dist/index.js b/dist/index.js index fd3b24f..eb0a002 100644 --- a/dist/index.js +++ b/dist/index.js @@ -31278,6 +31278,7 @@ const { handleDescribeCommand } = __nccwpck_require__(4334); const { handleImpactCommand } = __nccwpck_require__(7265); const reviewHandler = __nccwpck_require__(903); const explainHandler = __nccwpck_require__(1248); +const { handleScheduledEvent } = __nccwpck_require__(5679); const { DEFAULT_MAX_CHARS, fetchChangedFiles } = __nccwpck_require__(9990); const { loadContinuityState, mergeState, createCommentWithState } = __nccwpck_require__(4575); @@ -31623,6 +31624,8 @@ async function run() { await handleIssueCommentEvent(context, apiKey, model, owner, repo, zaiTimeout); } else if (eventType === 'pull_request_review_comment') { await handlePullRequestReviewCommentEvent(context, apiKey, model, owner, repo, zaiTimeout); + } else if (eventType === 'schedule') { + await handleScheduledEvent(context, apiKey, model, owner, repo); } } @@ -31947,6 +31950,7 @@ async function dispatchCommand(context, parseResult, apiKey, model, owner, repo, createCommentWithState: _createCommentWithState = createCommentWithState, COMMENT_MARKER: _COMMENT_MARKER = COMMENT_MARKER, REACTIONS: _REACTIONS = REACTIONS, + handleUpdateAgentsCommand: _handleUpdateAgentsCommand = null, } = deps; const { command, args } = parseResult; @@ -32209,6 +32213,74 @@ ${_COMMENT_MARKER}`; return; } + case 'update-agents': { + logger.info({ args }, 'Dispatching to update-agents handler'); + + const octokit = _github.getOctokit(process.env.GITHUB_TOKEN || _core.getInput('GITHUB_TOKEN')); + const targetBranch = baseRef || headRef || 'main'; + + try { + // Execute the update-agents task directly + const { handleUpdateAgentsTask, fetchFromUrl, fetchFileContent, createPR } = __nccwpck_require__(5679); + + const result = await handleUpdateAgentsTask({ + octokit, + owner, + repo, + task: { + id: 'manual-update-agents', + command: 'update-agents', + config: { + branch: targetBranch, + files: ['AGENTS.md'], + pr_title: 'chore: update AGENTS.md files', + pr_body: 'Manual update of AGENTS.md files from gist', + commit_message: 'docs: update AGENTS.md from manual command', + }, + }, + config: { + defaults: { + branch: targetBranch, + }, + }, + logger, + targetBranch, + fetchFromUrl, + fetchFile: async (path, ref) => fetchFileContent(octokit, owner, repo, path, ref || targetBranch), + createPullRequest: createPR, + }); + + if (result.success) { + if (result.prCreated) { + logger.info({ prNumber: result.prNumber, prUrl: result.prUrl }, 'Update agents command completed with PR'); + return { success: true }; + } else { + logger.info({ message: result.message }, 'Update agents command completed - no changes needed'); + return { success: true }; + } + } else { + logger.warn({ error: result.error }, 'Update agents command failed'); + terminalReaction = _REACTIONS.X; + responseMessage = `## Z.ai Update AGENTS.md + +**Error:** ${result.error} + +${_COMMENT_MARKER}`; + } + } catch (error) { + logger.error({ error: error.message, stack: error.stack }, 'Update agents handler threw error'); + terminalReaction = _REACTIONS.X; + responseMessage = `## Z.ai Update AGENTS.md + +**Error:** Failed to complete update. Please try again later. + +Details: ${error.message} + +${_COMMENT_MARKER}`; + } + break; + } + default: responseMessage = GUIDANCE_MESSAGES.unknown_command; } @@ -33705,7 +33777,7 @@ module.exports = { */ // Allowlisted commands -const ALLOWED_COMMANDS = ['ask', 'describe', 'explain', 'help', 'impact', 'review']; +const ALLOWED_COMMANDS = ['ask', 'describe', 'explain', 'help', 'impact', 'review', 'update-agents']; // Command metadata for help text const COMMAND_DESCRIPTIONS = { @@ -33733,6 +33805,10 @@ const COMMAND_DESCRIPTIONS = { usage: '/zai review [file]', description: 'Review specific files or all changed files', }, + 'update-agents': { + usage: '/zai update-agents', + description: 'Manually trigger AGENTS.md update from gist (collaborators only)', + }, }; // Error types @@ -33967,6 +34043,253 @@ module.exports = { }; +/***/ }), + +/***/ 4658: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +/** + * Scheduled Tasks Configuration Loader + * + * Loads and validates .zai-scheduled.yml configuration files from repositories. + * Provides schema validation and task filtering for scheduled execution. + */ + +const yaml = __nccwpck_require__(8815); + +// Configuration schema version +const CONFIG_VERSION = 1; + +/** + * Default configuration values + */ +const DEFAULTS = { + branch: 'main', + schedule: '0 0 * * 0', // Weekly on Sunday at midnight UTC + enabled: true, +}; + +/** + * Get the Gist URL for AGENTS.md updates + * Checks in order: task config, defaults, environment variable + * @param {Object} taskConfig - Task configuration + * @param {Object} defaults - Default configuration + * @returns {string|null} - Gist URL or null + */ +function getGistUrl(taskConfig, defaults) { + return taskConfig?.gist_url + || defaults?.gist_url + || process.env.ZAI_AGENTS_GIST_URL + || null; +} + +/** + * Load scheduled configuration from repository + * @param {Object} octokit - GitHub Octokit instance + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @param {string} ref - Branch/ref to load from (default: main) + * @returns {Promise} - Configuration object or null if not found + */ +async function loadScheduledConfig(octokit, owner, repo, ref = 'main') { + try { + const configPath = process.env.ZAI_SCHEDULED_CONFIG_PATH || '.zai-scheduled.yml'; + + const { data: configContent } = await octokit.rest.repos.getContent({ + owner, + repo, + path: configPath, + ref, + }); + + const rawContent = Buffer.from(configContent.content, 'base64').toString('utf8'); + const config = yaml.parse(rawContent); + + // Validate and normalize configuration + return validateAndNormalizeConfig(config); + } catch (error) { + if (error.status === 404) { + return null; // Config not found - scheduled tasks disabled + } + throw error; + } +} + +/** + * Validate configuration schema and normalize values + * @param {Object} config - Raw configuration object + * @returns {Object} - Validated and normalized configuration + * @throws {Error} - If configuration is invalid + */ +function validateAndNormalizeConfig(config) { + // Check version + if (!config.version) { + throw new Error(`Configuration missing required field: version`); + } + + if (config.version !== CONFIG_VERSION) { + throw new Error(`Unsupported config version: ${config.version}. Expected: ${CONFIG_VERSION}`); + } + + // Validate and normalize defaults + config.defaults = validateDefaults(config.defaults || {}); + + // Validate tasks + if (!config.tasks || !Array.isArray(config.tasks)) { + throw new Error('Configuration must contain a "tasks" array'); + } + + // Validate and normalize each task + config.tasks = config.tasks.map((task, index) => { + return validateAndNormalizeTask(task, index, config.defaults); + }); + + return config; +} + +/** + * Validate and normalize defaults section + * @param {Object} defaults - Raw defaults object + * @returns {Object} - Validated defaults + * @throws {Error} - If defaults are invalid + */ +function validateDefaults(defaults) { + const validated = { ...DEFAULTS, ...defaults }; + + if (validated.branch && typeof validated.branch !== 'string') { + throw new Error(`defaults.branch must be a string`); + } + + if (validated.schedule && typeof validated.schedule !== 'string') { + throw new Error(`defaults.schedule must be a string`); + } + + if (validated.gist_url && typeof validated.gist_url !== 'string') { + throw new Error(`defaults.gist_url must be a string`); + } + + return validated; +} + +/** + * Validate and normalize a single task + * @param {Object} task - Raw task object + * @param {number} index - Task index for error reporting + * @param {Object} defaults - Validated defaults + * @returns {Object} - Validated and normalized task + * @throws {Error} - If task is invalid + */ +function validateAndNormalizeTask(task, index, defaults) { + if (!task.id) { + throw new Error(`Task ${index} missing required field: id`); + } + + if (typeof task.id !== 'string') { + throw new Error(`Task ${index} field 'id' must be a string`); + } + + if (!task.command) { + throw new Error(`Task ${task.id} missing required field: command`); + } + + if (typeof task.command !== 'string') { + throw new Error(`Task ${task.id} field 'command' must be a string`); + } + + // Validate enabled flag + if (task.enabled !== undefined && typeof task.enabled !== 'boolean') { + throw new Error(`Task ${task.id} has invalid enabled value (must be boolean)`); + } + + // Apply defaults + const normalized = { + id: task.id, + name: task.name || task.id, + enabled: task.enabled !== undefined ? task.enabled : defaults.enabled, + schedule: task.schedule || defaults.schedule, + command: task.command, + config: { ...defaults, ...task.config }, + }; + + // Validate task-specific config + if (normalized.config.branch && typeof normalized.config.branch !== 'string') { + throw new Error(`Task ${task.id} has invalid branch value`); + } + + if (normalized.config.gist_url && typeof normalized.config.gist_url !== 'string') { + throw new Error(`Task ${task.id} has invalid gist_url value`); + } + + if (normalized.config.files && !Array.isArray(normalized.config.files)) { + throw new Error(`Task ${task.id} has invalid files value (must be array)`); + } + + return normalized; +} + +/** + * Get tasks that should run for this schedule event + * @param {Object} config - Validated configuration + * @param {string} eventSchedule - Cron schedule from GitHub event context + * @returns {Array} - Tasks to execute + */ +function getTasksToRun(config, eventSchedule) { + const enabledTasks = config.tasks.filter(t => t.enabled === true); + + // If no enabled tasks, return empty array + if (enabledTasks.length === 0) { + return []; + } + + // If event has specific schedule, only run tasks matching that schedule + if (eventSchedule) { + return enabledTasks.filter(t => { + return t.schedule === eventSchedule || + (config.defaults?.schedule && t.schedule === config.defaults.schedule); + }); + } + + // Otherwise run all enabled tasks + return enabledTasks; +} + +/** + * Get a specific task by ID + * @param {Object} config - Validated configuration + * @param {string} taskId - Task ID to find + * @returns {Object|null} - Task object or null if not found + */ +function getTaskById(config, taskId) { + return config.tasks.find(t => t.id === taskId) || null; +} + +/** + * Check if scheduled tasks are enabled in this repository + * @param {Object} octokit - GitHub Octokit instance + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @param {string} ref - Branch/ref to check (default: main) + * @returns {Promise} - True if scheduled tasks are enabled + */ +async function areScheduledTasksEnabled(octokit, owner, repo, ref = 'main') { + const config = await loadScheduledConfig(octokit, owner, repo, ref); + return config !== null; +} + +module.exports = { + CONFIG_VERSION, + loadScheduledConfig, + validateAndNormalizeConfig, + getTasksToRun, + getTaskById, + areScheduledTasksEnabled, + getGistUrl, + // Export for testing + validateDefaults, + validateAndNormalizeTask, +}; + + /***/ }), /***/ 9990: @@ -34563,7 +34886,7 @@ function extractReviewCommentAnchor(payload) { /** * Determines the type of GitHub event from the context * @param {Object} context - GitHub actions context object - * @returns {string} Event type: 'pull_request', 'issue_comment_pr', 'pull_request_review_comment', or 'issue_comment_non_pr' + * @returns {string} Event type: 'pull_request', 'issue_comment_pr', 'pull_request_review_comment', 'schedule', or 'issue_comment_non_pr' */ function getEventType(context) { const eventName = context.eventName; @@ -34585,6 +34908,11 @@ function getEventType(context) { return 'issue_comment_non_pr'; } + // Handle schedule events (cron-triggered) + if (eventName === 'schedule') { + return 'schedule'; + } + // Unknown event type - return as non-processable return 'issue_comment_non_pr'; } @@ -34638,6 +34966,11 @@ function shouldProcessEvent(context) { return { process: true, reason: 'pull_request_review_comment event' }; } + // Handle schedule events - always process (cron-triggered tasks) + if (eventType === 'schedule') { + return { process: true, reason: 'schedule event' }; + } + // Handle non-PR issue comments - reject if (eventType === 'issue_comment_non_pr') { return { process: false, reason: 'non-PR issue comment - not supported' }; @@ -36296,572 +36629,929 @@ module.exports = { /***/ }), -/***/ 2120: +/***/ 5679: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { /** - * Structured logging and error taxonomy for Z.ai Code Review - * Provides correlation IDs, standardized log fields, and user-safe error messages + * Scheduled Tasks Handler + * + * Handles scheduled (cron-based) execution of tasks like AGENTS.md updates. + * Supports flexible configuration via YAML and extensible command handlers. */ +const https = __nccwpck_require__(4708); +const { parseCommand, isValid } = __nccwpck_require__(5055); +const { loadScheduledConfig, getTasksToRun, getGistUrl } = __nccwpck_require__(4658); +const { createLogger, generateCorrelationId } = __nccwpck_require__(2120); const core = __nccwpck_require__(7484); -/** - * Error categories for classification - * @readonly - * @enum {string} - */ -const ERROR_CATEGORIES = { - /** Authentication/authorization failures */ - AUTH: 'AUTH', - /** Input validation failures */ - VALIDATION: 'VALIDATION', - /** External provider/API failures */ - PROVIDER: 'PROVIDER', - /** Rate limiting errors */ - RATE_LIMIT: 'RATE_LIMIT', - /** Timeout errors */ - TIMEOUT: 'TIMEOUT', - /** Internal/unexpected errors */ - INTERNAL: 'INTERNAL', -}; - -/** - * Fields that should be redacted from logs to prevent secret leakage - */ -const REDACT_FIELDS = [ - 'ZAI_API_KEY', - 'GITHUB_TOKEN', - 'authorization', - 'Authorization', - 'Bearer', - 'token', - 'api_key', - 'apikey', - 'secret', - 'password', - 'credential', -]; - -/** - * Mapping from internal error categories to user-safe messages - * These messages are designed to be displayed to end users - * without exposing internal implementation details - */ -const USER_MESSAGES = { - [ERROR_CATEGORIES.AUTH]: 'Authentication failed. Please verify your API credentials.', - [ERROR_CATEGORIES.VALIDATION]: 'Invalid input provided. Please check your action configuration.', - [ERROR_CATEGORIES.PROVIDER]: 'External service request failed. Please try again later.', - [ERROR_CATEGORIES.RATE_LIMIT]: 'Rate limit exceeded. Please wait before retrying.', - [ERROR_CATEGORIES.TIMEOUT]: 'Request timed out. Please try again.', - [ERROR_CATEGORIES.INTERNAL]: 'An unexpected error occurred. Please try again.', +// Handler registry for scheduled commands +const SCHEDULED_HANDLERS = { + 'update-agents': handleUpdateAgentsTask, }; /** - * Fields that should always be included in log context - */ -const STANDARD_FIELDS = [ - 'correlationId', - 'eventName', - 'prNumber', - 'command', - 'duration', -]; - -/** - * Generates a unique correlation ID for event tracking - * Format: timestamp-randomhex (e.g., "1700000000-a1b2c3d4") - * @returns {string} Unique correlation ID - */ -function generateCorrelationId() { - const timestamp = Date.now().toString(36); - const random = Math.random().toString(36).substring(2, 10); - return `${timestamp}-${random}`; -} - -/** - * Redacts sensitive fields from an object for safe logging - * @param {Object} data - Data object to redact - * @returns {Object} Data with sensitive fields redacted + * Main entry point for scheduled events + * @param {Object} context - GitHub actions context + * @param {string} apiKey - Z.ai API key + * @param {string} model - Z.ai model + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @returns {Promise} - Execution result */ -function redactSensitiveData(data) { - if (data === null || data === undefined) { - return data; - } - - if (typeof data !== 'object') { - return data; - } - - const redacted = Array.isArray(data) ? [] : {}; +async function handleScheduledEvent(context, apiKey, model, owner, repo) { + const github = __nccwpck_require__(3228); + const octokit = github.getOctokit(process.env.GITHUB_TOKEN || core.getInput('GITHUB_TOKEN')); + const logger = createLogger(generateCorrelationId(), { + eventName: 'schedule', + owner, + repo, + }); - for (const [key, value] of Object.entries(data)) { - const shouldRedact = REDACT_FIELDS.some( - field => key.toLowerCase().includes(field.toLowerCase()) - ); + logger.info('Scheduled event received'); + + try { + // Load configuration + const config = await loadScheduledConfig(octokit, owner, repo); - if (shouldRedact) { - redacted[key] = '[REDACTED]'; - } else if (typeof value === 'object' && value !== null) { - redacted[key] = redactSensitiveData(value); - } else { - redacted[key] = value; + if (!config) { + logger.info('No scheduled configuration found (.zai-scheduled.yml), skipping scheduled tasks'); + return { + success: true, + skipped: true, + reason: 'no configuration', + message: 'No .zai-scheduled.yml configuration found. Scheduled tasks are disabled.' + }; } + + logger.info(`Loaded scheduled configuration with ${config.tasks.length} task(s)`); + + // Get tasks to run for this schedule + const eventSchedule = context.payload?.schedule; + const tasks = getTasksToRun(config, eventSchedule); + + if (tasks.length === 0) { + logger.info('No tasks to run for this schedule'); + return { + success: true, + skipped: true, + reason: 'no tasks', + message: 'No enabled tasks match this schedule.' + }; + } + + logger.info(`Found ${tasks.length} task(s) to execute: ${tasks.map(t => t.id).join(', ')}`); + + // Execute each task sequentially + const results = []; + for (let i = 0; i < tasks.length; i++) { + const task = tasks[i]; + logger.info(`[${i + 1}/${tasks.length}] Executing task: ${task.id}`); + + const result = await executeScheduledTask({ + octokit, + apiKey, + model, + owner, + repo, + task, + config, + logger, + context, + }); + + results.push(result); + + // Add delay between tasks to avoid rate limiting (except for last task) + if (i < tasks.length - 1) { + const delayMs = 5000; + logger.info(`Waiting ${delayMs}ms before next task...`); + await new Promise(resolve => setTimeout(resolve, delayMs)); + } + } + + // Log summary + const successCount = results.filter(r => r.success).length; + const failureCount = results.filter(r => !r.success).length; + const prCount = results.filter(r => r.prCreated).length; + + logger.info(`Scheduled tasks completed: ${successCount} succeeded, ${failureCount} failed, ${prCount} PR(s) created`); + + return { + success: failureCount === 0, + results, + executed: tasks.length, + succeeded: successCount, + failed: failureCount, + prsCreated: prCount, + message: `Executed ${tasks.length} task(s): ${successCount} succeeded, ${failureCount} failed`, + }; + + } catch (error) { + logger.error({ error: error.message, stack: error.stack }, 'Scheduled event handler failed'); + return { + success: false, + error: error.message, + message: `Scheduled event failed: ${error.message}` + }; } - - return redacted; } /** - * Formats log data as a JSON-ish string for easier parsing - * @param {Object} fields - Log fields - * @returns {string} Formatted log string + * Execute a single scheduled task + * @param {Object} params - Execution parameters + * @param {Object} params.octokit - GitHub Octokit instance + * @param {string} params.apiKey - Z.ai API key + * @param {string} params.model - Z.ai model + * @param {string} params.owner - Repository owner + * @param {string} params.repo - Repository name + * @param {Object} params.task - Task configuration + * @param {Object} params.config - Full configuration + * @param {Object} params.logger - Logger instance + * @param {Object} params.context - GitHub actions context + * @returns {Promise} - Task execution result */ -function formatLogData(fields) { - const safeFields = redactSensitiveData(fields); - const entries = Object.entries(safeFields) - .filter(([_, value]) => value !== undefined && value !== null) - .map(([key, value]) => { - const formatted = typeof value === 'object' ? JSON.stringify(value) : String(value); - return `${key}=${formatted}`; +async function executeScheduledTask(params) { + const { octokit, apiKey, model, owner, repo, task, config, logger, context } = params; + + try { + // Get handler for this command + const handler = getScheduledHandler(task.command); + + if (!handler) { + logger.warn(`No handler found for command: ${task.command}`); + return { + success: false, + taskId: task.id, + command: task.command, + error: `Unknown scheduled command: ${task.command}`, + message: `No handler registered for command '${task.command}'`, + }; + } + + // Build execution context with utility functions + const executionContext = buildExecutionContext({ + octokit, + apiKey, + model, + owner, + repo, + task, + config, + logger, + context, }); - return entries.join(' '); + + // Execute handler + logger.info(`Executing handler for command: ${task.command}`); + const result = await handler(executionContext); + + return { + success: result.success, + taskId: task.id, + command: task.command, + changes: result.changes, + prCreated: result.prCreated, + prNumber: result.prNumber, + prUrl: result.prUrl, + message: result.message || `Task ${task.id} completed successfully`, + }; + + } catch (error) { + logger.error({ error: error.message, stack: error.stack, taskId: task.id }, 'Task execution failed'); + return { + success: false, + taskId: task.id, + command: task.command, + error: error.message, + message: `Task ${task.id} failed: ${error.message}`, + }; + } } /** - * Creates a logger instance with context fields - * @param {string} correlationId - Unique event correlation ID - * @param {Object} context - Context fields (eventName, prNumber, command, etc.) - * @returns {Object} Logger instance with info, warn, error methods + * Build execution context with utility functions for handlers + * @param {Object} params - Context parameters + * @returns {Object} - Execution context */ -function createLogger(correlationId, context = {}) { - const baseContext = { - correlationId, - ...context, +function buildExecutionContext(params) { + const { octokit, apiKey, model, owner, repo, task, config, logger, context } = params; + + const targetBranch = task.config?.branch || config.defaults?.branch || 'main'; + + return { + octokit, + apiKey, + model, + owner, + repo, + task, + config, + logger, + context, + targetBranch, + // Utility functions + fetchFromUrl: fetchFromUrl, + fetchFile: async (path, ref) => fetchFileContent(octokit, owner, repo, path, ref || targetBranch), + updateFile: async (path, content, ref, commitMessage) => + updateFileInRepo(octokit, owner, repo, path, content, ref || targetBranch, commitMessage), + createPullRequest: async (prParams) => createPR(octokit, owner, repo, prParams), + getFileSha: async (path, ref) => getFileSha(octokit, owner, repo, path, ref || targetBranch), }; +} - return { - /** - * Log informational message - * @param {Object} fields - Additional fields to log - * @param {string} message - Log message - */ - info(fields = {}, message) { - const logData = { - ...baseContext, - ...fields, - timestamp: new Date().toISOString(), - }; - core.info(formatLogData(logData) + (message ? ` ${message}` : '')); - }, +/** + * Get handler function for a scheduled command + * @param {string} command - Command name + * @returns {Function|null} - Handler function or null + */ +function getScheduledHandler(command) { + return SCHEDULED_HANDLERS[command] || null; +} - /** - * Log warning message - * @param {Object} fields - Additional fields to log - * @param {string} message - Log message - */ - warn(fields = {}, message) { - const logData = { - ...baseContext, - ...fields, - timestamp: new Date().toISOString(), - }; - core.warning(formatLogData(logData) + (message ? ` ${message}` : '')); - }, +/** + * Register a new scheduled command handler + * @param {string} command - Command name + * @param {Function} handler - Handler function + */ +function registerScheduledHandler(command, handler) { + SCHEDULED_HANDLERS[command] = handler; +} - /** - * Log error message - * @param {Object} fields - Additional fields to log - * @param {string} message - Log message - */ - error(fields = {}, message) { - const logData = { - ...baseContext, - ...fields, - timestamp: new Date().toISOString(), - }; - core.error(formatLogData(logData) + (message ? ` ${message}` : '')); - }, +/** + * Get all registered scheduled command handlers + * @returns {Object} - Map of command names to handlers + */ +function getAllScheduledHandlers() { + return { ...SCHEDULED_HANDLERS }; +} - /** - * Set failure status with user-safe message - * @param {Object} fields - Additional fields to log - * @param {string} message - User-safe message - */ - setFailed(fields = {}, message) { - const logData = { - ...baseContext, - ...fields, - timestamp: new Date().toISOString(), +// ============================================================================ +// TASK HANDLERS +// ============================================================================ + +/** + * Handle AGENTS.md update task + * Fetches command from Gist, executes it, and creates PR with updates + * @param {Object} context - Execution context + * @returns {Promise} - Result with changes information + */ +async function handleUpdateAgentsTask(context) { + const { + octokit, + apiKey, + model, + owner, + repo, + task, + config, + logger, + targetBranch, + fetchFromUrl, + fetchFile, + createPullRequest + } = context; + + const gistUrl = getGistUrl(task.config, config.defaults); + const prTitle = task.config?.pr_title || 'chore: update AGENTS.md files'; + const prBody = task.config?.pr_body || 'Automated weekly update of AGENTS.md files from gist'; + const commitMessage = task.config?.commit_message || 'docs: update AGENTS.md from scheduled task'; + + if (!gistUrl) { + logger.error('No gist_url configured for update-agents task. Set ZAI_AGENTS_GIST_URL environment variable or configure in .zai-scheduled.yml'); + return { + success: false, + error: 'Missing gist_url configuration', + message: 'update-agents task requires gist_url. Set ZAI_AGENTS_GIST_URL environment variable or configure in .zai-scheduled.yml' + }; + } + + logger.info(`Fetching content from gist: ${gistUrl}`); + + try { + // Step 1: Fetch content from gist + let gistContent; + try { + gistContent = await fetchFromUrl(gistUrl); + } catch (error) { + logger.error({ error: error.message, url: gistUrl }, 'Failed to fetch from gist URL'); + return { + success: false, + error: `Failed to fetch from gist: ${error.message}`, + message: `Could not fetch content from ${gistUrl}` }; - core.setFailed(formatLogData(logData) + (message ? ` ${message}` : '')); - }, - }; + } + + if (!gistContent || gistContent.trim() === '') { + logger.error('Gist returned empty content'); + return { + success: false, + error: 'Empty response from gist URL', + message: 'Gist URL returned no content' + }; + } + + logger.info(`Fetched ${gistContent.length} characters from gist`); + + // Step 2: Execute the command from Gist + // The command MUST return structured JSON with file paths and contents + logger.info('Executing command in auto-discovery mode - expecting structured file map'); + let fileUpdates = await executeCommandAndGetFileUpdates({ + commandText: gistContent, + octokit, + apiKey, + model, + owner, + repo, + targetBranch, + logger, + }); + + if (!fileUpdates || fileUpdates.length === 0) { + logger.error('Command returned no file updates. The command must return valid JSON with files array.'); + return { + success: false, + error: 'No file updates returned from command', + message: 'The command must return JSON with a "files" array containing AGENTS.md file updates' + }; + } + + logger.info(`Auto-discovery found ${fileUpdates.length} file(s) to update`); + + // Step 3: Check if there are any actual changes + const updatedFiles = fileUpdates.filter(f => f.changed).map(f => f.file); + + if (updatedFiles.length === 0) { + logger.info('No files need updating'); + return { + success: true, + changes: fileUpdates, + prCreated: false, + message: 'No updates needed - all files are current', + }; + } + + logger.info(`Creating PR with updates for ${updatedFiles.length} file(s): ${updatedFiles.join(', ')}`); + + // Create PR with all changes + const filesToUpdate = fileUpdates + .filter(f => f.changed) + .map(f => ({ + path: f.file, + content: f.newContent, + })); + + const prResult = await createPullRequest({ + title: prTitle, + body: buildPrBody(prBody, updatedFiles, task.id), + base: targetBranch, + files: filesToUpdate, + commitMessage, + }); + + return { + success: true, + changes: fileUpdates, + prCreated: true, + prNumber: prResult?.number, + prUrl: prResult?.html_url, + message: `Created PR #${prResult?.number} with ${updatedFiles.length} file(s) updated`, + }; + + } catch (error) { + logger.error({ error: error.message, stack: error.stack }, 'AGENTS.md update task failed'); + return { + success: false, + error: error.message, + message: `Update task failed: ${error.message}`, + }; + } } /** - * Maps internal errors to user-safe messages - * @param {string} category - Error category from ERROR_CATEGORIES - * @param {Error} [internalError] - Original error (not exposed to users) - * @returns {string} User-safe message + * Execute a command and get structured file updates (for auto-discovery mode) + * The command should return a JSON structure with file paths and their contents + * @param {Object} params - Execution parameters + * @param {string} params.commandText - The command text to execute + * @param {Object} params.octokit - GitHub Octokit instance + * @param {string} params.apiKey - Z.ai API key + * @param {string} params.model - Z.ai model + * @param {string} params.owner - Repository owner + * @param {string} params.repo - Repository name + * @param {string} params.targetBranch - Target branch + * @param {Object} params.logger - Logger instance + * @returns {Promise>} - Array of {file, oldContent, newContent, changed, isNew} objects */ -function getUserMessage(category, internalError = null) { - const message = USER_MESSAGES[category] || USER_MESSAGES[ERROR_CATEGORIES.INTERNAL]; +async function executeCommandAndGetFileUpdates(params) { + const { commandText, octokit, apiKey, model, owner, repo, targetBranch, logger } = params; - // Log the internal error details separately for debugging - if (internalError) { - const internalLogger = createLogger(generateCorrelationId()); - internalLogger.error( - { category, errorType: internalError.name }, - `Internal error: ${internalError.message}` - ); + if (!commandText || commandText.trim() === '') { + logger.warn('Command text is empty, cannot execute'); + return []; } - return message; + try { + // Check if it's a valid /zai command + const parseResult = parseCommand(commandText); + + // Build prompt for auto-discovery mode + // The command should scan the repo and return a JSON structure with all AGENTS.md files + const prompt = buildAutoDiscoveryPrompt(commandText, owner, repo, targetBranch); + + // Call Z.ai API to execute the command + const response = await callZaiApiWithRetry(apiKey, model, prompt, logger); + + if (!response || !response.content) { + logger.warn('Z.ai API returned empty content'); + return []; + } + + // Parse the response to extract file updates + // Expected format: JSON array of {file: string, content: string} objects + // Or a special format that we can parse + const fileUpdates = parseFileUpdatesFromResponse(response.content, octokit, owner, repo, targetBranch, fetchFileContent, logger); + + return fileUpdates; + + } catch (error) { + logger.error({ error: error.message }, 'Failed to execute command for file discovery'); + throw error; + } } /** - * Categorizes an error based on its characteristics - * @param {Error} error - Error to categorize - * @returns {string} Error category from ERROR_CATEGORIES + * Build prompt for auto-discovery mode + * @param {string} commandText - The command text + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @param {string} branch - Target branch + * @returns {string} - Formatted prompt */ -function categorizeError(error) { - const message = error.message?.toLowerCase() || ''; +function buildAutoDiscoveryPrompt(commandText, owner, repo, branch) { + return `Repository: ${owner}/${repo} +Branch: ${branch} - // Auth errors - if ( - message.includes('auth') || - message.includes('unauthorized') || - message.includes('forbidden') || - message.includes('invalid token') || - message.includes('401') || - message.includes('403') - ) { - return ERROR_CATEGORIES.AUTH; - } +You are an AI assistant tasked with generating and updating AGENTS.md files. - // Rate limit errors - if ( - message.includes('rate limit') || - message.includes('too many requests') || - message.includes('429') - ) { - return ERROR_CATEGORIES.RATE_LIMIT; - } - - // Timeout errors - if ( - message.includes('timeout') || - message.includes('etimedout') || - message.includes('timed out') - ) { - return ERROR_CATEGORIES.TIMEOUT; - } +Execute the following command to scan the repository and generate/update AGENTS.md files: - // Validation errors - if ( - message.includes('validation') || - message.includes('invalid input') || - message.includes('required') || - message.includes('schema') - ) { - return ERROR_CATEGORIES.VALIDATION; - } +${commandText} - // Provider errors (external API failures) - if ( - message.includes('api') || - message.includes('provider') || - message.includes('external') || - message.includes('5') - ) { - return ERROR_CATEGORIES.PROVIDER; - } +IMPORTANT: You must return the results in a specific JSON format so the bot can process your changes. - // Default to internal - return ERROR_CATEGORIES.INTERNAL; +Return a JSON object with the following structure: +{ + "summary": "Brief description of changes", + "files": [ + { + "path": "AGENTS.md", + "content": "... full file content ...", + "action": "created|updated|unchanged" + }, + { + "path": "src/lib/AGENTS.md", + "content": "... full file content ...", + "action": "created|updated|unchanged" + } + ] } -module.exports = { - ERROR_CATEGORIES, - REDACT_FIELDS, - STANDARD_FIELDS, - generateCorrelationId, - createLogger, - getUserMessage, - categorizeError, - redactSensitiveData, -}; - - -/***/ }), +Rules: +1. Scan the ENTIRE repository structure +2. Identify ALL existing AGENTS.md files +3. Determine if each needs to be updated +4. Create new AGENTS.md files where needed (root and important subdirectories) +5. For each file that needs changes, include the full new content +6. Only include files that have actual changes (action: "created" or "updated") +7. Return ONLY valid JSON, no other text, explanations, or markdown +8. The content must be the exact text that should be written to each file -/***/ 1669: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +Begin your response with the JSON object immediately, no preamble.`; +} /** - * Shared PR context fetch module with reusable async helpers. - * Provides functions for fetching PR files, file content at ref, and base/head refs. - * All functions return structured responses with user-safe error fallbacks. + * Parse file updates from Z.ai API response + * @param {string} responseContent - Response content from Z.ai + * @param {Object} octokit - GitHub Octokit instance + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @param {string} targetBranch - Target branch + * @param {Function} fetchFileContent - Function to fetch file content + * @param {Object} logger - Logger instance + * @returns {Promise>} - Array of file update objects */ - -const context = __nccwpck_require__(9990); -const logging = __nccwpck_require__(2120); -const { extractEnclosingBlock } = __nccwpck_require__(7437); -const { fetchAllChangedFiles } = __nccwpck_require__(4723); - -// Default size limits -const DEFAULT_MAX_FILE_SIZE = 100000; -const DEFAULT_MAX_FILE_LINES = 10000; -const DEFAULT_PER_PAGE = 100; -const DEFAULT_SLIDING_WINDOW = 40; -const DEFAULT_MAX_WINDOWS = 4; +async function parseFileUpdatesFromResponse(responseContent, octokit, owner, repo, targetBranch, fetchFileContent, logger) { + const fileUpdates = []; + + try { + // Try to parse as JSON + let parsed; + try { + // Remove any markdown code blocks if present + const cleaned = responseContent.trim(); + const jsonMatch = cleaned.match(/\{[\s\S]*\}/); + if (jsonMatch) { + parsed = JSON.parse(jsonMatch[0]); + } else { + parsed = JSON.parse(cleaned); + } + } catch (e) { + logger.warn('Could not parse response as JSON, attempting to extract file information'); + // Fallback: try to extract file paths and content from the response + return await extractFilesFromText(responseContent, octokit, owner, repo, targetBranch, fetchFileContent, logger); + } + + if (parsed && parsed.files && Array.isArray(parsed.files)) { + // Process each file from the structured response + for (const fileInfo of parsed.files) { + const filePath = fileInfo.path || fileInfo.file; + const newContent = fileInfo.content || fileInfo.body; + const action = (fileInfo.action || '').toLowerCase(); + + if (!filePath || !newContent) { + logger.warn(`Skipping invalid file entry: ${JSON.stringify(fileInfo)}`); + continue; + } + + try { + const currentContent = await fetchFileContent(octokit, owner, repo, filePath, targetBranch); + const changed = action === 'created' || action === 'updated' || currentContent === null || currentContent !== newContent; + + if (changed) { + fileUpdates.push({ + file: filePath, + oldContent: currentContent, + newContent, + changed: true, + isNew: currentContent === null, + }); + } else { + fileUpdates.push({ + file: filePath, + changed: false, + }); + } + } catch (error) { + logger.error({ error: error.message, file: filePath }, 'Failed to check file for auto-discovery'); + // Assume it needs to be created/updated + fileUpdates.push({ + file: filePath, + oldContent: null, + newContent, + changed: true, + isNew: true, + }); + } + } + + logger.info(`Parsed ${fileUpdates.length} file updates from structured response`); + } else { + logger.warn('Response does not contain expected files array, attempting fallback'); + // Fallback: try to extract from text + return await extractFilesFromText(responseContent, octokit, owner, repo, targetBranch, fetchFileContent, logger); + } + + } catch (error) { + logger.error({ error: error.message }, 'Failed to parse file updates from response'); + // Return empty array - caller will handle fallback + } + + return fileUpdates; +} /** - * User-safe fallback messages for common error types + * Fallback: Extract file information from plain text response + * @param {string} text - Plain text response + * @param {Object} octokit - GitHub Octokit instance + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @param {string} targetBranch - Target branch + * @param {Function} fetchFileContent - Function to fetch file content + * @param {Object} logger - Logger instance + * @returns {Promise>} - Array of file update objects */ -const FALLBACK_MESSAGES = { - NOT_FOUND: 'Content not found', - RATE_LIMITED: 'GitHub API rate limit exceeded. Please try again later.', - PERMISSION_DENIED: 'Permission denied to access this resource.', - UNAVAILABLE: 'Resource temporarily unavailable', - UNKNOWN: 'Failed to retrieve content' -}; +async function extractFilesFromText(text, octokit, owner, repo, targetBranch, fetchFileContent, logger) { + logger.warn('Attempting to extract file updates from plain text response'); + + // This is a fallback for when the response isn't structured JSON + // We'll just return the AGENTS.md file with the raw content + // In a real implementation, the command should return proper JSON + + const filePath = 'AGENTS.md'; + const newContent = text; + + try { + const currentContent = await fetchFileContent(octokit, owner, repo, filePath, targetBranch); + const changed = currentContent === null || currentContent !== newContent; + + return [{ + file: filePath, + oldContent: currentContent, + newContent, + changed, + isNew: currentContent === null, + }]; + } catch (error) { + return [{ + file: filePath, + oldContent: null, + newContent, + changed: true, + isNew: true, + }]; + } +} /** - * Checks if error is a rate limit error (429) - * @param {Error} error - Error to check - * @returns {boolean} True if rate limit error + * Build PR body with task information + * @param {string} baseBody - Base PR body + * @param {Array} updatedFiles - List of updated files + * @param {string} taskId - Task ID + * @returns {string} - Full PR body */ -function isRateLimitError(error) { - const message = (error?.message || '').toLowerCase(); - return error?.status === 429 || message.includes('rate limit') || message.includes('secondary rate limit'); +function buildPrBody(baseBody, updatedFiles, taskId) { + const timestamp = new Date().toISOString(); + const filesList = updatedFiles.map(f => `- ${f}`).join('\n'); + + return `${baseBody}\n\n` + + `**Task:** ${taskId}\n` + + `**Timestamp:** ${timestamp}\n` + + `**Files Updated:**\n${filesList}\n\n` + + `---\n` + + `*Generated automatically by zai-code-bot scheduled task*`; } +// ============================================================================ +// COMMAND EXECUTION +// ============================================================================ + /** - * Maps error to user-safe fallback message - * @param {Error} error - Error to map - * @param {string} resource - Resource being fetched (for context) - * @returns {{fallback: string, category: string}} Fallback message and error category + * Execute a command text to generate content + * Sends the command to Z.ai API and returns the generated content + * @param {Object} params - Execution parameters + * @param {string} params.commandText - The command text to execute + * @param {Object} params.octokit - GitHub Octokit instance + * @param {string} params.apiKey - Z.ai API key + * @param {string} params.model - Z.ai model + * @param {string} params.owner - Repository owner + * @param {string} params.repo - Repository name + * @param {string} params.targetBranch - Target branch + * @param {Object} params.logger - Logger instance + * @returns {Promise} - Generated content */ -function mapErrorToFallback(error, resource = 'content') { - if (error?.status === 404) { - return { fallback: `${FALLBACK_MESSAGES.NOT_FOUND}: ${resource}`, category: 'NOT_FOUND' }; - } - if (isRateLimitError(error)) { - return { fallback: FALLBACK_MESSAGES.RATE_LIMITED, category: 'RATE_LIMIT' }; - } - if (error?.status === 403) { - return { fallback: FALLBACK_MESSAGES.PERMISSION_DENIED, category: 'PERMISSION' }; +async function executeCommandAndGetContent(params) { + const { commandText, apiKey, model, owner, repo, targetBranch, logger } = params; + + // If command text is empty, return empty + if (!commandText || commandText.trim() === '') { + logger.warn('Command text is empty, cannot execute'); + return commandText; } - if (error?.status >= 500) { - return { fallback: FALLBACK_MESSAGES.UNAVAILABLE, category: 'PROVIDER' }; + + try { + // Check if it's a valid /zai command + const parseResult = parseCommand(commandText); + + if (isValid(parseResult)) { + logger.info(`Executing /zai command: ${parseResult.command} with args: ${parseResult.args.join(' ')}`); + + // For /zai commands, we need to execute them through the dispatch system + // But since we're not in a PR context, we'll use the Z.ai API directly + const prompt = buildCommandPrompt(commandText, owner, repo, targetBranch); + + // Call Z.ai API to execute the command + const response = await callZaiApiWithRetry(apiKey, model, prompt, logger); + + if (response && response.content) { + return response.content; + } + + logger.warn('Z.ai API returned empty content'); + return commandText; + } else { + // Not a /zai command - try to execute as a direct prompt + logger.info('Command is not a /zai command, sending as direct prompt to Z.ai API'); + + // Build a prompt that tells Z.ai to generate AGENTS.md content + const prompt = buildAgentsGenerationPrompt(commandText, owner, repo); + + // Call Z.ai API + const response = await callZaiApiWithRetry(apiKey, model, prompt, logger); + + if (response && response.content) { + return response.content; + } + + logger.warn('Z.ai API returned empty content for direct prompt'); + return commandText; + } + } catch (error) { + logger.error({ error: error.message }, 'Failed to execute command for content generation'); + throw error; } - return { fallback: FALLBACK_MESSAGES.UNKNOWN, category: 'UNKNOWN' }; -} - -function clamp(value, min, max) { - return Math.max(min, Math.min(max, value)); } -function parsePatchLineRanges(patch, side = 'new') { - if (!patch || typeof patch !== 'string') { - return []; - } +/** + * Build prompt for executing a /zai command + * @param {string} commandText - The command text + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @param {string} branch - Target branch + * @returns {string} - Formatted prompt + */ +function buildCommandPrompt(commandText, owner, repo, branch) { + return `Repository: ${owner}/${repo} +Branch: ${branch} - const ranges = []; - const regex = /^@@\s+-(\d+)(?:,(\d+))?\s+\+(\d+)(?:,(\d+))?\s+@@/gm; - let match = regex.exec(patch); - while (match !== null) { - const start = side === 'old' ? Number(match[1]) : Number(match[3]); - const countRaw = side === 'old' ? match[2] : match[4]; - const count = countRaw ? Number(countRaw) : 1; - const safeCount = Number.isFinite(count) ? Math.max(1, count) : 1; - const end = start + safeCount - 1; - if (Number.isFinite(start) && start >= 1) { - ranges.push({ start, end }); - } - match = regex.exec(patch); - } +Execute the following command: +${commandText} - return ranges; +Return only the generated content (AGENTS.md file content), without any explanation or formatting.`; } -function buildSlidingWindowsContent(content, changedRanges, options = {}) { - const windowSize = options.windowSize || DEFAULT_SLIDING_WINDOW; - const maxWindows = options.maxWindows || DEFAULT_MAX_WINDOWS; - const lines = String(content).split('\n'); - const maxLines = lines.length; - - const windows = (changedRanges || []) - .map((range) => { - const start = clamp(range.start - windowSize, 1, maxLines); - const end = clamp(range.end + windowSize, 1, maxLines); - return { start, end }; - }) - .sort((a, b) => a.start - b.start); - - const merged = []; - for (const window of windows) { - const last = merged[merged.length - 1]; - if (!last || window.start > last.end + 1) { - merged.push({ ...window }); - continue; - } - last.end = Math.max(last.end, window.end); - } +/** + * Build prompt for generating AGENTS.md from a direct prompt + * @param {string} promptText - The prompt/command text + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @returns {string} - Formatted prompt + */ +function buildAgentsGenerationPrompt(promptText, owner, repo) { + return `You are an AI assistant helping to generate AGENTS.md files for a repository. - const selected = merged.slice(0, maxWindows); - if (selected.length === 1) { - const only = selected[0]; - return { - content: lines.slice(only.start - 1, only.end).join('\n'), - scopeStrategy: 'sliding_window', - scopeStartLine: only.start, - scopeEndLine: only.end, - }; - } +Repository: ${owner}/${repo} - const chunks = selected.map((window, idx) => { - const snippet = lines.slice(window.start - 1, window.end).join('\n'); - return `# Window ${idx + 1} (lines ${window.start}-${window.end})\n${snippet}`; - }); +Task: Generate comprehensive AGENTS.md content based on the following instructions: - if (chunks.length === 0) { - const fallbackEnd = clamp(windowSize * 2, 1, maxLines); - return { - content: lines.slice(0, fallbackEnd).join('\n'), - scopeStrategy: 'top_window', - scopeStartLine: 1, - scopeEndLine: fallbackEnd, - }; - } +${promptText} - return { - content: chunks.join('\n\n---\n\n'), - scopeStrategy: 'sliding_window', - scopeStartLine: selected.length === 1 ? selected[0].start : null, - scopeEndLine: selected.length === 1 ? selected[0].end : null, - }; +Return ONLY the generated AGENTS.md file content. Do not include any explanation, formatting notes, or markdown headers. Start directly with the content that should be written to the AGENTS.md file.`; } -function scopeLargeFileContent(content, options = {}) { - const lines = String(content).split('\n'); - const lineCount = lines.length; - const maxFileLines = options.maxFileLines || DEFAULT_MAX_FILE_LINES; - - if (lineCount <= maxFileLines) { - return { - scoped: false, - scopeStrategy: 'full_file', - content, - lineCount, - scopeStartLine: 1, - scopeEndLine: lineCount, - }; - } - - const anchorLine = options.anchorLine; - const preferEnclosingBlock = Boolean(options.preferEnclosingBlock); - let changedRanges = options.changedRanges; - if ((!changedRanges || changedRanges.length === 0) && options.patch) { - changedRanges = parsePatchLineRanges(options.patch, options.patchSide || 'new'); - } - - if (preferEnclosingBlock && Number.isFinite(anchorLine) && anchorLine >= 1) { - const block = extractEnclosingBlock(content, anchorLine, { - windowSize: options.windowSize || DEFAULT_SLIDING_WINDOW, - }); - - return { - scoped: true, - scopeStrategy: 'enclosing_block', - content: block.target.join('\n'), - lineCount, - scopeStartLine: block?.bounds?.start || null, - scopeEndLine: block?.bounds?.end || null, - note: block.note, - }; +/** + * Call Z.ai API with retry logic + * @param {string} apiKey - Z.ai API key + * @param {string} model - Model to use + * @param {string} prompt - Prompt to send + * @param {Object} logger - Logger instance + * @param {number} retries - Number of retries (default: 3) + * @returns {Promise} - API response + */ +async function callZaiApiWithRetry(apiKey, model, prompt, logger, retries = 3) { + const https = __nccwpck_require__(4708); + const ZAI_API_URL = 'https://api.z.ai/api/coding/paas/v4/chat/completions'; + + for (let attempt = 1; attempt <= retries; attempt++) { + try { + const body = JSON.stringify({ + model, + messages: [ + { + role: 'user', + content: prompt, + }, + ], + }); + + const parsedUrl = new URL(ZAI_API_URL); + const options = { + hostname: parsedUrl.hostname, + path: parsedUrl.pathname, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${apiKey}`, + 'Content-Length': Buffer.byteLength(body), + }, + }; + + const response = await new Promise((resolve, reject) => { + const req = https.request(options, res => { + let data = ''; + res.on('data', chunk => (data += chunk)); + res.on('end', () => { + if (res.statusCode >= 200 && res.statusCode < 300) { + try { + const parsed = JSON.parse(data); + resolve(parsed); + } catch (e) { + reject(new Error(`Failed to parse response: ${e.message}`)); + } + } else { + reject(new Error(`API error ${res.statusCode}: ${data.substring(0, 200)}`)); + } + }); + }); + + req.on('error', reject); + req.write(body); + req.end(); + }); + + return { + content: response.choices?.[0]?.message?.content || '', + fullResponse: response, + }; + } catch (error) { + logger.warn({ error: error.message, attempt }, `Z.ai API call failed, attempt ${attempt} of ${retries}`); + + if (attempt === retries) { + throw error; + } + + // Exponential backoff + const delay = Math.pow(2, attempt) * 1000; + await new Promise(resolve => setTimeout(resolve, delay)); + } } +} - if (Array.isArray(changedRanges) && changedRanges.length > 0) { - const scoped = buildSlidingWindowsContent(content, changedRanges, { - windowSize: options.windowSize, - maxWindows: options.maxWindows, - }); - return { - scoped: true, - lineCount, - ...scoped, - }; - } +// ============================================================================ +// UTILITY FUNCTIONS +// ============================================================================ - const topFallback = buildSlidingWindowsContent(content, [], { - windowSize: options.windowSize, - maxWindows: 1, +/** + * Fetch content from a URL + * @param {string} url - URL to fetch + * @returns {Promise} - Content as string + */ +async function fetchFromUrl(url) { + return new Promise((resolve, reject) => { + try { + const parsedUrl = new URL(url); + const options = { + hostname: parsedUrl.hostname, + path: parsedUrl.pathname + parsedUrl.search, + method: 'GET', + headers: { + 'User-Agent': 'zai-code-bot', + 'Accept': 'text/plain,text/html,application/json', + }, + }; + + logger.info(`Fetching URL: ${url}`); + + const req = https.request(options, res => { + let data = ''; + res.on('data', chunk => (data += chunk)); + res.on('end', () => { + if (res.statusCode >= 200 && res.statusCode < 300) { + logger.info(`Fetched ${data.length} bytes from URL`); + resolve(data); + } else { + reject(new Error(`HTTP ${res.statusCode}: ${data.substring(0, 200)}`)); + } + }); + }); + + req.on('error', reject); + req.setTimeout(30000, () => { + req.destroy(new Error('Request timeout after 30s')); + }); + req.end(); + } catch (error) { + reject(new Error(`Invalid URL: ${error.message}`)); + } }); - - return { - scoped: true, - lineCount, - ...topFallback, - }; } /** - * Fetches the list of files changed in a PR. + * Fetch file content from repository * @param {Object} octokit - GitHub Octokit instance * @param {string} owner - Repository owner * @param {string} repo - Repository name - * @param {number} pullNumber - PR number - * @param {Object} [options={}] - Optional configuration - * @param {number} [options.perPage=100] - Files per page - * @returns {Promise<{success: boolean, data?: Array, error?: string, fallback?: string}>} + * @param {string} path - File path + * @param {string} ref - Branch/ref + * @returns {Promise} - File content or null if not found */ -async function fetchPrFiles(octokit, owner, repo, pullNumber, options = {}) { - const perPage = options.perPage || DEFAULT_PER_PAGE; - +async function fetchFileContent(octokit, owner, repo, path, ref) { try { - const result = await fetchAllChangedFiles(octokit, owner, repo, pullNumber, { perPage }); - const files = Array.isArray(result.files) ? result.files : []; - return { success: true, data: files }; + const { data } = await octokit.rest.repos.getContent({ + owner, + repo, + path, + ref, + }); + + return Buffer.from(data.content, 'base64').toString('utf8'); } catch (error) { - const { fallback } = mapErrorToFallback(error, `files for PR #${pullNumber}`); - - // Log internal error details - const internalLogger = logging.createLogger(logging.generateCorrelationId()); - internalLogger.warn( - { status: error?.status, operation: 'pulls.listFiles', pullNumber }, - `Failed to fetch PR files: ${error.message}` - ); - - return { success: false, error: error.message, fallback }; + if (error.status === 404) { + return null; + } + throw error; } } /** - * Fetches file content at a specific ref (branch, tag, or SHA). + * Get the SHA of a file in repository * @param {Object} octokit - GitHub Octokit instance * @param {string} owner - Repository owner * @param {string} repo - Repository name - * @param {string} path - File path in repository - * @param {string} ref - Git ref (branch, tag, or SHA) - * @param {Object} [options={}] - Optional configuration - * @param {number} [options.maxFileSize=100000] - Maximum characters to return - * @returns {Promise<{success: boolean, data?: string, error?: string, fallback?: string, truncated?: boolean}>} + * @param {string} path - File path + * @param {string} ref - Branch/ref + * @returns {Promise} - File SHA or null if not found */ -async function fetchFileAtRef(octokit, owner, repo, path, ref, options = {}) { - const maxFileSize = options.maxFileSize || DEFAULT_MAX_FILE_SIZE; - const maxFileLines = options.maxFileLines || DEFAULT_MAX_FILE_LINES; - - if (!path || !ref) { - return { - success: false, - error: 'path and ref are required', - fallback: FALLBACK_MESSAGES.VALIDATION || 'Invalid request parameters' - }; - } - +async function getFileSha(octokit, owner, repo, path, ref) { try { const { data } = await octokit.rest.repos.getContent({ owner, @@ -36869,627 +37559,9963 @@ async function fetchFileAtRef(octokit, owner, repo, path, ref, options = {}) { path, ref, }); - - // Handle directory or symlink responses - if (!data || Array.isArray(data)) { - return { - success: false, - error: `Path ${path} is a directory or not accessible`, - fallback: `Content not available for ${path}` - }; + return data.sha; + } catch (error) { + if (error.status === 404) { + return null; } + throw error; + } +} - // Check for binary files or missing content - if (!data.content) { - return { - success: false, - error: 'File content not available (possibly binary)', - fallback: `Binary or non-text content for ${path}` - }; +/** + * Create or update file in repository + * @param {Object} octokit - GitHub Octokit instance + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @param {string} path - File path + * @param {string} content - New content + * @param {string} ref - Branch/ref + * @param {string} commitMessage - Commit message + * @returns {Promise} - GitHub API response + */ +async function updateFileInRepo(octokit, owner, repo, path, content, ref, commitMessage) { + const existingSha = await getFileSha(octokit, owner, repo, path, ref); + + const response = await octokit.rest.repos.createOrUpdateFileContents({ + owner, + repo, + path, + message: commitMessage, + content: Buffer.from(content).toString('base64'), + sha: existingSha || undefined, + branch: ref, + }); + + return response.data; +} + +/** + * Create a pull request with file changes + * @param {Object} octokit - GitHub Octokit instance + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @param {Object} params - PR parameters + * @param {string} params.title - PR title + * @param {string} params.body - PR body + * @param {string} params.base - Base branch + * @param {Array} params.files - Files to update (each with path and content) + * @param {string} params.commitMessage - Commit message + * @returns {Promise} - PR creation result + */ +async function createPR(octokit, owner, repo, { title, body, base, files, commitMessage }) { + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + const hours = String(now.getHours()).padStart(2, '0'); + const minutes = String(now.getMinutes()).padStart(2, '0'); + const branchName = `zai-scheduled/${year}.${month}.${day}_${hours}.${minutes}`; + + logger.info(`Creating branch ${branchName} from ${base}`); + + // Get the base branch reference + const { data: baseRef } = await octokit.rest.git.getRef({ + owner, + repo, + ref: `heads/${base}`, + }); + + // Create a new branch + try { + await octokit.rest.git.createRef({ + owner, + repo, + ref: `refs/heads/${branchName}`, + sha: baseRef.object.sha, + }); + logger.info(`Created branch ${branchName}`); + } catch (error) { + if (error.status !== 422) { // Already exists + throw error; + } + logger.info(`Branch ${branchName} already exists, reusing`); + } + + // Apply all file changes to the new branch + let commitSha = baseRef.object.sha; + + for (const file of files) { + const { path, content } = file; + + logger.info(`Updating file ${path} in branch ${branchName}`); + + try { + const result = await updateFileInRepo( + octokit, + owner, + repo, + path, + content, + branchName, + commitMessage + ); + commitSha = result.commit.sha; + logger.info(`Updated ${path} successfully`); + } catch (error) { + logger.error({ error: error.message, file: path }, 'Failed to update file in branch'); + throw error; } + } + + // Create the pull request + logger.info(`Creating pull request from ${branchName} to ${base}`); + + const prResponse = await octokit.rest.pulls.create({ + owner, + repo, + title, + body, + head: branchName, + base, + }); + + logger.info(`Created PR #${prResponse.data.number}`); + + return { + ...prResponse.data, + commitSha, + }; +} - // Decode base64 content - const decoded = Buffer.from(data.content, 'base64').toString('utf8'); +// ============================================================================ +// EXPORTS +// ============================================================================ - const scopedResult = scopeLargeFileContent(decoded, { - maxFileLines, - anchorLine: options.anchorLine, - preferEnclosingBlock: options.preferEnclosingBlock, - changedRanges: options.changedRanges, - patch: options.patch, - patchSide: options.patchSide, - windowSize: options.windowSize, - maxWindows: options.maxWindows, - }); +module.exports = { + handleScheduledEvent, + executeScheduledTask, + getScheduledHandler, + registerScheduledHandler, + getAllScheduledHandlers, + handleUpdateAgentsTask, + // Export utility functions for testing and manual command integration + fetchFromUrl, + fetchFileContent, + getFileSha, + updateFileInRepo, + createPR, + buildExecutionContext, +}; - // Apply truncation if needed - const truncated = context.truncateContext(scopedResult.content, maxFileSize); - return { - success: true, - data: truncated.content, - truncated: truncated.truncated, - omitted: truncated.omitted, - scoped: scopedResult.scoped, - scopeStrategy: scopedResult.scopeStrategy, - lineCount: scopedResult.lineCount, - scopeStartLine: scopedResult.scopeStartLine, - scopeEndLine: scopedResult.scopeEndLine, - scopeNote: scopedResult.note, - }; - } catch (error) { - const { fallback } = mapErrorToFallback(error, path); +/***/ }), + +/***/ 2120: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +/** + * Structured logging and error taxonomy for Z.ai Code Review + * Provides correlation IDs, standardized log fields, and user-safe error messages + */ + +const core = __nccwpck_require__(7484); + +/** + * Error categories for classification + * @readonly + * @enum {string} + */ +const ERROR_CATEGORIES = { + /** Authentication/authorization failures */ + AUTH: 'AUTH', + /** Input validation failures */ + VALIDATION: 'VALIDATION', + /** External provider/API failures */ + PROVIDER: 'PROVIDER', + /** Rate limiting errors */ + RATE_LIMIT: 'RATE_LIMIT', + /** Timeout errors */ + TIMEOUT: 'TIMEOUT', + /** Internal/unexpected errors */ + INTERNAL: 'INTERNAL', +}; + +/** + * Fields that should be redacted from logs to prevent secret leakage + */ +const REDACT_FIELDS = [ + 'ZAI_API_KEY', + 'GITHUB_TOKEN', + 'authorization', + 'Authorization', + 'Bearer', + 'token', + 'api_key', + 'apikey', + 'secret', + 'password', + 'credential', +]; + +/** + * Mapping from internal error categories to user-safe messages + * These messages are designed to be displayed to end users + * without exposing internal implementation details + */ +const USER_MESSAGES = { + [ERROR_CATEGORIES.AUTH]: 'Authentication failed. Please verify your API credentials.', + [ERROR_CATEGORIES.VALIDATION]: 'Invalid input provided. Please check your action configuration.', + [ERROR_CATEGORIES.PROVIDER]: 'External service request failed. Please try again later.', + [ERROR_CATEGORIES.RATE_LIMIT]: 'Rate limit exceeded. Please wait before retrying.', + [ERROR_CATEGORIES.TIMEOUT]: 'Request timed out. Please try again.', + [ERROR_CATEGORIES.INTERNAL]: 'An unexpected error occurred. Please try again.', +}; + +/** + * Fields that should always be included in log context + */ +const STANDARD_FIELDS = [ + 'correlationId', + 'eventName', + 'prNumber', + 'command', + 'duration', +]; + +/** + * Generates a unique correlation ID for event tracking + * Format: timestamp-randomhex (e.g., "1700000000-a1b2c3d4") + * @returns {string} Unique correlation ID + */ +function generateCorrelationId() { + const timestamp = Date.now().toString(36); + const random = Math.random().toString(36).substring(2, 10); + return `${timestamp}-${random}`; +} + +/** + * Redacts sensitive fields from an object for safe logging + * @param {Object} data - Data object to redact + * @returns {Object} Data with sensitive fields redacted + */ +function redactSensitiveData(data) { + if (data === null || data === undefined) { + return data; + } + + if (typeof data !== 'object') { + return data; + } + + const redacted = Array.isArray(data) ? [] : {}; + + for (const [key, value] of Object.entries(data)) { + const shouldRedact = REDACT_FIELDS.some( + field => key.toLowerCase().includes(field.toLowerCase()) + ); + + if (shouldRedact) { + redacted[key] = '[REDACTED]'; + } else if (typeof value === 'object' && value !== null) { + redacted[key] = redactSensitiveData(value); + } else { + redacted[key] = value; + } + } + + return redacted; +} + +/** + * Formats log data as a JSON-ish string for easier parsing + * @param {Object} fields - Log fields + * @returns {string} Formatted log string + */ +function formatLogData(fields) { + const safeFields = redactSensitiveData(fields); + const entries = Object.entries(safeFields) + .filter(([_, value]) => value !== undefined && value !== null) + .map(([key, value]) => { + const formatted = typeof value === 'object' ? JSON.stringify(value) : String(value); + return `${key}=${formatted}`; + }); + return entries.join(' '); +} + +/** + * Creates a logger instance with context fields + * @param {string} correlationId - Unique event correlation ID + * @param {Object} context - Context fields (eventName, prNumber, command, etc.) + * @returns {Object} Logger instance with info, warn, error methods + */ +function createLogger(correlationId, context = {}) { + const baseContext = { + correlationId, + ...context, + }; + + return { + /** + * Log informational message + * @param {Object} fields - Additional fields to log + * @param {string} message - Log message + */ + info(fields = {}, message) { + const logData = { + ...baseContext, + ...fields, + timestamp: new Date().toISOString(), + }; + core.info(formatLogData(logData) + (message ? ` ${message}` : '')); + }, + + /** + * Log warning message + * @param {Object} fields - Additional fields to log + * @param {string} message - Log message + */ + warn(fields = {}, message) { + const logData = { + ...baseContext, + ...fields, + timestamp: new Date().toISOString(), + }; + core.warning(formatLogData(logData) + (message ? ` ${message}` : '')); + }, + + /** + * Log error message + * @param {Object} fields - Additional fields to log + * @param {string} message - Log message + */ + error(fields = {}, message) { + const logData = { + ...baseContext, + ...fields, + timestamp: new Date().toISOString(), + }; + core.error(formatLogData(logData) + (message ? ` ${message}` : '')); + }, + + /** + * Set failure status with user-safe message + * @param {Object} fields - Additional fields to log + * @param {string} message - User-safe message + */ + setFailed(fields = {}, message) { + const logData = { + ...baseContext, + ...fields, + timestamp: new Date().toISOString(), + }; + core.setFailed(formatLogData(logData) + (message ? ` ${message}` : '')); + }, + }; +} + +/** + * Maps internal errors to user-safe messages + * @param {string} category - Error category from ERROR_CATEGORIES + * @param {Error} [internalError] - Original error (not exposed to users) + * @returns {string} User-safe message + */ +function getUserMessage(category, internalError = null) { + const message = USER_MESSAGES[category] || USER_MESSAGES[ERROR_CATEGORIES.INTERNAL]; + + // Log the internal error details separately for debugging + if (internalError) { + const internalLogger = createLogger(generateCorrelationId()); + internalLogger.error( + { category, errorType: internalError.name }, + `Internal error: ${internalError.message}` + ); + } + + return message; +} + +/** + * Categorizes an error based on its characteristics + * @param {Error} error - Error to categorize + * @returns {string} Error category from ERROR_CATEGORIES + */ +function categorizeError(error) { + const message = error.message?.toLowerCase() || ''; + + // Auth errors + if ( + message.includes('auth') || + message.includes('unauthorized') || + message.includes('forbidden') || + message.includes('invalid token') || + message.includes('401') || + message.includes('403') + ) { + return ERROR_CATEGORIES.AUTH; + } + + // Rate limit errors + if ( + message.includes('rate limit') || + message.includes('too many requests') || + message.includes('429') + ) { + return ERROR_CATEGORIES.RATE_LIMIT; + } + + // Timeout errors + if ( + message.includes('timeout') || + message.includes('etimedout') || + message.includes('timed out') + ) { + return ERROR_CATEGORIES.TIMEOUT; + } + + // Validation errors + if ( + message.includes('validation') || + message.includes('invalid input') || + message.includes('required') || + message.includes('schema') + ) { + return ERROR_CATEGORIES.VALIDATION; + } + + // Provider errors (external API failures) + if ( + message.includes('api') || + message.includes('provider') || + message.includes('external') || + message.includes('5') + ) { + return ERROR_CATEGORIES.PROVIDER; + } + + // Default to internal + return ERROR_CATEGORIES.INTERNAL; +} + +module.exports = { + ERROR_CATEGORIES, + REDACT_FIELDS, + STANDARD_FIELDS, + generateCorrelationId, + createLogger, + getUserMessage, + categorizeError, + redactSensitiveData, +}; + + +/***/ }), + +/***/ 1669: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +/** + * Shared PR context fetch module with reusable async helpers. + * Provides functions for fetching PR files, file content at ref, and base/head refs. + * All functions return structured responses with user-safe error fallbacks. + */ + +const context = __nccwpck_require__(9990); +const logging = __nccwpck_require__(2120); +const { extractEnclosingBlock } = __nccwpck_require__(7437); +const { fetchAllChangedFiles } = __nccwpck_require__(4723); + +// Default size limits +const DEFAULT_MAX_FILE_SIZE = 100000; +const DEFAULT_MAX_FILE_LINES = 10000; +const DEFAULT_PER_PAGE = 100; +const DEFAULT_SLIDING_WINDOW = 40; +const DEFAULT_MAX_WINDOWS = 4; + +/** + * User-safe fallback messages for common error types + */ +const FALLBACK_MESSAGES = { + NOT_FOUND: 'Content not found', + RATE_LIMITED: 'GitHub API rate limit exceeded. Please try again later.', + PERMISSION_DENIED: 'Permission denied to access this resource.', + UNAVAILABLE: 'Resource temporarily unavailable', + UNKNOWN: 'Failed to retrieve content' +}; + +/** + * Checks if error is a rate limit error (429) + * @param {Error} error - Error to check + * @returns {boolean} True if rate limit error + */ +function isRateLimitError(error) { + const message = (error?.message || '').toLowerCase(); + return error?.status === 429 || message.includes('rate limit') || message.includes('secondary rate limit'); +} + +/** + * Maps error to user-safe fallback message + * @param {Error} error - Error to map + * @param {string} resource - Resource being fetched (for context) + * @returns {{fallback: string, category: string}} Fallback message and error category + */ +function mapErrorToFallback(error, resource = 'content') { + if (error?.status === 404) { + return { fallback: `${FALLBACK_MESSAGES.NOT_FOUND}: ${resource}`, category: 'NOT_FOUND' }; + } + if (isRateLimitError(error)) { + return { fallback: FALLBACK_MESSAGES.RATE_LIMITED, category: 'RATE_LIMIT' }; + } + if (error?.status === 403) { + return { fallback: FALLBACK_MESSAGES.PERMISSION_DENIED, category: 'PERMISSION' }; + } + if (error?.status >= 500) { + return { fallback: FALLBACK_MESSAGES.UNAVAILABLE, category: 'PROVIDER' }; + } + return { fallback: FALLBACK_MESSAGES.UNKNOWN, category: 'UNKNOWN' }; +} + +function clamp(value, min, max) { + return Math.max(min, Math.min(max, value)); +} + +function parsePatchLineRanges(patch, side = 'new') { + if (!patch || typeof patch !== 'string') { + return []; + } + + const ranges = []; + const regex = /^@@\s+-(\d+)(?:,(\d+))?\s+\+(\d+)(?:,(\d+))?\s+@@/gm; + let match = regex.exec(patch); + while (match !== null) { + const start = side === 'old' ? Number(match[1]) : Number(match[3]); + const countRaw = side === 'old' ? match[2] : match[4]; + const count = countRaw ? Number(countRaw) : 1; + const safeCount = Number.isFinite(count) ? Math.max(1, count) : 1; + const end = start + safeCount - 1; + if (Number.isFinite(start) && start >= 1) { + ranges.push({ start, end }); + } + match = regex.exec(patch); + } + + return ranges; +} + +function buildSlidingWindowsContent(content, changedRanges, options = {}) { + const windowSize = options.windowSize || DEFAULT_SLIDING_WINDOW; + const maxWindows = options.maxWindows || DEFAULT_MAX_WINDOWS; + const lines = String(content).split('\n'); + const maxLines = lines.length; + + const windows = (changedRanges || []) + .map((range) => { + const start = clamp(range.start - windowSize, 1, maxLines); + const end = clamp(range.end + windowSize, 1, maxLines); + return { start, end }; + }) + .sort((a, b) => a.start - b.start); + + const merged = []; + for (const window of windows) { + const last = merged[merged.length - 1]; + if (!last || window.start > last.end + 1) { + merged.push({ ...window }); + continue; + } + last.end = Math.max(last.end, window.end); + } + + const selected = merged.slice(0, maxWindows); + if (selected.length === 1) { + const only = selected[0]; + return { + content: lines.slice(only.start - 1, only.end).join('\n'), + scopeStrategy: 'sliding_window', + scopeStartLine: only.start, + scopeEndLine: only.end, + }; + } + + const chunks = selected.map((window, idx) => { + const snippet = lines.slice(window.start - 1, window.end).join('\n'); + return `# Window ${idx + 1} (lines ${window.start}-${window.end})\n${snippet}`; + }); + + if (chunks.length === 0) { + const fallbackEnd = clamp(windowSize * 2, 1, maxLines); + return { + content: lines.slice(0, fallbackEnd).join('\n'), + scopeStrategy: 'top_window', + scopeStartLine: 1, + scopeEndLine: fallbackEnd, + }; + } + + return { + content: chunks.join('\n\n---\n\n'), + scopeStrategy: 'sliding_window', + scopeStartLine: selected.length === 1 ? selected[0].start : null, + scopeEndLine: selected.length === 1 ? selected[0].end : null, + }; +} + +function scopeLargeFileContent(content, options = {}) { + const lines = String(content).split('\n'); + const lineCount = lines.length; + const maxFileLines = options.maxFileLines || DEFAULT_MAX_FILE_LINES; + + if (lineCount <= maxFileLines) { + return { + scoped: false, + scopeStrategy: 'full_file', + content, + lineCount, + scopeStartLine: 1, + scopeEndLine: lineCount, + }; + } + + const anchorLine = options.anchorLine; + const preferEnclosingBlock = Boolean(options.preferEnclosingBlock); + let changedRanges = options.changedRanges; + if ((!changedRanges || changedRanges.length === 0) && options.patch) { + changedRanges = parsePatchLineRanges(options.patch, options.patchSide || 'new'); + } + + if (preferEnclosingBlock && Number.isFinite(anchorLine) && anchorLine >= 1) { + const block = extractEnclosingBlock(content, anchorLine, { + windowSize: options.windowSize || DEFAULT_SLIDING_WINDOW, + }); + + return { + scoped: true, + scopeStrategy: 'enclosing_block', + content: block.target.join('\n'), + lineCount, + scopeStartLine: block?.bounds?.start || null, + scopeEndLine: block?.bounds?.end || null, + note: block.note, + }; + } + + if (Array.isArray(changedRanges) && changedRanges.length > 0) { + const scoped = buildSlidingWindowsContent(content, changedRanges, { + windowSize: options.windowSize, + maxWindows: options.maxWindows, + }); + return { + scoped: true, + lineCount, + ...scoped, + }; + } + + const topFallback = buildSlidingWindowsContent(content, [], { + windowSize: options.windowSize, + maxWindows: 1, + }); + + return { + scoped: true, + lineCount, + ...topFallback, + }; +} + +/** + * Fetches the list of files changed in a PR. + * @param {Object} octokit - GitHub Octokit instance + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @param {number} pullNumber - PR number + * @param {Object} [options={}] - Optional configuration + * @param {number} [options.perPage=100] - Files per page + * @returns {Promise<{success: boolean, data?: Array, error?: string, fallback?: string}>} + */ +async function fetchPrFiles(octokit, owner, repo, pullNumber, options = {}) { + const perPage = options.perPage || DEFAULT_PER_PAGE; + + try { + const result = await fetchAllChangedFiles(octokit, owner, repo, pullNumber, { perPage }); + const files = Array.isArray(result.files) ? result.files : []; + return { success: true, data: files }; + } catch (error) { + const { fallback } = mapErrorToFallback(error, `files for PR #${pullNumber}`); + + // Log internal error details + const internalLogger = logging.createLogger(logging.generateCorrelationId()); + internalLogger.warn( + { status: error?.status, operation: 'pulls.listFiles', pullNumber }, + `Failed to fetch PR files: ${error.message}` + ); + + return { success: false, error: error.message, fallback }; + } +} + +/** + * Fetches file content at a specific ref (branch, tag, or SHA). + * @param {Object} octokit - GitHub Octokit instance + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @param {string} path - File path in repository + * @param {string} ref - Git ref (branch, tag, or SHA) + * @param {Object} [options={}] - Optional configuration + * @param {number} [options.maxFileSize=100000] - Maximum characters to return + * @returns {Promise<{success: boolean, data?: string, error?: string, fallback?: string, truncated?: boolean}>} + */ +async function fetchFileAtRef(octokit, owner, repo, path, ref, options = {}) { + const maxFileSize = options.maxFileSize || DEFAULT_MAX_FILE_SIZE; + const maxFileLines = options.maxFileLines || DEFAULT_MAX_FILE_LINES; + + if (!path || !ref) { + return { + success: false, + error: 'path and ref are required', + fallback: FALLBACK_MESSAGES.VALIDATION || 'Invalid request parameters' + }; + } + + try { + const { data } = await octokit.rest.repos.getContent({ + owner, + repo, + path, + ref, + }); + + // Handle directory or symlink responses + if (!data || Array.isArray(data)) { + return { + success: false, + error: `Path ${path} is a directory or not accessible`, + fallback: `Content not available for ${path}` + }; + } + + // Check for binary files or missing content + if (!data.content) { + return { + success: false, + error: 'File content not available (possibly binary)', + fallback: `Binary or non-text content for ${path}` + }; + } + + // Decode base64 content + const decoded = Buffer.from(data.content, 'base64').toString('utf8'); + + const scopedResult = scopeLargeFileContent(decoded, { + maxFileLines, + anchorLine: options.anchorLine, + preferEnclosingBlock: options.preferEnclosingBlock, + changedRanges: options.changedRanges, + patch: options.patch, + patchSide: options.patchSide, + windowSize: options.windowSize, + maxWindows: options.maxWindows, + }); + + // Apply truncation if needed + const truncated = context.truncateContext(scopedResult.content, maxFileSize); + + return { + success: true, + data: truncated.content, + truncated: truncated.truncated, + omitted: truncated.omitted, + scoped: scopedResult.scoped, + scopeStrategy: scopedResult.scopeStrategy, + lineCount: scopedResult.lineCount, + scopeStartLine: scopedResult.scopeStartLine, + scopeEndLine: scopedResult.scopeEndLine, + scopeNote: scopedResult.note, + }; + } catch (error) { + const { fallback } = mapErrorToFallback(error, path); + + // Log internal error details + const internalLogger = logging.createLogger(logging.generateCorrelationId()); + internalLogger.warn( + { status: error?.status, operation: 'repos.getContent', path, ref }, + `Failed to fetch file content: ${error.message}` + ); + + return { success: false, error: error.message, fallback }; + } +} + +/** + * Resolves PR refs (base and head) from PR metadata. + * @param {Object} octokit - GitHub Octokit instance + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @param {number} pullNumber - PR number + * @returns {Promise<{success: boolean, data?: {base: {ref: string, sha: string}, head: {ref: string, sha: string}}, error?: string, fallback?: string}>} + */ +async function resolvePrRefs(octokit, owner, repo, pullNumber) { + if (!pullNumber) { + return { + success: false, + error: 'pullNumber is required', + fallback: 'PR number is required' + }; + } + + try { + const { data } = await octokit.rest.pulls.get({ + owner, + repo, + pull_number: pullNumber, + }); + + const baseRef = data.base?.ref; + const baseSha = data.base?.sha; + const headRef = data.head?.ref; + const headSha = data.head?.sha; + + if (!baseRef || !baseSha || !headRef || !headSha) { + return { + success: false, + error: 'PR base/head refs not found in response', + fallback: 'PR ref information unavailable' + }; + } + + return { + success: true, + data: { + base: { ref: baseRef, sha: baseSha }, + head: { ref: headRef, sha: headSha } + } + }; + } catch (error) { + const { fallback } = mapErrorToFallback(error, `PR #${pullNumber} metadata`); + + // Log internal error details + const internalLogger = logging.createLogger(logging.generateCorrelationId()); + internalLogger.warn( + { status: error?.status, operation: 'pulls.get', pullNumber }, + `Failed to resolve PR refs: ${error.message}` + ); + + return { success: false, error: error.message, fallback }; + } +} + +/** + * Fetches file content at the PR head commit. + * Convenience wrapper around fetchFileAtRef using resolved head SHA. + * @param {Object} octokit - GitHub Octokit instance + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @param {string} path - File path in repository + * @param {number} pullNumber - PR number + * @param {Object} [options={}] - Optional configuration + * @param {number} [options.maxFileSize=100000] - Maximum characters to return + * @returns {Promise<{success: boolean, data?: string, error?: string, fallback?: string, truncated?: boolean}>} + */ +async function fetchFileAtPrHead(octokit, owner, repo, path, pullNumber, options = {}) { + // First resolve the head SHA + const refsResult = await resolvePrRefs(octokit, owner, repo, pullNumber); + + if (!refsResult.success) { + return { + success: false, + error: refsResult.error, + fallback: refsResult.fallback + }; + } + + // Fetch file at head + return fetchFileAtRef(octokit, owner, repo, path, refsResult.data.head.sha, options); +} + +module.exports = { + fetchPrFiles, + fetchFileAtRef, + fetchFileAtPrHead, + resolvePrRefs, + isRateLimitError, + mapErrorToFallback, + DEFAULT_MAX_FILE_SIZE, + DEFAULT_MAX_FILE_LINES, + DEFAULT_PER_PAGE, + DEFAULT_SLIDING_WINDOW, + FALLBACK_MESSAGES, + parsePatchLineRanges, + scopeLargeFileContent, +}; + + +/***/ }), + +/***/ 2613: +/***/ ((module) => { + +"use strict"; +module.exports = require("assert"); + +/***/ }), + +/***/ 181: +/***/ ((module) => { + +"use strict"; +module.exports = require("buffer"); + +/***/ }), + +/***/ 5317: +/***/ ((module) => { + +"use strict"; +module.exports = require("child_process"); + +/***/ }), + +/***/ 6982: +/***/ ((module) => { + +"use strict"; +module.exports = require("crypto"); + +/***/ }), + +/***/ 4434: +/***/ ((module) => { + +"use strict"; +module.exports = require("events"); + +/***/ }), + +/***/ 9896: +/***/ ((module) => { + +"use strict"; +module.exports = require("fs"); + +/***/ }), + +/***/ 8611: +/***/ ((module) => { + +"use strict"; +module.exports = require("http"); + +/***/ }), + +/***/ 5692: +/***/ ((module) => { + +"use strict"; +module.exports = require("https"); + +/***/ }), + +/***/ 9278: +/***/ ((module) => { + +"use strict"; +module.exports = require("net"); + +/***/ }), + +/***/ 4589: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:assert"); + +/***/ }), + +/***/ 6698: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:async_hooks"); + +/***/ }), + +/***/ 4573: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:buffer"); + +/***/ }), + +/***/ 7540: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:console"); + +/***/ }), + +/***/ 7598: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:crypto"); + +/***/ }), + +/***/ 3053: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:diagnostics_channel"); + +/***/ }), + +/***/ 610: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:dns"); + +/***/ }), + +/***/ 8474: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:events"); + +/***/ }), + +/***/ 7067: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:http"); + +/***/ }), + +/***/ 2467: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:http2"); + +/***/ }), + +/***/ 4708: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:https"); + +/***/ }), + +/***/ 7030: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:net"); + +/***/ }), + +/***/ 643: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:perf_hooks"); + +/***/ }), + +/***/ 1792: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:querystring"); + +/***/ }), + +/***/ 7075: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:stream"); + +/***/ }), + +/***/ 1692: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:tls"); + +/***/ }), + +/***/ 3136: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:url"); + +/***/ }), + +/***/ 7975: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:util"); + +/***/ }), + +/***/ 3429: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:util/types"); + +/***/ }), + +/***/ 5919: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:worker_threads"); + +/***/ }), + +/***/ 8522: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:zlib"); + +/***/ }), + +/***/ 857: +/***/ ((module) => { + +"use strict"; +module.exports = require("os"); + +/***/ }), + +/***/ 6928: +/***/ ((module) => { + +"use strict"; +module.exports = require("path"); + +/***/ }), + +/***/ 932: +/***/ ((module) => { + +"use strict"; +module.exports = require("process"); + +/***/ }), + +/***/ 3193: +/***/ ((module) => { + +"use strict"; +module.exports = require("string_decoder"); + +/***/ }), + +/***/ 3557: +/***/ ((module) => { + +"use strict"; +module.exports = require("timers"); + +/***/ }), + +/***/ 4756: +/***/ ((module) => { + +"use strict"; +module.exports = require("tls"); + +/***/ }), + +/***/ 9023: +/***/ ((module) => { + +"use strict"; +module.exports = require("util"); + +/***/ }), + +/***/ 1120: +/***/ ((module) => { + +"use strict"; +var __webpack_unused_export__; + + +const NullObject = function NullObject () { } +NullObject.prototype = Object.create(null) + +/** + * RegExp to match *( ";" parameter ) in RFC 7231 sec 3.1.1.1 + * + * parameter = token "=" ( token / quoted-string ) + * token = 1*tchar + * tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" + * / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" + * / DIGIT / ALPHA + * ; any VCHAR, except delimiters + * quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE + * qdtext = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text + * obs-text = %x80-FF + * quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) + */ +const paramRE = /; *([!#$%&'*+.^\w`|~-]+)=("(?:[\v\u0020\u0021\u0023-\u005b\u005d-\u007e\u0080-\u00ff]|\\[\v\u0020-\u00ff])*"|[!#$%&'*+.^\w`|~-]+) */gu + +/** + * RegExp to match quoted-pair in RFC 7230 sec 3.2.6 + * + * quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) + * obs-text = %x80-FF + */ +const quotedPairRE = /\\([\v\u0020-\u00ff])/gu + +/** + * RegExp to match type in RFC 7231 sec 3.1.1.1 + * + * media-type = type "/" subtype + * type = token + * subtype = token + */ +const mediaTypeRE = /^[!#$%&'*+.^\w|~-]+\/[!#$%&'*+.^\w|~-]+$/u + +// default ContentType to prevent repeated object creation +const defaultContentType = { type: '', parameters: new NullObject() } +Object.freeze(defaultContentType.parameters) +Object.freeze(defaultContentType) + +/** + * Parse media type to object. + * + * @param {string|object} header + * @return {Object} + * @public + */ + +function parse (header) { + if (typeof header !== 'string') { + throw new TypeError('argument header is required and must be a string') + } + + let index = header.indexOf(';') + const type = index !== -1 + ? header.slice(0, index).trim() + : header.trim() + + if (mediaTypeRE.test(type) === false) { + throw new TypeError('invalid media type') + } + + const result = { + type: type.toLowerCase(), + parameters: new NullObject() + } + + // parse parameters + if (index === -1) { + return result + } + + let key + let match + let value + + paramRE.lastIndex = index + + while ((match = paramRE.exec(header))) { + if (match.index !== index) { + throw new TypeError('invalid parameter format') + } + + index += match[0].length + key = match[1].toLowerCase() + value = match[2] + + if (value[0] === '"') { + // remove quotes and escapes + value = value + .slice(1, value.length - 1) + + quotedPairRE.test(value) && (value = value.replace(quotedPairRE, '$1')) + } + + result.parameters[key] = value + } + + if (index !== header.length) { + throw new TypeError('invalid parameter format') + } + + return result +} + +function safeParse (header) { + if (typeof header !== 'string') { + return defaultContentType + } + + let index = header.indexOf(';') + const type = index !== -1 + ? header.slice(0, index).trim() + : header.trim() + + if (mediaTypeRE.test(type) === false) { + return defaultContentType + } + + const result = { + type: type.toLowerCase(), + parameters: new NullObject() + } + + // parse parameters + if (index === -1) { + return result + } + + let key + let match + let value + + paramRE.lastIndex = index + + while ((match = paramRE.exec(header))) { + if (match.index !== index) { + return defaultContentType + } + + index += match[0].length + key = match[1].toLowerCase() + value = match[2] + + if (value[0] === '"') { + // remove quotes and escapes + value = value + .slice(1, value.length - 1) + + quotedPairRE.test(value) && (value = value.replace(quotedPairRE, '$1')) + } + + result.parameters[key] = value + } + + if (index !== header.length) { + return defaultContentType + } + + return result +} + +__webpack_unused_export__ = { parse, safeParse } +__webpack_unused_export__ = parse +module.exports.xL = safeParse +__webpack_unused_export__ = defaultContentType + + +/***/ }), + +/***/ 7349: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var identity = __nccwpck_require__(1127); +var Scalar = __nccwpck_require__(3301); +var YAMLMap = __nccwpck_require__(4454); +var YAMLSeq = __nccwpck_require__(2223); +var resolveBlockMap = __nccwpck_require__(7103); +var resolveBlockSeq = __nccwpck_require__(334); +var resolveFlowCollection = __nccwpck_require__(3142); + +function resolveCollection(CN, ctx, token, onError, tagName, tag) { + const coll = token.type === 'block-map' + ? resolveBlockMap.resolveBlockMap(CN, ctx, token, onError, tag) + : token.type === 'block-seq' + ? resolveBlockSeq.resolveBlockSeq(CN, ctx, token, onError, tag) + : resolveFlowCollection.resolveFlowCollection(CN, ctx, token, onError, tag); + const Coll = coll.constructor; + // If we got a tagName matching the class, or the tag name is '!', + // then use the tagName from the node class used to create it. + if (tagName === '!' || tagName === Coll.tagName) { + coll.tag = Coll.tagName; + return coll; + } + if (tagName) + coll.tag = tagName; + return coll; +} +function composeCollection(CN, ctx, token, props, onError) { + const tagToken = props.tag; + const tagName = !tagToken + ? null + : ctx.directives.tagName(tagToken.source, msg => onError(tagToken, 'TAG_RESOLVE_FAILED', msg)); + if (token.type === 'block-seq') { + const { anchor, newlineAfterProp: nl } = props; + const lastProp = anchor && tagToken + ? anchor.offset > tagToken.offset + ? anchor + : tagToken + : (anchor ?? tagToken); + if (lastProp && (!nl || nl.offset < lastProp.offset)) { + const message = 'Missing newline after block sequence props'; + onError(lastProp, 'MISSING_CHAR', message); + } + } + const expType = token.type === 'block-map' + ? 'map' + : token.type === 'block-seq' + ? 'seq' + : token.start.source === '{' + ? 'map' + : 'seq'; + // shortcut: check if it's a generic YAMLMap or YAMLSeq + // before jumping into the custom tag logic. + if (!tagToken || + !tagName || + tagName === '!' || + (tagName === YAMLMap.YAMLMap.tagName && expType === 'map') || + (tagName === YAMLSeq.YAMLSeq.tagName && expType === 'seq')) { + return resolveCollection(CN, ctx, token, onError, tagName); + } + let tag = ctx.schema.tags.find(t => t.tag === tagName && t.collection === expType); + if (!tag) { + const kt = ctx.schema.knownTags[tagName]; + if (kt?.collection === expType) { + ctx.schema.tags.push(Object.assign({}, kt, { default: false })); + tag = kt; + } + else { + if (kt) { + onError(tagToken, 'BAD_COLLECTION_TYPE', `${kt.tag} used for ${expType} collection, but expects ${kt.collection ?? 'scalar'}`, true); + } + else { + onError(tagToken, 'TAG_RESOLVE_FAILED', `Unresolved tag: ${tagName}`, true); + } + return resolveCollection(CN, ctx, token, onError, tagName); + } + } + const coll = resolveCollection(CN, ctx, token, onError, tagName, tag); + const res = tag.resolve?.(coll, msg => onError(tagToken, 'TAG_RESOLVE_FAILED', msg), ctx.options) ?? coll; + const node = identity.isNode(res) + ? res + : new Scalar.Scalar(res); + node.range = coll.range; + node.tag = tagName; + if (tag?.format) + node.format = tag.format; + return node; +} + +exports.composeCollection = composeCollection; + + +/***/ }), + +/***/ 3683: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var Document = __nccwpck_require__(3021); +var composeNode = __nccwpck_require__(5937); +var resolveEnd = __nccwpck_require__(7788); +var resolveProps = __nccwpck_require__(4631); + +function composeDoc(options, directives, { offset, start, value, end }, onError) { + const opts = Object.assign({ _directives: directives }, options); + const doc = new Document.Document(undefined, opts); + const ctx = { + atKey: false, + atRoot: true, + directives: doc.directives, + options: doc.options, + schema: doc.schema + }; + const props = resolveProps.resolveProps(start, { + indicator: 'doc-start', + next: value ?? end?.[0], + offset, + onError, + parentIndent: 0, + startOnNewline: true + }); + if (props.found) { + doc.directives.docStart = true; + if (value && + (value.type === 'block-map' || value.type === 'block-seq') && + !props.hasNewline) + onError(props.end, 'MISSING_CHAR', 'Block collection cannot start on same line with directives-end marker'); + } + // @ts-expect-error If Contents is set, let's trust the user + doc.contents = value + ? composeNode.composeNode(ctx, value, props, onError) + : composeNode.composeEmptyNode(ctx, props.end, start, null, props, onError); + const contentEnd = doc.contents.range[2]; + const re = resolveEnd.resolveEnd(end, contentEnd, false, onError); + if (re.comment) + doc.comment = re.comment; + doc.range = [offset, contentEnd, re.offset]; + return doc; +} + +exports.composeDoc = composeDoc; + + +/***/ }), + +/***/ 5937: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var Alias = __nccwpck_require__(4065); +var identity = __nccwpck_require__(1127); +var composeCollection = __nccwpck_require__(7349); +var composeScalar = __nccwpck_require__(5413); +var resolveEnd = __nccwpck_require__(7788); +var utilEmptyScalarPosition = __nccwpck_require__(2599); + +const CN = { composeNode, composeEmptyNode }; +function composeNode(ctx, token, props, onError) { + const atKey = ctx.atKey; + const { spaceBefore, comment, anchor, tag } = props; + let node; + let isSrcToken = true; + switch (token.type) { + case 'alias': + node = composeAlias(ctx, token, onError); + if (anchor || tag) + onError(token, 'ALIAS_PROPS', 'An alias node must not specify any properties'); + break; + case 'scalar': + case 'single-quoted-scalar': + case 'double-quoted-scalar': + case 'block-scalar': + node = composeScalar.composeScalar(ctx, token, tag, onError); + if (anchor) + node.anchor = anchor.source.substring(1); + break; + case 'block-map': + case 'block-seq': + case 'flow-collection': + node = composeCollection.composeCollection(CN, ctx, token, props, onError); + if (anchor) + node.anchor = anchor.source.substring(1); + break; + default: { + const message = token.type === 'error' + ? token.message + : `Unsupported token (type: ${token.type})`; + onError(token, 'UNEXPECTED_TOKEN', message); + node = composeEmptyNode(ctx, token.offset, undefined, null, props, onError); + isSrcToken = false; + } + } + if (anchor && node.anchor === '') + onError(anchor, 'BAD_ALIAS', 'Anchor cannot be an empty string'); + if (atKey && + ctx.options.stringKeys && + (!identity.isScalar(node) || + typeof node.value !== 'string' || + (node.tag && node.tag !== 'tag:yaml.org,2002:str'))) { + const msg = 'With stringKeys, all keys must be strings'; + onError(tag ?? token, 'NON_STRING_KEY', msg); + } + if (spaceBefore) + node.spaceBefore = true; + if (comment) { + if (token.type === 'scalar' && token.source === '') + node.comment = comment; + else + node.commentBefore = comment; + } + // @ts-expect-error Type checking misses meaning of isSrcToken + if (ctx.options.keepSourceTokens && isSrcToken) + node.srcToken = token; + return node; +} +function composeEmptyNode(ctx, offset, before, pos, { spaceBefore, comment, anchor, tag, end }, onError) { + const token = { + type: 'scalar', + offset: utilEmptyScalarPosition.emptyScalarPosition(offset, before, pos), + indent: -1, + source: '' + }; + const node = composeScalar.composeScalar(ctx, token, tag, onError); + if (anchor) { + node.anchor = anchor.source.substring(1); + if (node.anchor === '') + onError(anchor, 'BAD_ALIAS', 'Anchor cannot be an empty string'); + } + if (spaceBefore) + node.spaceBefore = true; + if (comment) { + node.comment = comment; + node.range[2] = end; + } + return node; +} +function composeAlias({ options }, { offset, source, end }, onError) { + const alias = new Alias.Alias(source.substring(1)); + if (alias.source === '') + onError(offset, 'BAD_ALIAS', 'Alias cannot be an empty string'); + if (alias.source.endsWith(':')) + onError(offset + source.length - 1, 'BAD_ALIAS', 'Alias ending in : is ambiguous', true); + const valueEnd = offset + source.length; + const re = resolveEnd.resolveEnd(end, valueEnd, options.strict, onError); + alias.range = [offset, valueEnd, re.offset]; + if (re.comment) + alias.comment = re.comment; + return alias; +} + +exports.composeEmptyNode = composeEmptyNode; +exports.composeNode = composeNode; + + +/***/ }), + +/***/ 5413: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var identity = __nccwpck_require__(1127); +var Scalar = __nccwpck_require__(3301); +var resolveBlockScalar = __nccwpck_require__(8913); +var resolveFlowScalar = __nccwpck_require__(6842); + +function composeScalar(ctx, token, tagToken, onError) { + const { value, type, comment, range } = token.type === 'block-scalar' + ? resolveBlockScalar.resolveBlockScalar(ctx, token, onError) + : resolveFlowScalar.resolveFlowScalar(token, ctx.options.strict, onError); + const tagName = tagToken + ? ctx.directives.tagName(tagToken.source, msg => onError(tagToken, 'TAG_RESOLVE_FAILED', msg)) + : null; + let tag; + if (ctx.options.stringKeys && ctx.atKey) { + tag = ctx.schema[identity.SCALAR]; + } + else if (tagName) + tag = findScalarTagByName(ctx.schema, value, tagName, tagToken, onError); + else if (token.type === 'scalar') + tag = findScalarTagByTest(ctx, value, token, onError); + else + tag = ctx.schema[identity.SCALAR]; + let scalar; + try { + const res = tag.resolve(value, msg => onError(tagToken ?? token, 'TAG_RESOLVE_FAILED', msg), ctx.options); + scalar = identity.isScalar(res) ? res : new Scalar.Scalar(res); + } + catch (error) { + const msg = error instanceof Error ? error.message : String(error); + onError(tagToken ?? token, 'TAG_RESOLVE_FAILED', msg); + scalar = new Scalar.Scalar(value); + } + scalar.range = range; + scalar.source = value; + if (type) + scalar.type = type; + if (tagName) + scalar.tag = tagName; + if (tag.format) + scalar.format = tag.format; + if (comment) + scalar.comment = comment; + return scalar; +} +function findScalarTagByName(schema, value, tagName, tagToken, onError) { + if (tagName === '!') + return schema[identity.SCALAR]; // non-specific tag + const matchWithTest = []; + for (const tag of schema.tags) { + if (!tag.collection && tag.tag === tagName) { + if (tag.default && tag.test) + matchWithTest.push(tag); + else + return tag; + } + } + for (const tag of matchWithTest) + if (tag.test?.test(value)) + return tag; + const kt = schema.knownTags[tagName]; + if (kt && !kt.collection) { + // Ensure that the known tag is available for stringifying, + // but does not get used by default. + schema.tags.push(Object.assign({}, kt, { default: false, test: undefined })); + return kt; + } + onError(tagToken, 'TAG_RESOLVE_FAILED', `Unresolved tag: ${tagName}`, tagName !== 'tag:yaml.org,2002:str'); + return schema[identity.SCALAR]; +} +function findScalarTagByTest({ atKey, directives, schema }, value, token, onError) { + const tag = schema.tags.find(tag => (tag.default === true || (atKey && tag.default === 'key')) && + tag.test?.test(value)) || schema[identity.SCALAR]; + if (schema.compat) { + const compat = schema.compat.find(tag => tag.default && tag.test?.test(value)) ?? + schema[identity.SCALAR]; + if (tag.tag !== compat.tag) { + const ts = directives.tagString(tag.tag); + const cs = directives.tagString(compat.tag); + const msg = `Value may be parsed as either ${ts} or ${cs}`; + onError(token, 'TAG_RESOLVE_FAILED', msg, true); + } + } + return tag; +} + +exports.composeScalar = composeScalar; + + +/***/ }), + +/***/ 9984: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var node_process = __nccwpck_require__(932); +var directives = __nccwpck_require__(1342); +var Document = __nccwpck_require__(3021); +var errors = __nccwpck_require__(1464); +var identity = __nccwpck_require__(1127); +var composeDoc = __nccwpck_require__(3683); +var resolveEnd = __nccwpck_require__(7788); + +function getErrorPos(src) { + if (typeof src === 'number') + return [src, src + 1]; + if (Array.isArray(src)) + return src.length === 2 ? src : [src[0], src[1]]; + const { offset, source } = src; + return [offset, offset + (typeof source === 'string' ? source.length : 1)]; +} +function parsePrelude(prelude) { + let comment = ''; + let atComment = false; + let afterEmptyLine = false; + for (let i = 0; i < prelude.length; ++i) { + const source = prelude[i]; + switch (source[0]) { + case '#': + comment += + (comment === '' ? '' : afterEmptyLine ? '\n\n' : '\n') + + (source.substring(1) || ' '); + atComment = true; + afterEmptyLine = false; + break; + case '%': + if (prelude[i + 1]?.[0] !== '#') + i += 1; + atComment = false; + break; + default: + // This may be wrong after doc-end, but in that case it doesn't matter + if (!atComment) + afterEmptyLine = true; + atComment = false; + } + } + return { comment, afterEmptyLine }; +} +/** + * Compose a stream of CST nodes into a stream of YAML Documents. + * + * ```ts + * import { Composer, Parser } from 'yaml' + * + * const src: string = ... + * const tokens = new Parser().parse(src) + * const docs = new Composer().compose(tokens) + * ``` + */ +class Composer { + constructor(options = {}) { + this.doc = null; + this.atDirectives = false; + this.prelude = []; + this.errors = []; + this.warnings = []; + this.onError = (source, code, message, warning) => { + const pos = getErrorPos(source); + if (warning) + this.warnings.push(new errors.YAMLWarning(pos, code, message)); + else + this.errors.push(new errors.YAMLParseError(pos, code, message)); + }; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + this.directives = new directives.Directives({ version: options.version || '1.2' }); + this.options = options; + } + decorate(doc, afterDoc) { + const { comment, afterEmptyLine } = parsePrelude(this.prelude); + //console.log({ dc: doc.comment, prelude, comment }) + if (comment) { + const dc = doc.contents; + if (afterDoc) { + doc.comment = doc.comment ? `${doc.comment}\n${comment}` : comment; + } + else if (afterEmptyLine || doc.directives.docStart || !dc) { + doc.commentBefore = comment; + } + else if (identity.isCollection(dc) && !dc.flow && dc.items.length > 0) { + let it = dc.items[0]; + if (identity.isPair(it)) + it = it.key; + const cb = it.commentBefore; + it.commentBefore = cb ? `${comment}\n${cb}` : comment; + } + else { + const cb = dc.commentBefore; + dc.commentBefore = cb ? `${comment}\n${cb}` : comment; + } + } + if (afterDoc) { + Array.prototype.push.apply(doc.errors, this.errors); + Array.prototype.push.apply(doc.warnings, this.warnings); + } + else { + doc.errors = this.errors; + doc.warnings = this.warnings; + } + this.prelude = []; + this.errors = []; + this.warnings = []; + } + /** + * Current stream status information. + * + * Mostly useful at the end of input for an empty stream. + */ + streamInfo() { + return { + comment: parsePrelude(this.prelude).comment, + directives: this.directives, + errors: this.errors, + warnings: this.warnings + }; + } + /** + * Compose tokens into documents. + * + * @param forceDoc - If the stream contains no document, still emit a final document including any comments and directives that would be applied to a subsequent document. + * @param endOffset - Should be set if `forceDoc` is also set, to set the document range end and to indicate errors correctly. + */ + *compose(tokens, forceDoc = false, endOffset = -1) { + for (const token of tokens) + yield* this.next(token); + yield* this.end(forceDoc, endOffset); + } + /** Advance the composer by one CST token. */ + *next(token) { + if (node_process.env.LOG_STREAM) + console.dir(token, { depth: null }); + switch (token.type) { + case 'directive': + this.directives.add(token.source, (offset, message, warning) => { + const pos = getErrorPos(token); + pos[0] += offset; + this.onError(pos, 'BAD_DIRECTIVE', message, warning); + }); + this.prelude.push(token.source); + this.atDirectives = true; + break; + case 'document': { + const doc = composeDoc.composeDoc(this.options, this.directives, token, this.onError); + if (this.atDirectives && !doc.directives.docStart) + this.onError(token, 'MISSING_CHAR', 'Missing directives-end/doc-start indicator line'); + this.decorate(doc, false); + if (this.doc) + yield this.doc; + this.doc = doc; + this.atDirectives = false; + break; + } + case 'byte-order-mark': + case 'space': + break; + case 'comment': + case 'newline': + this.prelude.push(token.source); + break; + case 'error': { + const msg = token.source + ? `${token.message}: ${JSON.stringify(token.source)}` + : token.message; + const error = new errors.YAMLParseError(getErrorPos(token), 'UNEXPECTED_TOKEN', msg); + if (this.atDirectives || !this.doc) + this.errors.push(error); + else + this.doc.errors.push(error); + break; + } + case 'doc-end': { + if (!this.doc) { + const msg = 'Unexpected doc-end without preceding document'; + this.errors.push(new errors.YAMLParseError(getErrorPos(token), 'UNEXPECTED_TOKEN', msg)); + break; + } + this.doc.directives.docEnd = true; + const end = resolveEnd.resolveEnd(token.end, token.offset + token.source.length, this.doc.options.strict, this.onError); + this.decorate(this.doc, true); + if (end.comment) { + const dc = this.doc.comment; + this.doc.comment = dc ? `${dc}\n${end.comment}` : end.comment; + } + this.doc.range[2] = end.offset; + break; + } + default: + this.errors.push(new errors.YAMLParseError(getErrorPos(token), 'UNEXPECTED_TOKEN', `Unsupported token ${token.type}`)); + } + } + /** + * Call at end of input to yield any remaining document. + * + * @param forceDoc - If the stream contains no document, still emit a final document including any comments and directives that would be applied to a subsequent document. + * @param endOffset - Should be set if `forceDoc` is also set, to set the document range end and to indicate errors correctly. + */ + *end(forceDoc = false, endOffset = -1) { + if (this.doc) { + this.decorate(this.doc, true); + yield this.doc; + this.doc = null; + } + else if (forceDoc) { + const opts = Object.assign({ _directives: this.directives }, this.options); + const doc = new Document.Document(undefined, opts); + if (this.atDirectives) + this.onError(endOffset, 'MISSING_CHAR', 'Missing directives-end indicator line'); + doc.range = [0, endOffset, endOffset]; + this.decorate(doc, false); + yield doc; + } + } +} + +exports.Composer = Composer; + + +/***/ }), + +/***/ 7103: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var Pair = __nccwpck_require__(7165); +var YAMLMap = __nccwpck_require__(4454); +var resolveProps = __nccwpck_require__(4631); +var utilContainsNewline = __nccwpck_require__(9499); +var utilFlowIndentCheck = __nccwpck_require__(4051); +var utilMapIncludes = __nccwpck_require__(1187); + +const startColMsg = 'All mapping items must start at the same column'; +function resolveBlockMap({ composeNode, composeEmptyNode }, ctx, bm, onError, tag) { + const NodeClass = tag?.nodeClass ?? YAMLMap.YAMLMap; + const map = new NodeClass(ctx.schema); + if (ctx.atRoot) + ctx.atRoot = false; + let offset = bm.offset; + let commentEnd = null; + for (const collItem of bm.items) { + const { start, key, sep, value } = collItem; + // key properties + const keyProps = resolveProps.resolveProps(start, { + indicator: 'explicit-key-ind', + next: key ?? sep?.[0], + offset, + onError, + parentIndent: bm.indent, + startOnNewline: true + }); + const implicitKey = !keyProps.found; + if (implicitKey) { + if (key) { + if (key.type === 'block-seq') + onError(offset, 'BLOCK_AS_IMPLICIT_KEY', 'A block sequence may not be used as an implicit map key'); + else if ('indent' in key && key.indent !== bm.indent) + onError(offset, 'BAD_INDENT', startColMsg); + } + if (!keyProps.anchor && !keyProps.tag && !sep) { + commentEnd = keyProps.end; + if (keyProps.comment) { + if (map.comment) + map.comment += '\n' + keyProps.comment; + else + map.comment = keyProps.comment; + } + continue; + } + if (keyProps.newlineAfterProp || utilContainsNewline.containsNewline(key)) { + onError(key ?? start[start.length - 1], 'MULTILINE_IMPLICIT_KEY', 'Implicit keys need to be on a single line'); + } + } + else if (keyProps.found?.indent !== bm.indent) { + onError(offset, 'BAD_INDENT', startColMsg); + } + // key value + ctx.atKey = true; + const keyStart = keyProps.end; + const keyNode = key + ? composeNode(ctx, key, keyProps, onError) + : composeEmptyNode(ctx, keyStart, start, null, keyProps, onError); + if (ctx.schema.compat) + utilFlowIndentCheck.flowIndentCheck(bm.indent, key, onError); + ctx.atKey = false; + if (utilMapIncludes.mapIncludes(ctx, map.items, keyNode)) + onError(keyStart, 'DUPLICATE_KEY', 'Map keys must be unique'); + // value properties + const valueProps = resolveProps.resolveProps(sep ?? [], { + indicator: 'map-value-ind', + next: value, + offset: keyNode.range[2], + onError, + parentIndent: bm.indent, + startOnNewline: !key || key.type === 'block-scalar' + }); + offset = valueProps.end; + if (valueProps.found) { + if (implicitKey) { + if (value?.type === 'block-map' && !valueProps.hasNewline) + onError(offset, 'BLOCK_AS_IMPLICIT_KEY', 'Nested mappings are not allowed in compact mappings'); + if (ctx.options.strict && + keyProps.start < valueProps.found.offset - 1024) + onError(keyNode.range, 'KEY_OVER_1024_CHARS', 'The : indicator must be at most 1024 chars after the start of an implicit block mapping key'); + } + // value value + const valueNode = value + ? composeNode(ctx, value, valueProps, onError) + : composeEmptyNode(ctx, offset, sep, null, valueProps, onError); + if (ctx.schema.compat) + utilFlowIndentCheck.flowIndentCheck(bm.indent, value, onError); + offset = valueNode.range[2]; + const pair = new Pair.Pair(keyNode, valueNode); + if (ctx.options.keepSourceTokens) + pair.srcToken = collItem; + map.items.push(pair); + } + else { + // key with no value + if (implicitKey) + onError(keyNode.range, 'MISSING_CHAR', 'Implicit map keys need to be followed by map values'); + if (valueProps.comment) { + if (keyNode.comment) + keyNode.comment += '\n' + valueProps.comment; + else + keyNode.comment = valueProps.comment; + } + const pair = new Pair.Pair(keyNode); + if (ctx.options.keepSourceTokens) + pair.srcToken = collItem; + map.items.push(pair); + } + } + if (commentEnd && commentEnd < offset) + onError(commentEnd, 'IMPOSSIBLE', 'Map comment with trailing content'); + map.range = [bm.offset, offset, commentEnd ?? offset]; + return map; +} + +exports.resolveBlockMap = resolveBlockMap; + + +/***/ }), + +/***/ 8913: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var Scalar = __nccwpck_require__(3301); + +function resolveBlockScalar(ctx, scalar, onError) { + const start = scalar.offset; + const header = parseBlockScalarHeader(scalar, ctx.options.strict, onError); + if (!header) + return { value: '', type: null, comment: '', range: [start, start, start] }; + const type = header.mode === '>' ? Scalar.Scalar.BLOCK_FOLDED : Scalar.Scalar.BLOCK_LITERAL; + const lines = scalar.source ? splitLines(scalar.source) : []; + // determine the end of content & start of chomping + let chompStart = lines.length; + for (let i = lines.length - 1; i >= 0; --i) { + const content = lines[i][1]; + if (content === '' || content === '\r') + chompStart = i; + else + break; + } + // shortcut for empty contents + if (chompStart === 0) { + const value = header.chomp === '+' && lines.length > 0 + ? '\n'.repeat(Math.max(1, lines.length - 1)) + : ''; + let end = start + header.length; + if (scalar.source) + end += scalar.source.length; + return { value, type, comment: header.comment, range: [start, end, end] }; + } + // find the indentation level to trim from start + let trimIndent = scalar.indent + header.indent; + let offset = scalar.offset + header.length; + let contentStart = 0; + for (let i = 0; i < chompStart; ++i) { + const [indent, content] = lines[i]; + if (content === '' || content === '\r') { + if (header.indent === 0 && indent.length > trimIndent) + trimIndent = indent.length; + } + else { + if (indent.length < trimIndent) { + const message = 'Block scalars with more-indented leading empty lines must use an explicit indentation indicator'; + onError(offset + indent.length, 'MISSING_CHAR', message); + } + if (header.indent === 0) + trimIndent = indent.length; + contentStart = i; + if (trimIndent === 0 && !ctx.atRoot) { + const message = 'Block scalar values in collections must be indented'; + onError(offset, 'BAD_INDENT', message); + } + break; + } + offset += indent.length + content.length + 1; + } + // include trailing more-indented empty lines in content + for (let i = lines.length - 1; i >= chompStart; --i) { + if (lines[i][0].length > trimIndent) + chompStart = i + 1; + } + let value = ''; + let sep = ''; + let prevMoreIndented = false; + // leading whitespace is kept intact + for (let i = 0; i < contentStart; ++i) + value += lines[i][0].slice(trimIndent) + '\n'; + for (let i = contentStart; i < chompStart; ++i) { + let [indent, content] = lines[i]; + offset += indent.length + content.length + 1; + const crlf = content[content.length - 1] === '\r'; + if (crlf) + content = content.slice(0, -1); + /* istanbul ignore if already caught in lexer */ + if (content && indent.length < trimIndent) { + const src = header.indent + ? 'explicit indentation indicator' + : 'first line'; + const message = `Block scalar lines must not be less indented than their ${src}`; + onError(offset - content.length - (crlf ? 2 : 1), 'BAD_INDENT', message); + indent = ''; + } + if (type === Scalar.Scalar.BLOCK_LITERAL) { + value += sep + indent.slice(trimIndent) + content; + sep = '\n'; + } + else if (indent.length > trimIndent || content[0] === '\t') { + // more-indented content within a folded block + if (sep === ' ') + sep = '\n'; + else if (!prevMoreIndented && sep === '\n') + sep = '\n\n'; + value += sep + indent.slice(trimIndent) + content; + sep = '\n'; + prevMoreIndented = true; + } + else if (content === '') { + // empty line + if (sep === '\n') + value += '\n'; + else + sep = '\n'; + } + else { + value += sep + content; + sep = ' '; + prevMoreIndented = false; + } + } + switch (header.chomp) { + case '-': + break; + case '+': + for (let i = chompStart; i < lines.length; ++i) + value += '\n' + lines[i][0].slice(trimIndent); + if (value[value.length - 1] !== '\n') + value += '\n'; + break; + default: + value += '\n'; + } + const end = start + header.length + scalar.source.length; + return { value, type, comment: header.comment, range: [start, end, end] }; +} +function parseBlockScalarHeader({ offset, props }, strict, onError) { + /* istanbul ignore if should not happen */ + if (props[0].type !== 'block-scalar-header') { + onError(props[0], 'IMPOSSIBLE', 'Block scalar header not found'); + return null; + } + const { source } = props[0]; + const mode = source[0]; + let indent = 0; + let chomp = ''; + let error = -1; + for (let i = 1; i < source.length; ++i) { + const ch = source[i]; + if (!chomp && (ch === '-' || ch === '+')) + chomp = ch; + else { + const n = Number(ch); + if (!indent && n) + indent = n; + else if (error === -1) + error = offset + i; + } + } + if (error !== -1) + onError(error, 'UNEXPECTED_TOKEN', `Block scalar header includes extra characters: ${source}`); + let hasSpace = false; + let comment = ''; + let length = source.length; + for (let i = 1; i < props.length; ++i) { + const token = props[i]; + switch (token.type) { + case 'space': + hasSpace = true; + // fallthrough + case 'newline': + length += token.source.length; + break; + case 'comment': + if (strict && !hasSpace) { + const message = 'Comments must be separated from other tokens by white space characters'; + onError(token, 'MISSING_CHAR', message); + } + length += token.source.length; + comment = token.source.substring(1); + break; + case 'error': + onError(token, 'UNEXPECTED_TOKEN', token.message); + length += token.source.length; + break; + /* istanbul ignore next should not happen */ + default: { + const message = `Unexpected token in block scalar header: ${token.type}`; + onError(token, 'UNEXPECTED_TOKEN', message); + const ts = token.source; + if (ts && typeof ts === 'string') + length += ts.length; + } + } + } + return { mode, indent, chomp, comment, length }; +} +/** @returns Array of lines split up as `[indent, content]` */ +function splitLines(source) { + const split = source.split(/\n( *)/); + const first = split[0]; + const m = first.match(/^( *)/); + const line0 = m?.[1] + ? [m[1], first.slice(m[1].length)] + : ['', first]; + const lines = [line0]; + for (let i = 1; i < split.length; i += 2) + lines.push([split[i], split[i + 1]]); + return lines; +} + +exports.resolveBlockScalar = resolveBlockScalar; + + +/***/ }), + +/***/ 334: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var YAMLSeq = __nccwpck_require__(2223); +var resolveProps = __nccwpck_require__(4631); +var utilFlowIndentCheck = __nccwpck_require__(4051); + +function resolveBlockSeq({ composeNode, composeEmptyNode }, ctx, bs, onError, tag) { + const NodeClass = tag?.nodeClass ?? YAMLSeq.YAMLSeq; + const seq = new NodeClass(ctx.schema); + if (ctx.atRoot) + ctx.atRoot = false; + if (ctx.atKey) + ctx.atKey = false; + let offset = bs.offset; + let commentEnd = null; + for (const { start, value } of bs.items) { + const props = resolveProps.resolveProps(start, { + indicator: 'seq-item-ind', + next: value, + offset, + onError, + parentIndent: bs.indent, + startOnNewline: true + }); + if (!props.found) { + if (props.anchor || props.tag || value) { + if (value?.type === 'block-seq') + onError(props.end, 'BAD_INDENT', 'All sequence items must start at the same column'); + else + onError(offset, 'MISSING_CHAR', 'Sequence item without - indicator'); + } + else { + commentEnd = props.end; + if (props.comment) + seq.comment = props.comment; + continue; + } + } + const node = value + ? composeNode(ctx, value, props, onError) + : composeEmptyNode(ctx, props.end, start, null, props, onError); + if (ctx.schema.compat) + utilFlowIndentCheck.flowIndentCheck(bs.indent, value, onError); + offset = node.range[2]; + seq.items.push(node); + } + seq.range = [bs.offset, offset, commentEnd ?? offset]; + return seq; +} + +exports.resolveBlockSeq = resolveBlockSeq; + + +/***/ }), + +/***/ 7788: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +function resolveEnd(end, offset, reqSpace, onError) { + let comment = ''; + if (end) { + let hasSpace = false; + let sep = ''; + for (const token of end) { + const { source, type } = token; + switch (type) { + case 'space': + hasSpace = true; + break; + case 'comment': { + if (reqSpace && !hasSpace) + onError(token, 'MISSING_CHAR', 'Comments must be separated from other tokens by white space characters'); + const cb = source.substring(1) || ' '; + if (!comment) + comment = cb; + else + comment += sep + cb; + sep = ''; + break; + } + case 'newline': + if (comment) + sep += source; + hasSpace = true; + break; + default: + onError(token, 'UNEXPECTED_TOKEN', `Unexpected ${type} at node end`); + } + offset += source.length; + } + } + return { comment, offset }; +} + +exports.resolveEnd = resolveEnd; + + +/***/ }), + +/***/ 3142: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var identity = __nccwpck_require__(1127); +var Pair = __nccwpck_require__(7165); +var YAMLMap = __nccwpck_require__(4454); +var YAMLSeq = __nccwpck_require__(2223); +var resolveEnd = __nccwpck_require__(7788); +var resolveProps = __nccwpck_require__(4631); +var utilContainsNewline = __nccwpck_require__(9499); +var utilMapIncludes = __nccwpck_require__(1187); + +const blockMsg = 'Block collections are not allowed within flow collections'; +const isBlock = (token) => token && (token.type === 'block-map' || token.type === 'block-seq'); +function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onError, tag) { + const isMap = fc.start.source === '{'; + const fcName = isMap ? 'flow map' : 'flow sequence'; + const NodeClass = (tag?.nodeClass ?? (isMap ? YAMLMap.YAMLMap : YAMLSeq.YAMLSeq)); + const coll = new NodeClass(ctx.schema); + coll.flow = true; + const atRoot = ctx.atRoot; + if (atRoot) + ctx.atRoot = false; + if (ctx.atKey) + ctx.atKey = false; + let offset = fc.offset + fc.start.source.length; + for (let i = 0; i < fc.items.length; ++i) { + const collItem = fc.items[i]; + const { start, key, sep, value } = collItem; + const props = resolveProps.resolveProps(start, { + flow: fcName, + indicator: 'explicit-key-ind', + next: key ?? sep?.[0], + offset, + onError, + parentIndent: fc.indent, + startOnNewline: false + }); + if (!props.found) { + if (!props.anchor && !props.tag && !sep && !value) { + if (i === 0 && props.comma) + onError(props.comma, 'UNEXPECTED_TOKEN', `Unexpected , in ${fcName}`); + else if (i < fc.items.length - 1) + onError(props.start, 'UNEXPECTED_TOKEN', `Unexpected empty item in ${fcName}`); + if (props.comment) { + if (coll.comment) + coll.comment += '\n' + props.comment; + else + coll.comment = props.comment; + } + offset = props.end; + continue; + } + if (!isMap && ctx.options.strict && utilContainsNewline.containsNewline(key)) + onError(key, // checked by containsNewline() + 'MULTILINE_IMPLICIT_KEY', 'Implicit keys of flow sequence pairs need to be on a single line'); + } + if (i === 0) { + if (props.comma) + onError(props.comma, 'UNEXPECTED_TOKEN', `Unexpected , in ${fcName}`); + } + else { + if (!props.comma) + onError(props.start, 'MISSING_CHAR', `Missing , between ${fcName} items`); + if (props.comment) { + let prevItemComment = ''; + loop: for (const st of start) { + switch (st.type) { + case 'comma': + case 'space': + break; + case 'comment': + prevItemComment = st.source.substring(1); + break loop; + default: + break loop; + } + } + if (prevItemComment) { + let prev = coll.items[coll.items.length - 1]; + if (identity.isPair(prev)) + prev = prev.value ?? prev.key; + if (prev.comment) + prev.comment += '\n' + prevItemComment; + else + prev.comment = prevItemComment; + props.comment = props.comment.substring(prevItemComment.length + 1); + } + } + } + if (!isMap && !sep && !props.found) { + // item is a value in a seq + // → key & sep are empty, start does not include ? or : + const valueNode = value + ? composeNode(ctx, value, props, onError) + : composeEmptyNode(ctx, props.end, sep, null, props, onError); + coll.items.push(valueNode); + offset = valueNode.range[2]; + if (isBlock(value)) + onError(valueNode.range, 'BLOCK_IN_FLOW', blockMsg); + } + else { + // item is a key+value pair + // key value + ctx.atKey = true; + const keyStart = props.end; + const keyNode = key + ? composeNode(ctx, key, props, onError) + : composeEmptyNode(ctx, keyStart, start, null, props, onError); + if (isBlock(key)) + onError(keyNode.range, 'BLOCK_IN_FLOW', blockMsg); + ctx.atKey = false; + // value properties + const valueProps = resolveProps.resolveProps(sep ?? [], { + flow: fcName, + indicator: 'map-value-ind', + next: value, + offset: keyNode.range[2], + onError, + parentIndent: fc.indent, + startOnNewline: false + }); + if (valueProps.found) { + if (!isMap && !props.found && ctx.options.strict) { + if (sep) + for (const st of sep) { + if (st === valueProps.found) + break; + if (st.type === 'newline') { + onError(st, 'MULTILINE_IMPLICIT_KEY', 'Implicit keys of flow sequence pairs need to be on a single line'); + break; + } + } + if (props.start < valueProps.found.offset - 1024) + onError(valueProps.found, 'KEY_OVER_1024_CHARS', 'The : indicator must be at most 1024 chars after the start of an implicit flow sequence key'); + } + } + else if (value) { + if ('source' in value && value.source?.[0] === ':') + onError(value, 'MISSING_CHAR', `Missing space after : in ${fcName}`); + else + onError(valueProps.start, 'MISSING_CHAR', `Missing , or : between ${fcName} items`); + } + // value value + const valueNode = value + ? composeNode(ctx, value, valueProps, onError) + : valueProps.found + ? composeEmptyNode(ctx, valueProps.end, sep, null, valueProps, onError) + : null; + if (valueNode) { + if (isBlock(value)) + onError(valueNode.range, 'BLOCK_IN_FLOW', blockMsg); + } + else if (valueProps.comment) { + if (keyNode.comment) + keyNode.comment += '\n' + valueProps.comment; + else + keyNode.comment = valueProps.comment; + } + const pair = new Pair.Pair(keyNode, valueNode); + if (ctx.options.keepSourceTokens) + pair.srcToken = collItem; + if (isMap) { + const map = coll; + if (utilMapIncludes.mapIncludes(ctx, map.items, keyNode)) + onError(keyStart, 'DUPLICATE_KEY', 'Map keys must be unique'); + map.items.push(pair); + } + else { + const map = new YAMLMap.YAMLMap(ctx.schema); + map.flow = true; + map.items.push(pair); + const endRange = (valueNode ?? keyNode).range; + map.range = [keyNode.range[0], endRange[1], endRange[2]]; + coll.items.push(map); + } + offset = valueNode ? valueNode.range[2] : valueProps.end; + } + } + const expectedEnd = isMap ? '}' : ']'; + const [ce, ...ee] = fc.end; + let cePos = offset; + if (ce?.source === expectedEnd) + cePos = ce.offset + ce.source.length; + else { + const name = fcName[0].toUpperCase() + fcName.substring(1); + const msg = atRoot + ? `${name} must end with a ${expectedEnd}` + : `${name} in block collection must be sufficiently indented and end with a ${expectedEnd}`; + onError(offset, atRoot ? 'MISSING_CHAR' : 'BAD_INDENT', msg); + if (ce && ce.source.length !== 1) + ee.unshift(ce); + } + if (ee.length > 0) { + const end = resolveEnd.resolveEnd(ee, cePos, ctx.options.strict, onError); + if (end.comment) { + if (coll.comment) + coll.comment += '\n' + end.comment; + else + coll.comment = end.comment; + } + coll.range = [fc.offset, cePos, end.offset]; + } + else { + coll.range = [fc.offset, cePos, cePos]; + } + return coll; +} + +exports.resolveFlowCollection = resolveFlowCollection; + + +/***/ }), + +/***/ 6842: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var Scalar = __nccwpck_require__(3301); +var resolveEnd = __nccwpck_require__(7788); + +function resolveFlowScalar(scalar, strict, onError) { + const { offset, type, source, end } = scalar; + let _type; + let value; + const _onError = (rel, code, msg) => onError(offset + rel, code, msg); + switch (type) { + case 'scalar': + _type = Scalar.Scalar.PLAIN; + value = plainValue(source, _onError); + break; + case 'single-quoted-scalar': + _type = Scalar.Scalar.QUOTE_SINGLE; + value = singleQuotedValue(source, _onError); + break; + case 'double-quoted-scalar': + _type = Scalar.Scalar.QUOTE_DOUBLE; + value = doubleQuotedValue(source, _onError); + break; + /* istanbul ignore next should not happen */ + default: + onError(scalar, 'UNEXPECTED_TOKEN', `Expected a flow scalar value, but found: ${type}`); + return { + value: '', + type: null, + comment: '', + range: [offset, offset + source.length, offset + source.length] + }; + } + const valueEnd = offset + source.length; + const re = resolveEnd.resolveEnd(end, valueEnd, strict, onError); + return { + value, + type: _type, + comment: re.comment, + range: [offset, valueEnd, re.offset] + }; +} +function plainValue(source, onError) { + let badChar = ''; + switch (source[0]) { + /* istanbul ignore next should not happen */ + case '\t': + badChar = 'a tab character'; + break; + case ',': + badChar = 'flow indicator character ,'; + break; + case '%': + badChar = 'directive indicator character %'; + break; + case '|': + case '>': { + badChar = `block scalar indicator ${source[0]}`; + break; + } + case '@': + case '`': { + badChar = `reserved character ${source[0]}`; + break; + } + } + if (badChar) + onError(0, 'BAD_SCALAR_START', `Plain value cannot start with ${badChar}`); + return foldLines(source); +} +function singleQuotedValue(source, onError) { + if (source[source.length - 1] !== "'" || source.length === 1) + onError(source.length, 'MISSING_CHAR', "Missing closing 'quote"); + return foldLines(source.slice(1, -1)).replace(/''/g, "'"); +} +function foldLines(source) { + /** + * The negative lookbehind here and in the `re` RegExp is to + * prevent causing a polynomial search time in certain cases. + * + * The try-catch is for Safari, which doesn't support this yet: + * https://caniuse.com/js-regexp-lookbehind + */ + let first, line; + try { + first = new RegExp('(.*?)(? wsStart ? source.slice(wsStart, i + 1) : ch; + } + else { + res += ch; + } + } + if (source[source.length - 1] !== '"' || source.length === 1) + onError(source.length, 'MISSING_CHAR', 'Missing closing "quote'); + return res; +} +/** + * Fold a single newline into a space, multiple newlines to N - 1 newlines. + * Presumes `source[offset] === '\n'` + */ +function foldNewline(source, offset) { + let fold = ''; + let ch = source[offset + 1]; + while (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') { + if (ch === '\r' && source[offset + 2] !== '\n') + break; + if (ch === '\n') + fold += '\n'; + offset += 1; + ch = source[offset + 1]; + } + if (!fold) + fold = ' '; + return { fold, offset }; +} +const escapeCodes = { + '0': '\0', // null character + a: '\x07', // bell character + b: '\b', // backspace + e: '\x1b', // escape character + f: '\f', // form feed + n: '\n', // line feed + r: '\r', // carriage return + t: '\t', // horizontal tab + v: '\v', // vertical tab + N: '\u0085', // Unicode next line + _: '\u00a0', // Unicode non-breaking space + L: '\u2028', // Unicode line separator + P: '\u2029', // Unicode paragraph separator + ' ': ' ', + '"': '"', + '/': '/', + '\\': '\\', + '\t': '\t' +}; +function parseCharCode(source, offset, length, onError) { + const cc = source.substr(offset, length); + const ok = cc.length === length && /^[0-9a-fA-F]+$/.test(cc); + const code = ok ? parseInt(cc, 16) : NaN; + if (isNaN(code)) { + const raw = source.substr(offset - 2, length + 2); + onError(offset - 2, 'BAD_DQ_ESCAPE', `Invalid escape sequence ${raw}`); + return raw; + } + return String.fromCodePoint(code); +} + +exports.resolveFlowScalar = resolveFlowScalar; + + +/***/ }), + +/***/ 4631: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +function resolveProps(tokens, { flow, indicator, next, offset, onError, parentIndent, startOnNewline }) { + let spaceBefore = false; + let atNewline = startOnNewline; + let hasSpace = startOnNewline; + let comment = ''; + let commentSep = ''; + let hasNewline = false; + let reqSpace = false; + let tab = null; + let anchor = null; + let tag = null; + let newlineAfterProp = null; + let comma = null; + let found = null; + let start = null; + for (const token of tokens) { + if (reqSpace) { + if (token.type !== 'space' && + token.type !== 'newline' && + token.type !== 'comma') + onError(token.offset, 'MISSING_CHAR', 'Tags and anchors must be separated from the next token by white space'); + reqSpace = false; + } + if (tab) { + if (atNewline && token.type !== 'comment' && token.type !== 'newline') { + onError(tab, 'TAB_AS_INDENT', 'Tabs are not allowed as indentation'); + } + tab = null; + } + switch (token.type) { + case 'space': + // At the doc level, tabs at line start may be parsed + // as leading white space rather than indentation. + // In a flow collection, only the parser handles indent. + if (!flow && + (indicator !== 'doc-start' || next?.type !== 'flow-collection') && + token.source.includes('\t')) { + tab = token; + } + hasSpace = true; + break; + case 'comment': { + if (!hasSpace) + onError(token, 'MISSING_CHAR', 'Comments must be separated from other tokens by white space characters'); + const cb = token.source.substring(1) || ' '; + if (!comment) + comment = cb; + else + comment += commentSep + cb; + commentSep = ''; + atNewline = false; + break; + } + case 'newline': + if (atNewline) { + if (comment) + comment += token.source; + else if (!found || indicator !== 'seq-item-ind') + spaceBefore = true; + } + else + commentSep += token.source; + atNewline = true; + hasNewline = true; + if (anchor || tag) + newlineAfterProp = token; + hasSpace = true; + break; + case 'anchor': + if (anchor) + onError(token, 'MULTIPLE_ANCHORS', 'A node can have at most one anchor'); + if (token.source.endsWith(':')) + onError(token.offset + token.source.length - 1, 'BAD_ALIAS', 'Anchor ending in : is ambiguous', true); + anchor = token; + start ?? (start = token.offset); + atNewline = false; + hasSpace = false; + reqSpace = true; + break; + case 'tag': { + if (tag) + onError(token, 'MULTIPLE_TAGS', 'A node can have at most one tag'); + tag = token; + start ?? (start = token.offset); + atNewline = false; + hasSpace = false; + reqSpace = true; + break; + } + case indicator: + // Could here handle preceding comments differently + if (anchor || tag) + onError(token, 'BAD_PROP_ORDER', `Anchors and tags must be after the ${token.source} indicator`); + if (found) + onError(token, 'UNEXPECTED_TOKEN', `Unexpected ${token.source} in ${flow ?? 'collection'}`); + found = token; + atNewline = + indicator === 'seq-item-ind' || indicator === 'explicit-key-ind'; + hasSpace = false; + break; + case 'comma': + if (flow) { + if (comma) + onError(token, 'UNEXPECTED_TOKEN', `Unexpected , in ${flow}`); + comma = token; + atNewline = false; + hasSpace = false; + break; + } + // else fallthrough + default: + onError(token, 'UNEXPECTED_TOKEN', `Unexpected ${token.type} token`); + atNewline = false; + hasSpace = false; + } + } + const last = tokens[tokens.length - 1]; + const end = last ? last.offset + last.source.length : offset; + if (reqSpace && + next && + next.type !== 'space' && + next.type !== 'newline' && + next.type !== 'comma' && + (next.type !== 'scalar' || next.source !== '')) { + onError(next.offset, 'MISSING_CHAR', 'Tags and anchors must be separated from the next token by white space'); + } + if (tab && + ((atNewline && tab.indent <= parentIndent) || + next?.type === 'block-map' || + next?.type === 'block-seq')) + onError(tab, 'TAB_AS_INDENT', 'Tabs are not allowed as indentation'); + return { + comma, + found, + spaceBefore, + comment, + hasNewline, + anchor, + tag, + newlineAfterProp, + end, + start: start ?? end + }; +} + +exports.resolveProps = resolveProps; + + +/***/ }), + +/***/ 9499: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +function containsNewline(key) { + if (!key) + return null; + switch (key.type) { + case 'alias': + case 'scalar': + case 'double-quoted-scalar': + case 'single-quoted-scalar': + if (key.source.includes('\n')) + return true; + if (key.end) + for (const st of key.end) + if (st.type === 'newline') + return true; + return false; + case 'flow-collection': + for (const it of key.items) { + for (const st of it.start) + if (st.type === 'newline') + return true; + if (it.sep) + for (const st of it.sep) + if (st.type === 'newline') + return true; + if (containsNewline(it.key) || containsNewline(it.value)) + return true; + } + return false; + default: + return true; + } +} + +exports.containsNewline = containsNewline; + + +/***/ }), + +/***/ 2599: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +function emptyScalarPosition(offset, before, pos) { + if (before) { + pos ?? (pos = before.length); + for (let i = pos - 1; i >= 0; --i) { + let st = before[i]; + switch (st.type) { + case 'space': + case 'comment': + case 'newline': + offset -= st.source.length; + continue; + } + // Technically, an empty scalar is immediately after the last non-empty + // node, but it's more useful to place it after any whitespace. + st = before[++i]; + while (st?.type === 'space') { + offset += st.source.length; + st = before[++i]; + } + break; + } + } + return offset; +} + +exports.emptyScalarPosition = emptyScalarPosition; + + +/***/ }), + +/***/ 4051: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var utilContainsNewline = __nccwpck_require__(9499); + +function flowIndentCheck(indent, fc, onError) { + if (fc?.type === 'flow-collection') { + const end = fc.end[0]; + if (end.indent === indent && + (end.source === ']' || end.source === '}') && + utilContainsNewline.containsNewline(fc)) { + const msg = 'Flow end indicator should be more indented than parent'; + onError(end, 'BAD_INDENT', msg, true); + } + } +} + +exports.flowIndentCheck = flowIndentCheck; + + +/***/ }), + +/***/ 1187: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var identity = __nccwpck_require__(1127); + +function mapIncludes(ctx, items, search) { + const { uniqueKeys } = ctx.options; + if (uniqueKeys === false) + return false; + const isEqual = typeof uniqueKeys === 'function' + ? uniqueKeys + : (a, b) => a === b || (identity.isScalar(a) && identity.isScalar(b) && a.value === b.value); + return items.some(pair => isEqual(pair.key, search)); +} + +exports.mapIncludes = mapIncludes; + + +/***/ }), + +/***/ 3021: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var Alias = __nccwpck_require__(4065); +var Collection = __nccwpck_require__(101); +var identity = __nccwpck_require__(1127); +var Pair = __nccwpck_require__(7165); +var toJS = __nccwpck_require__(6424); +var Schema = __nccwpck_require__(5840); +var stringifyDocument = __nccwpck_require__(6829); +var anchors = __nccwpck_require__(1596); +var applyReviver = __nccwpck_require__(3661); +var createNode = __nccwpck_require__(2404); +var directives = __nccwpck_require__(1342); + +class Document { + constructor(value, replacer, options) { + /** A comment before this Document */ + this.commentBefore = null; + /** A comment immediately after this Document */ + this.comment = null; + /** Errors encountered during parsing. */ + this.errors = []; + /** Warnings encountered during parsing. */ + this.warnings = []; + Object.defineProperty(this, identity.NODE_TYPE, { value: identity.DOC }); + let _replacer = null; + if (typeof replacer === 'function' || Array.isArray(replacer)) { + _replacer = replacer; + } + else if (options === undefined && replacer) { + options = replacer; + replacer = undefined; + } + const opt = Object.assign({ + intAsBigInt: false, + keepSourceTokens: false, + logLevel: 'warn', + prettyErrors: true, + strict: true, + stringKeys: false, + uniqueKeys: true, + version: '1.2' + }, options); + this.options = opt; + let { version } = opt; + if (options?._directives) { + this.directives = options._directives.atDocument(); + if (this.directives.yaml.explicit) + version = this.directives.yaml.version; + } + else + this.directives = new directives.Directives({ version }); + this.setSchema(version, options); + // @ts-expect-error We can't really know that this matches Contents. + this.contents = + value === undefined ? null : this.createNode(value, _replacer, options); + } + /** + * Create a deep copy of this Document and its contents. + * + * Custom Node values that inherit from `Object` still refer to their original instances. + */ + clone() { + const copy = Object.create(Document.prototype, { + [identity.NODE_TYPE]: { value: identity.DOC } + }); + copy.commentBefore = this.commentBefore; + copy.comment = this.comment; + copy.errors = this.errors.slice(); + copy.warnings = this.warnings.slice(); + copy.options = Object.assign({}, this.options); + if (this.directives) + copy.directives = this.directives.clone(); + copy.schema = this.schema.clone(); + // @ts-expect-error We can't really know that this matches Contents. + copy.contents = identity.isNode(this.contents) + ? this.contents.clone(copy.schema) + : this.contents; + if (this.range) + copy.range = this.range.slice(); + return copy; + } + /** Adds a value to the document. */ + add(value) { + if (assertCollection(this.contents)) + this.contents.add(value); + } + /** Adds a value to the document. */ + addIn(path, value) { + if (assertCollection(this.contents)) + this.contents.addIn(path, value); + } + /** + * Create a new `Alias` node, ensuring that the target `node` has the required anchor. + * + * If `node` already has an anchor, `name` is ignored. + * Otherwise, the `node.anchor` value will be set to `name`, + * or if an anchor with that name is already present in the document, + * `name` will be used as a prefix for a new unique anchor. + * If `name` is undefined, the generated anchor will use 'a' as a prefix. + */ + createAlias(node, name) { + if (!node.anchor) { + const prev = anchors.anchorNames(this); + node.anchor = + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + !name || prev.has(name) ? anchors.findNewAnchor(name || 'a', prev) : name; + } + return new Alias.Alias(node.anchor); + } + createNode(value, replacer, options) { + let _replacer = undefined; + if (typeof replacer === 'function') { + value = replacer.call({ '': value }, '', value); + _replacer = replacer; + } + else if (Array.isArray(replacer)) { + const keyToStr = (v) => typeof v === 'number' || v instanceof String || v instanceof Number; + const asStr = replacer.filter(keyToStr).map(String); + if (asStr.length > 0) + replacer = replacer.concat(asStr); + _replacer = replacer; + } + else if (options === undefined && replacer) { + options = replacer; + replacer = undefined; + } + const { aliasDuplicateObjects, anchorPrefix, flow, keepUndefined, onTagObj, tag } = options ?? {}; + const { onAnchor, setAnchors, sourceObjects } = anchors.createNodeAnchors(this, + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + anchorPrefix || 'a'); + const ctx = { + aliasDuplicateObjects: aliasDuplicateObjects ?? true, + keepUndefined: keepUndefined ?? false, + onAnchor, + onTagObj, + replacer: _replacer, + schema: this.schema, + sourceObjects + }; + const node = createNode.createNode(value, tag, ctx); + if (flow && identity.isCollection(node)) + node.flow = true; + setAnchors(); + return node; + } + /** + * Convert a key and a value into a `Pair` using the current schema, + * recursively wrapping all values as `Scalar` or `Collection` nodes. + */ + createPair(key, value, options = {}) { + const k = this.createNode(key, null, options); + const v = this.createNode(value, null, options); + return new Pair.Pair(k, v); + } + /** + * Removes a value from the document. + * @returns `true` if the item was found and removed. + */ + delete(key) { + return assertCollection(this.contents) ? this.contents.delete(key) : false; + } + /** + * Removes a value from the document. + * @returns `true` if the item was found and removed. + */ + deleteIn(path) { + if (Collection.isEmptyPath(path)) { + if (this.contents == null) + return false; + // @ts-expect-error Presumed impossible if Strict extends false + this.contents = null; + return true; + } + return assertCollection(this.contents) + ? this.contents.deleteIn(path) + : false; + } + /** + * Returns item at `key`, or `undefined` if not found. By default unwraps + * scalar values from their surrounding node; to disable set `keepScalar` to + * `true` (collections are always returned intact). + */ + get(key, keepScalar) { + return identity.isCollection(this.contents) + ? this.contents.get(key, keepScalar) + : undefined; + } + /** + * Returns item at `path`, or `undefined` if not found. By default unwraps + * scalar values from their surrounding node; to disable set `keepScalar` to + * `true` (collections are always returned intact). + */ + getIn(path, keepScalar) { + if (Collection.isEmptyPath(path)) + return !keepScalar && identity.isScalar(this.contents) + ? this.contents.value + : this.contents; + return identity.isCollection(this.contents) + ? this.contents.getIn(path, keepScalar) + : undefined; + } + /** + * Checks if the document includes a value with the key `key`. + */ + has(key) { + return identity.isCollection(this.contents) ? this.contents.has(key) : false; + } + /** + * Checks if the document includes a value at `path`. + */ + hasIn(path) { + if (Collection.isEmptyPath(path)) + return this.contents !== undefined; + return identity.isCollection(this.contents) ? this.contents.hasIn(path) : false; + } + /** + * Sets a value in this document. For `!!set`, `value` needs to be a + * boolean to add/remove the item from the set. + */ + set(key, value) { + if (this.contents == null) { + // @ts-expect-error We can't really know that this matches Contents. + this.contents = Collection.collectionFromPath(this.schema, [key], value); + } + else if (assertCollection(this.contents)) { + this.contents.set(key, value); + } + } + /** + * Sets a value in this document. For `!!set`, `value` needs to be a + * boolean to add/remove the item from the set. + */ + setIn(path, value) { + if (Collection.isEmptyPath(path)) { + // @ts-expect-error We can't really know that this matches Contents. + this.contents = value; + } + else if (this.contents == null) { + // @ts-expect-error We can't really know that this matches Contents. + this.contents = Collection.collectionFromPath(this.schema, Array.from(path), value); + } + else if (assertCollection(this.contents)) { + this.contents.setIn(path, value); + } + } + /** + * Change the YAML version and schema used by the document. + * A `null` version disables support for directives, explicit tags, anchors, and aliases. + * It also requires the `schema` option to be given as a `Schema` instance value. + * + * Overrides all previously set schema options. + */ + setSchema(version, options = {}) { + if (typeof version === 'number') + version = String(version); + let opt; + switch (version) { + case '1.1': + if (this.directives) + this.directives.yaml.version = '1.1'; + else + this.directives = new directives.Directives({ version: '1.1' }); + opt = { resolveKnownTags: false, schema: 'yaml-1.1' }; + break; + case '1.2': + case 'next': + if (this.directives) + this.directives.yaml.version = version; + else + this.directives = new directives.Directives({ version }); + opt = { resolveKnownTags: true, schema: 'core' }; + break; + case null: + if (this.directives) + delete this.directives; + opt = null; + break; + default: { + const sv = JSON.stringify(version); + throw new Error(`Expected '1.1', '1.2' or null as first argument, but found: ${sv}`); + } + } + // Not using `instanceof Schema` to allow for duck typing + if (options.schema instanceof Object) + this.schema = options.schema; + else if (opt) + this.schema = new Schema.Schema(Object.assign(opt, options)); + else + throw new Error(`With a null YAML version, the { schema: Schema } option is required`); + } + // json & jsonArg are only used from toJSON() + toJS({ json, jsonArg, mapAsMap, maxAliasCount, onAnchor, reviver } = {}) { + const ctx = { + anchors: new Map(), + doc: this, + keep: !json, + mapAsMap: mapAsMap === true, + mapKeyWarned: false, + maxAliasCount: typeof maxAliasCount === 'number' ? maxAliasCount : 100 + }; + const res = toJS.toJS(this.contents, jsonArg ?? '', ctx); + if (typeof onAnchor === 'function') + for (const { count, res } of ctx.anchors.values()) + onAnchor(res, count); + return typeof reviver === 'function' + ? applyReviver.applyReviver(reviver, { '': res }, '', res) + : res; + } + /** + * A JSON representation of the document `contents`. + * + * @param jsonArg Used by `JSON.stringify` to indicate the array index or + * property name. + */ + toJSON(jsonArg, onAnchor) { + return this.toJS({ json: true, jsonArg, mapAsMap: false, onAnchor }); + } + /** A YAML representation of the document. */ + toString(options = {}) { + if (this.errors.length > 0) + throw new Error('Document with errors cannot be stringified'); + if ('indent' in options && + (!Number.isInteger(options.indent) || Number(options.indent) <= 0)) { + const s = JSON.stringify(options.indent); + throw new Error(`"indent" option must be a positive integer, not ${s}`); + } + return stringifyDocument.stringifyDocument(this, options); + } +} +function assertCollection(contents) { + if (identity.isCollection(contents)) + return true; + throw new Error('Expected a YAML collection as document contents'); +} + +exports.Document = Document; + + +/***/ }), + +/***/ 1596: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var identity = __nccwpck_require__(1127); +var visit = __nccwpck_require__(204); + +/** + * Verify that the input string is a valid anchor. + * + * Will throw on errors. + */ +function anchorIsValid(anchor) { + if (/[\x00-\x19\s,[\]{}]/.test(anchor)) { + const sa = JSON.stringify(anchor); + const msg = `Anchor must not contain whitespace or control characters: ${sa}`; + throw new Error(msg); + } + return true; +} +function anchorNames(root) { + const anchors = new Set(); + visit.visit(root, { + Value(_key, node) { + if (node.anchor) + anchors.add(node.anchor); + } + }); + return anchors; +} +/** Find a new anchor name with the given `prefix` and a one-indexed suffix. */ +function findNewAnchor(prefix, exclude) { + for (let i = 1; true; ++i) { + const name = `${prefix}${i}`; + if (!exclude.has(name)) + return name; + } +} +function createNodeAnchors(doc, prefix) { + const aliasObjects = []; + const sourceObjects = new Map(); + let prevAnchors = null; + return { + onAnchor: (source) => { + aliasObjects.push(source); + prevAnchors ?? (prevAnchors = anchorNames(doc)); + const anchor = findNewAnchor(prefix, prevAnchors); + prevAnchors.add(anchor); + return anchor; + }, + /** + * With circular references, the source node is only resolved after all + * of its child nodes are. This is why anchors are set only after all of + * the nodes have been created. + */ + setAnchors: () => { + for (const source of aliasObjects) { + const ref = sourceObjects.get(source); + if (typeof ref === 'object' && + ref.anchor && + (identity.isScalar(ref.node) || identity.isCollection(ref.node))) { + ref.node.anchor = ref.anchor; + } + else { + const error = new Error('Failed to resolve repeated object (this should not happen)'); + error.source = source; + throw error; + } + } + }, + sourceObjects + }; +} + +exports.anchorIsValid = anchorIsValid; +exports.anchorNames = anchorNames; +exports.createNodeAnchors = createNodeAnchors; +exports.findNewAnchor = findNewAnchor; + + +/***/ }), + +/***/ 3661: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +/** + * Applies the JSON.parse reviver algorithm as defined in the ECMA-262 spec, + * in section 24.5.1.1 "Runtime Semantics: InternalizeJSONProperty" of the + * 2021 edition: https://tc39.es/ecma262/#sec-json.parse + * + * Includes extensions for handling Map and Set objects. + */ +function applyReviver(reviver, obj, key, val) { + if (val && typeof val === 'object') { + if (Array.isArray(val)) { + for (let i = 0, len = val.length; i < len; ++i) { + const v0 = val[i]; + const v1 = applyReviver(reviver, val, String(i), v0); + // eslint-disable-next-line @typescript-eslint/no-array-delete + if (v1 === undefined) + delete val[i]; + else if (v1 !== v0) + val[i] = v1; + } + } + else if (val instanceof Map) { + for (const k of Array.from(val.keys())) { + const v0 = val.get(k); + const v1 = applyReviver(reviver, val, k, v0); + if (v1 === undefined) + val.delete(k); + else if (v1 !== v0) + val.set(k, v1); + } + } + else if (val instanceof Set) { + for (const v0 of Array.from(val)) { + const v1 = applyReviver(reviver, val, v0, v0); + if (v1 === undefined) + val.delete(v0); + else if (v1 !== v0) { + val.delete(v0); + val.add(v1); + } + } + } + else { + for (const [k, v0] of Object.entries(val)) { + const v1 = applyReviver(reviver, val, k, v0); + if (v1 === undefined) + delete val[k]; + else if (v1 !== v0) + val[k] = v1; + } + } + } + return reviver.call(obj, key, val); +} + +exports.applyReviver = applyReviver; + + +/***/ }), + +/***/ 2404: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var Alias = __nccwpck_require__(4065); +var identity = __nccwpck_require__(1127); +var Scalar = __nccwpck_require__(3301); + +const defaultTagPrefix = 'tag:yaml.org,2002:'; +function findTagObject(value, tagName, tags) { + if (tagName) { + const match = tags.filter(t => t.tag === tagName); + const tagObj = match.find(t => !t.format) ?? match[0]; + if (!tagObj) + throw new Error(`Tag ${tagName} not found`); + return tagObj; + } + return tags.find(t => t.identify?.(value) && !t.format); +} +function createNode(value, tagName, ctx) { + if (identity.isDocument(value)) + value = value.contents; + if (identity.isNode(value)) + return value; + if (identity.isPair(value)) { + const map = ctx.schema[identity.MAP].createNode?.(ctx.schema, null, ctx); + map.items.push(value); + return map; + } + if (value instanceof String || + value instanceof Number || + value instanceof Boolean || + (typeof BigInt !== 'undefined' && value instanceof BigInt) // not supported everywhere + ) { + // https://tc39.es/ecma262/#sec-serializejsonproperty + value = value.valueOf(); + } + const { aliasDuplicateObjects, onAnchor, onTagObj, schema, sourceObjects } = ctx; + // Detect duplicate references to the same object & use Alias nodes for all + // after first. The `ref` wrapper allows for circular references to resolve. + let ref = undefined; + if (aliasDuplicateObjects && value && typeof value === 'object') { + ref = sourceObjects.get(value); + if (ref) { + ref.anchor ?? (ref.anchor = onAnchor(value)); + return new Alias.Alias(ref.anchor); + } + else { + ref = { anchor: null, node: null }; + sourceObjects.set(value, ref); + } + } + if (tagName?.startsWith('!!')) + tagName = defaultTagPrefix + tagName.slice(2); + let tagObj = findTagObject(value, tagName, schema.tags); + if (!tagObj) { + if (value && typeof value.toJSON === 'function') { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + value = value.toJSON(); + } + if (!value || typeof value !== 'object') { + const node = new Scalar.Scalar(value); + if (ref) + ref.node = node; + return node; + } + tagObj = + value instanceof Map + ? schema[identity.MAP] + : Symbol.iterator in Object(value) + ? schema[identity.SEQ] + : schema[identity.MAP]; + } + if (onTagObj) { + onTagObj(tagObj); + delete ctx.onTagObj; + } + const node = tagObj?.createNode + ? tagObj.createNode(ctx.schema, value, ctx) + : typeof tagObj?.nodeClass?.from === 'function' + ? tagObj.nodeClass.from(ctx.schema, value, ctx) + : new Scalar.Scalar(value); + if (tagName) + node.tag = tagName; + else if (!tagObj.default) + node.tag = tagObj.tag; + if (ref) + ref.node = node; + return node; +} + +exports.createNode = createNode; + + +/***/ }), + +/***/ 1342: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var identity = __nccwpck_require__(1127); +var visit = __nccwpck_require__(204); + +const escapeChars = { + '!': '%21', + ',': '%2C', + '[': '%5B', + ']': '%5D', + '{': '%7B', + '}': '%7D' +}; +const escapeTagName = (tn) => tn.replace(/[!,[\]{}]/g, ch => escapeChars[ch]); +class Directives { + constructor(yaml, tags) { + /** + * The directives-end/doc-start marker `---`. If `null`, a marker may still be + * included in the document's stringified representation. + */ + this.docStart = null; + /** The doc-end marker `...`. */ + this.docEnd = false; + this.yaml = Object.assign({}, Directives.defaultYaml, yaml); + this.tags = Object.assign({}, Directives.defaultTags, tags); + } + clone() { + const copy = new Directives(this.yaml, this.tags); + copy.docStart = this.docStart; + return copy; + } + /** + * During parsing, get a Directives instance for the current document and + * update the stream state according to the current version's spec. + */ + atDocument() { + const res = new Directives(this.yaml, this.tags); + switch (this.yaml.version) { + case '1.1': + this.atNextDocument = true; + break; + case '1.2': + this.atNextDocument = false; + this.yaml = { + explicit: Directives.defaultYaml.explicit, + version: '1.2' + }; + this.tags = Object.assign({}, Directives.defaultTags); + break; + } + return res; + } + /** + * @param onError - May be called even if the action was successful + * @returns `true` on success + */ + add(line, onError) { + if (this.atNextDocument) { + this.yaml = { explicit: Directives.defaultYaml.explicit, version: '1.1' }; + this.tags = Object.assign({}, Directives.defaultTags); + this.atNextDocument = false; + } + const parts = line.trim().split(/[ \t]+/); + const name = parts.shift(); + switch (name) { + case '%TAG': { + if (parts.length !== 2) { + onError(0, '%TAG directive should contain exactly two parts'); + if (parts.length < 2) + return false; + } + const [handle, prefix] = parts; + this.tags[handle] = prefix; + return true; + } + case '%YAML': { + this.yaml.explicit = true; + if (parts.length !== 1) { + onError(0, '%YAML directive should contain exactly one part'); + return false; + } + const [version] = parts; + if (version === '1.1' || version === '1.2') { + this.yaml.version = version; + return true; + } + else { + const isValid = /^\d+\.\d+$/.test(version); + onError(6, `Unsupported YAML version ${version}`, isValid); + return false; + } + } + default: + onError(0, `Unknown directive ${name}`, true); + return false; + } + } + /** + * Resolves a tag, matching handles to those defined in %TAG directives. + * + * @returns Resolved tag, which may also be the non-specific tag `'!'` or a + * `'!local'` tag, or `null` if unresolvable. + */ + tagName(source, onError) { + if (source === '!') + return '!'; // non-specific tag + if (source[0] !== '!') { + onError(`Not a valid tag: ${source}`); + return null; + } + if (source[1] === '<') { + const verbatim = source.slice(2, -1); + if (verbatim === '!' || verbatim === '!!') { + onError(`Verbatim tags aren't resolved, so ${source} is invalid.`); + return null; + } + if (source[source.length - 1] !== '>') + onError('Verbatim tags must end with a >'); + return verbatim; + } + const [, handle, suffix] = source.match(/^(.*!)([^!]*)$/s); + if (!suffix) + onError(`The ${source} tag has no suffix`); + const prefix = this.tags[handle]; + if (prefix) { + try { + return prefix + decodeURIComponent(suffix); + } + catch (error) { + onError(String(error)); + return null; + } + } + if (handle === '!') + return source; // local tag + onError(`Could not resolve tag: ${source}`); + return null; + } + /** + * Given a fully resolved tag, returns its printable string form, + * taking into account current tag prefixes and defaults. + */ + tagString(tag) { + for (const [handle, prefix] of Object.entries(this.tags)) { + if (tag.startsWith(prefix)) + return handle + escapeTagName(tag.substring(prefix.length)); + } + return tag[0] === '!' ? tag : `!<${tag}>`; + } + toString(doc) { + const lines = this.yaml.explicit + ? [`%YAML ${this.yaml.version || '1.2'}`] + : []; + const tagEntries = Object.entries(this.tags); + let tagNames; + if (doc && tagEntries.length > 0 && identity.isNode(doc.contents)) { + const tags = {}; + visit.visit(doc.contents, (_key, node) => { + if (identity.isNode(node) && node.tag) + tags[node.tag] = true; + }); + tagNames = Object.keys(tags); + } + else + tagNames = []; + for (const [handle, prefix] of tagEntries) { + if (handle === '!!' && prefix === 'tag:yaml.org,2002:') + continue; + if (!doc || tagNames.some(tn => tn.startsWith(prefix))) + lines.push(`%TAG ${handle} ${prefix}`); + } + return lines.join('\n'); + } +} +Directives.defaultYaml = { explicit: false, version: '1.2' }; +Directives.defaultTags = { '!!': 'tag:yaml.org,2002:' }; + +exports.Directives = Directives; + + +/***/ }), + +/***/ 1464: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +class YAMLError extends Error { + constructor(name, pos, code, message) { + super(); + this.name = name; + this.code = code; + this.message = message; + this.pos = pos; + } +} +class YAMLParseError extends YAMLError { + constructor(pos, code, message) { + super('YAMLParseError', pos, code, message); + } +} +class YAMLWarning extends YAMLError { + constructor(pos, code, message) { + super('YAMLWarning', pos, code, message); + } +} +const prettifyError = (src, lc) => (error) => { + if (error.pos[0] === -1) + return; + error.linePos = error.pos.map(pos => lc.linePos(pos)); + const { line, col } = error.linePos[0]; + error.message += ` at line ${line}, column ${col}`; + let ci = col - 1; + let lineStr = src + .substring(lc.lineStarts[line - 1], lc.lineStarts[line]) + .replace(/[\n\r]+$/, ''); + // Trim to max 80 chars, keeping col position near the middle + if (ci >= 60 && lineStr.length > 80) { + const trimStart = Math.min(ci - 39, lineStr.length - 79); + lineStr = '…' + lineStr.substring(trimStart); + ci -= trimStart - 1; + } + if (lineStr.length > 80) + lineStr = lineStr.substring(0, 79) + '…'; + // Include previous line in context if pointing at line start + if (line > 1 && /^ *$/.test(lineStr.substring(0, ci))) { + // Regexp won't match if start is trimmed + let prev = src.substring(lc.lineStarts[line - 2], lc.lineStarts[line - 1]); + if (prev.length > 80) + prev = prev.substring(0, 79) + '…\n'; + lineStr = prev + lineStr; + } + if (/[^ ]/.test(lineStr)) { + let count = 1; + const end = error.linePos[1]; + if (end?.line === line && end.col > col) { + count = Math.max(1, Math.min(end.col - col, 80 - ci)); + } + const pointer = ' '.repeat(ci) + '^'.repeat(count); + error.message += `:\n\n${lineStr}\n${pointer}\n`; + } +}; + +exports.YAMLError = YAMLError; +exports.YAMLParseError = YAMLParseError; +exports.YAMLWarning = YAMLWarning; +exports.prettifyError = prettifyError; + + +/***/ }), + +/***/ 8815: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var composer = __nccwpck_require__(9984); +var Document = __nccwpck_require__(3021); +var Schema = __nccwpck_require__(5840); +var errors = __nccwpck_require__(1464); +var Alias = __nccwpck_require__(4065); +var identity = __nccwpck_require__(1127); +var Pair = __nccwpck_require__(7165); +var Scalar = __nccwpck_require__(3301); +var YAMLMap = __nccwpck_require__(4454); +var YAMLSeq = __nccwpck_require__(2223); +var cst = __nccwpck_require__(3461); +var lexer = __nccwpck_require__(361); +var lineCounter = __nccwpck_require__(6628); +var parser = __nccwpck_require__(3456); +var publicApi = __nccwpck_require__(4047); +var visit = __nccwpck_require__(204); + + + +exports.Composer = composer.Composer; +exports.Document = Document.Document; +exports.Schema = Schema.Schema; +exports.YAMLError = errors.YAMLError; +exports.YAMLParseError = errors.YAMLParseError; +exports.YAMLWarning = errors.YAMLWarning; +exports.Alias = Alias.Alias; +exports.isAlias = identity.isAlias; +exports.isCollection = identity.isCollection; +exports.isDocument = identity.isDocument; +exports.isMap = identity.isMap; +exports.isNode = identity.isNode; +exports.isPair = identity.isPair; +exports.isScalar = identity.isScalar; +exports.isSeq = identity.isSeq; +exports.Pair = Pair.Pair; +exports.Scalar = Scalar.Scalar; +exports.YAMLMap = YAMLMap.YAMLMap; +exports.YAMLSeq = YAMLSeq.YAMLSeq; +exports.CST = cst; +exports.Lexer = lexer.Lexer; +exports.LineCounter = lineCounter.LineCounter; +exports.Parser = parser.Parser; +exports.parse = publicApi.parse; +exports.parseAllDocuments = publicApi.parseAllDocuments; +exports.parseDocument = publicApi.parseDocument; +exports.stringify = publicApi.stringify; +exports.visit = visit.visit; +exports.visitAsync = visit.visitAsync; + + +/***/ }), + +/***/ 7249: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var node_process = __nccwpck_require__(932); + +function debug(logLevel, ...messages) { + if (logLevel === 'debug') + console.log(...messages); +} +function warn(logLevel, warning) { + if (logLevel === 'debug' || logLevel === 'warn') { + if (typeof node_process.emitWarning === 'function') + node_process.emitWarning(warning); + else + console.warn(warning); + } +} + +exports.debug = debug; +exports.warn = warn; + + +/***/ }), + +/***/ 4065: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var anchors = __nccwpck_require__(1596); +var visit = __nccwpck_require__(204); +var identity = __nccwpck_require__(1127); +var Node = __nccwpck_require__(6673); +var toJS = __nccwpck_require__(6424); + +class Alias extends Node.NodeBase { + constructor(source) { + super(identity.ALIAS); + this.source = source; + Object.defineProperty(this, 'tag', { + set() { + throw new Error('Alias nodes cannot have tags'); + } + }); + } + /** + * Resolve the value of this alias within `doc`, finding the last + * instance of the `source` anchor before this node. + */ + resolve(doc, ctx) { + let nodes; + if (ctx?.aliasResolveCache) { + nodes = ctx.aliasResolveCache; + } + else { + nodes = []; + visit.visit(doc, { + Node: (_key, node) => { + if (identity.isAlias(node) || identity.hasAnchor(node)) + nodes.push(node); + } + }); + if (ctx) + ctx.aliasResolveCache = nodes; + } + let found = undefined; + for (const node of nodes) { + if (node === this) + break; + if (node.anchor === this.source) + found = node; + } + return found; + } + toJSON(_arg, ctx) { + if (!ctx) + return { source: this.source }; + const { anchors, doc, maxAliasCount } = ctx; + const source = this.resolve(doc, ctx); + if (!source) { + const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`; + throw new ReferenceError(msg); + } + let data = anchors.get(source); + if (!data) { + // Resolve anchors for Node.prototype.toJS() + toJS.toJS(source, null, ctx); + data = anchors.get(source); + } + /* istanbul ignore if */ + if (data?.res === undefined) { + const msg = 'This should not happen: Alias anchor was not resolved?'; + throw new ReferenceError(msg); + } + if (maxAliasCount >= 0) { + data.count += 1; + if (data.aliasCount === 0) + data.aliasCount = getAliasCount(doc, source, anchors); + if (data.count * data.aliasCount > maxAliasCount) { + const msg = 'Excessive alias count indicates a resource exhaustion attack'; + throw new ReferenceError(msg); + } + } + return data.res; + } + toString(ctx, _onComment, _onChompKeep) { + const src = `*${this.source}`; + if (ctx) { + anchors.anchorIsValid(this.source); + if (ctx.options.verifyAliasOrder && !ctx.anchors.has(this.source)) { + const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`; + throw new Error(msg); + } + if (ctx.implicitKey) + return `${src} `; + } + return src; + } +} +function getAliasCount(doc, node, anchors) { + if (identity.isAlias(node)) { + const source = node.resolve(doc); + const anchor = anchors && source && anchors.get(source); + return anchor ? anchor.count * anchor.aliasCount : 0; + } + else if (identity.isCollection(node)) { + let count = 0; + for (const item of node.items) { + const c = getAliasCount(doc, item, anchors); + if (c > count) + count = c; + } + return count; + } + else if (identity.isPair(node)) { + const kc = getAliasCount(doc, node.key, anchors); + const vc = getAliasCount(doc, node.value, anchors); + return Math.max(kc, vc); + } + return 1; +} + +exports.Alias = Alias; + + +/***/ }), + +/***/ 101: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var createNode = __nccwpck_require__(2404); +var identity = __nccwpck_require__(1127); +var Node = __nccwpck_require__(6673); + +function collectionFromPath(schema, path, value) { + let v = value; + for (let i = path.length - 1; i >= 0; --i) { + const k = path[i]; + if (typeof k === 'number' && Number.isInteger(k) && k >= 0) { + const a = []; + a[k] = v; + v = a; + } + else { + v = new Map([[k, v]]); + } + } + return createNode.createNode(v, undefined, { + aliasDuplicateObjects: false, + keepUndefined: false, + onAnchor: () => { + throw new Error('This should not happen, please report a bug.'); + }, + schema, + sourceObjects: new Map() + }); +} +// Type guard is intentionally a little wrong so as to be more useful, +// as it does not cover untypable empty non-string iterables (e.g. []). +const isEmptyPath = (path) => path == null || + (typeof path === 'object' && !!path[Symbol.iterator]().next().done); +class Collection extends Node.NodeBase { + constructor(type, schema) { + super(type); + Object.defineProperty(this, 'schema', { + value: schema, + configurable: true, + enumerable: false, + writable: true + }); + } + /** + * Create a copy of this collection. + * + * @param schema - If defined, overwrites the original's schema + */ + clone(schema) { + const copy = Object.create(Object.getPrototypeOf(this), Object.getOwnPropertyDescriptors(this)); + if (schema) + copy.schema = schema; + copy.items = copy.items.map(it => identity.isNode(it) || identity.isPair(it) ? it.clone(schema) : it); + if (this.range) + copy.range = this.range.slice(); + return copy; + } + /** + * Adds a value to the collection. For `!!map` and `!!omap` the value must + * be a Pair instance or a `{ key, value }` object, which may not have a key + * that already exists in the map. + */ + addIn(path, value) { + if (isEmptyPath(path)) + this.add(value); + else { + const [key, ...rest] = path; + const node = this.get(key, true); + if (identity.isCollection(node)) + node.addIn(rest, value); + else if (node === undefined && this.schema) + this.set(key, collectionFromPath(this.schema, rest, value)); + else + throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`); + } + } + /** + * Removes a value from the collection. + * @returns `true` if the item was found and removed. + */ + deleteIn(path) { + const [key, ...rest] = path; + if (rest.length === 0) + return this.delete(key); + const node = this.get(key, true); + if (identity.isCollection(node)) + return node.deleteIn(rest); + else + throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`); + } + /** + * Returns item at `key`, or `undefined` if not found. By default unwraps + * scalar values from their surrounding node; to disable set `keepScalar` to + * `true` (collections are always returned intact). + */ + getIn(path, keepScalar) { + const [key, ...rest] = path; + const node = this.get(key, true); + if (rest.length === 0) + return !keepScalar && identity.isScalar(node) ? node.value : node; + else + return identity.isCollection(node) ? node.getIn(rest, keepScalar) : undefined; + } + hasAllNullValues(allowScalar) { + return this.items.every(node => { + if (!identity.isPair(node)) + return false; + const n = node.value; + return (n == null || + (allowScalar && + identity.isScalar(n) && + n.value == null && + !n.commentBefore && + !n.comment && + !n.tag)); + }); + } + /** + * Checks if the collection includes a value with the key `key`. + */ + hasIn(path) { + const [key, ...rest] = path; + if (rest.length === 0) + return this.has(key); + const node = this.get(key, true); + return identity.isCollection(node) ? node.hasIn(rest) : false; + } + /** + * Sets a value in this collection. For `!!set`, `value` needs to be a + * boolean to add/remove the item from the set. + */ + setIn(path, value) { + const [key, ...rest] = path; + if (rest.length === 0) { + this.set(key, value); + } + else { + const node = this.get(key, true); + if (identity.isCollection(node)) + node.setIn(rest, value); + else if (node === undefined && this.schema) + this.set(key, collectionFromPath(this.schema, rest, value)); + else + throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`); + } + } +} + +exports.Collection = Collection; +exports.collectionFromPath = collectionFromPath; +exports.isEmptyPath = isEmptyPath; + + +/***/ }), + +/***/ 6673: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var applyReviver = __nccwpck_require__(3661); +var identity = __nccwpck_require__(1127); +var toJS = __nccwpck_require__(6424); + +class NodeBase { + constructor(type) { + Object.defineProperty(this, identity.NODE_TYPE, { value: type }); + } + /** Create a copy of this node. */ + clone() { + const copy = Object.create(Object.getPrototypeOf(this), Object.getOwnPropertyDescriptors(this)); + if (this.range) + copy.range = this.range.slice(); + return copy; + } + /** A plain JavaScript representation of this node. */ + toJS(doc, { mapAsMap, maxAliasCount, onAnchor, reviver } = {}) { + if (!identity.isDocument(doc)) + throw new TypeError('A document argument is required'); + const ctx = { + anchors: new Map(), + doc, + keep: true, + mapAsMap: mapAsMap === true, + mapKeyWarned: false, + maxAliasCount: typeof maxAliasCount === 'number' ? maxAliasCount : 100 + }; + const res = toJS.toJS(this, '', ctx); + if (typeof onAnchor === 'function') + for (const { count, res } of ctx.anchors.values()) + onAnchor(res, count); + return typeof reviver === 'function' + ? applyReviver.applyReviver(reviver, { '': res }, '', res) + : res; + } +} + +exports.NodeBase = NodeBase; + + +/***/ }), + +/***/ 7165: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var createNode = __nccwpck_require__(2404); +var stringifyPair = __nccwpck_require__(9748); +var addPairToJSMap = __nccwpck_require__(7104); +var identity = __nccwpck_require__(1127); + +function createPair(key, value, ctx) { + const k = createNode.createNode(key, undefined, ctx); + const v = createNode.createNode(value, undefined, ctx); + return new Pair(k, v); +} +class Pair { + constructor(key, value = null) { + Object.defineProperty(this, identity.NODE_TYPE, { value: identity.PAIR }); + this.key = key; + this.value = value; + } + clone(schema) { + let { key, value } = this; + if (identity.isNode(key)) + key = key.clone(schema); + if (identity.isNode(value)) + value = value.clone(schema); + return new Pair(key, value); + } + toJSON(_, ctx) { + const pair = ctx?.mapAsMap ? new Map() : {}; + return addPairToJSMap.addPairToJSMap(ctx, pair, this); + } + toString(ctx, onComment, onChompKeep) { + return ctx?.doc + ? stringifyPair.stringifyPair(this, ctx, onComment, onChompKeep) + : JSON.stringify(this); + } +} + +exports.Pair = Pair; +exports.createPair = createPair; + + +/***/ }), + +/***/ 3301: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var identity = __nccwpck_require__(1127); +var Node = __nccwpck_require__(6673); +var toJS = __nccwpck_require__(6424); + +const isScalarValue = (value) => !value || (typeof value !== 'function' && typeof value !== 'object'); +class Scalar extends Node.NodeBase { + constructor(value) { + super(identity.SCALAR); + this.value = value; + } + toJSON(arg, ctx) { + return ctx?.keep ? this.value : toJS.toJS(this.value, arg, ctx); + } + toString() { + return String(this.value); + } +} +Scalar.BLOCK_FOLDED = 'BLOCK_FOLDED'; +Scalar.BLOCK_LITERAL = 'BLOCK_LITERAL'; +Scalar.PLAIN = 'PLAIN'; +Scalar.QUOTE_DOUBLE = 'QUOTE_DOUBLE'; +Scalar.QUOTE_SINGLE = 'QUOTE_SINGLE'; + +exports.Scalar = Scalar; +exports.isScalarValue = isScalarValue; + + +/***/ }), + +/***/ 4454: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var stringifyCollection = __nccwpck_require__(1212); +var addPairToJSMap = __nccwpck_require__(7104); +var Collection = __nccwpck_require__(101); +var identity = __nccwpck_require__(1127); +var Pair = __nccwpck_require__(7165); +var Scalar = __nccwpck_require__(3301); + +function findPair(items, key) { + const k = identity.isScalar(key) ? key.value : key; + for (const it of items) { + if (identity.isPair(it)) { + if (it.key === key || it.key === k) + return it; + if (identity.isScalar(it.key) && it.key.value === k) + return it; + } + } + return undefined; +} +class YAMLMap extends Collection.Collection { + static get tagName() { + return 'tag:yaml.org,2002:map'; + } + constructor(schema) { + super(identity.MAP, schema); + this.items = []; + } + /** + * A generic collection parsing method that can be extended + * to other node classes that inherit from YAMLMap + */ + static from(schema, obj, ctx) { + const { keepUndefined, replacer } = ctx; + const map = new this(schema); + const add = (key, value) => { + if (typeof replacer === 'function') + value = replacer.call(obj, key, value); + else if (Array.isArray(replacer) && !replacer.includes(key)) + return; + if (value !== undefined || keepUndefined) + map.items.push(Pair.createPair(key, value, ctx)); + }; + if (obj instanceof Map) { + for (const [key, value] of obj) + add(key, value); + } + else if (obj && typeof obj === 'object') { + for (const key of Object.keys(obj)) + add(key, obj[key]); + } + if (typeof schema.sortMapEntries === 'function') { + map.items.sort(schema.sortMapEntries); + } + return map; + } + /** + * Adds a value to the collection. + * + * @param overwrite - If not set `true`, using a key that is already in the + * collection will throw. Otherwise, overwrites the previous value. + */ + add(pair, overwrite) { + let _pair; + if (identity.isPair(pair)) + _pair = pair; + else if (!pair || typeof pair !== 'object' || !('key' in pair)) { + // In TypeScript, this never happens. + _pair = new Pair.Pair(pair, pair?.value); + } + else + _pair = new Pair.Pair(pair.key, pair.value); + const prev = findPair(this.items, _pair.key); + const sortEntries = this.schema?.sortMapEntries; + if (prev) { + if (!overwrite) + throw new Error(`Key ${_pair.key} already set`); + // For scalars, keep the old node & its comments and anchors + if (identity.isScalar(prev.value) && Scalar.isScalarValue(_pair.value)) + prev.value.value = _pair.value; + else + prev.value = _pair.value; + } + else if (sortEntries) { + const i = this.items.findIndex(item => sortEntries(_pair, item) < 0); + if (i === -1) + this.items.push(_pair); + else + this.items.splice(i, 0, _pair); + } + else { + this.items.push(_pair); + } + } + delete(key) { + const it = findPair(this.items, key); + if (!it) + return false; + const del = this.items.splice(this.items.indexOf(it), 1); + return del.length > 0; + } + get(key, keepScalar) { + const it = findPair(this.items, key); + const node = it?.value; + return (!keepScalar && identity.isScalar(node) ? node.value : node) ?? undefined; + } + has(key) { + return !!findPair(this.items, key); + } + set(key, value) { + this.add(new Pair.Pair(key, value), true); + } + /** + * @param ctx - Conversion context, originally set in Document#toJS() + * @param {Class} Type - If set, forces the returned collection type + * @returns Instance of Type, Map, or Object + */ + toJSON(_, ctx, Type) { + const map = Type ? new Type() : ctx?.mapAsMap ? new Map() : {}; + if (ctx?.onCreate) + ctx.onCreate(map); + for (const item of this.items) + addPairToJSMap.addPairToJSMap(ctx, map, item); + return map; + } + toString(ctx, onComment, onChompKeep) { + if (!ctx) + return JSON.stringify(this); + for (const item of this.items) { + if (!identity.isPair(item)) + throw new Error(`Map items must all be pairs; found ${JSON.stringify(item)} instead`); + } + if (!ctx.allNullValues && this.hasAllNullValues(false)) + ctx = Object.assign({}, ctx, { allNullValues: true }); + return stringifyCollection.stringifyCollection(this, ctx, { + blockItemPrefix: '', + flowChars: { start: '{', end: '}' }, + itemIndent: ctx.indent || '', + onChompKeep, + onComment + }); + } +} + +exports.YAMLMap = YAMLMap; +exports.findPair = findPair; + + +/***/ }), + +/***/ 2223: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var createNode = __nccwpck_require__(2404); +var stringifyCollection = __nccwpck_require__(1212); +var Collection = __nccwpck_require__(101); +var identity = __nccwpck_require__(1127); +var Scalar = __nccwpck_require__(3301); +var toJS = __nccwpck_require__(6424); + +class YAMLSeq extends Collection.Collection { + static get tagName() { + return 'tag:yaml.org,2002:seq'; + } + constructor(schema) { + super(identity.SEQ, schema); + this.items = []; + } + add(value) { + this.items.push(value); + } + /** + * Removes a value from the collection. + * + * `key` must contain a representation of an integer for this to succeed. + * It may be wrapped in a `Scalar`. + * + * @returns `true` if the item was found and removed. + */ + delete(key) { + const idx = asItemIndex(key); + if (typeof idx !== 'number') + return false; + const del = this.items.splice(idx, 1); + return del.length > 0; + } + get(key, keepScalar) { + const idx = asItemIndex(key); + if (typeof idx !== 'number') + return undefined; + const it = this.items[idx]; + return !keepScalar && identity.isScalar(it) ? it.value : it; + } + /** + * Checks if the collection includes a value with the key `key`. + * + * `key` must contain a representation of an integer for this to succeed. + * It may be wrapped in a `Scalar`. + */ + has(key) { + const idx = asItemIndex(key); + return typeof idx === 'number' && idx < this.items.length; + } + /** + * Sets a value in this collection. For `!!set`, `value` needs to be a + * boolean to add/remove the item from the set. + * + * If `key` does not contain a representation of an integer, this will throw. + * It may be wrapped in a `Scalar`. + */ + set(key, value) { + const idx = asItemIndex(key); + if (typeof idx !== 'number') + throw new Error(`Expected a valid index, not ${key}.`); + const prev = this.items[idx]; + if (identity.isScalar(prev) && Scalar.isScalarValue(value)) + prev.value = value; + else + this.items[idx] = value; + } + toJSON(_, ctx) { + const seq = []; + if (ctx?.onCreate) + ctx.onCreate(seq); + let i = 0; + for (const item of this.items) + seq.push(toJS.toJS(item, String(i++), ctx)); + return seq; + } + toString(ctx, onComment, onChompKeep) { + if (!ctx) + return JSON.stringify(this); + return stringifyCollection.stringifyCollection(this, ctx, { + blockItemPrefix: '- ', + flowChars: { start: '[', end: ']' }, + itemIndent: (ctx.indent || '') + ' ', + onChompKeep, + onComment + }); + } + static from(schema, obj, ctx) { + const { replacer } = ctx; + const seq = new this(schema); + if (obj && Symbol.iterator in Object(obj)) { + let i = 0; + for (let it of obj) { + if (typeof replacer === 'function') { + const key = obj instanceof Set ? it : String(i++); + it = replacer.call(obj, key, it); + } + seq.items.push(createNode.createNode(it, undefined, ctx)); + } + } + return seq; + } +} +function asItemIndex(key) { + let idx = identity.isScalar(key) ? key.value : key; + if (idx && typeof idx === 'string') + idx = Number(idx); + return typeof idx === 'number' && Number.isInteger(idx) && idx >= 0 + ? idx + : null; +} + +exports.YAMLSeq = YAMLSeq; + + +/***/ }), + +/***/ 7104: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var log = __nccwpck_require__(7249); +var merge = __nccwpck_require__(452); +var stringify = __nccwpck_require__(2148); +var identity = __nccwpck_require__(1127); +var toJS = __nccwpck_require__(6424); + +function addPairToJSMap(ctx, map, { key, value }) { + if (identity.isNode(key) && key.addToJSMap) + key.addToJSMap(ctx, map, value); + // TODO: Should drop this special case for bare << handling + else if (merge.isMergeKey(ctx, key)) + merge.addMergeToJSMap(ctx, map, value); + else { + const jsKey = toJS.toJS(key, '', ctx); + if (map instanceof Map) { + map.set(jsKey, toJS.toJS(value, jsKey, ctx)); + } + else if (map instanceof Set) { + map.add(jsKey); + } + else { + const stringKey = stringifyKey(key, jsKey, ctx); + const jsValue = toJS.toJS(value, stringKey, ctx); + if (stringKey in map) + Object.defineProperty(map, stringKey, { + value: jsValue, + writable: true, + enumerable: true, + configurable: true + }); + else + map[stringKey] = jsValue; + } + } + return map; +} +function stringifyKey(key, jsKey, ctx) { + if (jsKey === null) + return ''; + // eslint-disable-next-line @typescript-eslint/no-base-to-string + if (typeof jsKey !== 'object') + return String(jsKey); + if (identity.isNode(key) && ctx?.doc) { + const strCtx = stringify.createStringifyContext(ctx.doc, {}); + strCtx.anchors = new Set(); + for (const node of ctx.anchors.keys()) + strCtx.anchors.add(node.anchor); + strCtx.inFlow = true; + strCtx.inStringifyKey = true; + const strKey = key.toString(strCtx); + if (!ctx.mapKeyWarned) { + let jsonStr = JSON.stringify(strKey); + if (jsonStr.length > 40) + jsonStr = jsonStr.substring(0, 36) + '..."'; + log.warn(ctx.doc.options.logLevel, `Keys with collection values will be stringified due to JS Object restrictions: ${jsonStr}. Set mapAsMap: true to use object keys.`); + ctx.mapKeyWarned = true; + } + return strKey; + } + return JSON.stringify(jsKey); +} + +exports.addPairToJSMap = addPairToJSMap; + + +/***/ }), + +/***/ 1127: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +const ALIAS = Symbol.for('yaml.alias'); +const DOC = Symbol.for('yaml.document'); +const MAP = Symbol.for('yaml.map'); +const PAIR = Symbol.for('yaml.pair'); +const SCALAR = Symbol.for('yaml.scalar'); +const SEQ = Symbol.for('yaml.seq'); +const NODE_TYPE = Symbol.for('yaml.node.type'); +const isAlias = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === ALIAS; +const isDocument = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === DOC; +const isMap = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === MAP; +const isPair = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === PAIR; +const isScalar = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === SCALAR; +const isSeq = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === SEQ; +function isCollection(node) { + if (node && typeof node === 'object') + switch (node[NODE_TYPE]) { + case MAP: + case SEQ: + return true; + } + return false; +} +function isNode(node) { + if (node && typeof node === 'object') + switch (node[NODE_TYPE]) { + case ALIAS: + case MAP: + case SCALAR: + case SEQ: + return true; + } + return false; +} +const hasAnchor = (node) => (isScalar(node) || isCollection(node)) && !!node.anchor; + +exports.ALIAS = ALIAS; +exports.DOC = DOC; +exports.MAP = MAP; +exports.NODE_TYPE = NODE_TYPE; +exports.PAIR = PAIR; +exports.SCALAR = SCALAR; +exports.SEQ = SEQ; +exports.hasAnchor = hasAnchor; +exports.isAlias = isAlias; +exports.isCollection = isCollection; +exports.isDocument = isDocument; +exports.isMap = isMap; +exports.isNode = isNode; +exports.isPair = isPair; +exports.isScalar = isScalar; +exports.isSeq = isSeq; + + +/***/ }), + +/***/ 6424: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var identity = __nccwpck_require__(1127); + +/** + * Recursively convert any node or its contents to native JavaScript + * + * @param value - The input value + * @param arg - If `value` defines a `toJSON()` method, use this + * as its first argument + * @param ctx - Conversion context, originally set in Document#toJS(). If + * `{ keep: true }` is not set, output should be suitable for JSON + * stringification. + */ +function toJS(value, arg, ctx) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + if (Array.isArray(value)) + return value.map((v, i) => toJS(v, String(i), ctx)); + if (value && typeof value.toJSON === 'function') { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + if (!ctx || !identity.hasAnchor(value)) + return value.toJSON(arg, ctx); + const data = { aliasCount: 0, count: 1, res: undefined }; + ctx.anchors.set(value, data); + ctx.onCreate = res => { + data.res = res; + delete ctx.onCreate; + }; + const res = value.toJSON(arg, ctx); + if (ctx.onCreate) + ctx.onCreate(res); + return res; + } + if (typeof value === 'bigint' && !ctx?.keep) + return Number(value); + return value; +} + +exports.toJS = toJS; + + +/***/ }), + +/***/ 110: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var resolveBlockScalar = __nccwpck_require__(8913); +var resolveFlowScalar = __nccwpck_require__(6842); +var errors = __nccwpck_require__(1464); +var stringifyString = __nccwpck_require__(3069); + +function resolveAsScalar(token, strict = true, onError) { + if (token) { + const _onError = (pos, code, message) => { + const offset = typeof pos === 'number' ? pos : Array.isArray(pos) ? pos[0] : pos.offset; + if (onError) + onError(offset, code, message); + else + throw new errors.YAMLParseError([offset, offset + 1], code, message); + }; + switch (token.type) { + case 'scalar': + case 'single-quoted-scalar': + case 'double-quoted-scalar': + return resolveFlowScalar.resolveFlowScalar(token, strict, _onError); + case 'block-scalar': + return resolveBlockScalar.resolveBlockScalar({ options: { strict } }, token, _onError); + } + } + return null; +} +/** + * Create a new scalar token with `value` + * + * Values that represent an actual string but may be parsed as a different type should use a `type` other than `'PLAIN'`, + * as this function does not support any schema operations and won't check for such conflicts. + * + * @param value The string representation of the value, which will have its content properly indented. + * @param context.end Comments and whitespace after the end of the value, or after the block scalar header. If undefined, a newline will be added. + * @param context.implicitKey Being within an implicit key may affect the resolved type of the token's value. + * @param context.indent The indent level of the token. + * @param context.inFlow Is this scalar within a flow collection? This may affect the resolved type of the token's value. + * @param context.offset The offset position of the token. + * @param context.type The preferred type of the scalar token. If undefined, the previous type of the `token` will be used, defaulting to `'PLAIN'`. + */ +function createScalarToken(value, context) { + const { implicitKey = false, indent, inFlow = false, offset = -1, type = 'PLAIN' } = context; + const source = stringifyString.stringifyString({ type, value }, { + implicitKey, + indent: indent > 0 ? ' '.repeat(indent) : '', + inFlow, + options: { blockQuote: true, lineWidth: -1 } + }); + const end = context.end ?? [ + { type: 'newline', offset: -1, indent, source: '\n' } + ]; + switch (source[0]) { + case '|': + case '>': { + const he = source.indexOf('\n'); + const head = source.substring(0, he); + const body = source.substring(he + 1) + '\n'; + const props = [ + { type: 'block-scalar-header', offset, indent, source: head } + ]; + if (!addEndtoBlockProps(props, end)) + props.push({ type: 'newline', offset: -1, indent, source: '\n' }); + return { type: 'block-scalar', offset, indent, props, source: body }; + } + case '"': + return { type: 'double-quoted-scalar', offset, indent, source, end }; + case "'": + return { type: 'single-quoted-scalar', offset, indent, source, end }; + default: + return { type: 'scalar', offset, indent, source, end }; + } +} +/** + * Set the value of `token` to the given string `value`, overwriting any previous contents and type that it may have. + * + * Best efforts are made to retain any comments previously associated with the `token`, + * though all contents within a collection's `items` will be overwritten. + * + * Values that represent an actual string but may be parsed as a different type should use a `type` other than `'PLAIN'`, + * as this function does not support any schema operations and won't check for such conflicts. + * + * @param token Any token. If it does not include an `indent` value, the value will be stringified as if it were an implicit key. + * @param value The string representation of the value, which will have its content properly indented. + * @param context.afterKey In most cases, values after a key should have an additional level of indentation. + * @param context.implicitKey Being within an implicit key may affect the resolved type of the token's value. + * @param context.inFlow Being within a flow collection may affect the resolved type of the token's value. + * @param context.type The preferred type of the scalar token. If undefined, the previous type of the `token` will be used, defaulting to `'PLAIN'`. + */ +function setScalarValue(token, value, context = {}) { + let { afterKey = false, implicitKey = false, inFlow = false, type } = context; + let indent = 'indent' in token ? token.indent : null; + if (afterKey && typeof indent === 'number') + indent += 2; + if (!type) + switch (token.type) { + case 'single-quoted-scalar': + type = 'QUOTE_SINGLE'; + break; + case 'double-quoted-scalar': + type = 'QUOTE_DOUBLE'; + break; + case 'block-scalar': { + const header = token.props[0]; + if (header.type !== 'block-scalar-header') + throw new Error('Invalid block scalar header'); + type = header.source[0] === '>' ? 'BLOCK_FOLDED' : 'BLOCK_LITERAL'; + break; + } + default: + type = 'PLAIN'; + } + const source = stringifyString.stringifyString({ type, value }, { + implicitKey: implicitKey || indent === null, + indent: indent !== null && indent > 0 ? ' '.repeat(indent) : '', + inFlow, + options: { blockQuote: true, lineWidth: -1 } + }); + switch (source[0]) { + case '|': + case '>': + setBlockScalarValue(token, source); + break; + case '"': + setFlowScalarValue(token, source, 'double-quoted-scalar'); + break; + case "'": + setFlowScalarValue(token, source, 'single-quoted-scalar'); + break; + default: + setFlowScalarValue(token, source, 'scalar'); + } +} +function setBlockScalarValue(token, source) { + const he = source.indexOf('\n'); + const head = source.substring(0, he); + const body = source.substring(he + 1) + '\n'; + if (token.type === 'block-scalar') { + const header = token.props[0]; + if (header.type !== 'block-scalar-header') + throw new Error('Invalid block scalar header'); + header.source = head; + token.source = body; + } + else { + const { offset } = token; + const indent = 'indent' in token ? token.indent : -1; + const props = [ + { type: 'block-scalar-header', offset, indent, source: head } + ]; + if (!addEndtoBlockProps(props, 'end' in token ? token.end : undefined)) + props.push({ type: 'newline', offset: -1, indent, source: '\n' }); + for (const key of Object.keys(token)) + if (key !== 'type' && key !== 'offset') + delete token[key]; + Object.assign(token, { type: 'block-scalar', indent, props, source: body }); + } +} +/** @returns `true` if last token is a newline */ +function addEndtoBlockProps(props, end) { + if (end) + for (const st of end) + switch (st.type) { + case 'space': + case 'comment': + props.push(st); + break; + case 'newline': + props.push(st); + return true; + } + return false; +} +function setFlowScalarValue(token, source, type) { + switch (token.type) { + case 'scalar': + case 'double-quoted-scalar': + case 'single-quoted-scalar': + token.type = type; + token.source = source; + break; + case 'block-scalar': { + const end = token.props.slice(1); + let oa = source.length; + if (token.props[0].type === 'block-scalar-header') + oa -= token.props[0].source.length; + for (const tok of end) + tok.offset += oa; + delete token.props; + Object.assign(token, { type, source, end }); + break; + } + case 'block-map': + case 'block-seq': { + const offset = token.offset + source.length; + const nl = { type: 'newline', offset, indent: token.indent, source: '\n' }; + delete token.items; + Object.assign(token, { type, source, end: [nl] }); + break; + } + default: { + const indent = 'indent' in token ? token.indent : -1; + const end = 'end' in token && Array.isArray(token.end) + ? token.end.filter(st => st.type === 'space' || + st.type === 'comment' || + st.type === 'newline') + : []; + for (const key of Object.keys(token)) + if (key !== 'type' && key !== 'offset') + delete token[key]; + Object.assign(token, { type, indent, source, end }); + } + } +} + +exports.createScalarToken = createScalarToken; +exports.resolveAsScalar = resolveAsScalar; +exports.setScalarValue = setScalarValue; + + +/***/ }), + +/***/ 1733: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +/** + * Stringify a CST document, token, or collection item + * + * Fair warning: This applies no validation whatsoever, and + * simply concatenates the sources in their logical order. + */ +const stringify = (cst) => 'type' in cst ? stringifyToken(cst) : stringifyItem(cst); +function stringifyToken(token) { + switch (token.type) { + case 'block-scalar': { + let res = ''; + for (const tok of token.props) + res += stringifyToken(tok); + return res + token.source; + } + case 'block-map': + case 'block-seq': { + let res = ''; + for (const item of token.items) + res += stringifyItem(item); + return res; + } + case 'flow-collection': { + let res = token.start.source; + for (const item of token.items) + res += stringifyItem(item); + for (const st of token.end) + res += st.source; + return res; + } + case 'document': { + let res = stringifyItem(token); + if (token.end) + for (const st of token.end) + res += st.source; + return res; + } + default: { + let res = token.source; + if ('end' in token && token.end) + for (const st of token.end) + res += st.source; + return res; + } + } +} +function stringifyItem({ start, key, sep, value }) { + let res = ''; + for (const st of start) + res += st.source; + if (key) + res += stringifyToken(key); + if (sep) + for (const st of sep) + res += st.source; + if (value) + res += stringifyToken(value); + return res; +} + +exports.stringify = stringify; + + +/***/ }), + +/***/ 7715: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +const BREAK = Symbol('break visit'); +const SKIP = Symbol('skip children'); +const REMOVE = Symbol('remove item'); +/** + * Apply a visitor to a CST document or item. + * + * Walks through the tree (depth-first) starting from the root, calling a + * `visitor` function with two arguments when entering each item: + * - `item`: The current item, which included the following members: + * - `start: SourceToken[]` – Source tokens before the key or value, + * possibly including its anchor or tag. + * - `key?: Token | null` – Set for pair values. May then be `null`, if + * the key before the `:` separator is empty. + * - `sep?: SourceToken[]` – Source tokens between the key and the value, + * which should include the `:` map value indicator if `value` is set. + * - `value?: Token` – The value of a sequence item, or of a map pair. + * - `path`: The steps from the root to the current node, as an array of + * `['key' | 'value', number]` tuples. + * + * The return value of the visitor may be used to control the traversal: + * - `undefined` (default): Do nothing and continue + * - `visit.SKIP`: Do not visit the children of this token, continue with + * next sibling + * - `visit.BREAK`: Terminate traversal completely + * - `visit.REMOVE`: Remove the current item, then continue with the next one + * - `number`: Set the index of the next step. This is useful especially if + * the index of the current token has changed. + * - `function`: Define the next visitor for this item. After the original + * visitor is called on item entry, next visitors are called after handling + * a non-empty `key` and when exiting the item. + */ +function visit(cst, visitor) { + if ('type' in cst && cst.type === 'document') + cst = { start: cst.start, value: cst.value }; + _visit(Object.freeze([]), cst, visitor); +} +// Without the `as symbol` casts, TS declares these in the `visit` +// namespace using `var`, but then complains about that because +// `unique symbol` must be `const`. +/** Terminate visit traversal completely */ +visit.BREAK = BREAK; +/** Do not visit the children of the current item */ +visit.SKIP = SKIP; +/** Remove the current item */ +visit.REMOVE = REMOVE; +/** Find the item at `path` from `cst` as the root */ +visit.itemAtPath = (cst, path) => { + let item = cst; + for (const [field, index] of path) { + const tok = item?.[field]; + if (tok && 'items' in tok) { + item = tok.items[index]; + } + else + return undefined; + } + return item; +}; +/** + * Get the immediate parent collection of the item at `path` from `cst` as the root. + * + * Throws an error if the collection is not found, which should never happen if the item itself exists. + */ +visit.parentCollection = (cst, path) => { + const parent = visit.itemAtPath(cst, path.slice(0, -1)); + const field = path[path.length - 1][0]; + const coll = parent?.[field]; + if (coll && 'items' in coll) + return coll; + throw new Error('Parent collection not found'); +}; +function _visit(path, item, visitor) { + let ctrl = visitor(item, path); + if (typeof ctrl === 'symbol') + return ctrl; + for (const field of ['key', 'value']) { + const token = item[field]; + if (token && 'items' in token) { + for (let i = 0; i < token.items.length; ++i) { + const ci = _visit(Object.freeze(path.concat([[field, i]])), token.items[i], visitor); + if (typeof ci === 'number') + i = ci - 1; + else if (ci === BREAK) + return BREAK; + else if (ci === REMOVE) { + token.items.splice(i, 1); + i -= 1; + } + } + if (typeof ctrl === 'function' && field === 'key') + ctrl = ctrl(item, path); + } + } + return typeof ctrl === 'function' ? ctrl(item, path) : ctrl; +} + +exports.visit = visit; + + +/***/ }), + +/***/ 3461: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var cstScalar = __nccwpck_require__(110); +var cstStringify = __nccwpck_require__(1733); +var cstVisit = __nccwpck_require__(7715); + +/** The byte order mark */ +const BOM = '\u{FEFF}'; +/** Start of doc-mode */ +const DOCUMENT = '\x02'; // C0: Start of Text +/** Unexpected end of flow-mode */ +const FLOW_END = '\x18'; // C0: Cancel +/** Next token is a scalar value */ +const SCALAR = '\x1f'; // C0: Unit Separator +/** @returns `true` if `token` is a flow or block collection */ +const isCollection = (token) => !!token && 'items' in token; +/** @returns `true` if `token` is a flow or block scalar; not an alias */ +const isScalar = (token) => !!token && + (token.type === 'scalar' || + token.type === 'single-quoted-scalar' || + token.type === 'double-quoted-scalar' || + token.type === 'block-scalar'); +/* istanbul ignore next */ +/** Get a printable representation of a lexer token */ +function prettyToken(token) { + switch (token) { + case BOM: + return ''; + case DOCUMENT: + return ''; + case FLOW_END: + return ''; + case SCALAR: + return ''; + default: + return JSON.stringify(token); + } +} +/** Identify the type of a lexer token. May return `null` for unknown tokens. */ +function tokenType(source) { + switch (source) { + case BOM: + return 'byte-order-mark'; + case DOCUMENT: + return 'doc-mode'; + case FLOW_END: + return 'flow-error-end'; + case SCALAR: + return 'scalar'; + case '---': + return 'doc-start'; + case '...': + return 'doc-end'; + case '': + case '\n': + case '\r\n': + return 'newline'; + case '-': + return 'seq-item-ind'; + case '?': + return 'explicit-key-ind'; + case ':': + return 'map-value-ind'; + case '{': + return 'flow-map-start'; + case '}': + return 'flow-map-end'; + case '[': + return 'flow-seq-start'; + case ']': + return 'flow-seq-end'; + case ',': + return 'comma'; + } + switch (source[0]) { + case ' ': + case '\t': + return 'space'; + case '#': + return 'comment'; + case '%': + return 'directive-line'; + case '*': + return 'alias'; + case '&': + return 'anchor'; + case '!': + return 'tag'; + case "'": + return 'single-quoted-scalar'; + case '"': + return 'double-quoted-scalar'; + case '|': + case '>': + return 'block-scalar-header'; + } + return null; +} + +exports.createScalarToken = cstScalar.createScalarToken; +exports.resolveAsScalar = cstScalar.resolveAsScalar; +exports.setScalarValue = cstScalar.setScalarValue; +exports.stringify = cstStringify.stringify; +exports.visit = cstVisit.visit; +exports.BOM = BOM; +exports.DOCUMENT = DOCUMENT; +exports.FLOW_END = FLOW_END; +exports.SCALAR = SCALAR; +exports.isCollection = isCollection; +exports.isScalar = isScalar; +exports.prettyToken = prettyToken; +exports.tokenType = tokenType; + + +/***/ }), + +/***/ 361: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var cst = __nccwpck_require__(3461); + +/* +START -> stream + +stream + directive -> line-end -> stream + indent + line-end -> stream + [else] -> line-start + +line-end + comment -> line-end + newline -> . + input-end -> END + +line-start + doc-start -> doc + doc-end -> stream + [else] -> indent -> block-start + +block-start + seq-item-start -> block-start + explicit-key-start -> block-start + map-value-start -> block-start + [else] -> doc + +doc + line-end -> line-start + spaces -> doc + anchor -> doc + tag -> doc + flow-start -> flow -> doc + flow-end -> error -> doc + seq-item-start -> error -> doc + explicit-key-start -> error -> doc + map-value-start -> doc + alias -> doc + quote-start -> quoted-scalar -> doc + block-scalar-header -> line-end -> block-scalar(min) -> line-start + [else] -> plain-scalar(false, min) -> doc + +flow + line-end -> flow + spaces -> flow + anchor -> flow + tag -> flow + flow-start -> flow -> flow + flow-end -> . + seq-item-start -> error -> flow + explicit-key-start -> flow + map-value-start -> flow + alias -> flow + quote-start -> quoted-scalar -> flow + comma -> flow + [else] -> plain-scalar(true, 0) -> flow + +quoted-scalar + quote-end -> . + [else] -> quoted-scalar + +block-scalar(min) + newline + peek(indent < min) -> . + [else] -> block-scalar(min) + +plain-scalar(is-flow, min) + scalar-end(is-flow) -> . + peek(newline + (indent < min)) -> . + [else] -> plain-scalar(min) +*/ +function isEmpty(ch) { + switch (ch) { + case undefined: + case ' ': + case '\n': + case '\r': + case '\t': + return true; + default: + return false; + } +} +const hexDigits = new Set('0123456789ABCDEFabcdef'); +const tagChars = new Set("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-#;/?:@&=+$_.!~*'()"); +const flowIndicatorChars = new Set(',[]{}'); +const invalidAnchorChars = new Set(' ,[]{}\n\r\t'); +const isNotAnchorChar = (ch) => !ch || invalidAnchorChars.has(ch); +/** + * Splits an input string into lexical tokens, i.e. smaller strings that are + * easily identifiable by `tokens.tokenType()`. + * + * Lexing starts always in a "stream" context. Incomplete input may be buffered + * until a complete token can be emitted. + * + * In addition to slices of the original input, the following control characters + * may also be emitted: + * + * - `\x02` (Start of Text): A document starts with the next token + * - `\x18` (Cancel): Unexpected end of flow-mode (indicates an error) + * - `\x1f` (Unit Separator): Next token is a scalar value + * - `\u{FEFF}` (Byte order mark): Emitted separately outside documents + */ +class Lexer { + constructor() { + /** + * Flag indicating whether the end of the current buffer marks the end of + * all input + */ + this.atEnd = false; + /** + * Explicit indent set in block scalar header, as an offset from the current + * minimum indent, so e.g. set to 1 from a header `|2+`. Set to -1 if not + * explicitly set. + */ + this.blockScalarIndent = -1; + /** + * Block scalars that include a + (keep) chomping indicator in their header + * include trailing empty lines, which are otherwise excluded from the + * scalar's contents. + */ + this.blockScalarKeep = false; + /** Current input */ + this.buffer = ''; + /** + * Flag noting whether the map value indicator : can immediately follow this + * node within a flow context. + */ + this.flowKey = false; + /** Count of surrounding flow collection levels. */ + this.flowLevel = 0; + /** + * Minimum level of indentation required for next lines to be parsed as a + * part of the current scalar value. + */ + this.indentNext = 0; + /** Indentation level of the current line. */ + this.indentValue = 0; + /** Position of the next \n character. */ + this.lineEndPos = null; + /** Stores the state of the lexer if reaching the end of incpomplete input */ + this.next = null; + /** A pointer to `buffer`; the current position of the lexer. */ + this.pos = 0; + } + /** + * Generate YAML tokens from the `source` string. If `incomplete`, + * a part of the last line may be left as a buffer for the next call. + * + * @returns A generator of lexical tokens + */ + *lex(source, incomplete = false) { + if (source) { + if (typeof source !== 'string') + throw TypeError('source is not a string'); + this.buffer = this.buffer ? this.buffer + source : source; + this.lineEndPos = null; + } + this.atEnd = !incomplete; + let next = this.next ?? 'stream'; + while (next && (incomplete || this.hasChars(1))) + next = yield* this.parseNext(next); + } + atLineEnd() { + let i = this.pos; + let ch = this.buffer[i]; + while (ch === ' ' || ch === '\t') + ch = this.buffer[++i]; + if (!ch || ch === '#' || ch === '\n') + return true; + if (ch === '\r') + return this.buffer[i + 1] === '\n'; + return false; + } + charAt(n) { + return this.buffer[this.pos + n]; + } + continueScalar(offset) { + let ch = this.buffer[offset]; + if (this.indentNext > 0) { + let indent = 0; + while (ch === ' ') + ch = this.buffer[++indent + offset]; + if (ch === '\r') { + const next = this.buffer[indent + offset + 1]; + if (next === '\n' || (!next && !this.atEnd)) + return offset + indent + 1; + } + return ch === '\n' || indent >= this.indentNext || (!ch && !this.atEnd) + ? offset + indent + : -1; + } + if (ch === '-' || ch === '.') { + const dt = this.buffer.substr(offset, 3); + if ((dt === '---' || dt === '...') && isEmpty(this.buffer[offset + 3])) + return -1; + } + return offset; + } + getLine() { + let end = this.lineEndPos; + if (typeof end !== 'number' || (end !== -1 && end < this.pos)) { + end = this.buffer.indexOf('\n', this.pos); + this.lineEndPos = end; + } + if (end === -1) + return this.atEnd ? this.buffer.substring(this.pos) : null; + if (this.buffer[end - 1] === '\r') + end -= 1; + return this.buffer.substring(this.pos, end); + } + hasChars(n) { + return this.pos + n <= this.buffer.length; + } + setNext(state) { + this.buffer = this.buffer.substring(this.pos); + this.pos = 0; + this.lineEndPos = null; + this.next = state; + return null; + } + peek(n) { + return this.buffer.substr(this.pos, n); + } + *parseNext(next) { + switch (next) { + case 'stream': + return yield* this.parseStream(); + case 'line-start': + return yield* this.parseLineStart(); + case 'block-start': + return yield* this.parseBlockStart(); + case 'doc': + return yield* this.parseDocument(); + case 'flow': + return yield* this.parseFlowCollection(); + case 'quoted-scalar': + return yield* this.parseQuotedScalar(); + case 'block-scalar': + return yield* this.parseBlockScalar(); + case 'plain-scalar': + return yield* this.parsePlainScalar(); + } + } + *parseStream() { + let line = this.getLine(); + if (line === null) + return this.setNext('stream'); + if (line[0] === cst.BOM) { + yield* this.pushCount(1); + line = line.substring(1); + } + if (line[0] === '%') { + let dirEnd = line.length; + let cs = line.indexOf('#'); + while (cs !== -1) { + const ch = line[cs - 1]; + if (ch === ' ' || ch === '\t') { + dirEnd = cs - 1; + break; + } + else { + cs = line.indexOf('#', cs + 1); + } + } + while (true) { + const ch = line[dirEnd - 1]; + if (ch === ' ' || ch === '\t') + dirEnd -= 1; + else + break; + } + const n = (yield* this.pushCount(dirEnd)) + (yield* this.pushSpaces(true)); + yield* this.pushCount(line.length - n); // possible comment + this.pushNewline(); + return 'stream'; + } + if (this.atLineEnd()) { + const sp = yield* this.pushSpaces(true); + yield* this.pushCount(line.length - sp); + yield* this.pushNewline(); + return 'stream'; + } + yield cst.DOCUMENT; + return yield* this.parseLineStart(); + } + *parseLineStart() { + const ch = this.charAt(0); + if (!ch && !this.atEnd) + return this.setNext('line-start'); + if (ch === '-' || ch === '.') { + if (!this.atEnd && !this.hasChars(4)) + return this.setNext('line-start'); + const s = this.peek(3); + if ((s === '---' || s === '...') && isEmpty(this.charAt(3))) { + yield* this.pushCount(3); + this.indentValue = 0; + this.indentNext = 0; + return s === '---' ? 'doc' : 'stream'; + } + } + this.indentValue = yield* this.pushSpaces(false); + if (this.indentNext > this.indentValue && !isEmpty(this.charAt(1))) + this.indentNext = this.indentValue; + return yield* this.parseBlockStart(); + } + *parseBlockStart() { + const [ch0, ch1] = this.peek(2); + if (!ch1 && !this.atEnd) + return this.setNext('block-start'); + if ((ch0 === '-' || ch0 === '?' || ch0 === ':') && isEmpty(ch1)) { + const n = (yield* this.pushCount(1)) + (yield* this.pushSpaces(true)); + this.indentNext = this.indentValue + 1; + this.indentValue += n; + return yield* this.parseBlockStart(); + } + return 'doc'; + } + *parseDocument() { + yield* this.pushSpaces(true); + const line = this.getLine(); + if (line === null) + return this.setNext('doc'); + let n = yield* this.pushIndicators(); + switch (line[n]) { + case '#': + yield* this.pushCount(line.length - n); + // fallthrough + case undefined: + yield* this.pushNewline(); + return yield* this.parseLineStart(); + case '{': + case '[': + yield* this.pushCount(1); + this.flowKey = false; + this.flowLevel = 1; + return 'flow'; + case '}': + case ']': + // this is an error + yield* this.pushCount(1); + return 'doc'; + case '*': + yield* this.pushUntil(isNotAnchorChar); + return 'doc'; + case '"': + case "'": + return yield* this.parseQuotedScalar(); + case '|': + case '>': + n += yield* this.parseBlockScalarHeader(); + n += yield* this.pushSpaces(true); + yield* this.pushCount(line.length - n); + yield* this.pushNewline(); + return yield* this.parseBlockScalar(); + default: + return yield* this.parsePlainScalar(); + } + } + *parseFlowCollection() { + let nl, sp; + let indent = -1; + do { + nl = yield* this.pushNewline(); + if (nl > 0) { + sp = yield* this.pushSpaces(false); + this.indentValue = indent = sp; + } + else { + sp = 0; + } + sp += yield* this.pushSpaces(true); + } while (nl + sp > 0); + const line = this.getLine(); + if (line === null) + return this.setNext('flow'); + if ((indent !== -1 && indent < this.indentNext && line[0] !== '#') || + (indent === 0 && + (line.startsWith('---') || line.startsWith('...')) && + isEmpty(line[3]))) { + // Allowing for the terminal ] or } at the same (rather than greater) + // indent level as the initial [ or { is technically invalid, but + // failing here would be surprising to users. + const atFlowEndMarker = indent === this.indentNext - 1 && + this.flowLevel === 1 && + (line[0] === ']' || line[0] === '}'); + if (!atFlowEndMarker) { + // this is an error + this.flowLevel = 0; + yield cst.FLOW_END; + return yield* this.parseLineStart(); + } + } + let n = 0; + while (line[n] === ',') { + n += yield* this.pushCount(1); + n += yield* this.pushSpaces(true); + this.flowKey = false; + } + n += yield* this.pushIndicators(); + switch (line[n]) { + case undefined: + return 'flow'; + case '#': + yield* this.pushCount(line.length - n); + return 'flow'; + case '{': + case '[': + yield* this.pushCount(1); + this.flowKey = false; + this.flowLevel += 1; + return 'flow'; + case '}': + case ']': + yield* this.pushCount(1); + this.flowKey = true; + this.flowLevel -= 1; + return this.flowLevel ? 'flow' : 'doc'; + case '*': + yield* this.pushUntil(isNotAnchorChar); + return 'flow'; + case '"': + case "'": + this.flowKey = true; + return yield* this.parseQuotedScalar(); + case ':': { + const next = this.charAt(1); + if (this.flowKey || isEmpty(next) || next === ',') { + this.flowKey = false; + yield* this.pushCount(1); + yield* this.pushSpaces(true); + return 'flow'; + } + } + // fallthrough + default: + this.flowKey = false; + return yield* this.parsePlainScalar(); + } + } + *parseQuotedScalar() { + const quote = this.charAt(0); + let end = this.buffer.indexOf(quote, this.pos + 1); + if (quote === "'") { + while (end !== -1 && this.buffer[end + 1] === "'") + end = this.buffer.indexOf("'", end + 2); + } + else { + // double-quote + while (end !== -1) { + let n = 0; + while (this.buffer[end - 1 - n] === '\\') + n += 1; + if (n % 2 === 0) + break; + end = this.buffer.indexOf('"', end + 1); + } + } + // Only looking for newlines within the quotes + const qb = this.buffer.substring(0, end); + let nl = qb.indexOf('\n', this.pos); + if (nl !== -1) { + while (nl !== -1) { + const cs = this.continueScalar(nl + 1); + if (cs === -1) + break; + nl = qb.indexOf('\n', cs); + } + if (nl !== -1) { + // this is an error caused by an unexpected unindent + end = nl - (qb[nl - 1] === '\r' ? 2 : 1); + } + } + if (end === -1) { + if (!this.atEnd) + return this.setNext('quoted-scalar'); + end = this.buffer.length; + } + yield* this.pushToIndex(end + 1, false); + return this.flowLevel ? 'flow' : 'doc'; + } + *parseBlockScalarHeader() { + this.blockScalarIndent = -1; + this.blockScalarKeep = false; + let i = this.pos; + while (true) { + const ch = this.buffer[++i]; + if (ch === '+') + this.blockScalarKeep = true; + else if (ch > '0' && ch <= '9') + this.blockScalarIndent = Number(ch) - 1; + else if (ch !== '-') + break; + } + return yield* this.pushUntil(ch => isEmpty(ch) || ch === '#'); + } + *parseBlockScalar() { + let nl = this.pos - 1; // may be -1 if this.pos === 0 + let indent = 0; + let ch; + loop: for (let i = this.pos; (ch = this.buffer[i]); ++i) { + switch (ch) { + case ' ': + indent += 1; + break; + case '\n': + nl = i; + indent = 0; + break; + case '\r': { + const next = this.buffer[i + 1]; + if (!next && !this.atEnd) + return this.setNext('block-scalar'); + if (next === '\n') + break; + } // fallthrough + default: + break loop; + } + } + if (!ch && !this.atEnd) + return this.setNext('block-scalar'); + if (indent >= this.indentNext) { + if (this.blockScalarIndent === -1) + this.indentNext = indent; + else { + this.indentNext = + this.blockScalarIndent + (this.indentNext === 0 ? 1 : this.indentNext); + } + do { + const cs = this.continueScalar(nl + 1); + if (cs === -1) + break; + nl = this.buffer.indexOf('\n', cs); + } while (nl !== -1); + if (nl === -1) { + if (!this.atEnd) + return this.setNext('block-scalar'); + nl = this.buffer.length; + } + } + // Trailing insufficiently indented tabs are invalid. + // To catch that during parsing, we include them in the block scalar value. + let i = nl + 1; + ch = this.buffer[i]; + while (ch === ' ') + ch = this.buffer[++i]; + if (ch === '\t') { + while (ch === '\t' || ch === ' ' || ch === '\r' || ch === '\n') + ch = this.buffer[++i]; + nl = i - 1; + } + else if (!this.blockScalarKeep) { + do { + let i = nl - 1; + let ch = this.buffer[i]; + if (ch === '\r') + ch = this.buffer[--i]; + const lastChar = i; // Drop the line if last char not more indented + while (ch === ' ') + ch = this.buffer[--i]; + if (ch === '\n' && i >= this.pos && i + 1 + indent > lastChar) + nl = i; + else + break; + } while (true); + } + yield cst.SCALAR; + yield* this.pushToIndex(nl + 1, true); + return yield* this.parseLineStart(); + } + *parsePlainScalar() { + const inFlow = this.flowLevel > 0; + let end = this.pos - 1; + let i = this.pos - 1; + let ch; + while ((ch = this.buffer[++i])) { + if (ch === ':') { + const next = this.buffer[i + 1]; + if (isEmpty(next) || (inFlow && flowIndicatorChars.has(next))) + break; + end = i; + } + else if (isEmpty(ch)) { + let next = this.buffer[i + 1]; + if (ch === '\r') { + if (next === '\n') { + i += 1; + ch = '\n'; + next = this.buffer[i + 1]; + } + else + end = i; + } + if (next === '#' || (inFlow && flowIndicatorChars.has(next))) + break; + if (ch === '\n') { + const cs = this.continueScalar(i + 1); + if (cs === -1) + break; + i = Math.max(i, cs - 2); // to advance, but still account for ' #' + } + } + else { + if (inFlow && flowIndicatorChars.has(ch)) + break; + end = i; + } + } + if (!ch && !this.atEnd) + return this.setNext('plain-scalar'); + yield cst.SCALAR; + yield* this.pushToIndex(end + 1, true); + return inFlow ? 'flow' : 'doc'; + } + *pushCount(n) { + if (n > 0) { + yield this.buffer.substr(this.pos, n); + this.pos += n; + return n; + } + return 0; + } + *pushToIndex(i, allowEmpty) { + const s = this.buffer.slice(this.pos, i); + if (s) { + yield s; + this.pos += s.length; + return s.length; + } + else if (allowEmpty) + yield ''; + return 0; + } + *pushIndicators() { + switch (this.charAt(0)) { + case '!': + return ((yield* this.pushTag()) + + (yield* this.pushSpaces(true)) + + (yield* this.pushIndicators())); + case '&': + return ((yield* this.pushUntil(isNotAnchorChar)) + + (yield* this.pushSpaces(true)) + + (yield* this.pushIndicators())); + case '-': // this is an error + case '?': // this is an error outside flow collections + case ':': { + const inFlow = this.flowLevel > 0; + const ch1 = this.charAt(1); + if (isEmpty(ch1) || (inFlow && flowIndicatorChars.has(ch1))) { + if (!inFlow) + this.indentNext = this.indentValue + 1; + else if (this.flowKey) + this.flowKey = false; + return ((yield* this.pushCount(1)) + + (yield* this.pushSpaces(true)) + + (yield* this.pushIndicators())); + } + } + } + return 0; + } + *pushTag() { + if (this.charAt(1) === '<') { + let i = this.pos + 2; + let ch = this.buffer[i]; + while (!isEmpty(ch) && ch !== '>') + ch = this.buffer[++i]; + return yield* this.pushToIndex(ch === '>' ? i + 1 : i, false); + } + else { + let i = this.pos + 1; + let ch = this.buffer[i]; + while (ch) { + if (tagChars.has(ch)) + ch = this.buffer[++i]; + else if (ch === '%' && + hexDigits.has(this.buffer[i + 1]) && + hexDigits.has(this.buffer[i + 2])) { + ch = this.buffer[(i += 3)]; + } + else + break; + } + return yield* this.pushToIndex(i, false); + } + } + *pushNewline() { + const ch = this.buffer[this.pos]; + if (ch === '\n') + return yield* this.pushCount(1); + else if (ch === '\r' && this.charAt(1) === '\n') + return yield* this.pushCount(2); + else + return 0; + } + *pushSpaces(allowTabs) { + let i = this.pos - 1; + let ch; + do { + ch = this.buffer[++i]; + } while (ch === ' ' || (allowTabs && ch === '\t')); + const n = i - this.pos; + if (n > 0) { + yield this.buffer.substr(this.pos, n); + this.pos = i; + } + return n; + } + *pushUntil(test) { + let i = this.pos; + let ch = this.buffer[i]; + while (!test(ch)) + ch = this.buffer[++i]; + return yield* this.pushToIndex(i, false); + } +} + +exports.Lexer = Lexer; + + +/***/ }), + +/***/ 6628: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +/** + * Tracks newlines during parsing in order to provide an efficient API for + * determining the one-indexed `{ line, col }` position for any offset + * within the input. + */ +class LineCounter { + constructor() { + this.lineStarts = []; + /** + * Should be called in ascending order. Otherwise, call + * `lineCounter.lineStarts.sort()` before calling `linePos()`. + */ + this.addNewLine = (offset) => this.lineStarts.push(offset); + /** + * Performs a binary search and returns the 1-indexed { line, col } + * position of `offset`. If `line === 0`, `addNewLine` has never been + * called or `offset` is before the first known newline. + */ + this.linePos = (offset) => { + let low = 0; + let high = this.lineStarts.length; + while (low < high) { + const mid = (low + high) >> 1; // Math.floor((low + high) / 2) + if (this.lineStarts[mid] < offset) + low = mid + 1; + else + high = mid; + } + if (this.lineStarts[low] === offset) + return { line: low + 1, col: 1 }; + if (low === 0) + return { line: 0, col: offset }; + const start = this.lineStarts[low - 1]; + return { line: low, col: offset - start + 1 }; + }; + } +} + +exports.LineCounter = LineCounter; + + +/***/ }), + +/***/ 3456: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var node_process = __nccwpck_require__(932); +var cst = __nccwpck_require__(3461); +var lexer = __nccwpck_require__(361); + +function includesToken(list, type) { + for (let i = 0; i < list.length; ++i) + if (list[i].type === type) + return true; + return false; +} +function findNonEmptyIndex(list) { + for (let i = 0; i < list.length; ++i) { + switch (list[i].type) { + case 'space': + case 'comment': + case 'newline': + break; + default: + return i; + } + } + return -1; +} +function isFlowToken(token) { + switch (token?.type) { + case 'alias': + case 'scalar': + case 'single-quoted-scalar': + case 'double-quoted-scalar': + case 'flow-collection': + return true; + default: + return false; + } +} +function getPrevProps(parent) { + switch (parent.type) { + case 'document': + return parent.start; + case 'block-map': { + const it = parent.items[parent.items.length - 1]; + return it.sep ?? it.start; + } + case 'block-seq': + return parent.items[parent.items.length - 1].start; + /* istanbul ignore next should not happen */ + default: + return []; + } +} +/** Note: May modify input array */ +function getFirstKeyStartProps(prev) { + if (prev.length === 0) + return []; + let i = prev.length; + loop: while (--i >= 0) { + switch (prev[i].type) { + case 'doc-start': + case 'explicit-key-ind': + case 'map-value-ind': + case 'seq-item-ind': + case 'newline': + break loop; + } + } + while (prev[++i]?.type === 'space') { + /* loop */ + } + return prev.splice(i, prev.length); +} +function fixFlowSeqItems(fc) { + if (fc.start.type === 'flow-seq-start') { + for (const it of fc.items) { + if (it.sep && + !it.value && + !includesToken(it.start, 'explicit-key-ind') && + !includesToken(it.sep, 'map-value-ind')) { + if (it.key) + it.value = it.key; + delete it.key; + if (isFlowToken(it.value)) { + if (it.value.end) + Array.prototype.push.apply(it.value.end, it.sep); + else + it.value.end = it.sep; + } + else + Array.prototype.push.apply(it.start, it.sep); + delete it.sep; + } + } + } +} +/** + * A YAML concrete syntax tree (CST) parser + * + * ```ts + * const src: string = ... + * for (const token of new Parser().parse(src)) { + * // token: Token + * } + * ``` + * + * To use the parser with a user-provided lexer: + * + * ```ts + * function* parse(source: string, lexer: Lexer) { + * const parser = new Parser() + * for (const lexeme of lexer.lex(source)) + * yield* parser.next(lexeme) + * yield* parser.end() + * } + * + * const src: string = ... + * const lexer = new Lexer() + * for (const token of parse(src, lexer)) { + * // token: Token + * } + * ``` + */ +class Parser { + /** + * @param onNewLine - If defined, called separately with the start position of + * each new line (in `parse()`, including the start of input). + */ + constructor(onNewLine) { + /** If true, space and sequence indicators count as indentation */ + this.atNewLine = true; + /** If true, next token is a scalar value */ + this.atScalar = false; + /** Current indentation level */ + this.indent = 0; + /** Current offset since the start of parsing */ + this.offset = 0; + /** On the same line with a block map key */ + this.onKeyLine = false; + /** Top indicates the node that's currently being built */ + this.stack = []; + /** The source of the current token, set in parse() */ + this.source = ''; + /** The type of the current token, set in parse() */ + this.type = ''; + // Must be defined after `next()` + this.lexer = new lexer.Lexer(); + this.onNewLine = onNewLine; + } + /** + * Parse `source` as a YAML stream. + * If `incomplete`, a part of the last line may be left as a buffer for the next call. + * + * Errors are not thrown, but yielded as `{ type: 'error', message }` tokens. + * + * @returns A generator of tokens representing each directive, document, and other structure. + */ + *parse(source, incomplete = false) { + if (this.onNewLine && this.offset === 0) + this.onNewLine(0); + for (const lexeme of this.lexer.lex(source, incomplete)) + yield* this.next(lexeme); + if (!incomplete) + yield* this.end(); + } + /** + * Advance the parser by the `source` of one lexical token. + */ + *next(source) { + this.source = source; + if (node_process.env.LOG_TOKENS) + console.log('|', cst.prettyToken(source)); + if (this.atScalar) { + this.atScalar = false; + yield* this.step(); + this.offset += source.length; + return; + } + const type = cst.tokenType(source); + if (!type) { + const message = `Not a YAML token: ${source}`; + yield* this.pop({ type: 'error', offset: this.offset, message, source }); + this.offset += source.length; + } + else if (type === 'scalar') { + this.atNewLine = false; + this.atScalar = true; + this.type = 'scalar'; + } + else { + this.type = type; + yield* this.step(); + switch (type) { + case 'newline': + this.atNewLine = true; + this.indent = 0; + if (this.onNewLine) + this.onNewLine(this.offset + source.length); + break; + case 'space': + if (this.atNewLine && source[0] === ' ') + this.indent += source.length; + break; + case 'explicit-key-ind': + case 'map-value-ind': + case 'seq-item-ind': + if (this.atNewLine) + this.indent += source.length; + break; + case 'doc-mode': + case 'flow-error-end': + return; + default: + this.atNewLine = false; + } + this.offset += source.length; + } + } + /** Call at end of input to push out any remaining constructions */ + *end() { + while (this.stack.length > 0) + yield* this.pop(); + } + get sourceToken() { + const st = { + type: this.type, + offset: this.offset, + indent: this.indent, + source: this.source + }; + return st; + } + *step() { + const top = this.peek(1); + if (this.type === 'doc-end' && top?.type !== 'doc-end') { + while (this.stack.length > 0) + yield* this.pop(); + this.stack.push({ + type: 'doc-end', + offset: this.offset, + source: this.source + }); + return; + } + if (!top) + return yield* this.stream(); + switch (top.type) { + case 'document': + return yield* this.document(top); + case 'alias': + case 'scalar': + case 'single-quoted-scalar': + case 'double-quoted-scalar': + return yield* this.scalar(top); + case 'block-scalar': + return yield* this.blockScalar(top); + case 'block-map': + return yield* this.blockMap(top); + case 'block-seq': + return yield* this.blockSequence(top); + case 'flow-collection': + return yield* this.flowCollection(top); + case 'doc-end': + return yield* this.documentEnd(top); + } + /* istanbul ignore next should not happen */ + yield* this.pop(); + } + peek(n) { + return this.stack[this.stack.length - n]; + } + *pop(error) { + const token = error ?? this.stack.pop(); + /* istanbul ignore if should not happen */ + if (!token) { + const message = 'Tried to pop an empty stack'; + yield { type: 'error', offset: this.offset, source: '', message }; + } + else if (this.stack.length === 0) { + yield token; + } + else { + const top = this.peek(1); + if (token.type === 'block-scalar') { + // Block scalars use their parent rather than header indent + token.indent = 'indent' in top ? top.indent : 0; + } + else if (token.type === 'flow-collection' && top.type === 'document') { + // Ignore all indent for top-level flow collections + token.indent = 0; + } + if (token.type === 'flow-collection') + fixFlowSeqItems(token); + switch (top.type) { + case 'document': + top.value = token; + break; + case 'block-scalar': + top.props.push(token); // error + break; + case 'block-map': { + const it = top.items[top.items.length - 1]; + if (it.value) { + top.items.push({ start: [], key: token, sep: [] }); + this.onKeyLine = true; + return; + } + else if (it.sep) { + it.value = token; + } + else { + Object.assign(it, { key: token, sep: [] }); + this.onKeyLine = !it.explicitKey; + return; + } + break; + } + case 'block-seq': { + const it = top.items[top.items.length - 1]; + if (it.value) + top.items.push({ start: [], value: token }); + else + it.value = token; + break; + } + case 'flow-collection': { + const it = top.items[top.items.length - 1]; + if (!it || it.value) + top.items.push({ start: [], key: token, sep: [] }); + else if (it.sep) + it.value = token; + else + Object.assign(it, { key: token, sep: [] }); + return; + } + /* istanbul ignore next should not happen */ + default: + yield* this.pop(); + yield* this.pop(token); + } + if ((top.type === 'document' || + top.type === 'block-map' || + top.type === 'block-seq') && + (token.type === 'block-map' || token.type === 'block-seq')) { + const last = token.items[token.items.length - 1]; + if (last && + !last.sep && + !last.value && + last.start.length > 0 && + findNonEmptyIndex(last.start) === -1 && + (token.indent === 0 || + last.start.every(st => st.type !== 'comment' || st.indent < token.indent))) { + if (top.type === 'document') + top.end = last.start; + else + top.items.push({ start: last.start }); + token.items.splice(-1, 1); + } + } + } + } + *stream() { + switch (this.type) { + case 'directive-line': + yield { type: 'directive', offset: this.offset, source: this.source }; + return; + case 'byte-order-mark': + case 'space': + case 'comment': + case 'newline': + yield this.sourceToken; + return; + case 'doc-mode': + case 'doc-start': { + const doc = { + type: 'document', + offset: this.offset, + start: [] + }; + if (this.type === 'doc-start') + doc.start.push(this.sourceToken); + this.stack.push(doc); + return; + } + } + yield { + type: 'error', + offset: this.offset, + message: `Unexpected ${this.type} token in YAML stream`, + source: this.source + }; + } + *document(doc) { + if (doc.value) + return yield* this.lineEnd(doc); + switch (this.type) { + case 'doc-start': { + if (findNonEmptyIndex(doc.start) !== -1) { + yield* this.pop(); + yield* this.step(); + } + else + doc.start.push(this.sourceToken); + return; + } + case 'anchor': + case 'tag': + case 'space': + case 'comment': + case 'newline': + doc.start.push(this.sourceToken); + return; + } + const bv = this.startBlockValue(doc); + if (bv) + this.stack.push(bv); + else { + yield { + type: 'error', + offset: this.offset, + message: `Unexpected ${this.type} token in YAML document`, + source: this.source + }; + } + } + *scalar(scalar) { + if (this.type === 'map-value-ind') { + const prev = getPrevProps(this.peek(2)); + const start = getFirstKeyStartProps(prev); + let sep; + if (scalar.end) { + sep = scalar.end; + sep.push(this.sourceToken); + delete scalar.end; + } + else + sep = [this.sourceToken]; + const map = { + type: 'block-map', + offset: scalar.offset, + indent: scalar.indent, + items: [{ start, key: scalar, sep }] + }; + this.onKeyLine = true; + this.stack[this.stack.length - 1] = map; + } + else + yield* this.lineEnd(scalar); + } + *blockScalar(scalar) { + switch (this.type) { + case 'space': + case 'comment': + case 'newline': + scalar.props.push(this.sourceToken); + return; + case 'scalar': + scalar.source = this.source; + // block-scalar source includes trailing newline + this.atNewLine = true; + this.indent = 0; + if (this.onNewLine) { + let nl = this.source.indexOf('\n') + 1; + while (nl !== 0) { + this.onNewLine(this.offset + nl); + nl = this.source.indexOf('\n', nl) + 1; + } + } + yield* this.pop(); + break; + /* istanbul ignore next should not happen */ + default: + yield* this.pop(); + yield* this.step(); + } + } + *blockMap(map) { + const it = map.items[map.items.length - 1]; + // it.sep is true-ish if pair already has key or : separator + switch (this.type) { + case 'newline': + this.onKeyLine = false; + if (it.value) { + const end = 'end' in it.value ? it.value.end : undefined; + const last = Array.isArray(end) ? end[end.length - 1] : undefined; + if (last?.type === 'comment') + end?.push(this.sourceToken); + else + map.items.push({ start: [this.sourceToken] }); + } + else if (it.sep) { + it.sep.push(this.sourceToken); + } + else { + it.start.push(this.sourceToken); + } + return; + case 'space': + case 'comment': + if (it.value) { + map.items.push({ start: [this.sourceToken] }); + } + else if (it.sep) { + it.sep.push(this.sourceToken); + } + else { + if (this.atIndentedComment(it.start, map.indent)) { + const prev = map.items[map.items.length - 2]; + const end = prev?.value?.end; + if (Array.isArray(end)) { + Array.prototype.push.apply(end, it.start); + end.push(this.sourceToken); + map.items.pop(); + return; + } + } + it.start.push(this.sourceToken); + } + return; + } + if (this.indent >= map.indent) { + const atMapIndent = !this.onKeyLine && this.indent === map.indent; + const atNextItem = atMapIndent && + (it.sep || it.explicitKey) && + this.type !== 'seq-item-ind'; + // For empty nodes, assign newline-separated not indented empty tokens to following node + let start = []; + if (atNextItem && it.sep && !it.value) { + const nl = []; + for (let i = 0; i < it.sep.length; ++i) { + const st = it.sep[i]; + switch (st.type) { + case 'newline': + nl.push(i); + break; + case 'space': + break; + case 'comment': + if (st.indent > map.indent) + nl.length = 0; + break; + default: + nl.length = 0; + } + } + if (nl.length >= 2) + start = it.sep.splice(nl[1]); + } + switch (this.type) { + case 'anchor': + case 'tag': + if (atNextItem || it.value) { + start.push(this.sourceToken); + map.items.push({ start }); + this.onKeyLine = true; + } + else if (it.sep) { + it.sep.push(this.sourceToken); + } + else { + it.start.push(this.sourceToken); + } + return; + case 'explicit-key-ind': + if (!it.sep && !it.explicitKey) { + it.start.push(this.sourceToken); + it.explicitKey = true; + } + else if (atNextItem || it.value) { + start.push(this.sourceToken); + map.items.push({ start, explicitKey: true }); + } + else { + this.stack.push({ + type: 'block-map', + offset: this.offset, + indent: this.indent, + items: [{ start: [this.sourceToken], explicitKey: true }] + }); + } + this.onKeyLine = true; + return; + case 'map-value-ind': + if (it.explicitKey) { + if (!it.sep) { + if (includesToken(it.start, 'newline')) { + Object.assign(it, { key: null, sep: [this.sourceToken] }); + } + else { + const start = getFirstKeyStartProps(it.start); + this.stack.push({ + type: 'block-map', + offset: this.offset, + indent: this.indent, + items: [{ start, key: null, sep: [this.sourceToken] }] + }); + } + } + else if (it.value) { + map.items.push({ start: [], key: null, sep: [this.sourceToken] }); + } + else if (includesToken(it.sep, 'map-value-ind')) { + this.stack.push({ + type: 'block-map', + offset: this.offset, + indent: this.indent, + items: [{ start, key: null, sep: [this.sourceToken] }] + }); + } + else if (isFlowToken(it.key) && + !includesToken(it.sep, 'newline')) { + const start = getFirstKeyStartProps(it.start); + const key = it.key; + const sep = it.sep; + sep.push(this.sourceToken); + // @ts-expect-error type guard is wrong here + delete it.key; + // @ts-expect-error type guard is wrong here + delete it.sep; + this.stack.push({ + type: 'block-map', + offset: this.offset, + indent: this.indent, + items: [{ start, key, sep }] + }); + } + else if (start.length > 0) { + // Not actually at next item + it.sep = it.sep.concat(start, this.sourceToken); + } + else { + it.sep.push(this.sourceToken); + } + } + else { + if (!it.sep) { + Object.assign(it, { key: null, sep: [this.sourceToken] }); + } + else if (it.value || atNextItem) { + map.items.push({ start, key: null, sep: [this.sourceToken] }); + } + else if (includesToken(it.sep, 'map-value-ind')) { + this.stack.push({ + type: 'block-map', + offset: this.offset, + indent: this.indent, + items: [{ start: [], key: null, sep: [this.sourceToken] }] + }); + } + else { + it.sep.push(this.sourceToken); + } + } + this.onKeyLine = true; + return; + case 'alias': + case 'scalar': + case 'single-quoted-scalar': + case 'double-quoted-scalar': { + const fs = this.flowScalar(this.type); + if (atNextItem || it.value) { + map.items.push({ start, key: fs, sep: [] }); + this.onKeyLine = true; + } + else if (it.sep) { + this.stack.push(fs); + } + else { + Object.assign(it, { key: fs, sep: [] }); + this.onKeyLine = true; + } + return; + } + default: { + const bv = this.startBlockValue(map); + if (bv) { + if (bv.type === 'block-seq') { + if (!it.explicitKey && + it.sep && + !includesToken(it.sep, 'newline')) { + yield* this.pop({ + type: 'error', + offset: this.offset, + message: 'Unexpected block-seq-ind on same line with key', + source: this.source + }); + return; + } + } + else if (atMapIndent) { + map.items.push({ start }); + } + this.stack.push(bv); + return; + } + } + } + } + yield* this.pop(); + yield* this.step(); + } + *blockSequence(seq) { + const it = seq.items[seq.items.length - 1]; + switch (this.type) { + case 'newline': + if (it.value) { + const end = 'end' in it.value ? it.value.end : undefined; + const last = Array.isArray(end) ? end[end.length - 1] : undefined; + if (last?.type === 'comment') + end?.push(this.sourceToken); + else + seq.items.push({ start: [this.sourceToken] }); + } + else + it.start.push(this.sourceToken); + return; + case 'space': + case 'comment': + if (it.value) + seq.items.push({ start: [this.sourceToken] }); + else { + if (this.atIndentedComment(it.start, seq.indent)) { + const prev = seq.items[seq.items.length - 2]; + const end = prev?.value?.end; + if (Array.isArray(end)) { + Array.prototype.push.apply(end, it.start); + end.push(this.sourceToken); + seq.items.pop(); + return; + } + } + it.start.push(this.sourceToken); + } + return; + case 'anchor': + case 'tag': + if (it.value || this.indent <= seq.indent) + break; + it.start.push(this.sourceToken); + return; + case 'seq-item-ind': + if (this.indent !== seq.indent) + break; + if (it.value || includesToken(it.start, 'seq-item-ind')) + seq.items.push({ start: [this.sourceToken] }); + else + it.start.push(this.sourceToken); + return; + } + if (this.indent > seq.indent) { + const bv = this.startBlockValue(seq); + if (bv) { + this.stack.push(bv); + return; + } + } + yield* this.pop(); + yield* this.step(); + } + *flowCollection(fc) { + const it = fc.items[fc.items.length - 1]; + if (this.type === 'flow-error-end') { + let top; + do { + yield* this.pop(); + top = this.peek(1); + } while (top?.type === 'flow-collection'); + } + else if (fc.end.length === 0) { + switch (this.type) { + case 'comma': + case 'explicit-key-ind': + if (!it || it.sep) + fc.items.push({ start: [this.sourceToken] }); + else + it.start.push(this.sourceToken); + return; + case 'map-value-ind': + if (!it || it.value) + fc.items.push({ start: [], key: null, sep: [this.sourceToken] }); + else if (it.sep) + it.sep.push(this.sourceToken); + else + Object.assign(it, { key: null, sep: [this.sourceToken] }); + return; + case 'space': + case 'comment': + case 'newline': + case 'anchor': + case 'tag': + if (!it || it.value) + fc.items.push({ start: [this.sourceToken] }); + else if (it.sep) + it.sep.push(this.sourceToken); + else + it.start.push(this.sourceToken); + return; + case 'alias': + case 'scalar': + case 'single-quoted-scalar': + case 'double-quoted-scalar': { + const fs = this.flowScalar(this.type); + if (!it || it.value) + fc.items.push({ start: [], key: fs, sep: [] }); + else if (it.sep) + this.stack.push(fs); + else + Object.assign(it, { key: fs, sep: [] }); + return; + } + case 'flow-map-end': + case 'flow-seq-end': + fc.end.push(this.sourceToken); + return; + } + const bv = this.startBlockValue(fc); + /* istanbul ignore else should not happen */ + if (bv) + this.stack.push(bv); + else { + yield* this.pop(); + yield* this.step(); + } + } + else { + const parent = this.peek(2); + if (parent.type === 'block-map' && + ((this.type === 'map-value-ind' && parent.indent === fc.indent) || + (this.type === 'newline' && + !parent.items[parent.items.length - 1].sep))) { + yield* this.pop(); + yield* this.step(); + } + else if (this.type === 'map-value-ind' && + parent.type !== 'flow-collection') { + const prev = getPrevProps(parent); + const start = getFirstKeyStartProps(prev); + fixFlowSeqItems(fc); + const sep = fc.end.splice(1, fc.end.length); + sep.push(this.sourceToken); + const map = { + type: 'block-map', + offset: fc.offset, + indent: fc.indent, + items: [{ start, key: fc, sep }] + }; + this.onKeyLine = true; + this.stack[this.stack.length - 1] = map; + } + else { + yield* this.lineEnd(fc); + } + } + } + flowScalar(type) { + if (this.onNewLine) { + let nl = this.source.indexOf('\n') + 1; + while (nl !== 0) { + this.onNewLine(this.offset + nl); + nl = this.source.indexOf('\n', nl) + 1; + } + } + return { + type, + offset: this.offset, + indent: this.indent, + source: this.source + }; + } + startBlockValue(parent) { + switch (this.type) { + case 'alias': + case 'scalar': + case 'single-quoted-scalar': + case 'double-quoted-scalar': + return this.flowScalar(this.type); + case 'block-scalar-header': + return { + type: 'block-scalar', + offset: this.offset, + indent: this.indent, + props: [this.sourceToken], + source: '' + }; + case 'flow-map-start': + case 'flow-seq-start': + return { + type: 'flow-collection', + offset: this.offset, + indent: this.indent, + start: this.sourceToken, + items: [], + end: [] + }; + case 'seq-item-ind': + return { + type: 'block-seq', + offset: this.offset, + indent: this.indent, + items: [{ start: [this.sourceToken] }] + }; + case 'explicit-key-ind': { + this.onKeyLine = true; + const prev = getPrevProps(parent); + const start = getFirstKeyStartProps(prev); + start.push(this.sourceToken); + return { + type: 'block-map', + offset: this.offset, + indent: this.indent, + items: [{ start, explicitKey: true }] + }; + } + case 'map-value-ind': { + this.onKeyLine = true; + const prev = getPrevProps(parent); + const start = getFirstKeyStartProps(prev); + return { + type: 'block-map', + offset: this.offset, + indent: this.indent, + items: [{ start, key: null, sep: [this.sourceToken] }] + }; + } + } + return null; + } + atIndentedComment(start, indent) { + if (this.type !== 'comment') + return false; + if (this.indent <= indent) + return false; + return start.every(st => st.type === 'newline' || st.type === 'space'); + } + *documentEnd(docEnd) { + if (this.type !== 'doc-mode') { + if (docEnd.end) + docEnd.end.push(this.sourceToken); + else + docEnd.end = [this.sourceToken]; + if (this.type === 'newline') + yield* this.pop(); + } + } + *lineEnd(token) { + switch (this.type) { + case 'comma': + case 'doc-start': + case 'doc-end': + case 'flow-seq-end': + case 'flow-map-end': + case 'map-value-ind': + yield* this.pop(); + yield* this.step(); + break; + case 'newline': + this.onKeyLine = false; + // fallthrough + case 'space': + case 'comment': + default: + // all other values are errors + if (token.end) + token.end.push(this.sourceToken); + else + token.end = [this.sourceToken]; + if (this.type === 'newline') + yield* this.pop(); + } + } +} + +exports.Parser = Parser; + - // Log internal error details - const internalLogger = logging.createLogger(logging.generateCorrelationId()); - internalLogger.warn( - { status: error?.status, operation: 'repos.getContent', path, ref }, - `Failed to fetch file content: ${error.message}` - ); +/***/ }), + +/***/ 4047: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; - return { success: false, error: error.message, fallback }; - } -} +var composer = __nccwpck_require__(9984); +var Document = __nccwpck_require__(3021); +var errors = __nccwpck_require__(1464); +var log = __nccwpck_require__(7249); +var identity = __nccwpck_require__(1127); +var lineCounter = __nccwpck_require__(6628); +var parser = __nccwpck_require__(3456); + +function parseOptions(options) { + const prettyErrors = options.prettyErrors !== false; + const lineCounter$1 = options.lineCounter || (prettyErrors && new lineCounter.LineCounter()) || null; + return { lineCounter: lineCounter$1, prettyErrors }; +} /** - * Resolves PR refs (base and head) from PR metadata. - * @param {Object} octokit - GitHub Octokit instance - * @param {string} owner - Repository owner - * @param {string} repo - Repository name - * @param {number} pullNumber - PR number - * @returns {Promise<{success: boolean, data?: {base: {ref: string, sha: string}, head: {ref: string, sha: string}}, error?: string, fallback?: string}>} - */ -async function resolvePrRefs(octokit, owner, repo, pullNumber) { - if (!pullNumber) { - return { - success: false, - error: 'pullNumber is required', - fallback: 'PR number is required' - }; - } + * Parse the input as a stream of YAML documents. + * + * Documents should be separated from each other by `...` or `---` marker lines. + * + * @returns If an empty `docs` array is returned, it will be of type + * EmptyStream and contain additional stream information. In + * TypeScript, you should use `'empty' in docs` as a type guard for it. + */ +function parseAllDocuments(source, options = {}) { + const { lineCounter, prettyErrors } = parseOptions(options); + const parser$1 = new parser.Parser(lineCounter?.addNewLine); + const composer$1 = new composer.Composer(options); + const docs = Array.from(composer$1.compose(parser$1.parse(source))); + if (prettyErrors && lineCounter) + for (const doc of docs) { + doc.errors.forEach(errors.prettifyError(source, lineCounter)); + doc.warnings.forEach(errors.prettifyError(source, lineCounter)); + } + if (docs.length > 0) + return docs; + return Object.assign([], { empty: true }, composer$1.streamInfo()); +} +/** Parse an input string into a single YAML.Document */ +function parseDocument(source, options = {}) { + const { lineCounter, prettyErrors } = parseOptions(options); + const parser$1 = new parser.Parser(lineCounter?.addNewLine); + const composer$1 = new composer.Composer(options); + // `doc` is always set by compose.end(true) at the very latest + let doc = null; + for (const _doc of composer$1.compose(parser$1.parse(source), true, source.length)) { + if (!doc) + doc = _doc; + else if (doc.options.logLevel !== 'silent') { + doc.errors.push(new errors.YAMLParseError(_doc.range.slice(0, 2), 'MULTIPLE_DOCS', 'Source contains multiple documents; please use YAML.parseAllDocuments()')); + break; + } + } + if (prettyErrors && lineCounter) { + doc.errors.forEach(errors.prettifyError(source, lineCounter)); + doc.warnings.forEach(errors.prettifyError(source, lineCounter)); + } + return doc; +} +function parse(src, reviver, options) { + let _reviver = undefined; + if (typeof reviver === 'function') { + _reviver = reviver; + } + else if (options === undefined && reviver && typeof reviver === 'object') { + options = reviver; + } + const doc = parseDocument(src, options); + if (!doc) + return null; + doc.warnings.forEach(warning => log.warn(doc.options.logLevel, warning)); + if (doc.errors.length > 0) { + if (doc.options.logLevel !== 'silent') + throw doc.errors[0]; + else + doc.errors = []; + } + return doc.toJS(Object.assign({ reviver: _reviver }, options)); +} +function stringify(value, replacer, options) { + let _replacer = null; + if (typeof replacer === 'function' || Array.isArray(replacer)) { + _replacer = replacer; + } + else if (options === undefined && replacer) { + options = replacer; + } + if (typeof options === 'string') + options = options.length; + if (typeof options === 'number') { + const indent = Math.round(options); + options = indent < 1 ? undefined : indent > 8 ? { indent: 8 } : { indent }; + } + if (value === undefined) { + const { keepUndefined } = options ?? replacer ?? {}; + if (!keepUndefined) + return undefined; + } + if (identity.isDocument(value) && !_replacer) + return value.toString(options); + return new Document.Document(value, _replacer, options).toString(options); +} + +exports.parse = parse; +exports.parseAllDocuments = parseAllDocuments; +exports.parseDocument = parseDocument; +exports.stringify = stringify; - try { - const { data } = await octokit.rest.pulls.get({ - owner, - repo, - pull_number: pullNumber, - }); - const baseRef = data.base?.ref; - const baseSha = data.base?.sha; - const headRef = data.head?.ref; - const headSha = data.head?.sha; +/***/ }), - if (!baseRef || !baseSha || !headRef || !headSha) { - return { - success: false, - error: 'PR base/head refs not found in response', - fallback: 'PR ref information unavailable' - }; - } +/***/ 5840: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { - return { - success: true, - data: { - base: { ref: baseRef, sha: baseSha }, - head: { ref: headRef, sha: headSha } - } - }; - } catch (error) { - const { fallback } = mapErrorToFallback(error, `PR #${pullNumber} metadata`); +"use strict"; - // Log internal error details - const internalLogger = logging.createLogger(logging.generateCorrelationId()); - internalLogger.warn( - { status: error?.status, operation: 'pulls.get', pullNumber }, - `Failed to resolve PR refs: ${error.message}` - ); - return { success: false, error: error.message, fallback }; - } -} +var identity = __nccwpck_require__(1127); +var map = __nccwpck_require__(7451); +var seq = __nccwpck_require__(1706); +var string = __nccwpck_require__(6464); +var tags = __nccwpck_require__(18); + +const sortMapEntriesByKey = (a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0; +class Schema { + constructor({ compat, customTags, merge, resolveKnownTags, schema, sortMapEntries, toStringDefaults }) { + this.compat = Array.isArray(compat) + ? tags.getTags(compat, 'compat') + : compat + ? tags.getTags(null, compat) + : null; + this.name = (typeof schema === 'string' && schema) || 'core'; + this.knownTags = resolveKnownTags ? tags.coreKnownTags : {}; + this.tags = tags.getTags(customTags, this.name, merge); + this.toStringOptions = toStringDefaults ?? null; + Object.defineProperty(this, identity.MAP, { value: map.map }); + Object.defineProperty(this, identity.SCALAR, { value: string.string }); + Object.defineProperty(this, identity.SEQ, { value: seq.seq }); + // Used by createMap() + this.sortMapEntries = + typeof sortMapEntries === 'function' + ? sortMapEntries + : sortMapEntries === true + ? sortMapEntriesByKey + : null; + } + clone() { + const copy = Object.create(Schema.prototype, Object.getOwnPropertyDescriptors(this)); + copy.tags = this.tags.slice(); + return copy; + } +} + +exports.Schema = Schema; -/** - * Fetches file content at the PR head commit. - * Convenience wrapper around fetchFileAtRef using resolved head SHA. - * @param {Object} octokit - GitHub Octokit instance - * @param {string} owner - Repository owner - * @param {string} repo - Repository name - * @param {string} path - File path in repository - * @param {number} pullNumber - PR number - * @param {Object} [options={}] - Optional configuration - * @param {number} [options.maxFileSize=100000] - Maximum characters to return - * @returns {Promise<{success: boolean, data?: string, error?: string, fallback?: string, truncated?: boolean}>} - */ -async function fetchFileAtPrHead(octokit, owner, repo, path, pullNumber, options = {}) { - // First resolve the head SHA - const refsResult = await resolvePrRefs(octokit, owner, repo, pullNumber); - if (!refsResult.success) { - return { - success: false, - error: refsResult.error, - fallback: refsResult.fallback - }; - } +/***/ }), - // Fetch file at head - return fetchFileAtRef(octokit, owner, repo, path, refsResult.data.head.sha, options); -} +/***/ 7451: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { -module.exports = { - fetchPrFiles, - fetchFileAtRef, - fetchFileAtPrHead, - resolvePrRefs, - isRateLimitError, - mapErrorToFallback, - DEFAULT_MAX_FILE_SIZE, - DEFAULT_MAX_FILE_LINES, - DEFAULT_PER_PAGE, - DEFAULT_SLIDING_WINDOW, - FALLBACK_MESSAGES, - parsePatchLineRanges, - scopeLargeFileContent, +"use strict"; + + +var identity = __nccwpck_require__(1127); +var YAMLMap = __nccwpck_require__(4454); + +const map = { + collection: 'map', + default: true, + nodeClass: YAMLMap.YAMLMap, + tag: 'tag:yaml.org,2002:map', + resolve(map, onError) { + if (!identity.isMap(map)) + onError('Expected a mapping for this tag'); + return map; + }, + createNode: (schema, obj, ctx) => YAMLMap.YAMLMap.from(schema, obj, ctx) }; +exports.map = map; + /***/ }), -/***/ 2613: -/***/ ((module) => { +/***/ 3632: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { "use strict"; -module.exports = require("assert"); + + +var Scalar = __nccwpck_require__(3301); + +const nullTag = { + identify: value => value == null, + createNode: () => new Scalar.Scalar(null), + default: true, + tag: 'tag:yaml.org,2002:null', + test: /^(?:~|[Nn]ull|NULL)?$/, + resolve: () => new Scalar.Scalar(null), + stringify: ({ source }, ctx) => typeof source === 'string' && nullTag.test.test(source) + ? source + : ctx.options.nullStr +}; + +exports.nullTag = nullTag; + /***/ }), -/***/ 5317: -/***/ ((module) => { +/***/ 1706: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { "use strict"; -module.exports = require("child_process"); + + +var identity = __nccwpck_require__(1127); +var YAMLSeq = __nccwpck_require__(2223); + +const seq = { + collection: 'seq', + default: true, + nodeClass: YAMLSeq.YAMLSeq, + tag: 'tag:yaml.org,2002:seq', + resolve(seq, onError) { + if (!identity.isSeq(seq)) + onError('Expected a sequence for this tag'); + return seq; + }, + createNode: (schema, obj, ctx) => YAMLSeq.YAMLSeq.from(schema, obj, ctx) +}; + +exports.seq = seq; + /***/ }), -/***/ 6982: -/***/ ((module) => { +/***/ 6464: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { "use strict"; -module.exports = require("crypto"); + + +var stringifyString = __nccwpck_require__(3069); + +const string = { + identify: value => typeof value === 'string', + default: true, + tag: 'tag:yaml.org,2002:str', + resolve: str => str, + stringify(item, ctx, onComment, onChompKeep) { + ctx = Object.assign({ actualString: true }, ctx); + return stringifyString.stringifyString(item, ctx, onComment, onChompKeep); + } +}; + +exports.string = string; + /***/ }), -/***/ 4434: -/***/ ((module) => { +/***/ 3959: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { "use strict"; -module.exports = require("events"); + + +var Scalar = __nccwpck_require__(3301); + +const boolTag = { + identify: value => typeof value === 'boolean', + default: true, + tag: 'tag:yaml.org,2002:bool', + test: /^(?:[Tt]rue|TRUE|[Ff]alse|FALSE)$/, + resolve: str => new Scalar.Scalar(str[0] === 't' || str[0] === 'T'), + stringify({ source, value }, ctx) { + if (source && boolTag.test.test(source)) { + const sv = source[0] === 't' || source[0] === 'T'; + if (value === sv) + return source; + } + return value ? ctx.options.trueStr : ctx.options.falseStr; + } +}; + +exports.boolTag = boolTag; + /***/ }), -/***/ 9896: -/***/ ((module) => { +/***/ 8405: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { "use strict"; -module.exports = require("fs"); + + +var Scalar = __nccwpck_require__(3301); +var stringifyNumber = __nccwpck_require__(8689); + +const floatNaN = { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + test: /^(?:[-+]?\.(?:inf|Inf|INF)|\.nan|\.NaN|\.NAN)$/, + resolve: str => str.slice(-3).toLowerCase() === 'nan' + ? NaN + : str[0] === '-' + ? Number.NEGATIVE_INFINITY + : Number.POSITIVE_INFINITY, + stringify: stringifyNumber.stringifyNumber +}; +const floatExp = { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + format: 'EXP', + test: /^[-+]?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)[eE][-+]?[0-9]+$/, + resolve: str => parseFloat(str), + stringify(node) { + const num = Number(node.value); + return isFinite(num) ? num.toExponential() : stringifyNumber.stringifyNumber(node); + } +}; +const float = { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + test: /^[-+]?(?:\.[0-9]+|[0-9]+\.[0-9]*)$/, + resolve(str) { + const node = new Scalar.Scalar(parseFloat(str)); + const dot = str.indexOf('.'); + if (dot !== -1 && str[str.length - 1] === '0') + node.minFractionDigits = str.length - dot - 1; + return node; + }, + stringify: stringifyNumber.stringifyNumber +}; + +exports.float = float; +exports.floatExp = floatExp; +exports.floatNaN = floatNaN; + /***/ }), -/***/ 8611: -/***/ ((module) => { +/***/ 9874: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { "use strict"; -module.exports = require("http"); + + +var stringifyNumber = __nccwpck_require__(8689); + +const intIdentify = (value) => typeof value === 'bigint' || Number.isInteger(value); +const intResolve = (str, offset, radix, { intAsBigInt }) => (intAsBigInt ? BigInt(str) : parseInt(str.substring(offset), radix)); +function intStringify(node, radix, prefix) { + const { value } = node; + if (intIdentify(value) && value >= 0) + return prefix + value.toString(radix); + return stringifyNumber.stringifyNumber(node); +} +const intOct = { + identify: value => intIdentify(value) && value >= 0, + default: true, + tag: 'tag:yaml.org,2002:int', + format: 'OCT', + test: /^0o[0-7]+$/, + resolve: (str, _onError, opt) => intResolve(str, 2, 8, opt), + stringify: node => intStringify(node, 8, '0o') +}; +const int = { + identify: intIdentify, + default: true, + tag: 'tag:yaml.org,2002:int', + test: /^[-+]?[0-9]+$/, + resolve: (str, _onError, opt) => intResolve(str, 0, 10, opt), + stringify: stringifyNumber.stringifyNumber +}; +const intHex = { + identify: value => intIdentify(value) && value >= 0, + default: true, + tag: 'tag:yaml.org,2002:int', + format: 'HEX', + test: /^0x[0-9a-fA-F]+$/, + resolve: (str, _onError, opt) => intResolve(str, 2, 16, opt), + stringify: node => intStringify(node, 16, '0x') +}; + +exports.int = int; +exports.intHex = intHex; +exports.intOct = intOct; + /***/ }), -/***/ 5692: -/***/ ((module) => { +/***/ 896: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { "use strict"; -module.exports = require("https"); + + +var map = __nccwpck_require__(7451); +var _null = __nccwpck_require__(3632); +var seq = __nccwpck_require__(1706); +var string = __nccwpck_require__(6464); +var bool = __nccwpck_require__(3959); +var float = __nccwpck_require__(8405); +var int = __nccwpck_require__(9874); + +const schema = [ + map.map, + seq.seq, + string.string, + _null.nullTag, + bool.boolTag, + int.intOct, + int.int, + int.intHex, + float.floatNaN, + float.floatExp, + float.float +]; + +exports.schema = schema; + /***/ }), -/***/ 9278: -/***/ ((module) => { +/***/ 3559: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { "use strict"; -module.exports = require("net"); + + +var Scalar = __nccwpck_require__(3301); +var map = __nccwpck_require__(7451); +var seq = __nccwpck_require__(1706); + +function intIdentify(value) { + return typeof value === 'bigint' || Number.isInteger(value); +} +const stringifyJSON = ({ value }) => JSON.stringify(value); +const jsonScalars = [ + { + identify: value => typeof value === 'string', + default: true, + tag: 'tag:yaml.org,2002:str', + resolve: str => str, + stringify: stringifyJSON + }, + { + identify: value => value == null, + createNode: () => new Scalar.Scalar(null), + default: true, + tag: 'tag:yaml.org,2002:null', + test: /^null$/, + resolve: () => null, + stringify: stringifyJSON + }, + { + identify: value => typeof value === 'boolean', + default: true, + tag: 'tag:yaml.org,2002:bool', + test: /^true$|^false$/, + resolve: str => str === 'true', + stringify: stringifyJSON + }, + { + identify: intIdentify, + default: true, + tag: 'tag:yaml.org,2002:int', + test: /^-?(?:0|[1-9][0-9]*)$/, + resolve: (str, _onError, { intAsBigInt }) => intAsBigInt ? BigInt(str) : parseInt(str, 10), + stringify: ({ value }) => intIdentify(value) ? value.toString() : JSON.stringify(value) + }, + { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + test: /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]*)?(?:[eE][-+]?[0-9]+)?$/, + resolve: str => parseFloat(str), + stringify: stringifyJSON + } +]; +const jsonError = { + default: true, + tag: '', + test: /^/, + resolve(str, onError) { + onError(`Unresolved plain scalar ${JSON.stringify(str)}`); + return str; + } +}; +const schema = [map.map, seq.seq].concat(jsonScalars, jsonError); + +exports.schema = schema; + /***/ }), -/***/ 4589: -/***/ ((module) => { +/***/ 18: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { "use strict"; -module.exports = require("node:assert"); + + +var map = __nccwpck_require__(7451); +var _null = __nccwpck_require__(3632); +var seq = __nccwpck_require__(1706); +var string = __nccwpck_require__(6464); +var bool = __nccwpck_require__(3959); +var float = __nccwpck_require__(8405); +var int = __nccwpck_require__(9874); +var schema = __nccwpck_require__(896); +var schema$1 = __nccwpck_require__(3559); +var binary = __nccwpck_require__(6083); +var merge = __nccwpck_require__(452); +var omap = __nccwpck_require__(303); +var pairs = __nccwpck_require__(8385); +var schema$2 = __nccwpck_require__(5913); +var set = __nccwpck_require__(1528); +var timestamp = __nccwpck_require__(4371); + +const schemas = new Map([ + ['core', schema.schema], + ['failsafe', [map.map, seq.seq, string.string]], + ['json', schema$1.schema], + ['yaml11', schema$2.schema], + ['yaml-1.1', schema$2.schema] +]); +const tagsByName = { + binary: binary.binary, + bool: bool.boolTag, + float: float.float, + floatExp: float.floatExp, + floatNaN: float.floatNaN, + floatTime: timestamp.floatTime, + int: int.int, + intHex: int.intHex, + intOct: int.intOct, + intTime: timestamp.intTime, + map: map.map, + merge: merge.merge, + null: _null.nullTag, + omap: omap.omap, + pairs: pairs.pairs, + seq: seq.seq, + set: set.set, + timestamp: timestamp.timestamp +}; +const coreKnownTags = { + 'tag:yaml.org,2002:binary': binary.binary, + 'tag:yaml.org,2002:merge': merge.merge, + 'tag:yaml.org,2002:omap': omap.omap, + 'tag:yaml.org,2002:pairs': pairs.pairs, + 'tag:yaml.org,2002:set': set.set, + 'tag:yaml.org,2002:timestamp': timestamp.timestamp +}; +function getTags(customTags, schemaName, addMergeTag) { + const schemaTags = schemas.get(schemaName); + if (schemaTags && !customTags) { + return addMergeTag && !schemaTags.includes(merge.merge) + ? schemaTags.concat(merge.merge) + : schemaTags.slice(); + } + let tags = schemaTags; + if (!tags) { + if (Array.isArray(customTags)) + tags = []; + else { + const keys = Array.from(schemas.keys()) + .filter(key => key !== 'yaml11') + .map(key => JSON.stringify(key)) + .join(', '); + throw new Error(`Unknown schema "${schemaName}"; use one of ${keys} or define customTags array`); + } + } + if (Array.isArray(customTags)) { + for (const tag of customTags) + tags = tags.concat(tag); + } + else if (typeof customTags === 'function') { + tags = customTags(tags.slice()); + } + if (addMergeTag) + tags = tags.concat(merge.merge); + return tags.reduce((tags, tag) => { + const tagObj = typeof tag === 'string' ? tagsByName[tag] : tag; + if (!tagObj) { + const tagName = JSON.stringify(tag); + const keys = Object.keys(tagsByName) + .map(key => JSON.stringify(key)) + .join(', '); + throw new Error(`Unknown custom tag ${tagName}; use one of ${keys}`); + } + if (!tags.includes(tagObj)) + tags.push(tagObj); + return tags; + }, []); +} + +exports.coreKnownTags = coreKnownTags; +exports.getTags = getTags; + /***/ }), -/***/ 6698: -/***/ ((module) => { +/***/ 6083: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var node_buffer = __nccwpck_require__(181); +var Scalar = __nccwpck_require__(3301); +var stringifyString = __nccwpck_require__(3069); + +const binary = { + identify: value => value instanceof Uint8Array, // Buffer inherits from Uint8Array + default: false, + tag: 'tag:yaml.org,2002:binary', + /** + * Returns a Buffer in node and an Uint8Array in browsers + * + * To use the resulting buffer as an image, you'll want to do something like: + * + * const blob = new Blob([buffer], { type: 'image/jpeg' }) + * document.querySelector('#photo').src = URL.createObjectURL(blob) + */ + resolve(src, onError) { + if (typeof node_buffer.Buffer === 'function') { + return node_buffer.Buffer.from(src, 'base64'); + } + else if (typeof atob === 'function') { + // On IE 11, atob() can't handle newlines + const str = atob(src.replace(/[\n\r]/g, '')); + const buffer = new Uint8Array(str.length); + for (let i = 0; i < str.length; ++i) + buffer[i] = str.charCodeAt(i); + return buffer; + } + else { + onError('This environment does not support reading binary tags; either Buffer or atob is required'); + return src; + } + }, + stringify({ comment, type, value }, ctx, onComment, onChompKeep) { + if (!value) + return ''; + const buf = value; // checked earlier by binary.identify() + let str; + if (typeof node_buffer.Buffer === 'function') { + str = + buf instanceof node_buffer.Buffer + ? buf.toString('base64') + : node_buffer.Buffer.from(buf.buffer).toString('base64'); + } + else if (typeof btoa === 'function') { + let s = ''; + for (let i = 0; i < buf.length; ++i) + s += String.fromCharCode(buf[i]); + str = btoa(s); + } + else { + throw new Error('This environment does not support writing binary tags; either Buffer or btoa is required'); + } + type ?? (type = Scalar.Scalar.BLOCK_LITERAL); + if (type !== Scalar.Scalar.QUOTE_DOUBLE) { + const lineWidth = Math.max(ctx.options.lineWidth - ctx.indent.length, ctx.options.minContentWidth); + const n = Math.ceil(str.length / lineWidth); + const lines = new Array(n); + for (let i = 0, o = 0; i < n; ++i, o += lineWidth) { + lines[i] = str.substr(o, lineWidth); + } + str = lines.join(type === Scalar.Scalar.BLOCK_LITERAL ? '\n' : ' '); + } + return stringifyString.stringifyString({ comment, type, value: str }, ctx, onComment, onChompKeep); + } +}; + +exports.binary = binary; -"use strict"; -module.exports = require("node:async_hooks"); /***/ }), -/***/ 4573: -/***/ ((module) => { +/***/ 8398: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { "use strict"; -module.exports = require("node:buffer"); - -/***/ }), -/***/ 7540: -/***/ ((module) => { -"use strict"; -module.exports = require("node:console"); +var Scalar = __nccwpck_require__(3301); -/***/ }), +function boolStringify({ value, source }, ctx) { + const boolObj = value ? trueTag : falseTag; + if (source && boolObj.test.test(source)) + return source; + return value ? ctx.options.trueStr : ctx.options.falseStr; +} +const trueTag = { + identify: value => value === true, + default: true, + tag: 'tag:yaml.org,2002:bool', + test: /^(?:Y|y|[Yy]es|YES|[Tt]rue|TRUE|[Oo]n|ON)$/, + resolve: () => new Scalar.Scalar(true), + stringify: boolStringify +}; +const falseTag = { + identify: value => value === false, + default: true, + tag: 'tag:yaml.org,2002:bool', + test: /^(?:N|n|[Nn]o|NO|[Ff]alse|FALSE|[Oo]ff|OFF)$/, + resolve: () => new Scalar.Scalar(false), + stringify: boolStringify +}; -/***/ 7598: -/***/ ((module) => { +exports.falseTag = falseTag; +exports.trueTag = trueTag; -"use strict"; -module.exports = require("node:crypto"); /***/ }), -/***/ 3053: -/***/ ((module) => { +/***/ 5782: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { "use strict"; -module.exports = require("node:diagnostics_channel"); -/***/ }), -/***/ 610: -/***/ ((module) => { +var Scalar = __nccwpck_require__(3301); +var stringifyNumber = __nccwpck_require__(8689); + +const floatNaN = { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + test: /^(?:[-+]?\.(?:inf|Inf|INF)|\.nan|\.NaN|\.NAN)$/, + resolve: (str) => str.slice(-3).toLowerCase() === 'nan' + ? NaN + : str[0] === '-' + ? Number.NEGATIVE_INFINITY + : Number.POSITIVE_INFINITY, + stringify: stringifyNumber.stringifyNumber +}; +const floatExp = { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + format: 'EXP', + test: /^[-+]?(?:[0-9][0-9_]*)?(?:\.[0-9_]*)?[eE][-+]?[0-9]+$/, + resolve: (str) => parseFloat(str.replace(/_/g, '')), + stringify(node) { + const num = Number(node.value); + return isFinite(num) ? num.toExponential() : stringifyNumber.stringifyNumber(node); + } +}; +const float = { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + test: /^[-+]?(?:[0-9][0-9_]*)?\.[0-9_]*$/, + resolve(str) { + const node = new Scalar.Scalar(parseFloat(str.replace(/_/g, ''))); + const dot = str.indexOf('.'); + if (dot !== -1) { + const f = str.substring(dot + 1).replace(/_/g, ''); + if (f[f.length - 1] === '0') + node.minFractionDigits = f.length; + } + return node; + }, + stringify: stringifyNumber.stringifyNumber +}; + +exports.float = float; +exports.floatExp = floatExp; +exports.floatNaN = floatNaN; -"use strict"; -module.exports = require("node:dns"); /***/ }), -/***/ 8474: -/***/ ((module) => { +/***/ 873: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { "use strict"; -module.exports = require("node:events"); -/***/ }), -/***/ 7067: -/***/ ((module) => { +var stringifyNumber = __nccwpck_require__(8689); + +const intIdentify = (value) => typeof value === 'bigint' || Number.isInteger(value); +function intResolve(str, offset, radix, { intAsBigInt }) { + const sign = str[0]; + if (sign === '-' || sign === '+') + offset += 1; + str = str.substring(offset).replace(/_/g, ''); + if (intAsBigInt) { + switch (radix) { + case 2: + str = `0b${str}`; + break; + case 8: + str = `0o${str}`; + break; + case 16: + str = `0x${str}`; + break; + } + const n = BigInt(str); + return sign === '-' ? BigInt(-1) * n : n; + } + const n = parseInt(str, radix); + return sign === '-' ? -1 * n : n; +} +function intStringify(node, radix, prefix) { + const { value } = node; + if (intIdentify(value)) { + const str = value.toString(radix); + return value < 0 ? '-' + prefix + str.substr(1) : prefix + str; + } + return stringifyNumber.stringifyNumber(node); +} +const intBin = { + identify: intIdentify, + default: true, + tag: 'tag:yaml.org,2002:int', + format: 'BIN', + test: /^[-+]?0b[0-1_]+$/, + resolve: (str, _onError, opt) => intResolve(str, 2, 2, opt), + stringify: node => intStringify(node, 2, '0b') +}; +const intOct = { + identify: intIdentify, + default: true, + tag: 'tag:yaml.org,2002:int', + format: 'OCT', + test: /^[-+]?0[0-7_]+$/, + resolve: (str, _onError, opt) => intResolve(str, 1, 8, opt), + stringify: node => intStringify(node, 8, '0') +}; +const int = { + identify: intIdentify, + default: true, + tag: 'tag:yaml.org,2002:int', + test: /^[-+]?[0-9][0-9_]*$/, + resolve: (str, _onError, opt) => intResolve(str, 0, 10, opt), + stringify: stringifyNumber.stringifyNumber +}; +const intHex = { + identify: intIdentify, + default: true, + tag: 'tag:yaml.org,2002:int', + format: 'HEX', + test: /^[-+]?0x[0-9a-fA-F_]+$/, + resolve: (str, _onError, opt) => intResolve(str, 2, 16, opt), + stringify: node => intStringify(node, 16, '0x') +}; + +exports.int = int; +exports.intBin = intBin; +exports.intHex = intHex; +exports.intOct = intOct; -"use strict"; -module.exports = require("node:http"); /***/ }), -/***/ 2467: -/***/ ((module) => { +/***/ 452: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { "use strict"; -module.exports = require("node:http2"); -/***/ }), -/***/ 4708: -/***/ ((module) => { +var identity = __nccwpck_require__(1127); +var Scalar = __nccwpck_require__(3301); + +// If the value associated with a merge key is a single mapping node, each of +// its key/value pairs is inserted into the current mapping, unless the key +// already exists in it. If the value associated with the merge key is a +// sequence, then this sequence is expected to contain mapping nodes and each +// of these nodes is merged in turn according to its order in the sequence. +// Keys in mapping nodes earlier in the sequence override keys specified in +// later mapping nodes. -- http://yaml.org/type/merge.html +const MERGE_KEY = '<<'; +const merge = { + identify: value => value === MERGE_KEY || + (typeof value === 'symbol' && value.description === MERGE_KEY), + default: 'key', + tag: 'tag:yaml.org,2002:merge', + test: /^<<$/, + resolve: () => Object.assign(new Scalar.Scalar(Symbol(MERGE_KEY)), { + addToJSMap: addMergeToJSMap + }), + stringify: () => MERGE_KEY +}; +const isMergeKey = (ctx, key) => (merge.identify(key) || + (identity.isScalar(key) && + (!key.type || key.type === Scalar.Scalar.PLAIN) && + merge.identify(key.value))) && + ctx?.doc.schema.tags.some(tag => tag.tag === merge.tag && tag.default); +function addMergeToJSMap(ctx, map, value) { + value = ctx && identity.isAlias(value) ? value.resolve(ctx.doc) : value; + if (identity.isSeq(value)) + for (const it of value.items) + mergeValue(ctx, map, it); + else if (Array.isArray(value)) + for (const it of value) + mergeValue(ctx, map, it); + else + mergeValue(ctx, map, value); +} +function mergeValue(ctx, map, value) { + const source = ctx && identity.isAlias(value) ? value.resolve(ctx.doc) : value; + if (!identity.isMap(source)) + throw new Error('Merge sources must be maps or map aliases'); + const srcMap = source.toJSON(null, ctx, Map); + for (const [key, value] of srcMap) { + if (map instanceof Map) { + if (!map.has(key)) + map.set(key, value); + } + else if (map instanceof Set) { + map.add(key); + } + else if (!Object.prototype.hasOwnProperty.call(map, key)) { + Object.defineProperty(map, key, { + value, + writable: true, + enumerable: true, + configurable: true + }); + } + } + return map; +} + +exports.addMergeToJSMap = addMergeToJSMap; +exports.isMergeKey = isMergeKey; +exports.merge = merge; -"use strict"; -module.exports = require("node:https"); /***/ }), -/***/ 7030: -/***/ ((module) => { +/***/ 303: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { "use strict"; -module.exports = require("node:net"); -/***/ }), -/***/ 643: -/***/ ((module) => { +var identity = __nccwpck_require__(1127); +var toJS = __nccwpck_require__(6424); +var YAMLMap = __nccwpck_require__(4454); +var YAMLSeq = __nccwpck_require__(2223); +var pairs = __nccwpck_require__(8385); + +class YAMLOMap extends YAMLSeq.YAMLSeq { + constructor() { + super(); + this.add = YAMLMap.YAMLMap.prototype.add.bind(this); + this.delete = YAMLMap.YAMLMap.prototype.delete.bind(this); + this.get = YAMLMap.YAMLMap.prototype.get.bind(this); + this.has = YAMLMap.YAMLMap.prototype.has.bind(this); + this.set = YAMLMap.YAMLMap.prototype.set.bind(this); + this.tag = YAMLOMap.tag; + } + /** + * If `ctx` is given, the return type is actually `Map`, + * but TypeScript won't allow widening the signature of a child method. + */ + toJSON(_, ctx) { + if (!ctx) + return super.toJSON(_); + const map = new Map(); + if (ctx?.onCreate) + ctx.onCreate(map); + for (const pair of this.items) { + let key, value; + if (identity.isPair(pair)) { + key = toJS.toJS(pair.key, '', ctx); + value = toJS.toJS(pair.value, key, ctx); + } + else { + key = toJS.toJS(pair, '', ctx); + } + if (map.has(key)) + throw new Error('Ordered maps must not include duplicate keys'); + map.set(key, value); + } + return map; + } + static from(schema, iterable, ctx) { + const pairs$1 = pairs.createPairs(schema, iterable, ctx); + const omap = new this(); + omap.items = pairs$1.items; + return omap; + } +} +YAMLOMap.tag = 'tag:yaml.org,2002:omap'; +const omap = { + collection: 'seq', + identify: value => value instanceof Map, + nodeClass: YAMLOMap, + default: false, + tag: 'tag:yaml.org,2002:omap', + resolve(seq, onError) { + const pairs$1 = pairs.resolvePairs(seq, onError); + const seenKeys = []; + for (const { key } of pairs$1.items) { + if (identity.isScalar(key)) { + if (seenKeys.includes(key.value)) { + onError(`Ordered maps must not include duplicate keys: ${key.value}`); + } + else { + seenKeys.push(key.value); + } + } + } + return Object.assign(new YAMLOMap(), pairs$1); + }, + createNode: (schema, iterable, ctx) => YAMLOMap.from(schema, iterable, ctx) +}; + +exports.YAMLOMap = YAMLOMap; +exports.omap = omap; -"use strict"; -module.exports = require("node:perf_hooks"); /***/ }), -/***/ 1792: -/***/ ((module) => { +/***/ 8385: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { "use strict"; -module.exports = require("node:querystring"); -/***/ }), -/***/ 7075: -/***/ ((module) => { +var identity = __nccwpck_require__(1127); +var Pair = __nccwpck_require__(7165); +var Scalar = __nccwpck_require__(3301); +var YAMLSeq = __nccwpck_require__(2223); + +function resolvePairs(seq, onError) { + if (identity.isSeq(seq)) { + for (let i = 0; i < seq.items.length; ++i) { + let item = seq.items[i]; + if (identity.isPair(item)) + continue; + else if (identity.isMap(item)) { + if (item.items.length > 1) + onError('Each pair must have its own sequence indicator'); + const pair = item.items[0] || new Pair.Pair(new Scalar.Scalar(null)); + if (item.commentBefore) + pair.key.commentBefore = pair.key.commentBefore + ? `${item.commentBefore}\n${pair.key.commentBefore}` + : item.commentBefore; + if (item.comment) { + const cn = pair.value ?? pair.key; + cn.comment = cn.comment + ? `${item.comment}\n${cn.comment}` + : item.comment; + } + item = pair; + } + seq.items[i] = identity.isPair(item) ? item : new Pair.Pair(item); + } + } + else + onError('Expected a sequence for this tag'); + return seq; +} +function createPairs(schema, iterable, ctx) { + const { replacer } = ctx; + const pairs = new YAMLSeq.YAMLSeq(schema); + pairs.tag = 'tag:yaml.org,2002:pairs'; + let i = 0; + if (iterable && Symbol.iterator in Object(iterable)) + for (let it of iterable) { + if (typeof replacer === 'function') + it = replacer.call(iterable, String(i++), it); + let key, value; + if (Array.isArray(it)) { + if (it.length === 2) { + key = it[0]; + value = it[1]; + } + else + throw new TypeError(`Expected [key, value] tuple: ${it}`); + } + else if (it && it instanceof Object) { + const keys = Object.keys(it); + if (keys.length === 1) { + key = keys[0]; + value = it[key]; + } + else { + throw new TypeError(`Expected tuple with one key, not ${keys.length} keys`); + } + } + else { + key = it; + } + pairs.items.push(Pair.createPair(key, value, ctx)); + } + return pairs; +} +const pairs = { + collection: 'seq', + default: false, + tag: 'tag:yaml.org,2002:pairs', + resolve: resolvePairs, + createNode: createPairs +}; + +exports.createPairs = createPairs; +exports.pairs = pairs; +exports.resolvePairs = resolvePairs; -"use strict"; -module.exports = require("node:stream"); /***/ }), -/***/ 1692: -/***/ ((module) => { +/***/ 5913: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { "use strict"; -module.exports = require("node:tls"); -/***/ }), -/***/ 3136: -/***/ ((module) => { +var map = __nccwpck_require__(7451); +var _null = __nccwpck_require__(3632); +var seq = __nccwpck_require__(1706); +var string = __nccwpck_require__(6464); +var binary = __nccwpck_require__(6083); +var bool = __nccwpck_require__(8398); +var float = __nccwpck_require__(5782); +var int = __nccwpck_require__(873); +var merge = __nccwpck_require__(452); +var omap = __nccwpck_require__(303); +var pairs = __nccwpck_require__(8385); +var set = __nccwpck_require__(1528); +var timestamp = __nccwpck_require__(4371); + +const schema = [ + map.map, + seq.seq, + string.string, + _null.nullTag, + bool.trueTag, + bool.falseTag, + int.intBin, + int.intOct, + int.int, + int.intHex, + float.floatNaN, + float.floatExp, + float.float, + binary.binary, + merge.merge, + omap.omap, + pairs.pairs, + set.set, + timestamp.intTime, + timestamp.floatTime, + timestamp.timestamp +]; + +exports.schema = schema; -"use strict"; -module.exports = require("node:url"); /***/ }), -/***/ 7975: -/***/ ((module) => { +/***/ 1528: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { "use strict"; -module.exports = require("node:util"); -/***/ }), -/***/ 3429: -/***/ ((module) => { +var identity = __nccwpck_require__(1127); +var Pair = __nccwpck_require__(7165); +var YAMLMap = __nccwpck_require__(4454); + +class YAMLSet extends YAMLMap.YAMLMap { + constructor(schema) { + super(schema); + this.tag = YAMLSet.tag; + } + add(key) { + let pair; + if (identity.isPair(key)) + pair = key; + else if (key && + typeof key === 'object' && + 'key' in key && + 'value' in key && + key.value === null) + pair = new Pair.Pair(key.key, null); + else + pair = new Pair.Pair(key, null); + const prev = YAMLMap.findPair(this.items, pair.key); + if (!prev) + this.items.push(pair); + } + /** + * If `keepPair` is `true`, returns the Pair matching `key`. + * Otherwise, returns the value of that Pair's key. + */ + get(key, keepPair) { + const pair = YAMLMap.findPair(this.items, key); + return !keepPair && identity.isPair(pair) + ? identity.isScalar(pair.key) + ? pair.key.value + : pair.key + : pair; + } + set(key, value) { + if (typeof value !== 'boolean') + throw new Error(`Expected boolean value for set(key, value) in a YAML set, not ${typeof value}`); + const prev = YAMLMap.findPair(this.items, key); + if (prev && !value) { + this.items.splice(this.items.indexOf(prev), 1); + } + else if (!prev && value) { + this.items.push(new Pair.Pair(key)); + } + } + toJSON(_, ctx) { + return super.toJSON(_, ctx, Set); + } + toString(ctx, onComment, onChompKeep) { + if (!ctx) + return JSON.stringify(this); + if (this.hasAllNullValues(true)) + return super.toString(Object.assign({}, ctx, { allNullValues: true }), onComment, onChompKeep); + else + throw new Error('Set items must all have null values'); + } + static from(schema, iterable, ctx) { + const { replacer } = ctx; + const set = new this(schema); + if (iterable && Symbol.iterator in Object(iterable)) + for (let value of iterable) { + if (typeof replacer === 'function') + value = replacer.call(iterable, value, value); + set.items.push(Pair.createPair(value, null, ctx)); + } + return set; + } +} +YAMLSet.tag = 'tag:yaml.org,2002:set'; +const set = { + collection: 'map', + identify: value => value instanceof Set, + nodeClass: YAMLSet, + default: false, + tag: 'tag:yaml.org,2002:set', + createNode: (schema, iterable, ctx) => YAMLSet.from(schema, iterable, ctx), + resolve(map, onError) { + if (identity.isMap(map)) { + if (map.hasAllNullValues(true)) + return Object.assign(new YAMLSet(), map); + else + onError('Set items must all have null values'); + } + else + onError('Expected a mapping for this tag'); + return map; + } +}; + +exports.YAMLSet = YAMLSet; +exports.set = set; -"use strict"; -module.exports = require("node:util/types"); /***/ }), -/***/ 5919: -/***/ ((module) => { +/***/ 4371: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { "use strict"; -module.exports = require("node:worker_threads"); -/***/ }), -/***/ 8522: -/***/ ((module) => { +var stringifyNumber = __nccwpck_require__(8689); + +/** Internal types handle bigint as number, because TS can't figure it out. */ +function parseSexagesimal(str, asBigInt) { + const sign = str[0]; + const parts = sign === '-' || sign === '+' ? str.substring(1) : str; + const num = (n) => asBigInt ? BigInt(n) : Number(n); + const res = parts + .replace(/_/g, '') + .split(':') + .reduce((res, p) => res * num(60) + num(p), num(0)); + return (sign === '-' ? num(-1) * res : res); +} +/** + * hhhh:mm:ss.sss + * + * Internal types handle bigint as number, because TS can't figure it out. + */ +function stringifySexagesimal(node) { + let { value } = node; + let num = (n) => n; + if (typeof value === 'bigint') + num = n => BigInt(n); + else if (isNaN(value) || !isFinite(value)) + return stringifyNumber.stringifyNumber(node); + let sign = ''; + if (value < 0) { + sign = '-'; + value *= num(-1); + } + const _60 = num(60); + const parts = [value % _60]; // seconds, including ms + if (value < 60) { + parts.unshift(0); // at least one : is required + } + else { + value = (value - parts[0]) / _60; + parts.unshift(value % _60); // minutes + if (value >= 60) { + value = (value - parts[0]) / _60; + parts.unshift(value); // hours + } + } + return (sign + + parts + .map(n => String(n).padStart(2, '0')) + .join(':') + .replace(/000000\d*$/, '') // % 60 may introduce error + ); +} +const intTime = { + identify: value => typeof value === 'bigint' || Number.isInteger(value), + default: true, + tag: 'tag:yaml.org,2002:int', + format: 'TIME', + test: /^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+$/, + resolve: (str, _onError, { intAsBigInt }) => parseSexagesimal(str, intAsBigInt), + stringify: stringifySexagesimal +}; +const floatTime = { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + format: 'TIME', + test: /^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*$/, + resolve: str => parseSexagesimal(str, false), + stringify: stringifySexagesimal +}; +const timestamp = { + identify: value => value instanceof Date, + default: true, + tag: 'tag:yaml.org,2002:timestamp', + // If the time zone is omitted, the timestamp is assumed to be specified in UTC. The time part + // may be omitted altogether, resulting in a date format. In such a case, the time part is + // assumed to be 00:00:00Z (start of day, UTC). + test: RegExp('^([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})' + // YYYY-Mm-Dd + '(?:' + // time is optional + '(?:t|T|[ \\t]+)' + // t | T | whitespace + '([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2}(\\.[0-9]+)?)' + // Hh:Mm:Ss(.ss)? + '(?:[ \\t]*(Z|[-+][012]?[0-9](?::[0-9]{2})?))?' + // Z | +5 | -03:30 + ')?$'), + resolve(str) { + const match = str.match(timestamp.test); + if (!match) + throw new Error('!!timestamp expects a date, starting with yyyy-mm-dd'); + const [, year, month, day, hour, minute, second] = match.map(Number); + const millisec = match[7] ? Number((match[7] + '00').substr(1, 3)) : 0; + let date = Date.UTC(year, month - 1, day, hour || 0, minute || 0, second || 0, millisec); + const tz = match[8]; + if (tz && tz !== 'Z') { + let d = parseSexagesimal(tz, false); + if (Math.abs(d) < 30) + d *= 60; + date -= 60000 * d; + } + return new Date(date); + }, + stringify: ({ value }) => value?.toISOString().replace(/(T00:00:00)?\.000Z$/, '') ?? '' +}; + +exports.floatTime = floatTime; +exports.intTime = intTime; +exports.timestamp = timestamp; -"use strict"; -module.exports = require("node:zlib"); /***/ }), -/***/ 857: -/***/ ((module) => { +/***/ 4475: +/***/ ((__unused_webpack_module, exports) => { "use strict"; -module.exports = require("os"); -/***/ }), -/***/ 6928: -/***/ ((module) => { +const FOLD_FLOW = 'flow'; +const FOLD_BLOCK = 'block'; +const FOLD_QUOTED = 'quoted'; +/** + * Tries to keep input at up to `lineWidth` characters, splitting only on spaces + * not followed by newlines or spaces unless `mode` is `'quoted'`. Lines are + * terminated with `\n` and started with `indent`. + */ +function foldFlowLines(text, indent, mode = 'flow', { indentAtStart, lineWidth = 80, minContentWidth = 20, onFold, onOverflow } = {}) { + if (!lineWidth || lineWidth < 0) + return text; + if (lineWidth < minContentWidth) + minContentWidth = 0; + const endStep = Math.max(1 + minContentWidth, 1 + lineWidth - indent.length); + if (text.length <= endStep) + return text; + const folds = []; + const escapedFolds = {}; + let end = lineWidth - indent.length; + if (typeof indentAtStart === 'number') { + if (indentAtStart > lineWidth - Math.max(2, minContentWidth)) + folds.push(0); + else + end = lineWidth - indentAtStart; + } + let split = undefined; + let prev = undefined; + let overflow = false; + let i = -1; + let escStart = -1; + let escEnd = -1; + if (mode === FOLD_BLOCK) { + i = consumeMoreIndentedLines(text, i, indent.length); + if (i !== -1) + end = i + endStep; + } + for (let ch; (ch = text[(i += 1)]);) { + if (mode === FOLD_QUOTED && ch === '\\') { + escStart = i; + switch (text[i + 1]) { + case 'x': + i += 3; + break; + case 'u': + i += 5; + break; + case 'U': + i += 9; + break; + default: + i += 1; + } + escEnd = i; + } + if (ch === '\n') { + if (mode === FOLD_BLOCK) + i = consumeMoreIndentedLines(text, i, indent.length); + end = i + indent.length + endStep; + split = undefined; + } + else { + if (ch === ' ' && + prev && + prev !== ' ' && + prev !== '\n' && + prev !== '\t') { + // space surrounded by non-space can be replaced with newline + indent + const next = text[i + 1]; + if (next && next !== ' ' && next !== '\n' && next !== '\t') + split = i; + } + if (i >= end) { + if (split) { + folds.push(split); + end = split + endStep; + split = undefined; + } + else if (mode === FOLD_QUOTED) { + // white-space collected at end may stretch past lineWidth + while (prev === ' ' || prev === '\t') { + prev = ch; + ch = text[(i += 1)]; + overflow = true; + } + // Account for newline escape, but don't break preceding escape + const j = i > escEnd + 1 ? i - 2 : escStart - 1; + // Bail out if lineWidth & minContentWidth are shorter than an escape string + if (escapedFolds[j]) + return text; + folds.push(j); + escapedFolds[j] = true; + end = j + endStep; + split = undefined; + } + else { + overflow = true; + } + } + } + prev = ch; + } + if (overflow && onOverflow) + onOverflow(); + if (folds.length === 0) + return text; + if (onFold) + onFold(); + let res = text.slice(0, folds[0]); + for (let i = 0; i < folds.length; ++i) { + const fold = folds[i]; + const end = folds[i + 1] || text.length; + if (fold === 0) + res = `\n${indent}${text.slice(0, end)}`; + else { + if (mode === FOLD_QUOTED && escapedFolds[fold]) + res += `${text[fold]}\\`; + res += `\n${indent}${text.slice(fold + 1, end)}`; + } + } + return res; +} +/** + * Presumes `i + 1` is at the start of a line + * @returns index of last newline in more-indented block + */ +function consumeMoreIndentedLines(text, i, indent) { + let end = i; + let start = i + 1; + let ch = text[start]; + while (ch === ' ' || ch === '\t') { + if (i < start + indent) { + ch = text[++i]; + } + else { + do { + ch = text[++i]; + } while (ch && ch !== '\n'); + end = i; + start = i + 1; + ch = text[start]; + } + } + return end; +} + +exports.FOLD_BLOCK = FOLD_BLOCK; +exports.FOLD_FLOW = FOLD_FLOW; +exports.FOLD_QUOTED = FOLD_QUOTED; +exports.foldFlowLines = foldFlowLines; -"use strict"; -module.exports = require("path"); /***/ }), -/***/ 3193: -/***/ ((module) => { +/***/ 2148: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { "use strict"; -module.exports = require("string_decoder"); -/***/ }), -/***/ 3557: -/***/ ((module) => { +var anchors = __nccwpck_require__(1596); +var identity = __nccwpck_require__(1127); +var stringifyComment = __nccwpck_require__(9799); +var stringifyString = __nccwpck_require__(3069); + +function createStringifyContext(doc, options) { + const opt = Object.assign({ + blockQuote: true, + commentString: stringifyComment.stringifyComment, + defaultKeyType: null, + defaultStringType: 'PLAIN', + directives: null, + doubleQuotedAsJSON: false, + doubleQuotedMinMultiLineLength: 40, + falseStr: 'false', + flowCollectionPadding: true, + indentSeq: true, + lineWidth: 80, + minContentWidth: 20, + nullStr: 'null', + simpleKeys: false, + singleQuote: null, + trueStr: 'true', + verifyAliasOrder: true + }, doc.schema.toStringOptions, options); + let inFlow; + switch (opt.collectionStyle) { + case 'block': + inFlow = false; + break; + case 'flow': + inFlow = true; + break; + default: + inFlow = null; + } + return { + anchors: new Set(), + doc, + flowCollectionPadding: opt.flowCollectionPadding ? ' ' : '', + indent: '', + indentStep: typeof opt.indent === 'number' ? ' '.repeat(opt.indent) : ' ', + inFlow, + options: opt + }; +} +function getTagObject(tags, item) { + if (item.tag) { + const match = tags.filter(t => t.tag === item.tag); + if (match.length > 0) + return match.find(t => t.format === item.format) ?? match[0]; + } + let tagObj = undefined; + let obj; + if (identity.isScalar(item)) { + obj = item.value; + let match = tags.filter(t => t.identify?.(obj)); + if (match.length > 1) { + const testMatch = match.filter(t => t.test); + if (testMatch.length > 0) + match = testMatch; + } + tagObj = + match.find(t => t.format === item.format) ?? match.find(t => !t.format); + } + else { + obj = item; + tagObj = tags.find(t => t.nodeClass && obj instanceof t.nodeClass); + } + if (!tagObj) { + const name = obj?.constructor?.name ?? (obj === null ? 'null' : typeof obj); + throw new Error(`Tag not resolved for ${name} value`); + } + return tagObj; +} +// needs to be called before value stringifier to allow for circular anchor refs +function stringifyProps(node, tagObj, { anchors: anchors$1, doc }) { + if (!doc.directives) + return ''; + const props = []; + const anchor = (identity.isScalar(node) || identity.isCollection(node)) && node.anchor; + if (anchor && anchors.anchorIsValid(anchor)) { + anchors$1.add(anchor); + props.push(`&${anchor}`); + } + const tag = node.tag ?? (tagObj.default ? null : tagObj.tag); + if (tag) + props.push(doc.directives.tagString(tag)); + return props.join(' '); +} +function stringify(item, ctx, onComment, onChompKeep) { + if (identity.isPair(item)) + return item.toString(ctx, onComment, onChompKeep); + if (identity.isAlias(item)) { + if (ctx.doc.directives) + return item.toString(ctx); + if (ctx.resolvedAliases?.has(item)) { + throw new TypeError(`Cannot stringify circular structure without alias nodes`); + } + else { + if (ctx.resolvedAliases) + ctx.resolvedAliases.add(item); + else + ctx.resolvedAliases = new Set([item]); + item = item.resolve(ctx.doc); + } + } + let tagObj = undefined; + const node = identity.isNode(item) + ? item + : ctx.doc.createNode(item, { onTagObj: o => (tagObj = o) }); + tagObj ?? (tagObj = getTagObject(ctx.doc.schema.tags, node)); + const props = stringifyProps(node, tagObj, ctx); + if (props.length > 0) + ctx.indentAtStart = (ctx.indentAtStart ?? 0) + props.length + 1; + const str = typeof tagObj.stringify === 'function' + ? tagObj.stringify(node, ctx, onComment, onChompKeep) + : identity.isScalar(node) + ? stringifyString.stringifyString(node, ctx, onComment, onChompKeep) + : node.toString(ctx, onComment, onChompKeep); + if (!props) + return str; + return identity.isScalar(node) || str[0] === '{' || str[0] === '[' + ? `${props} ${str}` + : `${props}\n${ctx.indent}${str}`; +} + +exports.createStringifyContext = createStringifyContext; +exports.stringify = stringify; -"use strict"; -module.exports = require("timers"); /***/ }), -/***/ 4756: -/***/ ((module) => { +/***/ 1212: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { "use strict"; -module.exports = require("tls"); -/***/ }), -/***/ 9023: -/***/ ((module) => { +var identity = __nccwpck_require__(1127); +var stringify = __nccwpck_require__(2148); +var stringifyComment = __nccwpck_require__(9799); + +function stringifyCollection(collection, ctx, options) { + const flow = ctx.inFlow ?? collection.flow; + const stringify = flow ? stringifyFlowCollection : stringifyBlockCollection; + return stringify(collection, ctx, options); +} +function stringifyBlockCollection({ comment, items }, ctx, { blockItemPrefix, flowChars, itemIndent, onChompKeep, onComment }) { + const { indent, options: { commentString } } = ctx; + const itemCtx = Object.assign({}, ctx, { indent: itemIndent, type: null }); + let chompKeep = false; // flag for the preceding node's status + const lines = []; + for (let i = 0; i < items.length; ++i) { + const item = items[i]; + let comment = null; + if (identity.isNode(item)) { + if (!chompKeep && item.spaceBefore) + lines.push(''); + addCommentBefore(ctx, lines, item.commentBefore, chompKeep); + if (item.comment) + comment = item.comment; + } + else if (identity.isPair(item)) { + const ik = identity.isNode(item.key) ? item.key : null; + if (ik) { + if (!chompKeep && ik.spaceBefore) + lines.push(''); + addCommentBefore(ctx, lines, ik.commentBefore, chompKeep); + } + } + chompKeep = false; + let str = stringify.stringify(item, itemCtx, () => (comment = null), () => (chompKeep = true)); + if (comment) + str += stringifyComment.lineComment(str, itemIndent, commentString(comment)); + if (chompKeep && comment) + chompKeep = false; + lines.push(blockItemPrefix + str); + } + let str; + if (lines.length === 0) { + str = flowChars.start + flowChars.end; + } + else { + str = lines[0]; + for (let i = 1; i < lines.length; ++i) { + const line = lines[i]; + str += line ? `\n${indent}${line}` : '\n'; + } + } + if (comment) { + str += '\n' + stringifyComment.indentComment(commentString(comment), indent); + if (onComment) + onComment(); + } + else if (chompKeep && onChompKeep) + onChompKeep(); + return str; +} +function stringifyFlowCollection({ items }, ctx, { flowChars, itemIndent }) { + const { indent, indentStep, flowCollectionPadding: fcPadding, options: { commentString } } = ctx; + itemIndent += indentStep; + const itemCtx = Object.assign({}, ctx, { + indent: itemIndent, + inFlow: true, + type: null + }); + let reqNewline = false; + let linesAtValue = 0; + const lines = []; + for (let i = 0; i < items.length; ++i) { + const item = items[i]; + let comment = null; + if (identity.isNode(item)) { + if (item.spaceBefore) + lines.push(''); + addCommentBefore(ctx, lines, item.commentBefore, false); + if (item.comment) + comment = item.comment; + } + else if (identity.isPair(item)) { + const ik = identity.isNode(item.key) ? item.key : null; + if (ik) { + if (ik.spaceBefore) + lines.push(''); + addCommentBefore(ctx, lines, ik.commentBefore, false); + if (ik.comment) + reqNewline = true; + } + const iv = identity.isNode(item.value) ? item.value : null; + if (iv) { + if (iv.comment) + comment = iv.comment; + if (iv.commentBefore) + reqNewline = true; + } + else if (item.value == null && ik?.comment) { + comment = ik.comment; + } + } + if (comment) + reqNewline = true; + let str = stringify.stringify(item, itemCtx, () => (comment = null)); + if (i < items.length - 1) + str += ','; + if (comment) + str += stringifyComment.lineComment(str, itemIndent, commentString(comment)); + if (!reqNewline && (lines.length > linesAtValue || str.includes('\n'))) + reqNewline = true; + lines.push(str); + linesAtValue = lines.length; + } + const { start, end } = flowChars; + if (lines.length === 0) { + return start + end; + } + else { + if (!reqNewline) { + const len = lines.reduce((sum, line) => sum + line.length + 2, 2); + reqNewline = ctx.options.lineWidth > 0 && len > ctx.options.lineWidth; + } + if (reqNewline) { + let str = start; + for (const line of lines) + str += line ? `\n${indentStep}${indent}${line}` : '\n'; + return `${str}\n${indent}${end}`; + } + else { + return `${start}${fcPadding}${lines.join(' ')}${fcPadding}${end}`; + } + } +} +function addCommentBefore({ indent, options: { commentString } }, lines, comment, chompKeep) { + if (comment && chompKeep) + comment = comment.replace(/^\n+/, ''); + if (comment) { + const ic = stringifyComment.indentComment(commentString(comment), indent); + lines.push(ic.trimStart()); // Avoid double indent on first line + } +} + +exports.stringifyCollection = stringifyCollection; -"use strict"; -module.exports = require("util"); /***/ }), -/***/ 1120: -/***/ ((module) => { +/***/ 9799: +/***/ ((__unused_webpack_module, exports) => { "use strict"; -var __webpack_unused_export__; -const NullObject = function NullObject () { } -NullObject.prototype = Object.create(null) - /** - * RegExp to match *( ";" parameter ) in RFC 7231 sec 3.1.1.1 + * Stringifies a comment. * - * parameter = token "=" ( token / quoted-string ) - * token = 1*tchar - * tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" - * / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" - * / DIGIT / ALPHA - * ; any VCHAR, except delimiters - * quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE - * qdtext = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text - * obs-text = %x80-FF - * quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) + * Empty comment lines are left empty, + * lines consisting of a single space are replaced by `#`, + * and all other lines are prefixed with a `#`. */ -const paramRE = /; *([!#$%&'*+.^\w`|~-]+)=("(?:[\v\u0020\u0021\u0023-\u005b\u005d-\u007e\u0080-\u00ff]|\\[\v\u0020-\u00ff])*"|[!#$%&'*+.^\w`|~-]+) */gu +const stringifyComment = (str) => str.replace(/^(?!$)(?: $)?/gm, '#'); +function indentComment(comment, indent) { + if (/^\n+$/.test(comment)) + return comment.substring(1); + return indent ? comment.replace(/^(?! *$)/gm, indent) : comment; +} +const lineComment = (str, indent, comment) => str.endsWith('\n') + ? indentComment(comment, indent) + : comment.includes('\n') + ? '\n' + indentComment(comment, indent) + : (str.endsWith(' ') ? '' : ' ') + comment; -/** - * RegExp to match quoted-pair in RFC 7230 sec 3.2.6 - * - * quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) - * obs-text = %x80-FF - */ -const quotedPairRE = /\\([\v\u0020-\u00ff])/gu +exports.indentComment = indentComment; +exports.lineComment = lineComment; +exports.stringifyComment = stringifyComment; -/** - * RegExp to match type in RFC 7231 sec 3.1.1.1 - * - * media-type = type "/" subtype - * type = token - * subtype = token - */ -const mediaTypeRE = /^[!#$%&'*+.^\w|~-]+\/[!#$%&'*+.^\w|~-]+$/u -// default ContentType to prevent repeated object creation -const defaultContentType = { type: '', parameters: new NullObject() } -Object.freeze(defaultContentType.parameters) -Object.freeze(defaultContentType) +/***/ }), -/** - * Parse media type to object. - * - * @param {string|object} header - * @return {Object} - * @public - */ +/***/ 6829: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { -function parse (header) { - if (typeof header !== 'string') { - throw new TypeError('argument header is required and must be a string') - } +"use strict"; - let index = header.indexOf(';') - const type = index !== -1 - ? header.slice(0, index).trim() - : header.trim() - if (mediaTypeRE.test(type) === false) { - throw new TypeError('invalid media type') - } +var identity = __nccwpck_require__(1127); +var stringify = __nccwpck_require__(2148); +var stringifyComment = __nccwpck_require__(9799); + +function stringifyDocument(doc, options) { + const lines = []; + let hasDirectives = options.directives === true; + if (options.directives !== false && doc.directives) { + const dir = doc.directives.toString(doc); + if (dir) { + lines.push(dir); + hasDirectives = true; + } + else if (doc.directives.docStart) + hasDirectives = true; + } + if (hasDirectives) + lines.push('---'); + const ctx = stringify.createStringifyContext(doc, options); + const { commentString } = ctx.options; + if (doc.commentBefore) { + if (lines.length !== 1) + lines.unshift(''); + const cs = commentString(doc.commentBefore); + lines.unshift(stringifyComment.indentComment(cs, '')); + } + let chompKeep = false; + let contentComment = null; + if (doc.contents) { + if (identity.isNode(doc.contents)) { + if (doc.contents.spaceBefore && hasDirectives) + lines.push(''); + if (doc.contents.commentBefore) { + const cs = commentString(doc.contents.commentBefore); + lines.push(stringifyComment.indentComment(cs, '')); + } + // top-level block scalars need to be indented if followed by a comment + ctx.forceBlockIndent = !!doc.comment; + contentComment = doc.contents.comment; + } + const onChompKeep = contentComment ? undefined : () => (chompKeep = true); + let body = stringify.stringify(doc.contents, ctx, () => (contentComment = null), onChompKeep); + if (contentComment) + body += stringifyComment.lineComment(body, '', commentString(contentComment)); + if ((body[0] === '|' || body[0] === '>') && + lines[lines.length - 1] === '---') { + // Top-level block scalars with a preceding doc marker ought to use the + // same line for their header. + lines[lines.length - 1] = `--- ${body}`; + } + else + lines.push(body); + } + else { + lines.push(stringify.stringify(doc.contents, ctx)); + } + if (doc.directives?.docEnd) { + if (doc.comment) { + const cs = commentString(doc.comment); + if (cs.includes('\n')) { + lines.push('...'); + lines.push(stringifyComment.indentComment(cs, '')); + } + else { + lines.push(`... ${cs}`); + } + } + else { + lines.push('...'); + } + } + else { + let dc = doc.comment; + if (dc && chompKeep) + dc = dc.replace(/^\n+/, ''); + if (dc) { + if ((!chompKeep || contentComment) && lines[lines.length - 1] !== '') + lines.push(''); + lines.push(stringifyComment.indentComment(commentString(dc), '')); + } + } + return lines.join('\n') + '\n'; +} - const result = { - type: type.toLowerCase(), - parameters: new NullObject() - } +exports.stringifyDocument = stringifyDocument; - // parse parameters - if (index === -1) { - return result - } - let key - let match - let value +/***/ }), - paramRE.lastIndex = index +/***/ 8689: +/***/ ((__unused_webpack_module, exports) => { - while ((match = paramRE.exec(header))) { - if (match.index !== index) { - throw new TypeError('invalid parameter format') +"use strict"; + + +function stringifyNumber({ format, minFractionDigits, tag, value }) { + if (typeof value === 'bigint') + return String(value); + const num = typeof value === 'number' ? value : Number(value); + if (!isFinite(num)) + return isNaN(num) ? '.nan' : num < 0 ? '-.inf' : '.inf'; + let n = Object.is(value, -0) ? '-0' : JSON.stringify(value); + if (!format && + minFractionDigits && + (!tag || tag === 'tag:yaml.org,2002:float') && + /^\d/.test(n)) { + let i = n.indexOf('.'); + if (i < 0) { + i = n.length; + n += '.'; + } + let d = minFractionDigits - (n.length - i - 1); + while (d-- > 0) + n += '0'; } + return n; +} - index += match[0].length - key = match[1].toLowerCase() - value = match[2] +exports.stringifyNumber = stringifyNumber; - if (value[0] === '"') { - // remove quotes and escapes - value = value - .slice(1, value.length - 1) - quotedPairRE.test(value) && (value = value.replace(quotedPairRE, '$1')) - } +/***/ }), - result.parameters[key] = value - } +/***/ 9748: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { - if (index !== header.length) { - throw new TypeError('invalid parameter format') - } +"use strict"; - return result + +var identity = __nccwpck_require__(1127); +var Scalar = __nccwpck_require__(3301); +var stringify = __nccwpck_require__(2148); +var stringifyComment = __nccwpck_require__(9799); + +function stringifyPair({ key, value }, ctx, onComment, onChompKeep) { + const { allNullValues, doc, indent, indentStep, options: { commentString, indentSeq, simpleKeys } } = ctx; + let keyComment = (identity.isNode(key) && key.comment) || null; + if (simpleKeys) { + if (keyComment) { + throw new Error('With simple keys, key nodes cannot have comments'); + } + if (identity.isCollection(key) || (!identity.isNode(key) && typeof key === 'object')) { + const msg = 'With simple keys, collection cannot be used as a key value'; + throw new Error(msg); + } + } + let explicitKey = !simpleKeys && + (!key || + (keyComment && value == null && !ctx.inFlow) || + identity.isCollection(key) || + (identity.isScalar(key) + ? key.type === Scalar.Scalar.BLOCK_FOLDED || key.type === Scalar.Scalar.BLOCK_LITERAL + : typeof key === 'object')); + ctx = Object.assign({}, ctx, { + allNullValues: false, + implicitKey: !explicitKey && (simpleKeys || !allNullValues), + indent: indent + indentStep + }); + let keyCommentDone = false; + let chompKeep = false; + let str = stringify.stringify(key, ctx, () => (keyCommentDone = true), () => (chompKeep = true)); + if (!explicitKey && !ctx.inFlow && str.length > 1024) { + if (simpleKeys) + throw new Error('With simple keys, single line scalar must not span more than 1024 characters'); + explicitKey = true; + } + if (ctx.inFlow) { + if (allNullValues || value == null) { + if (keyCommentDone && onComment) + onComment(); + return str === '' ? '?' : explicitKey ? `? ${str}` : str; + } + } + else if ((allNullValues && !simpleKeys) || (value == null && explicitKey)) { + str = `? ${str}`; + if (keyComment && !keyCommentDone) { + str += stringifyComment.lineComment(str, ctx.indent, commentString(keyComment)); + } + else if (chompKeep && onChompKeep) + onChompKeep(); + return str; + } + if (keyCommentDone) + keyComment = null; + if (explicitKey) { + if (keyComment) + str += stringifyComment.lineComment(str, ctx.indent, commentString(keyComment)); + str = `? ${str}\n${indent}:`; + } + else { + str = `${str}:`; + if (keyComment) + str += stringifyComment.lineComment(str, ctx.indent, commentString(keyComment)); + } + let vsb, vcb, valueComment; + if (identity.isNode(value)) { + vsb = !!value.spaceBefore; + vcb = value.commentBefore; + valueComment = value.comment; + } + else { + vsb = false; + vcb = null; + valueComment = null; + if (value && typeof value === 'object') + value = doc.createNode(value); + } + ctx.implicitKey = false; + if (!explicitKey && !keyComment && identity.isScalar(value)) + ctx.indentAtStart = str.length + 1; + chompKeep = false; + if (!indentSeq && + indentStep.length >= 2 && + !ctx.inFlow && + !explicitKey && + identity.isSeq(value) && + !value.flow && + !value.tag && + !value.anchor) { + // If indentSeq === false, consider '- ' as part of indentation where possible + ctx.indent = ctx.indent.substring(2); + } + let valueCommentDone = false; + const valueStr = stringify.stringify(value, ctx, () => (valueCommentDone = true), () => (chompKeep = true)); + let ws = ' '; + if (keyComment || vsb || vcb) { + ws = vsb ? '\n' : ''; + if (vcb) { + const cs = commentString(vcb); + ws += `\n${stringifyComment.indentComment(cs, ctx.indent)}`; + } + if (valueStr === '' && !ctx.inFlow) { + if (ws === '\n' && valueComment) + ws = '\n\n'; + } + else { + ws += `\n${ctx.indent}`; + } + } + else if (!explicitKey && identity.isCollection(value)) { + const vs0 = valueStr[0]; + const nl0 = valueStr.indexOf('\n'); + const hasNewline = nl0 !== -1; + const flow = ctx.inFlow ?? value.flow ?? value.items.length === 0; + if (hasNewline || !flow) { + let hasPropsLine = false; + if (hasNewline && (vs0 === '&' || vs0 === '!')) { + let sp0 = valueStr.indexOf(' '); + if (vs0 === '&' && + sp0 !== -1 && + sp0 < nl0 && + valueStr[sp0 + 1] === '!') { + sp0 = valueStr.indexOf(' ', sp0 + 1); + } + if (sp0 === -1 || nl0 < sp0) + hasPropsLine = true; + } + if (!hasPropsLine) + ws = `\n${ctx.indent}`; + } + } + else if (valueStr === '' || valueStr[0] === '\n') { + ws = ''; + } + str += ws + valueStr; + if (ctx.inFlow) { + if (valueCommentDone && onComment) + onComment(); + } + else if (valueComment && !valueCommentDone) { + str += stringifyComment.lineComment(str, ctx.indent, commentString(valueComment)); + } + else if (chompKeep && onChompKeep) { + onChompKeep(); + } + return str; } -function safeParse (header) { - if (typeof header !== 'string') { - return defaultContentType - } +exports.stringifyPair = stringifyPair; - let index = header.indexOf(';') - const type = index !== -1 - ? header.slice(0, index).trim() - : header.trim() - if (mediaTypeRE.test(type) === false) { - return defaultContentType - } +/***/ }), - const result = { - type: type.toLowerCase(), - parameters: new NullObject() - } +/***/ 3069: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { - // parse parameters - if (index === -1) { - return result - } +"use strict"; - let key - let match - let value - paramRE.lastIndex = index +var Scalar = __nccwpck_require__(3301); +var foldFlowLines = __nccwpck_require__(4475); - while ((match = paramRE.exec(header))) { - if (match.index !== index) { - return defaultContentType +const getFoldOptions = (ctx, isBlock) => ({ + indentAtStart: isBlock ? ctx.indent.length : ctx.indentAtStart, + lineWidth: ctx.options.lineWidth, + minContentWidth: ctx.options.minContentWidth +}); +// Also checks for lines starting with %, as parsing the output as YAML 1.1 will +// presume that's starting a new document. +const containsDocumentMarker = (str) => /^(%|---|\.\.\.)/m.test(str); +function lineLengthOverLimit(str, lineWidth, indentLength) { + if (!lineWidth || lineWidth < 0) + return false; + const limit = lineWidth - indentLength; + const strLen = str.length; + if (strLen <= limit) + return false; + for (let i = 0, start = 0; i < strLen; ++i) { + if (str[i] === '\n') { + if (i - start > limit) + return true; + start = i + 1; + if (strLen - start <= limit) + return false; + } + } + return true; +} +function doubleQuotedString(value, ctx) { + const json = JSON.stringify(value); + if (ctx.options.doubleQuotedAsJSON) + return json; + const { implicitKey } = ctx; + const minMultiLineLength = ctx.options.doubleQuotedMinMultiLineLength; + const indent = ctx.indent || (containsDocumentMarker(value) ? ' ' : ''); + let str = ''; + let start = 0; + for (let i = 0, ch = json[i]; ch; ch = json[++i]) { + if (ch === ' ' && json[i + 1] === '\\' && json[i + 2] === 'n') { + // space before newline needs to be escaped to not be folded + str += json.slice(start, i) + '\\ '; + i += 1; + start = i; + ch = '\\'; + } + if (ch === '\\') + switch (json[i + 1]) { + case 'u': + { + str += json.slice(start, i); + const code = json.substr(i + 2, 4); + switch (code) { + case '0000': + str += '\\0'; + break; + case '0007': + str += '\\a'; + break; + case '000b': + str += '\\v'; + break; + case '001b': + str += '\\e'; + break; + case '0085': + str += '\\N'; + break; + case '00a0': + str += '\\_'; + break; + case '2028': + str += '\\L'; + break; + case '2029': + str += '\\P'; + break; + default: + if (code.substr(0, 2) === '00') + str += '\\x' + code.substr(2); + else + str += json.substr(i, 6); + } + i += 5; + start = i + 1; + } + break; + case 'n': + if (implicitKey || + json[i + 2] === '"' || + json.length < minMultiLineLength) { + i += 1; + } + else { + // folding will eat first newline + str += json.slice(start, i) + '\n\n'; + while (json[i + 2] === '\\' && + json[i + 3] === 'n' && + json[i + 4] !== '"') { + str += '\n'; + i += 2; + } + str += indent; + // space after newline needs to be escaped to not be folded + if (json[i + 2] === ' ') + str += '\\'; + i += 1; + start = i + 1; + } + break; + default: + i += 1; + } + } + str = start ? str + json.slice(start) : json; + return implicitKey + ? str + : foldFlowLines.foldFlowLines(str, indent, foldFlowLines.FOLD_QUOTED, getFoldOptions(ctx, false)); +} +function singleQuotedString(value, ctx) { + if (ctx.options.singleQuote === false || + (ctx.implicitKey && value.includes('\n')) || + /[ \t]\n|\n[ \t]/.test(value) // single quoted string can't have leading or trailing whitespace around newline + ) + return doubleQuotedString(value, ctx); + const indent = ctx.indent || (containsDocumentMarker(value) ? ' ' : ''); + const res = "'" + value.replace(/'/g, "''").replace(/\n+/g, `$&\n${indent}`) + "'"; + return ctx.implicitKey + ? res + : foldFlowLines.foldFlowLines(res, indent, foldFlowLines.FOLD_FLOW, getFoldOptions(ctx, false)); +} +function quotedString(value, ctx) { + const { singleQuote } = ctx.options; + let qs; + if (singleQuote === false) + qs = doubleQuotedString; + else { + const hasDouble = value.includes('"'); + const hasSingle = value.includes("'"); + if (hasDouble && !hasSingle) + qs = singleQuotedString; + else if (hasSingle && !hasDouble) + qs = doubleQuotedString; + else + qs = singleQuote ? singleQuotedString : doubleQuotedString; + } + return qs(value, ctx); +} +// The negative lookbehind avoids a polynomial search, +// but isn't supported yet on Safari: https://caniuse.com/js-regexp-lookbehind +let blockEndNewlines; +try { + blockEndNewlines = new RegExp('(^|(?\n'; + // determine chomping from whitespace at value end + let chomp; + let endStart; + for (endStart = value.length; endStart > 0; --endStart) { + const ch = value[endStart - 1]; + if (ch !== '\n' && ch !== '\t' && ch !== ' ') + break; + } + let end = value.substring(endStart); + const endNlPos = end.indexOf('\n'); + if (endNlPos === -1) { + chomp = '-'; // strip + } + else if (value === end || endNlPos !== end.length - 1) { + chomp = '+'; // keep + if (onChompKeep) + onChompKeep(); + } + else { + chomp = ''; // clip + } + if (end) { + value = value.slice(0, -end.length); + if (end[end.length - 1] === '\n') + end = end.slice(0, -1); + end = end.replace(blockEndNewlines, `$&${indent}`); + } + // determine indent indicator from whitespace at value start + let startWithSpace = false; + let startEnd; + let startNlPos = -1; + for (startEnd = 0; startEnd < value.length; ++startEnd) { + const ch = value[startEnd]; + if (ch === ' ') + startWithSpace = true; + else if (ch === '\n') + startNlPos = startEnd; + else + break; + } + let start = value.substring(0, startNlPos < startEnd ? startNlPos + 1 : startEnd); + if (start) { + value = value.substring(start.length); + start = start.replace(/\n+/g, `$&${indent}`); + } + const indentSize = indent ? '2' : '1'; // root is at -1 + // Leading | or > is added later + let header = (startWithSpace ? indentSize : '') + chomp; + if (comment) { + header += ' ' + commentString(comment.replace(/ ?[\r\n]+/g, ' ')); + if (onComment) + onComment(); + } + if (!literal) { + const foldedValue = value + .replace(/\n+/g, '\n$&') + .replace(/(?:^|\n)([\t ].*)(?:([\n\t ]*)\n(?![\n\t ]))?/g, '$1$2') // more-indented lines aren't folded + // ^ more-ind. ^ empty ^ capture next empty lines only at end of indent + .replace(/\n+/g, `$&${indent}`); + let literalFallback = false; + const foldOptions = getFoldOptions(ctx, true); + if (blockQuote !== 'folded' && type !== Scalar.Scalar.BLOCK_FOLDED) { + foldOptions.onOverflow = () => { + literalFallback = true; + }; + } + const body = foldFlowLines.foldFlowLines(`${start}${foldedValue}${end}`, indent, foldFlowLines.FOLD_BLOCK, foldOptions); + if (!literalFallback) + return `>${header}\n${indent}${body}`; + } + value = value.replace(/\n+/g, `$&${indent}`); + return `|${header}\n${indent}${start}${value}${end}`; +} +function plainString(item, ctx, onComment, onChompKeep) { + const { type, value } = item; + const { actualString, implicitKey, indent, indentStep, inFlow } = ctx; + if ((implicitKey && value.includes('\n')) || + (inFlow && /[[\]{},]/.test(value))) { + return quotedString(value, ctx); + } + if (/^[\n\t ,[\]{}#&*!|>'"%@`]|^[?-]$|^[?-][ \t]|[\n:][ \t]|[ \t]\n|[\n\t ]#|[\n\t :]$/.test(value)) { + // not allowed: + // - '-' or '?' + // - start with an indicator character (except [?:-]) or /[?-] / + // - '\n ', ': ' or ' \n' anywhere + // - '#' not preceded by a non-space char + // - end with ' ' or ':' + return implicitKey || inFlow || !value.includes('\n') + ? quotedString(value, ctx) + : blockString(item, ctx, onComment, onChompKeep); + } + if (!implicitKey && + !inFlow && + type !== Scalar.Scalar.PLAIN && + value.includes('\n')) { + // Where allowed & type not set explicitly, prefer block style for multiline strings + return blockString(item, ctx, onComment, onChompKeep); + } + if (containsDocumentMarker(value)) { + if (indent === '') { + ctx.forceBlockIndent = true; + return blockString(item, ctx, onComment, onChompKeep); + } + else if (implicitKey && indent === indentStep) { + return quotedString(value, ctx); + } + } + const str = value.replace(/\n+/g, `$&\n${indent}`); + // Verify that output will be parsed as a string, as e.g. plain numbers and + // booleans get parsed with those types in v1.2 (e.g. '42', 'true' & '0.9e-3'), + // and others in v1.1. + if (actualString) { + const test = (tag) => tag.default && tag.tag !== 'tag:yaml.org,2002:str' && tag.test?.test(str); + const { compat, tags } = ctx.doc.schema; + if (tags.some(test) || compat?.some(test)) + return quotedString(value, ctx); + } + return implicitKey + ? str + : foldFlowLines.foldFlowLines(str, indent, foldFlowLines.FOLD_FLOW, getFoldOptions(ctx, false)); +} +function stringifyString(item, ctx, onComment, onChompKeep) { + const { implicitKey, inFlow } = ctx; + const ss = typeof item.value === 'string' + ? item + : Object.assign({}, item, { value: String(item.value) }); + let { type } = item; + if (type !== Scalar.Scalar.QUOTE_DOUBLE) { + // force double quotes on control characters & unpaired surrogates + if (/[\x00-\x08\x0b-\x1f\x7f-\x9f\u{D800}-\u{DFFF}]/u.test(ss.value)) + type = Scalar.Scalar.QUOTE_DOUBLE; + } + const _stringify = (_type) => { + switch (_type) { + case Scalar.Scalar.BLOCK_FOLDED: + case Scalar.Scalar.BLOCK_LITERAL: + return implicitKey || inFlow + ? quotedString(ss.value, ctx) // blocks are not valid inside flow containers + : blockString(ss, ctx, onComment, onChompKeep); + case Scalar.Scalar.QUOTE_DOUBLE: + return doubleQuotedString(ss.value, ctx); + case Scalar.Scalar.QUOTE_SINGLE: + return singleQuotedString(ss.value, ctx); + case Scalar.Scalar.PLAIN: + return plainString(ss, ctx, onComment, onChompKeep); + default: + return null; + } + }; + let res = _stringify(type); + if (res === null) { + const { defaultKeyType, defaultStringType } = ctx.options; + const t = (implicitKey && defaultKeyType) || defaultStringType; + res = _stringify(t); + if (res === null) + throw new Error(`Unsupported default string type ${t}`); } + return res; +} - index += match[0].length - key = match[1].toLowerCase() - value = match[2] +exports.stringifyString = stringifyString; - if (value[0] === '"') { - // remove quotes and escapes - value = value - .slice(1, value.length - 1) - quotedPairRE.test(value) && (value = value.replace(quotedPairRE, '$1')) - } +/***/ }), - result.parameters[key] = value - } +/***/ 204: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { - if (index !== header.length) { - return defaultContentType - } +"use strict"; - return result + +var identity = __nccwpck_require__(1127); + +const BREAK = Symbol('break visit'); +const SKIP = Symbol('skip children'); +const REMOVE = Symbol('remove node'); +/** + * Apply a visitor to an AST node or document. + * + * Walks through the tree (depth-first) starting from `node`, calling a + * `visitor` function with three arguments: + * - `key`: For sequence values and map `Pair`, the node's index in the + * collection. Within a `Pair`, `'key'` or `'value'`, correspondingly. + * `null` for the root node. + * - `node`: The current node. + * - `path`: The ancestry of the current node. + * + * The return value of the visitor may be used to control the traversal: + * - `undefined` (default): Do nothing and continue + * - `visit.SKIP`: Do not visit the children of this node, continue with next + * sibling + * - `visit.BREAK`: Terminate traversal completely + * - `visit.REMOVE`: Remove the current node, then continue with the next one + * - `Node`: Replace the current node, then continue by visiting it + * - `number`: While iterating the items of a sequence or map, set the index + * of the next step. This is useful especially if the index of the current + * node has changed. + * + * If `visitor` is a single function, it will be called with all values + * encountered in the tree, including e.g. `null` values. Alternatively, + * separate visitor functions may be defined for each `Map`, `Pair`, `Seq`, + * `Alias` and `Scalar` node. To define the same visitor function for more than + * one node type, use the `Collection` (map and seq), `Value` (map, seq & scalar) + * and `Node` (alias, map, seq & scalar) targets. Of all these, only the most + * specific defined one will be used for each node. + */ +function visit(node, visitor) { + const visitor_ = initVisitor(visitor); + if (identity.isDocument(node)) { + const cd = visit_(null, node.contents, visitor_, Object.freeze([node])); + if (cd === REMOVE) + node.contents = null; + } + else + visit_(null, node, visitor_, Object.freeze([])); +} +// Without the `as symbol` casts, TS declares these in the `visit` +// namespace using `var`, but then complains about that because +// `unique symbol` must be `const`. +/** Terminate visit traversal completely */ +visit.BREAK = BREAK; +/** Do not visit the children of the current node */ +visit.SKIP = SKIP; +/** Remove the current node */ +visit.REMOVE = REMOVE; +function visit_(key, node, visitor, path) { + const ctrl = callVisitor(key, node, visitor, path); + if (identity.isNode(ctrl) || identity.isPair(ctrl)) { + replaceNode(key, path, ctrl); + return visit_(key, ctrl, visitor, path); + } + if (typeof ctrl !== 'symbol') { + if (identity.isCollection(node)) { + path = Object.freeze(path.concat(node)); + for (let i = 0; i < node.items.length; ++i) { + const ci = visit_(i, node.items[i], visitor, path); + if (typeof ci === 'number') + i = ci - 1; + else if (ci === BREAK) + return BREAK; + else if (ci === REMOVE) { + node.items.splice(i, 1); + i -= 1; + } + } + } + else if (identity.isPair(node)) { + path = Object.freeze(path.concat(node)); + const ck = visit_('key', node.key, visitor, path); + if (ck === BREAK) + return BREAK; + else if (ck === REMOVE) + node.key = null; + const cv = visit_('value', node.value, visitor, path); + if (cv === BREAK) + return BREAK; + else if (cv === REMOVE) + node.value = null; + } + } + return ctrl; +} +/** + * Apply an async visitor to an AST node or document. + * + * Walks through the tree (depth-first) starting from `node`, calling a + * `visitor` function with three arguments: + * - `key`: For sequence values and map `Pair`, the node's index in the + * collection. Within a `Pair`, `'key'` or `'value'`, correspondingly. + * `null` for the root node. + * - `node`: The current node. + * - `path`: The ancestry of the current node. + * + * The return value of the visitor may be used to control the traversal: + * - `Promise`: Must resolve to one of the following values + * - `undefined` (default): Do nothing and continue + * - `visit.SKIP`: Do not visit the children of this node, continue with next + * sibling + * - `visit.BREAK`: Terminate traversal completely + * - `visit.REMOVE`: Remove the current node, then continue with the next one + * - `Node`: Replace the current node, then continue by visiting it + * - `number`: While iterating the items of a sequence or map, set the index + * of the next step. This is useful especially if the index of the current + * node has changed. + * + * If `visitor` is a single function, it will be called with all values + * encountered in the tree, including e.g. `null` values. Alternatively, + * separate visitor functions may be defined for each `Map`, `Pair`, `Seq`, + * `Alias` and `Scalar` node. To define the same visitor function for more than + * one node type, use the `Collection` (map and seq), `Value` (map, seq & scalar) + * and `Node` (alias, map, seq & scalar) targets. Of all these, only the most + * specific defined one will be used for each node. + */ +async function visitAsync(node, visitor) { + const visitor_ = initVisitor(visitor); + if (identity.isDocument(node)) { + const cd = await visitAsync_(null, node.contents, visitor_, Object.freeze([node])); + if (cd === REMOVE) + node.contents = null; + } + else + await visitAsync_(null, node, visitor_, Object.freeze([])); +} +// Without the `as symbol` casts, TS declares these in the `visit` +// namespace using `var`, but then complains about that because +// `unique symbol` must be `const`. +/** Terminate visit traversal completely */ +visitAsync.BREAK = BREAK; +/** Do not visit the children of the current node */ +visitAsync.SKIP = SKIP; +/** Remove the current node */ +visitAsync.REMOVE = REMOVE; +async function visitAsync_(key, node, visitor, path) { + const ctrl = await callVisitor(key, node, visitor, path); + if (identity.isNode(ctrl) || identity.isPair(ctrl)) { + replaceNode(key, path, ctrl); + return visitAsync_(key, ctrl, visitor, path); + } + if (typeof ctrl !== 'symbol') { + if (identity.isCollection(node)) { + path = Object.freeze(path.concat(node)); + for (let i = 0; i < node.items.length; ++i) { + const ci = await visitAsync_(i, node.items[i], visitor, path); + if (typeof ci === 'number') + i = ci - 1; + else if (ci === BREAK) + return BREAK; + else if (ci === REMOVE) { + node.items.splice(i, 1); + i -= 1; + } + } + } + else if (identity.isPair(node)) { + path = Object.freeze(path.concat(node)); + const ck = await visitAsync_('key', node.key, visitor, path); + if (ck === BREAK) + return BREAK; + else if (ck === REMOVE) + node.key = null; + const cv = await visitAsync_('value', node.value, visitor, path); + if (cv === BREAK) + return BREAK; + else if (cv === REMOVE) + node.value = null; + } + } + return ctrl; +} +function initVisitor(visitor) { + if (typeof visitor === 'object' && + (visitor.Collection || visitor.Node || visitor.Value)) { + return Object.assign({ + Alias: visitor.Node, + Map: visitor.Node, + Scalar: visitor.Node, + Seq: visitor.Node + }, visitor.Value && { + Map: visitor.Value, + Scalar: visitor.Value, + Seq: visitor.Value + }, visitor.Collection && { + Map: visitor.Collection, + Seq: visitor.Collection + }, visitor); + } + return visitor; +} +function callVisitor(key, node, visitor, path) { + if (typeof visitor === 'function') + return visitor(key, node, path); + if (identity.isMap(node)) + return visitor.Map?.(key, node, path); + if (identity.isSeq(node)) + return visitor.Seq?.(key, node, path); + if (identity.isPair(node)) + return visitor.Pair?.(key, node, path); + if (identity.isScalar(node)) + return visitor.Scalar?.(key, node, path); + if (identity.isAlias(node)) + return visitor.Alias?.(key, node, path); + return undefined; +} +function replaceNode(key, path, node) { + const parent = path[path.length - 1]; + if (identity.isCollection(parent)) { + parent.items[key] = node; + } + else if (identity.isPair(parent)) { + if (key === 'key') + parent.key = node; + else + parent.value = node; + } + else if (identity.isDocument(parent)) { + parent.contents = node; + } + else { + const pt = identity.isAlias(parent) ? 'alias' : 'scalar'; + throw new Error(`Cannot replace node with ${pt} parent`); + } } -__webpack_unused_export__ = { parse, safeParse } -__webpack_unused_export__ = parse -module.exports.xL = safeParse -__webpack_unused_export__ = defaultContentType +exports.visit = visit; +exports.visitAsync = visitAsync; /***/ }), diff --git a/dist/licenses.txt b/dist/licenses.txt index 6e8855b..b40386c 100644 --- a/dist/licenses.txt +++ b/dist/licenses.txt @@ -559,3 +559,20 @@ Copyright (c) 2018-2021, Gregor Martynus (https://github.com/gr2m) Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + +yaml +ISC +Copyright Eemeli Aro + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. diff --git a/plans/scheduled-agents-update.md b/plans/scheduled-agents-update.md new file mode 100644 index 0000000..3d4e336 --- /dev/null +++ b/plans/scheduled-agents-update.md @@ -0,0 +1,344 @@ +# Scheduled AGENTS.md Update Feature - Integration Plan + +## Overview + +This document outlines the integration plan for adding **scheduled (cron-based) AGENTS.md file updates** to zai-code-bot. The feature will: + +1. Run on a configurable schedule (default: weekly) +2. Fetch command text from a Gist URL +3. Execute the command to update AGENTS.md files +4. Create a PR with changes if files are modified +5. Support flexible YAML-based configuration +6. Provide extensible architecture for future scheduled tasks + +--- + +## Architecture + +### Current System Flow +``` +GitHub Event → src/index.js (run()) → Event Router → Handler → Response +``` + +### Proposed Addition +``` +schedule Event → src/index.js → handleScheduleEvent() → Scheduled Handler → PR Creation +``` + +### Key Components + +| Component | File | Purpose | +|-----------|------|---------| +| Event Detection | `src/lib/events.js` | Detect and route schedule events | +| Configuration | `src/lib/config/scheduled-config.js` | Load and validate `.zai-scheduled.yml` | +| Main Handler | `src/lib/handlers/scheduled.js` | Execute scheduled tasks | +| Utility Functions | `src/lib/handlers/scheduled.js` | File operations, PR creation, URL fetching | + +--- + +## Configuration + +### File: `.zai-scheduled.yml` (Consumer Repository) + +```yaml +version: 1 + +defaults: + branch: main + schedule: "0 0 * * 0" # Weekly on Sunday + # Gist URL can also be set via ZAI_AGENTS_GIST_URL environment variable + gist_url: https://gist.githubusercontent.com/AndreiDrang/1580ae796fe56074b600cee6352a5f14/raw + +tasks: + - id: weekly-agents-update + name: "Weekly AGENTS.md Update" + enabled: true + schedule: "0 0 * * 0" + command: update-agents + config: + branch: main + # gist_url is optional - will use defaults.gist_url or ZAI_AGENTS_GIST_URL env var + files: + - AGENTS.md + - src/lib/AGENTS.md + pr_title: "chore: update AGENTS.md files" + pr_body: "Automated weekly update of AGENTS.md files from gist" + commit_message: "docs: update AGENTS.md from scheduled task" +``` + +### Workflow File: `.github/workflows/zai-scheduled.yml` (Consumer Repository) + +```yaml +name: Zai Scheduled Tasks + +on: + schedule: + - cron: "0 0 * * 0" + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + zai-scheduled: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: AndreiDrang/zai-code-bot@main + with: + ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }} + ZAI_MODEL: ${{ vars.ZAI_MODEL }} + ZAI_SCHEDULED_ENABLED: "true" + ZAI_AGENTS_GIST_URL: ${{ vars.ZAI_AGENTS_GIST_URL }} # Or use secrets +``` + +--- + +## Implementation Phases + +### Phase 1: Event Infrastructure (PRIORITY: HIGH) + +**Files to Modify:** +- `src/lib/events.js` - Add `schedule` event type support +- `src/index.js` - Add schedule event routing + +**Changes:** +```javascript +// In src/lib/events.js +function getEventType(context) { + // ... existing + if (eventName === 'schedule') { + return 'schedule'; + } + // ... +} + +function shouldProcessEvent(context) { + // ... existing + if (eventType === 'schedule') { + return { process: true, reason: 'schedule event' }; + } + // ... +} +``` + +### Phase 2: Configuration System (PRIORITY: HIGH) + +**New File:** `src/lib/config/scheduled-config.js` + +**Functions:** +- `loadScheduledConfig(octokit, owner, repo, ref)` - Load and parse YAML config +- `validateConfig(config)` - Schema validation +- `getTasksToRun(config, eventSchedule)` - Filter tasks by schedule + +### Phase 3: Scheduled Task Handler (PRIORITY: HIGH) + +**New File:** `src/lib/handlers/scheduled.js` + +**Main Functions:** +- `handleScheduledEvent(context, apiKey, model, owner, repo)` - Entry point +- `executeScheduledTask(params)` - Execute individual task +- `getScheduledHandler(command)` - Get handler for command +- `handleUpdateAgentsTask(context)` - **Main feature: Update AGENTS.md files** + +**Utility Functions:** +- `fetchFromUrl(url)` - HTTP GET request +- `fetchFileContent(octokit, owner, repo, path, ref)` - Get file from GitHub +- `updateFileInRepo(octokit, owner, repo, path, content, ref, message)` - Update file +- `createPR(octokit, owner, repo, params)` - Create pull request with changes + +**AGENTS.md Update Logic:** +1. Fetch command from Gist URL +2. Parse as `/zai` command +3. Execute command to generate new content +4. Compare with existing files +5. Create PR if changes detected + +### Phase 4: Integration (PRIORITY: MEDIUM) + +**Files to Modify:** +- `src/lib/handlers/index.js` - Export scheduled handler +- `action.yml` - Add new inputs: + - `ZAI_SCHEDULED_ENABLED` (default: true) + - `ZAI_SCHEDULED_CONFIG_PATH` (default: .zai-scheduled.yml) + +### Phase 5: Optional Manual Command (PRIORITY: LOW) + +For testing/debugging, add `/zai update-agents` command: +- `src/lib/commands.js` - Add to ALLOWED_COMMANDS +- `src/index.js` - Add case in dispatchCommand + +--- + +## Extensibility Design + +### Adding New Scheduled Commands + +1. **Create handler function** in `scheduled.js`: +```javascript +async function handleNewTask(context) { + // Custom logic here + return { success: true, changes: [...], message: "..." }; +} +``` + +2. **Register in handler map**: +```javascript +function getScheduledHandler(command) { + const handlers = { + 'update-agents': handleUpdateAgentsTask, + 'new-task': handleNewTask, // NEW + }; + return handlers[command] || null; +} +``` + +3. **Add to configuration** (optional): +```yaml +tasks: + - id: my-new-task + command: new-task + config: + # Custom configuration + setting1: value1 +``` + +### Configuration Flexibility + +- Each task receives full `task.config` object +- Handlers interpret configuration as needed +- Schema validation is optional per-task + +--- + +## Error Handling + +- Individual task failures don't stop other tasks +- All errors logged with correlation IDs +- No PR created on complete failure +- Partial success reported (some files updated, others failed) + +--- + +## Security Considerations + +- Uses existing `GITHUB_TOKEN` with repository scope +- Configuration validated against schema +- Gist URLs validated before fetching +- No user interaction (runs with repo permissions) +- Requires `contents: write` permission for PR creation + +--- + +## Performance + +- Sequential task execution with 5-second delays +- Respects GitHub API rate limits +- Memory bounded per task +- Typical execution: < 5 minutes + +--- + +## Files Summary + +### New Files +| File | Purpose | +|------|---------| +| `src/lib/config/scheduled-config.js` | Configuration loading/validation | +| `src/lib/handlers/scheduled.js` | Scheduled task handler | + +### Modified Files +| File | Changes | +|------|---------| +| `src/lib/events.js` | Add schedule event support | +| `src/index.js` | Add schedule event routing | +| `action.yml` | Add new inputs | +| `src/lib/handlers/index.js` | Export scheduled handler | +| `src/lib/commands.js` | Add update-agents command (optional) | + +### Consumer Files (Not in this repo) +| File | Purpose | +|------|---------| +| `.zai-scheduled.yml` | Task configuration | +| `.github/workflows/zai-scheduled.yml` | Workflow definition | + +--- + +## Success Criteria + +### MVP +- [ ] Schedule event detection works +- [ ] Configuration loading and validation works +- [ ] AGENTS.md update task executes successfully +- [ ] PR creation works when files change +- [ ] No PR created when no changes +- [ ] Error handling works for all scenarios +- [ ] All existing tests pass +- [ ] New tests cover 80%+ of new code + +### Quality Gates +- [ ] No breaking changes to existing functionality +- [ ] Security review passed +- [ ] Performance acceptable +- [ ] Documentation complete + +--- + +## Questions for Clarification (ANSWERED) + +1. **Gist Content**: Does the gist contain a `/zai` command or raw AGENTS.md content? + - ✅ **ANSWER**: Use gist raw file URL, configurable via ZAI_AGENTS_GIST_URL environment variable + +2. **PR Strategy**: Should each task create its own PR, or batch all changes? + - ✅ **ANSWER**: Each task creates its own PR + +3. **Branch Management**: Clean up old scheduled task branches? + - ✅ **ANSWER**: Create new branch each time with format `zai-scheduled/yyyy.mm.dd_hh.mm` + +4. **Fork Support**: Should scheduled tasks work on forks too? + - ✅ **ANSWER**: Yes, everywhere - uses fork's ZAI API key + +--- + +## Implementation Notes + +### Gist URL Configuration Priority +The Gist URL is resolved in the following order: +1. Task-level `gist_url` in configuration +2. Default `gist_url` in configuration +3. `ZAI_AGENTS_GIST_URL` environment variable + +This allows maximum flexibility for different deployment scenarios. + +### Branch Naming Format +Branches are created with the format: `zai-scheduled/yyyy.mm.dd_hh.mm` + +Example: `zai-scheduled/2026.06.27_14.30` + +This ensures: +- Unique branch names for each execution +- Human-readable timestamps +- Easy identification and cleanup if needed + +### Fork Support +The implementation uses the provided `apiKey` parameter, which means: +- On forks, it uses the fork's ZAI_API_KEY +- Each repository can have its own Gist URL +- No hardcoded URLs in the implementation + +--- + +## References + +- [GitHub Schedule Events](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule) +- [GitHub Octokit API](https://github.com/octokit/octokit.js) +- [YAML Package](https://www.npmjs.com/package/yaml) + +--- + +*Created: 2026-06-27* +*Status: Ready for Implementation* diff --git a/src/index.js b/src/index.js index 9056f32..ab71d04 100644 --- a/src/index.js +++ b/src/index.js @@ -28,6 +28,7 @@ const { handleDescribeCommand } = require('./lib/handlers/describe'); const { handleImpactCommand } = require('./lib/handlers/impact'); const reviewHandler = require('./lib/handlers/review.js'); const explainHandler = require('./lib/handlers/explain.js'); +const { handleScheduledEvent } = require('./lib/handlers/scheduled.js'); const { DEFAULT_MAX_CHARS, fetchChangedFiles } = require('./lib/context.js'); const { loadContinuityState, mergeState, createCommentWithState } = require('./lib/continuity.js'); @@ -373,6 +374,8 @@ async function run() { await handleIssueCommentEvent(context, apiKey, model, owner, repo, zaiTimeout); } else if (eventType === 'pull_request_review_comment') { await handlePullRequestReviewCommentEvent(context, apiKey, model, owner, repo, zaiTimeout); + } else if (eventType === 'schedule') { + await handleScheduledEvent(context, apiKey, model, owner, repo); } } @@ -697,6 +700,7 @@ async function dispatchCommand(context, parseResult, apiKey, model, owner, repo, createCommentWithState: _createCommentWithState = createCommentWithState, COMMENT_MARKER: _COMMENT_MARKER = COMMENT_MARKER, REACTIONS: _REACTIONS = REACTIONS, + handleUpdateAgentsCommand: _handleUpdateAgentsCommand = null, } = deps; const { command, args } = parseResult; @@ -959,6 +963,74 @@ ${_COMMENT_MARKER}`; return; } + case 'update-agents': { + logger.info({ args }, 'Dispatching to update-agents handler'); + + const octokit = _github.getOctokit(process.env.GITHUB_TOKEN || _core.getInput('GITHUB_TOKEN')); + const targetBranch = baseRef || headRef || 'main'; + + try { + // Execute the update-agents task directly + const { handleUpdateAgentsTask, fetchFromUrl, fetchFileContent, createPR } = require('./lib/handlers/scheduled'); + + const result = await handleUpdateAgentsTask({ + octokit, + owner, + repo, + task: { + id: 'manual-update-agents', + command: 'update-agents', + config: { + branch: targetBranch, + files: ['AGENTS.md'], + pr_title: 'chore: update AGENTS.md files', + pr_body: 'Manual update of AGENTS.md files from gist', + commit_message: 'docs: update AGENTS.md from manual command', + }, + }, + config: { + defaults: { + branch: targetBranch, + }, + }, + logger, + targetBranch, + fetchFromUrl, + fetchFile: async (path, ref) => fetchFileContent(octokit, owner, repo, path, ref || targetBranch), + createPullRequest: createPR, + }); + + if (result.success) { + if (result.prCreated) { + logger.info({ prNumber: result.prNumber, prUrl: result.prUrl }, 'Update agents command completed with PR'); + return { success: true }; + } else { + logger.info({ message: result.message }, 'Update agents command completed - no changes needed'); + return { success: true }; + } + } else { + logger.warn({ error: result.error }, 'Update agents command failed'); + terminalReaction = _REACTIONS.X; + responseMessage = `## Z.ai Update AGENTS.md + +**Error:** ${result.error} + +${_COMMENT_MARKER}`; + } + } catch (error) { + logger.error({ error: error.message, stack: error.stack }, 'Update agents handler threw error'); + terminalReaction = _REACTIONS.X; + responseMessage = `## Z.ai Update AGENTS.md + +**Error:** Failed to complete update. Please try again later. + +Details: ${error.message} + +${_COMMENT_MARKER}`; + } + break; + } + default: responseMessage = GUIDANCE_MESSAGES.unknown_command; } diff --git a/src/lib/commands.js b/src/lib/commands.js index e6f8bee..0cdf859 100644 --- a/src/lib/commands.js +++ b/src/lib/commands.js @@ -6,7 +6,7 @@ */ // Allowlisted commands -const ALLOWED_COMMANDS = ['ask', 'describe', 'explain', 'help', 'impact', 'review']; +const ALLOWED_COMMANDS = ['ask', 'describe', 'explain', 'help', 'impact', 'review', 'update-agents']; // Command metadata for help text const COMMAND_DESCRIPTIONS = { @@ -34,6 +34,10 @@ const COMMAND_DESCRIPTIONS = { usage: '/zai review [file]', description: 'Review specific files or all changed files', }, + 'update-agents': { + usage: '/zai update-agents', + description: 'Manually trigger AGENTS.md update from gist (collaborators only)', + }, }; // Error types diff --git a/src/lib/config/scheduled-config.js b/src/lib/config/scheduled-config.js new file mode 100644 index 0000000..76ae708 --- /dev/null +++ b/src/lib/config/scheduled-config.js @@ -0,0 +1,240 @@ +/** + * Scheduled Tasks Configuration Loader + * + * Loads and validates .zai-scheduled.yml configuration files from repositories. + * Provides schema validation and task filtering for scheduled execution. + */ + +const yaml = require('yaml'); + +// Configuration schema version +const CONFIG_VERSION = 1; + +/** + * Default configuration values + */ +const DEFAULTS = { + branch: 'main', + schedule: '0 0 * * 0', // Weekly on Sunday at midnight UTC + enabled: true, +}; + +/** + * Get the Gist URL for AGENTS.md updates + * Checks in order: task config, defaults, environment variable + * @param {Object} taskConfig - Task configuration + * @param {Object} defaults - Default configuration + * @returns {string|null} - Gist URL or null + */ +function getGistUrl(taskConfig, defaults) { + return taskConfig?.gist_url + || defaults?.gist_url + || process.env.ZAI_AGENTS_GIST_URL + || null; +} + +/** + * Load scheduled configuration from repository + * @param {Object} octokit - GitHub Octokit instance + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @param {string} ref - Branch/ref to load from (default: main) + * @returns {Promise} - Configuration object or null if not found + */ +async function loadScheduledConfig(octokit, owner, repo, ref = 'main') { + try { + const configPath = process.env.ZAI_SCHEDULED_CONFIG_PATH || '.zai-scheduled.yml'; + + const { data: configContent } = await octokit.rest.repos.getContent({ + owner, + repo, + path: configPath, + ref, + }); + + const rawContent = Buffer.from(configContent.content, 'base64').toString('utf8'); + const config = yaml.parse(rawContent); + + // Validate and normalize configuration + return validateAndNormalizeConfig(config); + } catch (error) { + if (error.status === 404) { + return null; // Config not found - scheduled tasks disabled + } + throw error; + } +} + +/** + * Validate configuration schema and normalize values + * @param {Object} config - Raw configuration object + * @returns {Object} - Validated and normalized configuration + * @throws {Error} - If configuration is invalid + */ +function validateAndNormalizeConfig(config) { + // Check version + if (!config.version) { + throw new Error(`Configuration missing required field: version`); + } + + if (config.version !== CONFIG_VERSION) { + throw new Error(`Unsupported config version: ${config.version}. Expected: ${CONFIG_VERSION}`); + } + + // Validate and normalize defaults + config.defaults = validateDefaults(config.defaults || {}); + + // Validate tasks + if (!config.tasks || !Array.isArray(config.tasks)) { + throw new Error('Configuration must contain a "tasks" array'); + } + + // Validate and normalize each task + config.tasks = config.tasks.map((task, index) => { + return validateAndNormalizeTask(task, index, config.defaults); + }); + + return config; +} + +/** + * Validate and normalize defaults section + * @param {Object} defaults - Raw defaults object + * @returns {Object} - Validated defaults + * @throws {Error} - If defaults are invalid + */ +function validateDefaults(defaults) { + const validated = { ...DEFAULTS, ...defaults }; + + if (validated.branch && typeof validated.branch !== 'string') { + throw new Error(`defaults.branch must be a string`); + } + + if (validated.schedule && typeof validated.schedule !== 'string') { + throw new Error(`defaults.schedule must be a string`); + } + + if (validated.gist_url && typeof validated.gist_url !== 'string') { + throw new Error(`defaults.gist_url must be a string`); + } + + return validated; +} + +/** + * Validate and normalize a single task + * @param {Object} task - Raw task object + * @param {number} index - Task index for error reporting + * @param {Object} defaults - Validated defaults + * @returns {Object} - Validated and normalized task + * @throws {Error} - If task is invalid + */ +function validateAndNormalizeTask(task, index, defaults) { + if (!task.id) { + throw new Error(`Task ${index} missing required field: id`); + } + + if (typeof task.id !== 'string') { + throw new Error(`Task ${index} field 'id' must be a string`); + } + + if (!task.command) { + throw new Error(`Task ${task.id} missing required field: command`); + } + + if (typeof task.command !== 'string') { + throw new Error(`Task ${task.id} field 'command' must be a string`); + } + + // Validate enabled flag + if (task.enabled !== undefined && typeof task.enabled !== 'boolean') { + throw new Error(`Task ${task.id} has invalid enabled value (must be boolean)`); + } + + // Apply defaults + const normalized = { + id: task.id, + name: task.name || task.id, + enabled: task.enabled !== undefined ? task.enabled : defaults.enabled, + schedule: task.schedule || defaults.schedule, + command: task.command, + config: { ...defaults, ...task.config }, + }; + + // Validate task-specific config + if (normalized.config.branch && typeof normalized.config.branch !== 'string') { + throw new Error(`Task ${task.id} has invalid branch value`); + } + + if (normalized.config.gist_url && typeof normalized.config.gist_url !== 'string') { + throw new Error(`Task ${task.id} has invalid gist_url value`); + } + + if (normalized.config.files && !Array.isArray(normalized.config.files)) { + throw new Error(`Task ${task.id} has invalid files value (must be array)`); + } + + return normalized; +} + +/** + * Get tasks that should run for this schedule event + * @param {Object} config - Validated configuration + * @param {string} eventSchedule - Cron schedule from GitHub event context + * @returns {Array} - Tasks to execute + */ +function getTasksToRun(config, eventSchedule) { + const enabledTasks = config.tasks.filter(t => t.enabled === true); + + // If no enabled tasks, return empty array + if (enabledTasks.length === 0) { + return []; + } + + // If event has specific schedule, only run tasks matching that schedule + if (eventSchedule) { + return enabledTasks.filter(t => { + return t.schedule === eventSchedule || + (config.defaults?.schedule && t.schedule === config.defaults.schedule); + }); + } + + // Otherwise run all enabled tasks + return enabledTasks; +} + +/** + * Get a specific task by ID + * @param {Object} config - Validated configuration + * @param {string} taskId - Task ID to find + * @returns {Object|null} - Task object or null if not found + */ +function getTaskById(config, taskId) { + return config.tasks.find(t => t.id === taskId) || null; +} + +/** + * Check if scheduled tasks are enabled in this repository + * @param {Object} octokit - GitHub Octokit instance + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @param {string} ref - Branch/ref to check (default: main) + * @returns {Promise} - True if scheduled tasks are enabled + */ +async function areScheduledTasksEnabled(octokit, owner, repo, ref = 'main') { + const config = await loadScheduledConfig(octokit, owner, repo, ref); + return config !== null; +} + +module.exports = { + CONFIG_VERSION, + loadScheduledConfig, + validateAndNormalizeConfig, + getTasksToRun, + getTaskById, + areScheduledTasksEnabled, + getGistUrl, + // Export for testing + validateDefaults, + validateAndNormalizeTask, +}; diff --git a/src/lib/events.js b/src/lib/events.js index 4e9c3b2..8dd160c 100644 --- a/src/lib/events.js +++ b/src/lib/events.js @@ -30,7 +30,7 @@ function extractReviewCommentAnchor(payload) { /** * Determines the type of GitHub event from the context * @param {Object} context - GitHub actions context object - * @returns {string} Event type: 'pull_request', 'issue_comment_pr', 'pull_request_review_comment', or 'issue_comment_non_pr' + * @returns {string} Event type: 'pull_request', 'issue_comment_pr', 'pull_request_review_comment', 'schedule', or 'issue_comment_non_pr' */ function getEventType(context) { const eventName = context.eventName; @@ -52,6 +52,11 @@ function getEventType(context) { return 'issue_comment_non_pr'; } + // Handle schedule events (cron-triggered) + if (eventName === 'schedule') { + return 'schedule'; + } + // Unknown event type - return as non-processable return 'issue_comment_non_pr'; } @@ -105,6 +110,11 @@ function shouldProcessEvent(context) { return { process: true, reason: 'pull_request_review_comment event' }; } + // Handle schedule events - always process (cron-triggered tasks) + if (eventType === 'schedule') { + return { process: true, reason: 'schedule event' }; + } + // Handle non-PR issue comments - reject if (eventType === 'issue_comment_non_pr') { return { process: false, reason: 'non-PR issue comment - not supported' }; diff --git a/src/lib/handlers/index.js b/src/lib/handlers/index.js index b07770d..e40bac8 100644 --- a/src/lib/handlers/index.js +++ b/src/lib/handlers/index.js @@ -4,6 +4,7 @@ const reviewHandler = require('./review'); const explainHandler = require('./explain'); const impactHandler = require('./impact'); const describeHandler = require('./describe'); +const scheduledHandler = require('./scheduled'); const HANDLERS = { ask: askHandler.handleAskCommand, @@ -37,4 +38,5 @@ module.exports = { explain: explainHandler, impact: impactHandler, describe: describeHandler, + scheduled: scheduledHandler, }; diff --git a/src/lib/handlers/scheduled.js b/src/lib/handlers/scheduled.js new file mode 100644 index 0000000..bf6cb04 --- /dev/null +++ b/src/lib/handlers/scheduled.js @@ -0,0 +1,1075 @@ +/** + * Scheduled Tasks Handler + * + * Handles scheduled (cron-based) execution of tasks like AGENTS.md updates. + * Supports flexible configuration via YAML and extensible command handlers. + */ + +const https = require('node:https'); +const { parseCommand, isValid } = require('../commands'); +const { loadScheduledConfig, getTasksToRun, getGistUrl } = require('../config/scheduled-config'); +const { createLogger, generateCorrelationId } = require('../logging'); +const core = require('@actions/core'); + +// Handler registry for scheduled commands +const SCHEDULED_HANDLERS = { + 'update-agents': handleUpdateAgentsTask, +}; + +/** + * Main entry point for scheduled events + * @param {Object} context - GitHub actions context + * @param {string} apiKey - Z.ai API key + * @param {string} model - Z.ai model + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @returns {Promise} - Execution result + */ +async function handleScheduledEvent(context, apiKey, model, owner, repo) { + const github = require('@actions/github'); + const octokit = github.getOctokit(process.env.GITHUB_TOKEN || core.getInput('GITHUB_TOKEN')); + const logger = createLogger(generateCorrelationId(), { + eventName: 'schedule', + owner, + repo, + }); + + logger.info('Scheduled event received'); + + try { + // Load configuration + const config = await loadScheduledConfig(octokit, owner, repo); + + if (!config) { + logger.info('No scheduled configuration found (.zai-scheduled.yml), skipping scheduled tasks'); + return { + success: true, + skipped: true, + reason: 'no configuration', + message: 'No .zai-scheduled.yml configuration found. Scheduled tasks are disabled.' + }; + } + + logger.info(`Loaded scheduled configuration with ${config.tasks.length} task(s)`); + + // Get tasks to run for this schedule + const eventSchedule = context.payload?.schedule; + const tasks = getTasksToRun(config, eventSchedule); + + if (tasks.length === 0) { + logger.info('No tasks to run for this schedule'); + return { + success: true, + skipped: true, + reason: 'no tasks', + message: 'No enabled tasks match this schedule.' + }; + } + + logger.info(`Found ${tasks.length} task(s) to execute: ${tasks.map(t => t.id).join(', ')}`); + + // Execute each task sequentially + const results = []; + for (let i = 0; i < tasks.length; i++) { + const task = tasks[i]; + logger.info(`[${i + 1}/${tasks.length}] Executing task: ${task.id}`); + + const result = await executeScheduledTask({ + octokit, + apiKey, + model, + owner, + repo, + task, + config, + logger, + context, + }); + + results.push(result); + + // Add delay between tasks to avoid rate limiting (except for last task) + if (i < tasks.length - 1) { + const delayMs = 5000; + logger.info(`Waiting ${delayMs}ms before next task...`); + await new Promise(resolve => setTimeout(resolve, delayMs)); + } + } + + // Log summary + const successCount = results.filter(r => r.success).length; + const failureCount = results.filter(r => !r.success).length; + const prCount = results.filter(r => r.prCreated).length; + + logger.info(`Scheduled tasks completed: ${successCount} succeeded, ${failureCount} failed, ${prCount} PR(s) created`); + + return { + success: failureCount === 0, + results, + executed: tasks.length, + succeeded: successCount, + failed: failureCount, + prsCreated: prCount, + message: `Executed ${tasks.length} task(s): ${successCount} succeeded, ${failureCount} failed`, + }; + + } catch (error) { + logger.error({ error: error.message, stack: error.stack }, 'Scheduled event handler failed'); + return { + success: false, + error: error.message, + message: `Scheduled event failed: ${error.message}` + }; + } +} + +/** + * Execute a single scheduled task + * @param {Object} params - Execution parameters + * @param {Object} params.octokit - GitHub Octokit instance + * @param {string} params.apiKey - Z.ai API key + * @param {string} params.model - Z.ai model + * @param {string} params.owner - Repository owner + * @param {string} params.repo - Repository name + * @param {Object} params.task - Task configuration + * @param {Object} params.config - Full configuration + * @param {Object} params.logger - Logger instance + * @param {Object} params.context - GitHub actions context + * @returns {Promise} - Task execution result + */ +async function executeScheduledTask(params) { + const { octokit, apiKey, model, owner, repo, task, config, logger, context } = params; + + try { + // Get handler for this command + const handler = getScheduledHandler(task.command); + + if (!handler) { + logger.warn(`No handler found for command: ${task.command}`); + return { + success: false, + taskId: task.id, + command: task.command, + error: `Unknown scheduled command: ${task.command}`, + message: `No handler registered for command '${task.command}'`, + }; + } + + // Build execution context with utility functions + const executionContext = buildExecutionContext({ + octokit, + apiKey, + model, + owner, + repo, + task, + config, + logger, + context, + }); + + // Execute handler + logger.info(`Executing handler for command: ${task.command}`); + const result = await handler(executionContext); + + return { + success: result.success, + taskId: task.id, + command: task.command, + changes: result.changes, + prCreated: result.prCreated, + prNumber: result.prNumber, + prUrl: result.prUrl, + message: result.message || `Task ${task.id} completed successfully`, + }; + + } catch (error) { + logger.error({ error: error.message, stack: error.stack, taskId: task.id }, 'Task execution failed'); + return { + success: false, + taskId: task.id, + command: task.command, + error: error.message, + message: `Task ${task.id} failed: ${error.message}`, + }; + } +} + +/** + * Build execution context with utility functions for handlers + * @param {Object} params - Context parameters + * @returns {Object} - Execution context + */ +function buildExecutionContext(params) { + const { octokit, apiKey, model, owner, repo, task, config, logger, context } = params; + + const targetBranch = task.config?.branch || config.defaults?.branch || 'main'; + + return { + octokit, + apiKey, + model, + owner, + repo, + task, + config, + logger, + context, + targetBranch, + // Utility functions + fetchFromUrl: fetchFromUrl, + fetchFile: async (path, ref) => fetchFileContent(octokit, owner, repo, path, ref || targetBranch), + updateFile: async (path, content, ref, commitMessage) => + updateFileInRepo(octokit, owner, repo, path, content, ref || targetBranch, commitMessage), + createPullRequest: async (prParams) => createPR(octokit, owner, repo, prParams), + getFileSha: async (path, ref) => getFileSha(octokit, owner, repo, path, ref || targetBranch), + }; +} + +/** + * Get handler function for a scheduled command + * @param {string} command - Command name + * @returns {Function|null} - Handler function or null + */ +function getScheduledHandler(command) { + return SCHEDULED_HANDLERS[command] || null; +} + +/** + * Register a new scheduled command handler + * @param {string} command - Command name + * @param {Function} handler - Handler function + */ +function registerScheduledHandler(command, handler) { + SCHEDULED_HANDLERS[command] = handler; +} + +/** + * Get all registered scheduled command handlers + * @returns {Object} - Map of command names to handlers + */ +function getAllScheduledHandlers() { + return { ...SCHEDULED_HANDLERS }; +} + +// ============================================================================ +// TASK HANDLERS +// ============================================================================ + +/** + * Handle AGENTS.md update task + * Fetches command from Gist, executes it, and creates PR with updates + * @param {Object} context - Execution context + * @returns {Promise} - Result with changes information + */ +async function handleUpdateAgentsTask(context) { + const { + octokit, + apiKey, + model, + owner, + repo, + task, + config, + logger, + targetBranch, + fetchFromUrl, + fetchFile, + createPullRequest + } = context; + + const gistUrl = getGistUrl(task.config, config.defaults); + const prTitle = task.config?.pr_title || 'chore: update AGENTS.md files'; + const prBody = task.config?.pr_body || 'Automated weekly update of AGENTS.md files from gist'; + const commitMessage = task.config?.commit_message || 'docs: update AGENTS.md from scheduled task'; + + if (!gistUrl) { + logger.error('No gist_url configured for update-agents task. Set ZAI_AGENTS_GIST_URL environment variable or configure in .zai-scheduled.yml'); + return { + success: false, + error: 'Missing gist_url configuration', + message: 'update-agents task requires gist_url. Set ZAI_AGENTS_GIST_URL environment variable or configure in .zai-scheduled.yml' + }; + } + + logger.info(`Fetching content from gist: ${gistUrl}`); + + try { + // Step 1: Fetch content from gist + let gistContent; + try { + gistContent = await fetchFromUrl(gistUrl); + } catch (error) { + logger.error({ error: error.message, url: gistUrl }, 'Failed to fetch from gist URL'); + return { + success: false, + error: `Failed to fetch from gist: ${error.message}`, + message: `Could not fetch content from ${gistUrl}` + }; + } + + if (!gistContent || gistContent.trim() === '') { + logger.error('Gist returned empty content'); + return { + success: false, + error: 'Empty response from gist URL', + message: 'Gist URL returned no content' + }; + } + + logger.info(`Fetched ${gistContent.length} characters from gist`); + + // Step 2: Execute the command from Gist + // The command MUST return structured JSON with file paths and contents + logger.info('Executing command in auto-discovery mode - expecting structured file map'); + let fileUpdates = await executeCommandAndGetFileUpdates({ + commandText: gistContent, + octokit, + apiKey, + model, + owner, + repo, + targetBranch, + logger, + }); + + if (!fileUpdates || fileUpdates.length === 0) { + logger.error('Command returned no file updates. The command must return valid JSON with files array.'); + return { + success: false, + error: 'No file updates returned from command', + message: 'The command must return JSON with a "files" array containing AGENTS.md file updates' + }; + } + + logger.info(`Auto-discovery found ${fileUpdates.length} file(s) to update`); + + // Step 3: Check if there are any actual changes + const updatedFiles = fileUpdates.filter(f => f.changed).map(f => f.file); + + if (updatedFiles.length === 0) { + logger.info('No files need updating'); + return { + success: true, + changes: fileUpdates, + prCreated: false, + message: 'No updates needed - all files are current', + }; + } + + logger.info(`Creating PR with updates for ${updatedFiles.length} file(s): ${updatedFiles.join(', ')}`); + + // Create PR with all changes + const filesToUpdate = fileUpdates + .filter(f => f.changed) + .map(f => ({ + path: f.file, + content: f.newContent, + })); + + const prResult = await createPullRequest({ + title: prTitle, + body: buildPrBody(prBody, updatedFiles, task.id), + base: targetBranch, + files: filesToUpdate, + commitMessage, + }); + + return { + success: true, + changes: fileUpdates, + prCreated: true, + prNumber: prResult?.number, + prUrl: prResult?.html_url, + message: `Created PR #${prResult?.number} with ${updatedFiles.length} file(s) updated`, + }; + + } catch (error) { + logger.error({ error: error.message, stack: error.stack }, 'AGENTS.md update task failed'); + return { + success: false, + error: error.message, + message: `Update task failed: ${error.message}`, + }; + } +} + +/** + * Execute a command and get structured file updates (for auto-discovery mode) + * The command should return a JSON structure with file paths and their contents + * @param {Object} params - Execution parameters + * @param {string} params.commandText - The command text to execute + * @param {Object} params.octokit - GitHub Octokit instance + * @param {string} params.apiKey - Z.ai API key + * @param {string} params.model - Z.ai model + * @param {string} params.owner - Repository owner + * @param {string} params.repo - Repository name + * @param {string} params.targetBranch - Target branch + * @param {Object} params.logger - Logger instance + * @returns {Promise>} - Array of {file, oldContent, newContent, changed, isNew} objects + */ +async function executeCommandAndGetFileUpdates(params) { + const { commandText, octokit, apiKey, model, owner, repo, targetBranch, logger } = params; + + if (!commandText || commandText.trim() === '') { + logger.warn('Command text is empty, cannot execute'); + return []; + } + + try { + // Check if it's a valid /zai command + const parseResult = parseCommand(commandText); + + // Build prompt for auto-discovery mode + // The command should scan the repo and return a JSON structure with all AGENTS.md files + const prompt = buildAutoDiscoveryPrompt(commandText, owner, repo, targetBranch); + + // Call Z.ai API to execute the command + const response = await callZaiApiWithRetry(apiKey, model, prompt, logger); + + if (!response || !response.content) { + logger.warn('Z.ai API returned empty content'); + return []; + } + + // Parse the response to extract file updates + // Expected format: JSON array of {file: string, content: string} objects + // Or a special format that we can parse + const fileUpdates = parseFileUpdatesFromResponse(response.content, octokit, owner, repo, targetBranch, fetchFileContent, logger); + + return fileUpdates; + + } catch (error) { + logger.error({ error: error.message }, 'Failed to execute command for file discovery'); + throw error; + } +} + +/** + * Build prompt for auto-discovery mode + * @param {string} commandText - The command text + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @param {string} branch - Target branch + * @returns {string} - Formatted prompt + */ +function buildAutoDiscoveryPrompt(commandText, owner, repo, branch) { + return `Repository: ${owner}/${repo} +Branch: ${branch} + +You are an AI assistant tasked with generating and updating AGENTS.md files. + +Execute the following command to scan the repository and generate/update AGENTS.md files: + +${commandText} + +IMPORTANT: You must return the results in a specific JSON format so the bot can process your changes. + +Return a JSON object with the following structure: +{ + "summary": "Brief description of changes", + "files": [ + { + "path": "AGENTS.md", + "content": "... full file content ...", + "action": "created|updated|unchanged" + }, + { + "path": "src/lib/AGENTS.md", + "content": "... full file content ...", + "action": "created|updated|unchanged" + } + ] +} + +Rules: +1. Scan the ENTIRE repository structure +2. Identify ALL existing AGENTS.md files +3. Determine if each needs to be updated +4. Create new AGENTS.md files where needed (root and important subdirectories) +5. For each file that needs changes, include the full new content +6. Only include files that have actual changes (action: "created" or "updated") +7. Return ONLY valid JSON, no other text, explanations, or markdown +8. The content must be the exact text that should be written to each file + +Begin your response with the JSON object immediately, no preamble.`; +} + +/** + * Parse file updates from Z.ai API response + * @param {string} responseContent - Response content from Z.ai + * @param {Object} octokit - GitHub Octokit instance + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @param {string} targetBranch - Target branch + * @param {Function} fetchFileContent - Function to fetch file content + * @param {Object} logger - Logger instance + * @returns {Promise>} - Array of file update objects + */ +async function parseFileUpdatesFromResponse(responseContent, octokit, owner, repo, targetBranch, fetchFileContent, logger) { + const fileUpdates = []; + + try { + // Try to parse as JSON + let parsed; + try { + // Remove any markdown code blocks if present + const cleaned = responseContent.trim(); + const jsonMatch = cleaned.match(/\{[\s\S]*\}/); + if (jsonMatch) { + parsed = JSON.parse(jsonMatch[0]); + } else { + parsed = JSON.parse(cleaned); + } + } catch (e) { + logger.warn('Could not parse response as JSON, attempting to extract file information'); + // Fallback: try to extract file paths and content from the response + return await extractFilesFromText(responseContent, octokit, owner, repo, targetBranch, fetchFileContent, logger); + } + + if (parsed && parsed.files && Array.isArray(parsed.files)) { + // Process each file from the structured response + for (const fileInfo of parsed.files) { + const filePath = fileInfo.path || fileInfo.file; + const newContent = fileInfo.content || fileInfo.body; + const action = (fileInfo.action || '').toLowerCase(); + + if (!filePath || !newContent) { + logger.warn(`Skipping invalid file entry: ${JSON.stringify(fileInfo)}`); + continue; + } + + try { + const currentContent = await fetchFileContent(octokit, owner, repo, filePath, targetBranch); + const changed = action === 'created' || action === 'updated' || currentContent === null || currentContent !== newContent; + + if (changed) { + fileUpdates.push({ + file: filePath, + oldContent: currentContent, + newContent, + changed: true, + isNew: currentContent === null, + }); + } else { + fileUpdates.push({ + file: filePath, + changed: false, + }); + } + } catch (error) { + logger.error({ error: error.message, file: filePath }, 'Failed to check file for auto-discovery'); + // Assume it needs to be created/updated + fileUpdates.push({ + file: filePath, + oldContent: null, + newContent, + changed: true, + isNew: true, + }); + } + } + + logger.info(`Parsed ${fileUpdates.length} file updates from structured response`); + } else { + logger.warn('Response does not contain expected files array, attempting fallback'); + // Fallback: try to extract from text + return await extractFilesFromText(responseContent, octokit, owner, repo, targetBranch, fetchFileContent, logger); + } + + } catch (error) { + logger.error({ error: error.message }, 'Failed to parse file updates from response'); + // Return empty array - caller will handle fallback + } + + return fileUpdates; +} + +/** + * Fallback: Extract file information from plain text response + * @param {string} text - Plain text response + * @param {Object} octokit - GitHub Octokit instance + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @param {string} targetBranch - Target branch + * @param {Function} fetchFileContent - Function to fetch file content + * @param {Object} logger - Logger instance + * @returns {Promise>} - Array of file update objects + */ +async function extractFilesFromText(text, octokit, owner, repo, targetBranch, fetchFileContent, logger) { + logger.warn('Attempting to extract file updates from plain text response'); + + // This is a fallback for when the response isn't structured JSON + // We'll just return the AGENTS.md file with the raw content + // In a real implementation, the command should return proper JSON + + const filePath = 'AGENTS.md'; + const newContent = text; + + try { + const currentContent = await fetchFileContent(octokit, owner, repo, filePath, targetBranch); + const changed = currentContent === null || currentContent !== newContent; + + return [{ + file: filePath, + oldContent: currentContent, + newContent, + changed, + isNew: currentContent === null, + }]; + } catch (error) { + return [{ + file: filePath, + oldContent: null, + newContent, + changed: true, + isNew: true, + }]; + } +} + +/** + * Build PR body with task information + * @param {string} baseBody - Base PR body + * @param {Array} updatedFiles - List of updated files + * @param {string} taskId - Task ID + * @returns {string} - Full PR body + */ +function buildPrBody(baseBody, updatedFiles, taskId) { + const timestamp = new Date().toISOString(); + const filesList = updatedFiles.map(f => `- ${f}`).join('\n'); + + return `${baseBody}\n\n` + + `**Task:** ${taskId}\n` + + `**Timestamp:** ${timestamp}\n` + + `**Files Updated:**\n${filesList}\n\n` + + `---\n` + + `*Generated automatically by zai-code-bot scheduled task*`; +} + +// ============================================================================ +// COMMAND EXECUTION +// ============================================================================ + +/** + * Execute a command text to generate content + * Sends the command to Z.ai API and returns the generated content + * @param {Object} params - Execution parameters + * @param {string} params.commandText - The command text to execute + * @param {Object} params.octokit - GitHub Octokit instance + * @param {string} params.apiKey - Z.ai API key + * @param {string} params.model - Z.ai model + * @param {string} params.owner - Repository owner + * @param {string} params.repo - Repository name + * @param {string} params.targetBranch - Target branch + * @param {Object} params.logger - Logger instance + * @returns {Promise} - Generated content + */ +async function executeCommandAndGetContent(params) { + const { commandText, apiKey, model, owner, repo, targetBranch, logger } = params; + + // If command text is empty, return empty + if (!commandText || commandText.trim() === '') { + logger.warn('Command text is empty, cannot execute'); + return commandText; + } + + try { + // Check if it's a valid /zai command + const parseResult = parseCommand(commandText); + + if (isValid(parseResult)) { + logger.info(`Executing /zai command: ${parseResult.command} with args: ${parseResult.args.join(' ')}`); + + // For /zai commands, we need to execute them through the dispatch system + // But since we're not in a PR context, we'll use the Z.ai API directly + const prompt = buildCommandPrompt(commandText, owner, repo, targetBranch); + + // Call Z.ai API to execute the command + const response = await callZaiApiWithRetry(apiKey, model, prompt, logger); + + if (response && response.content) { + return response.content; + } + + logger.warn('Z.ai API returned empty content'); + return commandText; + } else { + // Not a /zai command - try to execute as a direct prompt + logger.info('Command is not a /zai command, sending as direct prompt to Z.ai API'); + + // Build a prompt that tells Z.ai to generate AGENTS.md content + const prompt = buildAgentsGenerationPrompt(commandText, owner, repo); + + // Call Z.ai API + const response = await callZaiApiWithRetry(apiKey, model, prompt, logger); + + if (response && response.content) { + return response.content; + } + + logger.warn('Z.ai API returned empty content for direct prompt'); + return commandText; + } + } catch (error) { + logger.error({ error: error.message }, 'Failed to execute command for content generation'); + throw error; + } +} + +/** + * Build prompt for executing a /zai command + * @param {string} commandText - The command text + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @param {string} branch - Target branch + * @returns {string} - Formatted prompt + */ +function buildCommandPrompt(commandText, owner, repo, branch) { + return `Repository: ${owner}/${repo} +Branch: ${branch} + +Execute the following command: +${commandText} + +Return only the generated content (AGENTS.md file content), without any explanation or formatting.`; +} + +/** + * Build prompt for generating AGENTS.md from a direct prompt + * @param {string} promptText - The prompt/command text + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @returns {string} - Formatted prompt + */ +function buildAgentsGenerationPrompt(promptText, owner, repo) { + return `You are an AI assistant helping to generate AGENTS.md files for a repository. + +Repository: ${owner}/${repo} + +Task: Generate comprehensive AGENTS.md content based on the following instructions: + +${promptText} + +Return ONLY the generated AGENTS.md file content. Do not include any explanation, formatting notes, or markdown headers. Start directly with the content that should be written to the AGENTS.md file.`; +} + +/** + * Call Z.ai API with retry logic + * @param {string} apiKey - Z.ai API key + * @param {string} model - Model to use + * @param {string} prompt - Prompt to send + * @param {Object} logger - Logger instance + * @param {number} retries - Number of retries (default: 3) + * @returns {Promise} - API response + */ +async function callZaiApiWithRetry(apiKey, model, prompt, logger, retries = 3) { + const https = require('node:https'); + const ZAI_API_URL = 'https://api.z.ai/api/coding/paas/v4/chat/completions'; + + for (let attempt = 1; attempt <= retries; attempt++) { + try { + const body = JSON.stringify({ + model, + messages: [ + { + role: 'user', + content: prompt, + }, + ], + }); + + const parsedUrl = new URL(ZAI_API_URL); + const options = { + hostname: parsedUrl.hostname, + path: parsedUrl.pathname, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${apiKey}`, + 'Content-Length': Buffer.byteLength(body), + }, + }; + + const response = await new Promise((resolve, reject) => { + const req = https.request(options, res => { + let data = ''; + res.on('data', chunk => (data += chunk)); + res.on('end', () => { + if (res.statusCode >= 200 && res.statusCode < 300) { + try { + const parsed = JSON.parse(data); + resolve(parsed); + } catch (e) { + reject(new Error(`Failed to parse response: ${e.message}`)); + } + } else { + reject(new Error(`API error ${res.statusCode}: ${data.substring(0, 200)}`)); + } + }); + }); + + req.on('error', reject); + req.write(body); + req.end(); + }); + + return { + content: response.choices?.[0]?.message?.content || '', + fullResponse: response, + }; + } catch (error) { + logger.warn({ error: error.message, attempt }, `Z.ai API call failed, attempt ${attempt} of ${retries}`); + + if (attempt === retries) { + throw error; + } + + // Exponential backoff + const delay = Math.pow(2, attempt) * 1000; + await new Promise(resolve => setTimeout(resolve, delay)); + } + } +} + +// ============================================================================ +// UTILITY FUNCTIONS +// ============================================================================ + +/** + * Fetch content from a URL + * @param {string} url - URL to fetch + * @returns {Promise} - Content as string + */ +async function fetchFromUrl(url) { + return new Promise((resolve, reject) => { + try { + const parsedUrl = new URL(url); + const options = { + hostname: parsedUrl.hostname, + path: parsedUrl.pathname + parsedUrl.search, + method: 'GET', + headers: { + 'User-Agent': 'zai-code-bot', + 'Accept': 'text/plain,text/html,application/json', + }, + }; + + logger.info(`Fetching URL: ${url}`); + + const req = https.request(options, res => { + let data = ''; + res.on('data', chunk => (data += chunk)); + res.on('end', () => { + if (res.statusCode >= 200 && res.statusCode < 300) { + logger.info(`Fetched ${data.length} bytes from URL`); + resolve(data); + } else { + reject(new Error(`HTTP ${res.statusCode}: ${data.substring(0, 200)}`)); + } + }); + }); + + req.on('error', reject); + req.setTimeout(30000, () => { + req.destroy(new Error('Request timeout after 30s')); + }); + req.end(); + } catch (error) { + reject(new Error(`Invalid URL: ${error.message}`)); + } + }); +} + +/** + * Fetch file content from repository + * @param {Object} octokit - GitHub Octokit instance + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @param {string} path - File path + * @param {string} ref - Branch/ref + * @returns {Promise} - File content or null if not found + */ +async function fetchFileContent(octokit, owner, repo, path, ref) { + try { + const { data } = await octokit.rest.repos.getContent({ + owner, + repo, + path, + ref, + }); + + return Buffer.from(data.content, 'base64').toString('utf8'); + } catch (error) { + if (error.status === 404) { + return null; + } + throw error; + } +} + +/** + * Get the SHA of a file in repository + * @param {Object} octokit - GitHub Octokit instance + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @param {string} path - File path + * @param {string} ref - Branch/ref + * @returns {Promise} - File SHA or null if not found + */ +async function getFileSha(octokit, owner, repo, path, ref) { + try { + const { data } = await octokit.rest.repos.getContent({ + owner, + repo, + path, + ref, + }); + return data.sha; + } catch (error) { + if (error.status === 404) { + return null; + } + throw error; + } +} + +/** + * Create or update file in repository + * @param {Object} octokit - GitHub Octokit instance + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @param {string} path - File path + * @param {string} content - New content + * @param {string} ref - Branch/ref + * @param {string} commitMessage - Commit message + * @returns {Promise} - GitHub API response + */ +async function updateFileInRepo(octokit, owner, repo, path, content, ref, commitMessage) { + const existingSha = await getFileSha(octokit, owner, repo, path, ref); + + const response = await octokit.rest.repos.createOrUpdateFileContents({ + owner, + repo, + path, + message: commitMessage, + content: Buffer.from(content).toString('base64'), + sha: existingSha || undefined, + branch: ref, + }); + + return response.data; +} + +/** + * Create a pull request with file changes + * @param {Object} octokit - GitHub Octokit instance + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @param {Object} params - PR parameters + * @param {string} params.title - PR title + * @param {string} params.body - PR body + * @param {string} params.base - Base branch + * @param {Array} params.files - Files to update (each with path and content) + * @param {string} params.commitMessage - Commit message + * @returns {Promise} - PR creation result + */ +async function createPR(octokit, owner, repo, { title, body, base, files, commitMessage }) { + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + const hours = String(now.getHours()).padStart(2, '0'); + const minutes = String(now.getMinutes()).padStart(2, '0'); + const branchName = `zai-scheduled/${year}.${month}.${day}_${hours}.${minutes}`; + + logger.info(`Creating branch ${branchName} from ${base}`); + + // Get the base branch reference + const { data: baseRef } = await octokit.rest.git.getRef({ + owner, + repo, + ref: `heads/${base}`, + }); + + // Create a new branch + try { + await octokit.rest.git.createRef({ + owner, + repo, + ref: `refs/heads/${branchName}`, + sha: baseRef.object.sha, + }); + logger.info(`Created branch ${branchName}`); + } catch (error) { + if (error.status !== 422) { // Already exists + throw error; + } + logger.info(`Branch ${branchName} already exists, reusing`); + } + + // Apply all file changes to the new branch + let commitSha = baseRef.object.sha; + + for (const file of files) { + const { path, content } = file; + + logger.info(`Updating file ${path} in branch ${branchName}`); + + try { + const result = await updateFileInRepo( + octokit, + owner, + repo, + path, + content, + branchName, + commitMessage + ); + commitSha = result.commit.sha; + logger.info(`Updated ${path} successfully`); + } catch (error) { + logger.error({ error: error.message, file: path }, 'Failed to update file in branch'); + throw error; + } + } + + // Create the pull request + logger.info(`Creating pull request from ${branchName} to ${base}`); + + const prResponse = await octokit.rest.pulls.create({ + owner, + repo, + title, + body, + head: branchName, + base, + }); + + logger.info(`Created PR #${prResponse.data.number}`); + + return { + ...prResponse.data, + commitSha, + }; +} + +// ============================================================================ +// EXPORTS +// ============================================================================ + +module.exports = { + handleScheduledEvent, + executeScheduledTask, + getScheduledHandler, + registerScheduledHandler, + getAllScheduledHandlers, + handleUpdateAgentsTask, + // Export utility functions for testing and manual command integration + fetchFromUrl, + fetchFileContent, + getFileSha, + updateFileInRepo, + createPR, + buildExecutionContext, +};