implementing the github engine for gh-page deployments

This commit is contained in:
Egor 2020-07-12 19:52:59 -07:00
parent 1f9426f799
commit 048904c000
33 changed files with 778 additions and 503 deletions

18
.d.ts vendored
View file

@ -49,3 +49,21 @@ type UploadStreamOptions = {
ContentType?: string; ContentType?: string;
CacheControl?: string; CacheControl?: string;
}; };
type BaseDeploymentOptions = {
engine?: 'aws' | 'github';
debug?: boolean;
onPreDeploy?: () => Promise<void>;
onPostDeploy?: () => Promise<void>;
onShutdown?: () => Promise<void>;
build?: BuildOptions | boolean;
nextConfigDir?: string;
domain?: string | string[];
};
type BuildOptions = {
cwd?: string;
enabled?: boolean;
cmd: string;
args: string[];
};

View file

@ -10,3 +10,4 @@ CODE_OF_CONDUCT.md
.yarnignore .yarnignore
src src
tsconfig.build.json tsconfig.build.json
examples

View file

@ -1,28 +1,40 @@
# Next Deploy # Next Deploy
Effortless deployment for Next.js apps. 🚀 Effortless deployment for Next.js apps 🚀
## Table of Contents ## Table of Contents
- [Getting started](#Getting-started) - [Getting Started](#Getting-Started)
- [Background](#Background) - [Background](#Background)
- [Configuration](#Configuration) - [Configuration](#Configuration)
- [Advanced Configuration](#Advanced-Configuration) - [AWS](#AWS)
- [Options](#Configuration-Options) - [GitHub](#GitHub)
- [Advanced Configuration](#Advanced-Configuration)
- [CLI](#CLI)
- [Configuration Options](#Configuration-Options)
- [Base Options](#Base-Options)
- [GitHub Options](#GitHub-Options)
- [AWS Options](#AWS-Options)
- [CI/CD](#CI/CD)
## Getting started ## Getting Started
[Make sure your environment is configured to deploy.](#Configuration) Make sure your environment is [configured to deploy](#Configuration).
The one-liner: `npx next-deploy` Run your deployment with a one-liner:
You can also install locally: - `npx next-deploy`
`yarn add --dev next-deploy`
`yarn next-deploy` Optionally you can also add and run `next deploy` from your Next.js app:
- `yarn add --dev next-deploy`
- `yarn next-deploy`
## Background ## Background
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/). 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 our 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](https://github.com/serverless-nextjs/serverless-next.js) which itself is an orchestrator of various orphaned [serverless-components](https://github.com/serverless-components/). Next Deploy was created out of a need for a better, strongly typed codebase and an ability to provide more advanced functionality without the [influence of corporate backers](https://opencollective.com/goserverless#section-contributions).
## Configuration ## Configuration
@ -39,31 +51,30 @@ If your account is restricted, [ensure that you have enough permissions to deplo
### GitHub ### GitHub
TODO No specific configuration is necessary. By default, your app will be built and exported to the `gh-pages` branch.
## Advanced Configuration ### Advanced Configuration
The deployment configuration is to be provided through `next-deploy.config.js`, which will be automatically created for you the first time you run `next-deploy`. The deployment configuration is to be provided through `next-deploy.config.js`, which will be automatically created for you the first time you run `next-deploy`.
```javascript ```javascript
module.exports = { module.exports = {
debug: false, engine: 'aws',
onPreDeploy: () => console.log('⚡ Starting Deployment'), debug: true,
}; };
``` ```
A more advanced configuration that sets more [configurable options](#ConfigurationOptions): A more advanced configuration that sets more [configurable options](#Configuration-Options):
```javascript ```javascript
module.exports = { module.exports = {
engine: 'aws', engine: 'aws',
onPreDeploy: () => console.log('⚡ Starting Deployment ⚡'),
onShutdown: () => console.log('⛔ Interrupted ⛔'), onShutdown: () => console.log('⛔ Interrupted ⛔'),
onPostDeploy: () => console.log('🌟 Deployment Complete 🌟'), onPostDeploy: () => console.log('🌟 Deployment Complete 🌟'),
debug: true, debug: true,
bucketName: 'bucket-name', bucketName: 'my-bucket-name',
description: 'lambda-description', description: 'My new lambda description.',
name: 'lambda-name', name: 'lambda-name',
domain: ['foobar', 'example.com'], domain: ['foobar', 'example.com'],
}; };
@ -71,18 +82,51 @@ module.exports = {
Environment variables may be substituted from `process.env` to allow for more flexibility that one would need for CI/CD. Environment variables may be substituted from `process.env` to allow for more flexibility that one would need for CI/CD.
## CLI
Next Deploy comes with a `next-deploy [argument]` CLI that you can run like `npx next-deploy` or `yarn next-deploy`.
There are currently 5 supported arguments:
**Default** (default): Runs **Build** followed by **Deploy**.
**Init**: Creates the base next-deploy.config.js configuration for your project.
**Build**: Build the application for deployment.
**Deploy**: Deploy the built application.
**Remove**: Remove, or at least attempt to remove, the deployed resources. Note that some resources (such as lambda@edge lambdas need to be cleaned up manually due to a timing constraint).
## 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.
### Base Options
All engines support the basic options: All engines support the basic options:
| Name | Type | Default | Description | | Name | Type | Default | Description |
| :----------- | :-------------------- | :---------- | :------------------------------------------------------------------------------------------------------- | | :------------ | :---------------------- | :---------- | :------------------------------------------------------------------------------------------------------- |
| engine | `"aws" \| "github"` | `"aws"` | The platform to deploy to. | | engine | `"aws"\|"github"` | `"aws"` | The platform to deploy to. |
| debug | `boolean` | `false` | Print helpful messages to | | debug | `boolean` | `false` | Print helpful messages to |
| onPreDeploy | `() => Promise<void>` | `undefined` | A callback that gets called before the deployment. | | onPreDeploy | `() => Promise<void>` | `undefined` | A callback that gets called before the deployment. |
| onPostDeploy | `() => Promise<void>` | `undefined` | A callback that gets called after the deployment successfully finishes. | | onPostDeploy | `() => Promise<void>` | `undefined` | A callback that gets called after the deployment successfully finishes. |
| onShutdown | `() => Promise<void>` | `undefined` | A callback that gets called after the deployment is shutdown by a INT/QUIT/TERM signal like from ctrl+c. | | onShutdown | `() => Promise<void>` | `undefined` | A callback that gets called after the deployment is shutdown by a INT/QUIT/TERM signal like from ctrl+c. |
| build | `BuildOptions\|boolean` | `true` | Whether a new build should be run run or not. |
| nextConfigDir | `string` | `./` | The directory holding the `next.config.js`. |
| domain | `string\|string[]` | `null` | The domain to deploy to . |
### Github Options
| Name | Type | Default | Description |
| :------------- | :-------------------------------------------------------------- | :--------------------------------- | :--------------------------------------------------------------------------------------- |
| publishOptions | [`PublishOptions`](https://github.com/tschaub/gh-pages#options) | `{message: '...', dotfiles: true}` | The [git-hub page options](https://github.com/tschaub/gh-pages#options) to publish with. |
### AWS Options
TODO
## CI/CD
TODO TODO

View file

@ -0,0 +1,3 @@
Deployments to [GitHub Pages](#https://pages.github.com/) work strictly for static sites, but still possess most of the advantages of using Next.js such as automatic page pre-loading.
An up-to-date static-rendered implementation of https://www.nidratech.com and its deployment using Next Deploy can be found at: https://github.com/nidratech/nidratech.com

View file

@ -1,7 +1,7 @@
{ {
"name": "next-deploy", "name": "next-deploy",
"version": "0.1.0", "version": "0.1.2",
"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",
@ -14,7 +14,7 @@
"github-pages" "github-pages"
], ],
"scripts": { "scripts": {
"dev": "lerna exec -- yarn build -w", "dev": "lerna run --parallel build:watch",
"build": "lerna run build", "build": "lerna run build",
"clean": "lerna run clean", "clean": "lerna run clean",
"setup": "yarn && lerna exec -- yarn", "setup": "yarn && lerna exec -- yarn",
@ -51,6 +51,7 @@
"execa": "^4.0.3", "execa": "^4.0.3",
"figures": "^3.2.0", "figures": "^3.2.0",
"fs-extra": "^9.0.1", "fs-extra": "^9.0.1",
"gh-pages": "^3.1.0",
"globby": "^11.0.1", "globby": "^11.0.1",
"klaw": "^3.0.0", "klaw": "^3.0.0",
"klaw-sync": "^6.0.0", "klaw-sync": "^6.0.0",
@ -72,13 +73,14 @@
"@types/aws-lambda": "^8.10.59", "@types/aws-lambda": "^8.10.59",
"@types/execa": "^2.0.0", "@types/execa": "^2.0.0",
"@types/fs-extra": "^9.0.1", "@types/fs-extra": "^9.0.1",
"@types/gh-pages": "^3.0.0",
"@types/klaw": "^3.0.1", "@types/klaw": "^3.0.1",
"@types/klaw-sync": "^6.0.0", "@types/klaw-sync": "^6.0.0",
"@types/mime-types": "^2.1.0", "@types/mime-types": "^2.1.0",
"@types/node": "^14.0.22", "@types/node": "^14.0.22",
"@types/path-to-regexp": "^1.7.0", "@types/path-to-regexp": "^1.7.0",
"@types/ramda": "^0.27.10", "@types/ramda": "^0.27.11",
"@types/react": "^16.9.42", "@types/react": "^16.9.43",
"@types/react-dom": "^16.9.8", "@types/react-dom": "^16.9.8",
"@types/strip-ansi": "^5.2.1", "@types/strip-ansi": "^5.2.1",
"@types/webpack": "^4.41.21", "@types/webpack": "^4.41.21",

View file

@ -1,11 +1,12 @@
{ {
"name": "@next-deploy/aws-cloudfront", "name": "@next-deploy/aws-cloudfront",
"version": "1.0.0", "version": "0.0.0",
"license": "MIT", "license": "MIT",
"main": "serverless.js", "main": "serverless.js",
"types": "dist/component.d.ts", "types": "dist/component.d.ts",
"scripts": { "scripts": {
"build": "yarn clean && yarn compile", "build": "yarn clean && yarn compile",
"build:watch": "yarn compile -w",
"clean": "rm -rf ./dist", "clean": "rm -rf ./dist",
"compile": "tsc -p tsconfig.build.json" "compile": "tsc -p tsconfig.build.json"
}, },

View file

@ -89,7 +89,7 @@ class CloudFrontComponent extends Component {
this.state = {}; this.state = {};
await this.save(); await this.save();
this.context.debug(`CloudFront distribution was successfully removed.`); this.context.debug('CloudFront distribution was successfully removed.');
} }
} }

View file

@ -1,11 +1,12 @@
{ {
"name": "@next-deploy/aws-component", "name": "@next-deploy/aws-component",
"version": "1.0.0", "version": "0.0.0",
"license": "MIT", "license": "MIT",
"main": "serverless.js", "main": "serverless.js",
"types": "dist/component.d.ts", "types": "dist/component.d.ts",
"scripts": { "scripts": {
"build": "yarn clean && yarn compile", "build": "yarn clean && yarn compile",
"build:watch": "yarn compile -w",
"clean": "rm -rf ./dist", "clean": "rm -rf ./dist",
"compile": "tsc -p tsconfig.build.json" "compile": "tsc -p tsconfig.build.json"
}, },

View file

@ -12,13 +12,7 @@ import {
} from '@next-deploy/aws-lambda-builder/types'; } from '@next-deploy/aws-lambda-builder/types';
import { PathPatternConfig } from '@next-deploy/aws-cloudfront/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 { import { DeploymentResult, AwsComponentInputs, LambdaType, LambdaInput } from '../types';
DeploymentResult,
BuildOptions,
AwsComponentInputs,
LambdaType,
LambdaInput,
} from '../types';
export const BUILD_DIR = '.next-deploy-build'; export const BUILD_DIR = '.next-deploy-build';
export const DEFAULT_LAMBDA_CODE_DIR = `${BUILD_DIR}/default-lambda`; export const DEFAULT_LAMBDA_CODE_DIR = `${BUILD_DIR}/default-lambda`;

View file

@ -2,9 +2,7 @@ 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';
type AwsComponentInputs = { type AwsComponentInputs = BaseDeploymentOptions & {
build?: BuildOptions | boolean;
nextConfigDir?: string;
nextStaticDir?: string; nextStaticDir?: string;
bucketName?: string; bucketName?: string;
bucketRegion?: string; bucketRegion?: string;
@ -15,18 +13,10 @@ type AwsComponentInputs = {
runtime?: string | { defaultLambda?: string; apiLambda?: string }; runtime?: string | { defaultLambda?: string; apiLambda?: string };
description?: string; description?: string;
policy?: string; policy?: string;
domain?: string | string[];
domainType?: DomainType; domainType?: DomainType;
cloudfront?: CloudFrontInputs; cloudfront?: CloudFrontInputs;
}; };
type BuildOptions = {
cwd?: string;
enabled?: boolean;
cmd: string;
args: string[];
};
type LambdaType = 'defaultLambda' | 'apiLambda'; type LambdaType = 'defaultLambda' | 'apiLambda';
type LambdaInput = { type LambdaInput = {

View file

@ -1,11 +1,12 @@
{ {
"name": "@next-deploy/aws-domain", "name": "@next-deploy/aws-domain",
"version": "1.0.0", "version": "0.0.0",
"license": "MIT", "license": "MIT",
"main": "serverless.js", "main": "serverless.js",
"types": "dist/component.d.ts", "types": "dist/component.d.ts",
"scripts": { "scripts": {
"build": "yarn clean && yarn compile", "build": "yarn clean && yarn compile",
"build:watch": "yarn compile -w",
"clean": "rm -rf ./dist", "clean": "rm -rf ./dist",
"compile": "tsc -p tsconfig.build.json" "compile": "tsc -p tsconfig.build.json"
}, },

View file

@ -1,11 +1,12 @@
{ {
"name": "@next-deploy/aws-lambda-builder", "name": "@next-deploy/aws-lambda-builder",
"version": "1.0.0", "version": "0.0.0",
"license": "MIT", "license": "MIT",
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",
"scripts": { "scripts": {
"build": "yarn clean && yarn compile", "build": "yarn clean && yarn compile",
"build:watch": "yarn compile -w",
"clean": "rm -rf ./dist", "clean": "rm -rf ./dist",
"compile": "tsc -p tsconfig.build.json" "compile": "tsc -p tsconfig.build.json"
}, },

View file

@ -1,349 +0,0 @@
import nodeFileTrace, { NodeFileTraceReasons } from '@zeit/node-file-trace';
import execa from 'execa';
import fse from 'fs-extra';
import path, { join } from 'path';
import { pathToRegexp } from 'path-to-regexp';
import getAllFiles from './lib/getAllFilesInDirectory';
import { getSortedRoutes } from './lib/sortedRoutes';
import {
BuildOptions,
OriginRequestDefaultHandlerManifest,
OriginRequestApiHandlerManifest,
} from '../types';
import expressifyDynamicRoute from './lib/expressifyDynamicRoute';
import createServerlessConfig from './lib/createServerlessConfig';
export const DEFAULT_LAMBDA_CODE_DIR = 'default-lambda';
export const API_LAMBDA_CODE_DIR = 'api-lambda';
const pathToPosix = (path: string): string => path.replace(/\\/g, '/');
const normalizeNodeModules = (path: string): string => path.substring(path.indexOf('node_modules'));
// Identify /[param]/ in route string
const isDynamicRoute = (route: string): boolean => /\/\[[^\/]+?\](?=\/|$)/.test(route);
const pathToRegexStr = (path: string): string =>
pathToRegexp(path)
.toString()
.replace(/\/(.*)\/\i/, '$1');
const defaultBuildOptions = {
args: [],
cwd: process.cwd(),
cmd: './node_modules/.bin/next',
};
class Builder {
nextConfigDir: string;
dotNextDir: string;
serverlessDir: string;
outputDir: string;
buildOptions: BuildOptions = defaultBuildOptions;
constructor(nextConfigDir: string, outputDir: string, buildOptions?: BuildOptions) {
this.nextConfigDir = path.resolve(nextConfigDir);
this.dotNextDir = path.join(this.nextConfigDir, '.next');
this.serverlessDir = path.join(this.dotNextDir, 'serverless');
this.outputDir = outputDir;
if (buildOptions) {
this.buildOptions = buildOptions;
}
}
async readPublicFiles(): Promise<string[]> {
const dirExists = await fse.pathExists(join(this.nextConfigDir, 'public'));
if (dirExists) {
return getAllFiles(join(this.nextConfigDir, 'public'))
.map((e) => e.replace(this.nextConfigDir, ''))
.map((e) => e.split(path.sep).slice(2).join('/'));
} else {
return [];
}
}
async readPagesManifest(): Promise<{ [key: string]: string }> {
const path = join(this.serverlessDir, 'pages-manifest.json');
const hasServerlessPageManifest = await fse.pathExists(path);
if (!hasServerlessPageManifest) {
return Promise.reject(
"pages-manifest not found. Check if `next.config.js` target is set to 'serverless'"
);
}
const pagesManifest = await fse.readJSON(path);
const pagesManifestWithoutDynamicRoutes = Object.keys(pagesManifest).reduce(
(acc: { [key: string]: string }, route: string) => {
if (isDynamicRoute(route)) {
return acc;
}
acc[route] = pagesManifest[route];
return acc;
},
{}
);
const dynamicRoutedPages = Object.keys(pagesManifest).filter(isDynamicRoute);
const sortedDynamicRoutedPages = getSortedRoutes(dynamicRoutedPages);
const sortedPagesManifest = pagesManifestWithoutDynamicRoutes;
sortedDynamicRoutedPages.forEach((route) => {
sortedPagesManifest[route] = pagesManifest[route];
});
return sortedPagesManifest;
}
copyLambdaHandlerDependencies(
fileList: string[],
reasons: NodeFileTraceReasons,
handlerDirectory: string
): Promise<void>[] {
return (
fileList
// exclude "initial" files from lambda artifact. These are just the pages themselves which are copied over separately
.filter(
(file) => file !== 'package.json' && (!reasons[file] || reasons[file].type !== 'initial')
)
.map((filePath: string) => {
const resolvedFilePath = path.resolve(filePath);
const dst = normalizeNodeModules(path.relative(this.serverlessDir, resolvedFilePath));
return fse.copy(resolvedFilePath, join(this.outputDir, handlerDirectory, dst));
})
);
}
async buildDefaultLambda(buildManifest: OriginRequestDefaultHandlerManifest): Promise<void[]> {
const ignoreAppAndDocumentPages = (page: string): boolean => {
const basename = path.basename(page);
return basename !== '_app.js' && basename !== '_document.js';
};
const allSsrPages = [
...Object.values(buildManifest.pages.ssr.nonDynamic),
...Object.values(buildManifest.pages.ssr.dynamic).map((entry) => entry.file),
].filter(ignoreAppAndDocumentPages);
const ssrPages = Object.values(allSsrPages).map((pageFile) =>
path.join(this.serverlessDir, pageFile)
);
const { fileList, reasons } = await nodeFileTrace(ssrPages, {
base: process.cwd(),
});
const copyTraces = this.copyLambdaHandlerDependencies(
fileList,
reasons,
DEFAULT_LAMBDA_CODE_DIR
);
return Promise.all([
...copyTraces,
fse.copy(
require.resolve('@next-deploy/aws-lambda-builder/dist/default-handler.js'),
join(this.outputDir, DEFAULT_LAMBDA_CODE_DIR, 'index.js')
),
fse.copy(
require.resolve('@next-deploy/aws-lambda-builder/dist/compat.js'),
join(this.outputDir, DEFAULT_LAMBDA_CODE_DIR, 'compat.js')
),
fse.writeJson(join(this.outputDir, DEFAULT_LAMBDA_CODE_DIR, 'manifest.json'), buildManifest),
fse.copy(
join(this.serverlessDir, 'pages'),
join(this.outputDir, DEFAULT_LAMBDA_CODE_DIR, 'pages'),
{
filter: (file: string) => {
const isNotPrerenderedHTMLPage = path.extname(file) !== '.html';
const isNotStaticPropsJSONFile = path.extname(file) !== '.json';
const isNotApiPage = pathToPosix(file).indexOf('pages/api') === -1;
return isNotApiPage && isNotPrerenderedHTMLPage && isNotStaticPropsJSONFile;
},
}
),
fse.copy(
join(this.dotNextDir, 'prerender-manifest.json'),
join(this.outputDir, DEFAULT_LAMBDA_CODE_DIR, 'prerender-manifest.json')
),
]);
}
async buildApiLambda(apiBuildManifest: OriginRequestApiHandlerManifest): Promise<void[]> {
const allApiPages = [
...Object.values(apiBuildManifest.apis.nonDynamic),
...Object.values(apiBuildManifest.apis.dynamic).map((entry) => entry.file),
];
const apiPages = Object.values(allApiPages).map((pageFile) =>
path.join(this.serverlessDir, pageFile)
);
const { fileList, reasons } = await nodeFileTrace(apiPages, {
base: process.cwd(),
});
const copyTraces = this.copyLambdaHandlerDependencies(fileList, reasons, API_LAMBDA_CODE_DIR);
return Promise.all([
...copyTraces,
fse.copy(
require.resolve('@next-deploy/aws-lambda-builder/dist/api-handler.js'),
join(this.outputDir, API_LAMBDA_CODE_DIR, 'index.js')
),
fse.copy(
require.resolve('@next-deploy/aws-lambda-builder/dist/compat.js'),
join(this.outputDir, API_LAMBDA_CODE_DIR, 'compat.js')
),
fse.copy(
join(this.serverlessDir, 'pages/api'),
join(this.outputDir, API_LAMBDA_CODE_DIR, 'pages/api')
),
fse.writeJson(join(this.outputDir, API_LAMBDA_CODE_DIR, 'manifest.json'), apiBuildManifest),
]);
}
async prepareBuildManifests(): Promise<{
defaultBuildManifest: OriginRequestDefaultHandlerManifest;
apiBuildManifest: OriginRequestApiHandlerManifest;
}> {
const pagesManifest = await this.readPagesManifest();
const buildId = await fse.readFile(path.join(this.dotNextDir, 'BUILD_ID'), 'utf-8');
const defaultBuildManifest: OriginRequestDefaultHandlerManifest = {
buildId,
pages: {
ssr: {
dynamic: {},
nonDynamic: {},
},
html: {
dynamic: {},
nonDynamic: {},
},
},
publicFiles: {},
};
const apiBuildManifest: OriginRequestApiHandlerManifest = {
apis: {
dynamic: {},
nonDynamic: {},
},
};
const ssrPages = defaultBuildManifest.pages.ssr;
const htmlPages = defaultBuildManifest.pages.html;
const apiPages = apiBuildManifest.apis;
const isHtmlPage = (path: string): boolean => path.endsWith('.html');
const isApiPage = (path: string): boolean => path.startsWith('pages/api');
Object.entries(pagesManifest).forEach(([route, pageFile]) => {
const dynamicRoute = isDynamicRoute(route);
const expressRoute = dynamicRoute ? expressifyDynamicRoute(route) : null;
if (isHtmlPage(pageFile)) {
if (dynamicRoute) {
const route = expressRoute as string;
htmlPages.dynamic[route] = {
file: pageFile,
regex: pathToRegexStr(route),
};
} else {
htmlPages.nonDynamic[route] = pageFile;
}
} else if (isApiPage(pageFile)) {
if (dynamicRoute) {
const route = expressRoute as string;
apiPages.dynamic[route] = {
file: pageFile,
regex: pathToRegexStr(route),
};
} else {
apiPages.nonDynamic[route] = pageFile;
}
} else if (dynamicRoute) {
const route = expressRoute as string;
ssrPages.dynamic[route] = {
file: pageFile,
regex: pathToRegexStr(route),
};
} else {
ssrPages.nonDynamic[route] = pageFile;
}
});
const publicFiles = await this.readPublicFiles();
publicFiles.forEach((pf) => (defaultBuildManifest.publicFiles[`/${pf}`] = pf));
return {
defaultBuildManifest,
apiBuildManifest,
};
}
async cleanupDotNext(): Promise<void> {
const exists = await fse.pathExists(this.dotNextDir);
if (exists) {
const fileItems = await fse.readdir(this.dotNextDir);
await Promise.all(
fileItems
.filter(
(fileItem) => fileItem !== 'cache' // avoid deleting the cache folder as that would lead to slow builds!
)
.map((fileItem) => fse.remove(join(this.dotNextDir, fileItem)))
);
}
}
async build(debug?: (message: string) => void): Promise<void> {
const { cmd, args, cwd } = Object.assign(defaultBuildOptions, this.buildOptions);
await this.cleanupDotNext();
await fse.emptyDir(join(this.outputDir, DEFAULT_LAMBDA_CODE_DIR));
await fse.emptyDir(join(this.outputDir, API_LAMBDA_CODE_DIR));
const { restoreUserConfig } = await createServerlessConfig(cwd, path.join(this.nextConfigDir));
try {
if (debug) {
const { stdout: nextVersion } = await execa(cmd, ['--version'], {
cwd,
});
debug(`Starting a new build with ${nextVersion}`);
console.log();
}
const subprocess = execa(cmd, args, {
cwd,
});
if (debug && subprocess.stdout) {
subprocess.stdout.pipe(process.stdout);
}
await subprocess;
} finally {
await restoreUserConfig();
}
const { defaultBuildManifest, apiBuildManifest } = await this.prepareBuildManifests();
await this.buildDefaultLambda(defaultBuildManifest);
const hasAPIPages =
Object.keys(apiBuildManifest.apis.nonDynamic).length > 0 ||
Object.keys(apiBuildManifest.apis.dynamic).length > 0;
if (hasAPIPages) {
await this.buildApiLambda(apiBuildManifest);
}
}
}
export default Builder;

View file

@ -135,7 +135,7 @@ const handler = ({
} as CloudFrontResultResponse; } as CloudFrontResultResponse;
const newStream = new Stream.Readable(); const newStream = new Stream.Readable();
const req = Object.assign(newStream, IncomingMessage.prototype) as IncomingMessage; const req = { ...newStream, ...IncomingMessage.prototype } as IncomingMessage;
req.url = request.uri; req.url = request.uri;
req.method = request.method; req.method = request.method;
req.rawHeaders = []; req.rawHeaders = [];
@ -179,7 +179,7 @@ const handler = ({
response.status = status.toString(); response.status = status.toString();
if (headers) { if (headers) {
res.headers = Object.assign(res.headers, headers); res.headers = { ...res.headers, ...headers };
} }
return res; return res;

View file

@ -1,3 +1,354 @@
import Builder from './builder'; import nodeFileTrace, { NodeFileTraceReasons } from '@zeit/node-file-trace';
import execa from 'execa';
import {
emptyDir,
pathExists,
readJSON,
copy,
writeJson,
remove,
readdir,
readFile,
} from 'fs-extra';
import { join, resolve, sep, extname, relative, basename } from 'path';
import { pathToRegexp } from 'path-to-regexp';
import getAllFiles from './lib/getAllFilesInDirectory';
import { getSortedRoutes } from './lib/sortedRoutes';
import { OriginRequestDefaultHandlerManifest, OriginRequestApiHandlerManifest } from '../types';
import expressifyDynamicRoute from './lib/expressifyDynamicRoute';
import createServerlessConfig from './lib/createServerlessConfig';
export const DEFAULT_LAMBDA_CODE_DIR = 'default-lambda';
export const API_LAMBDA_CODE_DIR = 'api-lambda';
const pathToPosix = (path: string): string => path.replace(/\\/g, '/');
const normalizeNodeModules = (path: string): string => path.substring(path.indexOf('node_modules'));
// Identify /[param]/ in route string
const isDynamicRoute = (route: string): boolean => /\/\[[^\/]+?\](?=\/|$)/.test(route);
const pathToRegexStr = (path: string): string =>
pathToRegexp(path)
.toString()
.replace(/\/(.*)\/\i/, '$1');
const defaultBuildOptions = {
args: [],
cwd: process.cwd(),
cmd: './node_modules/.bin/next',
};
class Builder {
nextConfigDir: string;
dotNextDir: string;
serverlessDir: string;
outputDir: string;
buildOptions: BuildOptions = defaultBuildOptions;
constructor(nextConfigDir: string, outputDir: string, buildOptions?: BuildOptions) {
this.nextConfigDir = resolve(nextConfigDir);
this.dotNextDir = join(this.nextConfigDir, '.next');
this.serverlessDir = join(this.dotNextDir, 'serverless');
this.outputDir = outputDir;
if (buildOptions) {
this.buildOptions = buildOptions;
}
}
async readPublicFiles(): Promise<string[]> {
const dirExists = await pathExists(join(this.nextConfigDir, 'public'));
if (dirExists) {
return getAllFiles(join(this.nextConfigDir, 'public'))
.map((e) => e.replace(this.nextConfigDir, ''))
.map((e) => e.split(sep).slice(2).join('/'));
} else {
return [];
}
}
async readPagesManifest(): Promise<{ [key: string]: string }> {
const path = join(this.serverlessDir, 'pages-manifest.json');
const hasServerlessPageManifest = await pathExists(path);
if (!hasServerlessPageManifest) {
return Promise.reject(
"pages-manifest not found. Check if `next.config.js` target is set to 'serverless'"
);
}
const pagesManifest = await readJSON(path);
const pagesManifestWithoutDynamicRoutes = Object.keys(pagesManifest).reduce(
(acc: { [key: string]: string }, route: string) => {
if (isDynamicRoute(route)) {
return acc;
}
acc[route] = pagesManifest[route];
return acc;
},
{}
);
const dynamicRoutedPages = Object.keys(pagesManifest).filter(isDynamicRoute);
const sortedDynamicRoutedPages = getSortedRoutes(dynamicRoutedPages);
const sortedPagesManifest = pagesManifestWithoutDynamicRoutes;
sortedDynamicRoutedPages.forEach((route) => {
sortedPagesManifest[route] = pagesManifest[route];
});
return sortedPagesManifest;
}
copyLambdaHandlerDependencies(
fileList: string[],
reasons: NodeFileTraceReasons,
handlerDirectory: string
): Promise<void>[] {
return (
fileList
// exclude "initial" files from lambda artifact. These are just the pages themselves which are copied over separately
.filter(
(file) => file !== 'package.json' && (!reasons[file] || reasons[file].type !== 'initial')
)
.map((filePath: string) => {
const resolvedFilePath = resolve(filePath);
const dst = normalizeNodeModules(relative(this.serverlessDir, resolvedFilePath));
return copy(resolvedFilePath, join(this.outputDir, handlerDirectory, dst));
})
);
}
async buildDefaultLambda(buildManifest: OriginRequestDefaultHandlerManifest): Promise<void[]> {
const ignoreAppAndDocumentPages = (page: string): boolean => {
const pageBasename = basename(page);
return pageBasename !== '_app.js' && pageBasename !== '_document.js';
};
const allSsrPages = [
...Object.values(buildManifest.pages.ssr.nonDynamic),
...Object.values(buildManifest.pages.ssr.dynamic).map((entry) => entry.file),
].filter(ignoreAppAndDocumentPages);
const ssrPages = Object.values(allSsrPages).map((pageFile) =>
join(this.serverlessDir, pageFile)
);
const { fileList, reasons } = await nodeFileTrace(ssrPages, {
base: process.cwd(),
});
const copyTraces = this.copyLambdaHandlerDependencies(
fileList,
reasons,
DEFAULT_LAMBDA_CODE_DIR
);
return Promise.all([
...copyTraces,
copy(
require.resolve('@next-deploy/aws-lambda-builder/dist/default-handler.js'),
join(this.outputDir, DEFAULT_LAMBDA_CODE_DIR, 'index.js')
),
copy(
require.resolve('@next-deploy/aws-lambda-builder/dist/compat.js'),
join(this.outputDir, DEFAULT_LAMBDA_CODE_DIR, 'compat.js')
),
writeJson(join(this.outputDir, DEFAULT_LAMBDA_CODE_DIR, 'manifest.json'), buildManifest),
copy(
join(this.serverlessDir, 'pages'),
join(this.outputDir, DEFAULT_LAMBDA_CODE_DIR, 'pages'),
{
filter: (file: string) => {
const isNotPrerenderedHTMLPage = extname(file) !== '.html';
const isNotStaticPropsJSONFile = extname(file) !== '.json';
const isNotApiPage = pathToPosix(file).indexOf('pages/api') === -1;
return isNotApiPage && isNotPrerenderedHTMLPage && isNotStaticPropsJSONFile;
},
}
),
copy(
join(this.dotNextDir, 'prerender-manifest.json'),
join(this.outputDir, DEFAULT_LAMBDA_CODE_DIR, 'prerender-manifest.json')
),
]);
}
async buildApiLambda(apiBuildManifest: OriginRequestApiHandlerManifest): Promise<void[]> {
const allApiPages = [
...Object.values(apiBuildManifest.apis.nonDynamic),
...Object.values(apiBuildManifest.apis.dynamic).map((entry) => entry.file),
];
const apiPages = Object.values(allApiPages).map((pageFile) =>
join(this.serverlessDir, pageFile)
);
const { fileList, reasons } = await nodeFileTrace(apiPages, {
base: process.cwd(),
});
const copyTraces = this.copyLambdaHandlerDependencies(fileList, reasons, API_LAMBDA_CODE_DIR);
return Promise.all([
...copyTraces,
copy(
require.resolve('@next-deploy/aws-lambda-builder/dist/api-handler.js'),
join(this.outputDir, API_LAMBDA_CODE_DIR, 'index.js')
),
copy(
require.resolve('@next-deploy/aws-lambda-builder/dist/compat.js'),
join(this.outputDir, API_LAMBDA_CODE_DIR, 'compat.js')
),
copy(
join(this.serverlessDir, 'pages/api'),
join(this.outputDir, API_LAMBDA_CODE_DIR, 'pages/api')
),
writeJson(join(this.outputDir, API_LAMBDA_CODE_DIR, 'manifest.json'), apiBuildManifest),
]);
}
async prepareBuildManifests(): Promise<{
defaultBuildManifest: OriginRequestDefaultHandlerManifest;
apiBuildManifest: OriginRequestApiHandlerManifest;
}> {
const pagesManifest = await this.readPagesManifest();
const buildId = await readFile(join(this.dotNextDir, 'BUILD_ID'), 'utf-8');
const defaultBuildManifest: OriginRequestDefaultHandlerManifest = {
buildId,
pages: {
ssr: {
dynamic: {},
nonDynamic: {},
},
html: {
dynamic: {},
nonDynamic: {},
},
},
publicFiles: {},
};
const apiBuildManifest: OriginRequestApiHandlerManifest = {
apis: {
dynamic: {},
nonDynamic: {},
},
};
const ssrPages = defaultBuildManifest.pages.ssr;
const htmlPages = defaultBuildManifest.pages.html;
const apiPages = apiBuildManifest.apis;
const isHtmlPage = (path: string): boolean => path.endsWith('.html');
const isApiPage = (path: string): boolean => path.startsWith('pages/api');
Object.entries(pagesManifest).forEach(([route, pageFile]) => {
const dynamicRoute = isDynamicRoute(route);
const expressRoute = dynamicRoute ? expressifyDynamicRoute(route) : null;
if (isHtmlPage(pageFile)) {
if (dynamicRoute) {
const route = expressRoute as string;
htmlPages.dynamic[route] = {
file: pageFile,
regex: pathToRegexStr(route),
};
} else {
htmlPages.nonDynamic[route] = pageFile;
}
} else if (isApiPage(pageFile)) {
if (dynamicRoute) {
const route = expressRoute as string;
apiPages.dynamic[route] = {
file: pageFile,
regex: pathToRegexStr(route),
};
} else {
apiPages.nonDynamic[route] = pageFile;
}
} else if (dynamicRoute) {
const route = expressRoute as string;
ssrPages.dynamic[route] = {
file: pageFile,
regex: pathToRegexStr(route),
};
} else {
ssrPages.nonDynamic[route] = pageFile;
}
});
const publicFiles = await this.readPublicFiles();
publicFiles.forEach((pf) => (defaultBuildManifest.publicFiles[`/${pf}`] = pf));
return {
defaultBuildManifest,
apiBuildManifest,
};
}
async cleanupDotNext(): Promise<void> {
const exists = await pathExists(this.dotNextDir);
if (exists) {
const fileItems = await readdir(this.dotNextDir);
await Promise.all(
fileItems
.filter(
(fileItem) => fileItem !== 'cache' // avoid deleting the cache folder as that would lead to slow builds!
)
.map((fileItem) => remove(join(this.dotNextDir, fileItem)))
);
}
}
async build(debug?: (message: string) => void): Promise<void> {
const { cmd, args, cwd } = { ...defaultBuildOptions, ...this.buildOptions };
await this.cleanupDotNext();
await emptyDir(join(this.outputDir, DEFAULT_LAMBDA_CODE_DIR));
await emptyDir(join(this.outputDir, API_LAMBDA_CODE_DIR));
const { restoreUserConfig } = await createServerlessConfig(cwd, join(this.nextConfigDir));
try {
if (debug) {
const { stdout: nextVersion } = await execa(cmd, ['--version'], {
cwd,
});
debug(`Starting a new build with ${nextVersion}`);
console.log();
}
const subprocess = execa(cmd, args, {
cwd,
});
if (debug && subprocess.stdout) {
subprocess.stdout.pipe(process.stdout);
}
await subprocess;
} finally {
await restoreUserConfig();
}
const { defaultBuildManifest, apiBuildManifest } = await this.prepareBuildManifests();
await this.buildDefaultLambda(defaultBuildManifest);
const hasAPIPages =
Object.keys(apiBuildManifest.apis.nonDynamic).length > 0 ||
Object.keys(apiBuildManifest.apis.dynamic).length > 0;
if (hasAPIPages) {
await this.buildApiLambda(apiBuildManifest);
}
}
}
export default Builder; export default Builder;

View file

@ -5,5 +5,5 @@
"removeComments": true, "removeComments": true,
"outDir": "dist" "outDir": "dist"
}, },
"include": ["./src/"] "include": ["./src/", "../../.d.ts"]
} }

View file

@ -46,12 +46,6 @@ type OriginRequestEvent = {
Records: [{ cf: { request: CloudFrontRequest } }]; Records: [{ cf: { request: CloudFrontRequest } }];
}; };
type BuildOptions = {
args?: string[];
cwd?: string;
cmd?: string;
};
type CreateServerlessConfigResult = { type CreateServerlessConfigResult = {
restoreUserConfig: () => Promise<void>; restoreUserConfig: () => Promise<void>;
}; };

View file

@ -1,15 +1,17 @@
{ {
"name": "@next-deploy/aws-lambda", "name": "@next-deploy/aws-lambda",
"version": "1.0.0", "version": "0.0.0",
"license": "MIT", "license": "MIT",
"main": "serverless.js", "main": "serverless.js",
"types": "dist/component.d.ts", "types": "dist/component.d.ts",
"scripts": { "scripts": {
"build": "yarn clean && yarn compile", "build": "yarn clean && yarn compile",
"build:watch": "yarn compile -w",
"clean": "rm -rf ./dist", "clean": "rm -rf ./dist",
"compile": "tsc -p tsconfig.build.json" "compile": "tsc -p tsconfig.build.json"
}, },
"devDependencies": { "devDependencies": {
"@next-deploy/aws-s3": "link:../aws-s3",
"typescript": "^3.9.6" "typescript": "^3.9.6"
} }
} }

View file

@ -2,6 +2,10 @@
# yarn lockfile v1 # yarn lockfile v1
"@next-deploy/aws-s3@link:../aws-s3":
version "0.0.0"
uid ""
typescript@^3.9.6: typescript@^3.9.6:
version "3.9.6" version "3.9.6"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.6.tgz#8f3e0198a34c3ae17091b35571d3afd31999365a" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.6.tgz#8f3e0198a34c3ae17091b35571d3afd31999365a"

View file

@ -1,11 +1,12 @@
{ {
"name": "@next-deploy/aws-s3", "name": "@next-deploy/aws-s3",
"version": "1.0.0", "version": "0.0.0",
"license": "MIT", "license": "MIT",
"main": "serverless.js", "main": "serverless.js",
"types": "dist/component.d.ts", "types": "dist/component.d.ts",
"scripts": { "scripts": {
"build": "yarn clean && yarn compile", "build": "yarn clean && yarn compile",
"build:watch": "yarn compile -w",
"clean": "rm -rf ./dist", "clean": "rm -rf ./dist",
"compile": "tsc -p tsconfig.build.json" "compile": "tsc -p tsconfig.build.json"
}, },

View file

@ -1,16 +1,18 @@
{ {
"name": "@next-deploy/cli", "name": "@next-deploy/cli",
"version": "1.0.0", "version": "0.0.0",
"license": "MIT", "license": "MIT",
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",
"scripts": { "scripts": {
"build": "yarn clean && yarn compile", "build": "yarn clean && yarn compile",
"build:watch": "yarn compile -w",
"clean": "rm -rf ./dist", "clean": "rm -rf ./dist",
"compile": "tsc -p tsconfig.build.json" "compile": "tsc -p tsconfig.build.json"
}, },
"devDependencies": { "devDependencies": {
"@next-deploy/aws-component": "link:../aws-component", "@next-deploy/aws-component": "link:../aws-component",
"@next-deploy/github": "link:../github",
"typescript": "^3.9.6" "typescript": "^3.9.6"
} }
} }

View file

@ -11,9 +11,10 @@ export const SUPPORTED_ENGINES = [
export const DEFAULT_ENGINE = 'aws'; export const DEFAULT_ENGINE = 'aws';
export const DEPLOY_CONFIG_NAME = 'next-deploy.config.js'; export const DEPLOY_CONFIG_NAME = 'next-deploy.config.js';
export const METHOD_NAME_MAP = [ export const METHOD_NAME_MAP = [
{ name: 'default', action: 'Deploying' }, { name: 'init' },
{ name: 'build', action: 'Building' }, { name: 'default', action: 'Deploying', actionNoun: 'Deployment' },
{ name: 'deploy', action: 'Deploying' }, { name: 'build', action: 'Building', actionNoun: 'Build' },
{ name: 'remove', action: 'Removing' }, { name: 'deploy', action: 'Deploying', actionNoun: 'Deployment' },
{ name: 'remove', action: 'Removing', actionNoun: 'Removal' },
]; ];
export const STATE_ROOT = '.next-deploy'; export const STATE_ROOT = '.next-deploy';

View file

@ -192,7 +192,7 @@ class Context {
let content = ''; let content = '';
if (this.metrics.useTimer) { if (this.metrics.useTimer) {
content += `${grey(this.metrics.seconds + 's')}`; content += `${grey(`${this.metrics.seconds}s`)}`;
content += ` ${blue(figures.pointerSmall)}`; content += ` ${blue(figures.pointerSmall)}`;
} }
content += ` ${message}`; content += ` ${message}`;

View file

@ -1,8 +1,9 @@
import path from 'path'; import path from 'path';
import chalk from 'chalk';
import { BaseDeploymentOptions } from '../types';
import { DEFAULT_ENGINE, SUPPORTED_ENGINES, METHOD_NAME_MAP, STATE_ROOT } from './config'; import { DEFAULT_ENGINE, SUPPORTED_ENGINES, METHOD_NAME_MAP, STATE_ROOT } from './config';
import Context from './context'; import Context from './context';
import { createBaseConfig } from './utils';
const deploy = async (deployConfigPath: string, methodName = 'default'): Promise<void> => { const deploy = async (deployConfigPath: string, methodName = 'default'): Promise<void> => {
const { const {
@ -14,26 +15,37 @@ const deploy = async (deployConfigPath: string, methodName = 'default'): Promise
...componentOptions ...componentOptions
}: BaseDeploymentOptions = await import(deployConfigPath); }: BaseDeploymentOptions = await import(deployConfigPath);
const engineIndex = SUPPORTED_ENGINES.findIndex(({ type }) => type === engine); const engineIndex = SUPPORTED_ENGINES.findIndex(({ type }) => type === engine);
const isInit = methodName === 'init';
onShutdown && handleShutDown(onShutdown); onShutdown && handleShutDown(onShutdown);
if (engineIndex === -1) { if (engineIndex === -1) {
throw new Error( console.error(
`Engine ${engine} is unsupported. Pick one of: ${SUPPORTED_ENGINES.map( `${chalk.red(`Unsupported engine:`)}: ${engine}\nPick one of: ${SUPPORTED_ENGINES.map(
({ type }) => type ({ type }) => type
).join(', ')}.` ).join(', ')}`
); );
process.exit(1);
} }
const EngineComponent = await import(SUPPORTED_ENGINES[engineIndex].component); const EngineComponent = await import(SUPPORTED_ENGINES[engineIndex].component);
const method = METHOD_NAME_MAP.find(({ name }) => name === methodName); const method = METHOD_NAME_MAP.find(({ name }) => name === methodName);
if (!method) { if (!method) {
throw Error( console.error(
`Unsupported method ${methodName}. Try one of: ${METHOD_NAME_MAP.map(({ name }) => name).join( `${chalk.red(`Unsupported method:`)} ${methodName}\nPick one of: ${METHOD_NAME_MAP.map(
', ' ({ name }) => name
)}.` ).join(', ')}`
); );
process.exit(1);
}
createBaseConfig(deployConfigPath, isInit);
if (isInit) {
process.exit(0);
} }
const context = new Context({ const context = new Context({
@ -46,6 +58,8 @@ const deploy = async (deployConfigPath: string, methodName = 'default'): Promise
const component = new EngineComponent.default(undefined, context); const component = new EngineComponent.default(undefined, context);
try { try {
context.log(`⚡ Starting ${method.actionNoun}`);
onPreDeploy && (await onPreDeploy()); onPreDeploy && (await onPreDeploy());
await component.init(); await component.init();

View file

@ -1,6 +1,5 @@
#!/usr/bin/env node #!/usr/bin/env node
import fs from 'fs-extra';
import path from 'path'; import path from 'path';
import minimist from 'minimist'; import minimist from 'minimist';
@ -8,21 +7,7 @@ import deploy from './deploy';
import { DEPLOY_CONFIG_NAME } from './config'; import { DEPLOY_CONFIG_NAME } from './config';
const deployConfigPath = path.join(process.cwd(), DEPLOY_CONFIG_NAME); const deployConfigPath = path.join(process.cwd(), DEPLOY_CONFIG_NAME);
const configPathExists = fs.existsSync(deployConfigPath);
const args = minimist(process.argv.slice(2)); const args = minimist(process.argv.slice(2));
const method = args._[0] || undefined; const method = args._[0] || undefined;
// create a default next-deploy config if one doesn't exist yet
if (!configPathExists) {
fs.writeFileSync(
deployConfigPath,
`// for more configurable options see: https://github.com/nidratech/next-deploy#configuration-options
module.exports = {
debug: true,
onPreDeploy: () => console.log('⚡ Starting Deployment'),
};
`
);
}
deploy(deployConfigPath, method); deploy(deployConfigPath, method);

24
packages/cli/src/utils.ts Normal file
View file

@ -0,0 +1,24 @@
import fs from 'fs-extra';
import { DEPLOY_CONFIG_NAME } from './config';
export const createBaseConfig = (deployConfigPath: string, displayWarning?: boolean) => {
const configPathExists = fs.existsSync(deployConfigPath);
if (displayWarning && configPathExists) {
console.warn(`⚠️ The ${DEPLOY_CONFIG_NAME} configuration already exists.`);
}
// create a default next-deploy config if one doesn't exist yet
if (!configPathExists) {
fs.writeFileSync(
deployConfigPath,
`// for more configurable options see: https://github.com/nidratech/next-deploy#configuration-options
module.exports = {
engine: 'aws',
debug: true,
};
`
);
}
};

View file

@ -1,12 +1,4 @@
export type BaseDeploymentOptions = { export type ContextConfig = {
engine?: 'aws' | 'github';
debug?: boolean;
onPreDeploy?: () => Promise<void>;
onPostDeploy?: () => Promise<void>;
onShutdown?: () => Promise<void>;
};
type ContextConfig = {
root: string; root: string;
stateRoot: string; stateRoot: string;
credentials?: Record<string, unknown>; credentials?: Record<string, unknown>;

View file

@ -2,27 +2,11 @@
# yarn lockfile v1 # yarn lockfile v1
"@next-deploy/aws-cloudfront@link:../aws-cloudfront":
version "0.0.0"
uid ""
"@next-deploy/aws-component@link:../aws-component": "@next-deploy/aws-component@link:../aws-component":
version "0.0.0" version "0.0.0"
uid "" uid ""
"@next-deploy/aws-domain@link:../aws-domain": "@next-deploy/github@link:../github":
version "0.0.0"
uid ""
"@next-deploy/aws-lambda-builder@link:../aws-lambda-builder":
version "0.0.0"
uid ""
"@next-deploy/aws-lambda@link:../aws-lambda":
version "0.0.0"
uid ""
"@next-deploy/aws-s3@link:../aws-s3":
version "0.0.0" version "0.0.0"
uid "" uid ""

View file

@ -1,11 +1,12 @@
{ {
"name": "@next-deploy/github", "name": "@next-deploy/github",
"version": "1.0.0", "version": "0.0.0",
"license": "MIT", "license": "MIT",
"main": "dist/index.js", "main": "serverless.js",
"types": "dist/index.d.ts", "types": "dist/component.d.ts",
"scripts": { "scripts": {
"build": "yarn clean && yarn compile", "build": "yarn clean && yarn compile",
"build:watch": "yarn compile -w",
"clean": "rm -rf ./dist", "clean": "rm -rf ./dist",
"compile": "tsc -p tsconfig.build.json" "compile": "tsc -p tsconfig.build.json"
}, },

View file

@ -0,0 +1,43 @@
import execa from 'execa';
import { writeFileSync } from 'fs';
const NEXT_EXPORT_CMD = ['export'];
const build = async ({ cmd, cwd, args }: BuildOptions, debug?: (message: string) => void) => {
if (debug) {
const { stdout: nextVersion } = await execa(cmd, ['--version'], {
cwd,
});
debug(`Starting a new build with ${nextVersion}`);
console.log();
}
// run the build
let subprocess = execa(cmd, args, {
cwd,
});
if (debug && subprocess.stdout) {
subprocess.stdout.pipe(process.stdout);
}
await subprocess;
// export the static assets from the build
subprocess = execa(cmd, NEXT_EXPORT_CMD, {
cwd,
});
if (debug && subprocess.stdout) {
subprocess.stdout.pipe(process.stdout);
}
await subprocess;
// needed to be able for gh-pages to serve directories that start with _
writeFileSync('out/.nojekyll', '');
};
export default build;

View file

@ -1,25 +1,106 @@
import { Component } from '@serverless/core'; import { Component } from '@serverless/core';
import { resolve } from 'path';
import { publish } from 'gh-pages';
import { emptyDir } from 'fs-extra';
import { writeFileSync } from 'fs';
import build from './builder';
import { GithubInputs, DeploymentResult } from '../types'; import { GithubInputs, DeploymentResult } from '../types';
class GithubComponent extends Component { class GithubComponent extends Component {
async default(inputs: GithubInputs = {}): Promise<DeploymentResult> { async default(inputs: GithubInputs = {}): Promise<DeploymentResult> {
if (inputs.build !== false) {
await this.build(inputs);
}
return this.deploy(inputs); return this.deploy(inputs);
} }
async build(inputs: GithubInputs = {}): Promise<void> { async build(inputs: GithubInputs = {}): Promise<void> {
// TODO const nextConfigPath = inputs.nextConfigDir ? resolve(inputs.nextConfigDir) : process.cwd();
const buildCwd =
typeof inputs.build === 'boolean' || typeof inputs.build === 'undefined' || !inputs.build.cwd
? nextConfigPath
: resolve(inputs.build.cwd);
const buildConfig: BuildOptions = {
enabled: inputs.build
? // @ts-ignore
inputs.build !== false && inputs.build.enabled !== false
: true,
cmd: 'node_modules/.bin/next',
args: ['build'],
...(typeof inputs.build === 'object' ? inputs.build : {}),
cwd: buildCwd,
};
if (buildConfig.enabled) {
await build(buildConfig, this.context.instance.debugMode ? this.context.debug : undefined);
if (inputs?.domain?.length) {
const domain = getDomains(inputs.domain);
if (domain) {
writeFileSync('out/CNAME', domain);
}
}
}
} }
async deploy(inputs: GithubInputs = {}): Promise<DeploymentResult> { async deploy(inputs: GithubInputs = {}): Promise<DeploymentResult> {
// TODO let outputs: DeploymentResult = {};
return {}; if (inputs?.domain?.length) {
const domain = getDomains(inputs.domain);
outputs.appUrl = `https://${domain}`;
}
const publishPromise = new Promise((resolve, reject) => {
publish(
'out',
{ message: 'Next Deployment Update', ...inputs.publishOptions, dotfiles: true },
(err) => {
if (err) {
return reject(err);
}
resolve();
}
);
});
await publishPromise;
return outputs;
} }
async remove(): Promise<void> { async remove(): Promise<void> {
// TODO await emptyDir('out');
writeFileSync('out/empty', '');
const publishPromise = new Promise((resolve, reject) => {
publish('out', { message: 'Next Deployment Removal', remove: '*' }, (err) => {
if (err) {
return reject(err);
}
resolve();
});
});
await publishPromise;
} }
} }
function getDomains(inputDomain: string | string[]): string | undefined {
let domain;
if (typeof inputDomain === 'string') {
domain = inputDomain;
} else if (inputDomain instanceof Array) {
domain = inputDomain.join();
}
return domain;
}
export default GithubComponent; export default GithubComponent;

View file

@ -1,3 +1,9 @@
export type GithubInputs = {}; import { PublishOptions } from 'gh-pages';
type DeploymentResult = {}; export type GithubInputs = BaseDeploymentOptions & {
publishOptions?: PublishOptions;
};
type DeploymentResult = {
appUrl?: string;
};

129
yarn.lock
View file

@ -2159,6 +2159,11 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/gh-pages@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/gh-pages/-/gh-pages-3.0.0.tgz#375b0ddd43d2d2e696880c20bb43769fdaf88929"
integrity sha512-bWEiIqN0WSUQhUZXaaCVIRXdomjQCX9mCNLo6kGSML8VuiH5DXoiNOkXmxrJyrzMQmcV67JloDksM1I3WoVHhQ==
"@types/glob@*", "@types/glob@^7.1.1": "@types/glob@*", "@types/glob@^7.1.1":
version "7.1.3" version "7.1.3"
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183"
@ -2233,10 +2238,10 @@
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24" resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24"
integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug== integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==
"@types/ramda@^0.27.10": "@types/ramda@^0.27.11":
version "0.27.10" version "0.27.11"
resolved "https://registry.yarnpkg.com/@types/ramda/-/ramda-0.27.10.tgz#a006d24dd73fe38fac7fdfe970bfb992b9da5538" resolved "https://registry.yarnpkg.com/@types/ramda/-/ramda-0.27.11.tgz#a2efedc1fecc0faa2d3530eb53cf468e219f7c51"
integrity sha512-7DWYf5F4XYoJIb0bsYv66iiUiHYWlfC/Y9Lx1Ha0bEEw4wi9Uv51d49mMv+T41nEbAKPNVt5XJudwFR1bMCXTw== integrity sha512-pojA0+wBhjMWfwZ1tAea/OdUqZxaRQOi/vHldthrOIEPdRQdJb/hvU4yM1uHKGTT89ZZpxe43KM1sq5YGxS5lw==
dependencies: dependencies:
ts-toolbelt "^6.3.3" ts-toolbelt "^6.3.3"
@ -2247,10 +2252,10 @@
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
"@types/react@*", "@types/react@^16.9.42": "@types/react@*", "@types/react@^16.9.43":
version "16.9.42" version "16.9.43"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.42.tgz#9776508d59c1867bbf9bd7f036dab007fdaa1cb7" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.43.tgz#c287f23f6189666ee3bebc2eb8d0f84bcb6cdb6b"
integrity sha512-iGy6HwfVfotqJ+PfRZ4eqPHPP5NdPZgQlr0lTs8EfkODRBV9cYy8QMKcC9qPCe1JrESC1Im6SrCFR6tQgg74ag== integrity sha512-PxshAFcnJqIWYpJbLPriClH53Z2WlJcVZE+NP2etUtWQs2s7yIMj3/LDKZT/5CHJ/F62iyjVCDu2H3jHEXIxSg==
dependencies: dependencies:
"@types/prop-types" "*" "@types/prop-types" "*"
csstype "^2.2.0" csstype "^2.2.0"
@ -2866,7 +2871,7 @@ array-ify@^1.0.0:
resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece"
integrity sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4= integrity sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=
array-union@^1.0.2: array-union@^1.0.1, array-union@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39"
integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=
@ -2959,7 +2964,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.3: async@^2.6.1, async@^2.6.3:
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==
@ -3479,9 +3484,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.30001097" version "1.0.30001099"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001097.tgz#1129c40c9f5ee3282158da08fd915d301f4a9bd8" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001099.tgz#540118fcc6842d1fde62f4ee5521d1ec6afdb40e"
integrity sha512-TeuSleKt/vWXaPkLVFqGDnbweYfq4IaZ6rUugFf3rWY6dlII8StUZ8Ddin0PkADfgYZ4wRqCdO2ORl4Rn5eZIA== integrity sha512-sdS9A+sQTk7wKoeuZBN/YMAHVztUfVnjDi4/UV3sDE8xoh7YR12hKW+pIdB3oqKGwr9XaFL2ovfzt9w8eUI5CA==
caseless@~0.12.0: caseless@~0.12.0:
version "0.12.0" version "0.12.0"
@ -3752,7 +3757,7 @@ commander@2.19.x:
resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a"
integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==
commander@^2.20.0: commander@^2.18.0, commander@^2.20.0:
version "2.20.3" version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
@ -4609,6 +4614,11 @@ elliptic@^6.0.0, elliptic@^6.5.2:
minimalistic-assert "^1.0.0" minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.0" minimalistic-crypto-utils "^1.0.0"
email-addresses@^3.0.1:
version "3.1.0"
resolved "https://registry.yarnpkg.com/email-addresses/-/email-addresses-3.1.0.tgz#cabf7e085cbdb63008a70319a74e6136188812fb"
integrity sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==
emoji-regex@^7.0.1: emoji-regex@^7.0.1:
version "7.0.3" version "7.0.3"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
@ -5105,6 +5115,28 @@ file-uri-to-path@1.0.0:
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
filename-reserved-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-1.0.0.tgz#e61cf805f0de1c984567d0386dc5df50ee5af7e4"
integrity sha1-5hz4BfDeHJhFZ9A4bcXfUO5a9+Q=
filenamify-url@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/filenamify-url/-/filenamify-url-1.0.0.tgz#b32bd81319ef5863b73078bed50f46a4f7975f50"
integrity sha1-syvYExnvWGO3MHi+1Q9GpPeXX1A=
dependencies:
filenamify "^1.0.0"
humanize-url "^1.0.0"
filenamify@^1.0.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-1.2.1.tgz#a9f2ffd11c503bed300015029272378f1f1365a5"
integrity sha1-qfL/0RxQO+0wABUCknI3jx8TZaU=
dependencies:
filename-reserved-regex "^1.0.0"
strip-outer "^1.0.0"
trim-repeated "^1.0.0"
fill-range@^4.0.0: fill-range@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7"
@ -5122,7 +5154,7 @@ fill-range@^7.0.1:
dependencies: dependencies:
to-regex-range "^5.0.1" to-regex-range "^5.0.1"
find-cache-dir@3.3.1: find-cache-dir@3.3.1, find-cache-dir@^3.3.1:
version "3.3.1" version "3.3.1"
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.1.tgz#89b33fad4a4670daa94f855f7fbe31d6d84fe880" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.1.tgz#89b33fad4a4670daa94f855f7fbe31d6d84fe880"
integrity sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ== integrity sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==
@ -5413,6 +5445,19 @@ getpass@^0.1.1:
dependencies: dependencies:
assert-plus "^1.0.0" assert-plus "^1.0.0"
gh-pages@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/gh-pages/-/gh-pages-3.1.0.tgz#ec3ed0f6a6e3fc3d888758fa018f08191c96bd55"
integrity sha512-3b1rly9kuf3/dXsT8+ZxP0UhNLOo1CItj+3e31yUVcaph/yDsJ9RzD7JOw5o5zpBTJVQLlJAASNkUfepi9fe2w==
dependencies:
async "^2.6.1"
commander "^2.18.0"
email-addresses "^3.0.1"
filenamify-url "^1.0.0"
find-cache-dir "^3.3.1"
fs-extra "^8.1.0"
globby "^6.1.0"
git-raw-commits@2.0.0: git-raw-commits@2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-2.0.0.tgz#d92addf74440c14bcc5c83ecce3fb7f8a79118b5" resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-2.0.0.tgz#d92addf74440c14bcc5c83ecce3fb7f8a79118b5"
@ -5487,7 +5532,7 @@ glob-to-regexp@^0.4.1:
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: glob@^7.0.3, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
version "7.1.6" version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
@ -5523,6 +5568,17 @@ globby@^11.0.1:
merge2 "^1.3.0" merge2 "^1.3.0"
slash "^3.0.0" slash "^3.0.0"
globby@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c"
integrity sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=
dependencies:
array-union "^1.0.1"
glob "^7.0.3"
object-assign "^4.0.1"
pify "^2.0.0"
pinkie-promise "^2.0.0"
globby@^9.2.0: globby@^9.2.0:
version "9.2.0" version "9.2.0"
resolved "https://registry.yarnpkg.com/globby/-/globby-9.2.0.tgz#fd029a706c703d29bdd170f4b6db3a3f7a7cb63d" resolved "https://registry.yarnpkg.com/globby/-/globby-9.2.0.tgz#fd029a706c703d29bdd170f4b6db3a3f7a7cb63d"
@ -5767,6 +5823,14 @@ humanize-ms@^1.2.1:
dependencies: dependencies:
ms "^2.0.0" ms "^2.0.0"
humanize-url@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/humanize-url/-/humanize-url-1.0.1.tgz#f4ab99e0d288174ca4e1e50407c55fbae464efff"
integrity sha1-9KuZ4NKIF0yk4eUEB8VfuuRk7/8=
dependencies:
normalize-url "^1.0.0"
strip-url-auth "^1.0.0"
husky@^4.2.5: husky@^4.2.5:
version "4.2.5" version "4.2.5"
resolved "https://registry.yarnpkg.com/husky/-/husky-4.2.5.tgz#2b4f7622673a71579f901d9885ed448394b5fa36" resolved "https://registry.yarnpkg.com/husky/-/husky-4.2.5.tgz#2b4f7622673a71579f901d9885ed448394b5fa36"
@ -7423,7 +7487,7 @@ normalize-path@^3.0.0, normalize-path@~3.0.0:
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
normalize-url@1.9.1: normalize-url@1.9.1, normalize-url@^1.0.0:
version "1.9.1" version "1.9.1"
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c"
integrity sha1-LMDWazHqIwNkWENuNiDYWVTGbDw= integrity sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=
@ -9778,15 +9842,27 @@ strip-indent@^3.0.0:
min-indent "^1.0.0" min-indent "^1.0.0"
strip-json-comments@^3.1.0: strip-json-comments@^3.1.0:
version "3.1.0" version "3.1.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w== integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
strip-json-comments@~2.0.1: strip-json-comments@~2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
strip-outer@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/strip-outer/-/strip-outer-1.0.1.tgz#b2fd2abf6604b9d1e6013057195df836b8a9d631"
integrity sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==
dependencies:
escape-string-regexp "^1.0.2"
strip-url-auth@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/strip-url-auth/-/strip-url-auth-1.0.1.tgz#22b0fa3a41385b33be3f331551bbb837fa0cd7ae"
integrity sha1-IrD6OkE4WzO+PzMVUbu4N/oM164=
strong-log-transformer@^2.0.0: strong-log-transformer@^2.0.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz#0f5ed78d325e0421ac6f90f7f10e691d6ae3ae10" resolved "https://registry.yarnpkg.com/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz#0f5ed78d325e0421ac6f90f7f10e691d6ae3ae10"
@ -10122,15 +10198,22 @@ trim-off-newlines@^1.0.0:
resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3" resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3"
integrity sha1-n5up2e+odkw4dpi8v+sshI8RrbM= integrity sha1-n5up2e+odkw4dpi8v+sshI8RrbM=
trim-repeated@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/trim-repeated/-/trim-repeated-1.0.0.tgz#e3646a2ea4e891312bf7eace6cfb05380bc01c21"
integrity sha1-42RqLqTokTEr9+rObPsFOAvAHCE=
dependencies:
escape-string-regexp "^1.0.2"
ts-pnp@^1.1.6: ts-pnp@^1.1.6:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92" resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92"
integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw== integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==
ts-toolbelt@^6.3.3: ts-toolbelt@^6.3.3:
version "6.10.16" version "6.12.1"
resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-6.10.16.tgz#c45bab5b5227d4ea3f120635aa5632150ad8191d" resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-6.12.1.tgz#bfcfaf0e1cc7416bcfc412c9340233f57a1f86c4"
integrity sha512-MAad9fRk8oO0WqB927LLdEkZg+Oz0nAkuyS4zTsTk4L97AjHX7GdFGUk2ABkqgo9S3V/Wkn2pd2df2GcZoayvQ== integrity sha512-Y4ZMdw+CzHnzFp7yp8axTDl6G78ypeS1XcaarLoXpnW0McYR7kDLndU2tk7nIEdM99yoHjaRSbL09ULZMtN5RA==
tslib@^1.8.1, tslib@^1.9.0: tslib@^1.8.1, tslib@^1.9.0:
version "1.13.0" version "1.13.0"