From 32df2f8572dfc9f8c01138d47cdd09534f817ef1 Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Tue, 13 Oct 2026 23:38:39 +0300 Subject: [PATCH 1/2] ci(deps): automate weekly dependency updates with Renovate --- .github/workflows/ci.yml | 42 +++++++++++++++--- .github/workflows/renovate.yml | 63 ++++++++++++++++++++++++++ CONTRIBUTING.md | 81 ++++++++++++++++++++++++++++++++-- renovate.json | 54 +++++++++++++++++++++++ tests/consumer-types/check.mjs | 5 +-- 5 files changed, 234 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/renovate.yml create mode 100644 renovate.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d55be8c..f433c15 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,7 @@ on: - 'feature/**' pull_request: branches: + - main - develop - 'feature/**' workflow_call: @@ -26,13 +27,15 @@ jobs: name_suffix: ${{ steps.set.outputs.name_suffix }} steps: - id: set + env: + FULL_MATRIX: ${{ inputs.full || (github.event_name == 'pull_request' && github.base_ref == 'main') }} run: | - if [[ "${{ inputs.full }}" == "true" ]]; then - echo 'matrix={"os":["ubuntu-latest","windows-latest"],"node":[18,20,22]}' >> $GITHUB_OUTPUT - echo 'name_suffix=(full matrix)' >> $GITHUB_OUTPUT + if [[ "$FULL_MATRIX" == "true" ]]; then + echo 'matrix={"os":["ubuntu-latest","windows-latest"],"node":[18,20,22]}' >> "$GITHUB_OUTPUT" + echo 'name_suffix=(full matrix)' >> "$GITHUB_OUTPUT" else - echo 'matrix={"os":["ubuntu-latest"],"node":[20]}' >> $GITHUB_OUTPUT - echo 'name_suffix=' >> $GITHUB_OUTPUT + echo 'matrix={"os":["ubuntu-latest"],"node":[20]}' >> "$GITHUB_OUTPUT" + echo 'name_suffix=' >> "$GITHUB_OUTPUT" fi build-and-test: @@ -84,3 +87,32 @@ jobs: with: name: coverage-${{ matrix.os }}-node${{ matrix.node }} path: coverage + + renovate-config: + name: Validate Renovate configuration + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: 24 + + - name: Validate configuration + run: npx --yes --package renovate@44.155.8 -- renovate-config-validator --strict renovate.json + + ci-success: + name: CI passed + if: ${{ always() }} + needs: [compute-matrix, build-and-test, renovate-config] + runs-on: ubuntu-latest + permissions: {} + steps: + - name: Require all checks to pass + env: + CHECKS_PASSED: ${{ !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') && !contains(needs.*.result, 'skipped') }} + run: test "$CHECKS_PASSED" = true diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml new file mode 100644 index 0000000..e09e815 --- /dev/null +++ b/.github/workflows/renovate.yml @@ -0,0 +1,63 @@ +name: Update Dependencies + +on: + schedule: + - cron: '15 2 * * 0' + timezone: America/Los_Angeles + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: renovate-dependencies + cancel-in-progress: false + +jobs: + renovate: + name: Create or update dependency PR + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - name: Check GitHub App configuration + env: + APP_CLIENT_ID: ${{ vars.RENOVATE_APP_CLIENT_ID }} + APP_PRIVATE_KEY: ${{ secrets.RENOVATE_APP_PRIVATE_KEY }} + run: | + if [[ -z "$APP_CLIENT_ID" || -z "$APP_PRIVATE_KEY" ]]; then + echo '::error::Set RENOVATE_APP_CLIENT_ID (Actions variable) and RENOVATE_APP_PRIVATE_KEY (Actions secret). See CONTRIBUTING.md.' + exit 1 + fi + + - name: Create repository-scoped GitHub App token + id: app-token + uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0 + with: + # Omitting owner/repositories scopes the shared app's token to this repository. + client-id: ${{ vars.RENOVATE_APP_CLIENT_ID }} + private-key: ${{ secrets.RENOVATE_APP_PRIVATE_KEY }} + + - name: Resolve bot identity + id: bot + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + APP_SLUG: ${{ steps.app-token.outputs.app-slug }} + run: | + BOT_LOGIN="${APP_SLUG}[bot]" + BOT_ID=$(gh api "/users/${BOT_LOGIN}" --jq .id) + printf 'username=%s\n' "$BOT_LOGIN" >> "$GITHUB_OUTPUT" + printf 'git-author=%s <%s+%s@users.noreply.github.com>\n' "$BOT_LOGIN" "$BOT_ID" "$BOT_LOGIN" >> "$GITHUB_OUTPUT" + + - name: Run Renovate + uses: renovatebot/github-action@f0f41e5f2e706165f6814b1d0f0d0e319de7d1d9 # v46.3.8 + with: + token: ${{ steps.app-token.outputs.token }} + renovate-version: 44.155.8 + env: + RENOVATE_PLATFORM: github + RENOVATE_REPOSITORIES: ${{ github.repository }} + RENOVATE_AUTODISCOVER: 'false' + RENOVATE_ONBOARDING: 'false' + RENOVATE_REQUIRE_CONFIG: required + RENOVATE_USERNAME: ${{ steps.bot.outputs.username }} + RENOVATE_GIT_AUTHOR: ${{ steps.bot.outputs.git-author }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cb33482..d050737 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,6 +16,7 @@ Please also read and follow our [Code of Conduct](./CODE_OF_CONDUCT.md). - Commit messages (Conventional Commits) - What affects versioning (SemVer policy) - Release process (release-it + GitHub Actions) +- Automated dependency updates - Code style and quality - Local development - Running tests @@ -40,6 +41,7 @@ We follow a simplified GitFlow: Typical flows: - New work: branch from `develop` into `feature/`, open PR into `develop`. - Release: open a pull request from the chosen base branch (typically `develop`) into `main`. After merge, CI publishes a new version and npm package. Then merge `main` back into `develop` to sync. +- Dependency updates: Renovate creates `deps/update-dependencies` from `main` and opens a PR back into `main`. Note: We do not use `release/*` or `hotfix/*` branches. @@ -97,8 +99,8 @@ Notes: We automate releases with [release-it](https://github.com/release-it/release-it) and GitHub Actions. Branches/Triggers: -- Pull requests into `main`: CI runs checks and may run a release dry-run (no tag, no publish) to validate versioning and the - generated notes. +- Pull requests into `main`: CI runs the full OS/Node matrix, including published consumer declarations, and validates + the Renovate configuration. The `CI passed` check requires every job to succeed. - `main` (on merge): CI runs a Release Publish that will: - determine the next version from commits (SemVer + Conventional Commits), - update `CHANGELOG.md`, @@ -115,6 +117,79 @@ Important: - After a successful release, merge `main` back into `develop` to keep versions and files in sync. - We do not use `release/*` or `hotfix/*` branches. +## Automated dependency updates + +The **Update Dependencies** workflow is scheduled every Sunday at 02:15 in `America/Los_Angeles`. +GitHub handles daylight saving changes in that timezone and can delay scheduled runs. To run it at any other time, +open **Actions → Update Dependencies → Run workflow**, select `main`, and start the workflow. +The weekly schedule does not restrict manual runs. + +Renovate reads `renovate.json` from the default branch (`main`) and manages one branch, `deps/update-dependencies`, +with a PR into `main`. It updates only `@addon-core/browser` in `dependencies` and `@types/chrome` in `devDependencies`, +together with `package-lock.json`. Stable updates of any size share that PR, including when only one package changes. +The Chrome peer dependency remains `*`; unrelated direct dependencies and standalone lockfile maintenance are disabled. +Transitive lockfile changes required by the selected updates are still included. + +The bot uses `fix(deps)` commits, so an approved dependency-only update produces a **patch** release of Storage, +including Chrome-types-only updates and updates to a new upstream major version. Keep the `fix(deps): ...` title when +squashing; do not change it to `chore`, `feat`, or add a breaking marker. Other commits included in a release continue +to follow the normal release policy. Review upstream compatibility before merging. + +PR creation or updates automatically trigger CI through the GitHub App's token. **Merging is always manual.** Failed +checks leave the PR open; they do not update `main` or publish a release. Fix the incompatibility, rerun a transiently +failed check, or let a later weekly/manual run update the PR to a newer dependency version. If you add your own commit +to the bot branch, Renovate stops updating it automatically to preserve your changes. If no update or rebase is needed, +a manual run does not create an empty PR or rerun unchanged CI; use Actions **Re-run jobs** for that. + +After a successful merge, the existing Release workflow runs its checks, publishes Storage, and synchronizes `main` +back into `develop`. + +### One-time GitHub setup + +1. Create one shared GitHub App in the `addon-stack` organization, for example `addon-stack-dependency-updates`. + It does not need webhooks or user authorization. Install it using **Only select repositories**, starting with + `storage`; add other package repositories to the same installation as their workflows are introduced. +2. Give the app these repository permissions: **Contents**, **Pull requests**, **Issues**, **Checks**, and + **Commit statuses**: read and write; **Administration**: read; **Metadata**: read (automatic). + For an organization app, grant **Members**: read. Workflow-file writes, merge-queue access, and Dependabot-alert + access are not needed for this configuration. Do not give the dependency bot a branch-protection bypass. +3. Under the organization's **Settings → Secrets and variables → Actions**, create: + - Organization variable `RENOVATE_APP_CLIENT_ID`: the app's **Client ID**. + - Organization secret `RENOVATE_APP_PRIVATE_KEY`: the full PEM private key generated for the app. + Restrict access to the selected package repositories for both. The workflow reads organization-level values through + the same `vars` and `secrets` expressions; repository-level values also work if organization-level sharing is + unavailable. It creates a short-lived installation token scoped to the current repository. No PAT or npm publishing + token is needed by Renovate. Release publishing keeps its existing authentication. +4. Merge the workflow, `renovate.json`, and CI changes into `main`. Scheduled runs and the manual workflow entry require + the workflow file on the default branch. Do not also enable the hosted Mend Renovate App for this repository. +5. Run **Update Dependencies** manually and inspect the first PR. Require **CI passed** in the `main` branch + rules, require the branch to be up to date, and keep maintainer review/manual merging. Select the PR workflow's check, + not the nested check from a post-merge release. A failed, cancelled, or skipped prerequisite makes this check fail. +6. Account for the existing release workflow when configuring branch rules: `release-it` pushes its version/changelog + commit directly to `main`, and the sync job writes to `develop`. A rule requiring PRs for every write must have a + narrowly scoped exception for the release automation. The dependency app and its credentials are not used for + release publishing. Do not activate a rule that blocks these release writes without configuring a release writer. + +### Reusing the app in other packages + +Each package can keep its own workflow, schedule, dependency rules, CI, and release process. The shared app supplies +the bot identity and authentication. To onboard another package, add the repository to the app installation and to +the access lists for the organization variable and secret, then add its workflow and `renovate.json`. + +The current token action intentionally omits `owner` and `repositories`, which limits each generated token to the +repository running the workflow. `RENOVATE_REPOSITORIES` also targets that repository only. Concurrency groups are +repository-scoped, so matching workflow names in different packages do not block one another. + +The private key itself can mint tokens for all repositories accessible to the app. Share it only among repositories +with the same trusted maintainers; use separate apps for different trust boundaries or permission requirements. + +Renovate and the GitHub App token action are pinned in `.github/workflows/renovate.yml`. When upgrading Renovate itself, +also update the matching validator version in `.github/workflows/ci.yml`. + +References: [Renovate GitHub Action](https://github.com/renovatebot/github-action), +[GitHub App authentication](https://docs.renovatebot.com/modules/platform/github/), and +[GitHub workflow schedules](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#schedule). + ## Code style and quality We use [ESLint](https://eslint.org/) with TypeScript support and [ESLint Stylistic](https://eslint.style/). The single configuration is `eslint.config.js`. @@ -249,7 +324,7 @@ this checkout's Git state. Formatting checks also verify that a second pass make ## Pull requests Checklist for contributors: -- [ ] Branch from `develop`. +- [ ] Branch from `develop`, except for the automated dependency PR described above. - [ ] Follow code style and run locally: - `npm run lint && npm run typecheck && npm run test:all && npm run build` - [ ] Write or update tests when applicable. diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..b6bd15a --- /dev/null +++ b/renovate.json @@ -0,0 +1,54 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "baseBranchPatterns": [ + "main" + ], + "branchPrefix": "deps/", + "enabledManagers": [ + "npm" + ], + "includePaths": [ + "package.json", + "package-lock.json" + ], + "automerge": false, + "dependencyDashboard": false, + "ignoreUnstable": true, + "prCreation": "immediate", + "rebaseWhen": "behind-base-branch", + "semanticCommits": "enabled", + "semanticCommitType": "fix", + "semanticCommitScope": "deps", + "separateMajorMinor": false, + "separateMinorPatch": false, + "lockFileMaintenance": { + "enabled": false + }, + "vulnerabilityAlerts": { + "enabled": false + }, + "packageRules": [ + { + "description": "Keep dependency updates limited to the explicitly enabled packages.", + "matchPackageNames": [ + "*" + ], + "enabled": false + }, + { + "matchPackageNames": [ + "@addon-core/browser", + "@types/chrome" + ], + "matchDepTypes": [ + "dependencies", + "devDependencies" + ], + "enabled": true, + "rangeStrategy": "bump", + "groupName": "dependencies", + "groupSlug": "update-dependencies", + "groupSingleUpdates": true + } + ] +} diff --git a/tests/consumer-types/check.mjs b/tests/consumer-types/check.mjs index 1a1b915..aa3adc4 100644 --- a/tests/consumer-types/check.mjs +++ b/tests/consumer-types/check.mjs @@ -49,9 +49,8 @@ try { type: "module", dependencies: { "@addon-core/storage": `file:${archive}`, - "@types/chrome": readJson(join(root, "node_modules/@addon-core/browser/package.json")).dependencies[ - "@types/chrome" - ], + "@addon-core/browser": installedVersion("@addon-core/browser"), + "@types/chrome": installedVersion("@types/chrome"), react: react === "19" ? installedVersion("react") : "18.3.1", "react-dom": react === "19" ? installedVersion("react-dom") : "18.3.1", "@types/react": react === "19" ? installedVersion("@types/react") : "^18.3.0", From d34995f8cb08908a657170ec40324af674d3b9a4 Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Wed, 14 Oct 2026 00:21:12 +0300 Subject: [PATCH 2/2] ci: update action runtimes and pin Ubuntu runners --- .github/workflows/ci.yml | 20 ++++++++++---------- .github/workflows/release.yml | 6 +++--- .github/workflows/renovate.yml | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f433c15..065d4b7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ on: jobs: compute-matrix: name: Compute matrix - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: matrix: ${{ steps.set.outputs.matrix }} name_suffix: ${{ steps.set.outputs.name_suffix }} @@ -31,10 +31,10 @@ jobs: FULL_MATRIX: ${{ inputs.full || (github.event_name == 'pull_request' && github.base_ref == 'main') }} run: | if [[ "$FULL_MATRIX" == "true" ]]; then - echo 'matrix={"os":["ubuntu-latest","windows-latest"],"node":[18,20,22]}' >> "$GITHUB_OUTPUT" + echo 'matrix={"os":["ubuntu-24.04","windows-latest"],"node":[18,20,22]}' >> "$GITHUB_OUTPUT" echo 'name_suffix=(full matrix)' >> "$GITHUB_OUTPUT" else - echo 'matrix={"os":["ubuntu-latest"],"node":[20]}' >> "$GITHUB_OUTPUT" + echo 'matrix={"os":["ubuntu-24.04"],"node":[20]}' >> "$GITHUB_OUTPUT" echo 'name_suffix=' >> "$GITHUB_OUTPUT" fi @@ -49,7 +49,7 @@ jobs: matrix: ${{ fromJSON(needs.compute-matrix.outputs.matrix) }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Configure git EOL (Windows) if: runner.os == 'Windows' @@ -58,7 +58,7 @@ jobs: git config core.eol lf - name: Use Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: ${{ matrix.node }} cache: npm @@ -83,22 +83,22 @@ jobs: - name: Upload coverage artifact if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: coverage-${{ matrix.os }}-node${{ matrix.node }} path: coverage renovate-config: name: Validate Renovate configuration - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: read steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Use Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: 24 @@ -109,7 +109,7 @@ jobs: name: CI passed if: ${{ always() }} needs: [compute-matrix, build-and-test, renovate-config] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: {} steps: - name: Require all checks to pass diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index faf12ab..3c5fd72 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,17 +33,17 @@ jobs: release: name: Release & Publish - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: ci steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 fetch-tags: true - name: Use Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: 22 cache: 'npm' diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml index e09e815..be18985 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yml @@ -16,7 +16,7 @@ concurrency: jobs: renovate: name: Create or update dependency PR - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 45 steps: - name: Check GitHub App configuration