serverless
Version:
Serverless Framework - Build web, mobile and IoT applications with serverless architectures using AWS Lambda, Azure Functions, Google CloudFunctions & more
287 lines (258 loc) • 8.79 kB
JavaScript
'use strict';
const BbPromise = require('bluebird');
const path = require('path');
const fse = require('fs-extra');
const untildify = require('untildify');
const ServerlessError = require('../../classes/Error').ServerlessError;
const createFromTemplate = require('../../utils/createFromTemplate');
const download = require('../../utils/downloadTemplateFromRepo');
const renameService = require('../../utils/renameService').renameService;
const copyDirContentsSync = require('../../utils/fs/copyDirContentsSync');
const dirExistsSync = require('../../utils/fs/dirExistsSync');
// class wide constants
const validTemplates = [
'aws-clojure-gradle',
'aws-clojurescript-gradle',
'aws-nodejs',
'aws-nodejs-typescript',
'aws-alexa-typescript',
'aws-nodejs-ecma-script',
'aws-python',
'aws-python3',
'aws-groovy-gradle',
'aws-java-maven',
'aws-java-gradle',
'aws-kotlin-jvm-maven',
'aws-kotlin-jvm-gradle',
'aws-kotlin-jvm-gradle-kts',
'aws-kotlin-nodejs-gradle',
'aws-scala-sbt',
'aws-csharp',
'aws-fsharp',
'aws-go',
'aws-go-dep',
'aws-go-mod',
'aws-ruby',
'aws-provided',
'tencent-go',
'tencent-nodejs',
'tencent-python',
'tencent-php',
'azure-csharp',
'azure-nodejs',
'azure-nodejs-typescript',
'azure-python',
'cloudflare-workers',
'cloudflare-workers-enterprise',
'cloudflare-workers-rust',
'fn-nodejs',
'fn-go',
'google-nodejs',
'google-python',
'google-go',
'kubeless-python',
'kubeless-nodejs',
'knative-docker',
'openwhisk-java-maven',
'openwhisk-nodejs',
'openwhisk-php',
'openwhisk-python',
'openwhisk-ruby',
'openwhisk-swift',
'spotinst-nodejs',
'spotinst-python',
'spotinst-ruby',
'spotinst-java8',
'twilio-nodejs',
'aliyun-nodejs',
'plugin',
// this template is used to streamline the onboarding process
// it uses the Node.js runtime and AWS provider
'hello-world',
];
const humanReadableTemplateList = (() => {
let lastGroupName = null;
let result = '';
let lineCount = 0;
const templateGroupRe = /^([a-z0-9]+)(-|$)/;
for (const templateName of validTemplates) {
const groupName = templateName.match(templateGroupRe)[1];
if (groupName !== lastGroupName || lineCount === 8) {
result += `\n${' '.repeat(45)}"${templateName}"`;
lastGroupName = groupName;
lineCount = 1;
} else {
result += `, "${templateName}"`;
++lineCount;
}
}
return result;
})();
const handleServiceCreationError = (error) => {
if (error.code !== 'EACCESS') throw error;
const errorMessage = [
'Error unable to create a service in this directory. ',
'Please check that you have the required permissions to write to the directory',
].join('');
throw new this.serverless.classes.Error(errorMessage);
};
class Create {
constructor(serverless, options) {
this.serverless = serverless;
this.options = options;
this.commands = {
create: {
usage: 'Create new Serverless service',
lifecycleEvents: ['create'],
options: {
'template': {
usage: `Template for the service. Available templates: ${humanReadableTemplateList}`,
shortcut: 't',
},
'template-url': {
usage: 'Template URL for the service. Supports: GitHub, BitBucket',
shortcut: 'u',
},
'template-path': {
usage: 'Template local path for the service.',
},
'path': {
usage: 'The path where the service should be created (e.g. --path my-service)',
shortcut: 'p',
},
'name': {
usage: 'Name for the service. Overwrites the default name of the created service.',
shortcut: 'n',
},
},
},
};
this.hooks = {
'create:create': () => BbPromise.bind(this).then(this.create),
};
}
create() {
this.serverless.cli.log('Generating boilerplate...');
if ('template' in this.options) {
return this.createFromTemplate();
} else if ('template-url' in this.options) {
return download
.downloadTemplateFromRepo(
this.options['template-url'],
this.options.name,
this.options.path
)
.then((serviceName) => {
const message = [
`Successfully installed "${serviceName}" `,
`${
this.options.name && this.options.name !== serviceName
? `as "${this.options.name}"`
: ''
}`,
].join('');
this.serverless.cli.log(message);
})
.catch((err) => {
throw new this.serverless.classes.Error(err);
});
} else if ('template-path' in this.options) {
// Copying template from a local directory
const servicePath = this.options.path
? path.resolve(process.cwd(), untildify(this.options.path))
: path.join(process.cwd(), this.options.name);
if (dirExistsSync(servicePath)) {
const errorMessage = `A folder named "${servicePath}" already exists.`;
throw new ServerlessError(errorMessage);
}
copyDirContentsSync(untildify(this.options['template-path']), servicePath, {
noLinks: true,
});
if (this.options.name) {
renameService(this.options.name, servicePath);
}
} else {
const errorMessage = [
'You must either pass a template name (--template), ',
'a URL (--template-url) or a local path (--template-path).',
].join('');
throw new this.serverless.classes.Error(errorMessage);
}
return BbPromise.resolve();
}
createFromTemplate() {
const notPlugin = this.options.template !== 'plugin';
if (validTemplates.indexOf(this.options.template) === -1) {
const errorMessage = [
`Template "${this.options.template}" is not supported.`,
` Supported templates are: ${humanReadableTemplateList}.`,
].join('');
throw new this.serverless.classes.Error(errorMessage);
}
// store the custom options for the service if given
const boilerplatePath = this.options.path && String(this.options.path);
const serviceName = this.options.name && String(this.options.name);
const templateSrcDir = path.join(
this.serverless.config.serverlessPath,
'plugins',
'create',
'templates',
this.options.template
);
// create (if not yet present) and chdir into the directory for the service
if (boilerplatePath) {
const newPath = path.resolve(process.cwd(), untildify(boilerplatePath));
if (this.serverless.utils.dirExistsSync(newPath)) {
const errorMessage = [
`The directory "${newPath}" already exists, and serverless will not overwrite it. `,
'Rename or move the directory and try again if you want serverless to create it"',
].join('');
throw new this.serverless.classes.Error(errorMessage);
}
this.serverless.cli.log(`Generating boilerplate in "${newPath}"`);
fse.mkdirsSync(newPath);
process.chdir(newPath);
} else {
// ensure no template file already exists in cwd that we may overwrite
const templateFullFilePaths = this.serverless.utils.walkDirSync(templateSrcDir);
templateFullFilePaths.forEach((ffp) => {
const filename = path.basename(ffp);
if (this.serverless.utils.fileExistsSync(path.join(process.cwd(), filename))) {
const errorMessage = [
`The file "${filename}" already exists, and serverless will not overwrite it. `,
`Move the file and try again if you want serverless to write a new "${filename}"`,
].join('');
throw new this.serverless.classes.Error(errorMessage);
}
});
}
if (!notPlugin) {
return BbPromise.try(() => {
try {
fse.copySync(
path.join(__dirname, '../../../lib/plugins/create/templates', this.options.template),
process.cwd()
);
} catch (error) {
handleServiceCreationError(error);
}
});
}
this.serverless.config.update({ servicePath: process.cwd() });
return createFromTemplate(this.options.template, process.cwd(), { name: serviceName }).then(
() => {
this.serverless.cli.asciiGreeting();
this.serverless.cli.log(
`Successfully generated boilerplate for template: "${this.options.template}"`
);
if (!(boilerplatePath || serviceName) && notPlugin) {
this.serverless.cli.log(
'NOTE: Please update the "service" property in serverless.yml with your service name'
);
}
},
handleServiceCreationError
);
}
}
module.exports = Create;