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
8 changes: 4 additions & 4 deletions .github/actions/publish-npm-package/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ name: publish-npm-package
description: Build and publish react-native-gesture-handler package to npm
inputs:
release-type:
description: Release type to be published (stable, commitly, beta, rc).
default: "commitly"
description: Release type to be published (stable, nightly, beta, rc).
default: "nightly"
version:
description: Specific version to publish (usually inferred from x.y-stable branch name).
default: ""
Expand Down Expand Up @@ -31,15 +31,15 @@ runs:
id: set-package-version
shell: bash
run: |
VERSION=$(node ./scripts/release/set-package-version.js ${{ inputs.release-type == 'commitly' && '--commitly' || '' }} ${{ inputs.release-type == 'beta' && '--beta' || '' }} ${{ inputs.release-type == 'rc' && '--rc' || '' }} ${{ inputs.version != '' && format('--version ''{0}''', inputs.version) || '' }})
VERSION=$(node ./scripts/release/set-package-version.js ${{ inputs.release-type == 'nightly' && '--nightly' || '' }} ${{ inputs.release-type == 'beta' && '--beta' || '' }} ${{ inputs.release-type == 'rc' && '--rc' || '' }} ${{ inputs.version != '' && format('--version ''{0}''', inputs.version) || '' }})
echo "Updated package version to $VERSION"
echo "version=$VERSION" >> $GITHUB_OUTPUT

- name: Figure out the correct npm tag
id: figure-out-npm-tag
shell: bash
run: |
if [ "${{ inputs.release-type == 'commitly' }}" = "true" ]; then
if [ "${{ inputs.release-type == 'nightly' }}" = "true" ]; then
TAG_ARGUMENT="--tag nightly"
else
if [ "${{ inputs.release-type == 'beta' || inputs.release-type == 'rc' }}" = "true" ]; then
Expand Down
16 changes: 7 additions & 9 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
name: Publish release to npm
on:
# For commitlies
# push:
# branches:
# - main
# paths:
# - packages/react-native-gesture-handler/**
# For nightly releases
schedule:
- cron: '27 23 * * *' # at 23:27 every day
# For manual releases
workflow_dispatch:
inputs:
Expand All @@ -14,6 +11,7 @@ on:
type: choice
options:
- stable
- nightly
- beta
- rc
default: stable
Expand Down Expand Up @@ -52,9 +50,9 @@ jobs:
version: ${{ inputs.version }}
dry-run: ${{ inputs.dry-run }}

- name: Publish automatic commitly release
if: ${{ github.event_name == 'push' }}
- name: Publish automatic nightly release
if: ${{ github.event_name == 'schedule' }}
uses: ./.github/actions/publish-npm-package
with:
release-type: 'commitly'
release-type: 'nightly'
dry-run: false
57 changes: 46 additions & 11 deletions scripts/release/__tests__/get-version.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@ jest.mock('../version-utils', () => ({
parseVersion: jest.fn(),
}));

jest.mock('../npm-utils', () => ({
getPackageVersionByTag: jest.fn(),
}));

const { execSync } = require('child_process');
const { getStableBranchVersion, getLatestVersion, getNextPreReleaseVersion, getNextStableVersion, parseVersion } = require('../version-utils');
const { getPackageVersionByTag } = require('../npm-utils');
const { getVersion } = require('../get-version');
const { ReleaseType } = require('../parse-arguments');

Expand All @@ -29,40 +34,52 @@ describe('get-version', () => {
return new RealDate(...args);
}
};

// By default, simulate that the latest nightly was published with a different SHA
getPackageVersionByTag.mockReturnValue('3.0.0-nightly-20260128-previoussha');
});

afterEach(() => {
global.Date = RealDate;
});

