From c81af9a1d0b16cdc83cf1a0b73bd3012ebb80476 Mon Sep 17 00:00:00 2001 From: Alexandre Bodin Date: Mon, 12 Oct 2020 22:46:56 +0200 Subject: [PATCH 1/4] Init admin reset Signed-off-by: Alexandre Bodin --- packages/create-strapi-app/package.json | 2 +- .../services/__tests__/user.test.js | 86 +++++++++++++++++++ packages/strapi-admin/services/user.js | 29 ++++++- packages/strapi/bin/strapi.js | 57 ++++-------- packages/strapi/lib/commands/admin-reset.js | 51 +++++++++++ packages/strapi/package.json | 2 +- yarn.lock | 5 ++ 7 files changed, 189 insertions(+), 43 deletions(-) create mode 100644 packages/strapi/lib/commands/admin-reset.js diff --git a/packages/create-strapi-app/package.json b/packages/create-strapi-app/package.json index 997f82e27d..957be0a255 100644 --- a/packages/create-strapi-app/package.json +++ b/packages/create-strapi-app/package.json @@ -20,7 +20,7 @@ "index.js" ], "dependencies": { - "commander": "^2.20.0", + "commander": "6.1.0", "strapi-generate-new": "3.2.3" }, "scripts": { diff --git a/packages/strapi-admin/services/__tests__/user.test.js b/packages/strapi-admin/services/__tests__/user.test.js index fbcf734654..e1f55ebc56 100644 --- a/packages/strapi-admin/services/__tests__/user.test.js +++ b/packages/strapi-admin/services/__tests__/user.test.js @@ -744,4 +744,90 @@ describe('User', () => { ); }); }); + + describe('resetPasswordByEmail', () => { + test('Throws on missing user', async () => { + const email = 'email@email.fr'; + const password = 'invalidpass'; + + const findOne = jest.fn(() => { + return null; + }); + + global.strapi = { + query() { + return { + findOne, + }; + }, + }; + + expect.hasAssertions(); + + await userService.resetPasswordByEmail(email, password).catch(error => { + expect(findOne).toHaveBeenCalledWith({ email }, undefined); + expect(error).toEqual(new Error(`User not found for email: ${email}`)); + }); + }); + + test.each(['abc', 'Abcd', 'Abcdefgh', 'Abcd123'])( + 'Throws on invalid password', + async password => { + const email = 'email@email.fr'; + + const findOne = jest.fn(() => ({ id: 1 })); + + global.strapi = { + query() { + return { + findOne, + }; + }, + }; + + expect.hasAssertions(); + + await userService.resetPasswordByEmail(email, password).catch(error => { + expect(findOne).toHaveBeenCalledWith({ email }, undefined); + expect(error).toEqual( + new Error( + 'Invalid password. Expected a minimum of 8 characters with at least one number and one uppercase letter' + ) + ); + }); + } + ); + }); + + test('Call the update function with the expected params', async () => { + const email = 'email@email.fr'; + const password = 'Testing1234'; + const hash = 'hash'; + const userId = 1; + + const findOne = jest.fn(() => ({ id: userId })); + const update = jest.fn(); + const hashPassword = jest.fn(() => hash); + + global.strapi = { + query() { + return { + findOne, + update, + }; + }, + admin: { + services: { + auth: { + hashPassword, + }, + }, + }, + }; + + await userService.resetPasswordByEmail(email, password); + expect(findOne).toHaveBeenCalledWith({ email }, undefined); + expect(update).toHaveBeenCalledWith({ id: userId }, { password: hash }); + expect(hashPassword).toHaveBeenCalledWith(password); + }); }); diff --git a/packages/strapi-admin/services/user.js b/packages/strapi-admin/services/user.js index 3937982a29..b80c0486ae 100644 --- a/packages/strapi-admin/services/user.js +++ b/packages/strapi-admin/services/user.js @@ -4,6 +4,7 @@ const _ = require('lodash'); const { stringIncludes } = require('strapi-utils'); const { createUser, hasSuperAdminRole } = require('../domain/user'); const { SUPER_ADMIN_CODE } = require('./constants'); +const { password: passwordValidator } = require('../validation/common-validators'); const sanitizeUserRoles = role => _.pick(role, ['id', 'name', 'description', 'code']); @@ -43,7 +44,7 @@ const create = async attributes => { /** * Update a user in database - * @param params query params to find the user to update + * @param id query params to find the user to update * @param attributes A partial user object * @returns {Promise} */ @@ -89,6 +90,31 @@ const updateById = async (id, attributes) => { return strapi.query('user', 'admin').update({ id }, attributes); }; +/** + * Reset a user password by email. (Used in admin:reset CLI) + * @param {string} email - user email + * @param {string} password - new password + */ +const resetPasswordByEmail = async (email, password) => { + const user = await findOne({ email }); + + if (!user) { + throw new Error(`User not found for email: ${email}`); + } + + try { + await passwordValidator.validate(password); + } catch (error) { + throw new Error( + 'Invalid password. Expected a minimum of 8 characters with at least one number and one uppercase letter' + ); + } + + const { id: userId } = user; + + await updateById(userId, { password }); +}; + /** * Check if a user is the last super admin * @param {int|string} userId user's id to look for @@ -320,4 +346,5 @@ module.exports = { assignARoleToAll, displayWarningIfUsersDontHaveRole, migrateUsers, + resetPasswordByEmail, }; diff --git a/packages/strapi/bin/strapi.js b/packages/strapi/bin/strapi.js index 7eee3fac47..d3593ce27c 100755 --- a/packages/strapi/bin/strapi.js +++ b/packages/strapi/bin/strapi.js @@ -4,17 +4,12 @@ const _ = require('lodash'); const resolveCwd = require('resolve-cwd'); const { yellow } = require('chalk'); -const program = require('commander'); +const { Command } = require('commander'); +const program = new Command(); const packageJSON = require('../package.json'); -// Allow us to display `help()`, but omit the wildcard (`*`) command. -program.Command.prototype.usageMinusWildcard = program.usageMinusWildcard = () => { - program.commands = _.reject(program.commands, { - _name: '*', - }); - program.help(); -}; +program.storeOptionsAsProperties(false).passCommandToAction(false); const checkCwdIsStrapiApp = name => { let logErrorAndExit = () => { @@ -72,20 +67,15 @@ const getLocalScript = name => (...args) => { program.allowUnknownOption(true); -// Expose version. -program.version(packageJSON.version, '-v, --version'); - -// Make `-v` option case-insensitive. -process.argv = _.map(process.argv, arg => { - return arg === '-V' ? '-v' : arg; -}); +program.option('-v, --version', 'output the version number'); // `$ strapi version` (--version synonym) program .command('version') .description('output your version of Strapi') .action(() => { - console.log(packageJSON.version); + process.stdout.write(packageJSON.version + '\n'); + process.exit(0); }); // `$ strapi console` @@ -126,7 +116,7 @@ program .command('develop') .alias('dev') .option('--no-build', 'Disable build', false) - .option('--watch-admin', 'Enable watch', true) + .option('--watch-admin', 'Enable watch', false) .option('--browser ', 'Open the browser', true) .description('Start your Strapi application in development mode') .action(getLocalScript('develop')); @@ -221,38 +211,25 @@ program program .command('configuration:dump') .alias('config:dump') + .description('Dump configurations of your application') .option('-f, --file ', 'Output file, default output is stdout') .action(getLocalScript('configurationDump')); program .command('configuration:restore') .alias('config:restore') + .description('Restore configurations of your application') .option('-f, --file ', 'Input file, default input is stdin') .option('-s, --strategy ', 'Strategy name, one of: "replace", "merge", "keep"') .action(getLocalScript('configurationRestore')); -/** - * Normalize help argument - */ - -// `$ strapi help` (--help synonym) +// Admin program - .command('help') - .description('output the help') - .action(program.usageMinusWildcard); + .command('admin:reset-password') + .alias('admin:reset') + .description('Reset an admin user password') + .option('-e, --email ', 'The user email') + .option('-p, --password ', 'New password for the user') + .action(getLocalScript('admin-reset')); -// `$ strapi ` -// Mask the '*' in `help`. -program.command('*').action(program.usageMinusWildcard); - -// Don't balk at unknown options. - -/** - * `$ strapi` - */ - -program.parse(process.argv); -const NO_COMMAND_SPECIFIED = program.args.length === 0; -if (NO_COMMAND_SPECIFIED) { - program.usageMinusWildcard(); -} +program.parseAsync(process.argv); diff --git a/packages/strapi/lib/commands/admin-reset.js b/packages/strapi/lib/commands/admin-reset.js new file mode 100644 index 0000000000..8810ebaaa5 --- /dev/null +++ b/packages/strapi/lib/commands/admin-reset.js @@ -0,0 +1,51 @@ +'use strict'; + +const _ = require('lodash'); +const inquirer = require('inquirer'); +const strapi = require('../index'); + +const promptQuestions = [ + { type: 'input', name: 'email', message: 'User email?' }, + { type: 'password', name: 'password', message: 'New password?' }, + { + type: 'confirm', + name: 'confirm', + message: "Do you really want to reset this user's password?", + }, +]; + +/** + * Reset user's password + * @param {Object} cmdOptions - command options + * @param {string} cmdOptions.email - user's email + * @param {string} cmdOptions.password - user's new password + */ +module.exports = async function(cmdOptions) { + const { email, password } = cmdOptions; + + if (_.isEmpty(email) && _.isEmpty(password) && process.stdin.isTTY) { + const inquiry = await inquirer.prompt(promptQuestions); + + if (!inquiry.confirm) { + process.exit(0); + } + + return changePassword(inquiry); + } + + if (_.isEmpty(email) || _.isEmpty(password)) { + console.error('Missing required options `email` or `password`'); + process.exit(1); + } + + return changePassword({ email, password }); +}; + +async function changePassword({ email, password }) { + const app = await strapi().load(); + + await app.admin.services.user.resetPasswordByEmail(email, password); + + console.log(`Successfully reset user's password`); + process.exit(0); +} diff --git a/packages/strapi/package.json b/packages/strapi/package.json index 398469e4d2..3077198539 100644 --- a/packages/strapi/package.json +++ b/packages/strapi/package.json @@ -19,7 +19,7 @@ "chokidar": "3.3.1", "ci-info": "2.0.0", "cli-table3": "^0.6.0", - "commander": "^2.20.0", + "commander": "6.1.0", "cross-spawn": "^6.0.5", "debug": "^4.1.1", "delegates": "^1.0.0", diff --git a/yarn.lock b/yarn.lock index 66b18c9ca4..1bbb6142f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5651,6 +5651,11 @@ commander@2.3.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.3.0.tgz#fd430e889832ec353b9acd1de217c11cb3eef873" integrity sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM= +commander@6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.1.0.tgz#f8d722b78103141006b66f4c7ba1e97315ba75bc" + integrity sha512-wl7PNrYWd2y5mp1OK/LhTlv8Ff4kQJQRXXAvF+uU/TPNiVJUxZLRYGj/B0y/lPGAVcSbJqH2Za/cvHmrPMC8mA== + commander@^2.19.0, commander@^2.20.0, commander@^2.20.3: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" From b8d206ede73750ff5e8ca55c20b799b83dd69f7a Mon Sep 17 00:00:00 2001 From: Alexandre Bodin Date: Mon, 12 Oct 2020 23:07:21 +0200 Subject: [PATCH 2/4] Cleanup some useless option config Signed-off-by: Alexandre Bodin --- packages/strapi/bin/strapi.js | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/packages/strapi/bin/strapi.js b/packages/strapi/bin/strapi.js index d3593ce27c..8914694648 100755 --- a/packages/strapi/bin/strapi.js +++ b/packages/strapi/bin/strapi.js @@ -9,8 +9,6 @@ const program = new Command(); const packageJSON = require('../package.json'); -program.storeOptionsAsProperties(false).passCommandToAction(false); - const checkCwdIsStrapiApp = name => { let logErrorAndExit = () => { console.log( @@ -56,20 +54,14 @@ const getLocalScript = name => (...args) => { }); }; -/** - * Normalize version argument - * - * `$ strapi -v` - * `$ strapi -V` - * `$ strapi --version` - * `$ strapi version` - */ - -program.allowUnknownOption(true); - -program.option('-v, --version', 'output the version number'); +// Initial program setup +program + .storeOptionsAsProperties(false) + .passCommandToAction(false) + .allowUnknownOption(true); // `$ strapi version` (--version synonym) +program.option('-v, --version', 'output the version number'); program .command('version') .description('output your version of Strapi') @@ -115,7 +107,7 @@ program program .command('develop') .alias('dev') - .option('--no-build', 'Disable build', false) + .option('--no-build', 'Disable build') .option('--watch-admin', 'Enable watch', false) .option('--browser ', 'Open the browser', true) .description('Start your Strapi application in development mode') @@ -128,7 +120,7 @@ program .option('-p, --plugin ', 'Name of the local plugin') .option('-e, --extend ', 'Name of the plugin to extend') .option('-c, --connection ', 'The name of the connection to use') - .option('--draft-and-publish ', 'Enable draft/publish', false) + .option('--draft-and-publish', 'Enable draft/publish', false) .description('generate a basic API') .action((id, attributes, cliArguments) => { cliArguments.attributes = attributes; @@ -150,7 +142,7 @@ program .option('-a, --api ', 'API name to generate a sub API') .option('-p, --plugin ', 'plugin name') .option('-c, --connection ', 'The name of the connection to use') - .option('--draft-and-publish ', 'Enable draft/publish', false) + .option('--draft-and-publish', 'Enable draft/publish', false) .description('generate a model for an API') .action((id, attributes, cliArguments) => { cliArguments.attributes = attributes; @@ -184,7 +176,7 @@ program program .command('build') .option('--clean', 'Remove the build and .cache folders', false) - .option('--no-optimization', 'Build the Administration without assets optimization', false) + .option('--no-optimization', 'Build the Administration without assets optimization') .description('Builds the strapi admin app') .action(getLocalScript('build')); From fa43dd8add2abc82cbf9a363fed63909e82a46f8 Mon Sep 17 00:00:00 2001 From: Alexandre Bodin Date: Tue, 13 Oct 2020 12:28:40 +0200 Subject: [PATCH 3/4] Add tests Signed-off-by: Alexandre Bodin --- .../commands/__tests__/admin-reset.test.js | 141 ++++++++++++++++++ packages/strapi/lib/commands/admin-reset.js | 2 +- 2 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 packages/strapi/lib/commands/__tests__/admin-reset.test.js diff --git a/packages/strapi/lib/commands/__tests__/admin-reset.test.js b/packages/strapi/lib/commands/__tests__/admin-reset.test.js new file mode 100644 index 0000000000..832fbeae5e --- /dev/null +++ b/packages/strapi/lib/commands/__tests__/admin-reset.test.js @@ -0,0 +1,141 @@ +'use strict'; + +const load = jest.fn(() => mock); +const resetPasswordByEmail = jest.fn(); +const admin = { + services: { + user: { + resetPasswordByEmail, + }, + }, +}; + +const mock = { + load, + admin, +}; + +jest.mock('../../index', () => { + return jest.fn(() => mock); +}); + +const inquirer = require('inquirer'); +const resetAdminPasswordCommand = require('../admin-reset'); + +describe('admin:reset-password command', () => { + beforeEach(() => { + load.mockClear(); + resetPasswordByEmail.mockClear(); + }); + + test('resetAdminPasswordCommand accepts direct input', async () => { + const email = 'email@email.fr'; + const password = 'testPasword1234'; + + const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {}); + const consoleLog = jest.spyOn(console, 'log').mockImplementation(() => {}); + + await resetAdminPasswordCommand({ email, password }); + + expect(mockExit).toHaveBeenCalledWith(0); + expect(consoleLog).toHaveBeenCalled(); + expect(load).toHaveBeenCalled(); + expect(resetPasswordByEmail).toHaveBeenCalledWith(email, password); + + mockExit.mockRestore(); + consoleLog.mockRestore(); + }); + + describe('Handles prompt input', () => { + test('Only prompt on TTY', async () => { + const tmpTTY = process.stdin.isTTY; + process.stdin.isTTY = false; + + // throw so the code will stop executing + const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => { + throw new Error('exit'); + }); + + const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {}); + + await resetAdminPasswordCommand().catch(err => { + expect(err).toEqual(new Error('exit')); + }); + + expect(consoleError).toBeCalledWith('Missing required options `email` or `password`'); + expect(mockExit).toHaveBeenCalledWith(1); + expect(load).not.toHaveBeenCalled(); + expect(resetPasswordByEmail).not.toHaveBeenCalled(); + + process.stdin.isTTY = tmpTTY; + mockExit.mockRestore(); + consoleError.mockRestore(); + }); + + test('Stops if not confirmed', async () => { + process.stdin.isTTY = true; + const email = 'email@email.fr'; + const password = 'testPasword1234'; + + const mockInquiry = jest + .spyOn(inquirer, 'prompt') + .mockImplementationOnce(async () => ({ email, password, confirm: false })); + + // throw so the code will stop executing + const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => { + throw new Error('exit'); + }); + + await resetAdminPasswordCommand().catch(err => { + expect(err).toEqual(new Error('exit')); + }); + + expect(mockInquiry).toHaveBeenLastCalledWith([ + expect.objectContaining({ + message: expect.any(String), + name: 'email', + type: 'input', + }), + expect.objectContaining({ + message: expect.any(String), + name: 'password', + type: 'password', + }), + expect.objectContaining({ + message: expect.any(String), + name: 'confirm', + type: 'confirm', + }), + ]); + expect(mockExit).toHaveBeenCalledWith(0); + expect(load).not.toHaveBeenCalled(); + expect(resetPasswordByEmail).not.toHaveBeenCalled(); + + mockExit.mockRestore(); + mockInquiry.mockRestore(); + }); + + test('Calls the reset method with user input', async () => { + const email = 'email@email.fr'; + const password = 'testPasword1234'; + + const mockInquiry = jest + .spyOn(inquirer, 'prompt') + .mockImplementationOnce(async () => ({ email, password, confirm: true })); + + const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {}); + const consoleLog = jest.spyOn(console, 'log').mockImplementation(() => {}); + + await resetAdminPasswordCommand(); + + expect(mockExit).toHaveBeenCalledWith(0); + expect(consoleLog).toHaveBeenCalled(); + expect(load).toHaveBeenCalled(); + expect(resetPasswordByEmail).toHaveBeenCalledWith(email, password); + + mockInquiry.mockRestore(); + mockExit.mockRestore(); + consoleLog.mockRestore(); + }); + }); +}); diff --git a/packages/strapi/lib/commands/admin-reset.js b/packages/strapi/lib/commands/admin-reset.js index 8810ebaaa5..0446c6f4f1 100644 --- a/packages/strapi/lib/commands/admin-reset.js +++ b/packages/strapi/lib/commands/admin-reset.js @@ -20,7 +20,7 @@ const promptQuestions = [ * @param {string} cmdOptions.email - user's email * @param {string} cmdOptions.password - user's new password */ -module.exports = async function(cmdOptions) { +module.exports = async function(cmdOptions = {}) { const { email, password } = cmdOptions; if (_.isEmpty(email) && _.isEmpty(password) && process.stdin.isTTY) { From 8af60259cd05acaa4a8056bbe673547cdd69f378 Mon Sep 17 00:00:00 2001 From: Alexandre Bodin Date: Tue, 13 Oct 2020 15:13:15 +0200 Subject: [PATCH 4/4] Add CLI documentation Signed-off-by: Alexandre Bodin --- docs/v3.x/cli/CLI.md | 33 +++++++++++++++++-- .../services/__tests__/user.test.js | 24 ++++++-------- packages/strapi-admin/services/user.js | 4 +-- packages/strapi/bin/strapi.js | 33 ++++++++++--------- 4 files changed, 59 insertions(+), 35 deletions(-) diff --git a/docs/v3.x/cli/CLI.md b/docs/v3.x/cli/CLI.md index afc9535d2d..63080c91a6 100644 --- a/docs/v3.x/cli/CLI.md +++ b/docs/v3.x/cli/CLI.md @@ -32,7 +32,9 @@ options: [--no-run|--use-npm|--debug|--quickstart|--dbclient= --dbhost - **<dbssl>** and **<dbauth>** are available only for `mongo` and are optional. - **--dbforce** Allows you to overwrite content if the provided database is not empty. Only available for `postgres`, `mysql`, and is optional. -## strapi develop|dev +## strapi develop + +**Alias**: `dev` Start a Strapi application with autoReload enabled. @@ -89,7 +91,9 @@ options: [--no-optimization] - **strapi build --no-optimization**
Builds the administration panel without minimizing the assets. The build duration is faster. -## strapi configuration:dump|config:dump +## strapi configuration:dump + +**Alias**: `config:dump` Dumps configurations to a file or stdout to help you migrate to production. @@ -120,7 +124,9 @@ In case of doubt, you should avoid committing the dump file into a versioning sy ::: -## strapi configuration:restore|config:restore +## strapi configuration:restore + +**Alias**: `config:restore` Restores a configuration dump into your application. @@ -151,6 +157,27 @@ When running the restore command, you can choose from three different strategies - **merge**: Will create missing keys and merge existing keys with their new value. - **keep**: Will create missing keys and keep existing keys as is. +## strapi admin:reset-user-password + +**Alias** `admin:reset-password` + +Reset an admin user's password. +You can pass the email and new password as options or set them interactivly if you call the command without passing the options. + +**Example** + +```bash +strapi admin:reset-user-password --email=chef@strapi.io --password=Gourmet1234 +``` + +**Options** + +| Option | Type | Description | +| -------------- | ------ | ------------------------- | +| -e, --email | string | The user email | +| -p, --password | string | New password for the user | +| -h, --help | | display help for command | + ## strapi generate:api Scaffold a complete API with its configurations, controller, model and service. diff --git a/packages/strapi-admin/services/__tests__/user.test.js b/packages/strapi-admin/services/__tests__/user.test.js index e1f55ebc56..8e8fee8e39 100644 --- a/packages/strapi-admin/services/__tests__/user.test.js +++ b/packages/strapi-admin/services/__tests__/user.test.js @@ -762,12 +762,11 @@ describe('User', () => { }, }; - expect.hasAssertions(); + await expect(userService.resetPasswordByEmail(email, password)).rejects.toEqual( + new Error(`User not found for email: ${email}`) + ); - await userService.resetPasswordByEmail(email, password).catch(error => { - expect(findOne).toHaveBeenCalledWith({ email }, undefined); - expect(error).toEqual(new Error(`User not found for email: ${email}`)); - }); + expect(findOne).toHaveBeenCalledWith({ email }, undefined); }); test.each(['abc', 'Abcd', 'Abcdefgh', 'Abcd123'])( @@ -785,16 +784,13 @@ describe('User', () => { }, }; - expect.hasAssertions(); + await expect(userService.resetPasswordByEmail(email, password)).rejects.toEqual( + new Error( + 'Invalid password. Expected a minimum of 8 characters with at least one number and one uppercase letter' + ) + ); - await userService.resetPasswordByEmail(email, password).catch(error => { - expect(findOne).toHaveBeenCalledWith({ email }, undefined); - expect(error).toEqual( - new Error( - 'Invalid password. Expected a minimum of 8 characters with at least one number and one uppercase letter' - ) - ); - }); + expect(findOne).toHaveBeenCalledWith({ email }, undefined); } ); }); diff --git a/packages/strapi-admin/services/user.js b/packages/strapi-admin/services/user.js index b80c0486ae..0fb4a1642d 100644 --- a/packages/strapi-admin/services/user.js +++ b/packages/strapi-admin/services/user.js @@ -110,9 +110,7 @@ const resetPasswordByEmail = async (email, password) => { ); } - const { id: userId } = user; - - await updateById(userId, { password }); + await updateById(user.id, { password }); }; /** diff --git a/packages/strapi/bin/strapi.js b/packages/strapi/bin/strapi.js index 8914694648..469a6d47f2 100755 --- a/packages/strapi/bin/strapi.js +++ b/packages/strapi/bin/strapi.js @@ -60,11 +60,14 @@ program .passCommandToAction(false) .allowUnknownOption(true); +program.helpOption('-h, --help', 'Display help for command'); +program.addHelpCommand('help [command]', 'Display help for command'); + // `$ strapi version` (--version synonym) -program.option('-v, --version', 'output the version number'); +program.option('-v, --version', 'Output the version number'); program .command('version') - .description('output your version of Strapi') + .description('Output your version of Strapi') .action(() => { process.stdout.write(packageJSON.version + '\n'); process.exit(0); @@ -73,7 +76,7 @@ program // `$ strapi console` program .command('console') - .description('open the Strapi framework console') + .description('Open the Strapi framework console') .action(getLocalScript('console')); // `$ strapi new` @@ -94,7 +97,7 @@ program .option('--dbauth ', 'Authentication Database') .option('--dbfile ', 'Database file path for sqlite') .option('--dbforce', 'Overwrite database content if any') - .description('create a new application') + .description('Create a new application') .action(require('../lib/commands/new')); // `$ strapi start` @@ -121,7 +124,7 @@ program .option('-e, --extend ', 'Name of the plugin to extend') .option('-c, --connection ', 'The name of the connection to use') .option('--draft-and-publish', 'Enable draft/publish', false) - .description('generate a basic API') + .description('Generate a basic API') .action((id, attributes, cliArguments) => { cliArguments.attributes = attributes; getLocalScript('generate')(id, cliArguments); @@ -133,7 +136,7 @@ program .option('-a, --api ', 'API name to generate the files in') .option('-p, --plugin ', 'Name of the local plugin') .option('-e, --extend ', 'Name of the plugin to extend') - .description('generate a controller for an API') + .description('Generate a controller for an API') .action(getLocalScript('generate')); // `$ strapi generate:model` @@ -143,7 +146,7 @@ program .option('-p, --plugin ', 'plugin name') .option('-c, --connection ', 'The name of the connection to use') .option('--draft-and-publish', 'Enable draft/publish', false) - .description('generate a model for an API') + .description('Generate a model for an API') .action((id, attributes, cliArguments) => { cliArguments.attributes = attributes; getLocalScript('generate')(id, cliArguments); @@ -154,7 +157,7 @@ program .command('generate:policy ') .option('-a, --api ', 'API name') .option('-p, --plugin ', 'plugin name') - .description('generate a policy for an API') + .description('Generate a policy for an API') .action(getLocalScript('generate')); // `$ strapi generate:service` @@ -163,14 +166,14 @@ program .option('-a, --api ', 'API name') .option('-p, --plugin ', 'plugin name') .option('-t, --tpl