pulling in the iam-role model to the project, improving stage state syncing

This commit is contained in:
Egor 2020-07-19 18:33:09 -07:00
parent 676359c4d8
commit 440ac6c6f4
24 changed files with 645 additions and 376 deletions

8
.d.ts vendored
View file

@ -1,7 +1,6 @@
declare module '@serverless/core' { declare module '@serverless/core' {
import { Credentials } from 'aws-sdk'; import { Credentials } from 'aws-sdk';
export class Component { export class Component {
load(modulePath: string, moduleName?: string): any;
save(): void; save(): void;
state: any; state: any;
context: { context: {
@ -60,6 +59,7 @@ type BaseDeploymentOptions = {
build?: BuildOptions; build?: BuildOptions;
nextConfigDir?: string; nextConfigDir?: string;
domain?: string | string[]; domain?: string | string[];
stage?: Stage;
}; };
type BuildOptions = { type BuildOptions = {
@ -67,3 +67,9 @@ type BuildOptions = {
cmd: string; cmd: string;
args: string[]; args: string[];
}; };
type Stage = {
bucketName: string;
name: string;
versioned?: boolean;
};

113
README.md
View file

@ -1,6 +1,6 @@
# Next Deploy # Next Deploy
Effortless deployment for Next.js apps 🚀 Effortless deployment to AWS and GitHub Pages for Next.js apps 🚀
## Table of Contents ## Table of Contents
@ -10,12 +10,12 @@ Effortless deployment for Next.js apps 🚀
- [CLI](#CLI) - [CLI](#CLI)
- [Distributed Deployments](#Distributed-Deployments) - [Distributed Deployments](#Distributed-Deployments)
- [Environment](#Environment) - [Environment](#Environment)
- [GitHub](#GitHub)
- [AWS](#AWS) - [AWS](#AWS)
- [GitHub](#GitHub)
- [Configuration Options](#Configuration-Options) - [Configuration Options](#Configuration-Options)
- [Base Options](#Base-Options) - [Base Options](#Base-Options)
- [GitHub Options](#GitHub-Options)
- [AWS Options](#AWS-Options) - [AWS Options](#AWS-Options)
- [GitHub Options](#GitHub-Options)
- [Advanced Usage](#Advanced-Usage) - [Advanced Usage](#Advanced-Usage)
- [Redirecting Domains](#Redirecting-Domains) - [Redirecting Domains](#Redirecting-Domains)
- [Deployment State](#Deployment-State) - [Deployment State](#Deployment-State)
@ -25,37 +25,33 @@ Effortless deployment for Next.js apps 🚀
Make sure your environment is [configured to deploy](#Environment). Make sure your environment is [configured to deploy](#Environment).
Run your deployment with a one-liner:
- `npx next-deploy`
Optionally you can also add and run `next deploy` from your Next.js app:
- `yarn add --dev next-deploy` - `yarn add --dev next-deploy`
- `yarn next-deploy` - `yarn next-deploy`
Or as a one-liner:
- `npx next-deploy`
You can safely add the `.next-deploy` and `.next-deploy-build` directories to your `.gitignore`. You can safely add the `.next-deploy` and `.next-deploy-build` directories to your `.gitignore`.
<img src="deploy-term.gif" width="550" height="321" > <img src="deploy-term.gif" width="550" height="321" >
## Features ## Features
Next Deploy strives to support all the latest major Next.js features and allow for effortless deployment to either GitHub Pages ([static exports](#https://nextjs.org/docs/advanced-features/static-html-export) only) or AWS (full functionality). Next Deploy strives to support the latest
AWS deployments will be created by running `next build` in `serverless-trace` mode. The pre-rendered and static files will be published to S3 and served through CloudFront. The handling of application-specific routing and some advanced functionality will be provided through [Lambda@Edge](#https://aws.amazon.com/lambda/edge/) functions. - ✔ effortless deployment to AWS and GitHub pages
AWS deployments will also create new Route 53 records (if domains are configured). Note that you will need to [migrate to Route 53](#https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/MigratingDNS.html) for DNS hosting. - ✔ deployments to a custom domain
- ✔ static generation (SSG), server side rendering (SSR)
Work is underway to provide support for: - ✔ serverless AWS architecture serving your Next.js content globally via [CloudFront](https://aws.amazon.com/cloudfront/) and [Lambda@Edge](https://aws.amazon.com/lambda/edge/)
- ✔ multi-environment support
- getStaticPaths with fallback - getStaticPaths with fallback
- preview mode - preview mode
- incremental static regeneration - incremental static regeneration (beta)
## Background ## Background
Next Deploy was created to deploy web applications built using the wonderful [Next.js](https://nextjs.org/) framework. It allows teams to easily integrate with the supported engines (AWS, GitHub Pages) and keep the entirety of their code in source control. From frontend, to backend, to the deployment logic. Next Deploy was created to deploy web applications built using the wonderful [Next.js](https://nextjs.org/) framework. It allows teams to easily integrate with the supported engines (AWS, GitHub Pages) and keep the entirety of their code in source control. From frontend, to backend, to the deployment logic. Next Deploy started as a fork of serverless-next.js which itself is an orchestrator of various orphaned serverless-components.
Next Deploy started as a fork of [serverless-next.js](https://github.com/serverless-nextjs/serverless-next.js) which itself is an orchestrator of various [serverless-components](https://github.com/serverless-components/).
## CLI ## CLI
@ -88,10 +84,6 @@ Note how `build` and `deploy` can be run separately through the CLI. This allows
## Environment ## Environment
### GitHub
No specific environment configuration is necessary. By default, your app will be built and [exported](#https://nextjs.org/docs/advanced-features/static-html-export) to the `gh-pages` branch.
### AWS ### AWS
To deploy to AWS you will need to set your credentials in your environment: To deploy to AWS you will need to set your credentials in your environment:
@ -156,6 +148,10 @@ You will need the following permissions:
</details> </details>
### GitHub
No specific environment configuration is necessary. By default, your app will be built and [exported](#https://nextjs.org/docs/advanced-features/static-html-export) to the `gh-pages` branch.
## Configuration Options ## Configuration Options
The next-deploy config varies by the provider (engine) that you're deploying to. All configuration options are optional and come with sensible defaults. The next-deploy config varies by the provider (engine) that you're deploying to. All configuration options are optional and come with sensible defaults.
@ -166,15 +162,16 @@ The deployment configuration is to be provided through `next-deploy.config.js`,
All engines support the basic options: All engines support the basic options:
| Name | Type | Default | Description | | Name | Type | Default | Description |
| ------------- | --------------------- | ------- | -------------------------------------------------------------------------------------------------------- | | ------------- | --------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| build | [`Build`](#Build) | `{}` | Build related options. | | build | [`Build`](#Build) | `{}` | Build related options. |
| debug | `boolean` | `false` | Print helpful messages to | | debug | `boolean` | `false` | Print helpful messages to |
| domain | `string\|string[]` | `null` | The domain to deploy to . | | domain | `string \| string[]` | `null` | The deployment domain. |
| engine | `"aws"\|"github"` | `aws` | The platform to deploy to. | | engine | `"aws" \| "github"` | `aws` | The platform to deploy to. |
| nextConfigDir | `string` | `./` | The directory holding the `next.config.js`. | | nextConfigDir | `string` | `./` | The directory holding the `next.config.js`. |
| onPostDeploy | `() => Promise<void>` | `null` | A callback that gets called after the deployment successfully finishes. | | onPostDeploy | `() => Promise<void>` | `null` | A callback that gets called after the deployment successfully finishes. |
| onPreDeploy | `() => Promise<void>` | `null` | A callback that gets called before the deployment. | | onPreDeploy | `() => Promise<void>` | `null` | A callback that gets called before the deployment. |
| onShutdown | `() => Promise<void>` | `null` | A callback that gets called after the deployment is shutdown by a INT/QUIT/TERM signal like from ctrl+c. | | onShutdown | `() => Promise<void>` | `null` | A callback that gets called after the deployment is shutdown by a INT/QUIT/TERM signal like from ctrl+c. |
| stage | [`Stage`](#Stage) | `local` | Configure the stage ('dev', 'staging', 'production') of your deployment that will be used to synchronize its deployed state to an S3 bucket. |
#### Build #### Build
@ -184,29 +181,36 @@ All engines support the basic options:
| cmd | `string` | `node_modules/.bin/next` | The build command. | | cmd | `string` | `node_modules/.bin/next` | The build command. |
| cwd | `string` | `./` | The current working directory. | | cwd | `string` | `./` | The current working directory. |
#### Stage
| Name | Type | Default | Description |
| ---------- | --------- | -------------------------- | ----------------------------------------------------------------------------------------- |
| bucketName | `string` | `next-deploy-environments` | The S3 bucket name to sync the deployment stage to. `local` deployments don't get synced. |
| name | `string` | `local` | The name of the stage. |
| versioned | `boolean` | `false` | Whether the S3 bucket containing the stage's state should be versioned. |
### AWS Options
| Name | Type | Default | Description |
| -------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| bucketName | `string` | `*auto generated*` | Custom bucket name where static assets are stored. |
| bucketRegion | `string` | `us-east-1` | Region where you want to host your S3 bucket. |
| cloudfront | [`CloudFront`](#CloudFront) | `{}` | Additional cloudfront options. |
| description | `string` | <details>`"*lambda type* handler for the Next CloudFront distribution."`</details> | A description of the lambda. |
| domainType | `"www" \| "apex" \| "both"` | `both` | Can be one of: "**apex**" - apex domain only, don't create a www subdomain. "**www**" - www domain only, don't create an apex subdomain. "**both**" - create both www and apex domains when either one is provided. |
| memory | `number` | `512` | The amount of memory that a lambda has access to. Increasing the lambda's memory also increases its CPU allocation. The value must be a multiple of 64 MB. |
| name | `string` | `*auto generated*` | The name of the lambda function. |
| policy | `string` | <details>`arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole`</details> | The arn policy of the lambda. |
| publicDirectoryCache | `boolean \|`[`PublicDirectoryCache`](#PublicDirectoryCache) | `true` | Customize the public/static directory asset caching policy. Assigning an object lets you customize the caching policy and the types of files being cached. Assigning false disables caching. |
| runtime | `string` | `nodejs12.x` | The identifier of the lambda's runtime. |
| timeout | `number` | `10` | The amount of time that the lambda allows a function to run before stopping it. The maximum allowed value is 900 seconds. |
### Github Options ### Github Options
| Name | Type | Default | Description | | Name | Type | Default | Description |
| ------- | -------------------------------------------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------- | | ------- | -------------------------------------------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| publish | [`Publish`](https://github.com/tschaub/gh-pages#options) | `{message: "Next Deployment Update", dotfiles: true}` | The [git-hub page options](https://github.com/tschaub/gh-pages#options) to publish with. | | publish | [`Publish`](https://github.com/tschaub/gh-pages#options) | `{message: "Next Deployment Update", dotfiles: true}` | The [git-hub page options](https://github.com/tschaub/gh-pages#options) to publish with. |
### AWS Options
| Name | Type | Default | Description |
| -------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| bucketName | `string` | `*auto generated*` | Custom bucket name where static assets are stored. |
| bucketRegion | `string` | `us-east-1` | Region where you want to host your S3 bucket. |
| cloudfront | [`CloudFront`](#CloudFront) | `{}` | Additional cloudfront options. |
| description | `string` | <details>`"*lambda type* handler for the Next CloudFront distribution."`</details> | A description of the lambda. |
| domainType | `"www"\|"apex"\|"both"` | `both` | Can be one of: "**apex**" - apex domain only, don't create a www subdomain. "**www**" - www domain only, don't create an apex subdomain. "**both**" - create both www and apex domains when either one is provided. |
| memory | `number` | `512` | The amount of memory that a lambda has access to. Increasing the lambda's memory also increases its CPU allocation. The value must be a multiple of 64 MB. |
| name | `string` | `*auto generated*` | The name of the lambda function. |
| policy | `string` | <details>`arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole`</details> | The arn policy of the lambda. |
| publicDirectoryCache | `boolean\|`[`PublicDirectoryCache`](#PublicDirectoryCache) | `true` | Customize the public/static directory asset caching policy. Assigning an object lets you customize the caching policy and the types of files being cached. Assigning false disables caching. |
| runtime | `string` | `nodejs12.x` | The identifier of the lambda's runtime. |
| stage | `boolean \|`[`Stage`](#Stage) | `false` | Configure the stage ('dev', 'staging', 'production') of your deployment that will be used to synchronize its deployed state to an S3 bucket.. |
| timeout | `number` | `10` | The amount of time that the lambda allows a function to run before stopping it. The maximum allowed value is 900 seconds. |
#### PublicDirectoryCache #### PublicDirectoryCache
| Name | Type | Default | Description | | Name | Type | Default | Description |
@ -217,28 +221,21 @@ All engines support the basic options:
#### CloudFront #### CloudFront
| Name | Type | Default | Description | | Name | Type | Default | Description |
| ---------------------- | ----------------------------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | ---------------------- | ---------------------------------------------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| fieldLevelEncryptionId | `string` | `""` | The value of the ID for the field-level encryption configuration that you want to use. | | fieldLevelEncryptionId | `string` | `""` | The value of the ID for the field-level encryption configuration that you want to use. |
| forward | [`Forward`](#Forward) | `{}` | Determines the forwarding configuration | | forward | [`Forward`](#Forward) | `{}` | Determines the forwarding configuration |
| smoothStreaming | `boolean` | `false` | Indicates whether you want to distribute media files in the Microsoft Smooth Streaming format. | | smoothStreaming | `boolean` | `false` | Indicates whether you want to distribute media files in the Microsoft Smooth Streaming format. |
| priceClass | `"PriceClass_All" \| "PriceClass_200" \| "PriceClass_100"` | `PriceClass_All` | THe price class which determines the reach of the edge locations that will be used to serve your app. |
| ttl | `number` | `0` | The amount of time that you want objects to stay in CloudFront's cache before it forwards another request to determine whether the object has been updated. | | ttl | `number` | `0` | The amount of time that you want objects to stay in CloudFront's cache before it forwards another request to determine whether the object has been updated. |
| viewerCertificate | [`ViewerCertificate`](#ViewerCertificate) | `{}` | Determines the SSL/TLS configuration for communicating with viewers. | | viewerCertificate | [`ViewerCertificate`](#ViewerCertificate) | `{}` | Determines the SSL/TLS configuration for communicating with viewers. |
| viewerProtocolPolicy | `string` | `redirect-to-https` | The policy for viewers to access the content. | | viewerProtocolPolicy | `string` | `redirect-to-https` | The policy for viewers to access the content. |
| "lambda@edge" | [`LambdaAtEdge`](#LambdaAtEdge) | `{}` | Additional lambda@edge functions. | | "lambda@edge" | [`LambdaAtEdge`](#LambdaAtEdge) | `{}` | Additional lambda@edge functions. |
#### Stage
| Name | Type | Default | Description |
| ---------- | --------- | -------------------------- | ----------------------------------------------------------------------- |
| bucketName | `string` | `next-deploy-environments` | The S3 bucket name to sync the deployment stage to. |
| name | `string` | `dev` | The name of the stage. |
| versioned | `boolean` | `false` | Whether the S3 bucket containing the stage's state should be versioned. |
#### Forward #### Forward
| Name | Type | Default | Description | | Name | Type | Default | Description |
| -------------------- | ------------------ | ------- | ------------------------------------------------------------------------ | | -------------------- | -------------------- | ------- | ------------------------------------------------------------------------ |
| cookies | `string\|string[]` | `all` | Indicates which cookies should be forwarded. | | cookies | `string \| string[]` | `all` | Indicates which cookies should be forwarded. |
| queryString | `boolean` | `true` | Indicates whether the query string should be forwarded. | | queryString | `boolean` | `true` | Indicates whether the query string should be forwarded. |
| headers | `string[]` | `[]` | Headers to forward (whitelisted headers). | | headers | `string[]` | `[]` | Headers to forward (whitelisted headers). |
| queryStringCacheKeys | `string[]` | `[]` | Details of the query string parameters that you want to use for caching. | | queryStringCacheKeys | `string[]` | `[]` | Details of the query string parameters that you want to use for caching. |
@ -254,8 +251,8 @@ All engines support the basic options:
#### LambdaAtEdge #### LambdaAtEdge
| Name | Type | Default | Description | | Name | Type | Default | Description |
| -------------------- | ------------------------------------------ | ------- | ----------------------------------------------------------------------- | | -------------------- | -------------------------------------------- | ------- | ----------------------------------------------------------------------- |
| \*cloudfront event\* | `string\|{arn:string,includeBody:boolean}` | `null` | The customization for a new CloudFront event handler (lambda function). | | \*cloudfront event\* | `string \| {arn:string,includeBody:boolean}` | `null` | The customization for a new CloudFront event handler (lambda function). |
## Advanced Usage ## Advanced Usage

View file

@ -1,17 +1,19 @@
{ {
"name": "next-deploy", "name": "next-deploy",
"version": "0.3.1", "version": "1.0.0-alpha.0",
"description": "Effortless deployment for Next.js apps 🚀", "description": "Effortless deployment for Next.js apps 🚀",
"author": "Nidratech Ltd. <egor@nidratech.com>", "author": "Nidratech Ltd. <egor@nidratech.com>",
"keywords": [ "keywords": [
"next", "next",
"deploy", "deploy",
"nextjs", "nextjs",
"serverless",
"aws", "aws",
"github", "github",
"lambda", "lambda",
"lambda@edge",
"cloudfront", "cloudfront",
"github-pages" "gh-pages"
], ],
"scripts": { "scripts": {
"dev": "lerna run --parallel build:watch", "dev": "lerna run --parallel build:watch",
@ -33,8 +35,6 @@
}, },
"homepage": "https://github.com/nidratech/next-deploy#readme", "homepage": "https://github.com/nidratech/next-deploy#readme",
"dependencies": { "dependencies": {
"@serverless/aws-iam-role": "^1.0.0",
"@serverless/aws-lambda-layer": "^1.0.0",
"@serverless/core": "^1.1.2", "@serverless/core": "^1.1.2",
"@zeit/node-file-trace": "^0.8.0", "@zeit/node-file-trace": "^0.8.0",
"ansi-escapes": "^4.3.1", "ansi-escapes": "^4.3.1",
@ -79,7 +79,7 @@
"@types/webpack": "^4.41.21", "@types/webpack": "^4.41.21",
"@typescript-eslint/eslint-plugin": "^3.6.1", "@typescript-eslint/eslint-plugin": "^3.6.1",
"@typescript-eslint/parser": "^3.6.1", "@typescript-eslint/parser": "^3.6.1",
"eslint": "^7.4.0", "eslint": "^7.5.0",
"eslint-config-prettier": "^6.11.0", "eslint-config-prettier": "^6.11.0",
"eslint-plugin-prettier": "^3.1.4", "eslint-plugin-prettier": "^3.1.4",
"husky": "^4.2.5", "husky": "^4.2.5",

View file

@ -30,6 +30,11 @@ class CloudFront extends Component {
inputs.enabled = inputs.enabled === false ? false : true; inputs.enabled = inputs.enabled === false ? false : true;
inputs.comment = inputs.comment =
inputs.comment === null || inputs.comment === undefined ? '' : String(inputs.comment); inputs.comment === null || inputs.comment === undefined ? '' : String(inputs.comment);
inputs.priceClass = ['PriceClass_All', 'PriceClass_200', 'PriceClass_100'].includes(
inputs.priceClass || ''
)
? inputs.priceClass
: 'PriceClass_All';
this.context.debug( this.context.debug(
`Starting deployment of CloudFront distribution to the ${inputs.region} region.` `Starting deployment of CloudFront distribution to the ${inputs.region} region.`
@ -45,12 +50,15 @@ class CloudFront extends Component {
region: inputs.region, region: inputs.region,
}); });
this.state.id = inputs.distributionId || this.state.id;
if (this.state.id) { if (this.state.id) {
if ( if (
!equals(this.state.origins, inputs.origins) || !equals(this.state.origins, inputs.origins) ||
!equals(this.state.defaults, inputs.defaults) || !equals(this.state.defaults, inputs.defaults) ||
!equals(this.state.enabled, inputs.enabled) || !equals(this.state.enabled, inputs.enabled) ||
!equals(this.state.comment, inputs.comment) !equals(this.state.comment, inputs.comment) ||
!equals(this.state.priceClass, inputs.priceClass)
) { ) {
this.context.debug(`Updating CloudFront distribution of ID ${this.state.id}.`); this.context.debug(`Updating CloudFront distribution of ID ${this.state.id}.`);
this.state = await updateCloudFrontDistribution(cf, s3, this.state.id, inputs); this.state = await updateCloudFrontDistribution(cf, s3, this.state.id, inputs);
@ -63,8 +71,10 @@ class CloudFront extends Component {
this.state.region = inputs.region; this.state.region = inputs.region;
this.state.enabled = inputs.enabled; this.state.enabled = inputs.enabled;
this.state.comment = inputs.comment; this.state.comment = inputs.comment;
this.state.priceClass = inputs.priceClass;
this.state.origins = inputs.origins; this.state.origins = inputs.origins;
this.state.defaults = inputs.defaults; this.state.defaults = inputs.defaults;
await this.save(); await this.save();
this.context.debug(`CloudFront deployed successfully with URL: ${this.state.url}.`); this.context.debug(`CloudFront deployed successfully with URL: ${this.state.url}.`);

View file

@ -11,6 +11,7 @@ const triggersAllowedBody = ['viewer-request', 'origin-request'];
const makeCacheItem = (eventType: string, lambdaConfig: string | LambdaAtEdgeConfig) => { const makeCacheItem = (eventType: string, lambdaConfig: string | LambdaAtEdgeConfig) => {
let arn, includeBody; let arn, includeBody;
if (typeof lambdaConfig === 'string') { if (typeof lambdaConfig === 'string') {
arn = lambdaConfig; arn = lambdaConfig;
includeBody = triggersAllowedBody.includes(eventType); includeBody = triggersAllowedBody.includes(eventType);
@ -37,12 +38,18 @@ const addLambdaAtEdgeToCacheBehavior = (cacheBehavior: any, lambdaAtEdge: Lambda
); );
} }
const haveValidConfig =
//@ts-ignore
typeof lambdaAtEdge[eventType] === 'string' || lambdaAtEdge[eventType]?.arn;
if (haveValidConfig) {
cacheBehavior.LambdaFunctionAssociations.Items.push( cacheBehavior.LambdaFunctionAssociations.Items.push(
makeCacheItem(eventType, lambdaAtEdge[eventType]) makeCacheItem(eventType, lambdaAtEdge[eventType])
); );
cacheBehavior.LambdaFunctionAssociations.Quantity = cacheBehavior.LambdaFunctionAssociations.Quantity =
cacheBehavior.LambdaFunctionAssociations.Quantity + 1; cacheBehavior.LambdaFunctionAssociations.Quantity + 1;
}
}); });
}; };

View file

@ -65,7 +65,7 @@ export const createCloudFrontDistribution = async (
Items: [], Items: [],
}, },
Origins, Origins,
PriceClass: 'PriceClass_All', PriceClass: inputs.priceClass,
Enabled: inputs.enabled as boolean, Enabled: inputs.enabled as boolean,
HttpVersion: 'http2', HttpVersion: 'http2',
DefaultCacheBehavior: getDefaultCacheBehavior(Origins.Items[0].Id, inputs.defaults), DefaultCacheBehavior: getDefaultCacheBehavior(Origins.Items[0].Id, inputs.defaults),
@ -126,6 +126,7 @@ export const updateCloudFrontDistribution = async (
IfMatch: distributionConfigResponse.ETag, IfMatch: distributionConfigResponse.ETag,
DistributionConfig: { DistributionConfig: {
...distributionConfigResponse.DistributionConfig, ...distributionConfigResponse.DistributionConfig,
PriceClass: inputs.priceClass,
Enabled: inputs.enabled as boolean, Enabled: inputs.enabled as boolean,
Comment: inputs.comment as string, Comment: inputs.comment as string,
DefaultCacheBehavior: getDefaultCacheBehavior(Origins.Items[0].Id, inputs.defaults), DefaultCacheBehavior: getDefaultCacheBehavior(Origins.Items[0].Id, inputs.defaults),
@ -133,6 +134,21 @@ export const updateCloudFrontDistribution = async (
}, },
}; };
const origins = updateDistributionRequest.DistributionConfig.Origins;
const existingOriginIds = origins.Items.map((origin) => origin.Id);
Origins.Items.forEach((inputOrigin) => {
const originIndex = existingOriginIds.indexOf(inputOrigin.Id);
if (originIndex > -1) {
// replace origin with new input configuration
origins.Items.splice(originIndex, 1, inputOrigin);
} else {
origins.Items.push(inputOrigin);
origins.Quantity += 1;
}
});
if (CacheBehaviors) { if (CacheBehaviors) {
updateDistributionRequest.DistributionConfig.CacheBehaviors = CacheBehaviors; updateDistributionRequest.DistributionConfig.CacheBehaviors = CacheBehaviors;
} }

View file

@ -1,9 +1,11 @@
export type CloudFrontInputs = { export type CloudFrontInputs = {
distributionId?: string;
region?: string; region?: string;
enabled?: boolean; enabled?: boolean;
comment?: string; comment?: string;
origins: string[] | Origin[]; origins: string[] | Origin[];
defaults?: PathPatternConfig; defaults?: PathPatternConfig;
priceClass?: 'PriceClass_All' | 'PriceClass_200' | 'PriceClass_100';
}; };
type PathPatternConfig = { type PathPatternConfig = {

View file

@ -10,7 +10,6 @@ import { SubDomain } from '@next-deploy/aws-domain/types';
import AwsLambda from '@next-deploy/aws-lambda'; import AwsLambda from '@next-deploy/aws-lambda';
import { AwsLambdaInputs } from '@next-deploy/aws-lambda/types'; import { AwsLambdaInputs } from '@next-deploy/aws-lambda/types';
import { OriginRequestHandlerManifest as BuildManifest } from '@next-deploy/aws-lambda-builder/types'; import { OriginRequestHandlerManifest as BuildManifest } from '@next-deploy/aws-lambda-builder/types';
import { PathPatternConfig } from '@next-deploy/aws-cloudfront/types';
import { Origin } from '@next-deploy/aws-cloudfront/types'; import { Origin } from '@next-deploy/aws-cloudfront/types';
import { getDomains, load } from './utils'; import { getDomains, load } from './utils';
import { DeploymentResult, AwsComponentInputs, LambdaType } from '../types'; import { DeploymentResult, AwsComponentInputs, LambdaType } from '../types';
@ -136,17 +135,39 @@ class Aws extends Component {
const nextConfigPath = nextConfigDir ? resolve(nextConfigDir) : process.cwd(); const nextConfigPath = nextConfigDir ? resolve(nextConfigDir) : process.cwd();
const nextStaticPath = nextStaticDir ? resolve(nextStaticDir) : nextConfigPath; const nextStaticPath = nextStaticDir ? resolve(nextStaticDir) : nextConfigPath;
const customCloudFrontConfig: Record<string, any> = cloudfrontInput || {}; const {
defaults: cloudFrontDefaultsInputs,
origins: cloudFrontOriginsInputs,
priceClass: cloudFrontPriceClassInputs,
...cloudFrontOtherInputs
} = cloudfrontInput || {};
const cloudFrontDefaults = cloudFrontDefaultsInputs || {};
const calculatedBucketRegion = bucketRegion || 'us-east-1'; const calculatedBucketRegion = bucketRegion || 'us-east-1';
const [ const stageBucket = await load<AwsS3>('@next-deploy/aws-s3', this, 'StageStateStorage');
bucket, const stageStateBucketName =
stageBucket, (typeof stage !== 'boolean' && stage?.bucketName) || 'next-deploy-environments';
cloudfront, const isSyncStateVersioned = typeof stage !== 'boolean' && stage?.versioned;
requestEdgeLambda, const stageName = stage?.name || 'local';
defaultBuildManifest, const canSyncStageState = stageName !== 'local';
] = await Promise.all([
if (canSyncStageState) {
await stageBucket.default({
accelerated: true,
name: stageStateBucketName,
region: calculatedBucketRegion,
});
await AwsS3.syncStageStateDirectory({
name: stageName,
bucketName: stageStateBucketName,
versioned: isSyncStateVersioned,
nextConfigDir: nextConfigPath,
credentials: this.context.credentials.aws,
});
}
const [bucket, cloudfront, requestEdgeLambda, defaultBuildManifest] = await Promise.all([
load<AwsS3>('@next-deploy/aws-s3', this, 'StaticStorage'), load<AwsS3>('@next-deploy/aws-s3', this, 'StaticStorage'),
load<AwsS3>('@next-deploy/aws-s3', this, 'StageStateStorage'),
load<AwsCloudFront>('@next-deploy/aws-cloudfront', this), load<AwsCloudFront>('@next-deploy/aws-cloudfront', this),
load<AwsLambda>('@next-deploy/aws-lambda', this, 'RequestEdgeLambda'), load<AwsLambda>('@next-deploy/aws-lambda', this, 'RequestEdgeLambda'),
this.readRequestLambdaBuildManifest(nextConfigPath), this.readRequestLambdaBuildManifest(nextConfigPath),
@ -157,24 +178,6 @@ class Aws extends Component {
region: calculatedBucketRegion, region: calculatedBucketRegion,
}); });
const bucketUrl = `http://${bucketOutputs.name}.s3.${calculatedBucketRegion}.amazonaws.com`; const bucketUrl = `http://${bucketOutputs.name}.s3.${calculatedBucketRegion}.amazonaws.com`;
const stageStateBucketName =
(typeof stage !== 'boolean' && stage?.bucketName) || 'next-deploy-environments';
if (stage) {
const stageStateBucketOutputs = await stageBucket.default({
accelerated: true,
name: stageStateBucketName,
region: calculatedBucketRegion,
});
await AwsS3.syncStageStateDirectory({
name: (typeof stage !== 'boolean' && stage?.name) || 'dev',
bucketName: stageStateBucketOutputs.name,
versioned: typeof stage !== 'boolean' && stage.versioned,
nextConfigDir: nextConfigPath,
credentials: this.context.credentials.aws,
});
}
await AwsS3.uploadStaticAssets({ await AwsS3.uploadStaticAssets({
bucketName: bucketOutputs.name, bucketName: bucketOutputs.name,
@ -202,10 +205,9 @@ class Aws extends Component {
// parse origins from inputs // parse origins from inputs
let inputOrigins: any = []; let inputOrigins: any = [];
if (cloudfrontInput?.origins) { if (cloudFrontOriginsInputs) {
const origins = cloudfrontInput.origins as string[]; const origins = cloudFrontOriginsInputs as string[];
inputOrigins = origins.map(expandRelativeUrls); inputOrigins = origins.map(expandRelativeUrls);
delete cloudfrontInput.origins;
} }
const cloudFrontOrigins = [ const cloudFrontOrigins = [
@ -262,7 +264,7 @@ class Aws extends Component {
memory: getLambdaInputValue('memory', 'requestLambda', 512) as number, memory: getLambdaInputValue('memory', 'requestLambda', 512) as number,
timeout: getLambdaInputValue('timeout', 'requestLambda', 10) as number, timeout: getLambdaInputValue('timeout', 'requestLambda', 10) as number,
runtime: getLambdaInputValue('runtime', 'requestLambda', 'nodejs12.x') as string, runtime: getLambdaInputValue('runtime', 'requestLambda', 'nodejs12.x') as string,
name: getLambdaInputValue('name', 'requestLambda', undefined) as string | undefined, name: getLambdaInputValue('name', 'requestLambda', undefined) as string,
description: getLambdaInputValue( description: getLambdaInputValue(
'description', 'description',
'requestLambda', 'requestLambda',
@ -272,18 +274,12 @@ class Aws extends Component {
const requestEdgeLambdaOutputs = await requestEdgeLambda.default(defaultEdgeLambdaInput); const requestEdgeLambdaOutputs = await requestEdgeLambda.default(defaultEdgeLambdaInput);
const requestEdgeLambdaPublishOutputs = await requestEdgeLambda.publishVersion(); const requestEdgeLambdaPublishOutputs = await requestEdgeLambda.publishVersion();
let defaultCloudfrontInputs = {} as PathPatternConfig;
if (cloudfrontInput && cloudfrontInput.defaults) {
defaultCloudfrontInputs = cloudfrontInput.defaults;
delete cloudfrontInput.defaults;
}
// validate that the custom config paths match generated paths in the manifest // validate that the custom config paths match generated paths in the manifest
this.validatePathPatterns(Object.keys(customCloudFrontConfig), defaultBuildManifest); this.validatePathPatterns(Object.keys(cloudFrontOtherInputs), defaultBuildManifest);
// add any custom cloudfront configuration - this includes overrides for _next, static and api // add any custom cloudfront configuration - this includes overrides for _next, static and api
Object.entries(customCloudFrontConfig).map(([path, config]) => { Object.entries(cloudFrontOtherInputs).map(([path, config]) => {
const edgeConfig = { const edgeConfig = {
...(config['lambda@edge'] || {}), ...(config['lambda@edge'] || {}),
}; };
@ -319,16 +315,17 @@ class Aws extends Component {
}; };
const defaultLambdaAtEdgeConfig = { const defaultLambdaAtEdgeConfig = {
...(defaultCloudfrontInputs['lambda@edge'] || {}), ...(cloudFrontDefaults['lambda@edge'] || {}),
}; };
const cloudFrontOutputs = await cloudfront.default({ const cloudFrontOutputs = await cloudfront.default({
defaults: { defaults: {
ttl: 0, ttl: 0,
...defaultCloudfrontInputs, ...cloudFrontDefaults,
forward: { forward: {
cookies: 'all', cookies: 'all',
queryString: true, queryString: true,
...defaultCloudfrontInputs.forward, ...cloudFrontDefaults.forward,
}, },
allowedHttpMethods: ['HEAD', 'DELETE', 'POST', 'GET', 'OPTIONS', 'PUT', 'PATCH'], allowedHttpMethods: ['HEAD', 'DELETE', 'POST', 'GET', 'OPTIONS', 'PUT', 'PATCH'],
'lambda@edge': { 'lambda@edge': {
@ -338,6 +335,9 @@ class Aws extends Component {
compress: true, compress: true,
}, },
origins: cloudFrontOrigins, origins: cloudFrontOrigins,
...(cloudFrontPriceClassInputs && {
priceClass: cloudFrontPriceClassInputs,
}),
}); });
let appUrl = cloudFrontOutputs.url; let appUrl = cloudFrontOutputs.url;
@ -358,16 +358,16 @@ class Aws extends Component {
[subdomain]: cloudFrontOutputs as SubDomain, [subdomain]: cloudFrontOutputs as SubDomain,
}, },
domainType: domainType || 'both', domainType: domainType || 'both',
defaultCloudfrontInputs, defaultCloudfrontInputs: cloudFrontDefaults,
}); });
appUrl = domainOutputs.domains[0]; appUrl = domainOutputs.domains[0];
} }
if (stage) { if (canSyncStageState) {
await AwsS3.syncStageStateDirectory({ await AwsS3.syncStageStateDirectory({
name: (typeof stage !== 'boolean' && stage?.name) || 'dev', name: stageName,
bucketName: stageStateBucketName, bucketName: stageStateBucketName,
versioned: typeof stage !== 'boolean' && stage.versioned, versioned: isSyncStateVersioned,
nextConfigDir: nextConfigPath, nextConfigDir: nextConfigPath,
credentials: this.context.credentials.aws, credentials: this.context.credentials.aws,
syncTo: true, syncTo: true,

View file

@ -1,4 +1,4 @@
import { PublicDirectoryCache, Stage } from '@next-deploy/aws-s3/types'; import { PublicDirectoryCache } from '@next-deploy/aws-s3/types';
import { CloudFrontInputs } from '@next-deploy/aws-cloudfront/types'; import { CloudFrontInputs } from '@next-deploy/aws-cloudfront/types';
import { DomainType } from '@next-deploy/aws-domain/types'; import { DomainType } from '@next-deploy/aws-domain/types';
@ -15,7 +15,6 @@ type AwsComponentInputs = BaseDeploymentOptions & {
policy?: string; policy?: string;
domainType?: DomainType; domainType?: DomainType;
cloudfront?: CloudFrontInputs; cloudfront?: CloudFrontInputs;
stage?: boolean | Stage;
}; };
type LambdaType = 'requestLambda' | 'responseLambda'; type LambdaType = 'requestLambda' | 'responseLambda';

View file

@ -0,0 +1,13 @@
{
"name": "@next-deploy/aws-iam-role",
"version": "4.2.0",
"license": "MIT",
"main": "dist/component.js",
"types": "dist/component.d.ts",
"scripts": {
"build": "yarn clean && yarn compile",
"build:watch": "yarn compile -w",
"clean": "rm -rf ./dist",
"compile": "tsc -p tsconfig.build.json"
}
}

View file

@ -0,0 +1,150 @@
import { equals, mergeDeepRight } from 'ramda';
import { IAM } from 'aws-sdk';
import { Component } from '@serverless/core';
import { Role, Policy } from '../types';
import {
createRole,
deleteRole,
getRole,
addRolePolicy,
removeRolePolicy,
updateAssumeRolePolicy,
inputsChanged,
} from './utils';
const defaults: Role = {
service: 'lambda.amazonaws.com',
policy: {
arn: 'arn:aws:iam::aws:policy/AdministratorAccess',
},
region: 'us-east-1',
};
class IamRole extends Component {
async default(
inputs: Role = {}
): Promise<{
name: string;
arn: string;
service: string | string[];
policy: Policy;
}> {
inputs = mergeDeepRight(defaults, inputs) as Role;
const iam = new IAM({ region: inputs.region, credentials: this.context.credentials.aws });
this.context.status('Deploying');
inputs.name = this.state.name || this.context.resourceId();
this.context.debug(`Syncing role ${inputs.name} in region ${inputs.region}.`);
const prevRole = await getRole({ iam, name: inputs.name as string });
if (!prevRole) {
this.context.debug(`Creating role ${inputs.name}.`);
this.context.status('Creating');
inputs.arn = await createRole({
iam,
name: inputs.name as string,
service: inputs.service as string | string[],
policy: inputs.policy as Policy,
});
} else {
inputs.arn = prevRole.arn;
if (inputsChanged(prevRole as Role, inputs as Role)) {
this.context.status(`Updating`);
if (prevRole.service !== inputs.service) {
this.context.debug(`Updating service for role ${inputs.name}.`);
await updateAssumeRolePolicy({
iam,
name: inputs.name as string,
service: inputs.service as string | string[],
});
}
if (!equals(prevRole.policy, inputs.policy)) {
this.context.debug(`Updating policy for role ${inputs.name}.`);
await removeRolePolicy({
iam,
name: inputs.name as string,
policy: inputs.policy as Policy,
});
await addRolePolicy({
iam,
name: inputs.name as string,
policy: inputs.policy as Policy,
});
}
}
}
// todo we probably don't need this logic now that
// we auto generate unconfigurable names
if (this.state.name && this.state.name !== inputs.name) {
this.context.status(`Replacing`);
this.context.debug(`Deleting/Replacing role ${inputs.name}.`);
await deleteRole({ iam, name: this.state.name, policy: inputs.policy as Policy });
}
this.state.name = inputs.name;
this.state.arn = inputs.arn;
this.state.service = inputs.service;
this.state.policy = inputs.policy;
this.state.region = inputs.region;
await this.save();
this.context.debug(`Saved state for role ${inputs.name}.`);
const outputs = {
name: inputs.name as string,
arn: inputs.arn as string,
service: inputs.service as string | string[],
policy: inputs.policy as Policy,
};
this.context.debug(`Role ${inputs.name} was successfully deployed to region ${inputs.region}.`);
this.context.debug(`Deployed role arn is ${inputs.arn}.`);
return outputs;
}
async remove(): Promise<void | {
name: string;
arn: string;
service: string[];
policy: Policy;
}> {
this.context.status('Removing');
if (!this.state.name) {
this.context.debug('Aborting removal. Role name not found in state.');
return;
}
const iam = new IAM({
region: this.state.region,
credentials: this.context.credentials.aws,
});
this.context.debug(`Removing role ${this.state.name} from region ${this.state.region}.`);
await deleteRole({ iam, ...this.state });
this.context.debug(
`Role ${this.state.name} successfully removed from region ${this.state.region}.`
);
const outputs = {
name: this.state.name,
arn: this.state.arn,
service: this.state.service,
policy: this.state.policy,
};
this.state = {};
await this.save();
return outputs;
}
}
export default IamRole;

View file

@ -0,0 +1,193 @@
import { utils } from '@serverless/core';
import { IAM } from 'aws-sdk';
import { equals, isEmpty, has, not, pick, type } from 'ramda';
import { Policy, Role } from '../types';
export const addRolePolicy = async ({
iam,
name,
policy,
}: {
iam: IAM;
name: string;
policy: Policy;
}): Promise<void> => {
if (has('arn', policy)) {
await iam
.attachRolePolicy({
RoleName: name,
PolicyArn: policy.arn,
})
.promise();
} else if (!isEmpty(policy)) {
await iam
.putRolePolicy({
RoleName: name,
PolicyName: `${name}-policy`,
PolicyDocument: JSON.stringify(policy),
})
.promise();
}
return utils.sleep(15000);
};
export const removeRolePolicy = async ({
iam,
name,
policy,
}: {
iam: IAM;
name: string;
policy: Policy;
}): Promise<void> => {
if (has('arn', policy)) {
await iam
.detachRolePolicy({
RoleName: name,
PolicyArn: policy.arn,
})
.promise();
} else if (!isEmpty(policy)) {
await iam
.deleteRolePolicy({
RoleName: name,
PolicyName: `${name}-policy`,
})
.promise();
}
};
export const createRole = async ({
iam,
name,
service,
policy,
}: {
iam: IAM;
name: string;
service: string | string[];
policy: Policy;
}): Promise<string> => {
const assumeRolePolicyDocument = {
Version: '2012-10-17',
Statement: {
Effect: 'Allow',
Principal: {
Service: service,
},
Action: 'sts:AssumeRole',
},
};
const roleRes = await iam
.createRole({
RoleName: name,
Path: '/',
AssumeRolePolicyDocument: JSON.stringify(assumeRolePolicyDocument),
})
.promise();
await addRolePolicy({
iam,
name,
policy,
});
return roleRes.Role.Arn;
};
export const deleteRole = async ({
iam,
name,
policy,
}: {
iam: IAM;
name: string;
policy: Policy;
}): Promise<void> => {
try {
await removeRolePolicy({
iam,
name,
policy,
});
await iam
.deleteRole({
RoleName: name,
})
.promise();
} catch (error) {
if (error.message !== `Policy ${policy.arn} was not found.` && error.code !== 'NoSuchEntity') {
throw error;
}
}
};
export const getRole = async ({
iam,
name,
}: {
iam: IAM;
name: string;
}): Promise<null | undefined | Partial<Role>> => {
try {
const res = await iam.getRole({ RoleName: name }).promise();
// todo add policy
return {
name: res.Role.RoleName,
arn: res.Role.Arn,
service: JSON.parse(decodeURIComponent(res.Role.AssumeRolePolicyDocument as string))
.Statement[0].Principal.Service,
};
} catch (e) {
if (e.message.includes('cannot be found')) {
return null;
}
throw e;
}
};
export const updateAssumeRolePolicy = async ({
iam,
name,
service,
}: {
iam: IAM;
name: string;
service: string | string[];
}): Promise<void> => {
const assumeRolePolicyDocument = {
Version: '2012-10-17',
Statement: {
Effect: 'Allow',
Principal: {
Service: service,
},
Action: 'sts:AssumeRole',
},
};
await iam
.updateAssumeRolePolicy({
RoleName: name,
PolicyDocument: JSON.stringify(assumeRolePolicyDocument),
})
.promise();
};
export const inputsChanged = (prevRole: Role, role: Role): boolean => {
// todo add name and policy
const inputs = pick(['service'], role);
const prevInputs = pick(['service'], prevRole);
if (type(inputs.service) === 'Array') {
//@ts-ignore
inputs?.service?.sort();
}
if (type(prevInputs.service) === 'Array') {
//@ts-ignore
prevInputs?.service?.sort();
}
return not(equals(inputs, prevInputs));
};

View file

@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"sourceMap": false,
"removeComments": true,
"outDir": "dist"
},
"include": ["./src/", "../../.d.ts"]
}

11
packages/aws-iam-role/types.d.ts vendored Normal file
View file

@ -0,0 +1,11 @@
export type Role = {
name?: string;
region?: string;
policy?: Policy;
service?: string | string[];
arn?: string;
};
type Policy = {
arn: string;
};

View file

@ -140,6 +140,8 @@ const handler = ({
req.method = request.method; req.method = request.method;
req.rawHeaders = []; req.rawHeaders = [];
req.headers = {}; req.headers = {};
// @ts-ignore
req.connection = {};
if (request.querystring) { if (request.querystring) {
req.url = `${req.url}?${request.querystring}`; req.url = `${req.url}?${request.querystring}`;

View file

@ -11,6 +11,6 @@
"compile": "tsc -p tsconfig.build.json" "compile": "tsc -p tsconfig.build.json"
}, },
"dependencies": { "dependencies": {
"@next-deploy/aws-s3": "link:../aws-s3" "@next-deploy/aws-iam-role": "link:../aws-iam-role"
} }
} }

View file

@ -1,5 +1,4 @@
import path from 'path'; import { Lambda } from 'aws-sdk';
import aws from 'aws-sdk';
import { Component, utils } from '@serverless/core'; import { Component, utils } from '@serverless/core';
import { mergeDeepRight, pick } from 'ramda'; import { mergeDeepRight, pick } from 'ramda';
@ -11,7 +10,9 @@ import {
deleteLambda, deleteLambda,
configChanged, configChanged,
pack, pack,
load,
} from './utils'; } from './utils';
import AwsIamRole from '@next-deploy/aws-iam-role';
import { AwsLambdaInputs } from '../types'; import { AwsLambdaInputs } from '../types';
const outputsList = [ const outputsList = [
@ -27,7 +28,6 @@ const outputsList = [
'runtime', 'runtime',
'env', 'env',
'role', 'role',
'layer',
'arn', 'arn',
'region', 'region',
]; ];
@ -60,85 +60,25 @@ class LambdaComponent extends Component {
`Starting deployment of lambda ${config.name} to the ${config.region} region.` `Starting deployment of lambda ${config.name} to the ${config.region} region.`
); );
const lambda = new aws.Lambda({ const lambda = new Lambda({
region: config.region, region: config.region,
credentials: this.context.credentials.aws, credentials: this.context.credentials.aws,
}); });
const awsIamRole = await this.load('@serverless/aws-iam-role'); const awsIamRole = await load<AwsIamRole>('@next-deploy/aws-iam-role', this);
const outputsAwsIamRole = await awsIamRole.default(config.role);
// If no role exists, create a default role
let outputsAwsIamRole;
if (!config.role) {
this.context.debug(`No role provided for lambda ${config.name}.`);
outputsAwsIamRole = await awsIamRole({
service: 'lambda.amazonaws.com',
name: config.name,
policy: {
arn: 'arn:aws:iam::aws:policy/AdministratorAccess',
},
region: config.region,
});
config.role = { arn: outputsAwsIamRole.arn }; config.role = { arn: outputsAwsIamRole.arn };
} else {
outputsAwsIamRole = await awsIamRole(config.role);
config.role = { arn: outputsAwsIamRole.arn };
}
if (
config.bucket &&
SUPPORTED_RUNTIMES.includes(config.runtime) &&
(await utils.dirExists(path.join(config.code, 'node_modules')))
) {
this.context.debug(`Bucket ${config.bucket} is provided for lambda ${config.name}.`);
const layer = await this.load('@serverless/aws-lambda-layer');
const layerInputs = {
description: `${config.name} Dependencies Layer`,
code: path.join(config.code, 'node_modules'),
runtimes: SUPPORTED_RUNTIMES,
prefix: 'nodejs/node_modules',
bucket: config.bucket,
region: config.region,
};
this.context.status('Deploying Dependencies');
this.context.debug(`Packaging lambda code from ${config.code}.`);
this.context.debug(`Uploading dependencies as a layer for lambda ${config.name}.`);
const promises = [pack(config.code, config.shims, false), layer(layerInputs)];
const res = await Promise.all(promises);
config.zipPath = res[0];
config.layer = res[1];
} else {
this.context.status('Packaging'); this.context.status('Packaging');
this.context.debug(`Packaging lambda code from ${config.code}.`); this.context.debug(`Packaging lambda code from ${config.code}.`);
config.zipPath = (await pack(config.code, config.shims)) as string; config.zipPath = (await pack(config.code, config.shims)) as string;
}
config.hash = await utils.hashFile(config.zipPath as string); config.hash = await utils.hashFile(config.zipPath as string);
let deploymentBucket;
if (config.bucket) {
deploymentBucket = await this.load('@next-deploy/aws-s3');
}
const prevLambda = await getLambda({ lambda, ...config }); const prevLambda = await getLambda({ lambda, ...config });
if (!prevLambda) { if (!prevLambda) {
if (config.bucket) { this.context.status('Creating');
this.context.debug(`Uploading ${config.name} lambda package to bucket ${config.bucket}.`);
this.context.status(`Uploading`);
await deploymentBucket.upload({
name: config.bucket,
file: config.zipPath,
});
}
this.context.status(`Creating`);
this.context.debug(`Creating lambda ${config.name} in the ${config.region} region.`); this.context.debug(`Creating lambda ${config.name} in the ${config.region} region.`);
//@ts-ignore //@ts-ignore
@ -149,16 +89,7 @@ class LambdaComponent extends Component {
config.arn = prevLambda.arn; config.arn = prevLambda.arn;
if (configChanged(prevLambda, config)) { if (configChanged(prevLambda, config)) {
if (config.bucket && prevLambda.hash !== config.hash) { if (prevLambda.hash !== config.hash) {
this.context.status(`Uploading code`);
this.context.debug(`Uploading ${config.name} lambda code to bucket ${config.bucket}.`);
await deploymentBucket.upload({
name: config.bucket,
file: config.zipPath,
});
await updateLambdaCode({ lambda, ...config });
} else if (!config.bucket && prevLambda.hash !== config.hash) {
this.context.status(`Uploading code`); this.context.status(`Uploading code`);
this.context.debug(`Uploading ${config.name} lambda code.`); this.context.debug(`Uploading ${config.name} lambda code.`);
await updateLambdaCode({ lambda, ...config }); await updateLambdaCode({ lambda, ...config });
@ -193,7 +124,7 @@ class LambdaComponent extends Component {
async publishVersion(): Promise<{ version: string | undefined }> { async publishVersion(): Promise<{ version: string | undefined }> {
const { name, region, hash } = this.state; const { name, region, hash } = this.state;
const lambda = new aws.Lambda({ const lambda = new Lambda({
region: region as string, region: region as string,
credentials: this.context.credentials.aws, credentials: this.context.credentials.aws,
}); });
@ -218,16 +149,14 @@ class LambdaComponent extends Component {
const { name, region } = this.state; const { name, region } = this.state;
const lambda = new aws.Lambda({ const lambda = new Lambda({
region: region as string, region: region as string,
credentials: this.context.credentials.aws, credentials: this.context.credentials.aws,
}); });
const awsIamRole = await this.load('@serverless/aws-iam-role'); const awsIamRole = await load<AwsIamRole>('@next-deploy/aws-iam-role', this);
const layer = await this.load('@serverless/aws-lambda-layer');
await awsIamRole.remove(); await awsIamRole.remove();
await layer.remove();
this.context.debug(`Removing lambda ${name} from the ${region} region.`); this.context.debug(`Removing lambda ${name} from the ${region} region.`);
await deleteLambda({ lambda, name: name as string }); await deleteLambda({ lambda, name: name as string });

View file

@ -85,7 +85,6 @@ export const createLambda = async ({
zipPath, zipPath,
bucket, bucket,
role, role,
layer,
}: AwsLambdaInputs): Promise<{ arn?: string; hash?: string }> => { }: AwsLambdaInputs): Promise<{ arn?: string; hash?: string }> => {
const params: Lambda.Types.CreateFunctionRequest = { const params: Lambda.Types.CreateFunctionRequest = {
FunctionName: name, FunctionName: name,
@ -102,10 +101,6 @@ export const createLambda = async ({
}, },
}; };
if (layer && layer.arn) {
params.Layers = [layer.arn];
}
if (bucket) { if (bucket) {
params.Code.S3Bucket = bucket; params.Code.S3Bucket = bucket;
params.Code.S3Key = path.basename(zipPath); params.Code.S3Key = path.basename(zipPath);
@ -128,7 +123,6 @@ export const updateLambdaConfig = async ({
env, env,
description, description,
role, role,
layer,
}: AwsLambdaInputs): Promise<{ arn?: string; hash?: string }> => { }: AwsLambdaInputs): Promise<{ arn?: string; hash?: string }> => {
const functionConfigParams: Lambda.Types.UpdateFunctionConfigurationRequest = { const functionConfigParams: Lambda.Types.UpdateFunctionConfigurationRequest = {
FunctionName: name, FunctionName: name,
@ -143,10 +137,6 @@ export const updateLambdaConfig = async ({
}, },
}; };
if (layer && layer.arn) {
functionConfigParams.Layers = [layer.arn];
}
const res = await (lambda as Lambda).updateFunctionConfiguration(functionConfigParams).promise(); const res = await (lambda as Lambda).updateFunctionConfiguration(functionConfigParams).promise();
return { arn: res.FunctionArn, hash: res.CodeSha256 }; return { arn: res.FunctionArn, hash: res.CodeSha256 };
@ -285,3 +275,19 @@ export const pack = async (code: string, shims = [], packDeps = true): Promise<u
return packDir(code, outputFilePath, shims, exclude); return packDir(code, outputFilePath, shims, exclude);
}; };
// TODO: remove me, this is a duplicate of aws-component's load
export const load = async <T>(path: string, that: any, name?: string): Promise<T> => {
const EngineComponent = await import(path);
const component = new EngineComponent.default(
`${that.id}.${name || EngineComponent.default.name}`,
that.context.instance
);
await component.init();
component.context.log = () => ({});
component.context.status = () => ({});
component.context.output = () => ({});
return component;
};

View file

@ -1,4 +1,5 @@
import { Lambda } from 'aws-sdk'; import { Lambda } from 'aws-sdk';
import { Role } from '@next-deploy/aws-iam-role/types';
export type AwsLambdaInputs = { export type AwsLambdaInputs = {
name: string; name: string;
@ -12,18 +13,9 @@ export type AwsLambdaInputs = {
runtime: string; runtime: string;
env: Record<string, string>; env: Record<string, string>;
region: string; region: string;
role: Resource; role: Role;
arn?: string; arn?: string;
zipPath: string; zipPath: string;
hash?: string; hash?: string;
layer?: Resource;
lambda?: Lambda; lambda?: Lambda;
}; };
type Resource = {
policy?: {
arn: string;
};
service?: string[];
arn?: string;
};

View file

@ -23,18 +23,19 @@ const syncStageStateDirectory = async ({
const stateRootDirectory = path.join(nextConfigDir, STATE_ROOT); const stateRootDirectory = path.join(nextConfigDir, STATE_ROOT);
if (syncTo) { if (syncTo) {
const stateRootDirectoryFiles = await readDirectoryFiles(stateRootDirectory); const stateRootDirectoryFiles = await readDirectoryFiles(path.join(stateRootDirectory, stage));
const buildStateRootDirectoryFilesUploads = stateRootDirectoryFiles const buildStateRootDirectoryFilesUploads = stateRootDirectoryFiles
.filter(filterOutDirectories) .filter(filterOutDirectories)
.map(async (fileItem) => { .map(async (fileItem) =>
const s3Key = pathToPosix(path.relative(path.resolve(nextConfigDir), fileItem.path)); s3.uploadFile({
s3Key: pathToPosix(path.relative(path.resolve(nextConfigDir), fileItem.path)).replace(
return s3.uploadFile({ `${STATE_ROOT}/`,
s3Key: `${stage}/${s3Key}`, ''
),
filePath: fileItem.path, filePath: fileItem.path,
}); })
}); );
return Promise.all([...buildStateRootDirectoryFilesUploads]); return Promise.all([...buildStateRootDirectoryFilesUploads]);
} else { } else {
@ -51,7 +52,7 @@ const syncStageStateDirectory = async ({
for (const file of bucketFiles.Contents || []) { for (const file of bucketFiles.Contents || []) {
if (file.Key) { if (file.Key) {
const fileData = await s3.downloadFile({ s3Key: file.Key }); const fileData = await s3.downloadFile({ s3Key: file.Key });
files.push({ name: file.Key.replace(`${stage}/`, ''), data: fileData }); files.push({ name: path.join(STATE_ROOT, file.Key), data: fileData });
} }
} }

View file

@ -26,12 +26,6 @@ type SyncStageStateDirectoryOptions = Stage & {
syncTo?: boolean; syncTo?: boolean;
}; };
type Stage = {
bucketName: string;
name: string;
versioned?: boolean;
};
type UploadStaticAssetsOptions = { type UploadStaticAssetsOptions = {
bucketName: string; bucketName: string;
nextConfigDir: string; nextConfigDir: string;

View file

@ -10,6 +10,7 @@ import { utils } from '@serverless/core';
import { ContextMetrics, ContextConfig } from '../types'; import { ContextMetrics, ContextConfig } from '../types';
const { red, green, blue, dim: grey } = chalk; const { red, green, blue, dim: grey } = chalk;
const STATE_ROOT = '.next-deploy';
class Context { class Context {
root: string; root: string;
@ -21,26 +22,24 @@ class Context {
outputs: Record<string, unknown>; outputs: Record<string, unknown>;
metrics: ContextMetrics; metrics: ContextMetrics;
constructor(config: ContextConfig) { constructor({ root, stateRoot, credentials, debug, entity, message }: ContextConfig) {
this.root = path.resolve(config.root) || process.cwd(); this.root = path.resolve(root) || process.cwd();
this.stateRoot = config.stateRoot this.stateRoot = stateRoot ? path.resolve(stateRoot) : path.join(this.root, STATE_ROOT);
? path.resolve(config.stateRoot)
: path.join(this.root, '.next-deploy');
this.credentials = config.credentials || {}; this.credentials = credentials || {};
this.debugMode = config.debug || false; this.debugMode = debug || false;
this.state = { id: utils.randomId() }; this.state = { id: utils.randomId() };
this.id = this.state.id as string; this.id = this.state.id as string;
this.outputs = {}; this.outputs = {};
this.metrics = { this.metrics = {
entity: config.entity || 'Components', entity: entity || 'Component',
lastDebugTime: undefined, lastDebugTime: undefined,
useTimer: true, useTimer: true,
seconds: 0, seconds: 0,
status: { status: {
running: false, running: false,
message: config.message || 'Running', message: message || 'Running',
loadingDots: '', loadingDots: '',
loadingDotCount: 0, loadingDotCount: 0,
}, },

View file

@ -6,14 +6,15 @@ import Context from './context';
import { createBaseConfig } from './utils'; import { createBaseConfig } from './utils';
const deploy = async (deployConfigPath: string, methodName = 'default'): Promise<void> => { const deploy = async (deployConfigPath: string, methodName = 'default'): Promise<void> => {
const options: BaseDeploymentOptions = await import(deployConfigPath);
const { const {
debug = false, debug = false,
engine = DEFAULT_ENGINE, engine = DEFAULT_ENGINE,
onPreDeploy, onPreDeploy,
onPostDeploy, onPostDeploy,
onShutdown, onShutdown,
...componentOptions stage,
}: BaseDeploymentOptions = await import(deployConfigPath); } = options;
const engineIndex = SUPPORTED_ENGINES.findIndex(({ type }) => type === engine); const engineIndex = SUPPORTED_ENGINES.findIndex(({ type }) => type === engine);
const isInit = methodName === 'init'; const isInit = methodName === 'init';
@ -50,7 +51,7 @@ const deploy = async (deployConfigPath: string, methodName = 'default'): Promise
const context = new Context({ const context = new Context({
root: process.cwd(), root: process.cwd(),
stateRoot: path.join(process.cwd(), STATE_ROOT), stateRoot: path.join(process.cwd(), STATE_ROOT, stage?.name || 'local'),
debug, debug,
entity: engine.toUpperCase(), entity: engine.toUpperCase(),
message: method.action, message: method.action,
@ -67,7 +68,7 @@ const deploy = async (deployConfigPath: string, methodName = 'default'): Promise
context.metrics.lastDebugTime = new Date().getTime(); context.metrics.lastDebugTime = new Date().getTime();
context.statusEngineStart(); context.statusEngineStart();
const outputs = await component[methodName](componentOptions); const outputs = await component[methodName](options);
context.renderOutputs(outputs); context.renderOutputs(outputs);

134
yarn.lock
View file

@ -2029,43 +2029,7 @@
dependencies: dependencies:
"@types/node" ">= 8" "@types/node" ">= 8"
"@serverless/aws-iam-role@^1.0.0": "@serverless/core@^1.1.2":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@serverless/aws-iam-role/-/aws-iam-role-1.0.0.tgz#bab4ee9dd82f3f05b875daa9bb4b27f28038dc51"
integrity sha512-xEUF4upBki7O20QR51V/hRscBSqIsOun6ePtnJkiIEzMi6Fj51PbzySlj95+Ll5qBA5m8gDytu49BEClLfk5DQ==
dependencies:
"@serverless/core" "^1.0.0"
aws-sdk "^2.488.0"
ramda "^0.26.0"
"@serverless/aws-lambda-layer@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@serverless/aws-lambda-layer/-/aws-lambda-layer-1.0.0.tgz#be51ef02a003bd2450bd0c7db37363d75c8874db"
integrity sha512-kjh+ib257gPO9nCJUQ4ZEV54gAPd4JDWWcbGdN2aa1HVhPZf2V8qBI7bNQMknp+u2mRU3y1f7L/Qihb9bIDEOw==
dependencies:
"@serverless/aws-s3" "^1.0.0"
"@serverless/core" "^1.0.0"
archiver "^3.0.0"
aws-sdk "^2.387.0"
fs-extra "^7.0.1"
globby "^9.2.0"
ramda "^0.26.1"
"@serverless/aws-s3@^1.0.0":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@serverless/aws-s3/-/aws-s3-1.0.1.tgz#79befe9d92a083e22c99af7d33f7d1215561e6fd"
integrity sha512-RDN/ykANnUuqGUWCrQdEC9H4YVkqDCg46/ZzP7H/c6GSbRBL6Supbne3GOOPHspjxGZMJJ3Q9f7xSKHE6chKnQ==
dependencies:
"@serverless/core" "^1.0.0"
archiver "^3.0.0"
aws-sdk "^2.488.0"
fs-extra "^7.0.0"
klaw-sync "^6.0.0"
mime-types "^2.1.22"
ramda "^0.26.1"
s3-stream-upload "^2.0.2"
"@serverless/core@^1.0.0", "@serverless/core@^1.1.2":
version "1.1.2" version "1.1.2"
resolved "https://registry.yarnpkg.com/@serverless/core/-/core-1.1.2.tgz#96a2ac428d81c0459474e77db6881ebdd820065d" resolved "https://registry.yarnpkg.com/@serverless/core/-/core-1.1.2.tgz#96a2ac428d81c0459474e77db6881ebdd820065d"
integrity sha512-PY7gH+7aQ+MltcUD7SRDuQODJ9Sav9HhFJsgOiyf8IVo7XVD6FxZIsSnpMI6paSkptOB7n+0Jz03gNlEkKetQQ== integrity sha512-PY7gH+7aQ+MltcUD7SRDuQODJ9Sav9HhFJsgOiyf8IVo7XVD6FxZIsSnpMI6paSkptOB7n+0Jz03gNlEkKetQQ==
@ -2578,7 +2542,7 @@ acorn@^6.4.1:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474"
integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==
acorn@^7.1.1, acorn@^7.2.0: acorn@^7.1.1, acorn@^7.3.1:
version "7.3.1" version "7.3.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.3.1.tgz#85010754db53c3fbaf3b9ea3e083aa5c5d147ffd" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.3.1.tgz#85010754db53c3fbaf3b9ea3e083aa5c5d147ffd"
integrity sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA== integrity sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==
@ -2765,19 +2729,6 @@ archiver-utils@^2.1.0:
normalize-path "^3.0.0" normalize-path "^3.0.0"
readable-stream "^2.0.0" readable-stream "^2.0.0"
archiver@^3.0.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/archiver/-/archiver-3.1.1.tgz#9db7819d4daf60aec10fe86b16cb9258ced66ea0"
integrity sha512-5Hxxcig7gw5Jod/8Gq0OneVgLYET+oNHcxgWItq4TbhOzRLKNAFUb9edAftiMKXvXfCB0vbGrJdZDNq0dWMsxg==
dependencies:
archiver-utils "^2.1.0"
async "^2.6.3"
buffer-crc32 "^0.2.1"
glob "^7.1.4"
readable-stream "^3.4.0"
tar-stream "^2.1.0"
zip-stream "^2.1.2"
archiver@^4.0.2: archiver@^4.0.2:
version "4.0.2" version "4.0.2"
resolved "https://registry.yarnpkg.com/archiver/-/archiver-4.0.2.tgz#43c72865eadb4ddaaa2fb74852527b6a450d927c" resolved "https://registry.yarnpkg.com/archiver/-/archiver-4.0.2.tgz#43c72865eadb4ddaaa2fb74852527b6a450d927c"
@ -2934,7 +2885,7 @@ async-each@^1.0.1:
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==
async@^2.6.1, async@^2.6.3: async@^2.6.1:
version "2.6.3" version "2.6.3"
resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff"
integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==
@ -2966,7 +2917,7 @@ atob@^2.1.2:
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
aws-sdk@^2.387.0, aws-sdk@^2.488.0, aws-sdk@^2.715.0: aws-sdk@^2.715.0:
version "2.715.0" version "2.715.0"
resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.715.0.tgz#b890892098e0a4d9e7189ed341267d4a9a6e856b" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.715.0.tgz#b890892098e0a4d9e7189ed341267d4a9a6e856b"
integrity sha512-O6ytb66IXFCowp0Ng2bSPM6v/cZVOhjJWFTR1CG4ieG4IroAaVgB3YQKkfPKA0Cy9B/Ovlsm7B737iuroKsd0w== integrity sha512-O6ytb66IXFCowp0Ng2bSPM6v/cZVOhjJWFTR1CG4ieG4IroAaVgB3YQKkfPKA0Cy9B/Ovlsm7B737iuroKsd0w==
@ -3454,9 +3405,9 @@ caniuse-api@^3.0.0:
lodash.uniq "^4.5.0" lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001043, caniuse-lite@^1.0.30001093: caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001043, caniuse-lite@^1.0.30001093:
version "1.0.30001102" version "1.0.30001103"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001102.tgz#3275e7a8d09548f955f665e532df88de0b63741a" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001103.tgz#fe81536d075b97cd013d4988c9212418faa289a8"
integrity sha512-fOjqRmHjRXv1H1YD6QVLb96iKqnu17TjcLSaX64TwhGYed0P1E1CCWZ9OujbbK4Z/7zax7zAzvQidzdtjx8RcA== integrity sha512-EJkTPrZrgy712tjZ7GQDye5A67SQOyNS6X9b6GS/e5QFu5Renv5qfkx3GHq1S+vObxKzbWWYuPO/7nt4kYW/gA==
caseless@~0.12.0: caseless@~0.12.0:
version "0.12.0" version "0.12.0"
@ -3767,16 +3718,6 @@ compose-function@3.0.3:
dependencies: dependencies:
arity-n "^1.0.4" arity-n "^1.0.4"
compress-commons@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-2.1.1.tgz#9410d9a534cf8435e3fbbb7c6ce48de2dc2f0610"
integrity sha512-eVw6n7CnEMFzc3duyFVrQEuY1BlHR3rYsSztyG32ibGMW722i3C6IizEGMFmfMU+A+fALvBIwxN3czffTcdA+Q==
dependencies:
buffer-crc32 "^0.2.13"
crc32-stream "^3.0.1"
normalize-path "^3.0.0"
readable-stream "^2.3.6"
compress-commons@^3.0.0: compress-commons@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-3.0.0.tgz#833944d84596e537224dd91cf92f5246823d4f1d" resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-3.0.0.tgz#833944d84596e537224dd91cf92f5246823d4f1d"
@ -4567,9 +4508,9 @@ ecc-jsbn@~0.1.1:
safer-buffer "^2.1.0" safer-buffer "^2.1.0"
electron-to-chromium@^1.3.413, electron-to-chromium@^1.3.488: electron-to-chromium@^1.3.413, electron-to-chromium@^1.3.488:
version "1.3.499" version "1.3.500"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.499.tgz#06949f19877dafa42915e57dfeb4c1cfb86a8649" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.500.tgz#ac082dd2279251b14f1a7f6af6fd16655cf3eb7b"
integrity sha512-y7FwtQm/8xuLMnYQfBQDYzCpNn+VkSnf4c3Km5TWMNXg7JA5RQBuxmcLaKdDVcIK0K5xGIa7TlxpRt4BdNxNoA== integrity sha512-Zz8BZh4Ssb/rZBaicqpi+GOQ0uu3y+24+MxBLCk0UYt8EGoZRP4cYzYHHwXGZfrSbCU4VDjbWN+Tg+TPgOUX6Q==
elliptic@^6.0.0, elliptic@^6.5.2: elliptic@^6.0.0, elliptic@^6.5.2:
version "6.5.3" version "6.5.3"
@ -4777,22 +4718,22 @@ eslint-scope@^5.0.0, eslint-scope@^5.1.0:
esrecurse "^4.1.0" esrecurse "^4.1.0"
estraverse "^4.1.1" estraverse "^4.1.1"
eslint-utils@^2.0.0: eslint-utils@^2.0.0, eslint-utils@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27"
integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==
dependencies: dependencies:
eslint-visitor-keys "^1.1.0" eslint-visitor-keys "^1.1.0"
eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.2.0: eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==
eslint@^7.4.0: eslint@^7.5.0:
version "7.4.0" version "7.5.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.4.0.tgz#4e35a2697e6c1972f9d6ef2b690ad319f80f206f" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.5.0.tgz#9ecbfad62216d223b82ac9ffea7ef3444671d135"
integrity sha512-gU+lxhlPHu45H3JkEGgYhWhkR9wLHHEXC9FbWFnTlEkbKyZKWgWRLgf61E8zWmBuI6g5xKBph9ltg3NtZMVF8g== integrity sha512-vlUP10xse9sWt9SGRtcr1LAC67BENcQMFeV+w5EvLEoFe3xJ8cF1Skd0msziRx/VMC+72B4DxreCE+OR12OA6Q==
dependencies: dependencies:
"@babel/code-frame" "^7.0.0" "@babel/code-frame" "^7.0.0"
ajv "^6.10.0" ajv "^6.10.0"
@ -4802,9 +4743,9 @@ eslint@^7.4.0:
doctrine "^3.0.0" doctrine "^3.0.0"
enquirer "^2.3.5" enquirer "^2.3.5"
eslint-scope "^5.1.0" eslint-scope "^5.1.0"
eslint-utils "^2.0.0" eslint-utils "^2.1.0"
eslint-visitor-keys "^1.2.0" eslint-visitor-keys "^1.3.0"
espree "^7.1.0" espree "^7.2.0"
esquery "^1.2.0" esquery "^1.2.0"
esutils "^2.0.2" esutils "^2.0.2"
file-entry-cache "^5.0.1" file-entry-cache "^5.0.1"
@ -4818,7 +4759,7 @@ eslint@^7.4.0:
js-yaml "^3.13.1" js-yaml "^3.13.1"
json-stable-stringify-without-jsonify "^1.0.1" json-stable-stringify-without-jsonify "^1.0.1"
levn "^0.4.1" levn "^0.4.1"
lodash "^4.17.14" lodash "^4.17.19"
minimatch "^3.0.4" minimatch "^3.0.4"
natural-compare "^1.4.0" natural-compare "^1.4.0"
optionator "^0.9.1" optionator "^0.9.1"
@ -4831,14 +4772,14 @@ eslint@^7.4.0:
text-table "^0.2.0" text-table "^0.2.0"
v8-compile-cache "^2.0.3" v8-compile-cache "^2.0.3"
espree@^7.1.0: espree@^7.2.0:
version "7.1.0" version "7.2.0"
resolved "https://registry.yarnpkg.com/espree/-/espree-7.1.0.tgz#a9c7f18a752056735bf1ba14cb1b70adc3a5ce1c" resolved "https://registry.yarnpkg.com/espree/-/espree-7.2.0.tgz#1c263d5b513dbad0ac30c4991b93ac354e948d69"
integrity sha512-dcorZSyfmm4WTuTnE5Y7MEN1DyoPYy1ZR783QW1FJoenn7RailyWFsq/UL6ZAAA7uXurN9FIpYyUs3OfiIW+Qw== integrity sha512-H+cQ3+3JYRMEIOl87e7QdHX70ocly5iW4+dttuR8iYSPr/hXKFb+7dBsZ7+u1adC4VrnPlTkv0+OwuPnDop19g==
dependencies: dependencies:
acorn "^7.2.0" acorn "^7.3.1"
acorn-jsx "^5.2.0" acorn-jsx "^5.2.0"
eslint-visitor-keys "^1.2.0" eslint-visitor-keys "^1.3.0"
esprima@^4.0.0: esprima@^4.0.0:
version "4.0.1" version "4.0.1"
@ -5254,7 +5195,7 @@ fs-constants@^1.0.0:
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
fs-extra@^7.0.0, fs-extra@^7.0.1: fs-extra@^7.0.1:
version "7.0.1" version "7.0.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==
@ -6546,9 +6487,9 @@ lint-staged@^10.2.11:
stringify-object "^3.3.0" stringify-object "^3.3.0"
listr2@^2.1.0: listr2@^2.1.0:
version "2.2.0" version "2.2.1"
resolved "https://registry.yarnpkg.com/listr2/-/listr2-2.2.0.tgz#cb88631258abc578c7fb64e590fe5742f28e4aac" resolved "https://registry.yarnpkg.com/listr2/-/listr2-2.2.1.tgz#3a0abf78a7a9d9fb4121a541b524cb52e8dcbbba"
integrity sha512-Q8qbd7rgmEwDo1nSyHaWQeztfGsdL6rb4uh7BA+Q80AZiDET5rVntiU1+13mu2ZTDVaBVbvAD1Db11rnu3l9sg== integrity sha512-WhuhT7xpVi2otpY/OzJJ8DQhf6da8MjGiEhMdA9oQquwtsSfzZt+YKlasUBer717Uocd0oPmbPeiTD7MvGzctw==
dependencies: dependencies:
chalk "^4.0.0" chalk "^4.0.0"
cli-truncate "^2.1.0" cli-truncate "^2.1.0"
@ -6991,7 +6932,7 @@ mime-db@1.44.0:
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92"
integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==
mime-types@^2.1.12, mime-types@^2.1.22, mime-types@^2.1.27, mime-types@~2.1.19: mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.19:
version "2.1.27" version "2.1.27"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f"
integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==
@ -8632,7 +8573,7 @@ quick-lru@^4.0.1:
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==
ramda@^0.26.0, ramda@^0.26.1: ramda@^0.26.1:
version "0.26.1" version "0.26.1"
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06" resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06"
integrity sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ== integrity sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==
@ -9943,7 +9884,7 @@ tapable@^1.0.0, tapable@^1.1.3:
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
tar-stream@^2.1.0, tar-stream@^2.1.2: tar-stream@^2.1.2:
version "2.1.3" version "2.1.3"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.3.tgz#1e2022559221b7866161660f118255e20fa79e41" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.3.tgz#1e2022559221b7866161660f118255e20fa79e41"
integrity sha512-Z9yri56Dih8IaK8gncVPx4Wqt86NDmQTSh49XLZgjWpGZL9GK9HKParS2scqHCC4w6X9Gh2jwaU45V47XTKwVA== integrity sha512-Z9yri56Dih8IaK8gncVPx4Wqt86NDmQTSh49XLZgjWpGZL9GK9HKParS2scqHCC4w6X9Gh2jwaU45V47XTKwVA==
@ -10808,15 +10749,6 @@ yargs@^14.2.2:
y18n "^4.0.0" y18n "^4.0.0"
yargs-parser "^15.0.1" yargs-parser "^15.0.1"
zip-stream@^2.1.2:
version "2.1.3"
resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-2.1.3.tgz#26cc4bdb93641a8590dd07112e1f77af1758865b"
integrity sha512-EkXc2JGcKhO5N5aZ7TmuNo45budRaFGHOmz24wtJR7znbNqDPmdZtUauKX6et8KAVseAMBOyWJqEpXcHTBsh7Q==
dependencies:
archiver-utils "^2.1.0"
compress-commons "^2.1.1"
readable-stream "^3.4.0"
zip-stream@^3.0.1: zip-stream@^3.0.1:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-3.0.1.tgz#cb8db9d324a76c09f9b76b31a12a48638b0b9708" resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-3.0.1.tgz#cb8db9d324a76c09f9b76b31a12a48638b0b9708"