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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ on:
- checks_requested

concurrency:
# Keep the event name in the group: sharing one group across events once let
# a release-authorizing push run deadlock behind other events for 13 hours.
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: true

Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ permissions:
contents: read

concurrency:
# Keep the event name in the group: sharing one group across events once let
# a release-authorizing push run deadlock behind other events for 13 hours.
group: docs-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: true

Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/exact-package-candidate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,8 @@ jobs:
target: google_apis
profile: pixel_2
script: |
# Gradle must run from the generated consumer directory, not the
# repository root, or it builds the wrong project.
cd "$RUNNER_TEMP/ReorderableConsumer/android" && ./gradlew app:installDebug --no-daemon -PnewArchEnabled=true -PhermesEnabled=true
adb shell am start -W -n com.reorderableconsumer/.MainActivity

Expand Down Expand Up @@ -576,6 +578,9 @@ jobs:
bundle install
bundle exec pod install --project-directory=ios
)
# build-framework-cache must run before detox build: without a
# prebuilt framework cache, Detox compiles it lazily mid-build and
# the Apple contract build fails on hosted runners.
yarn detox build-framework-cache
yarn detox build --configuration '${{ matrix.configuration }}'

Expand Down
16 changes: 0 additions & 16 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,6 @@ jobs:
- name: Setup
uses: ./.github/actions/setup

- name: Require ordinary push gates for the same commit
env:
GH_TOKEN: ${{ github.token }}
RELEASE_COMMIT: ${{ github.event.workflow_run.head_sha }}
run: |
for workflow in ci.yml docs.yml; do
conclusion=$(gh run list \
--workflow "$workflow" \
--commit "$RELEASE_COMMIT" \
--event push \
--limit 10 \
--json conclusion,status \
--jq '[.[] | select(.status == "completed")][0].conclusion')
test "$conclusion" = success
done

- name: Select Changesets mode
id: mode
run: node scripts/select-release-mode.mjs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
The first stable release is `1.0.0`, not `0.x`, because the v1 effort freezes the exported API and portable behaviour and therefore makes a SemVer stability promise. Releases accumulate through Changesets and are cut on demand from an automated release PR; CI packs once, proves the exact tarball according to ADR-0006, and publishes that artifact through npm trusted publishing, while an optional `next` channel carries prereleases.

Rspress on GitHub Pages is the canonical documentation surface, backed by repository-authored guides, generated TypeScript API reference, and type-checked examples; the README remains a quick start. Release-surface pull requests require a changeset and the relevant full CI matrix unless a maintainer records a commit-scoped `No changeset needed` override, while contributor-facing guidance stays separate from maintainer-only agent and triage conventions. The project publishes its compatibility matrix but makes no maintenance cadence, SLA, continuity, or backport promise and adds no separate security policy for v1.

