- Implements Apache APISIX packaging for Cloudron platform. - Includes Dockerfile, CloudronManifest.json, and start.sh. - Configured to use Cloudron's etcd addon. 🤖 Generated with Gemini CLI Co-Authored-By: Gemini <noreply@google.com>
239 lines
7.4 KiB
TypeScript
Executable File
239 lines
7.4 KiB
TypeScript
Executable File
/*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership.
|
|
* The ASF licenses this file to You 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.
|
|
*/
|
|
|
|
import { execSync } from 'child_process';
|
|
import { readFileSync } from 'fs';
|
|
import { join } from 'path';
|
|
|
|
// Types
|
|
interface Version {
|
|
tag: string;
|
|
ref: string;
|
|
}
|
|
|
|
interface PR {
|
|
number: number;
|
|
title: string;
|
|
commit: string;
|
|
}
|
|
|
|
// Configuration
|
|
const IGNORE_TYPES = [
|
|
'docs',
|
|
'chore',
|
|
'test',
|
|
'ci'
|
|
];
|
|
|
|
const IGNORE_PRS = [
|
|
// 3.9.0
|
|
10655, 10857, 10858, 10887, 10959, 11029, 11041, 11053, 11055, 11061, 10976, 10984, 11025,
|
|
// 3.10.0
|
|
11105, 11128, 11169, 11171, 11280, 11333, 11081, 11202, 11469,
|
|
// 3.11.0
|
|
11463, 11570,
|
|
// 3.12.0
|
|
11769, 11816, 11881, 11905, 11924, 11926, 11973, 11991, 11992, 11829,
|
|
// 3.13.0
|
|
9945, 11420, 11765, 12036, 12048, 12057, 12076, 12122, 12123, 12168, 12199, 12218, 12225, 12272, 12277, 12300, 12306, 12329, 12353, 12364, 12375, 12358
|
|
];
|
|
|
|
|
|
function getGitRef(version: string): string {
|
|
try {
|
|
execSync(`git rev-parse ${version}`, { stdio: 'ignore' });
|
|
return version;
|
|
} catch {
|
|
return 'HEAD';
|
|
}
|
|
}
|
|
|
|
function extractVersionsFromChangelog(): Version[] {
|
|
const changelogPath = join(process.cwd(), '..', 'CHANGELOG.md');
|
|
const content = readFileSync(changelogPath, 'utf-8');
|
|
const versionRegex = /^## ([0-9]+\.[0-9]+\.[0-9]+)/gm;
|
|
const versions: Version[] = [];
|
|
let match;
|
|
|
|
while ((match = versionRegex.exec(content)) !== null) {
|
|
const tag = match[1];
|
|
versions.push({
|
|
tag,
|
|
ref: getGitRef(tag)
|
|
});
|
|
}
|
|
|
|
return versions;
|
|
}
|
|
|
|
function extractPRsFromChangelog(startTag: string, endTag: string): number[] {
|
|
const changelogPath = join(process.cwd(), '..', 'CHANGELOG.md');
|
|
const content = readFileSync(changelogPath, 'utf-8');
|
|
const lines = content.split('\n');
|
|
let inRange = false;
|
|
const prs: number[] = [];
|
|
|
|
for (const line of lines) {
|
|
if (line.startsWith(`## ${startTag}`)) {
|
|
inRange = true;
|
|
continue;
|
|
}
|
|
if (inRange && line.startsWith(`## ${endTag}`)) {
|
|
break;
|
|
}
|
|
if (inRange) {
|
|
const match = line.match(/#(\d+)/);
|
|
if (match) {
|
|
prs.push(parseInt(match[1], 10));
|
|
}
|
|
}
|
|
}
|
|
|
|
return prs.sort((a, b) => a - b);
|
|
}
|
|
|
|
function shouldIgnoreCommitMessage(message: string): boolean {
|
|
// Extract the commit message part (remove the commit hash)
|
|
const messagePart = message.split(' ').slice(1).join(' ');
|
|
|
|
// Check if the message starts with any of the ignored types
|
|
for (const type of IGNORE_TYPES) {
|
|
// Check simple format: "type: message"
|
|
if (messagePart.startsWith(`${type}:`)) {
|
|
return true;
|
|
}
|
|
// Check format with scope: "type(scope): message"
|
|
if (messagePart.startsWith(`${type}(`)) {
|
|
const closingBracketIndex = messagePart.indexOf('):');
|
|
if (closingBracketIndex !== -1) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function extractPRsFromGitLog(oldRef: string, newRef: string): PR[] {
|
|
const log = execSync(`git log ${oldRef}..${newRef} --oneline`, { encoding: 'utf-8' });
|
|
const prs: PR[] = [];
|
|
|
|
for (const line of log.split('\n')) {
|
|
if (!line.trim()) continue;
|
|
|
|
// Check if this commit should be ignored
|
|
if (shouldIgnoreCommitMessage(line)) continue;
|
|
|
|
// Find PR number
|
|
const prMatch = line.match(/#(\d+)/);
|
|
if (prMatch) {
|
|
const prNumber = parseInt(prMatch[1], 10);
|
|
if (!IGNORE_PRS.includes(prNumber)) {
|
|
prs.push({
|
|
number: prNumber,
|
|
title: line,
|
|
commit: line.split(' ')[0]
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
return prs.sort((a, b) => a.number - b.number);
|
|
}
|
|
|
|
function findMissingPRs(changelogPRs: number[], gitPRs: PR[]): PR[] {
|
|
const changelogPRSet = new Set(changelogPRs);
|
|
return gitPRs.filter(pr => !changelogPRSet.has(pr.number));
|
|
}
|
|
|
|
function versionGreaterThan(v1: string, v2: string): boolean {
|
|
// Remove 'v' prefix if present
|
|
const cleanV1 = v1.replace(/^v/, '');
|
|
const cleanV2 = v2.replace(/^v/, '');
|
|
|
|
// Split version strings into arrays of numbers
|
|
const v1Parts = cleanV1.split('.').map(Number);
|
|
const v2Parts = cleanV2.split('.').map(Number);
|
|
|
|
// Compare each part
|
|
for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
|
|
const v1Part = v1Parts[i] || 0;
|
|
const v2Part = v2Parts[i] || 0;
|
|
|
|
if (v1Part > v2Part) return true;
|
|
if (v1Part < v2Part) return false;
|
|
}
|
|
|
|
// If all parts are equal, return false
|
|
return false;
|
|
}
|
|
|
|
// Main function
|
|
async function main() {
|
|
try {
|
|
const versions = extractVersionsFromChangelog();
|
|
let hasErrors = false;
|
|
|
|
for (let i = 0; i < versions.length - 1; i++) {
|
|
const newVersion = versions[i];
|
|
const oldVersion = versions[i + 1];
|
|
|
|
// Skip if new version is less than or equal to 3.8.0
|
|
if (!versionGreaterThan(newVersion.tag, '3.8.0')) {
|
|
continue;
|
|
}
|
|
|
|
console.log(`\n=== Checking changes between ${newVersion.tag} (${newVersion.ref}) and ${oldVersion.tag} (${oldVersion.ref}) ===`);
|
|
|
|
const changelogPRs = extractPRsFromChangelog(newVersion.tag, oldVersion.tag);
|
|
const gitPRs = extractPRsFromGitLog(oldVersion.ref, newVersion.ref);
|
|
const missingPRs = findMissingPRs(changelogPRs, gitPRs);
|
|
|
|
console.log(`\n=== PR Comparison Results for ${newVersion.tag} ===`);
|
|
|
|
if (missingPRs.length === 0) {
|
|
console.log(`\n✅ All PRs are included in CHANGELOG.md for version ${newVersion.tag}`);
|
|
} else {
|
|
console.log(`\n❌ Missing PRs in CHANGELOG.md for version ${newVersion.tag} (sorted):`);
|
|
missingPRs.forEach(pr => {
|
|
console.log(` #${pr.number}`);
|
|
});
|
|
|
|
console.log(`\nDetailed information about missing PRs for version ${newVersion.tag}:`);
|
|
missingPRs.forEach(pr => {
|
|
console.log(`\nPR #${pr.number}:`);
|
|
console.log(` - ${pr.title}`);
|
|
console.log(` - PR URL: https://github.com/apache/apisix/pull/${pr.number}`);
|
|
});
|
|
|
|
console.log('Note: If you confirm that a PR should not appear in the changelog, please add its number to the IGNORE_PRS array in this script.');
|
|
hasErrors = true;
|
|
}
|
|
}
|
|
|
|
if (hasErrors) {
|
|
process.exit(1);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
(async () => {
|
|
await main();
|
|
})();
|