Files
openobserve/.github/workflows/npm-update.yml

249 lines
11 KiB
YAML

name: Quarterly NPM Update
on:
schedule:
# Run at 9:00 AM UTC on the 1st of Jan, Apr, Jul and Oct
- cron: '0 9 1 */3 *'
workflow_dispatch: # Allow manual trigger
permissions:
contents: write
pull-requests: write
jobs:
update-dependencies:
name: Update NPM Dependencies
# Bumps the manifest, gates on the OSS license check, and opens the PR; type check, tests and build run as PR checks.
runs-on: ubuntu-latest
env:
# Shared by both npm-check-updates invocations below — they must stay
# identical, or the PR body would advertise upgrades the commit didn't make.
#
# `--reject typescript`: TypeScript 7 is the Go rewrite. Its package no
# longer exports the `./lib/tsc` subpath, and vue-tsc — the only tool that
# can type-check .vue SFCs — resolves exactly that path, so `type-check:app`
# dies with ERR_PACKAGE_PATH_NOT_EXPORTED before checking a single file.
# `--peer` cannot catch this: vue-tsc declares `typescript: >=5.0.0`, which
# 7.x satisfies. Bump TypeScript by hand, together with a vue-tsc release
# that supports it (5.x and 6.x are both fine today).
#
# The other rejected packages have majors that break the build; bump them by hand:
# monaco-editor (restricted import paths), @tanstack/vue-table (v9 drops useVueTable),
# eslint (v10 drops @eslint/js, plugins need eslint 9), js-yaml (v5 drops the default export),
# vite (v8 swaps esbuild for rolldown), jsdom (v30 needs a newer Node),
# gridstack (13.1+ reorders widget DOM nodes that Vue's v-for owns), vue-router (v5), marked (v18), msw (v3),
# js-beautify (v2), cypress (v16), @cypress/vue (v7), happy-dom (v21), c8 (v12), @types/node (v26), sass-loader (v17): unverified majors.
# vue-tsc (3.3.10+ crashes type-check:app with RangeError: Map maximum size exceeded).
NCU_ARGS: --target latest --peer --reject typescript,monaco-editor,@tanstack/vue-table,eslint,js-yaml,vite,jsdom,@types/jsdom,@eslint/css,eslint-plugin-cypress,vite-plugin-istanbul,unplugin-icons,vue-tsc,gridstack,vue-router,marked,msw,js-beautify,cypress,@cypress/vue,happy-dom,c8,@types/node,sass-loader
steps:
- name: Clone the current repo
uses: actions/checkout@v5
with:
fetch-depth: 0
# A PAT, not GITHUB_TOKEN. Pushes made with GITHUB_TOKEN do not start
# workflow runs, so the resulting PR would sit there with zero checks —
# and the whole point of this job is to hand verification to PR CI.
token: ${{ secrets.ORG_ADMIN_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: 24
- name: Configure Git
env:
GH_TOKEN: ${{ secrets.ORG_ADMIN_TOKEN }}
run: |
# Commit as the PAT owner so the commit and the PR share one author.
read -r LOGIN ID < <(gh api user --jq '"\(.login) \(.id)"')
if [ -z "$LOGIN" ] || [ -z "$ID" ]; then
echo "::error::gh api user returned no identity; check that ORG_ADMIN_TOKEN is valid"
exit 1
fi
git config user.name "$LOGIN"
git config user.email "${ID}+${LOGIN}@users.noreply.github.com"
- name: Create update branch
run: |
BRANCH_NAME="chore/npm-update-$(date +%Y%m%d)"
git checkout -b "$BRANCH_NAME"
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV
- name: Check for available updates
id: check_updates
run: |
cd web
# Snapshot the pre-update manifest so the PR body can show old → new.
cp package.json "$RUNNER_TEMP/package.json.before"
npx --yes npm-check-updates $NCU_ARGS --jsonUpgraded \
> "$RUNNER_TEMP/upgrades.json"
if ! jq -e 'type == "object"' "$RUNNER_TEMP/upgrades.json" > /dev/null; then
echo "npm-check-updates did not return valid JSON:"
cat "$RUNNER_TEMP/upgrades.json"
exit 1
fi
if [ "$(jq 'length' "$RUNNER_TEMP/upgrades.json")" -gt 0 ]; then
echo "has_updates=true" >> $GITHUB_OUTPUT
echo "Packages to upgrade:"
jq '.' "$RUNNER_TEMP/upgrades.json"
else
echo "has_updates=false" >> $GITHUB_OUTPUT
echo "No updates available"
fi
- name: Update dependencies
if: steps.check_updates.outputs.has_updates == 'true'
run: |
cd web
echo "Updating dependencies..."
# Apply the same upgrades that were just reported.
npx --yes npm-check-updates -u $NCU_ARGS
# Locked typescript-eslint family versions conflict with the bumped peer ranges (ERESOLVE); re-resolve them.
node -e '
const fs = require("fs");
const lock = JSON.parse(fs.readFileSync("package-lock.json", "utf8"));
for (const k of Object.keys(lock.packages)) {
if (/node_modules\/(@typescript-eslint\/|typescript-eslint$)/.test(k)) delete lock.packages[k];
}
fs.writeFileSync("package-lock.json", JSON.stringify(lock, null, 2) + "\n");
'
# Regenerate package-lock.json only — node_modules is never needed
# here, and skipping the install keeps this job to well under a minute.
npm install --package-lock-only --ignore-scripts
echo "Dependencies updated successfully"
- name: Check licenses
if: steps.check_updates.outputs.has_updates == 'true'
run: |
cd web
# Keep the allow-list identical to .github/workflows/js-license-checker.yml.
npm ci --ignore-scripts
npx --yes license-checker --production --json --onlyAllow="MIT;ISC;Apache-2.0;BSD;MPL-2.0;Unlicense;CC0-1.0;CC-BY-4.0;Artistic-2.0;UNLICENSED;Python-2.0;BlueOak-1.0.0" > /dev/null
- name: Generate update summary
if: steps.check_updates.outputs.has_updates == 'true'
id: summary
run: |
# Map of package -> previous range, from the pre-update manifest.
BEFORE=$(jq -c '(.dependencies // {}) + (.devDependencies // {})' \
"$RUNNER_TEMP/package.json.before")
UPDATED_PACKAGES=$(jq -r --argjson before "$BEFORE" \
'to_entries[] | "- **\(.key)**: \($before[.key] // "n/a") → \(.value)"' \
"$RUNNER_TEMP/upgrades.json")
cat > "$RUNNER_TEMP/pr_body.md" <<EOF
## 📦 Quarterly NPM Dependencies Update
This automated PR updates \`web/\` npm packages to their latest versions
and regenerates \`package-lock.json\`.
### Updated Packages
$UPDATED_PACKAGES
### Not upgraded here
- **typescript** — excluded on purpose. TypeScript 7 (the Go rewrite)
drops the \`./lib/tsc\` subpath that vue-tsc needs, so it breaks
\`type-check:app\` outright. Bump it by hand alongside a vue-tsc that
supports it. See \`NCU_ARGS\` in \`.github/workflows/npm-update.yml\`.
### Validation
This workflow does **not** run any checks — type check, unit tests and
build all run as normal PR checks below. Review the results there before
merging; a red check usually means one of the upgrades above needs to be
pinned back or accompanied by a code change.
---
🤖 This PR was automatically created by the Quarterly NPM Update workflow
EOF
echo "summary_created=true" >> $GITHUB_OUTPUT
- name: Commit changes
if: steps.check_updates.outputs.has_updates == 'true'
run: |
# Scope the check to the manifest files so stray artifacts never
# trigger an empty dependency PR.
if git diff --quiet -- web/package.json web/package-lock.json; then
echo "No dependency changes to commit"
echo "changes_committed=false" >> $GITHUB_ENV
else
git add web/package.json web/package-lock.json
git commit \
-m "chore(deps): update web npm dependencies ($(date +%Y-%m-%d))" \
-m "- Updated packages to their latest versions" \
-m "- Regenerated package-lock.json" \
-m "🤖 Automated update by GitHub Actions"
# The branch name is date-stamped, so a second run on the same day
# (a re-run, or a manual dispatch after the scheduled one) finds the
# earlier run's branch already on the remote. This branch is entirely
# machine-owned and rebuilt from main every run, so replacing it is
# correct — a plain push would just fail non-fast-forward.
if git ls-remote --exit-code --heads origin "$BRANCH_NAME" > /dev/null 2>&1; then
echo "Branch $BRANCH_NAME already exists on the remote — replacing it"
git fetch origin "+refs/heads/$BRANCH_NAME:refs/remotes/origin/$BRANCH_NAME"
git push --force-with-lease origin "$BRANCH_NAME"
else
git push origin "$BRANCH_NAME"
fi
echo "changes_committed=true" >> $GITHUB_ENV
fi
- name: Create Pull Request
if: steps.check_updates.outputs.has_updates == 'true' && env.changes_committed == 'true'
env:
# Same PAT as the checkout — the PR author is whoever owns this token.
GH_TOKEN: ${{ secrets.ORG_ADMIN_TOKEN }}
run: |
TITLE="chore(deps): update web npm dependencies ($(date +%Y-%m-%d))"
# If an earlier run today already opened a PR for this branch, the
# force-push above updated it in place — `gh pr create` would fail with
# "a pull request for branch ... already exists". Refresh it instead.
PR_URL=$(gh pr list --head "$BRANCH_NAME" --state open \
--json url --jq '.[0].url // empty')
if [ -n "$PR_URL" ]; then
gh pr edit "$PR_URL" \
--title "$TITLE" \
--body-file "$RUNNER_TEMP/pr_body.md"
echo "Refreshed existing $PR_URL"
else
PR_URL=$(gh pr create \
--title "$TITLE" \
--body-file "$RUNNER_TEMP/pr_body.md" \
--base main \
--head "$BRANCH_NAME")
echo "Opened $PR_URL"
fi
# Labels and assignee are applied separately and best-effort. Passing
# them to `gh pr create` makes a single bad value abort the whole
# command, leaving the already-pushed branch behind with no PR at all.
gh pr edit "$PR_URL" \
--add-assignee "bjp232004" \
--add-label "dependencies" \
--add-label "auto-generated" \
|| echo "::warning::Could not apply assignee/labels to $PR_URL"
- name: No updates needed
if: steps.check_updates.outputs.has_updates == 'false'
run: |
echo "✅ All npm packages are up to date. No PR needed."