mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-23 18:59:59 +00:00
Compare commits
169 Commits
@vercel/ru
...
@vercel/bu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
041e8cc601 | ||
|
|
b118e461b1 | ||
|
|
e519d49d7b | ||
|
|
27683818ba | ||
|
|
e016e38229 | ||
|
|
5db1c5e610 | ||
|
|
24c228569f | ||
|
|
963de9b64f | ||
|
|
ab7fd52305 | ||
|
|
0fdb0dac91 | ||
|
|
bb0b632dcf | ||
|
|
ced9495143 | ||
|
|
fadc3f2588 | ||
|
|
a1d548dfef | ||
|
|
754090a8ab | ||
|
|
8269a48ee0 | ||
|
|
9f05a1865c | ||
|
|
8d1afc026f | ||
|
|
130f36aad6 | ||
|
|
dd87c9b0c6 | ||
|
|
f813b3340b | ||
|
|
976b02e895 | ||
|
|
843be9658c | ||
|
|
ad501a4cd0 | ||
|
|
727ae587db | ||
|
|
536b15079b | ||
|
|
887882697b | ||
|
|
e2db7c7734 | ||
|
|
022504787c | ||
|
|
0f424de406 | ||
|
|
4819c3ac61 | ||
|
|
c28ca7ef2d | ||
|
|
068ea00615 | ||
|
|
7f8145ab40 | ||
|
|
1a12715096 | ||
|
|
5b6d565360 | ||
|
|
0794158906 | ||
|
|
02cdb88d3b | ||
|
|
fa58855114 | ||
|
|
1fac11792f | ||
|
|
05ffc9ce2b | ||
|
|
f848551043 | ||
|
|
9712abc5bf | ||
|
|
a9ef3cc726 | ||
|
|
e5cc1d643a | ||
|
|
c68e83f972 | ||
|
|
80118de040 | ||
|
|
c69da18e9c | ||
|
|
e19446f89c | ||
|
|
ea3233502d | ||
|
|
0a8810b64f | ||
|
|
f6b373f0f4 | ||
|
|
7ad2a99cd7 | ||
|
|
0349eea494 | ||
|
|
ed4d006fb7 | ||
|
|
12a9d203e9 | ||
|
|
ac1f4cf789 | ||
|
|
b284ca350a | ||
|
|
a43bf6c912 | ||
|
|
62410806bb | ||
|
|
47e3111cab | ||
|
|
135f35002f | ||
|
|
ee40052cee | ||
|
|
86b730c1cd | ||
|
|
b440249c26 | ||
|
|
5380c12569 | ||
|
|
f11eb32b2c | ||
|
|
3d40e343ac | ||
|
|
80f525796f | ||
|
|
af4ad358f2 | ||
|
|
e6033d7a2d | ||
|
|
d3148dffaa | ||
|
|
30048cf4ff | ||
|
|
07c65fa5c8 | ||
|
|
411ec64986 | ||
|
|
e4d2cc704c | ||
|
|
38db720586 | ||
|
|
c26c7886be | ||
|
|
b0e5d308ca | ||
|
|
609b98cc73 | ||
|
|
e0ec6c792b | ||
|
|
b29db2fd1d | ||
|
|
b604ced99d | ||
|
|
b7fd69517e | ||
|
|
4c821a6fb5 | ||
|
|
5c4fb319af | ||
|
|
fbe9ea0750 | ||
|
|
fa0f1b90b4 | ||
|
|
2daa0e28d3 | ||
|
|
48358b4986 | ||
|
|
c59f44a63b | ||
|
|
ca27864201 | ||
|
|
6c81a87338 | ||
|
|
a7bef9387b | ||
|
|
65d6c5e1f4 | ||
|
|
c09355fdb3 | ||
|
|
d9ac4c45e1 | ||
|
|
c2ff95714f | ||
|
|
a51feb7a62 | ||
|
|
ff10918230 | ||
|
|
04bea1e3cd | ||
|
|
bc5e5e8a9c | ||
|
|
2e647175f5 | ||
|
|
1a4e1d2fdd | ||
|
|
49e2274d81 | ||
|
|
9af3938544 | ||
|
|
7b5bf061c2 | ||
|
|
ffb98781f1 | ||
|
|
3411fcbb68 | ||
|
|
55cfd33338 | ||
|
|
93a9e5bed3 | ||
|
|
b454021234 | ||
|
|
bb705cd091 | ||
|
|
0986f4bcb6 | ||
|
|
83f77223aa | ||
|
|
effda1fa6c | ||
|
|
2ab6a7ef0c | ||
|
|
200bf5e996 | ||
|
|
94ab2512e9 | ||
|
|
da3207278e | ||
|
|
8e10a82b43 | ||
|
|
d7731d191b | ||
|
|
0f5f99e667 | ||
|
|
93a7831943 | ||
|
|
0eacbeae11 | ||
|
|
e50417dc47 | ||
|
|
4840a25d30 | ||
|
|
785e91979d | ||
|
|
7a776c54b8 | ||
|
|
997031c53b | ||
|
|
fdee03a599 | ||
|
|
c5a93ecdad | ||
|
|
5a9391b7ce | ||
|
|
99e49473b8 | ||
|
|
de1cc6f9a7 | ||
|
|
08eedd8f34 | ||
|
|
5021a71a8e | ||
|
|
56671d7c2f | ||
|
|
5035fa537f | ||
|
|
63cc9009c8 | ||
|
|
ccf6e3c432 | ||
|
|
8d015e3138 | ||
|
|
42f2fa1a20 | ||
|
|
8397aac0e3 | ||
|
|
7bcdc144eb | ||
|
|
624da9170d | ||
|
|
fb5b013a03 | ||
|
|
0a4bb53a58 | ||
|
|
2fbd9c78e3 | ||
|
|
1ed2b7a57d | ||
|
|
88cd9ca3c3 | ||
|
|
ecea2ca4a3 | ||
|
|
3f132bc15b | ||
|
|
61d95094c0 | ||
|
|
f7c47975e3 | ||
|
|
7c96f9f9a5 | ||
|
|
a5c805b6eb | ||
|
|
ff2a22023d | ||
|
|
c6efc028aa | ||
|
|
96565da1cf | ||
|
|
afb5e7fc85 | ||
|
|
34cc987be8 | ||
|
|
55c60d30e6 | ||
|
|
eb993d47ac | ||
|
|
d2184628d1 | ||
|
|
65f0cc6797 | ||
|
|
c628c7b58e | ||
|
|
4e005274f9 | ||
|
|
482373f711 |
@@ -19,6 +19,11 @@ indent_style = space
|
|||||||
[*.py]
|
[*.py]
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.go]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 4
|
||||||
|
tab_width = 4
|
||||||
|
|
||||||
[*.asm]
|
[*.asm]
|
||||||
indent_size = 8
|
indent_size = 8
|
||||||
|
|
||||||
|
|||||||
@@ -34,3 +34,6 @@ packages/now-node-bridge/bridge.*
|
|||||||
|
|
||||||
# now-static-build
|
# now-static-build
|
||||||
packages/now-static-build/test/fixtures
|
packages/now-static-build/test/fixtures
|
||||||
|
|
||||||
|
# redwood
|
||||||
|
packages/redwood/test/fixtures
|
||||||
|
|||||||
28
.github/CODEOWNERS
vendored
28
.github/CODEOWNERS
vendored
@@ -1,27 +1,27 @@
|
|||||||
# Documentation
|
# Documentation
|
||||||
# https://help.github.com/en/articles/about-code-owners
|
# https://help.github.com/en/articles/about-code-owners
|
||||||
|
|
||||||
* @tootallnate
|
* @TooTallNate
|
||||||
/.github/workflows @AndyBitz @styfle
|
/.github/workflows @AndyBitz @styfle
|
||||||
/packages/frameworks @AndyBitz
|
/packages/frameworks @AndyBitz
|
||||||
/packages/now-cli/src/commands/dev/ @tootallnate @styfle @AndyBitz
|
/packages/now-cli/src/commands/dev @TooTallNate @styfle @AndyBitz
|
||||||
/packages/now-cli/src/util/dev/ @tootallnate @styfle @AndyBitz
|
/packages/now-cli/src/util/dev @TooTallNate @styfle @AndyBitz
|
||||||
/packages/now-cli/src/commands/domains/ @javivelasco @mglagola @anatrajkovska
|
/packages/now-cli/src/commands/domains @javivelasco @mglagola @anatrajkovska
|
||||||
/packages/now-cli/src/commands/certs/ @javivelasco @mglagola @anatrajkovska
|
/packages/now-cli/src/commands/certs @javivelasco @mglagola @anatrajkovska
|
||||||
/packages/now-cli/src/commands/env @styfle @lucleray
|
/packages/now-cli/src/commands/env @styfle @lucleray
|
||||||
/packages/now-client @rdev
|
/packages/now-client @rdev @styfle @TooTallNate
|
||||||
/packages/now-build-utils @styfle @AndyBitz
|
/packages/now-build-utils @styfle @AndyBitz @TooTallNate
|
||||||
/packages/now-node @styfle @tootallnate @lucleray
|
/packages/now-node @styfle @TooTallNate @lucleray
|
||||||
/packages/now-node-bridge @styfle @tootallnate @lucleray
|
/packages/now-node-bridge @styfle @TooTallNate @lucleray
|
||||||
/packages/now-next @Timer @ijjk
|
/packages/now-next @Timer @ijjk
|
||||||
/packages/now-go @styfle @sophearak
|
/packages/now-go @styfle @TooTallNate
|
||||||
/packages/now-python @styfle @sophearak
|
/packages/now-python @styfle @TooTallNate
|
||||||
/packages/now-ruby @styfle @coetry @nathancahill
|
/packages/now-ruby @styfle @coetry @TooTallNate
|
||||||
/packages/now-static-build @styfle @AndyBitz
|
/packages/now-static-build @styfle @AndyBitz
|
||||||
/packages/now-routing-utils @styfle @dav-is @ijjk
|
/packages/now-routing-utils @styfle @dav-is @ijjk
|
||||||
/examples @mcsdevv @timothyis
|
/examples @mcsdevv @timothyis
|
||||||
/examples/create-react-app @Timer
|
/examples/create-react-app @Timer
|
||||||
/examples/nextjs @timneutkens
|
/examples/nextjs @timneutkens @Timer
|
||||||
/examples/hugo @mcsdevv @timothyis @styfle
|
/examples/hugo @mcsdevv @timothyis @styfle
|
||||||
/examples/jekyll @mcsdevv @timothyis @sarupbanskota
|
/examples/jekyll @mcsdevv @timothyis @styfle
|
||||||
/examples/zola @mcsdevv @timothyis @styfle
|
/examples/zola @mcsdevv @timothyis @styfle
|
||||||
|
|||||||
@@ -1,10 +1,33 @@
|
|||||||
# Runtime Developer Reference
|
# Runtime Developer Reference
|
||||||
|
|
||||||
The following page is a reference for how to create a Runtime using the available Runtime API.
|
The following page is a reference for how to create a Runtime by implementing
|
||||||
|
the Runtime API interface.
|
||||||
|
|
||||||
A Runtime is an npm module that exposes a `build` function and optionally an `analyze` function and `prepareCache` function.
|
A Runtime is an npm module that implements the following interface:
|
||||||
Official Runtimes are published to [npmjs.com](https://npmjs.com) as a package and referenced in the `use` property of the `vercel.json` configuration file.
|
|
||||||
However, the `use` property will work with any [npm install argument](https://docs.npmjs.com/cli/install) such as a git repo url which is useful for testing your Runtime.
|
```typescript
|
||||||
|
interface Runtime {
|
||||||
|
version: number;
|
||||||
|
build: (options: BuildOptions) => Promise<BuildResult>;
|
||||||
|
analyze?: (options: AnalyzeOptions) => Promise<string>;
|
||||||
|
prepareCache?: (options: PrepareCacheOptions) => Promise<CacheOutputs>;
|
||||||
|
shouldServe?: (options: ShouldServeOptions) => Promise<boolean>;
|
||||||
|
startDevServer?: (
|
||||||
|
options: StartDevServerOptions
|
||||||
|
) => Promise<StartDevServerResult>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `version` property and the `build()` function are the only _required_ fields.
|
||||||
|
The rest are optional extensions that a Runtime _may_ implement in order to
|
||||||
|
enhance functionality. These functions are documented in more detail below.
|
||||||
|
|
||||||
|
Official Runtimes are published to [the npm registry](https://npmjs.com) as a package and referenced in the `use` property of the `vercel.json` configuration file.
|
||||||
|
|
||||||
|
> **Note:** The `use` property in the `builds` array will work with any [npm
|
||||||
|
> install argument](https://docs.npmjs.com/cli/install) such as a git repo URL,
|
||||||
|
> which is useful for testing your Runtime. Alternatively, the `functions` property
|
||||||
|
> requires that you specify a specifc tag published to npm, for stability purposes.
|
||||||
|
|
||||||
See the [Runtimes Documentation](https://vercel.com/docs/runtimes) to view example usage.
|
See the [Runtimes Documentation](https://vercel.com/docs/runtimes) to view example usage.
|
||||||
|
|
||||||
@@ -16,146 +39,170 @@ A **required** exported constant that decides which version of the Runtime API t
|
|||||||
|
|
||||||
The latest and suggested version is `3`.
|
The latest and suggested version is `3`.
|
||||||
|
|
||||||
### `analyze`
|
**Example:**
|
||||||
|
|
||||||
An **optional** exported function that returns a unique fingerprint used for the purpose of [build de-duplication](https://vercel.com/docs/v2/platform/deployments#deduplication). If the `analyze` function is not supplied, a random fingerprint is assigned to each build.
|
```typescript
|
||||||
|
export const version = 3;
|
||||||
```js
|
|
||||||
export analyze({
|
|
||||||
files: Files,
|
|
||||||
entrypoint: String,
|
|
||||||
workPath: String,
|
|
||||||
config: Object
|
|
||||||
}) : String fingerprint
|
|
||||||
```
|
```
|
||||||
|
|
||||||
If you are using TypeScript, you should use the following types:
|
### `build()`
|
||||||
|
|
||||||
```ts
|
A **required** exported function that returns a Serverless Function.
|
||||||
import { AnalyzeOptions } from '@vercel/build-utils'
|
|
||||||
|
|
||||||
export analyze(options: AnalyzeOptions) {
|
> What's a Serverless Function? Read about [Serverless Functions](https://vercel.com/docs/v2/serverless-functions/introduction) to learn more.
|
||||||
return 'fingerprint goes here'
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { BuildOptions, createLambda } from '@vercel/build-utils';
|
||||||
|
|
||||||
|
export async function build(options: BuildOptions) {
|
||||||
|
// Build the code here…
|
||||||
|
|
||||||
|
const lambda = createLambda(/* … */);
|
||||||
|
return {
|
||||||
|
output: lambda,
|
||||||
|
watch: [
|
||||||
|
// Dependent files to trigger a rebuild in `vercel dev` go here…
|
||||||
|
],
|
||||||
|
routes: [
|
||||||
|
// If your Runtime needs to define additional routing, define it here…
|
||||||
|
],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### `build`
|
### `analyze()`
|
||||||
|
|
||||||
A **required** exported function that returns a [Serverless Function](#serverless-function).
|
An **optional** exported function that returns a unique fingerprint used for the
|
||||||
|
purpose of [build
|
||||||
|
de-duplication](https://vercel.com/docs/v2/platform/deployments#deduplication).
|
||||||
|
If the `analyze()` function is not supplied, then a random fingerprint is
|
||||||
|
assigned to each build.
|
||||||
|
|
||||||
What's a Serverless Function? Read about [Serverless Functions](https://vercel.com/docs/v2/serverless-functions/introduction) to learn more.
|
**Example:**
|
||||||
|
|
||||||
```js
|
```typescript
|
||||||
build({
|
import { AnalyzeOptions } from '@vercel/build-utils';
|
||||||
files: Files,
|
|
||||||
entrypoint: String,
|
export async function analyze(options: AnalyzeOptions) {
|
||||||
workPath: String,
|
// Do calculations to generate a fingerprint based off the source code here…
|
||||||
config: Object,
|
|
||||||
meta?: {
|
return 'fingerprint goes here';
|
||||||
isDev?: Boolean,
|
|
||||||
requestPath?: String,
|
|
||||||
filesChanged?: Array<String>,
|
|
||||||
filesRemoved?: Array<String>
|
|
||||||
}
|
|
||||||
}) : {
|
|
||||||
watch?: Array<String>,
|
|
||||||
output: Lambda,
|
|
||||||
routes?: Object
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
If you are using TypeScript, you should use the following types:
|
### `prepareCache()`
|
||||||
|
|
||||||
```ts
|
An **optional** exported function that is executed after [`build()`](#build) is
|
||||||
import { BuildOptions } from '@vercel/build-utils'
|
completed. The implementation should return an object of `File`s that will be
|
||||||
|
pre-populated in the working directory for the next build run in the user's
|
||||||
|
project. An example use-case is that `@vercel/node` uses this function to cache
|
||||||
|
the `node_modules` directory, making it faster to install npm dependencies for
|
||||||
|
future builds.
|
||||||
|
|
||||||
export build(options: BuildOptions) {
|
**Example:**
|
||||||
// Build the code here
|
|
||||||
|
```typescript
|
||||||
|
import { PrepareCacheOptions } from '@vercel/build-utils';
|
||||||
|
|
||||||
|
export async function prepareCache(options: PrepareCacheOptions) {
|
||||||
|
// Create a mapping of file names and `File` object instances to cache here…
|
||||||
|
|
||||||
return {
|
return {
|
||||||
output: {
|
'path-to-file': File,
|
||||||
'path-to-file': File,
|
};
|
||||||
'path-to-lambda': Lambda
|
|
||||||
},
|
|
||||||
watch: [],
|
|
||||||
routes: {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### `prepareCache`
|
### `shouldServe()`
|
||||||
|
|
||||||
An **optional** exported function that is equivalent to [`build`](#build), but it executes the instructions necessary to prepare a cache for the next run.
|
An **optional** exported function that is only used by `vercel dev` in [Vercel
|
||||||
|
CLI](https://vercel.com/download) and indicates whether a
|
||||||
|
[Runtime](https://vercel.com/docs/runtimes) wants to be responsible for responding
|
||||||
|
to a certain request path.
|
||||||
|
|
||||||
```js
|
**Example:**
|
||||||
prepareCache({
|
|
||||||
files: Files,
|
|
||||||
entrypoint: String,
|
|
||||||
workPath: String,
|
|
||||||
cachePath: String,
|
|
||||||
config: Object
|
|
||||||
}) : Files cacheOutput
|
|
||||||
```
|
|
||||||
|
|
||||||
If you are using TypeScript, you can import the types for each of these functions by using the following:
|
```typescript
|
||||||
|
import { ShouldServeOptions } from '@vercel/build-utils';
|
||||||
|
|
||||||
```ts
|
export async function shouldServe(options: ShouldServeOptions) {
|
||||||
import { PrepareCacheOptions } from '@vercel/build-utils'
|
// Determine whether or not the Runtime should respond to the request path here…
|
||||||
|
|
||||||
export prepareCache(options: PrepareCacheOptions) {
|
return options.requestPath === options.entrypoint;
|
||||||
return { 'path-to-file': File }
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### `shouldServe`
|
If this function is not defined, Vercel CLI will use the [default implementation](https://github.com/vercel/vercel/blob/52994bfe26c5f4f179bdb49783ee57ce19334631/packages/now-build-utils/src/should-serve.ts).
|
||||||
|
|
||||||
An **optional** exported function that is only used by `vercel dev` in [Vercel CLI](https:///download) and indicates whether a [Runtime](https://vercel.com/docs/runtimes) wants to be responsible for building a certain request path.
|
### `startDevServer()`
|
||||||
|
|
||||||
```js
|
An **optional** exported function that is only used by `vercel dev` in [Vercel
|
||||||
shouldServe({
|
CLI](https://vercel.com/download). If this function is defined, Vercel CLI will
|
||||||
entrypoint: String,
|
**not** invoke the `build()` function, and instead invoke this function for every
|
||||||
files: Files,
|
HTTP request. It is an opportunity to provide an optimized development experience
|
||||||
config: Object,
|
rather than going through the entire `build()` process that is used in production.
|
||||||
requestPath: String,
|
|
||||||
workPath: String
|
|
||||||
}) : Boolean
|
|
||||||
```
|
|
||||||
|
|
||||||
If you are using TypeScript, you can import the types for each of these functions by using the following:
|
This function is invoked _once per HTTP request_ and is expected to spawn a child
|
||||||
|
process which creates an HTTP server that will execute the entrypoint code when
|
||||||
|
an HTTP request is received. This child process is _single-serve_ (only used for
|
||||||
|
a single HTTP request). After the HTTP response is complete, `vercel dev` sends
|
||||||
|
a shut down signal to the child process.
|
||||||
|
|
||||||
```ts
|
The `startDevServer()` function returns an object with the `port` number that the
|
||||||
import { ShouldServeOptions } from '@vercel/build-utils'
|
child process' HTTP server is listening on (which should be an [ephemeral
|
||||||
|
port](https://stackoverflow.com/a/28050404/376773)) as well as the child process'
|
||||||
|
Process ID, which `vercel dev` uses to send the shut down signal to.
|
||||||
|
|
||||||
export shouldServe(options: ShouldServeOptions) {
|
> **Hint:** To determine which ephemeral port the child process is listening on,
|
||||||
return Boolean
|
> some form of [IPC](https://en.wikipedia.org/wiki/Inter-process_communication) is
|
||||||
|
> required. For example, in `@vercel/go` the child process writes the port number
|
||||||
|
> to [_file descriptor 3_](https://en.wikipedia.org/wiki/File_descriptor), which is read by the `startDevServer()` function
|
||||||
|
> implementation.
|
||||||
|
|
||||||
|
It may also return `null` to opt-out of this behavior for a particular request
|
||||||
|
path or entrypoint.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { spawn } from 'child_process';
|
||||||
|
import { StartDevServerOptions } from '@vercel/build-utils';
|
||||||
|
|
||||||
|
export async function startDevServer(options: StartDevServerOptions) {
|
||||||
|
// Create a child process which will create an HTTP server.
|
||||||
|
//
|
||||||
|
// Note: `my-runtime-dev-server` is an example dev server program name.
|
||||||
|
// Your implementation will spawn a different program specific to your runtime.
|
||||||
|
const child = spawn('my-runtime-dev-server', [options.entrypoint], {
|
||||||
|
stdio: ['ignore', 'inherit', 'inherit', 'pipe'],
|
||||||
|
});
|
||||||
|
|
||||||
|
// In this example, the child process will write the port number to FD 3…
|
||||||
|
const portPipe = child.stdio[3];
|
||||||
|
const childPort = await new Promise(resolve => {
|
||||||
|
portPipe.setEncoding('utf8');
|
||||||
|
portPipe.once('data', data => {
|
||||||
|
resolve(Number(data));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return { pid: child.pid, port: childPort };
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
If this method is not defined, Vercel CLI will default to [this function](https://github.com/vercel/vercel/blob/52994bfe26c5f4f179bdb49783ee57ce19334631/packages/now-build-utils/src/should-serve.ts).
|
|
||||||
|
|
||||||
### Runtime Options
|
|
||||||
|
|
||||||
The exported functions [`analyze`](#analyze), [`build`](#build), and [`prepareCache`](#preparecache) receive one argument with the following properties.
|
|
||||||
|
|
||||||
**Properties:**
|
|
||||||
|
|
||||||
- `files`: All source files of the project as a [Files](#files) data structure.
|
|
||||||
- `entrypoint`: Name of entrypoint file for this particular build job. Value `files[entrypoint]` is guaranteed to exist and be a valid [File](#files) reference. `entrypoint` is always a discrete file and never a glob, since globs are expanded into separate builds at deployment time.
|
|
||||||
- `workPath`: A writable temporary directory where you are encouraged to perform your build process. This directory will be populated with the restored cache from the previous run (if any) for [`analyze`](#analyze) and [`build`](#build).
|
|
||||||
- `cachePath`: A writable temporary directory where you can build a cache for the next run. This is only passed to `prepareCache`.
|
|
||||||
- `config`: An arbitrary object passed from by the user in the [Build definition](#defining-the-build-step) in `vercel.json`.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
Check out our [Node.js Runtime](https://github.com/vercel/vercel/tree/master/packages/now-node), [Go Runtime](https://github.com/vercel/vercel/tree/master/packages/now-go), [Python Runtime](https://github.com/vercel/vercel/tree/master/packages/now-python) or [Ruby Runtime](https://github.com/vercel/vercel/tree/master/packages/now-ruby) for examples of how to build one.
|
|
||||||
|
|
||||||
## Technical Details
|
|
||||||
|
|
||||||
### Execution Context
|
### Execution Context
|
||||||
|
|
||||||
A [Serverless Function](https://vercel.com/docs/v2/serverless-functions/introduction) is created where the Runtime logic is executed. The lambda is run using the Node.js 8 runtime. A brand new sandbox is created for each deployment, for security reasons. The sandbox is cleaned up between executions to ensure no lingering temporary files are shared from build to build.
|
- Runtimes are executed in a Linux container that closely matches the Servereless Function runtime environment.
|
||||||
|
- The Runtime code is executed using Node.js version **12.x**.
|
||||||
|
- A brand new sandbox is created for each deployment, for security reasons.
|
||||||
|
- The sandbox is cleaned up between executions to ensure no lingering temporary files are shared from build to build.
|
||||||
|
|
||||||
All the APIs you export ([`analyze`](#analyze), [`build`](#build) and [`prepareCache`](#preparecache)) are not guaranteed to be run in the same process, but the filesystem we expose (e.g.: `workPath` and the results of calling [`getWriteableDirectory`](#getWriteableDirectory) ) is retained.
|
All the APIs you export ([`analyze()`](#analyze), [`build()`](#build),
|
||||||
|
[`prepareCache()`](#preparecache), etc.) are not guaranteed to be run in the
|
||||||
|
same process, but the filesystem we expose (e.g.: `workPath` and the results
|
||||||
|
of calling [`getWritableDirectory`](#getWritableDirectory) ) is retained.
|
||||||
|
|
||||||
If you need to share state between those steps, use the filesystem.
|
If you need to share state between those steps, use the filesystem.
|
||||||
|
|
||||||
@@ -173,11 +220,11 @@ The env and secrets specified by the user as `build.env` are passed to the Runti
|
|||||||
|
|
||||||
When you publish your Runtime to npm, make sure to not specify `@vercel/build-utils` (as seen below in the API definitions) as a dependency, but rather as part of `peerDependencies`.
|
When you publish your Runtime to npm, make sure to not specify `@vercel/build-utils` (as seen below in the API definitions) as a dependency, but rather as part of `peerDependencies`.
|
||||||
|
|
||||||
## Types
|
## `@vercel/build-utils` Types
|
||||||
|
|
||||||
### `Files`
|
### `Files`
|
||||||
|
|
||||||
```ts
|
```typescript
|
||||||
import { File } from '@vercel/build-utils';
|
import { File } from '@vercel/build-utils';
|
||||||
type Files = { [filePath: string]: File };
|
type Files = { [filePath: string]: File };
|
||||||
```
|
```
|
||||||
@@ -188,7 +235,7 @@ When used as an input, the `Files` object will only contain `FileRefs`. When `Fi
|
|||||||
|
|
||||||
An example of a valid output `Files` object is:
|
An example of a valid output `Files` object is:
|
||||||
|
|
||||||
```json
|
```javascript
|
||||||
{
|
{
|
||||||
"index.html": FileRef,
|
"index.html": FileRef,
|
||||||
"api/index.js": Lambda
|
"api/index.js": Lambda
|
||||||
@@ -199,7 +246,7 @@ An example of a valid output `Files` object is:
|
|||||||
|
|
||||||
This is an abstract type that can be imported if you are using TypeScript.
|
This is an abstract type that can be imported if you are using TypeScript.
|
||||||
|
|
||||||
```ts
|
```typescript
|
||||||
import { File } from '@vercel/build-utils';
|
import { File } from '@vercel/build-utils';
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -211,71 +258,71 @@ Valid `File` types include:
|
|||||||
|
|
||||||
### `FileRef`
|
### `FileRef`
|
||||||
|
|
||||||
```ts
|
```typescript
|
||||||
import { FileRef } from '@vercel/build-utils';
|
import { FileRef } from '@vercel/build-utils';
|
||||||
```
|
```
|
||||||
|
|
||||||
This is a [JavaScript class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) that represents an abstract file instance stored in our platform, based on the file identifier string (its checksum). When a `Files` object is passed as an input to `analyze` or `build`, all its values will be instances of `FileRef`.
|
This is a [class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) that represents an abstract file instance stored in our platform, based on the file identifier string (its checksum). When a `Files` object is passed as an input to `analyze` or `build`, all its values will be instances of `FileRef`.
|
||||||
|
|
||||||
**Properties:**
|
**Properties:**
|
||||||
|
|
||||||
- `mode : Number` file mode
|
- `mode: Number` file mode
|
||||||
- `digest : String` a checksum that represents the file
|
- `digest: String` a checksum that represents the file
|
||||||
|
|
||||||
**Methods:**
|
**Methods:**
|
||||||
|
|
||||||
- `toStream() : Stream` creates a [Stream](https://nodejs.org/api/stream.html) of the file body
|
- `toStream(): Stream` creates a [Stream](https://nodejs.org/api/stream.html) of the file body
|
||||||
|
|
||||||
### `FileFsRef`
|
### `FileFsRef`
|
||||||
|
|
||||||
```ts
|
```typescript
|
||||||
import { FileFsRef } from '@vercel/build-utils';
|
import { FileFsRef } from '@vercel/build-utils';
|
||||||
```
|
```
|
||||||
|
|
||||||
This is a [JavaScript class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) that represents an abstract instance of a file present in the filesystem that the build process is executing in.
|
This is a [class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) that represents an abstract instance of a file present in the filesystem that the build process is executing in.
|
||||||
|
|
||||||
**Properties:**
|
**Properties:**
|
||||||
|
|
||||||
- `mode : Number` file mode
|
- `mode: Number` file mode
|
||||||
- `fsPath : String` the absolute path of the file in file system
|
- `fsPath: String` the absolute path of the file in file system
|
||||||
|
|
||||||
**Methods:**
|
**Methods:**
|
||||||
|
|
||||||
- `static async fromStream({ mode : Number, stream : Stream, fsPath : String }) : FileFsRef` creates an instance of a [FileFsRef](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) from `Stream`, placing file at `fsPath` with `mode`
|
- `static async fromStream({ mode: Number, stream: Stream, fsPath: String }): FileFsRef` creates an instance of a [FileFsRef](#FileFsRef) from `Stream`, placing file at `fsPath` with `mode`
|
||||||
- `toStream() : Stream` creates a [Stream](https://nodejs.org/api/stream.html) of the file body
|
- `toStream(): Stream` creates a [Stream](https://nodejs.org/api/stream.html) of the file body
|
||||||
|
|
||||||
### `FileBlob`
|
### `FileBlob`
|
||||||
|
|
||||||
```ts
|
```typescript
|
||||||
import { FileBlob } from '@vercel/build-utils';
|
import { FileBlob } from '@vercel/build-utils';
|
||||||
```
|
```
|
||||||
|
|
||||||
This is a [JavaScript class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) that represents an abstract instance of a file present in memory.
|
This is a [class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) that represents an abstract instance of a file present in memory.
|
||||||
|
|
||||||
**Properties:**
|
**Properties:**
|
||||||
|
|
||||||
- `mode : Number` file mode
|
- `mode: Number` file mode
|
||||||
- `data : String | Buffer` the body of the file
|
- `data: String | Buffer` the body of the file
|
||||||
|
|
||||||
**Methods:**
|
**Methods:**
|
||||||
|
|
||||||
- `static async fromStream({ mode : Number, stream : Stream }) :FileBlob` creates an instance of a [FileBlob](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) from [`Stream`](https://nodejs.org/api/stream.html) with `mode`
|
- `static async fromStream({ mode: Number, stream: Stream }): FileBlob` creates an instance of a [FileBlob](#FileBlob) from [`Stream`](https://nodejs.org/api/stream.html) with `mode`
|
||||||
- `toStream() : Stream` creates a [Stream](https://nodejs.org/api/stream.html) of the file body
|
- `toStream(): Stream` creates a [Stream](https://nodejs.org/api/stream.html) of the file body
|
||||||
|
|
||||||
### `Lambda`
|
### `Lambda`
|
||||||
|
|
||||||
```ts
|
```typescript
|
||||||
import { Lambda } from '@vercel/build-utils';
|
import { Lambda } from '@vercel/build-utils';
|
||||||
```
|
```
|
||||||
|
|
||||||
This is a [JavaScript class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes), called a Serverless Function, that can be created by supplying `files`, `handler`, `runtime`, and `environment` as an object to the [`createLambda`](#createlambda) helper. The instances of this class should not be created directly. Instead, invoke the [`createLambda`](#createlambda) helper function.
|
This is a [class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) that represents a Serverless Function. An instance can be created by supplying `files`, `handler`, `runtime`, and `environment` as an object to the [`createLambda`](#createlambda) helper. The instances of this class should not be created directly. Instead, invoke the [`createLambda`](#createlambda) helper function.
|
||||||
|
|
||||||
**Properties:**
|
**Properties:**
|
||||||
|
|
||||||
- `files : Files` the internal filesystem of the lambda
|
- `files: Files` the internal filesystem of the lambda
|
||||||
- `handler : String` path to handler file and (optionally) a function name it exports
|
- `handler: String` path to handler file and (optionally) a function name it exports
|
||||||
- `runtime : LambdaRuntime` the name of the lambda runtime
|
- `runtime: LambdaRuntime` the name of the lambda runtime
|
||||||
- `environment : Object` key-value map of handler-related (aside of those passed by user) environment variables
|
- `environment: Object` key-value map of handler-related (aside of those passed by user) environment variables
|
||||||
|
|
||||||
### `LambdaRuntime`
|
### `LambdaRuntime`
|
||||||
|
|
||||||
@@ -291,15 +338,15 @@ This is an abstract enumeration type that is implemented by one of the following
|
|||||||
- `ruby2.5`
|
- `ruby2.5`
|
||||||
- `provided`
|
- `provided`
|
||||||
|
|
||||||
## JavaScript API
|
## `@vercel/build-utils` Helper Functions
|
||||||
|
|
||||||
The following is exposed by `@vercel/build-utils` to simplify the process of writing Runtimes, manipulating the file system, using the above types, etc.
|
The following is exposed by `@vercel/build-utils` to simplify the process of writing Runtimes, manipulating the file system, using the above types, etc.
|
||||||
|
|
||||||
### `createLambda`
|
### `createLambda()`
|
||||||
|
|
||||||
Signature: `createLambda(Object spec) : Lambda`
|
Signature: `createLambda(Object spec): Lambda`
|
||||||
|
|
||||||
```ts
|
```typescript
|
||||||
import { createLambda } from '@vercel/build-utils';
|
import { createLambda } from '@vercel/build-utils';
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -316,29 +363,33 @@ await createLambda({
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### `download`
|
### `download()`
|
||||||
|
|
||||||
Signature: `download() : Files`
|
Signature: `download(): Files`
|
||||||
|
|
||||||
```ts
|
```typescript
|
||||||
import { download } from '@vercel/build-utils';
|
import { download } from '@vercel/build-utils';
|
||||||
```
|
```
|
||||||
|
|
||||||
This utility allows you to download the contents of a [`Files`](#files) data structure, therefore creating the filesystem represented in it.
|
This utility allows you to download the contents of a [`Files`](#files) data
|
||||||
|
structure, therefore creating the filesystem represented in it.
|
||||||
|
|
||||||
Since `Files` is an abstract way of representing files, you can think of `download` as a way of making that virtual filesystem _real_.
|
Since `Files` is an abstract way of representing files, you can think of
|
||||||
|
`download()` as a way of making that virtual filesystem _real_.
|
||||||
|
|
||||||
If the **optional** `meta` property is passed (the argument for [build](#build)), only the files that have changed are downloaded. This is decided using `filesRemoved` and `filesChanged` inside that object.
|
If the **optional** `meta` property is passed (the argument for
|
||||||
|
[`build()`](#build)), only the files that have changed are downloaded.
|
||||||
|
This is decided using `filesRemoved` and `filesChanged` inside that object.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
await download(files, workPath, meta);
|
await download(files, workPath, meta);
|
||||||
```
|
```
|
||||||
|
|
||||||
### `glob`
|
### `glob()`
|
||||||
|
|
||||||
Signature: `glob() : Files`
|
Signature: `glob(): Files`
|
||||||
|
|
||||||
```ts
|
```typescript
|
||||||
import { glob } from '@vercel/build-utils';
|
import { glob } from '@vercel/build-utils';
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -355,21 +406,21 @@ exports.build = ({ files, workPath }) => {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### `getWriteableDirectory`
|
### `getWritableDirectory()`
|
||||||
|
|
||||||
Signature: `getWriteableDirectory() : String`
|
Signature: `getWritableDirectory(): String`
|
||||||
|
|
||||||
```ts
|
```typescript
|
||||||
import { getWriteableDirectory } from '@vercel/build-utils';
|
import { getWritableDirectory } from '@vercel/build-utils';
|
||||||
```
|
```
|
||||||
|
|
||||||
In some occasions, you might want to write to a temporary directory.
|
In some occasions, you might want to write to a temporary directory.
|
||||||
|
|
||||||
### `rename`
|
### `rename()`
|
||||||
|
|
||||||
Signature: `rename(Files) : Files`
|
Signature: `rename(Files, Function): Files`
|
||||||
|
|
||||||
```ts
|
```typescript
|
||||||
import { rename } from '@vercel/build-utils';
|
import { rename } from '@vercel/build-utils';
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { NowRequest, NowResponse } from '@now/node';
|
import { NowRequest, NowResponse } from '@vercel/node';
|
||||||
import { errorHandler } from './error-handler';
|
import { errorHandler } from './error-handler';
|
||||||
|
|
||||||
type Handler = (req: NowRequest, res: NowResponse) => Promise<any>;
|
type Handler = (req: NowRequest, res: NowResponse) => Promise<any>;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import fs from 'fs';
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import tar from 'tar-fs';
|
import tar from 'tar-fs';
|
||||||
import { extract } from '../../_lib/examples/extract';
|
import { extract } from '../../_lib/examples/extract';
|
||||||
import { NowRequest, NowResponse } from '@now/node';
|
import { NowRequest, NowResponse } from '@vercel/node';
|
||||||
import { withApiHandler } from '../../_lib/util/with-api-handler';
|
import { withApiHandler } from '../../_lib/util/with-api-handler';
|
||||||
|
|
||||||
const TMP_DIR = '/tmp';
|
const TMP_DIR = '/tmp';
|
||||||
@@ -15,8 +15,8 @@ function notFound(res: NowResponse, message: string) {
|
|||||||
return res.status(404).send({
|
return res.status(404).send({
|
||||||
error: {
|
error: {
|
||||||
code: 'not_found',
|
code: 'not_found',
|
||||||
message
|
message,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +35,10 @@ function streamToBuffer(stream: any) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withApiHandler(async function(req: NowRequest, res: NowResponse) {
|
export default withApiHandler(async function (
|
||||||
|
req: NowRequest,
|
||||||
|
res: NowResponse
|
||||||
|
) {
|
||||||
const ext = '.tar.gz';
|
const ext = '.tar.gz';
|
||||||
const { segment = '' } = req.query;
|
const { segment = '' } = req.query;
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import parseGitUrl from 'parse-github-url';
|
import parseGitUrl from 'parse-github-url';
|
||||||
import { NowRequest, NowResponse } from '@now/node';
|
import { NowRequest, NowResponse } from '@vercel/node';
|
||||||
import { withApiHandler } from '../_lib/util/with-api-handler';
|
import { withApiHandler } from '../_lib/util/with-api-handler';
|
||||||
import { getGitHubRepoInfo } from '../_lib/examples/github-repo-info';
|
import { getGitHubRepoInfo } from '../_lib/examples/github-repo-info';
|
||||||
import { getGitLabRepoInfo } from '../_lib/examples/gitlab-repo-info';
|
import { getGitLabRepoInfo } from '../_lib/examples/gitlab-repo-info';
|
||||||
|
|
||||||
export default withApiHandler(async function(
|
export default withApiHandler(async function (
|
||||||
req: NowRequest,
|
req: NowRequest,
|
||||||
res: NowResponse
|
res: NowResponse
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { NowRequest, NowResponse } from '@now/node';
|
import { NowRequest, NowResponse } from '@vercel/node';
|
||||||
import { getExampleList } from '../_lib/examples/example-list';
|
import { getExampleList } from '../_lib/examples/example-list';
|
||||||
import { withApiHandler } from '../_lib/util/with-api-handler';
|
import { withApiHandler } from '../_lib/util/with-api-handler';
|
||||||
|
|
||||||
export default withApiHandler(async function(
|
export default withApiHandler(async function (
|
||||||
req: NowRequest,
|
req: NowRequest,
|
||||||
res: NowResponse
|
res: NowResponse
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { extract } from '../_lib/examples/extract';
|
import { extract } from '../_lib/examples/extract';
|
||||||
import { summary } from '../_lib/examples/summary';
|
import { summary } from '../_lib/examples/summary';
|
||||||
import { NowRequest, NowResponse } from '@now/node';
|
import { NowRequest, NowResponse } from '@vercel/node';
|
||||||
import { mapOldToNew } from '../_lib/examples/map-old-to-new';
|
import { mapOldToNew } from '../_lib/examples/map-old-to-new';
|
||||||
import { withApiHandler } from '../_lib/util/with-api-handler';
|
import { withApiHandler } from '../_lib/util/with-api-handler';
|
||||||
|
|
||||||
export default withApiHandler(async function(
|
export default withApiHandler(async function (
|
||||||
req: NowRequest,
|
req: NowRequest,
|
||||||
res: NowResponse
|
res: NowResponse
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -1,21 +1,27 @@
|
|||||||
import { NowRequest, NowResponse } from '@now/node';
|
import { NowRequest, NowResponse } from '@vercel/node';
|
||||||
import { withApiHandler } from './_lib/util/with-api-handler';
|
import { withApiHandler } from './_lib/util/with-api-handler';
|
||||||
import frameworkList, { Framework } from '../packages/frameworks';
|
import _frameworks, { Framework } from '../packages/frameworks';
|
||||||
|
|
||||||
const frameworks = (frameworkList as Framework[]).map(frameworkItem => {
|
const frameworks = (_frameworks as Framework[])
|
||||||
const framework = {
|
.sort(
|
||||||
...frameworkItem,
|
(a, b) =>
|
||||||
detectors: undefined,
|
(a.sort || Number.MAX_SAFE_INTEGER) - (b.sort || Number.MAX_SAFE_INTEGER)
|
||||||
};
|
)
|
||||||
|
.map(frameworkItem => {
|
||||||
|
const framework = {
|
||||||
|
...frameworkItem,
|
||||||
|
detectors: undefined,
|
||||||
|
sort: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
if (framework.logo) {
|
if (framework.logo) {
|
||||||
framework.logo = `https://res.cloudinary.com/zeit-inc/image/fetch/${framework.logo}`;
|
framework.logo = `https://res.cloudinary.com/zeit-inc/image/fetch/${framework.logo}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return framework;
|
return framework;
|
||||||
});
|
});
|
||||||
|
|
||||||
export default withApiHandler(async function(
|
export default withApiHandler(async function (
|
||||||
req: NowRequest,
|
req: NowRequest,
|
||||||
res: NowResponse
|
res: NowResponse
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -13,12 +13,12 @@
|
|||||||
"node-fetch": "2.6.0",
|
"node-fetch": "2.6.0",
|
||||||
"parse-github-url": "1.0.2",
|
"parse-github-url": "1.0.2",
|
||||||
"tar-fs": "2.0.0",
|
"tar-fs": "2.0.0",
|
||||||
"typescript": "3.7.4",
|
|
||||||
"unzip-stream": "0.3.0"
|
"unzip-stream": "0.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@now/node": "1.3.3",
|
|
||||||
"@types/node": "13.1.4",
|
"@types/node": "13.1.4",
|
||||||
"@types/node-fetch": "2.5.4"
|
"@types/node-fetch": "2.5.4",
|
||||||
|
"@vercel/node": "1.7.2",
|
||||||
|
"typescript": "3.9.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,6 @@
|
|||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
"@now/node@1.3.3":
|
|
||||||
version "1.3.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/@now/node/-/node-1.3.3.tgz#5407cb6a730d4dd9b6b6b0bc4a316f29086c9feb"
|
|
||||||
integrity sha512-s1qajtQttWhhSs1k6FX0/6eTFYFUplzultrQeKfOPMoYzzz6OxDq5qrQ3elpsGlZlDVmO+x+JOJ7yad+3yBgpg==
|
|
||||||
dependencies:
|
|
||||||
"@types/node" "*"
|
|
||||||
|
|
||||||
"@sentry/apm@5.11.1":
|
"@sentry/apm@5.11.1":
|
||||||
version "5.11.1"
|
version "5.11.1"
|
||||||
resolved "https://registry.yarnpkg.com/@sentry/apm/-/apm-5.11.1.tgz#cc89fa4150056fbf009f92eca94fccc3980db34e"
|
resolved "https://registry.yarnpkg.com/@sentry/apm/-/apm-5.11.1.tgz#cc89fa4150056fbf009f92eca94fccc3980db34e"
|
||||||
@@ -141,11 +134,25 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@vercel/node@1.7.2":
|
||||||
|
version "1.7.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vercel/node/-/node-1.7.2.tgz#85cb8aac661c02dfef6fe752740f5b162e90767b"
|
||||||
|
integrity sha512-XV5lrLC+K/cxsaFj8H2OoGu1zliOqnxcrOnPInI8HmQjR/Tztt+0nzgpt+7sx8wXcrib0Nu7lK303jP7VjSETw==
|
||||||
|
dependencies:
|
||||||
|
"@types/node" "*"
|
||||||
|
ts-node "8.9.1"
|
||||||
|
typescript "3.9.3"
|
||||||
|
|
||||||
agent-base@5:
|
agent-base@5:
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c"
|
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c"
|
||||||
integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==
|
integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==
|
||||||
|
|
||||||
|
arg@^4.1.0:
|
||||||
|
version "4.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
|
||||||
|
integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
|
||||||
|
|
||||||
binary@^0.3.0:
|
binary@^0.3.0:
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79"
|
resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79"
|
||||||
@@ -161,6 +168,11 @@ bl@^3.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
readable-stream "^3.0.1"
|
readable-stream "^3.0.1"
|
||||||
|
|
||||||
|
buffer-from@^1.0.0:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
|
||||||
|
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
|
||||||
|
|
||||||
buffers@~0.1.1:
|
buffers@~0.1.1:
|
||||||
version "0.1.1"
|
version "0.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb"
|
resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb"
|
||||||
@@ -229,6 +241,11 @@ defer-to-connect@^1.1.1:
|
|||||||
resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.1.tgz#88ae694b93f67b81815a2c8c769aef6574ac8f2f"
|
resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.1.tgz#88ae694b93f67b81815a2c8c769aef6574ac8f2f"
|
||||||
integrity sha512-J7thop4u3mRTkYRQ+Vpfwy2G5Ehoy82I14+14W4YMDLKdWloI9gSzRbV30s/NckQGVJtPkWNcW4oMAUigTdqiQ==
|
integrity sha512-J7thop4u3mRTkYRQ+Vpfwy2G5Ehoy82I14+14W4YMDLKdWloI9gSzRbV30s/NckQGVJtPkWNcW4oMAUigTdqiQ==
|
||||||
|
|
||||||
|
diff@^4.0.1:
|
||||||
|
version "4.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
|
||||||
|
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
|
||||||
|
|
||||||
duplexer3@^0.1.4:
|
duplexer3@^0.1.4:
|
||||||
version "0.1.4"
|
version "0.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
|
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
|
||||||
@@ -313,6 +330,11 @@ lru_map@^0.3.3:
|
|||||||
resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd"
|
resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd"
|
||||||
integrity sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0=
|
integrity sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0=
|
||||||
|
|
||||||
|
make-error@^1.1.1:
|
||||||
|
version "1.3.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
|
||||||
|
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
|
||||||
|
|
||||||
mimic-response@^1.0.0:
|
mimic-response@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
|
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
|
||||||
@@ -396,6 +418,19 @@ safe-buffer@~5.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
|
||||||
integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
|
integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
|
||||||
|
|
||||||
|
source-map-support@^0.5.17:
|
||||||
|
version "0.5.19"
|
||||||
|
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
|
||||||
|
integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
|
||||||
|
dependencies:
|
||||||
|
buffer-from "^1.0.0"
|
||||||
|
source-map "^0.6.0"
|
||||||
|
|
||||||
|
source-map@^0.6.0:
|
||||||
|
version "0.6.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||||
|
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||||
|
|
||||||
string_decoder@^1.1.1:
|
string_decoder@^1.1.1:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
|
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
|
||||||
@@ -434,6 +469,17 @@ to-readable-stream@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9"
|
resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9"
|
||||||
integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=
|
integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=
|
||||||
|
|
||||||
|
ts-node@8.9.1:
|
||||||
|
version "8.9.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.9.1.tgz#2f857f46c47e91dcd28a14e052482eb14cfd65a5"
|
||||||
|
integrity sha512-yrq6ODsxEFTLz0R3BX2myf0WBCSQh9A+py8PBo1dCzWIOcvisbyH6akNKqDHMgXePF2kir5mm5JXJTH3OUJYOQ==
|
||||||
|
dependencies:
|
||||||
|
arg "^4.1.0"
|
||||||
|
diff "^4.0.1"
|
||||||
|
make-error "^1.1.1"
|
||||||
|
source-map-support "^0.5.17"
|
||||||
|
yn "3.1.1"
|
||||||
|
|
||||||
tslib@^1.9.3:
|
tslib@^1.9.3:
|
||||||
version "1.10.0"
|
version "1.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
|
||||||
@@ -444,10 +490,15 @@ type-fest@^0.8.0:
|
|||||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
|
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
|
||||||
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
|
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
|
||||||
|
|
||||||
typescript@3.7.4:
|
typescript@3.9.3:
|
||||||
version "3.7.4"
|
version "3.9.3"
|
||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.4.tgz#1743a5ec5fef6a1fa9f3e4708e33c81c73876c19"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.3.tgz#d3ac8883a97c26139e42df5e93eeece33d610b8a"
|
||||||
integrity sha512-A25xv5XCtarLwXpcDNZzCGvW2D1S3/bACratYBx2sax8PefsFhlYmkQicKHvpYflFS8if4zne5zT5kpJ7pzuvw==
|
integrity sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==
|
||||||
|
|
||||||
|
typescript@3.9.6:
|
||||||
|
version "3.9.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.6.tgz#8f3e0198a34c3ae17091b35571d3afd31999365a"
|
||||||
|
integrity sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw==
|
||||||
|
|
||||||
unzip-stream@0.3.0:
|
unzip-stream@0.3.0:
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
@@ -466,3 +517,8 @@ wrappy@1:
|
|||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
||||||
|
|
||||||
|
yn@3.1.1:
|
||||||
|
version "3.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
|
||||||
|
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
|
||||||
|
|||||||
@@ -2,16 +2,14 @@
|
|||||||
|
|
||||||
#### Why This Error Occurred
|
#### Why This Error Occurred
|
||||||
|
|
||||||
The domain you supplied cannot be verified using either the intended set of nameservers or the given verification TXT record.
|
The domain you supplied cannot be verified using the intended nameservers.
|
||||||
|
|
||||||
#### Possible Ways to Fix It
|
#### Possible Way to Fix It
|
||||||
|
|
||||||
Apply the intended set of nameservers to your domain or add the given TXT verification record through your domain provider.
|
Apply the intended set of nameservers to your domain.
|
||||||
|
|
||||||
You can retrieve both the intended nameservers and TXT verification record for the domain you wish to verify by running `vercel domains inspect <domain>`.
|
You can retrieve both the intended nameservers and TXT verification record for the domain you wish to verify by running `vercel domains inspect <domain>`.
|
||||||
|
|
||||||
When you have added either verification method to your domain, you can run `vercel domains verify <domain>` again to complete verification for your domain.
|
|
||||||
|
|
||||||
Vercel will also automatically check periodically that your domain has been verified and automatically mark it as such if we detect either verification method on the domain.
|
Vercel will also automatically check periodically that your domain has been verified and automatically mark it as such if we detect either verification method on the domain.
|
||||||
|
|
||||||
If you would not like to verify your domain, you can remove it from your account using `vercel domains rm <domain>`.
|
If you would not like to verify your domain, you can remove it from your account using `vercel domains rm <domain>`.
|
||||||
|
|||||||
14
errors/next-functions-config-optimized-lambdas.md
Normal file
14
errors/next-functions-config-optimized-lambdas.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# `@vercel/next` Functions Config Optimized Lambdas Opt-out
|
||||||
|
|
||||||
|
#### Why This Warning Occurred
|
||||||
|
|
||||||
|
`@vercel/next` by default now bundles pages into optimized functions, minimizing bootup time and increasing overall application throughput.
|
||||||
|
When the `functions` config is added in `now.json` or `vercel.json`, it causes conflicts with this optimization, so it is opted-out.
|
||||||
|
|
||||||
|
#### Possible Ways to Fix It
|
||||||
|
|
||||||
|
Remove the `functions` config from your `now.json` or `vercel.json` to take advantage of this optimization.
|
||||||
|
|
||||||
|
### Useful Links
|
||||||
|
|
||||||
|
- [Functions Config Documentation](https://vercel.com/docs/configuration?query=functions#project/functions)
|
||||||
16
errors/next-legacy-routes-optimized-lambdas.md
Normal file
16
errors/next-legacy-routes-optimized-lambdas.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# `@vercel/next` Legacy Routes Optimized Lambdas Opt-out
|
||||||
|
|
||||||
|
#### Why This Warning Occurred
|
||||||
|
|
||||||
|
`@vercel/next` by default now bundles pages into optimized functions, minimizing bootup time and increasing overall application throughput.
|
||||||
|
When legacy `routes` are added in `now.json` or `vercel.json`, they cause conflicts with this optimization, so it is opted-out.
|
||||||
|
|
||||||
|
#### Possible Ways to Fix It
|
||||||
|
|
||||||
|
Migrate from using legacy `routes` to the new `rewrites`, `redirects`, and `headers` configurations in your `now.json` or `vercel.json` file or leverage them directly in your `next.config.js` with the built-in [custom routes support](https://github.com/zeit/next.js/issues/9081)
|
||||||
|
|
||||||
|
### Useful Links
|
||||||
|
|
||||||
|
- [Rewrites Documentation](https://vercel.com/docs/configuration?query=rewrites#project/rewrites)
|
||||||
|
- [Redirects Documentation](https://vercel.com/docs/configuration?query=rewrites#project/redirects)
|
||||||
|
- [Headers Documentation](https://vercel.com/docs/configuration?query=rewrites#project/headers)
|
||||||
4
examples/blitzjs/.babelrc.js
Normal file
4
examples/blitzjs/.babelrc.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: ["next/babel"],
|
||||||
|
plugins: [],
|
||||||
|
}
|
||||||
10
examples/blitzjs/.eslintrc.js
Normal file
10
examples/blitzjs/.eslintrc.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: ["react-app", "plugin:jsx-a11y/recommended"],
|
||||||
|
plugins: ["jsx-a11y"],
|
||||||
|
rules: {
|
||||||
|
"import/no-anonymous-default-export": "error",
|
||||||
|
"import/no-webpack-loader-syntax": "off",
|
||||||
|
"react/react-in-jsx-scope": "off", // React is always in scope with Blitz
|
||||||
|
"jsx-a11y/anchor-is-valid": "off", //Doesn't play well with Blitz/Next <Link> usage
|
||||||
|
},
|
||||||
|
}
|
||||||
56
examples/blitzjs/.gitignore
vendored
Normal file
56
examples/blitzjs/.gitignore
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# dependencies
|
||||||
|
node_modules
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.pnp.*
|
||||||
|
.npm
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# blitz
|
||||||
|
/.blitz/
|
||||||
|
/.next/
|
||||||
|
*.sqlite
|
||||||
|
.now
|
||||||
|
.vercel
|
||||||
|
.blitz-console-history
|
||||||
|
blitz-log.log
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env
|
||||||
|
.envrc
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
.nyc_output
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Caches
|
||||||
|
*.tsbuildinfo
|
||||||
|
.eslintcache
|
||||||
|
.node_repl_history
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
1
examples/blitzjs/.npmrc
Normal file
1
examples/blitzjs/.npmrc
Normal file
@@ -0,0 +1 @@
|
|||||||
|
save-exact=true
|
||||||
6
examples/blitzjs/.prettierignore
Normal file
6
examples/blitzjs/.prettierignore
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
.gitkeep
|
||||||
|
.env
|
||||||
|
*.ico
|
||||||
|
*.lock
|
||||||
|
db/migrations
|
||||||
|
|
||||||
21
examples/blitzjs/README.md
Normal file
21
examples/blitzjs/README.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|

|
||||||
|
|
||||||
|
This is a [Blitz.js](https://blitzjs.com/) project bootstrapped with `npx blitz new`.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
First, run the development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx blitz start
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
To learn more about Blitz.js, view [Blitzjs.com](https://blitzjs.com)
|
||||||
|
|
||||||
|
## Deploy on Vercel
|
||||||
|
|
||||||
|
View the [documentation on deploying to Vercel](https://blitzjs.com/docs/deploy-vercel)
|
||||||
0
examples/blitzjs/app/components/.keep
Normal file
0
examples/blitzjs/app/components/.keep
Normal file
21
examples/blitzjs/app/components/ErrorBoundary.tsx
Normal file
21
examples/blitzjs/app/components/ErrorBoundary.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
export default class ErrorBoundary extends React.Component<{
|
||||||
|
fallback: (error: any) => React.ReactNode
|
||||||
|
}> {
|
||||||
|
state = { hasError: false, error: null }
|
||||||
|
|
||||||
|
static getDerivedStateFromError(error: any) {
|
||||||
|
return {
|
||||||
|
hasError: true,
|
||||||
|
error,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.hasError) {
|
||||||
|
return this.props.fallback(this.state.error)
|
||||||
|
}
|
||||||
|
return this.props.children
|
||||||
|
}
|
||||||
|
}
|
||||||
0
examples/blitzjs/app/layouts/.keep
Normal file
0
examples/blitzjs/app/layouts/.keep
Normal file
5
examples/blitzjs/app/pages/_app.tsx
Normal file
5
examples/blitzjs/app/pages/_app.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { AppProps } from "blitz"
|
||||||
|
|
||||||
|
export default function MyApp({ Component, pageProps }: AppProps) {
|
||||||
|
return <Component {...pageProps} />
|
||||||
|
}
|
||||||
23
examples/blitzjs/app/pages/_document.tsx
Normal file
23
examples/blitzjs/app/pages/_document.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { Document, Html, DocumentHead, Main, BlitzScript /*DocumentContext*/ } from "blitz"
|
||||||
|
|
||||||
|
class MyDocument extends Document {
|
||||||
|
// Only uncomment if you need to customize this behaviour
|
||||||
|
// static async getInitialProps(ctx: DocumentContext) {
|
||||||
|
// const initialProps = await Document.getInitialProps(ctx)
|
||||||
|
// return {...initialProps}
|
||||||
|
// }
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Html lang="en">
|
||||||
|
<DocumentHead />
|
||||||
|
<body>
|
||||||
|
<Main />
|
||||||
|
<BlitzScript />
|
||||||
|
</body>
|
||||||
|
</Html>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MyDocument
|
||||||
197
examples/blitzjs/app/pages/index.tsx
Normal file
197
examples/blitzjs/app/pages/index.tsx
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
import { Head, Link } from "blitz"
|
||||||
|
|
||||||
|
const Home = () => (
|
||||||
|
<div className="container">
|
||||||
|
<Head>
|
||||||
|
<title>blitzjs</title>
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
</Head>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<div className="logo">
|
||||||
|
<img src="/logo.png" alt="blitz.js" />
|
||||||
|
</div>
|
||||||
|
<p>1. Run this command in your terminal:</p>
|
||||||
|
<pre>
|
||||||
|
<code>blitz generate all project name:string</code>
|
||||||
|
</pre>
|
||||||
|
<p>2. Then run this command:</p>
|
||||||
|
<pre>
|
||||||
|
<code>blitz db migrate</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
3. Go to{" "}
|
||||||
|
<Link href="/projects">
|
||||||
|
<a>/projects</a>
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
<div className="buttons">
|
||||||
|
<a
|
||||||
|
className="button"
|
||||||
|
href="https://github.com/blitz-js/blitz/blob/master/USER_GUIDE.md?utm_source=blitz-new&utm_medium=app-template&utm_campaign=blitz-new"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
Documentation
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
className="button-outline"
|
||||||
|
href="https://github.com/blitz-js/blitz"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
Github Repo
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
className="button-outline"
|
||||||
|
href="https://slack.blitzjs.com"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
Slack Community
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<a
|
||||||
|
href="https://blitzjs.com?utm_source=blitz-new&utm_medium=app-template&utm_campaign=blitz-new"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
Powered by Blitz.js
|
||||||
|
</a>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<style jsx>{`
|
||||||
|
.container {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
padding: 5rem 0;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
main p {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
width: 100%;
|
||||||
|
height: 60px;
|
||||||
|
border-top: 1px solid #eaeaea;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #45009d;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a {
|
||||||
|
color: #f4f4f4;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo img {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
grid-gap: 0.5rem;
|
||||||
|
margin-top: 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.button {
|
||||||
|
background-color: #6700eb;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
color: #f4f4f4;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.button:hover {
|
||||||
|
background-color: #45009d;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.button-outline {
|
||||||
|
border: 2px solid #6700eb;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
color: #6700eb;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.button-outline:hover {
|
||||||
|
border-color: #45009d;
|
||||||
|
color: #45009d;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background: #fafafa;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||||
|
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
max-width: 800px;
|
||||||
|
margin-top: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.grid {
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
|
||||||
|
<style jsx global>{`
|
||||||
|
@import url("https://fonts.googleapis.com/css2?family=Libre+Franklin:wght@300;700&display=swap");
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
font-family: "Libre Franklin", -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||||
|
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default Home
|
||||||
15
examples/blitzjs/blitz.config.js
Normal file
15
examples/blitzjs/blitz.config.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
module.exports = {
|
||||||
|
/*
|
||||||
|
webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
|
||||||
|
// Note: we provide webpack above so you should not `require` it
|
||||||
|
// Perform customizations to webpack config
|
||||||
|
// Important: return the modified config
|
||||||
|
return config
|
||||||
|
},
|
||||||
|
webpackDevMiddleware: (config) => {
|
||||||
|
// Perform customizations to webpack dev middleware config
|
||||||
|
// Important: return the modified config
|
||||||
|
return config
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
}
|
||||||
15
examples/blitzjs/db/index.ts
Normal file
15
examples/blitzjs/db/index.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { PrismaClient } from "@prisma/client"
|
||||||
|
export * from "@prisma/client"
|
||||||
|
|
||||||
|
let prisma: PrismaClient
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === "production") {
|
||||||
|
prisma = new PrismaClient()
|
||||||
|
} else {
|
||||||
|
// Ensure the prisma instance is re-used during hot-reloading
|
||||||
|
// Otherwise, a new client will be created on every reload
|
||||||
|
global["prisma"] = global["prisma"] || new PrismaClient()
|
||||||
|
prisma = global["prisma"]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default prisma
|
||||||
0
examples/blitzjs/db/migrations/.keep
Normal file
0
examples/blitzjs/db/migrations/.keep
Normal file
27
examples/blitzjs/db/schema.prisma
Normal file
27
examples/blitzjs/db/schema.prisma
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// This is your Prisma schema file,
|
||||||
|
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||||
|
|
||||||
|
datasource sqlite {
|
||||||
|
provider = "sqlite"
|
||||||
|
url = "file:./db.sqlite"
|
||||||
|
}
|
||||||
|
|
||||||
|
// SQLite is easy to start with, but if you use Postgres in production
|
||||||
|
// you should also use it in development with the following:
|
||||||
|
//datasource postgresql {
|
||||||
|
// provider = "postgresql"
|
||||||
|
// url = env("DATABASE_URL")
|
||||||
|
//}
|
||||||
|
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --------------------------------------
|
||||||
|
|
||||||
|
//model Project {
|
||||||
|
// id Int @default(autoincrement()) @id
|
||||||
|
// name String
|
||||||
|
//}
|
||||||
|
|
||||||
0
examples/blitzjs/integrations/.keep
Normal file
0
examples/blitzjs/integrations/.keep
Normal file
55
examples/blitzjs/package.json
Normal file
55
examples/blitzjs/package.json
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"name": "blitzjs",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"start": "blitz start",
|
||||||
|
"studio": "blitz db studio",
|
||||||
|
"build": "blitz build",
|
||||||
|
"lint": "eslint --ignore-path .gitignore --ext .js,.ts,.tsx .",
|
||||||
|
"test": "echo \"No tests yet\""
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"defaults"
|
||||||
|
],
|
||||||
|
"prettier": {
|
||||||
|
"semi": false,
|
||||||
|
"printWidth": 100
|
||||||
|
},
|
||||||
|
"husky": {
|
||||||
|
"hooks": {
|
||||||
|
"pre-commit": "lint-staged && pretty-quick --staged",
|
||||||
|
"pre-push": "blitz test"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*.{js,ts,tsx}": [
|
||||||
|
"eslint --fix"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/cli": "latest",
|
||||||
|
"@prisma/client": "latest",
|
||||||
|
"blitz": "latest",
|
||||||
|
"react": "experimental",
|
||||||
|
"react-dom": "experimental"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "16.9.36",
|
||||||
|
"@typescript-eslint/eslint-plugin": "2.x",
|
||||||
|
"@typescript-eslint/parser": "2.x",
|
||||||
|
"babel-eslint": "10.1.0",
|
||||||
|
"eslint": "6.8.0",
|
||||||
|
"eslint-config-react-app": "5.2.1",
|
||||||
|
"eslint-plugin-flowtype": "4.7.0",
|
||||||
|
"eslint-plugin-import": "2.21.2",
|
||||||
|
"eslint-plugin-jsx-a11y": "6.2.3",
|
||||||
|
"eslint-plugin-react": "7.20.0",
|
||||||
|
"eslint-plugin-react-hooks": "3.0.0",
|
||||||
|
"husky": "4.2.5",
|
||||||
|
"lint-staged": "10.2.10",
|
||||||
|
"prettier": "2.0.5",
|
||||||
|
"pretty-quick": "2.0.1",
|
||||||
|
"typescript": "3.9.5"
|
||||||
|
},
|
||||||
|
"private": true
|
||||||
|
}
|
||||||
BIN
examples/blitzjs/public/favicon.ico
Executable file
BIN
examples/blitzjs/public/favicon.ico
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 556 B |
BIN
examples/blitzjs/public/logo.png
Normal file
BIN
examples/blitzjs/public/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
20
examples/blitzjs/tsconfig.json
Normal file
20
examples/blitzjs/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"baseUrl": "./",
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": false,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve"
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules"],
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
|
||||||
|
}
|
||||||
0
examples/blitzjs/utils/.keep
Normal file
0
examples/blitzjs/utils/.keep
Normal file
11913
examples/gatsby/yarn.lock
Normal file
11913
examples/gatsby/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@@ -20,6 +20,7 @@
|
|||||||
"@zeit/ncc": "0.20.4",
|
"@zeit/ncc": "0.20.4",
|
||||||
"async-retry": "1.2.3",
|
"async-retry": "1.2.3",
|
||||||
"buffer-replace": "1.0.0",
|
"buffer-replace": "1.0.0",
|
||||||
|
"cheerio": "1.0.0-rc.3",
|
||||||
"eslint": "6.2.2",
|
"eslint": "6.2.2",
|
||||||
"eslint-config-prettier": "6.1.0",
|
"eslint-config-prettier": "6.1.0",
|
||||||
"eslint-plugin-jest": "23.8.2",
|
"eslint-plugin-jest": "23.8.2",
|
||||||
@@ -28,7 +29,7 @@
|
|||||||
"lint-staged": "9.2.5",
|
"lint-staged": "9.2.5",
|
||||||
"node-fetch": "2.6.0",
|
"node-fetch": "2.6.0",
|
||||||
"npm-package-arg": "6.1.0",
|
"npm-package-arg": "6.1.0",
|
||||||
"prettier": "1.18.2"
|
"prettier": "2.0.5"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lerna": "lerna",
|
"lerna": "lerna",
|
||||||
@@ -63,7 +64,8 @@
|
|||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"trailingComma": "es5",
|
"trailingComma": "es5",
|
||||||
"singleQuote": true
|
"singleQuote": true,
|
||||||
|
"arrowParens": "avoid"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"root": true,
|
"root": true,
|
||||||
|
|||||||
@@ -1,12 +1,41 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"name": "Blitz.js",
|
||||||
|
"slug": "blitzjs",
|
||||||
|
"demo": "https://blitzjs.now-examples.now.sh",
|
||||||
|
"logo": "https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/blitz.svg",
|
||||||
|
"tagline": "Blitz.js: The Fullstack React Framework",
|
||||||
|
"description": "A brand new Blitz.js app - the result of running `npx blitz new`.",
|
||||||
|
"website": "https://blitzjs.com",
|
||||||
|
"detectors": {
|
||||||
|
"every": [
|
||||||
|
{
|
||||||
|
"path": "package.json",
|
||||||
|
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"blitz\":\\s*\".+?\"[^}]*}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"buildCommand": {
|
||||||
|
"placeholder": "`npm run build` or `blitz build`"
|
||||||
|
},
|
||||||
|
"devCommand": {
|
||||||
|
"value": "blitz start"
|
||||||
|
},
|
||||||
|
"outputDirectory": {
|
||||||
|
"placeholder": "Next.js default"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Next.js",
|
"name": "Next.js",
|
||||||
"slug": "nextjs",
|
"slug": "nextjs",
|
||||||
"demo": "https://nextjs.now-examples.now.sh",
|
"demo": "https://nextjs.now-examples.now.sh",
|
||||||
"logo": "https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/next.svg",
|
"logo": "https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/next.svg",
|
||||||
"tagline": "Next.js makes you productive with React instantly — whether you want to build static or dynamic sites. ",
|
"tagline": "Next.js makes you productive with React instantly — whether you want to build static or dynamic sites.",
|
||||||
"description": "A Next.js app and a Serverless Function API.",
|
"description": "A Next.js app and a Serverless Function API.",
|
||||||
"website": "https://nextjs.org",
|
"website": "https://nextjs.org",
|
||||||
|
"sort": 1,
|
||||||
"detectors": {
|
"detectors": {
|
||||||
"every": [
|
"every": [
|
||||||
{
|
{
|
||||||
@@ -35,6 +64,7 @@
|
|||||||
"tagline": "Gatsby helps developers build blazing fast websites and apps with React.",
|
"tagline": "Gatsby helps developers build blazing fast websites and apps with React.",
|
||||||
"description": "A Gatsby app, using the default starter theme and a Serverless Function API.",
|
"description": "A Gatsby app, using the default starter theme and a Serverless Function API.",
|
||||||
"website": "https://gatsbyjs.org",
|
"website": "https://gatsbyjs.org",
|
||||||
|
"sort": 2,
|
||||||
"detectors": {
|
"detectors": {
|
||||||
"every": [
|
"every": [
|
||||||
{
|
{
|
||||||
@@ -62,7 +92,8 @@
|
|||||||
"logo": "https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/hexo.svg",
|
"logo": "https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/hexo.svg",
|
||||||
"tagline": "Hexo is a fast, simple & powerful blog framework powered by Node.js.",
|
"tagline": "Hexo is a fast, simple & powerful blog framework powered by Node.js.",
|
||||||
"description": "A Hexo site, created with the Hexo CLI.",
|
"description": "A Hexo site, created with the Hexo CLI.",
|
||||||
"website": "https://hexo.io/",
|
"website": "https://hexo.io",
|
||||||
|
"sort": 3,
|
||||||
"detectors": {
|
"detectors": {
|
||||||
"every": [
|
"every": [
|
||||||
{
|
{
|
||||||
@@ -90,7 +121,8 @@
|
|||||||
"logo": "https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/eleventy.svg",
|
"logo": "https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/eleventy.svg",
|
||||||
"tagline": "11ty is a simpler static site generator written in JavaScript, created to be an alternative to Jekyll.",
|
"tagline": "11ty is a simpler static site generator written in JavaScript, created to be an alternative to Jekyll.",
|
||||||
"description": "An Eleventy site, created with npm init.",
|
"description": "An Eleventy site, created with npm init.",
|
||||||
"website": "https://www.11ty.dev/",
|
"website": "https://www.11ty.dev",
|
||||||
|
"sort": 4,
|
||||||
"detectors": {
|
"detectors": {
|
||||||
"every": [
|
"every": [
|
||||||
{
|
{
|
||||||
@@ -634,13 +666,13 @@
|
|||||||
"every": [
|
"every": [
|
||||||
{
|
{
|
||||||
"path": "package.json",
|
"path": "package.json",
|
||||||
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"nuxt\":\\s*\".+?\"[^}]*}"
|
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"nuxt(-edge)?\":\\s*\".+?\"[^}]*}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"buildCommand": {
|
"buildCommand": {
|
||||||
"placeholder": "`npm run build` or `nuxt build`"
|
"placeholder": "`npm run build` or `nuxt generate`"
|
||||||
},
|
},
|
||||||
"devCommand": {
|
"devCommand": {
|
||||||
"value": "nuxt"
|
"value": "nuxt"
|
||||||
@@ -658,6 +690,7 @@
|
|||||||
"tagline": "Hugo is the world’s fastest framework for building websites, written in Go.",
|
"tagline": "Hugo is the world’s fastest framework for building websites, written in Go.",
|
||||||
"description": "A Hugo site, created with the Hugo CLI.",
|
"description": "A Hugo site, created with the Hugo CLI.",
|
||||||
"website": "https://gohugo.io",
|
"website": "https://gohugo.io",
|
||||||
|
"sort": 5,
|
||||||
"detectors": {
|
"detectors": {
|
||||||
"some": [
|
"some": [
|
||||||
{
|
{
|
||||||
|
|||||||
1
packages/frameworks/index.d.ts
vendored
1
packages/frameworks/index.d.ts
vendored
@@ -19,6 +19,7 @@ export interface Framework {
|
|||||||
tagline?: string;
|
tagline?: string;
|
||||||
website?: string;
|
website?: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
sort?: number;
|
||||||
detectors?: {
|
detectors?: {
|
||||||
every?: FrameworkDetectionItem[];
|
every?: FrameworkDetectionItem[];
|
||||||
some?: FrameworkDetectionItem[];
|
some?: FrameworkDetectionItem[];
|
||||||
|
|||||||
30
packages/frameworks/logos/blitz.svg
Normal file
30
packages/frameworks/logos/blitz.svg
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0)">
|
||||||
|
<path d="M95.4242 249.857H173.991C203.89 249.857 232.049 263.909 250.026 287.799L327.526 390.789C328.991 392.736 329.212 395.349 328.095 397.513L283.421 484.069C281.278 488.221 275.532 488.71 272.719 484.978L95.4242 249.857Z" fill="url(#paint0_linear)"/>
|
||||||
|
<g filter="url(#filter0_d)">
|
||||||
|
<path d="M404.558 249.991H325.991C296.093 249.991 267.933 235.939 249.956 212.049L172.456 109.059C170.991 107.112 170.771 104.499 171.888 102.335L216.561 15.7794C218.705 11.6267 224.45 11.1382 227.264 14.8695L404.558 249.991Z" fill="url(#paint1_linear)"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter0_d" x="71.1812" y="-39.6553" width="433.377" height="437.646" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||||
|
<feOffset dy="48"/>
|
||||||
|
<feGaussianBlur stdDeviation="50"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0.270588 0 0 0 0 0 0 0 0 0 0.615686 0 0 0 0.2 0"/>
|
||||||
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
||||||
|
</filter>
|
||||||
|
<linearGradient id="paint0_linear" x1="163.936" y1="392.775" x2="316.429" y2="155.244" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#6700EB"/>
|
||||||
|
<stop offset="1" stop-color="#45009D"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint1_linear" x1="336.047" y1="107.073" x2="183.554" y2="344.604" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#6700EB"/>
|
||||||
|
<stop offset="1" stop-color="#45009D"/>
|
||||||
|
</linearGradient>
|
||||||
|
<clipPath id="clip0">
|
||||||
|
<rect width="500" height="500" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vercel/frameworks",
|
"name": "@vercel/frameworks",
|
||||||
"version": "0.0.15-canary.4",
|
"version": "0.0.17",
|
||||||
"main": "frameworks.json",
|
"main": "frameworks.json",
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "24.0.22",
|
"@types/jest": "24.0.22",
|
||||||
"@types/node": "12.0.4",
|
"@types/node": "12.0.4",
|
||||||
"ajv": "6.10.2",
|
"ajv": "6.12.2",
|
||||||
"jest": "24.9.0",
|
"jest": "24.9.0",
|
||||||
"ts-jest": "24.1.0",
|
"ts-jest": "24.1.0",
|
||||||
"typescript": "3.9.3"
|
"typescript": "3.9.3"
|
||||||
|
|||||||
38
packages/frameworks/test/frameworks.unit.test.ts
vendored
38
packages/frameworks/test/frameworks.unit.test.ts
vendored
@@ -1,11 +1,9 @@
|
|||||||
import Ajv from 'ajv';
|
import Ajv from 'ajv';
|
||||||
import path from 'path';
|
import { join } from 'path';
|
||||||
import { existsSync } from 'fs';
|
import { existsSync } from 'fs';
|
||||||
|
import { isString } from 'util';
|
||||||
import { Framework } from '../';
|
import { Framework } from '../';
|
||||||
|
const frameworkList = require('../frameworks.json') as Framework[];
|
||||||
function isString(arg: any): arg is string {
|
|
||||||
return typeof arg === 'string';
|
|
||||||
}
|
|
||||||
|
|
||||||
const SchemaFrameworkDetectionItem = {
|
const SchemaFrameworkDetectionItem = {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
@@ -60,6 +58,7 @@ const Schema = {
|
|||||||
properties: {
|
properties: {
|
||||||
name: { type: 'string' },
|
name: { type: 'string' },
|
||||||
slug: { type: ['string', 'null'] },
|
slug: { type: ['string', 'null'] },
|
||||||
|
sort: { type: 'number' },
|
||||||
logo: { type: 'string' },
|
logo: { type: 'string' },
|
||||||
demo: { type: 'string' },
|
demo: { type: 'string' },
|
||||||
tagline: { type: 'string' },
|
tagline: { type: 'string' },
|
||||||
@@ -89,12 +88,10 @@ const Schema = {
|
|||||||
|
|
||||||
describe('frameworks', () => {
|
describe('frameworks', () => {
|
||||||
it('ensure there is an example for every framework', async () => {
|
it('ensure there is an example for every framework', async () => {
|
||||||
const root = path.join(__dirname, '..', '..', '..');
|
const root = join(__dirname, '..', '..', '..');
|
||||||
const getExample = (name: string) => path.join(root, 'examples', name);
|
const getExample = (name: string) => join(root, 'examples', name);
|
||||||
|
|
||||||
const frameworks = require('../frameworks.json') as Framework[];
|
const result = frameworkList
|
||||||
|
|
||||||
const result = frameworks
|
|
||||||
.map(f => f.slug)
|
.map(f => f.slug)
|
||||||
.filter(isString)
|
.filter(isString)
|
||||||
.filter(f => existsSync(getExample(f)) === false);
|
.filter(f => existsSync(getExample(f)) === false);
|
||||||
@@ -103,10 +100,8 @@ describe('frameworks', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('ensure schema', async () => {
|
it('ensure schema', async () => {
|
||||||
const frameworks = require('../frameworks.json') as Framework[];
|
|
||||||
|
|
||||||
const ajv = new Ajv();
|
const ajv = new Ajv();
|
||||||
const result = ajv.validate(Schema, frameworks);
|
const result = ajv.validate(Schema, frameworkList);
|
||||||
|
|
||||||
if (ajv.errors) {
|
if (ajv.errors) {
|
||||||
console.error(ajv.errors);
|
console.error(ajv.errors);
|
||||||
@@ -116,17 +111,26 @@ describe('frameworks', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('ensure logo', async () => {
|
it('ensure logo', async () => {
|
||||||
const frameworks = require('../frameworks.json') as Framework[];
|
const missing = frameworkList
|
||||||
|
|
||||||
const missing = frameworks
|
|
||||||
.map(f => f.logo)
|
.map(f => f.logo)
|
||||||
.filter(url => {
|
.filter(url => {
|
||||||
const prefix =
|
const prefix =
|
||||||
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/';
|
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/';
|
||||||
const name = url.replace(prefix, '');
|
const name = url.replace(prefix, '');
|
||||||
return existsSync(path.join(__dirname, '..', 'logos', name)) === false;
|
return existsSync(join(__dirname, '..', 'logos', name)) === false;
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(missing).toEqual([]);
|
expect(missing).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('ensure unique sort number', async () => {
|
||||||
|
const sortNumToSlug = new Map<number, string | null>();
|
||||||
|
frameworkList.forEach(f => {
|
||||||
|
if (f.sort) {
|
||||||
|
const duplicateSlug = sortNumToSlug.get(f.sort);
|
||||||
|
expect(duplicateSlug).toStrictEqual(undefined);
|
||||||
|
sortNumToSlug.set(f.sort, f.slug);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#!/bin/sh
|
#!/bin/bash
|
||||||
set -eu
|
set -euo pipefail
|
||||||
|
|
||||||
out="dist"
|
out="dist"
|
||||||
|
|
||||||
rm -rf "$out"
|
rm -rf "$out"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vercel/build-utils",
|
"name": "@vercel/build-utils",
|
||||||
"version": "2.3.2-canary.3",
|
"version": "2.4.3-canary.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.js",
|
"types": "./dist/index.d.js",
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import { isOfficialRuntime } from './';
|
|||||||
interface ErrorResponse {
|
interface ErrorResponse {
|
||||||
code: string;
|
code: string;
|
||||||
message: string;
|
message: string;
|
||||||
|
action?: string;
|
||||||
|
link?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Options {
|
interface Options {
|
||||||
@@ -82,6 +84,7 @@ export async function detectBuilders(
|
|||||||
defaultRoutes: Route[] | null;
|
defaultRoutes: Route[] | null;
|
||||||
redirectRoutes: Route[] | null;
|
redirectRoutes: Route[] | null;
|
||||||
rewriteRoutes: Route[] | null;
|
rewriteRoutes: Route[] | null;
|
||||||
|
errorRoutes: Route[] | null;
|
||||||
}> {
|
}> {
|
||||||
const errors: ErrorResponse[] = [];
|
const errors: ErrorResponse[] = [];
|
||||||
const warnings: ErrorResponse[] = [];
|
const warnings: ErrorResponse[] = [];
|
||||||
@@ -99,6 +102,7 @@ export async function detectBuilders(
|
|||||||
defaultRoutes: null,
|
defaultRoutes: null,
|
||||||
redirectRoutes: null,
|
redirectRoutes: null,
|
||||||
rewriteRoutes: null,
|
rewriteRoutes: null,
|
||||||
|
errorRoutes: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,6 +158,7 @@ export async function detectBuilders(
|
|||||||
defaultRoutes: null,
|
defaultRoutes: null,
|
||||||
redirectRoutes: null,
|
redirectRoutes: null,
|
||||||
rewriteRoutes: null,
|
rewriteRoutes: null,
|
||||||
|
errorRoutes: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,6 +236,7 @@ export async function detectBuilders(
|
|||||||
redirectRoutes: null,
|
redirectRoutes: null,
|
||||||
defaultRoutes: null,
|
defaultRoutes: null,
|
||||||
rewriteRoutes: null,
|
rewriteRoutes: null,
|
||||||
|
errorRoutes: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,6 +278,7 @@ export async function detectBuilders(
|
|||||||
redirectRoutes: null,
|
redirectRoutes: null,
|
||||||
defaultRoutes: null,
|
defaultRoutes: null,
|
||||||
rewriteRoutes: null,
|
rewriteRoutes: null,
|
||||||
|
errorRoutes: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,6 +316,7 @@ export async function detectBuilders(
|
|||||||
redirectRoutes: routesResult.redirectRoutes,
|
redirectRoutes: routesResult.redirectRoutes,
|
||||||
defaultRoutes: routesResult.defaultRoutes,
|
defaultRoutes: routesResult.defaultRoutes,
|
||||||
rewriteRoutes: routesResult.rewriteRoutes,
|
rewriteRoutes: routesResult.rewriteRoutes,
|
||||||
|
errorRoutes: routesResult.errorRoutes,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -456,7 +464,7 @@ function detectFrontBuilder(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (framework === 'nextjs') {
|
if (framework === 'nextjs' || framework === 'blitzjs') {
|
||||||
return { src: 'package.json', use: `@vercel/next${withTag}`, config };
|
return { src: 'package.json', use: `@vercel/next${withTag}`, config };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -490,7 +498,7 @@ function getMissingBuildScriptError() {
|
|||||||
code: 'missing_build_script',
|
code: 'missing_build_script',
|
||||||
message:
|
message:
|
||||||
'Your `package.json` file is missing a `build` property inside the `scripts` property.' +
|
'Your `package.json` file is missing a `build` property inside the `scripts` property.' +
|
||||||
'\nMore details: https://vercel.com/docs/v2/platform/frequently-asked-questions#missing-build-script',
|
'\nLearn More: https://vercel.com/docs/v2/platform/frequently-asked-questions#missing-build-script',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -602,20 +610,22 @@ function checkUnusedFunctions(
|
|||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
code: 'unused_function',
|
code: 'unused_function',
|
||||||
message: `The function for ${fnKey} can't be handled by any builder`,
|
message: `The pattern "${fnKey}" defined in \`functions\` doesn't match any Serverless Functions.`,
|
||||||
|
action: 'Learn More',
|
||||||
|
link: 'https://vercel.link/unmatched-function-pattern',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unusedFunctions.size) {
|
if (unusedFunctions.size) {
|
||||||
const [unusedFunction] = Array.from(unusedFunctions);
|
const [fnKey] = Array.from(unusedFunctions);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: 'unused_function',
|
code: 'unused_function',
|
||||||
message:
|
message: `The pattern "${fnKey}" defined in \`functions\` doesn't match any Serverless Functions inside the \`api\` directory.`,
|
||||||
`The function for ${unusedFunction} can't be handled by any builder. ` +
|
action: 'Learn More',
|
||||||
`Make sure it is inside the api/ directory.`,
|
link: 'https://vercel.link/unmatched-function-pattern',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -898,10 +908,17 @@ function getRouteResult(
|
|||||||
defaultRoutes: Route[];
|
defaultRoutes: Route[];
|
||||||
redirectRoutes: Route[];
|
redirectRoutes: Route[];
|
||||||
rewriteRoutes: Route[];
|
rewriteRoutes: Route[];
|
||||||
|
errorRoutes: Route[];
|
||||||
} {
|
} {
|
||||||
const defaultRoutes: Route[] = [];
|
const defaultRoutes: Route[] = [];
|
||||||
const redirectRoutes: Route[] = [];
|
const redirectRoutes: Route[] = [];
|
||||||
const rewriteRoutes: Route[] = [];
|
const rewriteRoutes: Route[] = [];
|
||||||
|
const errorRoutes: Route[] = [];
|
||||||
|
const isNextjs =
|
||||||
|
frontendBuilder &&
|
||||||
|
((frontendBuilder.use && frontendBuilder.use.startsWith('@vercel/next')) ||
|
||||||
|
(frontendBuilder.config &&
|
||||||
|
frontendBuilder.config.framework === 'nextjs'));
|
||||||
|
|
||||||
if (apiRoutes && apiRoutes.length > 0) {
|
if (apiRoutes && apiRoutes.length > 0) {
|
||||||
if (options.featHandleMiss) {
|
if (options.featHandleMiss) {
|
||||||
@@ -968,10 +985,21 @@ function getRouteResult(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.featHandleMiss && !isNextjs) {
|
||||||
|
// Exclude Next.js to avoid overriding custom error page
|
||||||
|
// https://nextjs.org/docs/advanced-features/custom-error-page
|
||||||
|
errorRoutes.push({
|
||||||
|
status: 404,
|
||||||
|
src: '^/(?!.*api).*$',
|
||||||
|
dest: options.cleanUrls ? '/404' : '/404.html',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
defaultRoutes,
|
defaultRoutes,
|
||||||
redirectRoutes,
|
redirectRoutes,
|
||||||
rewriteRoutes,
|
rewriteRoutes,
|
||||||
|
errorRoutes,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ export class NowBuildError extends Error {
|
|||||||
public hideStackTrace = true;
|
public hideStackTrace = true;
|
||||||
public code: string;
|
public code: string;
|
||||||
public link?: string;
|
public link?: string;
|
||||||
|
public action?: string;
|
||||||
|
|
||||||
constructor({ message, code, link }: Props) {
|
constructor({ message, code, link, action }: Props) {
|
||||||
super(message);
|
super(message);
|
||||||
this.code = code;
|
this.code = code;
|
||||||
this.link = link;
|
this.link = link;
|
||||||
|
this.action = action;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,4 +33,83 @@ interface Props {
|
|||||||
* link to more information about this error.
|
* link to more information about this error.
|
||||||
*/
|
*/
|
||||||
link?: string;
|
link?: string;
|
||||||
|
/**
|
||||||
|
* Optional "action" to display before the `link`, such as "Learn More".
|
||||||
|
*/
|
||||||
|
action?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPrettyError(obj: {
|
||||||
|
dataPath?: string;
|
||||||
|
message?: string;
|
||||||
|
params: any;
|
||||||
|
}): NowBuildError {
|
||||||
|
const docsUrl = 'https://vercel.com/docs/configuration';
|
||||||
|
try {
|
||||||
|
const { dataPath, params, message: ajvMessage } = obj;
|
||||||
|
const prop = getTopLevelPropertyName(dataPath);
|
||||||
|
let message =
|
||||||
|
dataPath && dataPath.startsWith('.') ? `\`${dataPath.slice(1)}\` ` : '';
|
||||||
|
|
||||||
|
if (params && typeof params.additionalProperty === 'string') {
|
||||||
|
const suggestion = getSuggestion(prop, params.additionalProperty);
|
||||||
|
message += `should NOT have additional property \`${params.additionalProperty}\`. ${suggestion}`;
|
||||||
|
} else if (params && typeof params.missingProperty === 'string') {
|
||||||
|
message += `missing required property \`${params.missingProperty}\`.`;
|
||||||
|
} else {
|
||||||
|
message += `${ajvMessage}.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new NowBuildError({
|
||||||
|
code: 'DEV_VALIDATE_CONFIG',
|
||||||
|
message: message,
|
||||||
|
link: prop ? `${docsUrl}#project/${prop.toLowerCase()}` : docsUrl,
|
||||||
|
action: 'View Documentation',
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
return new NowBuildError({
|
||||||
|
code: 'DEV_VALIDATE_CONFIG',
|
||||||
|
message: `Failed to validate configuration.`,
|
||||||
|
link: docsUrl,
|
||||||
|
action: 'View Documentation',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the top level property from the dataPath.
|
||||||
|
* `.cleanUrls` => `cleanUrls`
|
||||||
|
* `.headers[0].source` => `headers`
|
||||||
|
* `.headers[0].headers[0]` => `headers`
|
||||||
|
* `` => ``
|
||||||
|
*/
|
||||||
|
function getTopLevelPropertyName(dataPath?: string): string {
|
||||||
|
if (dataPath && dataPath.startsWith('.')) {
|
||||||
|
const lastIndex = dataPath.indexOf('[');
|
||||||
|
return lastIndex > -1 ? dataPath.slice(1, lastIndex) : dataPath.slice(1);
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapTypoToSuggestion: { [key: string]: { [key: string]: string } } = {
|
||||||
|
'': {
|
||||||
|
builder: 'builds',
|
||||||
|
'build.env': '{ "build": { "env": {"name": "value"} } }',
|
||||||
|
'builds.env': '{ "build": { "env": {"name": "value"} } }',
|
||||||
|
},
|
||||||
|
rewrites: { src: 'source', dest: 'destination' },
|
||||||
|
redirects: { src: 'source', dest: 'destination', status: 'statusCode' },
|
||||||
|
headers: { src: 'source', header: 'headers' },
|
||||||
|
routes: {
|
||||||
|
source: 'src',
|
||||||
|
destination: 'dest',
|
||||||
|
header: 'headers',
|
||||||
|
method: 'methods',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function getSuggestion(topLevelProp: string, additionalProperty: string) {
|
||||||
|
const choices = mapTypoToSuggestion[topLevelProp];
|
||||||
|
const choice = choices ? choices[additionalProperty] : undefined;
|
||||||
|
return choice ? `Did you mean \`${choice}\`?` : 'Please remove it.';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ async function readFileOrNull(file: string) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function readConfigFile<T>(files: string | string[]) {
|
export async function readConfigFile<T>(
|
||||||
|
files: string | string[]
|
||||||
|
): Promise<T | null> {
|
||||||
files = Array.isArray(files) ? files : [files];
|
files = Array.isArray(files) ? files : [files];
|
||||||
|
|
||||||
for (const name of files) {
|
for (const name of files) {
|
||||||
@@ -24,11 +26,11 @@ export async function readConfigFile<T>(files: string | string[]) {
|
|||||||
if (data) {
|
if (data) {
|
||||||
const str = data.toString('utf8');
|
const str = data.toString('utf8');
|
||||||
if (name.endsWith('.json')) {
|
if (name.endsWith('.json')) {
|
||||||
return JSON.parse(str);
|
return JSON.parse(str) as T;
|
||||||
} else if (name.endsWith('.toml')) {
|
} else if (name.endsWith('.toml')) {
|
||||||
return (toml.parse(str) as unknown) as T;
|
return (toml.parse(str) as unknown) as T;
|
||||||
} else if (name.endsWith('.yaml') || name.endsWith('.yml')) {
|
} else if (name.endsWith('.yaml') || name.endsWith('.yml')) {
|
||||||
return yaml.safeLoad(str, { filename: name });
|
return yaml.safeLoad(str, { filename: name }) as T;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -349,20 +349,35 @@ export async function runPipInstall(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getScriptName(
|
||||||
|
pkg: Pick<PackageJson, 'scripts'> | null | undefined,
|
||||||
|
possibleNames: Iterable<string>
|
||||||
|
): string | null {
|
||||||
|
if (pkg && pkg.scripts) {
|
||||||
|
for (const name of possibleNames) {
|
||||||
|
if (name in pkg.scripts) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export async function runPackageJsonScript(
|
export async function runPackageJsonScript(
|
||||||
destPath: string,
|
destPath: string,
|
||||||
scriptName: string,
|
scriptNames: string | Iterable<string>,
|
||||||
spawnOpts?: SpawnOptions
|
spawnOpts?: SpawnOptions
|
||||||
) {
|
) {
|
||||||
assert(path.isAbsolute(destPath));
|
assert(path.isAbsolute(destPath));
|
||||||
const { packageJson, cliType } = await scanParentDirs(destPath, true);
|
const { packageJson, cliType } = await scanParentDirs(destPath, true);
|
||||||
const hasScript = Boolean(
|
const scriptName = getScriptName(
|
||||||
packageJson &&
|
packageJson,
|
||||||
packageJson.scripts &&
|
typeof scriptNames === 'string' ? [scriptNames] : scriptNames
|
||||||
scriptName &&
|
|
||||||
packageJson.scripts[scriptName]
|
|
||||||
);
|
);
|
||||||
if (!hasScript) return false;
|
if (!scriptName) return false;
|
||||||
|
|
||||||
|
debug('Running user script...');
|
||||||
|
const runScriptTime = Date.now();
|
||||||
|
|
||||||
if (cliType === 'npm') {
|
if (cliType === 'npm') {
|
||||||
const prettyCommand = `npm run ${scriptName}`;
|
const prettyCommand = `npm run ${scriptName}`;
|
||||||
@@ -382,6 +397,7 @@ export async function runPackageJsonScript(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug(`Script complete [${Date.now() - runScriptTime}ms]`);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
execCommand,
|
execCommand,
|
||||||
spawnCommand,
|
spawnCommand,
|
||||||
walkParentDirs,
|
walkParentDirs,
|
||||||
|
getScriptName,
|
||||||
installDependencies,
|
installDependencies,
|
||||||
runPackageJsonScript,
|
runPackageJsonScript,
|
||||||
runNpmInstall,
|
runNpmInstall,
|
||||||
@@ -27,6 +28,7 @@ import {
|
|||||||
getLatestNodeVersion,
|
getLatestNodeVersion,
|
||||||
getDiscontinuedNodeVersions,
|
getDiscontinuedNodeVersions,
|
||||||
} from './fs/node-version';
|
} from './fs/node-version';
|
||||||
|
import { NowBuildError } from './errors';
|
||||||
import streamToBuffer from './fs/stream-to-buffer';
|
import streamToBuffer from './fs/stream-to-buffer';
|
||||||
import shouldServe from './should-serve';
|
import shouldServe from './should-serve';
|
||||||
import debug from './debug';
|
import debug from './debug';
|
||||||
@@ -46,6 +48,7 @@ export {
|
|||||||
rename,
|
rename,
|
||||||
execAsync,
|
execAsync,
|
||||||
spawnAsync,
|
spawnAsync,
|
||||||
|
getScriptName,
|
||||||
installDependencies,
|
installDependencies,
|
||||||
runPackageJsonScript,
|
runPackageJsonScript,
|
||||||
execCommand,
|
execCommand,
|
||||||
@@ -111,9 +114,11 @@ export const getPlatformEnv = (name: string): string | undefined => {
|
|||||||
const n = process.env[nName];
|
const n = process.env[nName];
|
||||||
if (typeof v === 'string') {
|
if (typeof v === 'string') {
|
||||||
if (typeof n === 'string') {
|
if (typeof n === 'string') {
|
||||||
throw new Error(
|
throw new NowBuildError({
|
||||||
`Both "${vName}" and "${nName}" env vars are defined. Please only define the "${vName}" env var`
|
code: 'CONFLICTING_ENV_VAR_NAMES',
|
||||||
);
|
message: `Both "${vName}" and "${nName}" env vars are defined. Please only define the "${vName}" env var.`,
|
||||||
|
link: 'https://vercel.link/combining-old-and-new-config',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -493,7 +493,7 @@ describe('Test `detectBuilders`', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('use a custom runtime', async () => {
|
it('use a custom runtime', async () => {
|
||||||
const functions = { 'api/user.php': { runtime: 'now-php@0.0.8' } };
|
const functions = { 'api/user.php': { runtime: 'vercel-php@0.1.0' } };
|
||||||
const files = ['api/user.php'];
|
const files = ['api/user.php'];
|
||||||
const { builders, errors } = await detectBuilders(files, null, {
|
const { builders, errors } = await detectBuilders(files, null, {
|
||||||
functions,
|
functions,
|
||||||
@@ -501,11 +501,11 @@ describe('Test `detectBuilders`', () => {
|
|||||||
|
|
||||||
expect(errors).toBe(null);
|
expect(errors).toBe(null);
|
||||||
expect(builders!.length).toBe(1);
|
expect(builders!.length).toBe(1);
|
||||||
expect(builders![0].use).toBe('now-php@0.0.8');
|
expect(builders![0].use).toBe('vercel-php@0.1.0');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('use a custom runtime but without a source', async () => {
|
it('use a custom runtime but without a source', async () => {
|
||||||
const functions = { 'api/user.php': { runtime: 'now-php@0.0.8' } };
|
const functions = { 'api/user.php': { runtime: 'vercel-php@0.1.0' } };
|
||||||
const files = ['api/team.js'];
|
const files = ['api/team.js'];
|
||||||
const { errors } = await detectBuilders(files, null, {
|
const { errors } = await detectBuilders(files, null, {
|
||||||
functions,
|
functions,
|
||||||
@@ -775,8 +775,9 @@ describe('Test `detectBuilders`', () => {
|
|||||||
expect(errors).toEqual([
|
expect(errors).toEqual([
|
||||||
{
|
{
|
||||||
code: 'unused_function',
|
code: 'unused_function',
|
||||||
message:
|
message: `The pattern "server/**/*.ts" defined in \`functions\` doesn't match any Serverless Functions inside the \`api\` directory.`,
|
||||||
"The function for server/**/*.ts can't be handled by any builder. Make sure it is inside the api/ directory.",
|
action: 'Learn More',
|
||||||
|
link: 'https://vercel.link/unmatched-function-pattern',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -868,12 +869,14 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
defaultRoutes,
|
defaultRoutes,
|
||||||
redirectRoutes,
|
redirectRoutes,
|
||||||
rewriteRoutes,
|
rewriteRoutes,
|
||||||
|
errorRoutes,
|
||||||
} = await detectBuilders(files, pkg, { featHandleMiss });
|
} = await detectBuilders(files, pkg, { featHandleMiss });
|
||||||
expect(builders![0].use).toBe('@vercel/next');
|
expect(builders![0].use).toBe('@vercel/next');
|
||||||
expect(errors).toBe(null);
|
expect(errors).toBe(null);
|
||||||
expect(defaultRoutes).toStrictEqual([]);
|
expect(defaultRoutes).toStrictEqual([]);
|
||||||
expect(redirectRoutes).toStrictEqual([]);
|
expect(redirectRoutes).toStrictEqual([]);
|
||||||
expect(rewriteRoutes).toStrictEqual([]);
|
expect(rewriteRoutes).toStrictEqual([]);
|
||||||
|
expect(errorRoutes).toStrictEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('package.json + no build + next', async () => {
|
it('package.json + no build + next', async () => {
|
||||||
@@ -888,12 +891,14 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
defaultRoutes,
|
defaultRoutes,
|
||||||
redirectRoutes,
|
redirectRoutes,
|
||||||
rewriteRoutes,
|
rewriteRoutes,
|
||||||
|
errorRoutes,
|
||||||
} = await detectBuilders(files, pkg, { featHandleMiss });
|
} = await detectBuilders(files, pkg, { featHandleMiss });
|
||||||
expect(builders![0].use).toBe('@vercel/next');
|
expect(builders![0].use).toBe('@vercel/next');
|
||||||
expect(errors).toBe(null);
|
expect(errors).toBe(null);
|
||||||
expect(defaultRoutes).toStrictEqual([]);
|
expect(defaultRoutes).toStrictEqual([]);
|
||||||
expect(redirectRoutes).toStrictEqual([]);
|
expect(redirectRoutes).toStrictEqual([]);
|
||||||
expect(rewriteRoutes).toStrictEqual([]);
|
expect(rewriteRoutes).toStrictEqual([]);
|
||||||
|
expect(errorRoutes).toStrictEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('package.json + no build', async () => {
|
it('package.json + no build', async () => {
|
||||||
@@ -914,12 +919,15 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
defaultRoutes,
|
defaultRoutes,
|
||||||
redirectRoutes,
|
redirectRoutes,
|
||||||
rewriteRoutes,
|
rewriteRoutes,
|
||||||
|
errorRoutes,
|
||||||
} = await detectBuilders(files, null, { featHandleMiss });
|
} = await detectBuilders(files, null, { featHandleMiss });
|
||||||
expect(builders).toBe(null);
|
expect(builders).toBe(null);
|
||||||
expect(errors).toBe(null);
|
expect(errors).toBe(null);
|
||||||
expect(defaultRoutes).toStrictEqual([]);
|
expect(defaultRoutes).toStrictEqual([]);
|
||||||
expect(redirectRoutes).toStrictEqual([]);
|
expect(redirectRoutes).toStrictEqual([]);
|
||||||
expect(rewriteRoutes).toStrictEqual([]);
|
expect(rewriteRoutes).toStrictEqual([]);
|
||||||
|
expect(errorRoutes!.length).toBe(1);
|
||||||
|
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('no package.json + public', async () => {
|
it('no package.json + public', async () => {
|
||||||
@@ -930,6 +938,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
defaultRoutes,
|
defaultRoutes,
|
||||||
redirectRoutes,
|
redirectRoutes,
|
||||||
rewriteRoutes,
|
rewriteRoutes,
|
||||||
|
errorRoutes,
|
||||||
} = await detectBuilders(files, null, { featHandleMiss });
|
} = await detectBuilders(files, null, { featHandleMiss });
|
||||||
expect(builders![1].use).toBe('@vercel/static');
|
expect(builders![1].use).toBe('@vercel/static');
|
||||||
expect(errors).toBe(null);
|
expect(errors).toBe(null);
|
||||||
@@ -940,6 +949,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
expect(redirectRoutes).toStrictEqual([]);
|
expect(redirectRoutes).toStrictEqual([]);
|
||||||
expect(rewriteRoutes!.length).toBe(1);
|
expect(rewriteRoutes!.length).toBe(1);
|
||||||
expect((rewriteRoutes![0] as Source).status).toBe(404);
|
expect((rewriteRoutes![0] as Source).status).toBe(404);
|
||||||
|
expect(errorRoutes!.length).toBe(1);
|
||||||
|
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('no package.json + no build + raw static + api', async () => {
|
it('no package.json + no build + raw static + api', async () => {
|
||||||
@@ -950,6 +961,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
defaultRoutes,
|
defaultRoutes,
|
||||||
redirectRoutes,
|
redirectRoutes,
|
||||||
rewriteRoutes,
|
rewriteRoutes,
|
||||||
|
errorRoutes,
|
||||||
} = await detectBuilders(files, null, { featHandleMiss });
|
} = await detectBuilders(files, null, { featHandleMiss });
|
||||||
expect(builders![0].use).toBe('@vercel/node');
|
expect(builders![0].use).toBe('@vercel/node');
|
||||||
expect(builders![0].src).toBe('api/users.js');
|
expect(builders![0].src).toBe('api/users.js');
|
||||||
@@ -964,6 +976,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
expect(redirectRoutes).toStrictEqual([]);
|
expect(redirectRoutes).toStrictEqual([]);
|
||||||
expect(rewriteRoutes!.length).toBe(1);
|
expect(rewriteRoutes!.length).toBe(1);
|
||||||
expect((rewriteRoutes![0] as Source).status).toBe(404);
|
expect((rewriteRoutes![0] as Source).status).toBe(404);
|
||||||
|
expect(errorRoutes!.length).toBe(1);
|
||||||
|
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('package.json + no build + root + api', async () => {
|
it('package.json + no build + root + api', async () => {
|
||||||
@@ -991,6 +1005,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
defaultRoutes,
|
defaultRoutes,
|
||||||
redirectRoutes,
|
redirectRoutes,
|
||||||
rewriteRoutes,
|
rewriteRoutes,
|
||||||
|
errorRoutes,
|
||||||
} = await detectBuilders(files, undefined, { featHandleMiss });
|
} = await detectBuilders(files, undefined, { featHandleMiss });
|
||||||
expect(builders![0].use).toBe('@vercel/node');
|
expect(builders![0].use).toBe('@vercel/node');
|
||||||
expect(builders![0].src).toBe('api/[endpoint]/[id].js');
|
expect(builders![0].src).toBe('api/[endpoint]/[id].js');
|
||||||
@@ -1003,6 +1018,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
expect(rewriteRoutes!.length).toBe(2);
|
expect(rewriteRoutes!.length).toBe(2);
|
||||||
expect((rewriteRoutes![0] as Source).src).toBe('^/api/([^/]+)/([^/]+)$');
|
expect((rewriteRoutes![0] as Source).src).toBe('^/api/([^/]+)/([^/]+)$');
|
||||||
expect((rewriteRoutes![1] as Source).status).toBe(404);
|
expect((rewriteRoutes![1] as Source).status).toBe(404);
|
||||||
|
expect(errorRoutes!.length).toBe(1);
|
||||||
|
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('api + next + public', async () => {
|
it('api + next + public', async () => {
|
||||||
@@ -1017,6 +1034,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
defaultRoutes,
|
defaultRoutes,
|
||||||
redirectRoutes,
|
redirectRoutes,
|
||||||
rewriteRoutes,
|
rewriteRoutes,
|
||||||
|
errorRoutes,
|
||||||
} = await detectBuilders(files, pkg, { featHandleMiss });
|
} = await detectBuilders(files, pkg, { featHandleMiss });
|
||||||
expect(builders![0].use).toBe('@vercel/node');
|
expect(builders![0].use).toBe('@vercel/node');
|
||||||
expect(builders![0].src).toBe('api/endpoint.js');
|
expect(builders![0].src).toBe('api/endpoint.js');
|
||||||
@@ -1030,6 +1048,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
expect(redirectRoutes).toStrictEqual([]);
|
expect(redirectRoutes).toStrictEqual([]);
|
||||||
expect(rewriteRoutes!.length).toBe(1);
|
expect(rewriteRoutes!.length).toBe(1);
|
||||||
expect((rewriteRoutes![0] as Source).status).toBe(404);
|
expect((rewriteRoutes![0] as Source).status).toBe(404);
|
||||||
|
expect(errorRoutes).toStrictEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('api + next + raw static', async () => {
|
it('api + next + raw static', async () => {
|
||||||
@@ -1044,6 +1063,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
defaultRoutes,
|
defaultRoutes,
|
||||||
redirectRoutes,
|
redirectRoutes,
|
||||||
rewriteRoutes,
|
rewriteRoutes,
|
||||||
|
errorRoutes,
|
||||||
} = await detectBuilders(files, pkg, { featHandleMiss });
|
} = await detectBuilders(files, pkg, { featHandleMiss });
|
||||||
expect(builders![0].use).toBe('@vercel/node');
|
expect(builders![0].use).toBe('@vercel/node');
|
||||||
expect(builders![0].src).toBe('api/endpoint.js');
|
expect(builders![0].src).toBe('api/endpoint.js');
|
||||||
@@ -1057,6 +1077,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
expect(redirectRoutes).toStrictEqual([]);
|
expect(redirectRoutes).toStrictEqual([]);
|
||||||
expect(rewriteRoutes!.length).toBe(1);
|
expect(rewriteRoutes!.length).toBe(1);
|
||||||
expect((rewriteRoutes![0] as Source).status).toBe(404);
|
expect((rewriteRoutes![0] as Source).status).toBe(404);
|
||||||
|
expect(errorRoutes).toStrictEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('api + raw static', async () => {
|
it('api + raw static', async () => {
|
||||||
@@ -1067,6 +1088,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
defaultRoutes,
|
defaultRoutes,
|
||||||
redirectRoutes,
|
redirectRoutes,
|
||||||
rewriteRoutes,
|
rewriteRoutes,
|
||||||
|
errorRoutes,
|
||||||
} = await detectBuilders(files, null, { featHandleMiss });
|
} = await detectBuilders(files, null, { featHandleMiss });
|
||||||
expect(builders![0].use).toBe('@vercel/node');
|
expect(builders![0].use).toBe('@vercel/node');
|
||||||
expect(builders![0].src).toBe('api/endpoint.js');
|
expect(builders![0].src).toBe('api/endpoint.js');
|
||||||
@@ -1080,6 +1102,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
expect(redirectRoutes).toStrictEqual([]);
|
expect(redirectRoutes).toStrictEqual([]);
|
||||||
expect(rewriteRoutes!.length).toBe(1);
|
expect(rewriteRoutes!.length).toBe(1);
|
||||||
expect((rewriteRoutes![0] as Source).status).toBe(404);
|
expect((rewriteRoutes![0] as Source).status).toBe(404);
|
||||||
|
expect(errorRoutes!.length).toBe(1);
|
||||||
|
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('api + raw static + package.json no build script', async () => {
|
it('api + raw static + package.json no build script', async () => {
|
||||||
@@ -1094,6 +1118,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
defaultRoutes,
|
defaultRoutes,
|
||||||
redirectRoutes,
|
redirectRoutes,
|
||||||
rewriteRoutes,
|
rewriteRoutes,
|
||||||
|
errorRoutes,
|
||||||
} = await detectBuilders(files, pkg, { featHandleMiss });
|
} = await detectBuilders(files, pkg, { featHandleMiss });
|
||||||
expect(builders![0].use).toBe('@vercel/node');
|
expect(builders![0].use).toBe('@vercel/node');
|
||||||
expect(builders![0].src).toBe('api/version.js');
|
expect(builders![0].src).toBe('api/version.js');
|
||||||
@@ -1107,6 +1132,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
expect(redirectRoutes).toStrictEqual([]);
|
expect(redirectRoutes).toStrictEqual([]);
|
||||||
expect(rewriteRoutes!.length).toBe(1);
|
expect(rewriteRoutes!.length).toBe(1);
|
||||||
expect((rewriteRoutes![0] as Source).status).toBe(404);
|
expect((rewriteRoutes![0] as Source).status).toBe(404);
|
||||||
|
expect(errorRoutes!.length).toBe(1);
|
||||||
|
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('api + public', async () => {
|
it('api + public', async () => {
|
||||||
@@ -1117,7 +1144,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
'README.md',
|
'README.md',
|
||||||
];
|
];
|
||||||
|
|
||||||
const { builders } = await detectBuilders(files, undefined, {
|
const { builders, errorRoutes } = await detectBuilders(files, undefined, {
|
||||||
featHandleMiss,
|
featHandleMiss,
|
||||||
});
|
});
|
||||||
expect(builders![0].use).toBe('@vercel/node');
|
expect(builders![0].use).toBe('@vercel/node');
|
||||||
@@ -1125,6 +1152,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
expect(builders![1].use).toBe('@vercel/static');
|
expect(builders![1].use).toBe('@vercel/static');
|
||||||
expect(builders![1].src).toBe('public/**/*');
|
expect(builders![1].src).toBe('public/**/*');
|
||||||
expect(builders!.length).toBe(2);
|
expect(builders!.length).toBe(2);
|
||||||
|
expect(errorRoutes!.length).toBe(1);
|
||||||
|
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('api go with test files', async () => {
|
it('api go with test files', async () => {
|
||||||
@@ -1143,21 +1172,26 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
'api/src/controllers/user.module_test.go',
|
'api/src/controllers/user.module_test.go',
|
||||||
];
|
];
|
||||||
|
|
||||||
const { builders } = await detectBuilders(files, undefined, {
|
const { builders, errorRoutes } = await detectBuilders(files, undefined, {
|
||||||
featHandleMiss,
|
featHandleMiss,
|
||||||
});
|
});
|
||||||
expect(builders!.length).toBe(7);
|
expect(builders!.length).toBe(7);
|
||||||
expect(builders!.some(b => b.src.endsWith('_test.go'))).toBe(false);
|
expect(builders!.some(b => b.src.endsWith('_test.go'))).toBe(false);
|
||||||
|
expect(errorRoutes!.length).toBe(1);
|
||||||
|
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('just public', async () => {
|
it('just public', async () => {
|
||||||
const files = ['public/index.html', 'public/favicon.ico', 'README.md'];
|
const files = ['public/index.html', 'public/favicon.ico', 'README.md'];
|
||||||
|
|
||||||
const { builders } = await detectBuilders(files, undefined, {
|
const { builders, errorRoutes } = await detectBuilders(files, undefined, {
|
||||||
featHandleMiss,
|
featHandleMiss,
|
||||||
});
|
});
|
||||||
expect(builders![0].src).toBe('public/**/*');
|
expect(builders![0].src).toBe('public/**/*');
|
||||||
|
expect(builders![0].use).toBe('@vercel/static');
|
||||||
expect(builders!.length).toBe(1);
|
expect(builders!.length).toBe(1);
|
||||||
|
expect(errorRoutes!.length).toBe(1);
|
||||||
|
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('next + public', async () => {
|
it('next + public', async () => {
|
||||||
@@ -1167,10 +1201,13 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
};
|
};
|
||||||
const files = ['package.json', 'public/index.html', 'README.md'];
|
const files = ['package.json', 'public/index.html', 'README.md'];
|
||||||
|
|
||||||
const { builders } = await detectBuilders(files, pkg, { featHandleMiss });
|
const { builders, errorRoutes } = await detectBuilders(files, pkg, {
|
||||||
|
featHandleMiss,
|
||||||
|
});
|
||||||
expect(builders![0].use).toBe('@vercel/next');
|
expect(builders![0].use).toBe('@vercel/next');
|
||||||
expect(builders![0].src).toBe('package.json');
|
expect(builders![0].src).toBe('package.json');
|
||||||
expect(builders!.length).toBe(1);
|
expect(builders!.length).toBe(1);
|
||||||
|
expect(errorRoutes!.length).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('nuxt', async () => {
|
it('nuxt', async () => {
|
||||||
@@ -1180,10 +1217,14 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
};
|
};
|
||||||
const files = ['package.json', 'pages/index.js'];
|
const files = ['package.json', 'pages/index.js'];
|
||||||
|
|
||||||
const { builders } = await detectBuilders(files, pkg, { featHandleMiss });
|
const { builders, errorRoutes } = await detectBuilders(files, pkg, {
|
||||||
|
featHandleMiss,
|
||||||
|
});
|
||||||
expect(builders![0].use).toBe('@vercel/static-build');
|
expect(builders![0].use).toBe('@vercel/static-build');
|
||||||
expect(builders![0].src).toBe('package.json');
|
expect(builders![0].src).toBe('package.json');
|
||||||
expect(builders!.length).toBe(1);
|
expect(builders!.length).toBe(1);
|
||||||
|
expect(errorRoutes!.length).toBe(1);
|
||||||
|
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('nuxt + tag canary', async () => {
|
it('nuxt + tag canary', async () => {
|
||||||
@@ -1193,23 +1234,29 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
};
|
};
|
||||||
const files = ['package.json', 'pages/index.js'];
|
const files = ['package.json', 'pages/index.js'];
|
||||||
|
|
||||||
const { builders } = await detectBuilders(files, pkg, {
|
const { builders, errorRoutes } = await detectBuilders(files, pkg, {
|
||||||
tag: 'canary',
|
tag: 'canary',
|
||||||
featHandleMiss,
|
featHandleMiss,
|
||||||
});
|
});
|
||||||
expect(builders![0].use).toBe('@vercel/static-build@canary');
|
expect(builders![0].use).toBe('@vercel/static-build@canary');
|
||||||
expect(builders![0].src).toBe('package.json');
|
expect(builders![0].src).toBe('package.json');
|
||||||
expect(builders!.length).toBe(1);
|
expect(builders!.length).toBe(1);
|
||||||
|
expect(errorRoutes!.length).toBe(1);
|
||||||
|
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('package.json with no build + api', async () => {
|
it('package.json with no build + api', async () => {
|
||||||
const pkg = { dependencies: { next: '9.0.0' } };
|
const pkg = { dependencies: { next: '9.0.0' } };
|
||||||
const files = ['package.json', 'api/[endpoint].js'];
|
const files = ['package.json', 'api/[endpoint].js'];
|
||||||
|
|
||||||
const { builders } = await detectBuilders(files, pkg, { featHandleMiss });
|
const { builders, errorRoutes } = await detectBuilders(files, pkg, {
|
||||||
|
featHandleMiss,
|
||||||
|
});
|
||||||
expect(builders![0].use).toBe('@vercel/node');
|
expect(builders![0].use).toBe('@vercel/node');
|
||||||
expect(builders![0].src).toBe('api/[endpoint].js');
|
expect(builders![0].src).toBe('api/[endpoint].js');
|
||||||
expect(builders!.length).toBe(1);
|
expect(builders!.length).toBe(1);
|
||||||
|
expect(errorRoutes!.length).toBe(1);
|
||||||
|
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('package.json with no build + public directory', async () => {
|
it('package.json with no build + public directory', async () => {
|
||||||
@@ -1328,7 +1375,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
it('many static files + one api file', async () => {
|
it('many static files + one api file', async () => {
|
||||||
const files = Array.from({ length: 5000 }).map((_, i) => `file${i}.html`);
|
const files = Array.from({ length: 5000 }).map((_, i) => `file${i}.html`);
|
||||||
files.push('api/index.ts');
|
files.push('api/index.ts');
|
||||||
const { builders } = await detectBuilders(files, undefined, {
|
const { builders, errorRoutes } = await detectBuilders(files, undefined, {
|
||||||
featHandleMiss,
|
featHandleMiss,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1337,6 +1384,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
expect(builders![0].src).toBe('api/index.ts');
|
expect(builders![0].src).toBe('api/index.ts');
|
||||||
expect(builders![1].use).toBe('@vercel/static');
|
expect(builders![1].use).toBe('@vercel/static');
|
||||||
expect(builders![1].src).toBe('!{api/**,package.json}');
|
expect(builders![1].src).toBe('!{api/**,package.json}');
|
||||||
|
expect(errorRoutes!.length).toBe(1);
|
||||||
|
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('functions with nextjs', async () => {
|
it('functions with nextjs', async () => {
|
||||||
@@ -1490,7 +1539,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('use a custom runtime', async () => {
|
it('use a custom runtime', async () => {
|
||||||
const functions = { 'api/user.php': { runtime: 'now-php@0.0.8' } };
|
const functions = { 'api/user.php': { runtime: 'vercel-php@0.1.0' } };
|
||||||
const files = ['api/user.php'];
|
const files = ['api/user.php'];
|
||||||
const { builders, errors } = await detectBuilders(files, null, {
|
const { builders, errors } = await detectBuilders(files, null, {
|
||||||
functions,
|
functions,
|
||||||
@@ -1499,11 +1548,11 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
|
|
||||||
expect(errors).toBe(null);
|
expect(errors).toBe(null);
|
||||||
expect(builders!.length).toBe(1);
|
expect(builders!.length).toBe(1);
|
||||||
expect(builders![0].use).toBe('now-php@0.0.8');
|
expect(builders![0].use).toBe('vercel-php@0.1.0');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('use a custom runtime but without a source', async () => {
|
it('use a custom runtime but without a source', async () => {
|
||||||
const functions = { 'api/user.php': { runtime: 'now-php@0.0.8' } };
|
const functions = { 'api/user.php': { runtime: 'vercel-php@0.1.0' } };
|
||||||
const files = ['api/team.js'];
|
const files = ['api/team.js'];
|
||||||
const { errors } = await detectBuilders(files, null, {
|
const { errors } = await detectBuilders(files, null, {
|
||||||
functions,
|
functions,
|
||||||
@@ -1685,6 +1734,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
defaultRoutes,
|
defaultRoutes,
|
||||||
redirectRoutes,
|
redirectRoutes,
|
||||||
rewriteRoutes,
|
rewriteRoutes,
|
||||||
|
errorRoutes,
|
||||||
} = await detectBuilders(files, null, {
|
} = await detectBuilders(files, null, {
|
||||||
projectSettings,
|
projectSettings,
|
||||||
featHandleMiss,
|
featHandleMiss,
|
||||||
@@ -1697,6 +1747,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
expect(defaultRoutes).toStrictEqual([]);
|
expect(defaultRoutes).toStrictEqual([]);
|
||||||
expect(redirectRoutes).toStrictEqual([]);
|
expect(redirectRoutes).toStrictEqual([]);
|
||||||
expect(rewriteRoutes).toStrictEqual([]);
|
expect(rewriteRoutes).toStrictEqual([]);
|
||||||
|
expect(errorRoutes!.length).toBe(1);
|
||||||
|
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Custom static output directory with api', async () => {
|
it('Custom static output directory with api', async () => {
|
||||||
@@ -1711,6 +1763,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
defaultRoutes,
|
defaultRoutes,
|
||||||
redirectRoutes,
|
redirectRoutes,
|
||||||
rewriteRoutes,
|
rewriteRoutes,
|
||||||
|
errorRoutes,
|
||||||
} = await detectBuilders(files, null, {
|
} = await detectBuilders(files, null, {
|
||||||
projectSettings,
|
projectSettings,
|
||||||
featHandleMiss,
|
featHandleMiss,
|
||||||
@@ -1726,6 +1779,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
expect(redirectRoutes).toStrictEqual([]);
|
expect(redirectRoutes).toStrictEqual([]);
|
||||||
expect(rewriteRoutes!.length).toBe(1);
|
expect(rewriteRoutes!.length).toBe(1);
|
||||||
expect((rewriteRoutes![0] as Source).status).toBe(404);
|
expect((rewriteRoutes![0] as Source).status).toBe(404);
|
||||||
|
expect(errorRoutes!.length).toBe(1);
|
||||||
|
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Framework with non-package.json entrypoint', async () => {
|
it('Framework with non-package.json entrypoint', async () => {
|
||||||
@@ -1734,7 +1789,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
framework: 'hugo',
|
framework: 'hugo',
|
||||||
};
|
};
|
||||||
|
|
||||||
const { builders } = await detectBuilders(files, null, {
|
const { builders, errorRoutes } = await detectBuilders(files, null, {
|
||||||
projectSettings,
|
projectSettings,
|
||||||
featHandleMiss,
|
featHandleMiss,
|
||||||
});
|
});
|
||||||
@@ -1749,6 +1804,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
expect(errorRoutes!.length).toBe(1);
|
||||||
|
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('No framework, only package.json', async () => {
|
it('No framework, only package.json', async () => {
|
||||||
@@ -1759,7 +1816,9 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const { builders } = await detectBuilders(files, pkg, { featHandleMiss });
|
const { builders, errorRoutes } = await detectBuilders(files, pkg, {
|
||||||
|
featHandleMiss,
|
||||||
|
});
|
||||||
|
|
||||||
expect(builders).toEqual([
|
expect(builders).toEqual([
|
||||||
{
|
{
|
||||||
@@ -1770,13 +1829,15 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
expect(errorRoutes!.length).toBe(1);
|
||||||
|
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Framework with an API', async () => {
|
it('Framework with an API', async () => {
|
||||||
const files = ['config.rb', 'api/date.rb'];
|
const files = ['config.rb', 'api/date.rb'];
|
||||||
const projectSettings = { framework: 'middleman' };
|
const projectSettings = { framework: 'middleman' };
|
||||||
|
|
||||||
const { builders } = await detectBuilders(files, null, {
|
const { builders, errorRoutes } = await detectBuilders(files, null, {
|
||||||
projectSettings,
|
projectSettings,
|
||||||
featHandleMiss,
|
featHandleMiss,
|
||||||
});
|
});
|
||||||
@@ -1798,6 +1859,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
expect(errorRoutes!.length).toBe(1);
|
||||||
|
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Error for non-api functions', async () => {
|
it('Error for non-api functions', async () => {
|
||||||
@@ -1816,8 +1879,9 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
expect(errors).toEqual([
|
expect(errors).toEqual([
|
||||||
{
|
{
|
||||||
code: 'unused_function',
|
code: 'unused_function',
|
||||||
message:
|
message: `The pattern "server/**/*.ts" defined in \`functions\` doesn't match any Serverless Functions inside the \`api\` directory.`,
|
||||||
"The function for server/**/*.ts can't be handled by any builder. Make sure it is inside the api/ directory.",
|
action: 'Learn More',
|
||||||
|
link: 'https://vercel.link/unmatched-function-pattern',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -1847,13 +1911,19 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
it('All static if `buildCommand` is an empty string with an `outputDirectory`', async () => {
|
it('All static if `buildCommand` is an empty string with an `outputDirectory`', async () => {
|
||||||
const files = ['out/index.html'];
|
const files = ['out/index.html'];
|
||||||
const projectSettings = { buildCommand: '', outputDirectory: 'out' };
|
const projectSettings = { buildCommand: '', outputDirectory: 'out' };
|
||||||
const { builders, errors } = await detectBuilders(files, null, {
|
const { builders, errors, errorRoutes } = await detectBuilders(
|
||||||
projectSettings,
|
files,
|
||||||
featHandleMiss,
|
null,
|
||||||
});
|
{
|
||||||
|
projectSettings,
|
||||||
|
featHandleMiss,
|
||||||
|
}
|
||||||
|
);
|
||||||
expect(errors).toBe(null);
|
expect(errors).toBe(null);
|
||||||
expect(builders![0]!.use).toBe('@vercel/static');
|
expect(builders![0]!.use).toBe('@vercel/static');
|
||||||
expect(builders![0]!.src).toBe('out/**/*');
|
expect(builders![0]!.src).toBe('out/**/*');
|
||||||
|
expect(errorRoutes!.length).toBe(1);
|
||||||
|
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('do not require build script when `buildCommand` is an empty string', async () => {
|
it('do not require build script when `buildCommand` is an empty string', async () => {
|
||||||
@@ -2013,7 +2083,7 @@ it('Test `detectRoutes`', async () => {
|
|||||||
|
|
||||||
{
|
{
|
||||||
// use a custom runtime
|
// use a custom runtime
|
||||||
const functions = { 'api/user.php': { runtime: 'now-php@0.0.8' } };
|
const functions = { 'api/user.php': { runtime: 'vercel-php@0.1.0' } };
|
||||||
const files = ['api/user.php'];
|
const files = ['api/user.php'];
|
||||||
|
|
||||||
const { defaultRoutes } = await detectBuilders(files, null, { functions });
|
const { defaultRoutes } = await detectBuilders(files, null, { functions });
|
||||||
@@ -2029,9 +2099,13 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
|||||||
{
|
{
|
||||||
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
|
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
|
||||||
|
|
||||||
const { defaultRoutes, rewriteRoutes } = await detectBuilders(files, null, {
|
const { defaultRoutes, rewriteRoutes, errorRoutes } = await detectBuilders(
|
||||||
featHandleMiss,
|
files,
|
||||||
});
|
null,
|
||||||
|
{
|
||||||
|
featHandleMiss,
|
||||||
|
}
|
||||||
|
);
|
||||||
expect(defaultRoutes).toStrictEqual([
|
expect(defaultRoutes).toStrictEqual([
|
||||||
{ handle: 'miss' },
|
{ handle: 'miss' },
|
||||||
{
|
{
|
||||||
@@ -2047,6 +2121,44 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
|||||||
continue: true,
|
continue: true,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
expect(errorRoutes).toStrictEqual([
|
||||||
|
{
|
||||||
|
status: 404,
|
||||||
|
src: '^/(?!.*api).*$',
|
||||||
|
dest: '/404.html',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const pattern = new RegExp(errorRoutes![0].src!);
|
||||||
|
|
||||||
|
[
|
||||||
|
'/',
|
||||||
|
'/index.html',
|
||||||
|
'/page.html',
|
||||||
|
'/page',
|
||||||
|
'/another/index.html',
|
||||||
|
'/another/page.html',
|
||||||
|
'/another/page',
|
||||||
|
'/another/sub/index.html',
|
||||||
|
'/another/sub/page.html',
|
||||||
|
'/another/sub/page',
|
||||||
|
].forEach(file => {
|
||||||
|
expect(file).toMatch(pattern);
|
||||||
|
});
|
||||||
|
|
||||||
|
[
|
||||||
|
'/api',
|
||||||
|
'/api/',
|
||||||
|
'/api/index.html',
|
||||||
|
'/api/page.html',
|
||||||
|
'/api/page',
|
||||||
|
'/api/sub',
|
||||||
|
'/api/sub/index.html',
|
||||||
|
'/api/sub/page.html',
|
||||||
|
'/api/sub/page',
|
||||||
|
].forEach(file => {
|
||||||
|
expect(file).not.toMatch(pattern);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -2283,7 +2395,7 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
|||||||
|
|
||||||
{
|
{
|
||||||
// use a custom runtime
|
// use a custom runtime
|
||||||
const functions = { 'api/user.php': { runtime: 'now-php@0.0.8' } };
|
const functions = { 'api/user.php': { runtime: 'vercel-php@0.1.0' } };
|
||||||
const files = ['api/user.php'];
|
const files = ['api/user.php'];
|
||||||
|
|
||||||
const { defaultRoutes, rewriteRoutes } = await detectBuilders(files, null, {
|
const { defaultRoutes, rewriteRoutes } = await detectBuilders(files, null, {
|
||||||
@@ -2330,6 +2442,7 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
|||||||
defaultRoutes,
|
defaultRoutes,
|
||||||
redirectRoutes,
|
redirectRoutes,
|
||||||
rewriteRoutes,
|
rewriteRoutes,
|
||||||
|
errorRoutes,
|
||||||
} = await detectBuilders(files, null, options);
|
} = await detectBuilders(files, null, options);
|
||||||
testHeaders(redirectRoutes);
|
testHeaders(redirectRoutes);
|
||||||
expect(defaultRoutes).toStrictEqual([]);
|
expect(defaultRoutes).toStrictEqual([]);
|
||||||
@@ -2340,6 +2453,13 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
|||||||
continue: true,
|
continue: true,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
expect(errorRoutes).toStrictEqual([
|
||||||
|
{
|
||||||
|
status: 404,
|
||||||
|
src: '^/(?!.*api).*$',
|
||||||
|
dest: '/404',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
// expected redirect should match inputs
|
// expected redirect should match inputs
|
||||||
const getLocation = createReplaceLocation(redirectRoutes);
|
const getLocation = createReplaceLocation(redirectRoutes);
|
||||||
@@ -2574,7 +2694,7 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
|||||||
|
|
||||||
{
|
{
|
||||||
// use a custom runtime
|
// use a custom runtime
|
||||||
const functions = { 'api/user.php': { runtime: 'now-php@0.0.8' } };
|
const functions = { 'api/user.php': { runtime: 'vercel-php@0.1.0' } };
|
||||||
const files = ['api/user.php'];
|
const files = ['api/user.php'];
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -2823,7 +2943,7 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
|||||||
|
|
||||||
{
|
{
|
||||||
// use a custom runtime
|
// use a custom runtime
|
||||||
const functions = { 'api/user.php': { runtime: 'now-php@0.0.8' } };
|
const functions = { 'api/user.php': { runtime: 'vercel-php@0.1.0' } };
|
||||||
const files = ['api/user.php'];
|
const files = ['api/user.php'];
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ describe('Test `getPlatformEnv()`', () => {
|
|||||||
assert(err);
|
assert(err);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
err!.message,
|
err!.message,
|
||||||
'Both "VERCEL_FOO" and "NOW_FOO" env vars are defined. Please only define the "VERCEL_FOO" env var'
|
'Both "VERCEL_FOO" and "NOW_FOO" env vars are defined. Please only define the "VERCEL_FOO" env var.'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
72
packages/now-build-utils/test/unit.get-script-name.test.ts
vendored
Normal file
72
packages/now-build-utils/test/unit.get-script-name.test.ts
vendored
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import assert from 'assert';
|
||||||
|
import { getScriptName } from '../src';
|
||||||
|
|
||||||
|
describe('Test `getScriptName()`', () => {
|
||||||
|
it('should return "vercel-*"', () => {
|
||||||
|
const pkg = {
|
||||||
|
scripts: {
|
||||||
|
'vercel-dev': '',
|
||||||
|
'vercel-build': '',
|
||||||
|
dev: '',
|
||||||
|
build: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
assert.equal(
|
||||||
|
getScriptName(pkg, ['vercel-dev', 'now-dev', 'dev']),
|
||||||
|
'vercel-dev'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
getScriptName(pkg, ['vercel-build', 'now-build', 'build']),
|
||||||
|
'vercel-build'
|
||||||
|
);
|
||||||
|
assert.equal(getScriptName(pkg, ['dev']), 'dev');
|
||||||
|
assert.equal(getScriptName(pkg, ['build']), 'build');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return "now-*"', () => {
|
||||||
|
const pkg = {
|
||||||
|
scripts: {
|
||||||
|
'now-dev': '',
|
||||||
|
'now-build': '',
|
||||||
|
dev: '',
|
||||||
|
build: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
assert.equal(
|
||||||
|
getScriptName(pkg, ['vercel-dev', 'now-dev', 'dev']),
|
||||||
|
'now-dev'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
getScriptName(pkg, ['vercel-build', 'now-build', 'build']),
|
||||||
|
'now-build'
|
||||||
|
);
|
||||||
|
assert.equal(getScriptName(pkg, ['dev']), 'dev');
|
||||||
|
assert.equal(getScriptName(pkg, ['build']), 'build');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return base script name', () => {
|
||||||
|
const pkg = {
|
||||||
|
scripts: {
|
||||||
|
dev: '',
|
||||||
|
build: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
assert.equal(getScriptName(pkg, ['dev']), 'dev');
|
||||||
|
assert.equal(getScriptName(pkg, ['build']), 'build');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return `null`', () => {
|
||||||
|
assert.equal(getScriptName(undefined, ['build']), null);
|
||||||
|
assert.equal(getScriptName({}, ['build']), null);
|
||||||
|
assert.equal(getScriptName({ scripts: {} }, ['build']), null);
|
||||||
|
|
||||||
|
const pkg = {
|
||||||
|
scripts: {
|
||||||
|
dev: '',
|
||||||
|
build: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
assert.equal(getScriptName(pkg, ['vercel-dev', 'now-dev']), null);
|
||||||
|
assert.equal(getScriptName(pkg, ['vercel-build', 'now-build']), null);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vercel/cgi",
|
"name": "@vercel/cgi",
|
||||||
"version": "1.0.6-canary.0",
|
"version": "1.0.6",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "vercel",
|
"name": "vercel",
|
||||||
"version": "19.0.2-canary.11",
|
"version": "20.0.0-canary.2",
|
||||||
"preferGlobal": true,
|
"preferGlobal": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "The command-line interface for Now",
|
"description": "The command-line interface for Vercel",
|
||||||
"homepage": "https://vercel.com",
|
"homepage": "https://vercel.com",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "node ./scripts/preinstall.js",
|
"preinstall": "node ./scripts/preinstall.js",
|
||||||
"test-unit": "nyc ava test/unit.js test/dev-builder.unit.js test/dev-router.unit.js test/dev-server.unit.js --serial --fail-fast --verbose",
|
"test-unit": "nyc ava test/unit.js test/dev-builder.unit.js test/dev-router.unit.js test/dev-server.unit.js test/dev-validate.unit.js --serial --fail-fast --verbose",
|
||||||
"test-integration-cli": "ava test/integration.js --serial --fail-fast --verbose",
|
"test-integration-cli": "ava test/integration.js --serial --fail-fast --verbose",
|
||||||
"test-integration-v1": "ava test/integration-v1.js --serial --fail-fast",
|
"test-integration-v1": "ava test/integration-v1.js --serial --fail-fast",
|
||||||
"test-integration-dev": "ava test/dev/integration.js --serial --fail-fast --verbose",
|
"test-integration-dev": "ava test/dev/integration.js --serial --fail-fast --verbose",
|
||||||
@@ -62,13 +62,15 @@
|
|||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vercel/build-utils": "2.3.2-canary.3",
|
"@vercel/build-utils": "2.4.3-canary.0",
|
||||||
"@vercel/go": "1.1.2-canary.1",
|
"@vercel/go": "1.1.5-canary.0",
|
||||||
"@vercel/next": "2.6.3-canary.5",
|
"@vercel/next": "2.6.14-canary.1",
|
||||||
"@vercel/node": "1.6.2-canary.5",
|
"@vercel/node": "1.7.4-canary.0",
|
||||||
"@vercel/python": "1.2.2-canary.2",
|
"@vercel/python": "1.2.2",
|
||||||
"@vercel/ruby": "1.2.2-canary.1",
|
"@vercel/redwood": "0.0.2-canary.0",
|
||||||
"@vercel/static-build": "0.17.2-canary.1"
|
"@vercel/ruby": "1.2.3",
|
||||||
|
"@vercel/static-build": "0.17.7-canary.1",
|
||||||
|
"update-notifier": "4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sentry/node": "5.5.0",
|
"@sentry/node": "5.5.0",
|
||||||
@@ -106,7 +108,7 @@
|
|||||||
"@zeit/fun": "0.11.2",
|
"@zeit/fun": "0.11.2",
|
||||||
"@zeit/ncc": "0.18.5",
|
"@zeit/ncc": "0.18.5",
|
||||||
"@zeit/source-map-support": "0.6.2",
|
"@zeit/source-map-support": "0.6.2",
|
||||||
"ajv": "6.10.2",
|
"ajv": "6.12.2",
|
||||||
"alpha-sort": "2.0.1",
|
"alpha-sort": "2.0.1",
|
||||||
"ansi-escapes": "3.0.0",
|
"ansi-escapes": "3.0.0",
|
||||||
"ansi-regex": "3.0.0",
|
"ansi-regex": "3.0.0",
|
||||||
@@ -119,7 +121,7 @@
|
|||||||
"chalk": "2.4.2",
|
"chalk": "2.4.2",
|
||||||
"chokidar": "3.3.1",
|
"chokidar": "3.3.1",
|
||||||
"clipboardy": "2.1.0",
|
"clipboardy": "2.1.0",
|
||||||
"codecov": "3.6.5",
|
"codecov": "3.7.1",
|
||||||
"cpy": "7.2.0",
|
"cpy": "7.2.0",
|
||||||
"credit-card": "3.0.1",
|
"credit-card": "3.0.1",
|
||||||
"date-fns": "1.29.0",
|
"date-fns": "1.29.0",
|
||||||
@@ -136,6 +138,7 @@
|
|||||||
"escape-html": "1.0.3",
|
"escape-html": "1.0.3",
|
||||||
"esm": "3.1.4",
|
"esm": "3.1.4",
|
||||||
"execa": "3.2.0",
|
"execa": "3.2.0",
|
||||||
|
"fast-deep-equal": "3.1.3",
|
||||||
"fs-extra": "7.0.1",
|
"fs-extra": "7.0.1",
|
||||||
"get-port": "5.1.1",
|
"get-port": "5.1.1",
|
||||||
"glob": "7.1.2",
|
"glob": "7.1.2",
|
||||||
@@ -184,7 +187,6 @@
|
|||||||
"ts-node": "8.3.0",
|
"ts-node": "8.3.0",
|
||||||
"typescript": "3.9.3",
|
"typescript": "3.9.3",
|
||||||
"universal-analytics": "0.4.20",
|
"universal-analytics": "0.4.20",
|
||||||
"update-check": "1.5.3",
|
|
||||||
"utility-types": "2.1.0",
|
"utility-types": "2.1.0",
|
||||||
"which": "2.0.2",
|
"which": "2.0.2",
|
||||||
"which-promise": "1.0.0",
|
"which-promise": "1.0.0",
|
||||||
|
|||||||
@@ -49,7 +49,13 @@ async function main() {
|
|||||||
// Do the initial `ncc` build
|
// Do the initial `ncc` build
|
||||||
console.log();
|
console.log();
|
||||||
const src = join(dirRoot, 'src');
|
const src = join(dirRoot, 'src');
|
||||||
const args = ['@zeit/ncc', 'build', '--source-map'];
|
const args = [
|
||||||
|
'@zeit/ncc',
|
||||||
|
'build',
|
||||||
|
'--source-map',
|
||||||
|
'--external',
|
||||||
|
'update-notifier',
|
||||||
|
];
|
||||||
if (!isDev) {
|
if (!isDev) {
|
||||||
args.push('--minify');
|
args.push('--minify');
|
||||||
}
|
}
|
||||||
@@ -86,7 +92,7 @@ async function main() {
|
|||||||
// A bunch of source `.ts` files from CLI's `util` directory
|
// A bunch of source `.ts` files from CLI's `util` directory
|
||||||
await remove(join(dirRoot, 'dist', 'util'));
|
await remove(join(dirRoot, 'dist', 'util'));
|
||||||
|
|
||||||
console.log('Finished building `now-cli`');
|
console.log('Finished building Vercel CLI');
|
||||||
}
|
}
|
||||||
|
|
||||||
process.on('unhandledRejection', (reason: any, promise: Promise<any>) => {
|
process.on('unhandledRejection', (reason: any, promise: Promise<any>) => {
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import bytes from 'bytes';
|
|||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { write as copy } from 'clipboardy';
|
import { write as copy } from 'clipboardy';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import title from 'title';
|
|
||||||
import { fileNameSymbol } from '@vercel/client';
|
import { fileNameSymbol } from '@vercel/client';
|
||||||
|
import { getPrettyError } from '@vercel/build-utils';
|
||||||
import Client from '../../util/client';
|
import Client from '../../util/client';
|
||||||
import { handleError } from '../../util/error';
|
import { handleError } from '../../util/error';
|
||||||
import getArgs from '../../util/get-args';
|
import getArgs from '../../util/get-args';
|
||||||
@@ -39,7 +39,6 @@ import {
|
|||||||
} from '../../util/errors-ts';
|
} from '../../util/errors-ts';
|
||||||
import { SchemaValidationFailed } from '../../util/errors';
|
import { SchemaValidationFailed } from '../../util/errors';
|
||||||
import purchaseDomainIfAvailable from '../../util/domains/purchase-domain-if-available';
|
import purchaseDomainIfAvailable from '../../util/domains/purchase-domain-if-available';
|
||||||
import isWildcardAlias from '../../util/alias/is-wildcard-alias';
|
|
||||||
import confirm from '../../util/input/confirm';
|
import confirm from '../../util/input/confirm';
|
||||||
import editProjectSettings from '../../util/input/edit-project-settings';
|
import editProjectSettings from '../../util/input/edit-project-settings';
|
||||||
import {
|
import {
|
||||||
@@ -56,6 +55,7 @@ import validatePaths, {
|
|||||||
} from '../../util/validate-paths';
|
} from '../../util/validate-paths';
|
||||||
import { readLocalConfig } from '../../util/config/files';
|
import { readLocalConfig } from '../../util/config/files';
|
||||||
import { getCommandName } from '../../util/pkg-name.ts';
|
import { getCommandName } from '../../util/pkg-name.ts';
|
||||||
|
import { getPreferredPreviewURL } from '../../util/deploy/get-preferred-preview-url.ts';
|
||||||
|
|
||||||
const addProcessEnv = async (log, env) => {
|
const addProcessEnv = async (log, env) => {
|
||||||
let val;
|
let val;
|
||||||
@@ -87,6 +87,7 @@ const addProcessEnv = async (log, env) => {
|
|||||||
|
|
||||||
const printDeploymentStatus = async (
|
const printDeploymentStatus = async (
|
||||||
output,
|
output,
|
||||||
|
client,
|
||||||
{
|
{
|
||||||
readyState,
|
readyState,
|
||||||
alias: aliasList,
|
alias: aliasList,
|
||||||
@@ -119,18 +120,14 @@ const printDeploymentStatus = async (
|
|||||||
let previewUrl;
|
let previewUrl;
|
||||||
let isWildcard;
|
let isWildcard;
|
||||||
if (!isFile && Array.isArray(aliasList) && aliasList.length > 0) {
|
if (!isFile && Array.isArray(aliasList) && aliasList.length > 0) {
|
||||||
// search for a non now.sh/non wildcard domain
|
const previewUrlInfo = await getPreferredPreviewURL(client, aliasList);
|
||||||
// but fallback to the first alias in the list
|
if (previewUrlInfo) {
|
||||||
const mainAlias =
|
isWildcard = previewUrlInfo.isWildcard;
|
||||||
aliasList.find(
|
previewUrl = previewUrlInfo.previewUrl;
|
||||||
alias =>
|
} else {
|
||||||
!alias.endsWith('.now.sh') &&
|
isWildcard = false;
|
||||||
!alias.endsWith('.vercel.app') &&
|
previewUrl = `https://${deploymentUrl}`;
|
||||||
!isWildcardAlias(alias)
|
}
|
||||||
) || aliasList[0];
|
|
||||||
|
|
||||||
isWildcard = isWildcardAlias(mainAlias);
|
|
||||||
previewUrl = isWildcard ? mainAlias : `https://${mainAlias}`;
|
|
||||||
} else {
|
} else {
|
||||||
// fallback to deployment url
|
// fallback to deployment url
|
||||||
isWildcard = false;
|
isWildcard = false;
|
||||||
@@ -357,7 +354,7 @@ export default async function main(
|
|||||||
path,
|
path,
|
||||||
sourcePath,
|
sourcePath,
|
||||||
project
|
project
|
||||||
? `To change your project settings, go to https://vercel.com/${org.slug}/${project.name}/settings`
|
? `To change your Project Settings, go to https://vercel.com/${org.slug}/${project.name}/settings`
|
||||||
: ''
|
: ''
|
||||||
)) === false
|
)) === false
|
||||||
) {
|
) {
|
||||||
@@ -706,6 +703,12 @@ export default async function main(
|
|||||||
|
|
||||||
return printDeploymentStatus(
|
return printDeploymentStatus(
|
||||||
output,
|
output,
|
||||||
|
new Client({
|
||||||
|
apiUrl: ctx.apiUrl,
|
||||||
|
token: ctx.authConfig.token,
|
||||||
|
currentTeam: org.type === 'team' ? org.id : null,
|
||||||
|
debug: debugEnabled,
|
||||||
|
}),
|
||||||
deployment,
|
deployment,
|
||||||
deployStamp,
|
deployStamp,
|
||||||
!argv['--no-clipboard'],
|
!argv['--no-clipboard'],
|
||||||
@@ -735,53 +738,10 @@ function handleCreateDeployError(output, error, localConfig) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (error instanceof SchemaValidationFailed) {
|
if (error instanceof SchemaValidationFailed) {
|
||||||
const { message, params, keyword, dataPath } = error.meta;
|
const niceError = getPrettyError(error.meta);
|
||||||
|
const fileName = localConfig[fileNameSymbol] || 'vercel.json';
|
||||||
if (params && params.additionalProperty) {
|
niceError.message = `Invalid ${fileName} - ${niceError.message}`;
|
||||||
const prop = params.additionalProperty;
|
output.prettyError(niceError);
|
||||||
|
|
||||||
output.error(
|
|
||||||
`The property ${code(prop)} is not allowed in ${highlight(
|
|
||||||
localConfig[fileNameSymbol]
|
|
||||||
)} – please remove it.`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (prop === 'build.env' || prop === 'builds.env') {
|
|
||||||
output.note(
|
|
||||||
`Do you mean ${code('build')} (object) with a property ${code(
|
|
||||||
'env'
|
|
||||||
)} (object) instead of ${code(prop)}?`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dataPath === '.name') {
|
|
||||||
output.error(message);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keyword === 'type') {
|
|
||||||
const prop = dataPath.substr(1, dataPath.length);
|
|
||||||
|
|
||||||
output.error(
|
|
||||||
`The property ${code(prop)} in ${highlight(
|
|
||||||
localConfig[fileNameSymbol]
|
|
||||||
)} can only be of type ${code(title(params.type))}.`
|
|
||||||
);
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const link = 'https://vercel.com/docs/configuration';
|
|
||||||
|
|
||||||
output.error(
|
|
||||||
`Failed to validate ${highlight(
|
|
||||||
localConfig[fileNameSymbol]
|
|
||||||
)}: ${message}\nDocumentation: ${link}`
|
|
||||||
);
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (error instanceof TooManyRequests) {
|
if (error instanceof TooManyRequests) {
|
||||||
|
|||||||
@@ -8,11 +8,15 @@ import Client from '../../util/client';
|
|||||||
import { getLinkedProject } from '../../util/projects/link';
|
import { getLinkedProject } from '../../util/projects/link';
|
||||||
import { getFrameworks } from '../../util/get-frameworks';
|
import { getFrameworks } from '../../util/get-frameworks';
|
||||||
import { isSettingValue } from '../../util/is-setting-value';
|
import { isSettingValue } from '../../util/is-setting-value';
|
||||||
import { getCommandName } from '../../util/pkg-name';
|
import { ProjectSettings, ProjectEnvTarget } from '../../types';
|
||||||
|
import getDecryptedEnvRecords from '../../util/get-decrypted-env-records';
|
||||||
|
import { Env } from '@vercel/build-utils';
|
||||||
|
import setupAndLink from '../../util/link/setup-and-link';
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
'--debug'?: boolean;
|
'--debug'?: boolean;
|
||||||
'--listen'?: string;
|
'--listen'?: string;
|
||||||
|
'--confirm': boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function dev(
|
export default async function dev(
|
||||||
@@ -34,37 +38,56 @@ export default async function dev(
|
|||||||
});
|
});
|
||||||
|
|
||||||
// retrieve dev command
|
// retrieve dev command
|
||||||
const [link, frameworks] = await Promise.all([
|
let [link, frameworks] = await Promise.all([
|
||||||
getLinkedProject(output, client, cwd),
|
getLinkedProject(output, client, cwd),
|
||||||
getFrameworks(client),
|
getFrameworks(client),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (link.status === 'not_linked' && !process.env.__VERCEL_SKIP_DEV_CMD) {
|
||||||
|
const autoConfirm = opts['--confirm'];
|
||||||
|
const forceDelete = false;
|
||||||
|
|
||||||
|
link = await setupAndLink(
|
||||||
|
ctx,
|
||||||
|
output,
|
||||||
|
cwd,
|
||||||
|
forceDelete,
|
||||||
|
autoConfirm,
|
||||||
|
'link',
|
||||||
|
'Set up and develop'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (link.status === 'not_linked') {
|
||||||
|
// User aborted project linking questions
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (link.status === 'error') {
|
if (link.status === 'error') {
|
||||||
return link.exitCode;
|
return link.exitCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (link.status === 'not_linked' && !process.env.__VERCEL_SKIP_DEV_CMD) {
|
let devCommand: string | undefined;
|
||||||
output.error(
|
let frameworkSlug: string | undefined;
|
||||||
`Your codebase isn’t linked to a project on Vercel. Run ${getCommandName()} to link it.`
|
let projectSettings: ProjectSettings | undefined;
|
||||||
);
|
let environmentVars: Env | undefined;
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let devCommand: undefined | string;
|
|
||||||
let frameworkSlug: null | string = null;
|
|
||||||
if (link.status === 'linked') {
|
if (link.status === 'linked') {
|
||||||
const { project, org } = link;
|
const { project, org } = link;
|
||||||
client.currentTeam = org.type === 'team' ? org.id : undefined;
|
client.currentTeam = org.type === 'team' ? org.id : undefined;
|
||||||
|
|
||||||
|
projectSettings = project;
|
||||||
|
|
||||||
if (project.devCommand) {
|
if (project.devCommand) {
|
||||||
devCommand = project.devCommand;
|
devCommand = project.devCommand;
|
||||||
} else if (project.framework) {
|
} else if (project.framework) {
|
||||||
const framework = frameworks.find(f => f.slug === project.framework);
|
const framework = frameworks.find(f => f.slug === project.framework);
|
||||||
|
|
||||||
if (framework) {
|
if (framework) {
|
||||||
frameworkSlug = framework.slug;
|
if (framework.slug) {
|
||||||
const defaults = framework.settings.devCommand;
|
frameworkSlug = framework.slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaults = framework.settings.devCommand;
|
||||||
if (isSettingValue(defaults)) {
|
if (isSettingValue(defaults)) {
|
||||||
devCommand = defaults.value;
|
devCommand = defaults.value;
|
||||||
}
|
}
|
||||||
@@ -74,6 +97,13 @@ export default async function dev(
|
|||||||
if (project.rootDirectory) {
|
if (project.rootDirectory) {
|
||||||
cwd = join(cwd, project.rootDirectory);
|
cwd = join(cwd, project.rootDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
environmentVars = await getDecryptedEnvRecords(
|
||||||
|
output,
|
||||||
|
client,
|
||||||
|
project,
|
||||||
|
ProjectEnvTarget.Development
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const devServer = new DevServer(cwd, {
|
const devServer = new DevServer(cwd, {
|
||||||
@@ -81,6 +111,8 @@ export default async function dev(
|
|||||||
debug,
|
debug,
|
||||||
devCommand,
|
devCommand,
|
||||||
frameworkSlug,
|
frameworkSlug,
|
||||||
|
projectSettings,
|
||||||
|
environmentVars,
|
||||||
});
|
});
|
||||||
|
|
||||||
process.once('SIGINT', () => devServer.stop());
|
process.once('SIGINT', () => devServer.stop());
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import handleError from '../../util/handle-error';
|
|||||||
import createOutput from '../../util/output/create-output';
|
import createOutput from '../../util/output/create-output';
|
||||||
import logo from '../../util/output/logo';
|
import logo from '../../util/output/logo';
|
||||||
import cmd from '../../util/output/cmd';
|
import cmd from '../../util/output/cmd';
|
||||||
|
import highlight from '../../util/output/highlight';
|
||||||
import dev from './dev';
|
import dev from './dev';
|
||||||
import readPackage from '../../util/read-package';
|
import readPackage from '../../util/read-package';
|
||||||
import readConfig from '../../util/config/read-config';
|
import readConfig from '../../util/config/read-config';
|
||||||
@@ -31,6 +32,7 @@ const help = () => {
|
|||||||
-d, --debug Debug mode [off]
|
-d, --debug Debug mode [off]
|
||||||
-l, --listen [uri] Specify a URI endpoint on which to listen [0.0.0.0:3000]
|
-l, --listen [uri] Specify a URI endpoint on which to listen [0.0.0.0:3000]
|
||||||
-t, --token [token] Specify an Authorization Token
|
-t, --token [token] Specify an Authorization Token
|
||||||
|
--confirm Skip questions and use defaults when setting up a new project
|
||||||
|
|
||||||
${chalk.dim('Examples:')}
|
${chalk.dim('Examples:')}
|
||||||
|
|
||||||
@@ -55,6 +57,7 @@ export default async function main(ctx: NowContext) {
|
|||||||
argv = getArgs(ctx.argv.slice(2), {
|
argv = getArgs(ctx.argv.slice(2), {
|
||||||
'--listen': String,
|
'--listen': String,
|
||||||
'-l': '--listen',
|
'-l': '--listen',
|
||||||
|
'--confirm': Boolean,
|
||||||
|
|
||||||
// Deprecated
|
// Deprecated
|
||||||
'--port': Number,
|
'--port': Number,
|
||||||
@@ -96,7 +99,7 @@ export default async function main(ctx: NowContext) {
|
|||||||
'package.json'
|
'package.json'
|
||||||
)} must not contain ${cmd('now dev')}`
|
)} must not contain ${cmd('now dev')}`
|
||||||
);
|
);
|
||||||
output.error(`More details: http://err.sh/now/now-dev-as-dev-script`);
|
output.error(`Learn More: http://err.sh/now/now-dev-as-dev-script`);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (scripts && scripts.dev && /\bvercel\b\W+\bdev\b/.test(scripts.dev)) {
|
if (scripts && scripts.dev && /\bvercel\b\W+\bdev\b/.test(scripts.dev)) {
|
||||||
@@ -105,7 +108,7 @@ export default async function main(ctx: NowContext) {
|
|||||||
'package.json'
|
'package.json'
|
||||||
)} must not contain ${cmd('vercel dev')}`
|
)} must not contain ${cmd('vercel dev')}`
|
||||||
);
|
);
|
||||||
output.error(`More details: http://err.sh/now/now-dev-as-dev-script`);
|
output.error(`Learn More: http://err.sh/now/now-dev-as-dev-script`);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,7 +122,22 @@ export default async function main(ctx: NowContext) {
|
|||||||
try {
|
try {
|
||||||
return await dev(ctx, argv, args, output);
|
return await dev(ctx, argv, args, output);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
output.error(err.message);
|
if (err.code === 'ENOTFOUND') {
|
||||||
|
// Error message will look like the following:
|
||||||
|
// "request to https://api.vercel.com/www/user failed, reason: getaddrinfo ENOTFOUND api.vercel.com"
|
||||||
|
const matches = /getaddrinfo ENOTFOUND (.*)$/.exec(err.message || '');
|
||||||
|
if (matches && matches[1]) {
|
||||||
|
const hostname = matches[1];
|
||||||
|
output.error(
|
||||||
|
`The hostname ${highlight(
|
||||||
|
hostname
|
||||||
|
)} could not be resolved. Please verify your internet connectivity and DNS configuration.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
output.debug(err.stack);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
output.prettyError(err);
|
||||||
output.debug(stringifyError(err));
|
output.debug(stringifyError(err));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,24 @@
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import psl from 'psl';
|
|
||||||
|
|
||||||
import { NowContext } from '../../types';
|
import { NowContext } from '../../types';
|
||||||
import { Output } from '../../util/output';
|
import { Output } from '../../util/output';
|
||||||
import * as ERRORS from '../../util/errors-ts';
|
import * as ERRORS from '../../util/errors-ts';
|
||||||
import addDomain from '../../util/domains/add-domain';
|
|
||||||
import Client from '../../util/client';
|
import Client from '../../util/client';
|
||||||
import cmd from '../../util/output/cmd';
|
|
||||||
import formatDnsTable from '../../util/format-dns-table';
|
|
||||||
import formatNSTable from '../../util/format-ns-table';
|
import formatNSTable from '../../util/format-ns-table';
|
||||||
import getScope from '../../util/get-scope';
|
import getScope from '../../util/get-scope';
|
||||||
import stamp from '../../util/output/stamp';
|
import stamp from '../../util/output/stamp';
|
||||||
import param from '../../util/output/param';
|
import { getCommandName } from '../../util/pkg-name';
|
||||||
import { getCommandName, getTitleName } from '../../util/pkg-name';
|
import { getDomain } from '../../util/domains/get-domain';
|
||||||
|
import { getLinkedProject } from '../../util/projects/link';
|
||||||
|
import { isPublicSuffix } from '../../util/domains/is-public-suffix';
|
||||||
|
import { getDomainConfig } from '../../util/domains/get-domain-config';
|
||||||
|
import { addDomainToProject } from '../../util/projects/add-domain-to-project';
|
||||||
|
import { removeDomainFromProject } from '../../util/projects/remove-domain-from-project';
|
||||||
|
import code from '../../util/output/code';
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
'--cdn': boolean;
|
|
||||||
'--debug': boolean;
|
'--debug': boolean;
|
||||||
'--no-cdn': boolean;
|
'--force': boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function add(
|
export default async function add(
|
||||||
@@ -33,6 +34,7 @@ export default async function add(
|
|||||||
const { currentTeam } = config;
|
const { currentTeam } = config;
|
||||||
const { apiUrl } = ctx;
|
const { apiUrl } = ctx;
|
||||||
const debug = opts['--debug'];
|
const debug = opts['--debug'];
|
||||||
|
const force = opts['--force'];
|
||||||
const client = new Client({ apiUrl, token, currentTeam, debug });
|
const client = new Client({ apiUrl, token, currentTeam, debug });
|
||||||
let contextName = null;
|
let contextName = null;
|
||||||
|
|
||||||
@@ -47,105 +49,116 @@ export default async function add(
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts['--cdn'] !== undefined || opts['--no-cdn'] !== undefined) {
|
const project = await getLinkedProject(output, client).then(result => {
|
||||||
output.error(`Toggling CF from ${getTitleName()} CLI is deprecated.`);
|
if (result.status === 'linked') {
|
||||||
return 1;
|
return result.project;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.length !== 1) {
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (project && args.length !== 1) {
|
||||||
output.error(
|
output.error(
|
||||||
`${getCommandName('domains add <domain>')} expects one argument`
|
`${getCommandName('domains add <domain>')} expects one argument.`
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
} else if (!project && args.length !== 2) {
|
||||||
|
output.error(
|
||||||
|
`${getCommandName(
|
||||||
|
'domains add <domain> <project>'
|
||||||
|
)} expects two arguments.`
|
||||||
);
|
);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const domainName = String(args[0]);
|
const domainName = String(args[0]);
|
||||||
const parsedDomain = psl.parse(domainName);
|
const projectName = project ? project.name : String(args[1]);
|
||||||
if (parsedDomain.error) {
|
|
||||||
output.error(`The provided domain name ${param(domainName)} is invalid`);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { domain, subdomain } = parsedDomain;
|
|
||||||
if (!domain) {
|
|
||||||
output.error(`The provided domain '${param(domainName)}' is not valid.`);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (subdomain) {
|
|
||||||
output.error(
|
|
||||||
`You are adding '${domainName}' as a domain name containing a subdomain part '${subdomain}'\n` +
|
|
||||||
` This feature is deprecated, please add just the root domain: ${chalk.cyan(
|
|
||||||
`${getCommandName(`domain add ${domain}`)}`
|
|
||||||
)}`
|
|
||||||
);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const addStamp = stamp();
|
const addStamp = stamp();
|
||||||
const addedDomain = await addDomain(client, domainName, contextName);
|
|
||||||
|
|
||||||
if (addedDomain instanceof ERRORS.InvalidDomain) {
|
let aliasTarget = await addDomainToProject(client, projectName, domainName);
|
||||||
output.error(
|
|
||||||
`The provided domain name "${addedDomain.meta.domain}" is invalid`
|
|
||||||
);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addedDomain instanceof ERRORS.DomainAlreadyExists) {
|
if (aliasTarget instanceof Error) {
|
||||||
output.error(
|
if (
|
||||||
`The domain ${chalk.underline(
|
aliasTarget instanceof ERRORS.APIError &&
|
||||||
addedDomain.meta.domain
|
aliasTarget.code === 'ALIAS_DOMAIN_EXIST' &&
|
||||||
)} is already registered by a different account.\n` +
|
aliasTarget.project &&
|
||||||
` If this seems like a mistake, please contact us at support@vercel.com`
|
aliasTarget.project.id
|
||||||
);
|
) {
|
||||||
return 1;
|
if (force) {
|
||||||
|
const removeResponse = await removeDomainFromProject(
|
||||||
|
client,
|
||||||
|
aliasTarget.project.id,
|
||||||
|
domainName
|
||||||
|
);
|
||||||
|
|
||||||
|
if (removeResponse instanceof Error) {
|
||||||
|
output.prettyError(removeResponse);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
aliasTarget = await addDomainToProject(client, projectName, domainName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aliasTarget instanceof Error) {
|
||||||
|
output.prettyError(aliasTarget);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can cast the information because we've just added the domain and it should be there
|
// We can cast the information because we've just added the domain and it should be there
|
||||||
console.log(
|
console.log(
|
||||||
`${chalk.cyan('> Success!')} Domain ${chalk.bold(
|
`${chalk.cyan('> Success!')} Domain ${chalk.bold(
|
||||||
addedDomain.name
|
domainName
|
||||||
)} added correctly. ${addStamp()}\n`
|
)} added to project ${chalk.bold(projectName)}. ${addStamp()}`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!addedDomain.verified) {
|
if (isPublicSuffix(domainName)) {
|
||||||
|
output.log(
|
||||||
|
`The domain will automatically get assigned to your latest production deployment.`
|
||||||
|
);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const domainResponse = await getDomain(client, contextName, domainName);
|
||||||
|
|
||||||
|
if (domainResponse instanceof Error) {
|
||||||
|
output.prettyError(domainResponse);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const domainConfig = await getDomainConfig(client, contextName, domainName);
|
||||||
|
|
||||||
|
if (domainConfig.misconfigured) {
|
||||||
output.warn(
|
output.warn(
|
||||||
`The domain was added but it is not verified. To verify it, you should either:`
|
`This domain is not configured properly. To configure it you should either:`
|
||||||
);
|
);
|
||||||
output.print(
|
output.print(
|
||||||
` ${chalk.gray(
|
` ${chalk.grey('a)')} ` +
|
||||||
'a)'
|
`Set the following record on your DNS provider to continue: ` +
|
||||||
)} Change your domain nameservers to the following intended set: ${chalk.gray(
|
`${code(`A ${domainName} 76.76.21.21`)} ` +
|
||||||
'[recommended]'
|
`${chalk.grey('[recommended]')}\n`
|
||||||
)}\n`
|
);
|
||||||
|
output.print(
|
||||||
|
` ${chalk.grey('b)')} ` +
|
||||||
|
`Change your domain nameservers to the intended set`
|
||||||
);
|
);
|
||||||
output.print(
|
output.print(
|
||||||
`\n${formatNSTable(
|
`\n${formatNSTable(
|
||||||
addedDomain.intendedNameservers,
|
domainResponse.intendedNameservers,
|
||||||
addedDomain.nameservers,
|
domainResponse.nameservers,
|
||||||
{ extraSpace: ' ' }
|
{ extraSpace: ' ' }
|
||||||
)}\n\n`
|
)}\n\n`
|
||||||
);
|
);
|
||||||
output.print(
|
|
||||||
` ${chalk.gray(
|
|
||||||
'b)'
|
|
||||||
)} Add a DNS TXT record with the name and value shown below.\n`
|
|
||||||
);
|
|
||||||
output.print(
|
|
||||||
`\n${formatDnsTable([['_now', 'TXT', addedDomain.verificationRecord]], {
|
|
||||||
extraSpace: ' ',
|
|
||||||
})}\n\n`
|
|
||||||
);
|
|
||||||
output.print(
|
output.print(
|
||||||
` We will run a verification for you and you will receive an email upon completion.\n`
|
` We will run a verification for you and you will receive an email upon completion.\n`
|
||||||
);
|
);
|
||||||
output.print(
|
output.print(' Read more: https://vercel.link/domain-configuration\n\n');
|
||||||
` If you want to force running a verification, you can run ${cmd(
|
} else {
|
||||||
`${getCommandName('domains verify <domain>')}`
|
output.log(
|
||||||
)}\n`
|
`The domain will automatically get assigned to your latest production deployment.`
|
||||||
);
|
);
|
||||||
output.print(' Read more: https://err.sh/now/domain-verification\n\n');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import transferIn from './transfer-in';
|
|||||||
import inspect from './inspect';
|
import inspect from './inspect';
|
||||||
import ls from './ls';
|
import ls from './ls';
|
||||||
import rm from './rm';
|
import rm from './rm';
|
||||||
import verify from './verify';
|
|
||||||
import move from './move';
|
import move from './move';
|
||||||
import { getPkgName } from '../../util/pkg-name';
|
import { getPkgName } from '../../util/pkg-name';
|
||||||
|
|
||||||
@@ -25,17 +24,17 @@ const help = () => {
|
|||||||
|
|
||||||
ls Show all domains in a list
|
ls Show all domains in a list
|
||||||
inspect [name] Displays information related to a domain
|
inspect [name] Displays information related to a domain
|
||||||
add [name] Add a new domain that you already own
|
add [name] [project] Add a new domain that you already own
|
||||||
rm [name] Remove a domain
|
rm [name] Remove a domain
|
||||||
buy [name] Buy a domain that you don't yet own
|
buy [name] Buy a domain that you don't yet own
|
||||||
move [name] [destination] Move a domain to another user or team.
|
move [name] [destination] Move a domain to another user or team.
|
||||||
transfer-in [name] Transfer in a domain to Vercel
|
transfer-in [name] Transfer in a domain to Vercel
|
||||||
verify [name] Run a verification for a domain
|
|
||||||
|
|
||||||
${chalk.dim('Options:')}
|
${chalk.dim('Options:')}
|
||||||
|
|
||||||
-h, --help Output usage information
|
-h, --help Output usage information
|
||||||
-d, --debug Debug mode [off]
|
-d, --debug Debug mode [off]
|
||||||
|
-f, --force Force a domain on a project and remove it from an existing one
|
||||||
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
|
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
|
||||||
'FILE'
|
'FILE'
|
||||||
)} Path to the local ${'`vercel.json`'} file
|
)} Path to the local ${'`vercel.json`'} file
|
||||||
@@ -82,7 +81,6 @@ const COMMAND_CONFIG = {
|
|||||||
move: ['move'],
|
move: ['move'],
|
||||||
rm: ['rm', 'remove'],
|
rm: ['rm', 'remove'],
|
||||||
transferIn: ['transfer-in'],
|
transferIn: ['transfer-in'],
|
||||||
verify: ['verify'],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function main(ctx: NowContext) {
|
export default async function main(ctx: NowContext) {
|
||||||
@@ -90,10 +88,9 @@ export default async function main(ctx: NowContext) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
argv = getArgs(ctx.argv.slice(2), {
|
argv = getArgs(ctx.argv.slice(2), {
|
||||||
'--cdn': Boolean,
|
|
||||||
'--code': String,
|
'--code': String,
|
||||||
'--no-cdn': Boolean,
|
|
||||||
'--yes': Boolean,
|
'--yes': Boolean,
|
||||||
|
'--force': Boolean,
|
||||||
'--next': Number,
|
'--next': Number,
|
||||||
'-N': '--next',
|
'-N': '--next',
|
||||||
});
|
});
|
||||||
@@ -122,8 +119,6 @@ export default async function main(ctx: NowContext) {
|
|||||||
return rm(ctx, argv, args, output);
|
return rm(ctx, argv, args, output);
|
||||||
case 'transferIn':
|
case 'transferIn':
|
||||||
return transferIn(ctx, argv, args, output);
|
return transferIn(ctx, argv, args, output);
|
||||||
case 'verify':
|
|
||||||
return verify(ctx, argv, args, output);
|
|
||||||
default:
|
default:
|
||||||
return ls(ctx, argv, args, output);
|
return ls(ctx, argv, args, output);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,16 @@ import { NowContext } from '../../types';
|
|||||||
import { Output } from '../../util/output';
|
import { Output } from '../../util/output';
|
||||||
import Client from '../../util/client';
|
import Client from '../../util/client';
|
||||||
import stamp from '../../util/output/stamp';
|
import stamp from '../../util/output/stamp';
|
||||||
import dnsTable from '../../util/format-dns-table';
|
|
||||||
import formatDate from '../../util/format-date';
|
import formatDate from '../../util/format-date';
|
||||||
import formatNSTable from '../../util/format-ns-table';
|
import formatNSTable from '../../util/format-ns-table';
|
||||||
import getDomainByName from '../../util/domains/get-domain-by-name';
|
import getDomainByName from '../../util/domains/get-domain-by-name';
|
||||||
import getScope from '../../util/get-scope';
|
import getScope from '../../util/get-scope';
|
||||||
|
import formatTable from '../../util/format-table';
|
||||||
|
import { findProjectsForDomain } from '../../util/projects/find-projects-for-domain';
|
||||||
import getDomainPrice from '../../util/domains/get-domain-price';
|
import getDomainPrice from '../../util/domains/get-domain-price';
|
||||||
import { getCommandName } from '../../util/pkg-name';
|
import { getCommandName } from '../../util/pkg-name';
|
||||||
|
import { getDomainConfig } from '../../util/domains/get-domain-config';
|
||||||
|
import code from '../../util/output/code';
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
'--debug': boolean;
|
'--debug': boolean;
|
||||||
@@ -70,7 +73,7 @@ export default async function inspect(
|
|||||||
.then(res => (res instanceof Error ? null : res.price))
|
.then(res => (res instanceof Error ? null : res.price))
|
||||||
.catch(() => null),
|
.catch(() => null),
|
||||||
]);
|
]);
|
||||||
if (domain instanceof DomainNotFound) {
|
if (!domain || domain instanceof DomainNotFound) {
|
||||||
output.error(
|
output.error(
|
||||||
`Domain not found by "${domainName}" under ${chalk.bold(contextName)}`
|
`Domain not found by "${domainName}" under ${chalk.bold(contextName)}`
|
||||||
);
|
);
|
||||||
@@ -88,6 +91,15 @@ export default async function inspect(
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const projects = await findProjectsForDomain(client, domainName);
|
||||||
|
|
||||||
|
if (projects instanceof Error) {
|
||||||
|
output.prettyError(projects);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const domainConfig = await getDomainConfig(client, contextName, domainName);
|
||||||
|
|
||||||
output.log(
|
output.log(
|
||||||
`Domain ${domainName} found under ${chalk.bold(contextName)} ${chalk.gray(
|
`Domain ${domainName} found under ${chalk.bold(contextName)} ${chalk.gray(
|
||||||
inspectStamp()
|
inspectStamp()
|
||||||
@@ -129,6 +141,7 @@ export default async function inspect(
|
|||||||
domain.txtVerifiedAt
|
domain.txtVerifiedAt
|
||||||
)}\n`
|
)}\n`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (renewalPrice && domain.boughtAt) {
|
if (renewalPrice && domain.boughtAt) {
|
||||||
output.print(
|
output.print(
|
||||||
` ${chalk.cyan('Renewal Price')}\t\t$${renewalPrice} USD\n`
|
` ${chalk.cyan('Renewal Price')}\t\t$${renewalPrice} USD\n`
|
||||||
@@ -145,37 +158,57 @@ export default async function inspect(
|
|||||||
);
|
);
|
||||||
output.print('\n');
|
output.print('\n');
|
||||||
|
|
||||||
output.print(chalk.bold(' Verification Record\n\n'));
|
if (domainConfig.misconfigured) {
|
||||||
output.print(
|
output.warn(
|
||||||
`${dnsTable([['_now', 'TXT', domain.verificationRecord]], {
|
`This domain is not configured properly. To configure it you should either:`
|
||||||
extraSpace: ' ',
|
|
||||||
})}\n`
|
|
||||||
);
|
|
||||||
output.print('\n');
|
|
||||||
|
|
||||||
if (!domain.verified) {
|
|
||||||
output.warn(`This domain is not verified. To verify it you should either:`);
|
|
||||||
output.print(
|
|
||||||
` ${chalk.gray(
|
|
||||||
'a)'
|
|
||||||
)} Change your domain nameservers to the intended set detailed above. ${chalk.gray(
|
|
||||||
'[recommended]'
|
|
||||||
)}\n`
|
|
||||||
);
|
);
|
||||||
output.print(
|
output.print(
|
||||||
` ${chalk.gray(
|
` ${chalk.grey('a)')} ` +
|
||||||
'b)'
|
`Set the following record on your DNS provider to continue: ` +
|
||||||
)} Add a DNS TXT record with the name and value shown above.\n\n`
|
`${code(`A ${domainName} 76.76.21.21`)} ` +
|
||||||
|
`${chalk.grey('[recommended]')}\n`
|
||||||
|
);
|
||||||
|
output.print(
|
||||||
|
` ${chalk.grey('b)')} ` +
|
||||||
|
`Change your domain nameservers to the intended set detailed above.\n\n`
|
||||||
);
|
);
|
||||||
output.print(
|
output.print(
|
||||||
` We will run a verification for you and you will receive an email upon completion.\n`
|
` We will run a verification for you and you will receive an email upon completion.\n`
|
||||||
);
|
);
|
||||||
output.print(
|
output.print(' Read more: https://vercel.link/domain-configuration\n\n');
|
||||||
` If you want to force running a verification, you can run ${getCommandName(
|
}
|
||||||
`domains verify <domain>`
|
|
||||||
)}\n`
|
if (Array.isArray(projects) && projects.length > 0) {
|
||||||
|
output.print(chalk.bold(' Projects\n'));
|
||||||
|
|
||||||
|
const table = formatTable(
|
||||||
|
['Project', 'Domains'],
|
||||||
|
['l', 'l'],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
rows: projects.map(project => {
|
||||||
|
const name = project.name;
|
||||||
|
|
||||||
|
const domains = (project.alias || [])
|
||||||
|
.map(target => target.domain)
|
||||||
|
.filter(alias => alias.endsWith(domainName));
|
||||||
|
|
||||||
|
const cols = domains.length ? domains.join(', ') : '-';
|
||||||
|
|
||||||
|
return [name, cols];
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
);
|
);
|
||||||
output.print(' Read more: https://err.sh/now/domain-verification\n\n');
|
|
||||||
|
output.print(
|
||||||
|
table
|
||||||
|
.split('\n')
|
||||||
|
.map(line => ` ${line}`)
|
||||||
|
.join('\n')
|
||||||
|
);
|
||||||
|
|
||||||
|
output.print('\n\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -1,22 +1,37 @@
|
|||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
|
import psl from 'psl';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import table from 'text-table';
|
import plural from 'pluralize';
|
||||||
|
|
||||||
import Client from '../../util/client';
|
import Client from '../../util/client';
|
||||||
import getDomains from '../../util/domains/get-domains';
|
import getDomains from '../../util/domains/get-domains';
|
||||||
import getScope from '../../util/get-scope';
|
import getScope from '../../util/get-scope';
|
||||||
import stamp from '../../util/output/stamp';
|
import stamp from '../../util/output/stamp';
|
||||||
import strlen from '../../util/strlen';
|
|
||||||
import { Output } from '../../util/output';
|
import { Output } from '../../util/output';
|
||||||
import { Domain, NowContext } from '../../types';
|
import formatTable from '../../util/format-table';
|
||||||
|
import { formatDateWithoutTime } from '../../util/format-date';
|
||||||
|
import { Domain, Project, NowContext } from '../../types';
|
||||||
|
import { getProjectsWithDomains } from '../../util/projects/get-projects-with-domains';
|
||||||
import getCommandFlags from '../../util/get-command-flags';
|
import getCommandFlags from '../../util/get-command-flags';
|
||||||
import { getCommandName } from '../../util/pkg-name';
|
import { getCommandName } from '../../util/pkg-name';
|
||||||
|
import isDomainExternal from '../../util/domains/is-domain-external';
|
||||||
|
import { isPublicSuffix } from '../../util/domains/is-public-suffix';
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
'--debug': boolean;
|
'--debug': boolean;
|
||||||
'--next': number;
|
'--next': number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface DomainInfo {
|
||||||
|
domain: string;
|
||||||
|
apexDomain: string;
|
||||||
|
projectName: string | null;
|
||||||
|
dns: 'Vercel' | 'External';
|
||||||
|
configured: boolean;
|
||||||
|
expiresAt: number | null;
|
||||||
|
createdAt: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
export default async function ls(
|
export default async function ls(
|
||||||
ctx: NowContext,
|
ctx: NowContext,
|
||||||
opts: Options,
|
opts: Options,
|
||||||
@@ -60,16 +75,31 @@ export default async function ls(
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { domains, pagination } = await getDomains(
|
const [{ domains, pagination }, projects] = await Promise.all([
|
||||||
client,
|
getDomains(client, contextName),
|
||||||
contextName,
|
getProjectsWithDomains(client),
|
||||||
nextTimestamp
|
] as const);
|
||||||
);
|
|
||||||
|
if (projects instanceof Error) {
|
||||||
|
output.prettyError(projects);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const domainsInfo = createDomainsInfo(domains, projects);
|
||||||
|
|
||||||
output.log(
|
output.log(
|
||||||
`Domains found under ${chalk.bold(contextName)} ${chalk.gray(lsStamp())}\n`
|
`${plural(
|
||||||
|
'project domain',
|
||||||
|
domainsInfo.length,
|
||||||
|
true
|
||||||
|
)} found under ${chalk.bold(contextName)} ${chalk.gray(lsStamp())}`
|
||||||
);
|
);
|
||||||
if (domains.length > 0) {
|
|
||||||
console.log(`${formatDomainsTable(domains)}\n`);
|
if (domainsInfo.length > 0) {
|
||||||
|
output.print(
|
||||||
|
formatDomainsTable(domainsInfo).replace(/^(.*)/gm, `${' '.repeat(3)}$1`)
|
||||||
|
);
|
||||||
|
output.print('\n\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pagination && pagination.count === 20) {
|
if (pagination && pagination.count === 20) {
|
||||||
@@ -84,28 +114,92 @@ export default async function ls(
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDomainsTable(domains: Domain[]) {
|
function createDomainsInfo(domains: Domain[], projects: Project[]) {
|
||||||
const current = new Date();
|
const info = new Map<string, DomainInfo>();
|
||||||
return table(
|
|
||||||
[
|
domains.forEach(domain => {
|
||||||
[
|
info.set(domain.name, {
|
||||||
'',
|
domain: domain.name,
|
||||||
chalk.gray('domain'),
|
apexDomain: domain.name,
|
||||||
chalk.gray('serviceType'),
|
projectName: null,
|
||||||
chalk.gray('verified'),
|
expiresAt: domain.expiresAt || null,
|
||||||
chalk.gray('cdn'),
|
createdAt: domain.createdAt,
|
||||||
chalk.gray('age'),
|
configured: Boolean(domain.verified),
|
||||||
].map(s => chalk.dim(s)),
|
dns: isDomainExternal(domain) ? 'External' : 'Vercel',
|
||||||
...domains.map(domain => {
|
});
|
||||||
const url = chalk.bold(domain.name);
|
|
||||||
const time = chalk.gray(ms(current.getTime() - domain.createdAt));
|
projects.forEach(project => {
|
||||||
return ['', url, domain.serviceType, domain.verified, true, time];
|
(project.alias || []).forEach(target => {
|
||||||
}),
|
if (!target.domain.endsWith(domain.name)) return;
|
||||||
],
|
|
||||||
{
|
info.set(target.domain, {
|
||||||
align: ['l', 'l', 'l', 'l', 'l'],
|
domain: target.domain,
|
||||||
hsep: ' '.repeat(4),
|
apexDomain: domain.name,
|
||||||
stringLength: strlen,
|
projectName: project.name,
|
||||||
|
expiresAt: domain.expiresAt || null,
|
||||||
|
createdAt: domain.createdAt || target.createdAt || null,
|
||||||
|
configured: Boolean(domain.verified),
|
||||||
|
dns: isDomainExternal(domain) ? 'External' : 'Vercel',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
projects.forEach(project => {
|
||||||
|
(project.alias || []).forEach(target => {
|
||||||
|
if (info.has(target.domain)) return;
|
||||||
|
|
||||||
|
const { domain: apexDomain } = psl.parse(
|
||||||
|
target.domain
|
||||||
|
) as psl.ParsedDomain;
|
||||||
|
|
||||||
|
info.set(target.domain, {
|
||||||
|
domain: target.domain,
|
||||||
|
apexDomain: apexDomain || target.domain,
|
||||||
|
projectName: project.name,
|
||||||
|
expiresAt: null,
|
||||||
|
createdAt: target.createdAt || null,
|
||||||
|
configured: isPublicSuffix(target.domain),
|
||||||
|
dns: isPublicSuffix(target.domain) ? 'Vercel' : 'External',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const list = Array.from(info.values());
|
||||||
|
|
||||||
|
return list.sort((a, b) => {
|
||||||
|
if (a.apexDomain === b.apexDomain) {
|
||||||
|
if (a.apexDomain === a.domain) return -1;
|
||||||
|
if (b.apexDomain === b.domain) return 1;
|
||||||
|
return a.domain.localeCompare(b.domain);
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
return a.apexDomain.localeCompare(b.apexDomain);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDomainsTable(domainsInfo: DomainInfo[]) {
|
||||||
|
const current = Date.now();
|
||||||
|
|
||||||
|
const rows: string[][] = domainsInfo.map(info => {
|
||||||
|
const expiration = formatDateWithoutTime(info.expiresAt);
|
||||||
|
const age = info.createdAt ? ms(current - info.createdAt) : '-';
|
||||||
|
|
||||||
|
return [
|
||||||
|
info.domain,
|
||||||
|
info.projectName || '-',
|
||||||
|
info.dns,
|
||||||
|
expiration,
|
||||||
|
info.configured.toString(),
|
||||||
|
chalk.gray(age),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
const table = formatTable(
|
||||||
|
['domain', 'project', 'dns', 'expiration', 'configured', 'age'],
|
||||||
|
['l', 'l', 'l', 'l', 'l', 'l'],
|
||||||
|
[{ rows }]
|
||||||
|
);
|
||||||
|
|
||||||
|
return table;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import * as ERRORS from '../../util/errors-ts';
|
|||||||
import param from '../../util/output/param';
|
import param from '../../util/output/param';
|
||||||
import promptBool from '../../util/input/prompt-bool';
|
import promptBool from '../../util/input/prompt-bool';
|
||||||
import setCustomSuffix from '../../util/domains/set-custom-suffix';
|
import setCustomSuffix from '../../util/domains/set-custom-suffix';
|
||||||
|
import { findProjectsForDomain } from '../../util/projects/find-projects-for-domain';
|
||||||
import { getCommandName } from '../../util/pkg-name';
|
import { getCommandName } from '../../util/pkg-name';
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
@@ -67,7 +68,7 @@ export default async function rm(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const domain = await getDomainByName(client, contextName, domainName);
|
const domain = await getDomainByName(client, contextName, domainName);
|
||||||
if (domain instanceof DomainNotFound) {
|
if (domain instanceof DomainNotFound || domain.name !== domainName) {
|
||||||
output.error(
|
output.error(
|
||||||
`Domain not found by "${domainName}" under ${chalk.bold(contextName)}`
|
`Domain not found by "${domainName}" under ${chalk.bold(contextName)}`
|
||||||
);
|
);
|
||||||
@@ -85,6 +86,18 @@ export default async function rm(
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const projects = await findProjectsForDomain(client, domain.name);
|
||||||
|
|
||||||
|
if (Array.isArray(projects) && projects.length > 0) {
|
||||||
|
output.warn(
|
||||||
|
`The domain is currently used by ${plural(
|
||||||
|
'project',
|
||||||
|
projects.length,
|
||||||
|
true
|
||||||
|
)}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const skipConfirmation = opts['--yes'];
|
const skipConfirmation = opts['--yes'];
|
||||||
if (
|
if (
|
||||||
!skipConfirmation &&
|
!skipConfirmation &&
|
||||||
|
|||||||
@@ -1,147 +0,0 @@
|
|||||||
import chalk from 'chalk';
|
|
||||||
import { NowContext } from '../../types';
|
|
||||||
import { Output } from '../../util/output';
|
|
||||||
import * as ERRORS from '../../util/errors-ts';
|
|
||||||
import Client from '../../util/client';
|
|
||||||
import formatDnsTable from '../../util/format-dns-table';
|
|
||||||
import formatNSTable from '../../util/format-ns-table';
|
|
||||||
import getDomainByName from '../../util/domains/get-domain-by-name';
|
|
||||||
import getScope from '../../util/get-scope';
|
|
||||||
import stamp from '../../util/output/stamp';
|
|
||||||
import verifyDomain from '../../util/domains/verify-domain';
|
|
||||||
import { getCommandName } from '../../util/pkg-name';
|
|
||||||
|
|
||||||
type Options = {
|
|
||||||
'--debug': boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default async function verify(
|
|
||||||
ctx: NowContext,
|
|
||||||
opts: Options,
|
|
||||||
args: string[],
|
|
||||||
output: Output
|
|
||||||
) {
|
|
||||||
const {
|
|
||||||
authConfig: { token },
|
|
||||||
config,
|
|
||||||
} = ctx;
|
|
||||||
const { currentTeam } = config;
|
|
||||||
const { apiUrl } = ctx;
|
|
||||||
const debug = opts['--debug'];
|
|
||||||
const client = new Client({ apiUrl, token, currentTeam, debug });
|
|
||||||
|
|
||||||
let contextName = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
({ contextName } = await getScope(client));
|
|
||||||
} catch (err) {
|
|
||||||
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
|
|
||||||
output.error(err.message);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [domainName] = args;
|
|
||||||
|
|
||||||
if (!domainName) {
|
|
||||||
output.error(
|
|
||||||
`${getCommandName(`domains verify <domain>`)} expects one argument`
|
|
||||||
);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.length !== 1) {
|
|
||||||
output.error(
|
|
||||||
`Invalid number of arguments. Usage: ${chalk.cyan(
|
|
||||||
`${getCommandName('domains verify <domain>')}`
|
|
||||||
)}`
|
|
||||||
);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const domain = await getDomainByName(client, contextName, domainName);
|
|
||||||
if (domain instanceof ERRORS.DomainNotFound) {
|
|
||||||
output.error(
|
|
||||||
`Domain not found by "${domainName}" under ${chalk.bold(contextName)}`
|
|
||||||
);
|
|
||||||
output.log(`Run ${getCommandName(`domains ls`)} to see your domains.`);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (domain instanceof ERRORS.DomainPermissionDenied) {
|
|
||||||
output.error(
|
|
||||||
`You don't have access to the domain ${domainName} under ${chalk.bold(
|
|
||||||
contextName
|
|
||||||
)}`
|
|
||||||
);
|
|
||||||
output.log(`Run ${getCommandName(`domains ls`)} to see your domains.`);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const verifyStamp = stamp();
|
|
||||||
const result = await verifyDomain(client, domain.name, contextName);
|
|
||||||
if (result instanceof ERRORS.DomainVerificationFailed) {
|
|
||||||
const { nsVerification, txtVerification } = result.meta;
|
|
||||||
output.error(
|
|
||||||
`The domain ${
|
|
||||||
domain.name
|
|
||||||
} could not be verified due to the following reasons: ${verifyStamp()}\n`
|
|
||||||
);
|
|
||||||
output.print(
|
|
||||||
` ${chalk.gray(
|
|
||||||
'a)'
|
|
||||||
)} Nameservers verification failed since we see a different set than the intended set:`
|
|
||||||
);
|
|
||||||
output.print(
|
|
||||||
`\n${formatNSTable(
|
|
||||||
nsVerification.intendedNameservers,
|
|
||||||
nsVerification.nameservers,
|
|
||||||
{ extraSpace: ' ' }
|
|
||||||
)}\n\n`
|
|
||||||
);
|
|
||||||
output.print(
|
|
||||||
` ${chalk.gray(
|
|
||||||
'b)'
|
|
||||||
)} DNS TXT verification failed since found no matching records.`
|
|
||||||
);
|
|
||||||
output.print(
|
|
||||||
`\n${formatDnsTable(
|
|
||||||
[['_now', 'TXT', txtVerification.verificationRecord]],
|
|
||||||
{ extraSpace: ' ' }
|
|
||||||
)}\n\n`
|
|
||||||
);
|
|
||||||
output.print(
|
|
||||||
` Once your domain uses either the nameservers or the TXT DNS record from above, run again ${getCommandName(
|
|
||||||
`domains verify <domain>`
|
|
||||||
)}.\n`
|
|
||||||
);
|
|
||||||
output.print(
|
|
||||||
` We will also periodically run a verification check for you and you will receive an email once your domain is verified.\n`
|
|
||||||
);
|
|
||||||
output.print(' Read more: https://err.sh/now/domain-verification\n\n');
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.nsVerifiedAt) {
|
|
||||||
console.log(
|
|
||||||
`${chalk.cyan('> Success!')} Domain ${chalk.bold(
|
|
||||||
domain.name
|
|
||||||
)} was verified using nameservers. ${verifyStamp()}`
|
|
||||||
);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`${chalk.cyan('> Success!')} Domain ${chalk.bold(
|
|
||||||
domain.name
|
|
||||||
)} was verified using DNS TXT record. ${verifyStamp()}`
|
|
||||||
);
|
|
||||||
output.print(
|
|
||||||
` You can verify with nameservers too. Run ${getCommandName(
|
|
||||||
`domains inspect ${domain.name}`
|
|
||||||
)} to find out the intended set.\n`
|
|
||||||
);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
4
packages/now-cli/src/commands/env/index.ts
vendored
4
packages/now-cli/src/commands/env/index.ts
vendored
@@ -124,7 +124,9 @@ export default async function main(ctx: NowContext) {
|
|||||||
return link.exitCode;
|
return link.exitCode;
|
||||||
} else if (link.status === 'not_linked') {
|
} else if (link.status === 'not_linked') {
|
||||||
output.error(
|
output.error(
|
||||||
`Your codebase isn’t linked to a project on Vercel. Run ${getCommandName()} to link it.`
|
`Your codebase isn’t linked to a project on Vercel. Run ${getCommandName(
|
||||||
|
'link'
|
||||||
|
)} to begin.`
|
||||||
);
|
);
|
||||||
return 1;
|
return 1;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
97
packages/now-cli/src/commands/env/pull.ts
vendored
97
packages/now-cli/src/commands/env/pull.ts
vendored
@@ -4,21 +4,44 @@ import { Output } from '../../util/output';
|
|||||||
import promptBool from '../../util/prompt-bool';
|
import promptBool from '../../util/prompt-bool';
|
||||||
import Client from '../../util/client';
|
import Client from '../../util/client';
|
||||||
import stamp from '../../util/output/stamp';
|
import stamp from '../../util/output/stamp';
|
||||||
import getEnvVariables from '../../util/env/get-env-records';
|
import getDecryptedEnvRecords from '../../util/get-decrypted-env-records';
|
||||||
import getDecryptedSecret from '../../util/env/get-decrypted-secret';
|
|
||||||
import param from '../../util/output/param';
|
import param from '../../util/output/param';
|
||||||
import withSpinner from '../../util/with-spinner';
|
import withSpinner from '../../util/with-spinner';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { promises, existsSync } from 'fs';
|
import { promises, openSync, closeSync, readSync } from 'fs';
|
||||||
import { emoji, prependEmoji } from '../../util/emoji';
|
import { emoji, prependEmoji } from '../../util/emoji';
|
||||||
import { getCommandName } from '../../util/pkg-name';
|
import { getCommandName } from '../../util/pkg-name';
|
||||||
const { writeFile } = promises;
|
const { writeFile } = promises;
|
||||||
|
import { Env } from '@vercel/build-utils';
|
||||||
|
|
||||||
|
const CONTENTS_PREFIX = '# Created by Vercel CLI\n';
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
'--debug': boolean;
|
'--debug': boolean;
|
||||||
'--yes': boolean;
|
'--yes': boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function readHeadSync(path: string, length: number) {
|
||||||
|
const buffer = Buffer.alloc(length);
|
||||||
|
const fd = openSync(path, 'r');
|
||||||
|
try {
|
||||||
|
readSync(fd, buffer, 0, buffer.length, null);
|
||||||
|
} finally {
|
||||||
|
closeSync(fd);
|
||||||
|
}
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function tryReadHeadSync(path: string, length: number) {
|
||||||
|
try {
|
||||||
|
return readHeadSync(path, length);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code !== 'ENOENT') {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default async function pull(
|
export default async function pull(
|
||||||
client: Client,
|
client: Client,
|
||||||
project: Project,
|
project: Project,
|
||||||
@@ -35,10 +58,14 @@ export default async function pull(
|
|||||||
|
|
||||||
const [filename = '.env'] = args;
|
const [filename = '.env'] = args;
|
||||||
const fullPath = join(process.cwd(), filename);
|
const fullPath = join(process.cwd(), filename);
|
||||||
const exists = existsSync(fullPath);
|
|
||||||
const skipConfirmation = opts['--yes'];
|
const skipConfirmation = opts['--yes'];
|
||||||
|
|
||||||
if (
|
const head = tryReadHeadSync(fullPath, Buffer.byteLength(CONTENTS_PREFIX));
|
||||||
|
const exists = typeof head !== 'undefined';
|
||||||
|
|
||||||
|
if (head === CONTENTS_PREFIX) {
|
||||||
|
output.print(`Overwriting existing ${chalk.bold(filename)} file\n`);
|
||||||
|
} else if (
|
||||||
exists &&
|
exists &&
|
||||||
!skipConfirmation &&
|
!skipConfirmation &&
|
||||||
!(await promptBool(
|
!(await promptBool(
|
||||||
@@ -57,45 +84,23 @@ export default async function pull(
|
|||||||
);
|
);
|
||||||
const pullStamp = stamp();
|
const pullStamp = stamp();
|
||||||
|
|
||||||
const records = await withSpinner('Downloading', async () => {
|
const records: Env = await withSpinner(
|
||||||
const dev = ProjectEnvTarget.Development;
|
'Downloading',
|
||||||
const envs = await getEnvVariables(output, client, project.id, 4, dev);
|
async () =>
|
||||||
const decryptedValues = await Promise.all(
|
await getDecryptedEnvRecords(
|
||||||
envs.map(async env => {
|
output,
|
||||||
try {
|
client,
|
||||||
const value = await getDecryptedSecret(output, client, env.value);
|
project,
|
||||||
return { value, found: true };
|
ProjectEnvTarget.Development
|
||||||
} catch (error) {
|
)
|
||||||
if (error && error.status === 404) {
|
);
|
||||||
return { value: '', found: false };
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const results: { key: string; value: string; found: boolean }[] = [];
|
|
||||||
for (let i = 0; i < decryptedValues.length; i++) {
|
|
||||||
const { key } = envs[i];
|
|
||||||
const { value, found } = decryptedValues[i];
|
|
||||||
results.push({ key, value, found });
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
});
|
|
||||||
|
|
||||||
const contents =
|
const contents =
|
||||||
records
|
CONTENTS_PREFIX +
|
||||||
.filter(obj => {
|
Object.entries(records)
|
||||||
if (!obj.found) {
|
.map(([key, value]) => `${key}="${escapeValue(value)}"`)
|
||||||
output.print('');
|
.join('\n') +
|
||||||
output.warn(
|
'\n';
|
||||||
`Unable to download variable ${obj.key} because associated secret was deleted`
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
.map(({ key, value }) => `${key}="${escapeValue(value)}"`)
|
|
||||||
.join('\n') + '\n';
|
|
||||||
|
|
||||||
await writeFile(fullPath, contents, 'utf8');
|
await writeFile(fullPath, contents, 'utf8');
|
||||||
|
|
||||||
@@ -110,8 +115,10 @@ export default async function pull(
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeValue(value: string) {
|
function escapeValue(value: string | undefined) {
|
||||||
return value
|
return value
|
||||||
.replace(new RegExp('\n', 'g'), '\\n') // combine newlines (unix) into one line
|
? value
|
||||||
.replace(new RegExp('\r', 'g'), '\\r'); // combine newlines (windows) into one line
|
.replace(new RegExp('\n', 'g'), '\\n') // combine newlines (unix) into one line
|
||||||
|
.replace(new RegExp('\r', 'g'), '\\r') // combine newlines (windows) into one line
|
||||||
|
: '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export default new Map([
|
|||||||
['help', 'help'],
|
['help', 'help'],
|
||||||
['init', 'init'],
|
['init', 'init'],
|
||||||
['inspect', 'inspect'],
|
['inspect', 'inspect'],
|
||||||
|
['link', 'link'],
|
||||||
['list', 'list'],
|
['list', 'list'],
|
||||||
['ln', 'alias'],
|
['ln', 'alias'],
|
||||||
['log', 'logs'],
|
['log', 'logs'],
|
||||||
|
|||||||
98
packages/now-cli/src/commands/link/index.ts
Normal file
98
packages/now-cli/src/commands/link/index.ts
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import chalk from 'chalk';
|
||||||
|
import { NowContext } from '../../types';
|
||||||
|
import createOutput from '../../util/output';
|
||||||
|
import getArgs from '../../util/get-args';
|
||||||
|
import getSubcommand from '../../util/get-subcommand';
|
||||||
|
import handleError from '../../util/handle-error';
|
||||||
|
import logo from '../../util/output/logo';
|
||||||
|
import { getPkgName } from '../../util/pkg-name';
|
||||||
|
import setupAndLink from '../../util/link/setup-and-link';
|
||||||
|
|
||||||
|
const help = () => {
|
||||||
|
console.log(`
|
||||||
|
${chalk.bold(`${logo} ${getPkgName()} link`)} [options]
|
||||||
|
|
||||||
|
${chalk.dim('Options:')}
|
||||||
|
|
||||||
|
-h, --help Output usage information
|
||||||
|
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
|
||||||
|
'FILE'
|
||||||
|
)} Path to the local ${'`vercel.json`'} file
|
||||||
|
-Q ${chalk.bold.underline('DIR')}, --global-config=${chalk.bold.underline(
|
||||||
|
'DIR'
|
||||||
|
)} Path to the global ${'`.vercel`'} directory
|
||||||
|
-d, --debug Debug mode [off]
|
||||||
|
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||||
|
'TOKEN'
|
||||||
|
)} Login token
|
||||||
|
--confirm Confirm default options and skip questions
|
||||||
|
|
||||||
|
${chalk.dim('Examples:')}
|
||||||
|
|
||||||
|
${chalk.gray('–')} Link current directory to a Vercel Project
|
||||||
|
|
||||||
|
${chalk.cyan(`$ ${getPkgName()} link`)}
|
||||||
|
|
||||||
|
${chalk.gray(
|
||||||
|
'–'
|
||||||
|
)} Link current directory with default options and skip questions
|
||||||
|
|
||||||
|
${chalk.cyan(`$ ${getPkgName()} link --confirm`)}
|
||||||
|
|
||||||
|
${chalk.gray('–')} Link a specific directory to a Vercel Project
|
||||||
|
|
||||||
|
${chalk.cyan(`$ ${getPkgName()} link /usr/src/project`)}
|
||||||
|
`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const COMMAND_CONFIG = {
|
||||||
|
// No subcommands yet
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function main(ctx: NowContext) {
|
||||||
|
let argv;
|
||||||
|
|
||||||
|
try {
|
||||||
|
argv = getArgs(ctx.argv.slice(2), {
|
||||||
|
'--confirm': Boolean,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argv['--help']) {
|
||||||
|
help();
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
const debug = argv['--debug'];
|
||||||
|
const output = createOutput({ debug });
|
||||||
|
const { args } = getSubcommand(argv._.slice(1), COMMAND_CONFIG);
|
||||||
|
const path = args[0] || process.cwd();
|
||||||
|
const autoConfirm = argv['--confirm'];
|
||||||
|
const forceDelete = true;
|
||||||
|
|
||||||
|
const link = await setupAndLink(
|
||||||
|
ctx,
|
||||||
|
output,
|
||||||
|
path,
|
||||||
|
forceDelete,
|
||||||
|
autoConfirm,
|
||||||
|
'success',
|
||||||
|
'Set up'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (link.status === 'error') {
|
||||||
|
return link.exitCode;
|
||||||
|
} else if (link.status === 'not_linked') {
|
||||||
|
// User aborted project linking questions
|
||||||
|
return 0;
|
||||||
|
} else if (link.status === 'linked') {
|
||||||
|
// Successfully linked
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
const err: never = link;
|
||||||
|
throw new Error('Unknown link status: ' + err);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import table from 'text-table';
|
import table from 'text-table';
|
||||||
import mri from 'mri';
|
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
import strlen from '../util/strlen';
|
import strlen from '../util/strlen';
|
||||||
|
import getArgs from '../util/get-args';
|
||||||
import { handleError, error } from '../util/error';
|
import { handleError, error } from '../util/error';
|
||||||
import exit from '../util/exit';
|
import exit from '../util/exit';
|
||||||
import Client from '../util/client.ts';
|
import Client from '../util/client.ts';
|
||||||
@@ -11,7 +11,6 @@ import getScope from '../util/get-scope';
|
|||||||
import createOutput from '../util/output';
|
import createOutput from '../util/output';
|
||||||
import getCommandFlags from '../util/get-command-flags';
|
import getCommandFlags from '../util/get-command-flags';
|
||||||
import wait from '../util/output/wait';
|
import wait from '../util/output/wait';
|
||||||
import getPrefixedFlags from '../util/get-prefixed-flags';
|
|
||||||
import { getPkgName, getCommandName } from '../util/pkg-name.ts';
|
import { getPkgName, getCommandName } from '../util/pkg-name.ts';
|
||||||
|
|
||||||
const e = encodeURIComponent;
|
const e = encodeURIComponent;
|
||||||
@@ -56,23 +55,25 @@ let apiUrl;
|
|||||||
let subcommand;
|
let subcommand;
|
||||||
|
|
||||||
const main = async ctx => {
|
const main = async ctx => {
|
||||||
argv = mri(ctx.argv.slice(2), {
|
try {
|
||||||
boolean: ['help'],
|
argv = getArgs(ctx.argv.slice(2), {
|
||||||
alias: {
|
'--next': Number,
|
||||||
help: 'h',
|
'-N': '--next',
|
||||||
next: 'N',
|
});
|
||||||
},
|
} catch (error) {
|
||||||
});
|
handleError(error);
|
||||||
|
return exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
argv._ = argv._.slice(1);
|
argv._ = argv._.slice(1);
|
||||||
|
|
||||||
debug = argv.debug;
|
debug = argv['--debug'];
|
||||||
apiUrl = ctx.apiUrl;
|
apiUrl = ctx.apiUrl;
|
||||||
subcommand = argv._[0];
|
subcommand = argv._[0] || 'list';
|
||||||
|
|
||||||
if (argv.help || !subcommand) {
|
if (argv['--help']) {
|
||||||
help();
|
help();
|
||||||
await exit(0);
|
return exit(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
const output = createOutput({ debug });
|
const output = createOutput({ debug });
|
||||||
@@ -126,15 +127,16 @@ async function run({ client, contextName }) {
|
|||||||
)}`
|
)}`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
return exit(1);
|
return exit(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
const stopSpinner = wait(`Fetching projects in ${chalk.bold(contextName)}`);
|
const stopSpinner = wait(`Fetching projects in ${chalk.bold(contextName)}`);
|
||||||
|
|
||||||
let projectsUrl = '/v4/projects/?limit=20';
|
let projectsUrl = '/v4/projects/?limit=20';
|
||||||
|
|
||||||
if (argv.next) {
|
const next = argv['--next'];
|
||||||
projectsUrl += `&until=${argv.next}`;
|
if (next) {
|
||||||
|
projectsUrl += `&until=${next}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { projects: list, pagination } = await client.fetch(projectsUrl, {
|
const { projects: list, pagination } = await client.fetch(projectsUrl, {
|
||||||
@@ -175,14 +177,7 @@ async function run({ client, contextName }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (pagination && pagination.count === 20) {
|
if (pagination && pagination.count === 20) {
|
||||||
const prefixedArgs = getPrefixedFlags(argv);
|
const flags = getCommandFlags(argv, ['_', '--next', '-N', '-d', '-y']);
|
||||||
const flags = getCommandFlags(prefixedArgs, [
|
|
||||||
'_',
|
|
||||||
'--next',
|
|
||||||
'-N',
|
|
||||||
'-d',
|
|
||||||
'-y',
|
|
||||||
]);
|
|
||||||
const nextCmd = `projects ls${flags} --next ${pagination.next}`;
|
const nextCmd = `projects ls${flags} --next ${pagination.next}`;
|
||||||
console.log(`To display the next page run ${getCommandName(nextCmd)}`);
|
console.log(`To display the next page run ${getCommandName(nextCmd)}`);
|
||||||
}
|
}
|
||||||
@@ -271,7 +266,7 @@ async function run({ client, contextName }) {
|
|||||||
|
|
||||||
console.error(error('Please specify a valid subcommand: ls | add | rm'));
|
console.error(error('Please specify a valid subcommand: ls | add | rm'));
|
||||||
help();
|
help();
|
||||||
exit(1);
|
exit(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
process.on('uncaughtException', err => {
|
process.on('uncaughtException', err => {
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ import sourceMap from '@zeit/source-map-support';
|
|||||||
import { mkdirp } from 'fs-extra';
|
import { mkdirp } from 'fs-extra';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import epipebomb from 'epipebomb';
|
import epipebomb from 'epipebomb';
|
||||||
import checkForUpdate from 'update-check';
|
import updateNotifier from 'update-notifier';
|
||||||
import ms from 'ms';
|
|
||||||
import { URL } from 'url';
|
import { URL } from 'url';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
|
import { NowBuildError } from '@vercel/build-utils';
|
||||||
import getGlobalPathConfig from './util/config/global-path';
|
import getGlobalPathConfig from './util/config/global-path';
|
||||||
import {
|
import {
|
||||||
getDefaultConfig,
|
getDefaultConfig,
|
||||||
@@ -45,11 +45,20 @@ import reportError from './util/report-error';
|
|||||||
import getConfig from './util/get-config';
|
import getConfig from './util/get-config';
|
||||||
import * as ERRORS from './util/errors-ts';
|
import * as ERRORS from './util/errors-ts';
|
||||||
import { NowError } from './util/now-error';
|
import { NowError } from './util/now-error';
|
||||||
|
import { APIError } from './util/errors-ts.ts';
|
||||||
import { SENTRY_DSN } from './util/constants.ts';
|
import { SENTRY_DSN } from './util/constants.ts';
|
||||||
import getUpdateCommand from './util/get-update-command';
|
import getUpdateCommand from './util/get-update-command';
|
||||||
import { metrics, shouldCollectMetrics } from './util/metrics.ts';
|
import { metrics, shouldCollectMetrics } from './util/metrics.ts';
|
||||||
import { getCommandName, getTitleName } from './util/pkg-name.ts';
|
import { getCommandName, getTitleName } from './util/pkg-name.ts';
|
||||||
|
|
||||||
|
const isCanary = pkg.version.includes('canary');
|
||||||
|
|
||||||
|
// Checks for available update and returns an instance
|
||||||
|
const notifier = updateNotifier({
|
||||||
|
pkg,
|
||||||
|
distTag: isCanary ? 'canary' : 'latest',
|
||||||
|
});
|
||||||
|
|
||||||
const VERCEL_DIR = getGlobalPathConfig();
|
const VERCEL_DIR = getGlobalPathConfig();
|
||||||
const VERCEL_CONFIG_PATH = configFiles.getConfigFilePath();
|
const VERCEL_CONFIG_PATH = configFiles.getConfigFilePath();
|
||||||
const VERCEL_AUTH_CONFIG_PATH = configFiles.getAuthConfigFilePath();
|
const VERCEL_AUTH_CONFIG_PATH = configFiles.getAuthConfigFilePath();
|
||||||
@@ -64,7 +73,7 @@ sourceMap.install();
|
|||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: SENTRY_DSN,
|
dsn: SENTRY_DSN,
|
||||||
release: `vercel-cli@${pkg.version}`,
|
release: `vercel-cli@${pkg.version}`,
|
||||||
environment: pkg.version.includes('canary') ? 'canary' : 'stable',
|
environment: isCanary ? 'canary' : 'stable',
|
||||||
});
|
});
|
||||||
|
|
||||||
let debug = () => {};
|
let debug = () => {};
|
||||||
@@ -114,10 +123,10 @@ const main = async argv_ => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
localConfig instanceof NowError &&
|
(localConfig instanceof NowError || localConfig instanceof NowBuildError) &&
|
||||||
!(localConfig instanceof ERRORS.CantFindConfig)
|
!(localConfig instanceof ERRORS.CantFindConfig)
|
||||||
) {
|
) {
|
||||||
output.error(`Failed to load local config file: ${localConfig.message}`);
|
output.prettyError(localConfig);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,38 +135,20 @@ const main = async argv_ => {
|
|||||||
// (as in: `vercel ls`)
|
// (as in: `vercel ls`)
|
||||||
const targetOrSubcommand = argv._[2];
|
const targetOrSubcommand = argv._[2];
|
||||||
|
|
||||||
let update = null;
|
if (notifier.update && isTTY) {
|
||||||
|
const { latest } = notifier.update;
|
||||||
try {
|
|
||||||
if (targetOrSubcommand !== 'update') {
|
|
||||||
update = await checkForUpdate(pkg, {
|
|
||||||
interval: ms('1d'),
|
|
||||||
distTag: pkg.version.includes('canary') ? 'canary' : 'latest',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error(
|
|
||||||
error(`Checking for updates failed${isDebugging ? ':' : ''}`)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isDebugging) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (update && isTTY) {
|
|
||||||
console.log(
|
console.log(
|
||||||
info(
|
info(
|
||||||
`${chalk.bgRed('UPDATE AVAILABLE')} ` +
|
`${chalk.bgRed('UPDATE AVAILABLE')} ` +
|
||||||
`Run ${cmd(
|
`Run ${cmd(
|
||||||
await getUpdateCommand()
|
await getUpdateCommand()
|
||||||
)} to install ${getTitleName()} CLI ${update.latest}`
|
)} to install ${getTitleName()} CLI ${latest}`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
info(
|
info(
|
||||||
`Changelog: https://github.com/vercel/vercel/releases/tag/vercel@${update.latest}`
|
`Changelog: https://github.com/vercel/vercel/releases/tag/vercel@${latest}`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -167,7 +158,7 @@ const main = async argv_ => {
|
|||||||
`${getTitleName()} CLI ${pkg.version}${
|
`${getTitleName()} CLI ${pkg.version}${
|
||||||
targetOrSubcommand === 'dev' ? ' dev (beta)' : ''
|
targetOrSubcommand === 'dev' ? ' dev (beta)' : ''
|
||||||
}${
|
}${
|
||||||
pkg.version.includes('canary') || targetOrSubcommand === 'dev'
|
isCanary || targetOrSubcommand === 'dev'
|
||||||
? ' — https://vercel.com/feedback'
|
? ' — https://vercel.com/feedback'
|
||||||
: ''
|
: ''
|
||||||
}`
|
}`
|
||||||
@@ -189,8 +180,7 @@ const main = async argv_ => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(
|
console.error(
|
||||||
error(
|
error(
|
||||||
`${'An unexpected error occurred while trying to find the ' +
|
`An unexpected error occurred while trying to find the global directory: ${err.message}`
|
||||||
'global directory: '}${err.message}`
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -203,8 +193,10 @@ const main = async argv_ => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(
|
console.error(
|
||||||
error(
|
error(
|
||||||
`${'An unexpected error occurred while trying to create the ' +
|
`${
|
||||||
`global directory "${hp(VERCEL_DIR)}" `}${err.message}`
|
'An unexpected error occurred while trying to create the ' +
|
||||||
|
`global directory "${hp(VERCEL_DIR)}" `
|
||||||
|
}${err.message}`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -218,8 +210,10 @@ const main = async argv_ => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(
|
console.error(
|
||||||
error(
|
error(
|
||||||
`${'An unexpected error occurred while trying to find the ' +
|
`${
|
||||||
`config file "${hp(VERCEL_CONFIG_PATH)}" `}${err.message}`
|
'An unexpected error occurred while trying to find the ' +
|
||||||
|
`config file "${hp(VERCEL_CONFIG_PATH)}" `
|
||||||
|
}${err.message}`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -234,8 +228,10 @@ const main = async argv_ => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(
|
console.error(
|
||||||
error(
|
error(
|
||||||
`${'An unexpected error occurred while trying to read the ' +
|
`${
|
||||||
`config in "${hp(VERCEL_CONFIG_PATH)}" `}${err.message}`
|
'An unexpected error occurred while trying to read the ' +
|
||||||
|
`config in "${hp(VERCEL_CONFIG_PATH)}" `
|
||||||
|
}${err.message}`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -266,8 +262,10 @@ const main = async argv_ => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(
|
console.error(
|
||||||
error(
|
error(
|
||||||
`${'An unexpected error occurred while trying to write the ' +
|
`${
|
||||||
`default config to "${hp(VERCEL_CONFIG_PATH)}" `}${err.message}`
|
'An unexpected error occurred while trying to write the ' +
|
||||||
|
`default config to "${hp(VERCEL_CONFIG_PATH)}" `
|
||||||
|
}${err.message}`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -282,8 +280,10 @@ const main = async argv_ => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(
|
console.error(
|
||||||
error(
|
error(
|
||||||
`${'An unexpected error occurred while trying to find the ' +
|
`${
|
||||||
`auth file "${hp(VERCEL_AUTH_CONFIG_PATH)}" `}${err.message}`
|
'An unexpected error occurred while trying to find the ' +
|
||||||
|
`auth file "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
|
||||||
|
}${err.message}`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -300,8 +300,10 @@ const main = async argv_ => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(
|
console.error(
|
||||||
error(
|
error(
|
||||||
`${'An unexpected error occurred while trying to read the ' +
|
`${
|
||||||
`auth config in "${hp(VERCEL_AUTH_CONFIG_PATH)}" `}${err.message}`
|
'An unexpected error occurred while trying to read the ' +
|
||||||
|
`auth config in "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
|
||||||
|
}${err.message}`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -313,21 +315,6 @@ const main = async argv_ => {
|
|||||||
// need to migrate.
|
// need to migrate.
|
||||||
if (authConfig.credentials) {
|
if (authConfig.credentials) {
|
||||||
authConfigExists = false;
|
authConfigExists = false;
|
||||||
} else if (
|
|
||||||
!authConfig.token &&
|
|
||||||
!subcommandsWithoutToken.includes(targetOrSubcommand) &&
|
|
||||||
!argv['--help'] &&
|
|
||||||
!argv['--token']
|
|
||||||
) {
|
|
||||||
console.error(
|
|
||||||
error(
|
|
||||||
`The content of "${hp(VERCEL_AUTH_CONFIG_PATH)}" is invalid. ` +
|
|
||||||
`No \`token\` property found inside. Run ${getCommandName(
|
|
||||||
'login'
|
|
||||||
)} to authorize.`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const results = await getDefaultAuthConfig(authConfig);
|
const results = await getDefaultAuthConfig(authConfig);
|
||||||
@@ -340,10 +327,10 @@ const main = async argv_ => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(
|
console.error(
|
||||||
error(
|
error(
|
||||||
`${'An unexpected error occurred while trying to write the ' +
|
`${
|
||||||
`default config to "${hp(VERCEL_AUTH_CONFIG_PATH)}" `}${
|
'An unexpected error occurred while trying to write the ' +
|
||||||
err.message
|
`default config to "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
|
||||||
}`
|
}${err.message}`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
return 1;
|
return 1;
|
||||||
@@ -637,34 +624,25 @@ const main = async argv_ => {
|
|||||||
.send();
|
.send();
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code === 'ENOTFOUND' && err.hostname === 'api.vercel.com') {
|
if (err.code === 'ENOTFOUND') {
|
||||||
output.error(
|
// Error message will look like the following:
|
||||||
`The hostname ${highlight(
|
// "request to https://api.vercel.com/www/user failed, reason: getaddrinfo ENOTFOUND api.vercel.com"
|
||||||
'api.vercel.com'
|
const matches = /getaddrinfo ENOTFOUND (.*)$/.exec(err.message || '');
|
||||||
)} could not be resolved. Please verify your internet connectivity and DNS configuration.`
|
if (matches && matches[1]) {
|
||||||
);
|
const hostname = matches[1];
|
||||||
|
output.error(
|
||||||
|
`The hostname ${highlight(
|
||||||
|
hostname
|
||||||
|
)} could not be resolved. Please verify your internet connectivity and DNS configuration.`
|
||||||
|
);
|
||||||
|
}
|
||||||
output.debug(err.stack);
|
output.debug(err.stack);
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
await reportError(Sentry, err, apiUrl, configFiles);
|
if (err instanceof APIError && 400 <= err.status && err.status <= 499) {
|
||||||
|
err.message = err.serverMessage;
|
||||||
// If there is a code we should not consider the error unexpected
|
output.prettyError(err);
|
||||||
// but instead show the message. Any error that is handled by this should
|
|
||||||
// actually be handled in the sub command instead. Please make sure
|
|
||||||
// that happens for anything that lands here. It should NOT bubble up to here.
|
|
||||||
if (err.code) {
|
|
||||||
output.debug(err.stack);
|
|
||||||
output.error(err.message);
|
|
||||||
|
|
||||||
if (shouldCollectMetrics) {
|
|
||||||
metric
|
|
||||||
.event(eventCategory, '1', pkg.version)
|
|
||||||
.exception(err.message)
|
|
||||||
.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -675,9 +653,23 @@ const main = async argv_ => {
|
|||||||
.send();
|
.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise it is an unexpected error and we should show the trace
|
// If there is a code we should not consider the error unexpected
|
||||||
// and an unexpected error message
|
// but instead show the message. Any error that is handled by this should
|
||||||
output.error(`An unexpected error occurred in ${subcommand}: ${err.stack}`);
|
// actually be handled in the sub command instead. Please make sure
|
||||||
|
// that happens for anything that lands here. It should NOT bubble up to here.
|
||||||
|
if (err.code) {
|
||||||
|
output.debug(err.stack);
|
||||||
|
output.prettyError(err);
|
||||||
|
} else {
|
||||||
|
await reportError(Sentry, err, apiUrl, configFiles);
|
||||||
|
|
||||||
|
// Otherwise it is an unexpected error and we should show the trace
|
||||||
|
// and an unexpected error message
|
||||||
|
output.error(
|
||||||
|
`An unexpected error occurred in ${subcommand}: ${err.stack}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -688,8 +680,6 @@ const main = async argv_ => {
|
|||||||
return exitCode;
|
return exitCode;
|
||||||
};
|
};
|
||||||
|
|
||||||
debug('start');
|
|
||||||
|
|
||||||
const handleRejection = async err => {
|
const handleRejection = async err => {
|
||||||
debug('handling rejection');
|
debug('handling rejection');
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,16 @@ export type Domain = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type DomainConfig = {
|
||||||
|
configuredBy: null | 'CNAME' | 'A' | 'http';
|
||||||
|
misconfigured: boolean;
|
||||||
|
serviceType: 'zeit.world' | 'external' | 'na';
|
||||||
|
nameservers: string[];
|
||||||
|
cnames: string[] & { traceString?: string };
|
||||||
|
aValues: string[] & { traceString?: string };
|
||||||
|
dnssecEnabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export type Cert = {
|
export type Cert = {
|
||||||
uid: string;
|
uid: string;
|
||||||
autoRenew: boolean;
|
autoRenew: boolean;
|
||||||
@@ -217,6 +227,16 @@ export type DNSRecordData =
|
|||||||
| SRVRecordData
|
| SRVRecordData
|
||||||
| MXRecordData;
|
| MXRecordData;
|
||||||
|
|
||||||
|
export interface ProjectAliasTarget {
|
||||||
|
createdAt?: number;
|
||||||
|
domain: string;
|
||||||
|
redirect?: string | null;
|
||||||
|
target: 'PRODUCTION' | 'STAGING';
|
||||||
|
configuredBy?: null | 'CNAME' | 'A';
|
||||||
|
configuredChangedAt?: null | number;
|
||||||
|
configuredChangeAttempts?: [number, number];
|
||||||
|
}
|
||||||
|
|
||||||
export interface Secret {
|
export interface Secret {
|
||||||
uid: string;
|
uid: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -244,12 +264,21 @@ export interface ProjectEnvVariable {
|
|||||||
system?: boolean;
|
system?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Project {
|
export interface ProjectSettings {
|
||||||
|
framework?: string | null;
|
||||||
|
devCommand?: string | null;
|
||||||
|
buildCommand?: string | null;
|
||||||
|
outputDirectory?: string | null;
|
||||||
|
rootDirectory?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Project extends ProjectSettings {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
accountId: string;
|
accountId: string;
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
createdAt: number;
|
createdAt: number;
|
||||||
|
alias?: ProjectAliasTarget[];
|
||||||
devCommand?: string | null;
|
devCommand?: string | null;
|
||||||
framework?: string | null;
|
framework?: string | null;
|
||||||
rootDirectory?: string | null;
|
rootDirectory?: string | null;
|
||||||
@@ -272,3 +301,8 @@ export interface PaginationOptions {
|
|||||||
count: number;
|
count: number;
|
||||||
next?: number;
|
next?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ProjectLinkResult =
|
||||||
|
| { status: 'linked'; org: Org; project: Project }
|
||||||
|
| { status: 'not_linked'; org: null; project: null }
|
||||||
|
| { status: 'error'; exitCode: number };
|
||||||
|
|||||||
17
packages/now-cli/src/util/certs/get-certs-for-cn.ts
Normal file
17
packages/now-cli/src/util/certs/get-certs-for-cn.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { stringify } from 'querystring';
|
||||||
|
import { Cert } from '../../types';
|
||||||
|
import Client from '../client';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns certs that contain @param cn.
|
||||||
|
*/
|
||||||
|
export async function getCertsForCn(
|
||||||
|
client: Client,
|
||||||
|
cn: string,
|
||||||
|
{ limit }: { limit?: number } = {}
|
||||||
|
) {
|
||||||
|
const { certs } = await client.fetch<{
|
||||||
|
certs: Cert[];
|
||||||
|
}>(`/v4/now/certs?${stringify({ cn, ...(limit ? { limit } : {}) })}`);
|
||||||
|
return certs;
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@ export default class Client extends EventEmitter {
|
|||||||
_withCache: boolean;
|
_withCache: boolean;
|
||||||
_output: Output;
|
_output: Output;
|
||||||
_token: string;
|
_token: string;
|
||||||
currentTeam?: string;
|
currentTeam?: string | null;
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
apiUrl,
|
apiUrl,
|
||||||
@@ -36,7 +36,7 @@ export default class Client extends EventEmitter {
|
|||||||
}: {
|
}: {
|
||||||
apiUrl: string;
|
apiUrl: string;
|
||||||
token: string;
|
token: string;
|
||||||
currentTeam?: string;
|
currentTeam?: string | null;
|
||||||
forceNew?: boolean;
|
forceNew?: boolean;
|
||||||
withCache?: boolean;
|
withCache?: boolean;
|
||||||
debug?: boolean;
|
debug?: boolean;
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import isWildcardAlias from '../alias/is-wildcard-alias';
|
||||||
|
import { getCertsForCn } from '../certs/get-certs-for-cn';
|
||||||
|
import Client from '../client';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to find the "best" alias url.
|
||||||
|
* @param aliasList
|
||||||
|
*/
|
||||||
|
export async function getPreferredPreviewURL(
|
||||||
|
client: Client,
|
||||||
|
aliasList: string[]
|
||||||
|
) {
|
||||||
|
if (aliasList.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* First checks for non public aliases and non wildcard domains.
|
||||||
|
*/
|
||||||
|
const preferredAliases = aliasList.filter(
|
||||||
|
alias =>
|
||||||
|
!alias.endsWith('.now.sh') &&
|
||||||
|
!alias.endsWith('.vercel.app') &&
|
||||||
|
!isWildcardAlias(alias)
|
||||||
|
);
|
||||||
|
for (const alias of preferredAliases) {
|
||||||
|
const certs = await getCertsForCn(client, alias, { limit: 1 }).catch(() => {
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
if (certs && certs.length > 0) {
|
||||||
|
return { previewUrl: `https://${alias}`, isWildcard: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fallback to first alias
|
||||||
|
*/
|
||||||
|
const [firstAlias] = aliasList;
|
||||||
|
if (isWildcardAlias(firstAlias)) {
|
||||||
|
return { previewUrl: firstAlias, isWildcard: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstAlias.endsWith('.vercel.app') || firstAlias.endsWith('.now.sh')) {
|
||||||
|
return { previewUrl: `https://${firstAlias}`, isWildcard: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { previewUrl: `http://${firstAlias}`, isWildcard: false };
|
||||||
|
}
|
||||||
@@ -3,9 +3,9 @@ import semver from 'semver';
|
|||||||
import npa from 'npm-package-arg';
|
import npa from 'npm-package-arg';
|
||||||
import pluralize from 'pluralize';
|
import pluralize from 'pluralize';
|
||||||
import { basename, join } from 'path';
|
import { basename, join } from 'path';
|
||||||
import { PackageJson } from '@vercel/build-utils';
|
|
||||||
import XDGAppPaths from 'xdg-app-paths';
|
import XDGAppPaths from 'xdg-app-paths';
|
||||||
import { mkdirp, readJSON, writeJSON } from 'fs-extra';
|
import { mkdirp, readJSON, writeJSON } from 'fs-extra';
|
||||||
|
import { NowBuildError, PackageJson } from '@vercel/build-utils';
|
||||||
import cliPkg from '../pkg';
|
import cliPkg from '../pkg';
|
||||||
|
|
||||||
import { NoBuilderCacheError } from '../errors-ts';
|
import { NoBuilderCacheError } from '../errors-ts';
|
||||||
@@ -232,21 +232,41 @@ async function npmInstall(
|
|||||||
output.debug(`Running npm install in ${cwd}`);
|
output.debug(`Running npm install in ${cwd}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await execa(
|
const args = [
|
||||||
'npm',
|
'install',
|
||||||
[
|
'--save-exact',
|
||||||
'install',
|
'--no-package-lock',
|
||||||
'--save-exact',
|
'--no-audit',
|
||||||
'--no-package-lock',
|
'--no-progress',
|
||||||
'--no-audit',
|
];
|
||||||
'--no-progress',
|
if (process.stderr.isTTY) {
|
||||||
...sortedPackages,
|
// Force colors in the npm child process
|
||||||
],
|
// https://docs.npmjs.com/misc/config#color
|
||||||
{
|
args.push('--color=always');
|
||||||
cwd,
|
}
|
||||||
stdio: output.isDebugEnabled() ? 'inherit' : undefined,
|
args.push(...sortedPackages);
|
||||||
|
const result = await execa('npm', args, {
|
||||||
|
cwd,
|
||||||
|
reject: false,
|
||||||
|
stdio: output.isDebugEnabled() ? 'inherit' : 'pipe',
|
||||||
|
});
|
||||||
|
if (result.failed) {
|
||||||
|
stopSpinner();
|
||||||
|
if (result.stdout) {
|
||||||
|
console.log(result.stdout);
|
||||||
}
|
}
|
||||||
);
|
if (result.stderr) {
|
||||||
|
console.error(result.stderr);
|
||||||
|
}
|
||||||
|
throw new NowBuildError({
|
||||||
|
message:
|
||||||
|
(result as any).code === 'ENOENT'
|
||||||
|
? '`npm` is not installed'
|
||||||
|
: 'Failed to install `vercel dev` dependencies',
|
||||||
|
code: 'NPM_INSTALL_ERROR',
|
||||||
|
link: 'https://vercel.link/npm-install-failed-dev',
|
||||||
|
});
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
stopSpinner();
|
stopSpinner();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ async function createBuildProcess(
|
|||||||
workPath: string,
|
workPath: string,
|
||||||
output: Output
|
output: Output
|
||||||
): Promise<ChildProcess> {
|
): Promise<ChildProcess> {
|
||||||
|
output.debug(`Creating build process for "${match.entrypoint}"`);
|
||||||
|
|
||||||
const builderWorkerPath = join(__dirname, 'builder-worker.js');
|
const builderWorkerPath = join(__dirname, 'builder-worker.js');
|
||||||
|
|
||||||
// Ensure that `node` is in the builder's `PATH`
|
// Ensure that `node` is in the builder's `PATH`
|
||||||
@@ -65,8 +67,6 @@ async function createBuildProcess(
|
|||||||
...process.env,
|
...process.env,
|
||||||
PATH,
|
PATH,
|
||||||
...envConfigs.allEnv,
|
...envConfigs.allEnv,
|
||||||
NOW_REGION: 'dev1',
|
|
||||||
VERCEL_REGION: 'dev1',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildProcess = fork(builderWorkerPath, [], {
|
const buildProcess = fork(builderWorkerPath, [], {
|
||||||
@@ -78,7 +78,7 @@ async function createBuildProcess(
|
|||||||
|
|
||||||
buildProcess.on('exit', (code, signal) => {
|
buildProcess.on('exit', (code, signal) => {
|
||||||
output.debug(
|
output.debug(
|
||||||
`Build process for ${match.src} exited with ${signal || code}`
|
`Build process for "${match.entrypoint}" exited with ${signal || code}`
|
||||||
);
|
);
|
||||||
match.buildProcess = undefined;
|
match.buildProcess = undefined;
|
||||||
});
|
});
|
||||||
@@ -128,7 +128,6 @@ export async function executeBuild(
|
|||||||
|
|
||||||
let { buildProcess } = match;
|
let { buildProcess } = match;
|
||||||
if (!runInProcess && !buildProcess) {
|
if (!runInProcess && !buildProcess) {
|
||||||
devServer.output.debug(`Creating build process for ${entrypoint}`);
|
|
||||||
buildProcess = await createBuildProcess(
|
buildProcess = await createBuildProcess(
|
||||||
match,
|
match,
|
||||||
envConfigs,
|
envConfigs,
|
||||||
@@ -364,8 +363,6 @@ export async function executeBuild(
|
|||||||
...nowConfig.env,
|
...nowConfig.env,
|
||||||
...asset.environment,
|
...asset.environment,
|
||||||
...envConfigs.runEnv,
|
...envConfigs.runEnv,
|
||||||
NOW_REGION: 'dev1',
|
|
||||||
VERCEL_REGION: 'dev1',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -431,7 +428,7 @@ export async function getBuildMatches(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const files = fileList
|
const files = fileList
|
||||||
.filter(name => name === src || minimatch(name, src))
|
.filter(name => name === src || minimatch(name, src, { dot: true }))
|
||||||
.map(name => join(cwd, name));
|
.map(name => join(cwd, name));
|
||||||
|
|
||||||
if (files.length === 0) {
|
if (files.length === 0) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import PCRE from 'pcre-to-regexp';
|
|||||||
import isURL from './is-url';
|
import isURL from './is-url';
|
||||||
import DevServer from './server';
|
import DevServer from './server';
|
||||||
|
|
||||||
import { HttpHeadersConfig, RouteResult } from './types';
|
import { NowConfig, HttpHeadersConfig, RouteResult } from './types';
|
||||||
import { isHandler, Route, HandleValue } from '@vercel/routing-utils';
|
import { isHandler, Route, HandleValue } from '@vercel/routing-utils';
|
||||||
|
|
||||||
export function resolveRouteParameters(
|
export function resolveRouteParameters(
|
||||||
@@ -50,6 +50,7 @@ export async function devRouter(
|
|||||||
reqMethod?: string,
|
reqMethod?: string,
|
||||||
routes?: Route[],
|
routes?: Route[],
|
||||||
devServer?: DevServer,
|
devServer?: DevServer,
|
||||||
|
nowConfig?: NowConfig,
|
||||||
previousHeaders?: HttpHeadersConfig,
|
previousHeaders?: HttpHeadersConfig,
|
||||||
missRoutes?: Route[],
|
missRoutes?: Route[],
|
||||||
phase?: HandleValue | null
|
phase?: HandleValue | null
|
||||||
@@ -117,9 +118,12 @@ export async function devRouter(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (routeConfig.check && devServer && phase !== 'hit') {
|
if (routeConfig.check && devServer && nowConfig && phase !== 'hit') {
|
||||||
const { pathname = '/' } = url.parse(destPath);
|
const { pathname = '/' } = url.parse(destPath);
|
||||||
const hasDestFile = await devServer.hasFilesystem(pathname);
|
const hasDestFile = await devServer.hasFilesystem(
|
||||||
|
pathname,
|
||||||
|
nowConfig
|
||||||
|
);
|
||||||
|
|
||||||
if (!hasDestFile) {
|
if (!hasDestFile) {
|
||||||
if (routeConfig.status && phase !== 'miss') {
|
if (routeConfig.status && phase !== 'miss') {
|
||||||
@@ -131,6 +135,7 @@ export async function devRouter(
|
|||||||
reqMethod,
|
reqMethod,
|
||||||
missRoutes,
|
missRoutes,
|
||||||
devServer,
|
devServer,
|
||||||
|
nowConfig,
|
||||||
combinedHeaders,
|
combinedHeaders,
|
||||||
[],
|
[],
|
||||||
'miss'
|
'miss'
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -44,22 +44,16 @@
|
|||||||
<p>
|
<p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
If you are a visitor; contact the website owner or try again later.
|
Check the logs in your terminal window to see the application error.
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
If you are the owner; <a href="/_logs" target="_blank">check the logs</a> for the application error.
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<a target="_blank" href="https://vercel.com/docs/router-status/{{= it.http_status_code }}" class="docs-link" rel="noopener noreferrer">Developer Documentation →</a>
|
<a target="_blank" href="https://vercel.com/docs/error/application/{{= it.error_code }}" class="docs-link" rel="noopener noreferrer">Developer Documentation →</a>
|
||||||
</p>
|
</p>
|
||||||
{{??}}
|
{{??}}
|
||||||
<p>
|
<p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
If you are a visitor, please try again later.
|
Please open a <a target="_blank" href="https://github.com/vercel/vercel/issues/new/choose">GitHub issue</a> describing the problem you are experiencing with <code>vercel dev</code>.
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
If you are the owner, no action is needed. Our engineers have been notified.
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -17,19 +17,22 @@ import {
|
|||||||
import { NowConfig } from '@vercel/client';
|
import { NowConfig } from '@vercel/client';
|
||||||
import { HandleValue, Route } from '@vercel/routing-utils';
|
import { HandleValue, Route } from '@vercel/routing-utils';
|
||||||
import { Output } from '../output';
|
import { Output } from '../output';
|
||||||
|
import { ProjectSettings } from '../../types';
|
||||||
|
|
||||||
export { NowConfig };
|
export { NowConfig };
|
||||||
|
|
||||||
export interface DevServerOptions {
|
export interface DevServerOptions {
|
||||||
output: Output;
|
output: Output;
|
||||||
debug: boolean;
|
debug: boolean;
|
||||||
devCommand: string | undefined;
|
devCommand?: string;
|
||||||
frameworkSlug: string | null;
|
frameworkSlug?: string;
|
||||||
|
projectSettings?: ProjectSettings;
|
||||||
|
environmentVars?: Env;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EnvConfigs {
|
export interface EnvConfigs {
|
||||||
/**
|
/**
|
||||||
* environment variables from `.env.build` file (deprecated)
|
* environment variables from `.env.build` file
|
||||||
*/
|
*/
|
||||||
buildEnv: Env;
|
buildEnv: Env;
|
||||||
|
|
||||||
|
|||||||
@@ -8,71 +8,52 @@ import {
|
|||||||
trailingSlashSchema,
|
trailingSlashSchema,
|
||||||
} from '@vercel/routing-utils';
|
} from '@vercel/routing-utils';
|
||||||
import { NowConfig } from './types';
|
import { NowConfig } from './types';
|
||||||
import { functionsSchema, buildsSchema } from '@vercel/build-utils';
|
import {
|
||||||
|
functionsSchema,
|
||||||
|
buildsSchema,
|
||||||
|
NowBuildError,
|
||||||
|
getPrettyError,
|
||||||
|
} from '@vercel/build-utils';
|
||||||
|
import { fileNameSymbol } from '@vercel/client';
|
||||||
|
|
||||||
|
const vercelConfigSchema = {
|
||||||
|
type: 'object',
|
||||||
|
// These are not all possibilities because `vc dev`
|
||||||
|
// doesn't need to know about `regions`, `public`, etc.
|
||||||
|
additionalProperties: true,
|
||||||
|
properties: {
|
||||||
|
builds: buildsSchema,
|
||||||
|
routes: routesSchema,
|
||||||
|
cleanUrls: cleanUrlsSchema,
|
||||||
|
headers: headersSchema,
|
||||||
|
redirects: redirectsSchema,
|
||||||
|
rewrites: rewritesSchema,
|
||||||
|
trailingSlash: trailingSlashSchema,
|
||||||
|
functions: functionsSchema,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const ajv = new Ajv();
|
const ajv = new Ajv();
|
||||||
|
const validate = ajv.compile(vercelConfigSchema);
|
||||||
|
|
||||||
const validateBuilds = ajv.compile(buildsSchema);
|
export function validateConfig(config: NowConfig): NowBuildError | null {
|
||||||
const validateRoutes = ajv.compile(routesSchema);
|
if (!validate(config)) {
|
||||||
const validateCleanUrls = ajv.compile(cleanUrlsSchema);
|
if (validate.errors && validate.errors[0]) {
|
||||||
const validateHeaders = ajv.compile(headersSchema);
|
const error = validate.errors[0];
|
||||||
const validateRedirects = ajv.compile(redirectsSchema);
|
const fileName = config[fileNameSymbol] || 'vercel.json';
|
||||||
const validateRewrites = ajv.compile(rewritesSchema);
|
const niceError = getPrettyError(error);
|
||||||
const validateTrailingSlash = ajv.compile(trailingSlashSchema);
|
niceError.message = `Invalid ${fileName} - ${niceError.message}`;
|
||||||
const validateFunctions = ajv.compile(functionsSchema);
|
return niceError;
|
||||||
|
}
|
||||||
export function validateNowConfigBuilds(config: NowConfig) {
|
|
||||||
return validateKey(config, 'builds', validateBuilds);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function validateNowConfigRoutes(config: NowConfig) {
|
|
||||||
return validateKey(config, 'routes', validateRoutes);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function validateNowConfigCleanUrls(config: NowConfig) {
|
|
||||||
return validateKey(config, 'cleanUrls', validateCleanUrls);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function validateNowConfigHeaders(config: NowConfig) {
|
|
||||||
return validateKey(config, 'headers', validateHeaders);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function validateNowConfigRedirects(config: NowConfig) {
|
|
||||||
return validateKey(config, 'redirects', validateRedirects);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function validateNowConfigRewrites(config: NowConfig) {
|
|
||||||
return validateKey(config, 'rewrites', validateRewrites);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function validateNowConfigTrailingSlash(config: NowConfig) {
|
|
||||||
return validateKey(config, 'trailingSlash', validateTrailingSlash);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function validateNowConfigFunctions(config: NowConfig) {
|
|
||||||
return validateKey(config, 'functions', validateFunctions);
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateKey(
|
|
||||||
config: NowConfig,
|
|
||||||
key: keyof NowConfig,
|
|
||||||
validate: Ajv.ValidateFunction
|
|
||||||
) {
|
|
||||||
const value = config[key];
|
|
||||||
if (!value) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validate(value)) {
|
if (config.functions && config.builds) {
|
||||||
if (!validate.errors) {
|
return new NowBuildError({
|
||||||
return null;
|
code: 'FUNCTIONS_AND_BUILDS',
|
||||||
}
|
message:
|
||||||
|
'The `functions` property cannot be used in conjunction with the `builds` property. Please remove one of them.',
|
||||||
const error = validate.errors[0];
|
link: 'https://vercel.link/functions-and-builds',
|
||||||
|
});
|
||||||
return `Invalid \`${String(key)}\` property: ${error.dataPath} ${
|
|
||||||
error.message
|
|
||||||
}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
29
packages/now-cli/src/util/domains/get-domain-config.ts
Normal file
29
packages/now-cli/src/util/domains/get-domain-config.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import chalk from 'chalk';
|
||||||
|
import Client from '../client';
|
||||||
|
import wait from '../output/wait';
|
||||||
|
import { DomainConfig } from '../../types';
|
||||||
|
|
||||||
|
export async function getDomainConfig(
|
||||||
|
client: Client,
|
||||||
|
contextName: string,
|
||||||
|
domainName: string
|
||||||
|
) {
|
||||||
|
const cancelWait = wait(
|
||||||
|
`Fetching domain config ${domainName} under ${chalk.bold(contextName)}`
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
const config = await client.fetch<DomainConfig>(
|
||||||
|
`/v4/domains/${domainName}/config`
|
||||||
|
);
|
||||||
|
|
||||||
|
return config;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.status < 500) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
cancelWait();
|
||||||
|
}
|
||||||
|
}
|
||||||
33
packages/now-cli/src/util/domains/get-domain.ts
Normal file
33
packages/now-cli/src/util/domains/get-domain.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import chalk from 'chalk';
|
||||||
|
import Client from '../client';
|
||||||
|
import wait from '../output/wait';
|
||||||
|
import { Domain } from '../../types';
|
||||||
|
|
||||||
|
type Response = {
|
||||||
|
domain: Domain;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function getDomain(
|
||||||
|
client: Client,
|
||||||
|
contextName: string,
|
||||||
|
domainName: string
|
||||||
|
) {
|
||||||
|
const cancelWait = wait(
|
||||||
|
`Fetching domain ${domainName} under ${chalk.bold(contextName)}`
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
const { domain } = await client.fetch<Response>(
|
||||||
|
`/v4/domains/${domainName}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return domain;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.status < 500) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
cancelWait();
|
||||||
|
}
|
||||||
|
}
|
||||||
3
packages/now-cli/src/util/domains/is-public-suffix.ts
Normal file
3
packages/now-cli/src/util/domains/is-public-suffix.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export function isPublicSuffix(domainName: string) {
|
||||||
|
return domainName.endsWith('.vercel.app') || domainName.endsWith('.now.sh');
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import bytes from 'bytes';
|
import bytes from 'bytes';
|
||||||
import { Response } from 'node-fetch';
|
import { Response } from 'node-fetch';
|
||||||
|
import { NowBuildError } from '@vercel/build-utils';
|
||||||
import { NowError } from './now-error';
|
import { NowError } from './now-error';
|
||||||
import code from './output/code';
|
import code from './output/code';
|
||||||
import { getCommandName } from './pkg-name';
|
import { getCommandName } from './pkg-name';
|
||||||
@@ -13,6 +14,7 @@ export class APIError extends Error {
|
|||||||
status: number;
|
status: number;
|
||||||
serverMessage: string;
|
serverMessage: string;
|
||||||
link?: string;
|
link?: string;
|
||||||
|
action?: string;
|
||||||
retryAfter: number | null | 'never';
|
retryAfter: number | null | 'never';
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
|
|
||||||
@@ -771,17 +773,17 @@ export class CantParseJSONFile extends NowError<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ConflictingConfigFiles extends NowError<
|
export class ConflictingConfigFiles extends NowBuildError {
|
||||||
'CONFLICTING_CONFIG_FILES',
|
files: string[];
|
||||||
{ files: string[] }
|
|
||||||
> {
|
|
||||||
constructor(files: string[]) {
|
constructor(files: string[]) {
|
||||||
super({
|
super({
|
||||||
code: 'CONFLICTING_CONFIG_FILES',
|
code: 'CONFLICTING_CONFIG_FILES',
|
||||||
meta: { files },
|
|
||||||
message:
|
message:
|
||||||
'Cannot use both a `vercel.json` and `now.json` file. Please delete the `now.json` file.',
|
'Cannot use both a `vercel.json` and `now.json` file. Please delete the `now.json` file.',
|
||||||
|
link: 'https://vercel.link/combining-old-and-new-config',
|
||||||
});
|
});
|
||||||
|
this.files = files;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,3 +18,18 @@ export default function formatDate(dateStrOrNumber?: number | string | null) {
|
|||||||
`[in ${ms(diff)}]`
|
`[in ${ms(diff)}]`
|
||||||
)}`;
|
)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function formatDateWithoutTime(
|
||||||
|
dateStrOrNumber?: number | string | null
|
||||||
|
) {
|
||||||
|
if (!dateStrOrNumber) {
|
||||||
|
return chalk.gray('-');
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = new Date(dateStrOrNumber);
|
||||||
|
const diff = date.getTime() - Date.now();
|
||||||
|
|
||||||
|
return diff < 0
|
||||||
|
? `${format(date, 'MMM DD YYYY')} ${chalk.gray(`[${ms(-diff)} ago]`)}`
|
||||||
|
: `${format(date, 'MMM DD YYYY')} ${chalk.gray(`[in ${ms(diff)}]`)}`;
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import strlen from './strlen';
|
|||||||
export default function formatTable(
|
export default function formatTable(
|
||||||
header: string[],
|
header: string[],
|
||||||
align: Array<'l' | 'r' | 'c' | '.'>,
|
align: Array<'l' | 'r' | 'c' | '.'>,
|
||||||
blocks: { name: string; rows: string[][] }[],
|
blocks: { name?: string; rows: string[][] }[],
|
||||||
hsep = ' '
|
hsep = ' '
|
||||||
) {
|
) {
|
||||||
const nrCols = header.length;
|
const nrCols = header.length;
|
||||||
@@ -50,8 +50,10 @@ export default function formatTable(
|
|||||||
for (let j = 0; j < nrCols; j++) {
|
for (let j = 0; j < nrCols; j++) {
|
||||||
const col = `${row[j]}`;
|
const col = `${row[j]}`;
|
||||||
const al = align[j] || 'l';
|
const al = align[j] || 'l';
|
||||||
const spaces = Math.max(padding[j] * 8 - strlen(col), 0);
|
|
||||||
const pad = ' '.repeat(spaces);
|
const repeat = padding[j] > 1 ? padding[j] * 8 - strlen(col) : 0;
|
||||||
|
const pad = repeat > 0 ? ' '.repeat(repeat) : '';
|
||||||
|
|
||||||
rows[i][j] = al === 'l' ? col + pad : pad + col;
|
rows[i][j] = al === 'l' ? col + pad : pad + col;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
46
packages/now-cli/src/util/get-decrypted-env-records.ts
Normal file
46
packages/now-cli/src/util/get-decrypted-env-records.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import getEnvVariables from './env/get-env-records';
|
||||||
|
import getDecryptedSecret from './env/get-decrypted-secret';
|
||||||
|
import Client from './client';
|
||||||
|
import { Output } from './output/create-output';
|
||||||
|
import { ProjectEnvTarget, Project } from '../types';
|
||||||
|
|
||||||
|
import { Env } from '@vercel/build-utils';
|
||||||
|
|
||||||
|
export default async function getDecryptedEnvRecords(
|
||||||
|
output: Output,
|
||||||
|
client: Client,
|
||||||
|
project: Project,
|
||||||
|
target: ProjectEnvTarget
|
||||||
|
): Promise<Env> {
|
||||||
|
const envs = await getEnvVariables(output, client, project.id, 4, target);
|
||||||
|
const decryptedValues = await Promise.all(
|
||||||
|
envs.map(async env => {
|
||||||
|
try {
|
||||||
|
const value = await getDecryptedSecret(output, client, env.value);
|
||||||
|
return { value, found: true };
|
||||||
|
} catch (error) {
|
||||||
|
if (error && error.status === 404) {
|
||||||
|
return { value: '', found: false };
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const results: Env = {};
|
||||||
|
for (let i = 0; i < decryptedValues.length; i++) {
|
||||||
|
const { key } = envs[i];
|
||||||
|
const { value, found } = decryptedValues[i];
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
output.print('');
|
||||||
|
output.warn(
|
||||||
|
`Unable to download variable ${key} because associated secret was deleted`
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
results[key] = value ? value : '';
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { resolve, join } from 'path';
|
import { resolve } from 'path';
|
||||||
import ignore from 'ignore';
|
import ignore from 'ignore';
|
||||||
import dockerignore from '@zeit/dockerignore';
|
import dockerignore from '@zeit/dockerignore';
|
||||||
import _glob, { IOptions } from 'glob';
|
import _glob, { IOptions as GlobOptions } from 'glob';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import { getVercelIgnore } from '@vercel/client';
|
import { getVercelIgnore } from '@vercel/client';
|
||||||
import IGNORED from './ignored';
|
import IGNORED from './ignored';
|
||||||
@@ -12,11 +12,11 @@ import { NowConfig } from './dev/types';
|
|||||||
|
|
||||||
type NullableString = string | null;
|
type NullableString = string | null;
|
||||||
|
|
||||||
const flatten = (
|
function flatten(
|
||||||
arr: NullableString[] | NullableString[][],
|
arr: NullableString[] | NullableString[][],
|
||||||
res: NullableString[] = []
|
res: NullableString[] = []
|
||||||
) => {
|
): NullableString[] {
|
||||||
for (let cur of arr) {
|
for (const cur of arr) {
|
||||||
if (Array.isArray(cur)) {
|
if (Array.isArray(cur)) {
|
||||||
flatten(cur, res);
|
flatten(cur, res);
|
||||||
} else {
|
} else {
|
||||||
@@ -24,21 +24,17 @@ const flatten = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
};
|
}
|
||||||
|
|
||||||
const glob = async function(pattern: string, options: IOptions) {
|
async function glob(pattern: string, options: GlobOptions): Promise<string[]> {
|
||||||
return new Promise<string[]>((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
_glob(pattern, options, (error, files) => {
|
_glob(pattern, options, (err, files) => {
|
||||||
if (error) {
|
err ? reject(err) : resolve(files);
|
||||||
reject(error);
|
|
||||||
} else {
|
|
||||||
resolve(files);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
interface WalkSyncOptions {
|
interface WalkOptions {
|
||||||
output: Output;
|
output: Output;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,27 +47,27 @@ interface WalkSyncOptions {
|
|||||||
* - `output` {Object} "output" helper object
|
* - `output` {Object} "output" helper object
|
||||||
* @returns {Array}
|
* @returns {Array}
|
||||||
*/
|
*/
|
||||||
const walkSync = async (
|
async function walk(
|
||||||
dir: string,
|
dir: string,
|
||||||
path: string,
|
path: string,
|
||||||
filelist: string[] = [],
|
filelist: string[] = [],
|
||||||
opts: WalkSyncOptions
|
opts: WalkOptions
|
||||||
) => {
|
) {
|
||||||
const { debug } = opts.output;
|
const { debug } = opts.output;
|
||||||
const dirc = await fs.readdir(asAbsolute(dir, path));
|
const dirc = await fs.readdir(asAbsolute(dir, path));
|
||||||
for (let file of dirc) {
|
for (let file of dirc) {
|
||||||
file = asAbsolute(file, dir);
|
file = asAbsolute(file, dir);
|
||||||
try {
|
try {
|
||||||
const file_stat = await fs.stat(file);
|
const fileStat = await fs.stat(file);
|
||||||
filelist = file_stat.isDirectory()
|
filelist = fileStat.isDirectory()
|
||||||
? await walkSync(file, path, filelist, opts)
|
? await walk(file, path, filelist, opts)
|
||||||
: filelist.concat(file);
|
: filelist.concat(file);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debug(`Ignoring invalid file ${file}`);
|
debug(`Ignoring invalid file ${file}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return filelist;
|
return filelist;
|
||||||
};
|
}
|
||||||
|
|
||||||
interface FilesInWhitelistOptions {
|
interface FilesInWhitelistOptions {
|
||||||
output: Output;
|
output: Output;
|
||||||
@@ -85,7 +81,7 @@ interface FilesInWhitelistOptions {
|
|||||||
* - `output` {Object} "output" helper object
|
* - `output` {Object} "output" helper object
|
||||||
* @returns {Array} the expanded list of whitelisted files.
|
* @returns {Array} the expanded list of whitelisted files.
|
||||||
*/
|
*/
|
||||||
const getFilesInWhitelist = async function(
|
const getFilesInWhitelist = async function (
|
||||||
whitelist: string[],
|
whitelist: string[],
|
||||||
path: string,
|
path: string,
|
||||||
opts: FilesInWhitelistOptions
|
opts: FilesInWhitelistOptions
|
||||||
@@ -97,10 +93,10 @@ const getFilesInWhitelist = async function(
|
|||||||
whitelist.map(async (file: string) => {
|
whitelist.map(async (file: string) => {
|
||||||
file = asAbsolute(file, path);
|
file = asAbsolute(file, path);
|
||||||
try {
|
try {
|
||||||
const file_stat = await fs.stat(file);
|
const fileStat = await fs.stat(file);
|
||||||
if (file_stat.isDirectory()) {
|
if (fileStat.isDirectory()) {
|
||||||
const dir_files = await walkSync(file, path, [], opts);
|
const dirFiles = await walk(file, path, [], opts);
|
||||||
files.push(...dir_files);
|
files.push(...dirFiles);
|
||||||
} else {
|
} else {
|
||||||
files.push(file);
|
files.push(file);
|
||||||
}
|
}
|
||||||
@@ -117,7 +113,7 @@ const getFilesInWhitelist = async function(
|
|||||||
* because ignore doesn't like them :|
|
* because ignore doesn't like them :|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const clearRelative = function(str: string) {
|
const clearRelative = function (str: string) {
|
||||||
return str.replace(/(\n|^)\.\//g, '$1');
|
return str.replace(/(\n|^)\.\//g, '$1');
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -127,7 +123,7 @@ const clearRelative = function(str: string) {
|
|||||||
* @return {String} results or `''`
|
* @return {String} results or `''`
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const maybeRead = async function<T>(path: string, default_: T) {
|
const maybeRead = async function <T>(path: string, default_: T) {
|
||||||
try {
|
try {
|
||||||
return await fs.readFile(path, 'utf8');
|
return await fs.readFile(path, 'utf8');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -143,7 +139,7 @@ const maybeRead = async function<T>(path: string, default_: T) {
|
|||||||
* @param {String} parent full path
|
* @param {String} parent full path
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const asAbsolute = function(path: string, parent: string) {
|
const asAbsolute = function (path: string, parent: string) {
|
||||||
if (path[0] === '/') {
|
if (path[0] === '/') {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
@@ -272,7 +268,7 @@ export async function npm(
|
|||||||
const search = Array.prototype.concat.apply(
|
const search = Array.prototype.concat.apply(
|
||||||
[],
|
[],
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
search_.map(file =>
|
search_.map((file) =>
|
||||||
glob(file, { cwd: path, absolute: true, dot: true })
|
glob(file, { cwd: path, absolute: true, dot: true })
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -364,7 +360,7 @@ export async function docker(
|
|||||||
const search_ = ['.'];
|
const search_ = ['.'];
|
||||||
|
|
||||||
// Convert all filenames into absolute paths
|
// Convert all filenames into absolute paths
|
||||||
const search = search_.map(file => asAbsolute(file, path));
|
const search = search_.map((file) => asAbsolute(file, path));
|
||||||
|
|
||||||
// Compile list of ignored patterns and files
|
// Compile list of ignored patterns and files
|
||||||
const dockerIgnore = await maybeRead(resolve(path, '.dockerignore'), null);
|
const dockerIgnore = await maybeRead(resolve(path, '.dockerignore'), null);
|
||||||
@@ -382,7 +378,7 @@ export async function docker(
|
|||||||
.createFilter();
|
.createFilter();
|
||||||
|
|
||||||
const prefixLength = path.length + 1;
|
const prefixLength = path.length + 1;
|
||||||
const accepts = function(file: string) {
|
const accepts = function (file: string) {
|
||||||
const relativePath = file.substr(prefixLength);
|
const relativePath = file.substr(prefixLength);
|
||||||
|
|
||||||
if (relativePath === '') {
|
if (relativePath === '') {
|
||||||
@@ -415,24 +411,6 @@ export async function docker(
|
|||||||
return uniqueStrings(files);
|
return uniqueStrings(files);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a list of all files inside the project folder
|
|
||||||
*
|
|
||||||
* @param {String} of the current working directory
|
|
||||||
* @param {Object} output instance
|
|
||||||
* @return {Array} of {String}s with the found files
|
|
||||||
*/
|
|
||||||
export async function getAllProjectFiles(cwd: string, { debug }: Output) {
|
|
||||||
// We need a slash at the end to remove it later on from the matched files
|
|
||||||
const current = join(resolve(cwd), '/');
|
|
||||||
debug(`Searching files inside of ${current}`);
|
|
||||||
|
|
||||||
const list = await glob('**', { cwd: current, absolute: true, nodir: true });
|
|
||||||
|
|
||||||
// We need to replace \ with / for windows
|
|
||||||
return list.map(file => file.replace(current.replace(/\\/g, '/'), ''));
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ExplodeOptions {
|
interface ExplodeOptions {
|
||||||
accepts: (file: string) => boolean;
|
accepts: (file: string) => boolean;
|
||||||
output: Output;
|
output: Output;
|
||||||
@@ -482,7 +460,7 @@ async function explode(
|
|||||||
if (s.isDirectory()) {
|
if (s.isDirectory()) {
|
||||||
const all = await fs.readdir(file);
|
const all = await fs.readdir(file);
|
||||||
/* eslint-disable no-use-before-define */
|
/* eslint-disable no-use-before-define */
|
||||||
const recursive = many(all.map(subdir => asAbsolute(subdir, file)));
|
const recursive = many(all.map((subdir) => asAbsolute(subdir, file)));
|
||||||
return (recursive as any) as Promise<string | null>;
|
return (recursive as any) as Promise<string | null>;
|
||||||
/* eslint-enable no-use-before-define */
|
/* eslint-enable no-use-before-define */
|
||||||
}
|
}
|
||||||
@@ -494,7 +472,7 @@ async function explode(
|
|||||||
return path;
|
return path;
|
||||||
};
|
};
|
||||||
|
|
||||||
const many = (all: string[]) => Promise.all(all.map(file => list(file)));
|
const many = (all: string[]) => Promise.all(all.map((file) => list(file)));
|
||||||
const arrayOfArrays = await many(paths);
|
const arrayOfArrays = await many(paths);
|
||||||
return flatten(arrayOfArrays).filter(notNull);
|
return flatten(arrayOfArrays).filter(notNull);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,6 @@ export default async function getTeamById(
|
|||||||
team = await client.fetch<Team>(`/teams/${teamId}`);
|
team = await client.fetch<Team>(`/teams/${teamId}`);
|
||||||
teamCache.set(teamId, team);
|
teamCache.set(teamId, team);
|
||||||
}
|
}
|
||||||
|
|
||||||
return team;
|
return team;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ export default class Now extends EventEmitter {
|
|||||||
regions,
|
regions,
|
||||||
target: target || undefined,
|
target: target || undefined,
|
||||||
projectSettings,
|
projectSettings,
|
||||||
|
source: 'cli',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ignore specific items from vercel.json
|
// Ignore specific items from vercel.json
|
||||||
|
|||||||
@@ -4,18 +4,15 @@ import chalk from 'chalk';
|
|||||||
import { Output } from '../output';
|
import { Output } from '../output';
|
||||||
import { Framework } from '@vercel/frameworks';
|
import { Framework } from '@vercel/frameworks';
|
||||||
import { isSettingValue } from '../is-setting-value';
|
import { isSettingValue } from '../is-setting-value';
|
||||||
|
import { ProjectSettings } from '../../types';
|
||||||
|
|
||||||
export interface ProjectSettings {
|
export interface PartialProjectSettings {
|
||||||
buildCommand: string | null;
|
buildCommand: string | null;
|
||||||
outputDirectory: string | null;
|
outputDirectory: string | null;
|
||||||
devCommand: string | null;
|
devCommand: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProjectSettingsWithFramework extends ProjectSettings {
|
const fields: { name: string; value: keyof PartialProjectSettings }[] = [
|
||||||
framework: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fields: { name: string; value: keyof ProjectSettings }[] = [
|
|
||||||
{ name: 'Build Command', value: 'buildCommand' },
|
{ name: 'Build Command', value: 'buildCommand' },
|
||||||
{ name: 'Output Directory', value: 'outputDirectory' },
|
{ name: 'Output Directory', value: 'outputDirectory' },
|
||||||
{ name: 'Development Command', value: 'devCommand' },
|
{ name: 'Development Command', value: 'devCommand' },
|
||||||
@@ -23,13 +20,15 @@ const fields: { name: string; value: keyof ProjectSettings }[] = [
|
|||||||
|
|
||||||
export default async function editProjectSettings(
|
export default async function editProjectSettings(
|
||||||
output: Output,
|
output: Output,
|
||||||
projectSettings: ProjectSettings | null,
|
projectSettings: PartialProjectSettings | null,
|
||||||
framework: Framework | null
|
framework: Framework | null,
|
||||||
) {
|
autoConfirm: boolean
|
||||||
|
): Promise<ProjectSettings> {
|
||||||
// create new settings object, missing values will be filled with `null`
|
// create new settings object, missing values will be filled with `null`
|
||||||
const settings: Partial<ProjectSettingsWithFramework> = {
|
const settings: ProjectSettings = Object.assign(
|
||||||
...projectSettings,
|
{ framework: null },
|
||||||
};
|
projectSettings
|
||||||
|
);
|
||||||
|
|
||||||
for (let field of fields) {
|
for (let field of fields) {
|
||||||
settings[field.value] =
|
settings[field.value] =
|
||||||
@@ -44,8 +43,8 @@ export default async function editProjectSettings(
|
|||||||
|
|
||||||
output.print(
|
output.print(
|
||||||
!framework.slug
|
!framework.slug
|
||||||
? `No framework detected. Default project settings:\n`
|
? `No framework detected. Default Project Settings:\n`
|
||||||
: `Auto-detected project settings (${chalk.bold(framework.name)}):\n`
|
: `Auto-detected Project Settings (${chalk.bold(framework.name)}):\n`
|
||||||
);
|
);
|
||||||
|
|
||||||
settings.framework = framework.slug;
|
settings.framework = framework.slug;
|
||||||
@@ -64,7 +63,10 @@ export default async function editProjectSettings(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(await confirm(`Want to override the settings?`, false))) {
|
if (
|
||||||
|
autoConfirm ||
|
||||||
|
!(await confirm(`Want to override the settings?`, false))
|
||||||
|
) {
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +77,7 @@ export default async function editProjectSettings(
|
|||||||
choices: fields,
|
choices: fields,
|
||||||
});
|
});
|
||||||
|
|
||||||
for (let setting of settingFields as (keyof ProjectSettings)[]) {
|
for (let setting of settingFields as (keyof PartialProjectSettings)[]) {
|
||||||
const field = fields.find(f => f.value === setting);
|
const field = fields.find(f => f.value === setting);
|
||||||
const name = `${Date.now()}`;
|
const name = `${Date.now()}`;
|
||||||
const answers = await inquirer.prompt({
|
const answers = await inquirer.prompt({
|
||||||
|
|||||||
@@ -15,10 +15,6 @@ export default async function inputProject(
|
|||||||
detectedProjectName: string,
|
detectedProjectName: string,
|
||||||
autoConfirm: boolean
|
autoConfirm: boolean
|
||||||
): Promise<Project | string> {
|
): Promise<Project | string> {
|
||||||
if (autoConfirm) {
|
|
||||||
return detectedProjectName;
|
|
||||||
}
|
|
||||||
|
|
||||||
const slugifiedName = slugify(detectedProjectName);
|
const slugifiedName = slugify(detectedProjectName);
|
||||||
|
|
||||||
// attempt to auto-detect a project to link
|
// attempt to auto-detect a project to link
|
||||||
@@ -42,6 +38,10 @@ export default async function inputProject(
|
|||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
existingProjectSpinner();
|
existingProjectSpinner();
|
||||||
|
|
||||||
|
if (autoConfirm) {
|
||||||
|
return detectedProject || detectedProjectName;
|
||||||
|
}
|
||||||
|
|
||||||
let shouldLinkProject;
|
let shouldLinkProject;
|
||||||
|
|
||||||
if (!detectedProject) {
|
if (!detectedProject) {
|
||||||
|
|||||||
229
packages/now-cli/src/util/link/setup-and-link.ts
Normal file
229
packages/now-cli/src/util/link/setup-and-link.ts
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
import { join, basename } from 'path';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import { remove } from 'fs-extra';
|
||||||
|
import { NowContext, ProjectLinkResult } from '../../types';
|
||||||
|
import { NowConfig } from '../dev/types';
|
||||||
|
import { Output } from '../output';
|
||||||
|
import {
|
||||||
|
getLinkedProject,
|
||||||
|
linkFolderToProject,
|
||||||
|
getVercelDirectory,
|
||||||
|
} from '../projects/link';
|
||||||
|
import createProject from '../projects/create-project';
|
||||||
|
import updateProject from '../projects/update-project';
|
||||||
|
import Client from '../client';
|
||||||
|
import handleError from '../handle-error';
|
||||||
|
import confirm from '../input/confirm';
|
||||||
|
import toHumanPath from '../humanize-path';
|
||||||
|
import { isDirectory } from '../config/global-path';
|
||||||
|
import selectOrg from '../input/select-org';
|
||||||
|
import inputProject from '../input/input-project';
|
||||||
|
import { validateRootDirectory } from '../validate-paths';
|
||||||
|
import { inputRootDirectory } from '../input/input-root-directory';
|
||||||
|
import editProjectSettings from '../input/edit-project-settings';
|
||||||
|
import stamp from '../output/stamp';
|
||||||
|
import { EmojiLabel } from '../emoji';
|
||||||
|
//@ts-expect-error
|
||||||
|
import createDeploy from '../deploy/create-deploy';
|
||||||
|
//@ts-expect-error
|
||||||
|
import Now from '../index';
|
||||||
|
|
||||||
|
export default async function setupAndLink(
|
||||||
|
ctx: NowContext,
|
||||||
|
output: Output,
|
||||||
|
path: string,
|
||||||
|
forceDelete: boolean,
|
||||||
|
autoConfirm: boolean,
|
||||||
|
successEmoji: EmojiLabel,
|
||||||
|
setupMsg: string
|
||||||
|
): Promise<ProjectLinkResult> {
|
||||||
|
const {
|
||||||
|
authConfig: { token },
|
||||||
|
config,
|
||||||
|
} = ctx;
|
||||||
|
const { apiUrl } = ctx;
|
||||||
|
const debug = output.isDebugEnabled();
|
||||||
|
const client = new Client({
|
||||||
|
apiUrl,
|
||||||
|
token,
|
||||||
|
currentTeam: config.currentTeam,
|
||||||
|
debug,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isFile = !isDirectory(path);
|
||||||
|
if (isFile) {
|
||||||
|
output.error(`Expected directory but found file: ${path}`);
|
||||||
|
return { status: 'error', exitCode: 1 };
|
||||||
|
}
|
||||||
|
const link = await getLinkedProject(output, client, path);
|
||||||
|
const isTTY = process.stdout.isTTY;
|
||||||
|
const quiet = !isTTY;
|
||||||
|
let rootDirectory: string | null = null;
|
||||||
|
let newProjectName: string;
|
||||||
|
let org;
|
||||||
|
|
||||||
|
if (!forceDelete && link.status === 'linked') {
|
||||||
|
return link;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (forceDelete) {
|
||||||
|
const vercelDir = getVercelDirectory(path);
|
||||||
|
remove(vercelDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
const shouldStartSetup =
|
||||||
|
autoConfirm ||
|
||||||
|
(await confirm(
|
||||||
|
`${setupMsg} ${chalk.cyan(`“${toHumanPath(path)}”`)}?`,
|
||||||
|
true
|
||||||
|
));
|
||||||
|
|
||||||
|
if (!shouldStartSetup) {
|
||||||
|
output.print(`Aborted. Project not set up.\n`);
|
||||||
|
return { status: 'not_linked', org: null, project: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
org = await selectOrg(
|
||||||
|
output,
|
||||||
|
'Which scope should contain your project?',
|
||||||
|
client,
|
||||||
|
config.currentTeam,
|
||||||
|
autoConfirm
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
|
||||||
|
output.prettyError(err);
|
||||||
|
return { status: 'error', exitCode: 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
const detectedProjectName = basename(path);
|
||||||
|
|
||||||
|
const projectOrNewProjectName = await inputProject(
|
||||||
|
output,
|
||||||
|
client,
|
||||||
|
org,
|
||||||
|
detectedProjectName,
|
||||||
|
autoConfirm
|
||||||
|
);
|
||||||
|
|
||||||
|
if (typeof projectOrNewProjectName === 'string') {
|
||||||
|
newProjectName = projectOrNewProjectName;
|
||||||
|
rootDirectory = await inputRootDirectory(path, output, autoConfirm);
|
||||||
|
} else {
|
||||||
|
const project = projectOrNewProjectName;
|
||||||
|
|
||||||
|
await linkFolderToProject(
|
||||||
|
output,
|
||||||
|
path,
|
||||||
|
{
|
||||||
|
projectId: project.id,
|
||||||
|
orgId: org.id,
|
||||||
|
},
|
||||||
|
project.name,
|
||||||
|
org.slug,
|
||||||
|
successEmoji
|
||||||
|
);
|
||||||
|
return { status: 'linked', org, project };
|
||||||
|
}
|
||||||
|
const sourcePath = rootDirectory ? join(path, rootDirectory) : path;
|
||||||
|
|
||||||
|
if (
|
||||||
|
rootDirectory &&
|
||||||
|
!(await validateRootDirectory(output, path, sourcePath, ''))
|
||||||
|
) {
|
||||||
|
return { status: 'error', exitCode: 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
let localConfig: NowConfig = {};
|
||||||
|
if (ctx.localConfig && !(ctx.localConfig instanceof Error)) {
|
||||||
|
localConfig = ctx.localConfig;
|
||||||
|
}
|
||||||
|
client.currentTeam = org.type === 'team' ? org.id : undefined;
|
||||||
|
const now = new Now({
|
||||||
|
apiUrl,
|
||||||
|
token,
|
||||||
|
debug,
|
||||||
|
currentTeam: client.currentTeam,
|
||||||
|
});
|
||||||
|
let deployment = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const createArgs: any = {
|
||||||
|
name: newProjectName,
|
||||||
|
env: {},
|
||||||
|
build: { env: {} },
|
||||||
|
forceNew: undefined,
|
||||||
|
withCache: undefined,
|
||||||
|
quiet,
|
||||||
|
wantsPublic: localConfig.public,
|
||||||
|
isFile,
|
||||||
|
type: null,
|
||||||
|
nowConfig: localConfig,
|
||||||
|
regions: undefined,
|
||||||
|
meta: {},
|
||||||
|
deployStamp: stamp(),
|
||||||
|
target: undefined,
|
||||||
|
skipAutoDetectionConfirmation: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
deployment = await createDeploy(
|
||||||
|
output,
|
||||||
|
now,
|
||||||
|
client.currentTeam || 'current user',
|
||||||
|
[sourcePath],
|
||||||
|
createArgs,
|
||||||
|
org,
|
||||||
|
!isFile,
|
||||||
|
path
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!deployment ||
|
||||||
|
!('code' in deployment) ||
|
||||||
|
deployment.code !== 'missing_project_settings'
|
||||||
|
) {
|
||||||
|
output.error('Failed to detect project settings. Please try again.');
|
||||||
|
if (output.isDebugEnabled()) {
|
||||||
|
console.log(deployment);
|
||||||
|
}
|
||||||
|
return { status: 'error', exitCode: 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const { projectSettings, framework } = deployment;
|
||||||
|
|
||||||
|
if (rootDirectory) {
|
||||||
|
projectSettings.rootDirectory = rootDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings = await editProjectSettings(
|
||||||
|
output,
|
||||||
|
projectSettings,
|
||||||
|
framework,
|
||||||
|
autoConfirm
|
||||||
|
);
|
||||||
|
const project = await createProject(client, newProjectName);
|
||||||
|
await updateProject(client, project.id, settings);
|
||||||
|
Object.assign(project, settings);
|
||||||
|
|
||||||
|
await linkFolderToProject(
|
||||||
|
output,
|
||||||
|
path,
|
||||||
|
{
|
||||||
|
projectId: project.id,
|
||||||
|
orgId: org.id,
|
||||||
|
},
|
||||||
|
project.name,
|
||||||
|
org.slug,
|
||||||
|
successEmoji
|
||||||
|
);
|
||||||
|
|
||||||
|
return { status: 'linked', org, project };
|
||||||
|
} catch (err) {
|
||||||
|
handleError(err);
|
||||||
|
return { status: 'error', exitCode: 1 };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import chalk from 'chalk';
|
|||||||
import boxen from 'boxen';
|
import boxen from 'boxen';
|
||||||
import { format } from 'util';
|
import { format } from 'util';
|
||||||
import { Console } from 'console';
|
import { Console } from 'console';
|
||||||
|
import renderLink from './link';
|
||||||
import wait from './wait';
|
import wait from './wait';
|
||||||
|
|
||||||
export type Output = ReturnType<typeof createOutput>;
|
export type Output = ReturnType<typeof createOutput>;
|
||||||
@@ -26,7 +27,8 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
|
|||||||
function warn(
|
function warn(
|
||||||
str: string,
|
str: string,
|
||||||
slug: string | null = null,
|
slug: string | null = null,
|
||||||
link: string | null = null
|
link: string | null = null,
|
||||||
|
action: string = 'Learn More'
|
||||||
) {
|
) {
|
||||||
const prevTerm = process.env.TERM;
|
const prevTerm = process.env.TERM;
|
||||||
|
|
||||||
@@ -41,7 +43,7 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
|
|||||||
boxen(
|
boxen(
|
||||||
chalk.bold.yellow('WARN! ') +
|
chalk.bold.yellow('WARN! ') +
|
||||||
str +
|
str +
|
||||||
(details ? `\nMore details: ${details}` : ''),
|
(details ? `\n${action}: ${renderLink(details)}` : ''),
|
||||||
{
|
{
|
||||||
padding: {
|
padding: {
|
||||||
top: 0,
|
top: 0,
|
||||||
@@ -64,17 +66,21 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
|
|||||||
|
|
||||||
function error(
|
function error(
|
||||||
str: string,
|
str: string,
|
||||||
slug: string | null = null,
|
slug?: string,
|
||||||
link: string | null = null,
|
link?: string,
|
||||||
action = 'More details'
|
action = 'Learn More'
|
||||||
) {
|
) {
|
||||||
print(`${chalk.red(`Error!`)} ${str}\n`);
|
print(`${chalk.red(`Error!`)} ${str}\n`);
|
||||||
const details = slug ? `https://err.sh/now/${slug}` : link;
|
const details = slug ? `https://err.sh/now/${slug}` : link;
|
||||||
if (details) {
|
if (details) {
|
||||||
print(`${action}: ${details}\n`);
|
print(`${chalk.bold(action)}: ${renderLink(details)}\n`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function prettyError(err: Error & { link?: string; action?: string }) {
|
||||||
|
return error(err.message, undefined, err.link, err.action);
|
||||||
|
}
|
||||||
|
|
||||||
function ready(str: string) {
|
function ready(str: string) {
|
||||||
print(`${chalk.cyan('> Ready!')} ${str}\n`);
|
print(`${chalk.cyan('> Ready!')} ${str}\n`);
|
||||||
}
|
}
|
||||||
@@ -107,8 +113,6 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
|
|||||||
return wait(message, delay);
|
return wait(message, delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is pretty hacky, but since we control the version of Node.js
|
|
||||||
// being used because of `pkg` it's safe to do in this case.
|
|
||||||
const c = {
|
const c = {
|
||||||
_times: new Map(),
|
_times: new Map(),
|
||||||
log(a: string, ...args: string[]) {
|
log(a: string, ...args: string[]) {
|
||||||
@@ -135,6 +139,7 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
|
|||||||
log,
|
log,
|
||||||
warn,
|
warn,
|
||||||
error,
|
error,
|
||||||
|
prettyError,
|
||||||
ready,
|
ready,
|
||||||
success,
|
success,
|
||||||
debug,
|
debug,
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { metrics, shouldCollectMetrics } from '../metrics';
|
import { metrics, shouldCollectMetrics } from '../metrics';
|
||||||
import { APIError } from '../errors-ts';
|
import { APIError } from '../errors-ts';
|
||||||
|
import renderLink from './link';
|
||||||
|
|
||||||
const metric = metrics();
|
const metric = metrics();
|
||||||
|
|
||||||
export default function error(...input: string[] | [APIError]) {
|
export default function error(...input: string[] | [APIError]) {
|
||||||
let messages = input;
|
let messages = input;
|
||||||
if (typeof input[0] === 'object') {
|
if (typeof input[0] === 'object') {
|
||||||
const { slug, message, link } = input[0];
|
const { slug, message, link, action = 'Learn More' } = input[0];
|
||||||
messages = [message];
|
messages = [message];
|
||||||
if (slug) {
|
const details = slug ? `https://err.sh/now/${slug}` : link;
|
||||||
messages.push(`> More details: https://err.sh/now/${slug}`);
|
if (details) {
|
||||||
} else if (link) {
|
messages.push(`${chalk.bold(action)}: ${renderLink(details)}`);
|
||||||
messages.push(`> More details: ${link}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
46
packages/now-cli/src/util/projects/add-domain-to-project.ts
Normal file
46
packages/now-cli/src/util/projects/add-domain-to-project.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import chalk from 'chalk';
|
||||||
|
import Client from '../client';
|
||||||
|
import wait from '../output/wait';
|
||||||
|
import { ProjectAliasTarget } from '../../types';
|
||||||
|
|
||||||
|
export async function addDomainToProject(
|
||||||
|
client: Client,
|
||||||
|
projectNameOrId: string,
|
||||||
|
domain: string
|
||||||
|
) {
|
||||||
|
const cancelWait = wait(
|
||||||
|
`Adding domain ${domain} to project ${chalk.bold(projectNameOrId)}`
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
const response = await client.fetch<ProjectAliasTarget[]>(
|
||||||
|
`/projects/${encodeURIComponent(projectNameOrId)}/alias`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
target: 'PRODUCTION',
|
||||||
|
domain,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const aliasTarget: ProjectAliasTarget | undefined = response.find(
|
||||||
|
aliasTarget => aliasTarget.domain === domain
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!aliasTarget) {
|
||||||
|
throw new Error(
|
||||||
|
`Unexpected error when adding the domain "${domain}" to project "${projectNameOrId}".`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return aliasTarget;
|
||||||
|
} catch (err) {
|
||||||
|
if (err.status < 500) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
cancelWait();
|
||||||
|
}
|
||||||
|
}
|
||||||
13
packages/now-cli/src/util/projects/create-project.ts
Normal file
13
packages/now-cli/src/util/projects/create-project.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import Client from '../client';
|
||||||
|
import { Project } from '../../types';
|
||||||
|
|
||||||
|
export default async function createProject(
|
||||||
|
client: Client,
|
||||||
|
projectName: string
|
||||||
|
) {
|
||||||
|
const project = await client.fetch<Project>('/v1/projects', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ name: projectName }),
|
||||||
|
});
|
||||||
|
return project;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user