Skip to content

Commit 674ca60

Browse files
authored
chore: migrate from semantic-release to nx (#2478)
1 parent a451e8c commit 674ca60

File tree

7 files changed

+1860
-2113
lines changed

7 files changed

+1860
-2113
lines changed

.github/semantic-release.json

Lines changed: 0 additions & 31 deletions
This file was deleted.

.github/workflows/build.yml

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -625,8 +625,8 @@ jobs:
625625
timeout-minutes: 60
626626
release:
627627
permissions:
628-
contents: write # create releases (semantic-release)
629-
id-token: write # enable use of OIDC for npm provenance (semantic-release)
628+
contents: write # create releases (Nx Release)
629+
id-token: write # enable use of OIDC for npm provenance (Nx Release)
630630
needs:
631631
- review
632632
- ios
@@ -652,12 +652,18 @@ jobs:
652652
- name: Verify tarball content
653653
run: |
654654
yarn test test/pack.test.ts
655+
- name: Release (dry run)
656+
if: ${{ github.ref != 'refs/heads/trunk' }}
657+
run: |
658+
yarn nx release --dry-run
655659
- name: Release
660+
if: ${{ github.ref == 'refs/heads/trunk' }}
656661
env:
657662
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
658-
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
663+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
664+
NPM_CONFIG_PROVENANCE: true
659665
run: |
660-
yarn semantic-release
666+
yarn nx release
661667
autobot:
662668
name: "Autobot"
663669
permissions:

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
.cxx/
1010
.gradle/
1111
.idea/
12+
.nx/cache
13+
.nx/workspace-data
1214
.vs/
1315
.watchman-*
1416
.yarn/*

nx.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"$schema": "./node_modules/nx/schemas/nx-schema.json",
3+
"defaultBase": "trunk",
4+
"release": {
5+
"projects": ["react-native-test-app"],
6+
"projectsRelationship": "independent",
7+
"changelog": {
8+
"workspaceChangelog": false,
9+
"projectChangelogs": {
10+
"createRelease": "github",
11+
"file": false,
12+
"renderOptions": {
13+
"applyUsernameToAuthors": false
14+
}
15+
}
16+
},
17+
"version": {
18+
"conventionalCommits": true,
19+
"versionActionsOptions": {
20+
"skipLockFileUpdate": true
21+
}
22+
},
23+
"releaseTagPattern": "{version}",
24+
"git": {
25+
"commit": false
26+
}
27+
}
28+
}

package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
},
6868
"scripts": {
6969
"format:c": "clang-format -i $(git ls-files '*.cpp' '*.h' '*.m' '*.mm')",
70-
"format:js": "prettier --write --log-level error $(git ls-files '*.[cm][jt]s' '*.[jt]s' '*.tsx' '*.yml' 'CONTRIBUTING.md' 'README.md' 'test/**/*.json' ':!:.yarn/releases')",
70+
"format:js": "prettier --write --log-level error $(git ls-files '*.[cm][jt]s' '*.[jt]s' '*.tsx' '*.yml' '.github/*.json' 'CONTRIBUTING.md' 'README.md' 'nx.json' 'test/**/*.json' ':!:.yarn/releases')",
7171
"format:swift": "swiftformat $(git ls-files '*.swift')",
7272
"generate:code": "node --experimental-transform-types --no-warnings scripts/internal/generate-manifest.mts",
7373
"generate:docs": "node --experimental-transform-types --no-warnings scripts/internal/generate-manifest-docs.mts",
@@ -124,10 +124,13 @@
124124
"@babel/preset-env": "^7.20.0",
125125
"@expo/config-plugins": "^9.0.0",
126126
"@microsoft/eslint-plugin-sdl": "^1.0.0",
127+
"@nx/js": "^21.0.0",
127128
"@react-native-community/cli": "^15.0.1",
128129
"@react-native-community/template": "^0.78.0",
129130
"@rnx-kit/eslint-plugin": "^0.8.0",
130131
"@rnx-kit/tsconfig": "^2.0.0",
132+
"@swc-node/register": "^1.10.0",
133+
"@swc/core": "^1.11.0",
131134
"@types/js-yaml": "^4.0.5",
132135
"@types/mustache": "^4.0.0",
133136
"@types/node": "^22.0.0",
@@ -138,13 +141,13 @@
138141
"js-yaml": "^4.1.0",
139142
"memfs": "^4.0.0",
140143
"minimatch": "^9.0.0",
144+
"nx": "^21.0.0",
141145
"prettier": "^3.0.0",
142146
"prettier-plugin-organize-imports": "^4.1.0",
143147
"react": "19.0.0",
144148
"react-native": "^0.78.0",
145149
"react-native-macos": "^0.78.0",
146150
"react-native-windows": "^0.78.0",
147-
"semantic-release": "^24.0.0",
148151
"suggestion-bot": "^3.0.0",
149152
"typescript": "^5.0.0"
150153
},
@@ -179,7 +182,6 @@
179182
"@react-native/js-polyfills": "^0.78.0",
180183
"@react-native/normalize-colors": "^0.78.0",
181184
"@react-native/virtualized-lists": "^0.78.0",
182-
"@semantic-release/npm/npm": "link:./example",
183185
"appium/ajv": "^8.17.1",
184186
"appium/axios": "^1.8.3",
185187
"appium/semver": "^7.7.1",
@@ -196,6 +198,7 @@
196198
"safe-buffer": "~5.2.1"
197199
},
198200
"workspaces": [
201+
".",
199202
"example"
200203
],
201204
"defaultPlatformPackages": {
@@ -205,8 +208,5 @@
205208
"visionos": "@callstack/react-native-visionos",
206209
"windows": "react-native-windows"
207210
},
208-
"prettier": "./.github/prettierrc.json",
209-
"release": {
210-
"extends": "./.github/semantic-release.json"
211-
}
211+
"prettier": "./.github/prettierrc.json"
212212
}

