UNPKG

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
'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;