Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
097aafd
fix: defer unpause in setDataStream to prevent libcurl reentrant hang
JCMais Apr 12, 2026
50cdc77
fix: use setTimeout instead of setImmediate for unpause deferral
JCMais May 19, 2026
21ea9d1
fix: track CURLPAUSE_SEND state for mime read callback pause
JCMais May 21, 2026
230b11f
chore: bump node-gyp to 12.3.0 and node-pre-gyp to 2.0.3
JCMais May 22, 2026
c1c05f7
ci: add Node.js 26 to the build matrix
JCMais May 22, 2026
7ac436a
ci: stop pinning msvs_version on Windows builds
JCMais May 31, 2026
b0d8cc7
ci: bump Alpine container to 3.22 for Node 26 compatibility
JCMais May 31, 2026
90c7f41
fix: add OpenSSL pkgconfig path for libcurl + ngtcp2 build on Alpine
JCMais May 31, 2026
143152e
Merge branch 'develop' into master for v5.0.3
JCMais May 31, 2026
8b9d664
chore: prepare CHANGELOG for v5.0.3 release
JCMais May 31, 2026
f1f427c
chore: promote prep-release from slash command to project skill
JCMais May 31, 2026
446e35b
chore: bump prepared release from v5.0.3 to v5.1.0
JCMais May 31, 2026
46587f0
fix: defer multi.removeHandle to avoid CURLM_RECURSIVE_API_CALL
JCMais May 31, 2026
aec2b99
test: add stress workflow catching CURLM_RECURSIVE_API_CALL races
JCMais May 31, 2026
95a802b
ci: make download-and-unpack retry-safe against truncated responses
JCMais May 31, 2026
425f2f2
ci: portable retry loop in download-and-unpack for BusyBox wget
JCMais May 31, 2026
0d82b55
5.1.0
node-libcurl[bot] May 31, 2026
23d1736
Merge branch 'master' into develop (post-v5.1.0)
JCMais May 31, 2026
f727b00
fix: avoid Windows vcpkg libidn2 build path
JCMais Jun 1, 2026
785f589
chore: add codex marker
JCMais Jun 1, 2026
246d006
ci: opt the consumer test into running node-libcurl's install scripts
JCMais Jun 1, 2026
4d88c18
ci: pnpm --allow-build needs = syntax not space [skip ci]
JCMais Jun 1, 2026
99c489d
fix: enable git core.longpaths for vcpkg clone on Windows [skip ci]
JCMais Jun 1, 2026
68743c1
ci: enable Windows long paths so vcpkg can run from deep consumer pat…
JCMais Jun 1, 2026
67924bd
fix: clone vcpkg to a short path on Windows to dodge MAX_PATH [skip ci]
JCMais Jun 1, 2026
e2ef5e8
fix: also relocate vcpkg_installed off the deep consumer path [skip ci]
JCMais Jun 1, 2026
11a13a8
ci: actually fail the consumer test when the install fails [skip ci]
JCMais Jun 4, 2026
7670677
ci: reduce consumer test nesting from 12 to 4 to fit MSVC c1xx MAX_PA…
JCMais Jun 4, 2026
2831f10
Merge branch 'develop' into master for v5.1.1
JCMais Jun 5, 2026
5d5e0cb
chore: prepare CHANGELOG for v5.1.1 release
JCMais Jun 5, 2026
2d8588b
5.1.1
node-libcurl[bot] Jun 6, 2026
a222989
fix: disable LTO/ThinLTO in binding.gyp for Windows + Node 26 builds
JCMais Jun 7, 2026
2c28d96
fix: disable LTO/ThinLTO via gyp -D flags for Node 26 Windows builds
JCMais Jun 8, 2026
82ba321
Merge branch 'develop' into master for v5.1.2
JCMais Jun 8, 2026
4d0f347
chore: prepare CHANGELOG for v5.1.2 release
JCMais Jun 8, 2026
1e0bbef
5.1.2
node-libcurl[bot] Jun 10, 2026
3d0154c
Merge remote-tracking branch 'upstream/master' into chore/update-from…
aborovsky Jul 3, 2026
b77aedf
fix(mime-part): simplify stream pause/resume logic by removing redund…
aborovsky Jul 3, 2026
78d6864
chore(release): revert version to 5.0.8
aborovsky Jul 3, 2026
072bb75
ci: upgrade Alpine Linux base image from 3.21 to 3.22
aborovsky Jul 3, 2026
f1686b5
Merge pull request #35 from NeuraLegion/chore/update-from-upstream-re…
aborovsky Jul 3, 2026
1444b38
5.1.2-0
github-actions[bot] Jul 3, 2026
011df98
Merge branch 'master' into develop
aborovsky Jul 3, 2026
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
19 changes: 0 additions & 19 deletions .claude/commands/prep-release.md

