Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/workflows/auto-tag-release-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:
)
runs-on: ubuntu-latest
permissions:
actions: write
contents: write

steps:
Expand All @@ -36,13 +37,15 @@ jobs:
node-version: 24

- name: Create release tag from package version
id: create_tag
env:
MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }}
run: |
set -euo pipefail

version=$(node -p "require('./package.json').version")
tag="v${version}"
echo "tag=$tag" >> "$GITHUB_OUTPUT"

if [[ ! "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Derived invalid release tag: $tag"
Expand All @@ -53,6 +56,7 @@ jobs:

if git show-ref --tags --verify --quiet "refs/tags/$tag"; then
echo "Tag $tag already exists; skipping."
echo "created=false" >> "$GITHUB_OUTPUT"
exit 0
fi

Expand All @@ -61,3 +65,24 @@ jobs:

git tag -a "$tag" "$MERGE_SHA" -m "Release $tag"
git push origin "$tag"
echo "created=true" >> "$GITHUB_OUTPUT"

- name: Dispatch release workflow for new tag
if: steps.create_tag.outputs.created == 'true'
uses: actions/github-script@v7
env:
RELEASE_TAG: ${{ steps.create_tag.outputs.tag }}
with:
script: |
await github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'release.yml',
ref: context.payload.pull_request.base.ref,
inputs: {
tag_name: process.env.RELEASE_TAG,
publish_marketplace: 'true',
publish_openvsx: 'true',
publish_github_release: 'true'
}
});
9 changes: 8 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,18 @@ jobs:
with:
node-version: 24
cache: npm
cache-dependency-path: package-lock.json

- name: Cache VS Code test runtime
uses: actions/cache@v4
with:
path: .vscode-test
key: vscode-test-${{ runner.os }}-${{ hashFiles('package-lock.json', 'package.json', 'tests/integration/runTest.js') }}

- name: Install dependencies
env:
NPM_CONFIG_CAFILE: ""
run: npm ci
run: npm ci --prefer-offline --no-audit --fund=false

- name: Release check
env:
Expand Down
6 changes: 3 additions & 3 deletions docs/GITFLOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ TcTidier now follows a lightweight GitFlow model:
4. Open PRs from `feature/*` into `develop`.
5. Cut `release/<version>` from `develop` when the next release is ready.
6. On the release branch, update `CHANGELOG.md`, `package.json`, and any last release notes.
7. Merge the release branch into `main`; automation creates `v<version>` from the merged `main` commit, then open a `main` into `develop` PR for the back-merge.
8. For urgent production fixes, branch `hotfix/<version>` from `main`, merge back to `main`; automation creates the matching tag, then open a `main` into `develop` PR for the back-merge.
7. Merge the release branch into `main`; automation creates `v<version>` from the merged `main` commit, dispatches the release pipeline, then open a `main` into `develop` PR for the back-merge.
8. For urgent production fixes, branch `hotfix/<version>` from `main`, merge back to `main`; automation creates the matching tag, dispatches the release pipeline, then open a `main` into `develop` PR for the back-merge.

## Commands

Expand All @@ -48,7 +48,7 @@ git checkout -b hotfix/0.1.2
## Repo Automation

- CI runs on `main`, `develop`, `feature/*`, `release/*`, and `hotfix/*`.
- Merged `release/*` and `hotfix/*` PRs into `main` auto-create a matching `v*` tag when the version in `package.json` does not already have one.
- Merged `release/*` and `hotfix/*` PRs into `main` auto-create a matching `v*` tag when the version in `package.json` does not already have one, then dispatch the release workflow for that tag.
- Tag pushes matching `v*` package the extension, publish the generated `.vsix` to a GitHub release, and can publish to extension registries when secrets are configured.
- Manual `workflow_dispatch` runs can re-publish an existing `v*` tag when a release needs to be retried after workflow fixes or secret changes.
- `npm run release:check` verifies the repo and creates a local `.vsix` package before a release PR or tag.
Expand Down
61 changes: 60 additions & 1 deletion tests/integration/runTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,21 @@ const {
runTests
} = require("@vscode/test-electron");

const RETRYABLE_DOWNLOAD_CODES = new Set([
"ECONNABORTED",
"ECONNRESET",
"EAI_AGAIN",
"ENETDOWN",
"ENETRESET",
"ENETUNREACH",
"ENOTFOUND",
"ETIMEDOUT"
]);

function sleep(delayMs) {
return new Promise((resolve) => setTimeout(resolve, delayMs));
}

function resolveVsCodeExecutablePath() {
const candidates = [
process.env.VSCODE_EXECUTABLE_PATH,
Expand All @@ -27,6 +42,50 @@ function resolveVsCodeExecutablePath() {
return undefined;
}

function isRetryableDownloadError(error) {
if (!error || typeof error !== "object") {
return false;
}

const code = typeof error.code === "string" ? error.code : "";
if (RETRYABLE_DOWNLOAD_CODES.has(code)) {
return true;
}

const message = typeof error.message === "string" ? error.message.toLowerCase() : "";
return message.includes("aborted") || message.includes("socket hang up");
}

async function downloadVsCodeWithRetries() {
const attempts = Number.parseInt(
process.env.TCTIDIER_VSCODE_DOWNLOAD_ATTEMPTS || "3",
10
);
const delayMs = Number.parseInt(
process.env.TCTIDIER_VSCODE_DOWNLOAD_DELAY_MS || "2000",
10
);

for (let attempt = 1; attempt <= attempts; attempt += 1) {
try {
return await downloadAndUnzipVSCode();
} catch (error) {
if (!isRetryableDownloadError(error) || attempt === attempts) {
throw error;
}

console.warn(
`Retrying VS Code test runtime download (${attempt}/${attempts}) after error: ${
error instanceof Error ? error.message : String(error)
}`
);
await sleep(delayMs * attempt);
}
}

throw new Error("Failed to download the VS Code test runtime.");
}

async function resolveVsCodeCliPath() {
const installedPath = resolveVsCodeExecutablePath();
if (installedPath) {
Expand All @@ -36,7 +95,7 @@ async function resolveVsCodeCliPath() {
return installedPath;
}

const downloadedPath = await downloadAndUnzipVSCode();
const downloadedPath = await downloadVsCodeWithRetries();
return resolveCliPathFromVSCodeExecutablePath(downloadedPath);
}

Expand Down
Loading