Skip to content

feat(scripts): allow subset release and major #3174

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/scheduled-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ jobs:
type: minimal
language: dart # we need the dart deps because we use melos to bump dependencies for its client

- run: yarn release
- run: yarn cli release
env:
GITHUB_TOKEN: ${{ secrets.ALGOLIA_BOT_TOKEN }}
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
"github-actions:lint": "eslint --ext=yml .github/",
"postinstall": "husky && yarn workspace eslint-plugin-automation-custom build && yarn workspace scripts build:cli",
"playground:browser": "yarn workspace javascript-browser-playground start",
"release": "yarn workspace scripts createReleasePR",
"scripts:build": "yarn workspace scripts build",
"scripts:lint": "yarn workspace scripts lint",
"scripts:test": "yarn workspace scripts test",
Expand Down
26 changes: 26 additions & 0 deletions scripts/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import { startTestServer } from '../cts/testServer';
import { formatter } from '../formatter.js';
import { generate } from '../generate.js';
import { playground } from '../playground.js';
import { createReleasePR } from '../release/createReleasePR.js';
import { snippetsGenerateMany } from '../snippets/generate.js';
import type { Language } from '../types.js';

import type { LangArg } from './utils.js';
import {
Expand All @@ -23,6 +25,7 @@ import {

const args = {
language: new Argument('[language]', 'The language').choices(PROMPT_LANGUAGES),
languages: new Argument('[language...]', 'The language').choices(PROMPT_LANGUAGES),
clients: new Argument('[client...]', 'The client').choices(getClientChoices('all')),
client: new Argument('[client]', 'The client').choices(PROMPT_CLIENTS),
};
Expand All @@ -44,6 +47,10 @@ const flags = {
flag: '-d, --docs',
description: 'generates the doc specs with the code snippets',
},
major: {
flag: '-m, --major',
description: 'triggers a major release for the given language list',
},
};

program.name('cli');
Expand Down Expand Up @@ -200,4 +207,23 @@ program
await snippetsGenerateMany(generatorList({ language, client, clientList }));
});

program
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be really cool to have a dry run option for this one !

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true!! I'll add it in a subsequent pr

.command('release')
.description('Releases the client')
.addArgument(args.languages)
.option(flags.verbose.flag, flags.verbose.description)
.option(flags.major.flag, flags.major.description)
.action(async (langArgs: LangArg[], { verbose, major }) => {
setVerbose(Boolean(verbose));

if (langArgs.length === 0) {
langArgs = [ALL];
}

await createReleasePR({
languages: langArgs.includes(ALL) ? LANGUAGES : (langArgs as Language[]),
major,
});
});

program.parse();
1 change: 0 additions & 1 deletion scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
"build:cli": "tsc && tsc-alias",
"cleanGeneratedBranch": "NODE_NO_WARNINGS=1 node dist/scripts/ci/codegen/cleanGeneratedBranch.js",
"createMatrix": "NODE_NO_WARNINGS=1 node dist/scripts/ci/githubActions/createMatrix.js",
"createReleasePR": "NODE_NO_WARNINGS=1 RELEASE=true node dist/scripts/release/createReleasePR.js",
"lint": "eslint --ext=ts,js,mjs,cjs .",
"lint:deadcode": "knip",
"pre-commit": "node ./ci/husky/pre-commit.mjs",
Expand Down
85 changes: 85 additions & 0 deletions scripts/release/__tests__/createReleasePR.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { afterAll, describe, expect, it, vi } from "vitest";

import releaseConfig from '../../../config/release.config.json' assert { type: 'json' };
import type { PassedCommit } from '../types.js';
import { LANGUAGES } from "../../common.js";

const gitAuthor = releaseConfig.gitAuthor;

Expand Down Expand Up @@ -450,12 +451,87 @@ describe('createReleasePR', () => {
})
)) as PassedCommit,
],
languages: LANGUAGES,
});

expect(versions.javascript.releaseType).toEqual('major');
expect(versions.javascript.next).toEqual('1.0.0');
});

