* Copyright (c) 2026 Huawei Technologies Co., Ltd.
*
* This source code is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const fs = require("fs");
const path = require("path");
const ROOT = path.resolve(__dirname, "..");
const NOTES_DIR = path.join(ROOT, "docs/zh-cn/05-运维/release-notes");
const CHANGELOG = path.join(ROOT, "docs/zh-cn/05-运维/版本说明.md");
const NOTES_DIR_EN = path.join(ROOT, "docs/en/05-operations/release-notes");
const CHANGELOG_EN = path.join(
ROOT,
"docs/en/05-operations/release-notes-overview.md",
);
function getCurrentRnohVersion() {
const pkg = JSON.parse(
fs.readFileSync(
path.join(ROOT, "packages/react-native-harmony/package.json"),
"utf-8",
),
);
return pkg.version;
}
function getLatestReleaseNote() {
const latest = findLatestNoteFile(NOTES_DIR, null);
if (!latest) {
console.error("Error: no existing release notes found");
process.exit(1);
}
return {
filename: latest.filename,
content: fs.readFileSync(path.join(NOTES_DIR, latest.filename), "utf-8"),
};
}
function extractSection(content, heading) {
const idx = content.indexOf(heading);
if (idx === -1) {
return "";
}
const nextHeading = content.indexOf("\n## ", idx + heading.length);
if (nextHeading === -1) {
return content.slice(idx);
}
return content.slice(idx, nextHeading);
}
function extractFromMarker(content, marker) {
const idx = content.indexOf(marker);
if (idx === -1) {
return "";
}
const nextHeading = content.indexOf("\n## ", idx + marker.length);
if (nextHeading === -1) {
return content.slice(idx);
}
return content.slice(idx, nextHeading).trimEnd();
}
function today() {
const d = new Date();
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, "0");
const day = String(d.getDate()).padStart(2, "0");
return `${y}-${m}-${day}`;
}
function findLatestNoteFile(notesDir, excludeVersion) {
let files = [];
try {
files = fs
.readdirSync(notesDir)
.filter((f) => f.startsWith("react-native-harmony-v") && f.endsWith(".md"));
} catch {
return null;
}
let latest = null;
let latestParts = null;
for (const file of files) {
const match = file.match(/react-native-harmony-v(0\.\d+\.\d+)\.md$/);
if (!match) {
continue;
}
if (excludeVersion && match[1] === excludeVersion) {
continue;
}
const parts = match[1].split(".").map(Number);
if (
!latestParts ||
parts[0] > latestParts[0] ||
(parts[0] === latestParts[0] && parts[1] > latestParts[1]) ||
(parts[0] === latestParts[0] &&
parts[1] === latestParts[1] &&
parts[2] > latestParts[2])
) {
latest = { filename: file, version: match[1] };
latestParts = parts;
}
}
return latest;
}
function escapeRegExp(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function updatePreviousReleaseNote(notesDir, currentVersion, internalLabel) {
const prev = findLatestNoteFile(notesDir, currentVersion);
if (!prev) {
console.log(
`No previous release note found in ${path.relative(ROOT, notesDir)}, skipping update.`,
);
return;
}
const filepath = path.join(notesDir, prev.filename);
let content = fs.readFileSync(filepath, "utf-8");
if (!content.includes(internalLabel)) {
console.log(
`Previous release note ${prev.filename} already marked as published, skipping.`,
);
return;
}
const lines = content.split("\n");
const updatedLines = lines.map((line) => {
if (!line.includes("|") || !line.includes(internalLabel)) {
return line;
}
const nameMatch = line.match(
/\|\s*(react-native-harmony(?:-cli)?)\.tgz\s*\|/,
);
if (!nameMatch) {
return line;
}
const pkgName = nameMatch[1];
const npmUrl = `https://www.npmjs.com/package/@react-native-oh/${pkgName}/v/${prev.version}`;
return line.replace(
new RegExp(escapeRegExp(internalLabel) + "\\s*"),
npmUrl + " ",
);
});
fs.writeFileSync(filepath, updatedLines.join("\n"), "utf-8");
console.log(`✓ Updated ${prev.filename}: marked as published with npm URLs`);
}
function insertOverviewEntry(filePath, newEntry) {
let content = fs.readFileSync(filePath, "utf-8");
const hasCRLF = content.includes("\r\n");
const eol = hasCRLF ? "\r\n" : "\n";
const marker = `## React Native for OpenHarmony 6.x Release`;
const markerPos = content.indexOf(marker);
if (markerPos === -1) {
console.error(
`Error: could not find section header in ${path.relative(ROOT, filePath)}`,
);
return false;
}
const afterHeader = content.indexOf(eol, markerPos) + eol.length;
let insertPos = afterHeader;
while (content.slice(insertPos, insertPos + eol.length) === eol) {
insertPos += eol.length;
}
content = content.slice(0, insertPos) + newEntry + eol + content.slice(insertPos);
fs.writeFileSync(filePath, content, "utf-8");
return true;
}
const rnohVersion = getCurrentRnohVersion();
const latest = getLatestReleaseNote();
const date = today();
updatePreviousReleaseNote(NOTES_DIR, rnohVersion, "内部转测版本");
updatePreviousReleaseNote(NOTES_DIR_EN, rnohVersion, "Internal test build");
const compatSection = extractFromMarker(latest.content, "## 配套版本信息");
const sourceSection = extractFromMarker(latest.content, "## 源码发布地址");
const targetVersionSection = extractFromMarker(
latest.content,
"## 目标版本要求",
);
const minVersionSection = extractFromMarker(latest.content, "## 最低版本要求");
const compatibilitySection = extractFromMarker(latest.content, "## 兼容性说明");
const rnVersionMatch = latest.content.match(/社区RN\s*(\d+\.\d+\.\d+)/);
const rnVersion = rnVersionMatch ? rnVersionMatch[1] : rnohVersion;
const newFilename = `react-native-harmony-v${rnohVersion}.md`;
const newFilepath = path.join(NOTES_DIR, newFilename);
const changelogAnchor = `${rnohVersion.replace(/\./g, "")}-${date}`;
const releaseNote = `# React Native 鸿蒙化版本信息
当前React Native鸿蒙版本基于**社区RN ${rnVersion}**进行适配,发布版本信息如下:
## 版本发布信息
| 名称 | 版本号 | 版本类型 | npm地址 |
| ---------------------------- | ----------- | -------- | ------- |
| react-native-harmony.tgz | ${rnohVersion} | beta | 内部转测版本 |
| react-native-harmony-cli.tgz | ${rnohVersion} | beta | 内部转测版本 |
${compatSection}
${sourceSection}
${targetVersionSection}
${minVersionSection}
${compatibilitySection}
## 变更说明
[${rnohVersion} - ${date}](https://gitcode.com/CPF-RN/ohos_react_native/blob/main/CHANGELOG.md#${changelogAnchor})
`;
fs.writeFileSync(newFilepath, releaseNote, "utf-8");
console.log(`✓ Created ${newFilename}`);
insertOverviewEntry(
CHANGELOG,
`- [RNOH v${rnohVersion}(${date})](./release-notes/react-native-harmony-v${rnohVersion}.md)`,
);
console.log(`✓ Updated 版本说明.md`);
const enLatest = findLatestNoteFile(NOTES_DIR_EN, null);
if (enLatest) {
const enContent = fs.readFileSync(
path.join(NOTES_DIR_EN, enLatest.filename),
"utf-8",
);
const enCompatSection = extractFromMarker(
enContent,
"## Compatible Version Information",
);
const enSourceSection = extractFromMarker(
enContent,
"## Source Code Release Address",
);
const enTargetVersionSection = extractFromMarker(
enContent,
"## Target Version Requirements",
);
const enMinVersionSection = extractFromMarker(
enContent,
"## Minimum Version Requirements",
);
const enCompatibilitySection = extractFromMarker(
enContent,
"## Compatibility Notes",
);
const enRnVersionMatch = enContent.match(/community RN\s*(\d+\.\d+\.\d+)/);
const enRnVersion = enRnVersionMatch ? enRnVersionMatch[1] : rnVersion;
const enReleaseNote = `# React Native Harmony Version Information
This React Native Harmony release is adapted based on **community RN ${enRnVersion}**. Release information is as follows:
## Version Release Information
| Name | Version | Version Type | npm URL |
| ---------------------------- | ------- | ------------- | ------- |
| react-native-harmony.tgz | ${rnohVersion} | beta | Internal test build |
| react-native-harmony-cli.tgz | ${rnohVersion} | beta | Internal test build |
${enCompatSection}
${enSourceSection}
${enTargetVersionSection}
${enMinVersionSection}
${enCompatibilitySection}
## Change Notes
[${rnohVersion} - ${date}](https://gitcode.com/CPF-RN/ohos_react_native/blob/main/CHANGELOG.md#${changelogAnchor})
`;
fs.writeFileSync(path.join(NOTES_DIR_EN, newFilename), enReleaseNote, "utf-8");
console.log(`✓ Created en/${newFilename}`);
insertOverviewEntry(
CHANGELOG_EN,
`- [RNOH v${rnohVersion} (${date})](./release-notes/react-native-harmony-v${rnohVersion}.md)`,
);
console.log(`✓ Updated release-notes-overview.md`);
} else {
console.log("No English release notes found, skipping English generation.");
}
console.log(`\nDone!`);
console.log(` RNOH version: ${rnohVersion}`);