scripts/internal/release-notes.mts

Lines changed: 123 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,41 @@
11
/**
22
* This script is only used to help write release announcements.
33
*/
4-
// @ts-expect-error Could not find a declaration file for module
5-
import { generateNotes } from "@semantic-release/release-notes-generator";
64
import { spawn } from "node:child_process";
75
import * as path from "node:path";
8-
import { URL, fileURLToPath } from "node:url";
9-
import { readJSONFile } from "../helpers.js";
10-
import type { Manifest } from "../types.js";
6+
import { fileURLToPath } from "node:url";
117

12-
function reformat(
13-
output: string,
14-
lastRelease: string,
15-
nextRelease: string
16-
): string {
17-
const replacements: [RegExp, string][] = [
18-
[/^# .*/m, `📣 react-native-test-app ${nextRelease}`],
19-
[/^### .*/m, `Other fixes since ${lastRelease}:`],
20-
[/^\* \*\*android:\*\*/gm, "* **Android:**"],
21-
[/^\* \*\*apple:\*\*/gm, "* **Apple:**"],
22-
[/^\* \*\*ios:\*\*/gm, "* **iOS:**"],
23-
[/^\* \*\*macos:\*\*/gm, "* **macOS:**"],
24-
[/^\* \*\*visionos:\*\*/gm, "* **visionOS:**"],
25-
[/^\* \*\*windows:\*\*/gm, "* **Windows:**"],
26-
[/\s*\(\[#\d+\]\(https:\/\/github.com.*/gm, ""],
27-
];
28-
return replacements
29-
.reduce(
30-
(output, [search, replace]) => output.replace(search, replace),
31-
output
32-
)
33-
.trim();
8+
type Commit = {
9+
hash: string;
10+
message: string;
11+
};
12+
13+
type Group =
14+
| "general"
15+
| "android"
16+
| "apple"
17+
| "ios"
18+
| "macos"
19+
| "visionos"
20+
| "windows";
21+
22+
type Changes = Record<string, Record<Group, string[]>>;
23+
24+
function assertCategory(category: string): asserts category is "feat" | "fix" {
25+
if (category !== "feat" && category !== "fix") {
26+
throw new Error(`Unknown category: ${category}`);
27+
}
3428
}
3529

36-
function repositoryUrl() {
37-
const p = fileURLToPath(new URL("../../package.json", import.meta.url));
38-
const manifest = readJSONFile<Manifest>(p);
39-
return manifest.repository?.url;
30+
function capitalize(s: string): string {
31+
return String(s[0]).toUpperCase() + s.substring(1);
4032
}
4133

42-
/**
43-
* @param {string} lastRelease
44-
* @param {string} nextRelease
45-
*/
46-
function main(lastRelease: string, nextRelease: string): void {
34+
function getCommits(
35+
lastRelease: string,
36+
nextRelease: string,
37+
callback: (commits: Commit[]) => void
38+
): void {
4739
const args = [
4840
"log",
4941
`--pretty=format:{ "hash": "%H", "message": "%s" }`,
@@ -68,33 +60,113 @@ function main(lastRelease: string, nextRelease: string): void {
6860
.replaceAll('"', '\\"')
6961
.replaceAll(""", '"')
7062
.replaceAll("\n", ",");
63+
7164
const commits = JSON.parse(output);
7265
if (commits.length === 0) {
7366
return;
7467
}
7568

76-
const context = {
77-
commits,
78-
lastRelease: { gitTag: lastRelease },
79-
nextRelease: { gitTag: nextRelease },
80-
options: {
81-
repositoryUrl: repositoryUrl(),
82-
},
83-
cwd: process.cwd(),
84-
};
85-
86-
const releaseNotes: Promise<string> = generateNotes({}, context);
87-
releaseNotes
88-
.then((output) => reformat(output, lastRelease, nextRelease))
89-
.then((output) => console.log(output));
69+
callback(commits);
9070
});
9171
}
9272

73+
function sanitizeGroup(group: string): Group {
74+
switch (group) {
75+
case "android":
76+
case "apple":
77+
case "ios":
78+
case "macos":
79+
case "visionos":
80+
case "windows":
81+
return group;
82+
default:
83+
return "general";
84+
}
85+
}
86+
87+
function parseCommits(commits: Commit[]): Changes {
88+
const changes: Changes = {
89+
feat: {
90+
general: [],
91+
android: [],
92+
apple: [],
93+
ios: [],
94+
macos: [],
95+
visionos: [],
96+
windows: [],
97+
},
98+
fix: {
99+
general: [],
100+
android: [],
101+
apple: [],
102+
ios: [],
103+
macos: [],
104+
visionos: [],
105+
windows: [],
106+
},
107+
};
108+
109+
for (const { message } of commits) {
110+
const m = message.match(/^(feat|fix)(?:\((.*?)\))?: (.*)$/);
111+
if (m) {
112+
const [, cat, group, message] = m;
113+
assertCategory(cat);
114+
changes[cat][sanitizeGroup(group)].push(message);
115+
}
116+
}
117+
118+
return changes;
119+
}
120+
121+
function renderGroup(group: string): string {
122+
switch (group) {
123+
case "android":
124+
return "**Android:** ";
125+
case "apple":
126+
return "**Apple:** ";
127+
case "ios":
128+
return "**iOS:** ";
129+
case "macos":
130+
return "**macOS:** ";
131+
case "visionos":
132+
return "**visionOS:** ";
133+
case "windows":
134+
return "**Windows:** ";
135+
default:
136+
return "";
137+
}
138+
}
139+
140+
function renderCategory(
141+
header: string,
142+
changes: Changes[string],
143+
output: string[]
144+
): string[] {
145+
const groups = Object.entries(changes);
146+
if (groups.length > 0) {
147+
output.push("", header, "");
148+
for (const [group, entries] of groups) {
149+
for (const entry of entries) {
150+
output.push(`- ${renderGroup(group)}${capitalize(entry)}`);
151+
}
152+
}
153+
}
154+
return output;
155+
}
156+
93157
const [, , lastRelease, nextRelease] = process.argv;
94158
if (!lastRelease || !nextRelease) {
95159
const thisScript = path.basename(fileURLToPath(import.meta.url));
96160
console.log(`Usage: ${thisScript} <start tag> <end tag>`);
97161
process.exitCode = 1;
98162
} else {
99-
main(lastRelease, nextRelease);
163+
getCommits(lastRelease, nextRelease, (commits) => {
164+
const { feat, fix } = parseCommits(commits);
165+
166+
const lines = [`📣 react-native-test-app ${nextRelease}`];
167+
renderCategory("New features:", feat, lines);
168+
renderCategory(`Fixes since ${lastRelease}:`, fix, lines);
169+
170+
console.log(lines.join("\n"));
171+
});
100172
}

0 commit comments

Comments
 (0)