From 550a350734c2d004e692f8159a346e98fb4c74ee Mon Sep 17 00:00:00 2001 From: Wikid82 Date: Sun, 16 Aug 2026 22:59:08 -0400 Subject: [PATCH 1/2] ci: add weekly development-to-main promotion workflow --- .github/workflows/promote-dev-to-main.yml | 212 ++++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 .github/workflows/promote-dev-to-main.yml diff --git a/.github/workflows/promote-dev-to-main.yml b/.github/workflows/promote-dev-to-main.yml new file mode 100644 index 0000000..f3cd6ca --- /dev/null +++ b/.github/workflows/promote-dev-to-main.yml @@ -0,0 +1,212 @@ +name: Promote development to main + +# Weekly PR development -> main, human-reviewed (unlike sync-development.yml's +# direct development -> development push). Gated on CI having actually passed +# for development's current HEAD, so a broken development doesn't get promoted just +# because a week went by. + +on: + schedule: + - cron: '23 12 * * 1' # Mondays ~12:23 UTC, off the top of the hour + workflow_dispatch: + inputs: + reason: + description: 'Why are you running this manually?' + required: true + default: 'Ad-hoc promotion request' + skip_health_check: + description: 'Skip the CI health check on development?' + required: false + type: boolean + default: false + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + +env: + SOURCE_BRANCH: development + TARGET_BRANCH: main + +permissions: + contents: read + pull-requests: write + +jobs: + check-development-health: + runs-on: ubuntu-latest + outputs: + is_healthy: ${{ steps.check.outputs.is_healthy }} + run_url: ${{ steps.check.outputs.run_url }} + steps: + - name: Check CI status on development HEAD + id: check + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + SKIP: ${{ inputs.skip_health_check }} + with: + script: | + if (process.env.SKIP === 'true') { + core.info('Health check skipped by workflow_dispatch input'); + core.setOutput('is_healthy', 'true'); + core.setOutput('run_url', 'N/A - check skipped'); + return; + } + + const { data: branch } = await github.rest.repos.getBranch({ + owner: context.repo.owner, + repo: context.repo.repo, + branch: 'development', + }); + const headSha = branch.commit.sha; + core.info(`development HEAD: ${headSha}`); + + // sync-development.yml never creates a new commit — development is always + // either untouched or fast-forwarded/reset to development's exact + // SHA. So a completed CI run on 'development' at this same SHA is + // equally valid proof of health, and covers two gaps in checking + // 'development' alone: (1) if development was synced with the GITHUB_TOKEN + // fallback (doesn't trigger downstream workflows), CI never ran on + // development at all; (2) if development already matched development when + // the sync ran, no push happened, so no development-branch run exists + // for this SHA even with a trigger token configured. + let run = null; + for (let attempt = 1; attempt <= 4; attempt += 1) { + const { data } = await github.rest.actions.listWorkflowRuns({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'ci.yml', + status: 'completed', + per_page: 20, + }); + run = data.workflow_runs.find( + (r) => r.head_sha === headSha && (r.head_branch === 'development' || r.head_branch === 'development'), + ); + if (run) break; + core.info(`No completed CI run for development HEAD yet (attempt ${attempt}/4), waiting...`); + await new Promise((resolve) => setTimeout(resolve, 15000)); + } + + if (!run) { + core.setOutput('is_healthy', 'false'); + core.setOutput('run_url', `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/workflows/ci.yml`); + core.warning('No completed CI run found for development HEAD — blocking promotion'); + return; + } + + core.setOutput('is_healthy', run.conclusion === 'success' ? 'true' : 'false'); + core.setOutput('run_url', run.html_url); + if (run.conclusion !== 'success') { + core.warning(`CI on development HEAD concluded '${run.conclusion}' — blocking promotion`); + } + + create-promotion-pr: + needs: check-development-health + if: needs.check-development-health.outputs.is_healthy == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + ref: ${{ env.TARGET_BRANCH }} + fetch-depth: 0 + + - name: Check for differences + id: diff + run: | + git fetch origin "${{ env.SOURCE_BRANCH }}" + AHEAD=$(git rev-list --count "origin/${{ env.TARGET_BRANCH }}..origin/${{ env.SOURCE_BRANCH }}") + echo "development is $AHEAD commits ahead of main" + echo "ahead=$AHEAD" >> "$GITHUB_OUTPUT" + + - name: Generate commit summary + if: steps.diff.outputs.ahead != '0' + id: commits + run: | + { + echo "log<> "$GITHUB_OUTPUT" + + - name: Create or update promotion PR + if: steps.diff.outputs.ahead != '0' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + COMMIT_LOG: ${{ steps.commits.outputs.log }} + TRIGGER_REASON: ${{ inputs.reason || 'Scheduled weekly promotion' }} + RUN_URL: ${{ needs.check-development-health.outputs.run_url }} + with: + script: | + const source = process.env.SOURCE_BRANCH || 'development'; + const target = process.env.TARGET_BRANCH || 'main'; + + const { data: existing } = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + head: `${context.repo.owner}:development`, + base: 'main', + }); + + const date = new Date().toISOString().slice(0, 10); + const body = `## Promote development to main + + **Date:** ${date} + **Trigger:** ${process.env.TRIGGER_REASON} + **CI on development HEAD:** ${process.env.RUN_URL} + + ### Commits being promoted + \`\`\` + ${process.env.COMMIT_LOG} + \`\`\` + + ## Merge instructions — important + + **Use "Create a merge commit", not squash or rebase.** Squashing collapses every + \`feat:\`/\`fix:\` commit into one bullet-list body, which release-please can't parse — + version bumps and changelog entries silently stop working. + + --- + _Opened automatically by [Promote development to main](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})._ + `; + + if (existing.length > 0) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: existing[0].number, + body: `Nightly has moved on since this PR opened. New commits may be included.\n\n_Triggered by [this run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})._`, + }); + core.info(`Updated existing PR #${existing[0].number}`); + return; + } + + const pr = await github.rest.pulls.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: `chore: promote development to main (${date})`, + head: 'development', + base: 'main', + body, + }); + core.info(`Created PR #${pr.data.number}: ${pr.data.html_url}`); + + for (const label of ['automated', 'promotion']) { + try { + await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label }); + } catch { + await github.rest.issues.createLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: label, + color: label === 'automated' ? '0e8a16' : '5319e7', + }); + } + } + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.data.number, + labels: ['automated', 'promotion'], + }); From 4974efa5162def29c4836c51c73b2eaaf9fee70c Mon Sep 17 00:00:00 2001 From: Wikid82 Date: Wed, 19 Aug 2026 23:06:58 -0400 Subject: [PATCH 2/2] chore: bump @types/vscode to ^1.134.0 and update dependencies --- package-lock.json | 63 ++++++++++++++++++++++++++++------------------- package.json | 2 +- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6b92776..7ba4c70 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.5.0", "devDependencies": { "@types/node": "^26.2.0", - "@types/vscode": "^1.125.0", + "@types/vscode": "^1.134.0", "@vscode/vsce": "^3.9.2", "esbuild": "^0.28.2", "typescript": "~6.0.3" @@ -82,6 +82,16 @@ "node": ">=22.0.0" } }, + "node_modules/@azure/core-process": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@azure/core-process/-/core-process-1.0.0.tgz", + "integrity": "sha512-/shnJ+ooO8WPxDhPEeI/2oRQuubn16gZ6CvlbpWbEswZfzwI9tI/sMAHmF3x1LuQ9yZYXfLW3TjzGMLEC5blKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.0.0" + } + }, "node_modules/@azure/core-rest-pipeline": { "version": "1.25.0", "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.25.0.tgz", @@ -130,26 +140,27 @@ } }, "node_modules/@azure/identity": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.13.1.tgz", - "integrity": "sha512-5C/2WD5Vb1lHnZS16dNQRPMjN6oV/Upba+C9nBIs15PmOi6A3ZGs4Lr2u60zw4S04gi+u3cEXiqTVP7M4Pz3kw==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.13.2.tgz", + "integrity": "sha512-NXL2/pCJctLxgw8bvrwwgge743kEq8LBT+O1pmV0vyUwetzFPH9auP6jhkU/cgZCPPtWoewAe3ncaGCgPo07fA==", "dev": true, "license": "MIT", "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-auth": "^1.9.0", "@azure/core-client": "^1.9.2", + "@azure/core-process": "^1.0.0", "@azure/core-rest-pipeline": "^1.17.0", "@azure/core-tracing": "^1.0.0", "@azure/core-util": "^1.11.0", "@azure/logger": "^1.0.0", "@azure/msal-browser": "^5.5.0", - "@azure/msal-node": "^5.1.0", + "@azure/msal-node": "^5.1.5", "open": "^10.1.0", "tslib": "^2.2.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=22.0.0" } }, "node_modules/@azure/logger": { @@ -167,22 +178,22 @@ } }, "node_modules/@azure/msal-browser": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.18.0.tgz", - "integrity": "sha512-SPTeHYZghdEdRddJzNjhH+CI5MSQtquNYwGJnYXfOHIBRXCmrWimBS85OhwXpXFIlrCtNTbBPm5mPAWRNEoktA==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.19.0.tgz", + "integrity": "sha512-DHe9iRcyByGJuLPkl0K31a1JjOdRY2zX38Q07mQpSbR8zOj1EIgsWfTXhSQVyyijUxlcofVy/br7qWJbrMwVXQ==", "dev": true, "license": "MIT", "dependencies": { - "@azure/msal-common": "16.12.0" + "@azure/msal-common": "16.13.0" }, "engines": { "node": ">=0.8.0" } }, "node_modules/@azure/msal-common": { - "version": "16.12.0", - "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.12.0.tgz", - "integrity": "sha512-hgLgfRdbG2AmhXPygebf1KYJEvse86+ZZLWufdiTKaGRYEUqOzHdlf6AS1IiuUCHWbynkgbHc451jSNkbfhWlg==", + "version": "16.13.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.13.0.tgz", + "integrity": "sha512-rOAy0KUcyBbdwVJ+f3uPpthXatFLLZN+/KWAsTLzk1aB23Xl9DRmmXYwSvBFOZyXj4jUQQ5FKxxRkhAFW1fOow==", "dev": true, "license": "MIT", "engines": { @@ -190,13 +201,13 @@ } }, "node_modules/@azure/msal-node": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-5.5.0.tgz", - "integrity": "sha512-A/2WIsuH0vsC6JVkkafjS4kHpi2LDR4AzDT0kJ+oIRtXYeYtvGQ2pwN2X88thQPhSek+82ela3MprsKXWQRrhQ==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-5.6.0.tgz", + "integrity": "sha512-uFY9NxrWHw8PwZx7gAX6PDn+9vdfS05+levc/kwkx77IkjfaldnQbbcQzzDIZ5Hq5Zdr6/z92oAIoRWKp6MnOA==", "dev": true, "license": "MIT", "dependencies": { - "@azure/msal-common": "16.12.0", + "@azure/msal-common": "16.13.0", "jsonwebtoken": "^9.0.0" }, "engines": { @@ -1006,9 +1017,9 @@ "license": "MIT" }, "node_modules/@types/vscode": { - "version": "1.125.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.125.0.tgz", - "integrity": "sha512-0icm/ZQAaism87P0ekHqi4/Ju9du+Tm0RUW+y7vqRsxY2cY0FNRX1nAnaW7nT6npPt2tfHiheZ55Zm9UhqonFA==", + "version": "1.134.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.134.0.tgz", + "integrity": "sha512-NDEu0hg4sF7+vvFsADsktqUJ6f80LHSZvVK2Ovo1XiQ0/VHck1O3zst+ZZyVA/uvz6vo6LcuoqU2q48YMqOwWw==", "dev": true, "license": "MIT" }, @@ -1075,9 +1086,9 @@ } }, "node_modules/@vscode/vsce-sign": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@vscode/vsce-sign/-/vsce-sign-2.0.9.tgz", - "integrity": "sha512-8IvaRvtFyzUnGGl3f5+1Cnor3LqaUWvhaUjAYO8Y39OUYlOf3cRd+dowuQYLpZcP3uwSG+mURwjEBOSq4SOJ0g==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign/-/vsce-sign-2.1.0.tgz", + "integrity": "sha512-9AQrqazrBgTgRSuwleLVXUrIUphY02/SFCh2TKYoLV/xifJAdblhdmEmw5gUrYSPQ3sRwNs9iyCMD14sATEE6g==", "dev": true, "hasInstallScript": true, "license": "SEE LICENSE IN LICENSE.txt", @@ -1716,9 +1727,9 @@ } }, "node_modules/default-browser": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", - "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.1.tgz", + "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 58d5e45..532af70 100644 --- a/package.json +++ b/package.json @@ -212,7 +212,7 @@ }, "devDependencies": { "@types/node": "^26.2.0", - "@types/vscode": "^1.125.0", + "@types/vscode": "^1.134.0", "@vscode/vsce": "^3.9.2", "esbuild": "^0.28.2", "typescript": "~6.0.3"