it('allows forcing major releases', async () => {
const versions = await decideReleaseStrategy({
versions: {
javascript: {
current: '0.0.1',
},
java: {
current: '0.0.1',
},
php: {
current: '0.0.1',
},
},
commits: [],
languages: LANGUAGES,
major: true
});

expect(versions.javascript.releaseType).toEqual('major');
expect(versions.javascript.next).toEqual('1.0.0');

expect(versions.php.releaseType).toEqual('major');
expect(versions.php.next).toEqual('1.0.0');

expect(versions.java.releaseType).toEqual('major');
expect(versions.java.next).toEqual('1.0.0');
});

it('allows releasing subset of clients', async () => {
const versions = await decideReleaseStrategy({
versions: {
javascript: {
current: '0.0.1',
},
java: {
current: '0.0.1',
},
php: {
current: '0.0.1',
},
},
commits: [
(await parseCommit(
buildTestCommit({
type: 'feat',
scope: 'javascript',
message: 'update the API',
})
)) as PassedCommit,
(await parseCommit(
buildTestCommit({
type: 'feat',
scope: 'java',
message: 'update the API',
})
)) as PassedCommit,
(await parseCommit(
buildTestCommit({
type: 'feat',
scope: 'php',
message: 'update the API',
})
)) as PassedCommit,
],
languages: ['php'],
});

expect(versions.javascript.skipRelease).toEqual(true);
expect(versions.java.skipRelease).toEqual(true);
expect(versions.php.skipRelease).toEqual(false);
expect(versions.php.releaseType).toEqual('minor');
expect(versions.php.next).toEqual('0.1.0');
});

it('bumps minor version for feat', async () => {
const versions = await decideReleaseStrategy({
versions: {
Expand All @@ -478,6 +554,7 @@ describe('createReleasePR', () => {
})
)) as PassedCommit,
],
languages: LANGUAGES,
});

expect(versions.php.releaseType).toEqual('minor');
Expand Down Expand Up @@ -506,6 +583,7 @@ describe('createReleasePR', () => {
})
)) as PassedCommit,
],
languages: LANGUAGES,
});

expect(versions.java.releaseType).toEqual('patch');
Expand Down Expand Up @@ -534,6 +612,7 @@ describe('createReleasePR', () => {
})
)) as PassedCommit,
],
languages: LANGUAGES,
});

expect(versions.javascript.noCommit).toEqual(true);
Expand Down Expand Up @@ -565,6 +644,7 @@ describe('createReleasePR', () => {
})
)) as PassedCommit,
],
languages: LANGUAGES,
});

expect(versions.javascript.noCommit).toBeUndefined();
Expand Down Expand Up @@ -600,6 +680,7 @@ describe('createReleasePR', () => {
})
)) as PassedCommit,
],
languages: LANGUAGES,
});

expect(versions.javascript.noCommit).toBeUndefined();
Expand Down Expand Up @@ -642,6 +723,7 @@ describe('createReleasePR', () => {
})
)) as PassedCommit,
],
languages: LANGUAGES,
});

expect(versions.javascript.noCommit).toBeUndefined();
Expand Down Expand Up @@ -677,6 +759,7 @@ describe('createReleasePR', () => {
})
)) as PassedCommit,
],
languages: LANGUAGES,
});
expect(versions.javascript.skipRelease).toEqual(true);
expect(versions.java.skipRelease).toBeUndefined();
Expand Down Expand Up @@ -705,6 +788,7 @@ describe('createReleasePR', () => {
})
)) as PassedCommit,
],
languages: LANGUAGES,
});

expect(versions.javascript.noCommit).toBeUndefined();
Expand Down Expand Up @@ -740,6 +824,7 @@ describe('createReleasePR', () => {
})
)) as PassedCommit,
],
languages: LANGUAGES,
});

