├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── bin
├── createServerlessApi.js
└── index.js
├── package-lock.json
├── package.json
└── templates
└── create-serverless-api
├── .babelrc
├── .eslintrc.json
├── .gitignore
├── LICENSE
├── cdk.json
├── infra
├── StackConstruct.ts
├── entry.ts
├── modules
│ └── convertSwaggerToRestApi.ts
├── stacks
│ ├── CommonStack.ts
│ └── UploadS3.ts
└── utils
│ └── utils.ts
├── package-lock.json
├── package.json
├── scripts
├── afterDestroy.js
├── bundle.js
├── cleanUp.js
├── defineBucketName.js
├── executeS3Stack.js
├── install.js
├── modules
│ ├── map.ts
│ └── swaggerParser.ts
├── offline.ts
├── template.html
├── uploadLayer.js
├── uploadSwagger.js
└── utils.js
├── src
├── api
│ ├── getBasket.ts
│ ├── getBasketFruit.ts
│ ├── healthCheck.ts
│ └── postBasketFruit.ts
├── authorizers
│ └── sample.ts
└── modules
│ ├── basket.json
│ └── utils.ts
├── swagger.yaml
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | cdk.out
2 | dist
3 | infra-dist
4 | node_modules
5 | projectData.json
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | projectData.json
3 | .serverless
4 | .git
5 | .vscode
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2021 LEE JEONG-WOO
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Create Serverless API
2 |
3 | Create Serverless APIs with no infra configuration.
4 |
5 | It is an aws serverless-based backend api template created for beginners.
6 |
7 | You can service api without knowing the cloud infrastructure.
8 |
9 | And Create Serverless API works on macOS, Windows, and Linux.
10 |
11 | Create Serverless API was inspired by the [create-react-app](https://github.com/facebook/create-react-app).
12 |
13 | ## 🚀Quick Overview
14 |
15 | 📌 Before we proceed with the work below, you must set [aws credential](#detail-usage)
16 |
17 | ```
18 | npx create-serverless-api my-api
19 | cd my-api
20 | npm bootstrap
21 | npm deploy
22 | ```
23 |
24 | And check the API document URL and API endpoint at the terminal.
25 |
26 | You will have a fully operational api service.
27 |
28 | For detail, please refer to the explanation below.
29 |
30 | ## ✨Feature
31 |
32 | - Automatic aws infrastructure completion through documents in the OpenAPI specification.
33 | - Install only essential packages for each API.
34 |
35 | ### Detail Feature
36 |
37 | 👍 The detail feature roughly say that this package is very cool.
38 |
39 | - Infrastructure distribution through IaC makes it easy to modify.
40 | - Using FaaS, monitoring is easy and stability can be obtained by horizontal expansion.
41 | - Supports monitoring and logging with aws X-ray and aws CloudWatch.
42 | - Babel settings are set for using typescript.
43 | - Can execute local test similar to the aws lambda.
44 | - Swagger api documents are automatically serviced on the web.
45 |
46 | ## Environment in Lambda Container
47 |
48 | ✅ You can customize this.
49 |
50 | - Node 14.x
51 |
52 | ## Detail Usage
53 |
54 | - ⚠ Caution
55 |
56 | Following this usage, your api will be serviced as a cloud service.
57 | You spend money according to the amount of api calls.
58 | **so make sure to check out [AWS Pricing Policy](https://aws.amazon.com/pricing)**.
59 |
60 | 1. Login to [aws console](https://signin.aws.amazon.com/console)
61 |
62 | 2. Make iam user through [aws IAM users](https://console.aws.amazon.com/iamv2/home#/users).
63 |
64 | - Need **Access key** credential type.
65 | - Recommend **AdministratorAccess** policy through the attachment policies directy button for easy work.
66 | - Save the access key ID and secret access key that came out like that.
67 |
68 | 3. Configurate your aws credentials file
69 |
70 | ✅ **Windows** default path: C/Users/{userName}/.aws/credentials
71 |
72 | ✅ **Linux** or **macOS** default path: ~/.aws/credentials
73 |
74 | ```text
75 | [development]
76 | aws_access_key_id={your_aws_access_key_id}
77 | aws_secret_access_key={your_aws_secret_access_key}
78 |
79 | [production]
80 | aws_access_key_id={your_aws_access_key_id}
81 | aws_secret_access_key={your_aws_secret_access_key}
82 | ```
83 |
84 | 📌 If you want to change the name, please change it with the script of package.json
85 |
86 | 4. Creating an API
87 |
88 | ```
89 | npx create-serverless-api my-api
90 | ```
91 |
92 |
93 |
94 |
95 |
96 | 5. Go the project directory.
97 |
98 | ```
99 | cd my-api
100 | ```
101 |
102 |
103 |
104 |
105 |
106 | 6. Bootstrap for aws deployment.
107 |
108 | ```
109 | // Development
110 | npm run bootstrap
111 |
112 | // Production
113 | npm run bootstrap-prod
114 | ```
115 |
116 | 7. Deploy to aws
117 |
118 | ✅ The api is served on the address output to the terminal, **so make sure to save it**.
119 |
120 | ```
121 | // Development
122 | npm run deploy
123 |
124 | // Production
125 | npm run deploy-prod
126 | ```
127 |
128 | - Local execute
129 |
130 | ```
131 | npm run offline
132 | ```
133 |
134 | - Destroy the api
135 |
136 | ```
137 | // Development
138 | npm run destroy
139 |
140 | // Production
141 | npm run destroy-prod
142 | ```
143 |
144 | - src directory tree description
145 |
146 | ```
147 | /your-project
148 | └─src
149 | ├─api
150 | │ └─...
151 | └─modules
152 | ├─...
153 | ```
154 |
155 | - api
156 |
157 | Directory where the entry point of your API is saved.
158 |
159 | The position should be placed in the x-cdk-lambda-handler of swagger.yaml.
160 |
161 | - api
162 |
163 | A directory that puts the code that we use in common.
164 |
165 | ## 👏 Contributing
166 |
167 | Pull requests and 🌟 stars are always welcome.
168 |
169 | For major changes, please [open an issue](https://github.com/zao95/create-serverless-api/issues/new) first to discuss what you would like to change.
170 |
171 | ## ☕ Donate
172 |
173 | - Please buy me coffee so that I can continue to make convenient packages.
174 |
175 | https://www.buymeacoffee.com/awmaker
176 |
177 | ## 📩 Contact
178 |
179 | awmaker@kakao.com
180 |
181 | ## Others
182 |
183 | 👍 We recommend third-party services that suit the characteristics of serverless services.
184 |
185 | - We recommand [Dash Bird](https://app.dashbird.io/) service for watch lambda infomations.
186 | - We recommand [Planet Scale](https://planetscale.com/) service for database server.
187 |
--------------------------------------------------------------------------------
/bin/createServerlessApi.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const https = require('https')
4 | const chalk = require('chalk')
5 | const commander = require('commander')
6 | const dns = require('dns')
7 | const execSync = require('child_process').execSync
8 | const fs = require('fs-extra')
9 | const hyperquest = require('hyperquest')
10 | const os = require('os')
11 | const path = require('path')
12 | const semver = require('semver')
13 | const spawn = require('cross-spawn')
14 | const tmp = require('tmp')
15 | const unpack = require('tar-pack').unpack
16 | const url = require('url')
17 | const validateProjectName = require('validate-npm-package-name')
18 |
19 | const rootPackageJson = require('../package.json')
20 | const templatePackageJson = require('../templates/create-serverless-api/package.json')
21 |
22 | function isUsingYarn() {
23 | return (process.env.npm_config_user_agent || '').indexOf('yarn') === 0
24 | }
25 |
26 | let projectName
27 |
28 | function init() {
29 | const program = new commander.Command(rootPackageJson.name)
30 | .version(rootPackageJson.version)
31 | .arguments('')
32 | .usage(`${chalk.green('')} [options]`)
33 | .action((name) => {
34 | projectName = name
35 | })
36 | .allowUnknownOption()
37 | .on('--help', () => {
38 | console.log(
39 | ` Only ${chalk.green('')} is required.`
40 | )
41 | console.log()
42 | console.log(
43 | ` If you have any problems, please register the issue:`
44 | )
45 | console.log(
46 | ` ${chalk.cyan(
47 | 'https://github.com/zao95/create-serverless-api/issues/new'
48 | )}`
49 | )
50 | console.log()
51 | })
52 | .parse(process.argv)
53 |
54 | if (typeof projectName === 'undefined') {
55 | console.error('Please specify the project directory:')
56 | console.log(
57 | ` ${chalk.cyan(program.name())} ${chalk.green(
58 | ''
59 | )}`
60 | )
61 | console.log()
62 | console.log('For example:')
63 | console.log(
64 | ` ${chalk.cyan(program.name())} ${chalk.green(
65 | 'my-serverless-api'
66 | )}`
67 | )
68 | process.exit(1)
69 | }
70 |
71 | checkForLatestVersion()
72 | .catch(() => {
73 | try {
74 | return execSync('npm view create-serverless-api version')
75 | .toString()
76 | .trim()
77 | } catch (e) {
78 | return null
79 | }
80 | })
81 | .then((latest) => {
82 | if (latest && semver.lt(rootPackageJson.version, latest)) {
83 | console.log()
84 | console.error(
85 | chalk.yellow(
86 | `You are running \`create-serverless-api\` ${rootPackageJson.version}, which is behind the latest release (${latest}).\n\n` +
87 | 'We no longer support global installation of Create Serverless API.'
88 | )
89 | )
90 | console.log()
91 | console.log(
92 | 'Please remove any global installs with one of the following commands:\n' +
93 | '- npm uninstall -g create-serverless-api\n' +
94 | '- yarn global remove create-serverless-api'
95 | )
96 | console.log()
97 | process.exit(1)
98 | } else {
99 | const useYarn = isUsingYarn()
100 | createApp(projectName, useYarn)
101 | }
102 | })
103 | }
104 |
105 | function createApp(name, useYarn) {
106 | const unsupportedNodeVersion = !semver.satisfies(
107 | semver.coerce(process.version),
108 | '>=14'
109 | )
110 |
111 | if (unsupportedNodeVersion) {
112 | console.log(
113 | chalk.yellow(
114 | `You are using Node ${process.version}.\n\n` +
115 | `Please update to Node 14 or higher.\n`
116 | )
117 | )
118 | process.exit(1)
119 | }
120 |
121 | const root = path.resolve(name)
122 | const apiName = path.basename(root)
123 |
124 | checkApiName(apiName)
125 | fs.ensureDirSync(name)
126 | if (!isSafeToCreateProjectIn(root, name)) {
127 | process.exit(1)
128 | }
129 | console.log()
130 |
131 | console.log(`Creating a new Serverless API in ${chalk.green(root)}.`)
132 | console.log()
133 |
134 | const packageJson = { name: apiName }
135 | fs.writeFileSync(
136 | path.join(root, 'package.json'),
137 | JSON.stringify({ ...packageJson, ...templatePackageJson }, null, 2) +
138 | os.EOL
139 | )
140 |
141 | process.chdir(root)
142 | if (!useYarn && !checkThatNpmCanReadCwd()) {
143 | process.exit(1)
144 | }
145 |
146 | if (!useYarn) {
147 | const npmInfo = checkNpmVersion()
148 | if (!npmInfo.hasMinNpm) {
149 | if (npmInfo.npmVersion) {
150 | console.log(
151 | chalk.yellow(
152 | `You are using npm ${process.version}.\n\n` +
153 | `Please update to npm 6 or higher.\n`
154 | )
155 | )
156 | process.exit(1)
157 | }
158 | }
159 | }
160 |
161 | run(root, apiName, useYarn)
162 | }
163 |
164 | function install(root, useYarn, dependencies, isOnline) {
165 | return new Promise((resolve, reject) => {
166 | let command
167 | let args
168 | if (useYarn) {
169 | command = 'yarnpkg'
170 | args = ['add', '--exact']
171 | if (!isOnline) {
172 | args.push('--offline')
173 | }
174 | ;[].push.apply(args, dependencies)
175 |
176 | args.push('--cwd')
177 | args.push(root)
178 |
179 | if (!isOnline) {
180 | console.log(chalk.yellow('You appear to be offline.'))
181 | console.log(
182 | chalk.yellow('Falling back to the local Yarn cache.')
183 | )
184 | console.log()
185 | }
186 | } else {
187 | command = 'npm'
188 | args = [
189 | 'install',
190 | '--no-audit',
191 | '--save',
192 | '--save-exact',
193 | '--loglevel',
194 | 'error',
195 | ].concat(dependencies)
196 | }
197 |
198 | const child = spawn(command, args, { stdio: 'inherit' })
199 | child.on('close', (code) => {
200 | if (code !== 0) {
201 | reject({
202 | command: checkNodeVersion`${command} ${args.join(' ')}`,
203 | })
204 | return
205 | }
206 | resolve()
207 | })
208 | })
209 | }
210 |
211 | function run(root, apiName, useYarn) {
212 | const templateToInstall = 'create-serverless-api'
213 | const allDependencies = Object.keys(templatePackageJson.devDependencies)
214 |
215 | console.log('Installing packages. This might take a couple of minutes.')
216 |
217 | Promise.resolve(getPackageInfo(templateToInstall))
218 | .then((templateInfo) => {
219 | return checkIfOnline(useYarn).then((isOnline) => ({
220 | isOnline,
221 | templateInfo,
222 | }))
223 | })
224 | .then(({ isOnline, templateInfo }) => {
225 | console.log()
226 | console.log(
227 | `Installing\n${chalk.cyan(
228 | Object.keys(templatePackageJson.devDependencies).join(', ')
229 | )}...`
230 | )
231 | console.log()
232 |
233 | return install(root, useYarn, allDependencies, isOnline).then(
234 | () => ({
235 | templateInfo,
236 | })
237 | )
238 | })
239 | .then(async ({ templateInfo }) => {
240 | const templateName = templateInfo.name
241 |
242 | const currentDirectory = path.dirname(fs.realpathSync(__filename))
243 | const rootDirectory = currentDirectory.replace(/\\bin$|\/bin$/, '')
244 |
245 | const srcDir = path.resolve(
246 | `${rootDirectory}/templates/${templateName}`
247 | )
248 | const destDir = root
249 |
250 | fs.copy(srcDir, destDir, {
251 | overwrite: false,
252 | })
253 | .then(() => {
254 | console.log()
255 | console.log(`Success! Created ${apiName} at ${destDir}`)
256 | console.log(
257 | 'Inside that directory, you can run several commands:'
258 | )
259 | console.log()
260 | console.log(` ${chalk.cyan('npm run bootstrap')}`)
261 | console.log(
262 | ` Before deployment, deploy a stack for cdk toolkit in an aws environment.`
263 | )
264 | console.log(` You just have to run it once.`)
265 | console.log()
266 | console.log(` ${chalk.cyan('npm run deploy')}`)
267 | console.log(` Distribute api to aws.`)
268 | console.log()
269 | console.log(` ${chalk.cyan('npm run destroy')}`)
270 | console.log(` Remove the api distributed to aws.`)
271 | console.log()
272 | console.log(` ${chalk.cyan('npm run diff')}`)
273 | console.log(
274 | ` Compare the infrastructure posted on aws with the infrastructure changed on the code.`
275 | )
276 | console.log()
277 | console.log(` ${chalk.cyan('npm run offline')}`)
278 | console.log(
279 | ` You can test api in an offline environment.`
280 | )
281 | console.log()
282 | console.log(`We suggest that you begin by typing:`)
283 | console.log()
284 | console.log(` ${chalk.cyan('cd')} ${apiName}`)
285 | console.log(` ${chalk.cyan('npm run bootstrap')}`)
286 | console.log(` ${chalk.cyan('npm run deploy')}`)
287 | console.log()
288 | console.log('Happy hacking!')
289 | })
290 | .catch((err) => console.error(err))
291 | })
292 | .catch((reason) => {
293 | console.log()
294 | console.log('Aborting installation.')
295 | if (reason.command) {
296 | console.log(` ${chalk.cyan(reason.command)} has failed.`)
297 | } else {
298 | console.log(
299 | chalk.red('Unexpected error. Please report it as a bug:')
300 | )
301 | console.log(reason)
302 | }
303 | console.log()
304 |
305 | const knownGeneratedFiles = ['package.json', 'node_modules']
306 | const currentFiles = fs.readdirSync(path.join(root))
307 | currentFiles.forEach((file) => {
308 | knownGeneratedFiles.forEach((fileToMatch) => {
309 | if (file === fileToMatch) {
310 | console.log(
311 | `Deleting generated file... ${chalk.cyan(file)}`
312 | )
313 | fs.removeSync(path.join(root, file))
314 | }
315 | })
316 | })
317 | const remainingFiles = fs.readdirSync(path.join(root))
318 | if (!remainingFiles.length) {
319 | console.log(
320 | `Deleting ${chalk.cyan(`${apiName}/`)} from ${chalk.cyan(
321 | path.resolve(root, '..')
322 | )}`
323 | )
324 | process.chdir(path.resolve(root, '..'))
325 | fs.removeSync(path.join(root))
326 | }
327 | console.log('Done.')
328 | process.exit(1)
329 | })
330 | }
331 |
332 | function getTemporaryDirectory() {
333 | return new Promise((resolve, reject) => {
334 | tmp.dir({ unsafeCleanup: true }, (err, tmpdir, callback) => {
335 | if (err) {
336 | reject(err)
337 | } else {
338 | resolve({
339 | tmpdir: tmpdir,
340 | cleanup: () => {
341 | try {
342 | callback()
343 | } catch (ignored) {}
344 | },
345 | })
346 | }
347 | })
348 | })
349 | }
350 |
351 | function extractStream(stream, dest) {
352 | return new Promise((resolve, reject) => {
353 | stream.pipe(
354 | unpack(dest, (err) => {
355 | if (err) {
356 | reject(err)
357 | } else {
358 | resolve(dest)
359 | }
360 | })
361 | )
362 | })
363 | }
364 |
365 | function getPackageInfo(installPackage) {
366 | if (installPackage.match(/^.+\.(tgz|tar\.gz)$/)) {
367 | return getTemporaryDirectory()
368 | .then((obj) => {
369 | let stream
370 | if (/^http/.test(installPackage)) {
371 | stream = hyperquest(installPackage)
372 | } else {
373 | stream = fs.createReadStream(installPackage)
374 | }
375 | return extractStream(stream, obj.tmpdir).then(() => obj)
376 | })
377 | .then((obj) => {
378 | const { name, version } = require(path.join(
379 | obj.tmpdir,
380 | 'package.json'
381 | ))
382 | obj.cleanup()
383 | return { name, version }
384 | })
385 | .catch((err) => {
386 | console.log(
387 | `Could not extract the package name from the archive: ${err.message}`
388 | )
389 | const assumedProjectName = installPackage.match(
390 | /^.+\/(.+?)(?:-\d+.+)?\.(tgz|tar\.gz)$/
391 | )[1]
392 | console.log(
393 | `Based on the filename, assuming it is "${chalk.cyan(
394 | assumedProjectName
395 | )}"`
396 | )
397 | return Promise.resolve({ name: assumedProjectName })
398 | })
399 | } else if (installPackage.startsWith('git+')) {
400 | return Promise.resolve({
401 | name: installPackage.match(/([^/]+)\.git(#.*)?$/)[1],
402 | })
403 | } else if (installPackage.match(/.+@/)) {
404 | return Promise.resolve({
405 | name:
406 | installPackage.charAt(0) +
407 | installPackage.substr(1).split('@')[0],
408 | version: installPackage.split('@')[1],
409 | })
410 | } else if (installPackage.match(/^file:/)) {
411 | const installPackagePath = installPackage.match(/^file:(.*)?$/)[1]
412 | const { name, version } = require(path.join(
413 | installPackagePath,
414 | 'package.json'
415 | ))
416 | return Promise.resolve({ name, version })
417 | }
418 | return Promise.resolve({ name: installPackage })
419 | }
420 |
421 | function checkNpmVersion() {
422 | let hasMinNpm = false
423 | let npmVersion = null
424 | try {
425 | npmVersion = execSync('npm --version').toString().trim()
426 | hasMinNpm = semver.gte(npmVersion, '6.0.0')
427 | } catch (err) {}
428 | return {
429 | hasMinNpm: hasMinNpm,
430 | npmVersion: npmVersion,
431 | }
432 | }
433 |
434 | function checkYarnVersion() {
435 | const minYarnPnp = '1.12.0'
436 | const maxYarnPnp = '2.0.0'
437 | let hasMinYarnPnp = false
438 | let hasMaxYarnPnp = false
439 | let yarnVersion = null
440 | try {
441 | yarnVersion = execSync('yarnpkg --version').toString().trim()
442 | if (semver.valid(yarnVersion)) {
443 | hasMinYarnPnp = semver.gte(yarnVersion, minYarnPnp)
444 | hasMaxYarnPnp = semver.lt(yarnVersion, maxYarnPnp)
445 | } else {
446 | const trimmedYarnVersionMatch = /^(.+?)[-+].+$/.exec(yarnVersion)
447 | if (trimmedYarnVersionMatch) {
448 | const trimmedYarnVersion = trimmedYarnVersionMatch.pop()
449 | hasMinYarnPnp = semver.gte(trimmedYarnVersion, minYarnPnp)
450 | hasMaxYarnPnp = semver.lt(trimmedYarnVersion, maxYarnPnp)
451 | }
452 | }
453 | } catch (err) {
454 | // ignore
455 | }
456 | return {
457 | hasMinYarnPnp: hasMinYarnPnp,
458 | hasMaxYarnPnp: hasMaxYarnPnp,
459 | yarnVersion: yarnVersion,
460 | }
461 | }
462 |
463 | function checkNodeVersion(packageName) {
464 | const packageJsonPath = path.resolve(
465 | process.cwd(),
466 | 'node_modules',
467 | packageName,
468 | 'package.json'
469 | )
470 |
471 | if (!fs.existsSync(packageJsonPath)) {
472 | return
473 | }
474 |
475 | const packageJson = require(packageJsonPath)
476 | if (!packageJson.engines || !packageJson.engines.node) {
477 | return
478 | }
479 |
480 | if (!semver.satisfies(process.version, packageJson.engines.node)) {
481 | console.error(
482 | chalk.red(
483 | 'You are running Node %s.\n' +
484 | 'Create Serverless API requires Node %s or higher. \n' +
485 | 'Please update your version of Node.'
486 | ),
487 | process.version,
488 | packageJson.engines.node
489 | )
490 | process.exit(1)
491 | }
492 | }
493 |
494 | function checkApiName(apiName) {
495 | const validationResult = validateProjectName(apiName)
496 | if (!validationResult.validForNewPackages) {
497 | console.error(
498 | chalk.red(
499 | `Cannot create a project named ${chalk.green(
500 | `"${apiName}"`
501 | )} because of npm naming restrictions:\n`
502 | )
503 | )
504 | ;[
505 | ...(validationResult.errors || []),
506 | ...(validationResult.warnings || []),
507 | ].forEach((error) => {
508 | console.error(chalk.red(` * ${error}`))
509 | })
510 | console.error(chalk.red('\nPlease choose a different project name.'))
511 | process.exit(1)
512 | }
513 |
514 | const dependencies = ['react', 'react-dom', 'react-scripts'].sort()
515 | if (dependencies.includes(apiName)) {
516 | console.error(
517 | chalk.red(
518 | `Cannot create a project named ${chalk.green(
519 | `"${apiName}"`
520 | )} because a dependency with the same name exists.\n` +
521 | `Due to the way npm works, the following names are not allowed:\n\n`
522 | ) +
523 | chalk.cyan(
524 | dependencies.map((depName) => ` ${depName}`).join('\n')
525 | ) +
526 | chalk.red('\n\nPlease choose a different project name.')
527 | )
528 | process.exit(1)
529 | }
530 | }
531 |
532 | function makeCaretRange(dependencies, name) {
533 | const version = dependencies[name]
534 |
535 | if (typeof version === 'undefined') {
536 | console.error(chalk.red(`Missing ${name} dependency in package.json`))
537 | process.exit(1)
538 | }
539 |
540 | let patchedVersion = `^${version}`
541 |
542 | if (!semver.validRange(patchedVersion)) {
543 | console.error(
544 | `Unable to patch ${name} dependency version because version ${chalk.red(
545 | version
546 | )} will become invalid ${chalk.red(patchedVersion)}`
547 | )
548 | patchedVersion = version
549 | }
550 |
551 | dependencies[name] = patchedVersion
552 | }
553 |
554 | function setCaretRangeForRuntimeDeps(packageName) {
555 | const packagePath = path.join(process.cwd(), 'package.json')
556 | const packageJson = require(packagePath)
557 |
558 | if (typeof packageJson.dependencies === 'undefined') {
559 | console.error(chalk.red('Missing dependencies in package.json'))
560 | process.exit(1)
561 | }
562 |
563 | const packageVersion = packageJson.dependencies[packageName]
564 | if (typeof packageVersion === 'undefined') {
565 | console.error(
566 | chalk.red(`Unable to find ${packageName} in package.json`)
567 | )
568 | process.exit(1)
569 | }
570 |
571 | makeCaretRange(packageJson.dependencies, 'react')
572 | makeCaretRange(packageJson.dependencies, 'react-dom')
573 |
574 | fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2) + os.EOL)
575 | }
576 |
577 | function isSafeToCreateProjectIn(root, name) {
578 | const validFiles = [
579 | '.babelrc',
580 | '.eslintrc.json',
581 | '.gitignore',
582 | 'cdk.json',
583 | 'install.js',
584 | 'LICENSE',
585 | 'package.json',
586 | 'swagger.yaml',
587 | 'tsconfig.json',
588 | ]
589 | const errorLogFilePatterns = [
590 | 'npm-debug.log',
591 | 'yarn-error.log',
592 | 'yarn-debug.log',
593 | ]
594 | const isErrorLog = (file) => {
595 | return errorLogFilePatterns.some((pattern) => file.startsWith(pattern))
596 | }
597 |
598 | const conflicts = fs
599 | .readdirSync(root)
600 | .filter((file) => !validFiles.includes(file))
601 | .filter((file) => !/\.iml$/.test(file))
602 | .filter((file) => !isErrorLog(file))
603 |
604 | if (conflicts.length > 0) {
605 | console.log(
606 | `The directory ${chalk.green(
607 | name
608 | )} contains files that could conflict:`
609 | )
610 | console.log()
611 | for (const file of conflicts) {
612 | try {
613 | const stats = fs.lstatSync(path.join(root, file))
614 | if (stats.isDirectory()) {
615 | console.log(` ${chalk.blue(`${file}/`)}`)
616 | } else {
617 | console.log(` ${file}`)
618 | }
619 | } catch (e) {
620 | console.log(` ${file}`)
621 | }
622 | }
623 | console.log()
624 | console.log(
625 | 'Either try using a new directory name, or remove the files listed above.'
626 | )
627 |
628 | return false
629 | }
630 |
631 | fs.readdirSync(root).forEach((file) => {
632 | if (isErrorLog(file)) {
633 | fs.removeSync(path.join(root, file))
634 | }
635 | })
636 | return true
637 | }
638 |
639 | function getProxy() {
640 | if (process.env.https_proxy) {
641 | return process.env.https_proxy
642 | } else {
643 | try {
644 | let httpsProxy = execSync('npm config get https-proxy')
645 | .toString()
646 | .trim()
647 | return httpsProxy !== 'null' ? httpsProxy : undefined
648 | } catch (e) {
649 | return
650 | }
651 | }
652 | }
653 |
654 | function checkThatNpmCanReadCwd() {
655 | const cwd = process.cwd()
656 | let childOutput = null
657 | try {
658 | childOutput = spawn.sync('npm', ['config', 'list']).output.join('')
659 | } catch (err) {
660 | return true
661 | }
662 | if (typeof childOutput !== 'string') {
663 | return true
664 | }
665 | const lines = childOutput.split('\n')
666 | const prefix = '; cwd = '
667 | const line = lines.find((line) => line.startsWith(prefix))
668 | if (typeof line !== 'string') {
669 | return true
670 | }
671 | const npmCWD = line.substring(prefix.length)
672 | if (npmCWD === cwd) {
673 | return true
674 | }
675 | console.error(
676 | chalk.red(
677 | `Could not start an npm process in the right directory.\n\n` +
678 | `The current directory is: ${chalk.bold(cwd)}\n` +
679 | `However, a newly started npm process runs in: ${chalk.bold(
680 | npmCWD
681 | )}\n\n` +
682 | `This is probably caused by a misconfigured system terminal shell.`
683 | )
684 | )
685 | if (process.platform === 'win32') {
686 | console.error(
687 | chalk.red(`On Windows, this can usually be fixed by running:\n\n`) +
688 | ` ${chalk.cyan(
689 | 'reg'
690 | )} delete "HKCU\\Software\\Microsoft\\Command Processor" /v AutoRun /f\n` +
691 | ` ${chalk.cyan(
692 | 'reg'
693 | )} delete "HKLM\\Software\\Microsoft\\Command Processor" /v AutoRun /f\n\n` +
694 | chalk.red(`Try to run the above two lines in the terminal.\n`) +
695 | chalk.red(
696 | `To learn more about this problem, read: https://blogs.msdn.microsoft.com/oldnewthing/20071121-00/?p=24433/`
697 | )
698 | )
699 | }
700 | return false
701 | }
702 |
703 | function checkIfOnline(useYarn) {
704 | if (!useYarn) {
705 | return Promise.resolve(true)
706 | }
707 |
708 | return new Promise((resolve) => {
709 | dns.lookup('registry.yarnpkg.com', (err) => {
710 | let proxy
711 | if (err != null && (proxy = getProxy())) {
712 | dns.lookup(url.parse(proxy).hostname, (proxyErr) => {
713 | resolve(proxyErr == null)
714 | })
715 | } else {
716 | resolve(err == null)
717 | }
718 | })
719 | })
720 | }
721 |
722 | function checkForLatestVersion() {
723 | return new Promise((resolve, reject) => {
724 | https
725 | .get(
726 | 'https://registry.npmjs.org/-/package/create-serverless-api/dist-tags',
727 | (res) => {
728 | if (res.statusCode === 200) {
729 | let body = ''
730 | res.on('data', (data) => (body += data))
731 | res.on('end', () => {
732 | resolve(JSON.parse(body).latest)
733 | })
734 | } else {
735 | reject()
736 | }
737 | }
738 | )
739 | .on('error', () => {
740 | reject()
741 | })
742 | })
743 | }
744 |
745 | module.exports = {
746 | init,
747 | }
748 |
--------------------------------------------------------------------------------
/bin/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | 'use strict'
4 |
5 | const currentNodeVersion = process.versions.node
6 | const semver = currentNodeVersion.split('.')
7 | const major = semver[0]
8 |
9 | if (major < 14) {
10 | console.error(
11 | 'You are running Node ' +
12 | currentNodeVersion +
13 | '.\n' +
14 | 'Create Serverless api requires Node 14 or higher. \n' +
15 | 'Please update your version of Node.'
16 | )
17 | process.exit(1)
18 | }
19 |
20 | const { init } = require('./createServerlessApi')
21 |
22 | init()
23 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "create-serverless-api",
3 | "version": "0.4.2",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "create-serverless-api",
9 | "version": "0.4.2",
10 | "license": "MIT License",
11 | "dependencies": {
12 | "chalk": "4.1.2",
13 | "commander": "8.3.0",
14 | "cross-spawn": "7.0.3",
15 | "fs-extra": "10.0.0",
16 | "hyperquest": "2.1.3",
17 | "prompts": "2.4.2",
18 | "semver": "7.3.5",
19 | "tar": "4.4.18",
20 | "tar-pack": "3.4.1",
21 | "tmp": "0.2.1",
22 | "validate-npm-package-name": "3.0.0"
23 | },
24 | "bin": {
25 | "create-serverless-api": "bin/index.js"
26 | }
27 | },
28 | "node_modules/ansi-styles": {
29 | "version": "4.3.0",
30 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
31 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
32 | "dependencies": {
33 | "color-convert": "^2.0.1"
34 | },
35 | "engines": {
36 | "node": ">=8"
37 | },
38 | "funding": {
39 | "url": "https://github.com/chalk/ansi-styles?sponsor=1"
40 | }
41 | },
42 | "node_modules/balanced-match": {
43 | "version": "1.0.2",
44 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
45 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
46 | },
47 | "node_modules/block-stream": {
48 | "version": "0.0.9",
49 | "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
50 | "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
51 | "dependencies": {
52 | "inherits": "~2.0.0"
53 | },
54 | "engines": {
55 | "node": "0.4 || >=0.5.8"
56 | }
57 | },
58 | "node_modules/brace-expansion": {
59 | "version": "1.1.11",
60 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
61 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
62 | "dependencies": {
63 | "balanced-match": "^1.0.0",
64 | "concat-map": "0.0.1"
65 | }
66 | },
67 | "node_modules/buffer-from": {
68 | "version": "0.1.2",
69 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-0.1.2.tgz",
70 | "integrity": "sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg=="
71 | },
72 | "node_modules/builtins": {
73 | "version": "1.0.3",
74 | "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz",
75 | "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og="
76 | },
77 | "node_modules/chalk": {
78 | "version": "4.1.2",
79 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
80 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
81 | "dependencies": {
82 | "ansi-styles": "^4.1.0",
83 | "supports-color": "^7.1.0"
84 | },
85 | "engines": {
86 | "node": ">=10"
87 | },
88 | "funding": {
89 | "url": "https://github.com/chalk/chalk?sponsor=1"
90 | }
91 | },
92 | "node_modules/chownr": {
93 | "version": "1.1.4",
94 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
95 | "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
96 | },
97 | "node_modules/color-convert": {
98 | "version": "2.0.1",
99 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
100 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
101 | "dependencies": {
102 | "color-name": "~1.1.4"
103 | },
104 | "engines": {
105 | "node": ">=7.0.0"
106 | }
107 | },
108 | "node_modules/color-name": {
109 | "version": "1.1.4",
110 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
111 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
112 | },
113 | "node_modules/commander": {
114 | "version": "8.3.0",
115 | "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
116 | "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
117 | "engines": {
118 | "node": ">= 12"
119 | }
120 | },
121 | "node_modules/concat-map": {
122 | "version": "0.0.1",
123 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
124 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
125 | },
126 | "node_modules/core-util-is": {
127 | "version": "1.0.3",
128 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
129 | "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
130 | },
131 | "node_modules/cross-spawn": {
132 | "version": "7.0.3",
133 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
134 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
135 | "dependencies": {
136 | "path-key": "^3.1.0",
137 | "shebang-command": "^2.0.0",
138 | "which": "^2.0.1"
139 | },
140 | "engines": {
141 | "node": ">= 8"
142 | }
143 | },
144 | "node_modules/debug": {
145 | "version": "2.6.9",
146 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
147 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
148 | "dependencies": {
149 | "ms": "2.0.0"
150 | }
151 | },
152 | "node_modules/duplexer2": {
153 | "version": "0.0.2",
154 | "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz",
155 | "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=",
156 | "dependencies": {
157 | "readable-stream": "~1.1.9"
158 | }
159 | },
160 | "node_modules/fs-extra": {
161 | "version": "10.0.0",
162 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz",
163 | "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==",
164 | "dependencies": {
165 | "graceful-fs": "^4.2.0",
166 | "jsonfile": "^6.0.1",
167 | "universalify": "^2.0.0"
168 | },
169 | "engines": {
170 | "node": ">=12"
171 | }
172 | },
173 | "node_modules/fs-minipass": {
174 | "version": "1.2.7",
175 | "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz",
176 | "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
177 | "dependencies": {
178 | "minipass": "^2.6.0"
179 | }
180 | },
181 | "node_modules/fs.realpath": {
182 | "version": "1.0.0",
183 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
184 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
185 | },
186 | "node_modules/fstream": {
187 | "version": "1.0.12",
188 | "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
189 | "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
190 | "dependencies": {
191 | "graceful-fs": "^4.1.2",
192 | "inherits": "~2.0.0",
193 | "mkdirp": ">=0.5 0",
194 | "rimraf": "2"
195 | },
196 | "engines": {
197 | "node": ">=0.6"
198 | }
199 | },
200 | "node_modules/fstream-ignore": {
201 | "version": "1.0.5",
202 | "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz",
203 | "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=",
204 | "dependencies": {
205 | "fstream": "^1.0.0",
206 | "inherits": "2",
207 | "minimatch": "^3.0.0"
208 | }
209 | },
210 | "node_modules/glob": {
211 | "version": "7.2.0",
212 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
213 | "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
214 | "dependencies": {
215 | "fs.realpath": "^1.0.0",
216 | "inflight": "^1.0.4",
217 | "inherits": "2",
218 | "minimatch": "^3.0.4",
219 | "once": "^1.3.0",
220 | "path-is-absolute": "^1.0.0"
221 | },
222 | "engines": {
223 | "node": "*"
224 | },
225 | "funding": {
226 | "url": "https://github.com/sponsors/isaacs"
227 | }
228 | },
229 | "node_modules/graceful-fs": {
230 | "version": "4.2.9",
231 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz",
232 | "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ=="
233 | },
234 | "node_modules/has-flag": {
235 | "version": "4.0.0",
236 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
237 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
238 | "engines": {
239 | "node": ">=8"
240 | }
241 | },
242 | "node_modules/hyperquest": {
243 | "version": "2.1.3",
244 | "resolved": "https://registry.npmjs.org/hyperquest/-/hyperquest-2.1.3.tgz",
245 | "integrity": "sha512-fUuDOrB47PqNK/BAMOS13v41UoaqIxqSLHX6CAbOD7OfT+/GCWO1/vPLfTNutOeXrv1ikuaZ3yux+33Z9vh+rw==",
246 | "dependencies": {
247 | "buffer-from": "^0.1.1",
248 | "duplexer2": "~0.0.2",
249 | "through2": "~0.6.3"
250 | }
251 | },
252 | "node_modules/inflight": {
253 | "version": "1.0.6",
254 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
255 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
256 | "dependencies": {
257 | "once": "^1.3.0",
258 | "wrappy": "1"
259 | }
260 | },
261 | "node_modules/inherits": {
262 | "version": "2.0.4",
263 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
264 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
265 | },
266 | "node_modules/isarray": {
267 | "version": "0.0.1",
268 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
269 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
270 | },
271 | "node_modules/isexe": {
272 | "version": "2.0.0",
273 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
274 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
275 | },
276 | "node_modules/jsonfile": {
277 | "version": "6.1.0",
278 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
279 | "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
280 | "dependencies": {
281 | "universalify": "^2.0.0"
282 | },
283 | "optionalDependencies": {
284 | "graceful-fs": "^4.1.6"
285 | }
286 | },
287 | "node_modules/kleur": {
288 | "version": "3.0.3",
289 | "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
290 | "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
291 | "engines": {
292 | "node": ">=6"
293 | }
294 | },
295 | "node_modules/lru-cache": {
296 | "version": "6.0.0",
297 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
298 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
299 | "dependencies": {
300 | "yallist": "^4.0.0"
301 | },
302 | "engines": {
303 | "node": ">=10"
304 | }
305 | },
306 | "node_modules/minimatch": {
307 | "version": "3.0.4",
308 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
309 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
310 | "dependencies": {
311 | "brace-expansion": "^1.1.7"
312 | },
313 | "engines": {
314 | "node": "*"
315 | }
316 | },
317 | "node_modules/minimist": {
318 | "version": "1.2.5",
319 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
320 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
321 | },
322 | "node_modules/minipass": {
323 | "version": "2.9.0",
324 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
325 | "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
326 | "dependencies": {
327 | "safe-buffer": "^5.1.2",
328 | "yallist": "^3.0.0"
329 | }
330 | },
331 | "node_modules/minipass/node_modules/yallist": {
332 | "version": "3.1.1",
333 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
334 | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
335 | },
336 | "node_modules/minizlib": {
337 | "version": "1.3.3",
338 | "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz",
339 | "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
340 | "dependencies": {
341 | "minipass": "^2.9.0"
342 | }
343 | },
344 | "node_modules/mkdirp": {
345 | "version": "0.5.5",
346 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
347 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
348 | "dependencies": {
349 | "minimist": "^1.2.5"
350 | },
351 | "bin": {
352 | "mkdirp": "bin/cmd.js"
353 | }
354 | },
355 | "node_modules/ms": {
356 | "version": "2.0.0",
357 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
358 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
359 | },
360 | "node_modules/once": {
361 | "version": "1.4.0",
362 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
363 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
364 | "dependencies": {
365 | "wrappy": "1"
366 | }
367 | },
368 | "node_modules/path-is-absolute": {
369 | "version": "1.0.1",
370 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
371 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
372 | "engines": {
373 | "node": ">=0.10.0"
374 | }
375 | },
376 | "node_modules/path-key": {
377 | "version": "3.1.1",
378 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
379 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
380 | "engines": {
381 | "node": ">=8"
382 | }
383 | },
384 | "node_modules/process-nextick-args": {
385 | "version": "2.0.1",
386 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
387 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
388 | },
389 | "node_modules/prompts": {
390 | "version": "2.4.2",
391 | "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
392 | "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
393 | "dependencies": {
394 | "kleur": "^3.0.3",
395 | "sisteransi": "^1.0.5"
396 | },
397 | "engines": {
398 | "node": ">= 6"
399 | }
400 | },
401 | "node_modules/readable-stream": {
402 | "version": "1.1.14",
403 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
404 | "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
405 | "dependencies": {
406 | "core-util-is": "~1.0.0",
407 | "inherits": "~2.0.1",
408 | "isarray": "0.0.1",
409 | "string_decoder": "~0.10.x"
410 | }
411 | },
412 | "node_modules/rimraf": {
413 | "version": "2.7.1",
414 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
415 | "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
416 | "dependencies": {
417 | "glob": "^7.1.3"
418 | },
419 | "bin": {
420 | "rimraf": "bin.js"
421 | }
422 | },
423 | "node_modules/safe-buffer": {
424 | "version": "5.2.1",
425 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
426 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
427 | "funding": [
428 | {
429 | "type": "github",
430 | "url": "https://github.com/sponsors/feross"
431 | },
432 | {
433 | "type": "patreon",
434 | "url": "https://www.patreon.com/feross"
435 | },
436 | {
437 | "type": "consulting",
438 | "url": "https://feross.org/support"
439 | }
440 | ]
441 | },
442 | "node_modules/semver": {
443 | "version": "7.3.5",
444 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
445 | "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
446 | "dependencies": {
447 | "lru-cache": "^6.0.0"
448 | },
449 | "bin": {
450 | "semver": "bin/semver.js"
451 | },
452 | "engines": {
453 | "node": ">=10"
454 | }
455 | },
456 | "node_modules/shebang-command": {
457 | "version": "2.0.0",
458 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
459 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
460 | "dependencies": {
461 | "shebang-regex": "^3.0.0"
462 | },
463 | "engines": {
464 | "node": ">=8"
465 | }
466 | },
467 | "node_modules/shebang-regex": {
468 | "version": "3.0.0",
469 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
470 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
471 | "engines": {
472 | "node": ">=8"
473 | }
474 | },
475 | "node_modules/sisteransi": {
476 | "version": "1.0.5",
477 | "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
478 | "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="
479 | },
480 | "node_modules/string_decoder": {
481 | "version": "0.10.31",
482 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
483 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
484 | },
485 | "node_modules/supports-color": {
486 | "version": "7.2.0",
487 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
488 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
489 | "dependencies": {
490 | "has-flag": "^4.0.0"
491 | },
492 | "engines": {
493 | "node": ">=8"
494 | }
495 | },
496 | "node_modules/tar": {
497 | "version": "4.4.18",
498 | "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.18.tgz",
499 | "integrity": "sha512-ZuOtqqmkV9RE1+4odd+MhBpibmCxNP6PJhH/h2OqNuotTX7/XHPZQJv2pKvWMplFH9SIZZhitehh6vBH6LO8Pg==",
500 | "dependencies": {
501 | "chownr": "^1.1.4",
502 | "fs-minipass": "^1.2.7",
503 | "minipass": "^2.9.0",
504 | "minizlib": "^1.3.3",
505 | "mkdirp": "^0.5.5",
506 | "safe-buffer": "^5.2.1",
507 | "yallist": "^3.1.1"
508 | },
509 | "engines": {
510 | "node": ">=4.5"
511 | }
512 | },
513 | "node_modules/tar-pack": {
514 | "version": "3.4.1",
515 | "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.1.tgz",
516 | "integrity": "sha512-PPRybI9+jM5tjtCbN2cxmmRU7YmqT3Zv/UDy48tAh2XRkLa9bAORtSWLkVc13+GJF+cdTh1yEnHEk3cpTaL5Kg==",
517 | "dependencies": {
518 | "debug": "^2.2.0",
519 | "fstream": "^1.0.10",
520 | "fstream-ignore": "^1.0.5",
521 | "once": "^1.3.3",
522 | "readable-stream": "^2.1.4",
523 | "rimraf": "^2.5.1",
524 | "tar": "^2.2.1",
525 | "uid-number": "^0.0.6"
526 | }
527 | },
528 | "node_modules/tar-pack/node_modules/isarray": {
529 | "version": "1.0.0",
530 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
531 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
532 | },
533 | "node_modules/tar-pack/node_modules/readable-stream": {
534 | "version": "2.3.7",
535 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
536 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
537 | "dependencies": {
538 | "core-util-is": "~1.0.0",
539 | "inherits": "~2.0.3",
540 | "isarray": "~1.0.0",
541 | "process-nextick-args": "~2.0.0",
542 | "safe-buffer": "~5.1.1",
543 | "string_decoder": "~1.1.1",
544 | "util-deprecate": "~1.0.1"
545 | }
546 | },
547 | "node_modules/tar-pack/node_modules/safe-buffer": {
548 | "version": "5.1.2",
549 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
550 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
551 | },
552 | "node_modules/tar-pack/node_modules/string_decoder": {
553 | "version": "1.1.1",
554 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
555 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
556 | "dependencies": {
557 | "safe-buffer": "~5.1.0"
558 | }
559 | },
560 | "node_modules/tar-pack/node_modules/tar": {
561 | "version": "2.2.2",
562 | "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz",
563 | "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==",
564 | "deprecated": "This version of tar is no longer supported, and will not receive security updates. Please upgrade asap.",
565 | "dependencies": {
566 | "block-stream": "*",
567 | "fstream": "^1.0.12",
568 | "inherits": "2"
569 | }
570 | },
571 | "node_modules/tar/node_modules/yallist": {
572 | "version": "3.1.1",
573 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
574 | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
575 | },
576 | "node_modules/through2": {
577 | "version": "0.6.5",
578 | "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
579 | "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=",
580 | "dependencies": {
581 | "readable-stream": ">=1.0.33-1 <1.1.0-0",
582 | "xtend": ">=4.0.0 <4.1.0-0"
583 | }
584 | },
585 | "node_modules/through2/node_modules/readable-stream": {
586 | "version": "1.0.34",
587 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
588 | "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
589 | "dependencies": {
590 | "core-util-is": "~1.0.0",
591 | "inherits": "~2.0.1",
592 | "isarray": "0.0.1",
593 | "string_decoder": "~0.10.x"
594 | }
595 | },
596 | "node_modules/tmp": {
597 | "version": "0.2.1",
598 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
599 | "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
600 | "dependencies": {
601 | "rimraf": "^3.0.0"
602 | },
603 | "engines": {
604 | "node": ">=8.17.0"
605 | }
606 | },
607 | "node_modules/tmp/node_modules/rimraf": {
608 | "version": "3.0.2",
609 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
610 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
611 | "dependencies": {
612 | "glob": "^7.1.3"
613 | },
614 | "bin": {
615 | "rimraf": "bin.js"
616 | },
617 | "funding": {
618 | "url": "https://github.com/sponsors/isaacs"
619 | }
620 | },
621 | "node_modules/uid-number": {
622 | "version": "0.0.6",
623 | "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz",
624 | "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=",
625 | "engines": {
626 | "node": "*"
627 | }
628 | },
629 | "node_modules/universalify": {
630 | "version": "2.0.0",
631 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
632 | "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
633 | "engines": {
634 | "node": ">= 10.0.0"
635 | }
636 | },
637 | "node_modules/util-deprecate": {
638 | "version": "1.0.2",
639 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
640 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
641 | },
642 | "node_modules/validate-npm-package-name": {
643 | "version": "3.0.0",
644 | "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz",
645 | "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=",
646 | "dependencies": {
647 | "builtins": "^1.0.3"
648 | }
649 | },
650 | "node_modules/which": {
651 | "version": "2.0.2",
652 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
653 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
654 | "dependencies": {
655 | "isexe": "^2.0.0"
656 | },
657 | "bin": {
658 | "node-which": "bin/node-which"
659 | },
660 | "engines": {
661 | "node": ">= 8"
662 | }
663 | },
664 | "node_modules/wrappy": {
665 | "version": "1.0.2",
666 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
667 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
668 | },
669 | "node_modules/xtend": {
670 | "version": "4.0.2",
671 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
672 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
673 | "engines": {
674 | "node": ">=0.4"
675 | }
676 | },
677 | "node_modules/yallist": {
678 | "version": "4.0.0",
679 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
680 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
681 | }
682 | },
683 | "dependencies": {
684 | "ansi-styles": {
685 | "version": "4.3.0",
686 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
687 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
688 | "requires": {
689 | "color-convert": "^2.0.1"
690 | }
691 | },
692 | "balanced-match": {
693 | "version": "1.0.2",
694 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
695 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
696 | },
697 | "block-stream": {
698 | "version": "0.0.9",
699 | "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
700 | "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
701 | "requires": {
702 | "inherits": "~2.0.0"
703 | }
704 | },
705 | "brace-expansion": {
706 | "version": "1.1.11",
707 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
708 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
709 | "requires": {
710 | "balanced-match": "^1.0.0",
711 | "concat-map": "0.0.1"
712 | }
713 | },
714 | "buffer-from": {
715 | "version": "0.1.2",
716 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-0.1.2.tgz",
717 | "integrity": "sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg=="
718 | },
719 | "builtins": {
720 | "version": "1.0.3",
721 | "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz",
722 | "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og="
723 | },
724 | "chalk": {
725 | "version": "4.1.2",
726 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
727 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
728 | "requires": {
729 | "ansi-styles": "^4.1.0",
730 | "supports-color": "^7.1.0"
731 | }
732 | },
733 | "chownr": {
734 | "version": "1.1.4",
735 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
736 | "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
737 | },
738 | "color-convert": {
739 | "version": "2.0.1",
740 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
741 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
742 | "requires": {
743 | "color-name": "~1.1.4"
744 | }
745 | },
746 | "color-name": {
747 | "version": "1.1.4",
748 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
749 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
750 | },
751 | "commander": {
752 | "version": "8.3.0",
753 | "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
754 | "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="
755 | },
756 | "concat-map": {
757 | "version": "0.0.1",
758 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
759 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
760 | },
761 | "core-util-is": {
762 | "version": "1.0.3",
763 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
764 | "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
765 | },
766 | "cross-spawn": {
767 | "version": "7.0.3",
768 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
769 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
770 | "requires": {
771 | "path-key": "^3.1.0",
772 | "shebang-command": "^2.0.0",
773 | "which": "^2.0.1"
774 | }
775 | },
776 | "debug": {
777 | "version": "2.6.9",
778 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
779 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
780 | "requires": {
781 | "ms": "2.0.0"
782 | }
783 | },
784 | "duplexer2": {
785 | "version": "0.0.2",
786 | "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz",
787 | "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=",
788 | "requires": {
789 | "readable-stream": "~1.1.9"
790 | }
791 | },
792 | "fs-extra": {
793 | "version": "10.0.0",
794 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz",
795 | "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==",
796 | "requires": {
797 | "graceful-fs": "^4.2.0",
798 | "jsonfile": "^6.0.1",
799 | "universalify": "^2.0.0"
800 | }
801 | },
802 | "fs-minipass": {
803 | "version": "1.2.7",
804 | "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz",
805 | "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
806 | "requires": {
807 | "minipass": "^2.6.0"
808 | }
809 | },
810 | "fs.realpath": {
811 | "version": "1.0.0",
812 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
813 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
814 | },
815 | "fstream": {
816 | "version": "1.0.12",
817 | "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
818 | "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
819 | "requires": {
820 | "graceful-fs": "^4.1.2",
821 | "inherits": "~2.0.0",
822 | "mkdirp": ">=0.5 0",
823 | "rimraf": "2"
824 | }
825 | },
826 | "fstream-ignore": {
827 | "version": "1.0.5",
828 | "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz",
829 | "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=",
830 | "requires": {
831 | "fstream": "^1.0.0",
832 | "inherits": "2",
833 | "minimatch": "^3.0.0"
834 | }
835 | },
836 | "glob": {
837 | "version": "7.2.0",
838 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
839 | "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
840 | "requires": {
841 | "fs.realpath": "^1.0.0",
842 | "inflight": "^1.0.4",
843 | "inherits": "2",
844 | "minimatch": "^3.0.4",
845 | "once": "^1.3.0",
846 | "path-is-absolute": "^1.0.0"
847 | }
848 | },
849 | "graceful-fs": {
850 | "version": "4.2.9",
851 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz",
852 | "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ=="
853 | },
854 | "has-flag": {
855 | "version": "4.0.0",
856 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
857 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
858 | },
859 | "hyperquest": {
860 | "version": "2.1.3",
861 | "resolved": "https://registry.npmjs.org/hyperquest/-/hyperquest-2.1.3.tgz",
862 | "integrity": "sha512-fUuDOrB47PqNK/BAMOS13v41UoaqIxqSLHX6CAbOD7OfT+/GCWO1/vPLfTNutOeXrv1ikuaZ3yux+33Z9vh+rw==",
863 | "requires": {
864 | "buffer-from": "^0.1.1",
865 | "duplexer2": "~0.0.2",
866 | "through2": "~0.6.3"
867 | }
868 | },
869 | "inflight": {
870 | "version": "1.0.6",
871 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
872 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
873 | "requires": {
874 | "once": "^1.3.0",
875 | "wrappy": "1"
876 | }
877 | },
878 | "inherits": {
879 | "version": "2.0.4",
880 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
881 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
882 | },
883 | "isarray": {
884 | "version": "0.0.1",
885 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
886 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
887 | },
888 | "isexe": {
889 | "version": "2.0.0",
890 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
891 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
892 | },
893 | "jsonfile": {
894 | "version": "6.1.0",
895 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
896 | "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
897 | "requires": {
898 | "graceful-fs": "^4.1.6",
899 | "universalify": "^2.0.0"
900 | }
901 | },
902 | "kleur": {
903 | "version": "3.0.3",
904 | "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
905 | "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="
906 | },
907 | "lru-cache": {
908 | "version": "6.0.0",
909 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
910 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
911 | "requires": {
912 | "yallist": "^4.0.0"
913 | }
914 | },
915 | "minimatch": {
916 | "version": "3.0.4",
917 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
918 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
919 | "requires": {
920 | "brace-expansion": "^1.1.7"
921 | }
922 | },
923 | "minimist": {
924 | "version": "1.2.5",
925 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
926 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
927 | },
928 | "minipass": {
929 | "version": "2.9.0",
930 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
931 | "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
932 | "requires": {
933 | "safe-buffer": "^5.1.2",
934 | "yallist": "^3.0.0"
935 | },
936 | "dependencies": {
937 | "yallist": {
938 | "version": "3.1.1",
939 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
940 | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
941 | }
942 | }
943 | },
944 | "minizlib": {
945 | "version": "1.3.3",
946 | "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz",
947 | "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
948 | "requires": {
949 | "minipass": "^2.9.0"
950 | }
951 | },
952 | "mkdirp": {
953 | "version": "0.5.5",
954 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
955 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
956 | "requires": {
957 | "minimist": "^1.2.5"
958 | }
959 | },
960 | "ms": {
961 | "version": "2.0.0",
962 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
963 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
964 | },
965 | "once": {
966 | "version": "1.4.0",
967 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
968 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
969 | "requires": {
970 | "wrappy": "1"
971 | }
972 | },
973 | "path-is-absolute": {
974 | "version": "1.0.1",
975 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
976 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
977 | },
978 | "path-key": {
979 | "version": "3.1.1",
980 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
981 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="
982 | },
983 | "process-nextick-args": {
984 | "version": "2.0.1",
985 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
986 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
987 | },
988 | "prompts": {
989 | "version": "2.4.2",
990 | "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
991 | "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
992 | "requires": {
993 | "kleur": "^3.0.3",
994 | "sisteransi": "^1.0.5"
995 | }
996 | },
997 | "readable-stream": {
998 | "version": "1.1.14",
999 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
1000 | "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
1001 | "requires": {
1002 | "core-util-is": "~1.0.0",
1003 | "inherits": "~2.0.1",
1004 | "isarray": "0.0.1",
1005 | "string_decoder": "~0.10.x"
1006 | }
1007 | },
1008 | "rimraf": {
1009 | "version": "2.7.1",
1010 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
1011 | "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
1012 | "requires": {
1013 | "glob": "^7.1.3"
1014 | }
1015 | },
1016 | "safe-buffer": {
1017 | "version": "5.2.1",
1018 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
1019 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
1020 | },
1021 | "semver": {
1022 | "version": "7.3.5",
1023 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
1024 | "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
1025 | "requires": {
1026 | "lru-cache": "^6.0.0"
1027 | }
1028 | },
1029 | "shebang-command": {
1030 | "version": "2.0.0",
1031 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
1032 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
1033 | "requires": {
1034 | "shebang-regex": "^3.0.0"
1035 | }
1036 | },
1037 | "shebang-regex": {
1038 | "version": "3.0.0",
1039 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
1040 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
1041 | },
1042 | "sisteransi": {
1043 | "version": "1.0.5",
1044 | "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
1045 | "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="
1046 | },
1047 | "string_decoder": {
1048 | "version": "0.10.31",
1049 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
1050 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
1051 | },
1052 | "supports-color": {
1053 | "version": "7.2.0",
1054 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
1055 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
1056 | "requires": {
1057 | "has-flag": "^4.0.0"
1058 | }
1059 | },
1060 | "tar": {
1061 | "version": "4.4.18",
1062 | "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.18.tgz",
1063 | "integrity": "sha512-ZuOtqqmkV9RE1+4odd+MhBpibmCxNP6PJhH/h2OqNuotTX7/XHPZQJv2pKvWMplFH9SIZZhitehh6vBH6LO8Pg==",
1064 | "requires": {
1065 | "chownr": "^1.1.4",
1066 | "fs-minipass": "^1.2.7",
1067 | "minipass": "^2.9.0",
1068 | "minizlib": "^1.3.3",
1069 | "mkdirp": "^0.5.5",
1070 | "safe-buffer": "^5.2.1",
1071 | "yallist": "^3.1.1"
1072 | },
1073 | "dependencies": {
1074 | "yallist": {
1075 | "version": "3.1.1",
1076 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
1077 | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
1078 | }
1079 | }
1080 | },
1081 | "tar-pack": {
1082 | "version": "3.4.1",
1083 | "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.1.tgz",
1084 | "integrity": "sha512-PPRybI9+jM5tjtCbN2cxmmRU7YmqT3Zv/UDy48tAh2XRkLa9bAORtSWLkVc13+GJF+cdTh1yEnHEk3cpTaL5Kg==",
1085 | "requires": {
1086 | "debug": "^2.2.0",
1087 | "fstream": "^1.0.10",
1088 | "fstream-ignore": "^1.0.5",
1089 | "once": "^1.3.3",
1090 | "readable-stream": "^2.1.4",
1091 | "rimraf": "^2.5.1",
1092 | "tar": "^2.2.1",
1093 | "uid-number": "^0.0.6"
1094 | },
1095 | "dependencies": {
1096 | "isarray": {
1097 | "version": "1.0.0",
1098 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
1099 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
1100 | },
1101 | "readable-stream": {
1102 | "version": "2.3.7",
1103 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
1104 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
1105 | "requires": {
1106 | "core-util-is": "~1.0.0",
1107 | "inherits": "~2.0.3",
1108 | "isarray": "~1.0.0",
1109 | "process-nextick-args": "~2.0.0",
1110 | "safe-buffer": "~5.1.1",
1111 | "string_decoder": "~1.1.1",
1112 | "util-deprecate": "~1.0.1"
1113 | }
1114 | },
1115 | "safe-buffer": {
1116 | "version": "5.1.2",
1117 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
1118 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
1119 | },
1120 | "string_decoder": {
1121 | "version": "1.1.1",
1122 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
1123 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
1124 | "requires": {
1125 | "safe-buffer": "~5.1.0"
1126 | }
1127 | },
1128 | "tar": {
1129 | "version": "2.2.2",
1130 | "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz",
1131 | "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==",
1132 | "requires": {
1133 | "block-stream": "*",
1134 | "fstream": "^1.0.12",
1135 | "inherits": "2"
1136 | }
1137 | }
1138 | }
1139 | },
1140 | "through2": {
1141 | "version": "0.6.5",
1142 | "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
1143 | "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=",
1144 | "requires": {
1145 | "readable-stream": ">=1.0.33-1 <1.1.0-0",
1146 | "xtend": ">=4.0.0 <4.1.0-0"
1147 | },
1148 | "dependencies": {
1149 | "readable-stream": {
1150 | "version": "1.0.34",
1151 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
1152 | "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
1153 | "requires": {
1154 | "core-util-is": "~1.0.0",
1155 | "inherits": "~2.0.1",
1156 | "isarray": "0.0.1",
1157 | "string_decoder": "~0.10.x"
1158 | }
1159 | }
1160 | }
1161 | },
1162 | "tmp": {
1163 | "version": "0.2.1",
1164 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
1165 | "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
1166 | "requires": {
1167 | "rimraf": "^3.0.0"
1168 | },
1169 | "dependencies": {
1170 | "rimraf": {
1171 | "version": "3.0.2",
1172 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
1173 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
1174 | "requires": {
1175 | "glob": "^7.1.3"
1176 | }
1177 | }
1178 | }
1179 | },
1180 | "uid-number": {
1181 | "version": "0.0.6",
1182 | "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz",
1183 | "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE="
1184 | },
1185 | "universalify": {
1186 | "version": "2.0.0",
1187 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
1188 | "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ=="
1189 | },
1190 | "util-deprecate": {
1191 | "version": "1.0.2",
1192 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
1193 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
1194 | },
1195 | "validate-npm-package-name": {
1196 | "version": "3.0.0",
1197 | "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz",
1198 | "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=",
1199 | "requires": {
1200 | "builtins": "^1.0.3"
1201 | }
1202 | },
1203 | "which": {
1204 | "version": "2.0.2",
1205 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
1206 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
1207 | "requires": {
1208 | "isexe": "^2.0.0"
1209 | }
1210 | },
1211 | "wrappy": {
1212 | "version": "1.0.2",
1213 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
1214 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
1215 | },
1216 | "xtend": {
1217 | "version": "4.0.2",
1218 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
1219 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
1220 | },
1221 | "yallist": {
1222 | "version": "4.0.0",
1223 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
1224 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
1225 | }
1226 | }
1227 | }
1228 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "create-serverless-api",
3 | "version": "0.4.9",
4 | "description": "Create aws serverless api infra through OpenAPI Specification for beginners.",
5 | "keywords": [
6 | "aws",
7 | "serverless",
8 | "node",
9 | "javascript",
10 | "typescript",
11 | "swagger",
12 | "api",
13 | "documentation"
14 | ],
15 | "homepage": "https://github.com/zao95/create-serverless-api#readme",
16 | "repository": {
17 | "type": "git",
18 | "url": "https://github.com/zao95/create-serverless-api"
19 | },
20 | "license": "MIT License",
21 | "author": {
22 | "name": "Lee Jeongwoo",
23 | "email": "awmaker@kakao.com",
24 | "url": "https://portfolio.awmaker.com"
25 | },
26 | "contributors": [
27 | "Lim Jaeyoung"
28 | ],
29 | "main": "cdk.json",
30 | "bin": {
31 | "create-serverless-api": "bin/index.js"
32 | },
33 | "files": [
34 | "bin/*",
35 | "templates/*",
36 | "LICENSE",
37 | "package.json",
38 | "README.md"
39 | ],
40 | "dependencies": {
41 | "chalk": "4.1.2",
42 | "commander": "8.3.0",
43 | "cross-spawn": "7.0.3",
44 | "fs-extra": "10.0.0",
45 | "hyperquest": "2.1.3",
46 | "prompts": "2.4.2",
47 | "semver": "7.3.5",
48 | "tar": "4.4.18",
49 | "tar-pack": "3.4.1",
50 | "tmp": "0.2.1",
51 | "validate-npm-package-name": "3.0.0"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/templates/create-serverless-api/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "targets": "maintained node versions",
7 | "useBuiltIns": "usage",
8 | "corejs": { "version": "3.19", "proposals": true }
9 | }
10 | ],
11 | "@babel/typescript"
12 | ],
13 | "plugins": [
14 | "@babel/proposal-class-properties",
15 | "@babel/proposal-object-rest-spread"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/templates/create-serverless-api/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "commonjs": true,
5 | "es2021": true
6 | },
7 | "ignorePatterns": ["/dist/*.js"],
8 | "extends": [
9 | "eslint:recommended",
10 | "plugin:@typescript-eslint/recommended"
11 | ],
12 | "parser": "@typescript-eslint/parser",
13 | "parserOptions": {
14 | "ecmaVersion": 12
15 | },
16 | "plugins": [
17 | "@typescript-eslint"
18 | ],
19 | "rules": {
20 | "indent": [
21 | "error",
22 | 4
23 | ],
24 | "linebreak-style": 0,
25 | "quotes": [
26 | "error",
27 | "single"
28 | ],
29 | "semi": [
30 | "error",
31 | "never"
32 | ]
33 | }
34 | }
--------------------------------------------------------------------------------
/templates/create-serverless-api/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | cdk.out/
3 | .serverless
--------------------------------------------------------------------------------
/templates/create-serverless-api/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2021 LEE JEONG-WOO
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/templates/create-serverless-api/cdk.json:
--------------------------------------------------------------------------------
1 | {
2 | "app": "node .serverless/infra-dist/entry"
3 | }
--------------------------------------------------------------------------------
/templates/create-serverless-api/infra/StackConstruct.ts:
--------------------------------------------------------------------------------
1 | import { Construct, App, Tags } from '@aws-cdk/core'
2 | import CommonStack from './stacks/CommonStack'
3 | import UploadS3Stack from './stacks/UploadS3'
4 |
5 | interface props {
6 | swagger: any
7 | }
8 | class StackConstruct extends Construct {
9 | constructor(scope: App, id: string, props: props) {
10 | super(scope, id)
11 |
12 | const CommonStackObj = new CommonStack(
13 | scope,
14 | `${props.swagger.info.title}MainStack`,
15 | {
16 | env: {
17 | region: props.swagger.info['x-cdk-region'],
18 | },
19 | swagger: props.swagger,
20 | stackName: `${props.swagger.info.title}MainStack`,
21 | }
22 | )
23 |
24 | const uploads3 = new UploadS3Stack(
25 | scope,
26 | `${props.swagger.info.title}CreateBucketStack`,
27 | {
28 | env: {
29 | region: props.swagger.info['x-cdk-region'],
30 | },
31 | swagger: props.swagger,
32 | stackName: `${props.swagger.info.title}CreateBucketStack`,
33 | }
34 | )
35 | Tags.of(CommonStackObj).add('project', props.swagger.info.title)
36 | Tags.of(uploads3).add('project', props.swagger.info.title)
37 | }
38 | }
39 |
40 | export default StackConstruct
41 |
--------------------------------------------------------------------------------
/templates/create-serverless-api/infra/entry.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import { App } from '@aws-cdk/core'
4 | import SwaggerParser from '@apidevtools/swagger-parser'
5 | import StackConstruct from './StackConstruct'
6 |
7 | SwaggerParser.parse('./swagger.yaml').then((swagger) => {
8 | const app = new App()
9 | new StackConstruct(app, `${swagger.info.title}Construct`, { swagger })
10 | app.synth()
11 | })
12 |
--------------------------------------------------------------------------------
/templates/create-serverless-api/infra/modules/convertSwaggerToRestApi.ts:
--------------------------------------------------------------------------------
1 | import { Construct } from '@aws-cdk/core'
2 | import {
3 | RestApi,
4 | LambdaIntegration,
5 | JsonSchemaType,
6 | RequestValidator,
7 | } from '@aws-cdk/aws-apigateway'
8 | import {
9 | Function,
10 | Code,
11 | Tracing,
12 | FunctionProps,
13 | LayerVersion,
14 | } from '@aws-cdk/aws-lambda'
15 | import {
16 | changeToUppercaseFirstLetter,
17 | getParameterType,
18 | isEmpty,
19 | } from '../utils/utils'
20 |
21 | const jsonSchemaTypeDictionary: any = {
22 | array: JsonSchemaType.ARRAY,
23 | boolean: JsonSchemaType.BOOLEAN,
24 | integer: JsonSchemaType.INTEGER,
25 | null: JsonSchemaType.NULL,
26 | number: JsonSchemaType.NUMBER,
27 | object: JsonSchemaType.OBJECT,
28 | string: JsonSchemaType.STRING,
29 | }
30 | interface ILambdaProps extends Omit {
31 | key: string
32 | }
33 |
34 | interface IProps {
35 | apiGateway: RestApi
36 | swagger: any
37 | layersByLambda: { [key: string]: LayerVersion }
38 | lambdaProps: ILambdaProps
39 | }
40 |
41 | const convertSwaggerToRestApi = (
42 | scope: Construct,
43 | { apiGateway, swagger, layersByLambda, lambdaProps }: IProps
44 | ) => {
45 | let paths = Object.keys(swagger.paths)
46 |
47 | paths.forEach((pathName) => {
48 | const resource = apiGateway.root.resourceForPath(pathName)
49 | const methods = Object.keys(swagger.paths[pathName])
50 |
51 | methods.forEach((methodName) => {
52 | const apiData = swagger.paths[pathName][methodName]
53 | const lambdaId =
54 | swagger.info.title +
55 | changeToUppercaseFirstLetter(methodName) +
56 | (pathName === '/' ? '' : changeToUppercaseFirstLetter(pathName))
57 | const lambda = new Function(scope, lambdaId, {
58 | functionName: lambdaId,
59 | description: apiData['description'],
60 | code: Code.fromAsset(`./.serverless/dist/api/${lambdaId}`),
61 | handler: apiData['x-cdk-lambda-handler'],
62 | tracing: Tracing.ACTIVE,
63 | layers: [layersByLambda[lambdaId]],
64 | ...lambdaProps,
65 | })
66 |
67 | let hasRequestParameter: boolean = false
68 | let hasRequestBody: boolean = false
69 | let integrationParameters: any = undefined
70 | let methodParameters: any = undefined
71 | let modelSchema: any = undefined
72 |
73 | if (apiData['parameters'] && apiData['parameters'].length) {
74 | const parameters: any[] = apiData['parameters']
75 | integrationParameters = {}
76 | methodParameters = {}
77 |
78 | parameters.forEach((parameter, idx) => {
79 | const parameterType = getParameterType(parameter['in'])
80 | if (parameterType === 'body') {
81 | hasRequestBody = true
82 | modelSchema = {}
83 | const schema = parameter['schema']
84 | modelSchema.title = lambdaId
85 | modelSchema.description = apiData['description']
86 | modelSchema.type = jsonSchemaTypeDictionary[schema.type]
87 | const requiredArray =
88 | jsonSchemaTypeDictionary[schema.required]
89 | if (!isEmpty(schema.properties)) {
90 | modelSchema.properties = {}
91 | for (const property in schema.properties) {
92 | const propertyType =
93 | schema.properties[property].type
94 | modelSchema.properties[property] = {
95 | type: jsonSchemaTypeDictionary[
96 | propertyType
97 | ],
98 | }
99 | if (
100 | schema.properties[property].required ===
101 | true
102 | )
103 | requiredArray.push(property)
104 | }
105 | }
106 | if (requiredArray?.length) {
107 | modelSchema.required = requiredArray
108 | }
109 | } else {
110 | hasRequestParameter = true
111 | integrationParameters[
112 | `integration.request.${parameterType}.${parameter['name']}`
113 | ] = `method.request.${parameterType}.${parameter['name']}`
114 | methodParameters[
115 | `method.request.${parameterType}.${parameter['name']}`
116 | ] = parameter.required ?? false
117 | methodParameters[
118 | `method.request.${parameterType}.${parameter['name']}`
119 | ] = parameter.required ?? false
120 | }
121 | })
122 | }
123 |
124 | let model = undefined
125 | if (modelSchema) {
126 | model = apiGateway.addModel(`${lambdaId}Model`, {
127 | modelName: `${lambdaId}Model`,
128 | description: apiData['description'],
129 | contentType: apiData['produces'][0],
130 | schema: modelSchema,
131 | })
132 | }
133 |
134 | let requestValidator: any = undefined
135 | if (hasRequestBody || hasRequestParameter) {
136 | requestValidator = new RequestValidator(
137 | scope,
138 | `${lambdaId}RequestValidator`,
139 | {
140 | restApi: apiGateway,
141 | requestValidatorName: `${lambdaId}RequestValidator`,
142 | validateRequestBody: hasRequestBody,
143 | validateRequestParameters: hasRequestParameter,
144 | }
145 | )
146 | }
147 |
148 | resource.addMethod(
149 | methodName,
150 | new LambdaIntegration(lambda, {
151 | requestParameters: integrationParameters,
152 | }),
153 | {
154 | ...(modelSchema && {
155 | requestModels: {
156 | [apiData['produces'][0]]: model,
157 | },
158 | }),
159 | ...(requestValidator && {
160 | requestValidator: requestValidator,
161 | }),
162 | requestParameters: methodParameters,
163 | }
164 | )
165 | })
166 | })
167 | }
168 |
169 | export default convertSwaggerToRestApi
170 |
--------------------------------------------------------------------------------
/templates/create-serverless-api/infra/stacks/CommonStack.ts:
--------------------------------------------------------------------------------
1 | import { App, CfnOutput, RemovalPolicy, Stack, StackProps } from '@aws-cdk/core'
2 | import {
3 | Cors,
4 | Deployment,
5 | EndpointType,
6 | MethodLoggingLevel,
7 | RestApi,
8 | Stage,
9 | } from '@aws-cdk/aws-apigateway'
10 | import convertSwaggerToRestApiModule from '../modules/convertSwaggerToRestApi'
11 | import { LayerVersion, Runtime, S3Code } from '@aws-cdk/aws-lambda'
12 | import { SubnetType } from '@aws-cdk/aws-ec2'
13 | import { Bucket } from '@aws-cdk/aws-s3'
14 | import { camelCaseToDash, changeToUppercaseFirstLetter } from '../utils/utils'
15 | import { join } from 'path'
16 | import { readFileSync } from 'fs-extra'
17 | import path from 'path'
18 |
19 | interface IProps extends StackProps {
20 | swagger: any
21 | }
22 | class CommonStack extends Stack {
23 | public constructor(scope: App, key: string, props: IProps) {
24 | super(scope, key, props)
25 |
26 | const bucketName = camelCaseToDash(
27 | JSON.parse(
28 | readFileSync(
29 | path.join(process.cwd(), 'infra', 'projectData.json'),
30 | 'utf-8'
31 | )
32 | ).bucketName
33 | )
34 |
35 | const bucket = Bucket.fromBucketName(
36 | this,
37 | `${props.swagger.info.title}Bucket`,
38 | bucketName
39 | )
40 |
41 | const layersByLambda = JSON.parse(
42 | readFileSync(join(__dirname, '../../layers.json'), {
43 | encoding: 'utf-8',
44 | })
45 | )
46 | const layerSet = new Set(Object.values(layersByLambda))
47 | const layers = new Map()
48 | for (const layerName of layerSet) {
49 | layers.set(
50 | layerName,
51 | new LayerVersion(
52 | this,
53 | `${props.swagger.info.title}Layer-${layerName}`,
54 | {
55 | code: new S3Code(bucket, `${layerName}.zip`),
56 | compatibleRuntimes: [
57 | Runtime.NODEJS_12_X,
58 | Runtime.NODEJS_14_X,
59 | ],
60 | }
61 | )
62 | )
63 | }
64 | for (const lambdaName in layersByLambda) {
65 | layersByLambda[lambdaName] = layers.get(layersByLambda[lambdaName])
66 | }
67 |
68 | const apiGateway = new RestApi(
69 | this,
70 | `${props.swagger.info.title}CdkApiGateway`,
71 | {
72 | restApiName: `${changeToUppercaseFirstLetter(
73 | props.swagger.info.title
74 | )}_API_Gataway`,
75 | description: `${props.swagger.info.title} api gateways`,
76 | endpointTypes: [EndpointType.REGIONAL],
77 | failOnWarnings: true, // Rollback when error on deploy process
78 | defaultCorsPreflightOptions: {
79 | allowOrigins: Cors.ALL_ORIGINS,
80 | allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTION'],
81 | },
82 | deploy: true,
83 | deployOptions: {
84 | stageName: process.env.INFRA_ENV,
85 | description: `stage in ${process.env.INFRA_ENV} environment`,
86 | variables: {
87 | APP_ENV: process.env.INFRA_ENV,
88 | } as { [key: string]: any },
89 | tracingEnabled: true, // X-Ray enable
90 | dataTraceEnabled: true, // CloudWatch log enable
91 | loggingLevel: MethodLoggingLevel.INFO, // CloudWatch logging level
92 | throttlingBurstLimit: 10, // maximum process count at same time
93 | throttlingRateLimit: 10, // maximum request count per second
94 | },
95 | }
96 | )
97 | convertSwaggerToRestApiModule(this, {
98 | apiGateway: apiGateway,
99 | swagger: props.swagger,
100 | layersByLambda: layersByLambda,
101 | lambdaProps: {
102 | key: `${props.swagger.info.title}Functions`,
103 | runtime: Runtime.NODEJS_14_X,
104 | allowPublicSubnet: true,
105 | environment: {
106 | APP_ENV: process.env.INFRA_ENV,
107 | } as { [key: string]: string },
108 | vpcSubnets: {
109 | subnetType: SubnetType.PUBLIC,
110 | },
111 | },
112 | })
113 |
114 | new CfnOutput(this, 'apiEndpoint', {
115 | value: apiGateway.url,
116 | description: 'The api endpoint of the api gateway',
117 | exportName: 'apiEndpoint',
118 | })
119 | }
120 | }
121 |
122 | export default CommonStack
123 |
--------------------------------------------------------------------------------
/templates/create-serverless-api/infra/stacks/UploadS3.ts:
--------------------------------------------------------------------------------
1 | import { App, CfnOutput, RemovalPolicy, Stack } from '@aws-cdk/core'
2 | import { Bucket, HttpMethods } from '@aws-cdk/aws-s3'
3 | import { readFileSync } from 'fs-extra'
4 | import path from 'path'
5 | import { camelCaseToDash } from '../utils/utils'
6 | const fs = require('fs-extra')
7 |
8 | class UploadS3Stack extends Stack {
9 | constructor(scope: App, key: string, props: any) {
10 | super(scope, key, props)
11 |
12 | const bucketName = camelCaseToDash(
13 | JSON.parse(
14 | readFileSync(
15 | path.join(process.cwd(), 'infra', 'projectData.json'),
16 | 'utf-8'
17 | )
18 | ).bucketName
19 | )
20 |
21 | const bucket = new Bucket(this, `${props.swagger.info.title}-bucket`, {
22 | bucketName: bucketName,
23 | removalPolicy: RemovalPolicy.DESTROY,
24 | autoDeleteObjects: true,
25 | publicReadAccess: true,
26 | cors: [
27 | {
28 | allowedMethods: [HttpMethods.GET],
29 | allowedOrigins: ['*'],
30 | },
31 | ],
32 | })
33 |
34 | new CfnOutput(this, 'bucketName', {
35 | value: bucket.bucketName,
36 | description: 'The name of the s3 bucket',
37 | exportName: 'bucketName',
38 | })
39 | new CfnOutput(this, 'bucketRegionalDomainName', {
40 | value: bucket.bucketRegionalDomainName,
41 | description: 'The regional domain name of the s3 bucket',
42 | exportName: 'bucketRegionalDomainName',
43 | })
44 | }
45 | }
46 |
47 | export default UploadS3Stack
48 |
--------------------------------------------------------------------------------
/templates/create-serverless-api/infra/utils/utils.ts:
--------------------------------------------------------------------------------
1 | export const isEmpty = (param: Object) => Object.keys(param).length === 0
2 |
3 | export const changeToUppercaseFirstLetter = (strings: string): string => {
4 | return strings
5 | .replace(/\{|\}/g, '')
6 | .split(/\/|\_|\-/)
7 | .map(
8 | (string) =>
9 | string.slice(0, 1).toUpperCase() +
10 | string.slice(1, string.length)
11 | )
12 | .join('')
13 | }
14 |
15 | export const getParameterType = (swaggerIn: string): string => {
16 | if (swaggerIn === 'query') return 'querystring'
17 | else if (swaggerIn === 'path') return 'path'
18 | else if (swaggerIn === 'header') return 'header'
19 | else if (swaggerIn === 'body') return 'body'
20 | else
21 | throw new Error(
22 | `요청하신 api parameter 종류인 ${swaggerIn}을 찾을 수 없습니다.`
23 | )
24 | }
25 |
26 | export const camelCaseToDash = (str: string) =>
27 | str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
28 |
--------------------------------------------------------------------------------
/templates/create-serverless-api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.1.0",
3 | "main": "cdk.json",
4 | "type": "commonjs",
5 | "scripts": {
6 | "bootstrap": "npm run build && cross-env INFRA_ENV=development cdk bootstrap --profile development",
7 | "bootstrap-prod": "npm run build && cross-env INFRA_ENV=production cdk bootstrap --profile production",
8 | "build": "node ./scripts/defineBucketName.js && npm run build-infra && npm run build-server",
9 | "build-infra": "babel ./infra --out-dir ./.serverless/infra-dist --extensions \".ts\"",
10 | "build-server": "node ./scripts/bundle.js && babel ./.serverless/dist/temp --out-dir ./.serverless/dist/api --extensions \".ts\" --copy-files && node ./scripts/cleanUp.js",
11 | "deploy": "npm run build && cross-env INFRA_ENV=development node ./scripts/executeS3Stack.js deploy && cross-env INFRA_ENV=development node ./scripts/uploadLayer.js && cross-env INFRA_ENV=development cdk deploy --all --require-approval never --profile development --outputs-file ./.serverless/output.json && cross-env INFRA_ENV=development node ./scripts/uploadSwagger.js",
12 | "deploy-prod": "npm run build && cross-env INFRA_ENV=production node ./scripts/executeS3Stack.js deploy && cross-env INFRA_ENV=production node ./scripts/uploadLayer.js && cross-env INFRA_ENV=production cdk deploy --all --require-approval never --profile production --outputs-file ./.serverless/output.json && cross-env INFRA_ENV=production node ./scripts/uploadSwagger.js",
13 | "destroy": "cross-env INFRA_ENV=development cdk destroy --all --force --profile development && node scripts/afterDestroy.js",
14 | "destroy-prod": "cross-env INFRA_ENV=production cdk destroy --all --force --profile production && node scripts/afterDestroy.js",
15 | "diff": "npm run build && cross-env INFRA_ENV=development node ./scripts/executeS3Stack.js diff && cross-env INFRA_ENV=development cdk diff --profile development",
16 | "diff-prod": "npm run build && cross-env INFRA_ENV=production node ./scripts/executeS3Stack.js diff && cross-env INFRA_ENV=production cdk diff --profile production",
17 | "offline": "cross-env APP_ENV=Offline nodemon --watch ./**/*.ts --exec 'npx' 'ts-node' ./scripts/offline.ts"
18 | },
19 | "devDependencies": {
20 | "@aws-cdk/aws-apigateway": "1.134.0",
21 | "@aws-cdk/aws-lambda": "1.134.0",
22 | "@aws-cdk/aws-route53-targets": "1.134.0",
23 | "@aws-cdk/core": "1.134.0",
24 | "@babel/cli": "7.16.0",
25 | "@babel/core": "7.16.0",
26 | "@babel/plugin-proposal-class-properties": "7.16.0",
27 | "@babel/plugin-proposal-object-rest-spread": "7.16.0",
28 | "@babel/preset-env": "7.16.4",
29 | "@babel/preset-typescript": "7.16.0",
30 | "@types/aws-lambda": "8.10.85",
31 | "@types/cors": "2.8.12",
32 | "@types/express": "4.17.13",
33 | "@types/fs-extra": "9.0.13",
34 | "@types/node": "16.11.11",
35 | "aws-cdk": "1.134.0",
36 | "aws-sdk": "2.1053.0",
37 | "core-js": "3.19.2",
38 | "cors": "2.8.5",
39 | "cross-env": "7.0.3",
40 | "fs-extra": "10.0.0",
41 | "nodemon": "^2.0.15",
42 | "prompts": "2.4.2",
43 | "swagger-parser": "10.0.3",
44 | "ts-node": "10.5.0",
45 | "typescript": "4.5.4",
46 | "uuid": "8.3.2"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/templates/create-serverless-api/scripts/afterDestroy.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs/promises')
2 |
3 | const existCheck = async (path) => {
4 | try {
5 | let directoryIsExist = null
6 |
7 | try {
8 | await fs.access(path)
9 | directoryIsExist = true
10 | } catch (e) {
11 | if (e.errno === -4058) directoryIsExist = false
12 | else throw new Error(e)
13 | }
14 |
15 | return directoryIsExist
16 | } catch (e) {
17 | throw new Error(e)
18 | }
19 | }
20 |
21 | const remove = async (path) => {
22 | try {
23 | let isSuccess = true
24 |
25 | if (await existCheck(path)) {
26 | await fs.rm(path, {
27 | recursive: true,
28 | force: true,
29 | })
30 | }
31 | if (await existCheck(path)) {
32 | isSuccess = false
33 | }
34 |
35 | return isSuccess
36 | } catch (e) {
37 | throw new Error(e)
38 | }
39 | }
40 |
41 | const timer = () => {
42 | const timer = setInterval(() => {
43 | process.stdout.write('.')
44 | }, 100)
45 | const clearTimer = () => {
46 | clearInterval(timer)
47 | }
48 | return clearTimer
49 | }
50 |
51 | const loadProcess = async (name, func, params = []) => {
52 | const time = timer()
53 | process.stdout.write(`${name} start...`)
54 | try {
55 | const result = await func(...params)
56 | process.stdout.write(`complete\n`)
57 | return result
58 | } catch (e) {
59 | process.stdout.write(`error\n`)
60 | return Promise.reject(Error(e))
61 | } finally {
62 | time()
63 | }
64 | }
65 |
66 | const destroy = async () => {
67 | try {
68 | // Remove data file
69 | const removeData = await loadProcess('remove', remove, [
70 | './infra/projectData.json',
71 | ])
72 | if (!removeData) throw new Error('data 파일을 제거하지 못했습니다.')
73 |
74 | console.log('Destroy complete!')
75 | } catch (e) {
76 | console.error(e)
77 | }
78 | }
79 |
80 | ;(async () => await destroy())()
81 |
--------------------------------------------------------------------------------
/templates/create-serverless-api/scripts/bundle.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const SwaggerParser = require('@apidevtools/swagger-parser')
3 | const childProcess = require('child_process')
4 | const util = require('util')
5 | const fs = require('fs/promises')
6 | const { createHmac } = require('crypto')
7 | const os = require('os')
8 |
9 | const exec = util.promisify(childProcess.exec)
10 |
11 | const serverlessPath = path.join(process.cwd(), '.serverless')
12 | const distPath = path.join(serverlessPath, 'dist')
13 | const srcPath = path.join(process.cwd(), 'src')
14 | const tempPath = path.join(distPath, 'temp')
15 | const layerPath = path.join(distPath, 'layers')
16 | const commonModulePath = path.join(srcPath, 'modules')
17 |
18 | const extractDependencies = async () => {
19 | const packageJsonData = await fs.readFile(
20 | path.join(process.cwd(), 'package.json')
21 | )
22 | const packageJson = Buffer.from(packageJsonData).toString('utf-8')
23 | const packageInfo = JSON.parse(packageJson)
24 | const dependencies = packageInfo?.dependencies || {}
25 | return dependencies
26 | }
27 |
28 | const extractLambdaInfos = async (swagger) => {
29 | const {
30 | paths,
31 | info: { title },
32 | } = swagger
33 |
34 | const lambdaInfos = {}
35 | for (const pathKey in paths) {
36 | const uris = pathKey
37 | .replace('/', '')
38 | .split('/')
39 | .map((str) =>
40 | str
41 | .replace(/[a-z]/, (letter) => letter.toUpperCase())
42 | .replace(/_[a-z]/, (letter) =>
43 | letter.replace(/\_/, '').toUpperCase()
44 | )
45 | .replace(/\{/g, '')
46 | .replace(/\}/g, '')
47 | )
48 | const pathInfo = paths[pathKey]
49 | for (const method in pathInfo) {
50 | const info = pathInfo[method]
51 |
52 | const lambdaName = title.concat(
53 | method.replace(/[a-z]/, (letter) => letter.toUpperCase()),
54 | uris.join('')
55 | )
56 | const additionalLibrary = info['x-cdk-additional-library'] || []
57 |
58 | const handler = path.join(srcPath, info['x-cdk-lambda-handler'])
59 | const handlerPath = handler.replace(path.extname(handler), '.ts')
60 |
61 | lambdaInfos[lambdaName] = { additionalLibrary, handlerPath }
62 | }
63 | }
64 |
65 | return lambdaInfos
66 | }
67 |
68 | const bundle = async () => {
69 | await fs.rm(distPath, { recursive: true, force: true })
70 | await fs.mkdir(layerPath, { recursive: true })
71 | await fs.mkdir(tempPath, { recursive: true })
72 |
73 | const swagger = await SwaggerParser.parse(
74 | path.join(process.cwd(), 'swagger.yaml')
75 | )
76 |
77 | const dependencies = await extractDependencies()
78 | const lambdaInfos = await extractLambdaInfos(swagger)
79 |
80 | console.info('gen directories...')
81 | const commonDependencies = { ...dependencies }
82 | for (const lambdaName in lambdaInfos) {
83 | const { handlerPath, additionalLibrary } = lambdaInfos[lambdaName]
84 |
85 | const lambdaTempPath = path.join(tempPath, lambdaName)
86 | const copiedPath = path.join(
87 | lambdaTempPath,
88 | path.relative(srcPath, handlerPath)
89 | )
90 |
91 | await fs.mkdir(path.join(lambdaTempPath), { recursive: true })
92 | await copyForce(handlerPath, copiedPath)
93 |
94 | const modulePath = path.join(lambdaTempPath, 'modules')
95 | await fs.mkdir(modulePath)
96 |
97 | await copy(commonModulePath, modulePath)
98 |
99 | for (const libraryName of additionalLibrary) {
100 | delete commonDependencies[libraryName]
101 | }
102 | }
103 |
104 | console.info('gen layers...')
105 | const libraryCase = []
106 | for (const lambdaName in lambdaInfos) {
107 | const { additionalLibrary } = lambdaInfos[lambdaName]
108 |
109 | const useDependencies = { ...commonDependencies }
110 | for (const libraryName of additionalLibrary) {
111 | useDependencies[libraryName] = dependencies[libraryName]
112 | }
113 | const jsonUseDependencies = JSON.stringify(useDependencies)
114 | if (!libraryCase.includes(jsonUseDependencies)) {
115 | libraryCase.push(jsonUseDependencies)
116 | }
117 | lambdaInfos[lambdaName].layerCaseName = jsonToHash(jsonUseDependencies)
118 | }
119 |
120 | for await (const oneCase of libraryCase) {
121 | const layerCaseName = jsonToHash(oneCase)
122 | const layerCasePath = path.join(layerPath, layerCaseName)
123 | const nodePath = path.join(layerCasePath, 'nodejs')
124 | await fs.mkdir(nodePath, { recursive: true })
125 |
126 | const useDependencies = JSON.parse(oneCase)
127 | for (const libraryName in useDependencies) {
128 | const version = useDependencies[libraryName]
129 | await exec(
130 | `npm i --prefix ${nodePath} ${libraryName}@${version}`
131 | )
132 | }
133 |
134 | const fileCnt = (await fs.readdir(nodePath)).length
135 |
136 | if (fileCnt === 0) {
137 | await fs.mkdir(nodePath, { recursive: true })
138 | await fs.writeFile(path.join(nodePath, 'blank.json'), 'blank')
139 | }
140 | const zipPath = path.join(layerPath, `${layerCaseName}.zip`)
141 |
142 | const platform = os.platform()
143 | let zipCommand = 'zip'
144 | if(platform==='win32'){
145 | zipCommand = 'tar.exe -a -c -f'
146 | }
147 | await exec(
148 | `cd ${layerCasePath} && ${zipCommand} ${zipPath} nodejs/*`
149 | )
150 | await fs.rm(layerCasePath, { force: true, recursive: true })
151 | }
152 |
153 | const layerJson = {}
154 | for (const lambdaName in lambdaInfos) {
155 | const useCase = lambdaInfos[lambdaName].layerCaseName
156 | layerJson[lambdaName] = useCase
157 | }
158 |
159 | await fs.writeFile(
160 | path.join(serverlessPath, 'layers.json'),
161 | JSON.stringify(layerJson)
162 | )
163 | }
164 |
165 | const jsonToHash = string =>
166 | createHmac('sha256', 'library').update(string).digest('hex').slice(0, 20)
167 |
168 | const copy = async (src, dest) => {
169 | const stat = await fs.lstat(src)
170 | if (stat.isDirectory()) {
171 | await fs.mkdir(dest, { recursive: true })
172 | const dirs = await fs.readdir(src)
173 | for await (const dir of dirs) {
174 | const [srcPath, destPath] = [
175 | path.join(src, dir),
176 | path.join(dest, dir),
177 | ]
178 | await copy(srcPath, destPath)
179 | }
180 | } else {
181 | await fs.copyFile(src, dest)
182 | }
183 | }
184 |
185 | const copyForce = async (src, dest) => {
186 | const { dir } = path.parse(dest)
187 | await fs.mkdir(dir, { recursive: true })
188 | await fs.copyFile(src, dest)
189 | }
190 |
191 | bundle().catch((e) => {
192 | throw e
193 | })
194 |
--------------------------------------------------------------------------------
/templates/create-serverless-api/scripts/cleanUp.js:
--------------------------------------------------------------------------------
1 |
2 | const path = require('path')
3 | const fs = require('fs/promises')
4 |
5 | const cleanUp = async () => {
6 | const tempPath = path.join(process.cwd(), '.serverless', 'dist', 'temp')
7 | await fs.rm(tempPath, { recursive: true, force: true })
8 | }
9 |
10 | (async () => {
11 | try{
12 | await cleanUp()
13 | } catch (e) {
14 | console.error(e)
15 | throw e
16 | }
17 | })()
18 |
--------------------------------------------------------------------------------
/templates/create-serverless-api/scripts/defineBucketName.js:
--------------------------------------------------------------------------------
1 | const SwaggerParser = require('@apidevtools/swagger-parser')
2 | const fs = require('fs-extra')
3 | const prompts = require('prompts')
4 | const path = require('path')
5 | const { v4: uuidv4 } = require('uuid')
6 | const { camelCaseToDash } = require('./utils')
7 |
8 | const defineBucketName = async () => {
9 | const dataPath = path.join(process.cwd(), 'infra', 'projectData.json')
10 | try {
11 | await fs.access(dataPath)
12 | } catch (e) {
13 | const response = await prompts({
14 | type: 'confirm',
15 | name: 'value',
16 | message: `Is this your first time running this project?\nIf not, please import /infra/projectData.json from the existing project execution environment to the current environment and run it again.`,
17 | })
18 | if (response.value === false) {
19 | process.exit(1)
20 | }
21 |
22 | const swagger = await SwaggerParser.parse(
23 | path.join(process.cwd(), 'swagger.yaml')
24 | )
25 | try {
26 | await fs.ensureFile(dataPath)
27 | } catch (e) {
28 | console.error('File creation inside "/infra" directory failed.')
29 | process.exit(1)
30 | }
31 | const bucketName = `${camelCaseToDash(swagger.info.title)}-${uuidv4()}`
32 | await fs.writeJson(dataPath, {
33 | bucketName: bucketName,
34 | })
35 |
36 | console.log('Initial setting data was successfully generated.')
37 | }
38 | }
39 |
40 | ;(async () => await defineBucketName())()
41 |
--------------------------------------------------------------------------------
/templates/create-serverless-api/scripts/executeS3Stack.js:
--------------------------------------------------------------------------------
1 | const SwaggerParser = require('@apidevtools/swagger-parser')
2 | const util = require('util')
3 | const exec = util.promisify(require('child_process').exec)
4 |
5 | const executeS3Stack = async () => {
6 | try {
7 | return SwaggerParser.parse('./swagger.yaml').then(async (swagger) => {
8 | const cdkCommand = process.argv[2]
9 | const stackName = `${swagger.info.title}CreateBucketStack`
10 | const INFRA_ENV = process.env.INFRA_ENV
11 | const command = `cdk ${cdkCommand} ${stackName} --require-approval never --profile ${INFRA_ENV} --outputs-file ./.serverless/output.json`
12 |
13 | const result = await exec(command)
14 | return result
15 | })
16 | } catch (e) {
17 | throw new Error(e)
18 | }
19 | }
20 |
21 | const timer = () => {
22 | const timer = setInterval(() => {
23 | process.stdout.write('.')
24 | }, 1000)
25 | const clearTimer = () => {
26 | clearInterval(timer)
27 | }
28 | return clearTimer
29 | }
30 |
31 | const loadProcess = async (name, func, params = []) => {
32 | const time = timer()
33 | process.stdout.write(`${name} start...`)
34 | try {
35 | const result = await func(...params)
36 | process.stdout.write(`complete\n`)
37 | return result
38 | } catch (e) {
39 | process.stdout.write(`error\n`)
40 | return Promise.reject(Error(e))
41 | } finally {
42 | time()
43 | }
44 | }
45 |
46 | const main = async () => {
47 | try {
48 | const isMade = await loadProcess('executeS3Stack', executeS3Stack)
49 | if (!isMade) throw new Error('S3 stack 배포에 실패했습니다.')
50 |
51 | console.log('execute S3 Stack complete!')
52 | } catch (e) {
53 | console.error(e)
54 | }
55 | }
56 |
57 | ;(async () => await main())()
58 |
--------------------------------------------------------------------------------
/templates/create-serverless-api/scripts/install.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs/promises')
2 | const util = require('util')
3 | const exec = util.promisify(require('child_process').exec)
4 |
5 | const existCheck = async (path) => {
6 | try {
7 | let directoryIsExist = null
8 |
9 | try {
10 | await fs.access(path)
11 | directoryIsExist = true
12 | } catch (e) {
13 | if (e.errno === -4058) directoryIsExist = false
14 | else throw new Error(e)
15 | }
16 |
17 | return directoryIsExist
18 | } catch (e) {
19 | throw new Error(e)
20 | }
21 | }
22 |
23 | const makeDirectory = async (directoryPath) => {
24 | try {
25 | let isSuccess = null
26 |
27 | if (await existCheck(directoryPath)) {
28 | isSuccess = true
29 | } else {
30 | await fs.mkdir(directoryPath, { recursive: true })
31 | isSuccess = true
32 | }
33 | if (!(await existCheck(directoryPath))) {
34 | isSuccess = false
35 | }
36 |
37 | return isSuccess
38 | } catch (e) {
39 | throw new Error(e)
40 | }
41 | }
42 |
43 | const remove = async (path) => {
44 | try {
45 | let isSuccess = true
46 |
47 | if (await existCheck(path)) {
48 | await fs.rm(path, {
49 | recursive: true,
50 | force: true,
51 | })
52 | }
53 | if (await existCheck(path)) {
54 | isSuccess = false
55 | }
56 |
57 | return isSuccess
58 | } catch (e) {
59 | throw new Error(e)
60 | }
61 | }
62 |
63 | const copyFile = async (srcPath, distDirectoryPath, distFileName) => {
64 | try {
65 | let isSuccess = null
66 | const distPath = `${distDirectoryPath}/${distFileName}`
67 |
68 | if (!(await existCheck(distDirectoryPath))) {
69 | throw new Error(`${distDirectoryPath} 폴더가 없습니다.`)
70 | }
71 |
72 | await fs.copyFile(srcPath, distPath)
73 |
74 | if (await existCheck(distPath)) {
75 | isSuccess = true
76 | } else {
77 | isSuccess = false
78 | }
79 |
80 | return isSuccess
81 | } catch (e) {
82 | throw new Error(e)
83 | }
84 | }
85 |
86 | const installOnlyProd = async () => {
87 | try {
88 | let isSuccess = false
89 | const commands = {
90 | npm: 'cd ./dist && npm install --only=prod --silent',
91 | yarn: 'cd ./dist && yarn install --production=true --silent',
92 | }
93 | for (const command in commands) {
94 | const result = await exec(commands[command])
95 | if (!result.stderr) {
96 | isSuccess = true
97 | break
98 | }
99 | }
100 | if (!isSuccess) {
101 | throw new Error('라이브러리를 설치하지 못했습니다.')
102 | }
103 | } catch (e) {
104 | throw new Error(e)
105 | }
106 | }
107 |
108 | const installAll = async () => {
109 | let isSuccess = false
110 | const commands = {
111 | npm: 'npm install',
112 | yarn: 'yarn install',
113 | }
114 | for (const command in commands) {
115 | const result = await exec(commands[command])
116 | if (!result.stderr) {
117 | isSuccess = true
118 | break
119 | }
120 | }
121 | if (!isSuccess) {
122 | throw new Error('라이브러리를 설치하지 못했습니다.')
123 | }
124 | }
125 |
126 | const timer = () => {
127 | const timer = setInterval(() => {
128 | process.stdout.write('.')
129 | }, 100)
130 | const clearTimer = () => {
131 | clearInterval(timer)
132 | }
133 | return clearTimer
134 | }
135 |
136 | const loadProcess = async (name, func, params = []) => {
137 | const time = timer()
138 | process.stdout.write(`${name} start...`)
139 | try {
140 | const result = await func(...params)
141 | process.stdout.write(`complete\n`)
142 | return result
143 | } catch (e) {
144 | process.stdout.write(`error\n`)
145 | return Promise.reject(Error(e))
146 | } finally {
147 | time()
148 | }
149 | }
150 |
151 | const install = async () => {
152 | try {
153 | // Make dist directory
154 | const isMade = await loadProcess('makeDirectory', makeDirectory, [
155 | './dist',
156 | ])
157 | if (!isMade) throw new Error('dist 폴더를 생성하지 못했습니다.')
158 |
159 | // Copy package.json to ./dist/package.json
160 | const isCopiedPackage = await loadProcess('copyFile', copyFile, [
161 | './package.json',
162 | './dist',
163 | 'package.json',
164 | ])
165 | if (!isCopiedPackage)
166 | throw new Error('package.json 파일을 복사하지 못했습니다.')
167 |
168 | // Copy package-lock.json to ./dist/package-lock.json
169 | const isCopiedPackageLock = await loadProcess('copyFile', copyFile, [
170 | './package-lock.json',
171 | './dist',
172 | 'package-lock.json',
173 | ])
174 | if (!isCopiedPackageLock)
175 | throw new Error('package.json 파일을 복사하지 못했습니다.')
176 |
177 | // Install production libraries
178 | await loadProcess('installOnlyProd', installOnlyProd)
179 |
180 | // Remove ./dist/package.json
181 | await loadProcess('remove package.json', remove, [
182 | './dist/package.json',
183 | ])
184 |
185 | // Remove ./dist/package-lock.json
186 | await loadProcess('remove package-lock.json', remove, [
187 | './dist/package-lock.json',
188 | ])
189 |
190 | console.log('Install complete!')
191 | } catch (e) {
192 | console.error(e)
193 | }
194 | }
195 |
196 | ;(async () => await install())()
197 |
--------------------------------------------------------------------------------
/templates/create-serverless-api/scripts/modules/map.ts:
--------------------------------------------------------------------------------
1 |
2 | const getBody = async (req: any) => {
3 | const buffers = []
4 | for await (const chunk of req) {
5 | buffers.push(chunk)
6 | }
7 |
8 | return Buffer.concat(buffers).toString()
9 | }
10 |
11 | export const mappingLambdaEvent = async (req: any, pathParameters: any) => {
12 | const [capture, symbolHeaders] = Object.getOwnPropertySymbols(req)
13 | const headers = req[symbolHeaders]
14 | const { url: urlWithStage, method }: { method: string, [key: string]: any } = req
15 |
16 | if(!urlWithStage?.startsWith('/development/')){
17 | throw new Error('404: stage not found')
18 | }
19 | const [pathUrlWithStage, queryString]: any = urlWithStage?.split('?')
20 | const pathUrl = pathUrlWithStage?.replace('/development', '')
21 | const queries = queryString?.split('&') || []
22 |
23 | let queryStringParameters: any = {}
24 | let multiValueQueryStringParameters: any = {}
25 | queries.forEach((query: string) => {
26 | const [key, value] = query.split('=')
27 | queryStringParameters[key] = value
28 | multiValueQueryStringParameters[key] = [value]
29 | })
30 |
31 | const event = {
32 | "resource": pathUrl,
33 | "path": pathUrl,
34 | "httpMethod": method,
35 | headers,
36 | "multiValueHeaders": {
37 | "accept": [
38 | "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
39 | ],
40 | "accept-encoding": [
41 | "gzip, deflate, br"
42 | ],
43 | "accept-language": [
44 | "en-US,en;q=0.9"
45 | ],
46 | "cookie": [
47 | "s_fid=7AABXMPL1AFD9BBF-0643XMPL09956DE2; regStatus=pre-register;"
48 | ],
49 | "Host": [
50 | "70ixmpl4fl.execute-api.ca-central-1.amazonaws.com"
51 | ],
52 | "sec-fetch-dest": [
53 | "document"
54 | ],
55 | "sec-fetch-mode": [
56 | "navigate"
57 | ],
58 | "sec-fetch-site": [
59 | "none"
60 | ],
61 | "upgrade-insecure-requests": [
62 | "1"
63 | ],
64 | "User-Agent": [
65 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36"
66 | ],
67 | "X-Amzn-Trace-Id": [
68 | "Root=1-5e66d96f-7491f09xmpl79d18acf3d050"
69 | ],
70 | "X-Forwarded-For": [
71 | "52.255.255.12"
72 | ],
73 | "X-Forwarded-Port": [
74 | "443"
75 | ],
76 | "X-Forwarded-Proto": [
77 | "https"
78 | ]
79 | },
80 | queryStringParameters,
81 | multiValueQueryStringParameters,
82 | pathParameters,
83 | "stageVariables": null,
84 | "requestContext": {
85 | "resourceId": "2gxmpl",
86 | "resourcePath": pathUrl,
87 | "httpMethod": method,
88 | "extendedRequestId": "JJbxmplHYosFVYQ=",
89 | "requestTime": "10/Mar/2020:00:03:59 +0000",
90 | "path": pathUrlWithStage,
91 | "accountId": "123456789012",
92 | "protocol": "HTTP/1.1",
93 | "stage": "development",
94 | "domainPrefix": "70ixmpl4fl",
95 | "requestTimeEpoch": 1583798639428,
96 | "requestId": "77375676-xmpl-4b79-853a-f982474efe18",
97 | "identity": {
98 | "cognitoIdentityPoolId": null,
99 | "accountId": null,
100 | "cognitoIdentityId": null,
101 | "caller": null,
102 | "sourceIp": "52.255.255.12",
103 | "principalOrgId": null,
104 | "accessKey": null,
105 | "cognitoAuthenticationType": null,
106 | "cognitoAuthenticationProvider": null,
107 | "userArn": null,
108 | "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36",
109 | "user": null
110 | },
111 | "domainName": "70ixmpl4fl.execute-api.us-east-2.amazonaws.com",
112 | "apiId": "70ixmpl4fl"
113 | },
114 | "body": await getBody(req),
115 | "isBase64Encoded": false
116 | }
117 |
118 | return event
119 | }
120 |
121 | export const mappingLambdaContext = (functionName: string) => {
122 | const context = {
123 | functionName, // The name of the Lambda function.
124 | functionVersion: '', // The version of the function.
125 | invokedFunctionArn: '', // The Amazon Resource Name (ARN) that's used to invoke the function. Indicates if the invoker specified a version number or alias.
126 | memoryLimitInMB: '', // The amount of memory that's allocated for the function.
127 | awsRequestId: '', // The identifier of the invocation request.
128 | logGroupName: '', // The log group for the function.
129 | logStreamName: '', // The log stream for the function instance.
130 | callbackWaitsForEmptyEventLoop: '', // Set to false to send the response right away when the callback runs, instead of waiting for the Node.js event loop to be empty. If this is false, any outstanding events continue to run during the next invocation.
131 | }
132 |
133 | return context
134 | }
--------------------------------------------------------------------------------
/templates/create-serverless-api/scripts/modules/swaggerParser.ts:
--------------------------------------------------------------------------------
1 | import SwaggerParser from '@apidevtools/swagger-parser'
2 |
3 | export const parse = async (swaggerPath: string): Promise => { // method key와 resource key가 구분되게끔 분리.
4 | const swagger = await SwaggerParser.parse(swaggerPath)
5 | if(!swagger?.paths){
6 | throw 'no paths definition'
7 | }
8 |
9 | const paths = JSON.parse(JSON.stringify(swagger.paths))
10 | const resourceInfos = Object.keys(paths).map(pathKey => {
11 | const resourcePath = pathKey.endsWith('/') ? `root${pathKey}` : `root${pathKey}/`
12 | const resources = resourcePath.split('/')
13 | resources.pop()
14 | return { info: paths[pathKey], resources }
15 | })
16 |
17 | const { info: { title } } = swagger
18 | // console.log('sdsdsdsd: ', title)
19 | if (title === undefined) {
20 | throw new Error('project title is undefined.')
21 | }
22 |
23 | let resourceTree = {}
24 | for (const { info, resources } of resourceInfos){
25 | for (const methodKey in info){
26 | const method = info[methodKey]
27 |
28 | let treePeeker: any = null
29 | let next = resourceTree
30 | let lastResource = ''
31 | for (const resource of resources) {
32 | treePeeker = next
33 |
34 | let resourceKey = resource
35 | if (resource.match(/^\{.+\}$/)) {
36 | resourceKey = '{path}'
37 | }
38 | if(!treePeeker[resourceKey]){
39 | treePeeker[resourceKey] = { children: {}, methods: {}, alias: resource.replace('{', '').replace('}', '') }
40 | }
41 | next = treePeeker[resourceKey].children
42 | lastResource = resourceKey
43 | }
44 |
45 | treePeeker[lastResource].methods[methodKey] = JSON.parse(JSON.stringify(method))
46 | treePeeker[lastResource].methods[methodKey].name =
47 | `${title}${methodKey.replace(/\b[a-z]/, letter => letter.toUpperCase())}${
48 | resources.slice(1, resources.length)
49 | .map(str =>
50 | str.replace(/\{/g, '').replace(/\}/g, '')
51 | .replace(/\b[a-z]/, letter => letter.toUpperCase())
52 | )
53 | .join('')
54 | }`
55 | }
56 | }
57 | // console.log(JSON.stringify(resourceTree, null, 4))
58 | return resourceTree
59 | }
60 |
61 | export default {
62 | parse,
63 | }
--------------------------------------------------------------------------------
/templates/create-serverless-api/scripts/offline.ts:
--------------------------------------------------------------------------------
1 | import SwaggerParser from '@apidevtools/swagger-parser'
2 | import path from 'path'
3 | import http from 'http'
4 | import { mappingLambdaEvent, mappingLambdaContext } from './modules/map'
5 | import swaggerParser from './modules/swaggerParser'
6 |
7 | const offline = async () => {
8 | const swaggerPath = path.join(process.cwd(), 'swagger.yaml')
9 | const swagger = await SwaggerParser.parse(swaggerPath)
10 | if(!swagger?.paths){
11 | throw 'no paths definition'
12 | }
13 |
14 | const paths = JSON.parse(JSON.stringify(swagger.paths))
15 | const resourceTree = await swaggerParser.parse(swaggerPath)
16 |
17 | http.createServer(async (req, res) => {
18 | let response = { statusCode: 200, body: '' }
19 | try{
20 | const [pathUrlWithStage, queryString]: any = req.url?.split('?')
21 | const pathUrl = pathUrlWithStage?.replace('/development', '')
22 |
23 | const method = req?.method || ''
24 | const resourcePath = pathUrl.endsWith('/') ? `root${pathUrl}` : `root${pathUrl}/`
25 | const resources = resourcePath.split('/')
26 | resources.pop()
27 |
28 | let pathParameters: {[key: string]: any} = {}
29 | let pathResources = []
30 | let peeker: { children: {[key: string]: any}, methods: {[key: string]: any} } = { children: resourceTree, methods: {} }
31 | for (const resource of resources) {
32 | if(!peeker){
33 | throw new Error('404: path not found')
34 | }
35 | const nowPeekerNode = peeker.children
36 | const resourceKeys = Object.keys(nowPeekerNode)
37 | if (resourceKeys.includes('{path}') && resourceKeys.length !== 1) {
38 | throw new Error('path parameter resource must unique in same level.')
39 | }
40 |
41 | let node = nowPeekerNode[resource]
42 | let nextResource = resource
43 | if (node !== undefined) {
44 | nextResource = resource
45 | pathResources.push(nextResource)
46 | } else {
47 | nextResource = '{path}'
48 | const key = nowPeekerNode[nextResource].alias
49 | pathParameters[key] = resource
50 | pathResources.push(key)
51 | }
52 | peeker = nowPeekerNode[nextResource]
53 |
54 | }
55 | pathResources = pathResources.map(resource => resource.replace(/\b[a-z]/, (letter: string) => letter.toUpperCase()))
56 | pathResources[0] = method?.toLowerCase()
57 | const functionName = pathResources.join('')
58 |
59 | const methodInfo = peeker.methods[method?.toLowerCase()]
60 | if(!methodInfo){
61 | throw new Error('404: method definition not found')
62 | }
63 |
64 | const handlerInfo = methodInfo['x-cdk-lambda-handler']
65 | if(!handlerInfo){
66 | throw new Error('404: handler definition not found')
67 | }
68 |
69 | const apiPath = path.join(process.cwd(), 'src', handlerInfo)
70 | const { ext } = path.parse(apiPath)
71 |
72 | let apiModule = null
73 | try{
74 | apiModule = require(apiPath.replace(ext, ''))[ext.replace('.', '')]
75 | } catch (e) {
76 | throw new Error('404: module not found')
77 | }
78 |
79 | const event = { ...(await mappingLambdaEvent(req, pathParameters)) }
80 | const newContext = mappingLambdaContext(functionName)
81 | // console.log([event, newContext])
82 | response = await apiModule(event, newContext)
83 |
84 | } catch (e: any) {
85 | const eStr = e.toString().replace('Error: ', '')
86 | console.error(e)
87 | let statusCode = Number(eStr.slice(0, 3))
88 | if(isNaN(statusCode)){
89 | statusCode = 500
90 | }
91 | response.statusCode = statusCode
92 | } finally {
93 | res.statusCode = response.statusCode
94 | res.end(response.body)
95 | }
96 | }).listen(8080);
97 | // for (const [path, methods] of Object.entries(swagger.paths) as any[]) {
98 | // console.log('path', path)
99 | // for (const [method, api] of Object.entries(methods)) {
100 | // console.log('method', method)
101 | // console.log('api', api)
102 | // }
103 | // }
104 |
105 | // 1. 파싱
106 | // 2. request event, context 생성
107 | // 3. api path 탐색 및 참조
108 | // 4. response = api(event, context)
109 | // 5. response 반환
110 | }
111 |
112 | offline()
--------------------------------------------------------------------------------
/templates/create-serverless-api/scripts/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | SwaggerUI
8 |
12 |
13 |
14 |
15 |
19 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/templates/create-serverless-api/scripts/uploadLayer.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const fs = require('fs/promises')
3 | const AWS = require('aws-sdk')
4 | const { readFileSync } = require('fs-extra')
5 | const { camelCaseToDash } = require('./utils')
6 |
7 | const credentials = new AWS.SharedIniFileCredentials({
8 | profile: process.env.INFRA_ENV,
9 | })
10 | AWS.config.credentials = credentials
11 |
12 | const uploadLayer = async () => {
13 | try {
14 | const layersPath = path.join(
15 | process.cwd(),
16 | '.serverless',
17 | 'dist',
18 | 'layers'
19 | )
20 |
21 | const bucketName = camelCaseToDash(
22 | JSON.parse(
23 | readFileSync(
24 | path.join(process.cwd(), 'infra', 'projectData.json'),
25 | 'utf-8'
26 | )
27 | ).bucketName
28 | )
29 |
30 | console.info('get stored Layers...')
31 | const s3 = new AWS.S3()
32 | const params = {
33 | Bucket: bucketName,
34 | }
35 |
36 | const objectList = await s3.listObjects(params).promise()
37 | const contents = objectList?.Contents || []
38 |
39 | const s3LibraryTable = {}
40 | contents.forEach(({ Key }) => {
41 | s3LibraryTable[Key] = true
42 | })
43 |
44 | console.info('compare new Layers...')
45 | const unuseLibraryTable = { ...s3LibraryTable }
46 | const addLibraryList = []
47 | const localLibraryList = await fs.readdir(layersPath)
48 | localLibraryList.forEach((libraryName) => {
49 | if (unuseLibraryTable[libraryName]) {
50 | delete unuseLibraryTable[libraryName]
51 | } else {
52 | addLibraryList.push(libraryName)
53 | }
54 | })
55 |
56 | console.info('remove unuse Layers...')
57 | for (const Key in unuseLibraryTable) {
58 | await s3.deleteObject({ Bucket: bucketName, Key }).promise()
59 | }
60 |
61 | console.info('save new Layers...')
62 | for (const libraryName of addLibraryList) {
63 | const file = await fs.readFile(path.join(layersPath, libraryName))
64 | await s3
65 | .upload({ Bucket: bucketName, Key: libraryName, Body: file })
66 | .promise()
67 | }
68 | } catch (e) {
69 | console.error(e)
70 | process.exit(1)
71 | }
72 | }
73 |
74 | uploadLayer()
75 |
--------------------------------------------------------------------------------
/templates/create-serverless-api/scripts/uploadSwagger.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const fs = require('fs/promises')
3 | const AWS = require('aws-sdk')
4 | const { readFileSync } = require('fs-extra')
5 | const { camelCaseToDash } = require('./utils')
6 | const chalk = require('chalk')
7 |
8 | const credentials = new AWS.SharedIniFileCredentials({
9 | profile: process.env.INFRA_ENV,
10 | })
11 | AWS.config.credentials = credentials
12 |
13 | const saveSwagger = async () => {
14 | try {
15 | const swaggerPath = path.join(process.cwd(), 'swagger.yaml')
16 |
17 | const bucketName = camelCaseToDash(
18 | JSON.parse(
19 | readFileSync(
20 | path.join(process.cwd(), 'infra', 'projectData.json'),
21 | 'utf-8'
22 | )
23 | ).bucketName
24 | )
25 | const s3 = new AWS.S3()
26 |
27 | const file = await fs.readFile(path.join(swaggerPath))
28 | await s3
29 | .upload({
30 | Bucket: bucketName,
31 | Key: 'swagger.yaml',
32 | Body: file,
33 | ContentType: 'text/plain',
34 | })
35 | .promise()
36 | } catch (e) {
37 | throw new Error(e)
38 | }
39 | }
40 |
41 | const uploadApiDocument = async () => {
42 | try {
43 | const outputDataPath = path.join(
44 | process.cwd(),
45 | '.serverless',
46 | 'output.json'
47 | )
48 | const templatePath = path.join(
49 | process.cwd(),
50 | 'scripts',
51 | 'template.html'
52 | )
53 |
54 | const bucketName = camelCaseToDash(
55 | JSON.parse(
56 | readFileSync(
57 | path.join(process.cwd(), 'infra', 'projectData.json'),
58 | 'utf-8'
59 | )
60 | ).bucketName
61 | )
62 | const s3 = new AWS.S3()
63 |
64 | const outputData = await fs.readFile(outputDataPath)
65 | const data = {}
66 | Object.values(JSON.parse(outputData, 'utf-8')).forEach((stack) => {
67 | Object.keys(stack).forEach((outputKey) => {
68 | data[outputKey] = stack[outputKey]
69 | })
70 | })
71 |
72 | const s3Domain = data.bucketRegionalDomainName
73 | const swaggerPath = `https://${s3Domain}/swagger.yaml`
74 | const template = await fs.readFile(path.join(templatePath), 'utf-8')
75 | const file = template.replace('$URL_INSERT$', swaggerPath)
76 |
77 | await s3
78 | .upload({
79 | Bucket: bucketName,
80 | Key: 'index.html',
81 | Body: file,
82 | ContentType: 'text/html',
83 | })
84 | .promise()
85 | } catch (e) {
86 | throw new Error(e)
87 | }
88 | }
89 |
90 | const printResult = async () => {
91 | try {
92 | const outputDataPath = path.join(
93 | process.cwd(),
94 | '.serverless',
95 | 'output.json'
96 | )
97 |
98 | const outputData = await fs.readFile(path.join(outputDataPath), 'utf-8')
99 |
100 | const data = {}
101 | Object.values(JSON.parse(outputData)).forEach((stack) => {
102 | Object.keys(stack).forEach((outputKey) => {
103 | data[outputKey] = stack[outputKey]
104 | })
105 | })
106 |
107 | const s3Domain = data.bucketRegionalDomainName
108 | const apiEndpoint = data.apiEndpoint
109 |
110 | const apiDocumentURL = `https://${s3Domain}/index.html`
111 |
112 | const content = [
113 | `${chalk.blue('Project API Document')}: ${chalk.blue(
114 | apiDocumentURL
115 | )}`,
116 | `${chalk.blue('API Endpoint')}: ${chalk.blue(apiEndpoint)}`,
117 | ]
118 |
119 | return content
120 | } catch (e) {
121 | throw new Error(e)
122 | }
123 | }
124 |
125 | const timer = () => {
126 | const timer = setInterval(() => {
127 | process.stdout.write('.')
128 | }, 1000)
129 | const clearTimer = () => {
130 | clearInterval(timer)
131 | }
132 | return clearTimer
133 | }
134 |
135 | const loadProcess = async (name, func, params = []) => {
136 | const time = timer()
137 | process.stdout.write(`${name} start...`)
138 | try {
139 | const result = await func(...params)
140 | process.stdout.write(`complete\n`)
141 | return result
142 | } catch (e) {
143 | process.stdout.write(`error\n`)
144 | return Promise.reject(Error(e))
145 | } finally {
146 | time()
147 | }
148 | }
149 |
150 | const uploadSwagger = async () => {
151 | try {
152 | await loadProcess('saveSwagger', saveSwagger)
153 | await loadProcess('uploadApiDocument', uploadApiDocument)
154 | const contents = await loadProcess('printResult', printResult)
155 |
156 | console.info('')
157 | console.info('')
158 | console.info('')
159 | console.info('')
160 | console.info('')
161 | console.info('')
162 | console.info('')
163 | console.info('')
164 | console.info('')
165 | console.info('')
166 | console.info('')
167 | console.info('')
168 | console.info('')
169 | console.info('')
170 | console.info('')
171 | console.info('')
172 | console.info('')
173 | console.info('')
174 | console.info('')
175 | console.info('')
176 | console.info(`${chalk.blue('▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒')}`)
177 | console.info(`${chalk.blue('▒▒ ▒▒')}`)
178 | console.info(`${chalk.blue('▒▒ Create Serverless API Scripts ▒▒')}`)
179 | console.info(`${chalk.blue('▒▒ ▒▒')}`)
180 | console.info(`${chalk.blue('▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒')}`)
181 | console.info('')
182 | console.info(
183 | `${chalk.blue('The project deployment was successfully executed.')}`
184 | )
185 | console.info('')
186 | console.info(`${chalk.blue('Use api using the output results below.')}`)
187 | console.info('')
188 | for (const content of contents) {
189 | console.info(content)
190 | }
191 | } catch (e) {
192 | console.error(e)
193 | process.exit(1)
194 | }
195 | }
196 |
197 | ;(async () => await uploadSwagger())()
198 |
--------------------------------------------------------------------------------
/templates/create-serverless-api/scripts/utils.js:
--------------------------------------------------------------------------------
1 | const camelCaseToDash = (str) =>
2 | str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
3 |
4 | module.exports = {
5 | camelCaseToDash,
6 | }
7 |
--------------------------------------------------------------------------------
/templates/create-serverless-api/src/api/getBasket.ts:
--------------------------------------------------------------------------------
1 | import { APIGatewayEvent, Context } from 'aws-lambda'
2 | import fs from 'fs'
3 | import { createResponse } from '../modules/utils'
4 |
5 | export const handler = async (event: APIGatewayEvent, context: Context) => {
6 | const basket = fs.readFileSync('./modules/basket.json', 'utf-8')
7 | const basketJson = JSON.parse(basket)
8 | if (basketJson) {
9 | const response = createResponse({
10 | statusCode: 200,
11 | body: {
12 | basket: basketJson,
13 | },
14 | })
15 |
16 | return response
17 | } else {
18 | const response = createResponse({
19 | statusCode: 400,
20 | body: {
21 | message: `There is no basket.`,
22 | },
23 | })
24 |
25 | return response
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/templates/create-serverless-api/src/api/getBasketFruit.ts:
--------------------------------------------------------------------------------
1 | import { APIGatewayEvent, Context } from 'aws-lambda'
2 | import fs from 'fs'
3 | import { createResponse } from '../modules/utils'
4 |
5 | export const handler = async (event: APIGatewayEvent, context: Context) => {
6 | const { fruit } = event.pathParameters as any
7 | const basket = fs.readFileSync('./modules/basket.json', 'utf-8')
8 | const basketJson = JSON.parse(basket)
9 | if (basketJson[fruit]) {
10 | const response = createResponse({
11 | statusCode: 200,
12 | body: {
13 | message: `There are ${basketJson[fruit]} ${fruit} in the basket.`,
14 | },
15 | })
16 |
17 | return response
18 | } else {
19 | const response = createResponse({
20 | statusCode: 400,
21 | body: {
22 | message: `There is no fruit in the basket.`,
23 | },
24 | })
25 |
26 | return response
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/templates/create-serverless-api/src/api/healthCheck.ts:
--------------------------------------------------------------------------------
1 | import { APIGatewayEvent, Context } from 'aws-lambda'
2 | import { createResponse } from '../modules/utils'
3 |
4 | export const handler = async (event: APIGatewayEvent, context: Context) => {
5 | const response = createResponse({
6 | statusCode: 200,
7 | body: {
8 | health: true,
9 | },
10 | })
11 |
12 | return response
13 | }
14 |
--------------------------------------------------------------------------------
/templates/create-serverless-api/src/api/postBasketFruit.ts:
--------------------------------------------------------------------------------
1 | import { APIGatewayEvent, Context } from 'aws-lambda'
2 | import { createResponse } from '../modules/utils'
3 |
4 | export const handler = async (event: APIGatewayEvent, context: Context) => {
5 | try {
6 | const { fruit } = event.pathParameters as any
7 | const { count } = event.body as any
8 | if (count >= 1) throw new Error('count is not a valid value.')
9 |
10 | const response = createResponse({
11 | statusCode: 200,
12 | body: {
13 | message: `Succeeded in putting ${count} ${fruit} in the basket.\n`,
14 | },
15 | })
16 |
17 | return response
18 | } catch (e: any) {
19 | const response = createResponse({
20 | statusCode: 400,
21 | body: {
22 | message: e.message,
23 | },
24 | })
25 |
26 | return response
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/templates/create-serverless-api/src/authorizers/sample.ts:
--------------------------------------------------------------------------------
1 |
2 | import { AuthResponse as AuthResType, APIGatewayRequestAuthorizerEvent, APIGatewayTokenAuthorizerEvent } from 'aws-lambda'
3 |
4 | export type AuthResponse = AuthResType
5 | export interface APIGatewayTokenRequestAuthorizerEvent extends Omit, APIGatewayTokenAuthorizerEvent { }
6 |
7 | export const invoke = async (event: APIGatewayTokenRequestAuthorizerEvent): Promise => {
8 | let effect = 'Allow'
9 | return {
10 | principalId: 'user',
11 | policyDocument: {
12 | Version: '2012-10-17',
13 | Statement: [{
14 | Action: 'execute-api:Invoke',
15 | Effect: effect,
16 | Resource: '*',
17 | },
18 | {
19 | Action: "logs:CreateLogGroup",
20 | Effect: effect,
21 | Resource: '*',
22 | },
23 | {
24 | Action: [
25 | "logs:CreateLogStream",
26 | "logs:PutLogEvents"
27 | ],
28 | Effect: effect,
29 | Resource: '*',
30 | }]
31 | },
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/templates/create-serverless-api/src/modules/basket.json:
--------------------------------------------------------------------------------
1 | {
2 | "apple": 3,
3 | "banana": 0,
4 | "cherry": 1
5 | }
6 |
--------------------------------------------------------------------------------
/templates/create-serverless-api/src/modules/utils.ts:
--------------------------------------------------------------------------------
1 | export const camelCaseToDash = (str: string): string =>
2 | str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
3 |
4 | interface ICreateResponse {
5 | statusCode: number
6 | body: any
7 | }
8 | export const createResponse = ({ statusCode, body }: ICreateResponse) => ({
9 | statusCode: statusCode,
10 | headers: {
11 | 'Access-Control-Allow-Origin': '*',
12 | },
13 | body: JSON.stringify({
14 | body,
15 | }),
16 | })
17 |
--------------------------------------------------------------------------------
/templates/create-serverless-api/swagger.yaml:
--------------------------------------------------------------------------------
1 | swagger: '2.0'
2 |
3 | info:
4 | version: '1.0'
5 | title: projectFruit # Do not change it after the first deployment.
6 |
7 | paths:
8 | /:
9 | get:
10 | x-cdk-lambda-handler: ./api/healthCheck.handler # Input your API function path
11 | description: Check API health. # Input API description
12 | produces:
13 | - application/json
14 | responses:
15 | 200:
16 | description: Returns the API health.
17 | schema:
18 | type: object
19 | /basket:
20 | get:
21 | x-cdk-lambda-handler: ./api/getBasket.handler
22 | description: Check the inside of the basket..
23 | produces:
24 | - application/json
25 | responses:
26 | 200:
27 | description: Show the inside of the basket.
28 | schema:
29 | type: object
30 | 400:
31 | description: There is no basket.
32 | schema:
33 | type: object
34 | /basket/{fruit}:
35 | get:
36 | x-cdk-lambda-handler: ./api/getBasketFruit.handler
37 | description: Check how many fruits are in this basket.
38 | produces:
39 | - application/json
40 | parameters:
41 | - in: path
42 | name: fruit
43 | required: true
44 | type: string
45 | description: Fruits that you want to check.
46 | responses:
47 | 200:
48 | description: How many fruits are in this basket.
49 | schema:
50 | type: object
51 | 400:
52 | description: There is no fruit in the basket.
53 | schema:
54 | type: object
55 | post:
56 | x-cdk-lambda-handler: ./api/postBasketFruit.handler
57 | description: Check how many fruits are in this basket.
58 | produces:
59 | - application/json
60 | parameters:
61 | - in: path
62 | name: fruit
63 | required: true
64 | type: string
65 | description: Fruits that you want to check.
66 | - in: body
67 | name: payload
68 | schema:
69 | type: object
70 | required: ['count']
71 | properties:
72 | count:
73 | type: integer
74 | responses:
75 | 200:
76 | description: Succeeded in putting fruits in the basket.
77 | schema:
78 | type: object
79 | 400:
80 | description: An unknown cause error has occurred.
81 | schema:
82 | type: object
83 |
--------------------------------------------------------------------------------
/templates/create-serverless-api/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "CommonJS",
5 | "moduleResolution": "node",
6 | "lib": ["es2017", "esnext.asynciterable"],
7 | "emitDeclarationOnly": true,
8 | "declaration": true,
9 | "pretty": true,
10 | "sourceMap": true,
11 | "strict": true,
12 | "esModuleInterop": true,
13 | "allowSyntheticDefaultImports": true,
14 | "noImplicitReturns": true,
15 | "experimentalDecorators": true
16 | }
17 | }
18 |
--------------------------------------------------------------------------------