mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-23 09:59:12 +00:00
Compare commits
119 Commits
@vercel/bu
...
@vercel/bu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5380c12569 | ||
|
|
f11eb32b2c | ||
|
|
3d40e343ac | ||
|
|
80f525796f | ||
|
|
af4ad358f2 | ||
|
|
e6033d7a2d | ||
|
|
d3148dffaa | ||
|
|
30048cf4ff | ||
|
|
07c65fa5c8 | ||
|
|
411ec64986 | ||
|
|
e4d2cc704c | ||
|
|
38db720586 | ||
|
|
c26c7886be | ||
|
|
b0e5d308ca | ||
|
|
609b98cc73 | ||
|
|
e0ec6c792b | ||
|
|
b29db2fd1d | ||
|
|
b604ced99d | ||
|
|
b7fd69517e | ||
|
|
4c821a6fb5 | ||
|
|
5c4fb319af | ||
|
|
fbe9ea0750 | ||
|
|
fa0f1b90b4 | ||
|
|
2daa0e28d3 | ||
|
|
48358b4986 | ||
|
|
c59f44a63b | ||
|
|
ca27864201 | ||
|
|
6c81a87338 | ||
|
|
a7bef9387b | ||
|
|
65d6c5e1f4 | ||
|
|
c09355fdb3 | ||
|
|
d9ac4c45e1 | ||
|
|
c2ff95714f | ||
|
|
a51feb7a62 | ||
|
|
ff10918230 | ||
|
|
04bea1e3cd | ||
|
|
bc5e5e8a9c | ||
|
|
2e647175f5 | ||
|
|
1a4e1d2fdd | ||
|
|
49e2274d81 | ||
|
|
9af3938544 | ||
|
|
7b5bf061c2 | ||
|
|
ffb98781f1 | ||
|
|
3411fcbb68 | ||
|
|
55cfd33338 | ||
|
|
93a9e5bed3 | ||
|
|
b454021234 | ||
|
|
bb705cd091 | ||
|
|
0986f4bcb6 | ||
|
|
83f77223aa | ||
|
|
effda1fa6c | ||
|
|
2ab6a7ef0c | ||
|
|
200bf5e996 | ||
|
|
94ab2512e9 | ||
|
|
da3207278e | ||
|
|
8e10a82b43 | ||
|
|
d7731d191b | ||
|
|
0f5f99e667 | ||
|
|
93a7831943 | ||
|
|
0eacbeae11 | ||
|
|
e50417dc47 | ||
|
|
4840a25d30 | ||
|
|
785e91979d | ||
|
|
7a776c54b8 | ||
|
|
997031c53b | ||
|
|
fdee03a599 | ||
|
|
c5a93ecdad | ||
|
|
5a9391b7ce | ||
|
|
99e49473b8 | ||
|
|
de1cc6f9a7 | ||
|
|
08eedd8f34 | ||
|
|
5021a71a8e | ||
|
|
56671d7c2f | ||
|
|
5035fa537f | ||
|
|
63cc9009c8 | ||
|
|
ccf6e3c432 | ||
|
|
8d015e3138 | ||
|
|
42f2fa1a20 | ||
|
|
8397aac0e3 | ||
|
|
7bcdc144eb | ||
|
|
624da9170d | ||
|
|
fb5b013a03 | ||
|
|
0a4bb53a58 | ||
|
|
2fbd9c78e3 | ||
|
|
1ed2b7a57d | ||
|
|
88cd9ca3c3 | ||
|
|
ecea2ca4a3 | ||
|
|
3f132bc15b | ||
|
|
61d95094c0 | ||
|
|
f7c47975e3 | ||
|
|
7c96f9f9a5 | ||
|
|
a5c805b6eb | ||
|
|
ff2a22023d | ||
|
|
c6efc028aa | ||
|
|
96565da1cf | ||
|
|
afb5e7fc85 | ||
|
|
34cc987be8 | ||
|
|
55c60d30e6 | ||
|
|
eb993d47ac | ||
|
|
d2184628d1 | ||
|
|
65f0cc6797 | ||
|
|
c628c7b58e | ||
|
|
4e005274f9 | ||
|
|
482373f711 | ||
|
|
c80bb37e8d | ||
|
|
a7acd92ffd | ||
|
|
035720ca82 | ||
|
|
a6ae923a7a | ||
|
|
83c0711d6e | ||
|
|
231f18d56b | ||
|
|
9d73091d8c | ||
|
|
0ca3189f79 | ||
|
|
9ff5bb9cb3 | ||
|
|
45d05a603b | ||
|
|
6ef3b12fde | ||
|
|
b16f94098a | ||
|
|
be315bebcf | ||
|
|
5608a4c42c | ||
|
|
66458fe3e0 |
@@ -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)
|
||||
16
errors/next-legacy-routes-optimized-lambdas.md
Normal file
16
errors/next-legacy-routes-optimized-lambdas.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# `@vercel/next` Legacy Routes Optimized Lambdas Opt-out
|
||||
|
||||
#### Why This Warning Occurred
|
||||
|
||||
`@vercel/next` by default now bundles pages into optimized functions, minimizing bootup time and increasing overall application throughput.
|
||||
When legacy `routes` are added in `now.json` or `vercel.json`, they cause conflicts with this optimization, so it is opted-out.
|
||||
|
||||
#### Possible Ways to Fix It
|
||||
|
||||
Migrate from using legacy `routes` to the new `rewrites`, `redirects`, and `headers` configurations in your `now.json` or `vercel.json` file or leverage them directly in your `next.config.js` with the built-in [custom routes support](https://github.com/zeit/next.js/issues/9081)
|
||||
|
||||
### Useful Links
|
||||
|
||||
- [Rewrites Documentation](https://vercel.com/docs/configuration?query=rewrites#project/rewrites)
|
||||
- [Redirects Documentation](https://vercel.com/docs/configuration?query=rewrites#project/redirects)
|
||||
- [Headers Documentation](https://vercel.com/docs/configuration?query=rewrites#project/headers)
|
||||
4
examples/blitzjs/.babelrc.js
Normal file
4
examples/blitzjs/.babelrc.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
presets: ["next/babel"],
|
||||
plugins: [],
|
||||
}
|
||||
10
examples/blitzjs/.eslintrc.js
Normal file
10
examples/blitzjs/.eslintrc.js
Normal file
@@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
extends: ["react-app", "plugin:jsx-a11y/recommended"],
|
||||
plugins: ["jsx-a11y"],
|
||||
rules: {
|
||||
"import/no-anonymous-default-export": "error",
|
||||
"import/no-webpack-loader-syntax": "off",
|
||||
"react/react-in-jsx-scope": "off", // React is always in scope with Blitz
|
||||
"jsx-a11y/anchor-is-valid": "off", //Doesn't play well with Blitz/Next <Link> usage
|
||||
},
|
||||
}
|
||||
56
examples/blitzjs/.gitignore
vendored
Normal file
56
examples/blitzjs/.gitignore
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
# dependencies
|
||||
node_modules
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.pnp.*
|
||||
.npm
|
||||
web_modules/
|
||||
|
||||
# blitz
|
||||
/.blitz/
|
||||
/.next/
|
||||
*.sqlite
|
||||
.now
|
||||
.vercel
|
||||
.blitz-console-history
|
||||
blitz-log.log
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
|
||||
# local env files
|
||||
.env
|
||||
.envrc
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Testing
|
||||
coverage
|
||||
*.lcov
|
||||
.nyc_output
|
||||
lib-cov
|
||||
|
||||
# Caches
|
||||
*.tsbuildinfo
|
||||
.eslintcache
|
||||
.node_repl_history
|
||||
.yarn-integrity
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
1
examples/blitzjs/.npmrc
Normal file
1
examples/blitzjs/.npmrc
Normal file
@@ -0,0 +1 @@
|
||||
save-exact=true
|
||||
6
examples/blitzjs/.prettierignore
Normal file
6
examples/blitzjs/.prettierignore
Normal file
@@ -0,0 +1,6 @@
|
||||
.gitkeep
|
||||
.env
|
||||
*.ico
|
||||
*.lock
|
||||
db/migrations
|
||||
|
||||
21
examples/blitzjs/README.md
Normal file
21
examples/blitzjs/README.md
Normal file
@@ -0,0 +1,21 @@
|
||||

|
||||
|
||||
This is a [Blitz.js](https://blitzjs.com/) project bootstrapped with `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",
|
||||
|
||||
@@ -1,4 +1,31 @@
|
||||
[
|
||||
{
|
||||
"name": "Blitz.js",
|
||||
"slug": "blitzjs",
|
||||
"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 +667,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.3",
|
||||
"version": "0.0.16-canary.1",
|
||||
"main": "frameworks.json",
|
||||
"license": "UNLICENSED",
|
||||
"scripts": {
|
||||
@@ -9,9 +9,9 @@
|
||||
"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.5.2"
|
||||
"typescript": "3.9.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/build-utils",
|
||||
"version": "2.3.2-canary.2",
|
||||
"version": "2.4.1-canary.2",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
@@ -45,7 +45,7 @@
|
||||
"node-fetch": "2.2.0",
|
||||
"semver": "6.1.1",
|
||||
"ts-jest": "24.1.0",
|
||||
"typescript": "3.5.2",
|
||||
"typescript": "3.9.3",
|
||||
"yazl": "2.4.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ export async function detectBuilders(
|
||||
defaultRoutes: Route[] | null;
|
||||
redirectRoutes: Route[] | null;
|
||||
rewriteRoutes: Route[] | null;
|
||||
errorRoutes: Route[] | null;
|
||||
}> {
|
||||
const errors: ErrorResponse[] = [];
|
||||
const warnings: ErrorResponse[] = [];
|
||||
@@ -99,6 +100,7 @@ export async function detectBuilders(
|
||||
defaultRoutes: null,
|
||||
redirectRoutes: null,
|
||||
rewriteRoutes: null,
|
||||
errorRoutes: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -154,6 +156,7 @@ export async function detectBuilders(
|
||||
defaultRoutes: null,
|
||||
redirectRoutes: null,
|
||||
rewriteRoutes: null,
|
||||
errorRoutes: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -231,6 +234,7 @@ export async function detectBuilders(
|
||||
redirectRoutes: null,
|
||||
defaultRoutes: null,
|
||||
rewriteRoutes: null,
|
||||
errorRoutes: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -272,6 +276,7 @@ export async function detectBuilders(
|
||||
redirectRoutes: null,
|
||||
defaultRoutes: null,
|
||||
rewriteRoutes: null,
|
||||
errorRoutes: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -309,6 +314,7 @@ export async function detectBuilders(
|
||||
redirectRoutes: routesResult.redirectRoutes,
|
||||
defaultRoutes: routesResult.defaultRoutes,
|
||||
rewriteRoutes: routesResult.rewriteRoutes,
|
||||
errorRoutes: routesResult.errorRoutes,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -456,7 +462,7 @@ function detectFrontBuilder(
|
||||
});
|
||||
}
|
||||
|
||||
if (framework === 'nextjs') {
|
||||
if (framework === 'nextjs' || framework === 'blitzjs') {
|
||||
return { src: 'package.json', use: `@vercel/next${withTag}`, config };
|
||||
}
|
||||
|
||||
@@ -898,10 +904,17 @@ function getRouteResult(
|
||||
defaultRoutes: Route[];
|
||||
redirectRoutes: Route[];
|
||||
rewriteRoutes: Route[];
|
||||
errorRoutes: Route[];
|
||||
} {
|
||||
const defaultRoutes: Route[] = [];
|
||||
const redirectRoutes: Route[] = [];
|
||||
const rewriteRoutes: Route[] = [];
|
||||
const errorRoutes: Route[] = [];
|
||||
const isNextjs =
|
||||
frontendBuilder &&
|
||||
((frontendBuilder.use && frontendBuilder.use.startsWith('@vercel/next')) ||
|
||||
(frontendBuilder.config &&
|
||||
frontendBuilder.config.framework === 'nextjs'));
|
||||
|
||||
if (apiRoutes && apiRoutes.length > 0) {
|
||||
if (options.featHandleMiss) {
|
||||
@@ -968,10 +981,21 @@ function getRouteResult(
|
||||
});
|
||||
}
|
||||
|
||||
if (options.featHandleMiss && !isNextjs) {
|
||||
// Exclude Next.js to avoid overriding custom error page
|
||||
// https://nextjs.org/docs/advanced-features/custom-error-page
|
||||
errorRoutes.push({
|
||||
status: 404,
|
||||
src: '^/(?!.*api).*$',
|
||||
dest: options.cleanUrls ? '/404' : '/404.html',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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 "More details".
|
||||
*/
|
||||
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,
|
||||
@@ -531,6 +531,7 @@ describe('Test `detectBuilders`', () => {
|
||||
const files = ['api/user.php'];
|
||||
// @ts-ignore
|
||||
const { errors } = await detectBuilders(files, null, {
|
||||
// @ts-ignore
|
||||
functions,
|
||||
});
|
||||
|
||||
@@ -867,12 +868,14 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, pkg, { featHandleMiss });
|
||||
expect(builders![0].use).toBe('@vercel/next');
|
||||
expect(errors).toBe(null);
|
||||
expect(defaultRoutes).toStrictEqual([]);
|
||||
expect(redirectRoutes).toStrictEqual([]);
|
||||
expect(rewriteRoutes).toStrictEqual([]);
|
||||
expect(errorRoutes).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('package.json + no build + next', async () => {
|
||||
@@ -887,12 +890,14 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, pkg, { featHandleMiss });
|
||||
expect(builders![0].use).toBe('@vercel/next');
|
||||
expect(errors).toBe(null);
|
||||
expect(defaultRoutes).toStrictEqual([]);
|
||||
expect(redirectRoutes).toStrictEqual([]);
|
||||
expect(rewriteRoutes).toStrictEqual([]);
|
||||
expect(errorRoutes).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('package.json + no build', async () => {
|
||||
@@ -913,12 +918,15 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, null, { featHandleMiss });
|
||||
expect(builders).toBe(null);
|
||||
expect(errors).toBe(null);
|
||||
expect(defaultRoutes).toStrictEqual([]);
|
||||
expect(redirectRoutes).toStrictEqual([]);
|
||||
expect(rewriteRoutes).toStrictEqual([]);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('no package.json + public', async () => {
|
||||
@@ -929,6 +937,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, null, { featHandleMiss });
|
||||
expect(builders![1].use).toBe('@vercel/static');
|
||||
expect(errors).toBe(null);
|
||||
@@ -939,6 +948,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
expect(redirectRoutes).toStrictEqual([]);
|
||||
expect(rewriteRoutes!.length).toBe(1);
|
||||
expect((rewriteRoutes![0] as Source).status).toBe(404);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('no package.json + no build + raw static + api', async () => {
|
||||
@@ -949,6 +960,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, null, { featHandleMiss });
|
||||
expect(builders![0].use).toBe('@vercel/node');
|
||||
expect(builders![0].src).toBe('api/users.js');
|
||||
@@ -963,6 +975,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
expect(redirectRoutes).toStrictEqual([]);
|
||||
expect(rewriteRoutes!.length).toBe(1);
|
||||
expect((rewriteRoutes![0] as Source).status).toBe(404);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('package.json + no build + root + api', async () => {
|
||||
@@ -990,6 +1004,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, undefined, { featHandleMiss });
|
||||
expect(builders![0].use).toBe('@vercel/node');
|
||||
expect(builders![0].src).toBe('api/[endpoint]/[id].js');
|
||||
@@ -1002,6 +1017,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
expect(rewriteRoutes!.length).toBe(2);
|
||||
expect((rewriteRoutes![0] as Source).src).toBe('^/api/([^/]+)/([^/]+)$');
|
||||
expect((rewriteRoutes![1] as Source).status).toBe(404);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('api + next + public', async () => {
|
||||
@@ -1016,6 +1033,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, pkg, { featHandleMiss });
|
||||
expect(builders![0].use).toBe('@vercel/node');
|
||||
expect(builders![0].src).toBe('api/endpoint.js');
|
||||
@@ -1029,6 +1047,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
expect(redirectRoutes).toStrictEqual([]);
|
||||
expect(rewriteRoutes!.length).toBe(1);
|
||||
expect((rewriteRoutes![0] as Source).status).toBe(404);
|
||||
expect(errorRoutes).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('api + next + raw static', async () => {
|
||||
@@ -1043,6 +1062,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, pkg, { featHandleMiss });
|
||||
expect(builders![0].use).toBe('@vercel/node');
|
||||
expect(builders![0].src).toBe('api/endpoint.js');
|
||||
@@ -1056,6 +1076,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
expect(redirectRoutes).toStrictEqual([]);
|
||||
expect(rewriteRoutes!.length).toBe(1);
|
||||
expect((rewriteRoutes![0] as Source).status).toBe(404);
|
||||
expect(errorRoutes).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('api + raw static', async () => {
|
||||
@@ -1066,6 +1087,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, null, { featHandleMiss });
|
||||
expect(builders![0].use).toBe('@vercel/node');
|
||||
expect(builders![0].src).toBe('api/endpoint.js');
|
||||
@@ -1079,6 +1101,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
expect(redirectRoutes).toStrictEqual([]);
|
||||
expect(rewriteRoutes!.length).toBe(1);
|
||||
expect((rewriteRoutes![0] as Source).status).toBe(404);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('api + raw static + package.json no build script', async () => {
|
||||
@@ -1093,6 +1117,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, pkg, { featHandleMiss });
|
||||
expect(builders![0].use).toBe('@vercel/node');
|
||||
expect(builders![0].src).toBe('api/version.js');
|
||||
@@ -1106,6 +1131,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
expect(redirectRoutes).toStrictEqual([]);
|
||||
expect(rewriteRoutes!.length).toBe(1);
|
||||
expect((rewriteRoutes![0] as Source).status).toBe(404);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('api + public', async () => {
|
||||
@@ -1116,7 +1143,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
'README.md',
|
||||
];
|
||||
|
||||
const { builders } = await detectBuilders(files, undefined, {
|
||||
const { builders, errorRoutes } = await detectBuilders(files, undefined, {
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(builders![0].use).toBe('@vercel/node');
|
||||
@@ -1124,6 +1151,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
expect(builders![1].use).toBe('@vercel/static');
|
||||
expect(builders![1].src).toBe('public/**/*');
|
||||
expect(builders!.length).toBe(2);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('api go with test files', async () => {
|
||||
@@ -1142,21 +1171,26 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
'api/src/controllers/user.module_test.go',
|
||||
];
|
||||
|
||||
const { builders } = await detectBuilders(files, undefined, {
|
||||
const { builders, errorRoutes } = await detectBuilders(files, undefined, {
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(builders!.length).toBe(7);
|
||||
expect(builders!.some(b => b.src.endsWith('_test.go'))).toBe(false);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('just public', async () => {
|
||||
const files = ['public/index.html', 'public/favicon.ico', 'README.md'];
|
||||
|
||||
const { builders } = await detectBuilders(files, undefined, {
|
||||
const { builders, errorRoutes } = await detectBuilders(files, undefined, {
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(builders![0].src).toBe('public/**/*');
|
||||
expect(builders![0].use).toBe('@vercel/static');
|
||||
expect(builders!.length).toBe(1);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('next + public', async () => {
|
||||
@@ -1166,10 +1200,13 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
};
|
||||
const files = ['package.json', 'public/index.html', 'README.md'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg, { featHandleMiss });
|
||||
const { builders, errorRoutes } = await detectBuilders(files, pkg, {
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(builders![0].use).toBe('@vercel/next');
|
||||
expect(builders![0].src).toBe('package.json');
|
||||
expect(builders!.length).toBe(1);
|
||||
expect(errorRoutes!.length).toBe(0);
|
||||
});
|
||||
|
||||
it('nuxt', async () => {
|
||||
@@ -1179,10 +1216,14 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
};
|
||||
const files = ['package.json', 'pages/index.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg, { featHandleMiss });
|
||||
const { builders, errorRoutes } = await detectBuilders(files, pkg, {
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(builders![0].use).toBe('@vercel/static-build');
|
||||
expect(builders![0].src).toBe('package.json');
|
||||
expect(builders!.length).toBe(1);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('nuxt + tag canary', async () => {
|
||||
@@ -1192,23 +1233,29 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
};
|
||||
const files = ['package.json', 'pages/index.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg, {
|
||||
const { builders, errorRoutes } = await detectBuilders(files, pkg, {
|
||||
tag: 'canary',
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(builders![0].use).toBe('@vercel/static-build@canary');
|
||||
expect(builders![0].src).toBe('package.json');
|
||||
expect(builders!.length).toBe(1);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('package.json with no build + api', async () => {
|
||||
const pkg = { dependencies: { next: '9.0.0' } };
|
||||
const files = ['package.json', 'api/[endpoint].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg, { featHandleMiss });
|
||||
const { builders, errorRoutes } = await detectBuilders(files, pkg, {
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(builders![0].use).toBe('@vercel/node');
|
||||
expect(builders![0].src).toBe('api/[endpoint].js');
|
||||
expect(builders!.length).toBe(1);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('package.json with no build + public directory', async () => {
|
||||
@@ -1327,7 +1374,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
it('many static files + one api file', async () => {
|
||||
const files = Array.from({ length: 5000 }).map((_, i) => `file${i}.html`);
|
||||
files.push('api/index.ts');
|
||||
const { builders } = await detectBuilders(files, undefined, {
|
||||
const { builders, errorRoutes } = await detectBuilders(files, undefined, {
|
||||
featHandleMiss,
|
||||
});
|
||||
|
||||
@@ -1336,6 +1383,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
expect(builders![0].src).toBe('api/index.ts');
|
||||
expect(builders![1].use).toBe('@vercel/static');
|
||||
expect(builders![1].src).toBe('!{api/**,package.json}');
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('functions with nextjs', async () => {
|
||||
@@ -1489,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,
|
||||
@@ -1498,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,
|
||||
@@ -1530,6 +1579,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
const files = ['api/user.php'];
|
||||
// @ts-ignore
|
||||
const { errors } = await detectBuilders(files, null, {
|
||||
// @ts-ignore
|
||||
functions,
|
||||
featHandleMiss,
|
||||
});
|
||||
@@ -1630,6 +1680,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
|
||||
// @ts-ignore
|
||||
const { errors } = await detectBuilders(files, null, {
|
||||
// @ts-ignore
|
||||
functions,
|
||||
featHandleMiss,
|
||||
});
|
||||
@@ -1646,6 +1697,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
|
||||
// @ts-ignore: Since we test an invalid type
|
||||
const { errors } = await detectBuilders(files, null, {
|
||||
// @ts-ignore
|
||||
functions,
|
||||
featHandleMiss,
|
||||
});
|
||||
@@ -1681,6 +1733,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, null, {
|
||||
projectSettings,
|
||||
featHandleMiss,
|
||||
@@ -1693,6 +1746,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
expect(defaultRoutes).toStrictEqual([]);
|
||||
expect(redirectRoutes).toStrictEqual([]);
|
||||
expect(rewriteRoutes).toStrictEqual([]);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('Custom static output directory with api', async () => {
|
||||
@@ -1707,6 +1762,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, null, {
|
||||
projectSettings,
|
||||
featHandleMiss,
|
||||
@@ -1722,6 +1778,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
expect(redirectRoutes).toStrictEqual([]);
|
||||
expect(rewriteRoutes!.length).toBe(1);
|
||||
expect((rewriteRoutes![0] as Source).status).toBe(404);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('Framework with non-package.json entrypoint', async () => {
|
||||
@@ -1730,7 +1788,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
framework: 'hugo',
|
||||
};
|
||||
|
||||
const { builders } = await detectBuilders(files, null, {
|
||||
const { builders, errorRoutes } = await detectBuilders(files, null, {
|
||||
projectSettings,
|
||||
featHandleMiss,
|
||||
});
|
||||
@@ -1745,6 +1803,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
},
|
||||
},
|
||||
]);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('No framework, only package.json', async () => {
|
||||
@@ -1755,7 +1815,9 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg, { featHandleMiss });
|
||||
const { builders, errorRoutes } = await detectBuilders(files, pkg, {
|
||||
featHandleMiss,
|
||||
});
|
||||
|
||||
expect(builders).toEqual([
|
||||
{
|
||||
@@ -1766,13 +1828,15 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
},
|
||||
},
|
||||
]);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('Framework with an API', async () => {
|
||||
const files = ['config.rb', 'api/date.rb'];
|
||||
const projectSettings = { framework: 'middleman' };
|
||||
|
||||
const { builders } = await detectBuilders(files, null, {
|
||||
const { builders, errorRoutes } = await detectBuilders(files, null, {
|
||||
projectSettings,
|
||||
featHandleMiss,
|
||||
});
|
||||
@@ -1794,6 +1858,8 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
},
|
||||
},
|
||||
]);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('Error for non-api functions', async () => {
|
||||
@@ -1843,13 +1909,19 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
it('All static if `buildCommand` is an empty string with an `outputDirectory`', async () => {
|
||||
const files = ['out/index.html'];
|
||||
const projectSettings = { buildCommand: '', outputDirectory: 'out' };
|
||||
const { builders, errors } = await detectBuilders(files, null, {
|
||||
projectSettings,
|
||||
featHandleMiss,
|
||||
});
|
||||
const { builders, errors, errorRoutes } = await detectBuilders(
|
||||
files,
|
||||
null,
|
||||
{
|
||||
projectSettings,
|
||||
featHandleMiss,
|
||||
}
|
||||
);
|
||||
expect(errors).toBe(null);
|
||||
expect(builders![0]!.use).toBe('@vercel/static');
|
||||
expect(builders![0]!.src).toBe('out/**/*');
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('do not require build script when `buildCommand` is an empty string', async () => {
|
||||
@@ -2009,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 });
|
||||
@@ -2025,9 +2097,13 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
{
|
||||
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
|
||||
|
||||
const { defaultRoutes, rewriteRoutes } = await detectBuilders(files, null, {
|
||||
featHandleMiss,
|
||||
});
|
||||
const { defaultRoutes, rewriteRoutes, errorRoutes } = await detectBuilders(
|
||||
files,
|
||||
null,
|
||||
{
|
||||
featHandleMiss,
|
||||
}
|
||||
);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
{ handle: 'miss' },
|
||||
{
|
||||
@@ -2043,6 +2119,44 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
continue: true,
|
||||
},
|
||||
]);
|
||||
expect(errorRoutes).toStrictEqual([
|
||||
{
|
||||
status: 404,
|
||||
src: '^/(?!.*api).*$',
|
||||
dest: '/404.html',
|
||||
},
|
||||
]);
|
||||
|
||||
const pattern = new RegExp(errorRoutes![0].src!);
|
||||
|
||||
[
|
||||
'/',
|
||||
'/index.html',
|
||||
'/page.html',
|
||||
'/page',
|
||||
'/another/index.html',
|
||||
'/another/page.html',
|
||||
'/another/page',
|
||||
'/another/sub/index.html',
|
||||
'/another/sub/page.html',
|
||||
'/another/sub/page',
|
||||
].forEach(file => {
|
||||
expect(file).toMatch(pattern);
|
||||
});
|
||||
|
||||
[
|
||||
'/api',
|
||||
'/api/',
|
||||
'/api/index.html',
|
||||
'/api/page.html',
|
||||
'/api/page',
|
||||
'/api/sub',
|
||||
'/api/sub/index.html',
|
||||
'/api/sub/page.html',
|
||||
'/api/sub/page',
|
||||
].forEach(file => {
|
||||
expect(file).not.toMatch(pattern);
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
@@ -2279,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, {
|
||||
@@ -2326,6 +2440,7 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, null, options);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([]);
|
||||
@@ -2336,6 +2451,13 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
continue: true,
|
||||
},
|
||||
]);
|
||||
expect(errorRoutes).toStrictEqual([
|
||||
{
|
||||
status: 404,
|
||||
src: '^/(?!.*api).*$',
|
||||
dest: '/404',
|
||||
},
|
||||
]);
|
||||
|
||||
// expected redirect should match inputs
|
||||
const getLocation = createReplaceLocation(redirectRoutes);
|
||||
@@ -2570,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 {
|
||||
@@ -2819,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.8",
|
||||
"version": "19.1.2-canary.18",
|
||||
"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.2",
|
||||
"@vercel/go": "1.1.2-canary.0",
|
||||
"@vercel/next": "2.6.3-canary.4",
|
||||
"@vercel/node": "1.6.2-canary.4",
|
||||
"@vercel/python": "1.2.2-canary.1",
|
||||
"@vercel/ruby": "1.2.2-canary.0",
|
||||
"@vercel/static-build": "0.17.2-canary.0"
|
||||
"@vercel/build-utils": "2.4.1-canary.2",
|
||||
"@vercel/go": "1.1.3-canary.1",
|
||||
"@vercel/next": "2.6.9-canary.0",
|
||||
"@vercel/node": "1.7.2-canary.1",
|
||||
"@vercel/python": "1.2.2",
|
||||
"@vercel/ruby": "1.2.3-canary.0",
|
||||
"@vercel/static-build": "0.17.5-canary.0"
|
||||
},
|
||||
"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",
|
||||
@@ -182,7 +183,7 @@
|
||||
"tmp-promise": "1.0.3",
|
||||
"tree-kill": "1.2.1",
|
||||
"ts-node": "8.3.0",
|
||||
"typescript": "3.6.4",
|
||||
"typescript": "3.9.3",
|
||||
"universal-analytics": "0.4.20",
|
||||
"update-check": "1.5.3",
|
||||
"utility-types": "2.1.0",
|
||||
|
||||
@@ -75,6 +75,17 @@ async function main() {
|
||||
const dest = join(dirRoot, 'dist/runtimes');
|
||||
await cpy('**/*', dest, { parents: true, cwd: runtimes });
|
||||
|
||||
// Band-aid to delete stuff that `ncc` bundles, but it shouldn't:
|
||||
|
||||
// TypeScript definition files from `@vercel/build-utils`
|
||||
await remove(join(dirRoot, 'dist', 'dist'));
|
||||
|
||||
// The Readme and `package.json` from "config-chain" module
|
||||
await remove(join(dirRoot, 'dist', 'config-chain'));
|
||||
|
||||
// A bunch of source `.ts` files from CLI's `util` directory
|
||||
await remove(join(dirRoot, 'dist', 'util'));
|
||||
|
||||
console.log('Finished building `now-cli`');
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -251,7 +248,9 @@ export default async function main(
|
||||
if (argv['--name']) {
|
||||
output.print(
|
||||
`${prependEmoji(
|
||||
`The ${param('--name')} flag is deprecated (https://zeit.ink/1B)`,
|
||||
`The ${param(
|
||||
'--name'
|
||||
)} option is deprecated (https://vercel.link/name-flag)`,
|
||||
emoji('warning')
|
||||
)}\n`
|
||||
);
|
||||
@@ -389,7 +388,7 @@ export default async function main(
|
||||
`${prependEmoji(
|
||||
`The ${code('name')} property in ${highlight(
|
||||
localConfig[fileNameSymbol]
|
||||
)} is deprecated (https://zeit.ink/5F)`,
|
||||
)} is deprecated (https://vercel.link/name-prop)`,
|
||||
emoji('warning')
|
||||
)}\n`
|
||||
);
|
||||
@@ -573,8 +572,11 @@ export default async function main(
|
||||
|
||||
if (deployment instanceof Error) {
|
||||
output.error(
|
||||
`${deployment.message ||
|
||||
'An unexpected error occurred while deploying your project'} (http://zeit.ink/P4)`
|
||||
deployment.message ||
|
||||
'An unexpected error occurred while deploying your project',
|
||||
null,
|
||||
'https://vercel.link/help',
|
||||
'Contact Support'
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -701,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'],
|
||||
@@ -730,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';
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -257,7 +257,7 @@ const login = async ctx => {
|
||||
|
||||
output.print(
|
||||
`${prependEmoji(
|
||||
`Connect your Git Repositories to deploy every branch push automatically (https://zeit.ink/1X).`,
|
||||
`Connect your Git Repositories to deploy every branch push automatically (https://vercel.link/git).`,
|
||||
emoji('tip')
|
||||
)}\n`
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
@@ -114,10 +115,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;
|
||||
}
|
||||
|
||||
@@ -313,21 +314,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);
|
||||
@@ -529,7 +515,7 @@ const main = async argv_ => {
|
||||
|
||||
if (argv['--team']) {
|
||||
output.warn(
|
||||
`The ${param('--team')} flag is deprecated. Please use ${param(
|
||||
`The ${param('--team')} option is deprecated. Please use ${param(
|
||||
'--scope'
|
||||
)} instead.`
|
||||
);
|
||||
@@ -637,34 +623,19 @@ 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.`
|
||||
);
|
||||
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.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;
|
||||
}
|
||||
|
||||
@@ -675,9 +646,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 +673,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;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import path from 'path';
|
||||
import mri from 'mri';
|
||||
import { InvalidLocalConfig } from '../errors';
|
||||
import { ConflictingConfigFiles } from '../errors-ts';
|
||||
import { existsSync } from 'fs';
|
||||
|
||||
export default function getLocalPathConfig(prefix: string) {
|
||||
@@ -11,20 +12,30 @@ export default function getLocalPathConfig(prefix: string) {
|
||||
},
|
||||
});
|
||||
|
||||
// If `--local-config` flag was specified, then that takes priority
|
||||
const customPath = args['local-config'];
|
||||
|
||||
if (customPath && typeof customPath !== 'string') {
|
||||
throw new InvalidLocalConfig(customPath);
|
||||
if (customPath) {
|
||||
if (typeof customPath !== 'string') {
|
||||
throw new InvalidLocalConfig(customPath);
|
||||
}
|
||||
return path.resolve(prefix, customPath);
|
||||
}
|
||||
|
||||
const possibleConfigFiles = [
|
||||
path.join(prefix, 'vercel.json'),
|
||||
path.join(prefix, 'now.json'),
|
||||
];
|
||||
// Otherwise check for either `vercel.json` or `now.json`.
|
||||
// Throw an error if both exist.
|
||||
const vercelConfigPath = path.join(prefix, 'vercel.json');
|
||||
const nowConfigPath = path.join(prefix, 'now.json');
|
||||
|
||||
return (
|
||||
(customPath && path.resolve(prefix, customPath)) ||
|
||||
possibleConfigFiles.find(configFile => existsSync(configFile)) ||
|
||||
possibleConfigFiles[0]
|
||||
);
|
||||
const vercelConfigExists = existsSync(vercelConfigPath);
|
||||
const nowConfigExists = existsSync(nowConfigPath);
|
||||
|
||||
if (nowConfigExists && vercelConfigExists) {
|
||||
throw new ConflictingConfigFiles([vercelConfigPath, nowConfigPath]);
|
||||
}
|
||||
|
||||
if (nowConfigExists) {
|
||||
return nowConfigPath;
|
||||
}
|
||||
|
||||
return vercelConfigPath;
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
@@ -29,7 +29,7 @@ export function resolveRouteParameters(
|
||||
export function getRoutesTypes(routes: Route[] = []) {
|
||||
const handleMap = new Map<HandleValue | null, Route[]>();
|
||||
let prevHandle: HandleValue | null = null;
|
||||
routes.forEach(route => {
|
||||
routes.forEach((route) => {
|
||||
if (isHandler(route)) {
|
||||
prevHandle = route.handle;
|
||||
} else {
|
||||
@@ -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,8 +110,9 @@ 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;
|
||||
@@ -124,7 +121,6 @@ export default class DevServer {
|
||||
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 +135,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,6 +150,7 @@ 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;
|
||||
@@ -178,10 +176,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 +215,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 +230,17 @@ export default class DevServer {
|
||||
}
|
||||
}
|
||||
|
||||
// Update the build matches in case an entrypoint was created or deleted
|
||||
const nowConfig = await this.getNowConfig(false);
|
||||
|
||||
// Update the env vars configuration
|
||||
const [runEnv, buildEnv] = await Promise.all([
|
||||
this.getLocalEnv('.env', nowConfig.env),
|
||||
this.getLocalEnv('.env.build', nowConfig.build?.env),
|
||||
]);
|
||||
const allEnv = { ...buildEnv, ...runEnv };
|
||||
this.envConfigs = { buildEnv, runEnv, allEnv };
|
||||
|
||||
// Update the build matches in case an entrypoint was created or deleted
|
||||
await this.updateBuildMatches(nowConfig);
|
||||
|
||||
const filesChangedArray = [...filesChanged];
|
||||
@@ -279,18 +284,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 +380,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 +404,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 +439,9 @@ export default class DevServer {
|
||||
`Cleaning up "blockingBuildsPromise" after error: ${err}`
|
||||
);
|
||||
this.blockingBuildsPromise = null;
|
||||
if (err) {
|
||||
this.output.prettyError(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -517,54 +524,24 @@ export default class DevServer {
|
||||
return this.cachedNowConfig;
|
||||
}
|
||||
|
||||
const pkg = await this.getPackageJson();
|
||||
const configPath = getNowConfigPath(this.cwd);
|
||||
|
||||
// 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);
|
||||
this.output.prettyError(routeError);
|
||||
await this.exit();
|
||||
}
|
||||
config.routes = maybeRoutes || [];
|
||||
@@ -574,6 +551,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,
|
||||
@@ -581,10 +563,11 @@ export default class DevServer {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, pkg, {
|
||||
tag: getDistTag(cliPkg.version) === 'canary' ? 'canary' : 'latest',
|
||||
functions: config.functions,
|
||||
...(projectSettings ? { projectSettings } : {}),
|
||||
projectSettings: projectSettings || this.projectSettings,
|
||||
featHandleMiss,
|
||||
cleanUrls,
|
||||
trailingSlash,
|
||||
@@ -596,7 +579,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,19 +590,26 @@ export default class DevServer {
|
||||
config.builds = config.builds || [];
|
||||
config.builds.push(...builders);
|
||||
|
||||
const routes: Route[] = [];
|
||||
const { routes: nowConfigRoutes } = config;
|
||||
routes.push(...(redirectRoutes || []));
|
||||
routes.push(
|
||||
...appendRoutesToPhase({
|
||||
routes: nowConfigRoutes,
|
||||
newRoutes: rewriteRoutes,
|
||||
phase: 'filesystem',
|
||||
})
|
||||
);
|
||||
routes.push(...(defaultRoutes || []));
|
||||
config.routes = routes;
|
||||
delete config.functions;
|
||||
}
|
||||
|
||||
let routes: Route[] = [];
|
||||
const { routes: nowConfigRoutes } = config;
|
||||
routes.push(...(redirectRoutes || []));
|
||||
routes.push(
|
||||
...appendRoutesToPhase({
|
||||
routes: nowConfigRoutes,
|
||||
newRoutes: rewriteRoutes,
|
||||
phase: 'filesystem',
|
||||
})
|
||||
);
|
||||
routes = appendRoutesToPhase({
|
||||
routes,
|
||||
newRoutes: errorRoutes,
|
||||
phase: 'error',
|
||||
});
|
||||
routes.push(...(defaultRoutes || []));
|
||||
config.routes = routes;
|
||||
}
|
||||
|
||||
if (Array.isArray(config.builds)) {
|
||||
@@ -641,27 +631,35 @@ export default class DevServer {
|
||||
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(
|
||||
@@ -683,14 +681,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 {
|
||||
@@ -761,13 +757,11 @@ export default class DevServer {
|
||||
|
||||
// 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),
|
||||
this.getLocalEnv('.env.build', nowConfig.build?.env),
|
||||
]);
|
||||
const allEnv = { ...buildEnv, ...runEnv };
|
||||
Object.assign(process.env, allEnv);
|
||||
this.envConfigs = { buildEnv, runEnv, allEnv };
|
||||
|
||||
const opts = { output: this.output, isBuilds: true };
|
||||
@@ -796,11 +790,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);
|
||||
@@ -1141,6 +1135,7 @@ export default class DevServer {
|
||||
match: BuildMatch,
|
||||
requestPath: string | null,
|
||||
req: http.IncomingMessage | null,
|
||||
nowConfig: NowConfig,
|
||||
previousBuildResult?: BuildResult,
|
||||
filesChanged?: string[],
|
||||
filesRemoved?: string[]
|
||||
@@ -1156,10 +1151,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.
|
||||
@@ -1170,7 +1166,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,
|
||||
@@ -1251,7 +1249,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;
|
||||
@@ -1323,8 +1321,7 @@ export default class DevServer {
|
||||
const handleMap = getRoutesTypes(routes);
|
||||
const missRoutes = handleMap.get('miss') || [];
|
||||
const hitRoutes = handleMap.get('hit') || [];
|
||||
handleMap.delete('miss');
|
||||
handleMap.delete('hit');
|
||||
const errorRoutes = handleMap.get('error') || [];
|
||||
const phases: (HandleValue | null)[] = [null, 'filesystem'];
|
||||
|
||||
let routeResult: RouteResult | null = null;
|
||||
@@ -1342,6 +1339,7 @@ export default class DevServer {
|
||||
req.method,
|
||||
phaseRoutes,
|
||||
this,
|
||||
nowConfig,
|
||||
prevHeaders,
|
||||
missRoutes,
|
||||
phase
|
||||
@@ -1362,14 +1360,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 (
|
||||
@@ -1392,6 +1391,7 @@ export default class DevServer {
|
||||
req.method,
|
||||
missRoutes,
|
||||
this,
|
||||
nowConfig,
|
||||
routeResult.headers,
|
||||
[],
|
||||
'miss'
|
||||
@@ -1401,7 +1401,8 @@ export default class DevServer {
|
||||
this.buildMatches,
|
||||
this.files,
|
||||
routeResult.dest,
|
||||
this
|
||||
this,
|
||||
nowConfig
|
||||
);
|
||||
if (
|
||||
await this.exitWithStatus(
|
||||
@@ -1424,6 +1425,7 @@ export default class DevServer {
|
||||
req.method,
|
||||
hitRoutes,
|
||||
this,
|
||||
nowConfig,
|
||||
routeResult.headers,
|
||||
[],
|
||||
'hit'
|
||||
@@ -1431,6 +1433,34 @@ export default class DevServer {
|
||||
routeResult.status = prevStatus;
|
||||
}
|
||||
|
||||
if (!match && errorRoutes.length > 0) {
|
||||
// error phase
|
||||
const routeResultForError = await devRouter(
|
||||
getReqUrl(routeResult),
|
||||
req.method,
|
||||
errorRoutes,
|
||||
this,
|
||||
nowConfig,
|
||||
routeResult.headers,
|
||||
[],
|
||||
'error'
|
||||
);
|
||||
|
||||
const matchForError = await findBuildMatch(
|
||||
this.buildMatches,
|
||||
this.files,
|
||||
routeResultForError.dest,
|
||||
this,
|
||||
nowConfig
|
||||
);
|
||||
|
||||
if (matchForError) {
|
||||
// error phase only applies if the file was found
|
||||
routeResult = routeResultForError;
|
||||
match = matchForError;
|
||||
}
|
||||
}
|
||||
|
||||
statusCode = routeResult.status;
|
||||
|
||||
if (match) {
|
||||
@@ -1469,16 +1499,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 (
|
||||
@@ -1510,7 +1539,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}`);
|
||||
@@ -1560,7 +1590,7 @@ export default class DevServer {
|
||||
req,
|
||||
res,
|
||||
nowRequestId,
|
||||
'NO_STATUS_CODE_FROM_DEV_SERVER',
|
||||
'NO_RESPONSE_FROM_FUNCTION',
|
||||
502
|
||||
);
|
||||
return;
|
||||
@@ -1595,19 +1625,18 @@ export default class DevServer {
|
||||
req,
|
||||
res,
|
||||
`http://localhost:${port}`,
|
||||
this.proxy,
|
||||
this.output,
|
||||
this,
|
||||
nowRequestId,
|
||||
false
|
||||
);
|
||||
} else {
|
||||
debug(`Skipping \`startDevServer()\` for ${match.entrypoint}`);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -1626,8 +1655,8 @@ export default class DevServer {
|
||||
req,
|
||||
res,
|
||||
`http://localhost:${this.devProcessPort}`,
|
||||
this.proxy,
|
||||
this.output,
|
||||
this,
|
||||
nowRequestId,
|
||||
false
|
||||
);
|
||||
}
|
||||
@@ -1639,8 +1668,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 */
|
||||
@@ -1721,7 +1751,7 @@ export default class DevServer {
|
||||
req,
|
||||
res,
|
||||
nowRequestId,
|
||||
'NO_STATUS_CODE_FROM_LAMBDA',
|
||||
'NO_RESPONSE_FROM_FUNCTION',
|
||||
502
|
||||
);
|
||||
return;
|
||||
@@ -1759,7 +1789,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' ||
|
||||
@@ -1780,7 +1810,7 @@ export default class DevServer {
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map(p => {
|
||||
.map((p) => {
|
||||
let base = basename(p);
|
||||
let ext = '';
|
||||
let type = 'file';
|
||||
@@ -1832,8 +1862,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;
|
||||
@@ -1937,24 +1976,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}`);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -2011,15 +2053,37 @@ 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)) {
|
||||
return match;
|
||||
if (
|
||||
await shouldServe(
|
||||
match,
|
||||
files,
|
||||
requestPath,
|
||||
devServer,
|
||||
nowConfig,
|
||||
isFilesystem
|
||||
)
|
||||
) {
|
||||
if (!isIndex(match.src)) {
|
||||
return match;
|
||||
} else {
|
||||
// if isIndex === true and ends in .html, we're done. Otherwise, keep searching
|
||||
bestIndexMatch = match;
|
||||
if (extname(match.src) === '.html') {
|
||||
return bestIndexMatch;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
// return a non-.html index file or none are found
|
||||
return bestIndexMatch || null;
|
||||
}
|
||||
|
||||
async function shouldServe(
|
||||
@@ -2027,14 +2091,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)
|
||||
@@ -2078,7 +2142,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
|
||||
@@ -2091,7 +2155,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()) {
|
||||
@@ -2100,7 +2165,8 @@ async function findMatchingRoute(
|
||||
reqUrl,
|
||||
undefined,
|
||||
buildResult.routes,
|
||||
devServer
|
||||
devServer,
|
||||
nowConfig
|
||||
);
|
||||
if (route.found) {
|
||||
return route;
|
||||
@@ -2155,7 +2221,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(
|
||||
@@ -2184,7 +2252,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(
|
||||
@@ -2216,3 +2284,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;
|
||||
}
|
||||
|
||||
@@ -44,22 +44,16 @@
|
||||
<p>
|
||||
<ul>
|
||||
<li>
|
||||
If you are a visitor; contact the website owner or try again later.
|
||||
</li>
|
||||
<li>
|
||||
If you are the owner; <a href="/_logs" target="_blank">check the logs</a> for the application error.
|
||||
Check the logs in your terminal window to see the application error.
|
||||
</li>
|
||||
</ul>
|
||||
<a target="_blank" href="https://vercel.com/docs/router-status/{{= it.http_status_code }}" class="docs-link" rel="noopener noreferrer">Developer Documentation →</a>
|
||||
<a target="_blank" href="https://vercel.com/docs/error/application/{{= it.error_code }}" class="docs-link" rel="noopener noreferrer">Developer Documentation →</a>
|
||||
</p>
|
||||
{{??}}
|
||||
<p>
|
||||
<ul>
|
||||
<li>
|
||||
If you are a visitor, please try again later.
|
||||
</li>
|
||||
<li>
|
||||
If you are the owner, no action is needed. Our engineers have been notified.
|
||||
Please open a <a target="_blank" href="https://github.com/vercel/vercel/issues/new/choose">GitHub issue</a> describing the problem you are experiencing with <code>vercel dev</code>.
|
||||
</li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
@@ -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';
|
||||
@@ -771,6 +772,20 @@ export class CantParseJSONFile extends NowError<
|
||||
}
|
||||
}
|
||||
|
||||
export class ConflictingConfigFiles extends NowBuildError {
|
||||
files: string[];
|
||||
|
||||
constructor(files: string[]) {
|
||||
super({
|
||||
code: 'CONFLICTING_CONFIG_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;
|
||||
}
|
||||
}
|
||||
|
||||
export class CantFindConfig extends NowError<
|
||||
'CANT_FIND_CONFIG',
|
||||
{ paths: string[] }
|
||||
|
||||
@@ -3,6 +3,7 @@ import { fileNameSymbol } from '@vercel/client';
|
||||
import {
|
||||
CantParseJSONFile,
|
||||
CantFindConfig,
|
||||
ConflictingConfigFiles,
|
||||
WorkingDirectoryDoesNotExist,
|
||||
} from './errors-ts';
|
||||
import humanizePath from './humanize-path';
|
||||
@@ -49,28 +50,31 @@ export default async function getConfig(
|
||||
}
|
||||
}
|
||||
|
||||
// Then try with vercel.json in the same directory
|
||||
// Then try with `vercel.json` or `now.json` in the same directory
|
||||
const vercelFilePath = path.resolve(localPath, 'vercel.json');
|
||||
const vercelConfig = await readJSONFile(vercelFilePath);
|
||||
const nowFilePath = path.resolve(localPath, 'now.json');
|
||||
const [vercelConfig, nowConfig] = await Promise.all([
|
||||
readJSONFile(vercelFilePath),
|
||||
readJSONFile(nowFilePath),
|
||||
]);
|
||||
if (vercelConfig instanceof CantParseJSONFile) {
|
||||
return vercelConfig;
|
||||
}
|
||||
if (nowConfig instanceof CantParseJSONFile) {
|
||||
return nowConfig;
|
||||
}
|
||||
if (vercelConfig && nowConfig) {
|
||||
return new ConflictingConfigFiles([vercelFilePath, nowFilePath]);
|
||||
}
|
||||
if (vercelConfig !== null) {
|
||||
output.debug(`Found config in file ${vercelFilePath}`);
|
||||
output.debug(`Found config in file "${vercelFilePath}"`);
|
||||
config = vercelConfig as NowConfig;
|
||||
config[fileNameSymbol] = 'vercel.json';
|
||||
return config;
|
||||
}
|
||||
|
||||
// Then try with now.json in the same directory
|
||||
const nowFilePath = path.resolve(localPath, 'now.json');
|
||||
const mainConfig = await readJSONFile(nowFilePath);
|
||||
if (mainConfig instanceof CantParseJSONFile) {
|
||||
return mainConfig;
|
||||
}
|
||||
if (mainConfig !== null) {
|
||||
output.debug(`Found config in file ${nowFilePath}`);
|
||||
config = mainConfig as NowConfig;
|
||||
if (nowConfig !== null) {
|
||||
output.debug(`Found config in file "${nowFilePath}"`);
|
||||
config = nowConfig as NowConfig;
|
||||
config[fileNameSymbol] = 'now.json';
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>;
|
||||
@@ -41,7 +42,7 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
|
||||
boxen(
|
||||
chalk.bold.yellow('WARN! ') +
|
||||
str +
|
||||
(details ? `\nMore details: ${details}` : ''),
|
||||
(details ? `\nMore details: ${renderLink(details)}` : ''),
|
||||
{
|
||||
padding: {
|
||||
top: 0,
|
||||
@@ -64,16 +65,21 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
|
||||
|
||||
function error(
|
||||
str: string,
|
||||
slug: string | null = null,
|
||||
link: string | null = null
|
||||
slug?: string,
|
||||
link?: string,
|
||||
action = 'More details'
|
||||
) {
|
||||
print(`${chalk.red(`Error!`)} ${str}\n`);
|
||||
const details = slug ? `https://err.sh/now/${slug}` : link;
|
||||
if (details) {
|
||||
print(`More details: ${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`);
|
||||
}
|
||||
@@ -106,8 +112,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[]) {
|
||||
@@ -134,6 +138,7 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
|
||||
log,
|
||||
warn,
|
||||
error,
|
||||
prettyError,
|
||||
ready,
|
||||
success,
|
||||
debug,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import chalk from 'chalk';
|
||||
import { metrics, shouldCollectMetrics } from '../metrics';
|
||||
import { APIError } from '../errors-ts';
|
||||
import renderLink from './link';
|
||||
|
||||
const metric = metrics();
|
||||
|
||||
@@ -9,10 +10,9 @@ export default function error(...input: string[] | [APIError]) {
|
||||
if (typeof input[0] === 'object') {
|
||||
const { slug, message, link } = 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('More details')}: ${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);
|
||||
@@ -42,10 +42,21 @@ const linkSchema = {
|
||||
/**
|
||||
* Returns the `<cwd>/.vercel` directory for the current project
|
||||
* with a fallback to <cwd>/.now` if it exists.
|
||||
*
|
||||
* Throws an error if *both* `.vercel` and `.now` directories exist.
|
||||
*/
|
||||
export function getVercelDirectory(cwd: string = process.cwd()) {
|
||||
export function getVercelDirectory(cwd: string = process.cwd()): string {
|
||||
const possibleDirs = [join(cwd, VERCEL_DIR), join(cwd, VERCEL_DIR_FALLBACK)];
|
||||
return possibleDirs.find(d => isDirectory(d)) || possibleDirs[0];
|
||||
const existingDirs = possibleDirs.filter(d => isDirectory(d));
|
||||
if (existingDirs.length > 1) {
|
||||
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];
|
||||
}
|
||||
|
||||
async function getLink(path?: string): Promise<ProjectLink | null> {
|
||||
|
||||
@@ -82,7 +82,7 @@ export default async function validatePaths(
|
||||
if (isFile) {
|
||||
output.print(
|
||||
`${prependEmoji(
|
||||
'Deploying files with Vercel is deprecated (https://zeit.ink/3Z)',
|
||||
'Deploying files with Vercel is deprecated (https://vercel.link/faq-deploy-file)',
|
||||
emoji('warning')
|
||||
)}\n`
|
||||
);
|
||||
|
||||
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');
|
||||
});
|
||||
1
packages/now-cli/test/dev/fixtures/00-list-directory/.gitignore
vendored
Normal file
1
packages/now-cli/test/dev/fixtures/00-list-directory/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.vercel
|
||||
@@ -0,0 +1 @@
|
||||
proof goes here
|
||||
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
function Custom404() {
|
||||
return (
|
||||
<main>
|
||||
<h1>Custom Gatsby 404</h1>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
export default Custom404;
|
||||
@@ -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 Custom404() {
|
||||
return <h1>Custom Next 404</h1>;
|
||||
}
|
||||
@@ -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 @@
|
||||
This is index.css
|
||||
@@ -0,0 +1,2 @@
|
||||
This is index.html
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user