Skip to content
Merged
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
54 changes: 39 additions & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,20 @@ on:
branches: [main]
pull_request:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20
Expand All @@ -34,12 +43,7 @@ jobs:
# gate. While a second, pure-TypeScript parser existed, a runner with no
# C++ toolchain could still prove the library and merely skip the native
# measurements, so `--if-available` cost only numbers. Now a runner that
# skips this build runs *zero* markdown-parsing checks: jest quietly
# reports ten of twenty-six suites as skipped, the conformance run has
# nothing to score, and the job goes green having tested no parse. A
# missing compiler on ubuntu-latest is not a tolerable runner property,
# it is a broken job, so it fails here where the cause is named rather
# than passing silently downstream.
# skips this build runs no parsing suite: `describeNative` skips without the addon.
#
# This must come BEFORE `npm test`: the src/engine/native/ suites load
# build/selectable-markdown.node, and jest picks them up automatically.
Expand All @@ -61,7 +65,19 @@ jobs:
# Gates: unit suites, the every-character-prefix streaming oracle,
# copy-fidelity properties, and the tail-repair corpus.
- run: npm test
- run: npm run build
# Per-stage budgets are cliff detectors at 25-60x the measured medians, not targets.
- run: >-
npm run bench:pathological --
--require-engine
--budget-parse 750 --budget-repair 750 --budget-segment 200 --budget-project 750
# This transcript never anchors, so every append re-reads the whole text.
- run: >-
npm run bench:streaming --
--transcript conformance/fixtures/transcript-giant-list.json
--require-engine --repeat 1
--budget-chunk 20 --budget-finalize 50
# A deterministic count, not a clock: `cached` growth over 1.25x per doubling fails.
- run: npm run bench:projection -- --require-engine
# Gate: the packed tarball is what a consumer actually installs, and
# dist/ is never committed — this proves it still carries a working
# entrypoint, the podspec, and the native sources.
Expand Down Expand Up @@ -115,6 +131,8 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20
Expand All @@ -133,16 +151,20 @@ jobs:
else
sudo apt-get update && sudo apt-get install -y libboost-dev
fi
# Gate: every .cpp this package ships to a consumer's compiler is
# compiled against the genuine React Native 0.75.4 renderer headers — on
# BOTH platform header sets on the macOS leg, on the Android set here on
# ubuntu — including the Android include-order seam that decides whether
# runs measure at all.
- run: npm run check:fabric-cpp
# Gate: the checker can still fail. Five deliberately broken translation
# units must each be rejected, and rejected for the stated reason —
# against the iOS headers on macOS, the Android headers on ubuntu.
- run: npm run check:fabric-cpp:selftest
# The only job that compiles platform/cpp; `find` because a shell glob does not descend.
- name: Compile the markdown engine at the podspec's C++ standard
run: |
if [ "$RUNNER_OS" = "macOS" ]; then platform=ios; else platform=android; fi
files=$(find platform/cpp -path platform/cpp/vendor -prune -o -name '*.cpp' -print | sort)
if [ -z "$files" ]; then
echo "no .cpp found under platform/cpp — this gate would pass having compiled nothing"
exit 1
fi
echo "$files"
# Unquoted on purpose: one argument per path. No path here has a space.
npm run check:fabric-cpp -- --syntax-only --platform "$platform" $files

swift:
name: Swift (real RN headers + iOS SDK)
Expand All @@ -155,6 +177,8 @@ jobs:
RNSM_REQUIRE_SWIFT: '1'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20
Expand Down
148 changes: 116 additions & 32 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,24 @@ on:
push:
tags: ['v*']

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false

jobs:
release:
preflight:
name: Preflight (tag, lockfile, changelog)
runs-on: ubuntu-latest
permissions:
contents: write # attach the packed tarball to the GitHub release
id-token: write # npm publish --provenance
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
registry-url: https://registry.npmjs.org

# The podspec reads its version out of package.json, so a tag that
# disagrees with it ships a pod CocoaPods cannot resolve.
Expand All @@ -30,28 +35,92 @@ jobs:
exit 1
fi

# `npm ci` never checks the lock's root entry against package.json; only this does.
- name: Check package-lock.json mirrors the manifest
run: node scripts/check-lock-sync.mjs

- name: Check CHANGELOG.md has a section for this tag
run: node scripts/changelog-section.mjs "$GITHUB_REF_NAME" > /dev/null

- name: Check no BREAKING change ships under an already-tagged version
run: node scripts/check-unreleased-breaking.mjs --tag "$GITHUB_REF_NAME"

# A tag can be pushed at any commit, so CI having passed proves nothing about it.
macos-gates:
name: macOS gates (Swift + the iOS header set)
needs: preflight
runs-on: macos-latest
env:
RNSM_REQUIRE_SWIFT: '1'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
# Boost is the only dependency check-fabric-cpp.mjs does not fetch itself.
- run: brew install boost
- run: npm run check:fabric-cpp
- run: npm run check:fabric-cpp:selftest
- name: Compile the markdown engine (iOS header set)
run: |
files=$(find platform/cpp -path platform/cpp/vendor -prune -o -name '*.cpp' -print | sort)
if [ -z "$files" ]; then
echo "no .cpp found under platform/cpp — this gate would pass having compiled nothing"
exit 1
fi
echo "$files"
npm run check:fabric-cpp -- --syntax-only --platform ios $files
- run: npm run check:swift
- run: npm run check:swift:selftest

