Skip to content
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

Add CLI for monorepo releases: checks, building and versioning #8308

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"pre-push": "yarn --cwd packages/eui pre-push",
"preinstall": "echo \"\\x1b[K\\x1b[37;41mWarning: EUI has recently migrated to a monorepo structure. Please run EUI scripts like \\x1b[1;4myarn start\\x1b[0m\\x1b[37;41m or \\x1b[1;4myarn build\\x1b[0m\\x1b[37;41m from the \\x1b[1;4mpackages/eui\\x1b[0m\\x1b[37;41m directory instead!\n\nIf this is the first time you're running EUI after the monorepo migration, please run this first from the root repository's directory to clean up your local environment:\n \\x1b[1;4mrm -rf node_modules .cache-loader dist es lib optimize test-env types .eslintcache .loki reports docs .nyc_output eui.d.ts && yarn\\x1b[0m\\x1b[37;41m\n\nInstall process will continue in 10 seconds...\\x1b[0m\"; sleep 10",
"start": "echo '\\x1b[K\\x1b[37;41mPlease run this script from the \\x1b[1;4mpackages/eui\\x1b[0m\\x1b[37;41m directory instead\\x1b[0m'; exit 1",
"build": "echo '\\x1b[K\\x1b[37;41mPlease run this script from the \\x1b[1;4mpackages/eui\\x1b[0m\\x1b[37;41m directory instead\\x1b[0m'; exit 1"
"build": "echo '\\x1b[K\\x1b[37;41mPlease run this script from the \\x1b[1;4mpackages/eui\\x1b[0m\\x1b[37;41m directory instead\\x1b[0m'; exit 1",
"release": "node scripts/release"
},
"repository": {
"type": "git",
Expand All @@ -24,6 +25,9 @@
"devDependencies": {
"pre-push": "^0.1.4"
},
"dependencies": {
"@elastic/eui-release-cli": "link:packages/release-cli"
},
"resolutions": {
"prismjs": "1.27.0",
"react": "^18",
Expand Down
8 changes: 8 additions & 0 deletions packages/release-cli/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Dependencies
/node_modules

# Production
/dist

yarn-debug.log*
yarn-error.log*
25 changes: 25 additions & 0 deletions packages/release-cli/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "@elastic/eui-release-cli",
"private": true,
"version": "0.0.1",
"description": "",
"main": "dist/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "tsc"
},
"repository": {
"type": "git",
"url": "https://github.com/tkajtoch/eui.git",
"directory": "packages/release-cli"
},
"devDependencies": {
"typescript": "^5.7.3"
},
"dependencies": {
"chalk": "^4",
"glob": "^11.0.1",
"rimraf": "^6.0.1",
"yargs": "^17.7.2"
}
}
76 changes: 76 additions & 0 deletions packages/release-cli/src/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import yargs from 'yargs/yargs';
import { hideBin } from 'yargs/helpers';
import { release, type ReleaseType } from './release';
import { Logger } from './logger';
import { ValidationError } from './errors';

export const cli = () => {
yargs(hideBin(process.argv))
.command(
'run <type> [--tag] [--workspaces] [--allowCustom] [--verbose | -v]',
'Run the release process',
(yargs) => {
return yargs
.positional('type', {
type: 'string',
describe:
'Type of release to perform. Releases of type `official` will be tagged as `latest` in npm and are meant for official, stable builds only!',
choices: ['official', 'snapshot'] satisfies ReleaseType[],
demandOption: true,
})
.option('tag', {
type: 'string',
describe:
'npm tag for the release. It is forced to `latest` for official releases and defaults to `snapshot` for snapshot releases.',
})
.option('workspaces', {
type: 'array',
describe:
'An optional space-separated list of workspaces to release. Defaults to all workspaces changed since the last release.',
choices: [] as string[],
})
.option('allowCustom', {
type: 'boolean',
default: false,
})
.option('verbose', {
alias: 'v',
type: 'boolean',
description: 'Enable verbose logging',
default: false,
});
},
async (argv) => {
const { type, tag, workspaces, allowCustom, verbose } = argv;
const logger = new Logger(verbose);

try {
await release({
type,
tag,
workspaces,
logger,
allowCustomReleases: allowCustom,
});
} catch (err) {
if (err instanceof ValidationError) {
// ValidationErrors don't need the stacktrace printed out
logger.error(err.toString());
} else {
logger.error(err);
}
process.exit(1);
}
}
)
.demandCommand(1)
.parse();
};
28 changes: 28 additions & 0 deletions packages/release-cli/src/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

export class ValidationError extends Error {
public helpText: string | null = null;

constructor(message: string, helpText?: string) {
super(message);

if (helpText !== undefined) {
this.helpText = helpText;
}
}

toString() {
let finalHelpText = '';
if (this.helpText) {
finalHelpText += '\n\n';
finalHelpText += this.helpText;
}
return `${this.message}${finalHelpText}`;
}
}
42 changes: 42 additions & 0 deletions packages/release-cli/src/git_utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { promisify } from 'node:util';
import { exec } from 'node:child_process';

const execPromise = promisify(exec);

export const getCurrentBranch = async () => {
const result = await execPromise('git rev-parse --abbrev-ref HEAD');

return result.stdout;
}

export const isWorkingTreeClean = async () => {
const gitStatusResult = await execPromise('git status --porcelain');

return gitStatusResult.stdout === '' && gitStatusResult.stderr === '';
}

export const getRemoteHeadCommitHash = async (branchName: string) => {
const result = await execPromise(`git ls-remote --head --exit-code upstream refs/heads/${branchName}`);

return result.stdout.split('\t')[0];
}

export const getLocalHeadCommitHash = async () => {
const result = await execPromise('git rev-parse HEAD');

return result.stdout;
};

export const getCommitMessage = async (commitHash: string) => {
// Well, technically this returns commit subject, but we don't care about the whole commit body
const result = await execPromise(`git log -1 --pretty=format:%s ${commitHash}`);
return result.stdout;
};
11 changes: 11 additions & 0 deletions packages/release-cli/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { cli } from './cli';

cli();
37 changes: 37 additions & 0 deletions packages/release-cli/src/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import chalk from 'chalk';

export class Logger {
private readonly PREFIX_DEBUG = chalk.gray('[debug]');
private readonly PREFIX_INFO = chalk.white('[info]');
private readonly PREFIX_WARNING = chalk.yellow('[warning]');
private readonly PREFIX_ERROR = chalk.red('[error]');

constructor(private readonly verbose: boolean) {}

debug(message: any, ...args: any) {
if (!this.verbose) {
return;
}
console.debug(this.PREFIX_DEBUG, message, ...args);
}

info(message: any, ...args: any) {
console.info(this.PREFIX_INFO, message, ...args);
}

warning(message: any, ...args: any) {
console.warn(this.PREFIX_WARNING, message, ...args);
}

error(message: any, ...args: any) {
console.error(this.PREFIX_ERROR, message, ...args);
}
}
Loading