├── .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 | npx create-serverless-api my-api 94 |

95 | 96 | 5. Go the project directory. 97 | 98 | ``` 99 | cd my-api 100 | ``` 101 | 102 |

103 | npx create-serverless-api my-api 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 | --------------------------------------------------------------------------------