This file was deleted.

162 changes: 162 additions & 0 deletions .claude/skills/prep-release/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
---
name: prep-release
description: Prepare node-libcurl for a release — merge develop into master, decide the version bump, and convert the [Unreleased] CHANGELOG section into a dated version section. Use whenever Jonathan asks to "prep a release", "prepare a release", "do release prep", "merge develop into master", "cut a release", "ship a new version", or anything similar in this project. Also use when he says "we're shipping vX.Y.Z" or asks for help deciding what version number to use for the next release. Do NOT bump the package.json version and do NOT create tags — the release workflow handles those.
---

# Prep Release (node-libcurl)

This project uses a master/develop branching model. Active work lands on `develop`. To release, develop is merged into `master`, and a CI workflow (`build-and-release.yaml`) builds prebuilt binaries and publishes them when a tag is pushed.

**Your job here is the human-readable part of that flow**: getting master pointed at the right commit and converting the accumulated `[Unreleased]` changelog notes into a dated version section. The release workflow handles `package.json` version bumps and the git tag — don't touch those.

## The workflow

### 1. Pre-flight

Confirm intent and check the working tree:

```bash
git status
git fetch origin
git log --oneline origin/develop ^origin/master
```

That last command lists every commit on develop that hasn't reached master yet — i.e., everything that's about to ship. Read through it. You'll need this both for the version-bump call and to make sure the CHANGELOG actually mentions everything important.

### 2. Reconcile master with the remote

Local master is often stale on Jonathan's machine because the release workflow updates it on the remote. Don't merge develop into a stale local master and push that mess — first sync:

```bash
git checkout master
git log master --not origin/master --oneline # any local-only commits?
```

If local master has commits not on origin/master, **look at them before touching anything**:
- `chore: prepare release X.Y.Z` or `fix: version fix` style commits with `[skip ci]` are leftover prep-release attempts — safe to `git reset --hard origin/master`
- Anything else: stop and ask Jonathan what they are

If everything is fine and you just need to fast-forward:
```bash
git reset --hard origin/master
```

### 3. Decide the version bump

Look at the commits from step 1 and at the current `[Unreleased]` block in `CHANGELOG.md`. Use semver:

- **Patch (X.Y.Z+1)** — only when every change is a bug fix that doesn't alter the public API or any observable behavior contract for existing code. CI/build infrastructure fixes alone are patch-worthy.
- **Minor (X.Y+1.0)** — any of these, even if everything else is just a bug fix:
- **New runtime/platform support shipped as prebuilt binary.** Adding a Node.js major (e.g., Node 26) is the textbook minor for native addons: users on the new runtime couldn't use the old version at all, and now they can. This is the most common reason this project goes minor instead of patch.
- New public API surface (exported function, type, constant, method).
- Observable behavior change in existing API — including a "bug fix" that changes the value a getter returns or the timing of a callback. Code written against the broken behavior will see a different result.
- Documented contract change (e.g., examples in TSDoc now show a different API call that consumers may have copy-pasted).
- **Major** — breaking changes (removal/rename of public API, incompatible behavior changes that aren't bug fixes, etc.).

When the call is borderline, **lean minor**. Patch in this ecosystem signals "rebuilt with no consumer-visible changes" — anything beyond that deserves the bump.

Tell Jonathan which version you're proposing and why, and let them confirm before continuing. The version they pick drives the rest of the workflow.

### 4. Merge develop into master

```bash
git merge origin/develop --no-ff -m "Merge branch 'develop' into master for vX.Y.Z"
```

`--no-ff` keeps a merge commit so the release boundary is visible in the history. The release workflow expects this shape.

### 5. Update CHANGELOG.md

Two edits in the same commit:

**A. Convert the `[Unreleased]` section into a dated version.** Find this block at the top:
```markdown
## [Unreleased]

### Breaking Change

### Fixed
- ...accumulated bullets...

### Added
- ...

### Changed
- ...
```

Split it: insert a fresh empty `[Unreleased]` block above (with all four subsections, even if empty — keeps the diff for the next release minimal), and rename the original to the dated version header:

```markdown
## [Unreleased]

### Breaking Change

### Fixed

### Added

### Changed

## [X.Y.Z] - YYYY-MM-DD

### Fixed
- ...accumulated bullets...

### Added
- ...

### Changed
- ...
```

Use today's actual date (check `date +%Y-%m-%d` if unsure).

**B. Before committing, look for changes that landed since the last release but never got a changelog line.** Run `git log v<previous>..HEAD --oneline` and scan for things like CI fixes, dependency bumps, or build-script changes that the author skipped because they thought of them as "not user-facing." Some of them *are* user-facing in practice — e.g.:
- A CI fix that unblocks a platform that was failing to publish (users on that platform feel it).
- A node-gyp / node-pre-gyp bump that changes what compilers are detected when users build from source.
- A container image bump in the build matrix.

If you find any, add them to the appropriate section of the new `[X.Y.Z]` block. Don't pad — just capture what a user might care about.

**C. Update the compare links at the bottom of the file.** Find:
```markdown
[Unreleased]: https://github.com/JCMais/node-libcurl/compare/v<previous>...HEAD
[<previous>]: ...
```

Bump it to:
```markdown
[Unreleased]: https://github.com/JCMais/node-libcurl/compare/vX.Y.Z...HEAD
[X.Y.Z]: https://github.com/JCMais/node-libcurl/compare/v<previous>...vX.Y.Z
[<previous>]: ...
```

It's easy to forget the second edit and the bottom link will silently rot — always do both.

### 6. Commit (do not push, do not tag)

```bash
git add CHANGELOG.md
git commit -m "chore: prepare CHANGELOG for vX.Y.Z release"
```

Then **stop and tell Jonathan what's staged**. Show them the log of what's about to be pushed (the merge commit + the changelog commit). Do not push without explicit go-ahead — they sometimes want a final look first.

When they say push: `git push origin master`.

### 7. Don't do these things

- **Don't bump `package.json`'s `version` field.** The release workflow (`build-and-release.yaml`) handles that.
- **Don't create or push a tag.** Same — the workflow handles it when master moves.
- **Don't push develop.** Only master gets pushed in this flow.
- **Don't merge with `--ff-only` or squash.** The release boundary needs to be a real merge commit.

## Why the workflow has this exact shape

A few things in here look fussy until you've seen them break:

- **`git fetch` before checking master state** — a stale local view causes you to overwrite remote progress.
- **Insisting on an empty `[Unreleased]` block** — keeps the diff for the next release small; the next prep-release just fills it in. Without this, the next person has to remember to recreate the four subsections.
- **Reviewing commits before writing the version section** — the `[Unreleased]` block is whatever the contributors remembered to write. Things that landed in CI-fix or dep-bump commits often *aren't* in there but matter for users.
- **Patch vs minor on platform support** — Node.js majors in particular: a user on Node 26 trying to install v5.0.2 gets nothing (no ABI 145 binary). When that user shows up post-release saying "does this even support Node 26?", you want the answer to be "yes, since v5.1.0," not "yes since v5.0.3, technically." The version number is part of the user-facing answer.
Empty file added .codex
Empty file.
2 changes: 1 addition & 1 deletion .github/actions/setup-vcpkg-windows/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ runs:
- name: Setup vcpkg
shell: pwsh
env:
VCPKG_DISABLE_METRICS: false
VCPKG_DISABLE_METRICS: '1'
run: |
cd vcpkg
.\bootstrap-vcpkg.bat
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/build-and-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:
|| matrix.os == 'alpine-arm64' && 'ubuntu-24.04-arm'
|| matrix.os }}
container: >-
${{ matrix.os == 'alpine' && format('node:{0}-alpine3.21', matrix.node)
${{ matrix.os == 'alpine' && format('node:{0}-alpine3.22', matrix.node)
|| matrix.os == 'rocky-linux-8' && format('rockylinux:8')
|| '' }}
needs:
Expand All @@ -72,6 +72,7 @@ jobs:
libcurl-release:
- ${{ needs.config.outputs.latest-libcurl-release }}
node:
- 26
- 25
- 24
- 22
Expand Down Expand Up @@ -195,7 +196,7 @@ jobs:
-v "${HOME}/deps:/root/deps" \
-v "${HOME}/.node-gyp:/root/.node-gyp" \
-w "${GITHUB_WORKSPACE}" \
"node:${{ matrix.node }}-alpine3.21" \
"node:${{ matrix.node }}-alpine3.22" \
sh -c '
apk add --no-cache bash python3 make g++ cmake perl linux-headers \
autoconf automake libtool ca-certificates coreutils pkgconfig py3-pip \
Expand Down Expand Up @@ -283,7 +284,7 @@ jobs:
-e PNPM_VERSION="${PNPM_VERSION}" \
-v "${GITHUB_WORKSPACE}:${GITHUB_WORKSPACE}" \
-w "${GITHUB_WORKSPACE}" \
"node:${{ matrix.node }}-alpine3.21" \
"node:${{ matrix.node }}-alpine3.22" \
sh -c '
apk add --no-cache bash ca-certificates &&
npm install -g pnpm@${PNPM_VERSION} &&
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/build-lint-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
|| matrix.os == 'alpine-arm64' && 'ubuntu-24.04-arm'
|| matrix.os }}
container: >-
${{ matrix.os == 'alpine' && format('node:{0}-alpine3.21', matrix.node)
${{ matrix.os == 'alpine' && format('node:{0}-alpine3.22', matrix.node)
|| matrix.os == 'rocky-linux-8' && format('rockylinux:8')
|| '' }}
needs: config
Expand All @@ -51,6 +51,7 @@ jobs:
libcurl-release:
- ${{ needs.config.outputs.latest-libcurl-release }}
node:
- 26
- 25
- 24
- 22
Expand Down Expand Up @@ -153,7 +154,7 @@ jobs:
-v "${HOME}/deps:/root/deps" \
-v "${HOME}/.node-gyp:/root/.node-gyp" \
-w "${GITHUB_WORKSPACE}" \
"node:${{ matrix.node }}-alpine3.21" \
"node:${{ matrix.node }}-alpine3.22" \
sh -c '
apk add --no-cache bash python3 make g++ cmake perl linux-headers \
autoconf automake libtool ca-certificates coreutils pkgconfig py3-pip \
Expand Down Expand Up @@ -209,7 +210,7 @@ jobs:
-e PNPM_VERSION="${PNPM_VERSION}" \
-v "${GITHUB_WORKSPACE}:${GITHUB_WORKSPACE}" \
-w "${GITHUB_WORKSPACE}" \
"node:${{ matrix.node }}-alpine3.21" \
"node:${{ matrix.node }}-alpine3.22" \
sh -c 'npm install -g pnpm@${PNPM_VERSION} && pnpm test:coverage'

- name: Upload coverage to Codecov
Expand Down
116 changes: 116 additions & 0 deletions .github/workflows/stress-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Canary stress test for issues that only show up under concurrent load.
# Currently exercises the deferred multi.removeHandle fix for issue #439
# (CURLM_RECURSIVE_API_CALL), which reproduces most reliably on Alpine
# with libcurl 8.17+ but is a real race on any platform.
#
# This workflow doesn't publish or deploy anything — it just builds the
# addon and runs `pnpm test:stress`, which lives outside the default
# vitest run because it's expensive and timing-sensitive.

name: stress-test

defaults:
run:
shell: bash

on:
push:
branches:
- master
- develop
pull_request:

concurrency:
group: stress-test-${{ github.head_ref || github.ref_name }}
cancel-in-progress: true

jobs:
config:
uses: ./.github/workflows/reusable-config.yaml

stress:
needs: config
runs-on: ${{ matrix.os == 'alpine' && 'ubuntu-22.04' || matrix.os }}
container: ${{ matrix.os == 'alpine' && format('node:{0}-alpine3.22', matrix.node) || '' }}
strategy:
# Each cell is informative on its own — we want to see which platform
# tripped, not give up on the rest.
fail-fast: false
matrix:
# Keep this matrix narrow on purpose. Alpine is where the recursive
# API call manifests most aggressively (~73% failure rate without
# the fix), so it's the highest-signal canary. Ubuntu is included
# to catch glibc-side timing surprises.
os:
- alpine
- ubuntu-22.04
node:
- 24
env:
LIBCURL_RELEASE: ${{ needs.config.outputs.latest-libcurl-release }}
LATEST_LIBCURL_RELEASE: ${{ needs.config.outputs.latest-libcurl-release }}
PUBLISH_BINARY: false
RUN_TESTS: false
RUN_PREGYP_CLEAN: false
GIT_COMMIT: ${{ github.sha }}
GIT_REF_NAME: ${{ github.ref_name }}
steps:
- name: Install updated git (Alpine)
if: matrix.os == 'alpine'
shell: sh
run: |
apk add --no-cache git bash

- name: Checkout
uses: actions/checkout@v5
with:
submodules: true

- name: Install System Packages
uses: ./.github/actions/install-system-packages

- name: Setup Node and PNPM
uses: ./.github/actions/setup-node-pnpm
with:
node-version: '${{ matrix.node }}'
skip-node-setup: ${{ matrix.os == 'alpine' && 'true' || 'false' }}

- name: Setup Libcurl Cache (Restore)
id: libcurl-deps-cache
uses: ./.github/actions/setup-libcurl-cache
with:
libcurl-release: ${{ needs.config.outputs.latest-libcurl-release }}
node-version: ${{ matrix.node }}
electron-config-cache: ${{ needs.config.outputs.electron-config-cache }}
mode: 'restore-only'

- name: Build addon
run: ./scripts/ci/build.sh

- name: Check if fully installed and built
id: built-and-installed
run: |
if [[ -f built-and-installed.hidden.txt ]]; then
echo "status=true" >> $GITHUB_OUTPUT
else
echo "status=false" >> $GITHUB_OUTPUT
fi

- name: Setup Libcurl Cache (Save)
if: steps.libcurl-deps-cache.outputs.cache-hit != 'true' && steps.built-and-installed.outputs.status == 'true'
uses: ./.github/actions/setup-libcurl-cache
with:
libcurl-release: ${{ needs.config.outputs.latest-libcurl-release }}
node-version: ${{ matrix.node }}
electron-config-cache: ${{ needs.config.outputs.electron-config-cache }}
mode: 'save-only'

- name: Run stress tests
run: pnpm test:stress

- name: Upload Build Logs
if: always()
uses: ./.github/actions/upload-build-logs
with:
artifact-name: stress-test-logs-${{ matrix.os }}-node-${{ matrix.node }}
retention-days: '3'
Loading
Loading