Skip to content

Commit

Permalink
feat(CLI Onboarding): Add deploy step
Browse files Browse the repository at this point in the history
  • Loading branch information
pgrzesik committed Jun 4, 2021
1 parent 83806fb commit 75c4458
Show file tree
Hide file tree
Showing 10 changed files with 583 additions and 37 deletions.
6 changes: 6 additions & 0 deletions lib/classes/CLI.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ class CLI {
this.log = function () {};
}

suppressLog() {
this.log = function () {};
this.printDot = function () {};
this.consoleLog = function () {};
}

displayHelp() {
if (!resolveCliInput().isHelpRequest) return false;
renderHelp(this.serverless.pluginManager.externalPlugins);
Expand Down
12 changes: 11 additions & 1 deletion lib/cli/interactive-setup/aws-credentials.js
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,8 @@ module.exports = {
try {
const createdProviderUid = await steps.handleProviderCreation(context);
const hadExistingProviders = Boolean(providers.length);
if (createdProviderUid && hadExistingProviders) {
const shouldLinkProvider = createdProviderUid && hadExistingProviders;
if (shouldLinkProvider) {
// This is situation where user decided to create a new provider and already had previous providers setup
// In this case, we want to setup an explicit link between provider and service as the newly created provider
// might not be the default one.
Expand All @@ -404,6 +405,11 @@ module.exports = {
createdProviderUid
);
}
context.stepHistory.push({
type: 'event',
name: 'providerCreated',
hasExplicitLink: shouldLinkProvider,
});
return;
} catch (err) {
if (err.code === 'DASHBOARD_UNAVAILABLE') {
Expand Down Expand Up @@ -431,6 +437,10 @@ module.exports = {
);

if (linked) {
context.stepHistory.push({
type: 'event',
name: 'existingProviderLinked',
});
process.stdout.write(
`\n${chalk.green('Selected provider was successfully linked to your service')}\n`
);
Expand Down
190 changes: 190 additions & 0 deletions lib/cli/interactive-setup/deploy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
'use strict';

const Serverless = require('../../Serverless');
const chalk = require('chalk');
const { confirm } = require('./utils');
const _ = require('lodash');
const overrideStdoutWrite = require('process-utils/override-stdout-write');
const { getDashboardInteractUrl } = require('@serverless/dashboard-plugin/lib/dashboard');
const AWS = require('aws-sdk');

const printMessage = ({
serviceName,
hasBeenDeployed,
dashboardPlugin,
isConfiguredWithDashboard,
}) => {
if (isConfiguredWithDashboard) {
if (hasBeenDeployed) {
process.stdout.write(
`\n${chalk.green('Your project is live and available in ')}${chalk.white.bold(
`./${serviceName}`
)}\n`
);
process.stdout.write(`\n Run ${chalk.bold('serverless info')} in the project directory\n`);
process.stdout.write(' View your endpoints and services\n');
process.stdout.write(`\n Open ${chalk.bold(getDashboardInteractUrl(dashboardPlugin))}\n`);
process.stdout.write(' Invoke your functions and view logs in the dashboard\n');
process.stdout.write(`\n Run ${chalk.bold('serverless deploy')} in the project directory\n`);
process.stdout.write(
" Redeploy your service after you've updated your service code or configuration\n\n"
);

return;
}

process.stdout.write(
`\n${chalk.green('Your project is ready for deployment and available in ')}${chalk.white.bold(
`./${serviceName}`
)}\n`
);
process.stdout.write(`\n Run ${chalk.bold('serverless deploy')} in the project directory\n`);
process.stdout.write(' Deploy your newly created service\n');
process.stdout.write(
`\n Run ${chalk.bold('serverless info')} in the project directory after deployment\n`
);
process.stdout.write(' View your endpoints and services\n');
process.stdout.write('\n Open Serverless Dashboard after deployment\n');
process.stdout.write(' Invoke your functions and view logs in the dashboard\n\n');
return;
}

if (hasBeenDeployed) {
process.stdout.write(
`\n${chalk.green('Your project is live and available in ')}${chalk.white.bold(
`./${serviceName}`
)}\n`
);
process.stdout.write(`\n Run ${chalk.bold('serverless info')} in the project directory\n`);
process.stdout.write(' View your endpoints and services\n');
process.stdout.write(`\n Run ${chalk.bold('serverless deploy')} in the directory\n`);
process.stdout.write(
" Redeploy your service after you've updated your service code or configuration\n"
);
process.stdout.write(
`\n Run ${chalk.bold('serverless invoke')} and ${chalk.bold(
'serverless logs'
)} in the project directory\n`
);
process.stdout.write(' Invoke your functions directly and view the logs\n');
process.stdout.write(`\n Run ${chalk.bold('serverless')} in the project directory\n`);
process.stdout.write(
' Add metrics, alerts, and a log explorer, by enabling the dashboard functionality\n\n'
);
return;
}

process.stdout.write(
`\n${chalk.green('Your project is ready for deployment and available in ')}${chalk.white.bold(
`./${serviceName}`
)}\n`
);
process.stdout.write(`\n Run ${chalk.bold('serverless deploy')} in the project directory\n`);
process.stdout.write(' Deploy your newly created service\n');
process.stdout.write(
`\n Run ${chalk.bold('serverless info')} in the project directory after deployment\n`
);
process.stdout.write(' View your endpoints and services\n');
process.stdout.write(
`\n Run ${chalk.bold('serverless invoke')} and ${chalk.bold(
'serverless logs'
)} in the project directory after deployment\n`
);
process.stdout.write(' Invoke your functions directly and view the logs\n');
process.stdout.write(`\n Run ${chalk.bold('serverless')} in the project directory\n`);
process.stdout.write(
' Add metrics, alerts, and a log explorer, by enabling the dashboard functionality\n\n'
);
};

const configurePlugin = (serverless, originalStdWrite) => {
serverless.pluginManager.addPlugin(require('../../plugins/deploy-interactive'));
const interactivePlugin = serverless.pluginManager.plugins.find(
(plugin) => plugin.constructor.name === 'InteractiveDeployProgress'
);
interactivePlugin.progress._writeOriginalStdout = (data) => originalStdWrite(data);
return interactivePlugin;
};

module.exports = {
isApplicable({ configuration, serviceDir, history }) {
if (!serviceDir) {
return false;
}

// We only want to consider newly created services for deploy step
if (!history.has('service')) {
return false;
}

if (
_.get(configuration, 'provider') !== 'aws' &&
_.get(configuration, 'provider.name') !== 'aws'
) {
return false;
}

// If `awsCredentials` step was not executed, we should proceed as it means that user has available credentials
if (!history.has('awsCredentials')) return true;

// We want to proceed if local credentials are available
if (new AWS.Config().credentials) return true;

// We want to proceed only if user created or linked already existing provider in `awsCredentials` step
const awsCredentialsHistory = history.get('awsCredentials');
const hasUserCreatedOrUsedExistingProvider = awsCredentialsHistory.some((historyItem) => {
const hasUserLinkedExisting =
historyItem.type === 'event' && historyItem.name === 'existingProviderLinked';
const hasUserCreated = historyItem.type === 'event' && historyItem.name === 'providerCreated';
return hasUserCreated || hasUserLinkedExisting;
});
if (hasUserCreatedOrUsedExistingProvider) return true;

return false;
},
async run({ configuration, configurationFilename, serviceDir }) {
const serviceName = configuration.service;
if (!(await confirm('Do you want to deploy your project?', { name: 'shouldDeploy' }))) {
printMessage({
serviceName,
hasBeenDeployed: false,
isConfiguredWithDashboard: Boolean(configuration.org),
});
return;
}

const serverless = new Serverless({
configuration,
serviceDir,
configurationFilename,
isConfigurationResolved: true,
hasResolvedCommandsExternally: true,
isTelemetryReportedExternally: true,
commands: ['deploy'],
options: {},
});

let interactiveOutputPlugin;

try {
await overrideStdoutWrite(
() => {},
async (originalStdWrite) => {
await serverless.init();
interactiveOutputPlugin = configurePlugin(serverless, originalStdWrite);
await serverless.run();
}
);
} catch (err) {
interactiveOutputPlugin.handleError();
throw err;
}

printMessage({
serviceName,
hasBeenDeployed: true,
isConfiguredWithDashboard: Boolean(configuration.org),
dashboardPlugin: serverless.pluginManager.dashboardPlugin,
});
},
};
1 change: 1 addition & 0 deletions lib/cli/interactive-setup/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const steps = {
dashboardSetOrg: require('@serverless/dashboard-plugin/lib/cli/interactive-setup/dashboard-set-org'),
awsCredentials: require('./aws-credentials'),
autoUpdate: require('./auto-update'),
deploy: require('./deploy'),
};

module.exports = async (context) => {
Expand Down
6 changes: 4 additions & 2 deletions lib/cli/interactive-setup/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ module.exports = {
}

if (hasPackageJson) {
process.stdout.write(`\nInstalling dependencies with "npm" in "${projectName}" folder.\n`);
process.stdout.write(`\nInstalling dependencies with "npm" in "${projectName}" folder\n`);
const npmCommand = await npmCommandDeferred;
try {
await spawn(npmCommand, ['install'], { cwd: projectDir });
Expand Down Expand Up @@ -228,7 +228,9 @@ module.exports = {
}

process.stdout.write(
`\n${chalk.green(`Project successfully created in '${projectName}' folder.`)}\n`
`\n${chalk.green(
`Project successfully created in ${chalk.white.bold(projectName)} folder`
)}\n`
);
context.serviceDir = projectDir;
const configurationPath = await resolveConfigurationPath({ cwd: projectDir, options: {} });
Expand Down
36 changes: 36 additions & 0 deletions lib/plugins/deploy-interactive.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use strict';

const cliProgressFooter = require('cli-progress-footer');
const chalk = require('chalk');

class InteractiveDeployProgress {
constructor(serverless) {
this.serverless = serverless;
this.progress = cliProgressFooter({ overrideStdout: false });
this.progress.shouldAddProgressAnimationPrefix = true;

this.hooks = {
'before:deploy:deploy': async () => {
this.progress.updateProgress('Deploying your project...\n');
},
'deploy:finalize': async () => {
this.progress.updateProgress('');
this.progress.writeStdout(chalk.green('\nDeployment succesful\n'));
},

'package:initialize': async () => {
this.progress.updateProgress('Packaging your project...\n');
},
'package:finalize': async () => {
this.progress.updateProgress('');
this.progress.writeStdout(chalk.green('\nPackaging succesful\n'));
},
};
}

handleError() {
this.progress.updateProgress('');
}
}

module.exports = InteractiveDeployProgress;
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"chalk": "^4.1.1",
"child-process-ext": "^2.1.1",
"ci-info": "^3.1.1",
"cli-progress-footer": "^1.1.1",
"d": "^1.0.1",
"dayjs": "^1.10.4",
"decompress": "^4.2.1",
Expand All @@ -60,7 +61,9 @@
"ncjsm": "^4.2.0",
"node-fetch": "^2.6.1",
"object-hash": "^2.1.1",
"ora": "^5.4.0",
"path2": "^0.1.0",
"process-utils": "^4.0.0",
"promise-queue": "^2.2.5",
"replaceall": "^0.1.6",
"semver": "^7.3.5",
Expand Down Expand Up @@ -93,7 +96,6 @@
"nyc": "^15.1.0",
"pkg": "^4.5.1",
"prettier": "^2.3.0",
"process-utils": "^4.0.0",
"proxyquire": "^2.1.3",
"semver-regex": "^3.1.2",
"sinon": "^10.0.0",
Expand Down

0 comments on commit 75c4458

Please sign in to comment.