diff --git a/.github/ISSUE_TEMPLATE/1-bug.yml b/.github/ISSUE_TEMPLATE/1-bug.yml index 4f8192e..b03cd59 100644 --- a/.github/ISSUE_TEMPLATE/1-bug.yml +++ b/.github/ISSUE_TEMPLATE/1-bug.yml @@ -2,8 +2,8 @@ # SPDX-License-Identifier: Apache-2.0 name: Bug Report description: Report a reproducible bug -title: "[Bug] " -labels: ["bug"] +title: '[Bug] ' +labels: ['bug'] assignees: [] body: diff --git a/.github/ISSUE_TEMPLATE/2-feature.yml b/.github/ISSUE_TEMPLATE/2-feature.yml index cea8d63..b44bd15 100644 --- a/.github/ISSUE_TEMPLATE/2-feature.yml +++ b/.github/ISSUE_TEMPLATE/2-feature.yml @@ -2,8 +2,8 @@ # SPDX-License-Identifier: Apache-2.0 name: Feature Request description: Suggest a feature or improvement -title: "[Feature] " -labels: ["enhancement"] +title: '[Feature] ' +labels: ['enhancement'] assignees: [] body: diff --git a/.github/ISSUE_TEMPLATE/3-other.yml b/.github/ISSUE_TEMPLATE/3-other.yml index 0a95c08..6968199 100644 --- a/.github/ISSUE_TEMPLATE/3-other.yml +++ b/.github/ISSUE_TEMPLATE/3-other.yml @@ -2,8 +2,8 @@ # SPDX-License-Identifier: Apache-2.0 name: Other description: Use this for anything that doesn't fit the other templates -title: "[Other] " -labels: ["discussion"] +title: '[Other] ' +labels: ['discussion'] assignees: [] body: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index ec00ce9..954cfa2 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -13,8 +13,6 @@ Briefly describe the purpose of this PR and what changes it introduces. - [ ] Commits are **signed** (`git commit -S`) - [ ] Commit messages follow **Conventional Commits** - [ ] Documentation has been updated (if needed) -- [ ] The target branch follows the git flow model (`develop` for feature/fix PRs, `main` for release/hotfix PRs) -- [ ] PRs targeting `main` have a `release-type/...` label or are intentionally marked `skip-release` - [ ] I have read and agree to the project’s [Code of Conduct](../CODE_OF_CONDUCT.md) - [ ] I have signed the [Contributor License Agreement (CLA)](../CONTRIBUTING.md) diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..6bbd704 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: 2026 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.) +# +# SPDX-License-Identifier: Apache-2.0 + +version: 2 +updates: + - package-ecosystem: npm + directory: / + schedule: + interval: weekly diff --git a/.github/inditextech-ci-node.json b/.github/inditextech-ci-node.json new file mode 100644 index 0000000..299d7a7 --- /dev/null +++ b/.github/inditextech-ci-node.json @@ -0,0 +1,6 @@ +{ + "project_type": "single", + "publish_snapshot": true, + "schema_version": 1, + "working_directory": "." +} diff --git a/.github/inditextech-ci-sync-manifest.json b/.github/inditextech-ci-sync-manifest.json new file mode 100644 index 0000000..62944f9 --- /dev/null +++ b/.github/inditextech-ci-sync-manifest.json @@ -0,0 +1,62 @@ +{ + "configuration": { + "branch_roles": { + "development_branch": "develop", + "release_branch": "main" + }, + "development_flow": "git-flow", + "outputs": [ + "publish" + ], + "parameters": { + "project_type": "single", + "publish_snapshot": true, + "working_directory": "." + } + }, + "creation_year": 2026, + "integrity": { + "algorithm": "hmac-sha256", + "signature": "30da579cb160780970680b47a0265f360eb311100c20e8d0d3948bf03c146577" + }, + "managed_by": "InditexTech CI governance", + "managed_paths": { + ".github/ISSUE_TEMPLATE/1-bug.yml": "cd4dad3f32fe707e06324790a2fe2c25829c55566de2b459d744e785b36e1205", + ".github/ISSUE_TEMPLATE/2-feature.yml": "ebb92730d6c01641d055f97ac094cbe07f88fac24be9b689ddf26e47e7bda863", + ".github/ISSUE_TEMPLATE/3-other.yml": "5a839b39a98704036210dee498146f51bd5a6c057228685680e02004ff063a9a", + ".github/ISSUE_TEMPLATE/config.yml": "10e5dee4d49aa9e7792ceb4c986160c61f95da1ba00a64bf06e5f1f636cd17ec", + ".github/PULL_REQUEST_TEMPLATE.md": "23a0b0ac79a9020ccac9c013582a01f86e2df2ae7f914673583b9a3368313041", + ".github/inditextech-ci-node.json": "72449d1243d714b1ef9bd615e6dc67d0ec0b25f0c73440a08fa34d1e498864ac", + ".github/workflows/code-npm_node-PR_verify.yml": "2d4430d765f4a7539ae64fc75cb8254cc09fdcaa7a2a805faada3865887262ce", + ".github/workflows/code-npm_node-publish-release-and-snapshot.yml": "b8743248b8882fc5c3de16acc455c9660fde8bcf686784f0a21e7989cb66ccf7", + ".github/workflows/code-npm_node-release-core.yml": "4d049809fb1d88f41b15c28cbe62b164dfe1be297ab6d7db38a685e9f693bcaa", + ".github/workflows/code-npm_node-sonarcloud-analysis.yml": "e86284867ae0bb193af66233d87f3f78ea7e7a0c9d33bd0e80432486245bf12b", + ".github/workflows/code-release_preview.yml": "4e2e95d60a498eb12d97ba5c8330b1173f04b02c807140beddad06a6e225b166", + ".github/workflows/codeql.yml": "3deedabd1c2469f05588157631078d85528e538e094f3e603e26c04894303d38", + ".github/workflows/pr-verify.yml": "5628d1d93c09cb761602fcfc3857c325e2dc542cadc159a1edad58d1818d0de0", + ".github/workflows/push-verify.yml": "643f6905fa30284af4a2eabac752293b2e785a5e82449bb6d97950624386829c", + ".github/workflows/scorecard-analysis.yml": "b45c2a0f87801796cefb4363d9f1414891b0734bc07de3f50d4a880e22faea10", + ".github/workflows/sync-to-develop.yml": "2f80696aeb3aa35a47944535539ca1887a8cea0cf11dd62e1a103497aa395acc", + ".tool-versions": "5e7b05edf5d8df174df5dd99d012e57666ec9923ac8506df7dc7753ba41815fd", + "CODE_OF_CONDUCT.md": "ce1e7a8f68a7917d48c03f9f7aae5529367f73af0e959276e889d33ea1e8d4ab", + "CONTRIBUTING.md": "4e1264ca54a45df44b362c7533f0eba912bfa77b6f561121ae9e5e9d6aa00df3", + "SECURITY.md": "0ee7a3356bc3a1c7649e3b7a6a9012b0608011be2b1ff8d106ad02c8950bab25", + "repolinter.json": "0efb305c47a63f03c488a34906fc2d5471c84145c2ff553f2aacf3a41b92e1a1" + }, + "managed_variables": { + "DEVELOPMENT_FLOW": "git-flow", + "GIT_FLOW_DEVELOPMENT_BRANCH": "develop", + "GIT_FLOW_RELEASE_BRANCH": "main", + "PROJECT_TYPE": "single", + "PUBLISH_SNAPSHOT": "true", + "WORKING_DIRECTORY": "." + }, + "profile": "node", + "repository_id": 1291909277, + "retained_paths": {}, + "schema_version": 2, + "source_digests": { + "base": "d0ba4f054c8e0a0d6c828d02718efc01251f7897cfcbe864d3c76ef304dc4a13", + "node": "f96646bb1913b7965d0e5def0800aa90b68e36ce89487f555f6e9a0d1162119b" + } +} diff --git a/.github/workflows/code-PR_verify-fallback.yml b/.github/workflows/code-PR_verify-fallback.yml deleted file mode 100644 index 8da3e7a..0000000 --- a/.github/workflows/code-PR_verify-fallback.yml +++ /dev/null @@ -1,16 +0,0 @@ -# SPDX-FileCopyrightText: 2026 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.) -# SPDX-License-Identifier: Apache-2.0 ---- -name: code-PR-verify-fallback - -on: - pull_request: - -jobs: - unit-tests: - if: "false" - name: Code / Verify - runs-on: ubuntu-24.04 - permissions: {} - steps: - - run: echo "No Code / Verify required" diff --git a/.github/workflows/code-npm_node-PR_verify.yml b/.github/workflows/code-npm_node-PR_verify.yml index 1d3acea..90a0949 100644 --- a/.github/workflows/code-npm_node-PR_verify.yml +++ b/.github/workflows/code-npm_node-PR_verify.yml @@ -1,138 +1,116 @@ # SPDX-FileCopyrightText: 2026 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.) # SPDX-License-Identifier: Apache-2.0 ---- -name: code-npm-PR-verify -concurrency: - group: code-PR-verify-${{ github.event.pull_request.number }} - cancel-in-progress: true +name: code-npm-node-PR-verify on: pull_request: - paths: - - "package.json" - - "package-lock.json" - - ".npmrc" - - ".tool-versions" - - "CHANGELOG.md" - - "src/**" - - "types/**" - - "example/**" - - "*.config.*" - - ".prettier*" - - ".github/workflows/code*" types: [opened, synchronize, ready_for_review, reopened] +permissions: + contents: read + +concurrency: + group: code-npm-node-pr-verify-${{ github.repository }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +env: + PROJECT_TYPE: ${{ vars.PROJECT_TYPE }} + WORKING_DIRECTORY: ${{ vars.WORKING_DIRECTORY }} + PACKAGE_MANAGER: ${{ vars.PACKAGE_MANAGER }} + ASDF_BRANCH_VERSION: '0.18.0' + jobs: - unit-tests: + verify: name: Code / Verify - timeout-minutes: 60 runs-on: ubuntu-24.04 - permissions: - contents: read - env: - ASDF_BRANCH_VERSION: 0.18.0 + timeout-minutes: 30 steps: - - name: Checkout / Branch Head - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - name: Check out pull request head + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 + persist-credentials: false ref: ${{ github.event.pull_request.head.sha }} - - name: Detect NPM project - id: project - run: | - if [ -f package.json ]; then - echo "has-package=true" >> "$GITHUB_OUTPUT" - else - echo "::notice::package.json is not present yet; skipping NPM verification for the OSS shell PR." - echo "has-package=false" >> "$GITHUB_OUTPUT" - fi - - - name: NPM / Setup Dependencies Cache - if: steps.project.outputs.has-package == 'true' - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 - with: - path: ~/.npm - key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node- - - - name: asdf / Setup Dependencies Cache - if: steps.project.outputs.has-package == 'true' - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 - id: asdf-cache - with: - path: ~/.asdf - key: ${{ runner.os }}-asdf-${{ env.ASDF_BRANCH_VERSION }}-${{ hashFiles('.tool-versions') }} - - - name: Validate tool-versions content - if: steps.project.outputs.has-package == 'true' + - name: Resolve governed project type + shell: bash run: | - if [ ! -f .tool-versions ]; then - echo "::error::.tool-versions is required for NPM workflows" + set -euo pipefail + descriptor="$GITHUB_WORKSPACE/.github/inditextech-ci-node.json" + if [[ ! -f "$descriptor" ]]; then + echo "::error title=Missing descriptor::.github/inditextech-ci-node.json not found." exit 1 fi - if grep -Evq '^[a-zA-Z0-9_-]+ [a-zA-Z0-9._+-]+$' .tool-versions; then - echo "::error::Invalid .tool-versions content detected" + project_type="$(jq -r '.project_type // empty' "$descriptor")" + case "$project_type" in + single|workspaces) ;; + *) + echo "::error title=Invalid project type::Descriptor .project_type must be single or workspaces." + exit 1 + ;; + esac + workdir="${WORKING_DIRECTORY:-$(jq -r '.working_directory // empty' "$descriptor")}" + case "$workdir" in + ""|/*|..|../*|*/..|*/../*) + echo "::error title=Invalid working directory::WORKING_DIRECTORY must be a non-empty relative repository path." >&2 + exit 1 + ;; + esac + if [[ ! -d "$workdir" ]]; then + echo "::error title=Missing working directory::'${workdir}' is not a directory in this checkout." >&2 exit 1 fi + echo "PROJECT_TYPE=$project_type" >> "$GITHUB_ENV" + echo "WORKING_DIRECTORY=${workdir}" >> "$GITHUB_ENV" - - name: Save tool-versions content - if: steps.project.outputs.has-package == 'true' + - name: Read governed tool versions + id: tool-versions + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash run: | + set -euo pipefail + if grep -Evq '^[a-zA-Z0-9_-]+ [a-zA-Z0-9._+-]+$' .tool-versions; then + echo "::error title=Invalid tool-versions::${WORKING_DIRECTORY}/.tool-versions is malformed." + exit 1 + fi { - echo "TOOL_VERSIONS<> "$GITHUB_ENV" + } >> "$GITHUB_OUTPUT" - - name: NPM / Setup asdf environment - if: steps.project.outputs.has-package == 'true' && steps.asdf-cache.outputs.cache-hit != 'true' - uses: asdf-vm/actions/install@1902764435ca0dd2f3388eea723a4f92a4eb8302 # v4.0.0 + - name: Set up asdf-managed Node + uses: asdf-vm/actions/install@b7bcd026f18772e44fe1026d729e1611cc435d47 # v4.0.1 with: - tool_versions: ${{ env.TOOL_VERSIONS }} + tool_versions: ${{ steps.tool-versions.outputs.tool_versions }} asdf_version: ${{ env.ASDF_BRANCH_VERSION }} - - name: Restore asdf shims to PATH - if: steps.project.outputs.has-package == 'true' && steps.asdf-cache.outputs.cache-hit == 'true' - run: | - echo "$HOME/.asdf/bin" >> "$GITHUB_PATH" - echo "$HOME/.asdf/shims" >> "$GITHUB_PATH" - cp .tool-versions "$HOME/.tool-versions" - - - name: NPM / Set npmrc registry - if: steps.project.outputs.has-package == 'true' - env: - NPM_REGISTRY: "https://registry.npmjs.org/" - run: | - echo "registry=${NPM_REGISTRY}" >> "$HOME/.npmrc" - echo "@inditextech:registry=${NPM_REGISTRY}" >> "$HOME/.npmrc" - - - name: NPM / Create Cache Folders - if: steps.project.outputs.has-package == 'true' - run: mkdir -p "$HOME"/.npm - - - name: NPM / Install dependencies - if: steps.project.outputs.has-package == 'true' - run: npm ci - - - name: NPM / Verify - if: steps.project.outputs.has-package == 'true' - run: | - if node -e "process.exit(require('./package.json').scripts?.verify ? 0 : 1)"; then - npm run verify - else - npm run lint --if-present - npm run format:check --if-present - npm run test --if-present - npm run build --if-present - fi - - - name: Store project information - if: steps.project.outputs.has-package == 'true' - id: version + - name: Verify, test, and build + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash run: | - echo "app-version=$(jq -r ".version" package.json)" >> "$GITHUB_OUTPUT" - echo "app-name=$(jq -r ".name" package.json)" >> "$GITHUB_OUTPUT" - echo "github-repository=$(echo "$GITHUB_REPOSITORY" | cut -d'/' -f2)" >> "$GITHUB_OUTPUT" + set -euo pipefail + case "$PROJECT_TYPE" in + single|workspaces) : ;; + *) + echo "::error title=Invalid project type::PROJECT_TYPE must be single or workspaces." + exit 1 + ;; + esac + case "${PACKAGE_MANAGER:-npm}" in + npm) + npm ci + npm run verify + ;; + pnpm) + corepack enable + pnpm install --frozen-lockfile + pnpm run verify + ;; + *) + echo "::error title=Invalid package manager::PACKAGE_MANAGER must be npm or pnpm." + exit 1 + ;; + esac diff --git a/.github/workflows/code-npm_node-publish-release-and-snapshot.yml b/.github/workflows/code-npm_node-publish-release-and-snapshot.yml index b8016ca..35af958 100644 --- a/.github/workflows/code-npm_node-publish-release-and-snapshot.yml +++ b/.github/workflows/code-npm_node-publish-release-and-snapshot.yml @@ -1,594 +1,912 @@ # SPDX-FileCopyrightText: 2026 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.) # SPDX-License-Identifier: Apache-2.0 ---- -name: code-npm-publish-release-and-snapshot -run-name: "Publish workflow: ${{ github.event_name }}" + +name: code-npm-node-publish-release-and-snapshot +run-name: Publish npm (release or snapshot) from ${{ github.event_name }} on: - issue_comment: - types: [created] - pull_request: - types: [closed] - branches: ["main", "main-*"] - paths: - - "package.json" - - "package-lock.json" - - "CHANGELOG.md" - - "src/**" - - "types/**" - - "example/**" - - ".github/workflows/code**" workflow_dispatch: inputs: - BASELINE: - description: "Baseline branch" - required: true - default: "main" - RELEASE_TYPE: - description: "Release type to use" + release_type: + description: Release type to use required: true - default: "release-type/minor" + default: release-type/minor type: choice options: - - "release-type/hotfix" - - "release-type/multi-hotfix" - - "release-type/major" - - "release-type/minor" - - "release-type/patch" + - release-type/hotfix + - release-type/multi-hotfix + - release-type/major + - release-type/minor + - release-type/patch + pull_request: + types: [closed] + branches: [main, main-*] + push: + branches: [main, main-*, develop, develop-*] + issue_comment: + types: [created] + +# actionlint cache-call-unrestricted: this workflow is reachable from the +# low-trust issue_comment trigger (release re-run gated to OWNER, MEMBER and +# COLLABORATOR); cap its cache access explicitly. Declared at workflow level -- +# the job-level `cache-mode` key is not part of the workflow-call job schema. +cache-mode: none + +permissions: + contents: read + +concurrency: + group: >- + ${{ format('code-npm-node-publish-{0}-{1}', github.repository, + (github.event_name == 'push' || github.event_name == 'issue_comment') + && format('snapshot-{0}', github.event.issue.number || github.ref) + || format('release-{0}', github.event.pull_request.base.ref || github.ref_name)) }} + cancel-in-progress: ${{ github.event_name == 'push' || github.event_name == 'issue_comment' }} env: - ASDF_BRANCH_VERSION: 0.18.0 - NPM_REGISTRY: "https://registry.npmjs.org/" + PROJECT_TYPE: ${{ vars.PROJECT_TYPE }} + WORKING_DIRECTORY: ${{ vars.WORKING_DIRECTORY }} + PACKAGE_MANAGER: ${{ vars.PACKAGE_MANAGER }} + ASDF_BRANCH_VERSION: '0.18.0' + SNAPSHOT_ARTIFACT: npm-snapshot-dist jobs: - publish-snapshot: - name: Publish Snapshot - concurrency: code-publish-snapshot-${{ github.ref }} + authorize: + name: Authorize snapshot command + if: >- + github.event_name == 'issue_comment' && + github.event.issue.pull_request && + startsWith(github.event.comment.body, '/publish-snapshot') && + contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association) + runs-on: ubuntu-24.04 + timeout-minutes: 5 permissions: - id-token: write contents: read - issues: write - pull-requests: write - if: > - github.event_name == 'issue_comment' && - github.event.issue.pull_request != null && - github.event.comment.body == '/publish-snapshot' + pull-requests: read + outputs: + head_sha: ${{ steps.resolve.outputs.head_sha }} + steps: + - name: Resolve pull request head + id: resolve + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.issue.number }} + shell: bash + run: | + set -euo pipefail + head="$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" --jq '[.head.sha, .head.repo.full_name] | @tsv')" + head_sha="$(printf '%s' "$head" | cut -f1)" + head_repo="$(printf '%s' "$head" | cut -f2)" + if [[ -z "$head_sha" || "$head_sha" == "null" ]]; then + echo "::error title=Unresolved head::Could not resolve the pull request head commit." >&2 + exit 1 + fi + if [[ "$head_repo" != "$GITHUB_REPOSITORY" ]]; then + echo "::error title=Fork head rejected::Snapshot publishing only accepts branches of ${GITHUB_REPOSITORY}; refusing head from ${head_repo}." >&2 + exit 1 + fi + echo "head_sha=${head_sha}" >> "$GITHUB_OUTPUT" + + build: + name: Build snapshot + needs: [authorize] + if: >- + !cancelled() && + ( + ( + github.event_name == 'push' && + ( + (vars.DEVELOPMENT_FLOW == 'trunk-based-development' && (github.ref_name == 'main' || startsWith(github.ref_name, 'main-'))) || + (vars.DEVELOPMENT_FLOW == 'git-flow' && (github.ref_name == 'develop' || startsWith(github.ref_name, 'develop-'))) + ) + ) || + (github.event_name == 'issue_comment' && needs.authorize.result == 'success') + ) runs-on: ubuntu-24.04 + timeout-minutes: 30 + permissions: + contents: read + outputs: + artifact_name: ${{ steps.pack.outputs.artifact_name }} + should_publish: ${{ steps.plan.outputs.should_publish }} steps: - - name: Validate admin permissions - id: permission-check - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 - with: - script: | - const { data } = await github.rest.repos.getCollaboratorPermissionLevel({ - owner: context.repo.owner, - repo: context.repo.repo, - username: context.actor - }); - - const permission = data.permission; - core.info(`User permission level: ${permission}`); - - if (permission !== "admin") { - core.setFailed(`User @${context.actor} is not a repository admin.`); - } - - - name: Checkout PR branch - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - name: Check out snapshot candidate + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - ref: refs/pull/${{ github.event.issue.number }}/head - fetch-depth: 0 persist-credentials: false - - - name: Validate NPM project + ref: >- + ${{ + (github.event_name == 'issue_comment' && needs.authorize.outputs.head_sha) || + github.sha + }} + + - name: Betrayal check for comment-authorized snapshot + if: github.event_name == 'issue_comment' + shell: bash run: | - if [ ! -f package.json ]; then - echo "::error::package.json is required to publish an NPM snapshot" - exit 1 - fi - if [ ! -f package-lock.json ]; then - echo "::error::package-lock.json is required to publish an NPM snapshot" + set -euo pipefail + authorized="${{ needs.authorize.outputs.head_sha }}" + checked="$(git rev-parse HEAD)" + if [[ "$checked" != "$authorized" ]]; then + echo "::error title=Snapshot head race::The pull request advanced since the /publish-snapshot authorization (authorized $authorized, workspace $checked). Aborting before any build or publish." exit 1 fi + echo "::notice title=Snapshot head verified::Checked-out commit matches the authorized snapshot head." - - name: NPM / Setup Dependencies Cache - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 - with: - path: ~/.npm - key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node- - - - name: Restore asdf Cache - id: asdf-cache - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 - continue-on-error: true - with: - path: ~/.asdf - key: ${{ runner.os }}-asdf-${{ env.ASDF_BRANCH_VERSION }}-${{ hashFiles('.tool-versions') }} + - name: Plan snapshot + id: plan + env: + PUBLISH_SNAPSHOT: ${{ vars.PUBLISH_SNAPSHOT }} + shell: bash + run: | + set -euo pipefail + if [[ "${PUBLISH_SNAPSHOT:-false}" != "true" ]]; then + echo "::notice title=Snapshot disabled::publish_snapshot opt-in is off; no snapshot is published." + echo "should_publish=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + head_committer_email="$(git show -s --format=%ce HEAD)" + head_subject="$(git show -s --format=%s HEAD)" + # Release commits are signed by the organization release identity, + # not a [bot] account, so the release lane is also recognized by subject. + if [[ "$GITHUB_EVENT_NAME" == "push" && ( "$head_committer_email" == *"[bot]@users.noreply.github.com" || "$head_subject" == "[node-release] "* ) ]]; then + echo "::notice title=Snapshot skipped::Next-dev commit does not need a snapshot." + echo "should_publish=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + echo "should_publish=true" >> "$GITHUB_OUTPUT" - - name: Save tool-versions content + - name: Read governed tool versions + id: tool-versions + if: steps.plan.outputs.should_publish == 'true' + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash run: | + set -euo pipefail + if grep -Evq '^[a-zA-Z0-9_-]+ [a-zA-Z0-9._+-]+$' .tool-versions; then + echo "::error title=Invalid tool-versions::${WORKING_DIRECTORY}/.tool-versions is malformed." + exit 1 + fi { - echo "TOOL_VERSIONS<> "$GITHUB_ENV" + } >> "$GITHUB_OUTPUT" - - name: Setup asdf environment - uses: asdf-vm/actions/install@1902764435ca0dd2f3388eea723a4f92a4eb8302 # v4.0.0 - if: steps.asdf-cache.outputs.cache-hit != 'true' + - name: Set up asdf-managed Node + if: steps.plan.outputs.should_publish == 'true' + uses: asdf-vm/actions/install@b7bcd026f18772e44fe1026d729e1611cc435d47 # v4.0.1 with: - tool_versions: ${{ env.TOOL_VERSIONS }} + tool_versions: ${{ steps.tool-versions.outputs.tool_versions }} asdf_version: ${{ env.ASDF_BRANCH_VERSION }} - - name: Restore asdf shims to PATH - if: steps.asdf-cache.outputs.cache-hit == 'true' - run: | - echo "$HOME/.asdf/bin" >> "$GITHUB_PATH" - echo "$HOME/.asdf/shims" >> "$GITHUB_PATH" - cp .tool-versions "$HOME/.tool-versions" - - - name: NPM / Set npmrc registry - run: | - echo "registry=${NPM_REGISTRY}" >> "$HOME/.npmrc" - echo "@inditextech:registry=${NPM_REGISTRY}" >> "$HOME/.npmrc" - - - name: NPM / Create Cache Folders - run: mkdir -p "$HOME"/.npm - - - name: NPM / Install dependencies - run: npm ci - - - name: NPM / Verify - run: | - if node -e "process.exit(require('./package.json').scripts?.verify ? 0 : 1)"; then - npm run verify - else - npm run lint --if-present - npm run format:check --if-present - npm run test --if-present - npm run build --if-present - fi - - - name: Define snapshot version - run: | - BASE_VERSION=$(node -p "require('./package.json').version.replace(/-.*/, '')") - SNAPSHOT_VERSION="${BASE_VERSION}-SNAPSHOT.${{ github.run_number }}.${{ github.run_attempt }}" - echo "SNAPSHOT_VERSION=$SNAPSHOT_VERSION" >> "$GITHUB_ENV" - - - name: NPM / Prepare snapshot + - name: Build and pack snapshot + id: pack + if: steps.plan.outputs.should_publish == 'true' + working-directory: ${{ env.WORKING_DIRECTORY }} env: - SNAPSHOT_VERSION: ${{ env.SNAPSHOT_VERSION }} - run: | - npm version "$SNAPSHOT_VERSION" --no-git-tag-version - npm run release:prepare --if-present - npm run build --if-present - - - name: NPM / Ensure minimum npm version for trusted publishing - run: | - CURRENT_NPM=$(npm --version) - REQUIRED_NPM="11.5.1" - if printf '%s\n%s\n' "$REQUIRED_NPM" "$CURRENT_NPM" | sort -V -C; then - echo "npm $CURRENT_NPM already meets the minimum requirement ($REQUIRED_NPM). Skipping upgrade." - else - echo "npm $CURRENT_NPM is below the minimum requirement ($REQUIRED_NPM). Upgrading..." - npm install -g npm@^11.5.1 --registry="${NPM_REGISTRY}" - asdf reshim nodejs - fi - node --version - npm --version - - - name: NPM / Verify package exists in registry - id: verify-package + PR_NUMBER: ${{ github.event.issue.number }} + DIST_DIR: ${{ runner.temp }}/snapshot-dist + shell: bash run: | - PKG_NAME=$(node -p "require('./package.json').name") - ENCODED_PKG_NAME=$(node -p "encodeURIComponent(process.argv[1])" "$PKG_NAME") - RESPONSE_FILE=$(mktemp) - if ! HTTP_STATUS=$(curl -sS -o "$RESPONSE_FILE" -w "%{http_code}" \ - --connect-timeout 10 \ - --max-time 30 \ - --retry 3 \ - --retry-delay 2 \ - --retry-all-errors \ - "${NPM_REGISTRY}${ENCODED_PKG_NAME}"); then - echo "::error::Failed to verify $PKG_NAME due to npm registry connectivity error" - rm -f "$RESPONSE_FILE" - exit 1 - fi - case "$HTTP_STATUS" in - 200) - PARSE_STATUS=0 - node -e "try { const d=JSON.parse(require('fs').readFileSync(process.argv[1],'utf8')); process.exit(Object.keys(d.versions||{}).length > 0 ? 0 : 1); } catch(e) { console.error('JSON parse error:', e.message); process.exit(2); }" "$RESPONSE_FILE" || PARSE_STATUS=$? - case "$PARSE_STATUS" in - 0) - echo "✓ $PKG_NAME exists in registry with active versions" - echo "HAS_NEW_PACKAGE=false" >> "$GITHUB_ENV" + set -euo pipefail + + strip_snapshot() { printf '%s' "${1%-SNAPSHOT}"; } + compute_snapshot() { + if [[ "$GITHUB_EVENT_NAME" == "issue_comment" ]]; then + printf '%s-PR%s-SNAPSHOT.%s' "$1" "$PR_NUMBER" "$GITHUB_RUN_NUMBER" + else + printf '%s-SNAPSHOT.%s.%s' "$1" "$GITHUB_RUN_NUMBER" "$GITHUB_RUN_ATTEMPT" + fi + } + member_dir() { + printf '%s' "${1##*/}" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//' + } + + mkdir -p "$DIST_DIR" + + case "${PACKAGE_MANAGER:-npm}" in + npm) + npm ci + npm run build + case "$PROJECT_TYPE" in + single) + base="$(strip_snapshot "$(jq -r '.version' package.json)")" + npm pkg set "version=$(compute_snapshot "$base")" + npm pack --pack-destination "$DIST_DIR" ;; - 1) - echo "::warning::$PKG_NAME has no active versions (unpublished); NPM_TOKEN will be required" - echo "HAS_NEW_PACKAGE=true" >> "$GITHUB_ENV" + workspaces) + descriptor="$GITHUB_WORKSPACE/.github/inditextech-ci-node.json" + while IFS= read -r package; do + member="$(member_dir "$package")" + base="$(strip_snapshot "$(jq -r '.version' "packages/$member/package.json")")" + npm pkg set "version=$(compute_snapshot "$base")" --workspace "packages/$member" + done < <(jq -er '.release.packages[]' "$descriptor") + npm pack --workspaces --pack-destination "$DIST_DIR" ;; *) - echo "::error::Failed to verify $PKG_NAME — npm registry returned invalid JSON (HTTP 200)" - rm -f "$RESPONSE_FILE" + echo "::error title=Invalid project type::PROJECT_TYPE must be single or workspaces." exit 1 ;; esac ;; - 404) - echo "::warning::$PKG_NAME is not present in the npm registry; NPM_TOKEN will be required" - echo "HAS_NEW_PACKAGE=true" >> "$GITHUB_ENV" - ;; - 429|5??) - echo "::error::Failed to verify $PKG_NAME due to transient npm registry error (HTTP $HTTP_STATUS)" - exit 1 + pnpm) + corepack enable + pnpm install --frozen-lockfile + pnpm run build + case "$PROJECT_TYPE" in + single) + base="$(strip_snapshot "$(jq -r '.version' package.json)")" + npm pkg set "version=$(compute_snapshot "$base")" + pnpm pack --pack-destination "$DIST_DIR" + ;; + workspaces) + descriptor="$GITHUB_WORKSPACE/.github/inditextech-ci-node.json" + while IFS= read -r package; do + member="$(member_dir "$package")" + base="$(strip_snapshot "$(jq -r '.version' "packages/$member/package.json")")" + ( cd "packages/$member" && npm pkg set "version=$(compute_snapshot "$base")" ) + ( cd "packages/$member" && pnpm pack --pack-destination "$DIST_DIR" ) + done < <(jq -er '.release.packages[]' "$descriptor") + ;; + *) + echo "::error title=Invalid project type::PROJECT_TYPE must be single or workspaces." + exit 1 + ;; + esac ;; *) - echo "::error::Failed to verify $PKG_NAME due to unexpected npm registry response (HTTP $HTTP_STATUS)" + echo "::error title=Invalid package manager::PACKAGE_MANAGER must be npm or pnpm." exit 1 ;; esac - rm -f "$RESPONSE_FILE" - - name: NPM / Publish Snapshot (OIDC + provenance) - if: env.HAS_NEW_PACKAGE == 'false' - env: - NPM_CONFIG_PROVENANCE: "true" - run: npm publish --tag next --access public + echo "artifact_name=${SNAPSHOT_ARTIFACT}" >> "$GITHUB_OUTPUT" - - name: NPM / Publish Snapshot (NPM_TOKEN for new packages) - if: env.HAS_NEW_PACKAGE == 'true' - env: - NPM_AUTH: ${{ secrets.NPM_TOKEN }} - run: | - if [ -z "${NPM_AUTH:-}" ]; then - echo "::error::NPM_TOKEN secret is required for publishing new packages but is not set" - exit 1 - fi - TEMP_NPMRC="$(mktemp)" - trap 'rm -f "$TEMP_NPMRC"' EXIT - cp "$HOME/.npmrc" "$TEMP_NPMRC" - chmod 600 "$TEMP_NPMRC" - echo "//registry.npmjs.org/:_authToken=${NPM_AUTH}" >> "$TEMP_NPMRC" - export NPM_CONFIG_USERCONFIG="$TEMP_NPMRC" - npm publish --tag next --access public - - - name: Result / Comment on PR - if: always() - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + - name: Upload snapshot artifact + if: steps.plan.outputs.should_publish == 'true' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - script: | - const status = '${{ job.status }}'; - const authMethod = '${{ env.HAS_NEW_PACKAGE }}' === 'true' - ? 'NPM_TOKEN (new package detected)' - : 'OIDC trusted publishing (provenance)'; - - const body = status === 'success' - ? [ - '### :rocket: NPM snapshot published', - '', - `- Version: \`${process.env.SNAPSHOT_VERSION}\``, - '- Tag: `next`', - `- Auth: ${authMethod}` - ] - : [ - '### :x: NPM snapshot publication failed', - '', - 'Please check the workflow logs for details.' - ]; - - await github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: body.join('\n') - }); - - release: - name: Release - concurrency: code-release-${{ github.ref }} + name: ${{ env.SNAPSHOT_ARTIFACT }} + path: ${{ runner.temp }}/snapshot-dist/*.tgz + if-no-files-found: error + retention-days: 5 + + publish: + name: Publish snapshot + needs: [build] + if: >- + !cancelled() && + needs.build.outputs.should_publish == 'true' && + vars.PUBLISH_SNAPSHOT == 'true' + runs-on: ubuntu-24.04 + timeout-minutes: 30 + environment: npm-snapshot permissions: + contents: read id-token: write - contents: write - issues: write - pull-requests: write - if: github.event_name == 'workflow_dispatch' - || (github.event_name == 'pull_request' && github.event.pull_request.merged == true - && !contains(join(github.event.pull_request.labels.*.name, ', '), 'skip-release') - && (contains(join(github.event.pull_request.labels.*.name, ', '), 'release-type') - || vars.DEVELOPMENT_FLOW == 'trunk-based-development' )) + steps: + - name: Check out trusted publish ref + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Download pre-built snapshot artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: ${{ needs.build.outputs.artifact_name }} + path: ${{ runner.temp }}/publish-dist + + - name: Publish via npm trusted publishing + uses: InditexTech/gh-actions/npm@80ca79bcb04379e4fed8b91a55aa15dc6b8b638a # v1.1.6 + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + with: + working-directory: ${{ vars.WORKING_DIRECTORY }} + project-type: ${{ vars.PROJECT_TYPE }} + dist-tag: next + artifact-directory: ${{ runner.temp }}/publish-dist + + release-core: + name: Prepare and Build Release + if: >- + (github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request') && + ( + github.event_name != 'issue_comment' || + contains( + fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), + github.event.comment.author_association + ) + ) + uses: ./.github/workflows/code-npm_node-release-core.yml + with: + release_type: ${{ inputs.release_type }} + secrets: + APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} + CI_GPG_SECRET_KEY: ${{ secrets.CI_GPG_SECRET_KEY }} + CI_GPG_SECRET_KEY_PASSWORD: ${{ secrets.CI_GPG_SECRET_KEY_PASSWORD }} + + publish-npm: + name: Publish ${{ matrix.release.tag }} to npm + needs: release-core + if: vars.RELEASE_LIFECYCLE != 'delegated' runs-on: ubuntu-24.04 + timeout-minutes: 30 + environment: npm-registry + permissions: + contents: read + id-token: write + strategy: + fail-fast: false + matrix: + release: ${{ fromJSON(needs.release-core.outputs.releases) }} steps: - - name: Validate admin permissions - id: permission-check - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + - name: Check out trusted publish ref + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - script: | - const { data } = await github.rest.repos.getCollaboratorPermissionLevel({ - owner: context.repo.owner, - repo: context.repo.repo, - username: context.actor - }); + persist-credentials: false - const permission = data.permission; - core.info(`User permission level: ${permission}`); + - name: Download pre-built release artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: npm-distributions-${{ matrix.release.tag }} + path: ${{ runner.temp }}/publish-dist - if (permission !== "admin") { - core.setFailed(`User @${context.actor} is not a repository admin.`); - } + - name: Publish via npm trusted publishing + uses: InditexTech/gh-actions/npm@80ca79bcb04379e4fed8b91a55aa15dc6b8b638a # v1.1.6 + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + with: + working-directory: ${{ vars.WORKING_DIRECTORY }} + project-type: ${{ vars.PROJECT_TYPE }} + dist-tag: latest + artifact-directory: ${{ runner.temp }}/publish-dist + + publish-npm-delegated: + name: Promote and publish delegated release to npm + needs: release-core + if: >- + vars.RELEASE_LIFECYCLE == 'delegated' && + needs.release-core.result == 'success' && + (github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request') + runs-on: ubuntu-24.04 + timeout-minutes: 30 + environment: npm-registry + permissions: + contents: read + id-token: write + steps: + - name: Download immutable delegated release handoff + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: node-delegated-release-handoff + path: ${{ runner.temp }}/node-delegated-release-handoff - - name: Get input parameters + - name: Verify delegated release handoff + id: handoff + env: + HANDOFF_DIR: ${{ runner.temp }}/node-delegated-release-handoff + shell: bash run: | - if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then - RELEASE_LABELS="${{ github.event.inputs.RELEASE_TYPE }}" - else - RELEASE_LABELS="${{ join(github.event.pull_request.labels.*.name, ', ') }}" - if [[ $RELEASE_LABELS != *release-type/* ]]; then - RELEASE_LABELS="$RELEASE_LABELS, release-type/minor" + set -euo pipefail + metadata="$HANDOFF_DIR/release-handoff.json" + source_bundle="$HANDOFF_DIR/release-source.bundle" + publish_dist="${RUNNER_TEMP}/publish-dist" + if [[ ! -f "$metadata" || ! -f "$source_bundle" || ! -d "$HANDOFF_DIR/tarballs" ]]; then + echo "::error title=Incomplete release handoff::The immutable source, metadata, and tarball directory are all required." + exit 1 + fi + if [[ "$(sha256sum "$metadata" | awk '{print $1}')" != "${{ needs.release-core.outputs.handoff_digest }}" ]]; then + echo "::error title=Tampered release handoff::The run-bound release metadata digest does not match preparation." + exit 1 + fi + jq -e \ + --arg repository "$GITHUB_REPOSITORY" \ + --arg run_id "$GITHUB_RUN_ID" \ + --arg run_attempt "$GITHUB_RUN_ATTEMPT" \ + ' + .schema_version == 1 and + .repository == $repository and + .run_id == $run_id and + .run_attempt == $run_attempt and + (.expected_ref | test("^refs/heads/[A-Za-z0-9._/-]+$")) and + (.source_commit | test("^[0-9a-f]{40}$")) and + (.release_commit | test("^[0-9a-f]{40}$")) and + (.source_bundle.file == "release-source.bundle") and + (.source_bundle.sha256 | test("^[0-9a-f]{64}$")) and + (.releases | type == "array" and length > 0) and + (.tags | type == "array" and length > 0) and + (.tarballs | type == "array" and length > 0) + ' "$metadata" > /dev/null + if [[ "$(sha256sum "$source_bundle" | awk '{print $1}')" != "$(jq -r '.source_bundle.sha256' "$metadata")" ]]; then + echo "::error title=Tampered release handoff::The release source bundle digest does not match metadata." + exit 1 + fi + jq -e ' + (.tags | all(.[]; (.name | type == "string" and length > 0) and (.target | type == "string" and test("^[0-9a-f]{40}$")))) and + (.releases | all(.[]; (.tag | type == "string" and length > 0) and (.version | type == "string" and length > 0))) and + (([.tags[].name] | sort) == ([.releases[].tag] | sort)) and + (.tarballs | all(.[]; (.file | type == "string" and test("^[A-Za-z0-9][A-Za-z0-9._-]*\\.tgz$")) and (.package | type == "string" and length > 0) and (.release_tag | type == "string" and length > 0) and (.version | type == "string" and length > 0) and (.sha256 | type == "string" and test("^[0-9a-f]{64}$")))) + ' "$metadata" > /dev/null + + release_tag_for() { + local package_name="$1" + local package_version="$2" + local member record release_tag release_version + local -a exact_matches=() + local -a version_matches=() + member="$(printf '%s' "${package_name##*/}" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//')" + while IFS= read -r record; do + release_tag="$(jq -r '.tag' <<< "$record")" + release_version="$(jq -r '.version' <<< "$record")" + [[ "$release_version" == "$package_version" ]] || continue + version_matches+=("$release_tag") + if [[ "$release_tag" == "$package_version" || "$release_tag" == "${member}-${package_version}" || "$release_tag" == "${package_name}-${package_version}" ]]; then + exact_matches+=("$release_tag") + fi + done < <(jq -c '.releases[]' "$metadata") + if [[ ${#exact_matches[@]} -eq 1 ]]; then + printf '%s' "${exact_matches[0]}" + elif [[ ${#exact_matches[@]} -eq 0 && ${#version_matches[@]} -eq 1 ]]; then + printf '%s' "${version_matches[0]}" + else + echo "::error title=Ambiguous package release::Package '$package_name@$package_version' does not resolve to exactly one prepared release tag." >&2 + exit 1 fi + } + + rm -rf "$HANDOFF_DIR/source" "$publish_dist" + git clone --quiet --no-checkout "$source_bundle" "$HANDOFF_DIR/source" + source_commit="$(jq -r '.source_commit' "$metadata")" + release_commit="$(jq -r '.release_commit' "$metadata")" + git -C "$HANDOFF_DIR/source" rev-parse --verify --quiet "${source_commit}^{commit}" > /dev/null + git -C "$HANDOFF_DIR/source" rev-parse --verify --quiet "${release_commit}^{commit}" > /dev/null + if ! git -C "$HANDOFF_DIR/source" merge-base --is-ancestor "$source_commit" "$release_commit"; then + echo "::error title=Invalid release provenance::The prepared release is not descended from the verified source commit." + exit 1 fi - echo "RELEASE_LABELS=$RELEASE_LABELS" >> "$GITHUB_ENV" - BASELINE_BRANCH=${{ github.event.inputs.BASELINE || github.ref }} - echo "BASELINE_BRANCH=${BASELINE_BRANCH#refs/heads/}" >> "$GITHUB_ENV" - - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3 + tag_count="$(jq -r '.tags | length' "$metadata")" + unique_tag_count="$(jq -r '[.tags[].name] | unique | length' "$metadata")" + if [[ "$unique_tag_count" -ne "$tag_count" ]]; then + echo "::error title=Invalid release tags::The handoff repeats a release tag." + exit 1 + fi + while IFS= read -r tag; do + git check-ref-format --allow-onelevel "refs/tags/$tag" + target="$(jq -er --arg tag "$tag" '.tags[] | select(.name == $tag) | .target | select(test("^[0-9a-f]{40}$"))' "$metadata")" + if [[ "$(git -C "$HANDOFF_DIR/source" rev-parse "${tag}^{commit}")" != "$target" ]]; then + echo "::error title=Invalid release tag::Prepared tag '$tag' does not resolve to its recorded commit." + exit 1 + fi + if ! git -C "$HANDOFF_DIR/source" merge-base --is-ancestor "$source_commit" "$target" \ + || ! git -C "$HANDOFF_DIR/source" merge-base --is-ancestor "$target" "$release_commit"; then + echo "::error title=Invalid release tag provenance::Prepared tag '$tag' must satisfy source <= tag <= release." + exit 1 + fi + done < <(jq -r '.tags[].name' "$metadata") + + mkdir -p "$publish_dist" + tarball_count="$(jq -r '.tarballs | length' "$metadata")" + unique_tarball_count="$(jq -r '[.tarballs[].file] | unique | length' "$metadata")" + if [[ "$unique_tarball_count" -ne "$tarball_count" ]]; then + echo "::error title=Invalid package artifacts::The handoff repeats a tarball filename." + exit 1 + fi + while IFS= read -r tarball_file; do + tarball="$HANDOFF_DIR/tarballs/$tarball_file" + expected_sha="$(jq -er --arg file "$tarball_file" '.tarballs[] | select(.file == $file) | .sha256' "$metadata")" + expected_package="$(jq -er --arg file "$tarball_file" '.tarballs[] | select(.file == $file) | .package' "$metadata")" + expected_release_tag="$(jq -er --arg file "$tarball_file" '.tarballs[] | select(.file == $file) | .release_tag' "$metadata")" + expected_version="$(jq -er --arg file "$tarball_file" '.tarballs[] | select(.file == $file) | .version' "$metadata")" + if [[ ! -f "$tarball" ]] || [[ "$(sha256sum "$tarball" | awk '{print $1}')" != "$expected_sha" ]]; then + echo "::error title=Tampered package artifact::Tarball '$tarball_file' is missing or does not match the prepared digest." + exit 1 + fi + tar -tzf "$tarball" | grep -qx 'package/package.json' + if [[ "$(tar -xzOf "$tarball" package/package.json | jq -r '.name')" != "$expected_package" ]] \ + || [[ "$(tar -xzOf "$tarball" package/package.json | jq -r '.version')" != "$expected_version" ]]; then + echo "::error title=Tampered package artifact::Tarball '$tarball_file' package identity does not match the prepared metadata." + exit 1 + fi + mapped_release_tag="$(release_tag_for "$expected_package" "$expected_version")" + if [[ "$mapped_release_tag" != "$expected_release_tag" ]] \ + || [[ "$(jq -r --arg tag "$expected_release_tag" --arg version "$expected_version" '[.releases[] | select(.tag == $tag and .version == $version)] | length' "$metadata")" -ne 1 ]]; then + echo "::error title=Invalid package release::Tarball '$expected_package@$expected_version' is not bound to exactly one prepared release tag." + exit 1 + fi + cp "$tarball" "$publish_dist/$tarball_file" + done < <(jq -r '.tarballs[].file' "$metadata") + if [[ "$(find "$publish_dist" -maxdepth 1 -type f -name '*.tgz' | wc -l | tr -d ' ')" -ne "$tarball_count" ]]; then + echo "::error title=Incomplete package artifacts::The protected publish directory does not contain the complete verified tarball set." + exit 1 + fi + + { + echo "expected_ref=$(jq -r '.expected_ref' "$metadata")" + echo "release_commit=$release_commit" + echo "source_commit=$source_commit" + } >> "$GITHUB_OUTPUT" + + - name: Create GitHub App token id: app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: client-id: ${{ vars.APP_CLIENT_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} + permission-contents: write - - name: Checkout merge commit - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - name: Check out trusted release baseline + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - ref: ${{ env.BASELINE_BRANCH }} fetch-depth: 0 persist-credentials: false + ref: ${{ github.event.pull_request.base.ref || github.ref_name }} + token: ${{ steps.app-token.outputs.token }} - - name: Validate NPM project + - name: Promote verified delegated release refs + env: + EXPECTED_REF: ${{ steps.handoff.outputs.expected_ref }} + HANDOFF_DIR: ${{ runner.temp }}/node-delegated-release-handoff + GH_TOKEN: ${{ steps.app-token.outputs.token }} + RELEASE_COMMIT: ${{ steps.handoff.outputs.release_commit }} + SOURCE_COMMIT: ${{ steps.handoff.outputs.source_commit }} + shell: bash run: | - if [ ! -f package.json ]; then - echo "::error::package.json is required to release the NPM package" - exit 1 - fi - if [ ! -f package-lock.json ]; then - echo "::error::package-lock.json is required to release the NPM package" + set -euo pipefail + gh auth setup-git + source="$HANDOFF_DIR/source" + git -C "$source" remote set-url origin "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git" + if [[ "$(git ls-remote origin "$EXPECTED_REF" | cut -f1)" != "$SOURCE_COMMIT" ]]; then + echo "::error title=Release baseline moved::The protected release branch no longer points to the verified source commit." exit 1 fi + tag_refs=() + while IFS= read -r tag; do + if git ls-remote --exit-code --tags origin "refs/tags/$tag" > /dev/null 2>&1; then + echo "::error title=Tag conflict::Release tag '$tag' already exists on the remote." + exit 1 + fi + tag_refs+=("refs/tags/$tag") + done < <(jq -r '.tags[].name' "$HANDOFF_DIR/release-handoff.json") + git -C "$source" push --atomic origin "$RELEASE_COMMIT:$EXPECTED_REF" "${tag_refs[@]}" - - name: NPM / Setup Dependencies Cache - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + - name: Publish verified delegated tarballs via npm trusted publishing + uses: InditexTech/gh-actions/npm@80ca79bcb04379e4fed8b91a55aa15dc6b8b638a # v1.1.6 + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} with: - path: ~/.npm - key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node- - - - name: Restore asdf Cache - id: asdf-cache - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 - continue-on-error: true + working-directory: ${{ vars.WORKING_DIRECTORY }} + project-type: ${{ vars.PROJECT_TYPE }} + dist-tag: latest + artifact-directory: ${{ runner.temp }}/publish-dist + + prepare-delegated-next-development: + name: Prepare delegated next-development source + needs: [release-core, publish-npm-delegated] + if: >- + vars.RELEASE_LIFECYCLE == 'delegated' && + needs.release-core.result == 'success' && + needs.publish-npm-delegated.result == 'success' + runs-on: ubuntu-24.04 + timeout-minutes: 30 + permissions: + contents: read + outputs: + handoff_digest: ${{ steps.next-development.outputs.handoff_digest }} + steps: + - name: Download immutable delegated release handoff + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - path: ~/.asdf - key: ${{ runner.os }}-asdf-${{ env.ASDF_BRANCH_VERSION }}-${{ hashFiles('.tool-versions') }} + name: node-delegated-release-handoff + path: ${{ runner.temp }}/node-delegated-release-handoff - - name: Save tool-versions content + - name: Restore verified release source + id: source + env: + HANDOFF_DIGEST: ${{ needs.release-core.outputs.handoff_digest }} + HANDOFF_DIR: ${{ runner.temp }}/node-delegated-release-handoff + shell: bash run: | + set -euo pipefail + metadata="$HANDOFF_DIR/release-handoff.json" + source_bundle="$HANDOFF_DIR/release-source.bundle" + if [[ ! -f "$metadata" || ! -f "$source_bundle" ]] \ + || [[ "$(sha256sum "$metadata" | awk '{print $1}')" != "$HANDOFF_DIGEST" ]] \ + || [[ "$(sha256sum "$source_bundle" | awk '{print $1}')" != "$(jq -r '.source_bundle.sha256' "$metadata")" ]]; then + echo "::error title=Tampered release handoff::Next-development preparation requires the original verified release handoff." + exit 1 + fi + jq -e \ + --arg repository "$GITHUB_REPOSITORY" \ + --arg run_id "$GITHUB_RUN_ID" \ + --arg run_attempt "$GITHUB_RUN_ATTEMPT" \ + '.schema_version == 1 and .repository == $repository and .run_id == $run_id and .run_attempt == $run_attempt and (.release_commit | test("^[0-9a-f]{40}$"))' \ + "$metadata" > /dev/null + rm -rf "$HANDOFF_DIR/source" + git clone --quiet --no-checkout "$source_bundle" "$HANDOFF_DIR/source" + release_commit="$(jq -r '.release_commit' "$metadata")" + git -C "$HANDOFF_DIR/source" checkout --quiet --detach "$release_commit" + echo "release_commit=$release_commit" >> "$GITHUB_OUTPUT" + echo "source_root=$HANDOFF_DIR/source" >> "$GITHUB_OUTPUT" + + - name: Read governed tool versions + id: tool-versions + working-directory: ${{ steps.source.outputs.source_root }}/${{ env.WORKING_DIRECTORY }} + shell: bash + run: | + set -euo pipefail + if grep -Evq '^[a-zA-Z0-9_-]+ [a-zA-Z0-9._+-]+$' .tool-versions; then + echo "::error title=Invalid tool-versions::${WORKING_DIRECTORY}/.tool-versions is malformed." + exit 1 + fi { - echo "TOOL_VERSIONS<> "$GITHUB_ENV" + } >> "$GITHUB_OUTPUT" - - name: Setup asdf environment - uses: asdf-vm/actions/install@1902764435ca0dd2f3388eea723a4f92a4eb8302 # v4.0.0 - if: steps.asdf-cache.outputs.cache-hit != 'true' + - name: Set up asdf-managed Node + uses: asdf-vm/actions/install@b7bcd026f18772e44fe1026d729e1611cc435d47 # v4.0.1 with: - tool_versions: ${{ env.TOOL_VERSIONS }} + tool_versions: ${{ steps.tool-versions.outputs.tool_versions }} asdf_version: ${{ env.ASDF_BRANCH_VERSION }} - - name: Restore asdf shims to PATH - if: steps.asdf-cache.outputs.cache-hit == 'true' - run: | - echo "$HOME/.asdf/bin" >> "$GITHUB_PATH" - echo "$HOME/.asdf/shims" >> "$GITHUB_PATH" - cp .tool-versions "$HOME/.tool-versions" + - name: Configure release git signing + uses: InditexTech/gh-actions/configure-release-git@3dac9bbff47d1e983604671191a08e4d345d9064 + with: + token: ${{ github.token }} + gpg-secret-key: ${{ secrets.CI_GPG_SECRET_KEY }} + gpg-passphrase: ${{ secrets.CI_GPG_SECRET_KEY_PASSWORD }} - - name: NPM / Set npmrc registry + - name: Prepare next development source + env: + RELEASES: ${{ needs.release-core.outputs.releases }} + SOURCE_ROOT: ${{ steps.source.outputs.source_root }} + working-directory: ${{ steps.source.outputs.source_root }}/${{ env.WORKING_DIRECTORY }} + shell: bash run: | - echo "registry=${NPM_REGISTRY}" >> "$HOME/.npmrc" - echo "@inditextech:registry=${NPM_REGISTRY}" >> "$HOME/.npmrc" - - - name: NPM / Create Cache Folders - run: mkdir -p "$HOME"/.npm - - - name: Setup patch release type version - if: contains(env.RELEASE_LABELS, 'release-type/hotfix') || contains(env.RELEASE_LABELS, 'release-type/multi-hotfix') || contains(env.RELEASE_LABELS, 'release-type/patch') - run: echo "RELEASE_VERSION=patch" >> "$GITHUB_ENV" - - - name: Setup minor release type version - if: contains(env.RELEASE_LABELS, 'release-type/minor') - run: echo "RELEASE_VERSION=minor" >> "$GITHUB_ENV" - - - name: Setup minor release type version when no label set and TBD - if: ${{ !contains(env.RELEASE_LABELS, 'release-type') && vars.DEVELOPMENT_FLOW == 'trunk-based-development' }} - run: echo "RELEASE_VERSION=minor" >> "$GITHUB_ENV" - - - name: Setup major release type version - if: contains(env.RELEASE_LABELS, 'release-type/major') - run: echo "RELEASE_VERSION=major" >> "$GITHUB_ENV" + set -euo pipefail + release_bump="$(jq -er '.[0].release_bump | select(type == "string" and length > 0)' <<< "$RELEASES")" + case "${PACKAGE_MANAGER:-npm}" in + npm) RELEASE_BUMP="$release_bump" npm run version:development ;; + pnpm) corepack enable; RELEASE_BUMP="$release_bump" pnpm run version:development ;; + *) echo "::error title=Invalid package manager::PACKAGE_MANAGER must be npm or pnpm."; exit 1 ;; + esac + git -C "$SOURCE_ROOT" add --update -- "$WORKING_DIRECTORY" + if git -C "$SOURCE_ROOT" diff --cached --quiet; then + echo "::error title=Missing next-development update::version:development did not produce a source change." + exit 1 + fi + git -C "$SOURCE_ROOT" commit -m "[node-release] prepare for next development iteration" - - name: Prepare committer information + - name: Create next-development handoff + id: next-development env: - GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} - GPG_PRIVATE_KEY: ${{ secrets.CI_GPG_SECRET_KEY }} - GPG_PASSPHRASE: ${{ secrets.CI_GPG_SECRET_KEY_PASSWORD }} - run: | - git config --global credential.helper store - cat <> ~/.git-credentials - https://ci-user:$GITHUB_TOKEN@github.com - EOT - - # GPG: non-interactive signing setup - mkdir -p ~/.gnupg && chmod 700 ~/.gnupg - printf 'allow-loopback-pinentry\nallow-preset-passphrase\n' > ~/.gnupg/gpg-agent.conf - printf 'use-agent\npinentry-mode loopback\n' > ~/.gnupg/gpg.conf - gpgconf --kill gpg-agent || true - echo "$GPG_PRIVATE_KEY" | gpg --batch --import - KEY_DATA=$(gpg --list-secret-keys --with-colons) - echo "$KEY_DATA" | awk -F: '/^grp:/ {print $10}' | while read -r GRIP; do - /usr/lib/gnupg/gpg-preset-passphrase --preset "$GRIP" <<< "$GPG_PASSPHRASE" - done - - # Git: identity and signing - FPR=$(echo "$KEY_DATA" | awk -F: '/^fpr:/ {print $10; exit}') - git config user.name "srvcosoitxtech" - git config user.email "oso@inditex.com" - git config user.signingkey "$FPR" - git config commit.gpgsign true - git config tag.gpgsign true - - - name: NPM / Install dependencies - run: npm ci - - - name: NPM / Verify + HANDOFF_DIGEST: ${{ needs.release-core.outputs.handoff_digest }} + HANDOFF_DIR: ${{ runner.temp }}/node-delegated-release-handoff + RELEASE_COMMIT: ${{ steps.source.outputs.release_commit }} + SOURCE_ROOT: ${{ steps.source.outputs.source_root }} + shell: bash run: | - if node -e "process.exit(require('./package.json').scripts?.verify ? 0 : 1)"; then - npm run verify - else - npm run lint --if-present - npm run format:check --if-present - npm run test --if-present - npm run build --if-present + set -euo pipefail + next_development_commit="$(git -C "$SOURCE_ROOT" rev-parse HEAD)" + if ! git -C "$SOURCE_ROOT" merge-base --is-ancestor "$RELEASE_COMMIT" "$next_development_commit"; then + echo "::error title=Invalid next-development provenance::The next-development commit is not descended from the published release." + exit 1 fi + git -C "$SOURCE_ROOT" bundle create "$HANDOFF_DIR/next-development.bundle" "$next_development_commit" + bundle_sha256="$(sha256sum "$HANDOFF_DIR/next-development.bundle" | awk '{print $1}')" + jq -n \ + --arg bundle_sha256 "$bundle_sha256" \ + --arg handoff_digest "$HANDOFF_DIGEST" \ + --arg next_development_commit "$next_development_commit" \ + --arg release_commit "$RELEASE_COMMIT" \ + --arg repository "$GITHUB_REPOSITORY" \ + --arg run_attempt "$GITHUB_RUN_ATTEMPT" \ + --arg run_id "$GITHUB_RUN_ID" \ + '{ + schema_version: 1, + repository: $repository, + run_id: $run_id, + run_attempt: $run_attempt, + release_handoff_digest: $handoff_digest, + release_commit: $release_commit, + next_development_commit: $next_development_commit, + source_bundle: {file: "next-development.bundle", sha256: $bundle_sha256} + }' > "$HANDOFF_DIR/next-development-handoff.json" + echo "handoff_digest=$(sha256sum "$HANDOFF_DIR/next-development-handoff.json" | awk '{print $1}')" >> "$GITHUB_OUTPUT" + + - name: Upload next-development handoff + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: node-delegated-next-development-handoff + path: | + ${{ runner.temp }}/node-delegated-release-handoff/next-development.bundle + ${{ runner.temp }}/node-delegated-release-handoff/next-development-handoff.json + if-no-files-found: error + retention-days: 14 + + finalize-delegated-release: + name: Finalize delegated release (next dev + sync PR) + needs: [release-core, publish-npm-delegated, prepare-delegated-next-development] + if: >- + vars.RELEASE_LIFECYCLE == 'delegated' && + needs.release-core.result == 'success' && + needs.publish-npm-delegated.result == 'success' && + needs.prepare-delegated-next-development.result == 'success' + runs-on: ubuntu-24.04 + timeout-minutes: 30 + permissions: + contents: read + steps: + - name: Create GitHub App token + id: app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + client-id: ${{ vars.APP_CLIENT_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + permission-contents: write + permission-pull-requests: write - - name: Update CHANGELOG.md - id: update-changelog - uses: release-flow/keep-a-changelog-action@74931dec7ecdbfc8e38ac9ae7e8dd84c08db2f32 # v3.0.0 + - name: Check out release branch + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - command: bump - version: ${{ env.RELEASE_VERSION }} - changelog: CHANGELOG.md - fail-on-empty-release-notes: false - keep-unreleased-section: true - tag-prefix: "" + fetch-depth: 0 + persist-credentials: false + ref: ${{ github.event.pull_request.base.ref || github.ref_name }} + token: ${{ steps.app-token.outputs.token }} - - name: Format generated CHANGELOG.md - run: npx prettier --write CHANGELOG.md + - name: Download immutable next-development handoff + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: node-delegated-next-development-handoff + path: ${{ runner.temp }}/node-delegated-next-development-handoff - - name: NPM / Prepare release + - name: Verify and promote next development source env: - RELEASE_VERSION: ${{ steps.update-changelog.outputs.version }} + APP_SLUG: ${{ steps.app-token.outputs.app-slug }} + BASELINE_BRANCH: ${{ github.event.pull_request.base.ref || github.ref_name }} + DEVELOPMENT_FLOW: ${{ vars.DEVELOPMENT_FLOW }} + EXPECTED_HANDOFF_DIGEST: ${{ needs.prepare-delegated-next-development.outputs.handoff_digest }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} + HANDOFF_DIR: ${{ runner.temp }}/node-delegated-next-development-handoff + RELEASE_HANDOFF_DIGEST: ${{ needs.release-core.outputs.handoff_digest }} + RELEASES: ${{ needs.release-core.outputs.releases }} + RELEASE_COMMIT: ${{ needs.release-core.outputs.release_commit }} + shell: bash run: | - npm version "$RELEASE_VERSION" --no-git-tag-version - npm run release:prepare --if-present - npm run build --if-present + set -euo pipefail + metadata="$HANDOFF_DIR/next-development-handoff.json" + source_bundle="$HANDOFF_DIR/next-development.bundle" + if [[ ! -f "$metadata" || ! -f "$source_bundle" ]] \ + || [[ "$(sha256sum "$metadata" | awk '{print $1}')" != "$EXPECTED_HANDOFF_DIGEST" ]]; then + echo "::error title=Tampered next-development handoff::The run-bound next-development metadata is missing or changed." + exit 1 + fi + jq -e \ + --arg repository "$GITHUB_REPOSITORY" \ + --arg run_id "$GITHUB_RUN_ID" \ + --arg run_attempt "$GITHUB_RUN_ATTEMPT" \ + --arg release_commit "$RELEASE_COMMIT" \ + --arg release_handoff_digest "$RELEASE_HANDOFF_DIGEST" \ + ' + .schema_version == 1 and + .repository == $repository and + .run_id == $run_id and + .run_attempt == $run_attempt and + .release_commit == $release_commit and + .release_handoff_digest == $release_handoff_digest and + (.next_development_commit | test("^[0-9a-f]{40}$")) and + (.source_bundle.file == "next-development.bundle") and + (.source_bundle.sha256 | test("^[0-9a-f]{64}$")) + ' "$metadata" > /dev/null + if [[ "$(sha256sum "$source_bundle" | awk '{print $1}')" != "$(jq -r '.source_bundle.sha256' "$metadata")" ]]; then + echo "::error title=Tampered next-development handoff::The next-development source bundle digest does not match metadata." + exit 1 + fi - - name: NPM / Ensure minimum npm version for trusted publishing - run: | - CURRENT_NPM=$(npm --version) - REQUIRED_NPM="11.5.1" - if printf '%s\n%s\n' "$REQUIRED_NPM" "$CURRENT_NPM" | sort -V -C; then - echo "npm $CURRENT_NPM already meets the minimum requirement ($REQUIRED_NPM). Skipping upgrade." - else - echo "npm $CURRENT_NPM is below the minimum requirement ($REQUIRED_NPM). Upgrading..." - npm install -g npm@^11.5.1 --registry="${NPM_REGISTRY}" - asdf reshim nodejs + rm -rf "$HANDOFF_DIR/source" + git clone --quiet --no-checkout "$source_bundle" "$HANDOFF_DIR/source" + source="$HANDOFF_DIR/source" + next_development_commit="$(jq -r '.next_development_commit' "$metadata")" + git -C "$source" rev-parse --verify --quiet "${next_development_commit}^{commit}" > /dev/null + if ! git -C "$source" merge-base --is-ancestor "$RELEASE_COMMIT" "$next_development_commit"; then + echo "::error title=Invalid next-development provenance::The next-development source is not descended from the published release." + exit 1 fi - node --version - npm --version - - name: NPM / Verify package exists in registry - run: | - PKG_NAME=$(node -p "require('./package.json').name") - ENCODED_PKG_NAME=$(node -p "encodeURIComponent(process.argv[1])" "$PKG_NAME") - RESPONSE_FILE=$(mktemp) - if ! HTTP_STATUS=$(curl -sS -o "$RESPONSE_FILE" -w "%{http_code}" \ - --connect-timeout 10 \ - --max-time 30 \ - --retry 3 \ - --retry-delay 2 \ - --retry-all-errors \ - "${NPM_REGISTRY}${ENCODED_PKG_NAME}"); then - echo "::error::Failed to verify $PKG_NAME due to npm registry connectivity error" - rm -f "$RESPONSE_FILE" + gh auth setup-git + git -C "$source" remote set-url origin "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git" + expected_ref="refs/heads/$BASELINE_BRANCH" + if [[ "$(git ls-remote origin "$expected_ref" | cut -f1)" != "$RELEASE_COMMIT" ]]; then + echo "::error title=Release baseline moved::The protected release branch no longer points to the published release." exit 1 fi - case "$HTTP_STATUS" in - 200) - PARSE_STATUS=0 - node -e "try { const d=JSON.parse(require('fs').readFileSync(process.argv[1],'utf8')); process.exit(Object.keys(d.versions||{}).length > 0 ? 0 : 1); } catch(e) { console.error('JSON parse error:', e.message); process.exit(2); }" "$RESPONSE_FILE" || PARSE_STATUS=$? - case "$PARSE_STATUS" in - 0) - echo "✓ $PKG_NAME exists in registry with active versions" - echo "HAS_NEW_PACKAGE=false" >> "$GITHUB_ENV" - ;; - 1) - echo "::warning::$PKG_NAME has no active versions (unpublished); NPM_TOKEN will be required" - echo "HAS_NEW_PACKAGE=true" >> "$GITHUB_ENV" - ;; - *) - echo "::error::Failed to verify $PKG_NAME — npm registry returned invalid JSON (HTTP 200)" - rm -f "$RESPONSE_FILE" - exit 1 - ;; - esac - ;; - 404) - echo "::warning::$PKG_NAME is not present in the npm registry; NPM_TOKEN will be required" - echo "HAS_NEW_PACKAGE=true" >> "$GITHUB_ENV" - ;; - 429|5??) - echo "::error::Failed to verify $PKG_NAME due to transient npm registry error (HTTP $HTTP_STATUS)" - exit 1 - ;; - *) - echo "::error::Failed to verify $PKG_NAME due to unexpected npm registry response (HTTP $HTTP_STATUS)" - exit 1 - ;; - esac - rm -f "$RESPONSE_FILE" + git -C "$source" push origin "$next_development_commit:$expected_ref" - - name: Commit release metadata - env: - GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} - RELEASE_VERSION: ${{ steps.update-changelog.outputs.version }} - run: | - git add CHANGELOG.md package.json package-lock.json - git commit -S -m "chore(release): ${RELEASE_VERSION}" || echo "No release metadata changes to commit" - git tag -s "${RELEASE_VERSION}" -m "Release ${RELEASE_VERSION}" - git push origin "HEAD:${BASELINE_BRANCH}" --follow-tags + if [[ "$DEVELOPMENT_FLOW" != "git-flow" ]]; then + echo "::notice title=Sync skipped::Sync-to-develop applies only to git-flow." + exit 0 + fi - - name: NPM / Publish Release (OIDC + provenance) - if: env.HAS_NEW_PACKAGE == 'false' - env: - NPM_CONFIG_PROVENANCE: "true" - run: npm publish --access public + version="$(jq -r '.[0].version' <<< "$RELEASES")" + develop_branch="${BASELINE_BRANCH/main/develop}" + if [[ -z "$(git ls-remote --heads origin "$develop_branch")" ]]; then + echo "::notice title=Sync skipped::$develop_branch does not exist." + exit 0 + fi + sync_branch="automated/sync-release-${version}-to-${develop_branch}" + git fetch --no-tags origin "$BASELINE_BRANCH" + git switch --force-create "$sync_branch" "origin/$BASELINE_BRANCH" + git push --force-with-lease --set-upstream origin "$sync_branch" + + if [[ -z "$(gh pr list --repo "$GITHUB_REPOSITORY" --head "$sync_branch" --base "$develop_branch" --state open --json number --jq '.[0].number // empty')" ]]; then + gh pr create --repo "$GITHUB_REPOSITORY" \ + --base "$develop_branch" --head "$sync_branch" \ + --title "Sync release $version to $develop_branch" \ + --body "**Automated pull request** syncing the release cut on \`$BASELINE_BRANCH\` back into \`$develop_branch\`." + fi + + github-release: + name: Create GitHub Release ${{ matrix.release.tag }} + needs: [release-core, publish-npm, publish-npm-delegated] + if: >- + !cancelled() && + needs.release-core.result == 'success' && + (needs.publish-npm.result == 'success' || needs.publish-npm.result == 'skipped') && + (needs.publish-npm-delegated.result == 'success' || needs.publish-npm-delegated.result == 'skipped') + runs-on: ubuntu-24.04 + timeout-minutes: 30 + permissions: + contents: read + strategy: + fail-fast: false + matrix: + release: ${{ fromJSON(needs.release-core.outputs.releases) }} + steps: + - name: Create GitHub App token + id: app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + client-id: ${{ vars.APP_CLIENT_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + permission-contents: write + + - name: Download release distributions + if: vars.RELEASE_LIFECYCLE != 'delegated' + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: npm-distributions-${{ matrix.release.tag }} + path: dist - - name: NPM / Publish Release (NPM_TOKEN for new packages) - if: env.HAS_NEW_PACKAGE == 'true' + - name: Create or update GitHub Release env: - NPM_AUTH: ${{ secrets.NPM_TOKEN }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} + RELEASE_TAG: ${{ matrix.release.tag }} + RELEASE_VERSION: ${{ matrix.release.version }} + RELEASE_LIFECYCLE: ${{ vars.RELEASE_LIFECYCLE }} + shell: bash run: | - if [ -z "${NPM_AUTH:-}" ]; then - echo "::error::NPM_TOKEN secret is required for publishing new packages but is not set" + set -euo pipefail + shopt -s nullglob + assets=(dist/*.tgz) + if [[ "$RELEASE_LIFECYCLE" != "delegated" && ${#assets[@]} -eq 0 ]]; then + echo "::error title=Missing distributions::The release artifact is empty." exit 1 fi - TEMP_NPMRC="$(mktemp)" - trap 'rm -f "$TEMP_NPMRC"' EXIT - cp "$HOME/.npmrc" "$TEMP_NPMRC" - chmod 600 "$TEMP_NPMRC" - echo "//registry.npmjs.org/:_authToken=${NPM_AUTH}" >> "$TEMP_NPMRC" - export NPM_CONFIG_USERCONFIG="$TEMP_NPMRC" - npm publish --access public - - - name: GitHub Release / Create - uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 - id: github-release - continue-on-error: true - with: - name: ${{ steps.update-changelog.outputs.version }} - tag: ${{ steps.update-changelog.outputs.version }} - token: ${{ steps.app-token.outputs.token }} - body: | - Check out the [changelog](CHANGELOG.md) for version ${{ steps.update-changelog.outputs.version }} + + if gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then + if [[ ${#assets[@]} -gt 0 ]]; then + gh release upload "$RELEASE_TAG" "${assets[@]}" \ + --repo "$GITHUB_REPOSITORY" \ + --clobber + fi + else + gh release create "$RELEASE_TAG" "${assets[@]}" \ + --repo "$GITHUB_REPOSITORY" \ + --title "$RELEASE_VERSION" \ + --generate-notes + fi diff --git a/.github/workflows/code-npm_node-release-core.yml b/.github/workflows/code-npm_node-release-core.yml new file mode 100644 index 0000000..6dd1af6 --- /dev/null +++ b/.github/workflows/code-npm_node-release-core.yml @@ -0,0 +1,1515 @@ +# SPDX-FileCopyrightText: 2026 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.) +# SPDX-License-Identifier: Apache-2.0 + +name: code-npm-node-release-core + +on: + workflow_call: + inputs: + release_type: + description: Optional release type supplied by the fixed release workflow. + required: false + type: string + default: '' + outputs: + release_commit: + description: Immutable commit represented by the release handoff. + value: ${{ jobs.prepare-release.outputs.release_commit }} + releases: + description: JSON array of release tags, versions, package lists, and next-dev versions. + value: ${{ jobs.prepare-release.outputs.releases }} + handoff_digest: + description: SHA-256 of the run-bound delegated release metadata, or empty outside delegated lifecycle. + value: ${{ jobs.prepare-release.outputs.handoff_digest }} + secrets: + APP_PRIVATE_KEY: + required: true + CI_GPG_SECRET_KEY: + description: Armored GPG secret key signing release commits and tags. + required: false + CI_GPG_SECRET_KEY_PASSWORD: + description: Passphrase protecting CI_GPG_SECRET_KEY. + required: false + +permissions: + contents: read + +env: + CI_GOVERNANCE_RELEASE_STAGING_REF: refs/ci-governance/release/${{ github.run_id }}-${{ github.run_attempt }} + PROJECT_TYPE: ${{ vars.PROJECT_TYPE }} + WORKING_DIRECTORY: ${{ vars.WORKING_DIRECTORY }} + PACKAGE_MANAGER: ${{ vars.PACKAGE_MANAGER }} + ASDF_BRANCH_VERSION: '0.18.0' + +jobs: + verify-candidate: + name: Verify release candidate + if: >- + ( + github.event_name == 'workflow_dispatch' && + ( + (vars.DEVELOPMENT_FLOW == 'trunk-based-development' && (github.ref_name == 'main' || startsWith(github.ref_name, 'main-'))) || + (vars.DEVELOPMENT_FLOW == 'git-flow' && (github.ref_name == 'main' || startsWith(github.ref_name, 'main-'))) + ) + ) || + ( + github.event_name == 'pull_request' && + github.event.pull_request.merged && + github.event.pull_request.head.repo.full_name == github.repository && + github.event.pull_request.head.ref != 'automated/ci-governance-sync' && + !contains(join(github.event.pull_request.labels.*.name, ','), 'skip-release') && + ( + (vars.DEVELOPMENT_FLOW == 'trunk-based-development' && (github.event.pull_request.base.ref == 'main' || startsWith(github.event.pull_request.base.ref, 'main-'))) || + (vars.DEVELOPMENT_FLOW == 'git-flow' && (github.event.pull_request.base.ref == 'main' || startsWith(github.event.pull_request.base.ref, 'main-'))) + ) && + ( + contains(join(github.event.pull_request.labels.*.name, ','), 'release-type') || + vars.DEVELOPMENT_FLOW == 'trunk-based-development' + ) + ) + runs-on: ubuntu-24.04 + timeout-minutes: 30 + permissions: + contents: read + steps: + - name: Resolve release context + id: release-context + env: + EVENT_SHA: ${{ github.sha }} + PR_MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }} + shell: bash + run: | + set -euo pipefail + if [[ "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]]; then + release_ref="$EVENT_SHA" + else + release_ref="$PR_MERGE_SHA" + fi + if [[ ! "$release_ref" =~ ^[0-9a-f]{40}$ ]]; then + echo "::error title=Invalid release context::An immutable release commit is required." + exit 1 + fi + echo "release_ref=$release_ref" >> "$GITHUB_OUTPUT" + + - name: Check out release candidate + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + ref: ${{ steps.release-context.outputs.release_ref }} + + - name: Read governed tool versions + id: tool-versions + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash + run: | + set -euo pipefail + if grep -Evq '^[a-zA-Z0-9_-]+ [a-zA-Z0-9._+-]+$' .tool-versions; then + echo "::error title=Invalid tool-versions::${WORKING_DIRECTORY}/.tool-versions is malformed." + exit 1 + fi + { + echo "tool_versions<> "$GITHUB_OUTPUT" + + - name: Set up asdf-managed Node + uses: asdf-vm/actions/install@b7bcd026f18772e44fe1026d729e1611cc435d47 # v4.0.1 + with: + tool_versions: ${{ steps.tool-versions.outputs.tool_versions }} + asdf_version: ${{ env.ASDF_BRANCH_VERSION }} + + - name: Verify release candidate + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash + run: | + set -euo pipefail + case "$PROJECT_TYPE" in + single|workspaces) : ;; + *) + echo "::error title=Invalid project type::PROJECT_TYPE must be single or workspaces." + exit 1 + ;; + esac + case "${PACKAGE_MANAGER:-npm}" in + npm) + npm ci --ignore-scripts + npm run verify + ;; + pnpm) + corepack enable + pnpm install --frozen-lockfile --ignore-scripts + pnpm run verify + ;; + *) + echo "::error title=Invalid package manager::PACKAGE_MANAGER must be npm or pnpm." + exit 1 + ;; + esac + + prepare-release: + name: Prepare Release + needs: verify-candidate + if: >- + needs.verify-candidate.result == 'success' && + ( + ( + github.event_name == 'workflow_dispatch' && + ( + (vars.DEVELOPMENT_FLOW == 'trunk-based-development' && (github.ref_name == 'main' || startsWith(github.ref_name, 'main-'))) || + (vars.DEVELOPMENT_FLOW == 'git-flow' && (github.ref_name == 'main' || startsWith(github.ref_name, 'main-'))) + ) + ) || + ( + github.event_name == 'pull_request' && + github.event.pull_request.merged && + github.event.pull_request.head.repo.full_name == github.repository && + github.event.pull_request.head.ref != 'automated/ci-governance-sync' && + !contains(join(github.event.pull_request.labels.*.name, ','), 'skip-release') && + ( + (vars.DEVELOPMENT_FLOW == 'trunk-based-development' && (github.event.pull_request.base.ref == 'main' || startsWith(github.event.pull_request.base.ref, 'main-'))) || + (vars.DEVELOPMENT_FLOW == 'git-flow' && (github.event.pull_request.base.ref == 'main' || startsWith(github.event.pull_request.base.ref, 'main-'))) + ) && + ( + contains(join(github.event.pull_request.labels.*.name, ','), 'release-type') || + vars.DEVELOPMENT_FLOW == 'trunk-based-development' + ) + ) + ) + runs-on: ubuntu-24.04 + timeout-minutes: 30 + outputs: + release_commit: ${{ steps.release-metadata.outputs.release_commit }} + releases: ${{ steps.release-metadata.outputs.releases }} + expected_ref: ${{ steps.release-plan.outputs.expected_ref || steps.delegated-plan.outputs.expected_ref }} + staging_ref: ${{ steps.release-metadata.outputs.staging_ref }} + handoff_digest: ${{ steps.delegated-handoff.outputs.handoff_digest }} + steps: + - name: Resolve release context + id: release-context + env: + EVENT_SHA: ${{ github.sha }} + PR_BASE_BRANCH: ${{ github.event.pull_request.base.ref }} + PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') }} + PR_MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }} + REQUESTED_RELEASE_TYPE: ${{ inputs.release_type }} + shell: bash + run: | + set -euo pipefail + + if [[ "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]]; then + baseline_branch="$GITHUB_REF_NAME" + release_labels="$REQUESTED_RELEASE_TYPE" + release_ref="$EVENT_SHA" + else + baseline_branch="$PR_BASE_BRANCH" + release_labels="$PR_LABELS" + release_ref="$PR_MERGE_SHA" + fi + + if [[ -z "$baseline_branch" || ! "$release_ref" =~ ^[0-9a-f]{40}$ ]]; then + echo "::error title=Invalid release context::A baseline branch and immutable release commit are required." + exit 1 + fi + + { + echo "baseline_branch=$baseline_branch" + echo "release_labels=$release_labels" + echo "release_ref=$release_ref" + } >> "$GITHUB_OUTPUT" + + - name: Enforce protected release branch + env: + GH_TOKEN: ${{ github.token }} + BASELINE_BRANCH: ${{ steps.release-context.outputs.baseline_branch }} + shell: bash + run: | + set -euo pipefail + protected="$(gh api "repos/${GITHUB_REPOSITORY}/branches/${BASELINE_BRANCH}" --jq '.protected')" + if [[ "$protected" != "true" ]]; then + echo "::error title=Unprotected release branch::Releases require a protected baseline branch; '${BASELINE_BRANCH}' is not protected." >&2 + exit 1 + fi + + - name: Create GitHub App token + id: app-token + if: vars.RELEASE_LIFECYCLE != 'delegated' + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + client-id: ${{ vars.APP_CLIENT_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + permission-contents: write + permission-pull-requests: write + + - name: Check out immutable release source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + ref: ${{ steps.release-context.outputs.release_ref }} + + - name: Read governed tool versions + id: tool-versions + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash + run: | + set -euo pipefail + if grep -Evq '^[a-zA-Z0-9_-]+ [a-zA-Z0-9._+-]+$' .tool-versions; then + echo "::error title=Invalid tool-versions::${WORKING_DIRECTORY}/.tool-versions is malformed." + exit 1 + fi + { + echo "tool_versions<> "$GITHUB_OUTPUT" + + - name: Set up asdf-managed Node + uses: asdf-vm/actions/install@b7bcd026f18772e44fe1026d729e1611cc435d47 # v4.0.1 + with: + tool_versions: ${{ steps.tool-versions.outputs.tool_versions }} + asdf_version: ${{ env.ASDF_BRANCH_VERSION }} + + - name: Install dependencies for delegated release + if: vars.RELEASE_LIFECYCLE == 'delegated' + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash + run: | + set -euo pipefail + case "${PACKAGE_MANAGER:-npm}" in + npm) npm ci --ignore-scripts ;; + pnpm) corepack enable; pnpm install --frozen-lockfile --ignore-scripts ;; + *) echo "::error title=Invalid package manager::PACKAGE_MANAGER must be npm or pnpm."; exit 1 ;; + esac + + - name: Plan release metadata + id: release-plan + if: vars.RELEASE_LIFECYCLE != 'delegated' + env: + APP_SLUG: ${{ steps.app-token.outputs.app-slug }} + BASELINE_BRANCH: ${{ steps.release-context.outputs.baseline_branch }} + DEVELOPMENT_FLOW: ${{ vars.DEVELOPMENT_FLOW }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} + RELEASE_LABELS: ${{ steps.release-context.outputs.release_labels }} + RELEASE_REF: ${{ steps.release-context.outputs.release_ref }} + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash + run: | + set -euo pipefail + + gh auth setup-git + + descriptor="$GITHUB_WORKSPACE/.github/inditextech-ci-node.json" + records_file="$RUNNER_TEMP/node-release-records.jsonl" + members_file="$RUNNER_TEMP/node-release-members.txt" + source_commit="$(git rev-parse "$RELEASE_REF^{commit}")" + expected_ref="refs/heads/$BASELINE_BRANCH" + remote_line="$(git ls-remote --exit-code origin "$expected_ref")" + read -r remote_commit remote_ref extra <<< "$remote_line" + + if [[ "$remote_ref" != "$expected_ref" || -n "${extra:-}" ]]; then + echo "::error title=Release branch verification failed::Could not resolve exactly one destination branch." + exit 1 + fi + + resume="false" + release_commit="" + if [[ "$remote_commit" != "$source_commit" ]]; then + git fetch --no-tags origin "$remote_commit" + release_commit="$(git rev-list --ancestry-path --reverse "${source_commit}..${remote_commit}" | head -1)" + if [[ -z "$release_commit" ]] \ + || [[ "$(git rev-parse "${release_commit}^")" != "$source_commit" ]] \ + || [[ "$(git show -s --format=%s "$release_commit")" != "[node-release] Prepare release" ]]; then + echo "::error title=Release branch advanced::The destination branch moved after the release source commit." + exit 1 + fi + git checkout --detach "$release_commit" + resume="true" + fi + + case ",$RELEASE_LABELS," in + *",release-type/major,"*) release_bump="major" ;; + *",release-type/minor,"*) release_bump="minor" ;; + *",release-type/hotfix,"*|*",release-type/multi-hotfix,"*|*",release-type/patch,"*) + release_bump="patch" + ;; + *) + if [[ "$DEVELOPMENT_FLOW" == "trunk-based-development" ]]; then + release_bump="minor" + else + echo "::error title=Missing release type::git-flow releases require a release-type label." + exit 1 + fi + ;; + esac + + member_dir() { + printf '%s' "${1##*/}" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//' + } + member_directory() { + local mapped + if mapped="$(jq -er --arg p "$1" '.[$p]' <<< "$PACKAGE_PATHS" 2>/dev/null)"; then + if [[ ! "$mapped" =~ ^[a-z0-9][a-z0-9._-]*$ ]]; then + echo "::error title=Invalid release.package_paths::directory for '$1' must be a single lowercase [a-z0-9][a-z0-9._-]* segment under packages/." >&2 + exit 1 + fi + printf '%s' "$mapped" + return + fi + echo "::notice title=Derived release member::directory for '$1' derived from the package name." >&2 + member_dir "$1" + } + load_package_paths() { + PACKAGE_PATHS="$(jq -c '.release.package_paths // {}' "$descriptor")" + } + strip_snapshot() { printf '%s' "${1%-SNAPSHOT}"; } + require_member() { + local dir="." + if [[ -n "$1" ]]; then + dir="packages/$1" + fi + if [[ ! -f "$dir/package.json" ]]; then + echo "::error title=Release member missing::package.json not found at '$dir'. Check release.package_paths in $descriptor." + exit 1 + fi + } + read_version() { + if [[ -n "$1" ]]; then + jq -r '.version' "packages/$1/package.json" + else + jq -r '.version' package.json + fi + } + bump_semver() { + local major minor patch + IFS=. read -r major minor patch <<< "$1" + case "$2" in + major) major=$((major + 1)); minor=0; patch=0 ;; + minor) minor=$((minor + 1)); patch=0 ;; + patch) patch=$((patch + 1)) ;; + esac + printf '%s.%s.%s' "$major" "$minor" "$patch" + } + set_version() { + if [[ -n "$2" ]]; then + npm pkg set "version=$1" --workspace "packages/$2" + else + npm pkg set "version=$1" + fi + } + + if [[ "$PROJECT_TYPE" == "single" ]]; then + release_strategy=single + printf '%s\n' "$(jq -r '.name' package.json)" > "$members_file" + elif [[ "$PROJECT_TYPE" == "workspaces" ]]; then + release_strategy="$(jq -er '.release.strategy' "$descriptor")" + load_package_paths + : > "$members_file" + while IFS= read -r package; do + printf '%s\n' "$(member_directory "$package")" >> "$members_file" + done < <(jq -er '.release.packages[]' "$descriptor") + else + echo "::error title=Invalid project type::PROJECT_TYPE must be single or workspaces." + exit 1 + fi + + write_records() { + : > "$records_file" + if [[ "$release_strategy" == "single" ]]; then + package_name="$(<"$members_file")" + require_member "" + version="$(strip_snapshot "$(read_version "")")" + next_dev="$(bump_semver "$version" "$release_bump")-SNAPSHOT" + jq -cn \ + --arg package "$package_name" \ + --arg version "$version" \ + --arg tag "$version" \ + --arg next_dev "$next_dev" \ + '{packages: [$package], version: $version, tag: $tag, next_dev: $next_dev}' >> "$records_file" + return + fi + + if [[ "$release_strategy" == "locked-step" ]]; then + members=() + while IFS= read -r member; do + members+=("$member") + done < "$members_file" + versions=() + for member in "${members[@]}"; do + require_member "$member" + versions+=("$(strip_snapshot "$(read_version "$member")")") + done + if [[ "$(printf '%s\n' "${versions[@]}" | sort -u | wc -l | tr -d ' ')" -ne 1 ]]; then + echo "::error title=Invalid locked-step reactor::All release members must share the same version." + exit 1 + fi + version="${versions[0]}" + next_dev="$(bump_semver "$version" "$release_bump")-SNAPSHOT" + members_json="$(printf '%s\n' "${members[@]}" | jq -R . | jq -sc .)" + jq -cn \ + --argjson packages "$members_json" \ + --arg version "$version" \ + --arg tag "$version" \ + --arg next_dev "$next_dev" \ + '{packages: $packages, version: $version, tag: $tag, next_dev: $next_dev}' >> "$records_file" + return + fi + + while IFS= read -r member; do + require_member "$member" + version="$(strip_snapshot "$(read_version "$member")")" + next_dev="$(bump_semver "$version" "$release_bump")-SNAPSHOT" + jq -cn \ + --arg member "$member" \ + --arg version "$version" \ + --arg tag "${member}-${version}" \ + --arg next_dev "$next_dev" \ + '{packages: [$member], version: $version, tag: $tag, next_dev: $next_dev}' >> "$records_file" + done < "$members_file" + } + + if [[ "$resume" == "false" ]]; then + # A workflow_dispatch never passes through the Release Preview + # CHANGELOG gate, so a fresh cut must prove there is something to + # release: refuse an Unreleased section without a content entry. + if [[ "${GITHUB_EVENT_NAME:-}" == "workflow_dispatch" ]]; then + if [[ ! -f CHANGELOG.md ]] || ! awk ' + $0 ~ /^##[[:space:]]+\[Unreleased\][[:space:]]*$/ { unreleased = 1; next } + unreleased && $0 ~ /^##[[:space:]]/ { exit } + unreleased && $0 ~ /^[[:space:]]*([-*+]|[0-9]+\.)[[:space:]]+/ { entry = 1 } + END { exit !(unreleased && entry) } + ' CHANGELOG.md; then + echo "::error title=No CHANGELOG changes::A dispatched release needs a substantive entry under ## [Unreleased]." + exit 1 + fi + fi + + write_records + releases="$(jq -cs . "$records_file")" + + if [[ "$release_strategy" == "independent" ]]; then + while IFS= read -r record; do + member="$(jq -r '.packages[0]' <<< "$record")" + version="$(jq -r '.version' <<< "$record")" + set_version "$version" "$member" + done < <(jq -c '.[]' <<< "$releases") + elif [[ "$release_strategy" == "locked-step" ]]; then + version="$(jq -r '.[0].version' <<< "$releases")" + while IFS= read -r member; do + set_version "$version" "$member" + done < "$members_file" + else + set_version "$(jq -r '.[0].version' <<< "$releases")" "" + fi + + if [[ "$PROJECT_TYPE" == "workspaces" ]]; then + declare -A member_version=() + while IFS= read -r record; do + rv="$(jq -r '.version' <<< "$record")" + while IFS= read -r mdir; do + mname="$(jq -r '.name' "packages/$mdir/package.json")" + member_version["$mname"]="$rv" + done < <(jq -r '.packages[]' <<< "$record") + done < <(jq -c '.[]' <<< "$releases") + + while IFS= read -r member; do + pj="packages/$member/package.json" + for field in dependencies devDependencies peerDependencies optionalDependencies; do + while IFS= read -r dep; do + new="${member_version[$dep]:-}" + [[ -n "$new" ]] || continue + cur="$(jq -r --arg f "$field" --arg d "$dep" '.[$f][$d]' "$pj")" + [[ "$cur" =~ [0-9] ]] || continue + pre="$(printf '%s' "$cur" | sed -E 's/^([^0-9]*).*/\1/')" + tmp="$(mktemp)" + jq --arg f "$field" --arg d "$dep" --arg v "${pre}${new}" \ + '.[$f][$d] = $v' "$pj" > "$tmp" && mv "$tmp" "$pj" + done < <(jq -r --arg f "$field" '(.[$f] // {}) | keys[]' "$pj") + done + done < "$members_file" + + npm install --package-lock-only --ignore-scripts + fi + + while IFS= read -r tag; do + if git show-ref --tags --verify --quiet "refs/tags/$tag"; then + echo "::error title=Tag conflict::$tag already exists." + exit 1 + fi + done < <(jq -r '.[].tag' <<< "$releases") + + git -C "$GITHUB_WORKSPACE" add --update -- "$WORKING_DIRECTORY" + if git diff --cached --quiet; then + echo "::error title=Missing release metadata::Versioning did not produce changes to commit." + exit 1 + fi + + primary_version="$(jq -r '.[0].version' <<< "$releases")" + echo "RELEASE_VERSION=$primary_version" >> "$GITHUB_ENV" + # CHANGELOG links must name real tags: independent workspaces tag + # -, so the prefix is the primary tag minus its + # version (empty for single and locked-step releases). + primary_tag="$(jq -r '.[0].tag' <<< "$releases")" + echo "CHANGELOG_TAG_PREFIX=${primary_tag%"$primary_version"}" >> "$GITHUB_ENV" + echo "RELEASE_BUMP=$release_bump" >> "$GITHUB_ENV" + else + write_records + releases="$(jq -cs . "$records_file")" + head_commit="$(git rev-parse HEAD)" + while IFS= read -r tag; do + if [[ "$(git rev-list -n 1 "$tag")" != "$head_commit" ]]; then + echo "::error title=Incomplete release resume::$tag does not point to the release commit." + exit 1 + fi + done < <(jq -r '.[].tag' <<< "$releases") + fi + + { + echo "resume=$resume" + echo "expected_ref=$expected_ref" + echo "releases_intermediate<> "$GITHUB_OUTPUT" + + - name: Bump CHANGELOG for release + if: vars.RELEASE_LIFECYCLE != 'delegated' && steps.release-plan.outputs.resume != 'true' + uses: release-flow/keep-a-changelog-action@74931dec7ecdbfc8e38ac9ae7e8dd84c08db2f32 # v3.0.0 + with: + command: bump + version: ${{ env.RELEASE_BUMP }} + changelog: ${{ env.WORKING_DIRECTORY }}/CHANGELOG.md + fail-on-empty-release-notes: false + keep-unreleased-section: true + tag-prefix: ${{ env.CHANGELOG_TAG_PREFIX }} + + - name: Reconcile CHANGELOG version with release + if: vars.RELEASE_LIFECYCLE != 'delegated' && steps.release-plan.outputs.resume != 'true' + env: + RELEASE_VERSION: ${{ env.RELEASE_VERSION }} + TAG_PREFIX: ${{ env.CHANGELOG_TAG_PREFIX }} + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash + run: | + set -euo pipefail + cl="CHANGELOG.md" + cl_ver="$(grep -m1 -E '^## \[[0-9]' "$cl" | sed -E 's/^## \[([^]]+)\].*/\1/')" + if [[ -z "$cl_ver" ]]; then + echo "::error title=CHANGELOG has no release entry::Expected a '## [X.Y.Z]' heading after the bump step." + exit 1 + fi + if [[ "$cl_ver" != "$RELEASE_VERSION" ]]; then + echo "::notice title=CHANGELOG version reconciled::keep-a-changelog wrote ${cl_ver}; re-anchoring to release ${RELEASE_VERSION}." + cl_ver_re="${cl_ver//./\\.}" + tag_prefix="${TAG_PREFIX:-}" + tp_re="${tag_prefix//./\\.}" + tmp="$(mktemp)" + sed -E \ + -e "s@^## \[${cl_ver_re}\]( - )@## [${RELEASE_VERSION}]\1@" \ + -e "s@^(\[Unreleased\]: .*/compare/${tp_re})${cl_ver_re}(\.\.\.HEAD)@\1${RELEASE_VERSION}\2@" \ + -e "s@^\[${cl_ver_re}\]: (.*/)(compare/[^ ]*\.\.\.|releases/tag/)${tp_re}${cl_ver_re}@[${RELEASE_VERSION}]: \1\2${tag_prefix}${RELEASE_VERSION}@" \ + "$cl" > "$tmp" + cat "$tmp" > "$cl" + rm -f "$tmp" + fi + + - name: Configure release git signing + uses: InditexTech/gh-actions/configure-release-git@3dac9bbff47d1e983604671191a08e4d345d9064 + with: + token: ${{ steps.app-token.outputs.token }} + gpg-secret-key: ${{ secrets.CI_GPG_SECRET_KEY }} + gpg-passphrase: ${{ secrets.CI_GPG_SECRET_KEY_PASSWORD }} + + - name: Commit and stage release + if: vars.RELEASE_LIFECYCLE != 'delegated' && steps.release-plan.outputs.resume != 'true' + id: release-commit + env: + APP_SLUG: ${{ steps.app-token.outputs.app-slug }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} + RELEASES: ${{ steps.release-plan.outputs.releases_intermediate }} + EXPECTED_REF: ${{ steps.release-plan.outputs.expected_ref }} + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash + run: | + set -euo pipefail + git -C "$GITHUB_WORKSPACE" add -- "$WORKING_DIRECTORY/CHANGELOG.md" + git -C "$GITHUB_WORKSPACE" add --update -- "$WORKING_DIRECTORY" + if git diff --cached --quiet; then + echo "::error title=Missing release metadata::Versioning did not produce changes to commit." + exit 1 + fi + git commit -m "[node-release] Prepare release" + + while IFS= read -r record; do + git check-ref-format --allow-onelevel "refs/tags/$(jq -r '.tag' <<< "$record")" + done < <(jq -c '.[]' <<< "$RELEASES") + + git push --atomic --force origin "HEAD:$CI_GOVERNANCE_RELEASE_STAGING_REF" + + - name: Cut delegated release + id: delegated-plan + if: vars.RELEASE_LIFECYCLE == 'delegated' + env: + BASELINE_BRANCH: ${{ steps.release-context.outputs.baseline_branch }} + DEVELOPMENT_FLOW: ${{ vars.DEVELOPMENT_FLOW }} + RELEASE_LABELS: ${{ steps.release-context.outputs.release_labels }} + RELEASE_SOURCE_COMMIT: ${{ steps.release-context.outputs.release_ref }} + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash + run: | + set -euo pipefail + + case ",$RELEASE_LABELS," in + *",release-type/major,"*) release_bump="major" ;; + *",release-type/minor,"*) release_bump="minor" ;; + *",release-type/hotfix,"*|*",release-type/multi-hotfix,"*|*",release-type/patch,"*) + release_bump="patch" + ;; + *) + if [[ "$DEVELOPMENT_FLOW" == "trunk-based-development" ]]; then + release_bump="minor" + else + echo "::error title=Missing release type::git-flow releases require a release-type label." + exit 1 + fi + ;; + esac + export RELEASE_BUMP="$release_bump" + + git tag | sort > "$RUNNER_TEMP/tags-before.txt" + + case "${PACKAGE_MANAGER:-npm}" in + npm) npm run release:prepare ;; + pnpm) corepack enable; pnpm run release:prepare ;; + *) echo "::error title=Invalid package manager::PACKAGE_MANAGER must be npm or pnpm."; exit 1 ;; + esac + + git tag | sort > "$RUNNER_TEMP/tags-after.txt" + new_tags=() + while IFS= read -r tag; do + new_tags+=("$tag") + done < <(comm -13 "$RUNNER_TEMP/tags-before.txt" "$RUNNER_TEMP/tags-after.txt") + if [[ ${#new_tags[@]} -eq 0 ]]; then + echo "::error title=No release cut::release:prepare produced no new tags." + exit 1 + fi + + records_file="$RUNNER_TEMP/node-release-records.jsonl" + : > "$records_file" + for tag in "${new_tags[@]}"; do + tag_target="$(git rev-parse "${tag}^{commit}")" + if [[ "$tag" =~ ([0-9]+\.[0-9]+\.[0-9]+([-+.][0-9A-Za-z.-]+)?)$ ]]; then + version="${BASH_REMATCH[1]}" + else + version="$tag" + fi + jq -cn \ + --arg tag "$tag" \ + --arg tag_target "$tag_target" \ + --arg version "$version" \ + --arg bump "$release_bump" \ + '{packages: [], version: $version, tag: $tag, tag_target: $tag_target, next_dev: "", release_bump: $bump}' \ + >> "$records_file" + done + releases="$(jq -cs . "$records_file")" + primary_version="$(jq -r '.[0].version' <<< "$releases")" + + changelog_pending="false" + if [[ -f CHANGELOG.md ]] \ + && awk '/^## \[?[Uu]nreleased\]?/{f=1;next} /^## /{f=0} f && NF {print; exit}' CHANGELOG.md | grep -q .; then + changelog_pending="true" + fi + + echo "RELEASE_BUMP=$release_bump" >> "$GITHUB_ENV" + echo "RELEASE_VERSION=$primary_version" >> "$GITHUB_ENV" + # CHANGELOG links must name real tags: independent workspaces tag + # -, so the prefix is the primary tag minus its + # version (empty for single and locked-step releases). + primary_tag="$(jq -r '.[0].tag' <<< "$releases")" + echo "CHANGELOG_TAG_PREFIX=${primary_tag%"$primary_version"}" >> "$GITHUB_ENV" + + { + echo "expected_ref=refs/heads/$BASELINE_BRANCH" + echo "source_commit=$RELEASE_SOURCE_COMMIT" + echo "primary_version=$primary_version" + echo "changelog_pending=$changelog_pending" + echo "releases_intermediate<> "$GITHUB_OUTPUT" + + - name: Bump CHANGELOG for delegated release + if: vars.RELEASE_LIFECYCLE == 'delegated' && steps.delegated-plan.outputs.changelog_pending == 'true' + uses: release-flow/keep-a-changelog-action@74931dec7ecdbfc8e38ac9ae7e8dd84c08db2f32 # v3.0.0 + with: + command: bump + version: ${{ env.RELEASE_BUMP }} + changelog: ${{ env.WORKING_DIRECTORY }}/CHANGELOG.md + fail-on-empty-release-notes: false + keep-unreleased-section: true + tag-prefix: ${{ env.CHANGELOG_TAG_PREFIX }} + + - name: Reconcile CHANGELOG version with delegated release + if: vars.RELEASE_LIFECYCLE == 'delegated' && steps.delegated-plan.outputs.changelog_pending == 'true' + env: + RELEASE_VERSION: ${{ env.RELEASE_VERSION }} + TAG_PREFIX: ${{ env.CHANGELOG_TAG_PREFIX }} + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash + run: | + set -euo pipefail + cl="CHANGELOG.md" + cl_ver="$(grep -m1 -E '^## \[[0-9]' "$cl" | sed -E 's/^## \[([^]]+)\].*/\1/')" + if [[ -z "$cl_ver" ]]; then + echo "::error title=CHANGELOG has no release entry::Expected a '## [X.Y.Z]' heading after the bump step." + exit 1 + fi + if [[ "$cl_ver" != "$RELEASE_VERSION" ]]; then + echo "::notice title=CHANGELOG version reconciled::keep-a-changelog wrote ${cl_ver}; re-anchoring to release ${RELEASE_VERSION}." + cl_ver_re="${cl_ver//./\\.}" + tag_prefix="${TAG_PREFIX:-}" + tp_re="${tag_prefix//./\\.}" + tmp="$(mktemp)" + sed -E \ + -e "s@^## \[${cl_ver_re}\]( - )@## [${RELEASE_VERSION}]\1@" \ + -e "s@^(\[Unreleased\]: .*/compare/${tp_re})${cl_ver_re}(\.\.\.HEAD)@\1${RELEASE_VERSION}\2@" \ + -e "s@^\[${cl_ver_re}\]: (.*/)(compare/[^ ]*\.\.\.|releases/tag/)${tp_re}${cl_ver_re}@[${RELEASE_VERSION}]: \1\2${tag_prefix}${RELEASE_VERSION}@" \ + "$cl" > "$tmp" + cat "$tmp" > "$cl" + rm -f "$tmp" + fi + + - name: Seal delegated release source + id: delegated-source + if: vars.RELEASE_LIFECYCLE == 'delegated' + env: + SOURCE_COMMIT: ${{ steps.delegated-plan.outputs.source_commit }} + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash + run: | + set -euo pipefail + if [[ -f CHANGELOG.md ]]; then + git -C "$GITHUB_WORKSPACE" add -- "$WORKING_DIRECTORY/CHANGELOG.md" + fi + if ! git diff --cached --quiet; then + git commit -m "[node-release] Update changelog" + fi + + release_commit="$(git -C "$GITHUB_WORKSPACE" rev-parse HEAD)" + if [[ "$release_commit" == "$SOURCE_COMMIT" ]]; then + echo "::error title=Missing release commit::release:prepare did not produce a release commit." + exit 1 + fi + echo "release_commit=$release_commit" >> "$GITHUB_OUTPUT" + + - name: Build and pack delegated distributions + id: delegated-pack + if: vars.RELEASE_LIFECYCLE == 'delegated' + env: + HANDOFF_DIR: ${{ runner.temp }}/node-delegated-release-handoff + RELEASES: ${{ steps.delegated-plan.outputs.releases_intermediate }} + AUTHORIZED_COMMIT: ${{ steps.delegated-source.outputs.release_commit }} + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash + run: | + set -euo pipefail + workspace_commit="$(git -C "$GITHUB_WORKSPACE" rev-parse HEAD)" + if [[ "$workspace_commit" != "$AUTHORIZED_COMMIT" ]]; then + echo "::error title=Release source race::The workspace commit ($workspace_commit) is not the sealed release source ($AUTHORIZED_COMMIT). Aborting before any packaging." + exit 1 + fi + DIST_DIR="$HANDOFF_DIR/tarballs" + RECORDS_FILE="$HANDOFF_DIR/tarballs.jsonl" + rm -rf "$HANDOFF_DIR" + mkdir -p "$DIST_DIR" + : > "$RECORDS_FILE" + + inspect_declared_files() { + local package_dir="$1" + local tarball="$2" + local manifest="$package_dir/package.json" + local declared matched path relative + jq -e ' + .files + | type == "array" and length > 0 and + all(.[]; type == "string" and length > 0 and test("^[^\\r\\n]+$")) + ' "$manifest" > /dev/null + while IFS= read -r declared; do + if [[ "$declared" == /* || "$declared" == "." || "$declared" == ".." || "$declared" == ../* || "$declared" == */../* || "$declared" == */.. || "$declared" == "!"* || "$declared" == *"{"* || "$declared" == *"}"* || "$declared" == *\\* || "$declared" == *"@("* || "$declared" == *"+("* || "$declared" == *"?("* || "$declared" == *"*("* || "$declared" == *"!("* ]]; then + echo "::error title=Unsupported package contents declaration::${manifest} declares an unsafe or unverifiable pattern '$declared'." + exit 1 + fi + matched=0 + if [[ "$declared" == *"*"* || "$declared" == *"?"* || "$declared" == *"["* || "$declared" == *"]"* ]]; then + while IFS= read -r -d '' path; do + relative="${path#"$package_dir"/}" + matched=$((matched + 1)) + if ! tar -tzf "$tarball" | grep -qxF "package/$relative"; then + echo "::error title=Incomplete tarball::$(basename "$tarball") omits declared file '$relative'." + exit 1 + fi + done < <(node - "$package_dir" "$declared" <<'NODE' + const fs = require("fs"); + const path = require("path"); + const root = process.argv[2]; + const pattern = process.argv[3]; + let expression = "^"; + for (let index = 0; index < pattern.length; index += 1) { + const character = pattern[index]; + if (character === "*") { + let stars = 1; + while (pattern[index + stars] === "*") stars += 1; + if (stars > 1 && pattern[index + stars] === "/") { + expression += "(?:[^/]+/)*"; + index += stars; + } else { + expression += stars > 1 ? ".*" : "[^/]*"; + index += stars - 1; + } + } else if (character === "?") { + expression += "[^/]"; + } else if (character === "[") { + const closing = pattern.indexOf("]", index + 1); + if (closing === -1 || closing === index + 1) process.exit(2); + const characterClass = pattern.slice(index + 1, closing); + if (characterClass.includes("/") || characterClass.includes("\\")) process.exit(2); + expression += `[${characterClass[0] === "!" ? "^" + characterClass.slice(1) : characterClass}]`; + index = closing; + } else if (character === "]") { + process.exit(2); + } else { + expression += character.replace(/[|\\{}()[\]^$+?.]/g, "\\$&"); + } + } + const matcher = new RegExp(`${expression}$`); + const files = []; + const walk = (directory) => { + for (const entry of fs.readdirSync(directory, { withFileTypes: true })) { + const candidate = path.join(directory, entry.name); + if (entry.isDirectory()) walk(candidate); + else if (entry.isFile()) files.push(path.relative(root, candidate).split(path.sep).join("/")); + } + }; + walk(root); + for (const file of files.filter((file) => matcher.test(file)).sort()) process.stdout.write(`${path.join(root, file)}\0`); + NODE + ) + elif [[ -d "$package_dir/$declared" ]]; then + while IFS= read -r -d '' path; do + relative="${path#"$package_dir"/}" + matched=$((matched + 1)) + if ! tar -tzf "$tarball" | grep -qxF "package/$relative"; then + echo "::error title=Incomplete tarball::$(basename "$tarball") omits declared file '$relative'." + exit 1 + fi + done < <(find "$package_dir/$declared" -type f -print0) + elif [[ -f "$package_dir/$declared" ]]; then + matched=1 + if ! tar -tzf "$tarball" | grep -qxF "package/$declared"; then + echo "::error title=Incomplete tarball::$(basename "$tarball") omits declared file '$declared'." + exit 1 + fi + fi + if [[ "$matched" -eq 0 ]]; then + echo "::error title=Missing declared package content::${manifest} declaration '$declared' matches no built files." + exit 1 + fi + done < <(jq -r '.files[]' "$manifest") + } + + release_tag_for() { + local package_name="$1" + local package_version="$2" + local member record release_tag release_version + local -a exact_matches=() + local -a version_matches=() + member="$(printf '%s' "${package_name##*/}" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//')" + while IFS= read -r record; do + release_tag="$(jq -r '.tag' <<< "$record")" + release_version="$(jq -r '.version' <<< "$record")" + [[ "$release_version" == "$package_version" ]] || continue + version_matches+=("$release_tag") + if [[ "$release_tag" == "$package_version" || "$release_tag" == "${member}-${package_version}" || "$release_tag" == "${package_name}-${package_version}" ]]; then + exact_matches+=("$release_tag") + fi + done < <(jq -c '.[]' <<< "$RELEASES") + if [[ ${#exact_matches[@]} -eq 1 ]]; then + printf '%s' "${exact_matches[0]}" + elif [[ ${#exact_matches[@]} -eq 0 && ${#version_matches[@]} -eq 1 ]]; then + printf '%s' "${version_matches[0]}" + else + echo "::error title=Ambiguous package release::Package '$package_name@$package_version' does not resolve to exactly one prepared release tag." >&2 + exit 1 + fi + } + + pack_package() { + local package_dir="$1" + local package_name="$2" + local before="$HANDOFF_DIR/tarballs-before.txt" + local after="$HANDOFF_DIR/tarballs-after.txt" + local added="$HANDOFF_DIR/tarballs-added.txt" + local tarball + find "$DIST_DIR" -maxdepth 1 -name '*.tgz' -print | LC_ALL=C sort > "$before" + case "${PACKAGE_MANAGER:-npm}" in + npm) + npm pack "./$package_dir" --pack-destination "$DIST_DIR" + ;; + pnpm) + corepack enable + ( cd "$package_dir" && pnpm pack --pack-destination "$DIST_DIR" ) + ;; + *) + echo "::error title=Invalid package manager::PACKAGE_MANAGER must be npm or pnpm." + exit 1 + ;; + esac + find "$DIST_DIR" -maxdepth 1 -name '*.tgz' -print | LC_ALL=C sort > "$after" + comm -13 "$before" "$after" > "$added" + if [[ "$(wc -l < "$added" | tr -d ' ')" -ne 1 ]]; then + echo "::error title=Invalid package artifact::Packing '$package_name' must produce exactly one new tarball." + exit 1 + fi + tarball="$(<"$added")" + inspect_declared_files "$package_dir" "$tarball" + tarball_package="$(tar -xzOf "$tarball" package/package.json | jq -er '.name | select(type == "string" and length > 0)')" + tarball_version="$(tar -xzOf "$tarball" package/package.json | jq -er '.version | select(type == "string" and length > 0)')" + if [[ "$tarball_package" != "$package_name" ]]; then + echo "::error title=Invalid package artifact::$(basename "$tarball") identifies '$tarball_package', not declared package '$package_name'." + exit 1 + fi + tarball_release_tag="$(release_tag_for "$tarball_package" "$tarball_version")" + jq -cn \ + --arg file "$(basename "$tarball")" \ + --arg package "$package_name" \ + --arg release_tag "$tarball_release_tag" \ + --arg sha256 "$(sha256sum "$tarball" | awk '{print $1}')" \ + --arg version "$tarball_version" \ + '{file: $file, package: $package, release_tag: $release_tag, sha256: $sha256, version: $version}' >> "$RECORDS_FILE" + } + + case "${PACKAGE_MANAGER:-npm}" in + npm) + npm run build + ;; + pnpm) + corepack enable + pnpm run build + ;; + *) + echo "::error title=Invalid package manager::PACKAGE_MANAGER must be npm or pnpm." + exit 1 + ;; + esac + + case "$PROJECT_TYPE" in + single) + package_name="$(jq -er '.name | select(type == "string" and length > 0)' package.json)" + pack_package "." "$package_name" + ;; + workspaces) + jq -e ' + .release.packages + | type == "array" and length > 0 and + all(.[]; type == "string" and length > 0) + ' "$GITHUB_WORKSPACE/.github/inditextech-ci-node.json" > /dev/null + while IFS= read -r package_name; do + member="$(printf '%s' "${package_name##*/}" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//')" + package_dir="packages/$member" + if [[ ! -f "$package_dir/package.json" ]] || [[ "$(jq -r '.name' "$package_dir/package.json")" != "$package_name" ]]; then + echo "::error title=Unknown workspace package::release.packages entry '$package_name' does not resolve to its declared workspace manifest." + exit 1 + fi + pack_package "$package_dir" "$package_name" + done < <(jq -r '.release.packages[]' "$GITHUB_WORKSPACE/.github/inditextech-ci-node.json") + ;; + *) + echo "::error title=Invalid project type::PROJECT_TYPE must be single or workspaces." + exit 1 + ;; + esac + + jq -cs . "$RECORDS_FILE" > "$HANDOFF_DIR/tarballs.json" + echo "handoff_dir=$HANDOFF_DIR" >> "$GITHUB_OUTPUT" + + - name: Create delegated release handoff + id: delegated-handoff + if: vars.RELEASE_LIFECYCLE == 'delegated' + env: + EXPECTED_REF: ${{ steps.delegated-plan.outputs.expected_ref }} + HANDOFF_DIR: ${{ steps.delegated-pack.outputs.handoff_dir }} + RELEASES: ${{ steps.delegated-plan.outputs.releases_intermediate }} + RELEASE_COMMIT: ${{ steps.delegated-source.outputs.release_commit }} + SOURCE_COMMIT: ${{ steps.delegated-plan.outputs.source_commit }} + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash + run: | + set -euo pipefail + if ! git merge-base --is-ancestor "$SOURCE_COMMIT" "$RELEASE_COMMIT"; then + echo "::error title=Invalid release provenance::The prepared release is not descended from the verified source commit." + exit 1 + fi + + tag_refs=() + while IFS= read -r record; do + tag="$(jq -r '.tag' <<< "$record")" + tag_target="$(jq -r '.tag_target' <<< "$record")" + git check-ref-format --allow-onelevel "refs/tags/$tag" + if [[ "$(git rev-parse "${tag}^{commit}")" != "$tag_target" ]]; then + echo "::error title=Invalid release tag::Prepared tag '$tag' no longer points at its recorded commit." + exit 1 + fi + if ! git merge-base --is-ancestor "$SOURCE_COMMIT" "$tag_target" \ + || ! git merge-base --is-ancestor "$tag_target" "$RELEASE_COMMIT"; then + echo "::error title=Invalid release tag provenance::Prepared tag '$tag' must satisfy source <= tag <= release." + exit 1 + fi + tag_refs+=("refs/tags/$tag") + done < <(jq -c '.[]' <<< "$RELEASES") + + while IFS= read -r tarball; do + release_tag="$(jq -r '.release_tag' <<< "$tarball")" + package_name="$(jq -r '.package' <<< "$tarball")" + package_version="$(jq -r '.version' <<< "$tarball")" + if [[ "$(jq -r --arg tag "$release_tag" --arg version "$package_version" '[.[] | select(.tag == $tag and .version == $version)] | length' <<< "$RELEASES")" -ne 1 ]]; then + echo "::error title=Invalid package release::Tarball '$package_name@$package_version' is not bound to exactly one prepared release tag." + exit 1 + fi + done < <(jq -c '.[]' "$HANDOFF_DIR/tarballs.json") + + git bundle create "$HANDOFF_DIR/release-source.bundle" "$RELEASE_COMMIT" "${tag_refs[@]}" + source_bundle_sha256="$(sha256sum "$HANDOFF_DIR/release-source.bundle" | awk '{print $1}')" + jq -n \ + --arg expected_ref "$EXPECTED_REF" \ + --arg release_commit "$RELEASE_COMMIT" \ + --arg repository "$GITHUB_REPOSITORY" \ + --arg run_attempt "$GITHUB_RUN_ATTEMPT" \ + --arg run_id "$GITHUB_RUN_ID" \ + --arg source_bundle_sha256 "$source_bundle_sha256" \ + --arg source_commit "$SOURCE_COMMIT" \ + --argjson releases "$RELEASES" \ + --slurpfile tarballs "$HANDOFF_DIR/tarballs.json" \ + '{ + schema_version: 1, + repository: $repository, + run_id: $run_id, + run_attempt: $run_attempt, + expected_ref: $expected_ref, + source_commit: $source_commit, + release_commit: $release_commit, + releases: $releases, + tags: [$releases[] | {name: .tag, target: .tag_target}], + source_bundle: {file: "release-source.bundle", sha256: $source_bundle_sha256}, + tarballs: $tarballs[0] + }' > "$HANDOFF_DIR/release-handoff.json" + handoff_digest="$(sha256sum "$HANDOFF_DIR/release-handoff.json" | awk '{print $1}')" + rm -f \ + "$HANDOFF_DIR/tarballs.json" \ + "$HANDOFF_DIR/tarballs.jsonl" \ + "$HANDOFF_DIR/tarballs-added.txt" \ + "$HANDOFF_DIR/tarballs-after.txt" \ + "$HANDOFF_DIR/tarballs-before.txt" + echo "handoff_digest=$handoff_digest" >> "$GITHUB_OUTPUT" + + - name: Upload delegated release handoff + if: vars.RELEASE_LIFECYCLE == 'delegated' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: node-delegated-release-handoff + path: ${{ steps.delegated-pack.outputs.handoff_dir }} + if-no-files-found: error + retention-days: 14 + + - name: Publish release metadata + id: release-metadata + env: + RELEASES: ${{ steps.release-plan.outputs.releases_intermediate || steps.delegated-plan.outputs.releases_intermediate }} + RELEASE_LIFECYCLE: ${{ vars.RELEASE_LIFECYCLE }} + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash + run: | + set -euo pipefail + staging_ref="$CI_GOVERNANCE_RELEASE_STAGING_REF" + if [[ "$RELEASE_LIFECYCLE" == "delegated" ]]; then + staging_ref="" + fi + { + if [[ "$RELEASE_LIFECYCLE" == "delegated" ]]; then + echo "release_commit=${{ steps.delegated-source.outputs.release_commit }}" + else + echo "release_commit=$(git -C "$GITHUB_WORKSPACE" rev-parse HEAD)" + fi + echo "staging_ref=$staging_ref" + echo "releases<> "$GITHUB_OUTPUT" + + build-distributions: + name: Build ${{ matrix.release.tag }} + needs: prepare-release + if: vars.RELEASE_LIFECYCLE != 'delegated' + runs-on: ubuntu-24.04 + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + release: ${{ fromJSON(needs.prepare-release.outputs.releases) }} + steps: + - name: Check out release commit + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + ref: ${{ needs.prepare-release.outputs.release_commit }} + + - name: Verify sealed release commit + shell: bash + run: | + set -euo pipefail + expected="${{ needs.prepare-release.outputs.release_commit }}" + workspace="$(git rev-parse HEAD)" + if [[ "$workspace" != "$expected" ]]; then + echo "::error title=Sealed commit mismatch::The publish job checked out $workspace but the sealed release commit is $expected. Aborting." + exit 1 + fi + digest="${{ needs.prepare-release.outputs.handoff_digest }}" + if [[ -n "$digest" && ! -f .ci-governance-handoff-digest ]]; then + echo "::error title=Missing handoff digest::The delegated release handoff artifact is absent for digest $digest." + exit 1 + fi + + - name: Read governed tool versions + id: tool-versions + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash + run: | + set -euo pipefail + if grep -Evq '^[a-zA-Z0-9_-]+ [a-zA-Z0-9._+-]+$' .tool-versions; then + echo "::error title=Invalid tool-versions::${WORKING_DIRECTORY}/.tool-versions is malformed." + exit 1 + fi + { + echo "tool_versions<> "$GITHUB_OUTPUT" + + - name: Set up asdf-managed Node + uses: asdf-vm/actions/install@b7bcd026f18772e44fe1026d729e1611cc435d47 # v4.0.1 + with: + tool_versions: ${{ steps.tool-versions.outputs.tool_versions }} + asdf_version: ${{ env.ASDF_BRANCH_VERSION }} + + - name: Build and pack declared distributions + env: + RELEASE: ${{ toJSON(matrix.release) }} + DIST_DIR: ${{ runner.temp }}/dist + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash + run: | + set -euo pipefail + rm -rf "$DIST_DIR" + mkdir -p "$DIST_DIR" + case "${PACKAGE_MANAGER:-npm}" in + npm) + npm ci --ignore-scripts + npm run build + case "$PROJECT_TYPE" in + single) + npm pack --pack-destination "$DIST_DIR" + ;; + workspaces) + while IFS= read -r member; do + npm pack --workspace "packages/$member" --pack-destination "$DIST_DIR" + done < <(jq -r '.packages[]' <<< "$RELEASE") + ;; + *) + echo "::error title=Invalid project type::PROJECT_TYPE must be single or workspaces." + exit 1 + ;; + esac + ;; + pnpm) + corepack enable + pnpm install --frozen-lockfile --ignore-scripts + pnpm run build + case "$PROJECT_TYPE" in + single) + pnpm pack --pack-destination "$DIST_DIR" + ;; + workspaces) + while IFS= read -r member; do + ( cd "packages/$member" && pnpm pack --pack-destination "$DIST_DIR" ) + done < <(jq -r '.packages[]' <<< "$RELEASE") + ;; + *) + echo "::error title=Invalid project type::PROJECT_TYPE must be single or workspaces." + exit 1 + ;; + esac + ;; + *) + echo "::error title=Invalid package manager::PACKAGE_MANAGER must be npm or pnpm." + exit 1 + ;; + esac + + if ! ls "$DIST_DIR"/*.tgz > /dev/null 2>&1; then + echo "::error title=Missing artifacts::No tarballs were produced for ${RELEASE}." + exit 1 + fi + + - name: Upload release distributions + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: npm-distributions-${{ matrix.release.tag }} + path: ${{ runner.temp }}/dist/*.tgz + if-no-files-found: error + retention-days: 14 + + finalize-release: + name: Finalize release (next dev + sync PR) + needs: [prepare-release, build-distributions] + if: >- + !cancelled() && + vars.RELEASE_LIFECYCLE != 'delegated' && + needs.prepare-release.result == 'success' && + (needs.build-distributions.result == 'success' || needs.build-distributions.result == 'skipped') + runs-on: ubuntu-24.04 + timeout-minutes: 30 + permissions: + contents: read + steps: + - name: Create GitHub App token + id: app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + client-id: ${{ vars.APP_CLIENT_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + permission-contents: write + permission-pull-requests: write + + - name: Check out release branch + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + ref: ${{ github.event.pull_request.base.ref || github.ref_name }} + token: ${{ steps.app-token.outputs.token }} + + - name: Read governed tool versions + id: tool-versions + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash + run: | + set -euo pipefail + if grep -Evq '^[a-zA-Z0-9_-]+ [a-zA-Z0-9._+-]+$' .tool-versions; then + echo "::error title=Invalid tool-versions::${WORKING_DIRECTORY}/.tool-versions is malformed." + exit 1 + fi + { + echo "tool_versions<> "$GITHUB_OUTPUT" + + - name: Set up asdf-managed Node + uses: asdf-vm/actions/install@b7bcd026f18772e44fe1026d729e1611cc435d47 # v4.0.1 + with: + tool_versions: ${{ steps.tool-versions.outputs.tool_versions }} + asdf_version: ${{ env.ASDF_BRANCH_VERSION }} + + - name: Configure release git signing + uses: InditexTech/gh-actions/configure-release-git@3dac9bbff47d1e983604671191a08e4d345d9064 + with: + token: ${{ steps.app-token.outputs.token }} + gpg-secret-key: ${{ secrets.CI_GPG_SECRET_KEY }} + gpg-passphrase: ${{ secrets.CI_GPG_SECRET_KEY_PASSWORD }} + + - name: Promote the release to the default branch + id: promote + if: vars.RELEASE_LIFECYCLE != 'delegated' && (needs.build-distributions.result == 'success' || needs.build-distributions.result == 'skipped') + env: + APP_SLUG: ${{ steps.app-token.outputs.app-slug }} + EXPECTED_REF: ${{ needs.prepare-release.outputs.expected_ref }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} + RELEASES: ${{ needs.prepare-release.outputs.releases }} + RELEASE_COMMIT: ${{ needs.prepare-release.outputs.release_commit }} + STAGING_REF: ${{ needs.prepare-release.outputs.staging_ref }} + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash + run: | + set -euo pipefail + gh auth setup-git + + git fetch --no-tags origin "+${STAGING_REF}:refs/ci-governance/staged" 2>/dev/null || true + if [[ "$(git ls-remote origin "$EXPECTED_REF" | cut -f1)" == "$RELEASE_COMMIT" ]]; then + echo "::notice title=Promotion skipped::$EXPECTED_REF already points at the release commit." + git checkout --force --detach "$RELEASE_COMMIT" + exit 0 + fi + + if [[ "$(git rev-parse --verify --quiet refs/ci-governance/staged || true)" != "$RELEASE_COMMIT" ]]; then + echo "::error title=Release staging mismatch::$STAGING_REF does not point at the release commit." + exit 1 + fi + git checkout --force --detach "$RELEASE_COMMIT" + + tag_refs=() + while IFS= read -r record; do + tag="$(jq -r '.tag' <<< "$record")" + version="$(jq -r '.version' <<< "$record")" + git check-ref-format --allow-onelevel "refs/tags/$tag" + if git ls-remote --exit-code --tags origin "refs/tags/$tag" >/dev/null 2>&1; then + echo "::error title=Tag conflict::$tag already exists." + exit 1 + fi + git tag -s "$tag" -m "Release $version" + tag_refs+=("refs/tags/$tag") + done < <(jq -c '.[]' <<< "$RELEASES") + + git push --atomic origin "${RELEASE_COMMIT}:${EXPECTED_REF}" "${tag_refs[@]}" + + - name: Next development iteration and sync PR + if: steps.promote.outcome == 'success' + env: + APP_SLUG: ${{ steps.app-token.outputs.app-slug }} + BASELINE_BRANCH: ${{ github.event.pull_request.base.ref || github.ref_name }} + DEVELOPMENT_FLOW: ${{ vars.DEVELOPMENT_FLOW }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} + RELEASES: ${{ needs.prepare-release.outputs.releases }} + RELEASE_COMMIT: ${{ needs.prepare-release.outputs.release_commit }} + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash + run: | + set -euo pipefail + gh auth setup-git + + descriptor="$GITHUB_WORKSPACE/.github/inditextech-ci-node.json" + + member_dir() { + printf '%s' "${1##*/}" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//' + } + member_directory() { + local mapped + if mapped="$(jq -er --arg p "$1" '.[$p]' <<< "$PACKAGE_PATHS" 2>/dev/null)"; then + if [[ ! "$mapped" =~ ^[a-z0-9][a-z0-9._-]*$ ]]; then + echo "::error title=Invalid release.package_paths::directory for '$1' must be a single lowercase [a-z0-9][a-z0-9._-]* segment under packages/." >&2 + exit 1 + fi + printf '%s' "$mapped" + return + fi + echo "::notice title=Derived release member::directory for '$1' derived from the package name." >&2 + member_dir "$1" + } + require_member() { + local dir="." + if [[ -n "$1" ]]; then + dir="packages/$1" + fi + if [[ ! -f "$dir/package.json" ]]; then + echo "::error title=Release member missing::package.json not found at '$dir'. Check release.package_paths in $descriptor." + exit 1 + fi + } + + if [[ "$(git rev-parse HEAD)" != "$RELEASE_COMMIT" ]]; then + echo "::notice title=Next-dev skipped::Branch already advanced past the release commit." + else + PACKAGE_PATHS="$(jq -c '.release.package_paths // {}' "$descriptor")" + { + if [[ "$PROJECT_TYPE" == "single" ]]; then + next_dev="$(jq -r '.[0].next_dev' <<< "$RELEASES")" + npm pkg set "version=$next_dev" + elif [[ "$(jq -er '.release.strategy' "$descriptor")" == "locked-step" ]]; then + next_dev="$(jq -r '.[0].next_dev' <<< "$RELEASES")" + while IFS= read -r package; do + member="$(member_directory "$package")" + require_member "$member" + npm pkg set "version=$next_dev" --workspace "packages/$member" + done < <(jq -er '.release.packages[]' "$descriptor") + else + while IFS= read -r record; do + member="$(jq -r '.packages[0]' <<< "$record")" + next_dev="$(jq -r '.next_dev' <<< "$record")" + npm pkg set "version=$next_dev" --workspace "packages/$member" + done < <(jq -c '.[]' <<< "$RELEASES") + fi + + if [[ "$PROJECT_TYPE" == "workspaces" ]]; then + declare -A member_version=() + while IFS= read -r record; do + rv="$(jq -r '.next_dev' <<< "$record")" + while IFS= read -r mdir; do + mname="$(jq -r '.name' "packages/$mdir/package.json")" + member_version["$mname"]="$rv" + done < <(jq -r '.packages[]' <<< "$record") + done < <(jq -c '.[]' <<< "$RELEASES") + + while IFS= read -r mdir; do + pj="packages/$mdir/package.json" + for field in dependencies devDependencies peerDependencies optionalDependencies; do + while IFS= read -r dep; do + new="${member_version[$dep]:-}" + [[ -n "$new" ]] || continue + cur="$(jq -r --arg f "$field" --arg d "$dep" '.[$f][$d]' "$pj")" + [[ "$cur" =~ [0-9] ]] || continue + pre="$(printf '%s' "$cur" | sed -E 's/^([^0-9]*).*/\1/')" + tmp="$(mktemp)" + jq --arg f "$field" --arg d "$dep" --arg v "${pre}${new}" \ + '.[$f][$d] = $v' "$pj" > "$tmp" && mv "$tmp" "$pj" + done < <(jq -r --arg f "$field" '(.[$f] // {}) | keys[]' "$pj") + done + done < <(jq -r '.[].packages[]' <<< "$RELEASES") + + npm install --package-lock-only --ignore-scripts + fi + + git -C "$GITHUB_WORKSPACE" add --update -- "$WORKING_DIRECTORY" + git commit -m "[node-release] prepare for next development iteration" + } + git push origin "HEAD:refs/heads/$BASELINE_BRANCH" + fi + + if [[ "$DEVELOPMENT_FLOW" != "git-flow" ]]; then + echo "::notice title=Sync skipped::Sync-to-develop applies only to git-flow." + exit 0 + fi + + version="$(jq -r '.[0].version' <<< "$RELEASES")" + develop_branch="${BASELINE_BRANCH/main/develop}" + if [[ -z "$(git ls-remote --heads origin "$develop_branch")" ]]; then + echo "::notice title=Sync skipped::$develop_branch does not exist." + exit 0 + fi + sync_branch="automated/sync-release-${version}-to-${develop_branch}" + git switch --force-create "$sync_branch" "origin/$BASELINE_BRANCH" + git push --force-with-lease --set-upstream origin "$sync_branch" + + if [[ -z "$(gh pr list --repo "$GITHUB_REPOSITORY" --head "$sync_branch" --base "$develop_branch" --state open --json number --jq '.[0].number // empty')" ]]; then + gh pr create --repo "$GITHUB_REPOSITORY" \ + --base "$develop_branch" --head "$sync_branch" \ + --title "Sync release $version to $develop_branch" \ + --body "**Automated pull request** syncing the release cut on \`$BASELINE_BRANCH\` back into \`$develop_branch\`." + fi + + - name: Discard the release staging ref + if: always() + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + STAGING_REF: ${{ needs.prepare-release.outputs.staging_ref }} + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash + run: | + set -euo pipefail + if [[ -z "${STAGING_REF:-}" ]]; then + echo "::notice title=Nothing staged::No staging ref was written, so there is nothing to discard." + exit 0 + fi + gh auth setup-git + git push origin ":${STAGING_REF}" diff --git a/.github/workflows/code-npm_node-sonarcloud-analysis.yml b/.github/workflows/code-npm_node-sonarcloud-analysis.yml index 793915c..fedf79f 100644 --- a/.github/workflows/code-npm_node-sonarcloud-analysis.yml +++ b/.github/workflows/code-npm_node-sonarcloud-analysis.yml @@ -1,164 +1,156 @@ # SPDX-FileCopyrightText: 2026 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.) # SPDX-License-Identifier: Apache-2.0 ---- -name: code-npm-sonarcloud-analysis -run-name: SonarCloud analysis on ${{ github.head_ref || github.ref_name }} branch -concurrency: - group: sonarcloud-${{ github.ref }} - cancel-in-progress: true +name: code-npm-node-sonarcloud-analysis +run-name: SonarCloud analysis on ${{ github.head_ref || github.ref_name }} on: workflow_dispatch: pull_request: types: [opened, synchronize, reopened] - branches: ["develop", "develop-*", "main", "main-*"] - paths: - - "package.json" - - "package-lock.json" - - ".tool-versions" - - "src/**" - - "types/**" - - "example/**" - - ".github/workflows/code-*-sonarcloud-analysis.yml" - release: - types: - - published + branches: [main, main-*, develop, develop-*] + push: + branches: [main, main-*, develop, develop-*] + +permissions: + contents: read + +concurrency: + group: code-npm-node-sonarcloud-${{ github.repository }}-${{ github.ref }} + cancel-in-progress: true env: - ASDF_BRANCH_VERSION: 0.18.0 + PROJECT_TYPE: ${{ vars.PROJECT_TYPE }} + WORKING_DIRECTORY: ${{ vars.WORKING_DIRECTORY }} + ASDF_BRANCH_VERSION: '0.18.0' jobs: - unit-tests: - name: SonarCloud / Unit Tests - permissions: - contents: read - timeout-minutes: 30 + analyze: + name: SonarCloud / Analyze if: >- - ${{ - (github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.event_name == 'release') - && vars.IS_INDITEXTECH_REPO == 'true' - && vars.SONARCLOUD_ENABLED == 'true' - }} + vars.SONARCLOUD_ENABLED == 'true' && + (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && + ( + ( + vars.DEVELOPMENT_FLOW == 'trunk-based-development' && + ( + (github.event_name == 'workflow_dispatch' && (github.ref_name == 'main' || startsWith(github.ref_name, 'main-'))) || + (github.event_name == 'pull_request' && (github.event.pull_request.base.ref == 'main' || startsWith(github.event.pull_request.base.ref, 'main-'))) || + (github.event_name == 'push' && (github.ref_name == 'main' || startsWith(github.ref_name, 'main-'))) + ) + ) || + ( + vars.DEVELOPMENT_FLOW == 'git-flow' && + ( + (github.event_name == 'workflow_dispatch' && (github.ref_name == 'develop' || startsWith(github.ref_name, 'develop-'))) || + (github.event_name == 'pull_request' && (github.event.pull_request.base.ref == 'develop' || startsWith(github.event.pull_request.base.ref, 'develop-'))) || + (github.event_name == 'push' && (github.ref_name == 'develop' || startsWith(github.ref_name, 'develop-'))) + ) + ) + ) runs-on: ubuntu-24.04 + timeout-minutes: 30 steps: - - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - name: Check out source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 + persist-credentials: false - - name: Detect NPM project - id: project + - name: Read governed tool versions + id: tool-versions + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash run: | - if [ -f package.json ]; then - echo "has-package=true" >> "$GITHUB_OUTPUT" - else - echo "::notice::package.json is not present yet; skipping SonarCloud analysis for the OSS shell PR." - echo "has-package=false" >> "$GITHUB_OUTPUT" + set -euo pipefail + if grep -Evq '^[a-zA-Z0-9_-]+ [a-zA-Z0-9._+-]+$' .tool-versions; then + echo "::error title=Invalid tool-versions::${WORKING_DIRECTORY}/.tool-versions is malformed." + exit 1 fi - - - name: NPM / Setup Dependencies Cache - if: steps.project.outputs.has-package == 'true' - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 - with: - path: ~/.npm - key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node- - - - name: asdf / Setup Dependencies Cache - if: steps.project.outputs.has-package == 'true' - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 - id: asdf-cache - with: - path: ~/.asdf - key: ${{ runner.os }}-asdf-${{ env.ASDF_BRANCH_VERSION }}-${{ hashFiles('.tool-versions') }} - - - name: Save tool-versions content - if: steps.project.outputs.has-package == 'true' - run: | { - echo "TOOL_VERSIONS<> "$GITHUB_ENV" + } >> "$GITHUB_OUTPUT" - - name: NPM / Setup asdf environment - if: steps.project.outputs.has-package == 'true' && steps.asdf-cache.outputs.cache-hit != 'true' - uses: asdf-vm/actions/install@1902764435ca0dd2f3388eea723a4f92a4eb8302 # v4.0.0 + - name: Set up asdf-managed Node + uses: asdf-vm/actions/install@b7bcd026f18772e44fe1026d729e1611cc435d47 # v4.0.1 with: - tool_versions: ${{ env.TOOL_VERSIONS }} + tool_versions: ${{ steps.tool-versions.outputs.tool_versions }} asdf_version: ${{ env.ASDF_BRANCH_VERSION }} - - name: Restore asdf shims to PATH - if: steps.project.outputs.has-package == 'true' && steps.asdf-cache.outputs.cache-hit == 'true' + - name: Build and test with coverage + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash run: | - echo "$HOME/.asdf/bin" >> "$GITHUB_PATH" - echo "$HOME/.asdf/shims" >> "$GITHUB_PATH" - cp .tool-versions "$HOME/.tool-versions" - - - name: NPM / Install dependencies - if: steps.project.outputs.has-package == 'true' - run: npm ci - - - name: NPM / Build package - if: steps.project.outputs.has-package == 'true' - run: npm run build --if-present - - - name: NPM / Run unit tests with coverage - if: steps.project.outputs.has-package == 'true' - continue-on-error: true - run: npm run test:coverage --if-present - - - name: Store project information - if: steps.project.outputs.has-package == 'true' - id: version + set -euo pipefail + case "$PROJECT_TYPE" in + single|workspaces) + npm ci --ignore-scripts + npm run build + npm run test + ;; + *) + echo "::error title=Invalid project type::PROJECT_TYPE must be single or workspaces." + exit 1 + ;; + esac + + - name: Resolve SonarCloud analysis scope + shell: bash run: | - echo "app-version=$(jq -r '.version' package.json)" >> "$GITHUB_OUTPUT" - echo "app-name=$(jq -r '.name' package.json)" >> "$GITHUB_OUTPUT" - echo "github-repository=$(echo "$GITHUB_REPOSITORY" | cut -d'/' -f2)" >> "$GITHUB_OUTPUT" - - - name: Set up JDK 21 for SonarScanner - if: steps.project.outputs.has-package == 'true' - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 - with: - distribution: temurin - java-version: "21" - - - name: SonarCloud / Run analysis with release event - if: ${{ steps.project.outputs.has-package == 'true' && github.event_name == 'release' }} - env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - npx sonar-scanner \ - -Dsonar.host.url="https://sonarcloud.io/" \ - -Dsonar.organization=inditextech \ - -Dsonar.projectKey=InditexTech_"${{ steps.version.outputs.github-repository }}" \ - -Dsonar.projectName="${{ steps.version.outputs.github-repository }}" \ - -Dsonar.projectVersion="${{ github.event.release.tag_name }}" \ - -Dsonar.branch.name="release/${{ github.event.release.tag_name }}" \ - -Dsonar.sources=src \ - -Dsonar.javascript.lcov.reportPaths=coverage/lcov.info + set -euo pipefail + case "$PROJECT_TYPE" in + single) + sonar_sources="${WORKING_DIRECTORY}" + sonar_coverage="${WORKING_DIRECTORY}/coverage/lcov.info" + ;; + workspaces) + sources=() + coverage=() + for member_dir in "${WORKING_DIRECTORY}"/packages/*/; do + member_dir="${member_dir%/}" + if [[ ! -f "${member_dir}/package.json" ]]; then + continue + fi + sources+=("${member_dir}") + coverage+=("${member_dir}/coverage/lcov.info") + done + if [[ "${#sources[@]}" -eq 0 ]]; then + echo "::error title=No workspaces members::No ${WORKING_DIRECTORY}/packages/*/package.json members were found." >&2 + exit 1 + fi + sonar_sources="$(IFS=,; printf '%s' "${sources[*]}")" + sonar_coverage="$(IFS=,; printf '%s' "${coverage[*]}")" + ;; + *) + echo "::error title=Invalid project type::PROJECT_TYPE must be single or workspaces." >&2 + exit 1 + ;; + esac + for governed_value in "$sonar_sources" "$sonar_coverage"; do + if [[ "$governed_value" == *$'\n'* ]]; then + echo "::error title=Invalid Sonar scope::Resolved analysis paths must not contain newlines." >&2 + exit 1 + fi + done + { + echo "SONAR_SOURCES=${sonar_sources}" + echo "SONAR_COVERAGE_PATHS=${sonar_coverage}" + } >> "$GITHUB_ENV" - - name: SonarCloud / Run analysis - if: ${{ steps.project.outputs.has-package == 'true' && github.event_name != 'release' }} + - name: Run SonarCloud analysis + uses: SonarSource/sonarqube-scan-action@d209202bc7d53ff1cc128f7f907dac145c9d6ae9 # v8.3.0 env: + SONAR_HOST_URL: https://sonarcloud.io SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - if [ "${{ github.event_name }}" = "pull_request" ]; then - SCOPE_OPTIONS="-Dsonar.pullrequest.key=${{ github.event.pull_request.number }} -Dsonar.pullrequest.branch=${{ github.head_ref }} -Dsonar.pullrequest.base=${{ github.base_ref }}" - else - SCOPE_OPTIONS="-Dsonar.branch.name=${{ github.ref_name }}" - fi - - npx sonar-scanner \ - -Dsonar.host.url="https://sonarcloud.io/" \ - -Dsonar.organization=inditextech \ - -Dsonar.projectKey=InditexTech_"${{ steps.version.outputs.github-repository }}" \ - -Dsonar.projectName="${{ steps.version.outputs.github-repository }}" \ - -Dsonar.projectVersion="${{ steps.version.outputs.app-version }}" \ - -Dsonar.sources=src \ - -Dsonar.javascript.lcov.reportPaths=coverage/lcov.info \ - $SCOPE_OPTIONS + with: + args: >- + -Dsonar.organization=inditextech + -Dsonar.projectKey=InditexTech_${{ github.event.repository.name }} + -Dsonar.projectName=${{ github.event.repository.name }} + -Dsonar.sources=${{ env.SONAR_SOURCES }} + -Dsonar.test.inclusions=**/*.test.js,**/*.test.jsx,**/*.test.mjs,**/*.test.cjs,**/*.test.ts,**/*.test.tsx,**/*.spec.js,**/*.spec.jsx,**/*.spec.ts,**/*.spec.tsx + -Dsonar.exclusions=**/node_modules/**,**/dist/**,**/build/**,**/coverage/** + -Dsonar.javascript.lcov.reportPaths=${{ env.SONAR_COVERAGE_PATHS }} diff --git a/.github/workflows/code-release_preview.yml b/.github/workflows/code-release_preview.yml index f96e67f..30de715 100644 --- a/.github/workflows/code-release_preview.yml +++ b/.github/workflows/code-release_preview.yml @@ -1,164 +1,167 @@ # SPDX-FileCopyrightText: 2026 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.) # SPDX-License-Identifier: Apache-2.0 ---- -name: code-release-preview -concurrency: - group: release-preview - cancel-in-progress: true +name: code-release-preview on: pull_request: - types: [labeled, synchronize, ready_for_review, opened] - branches: ["main", "main-*"] + types: [labeled, synchronize, ready_for_review, opened, reopened] + branches: [main, main-*, develop, develop-*] + +permissions: + contents: read + +concurrency: + group: code-release-preview-${{ github.repository }}-${{ github.event.pull_request.number }} + cancel-in-progress: true env: - PR_HEAD_REF: ${{ github.head_ref }} + PROJECT_TYPE: ${{ vars.PROJECT_TYPE }} + WORKING_DIRECTORY: ${{ vars.WORKING_DIRECTORY }} jobs: - check-changes-in-paths: - name: Check for changes in corresponding paths - runs-on: ubuntu-24.04 - permissions: - contents: read - pull-requests: read - if: ${{ github.event.pull_request.draft == false || contains(join(github.event.pull_request.labels.*.name, ', '), 'release-type') || contains(join(github.event.pull_request.labels.*.name, ', '), 'release-preview') }} - outputs: - detected: ${{ steps.changes.outputs.paths }} - steps: - - name: Check for changed files in specific paths - id: changes - uses: dorny/paths-filter@ebc4d7e9ebcb0b1eb21480bb8f43113e996ac77a - with: - filters: | - paths: - - "package.json" - - "package-lock.json" - - "CHANGELOG.md" - - "src/**" - - "types/**" - - "example/**" - - ".github/workflows/code*" - release-preview: name: Release Preview - needs: check-changes-in-paths - if: ${{ (contains(join(github.event.pull_request.labels.*.name, ', '), 'release-type') || contains(join(github.event.pull_request.labels.*.name, ', '), 'release-preview')) && needs.check-changes-in-paths.outputs.detected == 'true' }} + if: >- + github.event.pull_request.draft == false && + github.event.pull_request.head.repo.full_name == github.repository && + ( + (vars.DEVELOPMENT_FLOW == 'trunk-based-development' && (github.event.pull_request.base.ref == 'main' || startsWith(github.event.pull_request.base.ref, 'main-'))) || + (vars.DEVELOPMENT_FLOW == 'git-flow' && (github.event.pull_request.base.ref == 'main' || startsWith(github.event.pull_request.base.ref, 'main-'))) + ) runs-on: ubuntu-24.04 + timeout-minutes: 15 permissions: contents: read pull-requests: write steps: - - name: Checkout merge commit - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - name: Check out pull request head + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 persist-credentials: false - - - name: Setup patch release type version - if: contains(github.event.pull_request.labels.*.name, 'release-type/hotfix') || contains(github.event.pull_request.labels.*.name, 'release-type/multi-hotfix') || contains(github.event.pull_request.labels.*.name, 'release-type/patch') - run: echo "RELEASE_VERSION=patch" >> "$GITHUB_ENV" - - - name: Setup minor release type version - if: contains(github.event.pull_request.labels.*.name, 'release-type/minor') - run: echo "RELEASE_VERSION=minor" >> "$GITHUB_ENV" - - - name: Setup major release type version - if: contains(github.event.pull_request.labels.*.name, 'release-type/major') - run: echo "RELEASE_VERSION=major" >> "$GITHUB_ENV" - - - name: Check merge strategy - run: | - if [[ "$PR_HEAD_REF" == hotfix* && "${{ contains(github.event.pull_request.labels.*.name, 'release-type/multi-hotfix') }}" != "true" ]]; then - echo "MERGE_STRATEGY=Squash and Merge" >> "$GITHUB_ENV" - elif [[ "${{ vars.DEVELOPMENT_FLOW }}" == "trunk-based-development" ]]; then - echo "MERGE_STRATEGY=Squash and Merge" >> "$GITHUB_ENV" - else - echo "MERGE_STRATEGY=Create a merge commit" >> "$GITHUB_ENV" - fi - - - name: Check if CHANGELOG.md has changes + ref: ${{ github.event.pull_request.head.sha }} + + - name: Check CHANGELOG has Unreleased changes + if: >- + !contains(github.event.pull_request.labels.*.name, 'skip-release') && + github.event.pull_request.head.ref != 'automated/ci-governance-sync' && + github.event.pull_request.user.login != 'dependabot[bot]' && + github.event.pull_request.user.login != 'renovate[bot]' env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ github.token }} + GITHUB_EVENT_NUMBER: ${{ github.event.pull_request.number }} + BASE_REF: ${{ github.event.pull_request.base.ref }} + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash run: | - if git diff --quiet HEAD^ HEAD -- CHANGELOG.md; then - echo "::error title={No CHANGELOG.md changes}::{No CHANGELOG.md changes were found. Update the Unreleased section with the new changes.}" - gh pr comment ${{ github.event.number }} --repo "$GITHUB_REPOSITORY" --body "### :x: No CHANGELOG.md changes - - No changes were found in CHANGELOG.md. Please update the Unreleased section with the changes that apply to this release." + set -euo pipefail + if git diff --quiet "origin/$BASE_REF"...HEAD -- CHANGELOG.md; then + echo "::error title=No CHANGELOG changes::Update the Unreleased section with this release's changes." + gh pr comment "$GITHUB_EVENT_NUMBER" --repo "$GITHUB_REPOSITORY" --body $'### :x: No changes in `CHANGELOG.md`\nUpdate the `## [Unreleased]` section listing the changes for this release before merging.' exit 1 fi - - name: Update CHANGELOG.md - id: update-changelog - uses: release-flow/keep-a-changelog-action@74931dec7ecdbfc8e38ac9ae7e8dd84c08db2f32 # v3.0.0 - with: - command: bump - version: ${{ env.RELEASE_VERSION }} - changelog: CHANGELOG.md - fail-on-empty-release-notes: false - keep-unreleased-section: true - tag-prefix: "" - - - name: Add PR comment with release preview + - name: Calculate release preview env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - TASKS=$(awk -v version="${{ steps.update-changelog.outputs.version }}" ' - BEGIN { capture=0; found_version=0 } - $0 ~ "## \\[" version "\\] -" { capture=1; found_version=1; print; next } - capture && $0 ~ /^## \[.*\] -/ { capture=0 } - capture { print } - END { - if (found_version) { - while ((getline line < "CHANGELOG.md") > 0) { - if (line ~ /^\[.*\]:/) { - print line - } - } - } - } - ' CHANGELOG.md) - MESSAGE=" - ### :rocket: Release Preview Success - You are going to release the version **${RELEASE_VERSION}** with the following changes: - $TASKS - - ### 💡 Merge Strategy: $MERGE_STRATEGY - - Remember to use the **'$MERGE_STRATEGY'** strategy to merge this Pull Request (\`$PR_HEAD_REF\` → \`${{ github.event.pull_request.base.ref }}\`). - " - - gh pr comment ${{ github.event.number }} --repo "$GITHUB_REPOSITORY" --body "$(echo -e "$MESSAGE")" - - release-preview-no-code-changes: - name: Add PR comment with configuration management information - needs: check-changes-in-paths - if: ${{ (contains(join(github.event.pull_request.labels.*.name, ', '), 'release-type') || contains(join(github.event.pull_request.labels.*.name, ', '), 'release-preview')) && needs.check-changes-in-paths.outputs.detected == 'false' }} - runs-on: ubuntu-24.04 - permissions: - pull-requests: write - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - name: Add PR comment with release preview + DEVELOPMENT_FLOW: ${{ vars.DEVELOPMENT_FLOW }} + GITHUB_EVENT_NUMBER: ${{ github.event.pull_request.number }} + GH_TOKEN: ${{ github.token }} + RELEASE_LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') }} + working-directory: ${{ env.WORKING_DIRECTORY }} + shell: bash run: | - gh pr comment ${{ github.event.number }} --repo "$GITHUB_REPOSITORY" --body "### :exclamation: This Pull Request will not trigger a release + set -euo pipefail + + if [[ ",$RELEASE_LABELS," == *",skip-release,"* ]]; then + body=$'### Release Preview\n\nThis pull request has the `skip-release` label and will not create an npm release.' + gh pr comment "$GITHUB_EVENT_NUMBER" \ + --repo "$GITHUB_REPOSITORY" \ + --body "$body" + exit 0 + fi - No package, source, example, changelog, or code workflow changes were detected." + case ",$RELEASE_LABELS," in + *",release-type/major,"*) release_bump="major" ;; + *",release-type/minor,"*) release_bump="minor" ;; + *",release-type/hotfix,"*|*",release-type/multi-hotfix,"*|*",release-type/patch,"*) + release_bump="patch" + ;; + *) + if [[ "$DEVELOPMENT_FLOW" == "trunk-based-development" ]]; then + release_bump="minor" + else + body=$'### Release Preview\n\nAdd a `release-type/...` label to calculate a release for this git-flow pull request.' + gh pr comment "$GITHUB_EVENT_NUMBER" \ + --repo "$GITHUB_REPOSITORY" \ + --body "$body" + exit 0 + fi + ;; + esac + + merge_strategy="Create a merge commit" + if [[ "$DEVELOPMENT_FLOW" == "trunk-based-development" ]]; then + merge_strategy="Squash and merge" + fi - release-preview-no-release-labels: - name: Add PR comment with release information - needs: check-changes-in-paths - if: ${{ !contains(join(github.event.pull_request.labels.*.name, ', '), 'release-type') && needs.check-changes-in-paths.outputs.detected == 'true' && github.event.pull_request.draft == false && vars.DEVELOPMENT_FLOW != 'trunk-based-development' }} - runs-on: ubuntu-24.04 - permissions: - pull-requests: write - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - name: Add PR comment with release preview - run: | - gh pr comment ${{ github.event.number }} --repo "$GITHUB_REPOSITORY" --body "### :exclamation: This Pull Request will not trigger a release + member_dir() { + printf '%s' "${1##*/}" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//' + } + strip_snapshot() { printf '%s' "${1%-SNAPSHOT}"; } + read_version() { + local manifest="package.json" + [[ -n "$1" ]] && manifest="$1/package.json" + jq -r '.version' "$manifest" + } + bump_semver() { + local major minor patch + IFS=. read -r major minor patch <<< "$1" + case "$2" in + major) major=$((major + 1)); minor=0; patch=0 ;; + minor) minor=$((minor + 1)); patch=0 ;; + patch) patch=$((patch + 1)) ;; + esac + printf '%s.%s.%s' "$major" "$minor" "$patch" + } + + if [[ "$PROJECT_TYPE" == "single" ]]; then + current_version="$(read_version "")" + release_version="$(strip_snapshot "$current_version")" + next_dev="$(bump_semver "$release_version" "$release_bump")-SNAPSHOT" + versions="$(printf '%s\n' \ + "- Current version: \`$current_version\`" \ + "- Proposed release version: \`$release_version\`" \ + "- Next development version: \`$next_dev\`")" + elif [[ "$PROJECT_TYPE" == "workspaces" ]]; then + descriptor="$GITHUB_WORKSPACE/.github/inditextech-ci-node.json" + release_strategy="$(jq -r '.release.strategy' "$descriptor")" + versions="- Reactor release strategy: \`$release_strategy\`" + while IFS= read -r package; do + member="$(member_dir "$package")" + current_version="$(read_version "packages/$member")" + release_version="$(strip_snapshot "$current_version")" + next_dev="$(bump_semver "$release_version" "$release_bump")-SNAPSHOT" + versions+=$'\n'"- \`$package\` (packages/$member): \`$current_version\` -> \`$release_version\` (next dev: \`$next_dev\`)" + done < <(jq -r '.release.packages[]' "$descriptor") + else + echo "::error title=Invalid project type::PROJECT_TYPE must be single or workspaces." + exit 1 + fi - A Pull Request with no \`release-type/...\` labels will not trigger a release, so you need to label this PR if you want to create a release." + changes="$(awk ' + BEGIN { capture=0 } + /^## \[[Uu]nreleased\]/ { capture=1; next } + capture && /^## \[/ { capture=0 } + capture { print } + ' CHANGELOG.md)" + + body="$(printf "%s\n\n%s\n- Release type: \`%s\`\n- Expected merge strategy: **%s**\n\n#### Changes\n%s" \ + '### Node Release Preview' \ + "$versions" \ + "$release_bump" \ + "$merge_strategy" \ + "$changes")" + gh pr comment "$GITHUB_EVENT_NUMBER" \ + --repo "$GITHUB_REPOSITORY" \ + --body "$body" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..e2801ce --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,109 @@ +# SPDX-FileCopyrightText: 2026 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.) +# SPDX-License-Identifier: Apache-2.0 + +name: CodeQL + +on: + push: + branches: [main, main-*, develop, develop-*] + pull_request: + branches: [main, main-*, develop, develop-*] + merge_group: + types: [checks_requested] + schedule: + - cron: '17 4 * * 1' + +permissions: + contents: read + +concurrency: + group: codeql-${{ github.repository }}-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + scanning-availability: + name: Resolve code scanning availability + if: >- + github.event_name == 'schedule' || + ( + vars.DEVELOPMENT_FLOW == 'trunk-based-development' && + ( + (github.event_name == 'push' && (github.ref_name == 'main' || startsWith(github.ref_name, 'main-'))) || + (github.event_name == 'pull_request' && (github.event.pull_request.base.ref == 'main' || startsWith(github.event.pull_request.base.ref, 'main-'))) || + (github.event_name == 'merge_group' && (github.event.merge_group.base_ref == 'main' || startsWith(github.event.merge_group.base_ref, 'main-'))) + ) + ) || + ( + vars.DEVELOPMENT_FLOW == 'git-flow' && + ( + (github.event_name == 'push' && (github.ref_name == 'develop' || startsWith(github.ref_name, 'develop-'))) || + (github.event_name == 'pull_request' && (github.event.pull_request.base.ref == 'develop' || startsWith(github.event.pull_request.base.ref, 'develop-'))) || + (github.event_name == 'merge_group' && (github.event.merge_group.base_ref == 'develop' || startsWith(github.event.merge_group.base_ref, 'develop-'))) + ) + ) + runs-on: ubuntu-24.04 + permissions: + contents: read + outputs: + enabled: ${{ steps.resolve.outputs.enabled }} + steps: + - name: Resolve availability + id: resolve + env: + GH_TOKEN: ${{ github.token }} + FORCE_ENABLED: ${{ vars.CODE_SCANNING_ENABLED }} + run: | + set -euo pipefail + visibility="$(gh api "/repos/${GITHUB_REPOSITORY}" --jq '.visibility' 2>/dev/null || echo unknown)" + seat="$(gh api "/repos/${GITHUB_REPOSITORY}" \ + --jq '.security_and_analysis.advanced_security.status // "unknown"' 2>/dev/null || echo unknown)" + enabled=false + if [ "${visibility}" = "public" ]; then + enabled=true + elif [ "${seat}" = "enabled" ]; then + enabled=true + elif [ "${FORCE_ENABLED:-}" = "true" ]; then + enabled=true + fi + echo "Code scanning availability: visibility=${visibility} seat=${seat} enabled=${enabled}" + echo "enabled=${enabled}" >> "${GITHUB_OUTPUT}" + + analyze: + name: Analyze (${{ matrix.language }}) + needs: scanning-availability + if: needs.scanning-availability.outputs.enabled == 'true' + runs-on: ubuntu-24.04 + permissions: + actions: read + contents: read + packages: read + security-events: write + strategy: + fail-fast: false + matrix: + include: + - language: actions + build-mode: none + - language: javascript-typescript + build-mode: none + steps: + - name: Check out repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Initialize CodeQL + uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4 + with: + build-mode: ${{ matrix.build-mode }} + languages: ${{ matrix.language }} + # Release caches are branch-scoped, so a release-core build can never write the default-branch cache. + config: | + query-filters: + - exclude: + id: actions/cache-poisoning/poisonable-step + + - name: Perform CodeQL analysis + uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4 + with: + category: /language:${{ matrix.language }} diff --git a/.github/workflows/pr-verify.yml b/.github/workflows/pr-verify.yml index 2e7cf05..7ceb084 100644 --- a/.github/workflows/pr-verify.yml +++ b/.github/workflows/pr-verify.yml @@ -7,43 +7,81 @@ permissions: on: pull_request: - workflow_dispatch: + +concurrency: + group: pr-verify-${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true jobs: + workflow-quality: + name: Workflow and Template Quality + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + ref: ${{ github.event.pull_request.head.sha }} + + - name: Lint GitHub Actions workflows + uses: reviewdog/action-actionlint@320fcdd9c860767cf17fab3b20e22e739d5d02b8 # v1.76.0 + with: + reporter: github-annotations + fail_level: error + repo-linter: name: Repo Linter - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 + timeout-minutes: 10 steps: - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 - ref: ${{ github.event.pull_request.head.sha || github.sha }} + persist-credentials: false + ref: ${{ github.event.pull_request.head.sha }} - - name: "Run Repolinter" + - name: 'Run Repolinter' uses: newrelic/repolinter-action@3f4448f855c351e9695b24524a4111c7847b84cb # v1.7.0 reuse-compliance: name: REUSE Compliance - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 + timeout-minutes: 10 steps: - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 - ref: ${{ github.event.pull_request.head.sha || github.sha }} + persist-credentials: false + ref: ${{ github.event.pull_request.head.sha }} - name: REUSE Compliance Check - uses: fsfe/reuse-action@bb774aa972c2a89ff34781233d275075cbddf542 # v5 + uses: fsfe/reuse-action@676e2d560c9a403aa252096d99fcab3e1132b0f5 # v6.0.0 conventional-commits: name: Conventional Commits - runs-on: ubuntu-latest + if: >- + !( + github.event.pull_request.head.repo.full_name == github.repository && + github.event.pull_request.user.login == 'inditextechbot[bot]' && + ( + github.head_ref == 'automated/sync-from-main-to-develop' || + startsWith(github.head_ref, 'automated/sync-release-') + ) + ) + runs-on: ubuntu-24.04 + timeout-minutes: 10 steps: - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 - ref: ${{ github.event.pull_request.head.sha || github.sha }} + persist-credentials: false + ref: ${{ github.event.pull_request.head.sha }} - - uses: webiny/action-conventional-commits@8bc41ff4e7d423d56fa4905f6ff79209a78776c7 # v1.3.0 + - uses: webiny/action-conventional-commits@7f91b1595ca1951cdb671ddc9f07a49081ec5b69 # v1.4.2 diff --git a/.github/workflows/push-verify.yml b/.github/workflows/push-verify.yml index e1e9bb2..d8bb882 100644 --- a/.github/workflows/push-verify.yml +++ b/.github/workflows/push-verify.yml @@ -2,65 +2,66 @@ # SPDX-License-Identifier: Apache-2.0 name: Push Verification -permissions: read-all +permissions: + contents: read + on: push: - # Run on the long-lived git-flow branches. - branches: ["develop", "main"] + +concurrency: + group: push-verify-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: - scorecard-analysis: - # remove once public - if: false - name: Scorecard analysis - runs-on: ubuntu-latest + workflow-quality: + if: >- + github.ref_type == 'branch' && + github.ref_name == github.event.repository.default_branch + name: Workflow and Template Quality + runs-on: ubuntu-24.04 + timeout-minutes: 10 permissions: - # Needed for GitHub OIDC token if publish_results is true - id-token: write + contents: read steps: - - name: "Checkout code" - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - with: - fetch-depth: 0 - - - name: "Run analysis" - uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - results_file: results.sarif - results_format: sarif - # Scorecard team runs a weekly scan of public GitHub repos, - # see https://github.com/ossf/scorecard#public-data. - # Setting `publish_results: true` helps us scale by leveraging your workflow to - # extract the results instead of relying on our own infrastructure to run scans. - # And it's free for you! - publish_results: true + persist-credentials: false - # Upload the results as artifacts (optional). Commenting out will disable - # uploads of run results in SARIF format to the repository Actions tab. - # https://docs.github.com/en/actions/advanced-guides/storing-workflow-data-as-artifacts - - name: "Upload artifact" - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + - name: Lint GitHub Actions workflows + uses: reviewdog/action-actionlint@320fcdd9c860767cf17fab3b20e22e739d5d02b8 # v1.76.0 with: - name: SARIF file - path: results.sarif - retention-days: 5 + reporter: github-annotations + fail_level: error repo-linter: + if: >- + github.ref_type == 'branch' && + github.ref_name == github.event.repository.default_branch name: Repo Linter - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 + timeout-minutes: 10 steps: - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - - name: "Run Repolinter" + - name: 'Run Repolinter' uses: newrelic/repolinter-action@3f4448f855c351e9695b24524a4111c7847b84cb # v1.7.0 reuse-compliance: + if: >- + github.ref_type == 'branch' && + github.ref_name == github.event.repository.default_branch name: REUSE Compliance - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 + timeout-minutes: 10 steps: - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: REUSE Compliance Check - uses: fsfe/reuse-action@bb774aa972c2a89ff34781233d275075cbddf542 # v5 + uses: fsfe/reuse-action@676e2d560c9a403aa252096d99fcab3e1132b0f5 # v6.0.0 diff --git a/.github/workflows/scorecard-analysis.yml b/.github/workflows/scorecard-analysis.yml new file mode 100644 index 0000000..4168eb2 --- /dev/null +++ b/.github/workflows/scorecard-analysis.yml @@ -0,0 +1,104 @@ +# SPDX-FileCopyrightText: 2026 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.) +# SPDX-License-Identifier: Apache-2.0 + +name: Scorecard analysis + +permissions: + contents: read + +on: + push: + +concurrency: + group: scorecard-analysis-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + resolve-code-scanning: + name: Resolve Code Scanning availability + if: >- + github.ref_type == 'branch' && + github.ref_name == github.event.repository.default_branch + runs-on: ubuntu-24.04 + timeout-minutes: 5 + permissions: + contents: read + outputs: + enabled: ${{ steps.scanning-availability.outputs.enabled }} + steps: + - name: Resolve code scanning availability + id: scanning-availability + env: + GH_TOKEN: ${{ github.token }} + FORCE_ENABLED: ${{ vars.CODE_SCANNING_ENABLED }} + REPOSITORY_VISIBILITY: ${{ github.event.repository.visibility }} + run: | + set -euo pipefail + visibility="${REPOSITORY_VISIBILITY}" + seat="unknown" + if ! seat="$(gh api "/repos/${GITHUB_REPOSITORY}" --jq '.security_and_analysis.advanced_security.status // "unknown"')"; then + echo "::notice title=Code Scanning availability unavailable::Unable to query the Advanced Security seat; retaining the SARIF artifact only." + seat="unknown" + fi + enabled=false + if [ "${visibility}" = "public" ] || [ "${seat}" = "enabled" ] || [ "${FORCE_ENABLED:-}" = "true" ]; then + enabled=true + fi + echo "Code scanning availability: visibility=${visibility} seat=${seat} enabled=${enabled}" + echo "enabled=${enabled}" >> "${GITHUB_OUTPUT}" + + scorecard-analysis: + needs: resolve-code-scanning + if: needs.resolve-code-scanning.result == 'success' + name: Scorecard analysis + runs-on: ubuntu-24.04 + timeout-minutes: 20 + permissions: + contents: read + issues: read # Lets Scorecard read issue metadata. + pull-requests: read # Lets Scorecard read pull request metadata. + id-token: write # Lets Scorecard attest result provenance. + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Run analysis + uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 + with: + results_file: results.sarif + results_format: sarif + publish_results: ${{ github.event.repository.visibility == 'public' }} + + - name: Upload artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + upload-scorecard-sarif: + name: Upload Scorecard SARIF + needs: [resolve-code-scanning, scorecard-analysis] + if: >- + always() && + needs.resolve-code-scanning.outputs.enabled == 'true' && + needs.scorecard-analysis.result == 'success' + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + actions: read # Lets SARIF upload read run metadata. + security-events: write # Lets CodeQL upload Scorecard SARIF. + steps: + - name: Download artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: SARIF file + + - name: Upload SARIF to Code Scanning + uses: github/codeql-action/upload-sarif@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 + with: + sarif_file: results.sarif + category: scorecard diff --git a/.github/workflows/sync-to-develop.yml b/.github/workflows/sync-to-develop.yml new file mode 100644 index 0000000..206a42c --- /dev/null +++ b/.github/workflows/sync-to-develop.yml @@ -0,0 +1,248 @@ +# SPDX-FileCopyrightText: 2026 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.) +# SPDX-License-Identifier: Apache-2.0 + +name: Sync default branch to develop + +on: + # The workflow checks out only the merged base branch, never fork-provided code. + pull_request_target: + types: [closed] + +permissions: + contents: read + +jobs: + sync-to-develop: + name: Synchronize default branch to develop + if: >- + github.event.pull_request.merged && + vars.DEVELOPMENT_FLOW == 'git-flow' && + !contains(join(github.event.pull_request.labels.*.name, ','), 'release-type') + runs-on: ubuntu-24.04 + timeout-minutes: 15 + concurrency: + group: sync-to-develop-${{ github.repository }}-${{ github.event.pull_request.base.ref }} + cancel-in-progress: false + env: + SYNC_BASE_REF: ${{ github.event.pull_request.base.ref }} + steps: + - name: Resolve sync context + id: context + env: + BASE_BRANCH: ${{ github.event.pull_request.base.ref }} + DEVELOPMENT_BRANCH: ${{ vars.GIT_FLOW_DEVELOPMENT_BRANCH }} + RELEASE_BRANCH: ${{ vars.GIT_FLOW_RELEASE_BRANCH }} + run: | + set -euo pipefail + + if [[ -z "$RELEASE_BRANCH" || -z "$DEVELOPMENT_BRANCH" ]]; then + echo "::error title=Missing git-flow branch roles::GIT_FLOW_RELEASE_BRANCH and GIT_FLOW_DEVELOPMENT_BRANCH are required." + exit 1 + fi + + if [[ "$BASE_BRANCH" == "$RELEASE_BRANCH" ]]; then + develop_branch="$DEVELOPMENT_BRANCH" + elif [[ "$BASE_BRANCH" == "$RELEASE_BRANCH"-* ]]; then + suffix="${BASE_BRANCH#"$RELEASE_BRANCH"-}" + if [[ -z "$suffix" ]]; then + echo "::notice title=Sync skipped::$BASE_BRANCH has no release suffix." + echo "should_sync=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + develop_branch="$DEVELOPMENT_BRANCH-$suffix" + else + echo "::notice title=Sync skipped::$BASE_BRANCH is not a configured release branch." + echo "should_sync=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + { + echo "develop_branch=$develop_branch" + echo "should_sync=true" + } >> "$GITHUB_OUTPUT" + + # The checkout runs *before* any App credential exists. This event carries + # the base repository's secrets, so ordering is the cheap half of the + # defence: content that lands in the workspace cannot reach a credential + # that has not been minted yet. + - name: Checkout the exact merged revision + if: steps.context.outputs.should_sync == 'true' + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + # Deliberately the base branch, not the merge SHA. Central policy + # requires this ref, and the branch is trusted here: this event only + # fires for an already-merged pull request. + ref: ${{ env.SYNC_BASE_REF }} + + - name: Create GitHub App token + if: steps.context.outputs.should_sync == 'true' + id: app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + client-id: ${{ vars.APP_CLIENT_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + permission-contents: write + permission-issues: write + permission-pull-requests: write + # Required because the synchronization branch carries workflow files + # from the default branch into develop, and Git refuses that push + # without it. + permission-workflows: write + + - name: Prepare sync branch + id: prepare + if: steps.context.outputs.should_sync == 'true' + env: + APP_SLUG: ${{ steps.app-token.outputs.app-slug }} + BASE_BRANCH: ${{ github.event.pull_request.base.ref }} + DEVELOP_BRANCH: ${{ steps.context.outputs.develop_branch }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} + run: | + set -euo pipefail + + # shellcheck disable=SC2153 + develop_branch="$DEVELOP_BRANCH" + + sync_branch="automated/sync-from-${BASE_BRANCH}-to-${develop_branch}" + + gh auth setup-git + git config user.name "${APP_SLUG}[bot]" + git config user.email "${APP_SLUG}[bot]@users.noreply.github.com" + + if ! git ls-remote --exit-code --heads origin "$develop_branch" > /dev/null; then + echo "::notice title=Sync skipped::The $develop_branch branch does not exist." + echo "should_sync=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + git fetch --no-tags origin "$BASE_BRANCH" "$develop_branch" + + if git merge-base --is-ancestor "origin/$BASE_BRANCH" "origin/$develop_branch"; then + echo "::notice title=Sync skipped::$develop_branch already contains $BASE_BRANCH." + echo "should_sync=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + if git ls-remote --exit-code --heads origin "$sync_branch" > /dev/null; then + git fetch --no-tags origin "$sync_branch" + git switch --force-create "$sync_branch" "origin/$sync_branch" + # Preserve merge commits created by conflict resolution on the sync pull request. + git rebase --rebase-merges "origin/$BASE_BRANCH" + git push \ + --force-with-lease="refs/heads/$sync_branch:$(git rev-parse "origin/$sync_branch")" \ + origin \ + "HEAD:refs/heads/$sync_branch" + else + git switch --create "$sync_branch" "origin/$BASE_BRANCH" + git push --set-upstream origin "$sync_branch" + fi + + { + echo "develop_branch=$develop_branch" + echo "should_sync=true" + echo "sync_branch=$sync_branch" + } >> "$GITHUB_OUTPUT" + + - name: Create or update sync pull request + if: steps.prepare.outputs.should_sync == 'true' + env: + BASE_BRANCH: ${{ github.event.pull_request.base.ref }} + DEVELOP_BRANCH: ${{ steps.context.outputs.develop_branch }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} + SOURCE_PULL_REQUEST: ${{ github.event.pull_request.number }} + SYNC_BRANCH: ${{ steps.prepare.outputs.sync_branch }} + run: | + set -euo pipefail + + body="$(printf '%s\n\n- #%s' \ + '**Automated pull request** related to:' \ + "$SOURCE_PULL_REQUEST")" + find_existing_pr() { + gh pr list \ + --repo "$GITHUB_REPOSITORY" \ + --head "$SYNC_BRANCH" \ + --base "$DEVELOP_BRANCH" \ + --state open \ + --json isCrossRepository,number \ + --jq '[.[] | select(.isCrossRepository == false)][0].number // empty' + } + + existing_pr="$(find_existing_pr)" + has_internal_label="$(gh label list \ + --repo "$GITHUB_REPOSITORY" \ + --search kind/internal \ + --json name \ + --jq 'any(.[]; .name == "kind/internal")')" + + if [[ -n "$existing_pr" ]]; then + gh pr edit \ + "$existing_pr" \ + --repo "$GITHUB_REPOSITORY" \ + --body "$body" + + if [[ "$has_internal_label" == "true" ]]; then + gh pr edit \ + "$existing_pr" \ + --repo "$GITHUB_REPOSITORY" \ + --add-label kind/internal + else + echo "::warning title=Missing label::The kind/internal label is not available." + fi + else + create_args=( + pr create + --repo "$GITHUB_REPOSITORY" + --base "$DEVELOP_BRANCH" + --head "$SYNC_BRANCH" + --title "Sync from $BASE_BRANCH to $DEVELOP_BRANCH" + --body "$body" + ) + if [[ "$has_internal_label" == "true" ]]; then + create_args+=(--label kind/internal) + else + echo "::warning title=Missing label::The kind/internal label is not available." + fi + + for attempt in 1 2 3; do + if create_output="$(gh "${create_args[@]}" 2>&1)"; then + printf '%s\n' "$create_output" + break + else + create_status=$? + fi + + # A 5xx can arrive after GitHub has committed the mutation. Read + # the exact head/base pair before retrying, never create blindly. + existing_pr="$(find_existing_pr)" + if [[ -n "$existing_pr" ]]; then + echo "::notice title=Sync pull request reconciled::PR #$existing_pr exists after a failed create request." + break + fi + + if [[ "$attempt" -lt 3 ]] && + [[ "$create_output" =~ HTTP\ (500|502|503|504): ]]; then + echo "::warning title=Transient PR creation failure::Attempt $attempt failed; retrying after reconciliation." + sleep "$((attempt * 5))" + continue + fi + + printf '%s\n' "$create_output" >&2 + exit "$create_status" + done + fi + + - name: Comment on source pull request after failure + if: >- + failure() && + !cancelled() && + steps.app-token.outcome == 'success' + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + SOURCE_PULL_REQUEST: ${{ github.event.pull_request.number }} + run: | + gh pr comment \ + "$SOURCE_PULL_REQUEST" \ + --repo "$GITHUB_REPOSITORY" \ + --body "The automatic synchronization to develop failed. See the [workflow log](https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID)." diff --git a/.prettierignore b/.prettierignore index 4b179cb..0650905 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,3 +2,10 @@ dist node_modules example package-lock.json +.github +.sync-exclude +CHANGELOG.md +CODE_OF_CONDUCT.md +CONTRIBUTING.md +SECURITY.md +repolinter.json diff --git a/.sync-exclude b/.sync-exclude new file mode 100644 index 0000000..383bfc9 --- /dev/null +++ b/.sync-exclude @@ -0,0 +1 @@ +# One normalized relative path per line. Lines in this file are not reconciled by InditexTech CI governance. diff --git a/CHANGELOG.md b/CHANGELOG.md index ba20d17..aaf745c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated transitive `postcss` to `>=8.5.23`, fixing an incomplete `sourceMappingURL` path-traversal fix (CVE-2026-69153) - Updated transitive `undici` to `>=7.29.0`, fixing several request/response handling issues +### Changed + +- Added a `verify` npm script (`lint`, `format:check`, `test`, `build`) used by the governed CI verify workflows. +- Updated governance CI: issue/PR templates, `REUSE.toml`, `repolinter.json`, `.prettierignore`, `.sync-exclude`, and the governed workflow set. +- Updated `vitest` to `^5.0.1` and refreshed lockfiles. + ## [0.1.0] - 2026-07-20 ### Added diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d82811c..a3b3c27 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,5 @@ @@ -19,34 +19,28 @@ To maintain a collaborative and respectful environment, please consider the foll 1. Open an issue to discuss and gather feedback on the feature or fix you wish to address. 2. Fork the repository and clone it to your local machine. -3. Create a new branch from the latest `develop` branch to work on your contribution: - `git checkout -b feature/your-branch-name`. +3. Create a new branch to work on your contribution: `git checkout -b your-branch-name`. 4. Make the necessary changes in your local branch. 5. Ensure that your code follows the established project style and formatting guidelines. 6. Perform testing to ensure your changes do not introduce errors. 7. Make clear and descriptive commits that explain your changes. 8. Push your branch to the remote repository: `git push origin your-branch-name`. -9. Open a pull request against `develop` describing your changes and linking the corresponding issue. +9. Open a pull request describing your changes and linking the corresponding issue. 10. Await comments and discussions on your pull request. Make any necessary modifications based on the received feedback. -11. Once your pull request is approved, your contribution will be merged into `develop`. - -Project maintainers prepare releases by opening `release/*` or `hotfix/*` pull requests against `main`. Release pull -requests must include a `release-type/...` label unless the release is intentionally skipped. +11. Once your pull request is approved, your contribution will be merged into the default branch. ## Contribution Guidelines - All contributors are expected to follow the project's [code of conduct](CODE_OF_CONDUCT.md). Please be respectful and - considerate towards other contributors. +considerate towards other contributors. - Before starting work on a new feature or fix, check existing [issues](../../issues) and [pull requests](../../pulls) - to avoid duplications and unnecessary discussions. +to avoid duplications and unnecessary discussions. - If you wish to work on an existing issue, comment on the issue to inform other contributors that you are working on it. - This will help coordinate efforts and prevent conflicts. +This will help coordinate efforts and prevent conflicts. - It is always advisable to discuss and gather feedback from the community before making significant changes to the - project's structure or architecture. -- Ensure a clean and organized commit history. Divide your changes into logical and descriptive commits. We recommend to use the [Conventional Commits Specification](https://www.conventionalcommits.org/en/v1.0.0/) +project's structure or architecture. +- Ensure a clean and organized commit history. Divide your changes into logical and descriptive commits. We recommend using the [Conventional Commits Specification](https://www.conventionalcommits.org/en/v1.0.0/) - Document any new changes or features you add. This will help other contributors and project users understand your work - and its purpose. +and its purpose. - Be sure to link the corresponding issue in your pull request to maintain proper tracking of contributions. - Remember to add license and copyright information following the [REUSE Specification](https://reuse.software/spec/#copyright-and-licensing-information). - -## Development diff --git a/REUSE.toml b/REUSE.toml index 1bcf221..191963d 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -13,6 +13,9 @@ path = [ ".prettierrc", ".tool-versions", ".github/CODEOWNERS", + ".github/inditextech-ci-node.json", + ".github/inditextech-ci-sync-manifest.json", + ".sync-exclude", "package.json", "package-lock.json", "example/.npmrc", diff --git a/example/package-lock.json b/example/package-lock.json index 5c156a0..5df6fe3 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -2164,9 +2164,9 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" @@ -2470,9 +2470,9 @@ } }, "node_modules/dompurify": { - "version": "3.4.14", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.14.tgz", - "integrity": "sha512-dVoH9z+MY+C9IilgGCk3YfFqjLi3fChm2OiKJMzh6axrJ5qwxqWaZamgmHrpv22CN/KdbZJuGEGgfQoL00LTdg==", + "version": "3.4.16", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.16.tgz", + "integrity": "sha512-sqo+pNp3qRhCIpbgRi1y8Tgk27Bo2Ry7w0dC1NBeNTdZChWjz9Xb/KOoZbRP/R6pQZ80Qw8YhXw13hWWBbMRnQ==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -3024,9 +3024,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", - "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "funding": [ { "type": "github", diff --git a/example/package.json b/example/package.json index 98d42c4..5a44d75 100644 --- a/example/package.json +++ b/example/package.json @@ -14,6 +14,9 @@ "@vitejs/plugin-react": "^4.3.4", "vite": "^6.0.7" }, + "overrides": { + "js-yaml": "^4.3.2" + }, "scripts": { "start": "vite", "build": "vite build", diff --git a/package-lock.json b/package-lock.json index 274fb60..72d5e53 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ "swagger-ui-react": "^5.32.14", "tsup": "^8.3.5", "typescript-eslint": "^8.62.1", - "vitest": "^4.1.9" + "vitest": "^5.0.1" }, "engines": { "node": ">=18" @@ -1261,19 +1261,20 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.148.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.148.0.tgz", - "integrity": "sha512-Nm4s/jB+4FpFsPhWGEC4h7rzksesmtnMXomo6rCMcg/b8zLQuOziRgkCS1fxDCXOlJB/6Q8oABOZ/OP6RIPj9A==", + "version": "0.150.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.150.0.tgz", + "integrity": "sha512-rDS5/31E9HfPl/CIzGrn0DOlvBbXFseQ5URJ9sYMfstbKLD/c6Gm9vmRzRGDdAXyOIL4zmO37lc9RIwYqVruZw==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/oxc-project" } }, "node_modules/@rolldown/binding-android-arm-eabi": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.7.tgz", - "integrity": "sha512-EypzgnYCwyVY4NDHKzGmNJT5b+XaQEBniHxsMdeIQLB/tcCzZnhqrzHpZFbX9iaxx+5RiB8caATBtfvZP7zVxQ==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.9.tgz", + "integrity": "sha512-tNISae1QEf/vkb3xkRcjV5SEdzPE97We5IVaa2Z8jSszQPZ8U60B/YCYpw4QI7VidYsBtKavczXf+DyDs9WGxw==", "cpu": [ "arm" ], @@ -1283,14 +1284,15 @@ "os": [ "android" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.7.tgz", - "integrity": "sha512-l17HE9EweWaqJZhuUuNBN/FzM62xw+DECVnJyvMsxn8vJFAGLy5QfLDoYAcronkAN8VxKZHezDpulHDPx95vFw==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.9.tgz", + "integrity": "sha512-YC8YsI30o606GTZi0VyzYlsDKFP8W61i/QzayHDkLbNEz/IShqAmTa+hsJRj13xTHA0H+6fk4b2UmGn+Q/cMlg==", "cpu": [ "arm64" ], @@ -1300,14 +1302,15 @@ "os": [ "android" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.7.tgz", - "integrity": "sha512-8ED8ELFvHXc6OCETIn4gXObPiaR6bckM/ipXtbzlPVDRMBfEGjCKgO90F9YtfdpDatVx/ZQw7aZ1vUMf/+T3Mw==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.9.tgz", + "integrity": "sha512-IwhlH3qK5urrY8hZiEgGkHKEFN901p/p2bjxCxJlr4GyNnF7wYpUvK+Y43uaRYuC4hpfjzbR3SJC3arX1jGvmw==", "cpu": [ "arm64" ], @@ -1317,14 +1320,15 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.7.tgz", - "integrity": "sha512-/WPripjtiAIZ2tWY7ddijORT0Ujg87wxWW/qcoFVCKAWVDPhtY0xr7Dj0M3GyNGz60jGwTElhro/mkF9dT7dDQ==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.9.tgz", + "integrity": "sha512-XxpJfVzFh+jilRxIXUqcfYAYcunIc/XEzIizsOL1fcJee5Sf7H3mH8WlLmfHfluz5amqR88QQo9izKtmMlavAw==", "cpu": [ "x64" ], @@ -1334,14 +1338,15 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.7.tgz", - "integrity": "sha512-14DI4NcqpvbICxSnGLx3PmtDaWqRP/KGSGb6C+JLLVPeZRl6dKdHba3pGsqT3vpdTqhEYIPG0MMQ8c0xYqoJxA==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.9.tgz", + "integrity": "sha512-kSfvhmgeWyfkbT3p/1s5vSgboogoah2zkm9fX2zjg2hHxSV7T4KhMWRUUaRk4OXNqoD3QAUeRqLcs1aZOK4U1g==", "cpu": [ "x64" ], @@ -1351,14 +1356,15 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.7.tgz", - "integrity": "sha512-bxrWIRvHWQvbJwi+VIie/kDJmQxcNE6xxWwZdqF/ExVAigtHkv54WTLQPb+QsZdnFy18fg7JPfWGL0RH6vwIlQ==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.9.tgz", + "integrity": "sha512-1RVzG17pxqbTfYLC352JlLt6kKLG+6Hr30n8DlIJqsnV5luUDd2Qdx9Ayw1Cabfyb1K9k0jXEZ7evxkRoT+uiw==", "cpu": [ "arm" ], @@ -1368,116 +1374,141 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.7.tgz", - "integrity": "sha512-toOY2BChBZyuxU7OYX6Tn389di4IzAqPTycVcci0O7FSfBqzRB3RZn+K5Is6ANf4tmgRd/K1yZTsNTXbkXsnLg==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.9.tgz", + "integrity": "sha512-BXqPvZ2drqVD+/Z8UpKwcs4Mp7grM+eGFku4CAEKrEtcbAsUpzREphK1sogCRZGreVPiMkiiBtw0n3TPteuqvw==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.7.tgz", - "integrity": "sha512-lAIXTH/aiLRLxsTgQvfhjo4K1ydWIp00+V0voOr9beb/9ZmkUFrSIb03dXNFRgMNvkE6oGsF10ioQ6UsI+vS5Q==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.9.tgz", + "integrity": "sha512-11vWvo8YDwLzukt27J3aYDWU+gg2P7J+ZOmiJ0hkF5BXZDW7pVya7r40MXDy6ya0i9KamoENSVKIugvJNgFXIA==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.7.tgz", - "integrity": "sha512-kdnwS28Pkenp/mZMRwjXXXwxQ7pIsm+bF919LUK93BOyhcLsrVKdP2p9fxpiPNPAbNuch8ypQt0pm2P2LYCAGg==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.9.tgz", + "integrity": "sha512-a1tijMkdwsIARtc0F39ApURROkf3NwqinI6TOiSSWCTR7dT96dffNvMUtDHnq64wKNTIZOIlzKrFvvFUznJiyw==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.7.tgz", - "integrity": "sha512-516OdsyLdr5E65paF3yBF55t8mfm9+gmtCsK3xI7XKXIT7EfRlHhxL8K/NR6Hu8BWSgF5+1w74lTL0+nxcc8Qw==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.9.tgz", + "integrity": "sha512-x6SQNdAvv4c3hWqTMaWuawzMX9myaCs/yEmlGsxJzkdClnHW7FbrjQuSiRDhuSYzEYoEMhsaJy9qHG/XNemJPQ==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.7.tgz", - "integrity": "sha512-r8/z8n7GFaYRln3xmP1Cxy0HH/HLM0uBUPkEuSVEfKGDA89M0FsZRZJRSwe/tJjRx+fpH/gjorfhB8tmEbSFLA==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.9.tgz", + "integrity": "sha512-9s0AZ8BFK5/n7B/TBoa2yJE3gI3KURrbXcPBlsAsvjU4VeJKgE90y1YtNxyEUIcHPQkg6/yfF3qihUrcM/Kf0Q==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.7.tgz", - "integrity": "sha512-pAsE8iiDxUg1xBqdhrTfg45AVDVpirjz00sblEYClGNNcMnDb+e8beQgqIAw6LvauX/APvgxUnwrgun/YYGBhw==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.9.tgz", + "integrity": "sha512-P7VWAmV+WdJluH7ovnRGoiv2i8To7GAZ+kGzfGup635cyL7SyYl3lSUaA3Gp5THf0n/Co5EyEqb2zbqq+nMOHQ==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.7.tgz", - "integrity": "sha512-lTcIYmmnQQA8Or/2DatS6oSqcdLHvendjS+zLu+FwgToynWMRSmQdpM65fTANJgIS4mjbMOo5KT2lnT9SAb96w==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.9.tgz", + "integrity": "sha512-1qixtsE4BK8h+yS3BfmZ09UhA7O/N4IACva6YBr7EBvCJraByTuRcgOTaiA62Tm0vey3UcKXLOaoGHtYmNGEVg==", "cpu": [ "arm64" ], @@ -1487,14 +1518,15 @@ "os": [ "openharmony" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.7.tgz", - "integrity": "sha512-e3Gu3WxbNk/UqQhxqU7YIYO+9ZBvWNz3U+h/qRFosscMFzdRPbXYSaSWgSnklv2fz1TgzBTcti2z35c/7irsHw==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.9.tgz", + "integrity": "sha512-ok8IQjcEPs1AKZfuEUznVBrJw+gK4soq+bx8b1X2XoMqVClarc1q5JDmVtWXY1xfr6ZuHTAsPXHTgTrqKTZeww==", "cpu": [ "arm64" ], @@ -1504,14 +1536,15 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.7.tgz", - "integrity": "sha512-W/jg5qoRSqjsEv0+dZi4e687mcHqmVuU0P4fK6qS/xjetW2Gmc1W8j//z5nAeNcC8Ttm0hV46IjcYeuVwYhuiw==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.9.tgz", + "integrity": "sha512-Ip2mXoU0hM0boq3Rf+ekuT653OROSo6aSYcPT1VHE4q52KvyxgFkQgrgb/IEsxOuvQ2fZZbs8khJAyCEPM24/g==", "cpu": [ "x64" ], @@ -1521,6 +1554,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -1530,7 +1564,8 @@ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.63.1", @@ -1890,22 +1925,15 @@ "hasInstallScript": true, "license": "Apache-2.0" }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, - "license": "MIT" - }, "node_modules/@swagger-api/apidom-ast": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-1.12.0.tgz", - "integrity": "sha512-C4Px0M/hNIBf2jFphrHlKe8rN5OuUaxNb3FNandzM8++Uyp9TKjdt0+FI6+sX4s8u3axvQ4c4jrjb0LW7r2jPA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-1.11.6.tgz", + "integrity": "sha512-HzudlOOzs17o2lDnDfcOFT5STKq4ckOYAK6jOaN5udDRA9luDm0axRuc3sHlImHDaOHFN+Yf+rp0zUpT+2Dsiw==", "dev": true, "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-error": "^1.12.0", + "@swagger-api/apidom-error": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -1913,15 +1941,15 @@ } }, "node_modules/@swagger-api/apidom-core": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-core/-/apidom-core-1.12.0.tgz", - "integrity": "sha512-HXiJ6c7R/Sfpj1nz8Tgzy6jvaKE333b3FA4XTuKrwne/fgGq+ITksp6SaDMp7F/FbvP8iik2OFT9wJ3BRw1wjw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-core/-/apidom-core-1.11.6.tgz", + "integrity": "sha512-OdHzm974FpfYuO0L+W5B5x+byBMjs9gWuQy37LZYjIe4K8X8IHsN00KAgODLSKgccGlnWi4QBBs8WfHNgRWqGA==", "dev": true, "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-ast": "^1.12.0", - "@swagger-api/apidom-error": "^1.12.0", + "@swagger-api/apidom-ast": "1.11.6", + "@swagger-api/apidom-error": "1.11.6", "@types/ramda": "~0.30.0", "minim": "~0.23.8", "ramda": "~0.30.0", @@ -1931,9 +1959,9 @@ } }, "node_modules/@swagger-api/apidom-error": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-error/-/apidom-error-1.12.0.tgz", - "integrity": "sha512-mdQbyeolFyhyJiVchAJlfOUYntD4p10ORtpVRTLw0vFfqQZtDfr+uMVgiB/kmmyTZ27PoJPR51RDqaXB88aq2Q==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-error/-/apidom-error-1.11.6.tgz", + "integrity": "sha512-q44Zh9Ro0qDH/GLiFvGfe9tISSOSmq6ktrwmjuADVHwpAB//SfvhihIE9LwqNXYEZJTA4ExnFuxy01jiql++HQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1941,46 +1969,30 @@ } }, "node_modules/@swagger-api/apidom-json-pointer": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-1.12.0.tgz", - "integrity": "sha512-L6JaaeNjG6J5Ros4MLd+Yb2ZS0RhfJ5fj8w5UtCyf1I0cnwJo7hrRExE5RWXVBtF5nl8mO3eIGUe959hC1DtVQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-1.11.6.tgz", + "integrity": "sha512-sqRsfWsTEy+NQp5Ea3xwsGlPRYnXVLcf2WXCCrexcD2OrTOxhlZ6rCZQyc2trrUwAptna/Px/czVO+TjofXf4g==", "dev": true, "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-error": "^1.12.0", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-error": "1.11.6", "@swaggerexpert/json-pointer": "^2.10.1" } }, - "node_modules/@swagger-api/apidom-ns-a2a-1": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-a2a-1/-/apidom-ns-a2a-1-1.12.0.tgz", - "integrity": "sha512-kYfltECH/D3+bT+7IxYtF7VGNrSeDqqDttiIjx7R3pBgPPbxNWAi/EA4Rx+c7qY56sDz0CdbtZNTH28bNi00og==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@types/ramda": "~0.30.0", - "ramda": "~0.30.0", - "ramda-adjunct": "^5.0.0", - "ts-mixer": "^6.0.3" - } - }, "node_modules/@swagger-api/apidom-ns-api-design-systems": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-api-design-systems/-/apidom-ns-api-design-systems-1.12.0.tgz", - "integrity": "sha512-opXxaAZGxy/IDdUxqfTugtoGeKarb+pDchdVMOWNUD4NZH5weE519dcqCdLMqWtcHX2RL2m9UdcJjGLnAt+GQQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-api-design-systems/-/apidom-ns-api-design-systems-1.11.6.tgz", + "integrity": "sha512-lp7YvMSXoW/UQVxOzJYCLVFIIPU2NYsT7mljIiPQTjUZAFuQ20Vi6v93YHfgVIXUj0yQYSraAnvXuO4GciWlPQ==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-error": "^1.12.0", - "@swagger-api/apidom-ns-openapi-3-1": "^1.12.0", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-error": "1.11.6", + "@swagger-api/apidom-ns-openapi-3-1": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -1988,16 +2000,16 @@ } }, "node_modules/@swagger-api/apidom-ns-arazzo-1": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-arazzo-1/-/apidom-ns-arazzo-1-1.12.0.tgz", - "integrity": "sha512-xqtr7pZ5IQBsHZA6yWbSprVSTzpIbM5FtX40rY7lx4wsm4TyhIfJBxOf99+jFmELt7QHoKCUZ8zZQOdf+uOqLw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-arazzo-1/-/apidom-ns-arazzo-1-1.11.6.tgz", + "integrity": "sha512-GiAuYiAMwfX2AhUq4ANpmNmjJxSWiTV4QJYJUX3wHZpyU4EMXEM/BTaExGQpbmu7ewRPF4Nve/d9PKNouNXhJQ==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-ns-json-schema-2020-12": "^1.12.0", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-ns-json-schema-2020-12": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -2005,16 +2017,16 @@ } }, "node_modules/@swagger-api/apidom-ns-asyncapi-2": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-asyncapi-2/-/apidom-ns-asyncapi-2-1.12.0.tgz", - "integrity": "sha512-xe3Y3PeRqohaOinEtKTqnUxPoMAopTYAPEnXew5qRcnH7wVWO99J3+XrbY+x9JEXhcAKHpKSWmuNx5o7oU0WdA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-asyncapi-2/-/apidom-ns-asyncapi-2-1.11.6.tgz", + "integrity": "sha512-RHTv4qINvCKe40bDyoWkhve/2/qv6GObFY0umALw2Lv1KiW2hjHrBO63Y+kU8vYu34bnhFCUDIqyCyEmJt6zkQ==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-ns-json-schema-draft-7": "^1.12.0", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-ns-json-schema-draft-7": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -2022,16 +2034,16 @@ } }, "node_modules/@swagger-api/apidom-ns-asyncapi-3": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-asyncapi-3/-/apidom-ns-asyncapi-3-1.12.0.tgz", - "integrity": "sha512-WZ/j/Hk8nsX0lkHiTUFfESkQgGGEba9dwiKMP9OvX9qLsoxJ04Dg1Z7E58WCrmwistEpynMHO+KY3qo2trWOUQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-asyncapi-3/-/apidom-ns-asyncapi-3-1.11.6.tgz", + "integrity": "sha512-bGUXNP+vS9bjNl8ehjmoyUck1liWjxOUQReLZG4yqIix7dHWvLSeDw1hi0pq1A+VWjixPazAcb9Y6f/t5Bqvyg==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-ns-asyncapi-2": "^1.12.0", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-ns-asyncapi-2": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -2039,16 +2051,16 @@ } }, "node_modules/@swagger-api/apidom-ns-json-schema-2019-09": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-2019-09/-/apidom-ns-json-schema-2019-09-1.12.0.tgz", - "integrity": "sha512-fWRjOvOiOFqKp0vJgFJn1fm9i+H5eSOQ1J0xWldTnHJPHsYPQkYPLAQdpXcBbBiB5o0JYVeJe7PyWv3DvNxqSA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-2019-09/-/apidom-ns-json-schema-2019-09-1.11.6.tgz", + "integrity": "sha512-XZmo85MS8Y5bYzrxmYbbqvXOghWZt4tInhE26c5nGbKyCx5CbsLlhzcp0wMzkb5k22BnUmlwmniVFrnveYnOGQ==", "dev": true, "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-error": "^1.12.0", - "@swagger-api/apidom-ns-json-schema-draft-7": "^1.12.0", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-error": "1.11.6", + "@swagger-api/apidom-ns-json-schema-draft-7": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -2056,16 +2068,16 @@ } }, "node_modules/@swagger-api/apidom-ns-json-schema-2020-12": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-2020-12/-/apidom-ns-json-schema-2020-12-1.12.0.tgz", - "integrity": "sha512-U0sv+7aD6njyxcvQOS9Lkpr4feJRowMn0/shiIlXrXY1ox3BDJBgipsgx/0IqfQNseP3Zj43Ec5kGk4PFmsjCg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-2020-12/-/apidom-ns-json-schema-2020-12-1.11.6.tgz", + "integrity": "sha512-WU6Vw8aa0TLs1COGHjCFS2gz6MkQzSNZYFejs7EtZOnlAkgG3TN8H55BHVoif1QGBlrhTQ6o6RxCdk5bI1WneQ==", "dev": true, "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-error": "^1.12.0", - "@swagger-api/apidom-ns-json-schema-2019-09": "^1.12.0", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-error": "1.11.6", + "@swagger-api/apidom-ns-json-schema-2019-09": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -2073,15 +2085,15 @@ } }, "node_modules/@swagger-api/apidom-ns-json-schema-draft-4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-1.12.0.tgz", - "integrity": "sha512-c+NYp+DZvoZ0+3hFgKhQn507kba0HUv8C60cJEqQPCpIKqKpWvpZ6YF8GsKVhJoML4PAr6Zl6SWjFVvOnDUbBg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-1.11.6.tgz", + "integrity": "sha512-kiYyTkUCsHBKS2EFTQgI0bF0yKjOlt+QMiPzsXKzHZ7+XqMmEeRBFn5wSfTF3h9aJ5JMgtmku4j3TtdlxEUvUA==", "dev": true, "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-ast": "^1.12.0", - "@swagger-api/apidom-core": "^1.12.0", + "@swagger-api/apidom-ast": "1.11.6", + "@swagger-api/apidom-core": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -2089,16 +2101,16 @@ } }, "node_modules/@swagger-api/apidom-ns-json-schema-draft-6": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-6/-/apidom-ns-json-schema-draft-6-1.12.0.tgz", - "integrity": "sha512-O8aM7l/+K2ej3AH6f7NbEVADAuZhulLWmAGFFwRBHvE022mUeOQiDrcA1cd5q/s8fY+UFE+GIrfVAq0aBPLvxA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-6/-/apidom-ns-json-schema-draft-6-1.11.6.tgz", + "integrity": "sha512-vTlvnkmQ0UP0NeiPD8h6Kls2So8tNVruANah1n9BskJLUpvTVJV820HFDw88/+ocXUglAEg8aKENcPo0AWsrkw==", "dev": true, "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-error": "^1.12.0", - "@swagger-api/apidom-ns-json-schema-draft-4": "^1.12.0", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-error": "1.11.6", + "@swagger-api/apidom-ns-json-schema-draft-4": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -2106,16 +2118,16 @@ } }, "node_modules/@swagger-api/apidom-ns-json-schema-draft-7": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-7/-/apidom-ns-json-schema-draft-7-1.12.0.tgz", - "integrity": "sha512-gWTTBmz6CyBytc4v7tMcm0KJ/0xp8n0zyWE1VSBXvLjFylOfvlkzmPL/MD8sqiEqUCGXXSQAyUmyL7heU8uGzQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-7/-/apidom-ns-json-schema-draft-7-1.11.6.tgz", + "integrity": "sha512-kddXboLPDceL0302icmJvltcg/LzwGPaK1W/35PiHbs0lCtgRg5bz8F0tzGbPa1zk6n2o5BSS8+YU0J6vGKf3g==", "dev": true, "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-error": "^1.12.0", - "@swagger-api/apidom-ns-json-schema-draft-6": "^1.12.0", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-error": "1.11.6", + "@swagger-api/apidom-ns-json-schema-draft-6": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -2123,17 +2135,17 @@ } }, "node_modules/@swagger-api/apidom-ns-openapi-2": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-2/-/apidom-ns-openapi-2-1.12.0.tgz", - "integrity": "sha512-eISSf1rj7/HZAzOBGdZneUNp4M3FPwvqmKFL9ODBN+kpKFQ438TnYPdMVm60EoiinpvKa7hrKVKj/RubDfvcIQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-2/-/apidom-ns-openapi-2-1.11.6.tgz", + "integrity": "sha512-eMlB/P7apYuiwiBPsCW5zc5cyb6T/ZY7VeMSLBBN/JDy8I4rtTpFQq3ltQKXJlFhU321CuCibRiZ6p7z45wWKw==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-error": "^1.12.0", - "@swagger-api/apidom-ns-json-schema-draft-4": "^1.12.0", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-error": "1.11.6", + "@swagger-api/apidom-ns-json-schema-draft-4": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -2141,16 +2153,16 @@ } }, "node_modules/@swagger-api/apidom-ns-openapi-3-0": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-1.12.0.tgz", - "integrity": "sha512-8DqRCkRTxQPtFjRjWiFo5F/IZcBqFFcqjmtoVWbxkUNGyJpgfZjx5Mne/QCB9ZV5vqNeBPF2IYfjQZEvgOBKvA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-1.11.6.tgz", + "integrity": "sha512-RGngR9Daer9PtkPevSKgBqWZdXxCbDyjudadq0CImqFfjYd8OWhya20haKbPGpJuHDz7Rwmu2m92+0wZFjCcew==", "dev": true, "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-error": "^1.12.0", - "@swagger-api/apidom-ns-json-schema-draft-4": "^1.12.0", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-error": "1.11.6", + "@swagger-api/apidom-ns-json-schema-draft-4": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -2158,18 +2170,18 @@ } }, "node_modules/@swagger-api/apidom-ns-openapi-3-1": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-1/-/apidom-ns-openapi-3-1-1.12.0.tgz", - "integrity": "sha512-6h4NJyCb/I9GkPYCma7SiEp3MlMNH9DFDntamb49AJDBNlibOG1VyR+y3R4+vzUlYOZayMDwe7Z1zC+qN0ulYw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-1/-/apidom-ns-openapi-3-1-1.11.6.tgz", + "integrity": "sha512-mZe+3NgDxTjUmitxxgvlmLyK9mWSNkQajqA26j9JxcFdEvN/lcp/EsjxMX5kST4E0Qm4tQKD0i1QnJmjBSaHWw==", "dev": true, "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-ast": "^1.12.0", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-json-pointer": "^1.12.0", - "@swagger-api/apidom-ns-json-schema-2020-12": "^1.12.0", - "@swagger-api/apidom-ns-openapi-3-0": "^1.12.0", + "@swagger-api/apidom-ast": "1.11.6", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-json-pointer": "1.11.6", + "@swagger-api/apidom-ns-json-schema-2020-12": "1.11.6", + "@swagger-api/apidom-ns-openapi-3-0": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -2177,207 +2189,173 @@ } }, "node_modules/@swagger-api/apidom-ns-openapi-3-2": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-2/-/apidom-ns-openapi-3-2-1.12.0.tgz", - "integrity": "sha512-kkrXfvRPpiJTcK6qr0dB5Jf80kD3jgTo1DU9W6Q5/n06WcyGQhtKTofY7YRHQ52r2Nfpuh9JbxtoD0PIHE0STQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-2/-/apidom-ns-openapi-3-2-1.11.6.tgz", + "integrity": "sha512-TBDgIDAZodphp/1DS+mTilPLfTS84e/IXGEoRCE/MFrLKa4GZFY7bx8hXQmgGJfDWwxsNcQk5Q/X+SCi+WQg0g==", "dev": true, "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-ast": "^1.12.0", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-json-pointer": "^1.12.0", - "@swagger-api/apidom-ns-json-schema-2020-12": "^1.12.0", - "@swagger-api/apidom-ns-openapi-3-0": "^1.12.0", - "@swagger-api/apidom-ns-openapi-3-1": "^1.12.0", + "@swagger-api/apidom-ast": "1.11.6", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-json-pointer": "1.11.6", + "@swagger-api/apidom-ns-json-schema-2020-12": "1.11.6", + "@swagger-api/apidom-ns-openapi-3-0": "1.11.6", + "@swagger-api/apidom-ns-openapi-3-1": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", "ts-mixer": "^6.0.3" } }, - "node_modules/@swagger-api/apidom-parser-adapter-a2a-json-1": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-a2a-json-1/-/apidom-parser-adapter-a2a-json-1-1.12.0.tgz", - "integrity": "sha512-RwUkRb92938NiTriueninR0MaeXYJRZKcZtYyEB5h2JRQ7nQGsCO6a6F1rNwehzLoPeRWLGMYr0wRWP1tTy7fw==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-ns-a2a-1": "^1.12.0", - "@swagger-api/apidom-parser-adapter-json": "^1.12.0", - "@types/ramda": "~0.30.0", - "ramda": "~0.30.0", - "ramda-adjunct": "^5.0.0" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-a2a-yaml-1": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-a2a-yaml-1/-/apidom-parser-adapter-a2a-yaml-1-1.12.0.tgz", - "integrity": "sha512-G2L1mRcuAJQICU18cjYH6FIZshYVJOCAmz5JSNknriIL0LYXr8xlbQ7kStTC5cmM9YfxDfD3eLb92vQ+yvpbCA==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-ns-a2a-1": "^1.12.0", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.12.0", - "@types/ramda": "~0.30.0", - "ramda": "~0.30.0", - "ramda-adjunct": "^5.0.0" - } - }, "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-json": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-json/-/apidom-parser-adapter-api-design-systems-json-1.12.0.tgz", - "integrity": "sha512-Co9BcoTlEAcrDIVTE/o0B6+HAlIuwB5hvKMs+w9+P+r6cSDRqx4NOi16iqBH6leCEtO6xU8H152ua0e8hKkQSQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-json/-/apidom-parser-adapter-api-design-systems-json-1.11.6.tgz", + "integrity": "sha512-vFwzMj6z5HBf8d8DM0eNjVkaI2hiewE4+RDkTYhpfydAUq8m8jTuzJCf7DDdQ0YdvHH7/Zwdj4EshYnzEAp8Aw==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-ns-api-design-systems": "^1.12.0", - "@swagger-api/apidom-parser-adapter-json": "^1.12.0", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-ns-api-design-systems": "1.11.6", + "@swagger-api/apidom-parser-adapter-json": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-yaml": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-yaml/-/apidom-parser-adapter-api-design-systems-yaml-1.12.0.tgz", - "integrity": "sha512-vBnmuMflgcxPkqQX8rXFIWU6GdC0OPMO73LU4iOOjG2kxycET6NTs0zu6mTKRwfCk7PmVR+MzaqLyORCkL3Rjw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-yaml/-/apidom-parser-adapter-api-design-systems-yaml-1.11.6.tgz", + "integrity": "sha512-bwRh9F9phloYhpQJImsFuY3e8M5uf9lf436zqU413UCNnbU0F1p4HzIxKjFeIwXxiTzHfN89tCpsrPRsmLySaA==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-ns-api-design-systems": "^1.12.0", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.12.0", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-ns-api-design-systems": "1.11.6", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-arazzo-json-1": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-arazzo-json-1/-/apidom-parser-adapter-arazzo-json-1-1.12.0.tgz", - "integrity": "sha512-5SHEWHdLXpbugsDN2NVYxqh87Sson4pDS+Yk9Zp00cJSl2dNtLZ+LHq3dtIgjFcKy9yOCnaiKjzqoZtpdQB1zQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-arazzo-json-1/-/apidom-parser-adapter-arazzo-json-1-1.11.6.tgz", + "integrity": "sha512-opQc80CzT9buTKr3Q4k8R1DxVFJpHuJ4ej1Z6V4glGd7d2gEoabMkLKT9v9nKy50XLhJUTLkA86MLEyZPHxsfQ==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-ns-arazzo-1": "^1.12.0", - "@swagger-api/apidom-parser-adapter-json": "^1.12.0", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-ns-arazzo-1": "1.11.6", + "@swagger-api/apidom-parser-adapter-json": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-arazzo-yaml-1": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-arazzo-yaml-1/-/apidom-parser-adapter-arazzo-yaml-1-1.12.0.tgz", - "integrity": "sha512-l0ocWS0IcWYx0ZFosEQguOS72dJXYnEC42KrQgYJMvZCfpMZPtP4HahAYryFxRZWiHZnhunkSChv8MRnBplcQA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-arazzo-yaml-1/-/apidom-parser-adapter-arazzo-yaml-1-1.11.6.tgz", + "integrity": "sha512-7wMLd6yGGNW0UI1ourkMfGJaR79fav1HntVCC0IvmcaO5u8X6gUIwDpqMcIuX71Ipw1FHX2x9PKSakMiWy5LJQ==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-ns-arazzo-1": "^1.12.0", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.12.0", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-ns-arazzo-1": "1.11.6", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-json-2": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-json-2/-/apidom-parser-adapter-asyncapi-json-2-1.12.0.tgz", - "integrity": "sha512-oZt3H7qH4ttU+RILLQ8G8tgOLe+hRMQDh7q/W/Dfxc2xGGsLb6jYG4gl45ry/QHDpudCc2SKC3GxdXfQ3Dl4pg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-json-2/-/apidom-parser-adapter-asyncapi-json-2-1.11.6.tgz", + "integrity": "sha512-jsx299Uconwq9oPHDNK51v3VPqm7JqkvovM2w/azW4aXQNv9u3ZcSqHMAbfC8KpYCbnx0LIuCU9OgRyH8dj8Nw==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-ns-asyncapi-2": "^1.12.0", - "@swagger-api/apidom-parser-adapter-json": "^1.12.0", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-ns-asyncapi-2": "1.11.6", + "@swagger-api/apidom-parser-adapter-json": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-json-3": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-json-3/-/apidom-parser-adapter-asyncapi-json-3-1.12.0.tgz", - "integrity": "sha512-O4osrUo/q0ErGPqmCHfYEdRpeH894Dix0AQwLwuovgIS5OxWMa2r6Z8mzz3emRoyVi9ucF+0//OFm7WeU7NcGg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-json-3/-/apidom-parser-adapter-asyncapi-json-3-1.11.6.tgz", + "integrity": "sha512-JePNK/AwnJk+U0yxUKDQynq6ER8Snx/zcZKxar+cBy/COMj1EV0Bif0vUdmF3RCEoPjoXNRq2HfBIbZ6yT+9AQ==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-ns-asyncapi-3": "^1.12.0", - "@swagger-api/apidom-parser-adapter-json": "^1.12.0", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-ns-asyncapi-3": "1.11.6", + "@swagger-api/apidom-parser-adapter-json": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2/-/apidom-parser-adapter-asyncapi-yaml-2-1.12.0.tgz", - "integrity": "sha512-AfcdwWREQhBAEbVTwIVjFLvW/j5wTXmY8/VkKp1OmF7zXyyZDMA+xRAoGZlOlc+KNJ46O6yeN9CLfUaJ0IoRog==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2/-/apidom-parser-adapter-asyncapi-yaml-2-1.11.6.tgz", + "integrity": "sha512-SJqn9y4tPSCq6dfE5hHYGpl45oLwogbXtrsbd4IWmAFrxT9VnbSUSyGmLm+bBYycQiXsvg0SJ7QRuecCeEJvfw==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-ns-asyncapi-2": "^1.12.0", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.12.0", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-ns-asyncapi-2": "1.11.6", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-yaml-3": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-yaml-3/-/apidom-parser-adapter-asyncapi-yaml-3-1.12.0.tgz", - "integrity": "sha512-+gAKV6dwr1neG3ngONPYWYRATrYD/ooRO+dY6kOgc2yy99YagtSZjtNNSosvoMdPL8vhI6OkZRyew41kyW+Vwg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-yaml-3/-/apidom-parser-adapter-asyncapi-yaml-3-1.11.6.tgz", + "integrity": "sha512-gTDh14ge0xBHu+44I/oBsKFC24sFK18yaL3paEdi0LGdSVKYmAbPyOaQbX3JEJ5BkOAwLvXAEQBm2snZ75JcXw==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-ns-asyncapi-3": "^1.12.0", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.12.0", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-ns-asyncapi-3": "1.11.6", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-json": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-json/-/apidom-parser-adapter-json-1.12.0.tgz", - "integrity": "sha512-Tftr63g71VwTsWtafYgI7xrmkp6lcs8p6hwo06UzZICTyEsfMrye6Hjvs2TM0KBO/e6Hw8cTdCIBVtEV10Knqw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-json/-/apidom-parser-adapter-json-1.11.6.tgz", + "integrity": "sha512-LIbKkaydhviIJJ9VdMMmDrx96B2H21l1deH+osQt9QyrBr6Cdpo0NCv1xMr8TZi7CRjm6YD7+ec8mNmUNu54Bw==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-ast": "^1.12.0", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-error": "^1.12.0", + "@swagger-api/apidom-ast": "1.11.6", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-error": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -2387,153 +2365,153 @@ } }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-2": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-2/-/apidom-parser-adapter-openapi-json-2-1.12.0.tgz", - "integrity": "sha512-2cMgq3NBAS7magAFqgtBZOoi3vRR7/txQOOA1nzEODiS4WV4ERbe2B59+uaQ7+yzJlkm0OPqzFhXy3IkOB6Z0g==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-2/-/apidom-parser-adapter-openapi-json-2-1.11.6.tgz", + "integrity": "sha512-AeaRUdwEtQ1ThWQ3d47ZEH3WGLIHFR9Z5+uBhuYeT0jke0fHlLW6tnBWseZZ91Fk7Xo0sJR0fMnXRuCeBs4qTQ==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-ns-openapi-2": "^1.12.0", - "@swagger-api/apidom-parser-adapter-json": "^1.12.0", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-ns-openapi-2": "1.11.6", + "@swagger-api/apidom-parser-adapter-json": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-0": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-0/-/apidom-parser-adapter-openapi-json-3-0-1.12.0.tgz", - "integrity": "sha512-ws5vsTv8KtrwzPPGwyeNUrEGYLlsRSByTWHFXH8hU8eGbSAcmmKsENhNeEjslimzD2Cev5vJzmdat5oWpKY4fA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-0/-/apidom-parser-adapter-openapi-json-3-0-1.11.6.tgz", + "integrity": "sha512-KJ9NyuwhRc17GKMMF9IS/wFbzJ2hRNssoza16v2mVhL+xs+1qYrUi5sOwb2vCWWfNtso8Wst/ILQWxYbhFfEHw==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-ns-openapi-3-0": "^1.12.0", - "@swagger-api/apidom-parser-adapter-json": "^1.12.0", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-ns-openapi-3-0": "1.11.6", + "@swagger-api/apidom-parser-adapter-json": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-1": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-1/-/apidom-parser-adapter-openapi-json-3-1-1.12.0.tgz", - "integrity": "sha512-C2/oMuyGarGAPQj5Taob9MOI9Hme6D5Qm/ZyJkRgXBg50ZA3WeaFCr51St6dNU1lDhe8i++cZeWMsp4wegPllw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-1/-/apidom-parser-adapter-openapi-json-3-1-1.11.6.tgz", + "integrity": "sha512-9CG6vfA5usCEYf18g7+SYVRhD/zds8vvwV8gnF6gyrJOK+RbaAeVp1/Oeg499uRXy8D1LvypY07eB8aeVsaw3w==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-ns-openapi-3-1": "^1.12.0", - "@swagger-api/apidom-parser-adapter-json": "^1.12.0", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-ns-openapi-3-1": "1.11.6", + "@swagger-api/apidom-parser-adapter-json": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-2": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-2/-/apidom-parser-adapter-openapi-json-3-2-1.12.0.tgz", - "integrity": "sha512-kuSIq/Tht5LQ2lezsz6O1x+PM1U5DWeZBQKqHkTgQokhsbxM9dmO6xmSW/aQUk+0jUWf8n6ROrGmscrw6AawWg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-2/-/apidom-parser-adapter-openapi-json-3-2-1.11.6.tgz", + "integrity": "sha512-2EGjNq/tSFDJLVw46ApAaG2cRTxHF0Hhba5jEe5JiHTf52qZi0aPoi+YDxNnMvDKIzTrcEw34ZQddACu/JvOhQ==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-ns-openapi-3-2": "^1.12.0", - "@swagger-api/apidom-parser-adapter-json": "^1.12.0", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-ns-openapi-3-2": "1.11.6", + "@swagger-api/apidom-parser-adapter-json": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-2": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-2/-/apidom-parser-adapter-openapi-yaml-2-1.12.0.tgz", - "integrity": "sha512-QR0XdBWzjN/8ZmgBrbswP+Bpj+BllUqFiJu43VZNm1M8u/AtbJnsdlTw3coXLVpVpV7ruAKjp0GtsqQyOKNA1w==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-2/-/apidom-parser-adapter-openapi-yaml-2-1.11.6.tgz", + "integrity": "sha512-ohy7FFWM0DN0bhIqHUZJrx8qCN0qGnvjf8xqJGx/cRfxmqToEeukAXvrBBhBhtz4eqLKiSeE9TkdgHPYRRDZWg==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-ns-openapi-2": "^1.12.0", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.12.0", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-ns-openapi-2": "1.11.6", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0/-/apidom-parser-adapter-openapi-yaml-3-0-1.12.0.tgz", - "integrity": "sha512-2Mzft9tXXodoRBqEEXQ1p5MRLrzIDt7SCqZyXF9cBF5ukIdHfgq7vDoX7XUECELzhCf3mMRxLaxH+JcPC26izg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0/-/apidom-parser-adapter-openapi-yaml-3-0-1.11.6.tgz", + "integrity": "sha512-6U+yTLDTdlMs68YLVKsOOpN3eFlFWeo0wbkkSSDQw7ugfpUi2PEEsjrQ+C6cviPYH3ZfZNchlj4EZaNFtMZ1/Q==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-ns-openapi-3-0": "^1.12.0", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.12.0", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-ns-openapi-3-0": "1.11.6", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1/-/apidom-parser-adapter-openapi-yaml-3-1-1.12.0.tgz", - "integrity": "sha512-dYrUHnD7r6uhsh2H+gMwxFTSaqSADFHmpG1tFi7b7m/aFbNFCppB63iJ7LYg9Ew6IBej9WrYkr2jXDLKBgsMaA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1/-/apidom-parser-adapter-openapi-yaml-3-1-1.11.6.tgz", + "integrity": "sha512-P3t3Q4uUt92ZUrm0zPlWacPRWDHfMFqlFXdzH5dJN5phyQMMu08vdPFMaWFPv+w48wcZFXbrT/GeilorAoPlDw==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-ns-openapi-3-1": "^1.12.0", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.12.0", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-ns-openapi-3-1": "1.11.6", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-2": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-2/-/apidom-parser-adapter-openapi-yaml-3-2-1.12.0.tgz", - "integrity": "sha512-KrNSQmsmp6iO2o+AxJ9jpwKd4lVmPIaDcWrbp4se5b1MnHUmtf7d7h9kvI5pXhGFGuyA59HMp6qQzNfuS5SMEA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-2/-/apidom-parser-adapter-openapi-yaml-3-2-1.11.6.tgz", + "integrity": "sha512-bty6P85OHEV4YCdbVlNTsJRXZPs4ii3ynzTISURoIuyn08Y28C/Pm9H484Pl4iC9t16d1TKGngTAWc5RX0ve1Q==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-ns-openapi-3-2": "^1.12.0", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.12.0", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-ns-openapi-3-2": "1.11.6", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "1.11.6", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-yaml-1-2": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-yaml-1-2/-/apidom-parser-adapter-yaml-1-2-1.12.0.tgz", - "integrity": "sha512-vydWYSj1I0t4fsBeu7nAieivBKsW2A7KW9keS+osXLuyYWs1GLHdby5AXRHPv6ktR507vAq8RxQUXkODRxyRCg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-yaml-1-2/-/apidom-parser-adapter-yaml-1-2-1.11.6.tgz", + "integrity": "sha512-voxU8HMUypS36DYmZX8OsZ9pHz0hoT66GiBXjGKb1ETkXQR/8rVIYJnbxvVqtyXUI82aZy3dRTcXd6C2BHqH/A==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-ast": "^1.12.0", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-error": "^1.12.0", + "@swagger-api/apidom-ast": "1.11.6", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-error": "1.11.6", "@tree-sitter-grammars/tree-sitter-yaml": "=0.7.1", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", @@ -2577,15 +2555,15 @@ } }, "node_modules/@swagger-api/apidom-reference": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-reference/-/apidom-reference-1.12.0.tgz", - "integrity": "sha512-GApJUvwLROSyM2KfbdvuV/zC6ccb54khvhy2eJ7jPhAk5FU0zZE5Ijt0NmY5nSf97sET87MjrWgCCMszdqPXfw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-reference/-/apidom-reference-1.11.6.tgz", + "integrity": "sha512-wAU/b6fBw0zS2KLvbiwY13apXbXO43N+CnlnHvGuOf4WjV+msdcb31OPUAehMENsC1BUcrf/vroQj+lJqz77dA==", "dev": true, "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-error": "^1.12.0", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-error": "1.11.6", "@types/ramda": "~0.30.0", "axios": "^1.18.0", "minimatch": "^10.2.6", @@ -2593,34 +2571,31 @@ "ramda-adjunct": "^5.0.0" }, "optionalDependencies": { - "@swagger-api/apidom-json-pointer": "^1.12.0", - "@swagger-api/apidom-ns-a2a-1": "^1.12.0", - "@swagger-api/apidom-ns-arazzo-1": "^1.12.0", - "@swagger-api/apidom-ns-asyncapi-2": "^1.12.0", - "@swagger-api/apidom-ns-openapi-2": "^1.12.0", - "@swagger-api/apidom-ns-openapi-3-0": "^1.12.0", - "@swagger-api/apidom-ns-openapi-3-1": "^1.12.0", - "@swagger-api/apidom-ns-openapi-3-2": "^1.12.0", - "@swagger-api/apidom-parser-adapter-a2a-json-1": "^1.12.0", - "@swagger-api/apidom-parser-adapter-a2a-yaml-1": "^1.12.0", - "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^1.12.0", - "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "^1.12.0", - "@swagger-api/apidom-parser-adapter-arazzo-json-1": "^1.12.0", - "@swagger-api/apidom-parser-adapter-arazzo-yaml-1": "^1.12.0", - "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^1.12.0", - "@swagger-api/apidom-parser-adapter-asyncapi-json-3": "^1.12.0", - "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^1.12.0", - "@swagger-api/apidom-parser-adapter-asyncapi-yaml-3": "^1.12.0", - "@swagger-api/apidom-parser-adapter-json": "^1.12.0", - "@swagger-api/apidom-parser-adapter-openapi-json-2": "^1.12.0", - "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "^1.12.0", - "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "^1.12.0", - "@swagger-api/apidom-parser-adapter-openapi-json-3-2": "^1.12.0", - "@swagger-api/apidom-parser-adapter-openapi-yaml-2": "^1.12.0", - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "^1.12.0", - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "^1.12.0", - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-2": "^1.12.0", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.12.0" + "@swagger-api/apidom-json-pointer": "1.11.6", + "@swagger-api/apidom-ns-arazzo-1": "1.11.6", + "@swagger-api/apidom-ns-asyncapi-2": "1.11.6", + "@swagger-api/apidom-ns-openapi-2": "1.11.6", + "@swagger-api/apidom-ns-openapi-3-0": "1.11.6", + "@swagger-api/apidom-ns-openapi-3-1": "1.11.6", + "@swagger-api/apidom-ns-openapi-3-2": "1.11.6", + "@swagger-api/apidom-parser-adapter-api-design-systems-json": "1.11.6", + "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "1.11.6", + "@swagger-api/apidom-parser-adapter-arazzo-json-1": "1.11.6", + "@swagger-api/apidom-parser-adapter-arazzo-yaml-1": "1.11.6", + "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "1.11.6", + "@swagger-api/apidom-parser-adapter-asyncapi-json-3": "1.11.6", + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "1.11.6", + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-3": "1.11.6", + "@swagger-api/apidom-parser-adapter-json": "1.11.6", + "@swagger-api/apidom-parser-adapter-openapi-json-2": "1.11.6", + "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "1.11.6", + "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "1.11.6", + "@swagger-api/apidom-parser-adapter-openapi-json-3-2": "1.11.6", + "@swagger-api/apidom-parser-adapter-openapi-yaml-2": "1.11.6", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "1.11.6", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "1.11.6", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-2": "1.11.6", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "1.11.6" } }, "node_modules/@swagger-api/apidom-reference/node_modules/balanced-match": { @@ -2634,9 +2609,9 @@ } }, "node_modules/@swagger-api/apidom-reference/node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2688,6 +2663,35 @@ "node": ">=12.20.0" } }, + "node_modules/@tanstack/react-virtual": { + "version": "3.14.10", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.14.10.tgz", + "integrity": "sha512-SRyoUbdFMRHuYXMijV5H4ZarQWpXkj3iANq8OFre+pybeVap8ZJjZ3Nz9bVjx4d8PfobVUQUdKyyyHYk3E+djw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.17.8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.17.8", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.17.8.tgz", + "integrity": "sha512-BfEvehNpOT75r5Ksc5xW6NZuXujTfb7nlSEyVu4XHG3gdxNg1KqXruWbDewXOUaUYIo4oRbSfkjIajz4MAT8tA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", @@ -3052,9 +3056,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3148,34 +3152,17 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@vitest/expect": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz", - "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.11", - "@vitest/utils": "4.1.11", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, "node_modules/@vitest/mocker": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz", - "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.1.tgz", + "integrity": "sha512-6K1DoBNAPGvuOcSsGA4D6x+5zEEff/KmOOP3uetT2TrGpVfI+HRHRnJJfKi5ib/g1vx8IYHQD8s0pbJz8WQI7Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.11", + "@jridgewell/trace-mapping": "0.3.31", + "@vitest/spy": "5.0.1", "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" + "magic-string": "^1.2.3" }, "funding": { "url": "https://opencollective.com/vitest" @@ -3193,70 +3180,22 @@ } } }, - "node_modules/@vitest/pretty-format": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz", - "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz", - "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.11", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz", - "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==", + "node_modules/@vitest/mocker/node_modules/magic-string": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.1.tgz", + "integrity": "sha512-8lyCu36ErXR0J9uaGKlKQoiLZKmtI63YGLE8G2o9jyRPdr4X47LusSOwgOJOzcVtp81fTAAjxR7BwKz682Jhow==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.11", - "@vitest/utils": "4.1.11", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "@jridgewell/sourcemap-codec": "^1.6.0" } }, "node_modules/@vitest/spy": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz", - "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz", - "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.1.tgz", + "integrity": "sha512-rbto/mF/SGERxEgYOek7Xm6B9b+y+mVoo+f4b2LymYO8zM1b7uB5nHuhVMTP2hxdzgxvGiZYGxGIaMvL5y180Q==", "dev": true, "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.11", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" - }, "funding": { "url": "https://opencollective.com/vitest" } @@ -3628,9 +3567,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -4229,6 +4168,7 @@ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8" } @@ -4255,9 +4195,9 @@ "peer": true }, "node_modules/dompurify": { - "version": "3.4.14", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.14.tgz", - "integrity": "sha512-dVoH9z+MY+C9IilgGCk3YfFqjLi3fChm2OiKJMzh6axrJ5qwxqWaZamgmHrpv22CN/KdbZJuGEGgfQoL00LTdg==", + "version": "3.4.16", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.16.tgz", + "integrity": "sha512-sqo+pNp3qRhCIpbgRi1y8Tgk27Bo2Ry7w0dC1NBeNTdZChWjz9Xb/KOoZbRP/R6pQZ80Qw8YhXw13hWWBbMRnQ==", "dev": true, "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { @@ -6172,6 +6112,7 @@ "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "dev": true, "license": "MPL-2.0", + "peer": true, "dependencies": { "detect-libc": "^2.0.3" }, @@ -6209,6 +6150,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -6230,6 +6172,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -6251,6 +6194,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -6272,6 +6216,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -6293,6 +6238,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -6309,11 +6255,15 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -6330,11 +6280,15 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -6351,11 +6305,15 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -6372,11 +6330,15 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -6398,6 +6360,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -6419,6 +6382,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -6673,6 +6637,7 @@ } ], "license": "MIT", + "peer": true, "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -7114,9 +7079,9 @@ } }, "node_modules/postcss": { - "version": "8.5.26", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", - "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", "dev": true, "funding": [ { @@ -7133,8 +7098,9 @@ } ], "license": "MIT", + "peer": true, "dependencies": { - "nanoid": "^3.3.17", + "nanoid": "^3.3.18", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -7196,9 +7162,9 @@ } }, "node_modules/prettier": { - "version": "3.9.6", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", - "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", + "version": "3.9.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.8.tgz", + "integrity": "sha512-WRFq3Wn3WId7LLROfMLdH7xaFr2jR62wU8nLO6rQUOLOxNZUviyJQs1M0iIhLexSFy+L+w0ch66wtoO2jRjG0A==", "dev": true, "license": "MIT", "bin": { @@ -7538,16 +7504,6 @@ "dev": true, "license": "MIT" }, - "node_modules/redux-immutable": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/redux-immutable/-/redux-immutable-4.0.0.tgz", - "integrity": "sha512-SchSn/DWfGb3oAejd+1hhHx01xUoxY+V7TeK0BKqpkLKiQPVFf7DYzEaKmrEVxsWxielKfSK9/Xq66YyxgR1cg==", - "dev": true, - "license": "BSD-3-Clause", - "peerDependencies": { - "immutable": "^3.8.1 || ^4.0.0-rc.1" - } - }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -7715,13 +7671,14 @@ } }, "node_modules/rolldown": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.7.tgz", - "integrity": "sha512-g0EtLvBjTUB7jhyV0S/TCup3v/XSVl45vUIGbOGU4QPiyjTenCe4mKuFvW9fEgYmS2Fo42AUssRmNuMziXdrig==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.9.tgz", + "integrity": "sha512-hx/Pv0N1haXRb11qkfnK5MXB/iqr7i0yjWQqmO9uHqZpBgQSqzc8UsSnEpalsh+j1I8qQ2CkXAkJC8Br3dKSlg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@oxc-project/types": "=0.148.0", + "@oxc-project/types": "=0.150.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -7731,21 +7688,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm-eabi": "1.2.7", - "@rolldown/binding-android-arm64": "1.2.7", - "@rolldown/binding-darwin-arm64": "1.2.7", - "@rolldown/binding-darwin-x64": "1.2.7", - "@rolldown/binding-freebsd-x64": "1.2.7", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.7", - "@rolldown/binding-linux-arm64-gnu": "1.2.7", - "@rolldown/binding-linux-arm64-musl": "1.2.7", - "@rolldown/binding-linux-ppc64-gnu": "1.2.7", - "@rolldown/binding-linux-s390x-gnu": "1.2.7", - "@rolldown/binding-linux-x64-gnu": "1.2.7", - "@rolldown/binding-linux-x64-musl": "1.2.7", - "@rolldown/binding-openharmony-arm64": "1.2.7", - "@rolldown/binding-win32-arm64-msvc": "1.2.7", - "@rolldown/binding-win32-x64-msvc": "1.2.7" + "@rolldown/binding-android-arm-eabi": "1.2.9", + "@rolldown/binding-android-arm64": "1.2.9", + "@rolldown/binding-darwin-arm64": "1.2.9", + "@rolldown/binding-darwin-x64": "1.2.9", + "@rolldown/binding-freebsd-x64": "1.2.9", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.9", + "@rolldown/binding-linux-arm64-gnu": "1.2.9", + "@rolldown/binding-linux-arm64-musl": "1.2.9", + "@rolldown/binding-linux-ppc64-gnu": "1.2.9", + "@rolldown/binding-linux-s390x-gnu": "1.2.9", + "@rolldown/binding-linux-x64-gnu": "1.2.9", + "@rolldown/binding-linux-x64-musl": "1.2.9", + "@rolldown/binding-openharmony-arm64": "1.2.9", + "@rolldown/binding-win32-arm64-msvc": "1.2.9", + "@rolldown/binding-win32-x64-msvc": "1.2.9" } }, "node_modules/rollup": { @@ -8117,9 +8074,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -8347,24 +8304,24 @@ } }, "node_modules/swagger-client": { - "version": "3.38.0", - "resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.38.0.tgz", - "integrity": "sha512-n7aykm1BEdQ3fKePJJx63UGjYe8/5fuxFMi3qZP4OJGZvzljKvmhxNwIF/MB71sF/lop9NeWZReKvPib9CY+2g==", + "version": "3.38.2", + "resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.38.2.tgz", + "integrity": "sha512-MrMYADBRg7nY5Y+CY9E76/Zm2LSghhln4wNNU6jGn3bNZ76BRQ3hQRyCHCyJK9tVgTDUmYCcYgYWrwGruL1mIw==", "dev": true, "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.22.15", "@scarf/scarf": "=1.4.0", - "@swagger-api/apidom-core": "^1.12.0", - "@swagger-api/apidom-error": "^1.12.0", - "@swagger-api/apidom-json-pointer": "^1.12.0", - "@swagger-api/apidom-ns-openapi-3-1": "^1.12.0", - "@swagger-api/apidom-ns-openapi-3-2": "^1.12.0", - "@swagger-api/apidom-reference": "^1.12.0", + "@swagger-api/apidom-core": "1.11.6", + "@swagger-api/apidom-error": "1.11.6", + "@swagger-api/apidom-json-pointer": "1.11.6", + "@swagger-api/apidom-ns-openapi-3-1": "1.11.6", + "@swagger-api/apidom-ns-openapi-3-2": "1.11.6", + "@swagger-api/apidom-reference": "1.11.6", "@swaggerexpert/cookie": "^2.0.2", "deepmerge": "~4.3.0", "fast-json-patch": "^3.0.0-1", - "js-yaml": "^4.2.0", + "js-yaml": "^4.3.2", "neotraverse": "=1.0.1", "node-abort-controller": "^3.1.1", "openapi-path-templating": "^2.2.1", @@ -8376,38 +8333,39 @@ "node": ">=22" }, "optionalDependencies": { - "@swagger-api/apidom-ns-asyncapi-2": "^1.12.0", - "@swagger-api/apidom-ns-asyncapi-3": "^1.12.0", - "@swagger-api/apidom-ns-openapi-2": "^1.12.0", - "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^1.12.0", - "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "^1.12.0", - "@swagger-api/apidom-parser-adapter-arazzo-json-1": "^1.12.0", - "@swagger-api/apidom-parser-adapter-arazzo-yaml-1": "^1.12.0", - "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^1.12.0", - "@swagger-api/apidom-parser-adapter-asyncapi-json-3": "^1.12.0", - "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^1.12.0", - "@swagger-api/apidom-parser-adapter-asyncapi-yaml-3": "^1.12.0", - "@swagger-api/apidom-parser-adapter-json": "^1.12.0", - "@swagger-api/apidom-parser-adapter-openapi-json-2": "^1.12.0", - "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "^1.12.0", - "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "^1.12.0", - "@swagger-api/apidom-parser-adapter-openapi-json-3-2": "^1.12.0", - "@swagger-api/apidom-parser-adapter-openapi-yaml-2": "^1.12.0", - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "^1.12.0", - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "^1.12.0", - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-2": "^1.12.0", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.12.0" + "@swagger-api/apidom-ns-asyncapi-2": "1.11.6", + "@swagger-api/apidom-ns-asyncapi-3": "1.11.6", + "@swagger-api/apidom-ns-openapi-2": "1.11.6", + "@swagger-api/apidom-parser-adapter-api-design-systems-json": "1.11.6", + "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "1.11.6", + "@swagger-api/apidom-parser-adapter-arazzo-json-1": "1.11.6", + "@swagger-api/apidom-parser-adapter-arazzo-yaml-1": "1.11.6", + "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "1.11.6", + "@swagger-api/apidom-parser-adapter-asyncapi-json-3": "1.11.6", + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "1.11.6", + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-3": "1.11.6", + "@swagger-api/apidom-parser-adapter-json": "1.11.6", + "@swagger-api/apidom-parser-adapter-openapi-json-2": "1.11.6", + "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "1.11.6", + "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "1.11.6", + "@swagger-api/apidom-parser-adapter-openapi-json-3-2": "1.11.6", + "@swagger-api/apidom-parser-adapter-openapi-yaml-2": "1.11.6", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "1.11.6", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "1.11.6", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-2": "1.11.6", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "1.11.6" } }, "node_modules/swagger-ui-react": { - "version": "5.32.14", - "resolved": "https://registry.npmjs.org/swagger-ui-react/-/swagger-ui-react-5.32.14.tgz", - "integrity": "sha512-6LAVBeC78DplbJ7kutm/YeBYo22nPzGOca4bIZAvQG4w2eSetnYDdazaUfY0qzQUlg/H90HnYZX3rg67EmENOw==", + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/swagger-ui-react/-/swagger-ui-react-5.33.0.tgz", + "integrity": "sha512-4CJm/NyzuVHYRH3rVWgMeBcsx4TKnSvSHf+hd5uWf6od6l7VbXED5fMhoPB7IkJe+SKnBm5uSxbomoLpXkoFXQ==", "dev": true, "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.27.1", "@scarf/scarf": "=1.4.0", + "@tanstack/react-virtual": "=3.14.10", "base64-js": "^1.5.1", "buffer": "^6.0.3", "classnames": "^2.5.1", @@ -8415,9 +8373,9 @@ "deep-extend": "0.6.0", "dompurify": "^3.4.13", "ieee754": "^1.2.1", - "immutable": "^4.3.9", + "immutable": "^5.1.9", "js-file-download": "^0.4.12", - "js-yaml": "=4.3.1", + "js-yaml": "=4.3.2", "lodash": "^4.18.1", "prop-types": "^15.8.1", "randexp": "^0.5.3", @@ -8430,12 +8388,11 @@ "react-redux": "^9.3.0", "react-syntax-highlighter": "^16.0.0", "redux": "^5.0.1", - "redux-immutable": "^4.0.0", "remarkable": "^2.0.1", "reselect": "^5.1.1", "serialize-error": "^8.1.0", "sha.js": "^2.4.12", - "swagger-client": "^3.38.0", + "swagger-client": "^3.38.2", "url-parse": "^1.5.10", "xml": "=1.0.1", "xml-but-prettier": "^1.0.1", @@ -8446,28 +8403,12 @@ "react-dom": ">=16.8.0 <20" } }, - "node_modules/swagger-ui-react/node_modules/js-yaml": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", - "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "node_modules/swagger-ui-react/node_modules/immutable": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz", + "integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/nodeca" - } - ], - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } + "license": "MIT" }, "node_modules/symbol-tree": { "version": "3.2.4", @@ -8500,11 +8441,14 @@ } }, "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.1.4.tgz", + "integrity": "sha512-9APumHG7r4yOk4X4WlkmE71aZcv1gvin1czO3OQ1U9iJcFA5Ja/ygyb0vPOVHTthFozUYs8CLoLUlM8grb2lTQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } }, "node_modules/tinyexec": { "version": "0.3.2", @@ -8530,16 +8474,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyrainbow": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", - "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/tldts": { "version": "7.4.11", "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.11.tgz", @@ -8936,9 +8870,9 @@ } }, "node_modules/undici": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", - "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", + "version": "7.30.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.30.0.tgz", + "integrity": "sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==", "dev": true, "license": "MIT", "engines": { @@ -9015,16 +8949,17 @@ } }, "node_modules/vite": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz", - "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.0.tgz", + "integrity": "sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "lightningcss": "^1.33.0", - "picomatch": "^4.0.5", - "postcss": "^8.5.26", - "rolldown": "~1.2.4", + "picomatch": "^4.0.7", + "postcss": "^8.5.28", + "rolldown": "~1.2.6", "tinyglobby": "^0.2.17" }, "bin": { @@ -9041,7 +8976,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.4.0 || ^0.5.0", + "@vitejs/devtools": "^0.7.1", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -9093,38 +9028,31 @@ } }, "node_modules/vitest": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz", - "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "4.1.11", - "@vitest/mocker": "4.1.11", - "@vitest/pretty-format": "4.1.11", - "@vitest/runner": "4.1.11", - "@vitest/snapshot": "4.1.11", - "@vitest/spy": "4.1.11", - "@vitest/utils": "4.1.11", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.1.0", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-5.0.1.tgz", + "integrity": "sha512-iA95lQbKEkvrtTkdAgnWbXfbipWiiWe/hDl2P5tMi6WFwD76G0NxXAGp/M9EOcYupeGJRr6wppMc7CoA41TQjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/mocker": "5.0.1", + "chai": "^6.2.2", + "es-module-lexer": "^2.3.2", + "expect-type": "^1.4.0", + "magic-string": "^1.2.3", + "obug": "^2.1.4", + "picomatch": "^4.0.7", + "std-env": "^4.2.0", + "tinybench": "6.1.4", + "tinyexec": "1.3.0", + "tinyglobby": "^0.2.17", "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^22.12.0 || ^24.0.0 || >=26.0.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -9132,16 +9060,16 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.11", - "@vitest/browser-preview": "4.1.11", - "@vitest/browser-webdriverio": "4.1.11", - "@vitest/coverage-istanbul": "4.1.11", - "@vitest/coverage-v8": "4.1.11", - "@vitest/ui": "4.1.11", + "@types/node": "^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "5.0.1", + "@vitest/browser-preview": "5.0.1", + "@vitest/browser-webdriverio": "^5.0.0-beta.5 || >=5.0.0", + "@vitest/coverage-istanbul": "5.0.1", + "@vitest/coverage-v8": "5.0.1", + "@vitest/ui": "5.0.1", "happy-dom": "*", "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + "vite": "^6.4.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "@edge-runtime/vm": { @@ -9182,6 +9110,16 @@ } } }, + "node_modules/vitest/node_modules/magic-string": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.1.tgz", + "integrity": "sha512-8lyCu36ErXR0J9uaGKlKQoiLZKmtI63YGLE8G2o9jyRPdr4X47LusSOwgOJOzcVtp81fTAAjxR7BwKz682Jhow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.6.0" + } + }, "node_modules/vitest/node_modules/tinyexec": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", diff --git a/package.json b/package.json index 7b2c2c0..4f5b36c 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "lint:fix": "eslint . --fix", "format": "prettier --write .", "format:check": "prettier --check .", + "verify": "npm run lint && npm run format:check && npm run test && npm run build", "prepublishOnly": "npm run lint && npm run format:check && npm test && npm run build" }, "peerDependencies": { @@ -86,10 +87,11 @@ "swagger-ui-react": "^5.32.14", "tsup": "^8.3.5", "typescript-eslint": "^8.62.1", - "vitest": "^4.1.9" + "vitest": "^5.0.1" }, "overrides": { - "esbuild": "^0.28.1" + "esbuild": "^0.28.1", + "js-yaml": "^4.3.2" }, "publishConfig": { "access": "public" diff --git a/repolinter.json b/repolinter.json index ba10430..4e64643 100644 --- a/repolinter.json +++ b/repolinter.json @@ -85,7 +85,9 @@ "rule": { "type": "file-existence", "options": { - "globsAny": [".github/workflows/"] + "globsAny": [ + ".github/workflows/" + ] } } }, @@ -137,7 +139,7 @@ "rule": { "type": "file-existence", "options": { - "globsAny": ["package.json"] + "globsAny": ["{,**/}package.json"] } } }, @@ -157,7 +159,7 @@ "rule": { "type": "file-existence", "options": { - "globsAny": ["pom.xml", "build.xml", "build.gradle"] + "globsAny": ["{,**/}pom.xml", "{,**/}build.xml", "{,**/}build.gradle"] } } }, @@ -167,7 +169,11 @@ "rule": { "type": "file-existence", "options": { - "globsAny": ["setup.py", "requirements.txt", "pyproject.toml"] + "globsAny": [ + "{,**/}setup.py", + "{,**/}requirements.txt", + "{,**/}pyproject.toml" + ] } } }, @@ -249,4 +255,4 @@ } } } -} +} \ No newline at end of file