/
create.js
253 lines (218 loc) · 8.2 KB
/
create.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
'use strict';
const BbPromise = require('bluebird');
const path = require('path');
const fse = require('fs-extra');
const _ = require('lodash');
const ServerlessError = require('../../classes/Error').ServerlessError;
const userStats = require('../../utils/userStats');
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-nodejs',
'aws-nodejs-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-nodejs-gradle',
'aws-scala-sbt',
'aws-csharp',
'aws-fsharp',
'aws-go',
'aws-go-dep',
'azure-nodejs',
'google-nodejs',
'kubeless-python',
'kubeless-nodejs',
'openwhisk-nodejs',
'openwhisk-php',
'openwhisk-python',
'openwhisk-swift',
'spotinst-nodejs',
'spotinst-python',
'spotinst-ruby',
'spotinst-java8',
'webtasks-nodejs',
'plugin',
// this template is used to streamline the onboarding process
// it uses the Node.js runtime and AWS provider
'hello-world',
];
const humanReadableTemplateList = `${validTemplates.slice(0, -1)
.map((template) => `"${template}"`).join(', ')} and "${validTemplates.slice(-1)}"`;
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) {
this.createFromTemplate();
} else if ('template-url' in this.options) {
return download.downloadTemplateFromRepo(
this.options['template-url'],
this.options.name,
this.options.path
)
.then(dirName => {
const message = [
`Successfully installed "${dirName}" `,
`${this.options.name && this.options.name !== dirName ? `as "${dirName}"` : ''}`,
].join('');
this.serverless.cli.log(message);
userStats.track('service_created', {
template: this.options.template,
serviceName: this.options.name,
});
})
.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.join(process.cwd(), this.options.name);
if (dirExistsSync(servicePath)) {
const errorMessage = `A folder named "${servicePath}" already exists.`;
throw new ServerlessError(errorMessage);
}
copyDirContentsSync(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 = _.toString(this.options.path);
const serviceName = _.toString(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.join(process.cwd(), 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) {
this.serverless.config.update({ servicePath: process.cwd() });
}
// copy template files recursively to cwd
// while keeping template file tree
try {
this.serverless.utils.copyDirContentsSync(templateSrcDir, process.cwd());
// NPM renames .gitignore to .npmignore on publish so we have to rename it.
if (fse.existsSync(path.join(process.cwd(), 'gitignore'))) {
fse.renameSync(path.join(process.cwd(), 'gitignore'),
path.join(process.cwd(), '.gitignore'));
}
} catch (err) {
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);
}
// rename the service if the user has provided a path via options and is creating a service
if ((boilerplatePath || serviceName) && notPlugin) {
const newServiceName = serviceName || boilerplatePath.split(path.sep).pop();
const serverlessYmlFilePath = path
.join(this.serverless.config.servicePath, 'serverless.yml');
let serverlessYmlFileContent = fse
.readFileSync(serverlessYmlFilePath).toString();
serverlessYmlFileContent = serverlessYmlFileContent
.replace(/service: .+/, `service: ${newServiceName}`);
fse.writeFileSync(serverlessYmlFilePath, serverlessYmlFileContent);
}
userStats.track('service_created', {
template: this.options.template,
serviceName,
});
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');
}
}
}
module.exports = Create;