mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-26 03:39:11 +00:00
Compare commits
114 Commits
@vercel/ne
...
@vercel/cl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -19,6 +19,11 @@ indent_style = space
|
||||
[*.py]
|
||||
indent_size = 4
|
||||
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
tab_width = 4
|
||||
|
||||
[*.asm]
|
||||
indent_size = 8
|
||||
|
||||
|
||||
28
.github/CODEOWNERS
vendored
28
.github/CODEOWNERS
vendored
@@ -1,27 +1,27 @@
|
||||
# Documentation
|
||||
# https://help.github.com/en/articles/about-code-owners
|
||||
|
||||
* @tootallnate
|
||||
* @TooTallNate
|
||||
/.github/workflows @AndyBitz @styfle
|
||||
/packages/frameworks @AndyBitz
|
||||
/packages/now-cli/src/commands/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/certs/ @javivelasco @mglagola @anatrajkovska
|
||||
/packages/now-cli/src/commands/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/certs @javivelasco @mglagola @anatrajkovska
|
||||
/packages/now-cli/src/commands/env @styfle @lucleray
|
||||
/packages/now-client @rdev
|
||||
/packages/now-build-utils @styfle @AndyBitz
|
||||
/packages/now-node @styfle @tootallnate @lucleray
|
||||
/packages/now-node-bridge @styfle @tootallnate @lucleray
|
||||
/packages/now-client @rdev @styfle @TooTallNate
|
||||
/packages/now-build-utils @styfle @AndyBitz @TooTallNate
|
||||
/packages/now-node @styfle @TooTallNate @lucleray
|
||||
/packages/now-node-bridge @styfle @TooTallNate @lucleray
|
||||
/packages/now-next @Timer @ijjk
|
||||
/packages/now-go @styfle @sophearak
|
||||
/packages/now-python @styfle @sophearak
|
||||
/packages/now-ruby @styfle @coetry @nathancahill
|
||||
/packages/now-go @styfle @TooTallNate
|
||||
/packages/now-python @styfle @TooTallNate
|
||||
/packages/now-ruby @styfle @coetry @TooTallNate
|
||||
/packages/now-static-build @styfle @AndyBitz
|
||||
/packages/now-routing-utils @styfle @dav-is @ijjk
|
||||
/examples @mcsdevv @timothyis
|
||||
/examples/create-react-app @Timer
|
||||
/examples/nextjs @timneutkens
|
||||
/examples/nextjs @timneutkens @Timer
|
||||
/examples/hugo @mcsdevv @timothyis @styfle
|
||||
/examples/jekyll @mcsdevv @timothyis @sarupbanskota
|
||||
/examples/jekyll @mcsdevv @timothyis @styfle
|
||||
/examples/zola @mcsdevv @timothyis @styfle
|
||||
|
||||
@@ -1,10 +1,33 @@
|
||||
# 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.
|
||||
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.
|
||||
A Runtime is an npm module that implements the following interface:
|
||||
|
||||
```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.
|
||||
|
||||
@@ -16,146 +39,170 @@ A **required** exported constant that decides which version of the Runtime API t
|
||||
|
||||
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.
|
||||
|
||||
```js
|
||||
export analyze({
|
||||
files: Files,
|
||||
entrypoint: String,
|
||||
workPath: String,
|
||||
config: Object
|
||||
}) : String fingerprint
|
||||
```typescript
|
||||
export const version = 3;
|
||||
```
|
||||
|
||||
If you are using TypeScript, you should use the following types:
|
||||
### `build()`
|
||||
|
||||
```ts
|
||||
import { AnalyzeOptions } from '@vercel/build-utils'
|
||||
A **required** exported function that returns a Serverless Function.
|
||||
|
||||
export analyze(options: AnalyzeOptions) {
|
||||
return 'fingerprint goes here'
|
||||
> What's a Serverless Function? Read about [Serverless Functions](https://vercel.com/docs/v2/serverless-functions/introduction) to learn more.
|
||||
|
||||
**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
|
||||
build({
|
||||
files: Files,
|
||||
entrypoint: String,
|
||||
workPath: String,
|
||||
config: Object,
|
||||
meta?: {
|
||||
isDev?: Boolean,
|
||||
requestPath?: String,
|
||||
filesChanged?: Array<String>,
|
||||
filesRemoved?: Array<String>
|
||||
}
|
||||
}) : {
|
||||
watch?: Array<String>,
|
||||
output: Lambda,
|
||||
routes?: Object
|
||||
```typescript
|
||||
import { AnalyzeOptions } from '@vercel/build-utils';
|
||||
|
||||
export async function analyze(options: AnalyzeOptions) {
|
||||
// Do calculations to generate a fingerprint based off the source code here…
|
||||
|
||||
return 'fingerprint goes here';
|
||||
}
|
||||
```
|
||||
|
||||
If you are using TypeScript, you should use the following types:
|
||||
### `prepareCache()`
|
||||
|
||||
```ts
|
||||
import { BuildOptions } from '@vercel/build-utils'
|
||||
An **optional** exported function that is executed after [`build()`](#build) is
|
||||
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) {
|
||||
// Build the code here
|
||||
**Example:**
|
||||
|
||||
```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 {
|
||||
output: {
|
||||
'path-to-file': File,
|
||||
'path-to-lambda': Lambda
|
||||
},
|
||||
watch: [],
|
||||
routes: {}
|
||||
}
|
||||
'path-to-file': File,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### `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
|
||||
prepareCache({
|
||||
files: Files,
|
||||
entrypoint: String,
|
||||
workPath: String,
|
||||
cachePath: String,
|
||||
config: Object
|
||||
}) : Files cacheOutput
|
||||
```
|
||||
**Example:**
|
||||
|
||||
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
|
||||
import { PrepareCacheOptions } from '@vercel/build-utils'
|
||||
export async function shouldServe(options: ShouldServeOptions) {
|
||||
// Determine whether or not the Runtime should respond to the request path here…
|
||||
|
||||
export prepareCache(options: PrepareCacheOptions) {
|
||||
return { 'path-to-file': File }
|
||||
return options.requestPath === options.entrypoint;
|
||||
}
|
||||
```
|
||||
|
||||
### `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
|
||||
shouldServe({
|
||||
entrypoint: String,
|
||||
files: Files,
|
||||
config: Object,
|
||||
requestPath: String,
|
||||
workPath: String
|
||||
}) : Boolean
|
||||
```
|
||||
An **optional** exported function that is only used by `vercel dev` in [Vercel
|
||||
CLI](https://vercel.com/download). If this function is defined, Vercel CLI will
|
||||
**not** invoke the `build()` function, and instead invoke this function for every
|
||||
HTTP request. It is an opportunity to provide an optimized development experience
|
||||
rather than going through the entire `build()` process that is used in production.
|
||||
|
||||
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
|
||||
import { ShouldServeOptions } from '@vercel/build-utils'
|
||||
The `startDevServer()` function returns an object with the `port` number that the
|
||||
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) {
|
||||
return Boolean
|
||||
> **Hint:** To determine which ephemeral port the child process is listening on,
|
||||
> 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
|
||||
|
||||
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.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
## Types
|
||||
## `@vercel/build-utils` Types
|
||||
|
||||
### `Files`
|
||||
|
||||
```ts
|
||||
```typescript
|
||||
import { File } from '@vercel/build-utils';
|
||||
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:
|
||||
|
||||
```json
|
||||
```javascript
|
||||
{
|
||||
"index.html": FileRef,
|
||||
"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.
|
||||
|
||||
```ts
|
||||
```typescript
|
||||
import { File } from '@vercel/build-utils';
|
||||
```
|
||||
|
||||
@@ -211,71 +258,71 @@ Valid `File` types include:
|
||||
|
||||
### `FileRef`
|
||||
|
||||
```ts
|
||||
```typescript
|
||||
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:**
|
||||
|
||||
- `mode : Number` file mode
|
||||
- `digest : String` a checksum that represents the file
|
||||
- `mode: Number` file mode
|
||||
- `digest: String` a checksum that represents the file
|
||||
|
||||
**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`
|
||||
|
||||
```ts
|
||||
```typescript
|
||||
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:**
|
||||
|
||||
- `mode : Number` file mode
|
||||
- `fsPath : String` the absolute path of the file in file system
|
||||
- `mode: Number` file mode
|
||||
- `fsPath: String` the absolute path of the file in file system
|
||||
|
||||
**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`
|
||||
- `toStream() : Stream` creates a [Stream](https://nodejs.org/api/stream.html) of the file body
|
||||
- `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
|
||||
|
||||
### `FileBlob`
|
||||
|
||||
```ts
|
||||
```typescript
|
||||
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:**
|
||||
|
||||
- `mode : Number` file mode
|
||||
- `data : String | Buffer` the body of the file
|
||||
- `mode: Number` file mode
|
||||
- `data: String | Buffer` the body of the file
|
||||
|
||||
**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`
|
||||
- `toStream() : Stream` creates a [Stream](https://nodejs.org/api/stream.html) of the file body
|
||||
- `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
|
||||
|
||||
### `Lambda`
|
||||
|
||||
```ts
|
||||
```typescript
|
||||
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:**
|
||||
|
||||
- `files : Files` the internal filesystem of the lambda
|
||||
- `handler : String` path to handler file and (optionally) a function name it exports
|
||||
- `runtime : LambdaRuntime` the name of the lambda runtime
|
||||
- `environment : Object` key-value map of handler-related (aside of those passed by user) environment variables
|
||||
- `files: Files` the internal filesystem of the lambda
|
||||
- `handler: String` path to handler file and (optionally) a function name it exports
|
||||
- `runtime: LambdaRuntime` the name of the lambda runtime
|
||||
- `environment: Object` key-value map of handler-related (aside of those passed by user) environment variables
|
||||
|
||||
### `LambdaRuntime`
|
||||
|
||||
@@ -291,15 +338,15 @@ This is an abstract enumeration type that is implemented by one of the following
|
||||
- `ruby2.5`
|
||||
- `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.
|
||||
|
||||
### `createLambda`
|
||||
### `createLambda()`
|
||||
|
||||
Signature: `createLambda(Object spec) : Lambda`
|
||||
Signature: `createLambda(Object spec): Lambda`
|
||||
|
||||
```ts
|
||||
```typescript
|
||||
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';
|
||||
```
|
||||
|
||||
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
|
||||
await download(files, workPath, meta);
|
||||
```
|
||||
|
||||
### `glob`
|
||||
### `glob()`
|
||||
|
||||
Signature: `glob() : Files`
|
||||
Signature: `glob(): Files`
|
||||
|
||||
```ts
|
||||
```typescript
|
||||
import { glob } from '@vercel/build-utils';
|
||||
```
|
||||
|
||||
@@ -355,21 +406,21 @@ exports.build = ({ files, workPath }) => {
|
||||
}
|
||||
```
|
||||
|
||||
### `getWriteableDirectory`
|
||||
### `getWritableDirectory()`
|
||||
|
||||
Signature: `getWriteableDirectory() : String`
|
||||
Signature: `getWritableDirectory(): String`
|
||||
|
||||
```ts
|
||||
import { getWriteableDirectory } from '@vercel/build-utils';
|
||||
```typescript
|
||||
import { getWritableDirectory } from '@vercel/build-utils';
|
||||
```
|
||||
|
||||
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';
|
||||
```
|
||||
|
||||
|
||||
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)
|
||||
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 `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",
|
||||
"async-retry": "1.2.3",
|
||||
"buffer-replace": "1.0.0",
|
||||
"cheerio": "1.0.0-rc.3",
|
||||
"eslint": "6.2.2",
|
||||
"eslint-config-prettier": "6.1.0",
|
||||
"eslint-plugin-jest": "23.8.2",
|
||||
@@ -28,7 +29,7 @@
|
||||
"lint-staged": "9.2.5",
|
||||
"node-fetch": "2.6.0",
|
||||
"npm-package-arg": "6.1.0",
|
||||
"prettier": "1.18.2"
|
||||
"prettier": "2.0.5"
|
||||
},
|
||||
"scripts": {
|
||||
"lerna": "lerna",
|
||||
@@ -63,7 +64,8 @@
|
||||
},
|
||||
"prettier": {
|
||||
"trailingComma": "es5",
|
||||
"singleQuote": true
|
||||
"singleQuote": true,
|
||||
"arrowParens": "avoid"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
|
||||
@@ -1,4 +1,32 @@
|
||||
[
|
||||
{
|
||||
"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 output of running `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",
|
||||
"slug": "nextjs",
|
||||
@@ -640,7 +668,7 @@
|
||||
},
|
||||
"settings": {
|
||||
"buildCommand": {
|
||||
"placeholder": "`npm run build` or `nuxt build`"
|
||||
"placeholder": "`npm run build` or `nuxt generate`"
|
||||
},
|
||||
"devCommand": {
|
||||
"value": "nuxt"
|
||||
|
||||
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",
|
||||
"version": "0.0.15-canary.4",
|
||||
"version": "0.0.16",
|
||||
"main": "frameworks.json",
|
||||
"license": "UNLICENSED",
|
||||
"scripts": {
|
||||
@@ -9,7 +9,7 @@
|
||||
"devDependencies": {
|
||||
"@types/jest": "24.0.22",
|
||||
"@types/node": "12.0.4",
|
||||
"ajv": "6.10.2",
|
||||
"ajv": "6.12.2",
|
||||
"jest": "24.9.0",
|
||||
"ts-jest": "24.1.0",
|
||||
"typescript": "3.9.3"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/build-utils",
|
||||
"version": "2.3.2-canary.4",
|
||||
"version": "2.4.1",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
|
||||
@@ -462,7 +462,7 @@ function detectFrontBuilder(
|
||||
});
|
||||
}
|
||||
|
||||
if (framework === 'nextjs') {
|
||||
if (framework === 'nextjs' || framework === 'blitzjs') {
|
||||
return { src: 'package.json', use: `@vercel/next${withTag}`, config };
|
||||
}
|
||||
|
||||
@@ -496,7 +496,7 @@ function getMissingBuildScriptError() {
|
||||
code: 'missing_build_script',
|
||||
message:
|
||||
'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',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,13 @@ export class NowBuildError extends Error {
|
||||
public hideStackTrace = true;
|
||||
public code: string;
|
||||
public link?: string;
|
||||
public action?: string;
|
||||
|
||||
constructor({ message, code, link }: Props) {
|
||||
constructor({ message, code, link, action }: Props) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
this.link = link;
|
||||
this.action = action;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,4 +33,83 @@ interface Props {
|
||||
* link to more information about this error.
|
||||
*/
|
||||
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.';
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
getLatestNodeVersion,
|
||||
getDiscontinuedNodeVersions,
|
||||
} from './fs/node-version';
|
||||
import { NowBuildError } from './errors';
|
||||
import streamToBuffer from './fs/stream-to-buffer';
|
||||
import shouldServe from './should-serve';
|
||||
import debug from './debug';
|
||||
@@ -111,9 +112,11 @@ export const getPlatformEnv = (name: string): string | undefined => {
|
||||
const n = process.env[nName];
|
||||
if (typeof v === 'string') {
|
||||
if (typeof n === 'string') {
|
||||
throw new Error(
|
||||
`Both "${vName}" and "${nName}" env vars are defined. Please only define the "${vName}" env var`
|
||||
);
|
||||
throw new NowBuildError({
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -493,7 +493,7 @@ describe('Test `detectBuilders`', () => {
|
||||
});
|
||||
|
||||
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 { builders, errors } = await detectBuilders(files, null, {
|
||||
functions,
|
||||
@@ -501,11 +501,11 @@ describe('Test `detectBuilders`', () => {
|
||||
|
||||
expect(errors).toBe(null);
|
||||
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 () => {
|
||||
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 { errors } = await detectBuilders(files, null, {
|
||||
functions,
|
||||
@@ -1538,7 +1538,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
});
|
||||
|
||||
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 { builders, errors } = await detectBuilders(files, null, {
|
||||
functions,
|
||||
@@ -1547,11 +1547,11 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
|
||||
expect(errors).toBe(null);
|
||||
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 () => {
|
||||
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 { errors } = await detectBuilders(files, null, {
|
||||
functions,
|
||||
@@ -2081,7 +2081,7 @@ it('Test `detectRoutes`', async () => {
|
||||
|
||||
{
|
||||
// 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 { defaultRoutes } = await detectBuilders(files, null, { functions });
|
||||
@@ -2393,7 +2393,7 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
|
||||
{
|
||||
// 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 { defaultRoutes, rewriteRoutes } = await detectBuilders(files, null, {
|
||||
@@ -2692,7 +2692,7 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
|
||||
{
|
||||
// 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 {
|
||||
@@ -2941,7 +2941,7 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
||||
|
||||
{
|
||||
// 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 {
|
||||
|
||||
@@ -39,7 +39,7 @@ describe('Test `getPlatformEnv()`', () => {
|
||||
assert(err);
|
||||
assert.equal(
|
||||
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.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/cgi",
|
||||
"version": "1.0.6-canary.0",
|
||||
"version": "1.0.6",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "vercel",
|
||||
"version": "19.0.2-canary.13",
|
||||
"version": "19.1.2",
|
||||
"preferGlobal": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "The command-line interface for Now",
|
||||
"description": "The command-line interface for Vercel",
|
||||
"homepage": "https://vercel.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -12,7 +12,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"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-v1": "ava test/integration-v1.js --serial --fail-fast",
|
||||
"test-integration-dev": "ava test/dev/integration.js --serial --fail-fast --verbose",
|
||||
@@ -62,13 +62,13 @@
|
||||
"node": ">= 10"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "2.3.2-canary.4",
|
||||
"@vercel/go": "1.1.2-canary.2",
|
||||
"@vercel/next": "2.6.3-canary.6",
|
||||
"@vercel/node": "1.6.2-canary.5",
|
||||
"@vercel/python": "1.2.2-canary.2",
|
||||
"@vercel/ruby": "1.2.2-canary.1",
|
||||
"@vercel/static-build": "0.17.2-canary.1"
|
||||
"@vercel/build-utils": "2.4.1",
|
||||
"@vercel/go": "1.1.3",
|
||||
"@vercel/next": "2.6.11",
|
||||
"@vercel/node": "1.7.2",
|
||||
"@vercel/python": "1.2.2",
|
||||
"@vercel/ruby": "1.2.3",
|
||||
"@vercel/static-build": "0.17.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sentry/node": "5.5.0",
|
||||
@@ -106,7 +106,7 @@
|
||||
"@zeit/fun": "0.11.2",
|
||||
"@zeit/ncc": "0.18.5",
|
||||
"@zeit/source-map-support": "0.6.2",
|
||||
"ajv": "6.10.2",
|
||||
"ajv": "6.12.2",
|
||||
"alpha-sort": "2.0.1",
|
||||
"ansi-escapes": "3.0.0",
|
||||
"ansi-regex": "3.0.0",
|
||||
@@ -136,6 +136,7 @@
|
||||
"escape-html": "1.0.3",
|
||||
"esm": "3.1.4",
|
||||
"execa": "3.2.0",
|
||||
"fast-deep-equal": "3.1.3",
|
||||
"fs-extra": "7.0.1",
|
||||
"get-port": "5.1.1",
|
||||
"glob": "7.1.2",
|
||||
|
||||
@@ -3,8 +3,8 @@ import bytes from 'bytes';
|
||||
import { join } from 'path';
|
||||
import { write as copy } from 'clipboardy';
|
||||
import chalk from 'chalk';
|
||||
import title from 'title';
|
||||
import { fileNameSymbol } from '@vercel/client';
|
||||
import { getPrettyError } from '@vercel/build-utils';
|
||||
import Client from '../../util/client';
|
||||
import { handleError } from '../../util/error';
|
||||
import getArgs from '../../util/get-args';
|
||||
@@ -39,7 +39,6 @@ import {
|
||||
} from '../../util/errors-ts';
|
||||
import { SchemaValidationFailed } from '../../util/errors';
|
||||
import purchaseDomainIfAvailable from '../../util/domains/purchase-domain-if-available';
|
||||
import isWildcardAlias from '../../util/alias/is-wildcard-alias';
|
||||
import confirm from '../../util/input/confirm';
|
||||
import editProjectSettings from '../../util/input/edit-project-settings';
|
||||
import {
|
||||
@@ -56,6 +55,7 @@ import validatePaths, {
|
||||
} from '../../util/validate-paths';
|
||||
import { readLocalConfig } from '../../util/config/files';
|
||||
import { getCommandName } from '../../util/pkg-name.ts';
|
||||
import { getPreferredPreviewURL } from '../../util/deploy/get-preferred-preview-url.ts';
|
||||
|
||||
const addProcessEnv = async (log, env) => {
|
||||
let val;
|
||||
@@ -87,6 +87,7 @@ const addProcessEnv = async (log, env) => {
|
||||
|
||||
const printDeploymentStatus = async (
|
||||
output,
|
||||
client,
|
||||
{
|
||||
readyState,
|
||||
alias: aliasList,
|
||||
@@ -119,18 +120,14 @@ const printDeploymentStatus = async (
|
||||
let previewUrl;
|
||||
let isWildcard;
|
||||
if (!isFile && Array.isArray(aliasList) && aliasList.length > 0) {
|
||||
// search for a non now.sh/non wildcard domain
|
||||
// but fallback to the first alias in the list
|
||||
const mainAlias =
|
||||
aliasList.find(
|
||||
alias =>
|
||||
!alias.endsWith('.now.sh') &&
|
||||
!alias.endsWith('.vercel.app') &&
|
||||
!isWildcardAlias(alias)
|
||||
) || aliasList[0];
|
||||
|
||||
isWildcard = isWildcardAlias(mainAlias);
|
||||
previewUrl = isWildcard ? mainAlias : `https://${mainAlias}`;
|
||||
const previewUrlInfo = await getPreferredPreviewURL(client, aliasList);
|
||||
if (previewUrlInfo) {
|
||||
isWildcard = previewUrlInfo.isWildcard;
|
||||
previewUrl = previewUrlInfo.previewUrl;
|
||||
} else {
|
||||
isWildcard = false;
|
||||
previewUrl = `https://${deploymentUrl}`;
|
||||
}
|
||||
} else {
|
||||
// fallback to deployment url
|
||||
isWildcard = false;
|
||||
@@ -357,7 +354,7 @@ export default async function main(
|
||||
path,
|
||||
sourcePath,
|
||||
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
|
||||
) {
|
||||
@@ -706,6 +703,12 @@ export default async function main(
|
||||
|
||||
return printDeploymentStatus(
|
||||
output,
|
||||
new Client({
|
||||
apiUrl: ctx.apiUrl,
|
||||
token: ctx.authConfig.token,
|
||||
currentTeam: org.type === 'team' ? org.id : null,
|
||||
debug: debugEnabled,
|
||||
}),
|
||||
deployment,
|
||||
deployStamp,
|
||||
!argv['--no-clipboard'],
|
||||
@@ -735,53 +738,10 @@ function handleCreateDeployError(output, error, localConfig) {
|
||||
return 1;
|
||||
}
|
||||
if (error instanceof SchemaValidationFailed) {
|
||||
const { message, params, keyword, dataPath } = error.meta;
|
||||
|
||||
if (params && params.additionalProperty) {
|
||||
const prop = params.additionalProperty;
|
||||
|
||||
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}`
|
||||
);
|
||||
|
||||
const niceError = getPrettyError(error.meta);
|
||||
const fileName = localConfig[fileNameSymbol] || 'vercel.json';
|
||||
niceError.message = `Invalid ${fileName} - ${niceError.message}`;
|
||||
output.prettyError(niceError);
|
||||
return 1;
|
||||
}
|
||||
if (error instanceof TooManyRequests) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import { getLinkedProject } from '../../util/projects/link';
|
||||
import { getFrameworks } from '../../util/get-frameworks';
|
||||
import { isSettingValue } from '../../util/is-setting-value';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import { ProjectSettings } from '../../types';
|
||||
|
||||
type Options = {
|
||||
'--debug'?: boolean;
|
||||
@@ -50,21 +51,26 @@ export default async function dev(
|
||||
return 1;
|
||||
}
|
||||
|
||||
let devCommand: undefined | string;
|
||||
let frameworkSlug: null | string = null;
|
||||
let devCommand: string | undefined;
|
||||
let frameworkSlug: string | undefined;
|
||||
let projectSettings: ProjectSettings | undefined;
|
||||
if (link.status === 'linked') {
|
||||
const { project, org } = link;
|
||||
client.currentTeam = org.type === 'team' ? org.id : undefined;
|
||||
|
||||
projectSettings = project;
|
||||
|
||||
if (project.devCommand) {
|
||||
devCommand = project.devCommand;
|
||||
} else if (project.framework) {
|
||||
const framework = frameworks.find(f => f.slug === project.framework);
|
||||
|
||||
if (framework) {
|
||||
frameworkSlug = framework.slug;
|
||||
const defaults = framework.settings.devCommand;
|
||||
if (framework.slug) {
|
||||
frameworkSlug = framework.slug;
|
||||
}
|
||||
|
||||
const defaults = framework.settings.devCommand;
|
||||
if (isSettingValue(defaults)) {
|
||||
devCommand = defaults.value;
|
||||
}
|
||||
@@ -81,6 +87,7 @@ export default async function dev(
|
||||
debug,
|
||||
devCommand,
|
||||
frameworkSlug,
|
||||
projectSettings,
|
||||
});
|
||||
|
||||
process.once('SIGINT', () => devServer.stop());
|
||||
|
||||
@@ -10,6 +10,7 @@ import handleError from '../../util/handle-error';
|
||||
import createOutput from '../../util/output/create-output';
|
||||
import logo from '../../util/output/logo';
|
||||
import cmd from '../../util/output/cmd';
|
||||
import highlight from '../../util/output/highlight';
|
||||
import dev from './dev';
|
||||
import readPackage from '../../util/read-package';
|
||||
import readConfig from '../../util/config/read-config';
|
||||
@@ -96,7 +97,7 @@ export default async function main(ctx: NowContext) {
|
||||
'package.json'
|
||||
)} 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;
|
||||
}
|
||||
if (scripts && scripts.dev && /\bvercel\b\W+\bdev\b/.test(scripts.dev)) {
|
||||
@@ -105,7 +106,7 @@ export default async function main(ctx: NowContext) {
|
||||
'package.json'
|
||||
)} 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;
|
||||
}
|
||||
}
|
||||
@@ -119,7 +120,22 @@ export default async function main(ctx: NowContext) {
|
||||
try {
|
||||
return await dev(ctx, argv, args, output);
|
||||
} 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));
|
||||
return 1;
|
||||
}
|
||||
|
||||
37
packages/now-cli/src/commands/env/pull.ts
vendored
37
packages/now-cli/src/commands/env/pull.ts
vendored
@@ -9,16 +9,39 @@ import getDecryptedSecret from '../../util/env/get-decrypted-secret';
|
||||
import param from '../../util/output/param';
|
||||
import withSpinner from '../../util/with-spinner';
|
||||
import { join } from 'path';
|
||||
import { promises, existsSync } from 'fs';
|
||||
import { promises, openSync, closeSync, readSync } from 'fs';
|
||||
import { emoji, prependEmoji } from '../../util/emoji';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
const { writeFile } = promises;
|
||||
|
||||
const CONTENTS_PREFIX = '# Created by Vercel CLI\n';
|
||||
|
||||
type Options = {
|
||||
'--debug': 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(
|
||||
client: Client,
|
||||
project: Project,
|
||||
@@ -35,10 +58,14 @@ export default async function pull(
|
||||
|
||||
const [filename = '.env'] = args;
|
||||
const fullPath = join(process.cwd(), filename);
|
||||
const exists = existsSync(fullPath);
|
||||
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 &&
|
||||
!skipConfirmation &&
|
||||
!(await promptBool(
|
||||
@@ -83,6 +110,7 @@ export default async function pull(
|
||||
});
|
||||
|
||||
const contents =
|
||||
CONTENTS_PREFIX +
|
||||
records
|
||||
.filter(obj => {
|
||||
if (!obj.found) {
|
||||
@@ -95,7 +123,8 @@ export default async function pull(
|
||||
return true;
|
||||
})
|
||||
.map(({ key, value }) => `${key}="${escapeValue(value)}"`)
|
||||
.join('\n') + '\n';
|
||||
.join('\n') +
|
||||
'\n';
|
||||
|
||||
await writeFile(fullPath, contents, 'utf8');
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import chalk from 'chalk';
|
||||
import table from 'text-table';
|
||||
import mri from 'mri';
|
||||
import ms from 'ms';
|
||||
import strlen from '../util/strlen';
|
||||
import getArgs from '../util/get-args';
|
||||
import { handleError, error } from '../util/error';
|
||||
import exit from '../util/exit';
|
||||
import Client from '../util/client.ts';
|
||||
@@ -11,7 +11,6 @@ import getScope from '../util/get-scope';
|
||||
import createOutput from '../util/output';
|
||||
import getCommandFlags from '../util/get-command-flags';
|
||||
import wait from '../util/output/wait';
|
||||
import getPrefixedFlags from '../util/get-prefixed-flags';
|
||||
import { getPkgName, getCommandName } from '../util/pkg-name.ts';
|
||||
|
||||
const e = encodeURIComponent;
|
||||
@@ -56,23 +55,25 @@ let apiUrl;
|
||||
let subcommand;
|
||||
|
||||
const main = async ctx => {
|
||||
argv = mri(ctx.argv.slice(2), {
|
||||
boolean: ['help'],
|
||||
alias: {
|
||||
help: 'h',
|
||||
next: 'N',
|
||||
},
|
||||
});
|
||||
try {
|
||||
argv = getArgs(ctx.argv.slice(2), {
|
||||
'--next': Number,
|
||||
'-N': '--next',
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
return exit(1);
|
||||
}
|
||||
|
||||
argv._ = argv._.slice(1);
|
||||
|
||||
debug = argv.debug;
|
||||
debug = argv['--debug'];
|
||||
apiUrl = ctx.apiUrl;
|
||||
subcommand = argv._[0];
|
||||
subcommand = argv._[0] || 'list';
|
||||
|
||||
if (argv.help || !subcommand) {
|
||||
if (argv['--help']) {
|
||||
help();
|
||||
await exit(0);
|
||||
return exit(2);
|
||||
}
|
||||
|
||||
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)}`);
|
||||
|
||||
let projectsUrl = '/v4/projects/?limit=20';
|
||||
|
||||
if (argv.next) {
|
||||
projectsUrl += `&until=${argv.next}`;
|
||||
const next = argv['--next'];
|
||||
if (next) {
|
||||
projectsUrl += `&until=${next}`;
|
||||
}
|
||||
|
||||
const { projects: list, pagination } = await client.fetch(projectsUrl, {
|
||||
@@ -175,14 +177,7 @@ async function run({ client, contextName }) {
|
||||
}
|
||||
|
||||
if (pagination && pagination.count === 20) {
|
||||
const prefixedArgs = getPrefixedFlags(argv);
|
||||
const flags = getCommandFlags(prefixedArgs, [
|
||||
'_',
|
||||
'--next',
|
||||
'-N',
|
||||
'-d',
|
||||
'-y',
|
||||
]);
|
||||
const flags = getCommandFlags(argv, ['_', '--next', '-N', '-d', '-y']);
|
||||
const nextCmd = `projects ls${flags} --next ${pagination.next}`;
|
||||
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'));
|
||||
help();
|
||||
exit(1);
|
||||
exit(2);
|
||||
}
|
||||
|
||||
process.on('uncaughtException', err => {
|
||||
|
||||
@@ -24,6 +24,7 @@ import checkForUpdate from 'update-check';
|
||||
import ms from 'ms';
|
||||
import { URL } from 'url';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { NowBuildError } from '@vercel/build-utils';
|
||||
import getGlobalPathConfig from './util/config/global-path';
|
||||
import {
|
||||
getDefaultConfig,
|
||||
@@ -45,6 +46,7 @@ import reportError from './util/report-error';
|
||||
import getConfig from './util/get-config';
|
||||
import * as ERRORS from './util/errors-ts';
|
||||
import { NowError } from './util/now-error';
|
||||
import { APIError } from './util/errors-ts.ts';
|
||||
import { SENTRY_DSN } from './util/constants.ts';
|
||||
import getUpdateCommand from './util/get-update-command';
|
||||
import { metrics, shouldCollectMetrics } from './util/metrics.ts';
|
||||
@@ -114,10 +116,10 @@ const main = async argv_ => {
|
||||
}
|
||||
|
||||
if (
|
||||
localConfig instanceof NowError &&
|
||||
(localConfig instanceof NowError || localConfig instanceof NowBuildError) &&
|
||||
!(localConfig instanceof ERRORS.CantFindConfig)
|
||||
) {
|
||||
output.error(`Failed to load local config file: ${localConfig.message}`);
|
||||
output.prettyError(localConfig);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -189,8 +191,9 @@ const main = async argv_ => {
|
||||
} catch (err) {
|
||||
console.error(
|
||||
error(
|
||||
`${'An unexpected error occurred while trying to find the ' +
|
||||
'global directory: '}${err.message}`
|
||||
`An unexpected error occurred while trying to find the global directory: ${
|
||||
err.message
|
||||
}`
|
||||
)
|
||||
);
|
||||
|
||||
@@ -203,8 +206,10 @@ const main = async argv_ => {
|
||||
} catch (err) {
|
||||
console.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 +223,10 @@ const main = async argv_ => {
|
||||
} catch (err) {
|
||||
console.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 +241,10 @@ const main = async argv_ => {
|
||||
} catch (err) {
|
||||
console.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 +275,10 @@ const main = async argv_ => {
|
||||
} catch (err) {
|
||||
console.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 +293,10 @@ const main = async argv_ => {
|
||||
} catch (err) {
|
||||
console.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 +313,10 @@ const main = async argv_ => {
|
||||
} catch (err) {
|
||||
console.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 +328,6 @@ const main = async argv_ => {
|
||||
// need to migrate.
|
||||
if (authConfig.credentials) {
|
||||
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 {
|
||||
const results = await getDefaultAuthConfig(authConfig);
|
||||
@@ -340,10 +340,10 @@ const main = async argv_ => {
|
||||
} catch (err) {
|
||||
console.error(
|
||||
error(
|
||||
`${'An unexpected error occurred while trying to write the ' +
|
||||
`default config to "${hp(VERCEL_AUTH_CONFIG_PATH)}" `}${
|
||||
err.message
|
||||
}`
|
||||
`${
|
||||
'An unexpected error occurred while trying to write the ' +
|
||||
`default config to "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
|
||||
}${err.message}`
|
||||
)
|
||||
);
|
||||
return 1;
|
||||
@@ -637,34 +637,25 @@ const main = async argv_ => {
|
||||
.send();
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOTFOUND' && err.hostname === 'api.vercel.com') {
|
||||
output.error(
|
||||
`The hostname ${highlight(
|
||||
'api.vercel.com'
|
||||
)} could not be resolved. Please verify your internet connectivity and DNS configuration.`
|
||||
);
|
||||
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;
|
||||
}
|
||||
|
||||
await reportError(Sentry, err, apiUrl, configFiles);
|
||||
|
||||
// If there is a code we should not consider the error unexpected
|
||||
// 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();
|
||||
}
|
||||
|
||||
if (err instanceof APIError && 400 <= err.status && err.status <= 499) {
|
||||
err.message = err.serverMessage;
|
||||
output.prettyError(err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -675,9 +666,23 @@ const main = async argv_ => {
|
||||
.send();
|
||||
}
|
||||
|
||||
// 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}`);
|
||||
// If there is a code we should not consider the error unexpected
|
||||
// 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.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;
|
||||
}
|
||||
|
||||
@@ -688,8 +693,6 @@ const main = async argv_ => {
|
||||
return exitCode;
|
||||
};
|
||||
|
||||
debug('start');
|
||||
|
||||
const handleRejection = async err => {
|
||||
debug('handling rejection');
|
||||
|
||||
|
||||
@@ -244,15 +244,20 @@ export interface ProjectEnvVariable {
|
||||
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;
|
||||
name: string;
|
||||
accountId: string;
|
||||
updatedAt: number;
|
||||
createdAt: number;
|
||||
devCommand?: string | null;
|
||||
framework?: string | null;
|
||||
rootDirectory?: string | null;
|
||||
latestDeployments?: Partial<Deployment>[];
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
_output: Output;
|
||||
_token: string;
|
||||
currentTeam?: string;
|
||||
currentTeam?: string | null;
|
||||
|
||||
constructor({
|
||||
apiUrl,
|
||||
@@ -36,7 +36,7 @@ export default class Client extends EventEmitter {
|
||||
}: {
|
||||
apiUrl: string;
|
||||
token: string;
|
||||
currentTeam?: string;
|
||||
currentTeam?: string | null;
|
||||
forceNew?: boolean;
|
||||
withCache?: 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 pluralize from 'pluralize';
|
||||
import { basename, join } from 'path';
|
||||
import { PackageJson } from '@vercel/build-utils';
|
||||
import XDGAppPaths from 'xdg-app-paths';
|
||||
import { mkdirp, readJSON, writeJSON } from 'fs-extra';
|
||||
import { NowBuildError, PackageJson } from '@vercel/build-utils';
|
||||
import cliPkg from '../pkg';
|
||||
|
||||
import { NoBuilderCacheError } from '../errors-ts';
|
||||
@@ -232,21 +232,41 @@ async function npmInstall(
|
||||
output.debug(`Running npm install in ${cwd}`);
|
||||
|
||||
try {
|
||||
await execa(
|
||||
'npm',
|
||||
[
|
||||
'install',
|
||||
'--save-exact',
|
||||
'--no-package-lock',
|
||||
'--no-audit',
|
||||
'--no-progress',
|
||||
...sortedPackages,
|
||||
],
|
||||
{
|
||||
cwd,
|
||||
stdio: output.isDebugEnabled() ? 'inherit' : undefined,
|
||||
const args = [
|
||||
'install',
|
||||
'--save-exact',
|
||||
'--no-package-lock',
|
||||
'--no-audit',
|
||||
'--no-progress',
|
||||
];
|
||||
if (process.stderr.isTTY) {
|
||||
// Force colors in the npm child process
|
||||
// https://docs.npmjs.com/misc/config#color
|
||||
args.push('--color=always');
|
||||
}
|
||||
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 {
|
||||
stopSpinner();
|
||||
}
|
||||
|
||||
@@ -56,6 +56,8 @@ async function createBuildProcess(
|
||||
workPath: string,
|
||||
output: Output
|
||||
): Promise<ChildProcess> {
|
||||
output.debug(`Creating build process for "${match.entrypoint}"`);
|
||||
|
||||
const builderWorkerPath = join(__dirname, 'builder-worker.js');
|
||||
|
||||
// Ensure that `node` is in the builder's `PATH`
|
||||
@@ -78,7 +80,7 @@ async function createBuildProcess(
|
||||
|
||||
buildProcess.on('exit', (code, signal) => {
|
||||
output.debug(
|
||||
`Build process for ${match.src} exited with ${signal || code}`
|
||||
`Build process for "${match.entrypoint}" exited with ${signal || code}`
|
||||
);
|
||||
match.buildProcess = undefined;
|
||||
});
|
||||
@@ -128,7 +130,6 @@ export async function executeBuild(
|
||||
|
||||
let { buildProcess } = match;
|
||||
if (!runInProcess && !buildProcess) {
|
||||
devServer.output.debug(`Creating build process for ${entrypoint}`);
|
||||
buildProcess = await createBuildProcess(
|
||||
match,
|
||||
envConfigs,
|
||||
@@ -431,7 +432,7 @@ export async function getBuildMatches(
|
||||
}
|
||||
|
||||
const files = fileList
|
||||
.filter(name => name === src || minimatch(name, src))
|
||||
.filter(name => name === src || minimatch(name, src, { dot: true }))
|
||||
.map(name => join(cwd, name));
|
||||
|
||||
if (files.length === 0) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import PCRE from 'pcre-to-regexp';
|
||||
import isURL from './is-url';
|
||||
import DevServer from './server';
|
||||
|
||||
import { HttpHeadersConfig, RouteResult } from './types';
|
||||
import { NowConfig, HttpHeadersConfig, RouteResult } from './types';
|
||||
import { isHandler, Route, HandleValue } from '@vercel/routing-utils';
|
||||
|
||||
export function resolveRouteParameters(
|
||||
@@ -50,6 +50,7 @@ export async function devRouter(
|
||||
reqMethod?: string,
|
||||
routes?: Route[],
|
||||
devServer?: DevServer,
|
||||
nowConfig?: NowConfig,
|
||||
previousHeaders?: HttpHeadersConfig,
|
||||
missRoutes?: Route[],
|
||||
phase?: HandleValue | null
|
||||
@@ -117,9 +118,12 @@ export async function devRouter(
|
||||
continue;
|
||||
}
|
||||
|
||||
if (routeConfig.check && devServer && phase !== 'hit') {
|
||||
if (routeConfig.check && devServer && nowConfig && phase !== 'hit') {
|
||||
const { pathname = '/' } = url.parse(destPath);
|
||||
const hasDestFile = await devServer.hasFilesystem(pathname);
|
||||
const hasDestFile = await devServer.hasFilesystem(
|
||||
pathname,
|
||||
nowConfig
|
||||
);
|
||||
|
||||
if (!hasDestFile) {
|
||||
if (routeConfig.status && phase !== 'miss') {
|
||||
@@ -131,6 +135,7 @@ export async function devRouter(
|
||||
reqMethod,
|
||||
missRoutes,
|
||||
devServer,
|
||||
nowConfig,
|
||||
combinedHeaders,
|
||||
[],
|
||||
'miss'
|
||||
|
||||
@@ -12,12 +12,13 @@ import { randomBytes } from 'crypto';
|
||||
import serveHandler from 'serve-handler';
|
||||
import { watch, FSWatcher } from 'chokidar';
|
||||
import { parse as parseDotenv } from 'dotenv';
|
||||
import { basename, dirname, extname, join } from 'path';
|
||||
import path, { isAbsolute, basename, dirname, extname, join } from 'path';
|
||||
import once from '@tootallnate/once';
|
||||
import directoryTemplate from 'serve-handler/src/directory';
|
||||
import getPort from 'get-port';
|
||||
import { ChildProcess } from 'child_process';
|
||||
import isPortReachable from 'is-port-reachable';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
import which from 'which';
|
||||
|
||||
import { getVercelIgnore, fileNameSymbol } from '@vercel/client';
|
||||
@@ -48,18 +49,8 @@ import getNowConfigPath from '../config/local-path';
|
||||
import { MissingDotenvVarsError } from '../errors-ts';
|
||||
import cliPkg from '../pkg';
|
||||
import { getVercelDirectory } from '../projects/link';
|
||||
import { staticFiles as getFiles, getAllProjectFiles } from '../get-files';
|
||||
import {
|
||||
validateNowConfigBuilds,
|
||||
validateNowConfigRoutes,
|
||||
validateNowConfigCleanUrls,
|
||||
validateNowConfigHeaders,
|
||||
validateNowConfigRedirects,
|
||||
validateNowConfigRewrites,
|
||||
validateNowConfigTrailingSlash,
|
||||
validateNowConfigFunctions,
|
||||
} from './validate';
|
||||
|
||||
import { staticFiles as getFiles } from '../get-files';
|
||||
import { validateConfig } from './validate';
|
||||
import { devRouter, getRoutesTypes } from './router';
|
||||
import getMimeType from './mime-type';
|
||||
import { executeBuild, getBuildMatches, shutdownBuilder } from './builder';
|
||||
@@ -92,12 +83,17 @@ import {
|
||||
HttpHeadersConfig,
|
||||
EnvConfigs,
|
||||
} from './types';
|
||||
import { ProjectSettings } from '../../types';
|
||||
|
||||
interface FSEvent {
|
||||
type: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
type WithFileNameSymbol<T> = T & {
|
||||
[fileNameSymbol]: string;
|
||||
};
|
||||
|
||||
function sortBuilders(buildA: Builder, buildB: Builder) {
|
||||
if (buildA && buildA.use && isOfficialRuntime('static-build', buildA.use)) {
|
||||
return 1;
|
||||
@@ -114,17 +110,16 @@ export default class DevServer {
|
||||
public cwd: string;
|
||||
public debug: boolean;
|
||||
public output: Output;
|
||||
public proxy: httpProxy;
|
||||
public envConfigs: EnvConfigs;
|
||||
public frameworkSlug: string | null;
|
||||
public frameworkSlug?: string;
|
||||
public files: BuilderInputs;
|
||||
public address: string;
|
||||
public devCacheDir: string;
|
||||
|
||||
private cachedNowConfig: NowConfig | null;
|
||||
private caseSensitive: boolean;
|
||||
private apiDir: string | null;
|
||||
private apiExtensions: Set<string>;
|
||||
private proxy: httpProxy;
|
||||
private server: http.Server;
|
||||
private stopping: boolean;
|
||||
private buildMatches: Map<string, BuildMatch>;
|
||||
@@ -139,6 +134,7 @@ export default class DevServer {
|
||||
private devProcess?: ChildProcess;
|
||||
private devProcessPort?: number;
|
||||
private devServerPids: Set<number>;
|
||||
private projectSettings?: ProjectSettings;
|
||||
|
||||
private getNowConfigPromise: Promise<NowConfig> | null;
|
||||
private blockingBuildsPromise: Promise<void> | null;
|
||||
@@ -153,8 +149,8 @@ export default class DevServer {
|
||||
this.files = {};
|
||||
this.address = '';
|
||||
this.devCommand = options.devCommand;
|
||||
this.projectSettings = options.projectSettings;
|
||||
this.frameworkSlug = options.frameworkSlug;
|
||||
this.cachedNowConfig = null;
|
||||
this.caseSensitive = false;
|
||||
this.apiDir = null;
|
||||
this.apiExtensions = new Set();
|
||||
@@ -178,10 +174,8 @@ export default class DevServer {
|
||||
this.watchAggregationEvents = [];
|
||||
this.watchAggregationTimeout = 500;
|
||||
|
||||
this.filter = path => Boolean(path);
|
||||
this.podId = Math.random()
|
||||
.toString(32)
|
||||
.slice(-5);
|
||||
this.filter = (path) => Boolean(path);
|
||||
this.podId = Math.random().toString(32).slice(-5);
|
||||
|
||||
this.devServerPids = new Set();
|
||||
}
|
||||
@@ -219,8 +213,8 @@ export default class DevServer {
|
||||
}
|
||||
}
|
||||
|
||||
events = events.filter(event =>
|
||||
distPaths.every(distPath => !event.path.startsWith(distPath))
|
||||
events = events.filter((event) =>
|
||||
distPaths.every((distPath) => !event.path.startsWith(distPath))
|
||||
);
|
||||
|
||||
// First, update the `files` mapping of source files
|
||||
@@ -234,8 +228,9 @@ export default class DevServer {
|
||||
}
|
||||
}
|
||||
|
||||
const nowConfig = await this.getNowConfig();
|
||||
|
||||
// Update the build matches in case an entrypoint was created or deleted
|
||||
const nowConfig = await this.getNowConfig(false);
|
||||
await this.updateBuildMatches(nowConfig);
|
||||
|
||||
const filesChangedArray = [...filesChanged];
|
||||
@@ -279,18 +274,19 @@ export default class DevServer {
|
||||
for (const [result, [requestPath, match]] of needsRebuild) {
|
||||
if (
|
||||
requestPath === null ||
|
||||
(await shouldServe(match, this.files, requestPath, this))
|
||||
(await shouldServe(match, this.files, requestPath, this, nowConfig))
|
||||
) {
|
||||
this.triggerBuild(
|
||||
match,
|
||||
requestPath,
|
||||
null,
|
||||
nowConfig,
|
||||
result,
|
||||
filesChangedArray,
|
||||
filesRemovedArray
|
||||
).catch((err: Error) => {
|
||||
this.output.warn(
|
||||
`An error occurred while rebuilding ${match.src}:`
|
||||
`An error occurred while rebuilding \`${match.src}\`:`
|
||||
);
|
||||
console.error(err.stack);
|
||||
});
|
||||
@@ -374,12 +370,10 @@ export default class DevServer {
|
||||
this,
|
||||
fileList
|
||||
);
|
||||
const sources = matches.map(m => m.src);
|
||||
const sources = matches.map((m) => m.src);
|
||||
|
||||
if (isInitial && fileList.length === 0) {
|
||||
this.output.warn(
|
||||
'There are no files (or only files starting with a dot) inside your deployment.'
|
||||
);
|
||||
this.output.warn('There are no files inside your deployment.');
|
||||
}
|
||||
|
||||
// Delete build matches that no longer exists
|
||||
@@ -400,7 +394,7 @@ export default class DevServer {
|
||||
const blockingBuilds: Promise<void>[] = [];
|
||||
for (const match of matches) {
|
||||
const currentMatch = this.buildMatches.get(match.src);
|
||||
if (!currentMatch || currentMatch.use !== match.use) {
|
||||
if (!buildMatchEquals(currentMatch, match)) {
|
||||
this.output.debug(
|
||||
`Adding build match for "${match.src}" with "${match.use}"`
|
||||
);
|
||||
@@ -435,6 +429,9 @@ export default class DevServer {
|
||||
`Cleaning up "blockingBuildsPromise" after error: ${err}`
|
||||
);
|
||||
this.blockingBuildsPromise = null;
|
||||
if (err) {
|
||||
this.output.prettyError(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -500,71 +497,42 @@ export default class DevServer {
|
||||
return {};
|
||||
}
|
||||
|
||||
async getNowConfig(canUseCache: boolean = true): Promise<NowConfig> {
|
||||
clearNowConfigPromise = () => {
|
||||
this.getNowConfigPromise = null;
|
||||
};
|
||||
|
||||
getNowConfig(): Promise<NowConfig> {
|
||||
if (this.getNowConfigPromise) {
|
||||
return this.getNowConfigPromise;
|
||||
}
|
||||
this.getNowConfigPromise = this._getNowConfig(canUseCache);
|
||||
try {
|
||||
return await this.getNowConfigPromise;
|
||||
} finally {
|
||||
this.getNowConfigPromise = null;
|
||||
}
|
||||
this.getNowConfigPromise = this._getNowConfig();
|
||||
|
||||
// Clean up the promise once it has resolved
|
||||
const clear = this.clearNowConfigPromise;
|
||||
this.getNowConfigPromise.finally(clear);
|
||||
|
||||
return this.getNowConfigPromise;
|
||||
}
|
||||
|
||||
async _getNowConfig(canUseCache: boolean = true): Promise<NowConfig> {
|
||||
if (canUseCache && this.cachedNowConfig) {
|
||||
return this.cachedNowConfig;
|
||||
}
|
||||
async _getNowConfig(): Promise<NowConfig> {
|
||||
const configPath = getNowConfigPath(this.cwd);
|
||||
|
||||
const pkg = await this.getPackageJson();
|
||||
|
||||
// The default empty `vercel.json` is used to serve all files as static
|
||||
// when no `vercel.json` is present
|
||||
let configPath = 'vercel.json';
|
||||
let config: NowConfig = this.cachedNowConfig || {
|
||||
version: 2,
|
||||
[fileNameSymbol]: configPath,
|
||||
};
|
||||
|
||||
// We need to delete these properties for zero config to work
|
||||
// with file changes
|
||||
if (this.cachedNowConfig) {
|
||||
delete this.cachedNowConfig.builds;
|
||||
delete this.cachedNowConfig.routes;
|
||||
}
|
||||
|
||||
try {
|
||||
configPath = getNowConfigPath(this.cwd);
|
||||
this.output.debug(`Reading ${configPath}`);
|
||||
config = JSON.parse(await fs.readFile(configPath, 'utf8'));
|
||||
config[fileNameSymbol] = configPath;
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
this.output.debug(err.toString());
|
||||
} else if (err.name === 'SyntaxError') {
|
||||
this.output.warn(
|
||||
`There is a syntax error in ${configPath}: ${err.message}`
|
||||
);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
const allFiles = await getAllProjectFiles(this.cwd, this.output);
|
||||
const files = allFiles.filter(this.filter);
|
||||
|
||||
this.output.debug(
|
||||
`Found ${allFiles.length} and ` +
|
||||
`filtered out ${allFiles.length - files.length} files`
|
||||
);
|
||||
const [
|
||||
pkg = null,
|
||||
// The default empty `vercel.json` is used to serve all
|
||||
// files as static when no `vercel.json` is present
|
||||
config = { version: 2, [fileNameSymbol]: 'vercel.json' },
|
||||
] = await Promise.all([
|
||||
this.readJsonFile<PackageJson>('package.json'),
|
||||
this.readJsonFile<NowConfig>(configPath),
|
||||
]);
|
||||
|
||||
await this.validateNowConfig(config);
|
||||
const { error: routeError, routes: maybeRoutes } = getTransformedRoutes({
|
||||
nowConfig: config,
|
||||
});
|
||||
if (routeError) {
|
||||
this.output.error(routeError.message, null, routeError.link);
|
||||
this.output.prettyError(routeError);
|
||||
await this.exit();
|
||||
}
|
||||
config.routes = maybeRoutes || [];
|
||||
@@ -574,6 +542,11 @@ export default class DevServer {
|
||||
const featHandleMiss = true; // enable for zero config
|
||||
const { projectSettings, cleanUrls, trailingSlash } = config;
|
||||
|
||||
const opts = { output: this.output, isBuilds: true };
|
||||
const files = (await getFiles(this.cwd, config, opts)).map((f) =>
|
||||
relative(this.cwd, f)
|
||||
);
|
||||
|
||||
let {
|
||||
builders,
|
||||
warnings,
|
||||
@@ -585,7 +558,7 @@ export default class DevServer {
|
||||
} = await detectBuilders(files, pkg, {
|
||||
tag: getDistTag(cliPkg.version) === 'canary' ? 'canary' : 'latest',
|
||||
functions: config.functions,
|
||||
...(projectSettings ? { projectSettings } : {}),
|
||||
projectSettings: projectSettings || this.projectSettings,
|
||||
featHandleMiss,
|
||||
cleanUrls,
|
||||
trailingSlash,
|
||||
@@ -597,7 +570,7 @@ export default class DevServer {
|
||||
}
|
||||
|
||||
if (warnings && warnings.length > 0) {
|
||||
warnings.forEach(warning => this.output.warn(warning.message));
|
||||
warnings.forEach((warning) => this.output.warn(warning.message));
|
||||
}
|
||||
|
||||
if (builders) {
|
||||
@@ -607,6 +580,8 @@ export default class DevServer {
|
||||
|
||||
config.builds = config.builds || [];
|
||||
config.builds.push(...builders);
|
||||
|
||||
delete config.functions;
|
||||
}
|
||||
|
||||
let routes: Route[] = [];
|
||||
@@ -640,34 +615,50 @@ export default class DevServer {
|
||||
|
||||
await this.validateNowConfig(config);
|
||||
|
||||
this.cachedNowConfig = config;
|
||||
this.caseSensitive = hasNewRoutingProperties(config);
|
||||
this.apiDir = detectApiDirectory(config.builds || []);
|
||||
this.apiExtensions = detectApiExtensions(config.builds || []);
|
||||
|
||||
// Update the env vars configuration
|
||||
const [runEnv, buildEnv] = await Promise.all([
|
||||
this.getLocalEnv('.env', config.env),
|
||||
this.getLocalEnv('.env.build', config.build?.env),
|
||||
]);
|
||||
const allEnv = { ...buildEnv, ...runEnv };
|
||||
this.envConfigs = { buildEnv, runEnv, allEnv };
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
async getPackageJson(): Promise<PackageJson | null> {
|
||||
const pkgPath = join(this.cwd, 'package.json');
|
||||
let pkg: PackageJson | null = null;
|
||||
|
||||
this.output.debug('Reading `package.json` file');
|
||||
async readJsonFile<T>(
|
||||
filePath: string
|
||||
): Promise<WithFileNameSymbol<T> | void> {
|
||||
let rel, abs;
|
||||
if (isAbsolute(filePath)) {
|
||||
rel = path.relative(this.cwd, filePath);
|
||||
abs = filePath;
|
||||
} else {
|
||||
rel = filePath;
|
||||
abs = join(this.cwd, filePath);
|
||||
}
|
||||
this.output.debug(`Reading \`${rel}\` file`);
|
||||
|
||||
try {
|
||||
pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
|
||||
const raw = await fs.readFile(abs, 'utf8');
|
||||
const parsed: WithFileNameSymbol<T> = JSON.parse(raw);
|
||||
parsed[fileNameSymbol] = rel;
|
||||
return parsed;
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
this.output.debug('No `package.json` file present');
|
||||
this.output.debug(`No \`${rel}\` file present`);
|
||||
} else if (err.name === 'SyntaxError') {
|
||||
this.output.warn(
|
||||
`There is a syntax error in the \`package.json\` file: ${err.message}`
|
||||
`There is a syntax error in the \`${rel}\` file: ${err.message}`
|
||||
);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
return pkg;
|
||||
}
|
||||
|
||||
async tryValidateOrExit(
|
||||
@@ -689,14 +680,12 @@ export default class DevServer {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.tryValidateOrExit(config, validateNowConfigBuilds);
|
||||
await this.tryValidateOrExit(config, validateNowConfigRoutes);
|
||||
await this.tryValidateOrExit(config, validateNowConfigCleanUrls);
|
||||
await this.tryValidateOrExit(config, validateNowConfigHeaders);
|
||||
await this.tryValidateOrExit(config, validateNowConfigRedirects);
|
||||
await this.tryValidateOrExit(config, validateNowConfigRewrites);
|
||||
await this.tryValidateOrExit(config, validateNowConfigTrailingSlash);
|
||||
await this.tryValidateOrExit(config, validateNowConfigFunctions);
|
||||
const error = validateConfig(config);
|
||||
|
||||
if (error) {
|
||||
this.output.prettyError(error);
|
||||
await this.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
validateEnvConfig(type: string, env: Env = {}, localEnv: Env = {}): Env {
|
||||
@@ -765,16 +754,7 @@ export default class DevServer {
|
||||
const { ig } = await getVercelIgnore(this.cwd);
|
||||
this.filter = ig.createFilter();
|
||||
|
||||
// Retrieve the path of the native module
|
||||
const nowConfig = await this.getNowConfig(false);
|
||||
const nowConfigBuild = nowConfig.build || {};
|
||||
const [runEnv, buildEnv] = await Promise.all([
|
||||
this.getLocalEnv('.env', nowConfig.env),
|
||||
this.getLocalEnv('.env.build', nowConfigBuild.env),
|
||||
]);
|
||||
const allEnv = { ...buildEnv, ...runEnv };
|
||||
Object.assign(process.env, allEnv);
|
||||
this.envConfigs = { buildEnv, runEnv, allEnv };
|
||||
const nowConfig = await this.getNowConfig();
|
||||
|
||||
const opts = { output: this.output, isBuilds: true };
|
||||
const files = await getFiles(this.cwd, nowConfig, opts);
|
||||
@@ -802,11 +782,11 @@ export default class DevServer {
|
||||
// get their "build matches" invalidated so that the new version is used.
|
||||
this.updateBuildersTimeout = setTimeout(() => {
|
||||
this.updateBuildersPromise = updateBuilders(builders, this.output)
|
||||
.then(updatedBuilders => {
|
||||
.then((updatedBuilders) => {
|
||||
this.updateBuildersPromise = null;
|
||||
this.invalidateBuildMatches(nowConfig, updatedBuilders);
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
this.updateBuildersPromise = null;
|
||||
this.output.error(`Failed to update builders: ${err.message}`);
|
||||
this.output.debug(err.stack);
|
||||
@@ -1147,6 +1127,7 @@ export default class DevServer {
|
||||
match: BuildMatch,
|
||||
requestPath: string | null,
|
||||
req: http.IncomingMessage | null,
|
||||
nowConfig: NowConfig,
|
||||
previousBuildResult?: BuildResult,
|
||||
filesChanged?: string[],
|
||||
filesRemoved?: string[]
|
||||
@@ -1162,10 +1143,11 @@ export default class DevServer {
|
||||
// A build for `buildKey` is already in progress, so don't trigger
|
||||
// another rebuild for this request - just wait on the existing one.
|
||||
let msg = `De-duping build "${buildKey}"`;
|
||||
if (req) msg += ` for "${req.method} ${req.url}"`;
|
||||
if (req) {
|
||||
msg += ` for "${req.method} ${req.url}"`;
|
||||
}
|
||||
this.output.debug(msg);
|
||||
} else {
|
||||
const nowConfig = await this.getNowConfig();
|
||||
if (previousBuildResult) {
|
||||
// Tear down any `output` assets from a previous build, so that they
|
||||
// are not available to be served while the rebuild is in progress.
|
||||
@@ -1176,7 +1158,9 @@ export default class DevServer {
|
||||
}
|
||||
}
|
||||
let msg = `Building asset "${buildKey}"`;
|
||||
if (req) msg += ` for "${req.method} ${req.url}"`;
|
||||
if (req) {
|
||||
msg += ` for "${req.method} ${req.url}"`;
|
||||
}
|
||||
this.output.debug(msg);
|
||||
buildPromise = executeBuild(
|
||||
nowConfig,
|
||||
@@ -1257,7 +1241,7 @@ export default class DevServer {
|
||||
const { status, headers, dest } = routeResult;
|
||||
const location = headers['location'] || dest;
|
||||
|
||||
if (status && location && (300 <= status && status <= 399)) {
|
||||
if (status && location && 300 <= status && status <= 399) {
|
||||
this.output.debug(`Route found with redirect status code ${status}`);
|
||||
await this.sendRedirect(req, res, nowRequestId, location, status);
|
||||
return true;
|
||||
@@ -1347,6 +1331,7 @@ export default class DevServer {
|
||||
req.method,
|
||||
phaseRoutes,
|
||||
this,
|
||||
nowConfig,
|
||||
prevHeaders,
|
||||
missRoutes,
|
||||
phase
|
||||
@@ -1367,14 +1352,15 @@ export default class DevServer {
|
||||
|
||||
debug(`ProxyPass: ${destUrl}`);
|
||||
this.setResponseHeaders(res, nowRequestId);
|
||||
return proxyPass(req, res, destUrl, this.proxy, this.output);
|
||||
return proxyPass(req, res, destUrl, this, nowRequestId);
|
||||
}
|
||||
|
||||
match = await findBuildMatch(
|
||||
this.buildMatches,
|
||||
this.files,
|
||||
routeResult.dest,
|
||||
this
|
||||
this,
|
||||
nowConfig
|
||||
);
|
||||
|
||||
if (
|
||||
@@ -1397,6 +1383,7 @@ export default class DevServer {
|
||||
req.method,
|
||||
missRoutes,
|
||||
this,
|
||||
nowConfig,
|
||||
routeResult.headers,
|
||||
[],
|
||||
'miss'
|
||||
@@ -1406,7 +1393,8 @@ export default class DevServer {
|
||||
this.buildMatches,
|
||||
this.files,
|
||||
routeResult.dest,
|
||||
this
|
||||
this,
|
||||
nowConfig
|
||||
);
|
||||
if (
|
||||
await this.exitWithStatus(
|
||||
@@ -1429,6 +1417,7 @@ export default class DevServer {
|
||||
req.method,
|
||||
hitRoutes,
|
||||
this,
|
||||
nowConfig,
|
||||
routeResult.headers,
|
||||
[],
|
||||
'hit'
|
||||
@@ -1443,6 +1432,7 @@ export default class DevServer {
|
||||
req.method,
|
||||
errorRoutes,
|
||||
this,
|
||||
nowConfig,
|
||||
routeResult.headers,
|
||||
[],
|
||||
'error'
|
||||
@@ -1452,7 +1442,8 @@ export default class DevServer {
|
||||
this.buildMatches,
|
||||
this.files,
|
||||
routeResultForError.dest,
|
||||
this
|
||||
this,
|
||||
nowConfig
|
||||
);
|
||||
|
||||
if (matchForError) {
|
||||
@@ -1500,16 +1491,15 @@ export default class DevServer {
|
||||
if (!match) {
|
||||
// If the dev command is started, then proxy to it
|
||||
if (this.devProcessPort) {
|
||||
debug('Proxying to frontend dev server');
|
||||
const upstream = `http://localhost:${this.devProcessPort}`;
|
||||
debug(`Proxying to frontend dev server: ${upstream}`);
|
||||
this.setResponseHeaders(res, nowRequestId);
|
||||
return proxyPass(
|
||||
req,
|
||||
res,
|
||||
`http://localhost:${this.devProcessPort}`,
|
||||
this.proxy,
|
||||
this.output,
|
||||
false
|
||||
);
|
||||
const origUrl = url.parse(req.url || '/', true);
|
||||
delete origUrl.search;
|
||||
origUrl.pathname = dest;
|
||||
Object.assign(origUrl.query, uri_args);
|
||||
req.url = url.format(origUrl);
|
||||
return proxyPass(req, res, upstream, this, nowRequestId, false);
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -1541,7 +1531,8 @@ export default class DevServer {
|
||||
newUrl,
|
||||
req.method,
|
||||
buildResult.routes,
|
||||
this
|
||||
this,
|
||||
nowConfig
|
||||
);
|
||||
if (matchedRoute.found && callLevel === 0) {
|
||||
debug(`Found matching route ${matchedRoute.dest} for ${newUrl}`);
|
||||
@@ -1626,8 +1617,8 @@ export default class DevServer {
|
||||
req,
|
||||
res,
|
||||
`http://localhost:${port}`,
|
||||
this.proxy,
|
||||
this.output,
|
||||
this,
|
||||
nowRequestId,
|
||||
false
|
||||
);
|
||||
} else {
|
||||
@@ -1637,7 +1628,7 @@ export default class DevServer {
|
||||
let foundAsset = findAsset(match, requestPath, nowConfig);
|
||||
|
||||
if (!foundAsset && callLevel === 0) {
|
||||
await this.triggerBuild(match, buildRequestPath, req);
|
||||
await this.triggerBuild(match, buildRequestPath, req, nowConfig);
|
||||
|
||||
// Since the `asset` was just built, resolve again to get the new asset
|
||||
foundAsset = findAsset(match, requestPath, nowConfig);
|
||||
@@ -1656,8 +1647,8 @@ export default class DevServer {
|
||||
req,
|
||||
res,
|
||||
`http://localhost:${this.devProcessPort}`,
|
||||
this.proxy,
|
||||
this.output,
|
||||
this,
|
||||
nowRequestId,
|
||||
false
|
||||
);
|
||||
}
|
||||
@@ -1669,8 +1660,9 @@ export default class DevServer {
|
||||
|
||||
const { asset, assetKey } = foundAsset;
|
||||
debug(
|
||||
`Serving asset: [${asset.type}] ${assetKey} ${(asset as any)
|
||||
.contentType || ''}`
|
||||
`Serving asset: [${asset.type}] ${assetKey} ${
|
||||
(asset as any).contentType || ''
|
||||
}`
|
||||
);
|
||||
|
||||
/* eslint-disable no-case-declarations */
|
||||
@@ -1789,7 +1781,7 @@ export default class DevServer {
|
||||
|
||||
const dirs: Set<string> = new Set();
|
||||
const files = Array.from(this.buildMatches.keys())
|
||||
.filter(p => {
|
||||
.filter((p) => {
|
||||
const base = basename(p);
|
||||
if (
|
||||
base === 'now.json' ||
|
||||
@@ -1810,7 +1802,7 @@ export default class DevServer {
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map(p => {
|
||||
.map((p) => {
|
||||
let base = basename(p);
|
||||
let ext = '';
|
||||
let type = 'file';
|
||||
@@ -1862,8 +1854,17 @@ export default class DevServer {
|
||||
return true;
|
||||
}
|
||||
|
||||
async hasFilesystem(dest: string): Promise<boolean> {
|
||||
if (await findBuildMatch(this.buildMatches, this.files, dest, this, true)) {
|
||||
async hasFilesystem(dest: string, nowConfig: NowConfig): Promise<boolean> {
|
||||
if (
|
||||
await findBuildMatch(
|
||||
this.buildMatches,
|
||||
this.files,
|
||||
dest,
|
||||
this,
|
||||
nowConfig,
|
||||
true
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -1967,24 +1968,27 @@ function proxyPass(
|
||||
req: http.IncomingMessage,
|
||||
res: http.ServerResponse,
|
||||
dest: string,
|
||||
proxy: httpProxy,
|
||||
output: Output,
|
||||
devServer: DevServer,
|
||||
nowRequestId: string,
|
||||
ignorePath: boolean = true
|
||||
): void {
|
||||
return proxy.web(
|
||||
return devServer.proxy.web(
|
||||
req,
|
||||
res,
|
||||
{ target: dest, ignorePath },
|
||||
(error: NodeJS.ErrnoException) => {
|
||||
// If the client hangs up a socket, we do not
|
||||
// want to do anything, as the client just expects
|
||||
// the connection to be closed.
|
||||
if (error.code === 'ECONNRESET') {
|
||||
res.end();
|
||||
return;
|
||||
devServer.output.error(
|
||||
`Failed to complete request to ${req.url}: ${error}`
|
||||
);
|
||||
if (!res.headersSent) {
|
||||
devServer.sendError(
|
||||
req,
|
||||
res,
|
||||
nowRequestId,
|
||||
'NO_RESPONSE_FROM_FUNCTION',
|
||||
502
|
||||
);
|
||||
}
|
||||
|
||||
output.error(`Failed to complete request to ${req.url}: ${error}`);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -2041,13 +2045,23 @@ async function findBuildMatch(
|
||||
files: BuilderInputs,
|
||||
requestPath: string,
|
||||
devServer: DevServer,
|
||||
isFilesystem?: boolean
|
||||
nowConfig: NowConfig,
|
||||
isFilesystem = false
|
||||
): Promise<BuildMatch | null> {
|
||||
requestPath = requestPath.replace(/^\//, '');
|
||||
|
||||
let bestIndexMatch: undefined | BuildMatch;
|
||||
for (const match of matches.values()) {
|
||||
if (await shouldServe(match, files, requestPath, devServer, isFilesystem)) {
|
||||
if (
|
||||
await shouldServe(
|
||||
match,
|
||||
files,
|
||||
requestPath,
|
||||
devServer,
|
||||
nowConfig,
|
||||
isFilesystem
|
||||
)
|
||||
) {
|
||||
if (!isIndex(match.src)) {
|
||||
return match;
|
||||
} else {
|
||||
@@ -2069,14 +2083,14 @@ async function shouldServe(
|
||||
files: BuilderInputs,
|
||||
requestPath: string,
|
||||
devServer: DevServer,
|
||||
isFilesystem?: boolean
|
||||
nowConfig: NowConfig,
|
||||
isFilesystem = false
|
||||
): Promise<boolean> {
|
||||
const {
|
||||
src,
|
||||
config,
|
||||
builderWithPkg: { builder },
|
||||
} = match;
|
||||
const nowConfig = await devServer.getNowConfig();
|
||||
const cleanSrc = src.endsWith('.html') ? src.slice(0, -5) : src;
|
||||
const trimmedPath = requestPath.endsWith('/')
|
||||
? requestPath.slice(0, -1)
|
||||
@@ -2120,7 +2134,7 @@ async function shouldServe(
|
||||
return true;
|
||||
} else if (
|
||||
!isFilesystem &&
|
||||
(await findMatchingRoute(match, requestPath, devServer))
|
||||
(await findMatchingRoute(match, requestPath, devServer, nowConfig))
|
||||
) {
|
||||
// If there's no `shouldServe()` function and no matched asset, then look
|
||||
// up if there's a matching build route on the `match` that has already
|
||||
@@ -2133,7 +2147,8 @@ async function shouldServe(
|
||||
async function findMatchingRoute(
|
||||
match: BuildMatch,
|
||||
requestPath: string,
|
||||
devServer: DevServer
|
||||
devServer: DevServer,
|
||||
nowConfig: NowConfig
|
||||
): Promise<RouteResult | void> {
|
||||
const reqUrl = `/${requestPath}`;
|
||||
for (const buildResult of match.buildResults.values()) {
|
||||
@@ -2142,7 +2157,8 @@ async function findMatchingRoute(
|
||||
reqUrl,
|
||||
undefined,
|
||||
buildResult.routes,
|
||||
devServer
|
||||
devServer,
|
||||
nowConfig
|
||||
);
|
||||
if (route.found) {
|
||||
return route;
|
||||
@@ -2197,7 +2213,9 @@ function isIndex(path: string): boolean {
|
||||
}
|
||||
|
||||
function minimatches(files: string[], pattern: string): boolean {
|
||||
return files.some(file => file === pattern || minimatch(file, pattern));
|
||||
return files.some(
|
||||
(file) => file === pattern || minimatch(file, pattern, { dot: true })
|
||||
);
|
||||
}
|
||||
|
||||
function fileChanged(
|
||||
@@ -2226,7 +2244,7 @@ function needsBlockingBuild(buildMatch: BuildMatch): boolean {
|
||||
}
|
||||
|
||||
async function sleep(n: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, n));
|
||||
return new Promise((resolve) => setTimeout(resolve, n));
|
||||
}
|
||||
|
||||
async function checkForPort(
|
||||
@@ -2258,3 +2276,11 @@ function hasNewRoutingProperties(nowConfig: NowConfig) {
|
||||
typeof nowConfig.trailingSlash !== undefined
|
||||
);
|
||||
}
|
||||
|
||||
function buildMatchEquals(a?: BuildMatch, b?: BuildMatch): boolean {
|
||||
if (!a || !b) return false;
|
||||
if (a.src !== b.src) return false;
|
||||
if (a.use !== b.use) return false;
|
||||
if (!deepEqual(a.config || {}, b.config || {})) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -17,14 +17,16 @@ import {
|
||||
import { NowConfig } from '@vercel/client';
|
||||
import { HandleValue, Route } from '@vercel/routing-utils';
|
||||
import { Output } from '../output';
|
||||
import { ProjectSettings } from '../../types';
|
||||
|
||||
export { NowConfig };
|
||||
|
||||
export interface DevServerOptions {
|
||||
output: Output;
|
||||
debug: boolean;
|
||||
devCommand: string | undefined;
|
||||
frameworkSlug: string | null;
|
||||
devCommand?: string;
|
||||
frameworkSlug?: string;
|
||||
projectSettings?: ProjectSettings;
|
||||
}
|
||||
|
||||
export interface EnvConfigs {
|
||||
|
||||
@@ -8,71 +8,52 @@ import {
|
||||
trailingSlashSchema,
|
||||
} from '@vercel/routing-utils';
|
||||
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 validate = ajv.compile(vercelConfigSchema);
|
||||
|
||||
const validateBuilds = ajv.compile(buildsSchema);
|
||||
const validateRoutes = ajv.compile(routesSchema);
|
||||
const validateCleanUrls = ajv.compile(cleanUrlsSchema);
|
||||
const validateHeaders = ajv.compile(headersSchema);
|
||||
const validateRedirects = ajv.compile(redirectsSchema);
|
||||
const validateRewrites = ajv.compile(rewritesSchema);
|
||||
const validateTrailingSlash = ajv.compile(trailingSlashSchema);
|
||||
const validateFunctions = ajv.compile(functionsSchema);
|
||||
|
||||
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;
|
||||
export function validateConfig(config: NowConfig): NowBuildError | null {
|
||||
if (!validate(config)) {
|
||||
if (validate.errors && validate.errors[0]) {
|
||||
const error = validate.errors[0];
|
||||
const fileName = config[fileNameSymbol] || 'vercel.json';
|
||||
const niceError = getPrettyError(error);
|
||||
niceError.message = `Invalid ${fileName} - ${niceError.message}`;
|
||||
return niceError;
|
||||
}
|
||||
}
|
||||
|
||||
if (!validate(value)) {
|
||||
if (!validate.errors) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const error = validate.errors[0];
|
||||
|
||||
return `Invalid \`${String(key)}\` property: ${error.dataPath} ${
|
||||
error.message
|
||||
}`;
|
||||
if (config.functions && config.builds) {
|
||||
return new NowBuildError({
|
||||
code: 'FUNCTIONS_AND_BUILDS',
|
||||
message:
|
||||
'The `functions` property cannot be used in conjunction with the `builds` property. Please remove one of them.',
|
||||
link: 'https://vercel.link/functions-and-builds',
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import bytes from 'bytes';
|
||||
import { Response } from 'node-fetch';
|
||||
import { NowBuildError } from '@vercel/build-utils';
|
||||
import { NowError } from './now-error';
|
||||
import code from './output/code';
|
||||
import { getCommandName } from './pkg-name';
|
||||
@@ -13,6 +14,7 @@ export class APIError extends Error {
|
||||
status: number;
|
||||
serverMessage: string;
|
||||
link?: string;
|
||||
action?: string;
|
||||
retryAfter: number | null | 'never';
|
||||
[key: string]: any;
|
||||
|
||||
@@ -771,17 +773,17 @@ export class CantParseJSONFile extends NowError<
|
||||
}
|
||||
}
|
||||
|
||||
export class ConflictingConfigFiles extends NowError<
|
||||
'CONFLICTING_CONFIG_FILES',
|
||||
{ files: string[] }
|
||||
> {
|
||||
export class ConflictingConfigFiles extends NowBuildError {
|
||||
files: string[];
|
||||
|
||||
constructor(files: string[]) {
|
||||
super({
|
||||
code: 'CONFLICTING_CONFIG_FILES',
|
||||
meta: { files },
|
||||
message:
|
||||
'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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { resolve, join } from 'path';
|
||||
import { resolve } from 'path';
|
||||
import ignore from 'ignore';
|
||||
import dockerignore from '@zeit/dockerignore';
|
||||
import _glob, { IOptions } from 'glob';
|
||||
import _glob, { IOptions as GlobOptions } from 'glob';
|
||||
import fs from 'fs-extra';
|
||||
import { getVercelIgnore } from '@vercel/client';
|
||||
import IGNORED from './ignored';
|
||||
@@ -12,11 +12,11 @@ import { NowConfig } from './dev/types';
|
||||
|
||||
type NullableString = string | null;
|
||||
|
||||
const flatten = (
|
||||
function flatten(
|
||||
arr: NullableString[] | NullableString[][],
|
||||
res: NullableString[] = []
|
||||
) => {
|
||||
for (let cur of arr) {
|
||||
): NullableString[] {
|
||||
for (const cur of arr) {
|
||||
if (Array.isArray(cur)) {
|
||||
flatten(cur, res);
|
||||
} else {
|
||||
@@ -24,21 +24,17 @@ const flatten = (
|
||||
}
|
||||
}
|
||||
return res;
|
||||
};
|
||||
}
|
||||
|
||||
const glob = async function(pattern: string, options: IOptions) {
|
||||
return new Promise<string[]>((resolve, reject) => {
|
||||
_glob(pattern, options, (error, files) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(files);
|
||||
}
|
||||
async function glob(pattern: string, options: GlobOptions): Promise<string[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
_glob(pattern, options, (err, files) => {
|
||||
err ? reject(err) : resolve(files);
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
interface WalkSyncOptions {
|
||||
interface WalkOptions {
|
||||
output: Output;
|
||||
}
|
||||
|
||||
@@ -51,27 +47,27 @@ interface WalkSyncOptions {
|
||||
* - `output` {Object} "output" helper object
|
||||
* @returns {Array}
|
||||
*/
|
||||
const walkSync = async (
|
||||
async function walk(
|
||||
dir: string,
|
||||
path: string,
|
||||
filelist: string[] = [],
|
||||
opts: WalkSyncOptions
|
||||
) => {
|
||||
opts: WalkOptions
|
||||
) {
|
||||
const { debug } = opts.output;
|
||||
const dirc = await fs.readdir(asAbsolute(dir, path));
|
||||
for (let file of dirc) {
|
||||
file = asAbsolute(file, dir);
|
||||
try {
|
||||
const file_stat = await fs.stat(file);
|
||||
filelist = file_stat.isDirectory()
|
||||
? await walkSync(file, path, filelist, opts)
|
||||
const fileStat = await fs.stat(file);
|
||||
filelist = fileStat.isDirectory()
|
||||
? await walk(file, path, filelist, opts)
|
||||
: filelist.concat(file);
|
||||
} catch (e) {
|
||||
debug(`Ignoring invalid file ${file}`);
|
||||
}
|
||||
}
|
||||
return filelist;
|
||||
};
|
||||
}
|
||||
|
||||
interface FilesInWhitelistOptions {
|
||||
output: Output;
|
||||
@@ -85,7 +81,7 @@ interface FilesInWhitelistOptions {
|
||||
* - `output` {Object} "output" helper object
|
||||
* @returns {Array} the expanded list of whitelisted files.
|
||||
*/
|
||||
const getFilesInWhitelist = async function(
|
||||
const getFilesInWhitelist = async function (
|
||||
whitelist: string[],
|
||||
path: string,
|
||||
opts: FilesInWhitelistOptions
|
||||
@@ -97,10 +93,10 @@ const getFilesInWhitelist = async function(
|
||||
whitelist.map(async (file: string) => {
|
||||
file = asAbsolute(file, path);
|
||||
try {
|
||||
const file_stat = await fs.stat(file);
|
||||
if (file_stat.isDirectory()) {
|
||||
const dir_files = await walkSync(file, path, [], opts);
|
||||
files.push(...dir_files);
|
||||
const fileStat = await fs.stat(file);
|
||||
if (fileStat.isDirectory()) {
|
||||
const dirFiles = await walk(file, path, [], opts);
|
||||
files.push(...dirFiles);
|
||||
} else {
|
||||
files.push(file);
|
||||
}
|
||||
@@ -117,7 +113,7 @@ const getFilesInWhitelist = async function(
|
||||
* because ignore doesn't like them :|
|
||||
*/
|
||||
|
||||
const clearRelative = function(str: string) {
|
||||
const clearRelative = function (str: string) {
|
||||
return str.replace(/(\n|^)\.\//g, '$1');
|
||||
};
|
||||
|
||||
@@ -127,7 +123,7 @@ const clearRelative = function(str: string) {
|
||||
* @return {String} results or `''`
|
||||
*/
|
||||
|
||||
const maybeRead = async function<T>(path: string, default_: T) {
|
||||
const maybeRead = async function <T>(path: string, default_: T) {
|
||||
try {
|
||||
return await fs.readFile(path, 'utf8');
|
||||
} catch (err) {
|
||||
@@ -143,7 +139,7 @@ const maybeRead = async function<T>(path: string, default_: T) {
|
||||
* @param {String} parent full path
|
||||
*/
|
||||
|
||||
const asAbsolute = function(path: string, parent: string) {
|
||||
const asAbsolute = function (path: string, parent: string) {
|
||||
if (path[0] === '/') {
|
||||
return path;
|
||||
}
|
||||
@@ -272,7 +268,7 @@ export async function npm(
|
||||
const search = Array.prototype.concat.apply(
|
||||
[],
|
||||
await Promise.all(
|
||||
search_.map(file =>
|
||||
search_.map((file) =>
|
||||
glob(file, { cwd: path, absolute: true, dot: true })
|
||||
)
|
||||
)
|
||||
@@ -364,7 +360,7 @@ export async function docker(
|
||||
const search_ = ['.'];
|
||||
|
||||
// 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
|
||||
const dockerIgnore = await maybeRead(resolve(path, '.dockerignore'), null);
|
||||
@@ -382,7 +378,7 @@ export async function docker(
|
||||
.createFilter();
|
||||
|
||||
const prefixLength = path.length + 1;
|
||||
const accepts = function(file: string) {
|
||||
const accepts = function (file: string) {
|
||||
const relativePath = file.substr(prefixLength);
|
||||
|
||||
if (relativePath === '') {
|
||||
@@ -415,24 +411,6 @@ export async function docker(
|
||||
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 {
|
||||
accepts: (file: string) => boolean;
|
||||
output: Output;
|
||||
@@ -482,7 +460,7 @@ async function explode(
|
||||
if (s.isDirectory()) {
|
||||
const all = await fs.readdir(file);
|
||||
/* 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>;
|
||||
/* eslint-enable no-use-before-define */
|
||||
}
|
||||
@@ -494,7 +472,7 @@ async function explode(
|
||||
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);
|
||||
return flatten(arrayOfArrays).filter(notNull);
|
||||
}
|
||||
|
||||
@@ -145,6 +145,7 @@ export default class Now extends EventEmitter {
|
||||
regions,
|
||||
target: target || undefined,
|
||||
projectSettings,
|
||||
source: 'cli',
|
||||
};
|
||||
|
||||
// Ignore specific items from vercel.json
|
||||
|
||||
@@ -44,8 +44,8 @@ export default async function editProjectSettings(
|
||||
|
||||
output.print(
|
||||
!framework.slug
|
||||
? `No framework detected. Default project settings:\n`
|
||||
: `Auto-detected project settings (${chalk.bold(framework.name)}):\n`
|
||||
? `No framework detected. Default Project Settings:\n`
|
||||
: `Auto-detected Project Settings (${chalk.bold(framework.name)}):\n`
|
||||
);
|
||||
|
||||
settings.framework = framework.slug;
|
||||
|
||||
@@ -2,6 +2,7 @@ import chalk from 'chalk';
|
||||
import boxen from 'boxen';
|
||||
import { format } from 'util';
|
||||
import { Console } from 'console';
|
||||
import renderLink from './link';
|
||||
import wait from './wait';
|
||||
|
||||
export type Output = ReturnType<typeof createOutput>;
|
||||
@@ -26,7 +27,8 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
|
||||
function warn(
|
||||
str: string,
|
||||
slug: string | null = null,
|
||||
link: string | null = null
|
||||
link: string | null = null,
|
||||
action: string = 'Learn More'
|
||||
) {
|
||||
const prevTerm = process.env.TERM;
|
||||
|
||||
@@ -41,7 +43,7 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
|
||||
boxen(
|
||||
chalk.bold.yellow('WARN! ') +
|
||||
str +
|
||||
(details ? `\nMore details: ${details}` : ''),
|
||||
(details ? `\n${action}: ${renderLink(details)}` : ''),
|
||||
{
|
||||
padding: {
|
||||
top: 0,
|
||||
@@ -64,17 +66,21 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
|
||||
|
||||
function error(
|
||||
str: string,
|
||||
slug: string | null = null,
|
||||
link: string | null = null,
|
||||
action = 'More details'
|
||||
slug?: string,
|
||||
link?: string,
|
||||
action = 'Learn More'
|
||||
) {
|
||||
print(`${chalk.red(`Error!`)} ${str}\n`);
|
||||
const details = slug ? `https://err.sh/now/${slug}` : link;
|
||||
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) {
|
||||
print(`${chalk.cyan('> Ready!')} ${str}\n`);
|
||||
}
|
||||
@@ -107,8 +113,6 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
|
||||
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 = {
|
||||
_times: new Map(),
|
||||
log(a: string, ...args: string[]) {
|
||||
@@ -135,6 +139,7 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
|
||||
log,
|
||||
warn,
|
||||
error,
|
||||
prettyError,
|
||||
ready,
|
||||
success,
|
||||
debug,
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import chalk from 'chalk';
|
||||
import { metrics, shouldCollectMetrics } from '../metrics';
|
||||
import { APIError } from '../errors-ts';
|
||||
import renderLink from './link';
|
||||
|
||||
const metric = metrics();
|
||||
|
||||
export default function error(...input: string[] | [APIError]) {
|
||||
let messages = input;
|
||||
if (typeof input[0] === 'object') {
|
||||
const { slug, message, link } = input[0];
|
||||
const { slug, message, link, action = 'Learn More' } = input[0];
|
||||
messages = [message];
|
||||
if (slug) {
|
||||
messages.push(`> More details: https://err.sh/now/${slug}`);
|
||||
} else if (link) {
|
||||
messages.push(`> More details: ${link}`);
|
||||
const details = slug ? `https://err.sh/now/${slug}` : link;
|
||||
if (details) {
|
||||
messages.push(`${chalk.bold(action)}: ${renderLink(details)}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import chalk from 'chalk';
|
||||
import { prependEmoji, emoji } from '../emoji';
|
||||
import AJV from 'ajv';
|
||||
import { isDirectory } from '../config/global-path';
|
||||
import { getPlatformEnv } from '@vercel/build-utils';
|
||||
import { NowBuildError, getPlatformEnv } from '@vercel/build-utils';
|
||||
|
||||
const readFile = promisify(fs.readFile);
|
||||
const writeFile = promisify(fs.writeFile);
|
||||
@@ -49,9 +49,12 @@ export function getVercelDirectory(cwd: string = process.cwd()): string {
|
||||
const possibleDirs = [join(cwd, VERCEL_DIR), join(cwd, VERCEL_DIR_FALLBACK)];
|
||||
const existingDirs = possibleDirs.filter(d => isDirectory(d));
|
||||
if (existingDirs.length > 1) {
|
||||
throw new Error(
|
||||
'Both `.vercel` and `.now` directories exist. Please remove the `.now` directory.'
|
||||
);
|
||||
throw new NowBuildError({
|
||||
code: 'CONFLICTING_CONFIG_DIRECTORIES',
|
||||
message:
|
||||
'Both `.vercel` and `.now` directories exist. Please remove the `.now` directory.',
|
||||
link: 'https://vercel.link/combining-old-and-new-config',
|
||||
});
|
||||
}
|
||||
return existingDirs[0] || possibleDirs[0];
|
||||
}
|
||||
@@ -70,7 +73,7 @@ async function getLinkFromDir(dir: string): Promise<ProjectLink | null> {
|
||||
|
||||
if (!ajv.validate(linkSchema, link)) {
|
||||
throw new Error(
|
||||
`Project settings are invalid. To link your project again, remove the ${dir} directory.`
|
||||
`Project Settings are invalid. To link your project again, remove the ${dir} directory.`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -84,7 +87,7 @@ async function getLinkFromDir(dir: string): Promise<ProjectLink | null> {
|
||||
// link file can't be read
|
||||
if (error.name === 'SyntaxError') {
|
||||
throw new Error(
|
||||
`Project settings could not be retrieved. To link your project again, remove the ${dir} directory.`
|
||||
`Project Settings could not be retrieved. To link your project again, remove the ${dir} directory.`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ export default async function responseError(
|
||||
fallbackMessage = null,
|
||||
parsedBody = {}
|
||||
) {
|
||||
let message;
|
||||
let bodyError;
|
||||
|
||||
if (res.status >= 400 && res.status < 500) {
|
||||
@@ -20,12 +19,9 @@ export default async function responseError(
|
||||
|
||||
// Some APIs wrongly return `err` instead of `error`
|
||||
bodyError = body.error || body.err || body;
|
||||
message = bodyError.message;
|
||||
}
|
||||
|
||||
if (message == null) {
|
||||
message = fallbackMessage === null ? 'Response Error' : fallbackMessage;
|
||||
}
|
||||
const msg = bodyError?.message || fallbackMessage || 'Response Error';
|
||||
|
||||
return new APIError(message, res, bodyError);
|
||||
return new APIError(msg, res, bodyError);
|
||||
}
|
||||
|
||||
283
packages/now-cli/test/dev-validate.unit.js
vendored
Normal file
283
packages/now-cli/test/dev-validate.unit.js
vendored
Normal file
@@ -0,0 +1,283 @@
|
||||
import test from 'ava';
|
||||
import { validateConfig } from '../src/util/dev/validate';
|
||||
|
||||
test('[dev-validate] should not error with empty config', async (t) => {
|
||||
const config = {};
|
||||
const error = validateConfig(config);
|
||||
t.deepEqual(error, null);
|
||||
});
|
||||
|
||||
test('[dev-validate] should not error with complete config', async (t) => {
|
||||
const config = {
|
||||
version: 2,
|
||||
public: true,
|
||||
regions: ['sfo1', 'iad1'],
|
||||
cleanUrls: true,
|
||||
headers: [{ source: '/', headers: [{ key: 'x-id', value: '123' }] }],
|
||||
rewrites: [{ source: '/help', destination: '/support' }],
|
||||
redirects: [{ source: '/kb', destination: 'https://example.com' }],
|
||||
trailingSlash: false,
|
||||
functions: { 'api/user.go': { memory: 128, maxDuration: 5 } },
|
||||
};
|
||||
const error = validateConfig(config);
|
||||
t.deepEqual(error, null);
|
||||
});
|
||||
|
||||
test('[dev-validate] should not error with builds and routes', async (t) => {
|
||||
const config = {
|
||||
builds: [{ src: 'api/index.js', use: '@vercel/node' }],
|
||||
routes: [{ src: '/(.*)', dest: '/api/index.js' }],
|
||||
};
|
||||
const error = validateConfig(config);
|
||||
t.deepEqual(error, null);
|
||||
});
|
||||
|
||||
test('[dev-validate] should error with invalid rewrites due to additional property and offer suggestion', async (t) => {
|
||||
const config = {
|
||||
rewrites: [{ src: '/(.*)', dest: '/api/index.js' }],
|
||||
};
|
||||
const error = validateConfig(config);
|
||||
t.deepEqual(
|
||||
error.message,
|
||||
'Invalid vercel.json - `rewrites[0]` should NOT have additional property `src`. Did you mean `source`?'
|
||||
);
|
||||
t.deepEqual(
|
||||
error.link,
|
||||
'https://vercel.com/docs/configuration#project/rewrites'
|
||||
);
|
||||
});
|
||||
|
||||
test('[dev-validate] should error with invalid routes due to additional property and offer suggestion', async (t) => {
|
||||
const config = {
|
||||
routes: [{ source: '/(.*)', destination: '/api/index.js' }],
|
||||
};
|
||||
const error = validateConfig(config);
|
||||
t.deepEqual(
|
||||
error.message,
|
||||
'Invalid vercel.json - `routes[0]` should NOT have additional property `source`. Did you mean `src`?'
|
||||
);
|
||||
t.deepEqual(
|
||||
error.link,
|
||||
'https://vercel.com/docs/configuration#project/routes'
|
||||
);
|
||||
});
|
||||
|
||||
test('[dev-validate] should error with invalid routes array type', async (t) => {
|
||||
const config = {
|
||||
routes: { src: '/(.*)', dest: '/api/index.js' },
|
||||
};
|
||||
const error = validateConfig(config);
|
||||
t.deepEqual(error.message, 'Invalid vercel.json - `routes` should be array.');
|
||||
t.deepEqual(
|
||||
error.link,
|
||||
'https://vercel.com/docs/configuration#project/routes'
|
||||
);
|
||||
});
|
||||
|
||||
test('[dev-validate] should error with invalid redirects array object', async (t) => {
|
||||
const config = {
|
||||
redirects: [
|
||||
{
|
||||
/* intentionally empty */
|
||||
},
|
||||
],
|
||||
};
|
||||
const error = validateConfig(config);
|
||||
t.deepEqual(
|
||||
error.message,
|
||||
'Invalid vercel.json - `redirects[0]` missing required property `source`.'
|
||||
);
|
||||
t.deepEqual(
|
||||
error.link,
|
||||
'https://vercel.com/docs/configuration#project/redirects'
|
||||
);
|
||||
});
|
||||
|
||||
test('[dev-validate] should error with invalid redirects.permanent poperty', async (t) => {
|
||||
const config = {
|
||||
redirects: [{ source: '/', destination: '/go', permanent: 'yes' }],
|
||||
};
|
||||
const error = validateConfig(config);
|
||||
t.deepEqual(
|
||||
error.message,
|
||||
'Invalid vercel.json - `redirects[0].permanent` should be boolean.'
|
||||
);
|
||||
t.deepEqual(
|
||||
error.link,
|
||||
'https://vercel.com/docs/configuration#project/redirects'
|
||||
);
|
||||
});
|
||||
|
||||
test('[dev-validate] should error with invalid cleanUrls type', async (t) => {
|
||||
const config = {
|
||||
cleanUrls: 'true',
|
||||
};
|
||||
const error = validateConfig(config);
|
||||
t.deepEqual(
|
||||
error.message,
|
||||
'Invalid vercel.json - `cleanUrls` should be boolean.'
|
||||
);
|
||||
t.deepEqual(
|
||||
error.link,
|
||||
'https://vercel.com/docs/configuration#project/cleanurls'
|
||||
);
|
||||
});
|
||||
|
||||
test('[dev-validate] should error with invalid trailingSlash type', async (t) => {
|
||||
const config = {
|
||||
trailingSlash: [true],
|
||||
};
|
||||
const error = validateConfig(config);
|
||||
t.deepEqual(
|
||||
error.message,
|
||||
'Invalid vercel.json - `trailingSlash` should be boolean.'
|
||||
);
|
||||
t.deepEqual(
|
||||
error.link,
|
||||
'https://vercel.com/docs/configuration#project/trailingslash'
|
||||
);
|
||||
});
|
||||
|
||||
test('[dev-validate] should error with invalid headers property', async (t) => {
|
||||
const config = {
|
||||
headers: [{ 'Content-Type': 'text/html' }],
|
||||
};
|
||||
const error = validateConfig(config);
|
||||
t.deepEqual(
|
||||
error.message,
|
||||
'Invalid vercel.json - `headers[0]` should NOT have additional property `Content-Type`. Please remove it.'
|
||||
);
|
||||
t.deepEqual(
|
||||
error.link,
|
||||
'https://vercel.com/docs/configuration#project/headers'
|
||||
);
|
||||
});
|
||||
|
||||
test('[dev-validate] should error with invalid headers.source type', async (t) => {
|
||||
const config = {
|
||||
headers: [{ source: [{ 'Content-Type': 'text/html' }] }],
|
||||
};
|
||||
const error = validateConfig(config);
|
||||
t.deepEqual(
|
||||
error.message,
|
||||
'Invalid vercel.json - `headers[0].source` should be string.'
|
||||
);
|
||||
t.deepEqual(
|
||||
error.link,
|
||||
'https://vercel.com/docs/configuration#project/headers'
|
||||
);
|
||||
});
|
||||
|
||||
test('[dev-validate] should error with invalid headers additional property', async (t) => {
|
||||
const config = {
|
||||
headers: [{ source: '/', stuff: [{ 'Content-Type': 'text/html' }] }],
|
||||
};
|
||||
const error = validateConfig(config);
|
||||
t.deepEqual(
|
||||
error.message,
|
||||
'Invalid vercel.json - `headers[0]` should NOT have additional property `stuff`. Please remove it.'
|
||||
);
|
||||
t.deepEqual(
|
||||
error.link,
|
||||
'https://vercel.com/docs/configuration#project/headers'
|
||||
);
|
||||
});
|
||||
|
||||
test('[dev-validate] should error with invalid headers wrong nested headers type', async (t) => {
|
||||
const config = {
|
||||
headers: [{ source: '/', headers: [{ 'Content-Type': 'text/html' }] }],
|
||||
};
|
||||
const error = validateConfig(config);
|
||||
t.deepEqual(
|
||||
error.message,
|
||||
'Invalid vercel.json - `headers[0].headers[0]` should NOT have additional property `Content-Type`. Please remove it.'
|
||||
);
|
||||
t.deepEqual(
|
||||
error.link,
|
||||
'https://vercel.com/docs/configuration#project/headers'
|
||||
);
|
||||
});
|
||||
|
||||
test('[dev-validate] should error with invalid headers wrong nested headers additional property', async (t) => {
|
||||
const config = {
|
||||
headers: [
|
||||
{ source: '/', headers: [{ key: 'Content-Type', val: 'text/html' }] },
|
||||
],
|
||||
};
|
||||
const error = validateConfig(config);
|
||||
t.deepEqual(
|
||||
error.message,
|
||||
'Invalid vercel.json - `headers[0].headers[0]` should NOT have additional property `val`. Please remove it.'
|
||||
);
|
||||
t.deepEqual(
|
||||
error.link,
|
||||
'https://vercel.com/docs/configuration#project/headers'
|
||||
);
|
||||
});
|
||||
|
||||
test('[dev-validate] should error with too many redirects', async (t) => {
|
||||
const config = {
|
||||
redirects: Array.from({ length: 5000 }).map((_, i) => ({
|
||||
source: `/${i}`,
|
||||
destination: `/v/${i}`,
|
||||
})),
|
||||
};
|
||||
const error = validateConfig(config);
|
||||
t.deepEqual(
|
||||
error.message,
|
||||
'Invalid vercel.json - `redirects` should NOT have more than 1024 items.'
|
||||
);
|
||||
t.deepEqual(
|
||||
error.link,
|
||||
'https://vercel.com/docs/configuration#project/redirects'
|
||||
);
|
||||
});
|
||||
|
||||
test('[dev-validate] should error with too many nested headers', async (t) => {
|
||||
const config = {
|
||||
headers: [
|
||||
{
|
||||
source: '/',
|
||||
headers: [{ key: `x-id`, value: `123` }],
|
||||
},
|
||||
{
|
||||
source: '/too-many',
|
||||
headers: Array.from({ length: 5000 }).map((_, i) => ({
|
||||
key: `${i}`,
|
||||
value: `${i}`,
|
||||
})),
|
||||
},
|
||||
],
|
||||
};
|
||||
const error = validateConfig(config);
|
||||
t.deepEqual(
|
||||
error.message,
|
||||
'Invalid vercel.json - `headers[1].headers` should NOT have more than 1024 items.'
|
||||
);
|
||||
t.deepEqual(
|
||||
error.link,
|
||||
'https://vercel.com/docs/configuration#project/headers'
|
||||
);
|
||||
});
|
||||
|
||||
test('[dev-validate] should error with "functions" and "builds"', async (t) => {
|
||||
const config = {
|
||||
builds: [
|
||||
{
|
||||
src: 'index.html',
|
||||
use: '@vercel/static',
|
||||
},
|
||||
],
|
||||
functions: {
|
||||
'api/test.js': {
|
||||
memory: 1024,
|
||||
},
|
||||
},
|
||||
};
|
||||
const error = validateConfig(config);
|
||||
t.deepEqual(
|
||||
error.message,
|
||||
'The `functions` property cannot be used in conjunction with the `builds` property. Please remove one of them.'
|
||||
);
|
||||
t.deepEqual(error.link, 'https://vercel.link/functions-and-builds');
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
proof goes here
|
||||
@@ -3,7 +3,7 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "gridsome build",
|
||||
"develop": "gridsome develop",
|
||||
"dev": "gridsome develop -p $PORT",
|
||||
"explore": "gridsome explore"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<Layout>
|
||||
<h1>Not Found</h1>
|
||||
<p>This is a Custom Gridsome 404.</p>
|
||||
</Layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
metaInfo: {
|
||||
title: 'Not Found'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<Layout>
|
||||
|
||||
|
||||
<!-- Learn how to use images here: https://gridsome.org/docs/images -->
|
||||
<g-image alt="Example image" src="~/favicon.png" width="135" />
|
||||
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
|
||||
<h1>Gridsome on Vercel</h1>
|
||||
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Pariatur excepturi labore tempore expedita, et iste tenetur suscipit explicabo! Dolores, aperiam non officia eos quod asperiores
|
||||
</p>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"redirects": [{ "source": "/support", "destination": "/about?ref=support" }]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<h1>Contact Us</h1>
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"rewrites": [{ "source": "/support", "destination": "/contact.html" }]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default function Contact() {
|
||||
return <h1>Contact Page</h1>;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"rewrites": [{ "source": "/support", "destination": "/contact" }]
|
||||
}
|
||||
1
packages/now-cli/test/dev/fixtures/force-module-commonjs/.gitignore
vendored
Normal file
1
packages/now-cli/test/dev/fixtures/force-module-commonjs/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.vercel
|
||||
@@ -0,0 +1,3 @@
|
||||
export default function(req, res) {
|
||||
res.end('Force "module: commonjs" JavaScript with ES Modules API endpoint');
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { IncomingMessage, ServerResponse } from 'http';
|
||||
|
||||
export default function(req: IncomingMessage, res: ServerResponse) {
|
||||
res.end('Force "module: commonjs" TypeScript API endpoint');
|
||||
}
|
||||
2
packages/now-cli/test/dev/fixtures/force-module-commonjs/next-env.d.ts
vendored
Normal file
2
packages/now-cli/test/dev/fixtures/force-module-commonjs/next-env.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/types/global" />
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "force-module-commonjs",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next",
|
||||
"build": "next build",
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "^9.3.4",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "14.0.9",
|
||||
"@types/react": "^16.9.32",
|
||||
"typescript": "^3.8.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default function () {
|
||||
return <div>Force "module: commonjs" test page</div>;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"target": "esnext",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"paths": {
|
||||
"@components/*": ["components/*"],
|
||||
"@lib/*": ["lib/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "api"],
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
|
||||
}
|
||||
5633
packages/now-cli/test/dev/fixtures/force-module-commonjs/yarn.lock
Normal file
5633
packages/now-cli/test/dev/fixtures/force-module-commonjs/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
1
packages/now-cli/test/dev/fixtures/go/.gitignore
vendored
Normal file
1
packages/now-cli/test/dev/fixtures/go/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.vercel
|
||||
10
packages/now-cli/test/dev/fixtures/go/api/[segment].go
Normal file
10
packages/now-cli/test/dev/fixtures/go/api/[segment].go
Normal file
@@ -0,0 +1,10 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Req Path: %s", r.URL.Path)
|
||||
}
|
||||
10
packages/now-cli/test/dev/fixtures/go/api/another.go
Normal file
10
packages/now-cli/test/dev/fixtures/go/api/another.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package another
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Another(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "This is another page")
|
||||
}
|
||||
10
packages/now-cli/test/dev/fixtures/go/api/index.go
Normal file
10
packages/now-cli/test/dev/fixtures/go/api/index.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "This is the index page")
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default (req, res) => {
|
||||
res.send(process.env);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
{"env":{"FOO":"bar"}}
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
assetPrefix: '/blog',
|
||||
env: {
|
||||
ASSET_PREFIX: '/blog',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "with-zones-blog",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"next": "latest",
|
||||
"react": "latest",
|
||||
"react-dom": "latest"
|
||||
},
|
||||
"license": "ISC"
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function Blog() {
|
||||
return (
|
||||
<div>
|
||||
<h3>This is our blog</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<Link href="/blog/post/[id]" as="/blog/post/1">
|
||||
<a>Post 1</a>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/blog/post/[id]" as="/blog/post/2">
|
||||
<a>Post 2</a>
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
<a href="/">Home</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
export default function Post() {
|
||||
const router = useRouter()
|
||||
return (
|
||||
<div>
|
||||
<h3>Post #{router.query.id}</h3>
|
||||
<p>Lorem ipsum</p>
|
||||
<Link href="/blog">
|
||||
<a>Back to blog</a>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
const Header = () => (
|
||||
<div>
|
||||
<h2>The Company</h2>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Header;
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "with-zones-home",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"next": "latest",
|
||||
"react": "latest",
|
||||
"react-dom": "latest"
|
||||
},
|
||||
"license": "ISC"
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
const About = ({ id }) => {
|
||||
return (
|
||||
<div>
|
||||
<p>This is the {id} page with static props.</p>
|
||||
<div>
|
||||
<Link href="/">
|
||||
<a>Go Back</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const getStaticProps = ({ params }) => {
|
||||
return { props: { id: params.id } };
|
||||
};
|
||||
export const getStaticPaths = () => ({ paths: ['/1/dynamic'], fallback: true });
|
||||
|
||||
export default About;
|
||||
@@ -0,0 +1,19 @@
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
const About = () => {
|
||||
const router = useRouter();
|
||||
const { id } = router.query;
|
||||
return (
|
||||
<div>
|
||||
<p>This is the {id} page without static props.</p>
|
||||
<div>
|
||||
<Link href="/">
|
||||
<a>Go Back</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default About;
|
||||
@@ -0,0 +1,19 @@
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
const About = () => {
|
||||
const router = useRouter();
|
||||
const { id } = router.query;
|
||||
return (
|
||||
<div>
|
||||
<p>This is the about static page.</p>
|
||||
<div>
|
||||
<Link href="/">
|
||||
<a>Go Back</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default About;
|
||||
@@ -0,0 +1,36 @@
|
||||
import Link from 'next/link';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const Header = dynamic(import('../components/Header'));
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div>
|
||||
<Header />
|
||||
<p>This is our homepage</p>
|
||||
<div>
|
||||
<a href="/blog">Blog</a>
|
||||
</div>
|
||||
<div>
|
||||
<Link href="/about">
|
||||
<a>About us</a>
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<Link href="/foo">
|
||||
<a>foo</a>
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<Link href="/1/dynamic">
|
||||
<a>1/dynamic</a>
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<Link href="/1/foo">
|
||||
<a>1/foo</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "with-zones-example",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"home",
|
||||
"blog"
|
||||
],
|
||||
"devDependencies": {
|
||||
"vercel": "canary"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"version": 2,
|
||||
"builds": [
|
||||
{ "src": "blog/package.json", "use": "@vercel/next@canary" },
|
||||
{ "src": "home/package.json", "use": "@vercel/next@canary" }
|
||||
],
|
||||
"routes": [
|
||||
{ "src": "/blog/_next(.*)", "dest": "blog/_next$1" },
|
||||
{ "src": "/blog(.*)", "dest": "blog/blog$1" },
|
||||
{ "src": "(.*)", "dest": "home$1" }
|
||||
]
|
||||
}
|
||||
5682
packages/now-cli/test/dev/fixtures/monorepo-dynamic-paths/yarn.lock
Normal file
5682
packages/now-cli/test/dev/fixtures/monorepo-dynamic-paths/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user