expect(versions.javascript.noCommit).toBeUndefined();
Expand Down
94 changes: 51 additions & 43 deletions scripts/release/createReleasePR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,18 +208,37 @@ export function getNextVersion(current: string, releaseType: semver.ReleaseType
export async function decideReleaseStrategy({
versions,
commits,
languages,
major,
}: {
versions: VersionsBeforeBump;
commits: PassedCommit[];
languages: Language[];
major?: boolean;
}): Promise<Versions> {
const versionsToPublish: Versions = {};

for (const [lang, version] of Object.entries(versions)) {
const currentVersion = versions[lang].current;

if (!languages.includes(lang as Language)) {
console.log(`${lang} is not in the given language list, skipping release`);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this console log is interesting for debug mode, but in regular use it would be more interesting to log only the chosen languages

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the release script is pretty verbose which I believe is nice as it's a process that can fail in many ways :/ I'm fine with keeping the log, wdyt?


versionsToPublish[lang] = {
...version,
noCommit: true,
releaseType: null,
skipRelease: true,
next: getNextVersion(currentVersion, null),
};

continue;
}

const commitsPerLang = commits.filter(
(commit) => commit.scope === lang || COMMON_SCOPES.includes(commit.scope),
);

const currentVersion = versions[lang].current;
let nbGitDiff = await getNbGitDiff({
branch: await getLastReleasedTag(),
head: null,
Expand All @@ -233,7 +252,7 @@ export async function decideReleaseStrategy({
nbGitDiff = 1;
}

if (nbGitDiff === 0 || commitsPerLang.length === 0) {
if (!major && (nbGitDiff === 0 || commitsPerLang.length === 0)) {
versionsToPublish[lang] = {
...version,
noCommit: true,
Expand All @@ -253,47 +272,33 @@ export async function decideReleaseStrategy({
continue;
}

// snapshots should not be bumped as prerelease
if (semver.prerelease(currentVersion) && !currentVersion.endsWith('-SNAPSHOT')) {
// if version is like 0.1.2-beta.1, it increases to 0.1.2-beta.2, even if there's a breaking change.
versionsToPublish[lang] = {
...version,
releaseType: 'prerelease',
next: getNextVersion(currentVersion, 'prerelease'),
};

continue;
}

if (commitsPerLang.some((commit) => commit.message.includes('BREAKING CHANGE'))) {
versionsToPublish[lang] = {
...version,
releaseType: 'major',
next: getNextVersion(currentVersion, 'major'),
};

continue;
}

let releaseType: semver.ReleaseType = 'patch';
let skipRelease = false;
const commitTypes = new Set(commitsPerLang.map(({ type }) => type));
if (commitTypes.has('feat')) {
versionsToPublish[lang] = {
...version,
releaseType: 'minor',
next: getNextVersion(currentVersion, 'minor'),
};

continue;
switch (true) {
case major || commitsPerLang.some((commit) => commit.message.includes('BREAKING CHANGE')):
releaseType = 'major';
break;
case semver.prerelease(currentVersion) && !currentVersion.endsWith('-SNAPSHOT'):
releaseType = 'prerelease';
break;
case commitTypes.has('feat'):
releaseType = 'minor';
break;
case !commitTypes.has('fix'):
skipRelease = true;
break;
default:
releaseType = 'patch';
}

versionsToPublish[lang] = {
...version,
releaseType: 'patch',
...(commitTypes.has('fix') ? undefined : { skipRelease: true }),
next: getNextVersion(currentVersion, 'patch'),
releaseType,
skipRelease,
next: getNextVersion(currentVersion, releaseType),
};

continue;
}

return versionsToPublish;
Expand Down Expand Up @@ -475,7 +480,13 @@ async function updateLTS(versions: Versions, withGraphs?: boolean): Promise<void
);
}

async function createReleasePR(): Promise<void> {
export async function createReleasePR({
languages,
major,
}: {
languages: Language[];
major?: boolean;
}): Promise<void> {
await prepareGitEnvironment();

console.log('Searching for commits since last release...');
Expand All @@ -484,11 +495,13 @@ async function createReleasePR(): Promise<void> {
const versions = await decideReleaseStrategy({
versions: readVersions(),
commits: validCommits,
languages,
major,
});

const versionChanges = getVersionChangesText(versions);

console.log('Creating changelogs for all languages...');
console.log(`Creating changelogs for languages: ${languages}...`);
const changelog: Changelog = LANGUAGES.reduce((newChangelog, lang) => {
if (versions[lang].noCommit) {
return newChangelog;
Expand Down Expand Up @@ -571,8 +584,3 @@ async function createReleasePR(): Promise<void> {
console.log(`Release PR #${data.number} is ready for review.`);
console.log(` > ${data.html_url}`);
}

if (import.meta.url.endsWith(process.argv[1])) {
setVerbose(false);
createReleasePR();
}
Loading
Loading