Amended 2026-09-05 ([#58](https://github.com/thiagobrez/react-native-reorderable/issues/58)): fast-check authorization moved from runtime re-verification to the `main` ruleset. The release workflow no longer polls `gh run list` for ci.yml/docs.yml conclusions on the release commit; the ruleset (pull requests required, strict up-to-date checks, zero bypass actors) is the guarantee that every `main` SHA passed the fast set. The sha256-pinned tarball chain — `workflow_run` gated to `event == push && head_branch == main`, artifact download pinned to the triggering run id, and `publish-verified-candidate.mjs` re-asserting sha256/byte-length/sourceCommit — is the only runtime verification.
2 changes: 1 addition & 1 deletion docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ The release workflow intentionally has no npm token. Its publish job receives a

Every user-visible package change carries a file from `yarn changeset`. After a fully green push to `main`, Changesets creates or updates `changeset-release/main`. This pull request is the on-demand release boundary: leave it open while accumulating changes and merge it when the release should happen.

Merging the release pull request causes the exact-package workflow to build one tarball and run that same tarball through package inspection, clean consumers, the supported React Native compatibility matrix, performance checks, and the four-engine device contract. Publication starts only after that workflow and the ordinary `CI` and `Documentation` workflows are green for the same commit. Immediately before npm, the publisher rechecks the tarball bytes, SHA-256, source commit, package name, and version against its manifest.
Merging the release pull request causes the exact-package workflow to build one tarball and run that same tarball through package inspection, clean consumers, the supported React Native compatibility matrix, performance checks, and the four-engine device contract. Publication starts only after that workflow succeeds; the `main` ruleset's required pull-request checks are what guarantee the commit already passed the fast set. Immediately before npm, the publisher rechecks the tarball bytes, SHA-256, source commit, package name, and version against its manifest.

Changesets then creates the package tag and GitHub release. The same release workflow builds and deploys the documentation from that tagged commit. Stable versions use the npm `latest` tag.

Expand Down
223 changes: 0 additions & 223 deletions scripts/__tests__/ci-fixtures.test.mjs

This file was deleted.

99 changes: 99 additions & 0 deletions scripts/__tests__/consumer-scripts.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import {
chmodSync,
mkdtempSync,
readFileSync,
statSync,
writeFileSync,
} from 'node:fs';
import { tmpdir } from 'node:os';
import { resolve } from 'node:path';
import test from 'node:test';

const repository = resolve(import.meta.dirname, '../..');

test('Expo clean consumers do not install the React Native community CLI', () => {
const fixture = mkdtempSync(resolve(tmpdir(), 'reorderable-consumer-'));
writeFileSync(
resolve(fixture, 'package.json'),
`${JSON.stringify({ dependencies: {} }, null, 2)}\n`
);

execFileSync(
process.execPath,
[
resolve(repository, 'scripts/configure-clean-consumer.mjs'),
fixture,
resolve(repository, 'candidate.tgz'),
'expo',
JSON.stringify({
react: '19.2.3',
reactNative: '0.86.2',
reactNativeGestureHandler: '3.2.1',
reactNativeReanimated: '4.5.3',
reactNativeWorklets: '0.11.4',
}),
],
{ stdio: 'pipe' }
);

const metadata = JSON.parse(
readFileSync(resolve(fixture, 'package.json'), 'utf8')
);
assert.equal(
metadata.devDependencies?.['@react-native-community/cli'],
undefined
);
});

test('the autolinking verifier accepts Expo platform-specific output', () => {
execFileSync(
process.execPath,
[
resolve(repository, 'scripts/verify-consumer-autolinking.mjs'),
'--platform',
'ios',
],
{
input: JSON.stringify({
dependencies: {
'react-native-reorderable': {
platforms: { ios: { podspecPath: 'Reorderable.podspec' } },
},
},
}),
stdio: ['pipe', 'pipe', 'pipe'],
}
);
});

test('the fmt workaround disables only the vulnerable 11.0.2 consteval branch', () => {
const fixture = mkdtempSync(resolve(tmpdir(), 'reorderable-fmt-'));
const header = resolve(fixture, 'base.h');
writeFileSync(
header,
`#define FMT_VERSION 110002
#elif defined(__cpp_consteval)
# define FMT_USE_CONSTEVAL 1
#elif FMT_GCC_VERSION >= 1002 || FMT_CLANG_VERSION >= 1101
# define FMT_USE_CONSTEVAL 1
`
);
chmodSync(header, 0o444);

const patcher = resolve(repository, 'scripts/patch-fmt-consteval.mjs');
execFileSync(process.execPath, [patcher, header]);
execFileSync(process.execPath, [patcher, header]);

const patched = readFileSync(header, 'utf8');
assert.match(
patched,
/#elif defined\(__cpp_consteval\)\n# define FMT_USE_CONSTEVAL 0/
);
assert.match(
patched,
/#elif FMT_GCC_VERSION >= 1002 \|\| FMT_CLANG_VERSION >= 1101\n# define FMT_USE_CONSTEVAL 1/
);
assert.equal(statSync(header).mode & 0o777, 0o444);
});
Loading
Loading