mirror of
https://github.com/appium/appium.git
synced 2026-10-16 20:32:05 +08:00
ci: fix conventional changelog immutable (#22668)
This commit is contained in:
@@ -197,3 +197,5 @@ jobs:
|
||||
run: npm run lint:ci
|
||||
- name: Format check
|
||||
run: npm run format:check
|
||||
- name: Verify changelog generation
|
||||
run: npm run verify:changelog-generation
|
||||
|
||||
Generated
+9
-21
@@ -36,7 +36,7 @@
|
||||
"@types/ws": "8.18.1",
|
||||
"@types/yargs": "17.0.35",
|
||||
"asyncbox": "6.4.2",
|
||||
"conventional-changelog-conventionalcommits": "7.0.2",
|
||||
"conventional-changelog-conventionalcommits": "9.3.1",
|
||||
"cpy-cli": "7.0.0",
|
||||
"cross-env": "10.1.0",
|
||||
"finalhandler": "2.1.1",
|
||||
@@ -241,9 +241,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@conventional-changelog/template": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@conventional-changelog/template/-/template-1.2.1.tgz",
|
||||
"integrity": "sha512-TzlTVpKPjaqW6qOYjQcYUDuGsLCNsvFHVBXkYGTAnf5V37jCWrE5haKNXzz0WZUtVHjrpV76L1buANjwXMfT8w==",
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@conventional-changelog/template/-/template-1.4.0.tgz",
|
||||
"integrity": "sha512-aalGyl7dbB5PArRebDIX43ZvBlXrYm9uWzGJ26t+4SzJVPsOuvfILGGbw5X4yX7i50YEmJ8zvbiWnqH/AAnZqg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -8493,15 +8493,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/conventional-changelog-conventionalcommits": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-7.0.2.tgz",
|
||||
"integrity": "sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==",
|
||||
"dev": true,
|
||||
"version": "9.3.1",
|
||||
"resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-9.3.1.tgz",
|
||||
"integrity": "sha512-dTYtpIacRpcZgrvBYvBfArMmK2xvIpv2TaxM0/ZI5CBtNUzvF2x0t15HsbRABWprS6UPmvj+PzHVjSx4qAVKyw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"compare-func": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/conventional-changelog-preset-loader": {
|
||||
@@ -23185,18 +23185,6 @@
|
||||
"npm": ">=10"
|
||||
}
|
||||
},
|
||||
"packages/semantic-release-config/node_modules/conventional-changelog-conventionalcommits": {
|
||||
"version": "9.3.1",
|
||||
"resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-9.3.1.tgz",
|
||||
"integrity": "sha512-dTYtpIacRpcZgrvBYvBfArMmK2xvIpv2TaxM0/ZI5CBtNUzvF2x0t15HsbRABWprS6UPmvj+PzHVjSx4qAVKyw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"compare-func": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"packages/storage-plugin": {
|
||||
"name": "@appium/storage-plugin",
|
||||
"version": "1.1.8",
|
||||
|
||||
+2
-1
@@ -75,6 +75,7 @@
|
||||
"test:types": "lerna run test:types",
|
||||
"test:unit": "lerna run test",
|
||||
"upload": "gulp github-upload",
|
||||
"verify:changelog-generation": "node ./scripts/verify-changelog-generation.mjs",
|
||||
"zip": "zip -qr ./appium.zip ./packages/appium",
|
||||
"zip-and-upload": "run-s zip upload"
|
||||
},
|
||||
@@ -103,7 +104,7 @@
|
||||
"@types/ws": "8.18.1",
|
||||
"@types/yargs": "17.0.35",
|
||||
"asyncbox": "6.4.2",
|
||||
"conventional-changelog-conventionalcommits": "7.0.2",
|
||||
"conventional-changelog-conventionalcommits": "9.3.1",
|
||||
"cpy-cli": "7.0.0",
|
||||
"cross-env": "10.1.0",
|
||||
"finalhandler": "2.1.1",
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
#!/usr/bin/env node
|
||||
/* eslint-disable no-console */
|
||||
// Regression guard: lerna, its conventional-changelog dependency, and the changelog preset
|
||||
// package can drift out of sync (e.g. a preset transform that mutates commits, fed to a writer
|
||||
// that freezes them) and only fail during an actual release. This replays lerna's own preset
|
||||
// resolution + legacy-config normalization (parserOpts/writerOpts -> parser/writer, replicated
|
||||
// below since lerna doesn't expose it publicly) and a real changelog write, failing if it throws.
|
||||
//
|
||||
// If lerna changes its preset normalization, re-sync from its bundle ("normalizePresetConfig").
|
||||
|
||||
import {readFile} from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import {pathToFileURL} from 'node:url';
|
||||
|
||||
import {ConventionalChangelog} from 'conventional-changelog';
|
||||
import {loadPreset} from 'conventional-changelog-preset-loader';
|
||||
|
||||
// Ported from lerna, which hardcodes Handlebars for legacy string templates - not an assumption
|
||||
// this script adds on top. Not a devDependency of its own: lerna depends on it directly, so it's
|
||||
// always present alongside lerna itself; `main()` fails loudly if that ever stops being true.
|
||||
function normalizeLegacyWriterOptions(writer, Handlebars) {
|
||||
if (!writer || typeof writer !== 'object') {
|
||||
return writer;
|
||||
}
|
||||
const normalized = {...writer};
|
||||
const legacyMainTemplate =
|
||||
typeof writer.mainTemplate === 'string' ? Handlebars.compile(writer.mainTemplate) : undefined;
|
||||
const legacyHeaderPartial =
|
||||
typeof writer.headerPartial === 'string' ? Handlebars.compile(writer.headerPartial) : undefined;
|
||||
const legacyCommitPartial =
|
||||
typeof writer.commitPartial === 'string' ? Handlebars.compile(writer.commitPartial) : undefined;
|
||||
const legacyFooterPartial =
|
||||
typeof writer.footerPartial === 'string' ? Handlebars.compile(writer.footerPartial) : undefined;
|
||||
if (legacyMainTemplate) {
|
||||
normalized.template = (context) =>
|
||||
legacyMainTemplate(context, {
|
||||
data: {root: context},
|
||||
partials: {
|
||||
header:
|
||||
legacyHeaderPartial ||
|
||||
((partialContext) => (writer.headerPartial || context.headerPartial)(partialContext)),
|
||||
commit:
|
||||
legacyCommitPartial ||
|
||||
((commit, options) => (writer.commitPartial || context.commitPartial)(options.data.root, commit)),
|
||||
footer:
|
||||
legacyFooterPartial ||
|
||||
((partialContext) => (writer.footerPartial || context.footerPartial)(partialContext)),
|
||||
},
|
||||
});
|
||||
delete normalized.mainTemplate;
|
||||
delete normalized.headerPartial;
|
||||
delete normalized.commitPartial;
|
||||
delete normalized.footerPartial;
|
||||
} else {
|
||||
if (legacyHeaderPartial) {
|
||||
normalized.headerPartial = (context) => legacyHeaderPartial(context);
|
||||
}
|
||||
if (legacyCommitPartial) {
|
||||
normalized.commitPartial = (context, commit) => legacyCommitPartial(commit, {data: {root: context}});
|
||||
}
|
||||
if (legacyFooterPartial) {
|
||||
normalized.footerPartial = (context) => legacyFooterPartial(context);
|
||||
}
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function normalizePresetConfig(config, Handlebars) {
|
||||
if (
|
||||
config &&
|
||||
(config.parser || config.writer || config.whatBump) &&
|
||||
!config.parserOpts &&
|
||||
!config.writerOpts &&
|
||||
!config.conventionalChangelog
|
||||
) {
|
||||
return {...config, writer: normalizeLegacyWriterOptions(config.writer, Handlebars)};
|
||||
}
|
||||
if (
|
||||
config &&
|
||||
(config.parserOpts ||
|
||||
config.writerOpts ||
|
||||
config.conventionalChangelog ||
|
||||
config.recommendedBumpOpts ||
|
||||
config.gitRawCommitsOpts)
|
||||
) {
|
||||
const normalized = {...config};
|
||||
const cc = config.conventionalChangelog || config;
|
||||
normalized.parser ||= cc.parserOpts || config.parserOpts;
|
||||
normalized.writer ||= cc.writerOpts || config.writerOpts;
|
||||
normalized.writer = normalizeLegacyWriterOptions(normalized.writer, Handlebars);
|
||||
normalized.commits ||= cc.gitRawCommitsOpts || config.gitRawCommitsOpts;
|
||||
normalized.whatBump ||= config.recommendedBumpOpts?.whatBump || config.whatBump;
|
||||
return normalized;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
let Handlebars;
|
||||
try {
|
||||
({default: Handlebars} = await import('handlebars'));
|
||||
} catch (err) {
|
||||
console.error(
|
||||
'Could not resolve "handlebars". This script relies on it being installed alongside ' +
|
||||
'lerna (which depends on it directly) rather than declaring its own dependency - if ' +
|
||||
'lerna stopped depending on it, add "handlebars" back as a devDependency here.',
|
||||
);
|
||||
console.error(err);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
const rootDir = path.resolve(import.meta.dirname, '..');
|
||||
const lernaConfig = JSON.parse(await readFile(path.join(rootDir, 'lerna.json'), 'utf8'));
|
||||
const preset = lernaConfig.changelogPreset ?? 'conventional-changelog-angular';
|
||||
|
||||
const config = normalizePresetConfig(await loadPreset(preset), Handlebars);
|
||||
const generator = new ConventionalChangelog(rootDir);
|
||||
generator.config(config);
|
||||
generator.context({version: '0.0.0-verify'});
|
||||
|
||||
try {
|
||||
// draining the stream is enough to exercise commit parsing and template rendering
|
||||
for await (const chunk of generator.writeStream()) {
|
||||
void chunk;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Changelog generation with preset "${preset}" (as configured in lerna.json) threw an error:\n`);
|
||||
console.error(err);
|
||||
console.error(
|
||||
'\nThis usually means the installed versions of "lerna", its "conventional-changelog" ' +
|
||||
'dependency, and the changelog preset package are incompatible with each other.',
|
||||
);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`OK: changelog generation with preset "${preset}" completed without error.`);
|
||||
}
|
||||
|
||||
// Check if this module is being run directly
|
||||
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
|
||||
main();
|
||||
}
|
||||
Reference in New Issue
Block a user