mirror of
https://github.com/serverless/serverless.git
synced 2026-01-25 15:07:39 +00:00
feat(CLI Onboarding): Add telemetry for interactive flow
This commit is contained in:
parent
f5c1c47fa2
commit
0eba2dcdfe
@ -77,8 +77,8 @@ const getProviders = memoizee(
|
||||
}
|
||||
);
|
||||
|
||||
const awsAccessKeyIdInput = async () =>
|
||||
(
|
||||
const awsAccessKeyIdInput = async ({ stepHistory }) => {
|
||||
const accessKeyId = (
|
||||
await inquirer.prompt({
|
||||
message: 'AWS Access Key Id:',
|
||||
type: 'input',
|
||||
@ -89,9 +89,12 @@ const awsAccessKeyIdInput = async () =>
|
||||
},
|
||||
})
|
||||
).accessKeyId.trim();
|
||||
stepHistory.set('accessKeyId', '_user_provided_');
|
||||
return accessKeyId;
|
||||
};
|
||||
|
||||
const awsSecretAccessKeyInput = async () =>
|
||||
(
|
||||
const awsSecretAccessKeyInput = async ({ stepHistory }) => {
|
||||
const secretAccessKey = (
|
||||
await inquirer.prompt({
|
||||
message: 'AWS Secret Access Key:',
|
||||
type: 'input',
|
||||
@ -105,8 +108,11 @@ const awsSecretAccessKeyInput = async () =>
|
||||
},
|
||||
})
|
||||
).secretAccessKey.trim();
|
||||
stepHistory.set('secretAccessKey', '_user_provided_');
|
||||
return secretAccessKey;
|
||||
};
|
||||
|
||||
const credentialsSetupChoice = async (providers) => {
|
||||
const credentialsSetupChoice = async (context, providers) => {
|
||||
let credentialsSetupChoices = [];
|
||||
let message = 'No AWS credentials found, what credentials do you want to use?';
|
||||
|
||||
@ -142,7 +148,7 @@ const credentialsSetupChoice = async (providers) => {
|
||||
{ name: 'Skip', value: CREDENTIALS_SETUP_CHOICE.SKIP }
|
||||
);
|
||||
|
||||
return (
|
||||
const result = (
|
||||
await inquirer.prompt({
|
||||
message,
|
||||
type: 'list',
|
||||
@ -150,6 +156,12 @@ const credentialsSetupChoice = async (providers) => {
|
||||
choices: credentialsSetupChoices,
|
||||
})
|
||||
).credentialsSetupChoice;
|
||||
|
||||
context.stepHistory.set(
|
||||
'credentialsSetupChoice',
|
||||
result.startsWith('_') ? result : '_user_provided_'
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
const steps = {
|
||||
@ -158,15 +170,16 @@ const steps = {
|
||||
|
||||
http://slss.io/aws-creds-setup\n`),
|
||||
|
||||
ensureAwsAccount: async () => {
|
||||
ensureAwsAccount: async ({ stepHistory }) => {
|
||||
if (await confirm('Do you have an AWS account?', { name: 'hasAwsAccount' })) return;
|
||||
openBrowser('https://portal.aws.amazon.com/billing/signup');
|
||||
await inquirer.prompt({
|
||||
message: 'Press Enter to continue after creating an AWS account',
|
||||
name: 'createAwsAccountPrompt',
|
||||
});
|
||||
stepHistory.set('createAwsAccountPrompt', true);
|
||||
},
|
||||
ensureAwsCredentials: async ({ options, configuration }) => {
|
||||
ensureAwsCredentials: async ({ options, configuration, stepHistory }) => {
|
||||
const region = options.region || configuration.provider.region || 'us-east-1';
|
||||
openBrowser(
|
||||
`https://console.aws.amazon.com/iam/home?region=${region}#/users$new?step=final&accessKey&userNames=serverless&permissionType=policies&policies=arn:aws:iam::aws:policy%2FAdministratorAccess`
|
||||
@ -175,10 +188,11 @@ const steps = {
|
||||
message: 'Press Enter to continue after creating an AWS user with access keys',
|
||||
name: 'generateAwsCredsPrompt',
|
||||
});
|
||||
stepHistory.set('generateAwsCredsPrompt', true);
|
||||
},
|
||||
inputAwsCredentials: async () => {
|
||||
const accessKeyId = await awsAccessKeyIdInput();
|
||||
const secretAccessKey = await awsSecretAccessKeyInput();
|
||||
inputAwsCredentials: async (context) => {
|
||||
const accessKeyId = await awsAccessKeyIdInput(context);
|
||||
const secretAccessKey = await awsSecretAccessKeyInput(context);
|
||||
await awsCredentials.saveFileProfiles(new Map([['default', { accessKeyId, secretAccessKey }]]));
|
||||
process.stdout.write(
|
||||
`\n${chalk.green(
|
||||
@ -188,7 +202,7 @@ const steps = {
|
||||
)}\n`
|
||||
);
|
||||
},
|
||||
handleProviderCreation: async ({ configuration: { org: orgName } }) => {
|
||||
handleProviderCreation: async ({ configuration: { org: orgName }, stepHistory }) => {
|
||||
const providersUrl = `https://app.serverless.com/${orgName}/settings/providers?source=cli&providerId=new&provider=aws`;
|
||||
openBrowser(chalk.bold.white(providersUrl));
|
||||
process.stdout.write(
|
||||
@ -210,7 +224,10 @@ const steps = {
|
||||
name: 'skipProviderSetup',
|
||||
});
|
||||
|
||||
inquirerPrompt.then(() => resolve(null));
|
||||
inquirerPrompt.then(() => {
|
||||
stepHistory.set('skipProviderSetup', true);
|
||||
resolve(null);
|
||||
});
|
||||
}, timeoutDuration);
|
||||
|
||||
onEvent = (event) => {
|
||||
@ -307,10 +324,17 @@ module.exports = {
|
||||
_.get(configuration, 'provider') !== 'aws' &&
|
||||
_.get(configuration, 'provider.name') !== 'aws'
|
||||
) {
|
||||
context.inapplicabilityReasonCode = 'NON_AWS_PROVIDER';
|
||||
return false;
|
||||
}
|
||||
if (new AWS.S3().config.credentials) {
|
||||
context.inapplicabilityReasonCode = 'LOCAL_CREDENTIALS_CONFIGURED';
|
||||
return false;
|
||||
}
|
||||
if ((await awsCredentials.resolveFileProfiles()).size) {
|
||||
context.inapplicabilityReasonCode = 'LOCAL_CREDENTIAL_PROFILES_CONFIGURED';
|
||||
return false;
|
||||
}
|
||||
if (new AWS.S3().config.credentials) return false;
|
||||
if ((await awsCredentials.resolveFileProfiles()).size) return false;
|
||||
|
||||
const orgName = configuration.org;
|
||||
if (orgName && isAuthenticated()) {
|
||||
@ -326,7 +350,10 @@ module.exports = {
|
||||
}
|
||||
const hasDefaultProvider = providers.some((provider) => provider.isDefault);
|
||||
|
||||
if (hasDefaultProvider) return false;
|
||||
if (hasDefaultProvider) {
|
||||
context.inapplicabilityReasonCode = 'DEFAULT_PROVIDER_CONFIGURED';
|
||||
return false;
|
||||
}
|
||||
|
||||
// For situation where it is invoked for already existing service
|
||||
// We need to check if service already has a linked provider
|
||||
@ -335,6 +362,7 @@ module.exports = {
|
||||
!history.has('service') &&
|
||||
(await doesServiceInstanceHaveLinkedProvider({ configuration, options }))
|
||||
) {
|
||||
context.inapplicabilityReasonCode = 'LINKED_PROVIDER_CONFIGURED';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -357,7 +385,7 @@ module.exports = {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
const credentialsSetupChoiceAnswer = await credentialsSetupChoice(providers);
|
||||
const credentialsSetupChoiceAnswer = await credentialsSetupChoice(context, providers);
|
||||
|
||||
if (credentialsSetupChoiceAnswer === CREDENTIALS_SETUP_CHOICE.CREATE_PROVIDER) {
|
||||
try {
|
||||
@ -386,9 +414,9 @@ module.exports = {
|
||||
steps.writeOnSetupSkip();
|
||||
return;
|
||||
} else if (credentialsSetupChoiceAnswer === CREDENTIALS_SETUP_CHOICE.LOCAL) {
|
||||
await steps.ensureAwsAccount();
|
||||
await steps.ensureAwsAccount(context);
|
||||
await steps.ensureAwsCredentials(context);
|
||||
await steps.inputAwsCredentials();
|
||||
await steps.inputAwsCredentials(context);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -406,4 +434,12 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
steps,
|
||||
configuredQuestions: [
|
||||
'credentialsSetupChoice',
|
||||
'createAwsAccountPrompt',
|
||||
'generateAwsCredsPrompt',
|
||||
'accessKeyId',
|
||||
'secretAccessKey',
|
||||
'skipProviderSetup',
|
||||
],
|
||||
};
|
||||
|
||||
@ -100,8 +100,10 @@ const configurePlugin = (serverless, originalStdWrite) => {
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
async isApplicable({ configuration, serviceDir, history, options }) {
|
||||
async isApplicable(context) {
|
||||
const { configuration, serviceDir, history, options } = context;
|
||||
if (!serviceDir) {
|
||||
context.inapplicabilityReasonCode = 'NOT_IN_SERVICE_DIRECTORY';
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -109,6 +111,7 @@ module.exports = {
|
||||
_.get(configuration, 'provider') !== 'aws' &&
|
||||
_.get(configuration, 'provider.name') !== 'aws'
|
||||
) {
|
||||
context.inapplicabilityReasonCode = 'NON_AWS_PROVIDER';
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -127,11 +130,17 @@ module.exports = {
|
||||
// We want to proceed if local credentials are available
|
||||
if (new AWS.Config().credentials) return true;
|
||||
|
||||
context.inapplicabilityReasonCode = 'NO_CREDENTIALS_CONFIGURED';
|
||||
return false;
|
||||
},
|
||||
async run({ configuration, configurationFilename, serviceDir }) {
|
||||
async run({ configuration, configurationFilename, serviceDir, stepHistory }) {
|
||||
const serviceName = configuration.service;
|
||||
if (!(await confirm('Do you want to deploy your project?', { name: 'shouldDeploy' }))) {
|
||||
const shouldDeploy = await confirm('Do you want to deploy your project?', {
|
||||
name: 'shouldDeploy',
|
||||
});
|
||||
stepHistory.set('shouldDeploy', shouldDeploy);
|
||||
|
||||
if (!shouldDeploy) {
|
||||
printMessage({
|
||||
serviceName,
|
||||
hasBeenDeployed: false,
|
||||
@ -174,4 +183,5 @@ module.exports = {
|
||||
dashboardPlugin: serverless.pluginManager.dashboardPlugin,
|
||||
});
|
||||
},
|
||||
configuredQuestions: ['shouldDeploy'],
|
||||
};
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const inquirer = require('@serverless/utils/inquirer');
|
||||
const { StepHistory } = require('@serverless/utils/telemetry');
|
||||
|
||||
const steps = {
|
||||
service: require('./service'),
|
||||
@ -12,14 +13,32 @@ const steps = {
|
||||
|
||||
module.exports = async (context) => {
|
||||
context = { ...context, inquirer, history: new Map() };
|
||||
const stepsDetails = new Map();
|
||||
for (const [stepName, step] of Object.entries(steps)) {
|
||||
delete context.stepHistory;
|
||||
delete context.inapplicabilityReasonCode;
|
||||
const stepData = await step.isApplicable(context);
|
||||
stepsDetails.set(stepName, {
|
||||
isApplicable: Boolean(stepData),
|
||||
inapplicabilityReasonCode: context.inapplicabilityReasonCode,
|
||||
timestamp: Date.now(),
|
||||
configuredQuestions: step.configuredQuestions,
|
||||
});
|
||||
if (stepData) {
|
||||
process.stdout.write('\n');
|
||||
context.stepHistory = [];
|
||||
context.stepHistory = new StepHistory();
|
||||
context.history.set(stepName, context.stepHistory);
|
||||
await step.run(context, stepData);
|
||||
}
|
||||
}
|
||||
|
||||
const commandUsage = Array.from(stepsDetails.entries()).map(([step, stepDetails]) => {
|
||||
const stepHistory = context.history.get(step);
|
||||
return {
|
||||
name: step,
|
||||
...stepDetails,
|
||||
history: stepHistory ? stepHistory.toJSON() : [],
|
||||
};
|
||||
});
|
||||
return { commandUsage, configuration: context.configuration };
|
||||
};
|
||||
|
||||
@ -32,8 +32,8 @@ const initializeProjectChoices = [
|
||||
{ name: 'Other', value: 'other' },
|
||||
];
|
||||
|
||||
const projectTypeChoice = async () =>
|
||||
(
|
||||
const projectTypeChoice = async (stepHistory) => {
|
||||
const projectType = (
|
||||
await inquirer.prompt({
|
||||
message: 'What do you want to make?',
|
||||
type: 'list',
|
||||
@ -43,14 +43,18 @@ const projectTypeChoice = async () =>
|
||||
})
|
||||
).projectType;
|
||||
|
||||
stepHistory.set('projectType', projectType);
|
||||
return projectType;
|
||||
};
|
||||
|
||||
const INVALID_PROJECT_NAME_MESSAGE =
|
||||
'Project name is not valid.\n' +
|
||||
' - It should only contain alphanumeric and hyphens.\n' +
|
||||
' - It should start with an alphabetic character.\n' +
|
||||
" - Shouldn't exceed 128 characters";
|
||||
|
||||
const projectNameInput = async (workingDir, projectType) =>
|
||||
(
|
||||
const projectNameInput = async (workingDir, projectType, stepHistory) => {
|
||||
const projectName = (
|
||||
await inquirer.prompt({
|
||||
message: 'What do you want to call this project?',
|
||||
type: 'input',
|
||||
@ -72,7 +76,11 @@ const projectNameInput = async (workingDir, projectType) =>
|
||||
})
|
||||
).projectName.trim();
|
||||
|
||||
const resolveProjectNameInput = async (options, workingDir, projectType = null) => {
|
||||
stepHistory.set('projectName', '_user_provided_');
|
||||
return projectName;
|
||||
};
|
||||
|
||||
const resolveProjectNameInput = async ({ options, workingDir, projectType, stepHistory }) => {
|
||||
if (options.name) {
|
||||
if (!isValidServiceName(options.name)) {
|
||||
throw new ServerlessError(INVALID_PROJECT_NAME_MESSAGE, 'INVALID_PROJECT_NAME');
|
||||
@ -96,11 +104,12 @@ const resolveProjectNameInput = async (options, workingDir, projectType = null)
|
||||
return options.name;
|
||||
}
|
||||
|
||||
return projectNameInput(workingDir, projectType);
|
||||
return projectNameInput(workingDir, projectType, stepHistory);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
isApplicable({ options, serviceDir }) {
|
||||
isApplicable(context) {
|
||||
const { options, serviceDir } = context;
|
||||
const notApplicableOptions = new Set(['name', 'template-path', 'template', 'template-url']);
|
||||
if (serviceDir && Object.keys(options).some((key) => notApplicableOptions.has(key))) {
|
||||
throw new ServerlessError(
|
||||
@ -113,7 +122,11 @@ module.exports = {
|
||||
);
|
||||
}
|
||||
|
||||
return !serviceDir;
|
||||
const inServiceDir = Boolean(serviceDir);
|
||||
if (inServiceDir) {
|
||||
context.inapplicabilityReasonCode = 'IN_SERVICE_DIRECTORY';
|
||||
}
|
||||
return !inServiceDir;
|
||||
},
|
||||
async run(context) {
|
||||
const workingDir = context.cwd || process.cwd();
|
||||
@ -132,7 +145,11 @@ module.exports = {
|
||||
let projectDir;
|
||||
let projectName;
|
||||
if (context.options['template-path']) {
|
||||
projectName = await resolveProjectNameInput(context.options, workingDir);
|
||||
projectName = await resolveProjectNameInput({
|
||||
options: context.options,
|
||||
workingDir,
|
||||
stepHistory: context.stepHistory,
|
||||
});
|
||||
projectDir = join(workingDir, projectName);
|
||||
await createFromLocalTemplate({
|
||||
templatePath: context.options['template-path'],
|
||||
@ -140,7 +157,11 @@ module.exports = {
|
||||
projectName,
|
||||
});
|
||||
} else if (context.options['template-url']) {
|
||||
projectName = await resolveProjectNameInput(context.options, workingDir);
|
||||
projectName = await resolveProjectNameInput({
|
||||
options: context.options,
|
||||
workingDir,
|
||||
stepHistory: context.stepHistory,
|
||||
});
|
||||
projectDir = join(workingDir, projectName);
|
||||
const templateUrl = context.options['template-url'];
|
||||
process.stdout.write(`\nDownloading template from provided url: ${templateUrl}...\n`);
|
||||
@ -159,7 +180,7 @@ module.exports = {
|
||||
if (context.options.template) {
|
||||
projectType = context.options.template;
|
||||
} else {
|
||||
projectType = await projectTypeChoice();
|
||||
projectType = await projectTypeChoice(context.stepHistory);
|
||||
if (projectType === 'other') {
|
||||
process.stdout.write(
|
||||
'\nRun “serverless create --help” to view available templates and create a new project ' +
|
||||
@ -168,7 +189,12 @@ module.exports = {
|
||||
return;
|
||||
}
|
||||
}
|
||||
projectName = await resolveProjectNameInput(context.options, workingDir, projectType);
|
||||
projectName = await resolveProjectNameInput({
|
||||
options: context.options,
|
||||
workingDir,
|
||||
projectType,
|
||||
stepHistory: context.stepHistory,
|
||||
});
|
||||
projectDir = join(workingDir, projectName);
|
||||
const templateUrl = `https://github.com/serverless/examples/tree/master/${projectType}`;
|
||||
process.stdout.write(`\nDownloading "${projectType}" template...\n`);
|
||||
@ -238,4 +264,5 @@ module.exports = {
|
||||
context.configuration = await readConfiguration(configurationPath);
|
||||
await resolveVariables(context);
|
||||
},
|
||||
configuredQuestions: ['projectType', 'projectName'],
|
||||
};
|
||||
|
||||
@ -123,6 +123,7 @@ module.exports = async ({
|
||||
serviceDir,
|
||||
configuration,
|
||||
serverless,
|
||||
commandUsage,
|
||||
}) => {
|
||||
let commandDurationMs;
|
||||
|
||||
@ -268,5 +269,9 @@ module.exports = async ({
|
||||
payload.dashboard.orgUid = serverless && serverless.service.orgUid;
|
||||
}
|
||||
|
||||
if (commandUsage) {
|
||||
payload.commandUsage = commandUsage;
|
||||
}
|
||||
|
||||
return payload;
|
||||
};
|
||||
|
||||
@ -403,12 +403,13 @@ const processSpanPromise = (async () => {
|
||||
'INTERACTIVE_SETUP_IN_NON_TTY'
|
||||
);
|
||||
}
|
||||
await require('../lib/cli/interactive-setup')({
|
||||
configuration,
|
||||
serviceDir,
|
||||
configurationFilename,
|
||||
options,
|
||||
});
|
||||
const { commandUsage, configuration: configurationFromInteractive } =
|
||||
await require('../lib/cli/interactive-setup')({
|
||||
configuration,
|
||||
serviceDir,
|
||||
configurationFilename,
|
||||
options,
|
||||
});
|
||||
hasTelemetryBeenReported = true;
|
||||
if (!isTelemetryDisabled) {
|
||||
await storeTelemetryLocally(
|
||||
@ -417,7 +418,8 @@ const processSpanPromise = (async () => {
|
||||
options,
|
||||
commandSchema,
|
||||
serviceDir,
|
||||
configuration,
|
||||
configuration: configurationFromInteractive,
|
||||
commandUsage,
|
||||
})
|
||||
);
|
||||
await sendTelemetry({ serverlessExecutionSpan: processSpanPromise });
|
||||
|
||||
@ -7,6 +7,7 @@ const overrideEnv = require('process-utils/override-env');
|
||||
const overrideStdoutWrite = require('process-utils/override-stdout-write');
|
||||
const requireUncached = require('ncjsm/require-uncached');
|
||||
const chalk = require('chalk');
|
||||
const { StepHistory } = require('@serverless/utils/telemetry');
|
||||
|
||||
const { expect } = chai;
|
||||
|
||||
@ -53,17 +54,21 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it('Should be ineffective, when not at service path', async () =>
|
||||
expect(await step.isApplicable({})).to.equal(false));
|
||||
it('Should be ineffective, when not at service path', async () => {
|
||||
const context = {};
|
||||
expect(await step.isApplicable(context)).to.equal(false);
|
||||
expect(context.inapplicabilityReasonCode).to.equal('NON_AWS_PROVIDER');
|
||||
});
|
||||
|
||||
it('Should be ineffective, when not at AWS service', async () =>
|
||||
expect(
|
||||
await step.isApplicable({
|
||||
serviceDir: process.cwd(),
|
||||
configuration: {},
|
||||
configurationFilename: 'serverless.yml',
|
||||
})
|
||||
).to.equal(false));
|
||||
it('Should be ineffective, when not at AWS service', async () => {
|
||||
const context = {
|
||||
serviceDir: process.cwd(),
|
||||
configuration: {},
|
||||
configurationFilename: 'serverless.yml',
|
||||
};
|
||||
expect(await step.isApplicable(context)).to.equal(false);
|
||||
expect(context.inapplicabilityReasonCode).to.equal('NON_AWS_PROVIDER');
|
||||
});
|
||||
|
||||
it('Should be ineffective, when user has default provider set', async () => {
|
||||
const internalMockedSdk = {
|
||||
@ -92,13 +97,13 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => {
|
||||
'@serverless/dashboard-plugin/lib/isAuthenticated': () => true,
|
||||
});
|
||||
|
||||
expect(
|
||||
await mockedStep.isApplicable({
|
||||
serviceDir: process.cwd(),
|
||||
configuration: { provider: { name: 'aws' }, org: 'someorg' },
|
||||
configurationFilename: 'serverless.yml',
|
||||
})
|
||||
).to.be.false;
|
||||
const context = {
|
||||
serviceDir: process.cwd(),
|
||||
configuration: { provider: { name: 'aws' }, org: 'someorg' },
|
||||
configurationFilename: 'serverless.yml',
|
||||
};
|
||||
expect(await mockedStep.isApplicable(context)).to.be.false;
|
||||
expect(context.inapplicabilityReasonCode).to.equal('DEFAULT_PROVIDER_CONFIGURED');
|
||||
});
|
||||
|
||||
it('Should be ineffective, when existing service already has a provider set', async () => {
|
||||
@ -131,20 +136,20 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
await mockedStep.isApplicable({
|
||||
history: new Set(),
|
||||
serviceDir: process.cwd(),
|
||||
configuration: {
|
||||
provider: { name: 'aws' },
|
||||
org: 'someorg',
|
||||
app: 'someapp',
|
||||
service: 'service',
|
||||
},
|
||||
options: {},
|
||||
configurationFilename: 'serverless.yml',
|
||||
})
|
||||
).to.be.false;
|
||||
const context = {
|
||||
history: new Set(),
|
||||
serviceDir: process.cwd(),
|
||||
configuration: {
|
||||
provider: { name: 'aws' },
|
||||
org: 'someorg',
|
||||
app: 'someapp',
|
||||
service: 'service',
|
||||
},
|
||||
options: {},
|
||||
configurationFilename: 'serverless.yml',
|
||||
};
|
||||
expect(await mockedStep.isApplicable(context)).to.be.false;
|
||||
expect(context.inapplicabilityReasonCode).to.equal('LINKED_PROVIDER_CONFIGURED');
|
||||
});
|
||||
|
||||
it('Should be effective, when existing service instance does not have a provider set', async () => {
|
||||
@ -239,18 +244,23 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => {
|
||||
list: { credentialsSetupChoice: '_skip_' },
|
||||
});
|
||||
|
||||
const context = {
|
||||
serviceDir: process.cwd(),
|
||||
configuration: { provider: { name: 'aws' } },
|
||||
configurationFilename: 'serverless.yml',
|
||||
stepHistory: new StepHistory(),
|
||||
options: {},
|
||||
};
|
||||
let stdoutData = '';
|
||||
await overrideStdoutWrite(
|
||||
(data) => (stdoutData += data),
|
||||
async () =>
|
||||
await step.run({
|
||||
serviceDir: process.cwd(),
|
||||
configuration: { provider: { name: 'aws' }, org: 'someorg' },
|
||||
configurationFilename: 'serverless.yml',
|
||||
})
|
||||
async () => await step.run(context)
|
||||
);
|
||||
|
||||
expect(stdoutData).to.include('You can setup your AWS account later');
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(
|
||||
new Map([['credentialsSetupChoice', '_skip_']])
|
||||
);
|
||||
});
|
||||
|
||||
describe('In environment credentials', () => {
|
||||
@ -323,13 +333,27 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => {
|
||||
secretAccessKey,
|
||||
},
|
||||
});
|
||||
await step.run({ configuration: { provider: {} }, options: {} });
|
||||
const context = {
|
||||
configuration: { provider: {} },
|
||||
options: {},
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
await step.run(context);
|
||||
expect(openBrowserUrls.length).to.equal(2);
|
||||
expect(openBrowserUrls[0].includes('signup')).to.be.true;
|
||||
expect(openBrowserUrls[1].includes('console.aws.amazon.com')).to.be.true;
|
||||
resolveFileProfiles().then((profiles) => {
|
||||
expect(profiles).to.deep.equal(new Map([['default', { accessKeyId, secretAccessKey }]]));
|
||||
});
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(
|
||||
new Map([
|
||||
['credentialsSetupChoice', '_local_'],
|
||||
['createAwsAccountPrompt', true],
|
||||
['generateAwsCredsPrompt', true],
|
||||
['accessKeyId', '_user_provided_'],
|
||||
['secretAccessKey', '_user_provided_'],
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('Should setup credentials for users having an AWS account', async () => {
|
||||
@ -338,9 +362,22 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => {
|
||||
confirm: { hasAwsAccount: true },
|
||||
input: { generateAwsCredsPrompt: '', accessKeyId, secretAccessKey },
|
||||
});
|
||||
await step.run({ configuration: { provider: {} }, options: {} });
|
||||
const context = {
|
||||
configuration: { provider: {} },
|
||||
options: {},
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
await step.run(context);
|
||||
expect(openBrowserUrls.length).to.equal(1);
|
||||
expect(openBrowserUrls[0].includes('console.aws.amazon.com')).to.be.true;
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(
|
||||
new Map([
|
||||
['credentialsSetupChoice', '_local_'],
|
||||
['generateAwsCredsPrompt', true],
|
||||
['accessKeyId', '_user_provided_'],
|
||||
['secretAccessKey', '_user_provided_'],
|
||||
])
|
||||
);
|
||||
return resolveFileProfiles().then((profiles) => {
|
||||
expect(profiles).to.deep.equal(new Map([['default', { accessKeyId, secretAccessKey }]]));
|
||||
});
|
||||
@ -352,12 +389,21 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => {
|
||||
confirm: { hasAwsAccount: true },
|
||||
input: { generateAwsCredsPrompt: '', accessKeyId: 'foo', secretAccessKey },
|
||||
});
|
||||
await expect(
|
||||
step.run({
|
||||
configuration: { provider: {} },
|
||||
options: {},
|
||||
})
|
||||
).to.eventually.be.rejected.and.have.property('code', 'INVALID_ANSWER');
|
||||
const context = {
|
||||
configuration: { provider: {} },
|
||||
options: {},
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
await expect(step.run(context)).to.eventually.be.rejected.and.have.property(
|
||||
'code',
|
||||
'INVALID_ANSWER'
|
||||
);
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(
|
||||
new Map([
|
||||
['credentialsSetupChoice', '_local_'],
|
||||
['generateAwsCredsPrompt', true],
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('Should not accept invalid secret access key', async () => {
|
||||
@ -366,12 +412,22 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => {
|
||||
confirm: { hasAwsAccount: true },
|
||||
input: { generateAwsCredsPrompt: '', accessKeyId, secretAccessKey: 'foo' },
|
||||
});
|
||||
await expect(
|
||||
step.run({
|
||||
configuration: { provider: {} },
|
||||
options: {},
|
||||
})
|
||||
).to.eventually.be.rejected.and.have.property('code', 'INVALID_ANSWER');
|
||||
const context = {
|
||||
configuration: { provider: {} },
|
||||
options: {},
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
await expect(step.run(context)).to.eventually.be.rejected.and.have.property(
|
||||
'code',
|
||||
'INVALID_ANSWER'
|
||||
);
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(
|
||||
new Map([
|
||||
['credentialsSetupChoice', '_local_'],
|
||||
['generateAwsCredsPrompt', true],
|
||||
['accessKeyId', '_user_provided_'],
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -418,6 +474,7 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => {
|
||||
},
|
||||
options: {},
|
||||
configurationFilename: 'serverless.yml',
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
await overrideStdoutWrite(
|
||||
(data) => (stdoutData += data),
|
||||
@ -432,6 +489,9 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => {
|
||||
);
|
||||
expect(mockedDisconnect).to.have.been.called;
|
||||
expect(mockedCreateProviderLink).not.to.have.been.called;
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(
|
||||
new Map([['credentialsSetupChoice', '_create_provider_']])
|
||||
);
|
||||
});
|
||||
|
||||
it('Should correctly setup with newly created provider when previous providers exist', async () => {
|
||||
@ -490,6 +550,7 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => {
|
||||
},
|
||||
options: {},
|
||||
configurationFilename: 'serverless.yml',
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
let stdoutData = '';
|
||||
await overrideStdoutWrite(
|
||||
@ -510,6 +571,9 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => {
|
||||
'appName|someapp|serviceName|someservice|stage|dev|region|us-east-1',
|
||||
providerUid
|
||||
);
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(
|
||||
new Map([['credentialsSetupChoice', '_create_provider_']])
|
||||
);
|
||||
});
|
||||
|
||||
it('Should emit warning when dashboard unavailable when connecting to it', async () => {
|
||||
@ -533,20 +597,21 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => {
|
||||
list: { credentialsSetupChoice: '_create_provider_' },
|
||||
});
|
||||
|
||||
const context = {
|
||||
serviceDir: process.cwd(),
|
||||
configuration: {
|
||||
service: 'someservice',
|
||||
provider: { name: 'aws' },
|
||||
org: 'someorg',
|
||||
app: 'someapp',
|
||||
},
|
||||
configurationFilename: 'serverless.yml',
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
let stdoutData = '';
|
||||
await overrideStdoutWrite(
|
||||
(data) => (stdoutData += data),
|
||||
async () =>
|
||||
await mockedStep.run({
|
||||
serviceDir: process.cwd(),
|
||||
configuration: {
|
||||
service: 'someservice',
|
||||
provider: { name: 'aws' },
|
||||
org: 'someorg',
|
||||
app: 'someapp',
|
||||
},
|
||||
configurationFilename: 'serverless.yml',
|
||||
})
|
||||
async () => await mockedStep.run(context)
|
||||
);
|
||||
|
||||
expect(stdoutData).to.include('Dashboard service is currently unavailable');
|
||||
@ -555,6 +620,9 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => {
|
||||
'https://app.serverless.com/someorg/settings/providers?source=cli&providerId=new&provider=aws'
|
||||
)
|
||||
);
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(
|
||||
new Map([['credentialsSetupChoice', '_create_provider_']])
|
||||
);
|
||||
});
|
||||
|
||||
it('Should correctly setup with existing provider', async () => {
|
||||
@ -599,6 +667,7 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => {
|
||||
},
|
||||
options: {},
|
||||
configurationFilename: 'serverless.yml',
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
let stdoutData = '';
|
||||
await overrideStdoutWrite(
|
||||
@ -613,6 +682,9 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => {
|
||||
'provideruid'
|
||||
);
|
||||
expect(stdoutData).to.include('Selected provider was successfully linked');
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(
|
||||
new Map([['credentialsSetupChoice', '_user_provided_']])
|
||||
);
|
||||
});
|
||||
|
||||
it('Should emit a warning when dashboard is not available and link cannot be created', async () => {
|
||||
@ -651,21 +723,22 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => {
|
||||
list: { credentialsSetupChoice: providerUid },
|
||||
});
|
||||
|
||||
const context = {
|
||||
serviceDir: process.cwd(),
|
||||
configuration: {
|
||||
service: 'someservice',
|
||||
provider: { name: 'aws' },
|
||||
org: 'someorg',
|
||||
app: 'someapp',
|
||||
},
|
||||
options: {},
|
||||
stepHistory: new StepHistory(),
|
||||
configurationFilename: 'serverless.yml',
|
||||
};
|
||||
let stdoutData = '';
|
||||
await overrideStdoutWrite(
|
||||
(data) => (stdoutData += data),
|
||||
async () =>
|
||||
await mockedStep.run({
|
||||
serviceDir: process.cwd(),
|
||||
configuration: {
|
||||
service: 'someservice',
|
||||
provider: { name: 'aws' },
|
||||
org: 'someorg',
|
||||
app: 'someapp',
|
||||
},
|
||||
options: {},
|
||||
configurationFilename: 'serverless.yml',
|
||||
})
|
||||
async () => await mockedStep.run(context)
|
||||
);
|
||||
|
||||
expect(stdoutData).to.include(
|
||||
@ -678,6 +751,10 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => {
|
||||
'appName|someapp|serviceName|someservice|stage|dev|region|us-east-1',
|
||||
'provideruid'
|
||||
);
|
||||
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(
|
||||
new Map([['credentialsSetupChoice', '_user_provided_']])
|
||||
);
|
||||
});
|
||||
|
||||
it('Should emit a warning when dashboard is not available when fetching providers', async () => {
|
||||
|
||||
@ -8,6 +8,7 @@ const overrideEnv = require('process-utils/override-env');
|
||||
const step = require('../../../../../lib/cli/interactive-setup/deploy');
|
||||
const proxyquire = require('proxyquire');
|
||||
const overrideStdoutWrite = require('process-utils/override-stdout-write');
|
||||
const { StepHistory } = require('@serverless/utils/telemetry');
|
||||
|
||||
const { expect } = chai;
|
||||
|
||||
@ -17,18 +18,24 @@ chai.use(require('sinon-chai'));
|
||||
const inquirer = require('@serverless/utils/inquirer');
|
||||
|
||||
describe('test/unit/lib/cli/interactive-setup/deploy.test.js', () => {
|
||||
it('Should be not applied, when not at service path', async () =>
|
||||
expect(await step.isApplicable({ options: {} })).to.equal(false));
|
||||
it('Should be not applied, when not at service path', async () => {
|
||||
const context = {
|
||||
options: {},
|
||||
};
|
||||
expect(await step.isApplicable(context)).to.equal(false);
|
||||
expect(context.inapplicabilityReasonCode).to.equal('NOT_IN_SERVICE_DIRECTORY');
|
||||
});
|
||||
|
||||
it('Should be not applied, when service is not configured with AWS provider', async () =>
|
||||
expect(
|
||||
await step.isApplicable({
|
||||
configuration: { provider: { name: 'notaws' } },
|
||||
serviceDir: '/foo',
|
||||
options: {},
|
||||
history: new Map(),
|
||||
})
|
||||
).to.equal(false));
|
||||
it('Should be not applied, when service is not configured with AWS provider', async () => {
|
||||
const context = {
|
||||
configuration: { provider: { name: 'notaws' } },
|
||||
serviceDir: '/foo',
|
||||
options: {},
|
||||
history: new Map([['service', []]]),
|
||||
};
|
||||
expect(await step.isApplicable(context)).to.equal(false);
|
||||
expect(context.inapplicabilityReasonCode).to.equal('NON_AWS_PROVIDER');
|
||||
});
|
||||
|
||||
it('Should be applied, if awsCredentials step was not executed which means user already had credentials', async () =>
|
||||
expect(
|
||||
@ -80,22 +87,24 @@ describe('test/unit/lib/cli/interactive-setup/deploy.test.js', () => {
|
||||
confirm: { shouldDeploy: false },
|
||||
});
|
||||
|
||||
const context = {
|
||||
serviceDir: process.cwd(),
|
||||
configuration: {
|
||||
service: 'someservice',
|
||||
provider: { name: 'aws' },
|
||||
},
|
||||
configurationFilename: 'serverless.yml',
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
let stdoutData = '';
|
||||
await overrideStdoutWrite(
|
||||
(data) => (stdoutData += data),
|
||||
async () =>
|
||||
await step.run({
|
||||
serviceDir: process.cwd(),
|
||||
configuration: {
|
||||
service: 'someservice',
|
||||
provider: { name: 'aws' },
|
||||
},
|
||||
configurationFilename: 'serverless.yml',
|
||||
})
|
||||
async () => await step.run(context)
|
||||
);
|
||||
|
||||
expect(stdoutData).to.include('Your project is ready for deployment');
|
||||
expect(stdoutData).to.include(`Run ${chalk.bold('serverless')} in the project directory`);
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(new Map([['shouldDeploy', false]]));
|
||||
});
|
||||
|
||||
it('should correctly handle skipping deployment for service not configured with dashboard', async () => {
|
||||
@ -103,24 +112,26 @@ describe('test/unit/lib/cli/interactive-setup/deploy.test.js', () => {
|
||||
confirm: { shouldDeploy: false },
|
||||
});
|
||||
|
||||
const context = {
|
||||
serviceDir: process.cwd(),
|
||||
configuration: {
|
||||
service: 'someservice',
|
||||
provider: { name: 'aws' },
|
||||
org: 'someorg',
|
||||
app: 'someapp',
|
||||
},
|
||||
configurationFilename: 'serverless.yml',
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
let stdoutData = '';
|
||||
await overrideStdoutWrite(
|
||||
(data) => (stdoutData += data),
|
||||
async () =>
|
||||
await step.run({
|
||||
serviceDir: process.cwd(),
|
||||
configuration: {
|
||||
service: 'someservice',
|
||||
provider: { name: 'aws' },
|
||||
org: 'someorg',
|
||||
app: 'someapp',
|
||||
},
|
||||
configurationFilename: 'serverless.yml',
|
||||
})
|
||||
async () => await step.run(context)
|
||||
);
|
||||
|
||||
expect(stdoutData).to.include('Your project is ready for deployment');
|
||||
expect(stdoutData).to.include('Invoke your functions and view logs in the dashboard');
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(new Map([['shouldDeploy', false]]));
|
||||
});
|
||||
|
||||
it('should correctly handle deployment for service configured with dashboard', async () => {
|
||||
@ -156,26 +167,29 @@ describe('test/unit/lib/cli/interactive-setup/deploy.test.js', () => {
|
||||
confirm: { shouldDeploy: true },
|
||||
});
|
||||
|
||||
const context = {
|
||||
serviceDir: process.cwd(),
|
||||
configuration: {
|
||||
service: 'someservice',
|
||||
provider: { name: 'aws' },
|
||||
org: 'someorg',
|
||||
app: 'someapp',
|
||||
},
|
||||
configurationFilename: 'serverless.yml',
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
let stdoutData = '';
|
||||
await overrideStdoutWrite(
|
||||
(data) => (stdoutData += data),
|
||||
async () =>
|
||||
await mockedStep.run({
|
||||
serviceDir: process.cwd(),
|
||||
configuration: {
|
||||
service: 'someservice',
|
||||
provider: { name: 'aws' },
|
||||
org: 'someorg',
|
||||
app: 'someapp',
|
||||
},
|
||||
configurationFilename: 'serverless.yml',
|
||||
})
|
||||
async () => await mockedStep.run(context)
|
||||
);
|
||||
|
||||
expect(stdoutData).to.include('Your project is live and available');
|
||||
expect(stdoutData).to.include(
|
||||
`Open ${chalk.bold('https://app.serverless-dev.com/path/to/dashboard')}`
|
||||
);
|
||||
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(new Map([['shouldDeploy', true]]));
|
||||
});
|
||||
|
||||
it('should correctly handle deployment for service not configured with dashboard', async () => {
|
||||
@ -207,22 +221,24 @@ describe('test/unit/lib/cli/interactive-setup/deploy.test.js', () => {
|
||||
confirm: { shouldDeploy: true },
|
||||
});
|
||||
|
||||
const context = {
|
||||
serviceDir: process.cwd(),
|
||||
configuration: {
|
||||
service: 'someservice',
|
||||
provider: { name: 'aws' },
|
||||
},
|
||||
configurationFilename: 'serverless.yml',
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
let stdoutData = '';
|
||||
await overrideStdoutWrite(
|
||||
(data) => (stdoutData += data),
|
||||
async () =>
|
||||
await mockedStep.run({
|
||||
serviceDir: process.cwd(),
|
||||
configuration: {
|
||||
service: 'someservice',
|
||||
provider: { name: 'aws' },
|
||||
},
|
||||
configurationFilename: 'serverless.yml',
|
||||
})
|
||||
async () => await mockedStep.run(context)
|
||||
);
|
||||
|
||||
expect(stdoutData).to.include('Your project is live and available');
|
||||
expect(stdoutData).to.include(`Run ${chalk.bold('serverless')}`);
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(new Map([['shouldDeploy', true]]));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -8,6 +8,7 @@ const step = require('../../../../../lib/cli/interactive-setup/service');
|
||||
const proxyquire = require('proxyquire');
|
||||
const overrideStdoutWrite = require('process-utils/override-stdout-write');
|
||||
const ServerlessError = require('../../../../../lib/serverless-error');
|
||||
const { StepHistory } = require('@serverless/utils/telemetry');
|
||||
|
||||
const templatesPath = path.resolve(__dirname, '../../../../../lib/plugins/create/templates');
|
||||
|
||||
@ -23,12 +24,24 @@ const confirmEmptyWorkingDir = async () =>
|
||||
expect(await fsp.readdir(process.cwd())).to.deep.equal([]);
|
||||
|
||||
describe('test/unit/lib/cli/interactive-setup/service.test.js', () => {
|
||||
afterEach(() => sinon.restore());
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it('Should be not applied, when at service path', () =>
|
||||
expect(step.isApplicable({ serviceDir: '/foo', options: {} })).to.equal(false));
|
||||
it('Should be applied, when not at service path', () =>
|
||||
expect(step.isApplicable({ options: {} })).to.equal(true));
|
||||
it('Should be not applied, when at service path', () => {
|
||||
const context = {
|
||||
serviceDir: '/foo',
|
||||
options: {},
|
||||
};
|
||||
expect(step.isApplicable(context)).to.equal(false);
|
||||
expect(context.inapplicabilityReasonCode).to.equal('IN_SERVICE_DIRECTORY');
|
||||
});
|
||||
|
||||
it('Should be applied, when not at service path', () => {
|
||||
const context = { options: {} };
|
||||
expect(step.isApplicable(context)).to.equal(true);
|
||||
expect(context.inapplicabilityReasonCode).to.be.undefined;
|
||||
});
|
||||
|
||||
it('Should result in an error when at service path with `template-path` options provided', () => {
|
||||
expect(() =>
|
||||
@ -56,7 +69,9 @@ describe('test/unit/lib/cli/interactive-setup/service.test.js', () => {
|
||||
configureInquirerStub(inquirer, {
|
||||
list: { projectType: 'other' },
|
||||
});
|
||||
await step.run({ options: {} });
|
||||
const context = { options: {}, stepHistory: new StepHistory() };
|
||||
await step.run(context);
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(new Map([['projectType', 'other']]));
|
||||
return confirmEmptyWorkingDir();
|
||||
});
|
||||
|
||||
@ -84,7 +99,8 @@ describe('test/unit/lib/cli/interactive-setup/service.test.js', () => {
|
||||
list: { projectType: 'aws-nodejs' },
|
||||
input: { projectName: 'test-project' },
|
||||
});
|
||||
await mockedStep.run({ options: {} });
|
||||
const context = { options: {}, stepHistory: new StepHistory() };
|
||||
await mockedStep.run(context);
|
||||
const stats = await fsp.lstat('test-project/serverless.yml');
|
||||
expect(stats.isFile()).to.be.true;
|
||||
expect(downloadTemplateFromRepoStub).to.have.been.calledWith(
|
||||
@ -92,6 +108,12 @@ describe('test/unit/lib/cli/interactive-setup/service.test.js', () => {
|
||||
'aws-nodejs',
|
||||
'test-project'
|
||||
);
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(
|
||||
new Map([
|
||||
['projectType', 'aws-nodejs'],
|
||||
['projectName', '_user_provided_'],
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('Should remove `serverless.template.yml` if its a part of the template', async () => {
|
||||
@ -118,7 +140,8 @@ describe('test/unit/lib/cli/interactive-setup/service.test.js', () => {
|
||||
list: { projectType: 'aws-nodejs' },
|
||||
input: { projectName: 'test-project-template' },
|
||||
});
|
||||
await mockedStep.run({ options: {} });
|
||||
const context = { options: {}, stepHistory: new StepHistory() };
|
||||
await mockedStep.run(context);
|
||||
const stats = await fsp.lstat('test-project-template/serverless.yml');
|
||||
expect(stats.isFile()).to.be.true;
|
||||
expect(downloadTemplateFromRepoStub).to.have.been.calledWith(
|
||||
@ -129,6 +152,13 @@ describe('test/unit/lib/cli/interactive-setup/service.test.js', () => {
|
||||
await expect(
|
||||
fsp.lstat('test-proejct-template/serverless.template.yml')
|
||||
).to.eventually.be.rejected.and.have.property('code', 'ENOENT');
|
||||
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(
|
||||
new Map([
|
||||
['projectType', 'aws-nodejs'],
|
||||
['projectName', '_user_provided_'],
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('Should run `npm install` if `package.json` present', async () => {
|
||||
@ -157,7 +187,8 @@ describe('test/unit/lib/cli/interactive-setup/service.test.js', () => {
|
||||
list: { projectType: 'aws-nodejs' },
|
||||
input: { projectName: 'test-project-package-json' },
|
||||
});
|
||||
await mockedStep.run({ options: {} });
|
||||
const context = { options: {}, stepHistory: new StepHistory() };
|
||||
await mockedStep.run(context);
|
||||
const stats = await fsp.lstat('test-project-package-json/serverless.yml');
|
||||
expect(stats.isFile()).to.be.true;
|
||||
expect(downloadTemplateFromRepoStub).to.have.been.calledWith(
|
||||
@ -168,6 +199,13 @@ describe('test/unit/lib/cli/interactive-setup/service.test.js', () => {
|
||||
expect(spawnStub).to.have.been.calledWith('npm', ['install'], {
|
||||
cwd: path.join(process.cwd(), 'test-project-package-json'),
|
||||
});
|
||||
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(
|
||||
new Map([
|
||||
['projectType', 'aws-nodejs'],
|
||||
['projectName', '_user_provided_'],
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('Should emit warning if npm installation not found', async () => {
|
||||
@ -196,15 +234,23 @@ describe('test/unit/lib/cli/interactive-setup/service.test.js', () => {
|
||||
input: { projectName: 'test-project-missing-npm' },
|
||||
});
|
||||
|
||||
const context = { options: {}, stepHistory: new StepHistory() };
|
||||
let stdoutData = '';
|
||||
await overrideStdoutWrite(
|
||||
(data) => (stdoutData += data),
|
||||
async () => mockedStep.run({ options: {} })
|
||||
async () => mockedStep.run(context)
|
||||
);
|
||||
|
||||
const stats = await fsp.lstat('test-project-missing-npm/serverless.yml');
|
||||
expect(stats.isFile()).to.be.true;
|
||||
expect(stdoutData).to.include('Cannot install dependencies');
|
||||
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(
|
||||
new Map([
|
||||
['projectType', 'aws-nodejs'],
|
||||
['projectName', '_user_provided_'],
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('Should emit warning if npm installation not found', async () => {
|
||||
@ -233,19 +279,35 @@ describe('test/unit/lib/cli/interactive-setup/service.test.js', () => {
|
||||
input: { projectName: 'test-project-failed-install' },
|
||||
});
|
||||
|
||||
await expect(mockedStep.run({ options: {} })).to.be.eventually.rejected.and.have.property(
|
||||
const context = { options: {}, stepHistory: new StepHistory() };
|
||||
await expect(mockedStep.run(context)).to.be.eventually.rejected.and.have.property(
|
||||
'code',
|
||||
'DEPENDENCIES_INSTALL_FAILED'
|
||||
);
|
||||
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(
|
||||
new Map([
|
||||
['projectType', 'aws-nodejs'],
|
||||
['projectName', '_user_provided_'],
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('Should create project at not existing directory from a provided `template-path`', async () => {
|
||||
configureInquirerStub(inquirer, {
|
||||
input: { projectName: 'test-project-from-local-template' },
|
||||
});
|
||||
await step.run({ options: { 'template-path': path.join(templatesPath, 'aws-nodejs') } });
|
||||
const context = {
|
||||
options: { 'template-path': path.join(templatesPath, 'aws-nodejs') },
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
await step.run(context);
|
||||
const stats = await fsp.lstat('test-project-from-local-template/serverless.yml');
|
||||
expect(stats.isFile()).to.be.true;
|
||||
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(
|
||||
new Map([['projectName', '_user_provided_']])
|
||||
);
|
||||
});
|
||||
|
||||
it('Should create project at not existing directory with provided `name`', async () => {
|
||||
@ -268,9 +330,16 @@ describe('test/unit/lib/cli/interactive-setup/service.test.js', () => {
|
||||
configureInquirerStub(inquirer, {
|
||||
list: { projectType: 'aws-nodejs' },
|
||||
});
|
||||
await mockedStep.run({ options: { name: 'test-project-from-cli-option' } });
|
||||
const context = {
|
||||
options: { name: 'test-project-from-cli-option' },
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
await mockedStep.run(context);
|
||||
const stats = await fsp.lstat('test-project-from-cli-option/serverless.yml');
|
||||
expect(stats.isFile()).to.be.true;
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(
|
||||
new Map([['projectType', 'aws-nodejs']])
|
||||
);
|
||||
});
|
||||
|
||||
it('Should create project at not existing directory with provided template', async () => {
|
||||
@ -294,7 +363,8 @@ describe('test/unit/lib/cli/interactive-setup/service.test.js', () => {
|
||||
configureInquirerStub(inquirer, {
|
||||
input: { projectName: 'test-project-from-provided-template' },
|
||||
});
|
||||
await mockedStep.run({ options: { template: 'test-template' } });
|
||||
const context = { options: { template: 'test-template' }, stepHistory: new StepHistory() };
|
||||
await mockedStep.run(context);
|
||||
const stats = await fsp.lstat('test-project-from-provided-template/serverless.yml');
|
||||
expect(stats.isFile()).to.be.true;
|
||||
expect(downloadTemplateFromRepoStub).to.have.been.calledWith(
|
||||
@ -302,6 +372,10 @@ describe('test/unit/lib/cli/interactive-setup/service.test.js', () => {
|
||||
'test-template',
|
||||
'test-project-from-provided-template'
|
||||
);
|
||||
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(
|
||||
new Map([['projectName', '_user_provided_']])
|
||||
);
|
||||
});
|
||||
|
||||
it('Should create project at not existing directory with provided `template-url`', async () => {
|
||||
@ -327,7 +401,11 @@ describe('test/unit/lib/cli/interactive-setup/service.test.js', () => {
|
||||
configureInquirerStub(inquirer, {
|
||||
input: { projectName: 'test-project-from-provided-template-url' },
|
||||
});
|
||||
await mockedStep.run({ options: { 'template-url': providedTemplateUrl } });
|
||||
const context = {
|
||||
options: { 'template-url': providedTemplateUrl },
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
await mockedStep.run(context);
|
||||
const stats = await fsp.lstat('test-project-from-provided-template-url/serverless.yml');
|
||||
expect(stats.isFile()).to.be.true;
|
||||
expect(downloadTemplateFromRepoStub).to.have.been.calledWith(
|
||||
@ -335,6 +413,10 @@ describe('test/unit/lib/cli/interactive-setup/service.test.js', () => {
|
||||
null,
|
||||
'test-project-from-provided-template-url'
|
||||
);
|
||||
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(
|
||||
new Map([['projectName', '_user_provided_']])
|
||||
);
|
||||
});
|
||||
|
||||
it('Should throw an error when template cannot be downloaded', async () => {
|
||||
@ -349,10 +431,18 @@ describe('test/unit/lib/cli/interactive-setup/service.test.js', () => {
|
||||
list: { projectType: 'aws-nodejs' },
|
||||
input: { projectName: 'test-error-during-download' },
|
||||
});
|
||||
await expect(mockedStep.run({ options: {} })).to.be.eventually.rejected.and.have.property(
|
||||
const context = { options: {}, stepHistory: new StepHistory() };
|
||||
await expect(mockedStep.run(context)).to.be.eventually.rejected.and.have.property(
|
||||
'code',
|
||||
'TEMPLATE_DOWNLOAD_FAILED'
|
||||
);
|
||||
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(
|
||||
new Map([
|
||||
['projectType', 'aws-nodejs'],
|
||||
['projectName', '_user_provided_'],
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('Should throw an error when provided template cannot be found', async () => {
|
||||
@ -364,9 +454,14 @@ describe('test/unit/lib/cli/interactive-setup/service.test.js', () => {
|
||||
configureInquirerStub(inquirer, {
|
||||
input: { projectName: 'test-error-during-download' },
|
||||
});
|
||||
await expect(
|
||||
mockedStep.run({ options: { template: 'test-template' } })
|
||||
).to.be.eventually.rejected.and.have.property('code', 'INVALID_TEMPLATE');
|
||||
const context = { options: { template: 'test-template' }, stepHistory: new StepHistory() };
|
||||
await expect(mockedStep.run(context)).to.be.eventually.rejected.and.have.property(
|
||||
'code',
|
||||
'INVALID_TEMPLATE'
|
||||
);
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(
|
||||
new Map([['projectName', '_user_provided_']])
|
||||
);
|
||||
});
|
||||
|
||||
it('Should throw an error when template provided with url cannot be found', async () => {
|
||||
@ -380,9 +475,18 @@ describe('test/unit/lib/cli/interactive-setup/service.test.js', () => {
|
||||
configureInquirerStub(inquirer, {
|
||||
input: { projectName: 'test-error-during-download-custom-template' },
|
||||
});
|
||||
await expect(
|
||||
mockedStep.run({ options: { 'template-url': 'test-template-url' } })
|
||||
).to.be.eventually.rejected.and.have.property('code', 'INVALID_TEMPLATE_URL');
|
||||
const context = {
|
||||
options: { 'template-url': 'test-template-url' },
|
||||
stepHistory: new StepHistory(),
|
||||
};
|
||||
await expect(mockedStep.run(context)).to.be.eventually.rejected.and.have.property(
|
||||
'code',
|
||||
'INVALID_TEMPLATE_URL'
|
||||
);
|
||||
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(
|
||||
new Map([['projectName', '_user_provided_']])
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -394,10 +498,13 @@ describe('test/unit/lib/cli/interactive-setup/service.test.js', () => {
|
||||
|
||||
await fsp.mkdir('existing');
|
||||
|
||||
await expect(step.run({ options: {} })).to.eventually.be.rejected.and.have.property(
|
||||
const context = { options: {}, stepHistory: new StepHistory() };
|
||||
await expect(step.run(context)).to.eventually.be.rejected.and.have.property(
|
||||
'code',
|
||||
'INVALID_ANSWER'
|
||||
);
|
||||
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(new Map([['projectType', 'aws-nodejs']]));
|
||||
});
|
||||
|
||||
it('Should not allow project creation in a directory in which already service is configured when `name` flag provided', async () => {
|
||||
@ -407,9 +514,13 @@ describe('test/unit/lib/cli/interactive-setup/service.test.js', () => {
|
||||
|
||||
await fsp.mkdir('anotherexisting');
|
||||
|
||||
await expect(
|
||||
step.run({ options: { name: 'anotherexisting' } })
|
||||
).to.eventually.be.rejected.and.have.property('code', 'TARGET_FOLDER_ALREADY_EXISTS');
|
||||
const context = { options: { name: 'anotherexisting' }, stepHistory: new StepHistory() };
|
||||
await expect(step.run(context)).to.eventually.be.rejected.and.have.property(
|
||||
'code',
|
||||
'TARGET_FOLDER_ALREADY_EXISTS'
|
||||
);
|
||||
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(new Map([['projectType', 'aws-nodejs']]));
|
||||
});
|
||||
|
||||
it('Should not allow project creation using an invalid project name', async () => {
|
||||
@ -417,19 +528,26 @@ describe('test/unit/lib/cli/interactive-setup/service.test.js', () => {
|
||||
list: { projectType: 'aws-nodejs' },
|
||||
input: { projectName: 'elo grzegżółka' },
|
||||
});
|
||||
await expect(step.run({ options: {} })).to.eventually.be.rejected.and.have.property(
|
||||
const context = { options: {}, stepHistory: new StepHistory() };
|
||||
await expect(step.run(context)).to.eventually.be.rejected.and.have.property(
|
||||
'code',
|
||||
'INVALID_ANSWER'
|
||||
);
|
||||
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(new Map([['projectType', 'aws-nodejs']]));
|
||||
});
|
||||
|
||||
it('Should not allow project creation using an invalid project name when `name` flag provided', async () => {
|
||||
configureInquirerStub(inquirer, {
|
||||
list: { projectType: 'aws-nodejs' },
|
||||
});
|
||||
await expect(
|
||||
step.run({ options: { name: 'elo grzegżółka' } })
|
||||
).to.eventually.be.rejected.and.have.property('code', 'INVALID_PROJECT_NAME');
|
||||
const context = { options: { name: 'elo grzegżółka' }, stepHistory: new StepHistory() };
|
||||
await expect(step.run(context)).to.eventually.be.rejected.and.have.property(
|
||||
'code',
|
||||
'INVALID_PROJECT_NAME'
|
||||
);
|
||||
|
||||
expect(context.stepHistory.valuesMap()).to.deep.equal(new Map([['projectType', 'aws-nodejs']]));
|
||||
});
|
||||
|
||||
it('Should not allow project creation if multiple template-related options are provided', async () => {
|
||||
|
||||
@ -6,6 +6,7 @@ const fs = require('fs');
|
||||
const os = require('os');
|
||||
const overrideEnv = require('process-utils/override-env');
|
||||
const overrideCwd = require('process-utils/override-cwd');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const resolveLocalServerless = require('../../../../../lib/cli/resolve-local-serverless-path');
|
||||
const commandsSchema = require('../../../../../lib/cli/commands-schema');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user