release:
needs: macos-gates
runs-on: ubuntu-latest
permissions:
contents: write # attach the packed tarball to the GitHub release
id-token: write # npm publish --provenance
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
registry-url: https://registry.npmjs.org

- run: npm ci

# pipefail: otherwise the status is tee's, and a failed extract publishes empty notes.
- name: Extract the release notes for this tag
run: |
set -o pipefail
node scripts/changelog-section.mjs "$GITHUB_REF_NAME" | tee "${RUNNER_TEMP:-/tmp}/release-notes.md"

# THE SAME GATES CI RUNS, NOT A SUBSET. A tag can be pushed at any
# commit — including one CI never saw, or one CI saw fail — and this
# workflow is the last thing between the code and the npm registry.
# Running only `npm test` here meant a release could ship a type error,
# a codegen shape change that silently disables the component, or C++
# that no longer compiles against React Native's headers.
#
# Everything below runs on this ubuntu runner. Two gates need macOS and
# are deliberately not repeated here, because adding a job the publish
# depends on doubles the time between pushing a tag and having an
# artifact: the Swift gate (Xcode), and the iOS half of check:fabric-cpp
# (React Native's iOS renderer headers need an Apple SDK, so on this
# runner the script skips that pass out loud and gates the Android
# pass). CI runs both on macOS on every push to main and every pull
# request, which is where those regressions should be caught.
- run: npm run typecheck
- run: npm run check:codegen
- run: sudo apt-get update && sudo apt-get install -y libboost-dev
- run: npm run check:fabric-cpp
- run: npm run check:fabric-cpp:selftest
- name: Compile the markdown engine (Android header set)
run: |
files=$(find platform/cpp -path platform/cpp/vendor -prune -o -name '*.cpp' -print | sort)
if [ -z "$files" ]; then
echo "no .cpp found under platform/cpp — this gate would pass having compiled nothing"
exit 1
fi
echo "$files"
npm run check:fabric-cpp -- --syntax-only --platform android $files

# HARD GATE, AND IT MUST COME BEFORE `npm test` — the same reasoning as
# ci.yml. `describeNative` in src/engine/native/__tests__/support.ts is
Expand All @@ -64,26 +133,26 @@ jobs:
- run: node scripts/build-node-addon.mjs
- run: npm test

# dist/ is never committed: `npm pack` runs prepare/prepack, which build
# it, and verify:pack proves the resulting tarball is consumable.
# No build step: `npm pack` runs `prepare`, which builds dist/.
- run: npm run verify:pack
- run: npm pack

# The prebuilt tarball is the fast pin for consumers who cannot use the
# registry — installing it needs no toolchain and no build step, unlike
# a `github:` dependency.
- name: Attach the tarball to the release
env:
GH_TOKEN: ${{ github.token }}
- run: >-
npm run bench:pathological --
--require-engine
--budget-parse 750 --budget-repair 750 --budget-segment 200 --budget-project 750
- run: >-
npm run bench:streaming --
--transcript conformance/fixtures/transcript-giant-list.json
--require-engine --repeat 1
--budget-chunk 20 --budget-finalize 50
- run: npm run bench:projection -- --require-engine
- name: Pack the release artifact
run: |
if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then
gh release upload "$GITHUB_REF_NAME" ./*.tgz --clobber
else
gh release create "$GITHUB_REF_NAME" ./*.tgz --generate-notes --verify-tag
fi
npm pack
version=$(node -p "require('./package.json').version")
echo "RELEASE_TARBALL=react-native-selectable-markdown-${version}.tgz" >> "$GITHUB_ENV"

# Skipped, not failed, when no NPM_TOKEN secret is configured — the
# GitHub release above is still a complete, installable artifact.
# GitHub release below is still a complete, installable artifact.
- name: Publish to npm
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
Expand All @@ -92,4 +161,19 @@ jobs:
echo "NPM_TOKEN is not set — skipping npm publish."
exit 0
fi
npm publish --provenance --access public
npm publish "$RELEASE_TARBALL" --provenance --access public

# The prebuilt tarball is the fast pin for consumers who cannot use the
# registry — installing it needs no toolchain and no build step, unlike
# a `github:` dependency.
- name: Attach the tarball to the release
env:
GH_TOKEN: ${{ github.token }}
run: |
if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then
gh release upload "$GITHUB_REF_NAME" "$RELEASE_TARBALL" --clobber
gh release edit "$GITHUB_REF_NAME" --notes-file "${RUNNER_TEMP:-/tmp}/release-notes.md"
else
gh release create "$GITHUB_REF_NAME" "$RELEASE_TARBALL" \
--notes-file "${RUNNER_TEMP:-/tmp}/release-notes.md" --verify-tag
fi
Loading
Loading