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

[BD-46] feat: help command for Paragon CLI #2603

Merged
merged 4 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ Usage on with `@edx/brand`:

Note that including fonts will affect performance. In some applications may choose not to load the custom font to keep it highly performant.

## Paragon CLI

The Paragon CLI (Command Line Interface) is a tool that provides various utility commands to automate actions within the Open edX environment.

### Available Commands

- `paragon install-theme [theme]`: Installs the specific @edx/brand package.

Use `paragon help` to see more information.

## Getting Help

Please reach out to the Paragon Working Group (PWG):
Expand Down
54 changes: 48 additions & 6 deletions bin/paragon-scripts.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,68 @@
#!/usr/bin/env node
/* eslint-disable no-console */
Copy link
Contributor

Choose a reason for hiding this comment

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

You turned off the linter for the entire page, even though you're only using console.log in one place. Should I turn off the linter rule only in one place?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it is used twice there but I agree for two cases it is good idea.

const chalk = require('chalk');
const themeCommand = require('../lib/install-theme');
const helpCommand = require('../lib/help');

// command: executor function
const COMMANDS = {
'install-theme': themeCommand,
// 'command-name': {
// executor: executorFunc,
//
// ********** Block for help command start **********
// description: 'Command description',
// parameters: [
// {
// name: 'paramName',
// description: 'paramDescription',
// defaultValue: 'paramDefaultValue',
// required: true/false,
// },
// ...
// ],
// options: [
// {
// name: '--optionName',
// description: 'optionDescription',
// },
// ...
// ],
// ********** Block for help command end **********
// },
Copy link
Contributor

Choose a reason for hiding this comment

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

Does it make sense to use the syntax to describe JSDocs?

'install-theme': {
executor: themeCommand,
description: 'Installs the specific @edx/brand package.',
parameters: [
{
name: 'theme',
description: 'The @edx/brand package to install.',
defaultValue: '@edx/brand-openedx@latest',
required: false,
},
],
},
help: {
executor: helpCommand,
description: 'Displays help for available commands.',
},
};

(async () => {
const [command] = process.argv.slice(2);
const executor = COMMANDS[command];

if (!executor) {
// eslint-disable-next-line no-console
console.log(chalk.red.bold('Unknown command. Usage: paragon <command>'));
console.log(chalk.red.bold('Unknown command. Usage: paragon <command>.'));
viktorrusakov marked this conversation as resolved.
Show resolved Hide resolved
return;
}

if (command === 'help') {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it necessary to move the word help into a constant with a meaningful name?

helpCommand(COMMANDS);
return;
}

try {
await executor();
await executor.executor();
} catch (error) {
// eslint-disable-next-line no-console
console.error(chalk.red.bold('An error occurred:', error.message));
process.exit(1);
}
Expand Down
57 changes: 57 additions & 0 deletions lib/help.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/* eslint-disable no-console */
const chalk = require('chalk');

const DESCRIPTION_PAD = 20;

/**
* Pads a description string to align with a specified offset string.
*
* @param {string} description - The description to pad.
* @param {string} offsetString - The offset string that the description should align with.
* @returns {string} - The padded description.
*/
function padLeft(description, offsetString) {
// Calculate the necessary padding based on the offsetString length
const padding = ' '.repeat(Math.max(0, DESCRIPTION_PAD - offsetString.length));
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we give a meaningful name for this constant? This way you can remove the redundant comment

Copy link
Contributor Author

@monteri monteri Sep 8, 2023

Choose a reason for hiding this comment

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

I think it is descriptive right now. Meaningful variable name would be very long

return `${padding}${description}`;
}

/**
* Displays a help message for available commands, including descriptions, parameters, and options.
*
* @param {Object} commands - An object containing information about available commands.
*/
function helpCommand(commands) {
console.log(chalk.yellow.bold('Paragon Help'));
console.log();
console.log('Available commands:');
console.log();

Object.keys(commands).forEach(command => {
viktorrusakov marked this conversation as resolved.
Show resolved Hide resolved
console.log(` ${chalk.green.bold(command)}`);
if (commands[command].description) {
console.log(` ${commands[command].description}`);
}

if (commands[command].parameters && commands[command].parameters.length > 0) {
console.log(` ${chalk.cyan('Parameters: ')}`);
commands[command].parameters.forEach(parameter => {
const requiredStatus = parameter.required ? 'Required' : 'Optional';
const formattedDescription = padLeft(parameter.description, parameter.name);
console.log(` ${parameter.name}${formattedDescription} (${requiredStatus}, Default: ${parameter.defaultValue || 'None'})`);
});
}

if (commands[command].options && commands[command].options.length > 0) {
console.log(` ${chalk.cyan('Options: ')}`);
commands[command].options.forEach(option => {
const formattedDescription = padLeft(option.description, option.name);
console.log(` ${option.name}${formattedDescription}`);
});
}

console.log();
});
}

module.exports = helpCommand;
24 changes: 22 additions & 2 deletions lib/install-theme.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
/* eslint-disable no-console */
viktorrusakov marked this conversation as resolved.
Show resolved Hide resolved
const inquirer = require('inquirer');
const childProcess = require('child_process');

/**
* Prompts the user to enter the @edx/brand package they want to install.
*
* @returns {Promise<Object>} - A Promise that resolves to an object containing the user's input.
*/
function promptUserForTheme() {
return inquirer.prompt([
{
Expand All @@ -12,6 +18,11 @@ function promptUserForTheme() {
]);
}

/**
* Installs a specified @edx/brand package.
*
* @param {string} theme - The @edx/brand package to install.
*/
function installTheme(theme) {
const version = theme ? `npm:${theme}` : '';

Expand All @@ -20,9 +31,18 @@ function installTheme(theme) {
childProcess.execSync(installCommand, { stdio: 'inherit' });
}

/**
* Command handler for installing an @edx/brand package.
*/
async function themeCommand() {
const answers = await promptUserForTheme();
installTheme(answers.theme);
// Check if the user passed a theme parameter as a command-line argument
if (process.argv.length === 4) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we give a meaningful name for this expression? This way you can remove the redundant comment

const providedTheme = process.argv[3];
installTheme(providedTheme);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I decided to add it in this PR since it is small and I could use newly created help command functionality to describe it straight away.

} else {
const answers = await promptUserForTheme();
installTheme(answers.theme);
}
}

module.exports = themeCommand;
Loading