mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-12 16:10:30 +02:00
ci: Account for pnpm-workspace changes in bump-versions.mjs (#28503)
This commit is contained in:
parent
57af37fc61
commit
80de266be4
108
.github/scripts/bump-versions.mjs
vendored
108
.github/scripts/bump-versions.mjs
vendored
|
|
@ -1,4 +1,5 @@
|
||||||
import semver from 'semver';
|
import semver from 'semver';
|
||||||
|
import { parse } from 'yaml';
|
||||||
import { writeFile, readFile } from 'fs/promises';
|
import { writeFile, readFile } from 'fs/promises';
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
import child_process from 'child_process';
|
import child_process from 'child_process';
|
||||||
|
|
@ -7,14 +8,19 @@ import assert from 'assert';
|
||||||
|
|
||||||
const exec = promisify(child_process.exec);
|
const exec = promisify(child_process.exec);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string | semver.SemVer} currentVersion
|
||||||
|
*/
|
||||||
function generateExperimentalVersion(currentVersion) {
|
function generateExperimentalVersion(currentVersion) {
|
||||||
const parsed = semver.parse(currentVersion);
|
const parsed = semver.parse(currentVersion);
|
||||||
if (!parsed) throw new Error(`Invalid version: ${currentVersion}`);
|
if (!parsed) throw new Error(`Invalid version: ${currentVersion}`);
|
||||||
|
|
||||||
// Check if it's already an experimental version
|
// Check if it's already an experimental version
|
||||||
if (parsed.prerelease.length > 0 && parsed.prerelease[0] === 'exp') {
|
if (parsed.prerelease.length > 0 && parsed.prerelease[0] === 'exp') {
|
||||||
|
const minor = parsed.prerelease[1] || 0;
|
||||||
|
const minorInt = typeof minor === 'string' ? parseInt(minor) : minor;
|
||||||
// Increment the experimental minor version
|
// Increment the experimental minor version
|
||||||
const expMinor = (parsed.prerelease[1] || 0) + 1;
|
const expMinor = minorInt + 1;
|
||||||
return `${parsed.major}.${parsed.minor}.${parsed.patch}-exp.${expMinor}`;
|
return `${parsed.major}.${parsed.minor}.${parsed.patch}-exp.${expMinor}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -23,7 +29,10 @@ function generateExperimentalVersion(currentVersion) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const rootDir = process.cwd();
|
const rootDir = process.cwd();
|
||||||
const releaseType = process.env.RELEASE_TYPE;
|
|
||||||
|
const releaseType = /** @type { import('semver').ReleaseType | "experimental" } */ (
|
||||||
|
process.env.RELEASE_TYPE
|
||||||
|
);
|
||||||
assert.match(releaseType, /^(patch|minor|major|experimental|premajor)$/, 'Invalid RELEASE_TYPE');
|
assert.match(releaseType, /^(patch|minor|major|experimental|premajor)$/, 'Invalid RELEASE_TYPE');
|
||||||
|
|
||||||
// TODO: if releaseType is `auto` determine release type based on the changelog
|
// TODO: if releaseType is `auto` determine release type based on the changelog
|
||||||
|
|
@ -39,8 +48,12 @@ const packages = JSON.parse(
|
||||||
|
|
||||||
const packageMap = {};
|
const packageMap = {};
|
||||||
for (let { name, path, version, private: isPrivate } of packages) {
|
for (let { name, path, version, private: isPrivate } of packages) {
|
||||||
if (isPrivate && path !== rootDir) continue;
|
if (isPrivate && path !== rootDir) {
|
||||||
if (path === rootDir) name = 'monorepo-root';
|
continue;
|
||||||
|
}
|
||||||
|
if (path === rootDir) {
|
||||||
|
name = 'monorepo-root';
|
||||||
|
}
|
||||||
|
|
||||||
const isDirty = await exec(`git diff --quiet HEAD ${lastTag} -- ${path}`)
|
const isDirty = await exec(`git diff --quiet HEAD ${lastTag} -- ${path}`)
|
||||||
.then(() => false)
|
.then(() => false)
|
||||||
|
|
@ -57,11 +70,94 @@ assert.ok(
|
||||||
// Propagate isDirty transitively: if a package's dependency will be bumped,
|
// Propagate isDirty transitively: if a package's dependency will be bumped,
|
||||||
// that package also needs a bump (e.g. design-system → editor-ui → cli).
|
// that package also needs a bump (e.g. design-system → editor-ui → cli).
|
||||||
|
|
||||||
|
// Detect root-level changes that affect resolved dep versions without touching individual
|
||||||
|
// package.json files: pnpm.overrides (applies to all specifiers)
|
||||||
|
// and pnpm-workspace.yaml catalog entries (applies only to deps using a "catalog:…" specifier).
|
||||||
|
|
||||||
|
const rootPkgJson = JSON.parse(await readFile(resolve(rootDir, 'package.json'), 'utf-8'));
|
||||||
|
const rootPkgJsonAtTag = await exec(`git show ${lastTag}:package.json`)
|
||||||
|
.then(({ stdout }) => JSON.parse(stdout))
|
||||||
|
.catch(() => ({}));
|
||||||
|
|
||||||
|
const getOverrides = (pkg) => ({ ...pkg.pnpm?.overrides, ...pkg.overrides });
|
||||||
|
|
||||||
|
const currentOverrides = getOverrides(rootPkgJson);
|
||||||
|
const previousOverrides = getOverrides(rootPkgJsonAtTag);
|
||||||
|
|
||||||
|
const changedOverrides = new Set(
|
||||||
|
Object.keys({ ...currentOverrides, ...previousOverrides }).filter(
|
||||||
|
(k) => currentOverrides[k] !== previousOverrides[k],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const parseWorkspaceYaml = (content) => {
|
||||||
|
try {
|
||||||
|
return /** @type {Record<string, unknown>} */ (parse(content) ?? {});
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const workspaceYaml = parseWorkspaceYaml(
|
||||||
|
await readFile(resolve(rootDir, 'pnpm-workspace.yaml'), 'utf-8').catch(() => ''),
|
||||||
|
);
|
||||||
|
const workspaceYamlAtTag = parseWorkspaceYaml(
|
||||||
|
await exec(`git show ${lastTag}:pnpm-workspace.yaml`)
|
||||||
|
.then(({ stdout }) => stdout)
|
||||||
|
.catch(() => ''),
|
||||||
|
);
|
||||||
|
const getCatalogs = (ws) => {
|
||||||
|
const result = new Map();
|
||||||
|
if (ws.catalog) {
|
||||||
|
result.set('default', /** @type {Record<string,string>} */ (ws.catalog));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [name, entries] of Object.entries(ws.catalogs ?? {})) {
|
||||||
|
result.set(name, entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
// changedCatalogEntries: Map<catalogName, Set<depName>>
|
||||||
|
const currentCatalogs = getCatalogs(workspaceYaml);
|
||||||
|
const previousCatalogs = getCatalogs(workspaceYamlAtTag);
|
||||||
|
const changedCatalogEntries = new Map();
|
||||||
|
for (const catalogName of new Set([...currentCatalogs.keys(), ...previousCatalogs.keys()])) {
|
||||||
|
const current = currentCatalogs.get(catalogName) ?? {};
|
||||||
|
const previous = previousCatalogs.get(catalogName) ?? {};
|
||||||
|
const changedDeps = new Set(
|
||||||
|
Object.keys({ ...current, ...previous }).filter((dep) => current[dep] !== previous[dep]),
|
||||||
|
);
|
||||||
|
if (changedDeps.size > 0) {
|
||||||
|
changedCatalogEntries.set(catalogName, changedDeps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store full dep objects (with specifiers) so we can inspect "catalog:…" values below.
|
||||||
const depsByPackage = {};
|
const depsByPackage = {};
|
||||||
for (const packageName in packageMap) {
|
for (const packageName in packageMap) {
|
||||||
const packageFile = resolve(packageMap[packageName].path, 'package.json');
|
const packageFile = resolve(packageMap[packageName].path, 'package.json');
|
||||||
const packageJson = JSON.parse(await readFile(packageFile, 'utf-8'));
|
const packageJson = JSON.parse(await readFile(packageFile, 'utf-8'));
|
||||||
depsByPackage[packageName] = Object.keys(packageJson.dependencies || {});
|
depsByPackage[packageName] = /** @type {Record<string,string>} */ (
|
||||||
|
packageJson.dependencies ?? {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark packages dirty if any dep had a root-level override or catalog version change.
|
||||||
|
for (const [packageName, deps] of Object.entries(depsByPackage)) {
|
||||||
|
if (packageMap[packageName].isDirty) continue;
|
||||||
|
for (const [dep, specifier] of Object.entries(deps)) {
|
||||||
|
if (changedOverrides.has(dep)) {
|
||||||
|
packageMap[packageName].isDirty = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (typeof specifier === 'string' && specifier.startsWith('catalog:')) {
|
||||||
|
const catalogName = specifier === 'catalog:' ? 'default' : specifier.slice(8);
|
||||||
|
if (changedCatalogEntries.get(catalogName)?.has(dep)) {
|
||||||
|
packageMap[packageName].isDirty = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let changed = true;
|
let changed = true;
|
||||||
|
|
@ -69,7 +165,7 @@ while (changed) {
|
||||||
changed = false;
|
changed = false;
|
||||||
for (const packageName in packageMap) {
|
for (const packageName in packageMap) {
|
||||||
if (packageMap[packageName].isDirty) continue;
|
if (packageMap[packageName].isDirty) continue;
|
||||||
if (depsByPackage[packageName].some((dep) => packageMap[dep]?.isDirty)) {
|
if (Object.keys(depsByPackage[packageName]).some((dep) => packageMap[dep]?.isDirty)) {
|
||||||
packageMap[packageName].isDirty = true;
|
packageMap[packageName].isDirty = true;
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
3
.github/scripts/package.json
vendored
3
.github/scripts/package.json
vendored
|
|
@ -11,7 +11,8 @@
|
||||||
"glob": "13.0.6",
|
"glob": "13.0.6",
|
||||||
"minimatch": "10.2.4",
|
"minimatch": "10.2.4",
|
||||||
"semver": "7.7.4",
|
"semver": "7.7.4",
|
||||||
"tempfile": "6.0.1"
|
"tempfile": "6.0.1",
|
||||||
|
"yaml": "^2.8.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"conventional-changelog-angular": "8.3.0"
|
"conventional-changelog-angular": "8.3.0"
|
||||||
|
|
|
||||||
10
.github/scripts/pnpm-lock.yaml
vendored
10
.github/scripts/pnpm-lock.yaml
vendored
|
|
@ -32,6 +32,9 @@ importers:
|
||||||
tempfile:
|
tempfile:
|
||||||
specifier: 6.0.1
|
specifier: 6.0.1
|
||||||
version: 6.0.1
|
version: 6.0.1
|
||||||
|
yaml:
|
||||||
|
specifier: ^2.8.3
|
||||||
|
version: 2.8.3
|
||||||
devDependencies:
|
devDependencies:
|
||||||
conventional-changelog-angular:
|
conventional-changelog-angular:
|
||||||
specifier: 8.3.0
|
specifier: 8.3.0
|
||||||
|
|
@ -292,6 +295,11 @@ packages:
|
||||||
wordwrap@1.0.0:
|
wordwrap@1.0.0:
|
||||||
resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==}
|
resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==}
|
||||||
|
|
||||||
|
yaml@2.8.3:
|
||||||
|
resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==}
|
||||||
|
engines: {node: '>= 14.6'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
|
|
||||||
'@actions/github@9.0.0':
|
'@actions/github@9.0.0':
|
||||||
|
|
@ -540,3 +548,5 @@ snapshots:
|
||||||
walk-up-path@4.0.0: {}
|
walk-up-path@4.0.0: {}
|
||||||
|
|
||||||
wordwrap@1.0.0: {}
|
wordwrap@1.0.0: {}
|
||||||
|
|
||||||
|
yaml@2.8.3: {}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user