describe('getVersion', () => {
// Commitly/nightly release tests
describe('commitly releases', () => {
// Nightly release tests
describe('nightly releases', () => {
test('returns nightly version with date and SHA', () => {
getLatestVersion.mockReturnValue([2, 22, 0, null]);
getLatestVersion.mockReturnValue([3, 0, 0, null]);
execSync.mockReturnValue(Buffer.from('abc123def456789\n'));

const result = getVersion(ReleaseType.COMMITLY);
const result = getVersion(ReleaseType.NIGHTLY);

expect(result).toBe('2.23.0-nightly-20260129-abc123def');
expect(result).toBe('3.1.0-nightly-20260129-abc123def');
expect(getLatestVersion).toHaveBeenCalled();
expect(execSync).toHaveBeenCalledWith('git rev-parse HEAD');
});

test('increments minor version from latest', () => {
getLatestVersion.mockReturnValue([2, 25, 3, null]);
getLatestVersion.mockReturnValue([3, 0, 0, null]);
execSync.mockReturnValue(Buffer.from('fedcba987654321\n'));

const result = getVersion(ReleaseType.NIGHTLY);

expect(result).toBe('3.1.0-nightly-20260129-fedcba987');
});
Comment thread
j-piasecki marked this conversation as resolved.

test('overrides major 2.x release to 3.0.0 nightly', () => {
getLatestVersion.mockReturnValue([2, 22, 0, null]);
execSync.mockReturnValue(Buffer.from('fedcba987654321\n'));

const result = getVersion(ReleaseType.COMMITLY);
const result = getVersion(ReleaseType.NIGHTLY);

expect(result).toBe('2.26.0-nightly-20260129-fedcba987');
expect(result).toBe('3.0.0-nightly-20260129-fedcba987');
});

test('uses first 9 characters of SHA', () => {
getLatestVersion.mockReturnValue([2, 22, 0, null]);
execSync.mockReturnValue(Buffer.from('123456789abcdef0\n'));

const result = getVersion(ReleaseType.COMMITLY);
const result = getVersion(ReleaseType.NIGHTLY);

expect(result).toContain('-123456789');
expect(result).not.toContain('abcdef0');
Expand All @@ -80,11 +97,29 @@ describe('get-version', () => {
getLatestVersion.mockReturnValue([2, 22, 0, null]);
execSync.mockReturnValue(Buffer.from('abc123def\n'));

const result = getVersion(ReleaseType.COMMITLY);
const result = getVersion(ReleaseType.NIGHTLY);

expect(result).toContain('-nightly-20261205-');
});

test('throws when latest nightly SHA matches current SHA', () => {
global.Date = class extends RealDate {
constructor(...args) {
if (args.length === 0) {
return new RealDate('2026-12-05T12:00:00Z');
}
return new RealDate(...args);
}
};
getLatestVersion.mockReturnValue([2, 22, 0, null]);
execSync.mockReturnValue(Buffer.from('abc123def\n'));
getPackageVersionByTag.mockReturnValue('3.0.0-nightly-20261205-abc123def');

expect(() => getVersion(ReleaseType.NIGHTLY)).toThrow(
'Latest nightly version 3.0.0-nightly-20261205-abc123def SHA abc123def is the same as current SHA abc123def'
);
});

test('pads single digit month and day', () => {
global.Date = class extends RealDate {
constructor(...args) {
Expand All @@ -97,7 +132,7 @@ describe('get-version', () => {
getLatestVersion.mockReturnValue([2, 22, 0, null]);
execSync.mockReturnValue(Buffer.from('abc123def\n'));

const result = getVersion(ReleaseType.COMMITLY);
const result = getVersion(ReleaseType.NIGHTLY);

expect(result).toContain('-nightly-20260307-');
});
Expand Down
32 changes: 16 additions & 16 deletions scripts/release/__tests__/parse-arguments.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ describe('parse-arguments', () => {
});

// Single flag tests
test('returns commitly release type with --commitly flag', () => {
process.argv = ['node', 'script.js', '--commitly'];
test('returns nightly release type with --nightly flag', () => {
process.argv = ['node', 'script.js', '--nightly'];
const result = parseArguments();
expect(result).toEqual({ releaseType: ReleaseType.COMMITLY, version: null });
expect(result).toEqual({ releaseType: ReleaseType.NIGHTLY, version: null });
});

test('returns beta release type with --beta flag', () => {
Expand Down Expand Up @@ -64,29 +64,29 @@ describe('parse-arguments', () => {
});

// Mutual exclusivity tests
test('throws error when --commitly and --beta are both provided', () => {
process.argv = ['node', 'script.js', '--commitly', '--beta'];
expect(() => parseArguments()).toThrow('Release flags --commitly, --beta, and --rc are mutually exclusive');
test('throws error when --nightly and --beta are both provided', () => {
process.argv = ['node', 'script.js', '--nightly', '--beta'];
expect(() => parseArguments()).toThrow('Release flags --nightly, --beta, and --rc are mutually exclusive');
});

test('throws error when --commitly and --rc are both provided', () => {
process.argv = ['node', 'script.js', '--commitly', '--rc'];
expect(() => parseArguments()).toThrow('Release flags --commitly, --beta, and --rc are mutually exclusive');
test('throws error when --nightly and --rc are both provided', () => {
process.argv = ['node', 'script.js', '--nightly', '--rc'];
expect(() => parseArguments()).toThrow('Release flags --nightly, --beta, and --rc are mutually exclusive');
});

test('throws error when --beta and --rc are both provided', () => {
process.argv = ['node', 'script.js', '--beta', '--rc'];
expect(() => parseArguments()).toThrow('Release flags --commitly, --beta, and --rc are mutually exclusive');
expect(() => parseArguments()).toThrow('Release flags --nightly, --beta, and --rc are mutually exclusive');
});

test('throws error when all three flags are provided', () => {
process.argv = ['node', 'script.js', '--commitly', '--beta', '--rc'];
expect(() => parseArguments()).toThrow('Release flags --commitly, --beta, and --rc are mutually exclusive');
process.argv = ['node', 'script.js', '--nightly', '--beta', '--rc'];
expect(() => parseArguments()).toThrow('Release flags --nightly, --beta, and --rc are mutually exclusive');
});

// Version not allowed for commitly
test('throws error when version provided for commitly release', () => {
process.argv = ['node', 'script.js', '--commitly', '--version', '2.22.0'];
// Version not allowed for nightly
test('throws error when version provided for nightly release', () => {
process.argv = ['node', 'script.js', '--nightly', '--version', '2.22.0'];
expect(() => parseArguments()).toThrow();
});

Expand Down Expand Up @@ -147,7 +147,7 @@ describe('parse-arguments', () => {
expect(ReleaseType.STABLE).toBe('stable');
expect(ReleaseType.BETA).toBe('beta');
expect(ReleaseType.RELEASE_CANDIDATE).toBe('rc');
expect(ReleaseType.COMMITLY).toBe('commitly');
expect(ReleaseType.NIGHTLY).toBe('nightly');
});
});
});
28 changes: 23 additions & 5 deletions scripts/release/get-version.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,38 @@
const { execSync } = require('child_process');
const { getStableBranchVersion, getLatestVersion, getNextPreReleaseVersion, getNextStableVersion, parseVersion } = require('./version-utils');
const { ReleaseType } = require('./parse-arguments');
const { getPackageVersionByTag } = require('./npm-utils');

function getVersion(releaseType, versionHint = null) {
if (releaseType === ReleaseType.COMMITLY) {
const [major, minor] = getLatestVersion();
if (releaseType === ReleaseType.NIGHTLY) {
let [major, minor] = getLatestVersion();

if (major === 2) {
// If the latest version is 2.x.x, we are still in the beta period for 3.x.x
// Override the values so the resulting version is 3.0.0
// TODO: Remove this once we have a stable 3.x.x release
major = 3;
minor = -1;
}

const currentSHA = execSync('git rev-parse HEAD').toString().trim().slice(0, 9);

const latestNightlyVersion = getPackageVersionByTag('react-native-gesture-handler', 'nightly');
const latestNightlySHA = latestNightlyVersion.split('-').pop();

Comment thread
j-piasecki marked this conversation as resolved.
// Don't publish the same commit twice
if (latestNightlySHA === currentSHA) {
Comment thread
j-piasecki marked this conversation as resolved.
throw new Error(`Latest nightly version ${latestNightlyVersion} SHA ${latestNightlySHA} is the same as current SHA ${currentSHA}`);
}
Comment thread
j-piasecki marked this conversation as resolved.

const currentSHA = execSync('git rev-parse HEAD').toString().trim();
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const currentDate = `${year}${month}${day}`;

const commitlyVersion = `${major}.${minor + 1}.${0}-nightly-${currentDate}-${currentSHA.slice(0, 9)}`;
return commitlyVersion;
const nightlyVersion = `${major}.${minor + 1}.${0}-nightly-${currentDate}-${currentSHA}`;
return nightlyVersion;
} else if (releaseType === ReleaseType.BETA || releaseType === ReleaseType.RELEASE_CANDIDATE) {
let versionToUse = versionHint;

Expand Down
16 changes: 8 additions & 8 deletions scripts/release/parse-arguments.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@ const ReleaseType = {
STABLE: 'stable',
BETA: 'beta',
RELEASE_CANDIDATE: 'rc',
COMMITLY: 'commitly',
NIGHTLY: 'nightly',
};

function parseArguments() {
let version = null;
let isCommitly = false;
let isNightly = false;
let isBeta = false;
let isReleaseCandidate = false;

for (let i = 2; i < process.argv.length; i++) {
const arg = process.argv[i];
if (arg === '--commitly') {
isCommitly = true;
if (arg === '--nightly') {
isNightly = true;
} else if (arg === '--beta') {
isBeta = true;
} else if (arg === '--rc') {
Expand All @@ -31,11 +31,11 @@ function parseArguments() {
}
}

assert([isCommitly, isBeta, isReleaseCandidate].filter(Boolean).length <= 1, 'Release flags --commitly, --beta, and --rc are mutually exclusive; specify at most one');
assert(version === null || isBeta || isReleaseCandidate || !isCommitly, 'Version should not be provided for commitly releases');
assert([isNightly, isBeta, isReleaseCandidate].filter(Boolean).length <= 1, 'Release flags --nightly, --beta, and --rc are mutually exclusive; specify at most one');
assert(version === null || isBeta || isReleaseCandidate || !isNightly, 'Version should not be provided for nightly releases');

const releaseType = isCommitly
? ReleaseType.COMMITLY
const releaseType = isNightly
? ReleaseType.NIGHTLY
: isBeta
? ReleaseType.BETA
: isReleaseCandidate
Expand Down
Loading