mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-23 18:59:59 +00:00
Compare commits
204 Commits
@vercel/bu
...
@vercel/no
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47f92f8f14 | ||
|
|
9aa669d735 | ||
|
|
99cab6f34a | ||
|
|
547e7dccf7 | ||
|
|
dce9a89407 | ||
|
|
c1cdddd974 | ||
|
|
4bce77dd4b | ||
|
|
3a79810944 | ||
|
|
5b3fbdddb3 | ||
|
|
25e9ecb5a2 | ||
|
|
415515840c | ||
|
|
9ba14be12e | ||
|
|
f1321946c5 | ||
|
|
230b69abf4 | ||
|
|
0d96f80ac8 | ||
|
|
178f5e7f00 | ||
|
|
dbfdf20c9d | ||
|
|
a80ed4ec9b | ||
|
|
833c3d7c01 | ||
|
|
19d0db7270 | ||
|
|
041e8cc601 | ||
|
|
b118e461b1 | ||
|
|
e519d49d7b | ||
|
|
27683818ba | ||
|
|
e016e38229 | ||
|
|
5db1c5e610 | ||
|
|
24c228569f | ||
|
|
963de9b64f | ||
|
|
ab7fd52305 | ||
|
|
0fdb0dac91 | ||
|
|
bb0b632dcf | ||
|
|
ced9495143 | ||
|
|
fadc3f2588 | ||
|
|
a1d548dfef | ||
|
|
754090a8ab | ||
|
|
8269a48ee0 | ||
|
|
9f05a1865c | ||
|
|
8d1afc026f | ||
|
|
130f36aad6 | ||
|
|
dd87c9b0c6 | ||
|
|
f813b3340b | ||
|
|
976b02e895 | ||
|
|
843be9658c | ||
|
|
ad501a4cd0 | ||
|
|
727ae587db | ||
|
|
536b15079b | ||
|
|
887882697b | ||
|
|
e2db7c7734 | ||
|
|
022504787c | ||
|
|
0f424de406 | ||
|
|
4819c3ac61 | ||
|
|
c28ca7ef2d | ||
|
|
068ea00615 | ||
|
|
7f8145ab40 | ||
|
|
1a12715096 | ||
|
|
5b6d565360 | ||
|
|
0794158906 | ||
|
|
02cdb88d3b | ||
|
|
fa58855114 | ||
|
|
1fac11792f | ||
|
|
05ffc9ce2b | ||
|
|
f848551043 | ||
|
|
9712abc5bf | ||
|
|
a9ef3cc726 | ||
|
|
e5cc1d643a | ||
|
|
c68e83f972 | ||
|
|
80118de040 | ||
|
|
c69da18e9c | ||
|
|
e19446f89c | ||
|
|
ea3233502d | ||
|
|
0a8810b64f | ||
|
|
f6b373f0f4 | ||
|
|
7ad2a99cd7 | ||
|
|
0349eea494 | ||
|
|
ed4d006fb7 | ||
|
|
12a9d203e9 | ||
|
|
ac1f4cf789 | ||
|
|
b284ca350a | ||
|
|
a43bf6c912 | ||
|
|
62410806bb | ||
|
|
47e3111cab | ||
|
|
135f35002f | ||
|
|
ee40052cee | ||
|
|
86b730c1cd | ||
|
|
b440249c26 | ||
|
|
5380c12569 | ||
|
|
f11eb32b2c | ||
|
|
3d40e343ac | ||
|
|
80f525796f | ||
|
|
af4ad358f2 | ||
|
|
e6033d7a2d | ||
|
|
d3148dffaa | ||
|
|
30048cf4ff | ||
|
|
07c65fa5c8 | ||
|
|
411ec64986 | ||
|
|
e4d2cc704c | ||
|
|
38db720586 | ||
|
|
c26c7886be | ||
|
|
b0e5d308ca | ||
|
|
609b98cc73 | ||
|
|
e0ec6c792b | ||
|
|
b29db2fd1d | ||
|
|
b604ced99d | ||
|
|
b7fd69517e | ||
|
|
4c821a6fb5 | ||
|
|
5c4fb319af | ||
|
|
fbe9ea0750 | ||
|
|
fa0f1b90b4 | ||
|
|
2daa0e28d3 | ||
|
|
48358b4986 | ||
|
|
c59f44a63b | ||
|
|
ca27864201 | ||
|
|
6c81a87338 | ||
|
|
a7bef9387b | ||
|
|
65d6c5e1f4 | ||
|
|
c09355fdb3 | ||
|
|
d9ac4c45e1 | ||
|
|
c2ff95714f | ||
|
|
a51feb7a62 | ||
|
|
ff10918230 | ||
|
|
04bea1e3cd | ||
|
|
bc5e5e8a9c | ||
|
|
2e647175f5 | ||
|
|
1a4e1d2fdd | ||
|
|
49e2274d81 | ||
|
|
9af3938544 | ||
|
|
7b5bf061c2 | ||
|
|
ffb98781f1 | ||
|
|
3411fcbb68 | ||
|
|
55cfd33338 | ||
|
|
93a9e5bed3 | ||
|
|
b454021234 | ||
|
|
bb705cd091 | ||
|
|
0986f4bcb6 | ||
|
|
83f77223aa | ||
|
|
effda1fa6c | ||
|
|
2ab6a7ef0c | ||
|
|
200bf5e996 | ||
|
|
94ab2512e9 | ||
|
|
da3207278e | ||
|
|
8e10a82b43 | ||
|
|
d7731d191b | ||
|
|
0f5f99e667 | ||
|
|
93a7831943 | ||
|
|
0eacbeae11 | ||
|
|
e50417dc47 | ||
|
|
4840a25d30 | ||
|
|
785e91979d | ||
|
|
7a776c54b8 | ||
|
|
997031c53b | ||
|
|
fdee03a599 | ||
|
|
c5a93ecdad | ||
|
|
5a9391b7ce | ||
|
|
99e49473b8 | ||
|
|
de1cc6f9a7 | ||
|
|
08eedd8f34 | ||
|
|
5021a71a8e | ||
|
|
56671d7c2f | ||
|
|
5035fa537f | ||
|
|
63cc9009c8 | ||
|
|
ccf6e3c432 | ||
|
|
8d015e3138 | ||
|
|
42f2fa1a20 | ||
|
|
8397aac0e3 | ||
|
|
7bcdc144eb | ||
|
|
624da9170d | ||
|
|
fb5b013a03 | ||
|
|
0a4bb53a58 | ||
|
|
2fbd9c78e3 | ||
|
|
1ed2b7a57d | ||
|
|
88cd9ca3c3 | ||
|
|
ecea2ca4a3 | ||
|
|
3f132bc15b | ||
|
|
61d95094c0 | ||
|
|
f7c47975e3 | ||
|
|
7c96f9f9a5 | ||
|
|
a5c805b6eb | ||
|
|
ff2a22023d | ||
|
|
c6efc028aa | ||
|
|
96565da1cf | ||
|
|
afb5e7fc85 | ||
|
|
34cc987be8 | ||
|
|
55c60d30e6 | ||
|
|
eb993d47ac | ||
|
|
d2184628d1 | ||
|
|
65f0cc6797 | ||
|
|
c628c7b58e | ||
|
|
4e005274f9 | ||
|
|
482373f711 | ||
|
|
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
|
||||
|
||||
|
||||
@@ -34,3 +34,6 @@ packages/now-node-bridge/bridge.*
|
||||
|
||||
# now-static-build
|
||||
packages/now-static-build/test/fixtures
|
||||
|
||||
# redwood
|
||||
packages/redwood/test/fixtures
|
||||
|
||||
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';
|
||||
```
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { NowRequest, NowResponse } from '@now/node';
|
||||
import { NowRequest, NowResponse } from '@vercel/node';
|
||||
import { errorHandler } from './error-handler';
|
||||
|
||||
type Handler = (req: NowRequest, res: NowResponse) => Promise<any>;
|
||||
|
||||
@@ -2,7 +2,7 @@ import fs from 'fs';
|
||||
// @ts-ignore
|
||||
import tar from 'tar-fs';
|
||||
import { extract } from '../../_lib/examples/extract';
|
||||
import { NowRequest, NowResponse } from '@now/node';
|
||||
import { NowRequest, NowResponse } from '@vercel/node';
|
||||
import { withApiHandler } from '../../_lib/util/with-api-handler';
|
||||
|
||||
const TMP_DIR = '/tmp';
|
||||
@@ -15,8 +15,8 @@ function notFound(res: NowResponse, message: string) {
|
||||
return res.status(404).send({
|
||||
error: {
|
||||
code: 'not_found',
|
||||
message
|
||||
}
|
||||
message,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -35,7 +35,10 @@ function streamToBuffer(stream: any) {
|
||||
});
|
||||
}
|
||||
|
||||
export default withApiHandler(async function(req: NowRequest, res: NowResponse) {
|
||||
export default withApiHandler(async function (
|
||||
req: NowRequest,
|
||||
res: NowResponse
|
||||
) {
|
||||
const ext = '.tar.gz';
|
||||
const { segment = '' } = req.query;
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
|
||||
// @ts-ignore
|
||||
import parseGitUrl from 'parse-github-url';
|
||||
import { NowRequest, NowResponse } from '@now/node';
|
||||
import { NowRequest, NowResponse } from '@vercel/node';
|
||||
import { withApiHandler } from '../_lib/util/with-api-handler';
|
||||
import { getGitHubRepoInfo } from '../_lib/examples/github-repo-info';
|
||||
import { getGitLabRepoInfo } from '../_lib/examples/gitlab-repo-info';
|
||||
|
||||
export default withApiHandler(async function(
|
||||
export default withApiHandler(async function (
|
||||
req: NowRequest,
|
||||
res: NowResponse
|
||||
) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { NowRequest, NowResponse } from '@now/node';
|
||||
import { NowRequest, NowResponse } from '@vercel/node';
|
||||
import { getExampleList } from '../_lib/examples/example-list';
|
||||
import { withApiHandler } from '../_lib/util/with-api-handler';
|
||||
|
||||
export default withApiHandler(async function(
|
||||
export default withApiHandler(async function (
|
||||
req: NowRequest,
|
||||
res: NowResponse
|
||||
) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { extract } from '../_lib/examples/extract';
|
||||
import { summary } from '../_lib/examples/summary';
|
||||
import { NowRequest, NowResponse } from '@now/node';
|
||||
import { NowRequest, NowResponse } from '@vercel/node';
|
||||
import { mapOldToNew } from '../_lib/examples/map-old-to-new';
|
||||
import { withApiHandler } from '../_lib/util/with-api-handler';
|
||||
|
||||
export default withApiHandler(async function(
|
||||
export default withApiHandler(async function (
|
||||
req: NowRequest,
|
||||
res: NowResponse
|
||||
) {
|
||||
|
||||
@@ -1,21 +1,27 @@
|
||||
import { NowRequest, NowResponse } from '@now/node';
|
||||
import { NowRequest, NowResponse } from '@vercel/node';
|
||||
import { withApiHandler } from './_lib/util/with-api-handler';
|
||||
import frameworkList, { Framework } from '../packages/frameworks';
|
||||
import _frameworks, { Framework } from '../packages/frameworks';
|
||||
|
||||
const frameworks = (frameworkList as Framework[]).map(frameworkItem => {
|
||||
const framework = {
|
||||
...frameworkItem,
|
||||
detectors: undefined,
|
||||
};
|
||||
const frameworks = (_frameworks as Framework[])
|
||||
.sort(
|
||||
(a, b) =>
|
||||
(a.sort || Number.MAX_SAFE_INTEGER) - (b.sort || Number.MAX_SAFE_INTEGER)
|
||||
)
|
||||
.map(frameworkItem => {
|
||||
const framework = {
|
||||
...frameworkItem,
|
||||
detectors: undefined,
|
||||
sort: undefined,
|
||||
};
|
||||
|
||||
if (framework.logo) {
|
||||
framework.logo = `https://res.cloudinary.com/zeit-inc/image/fetch/${framework.logo}`;
|
||||
}
|
||||
if (framework.logo) {
|
||||
framework.logo = `https://res.cloudinary.com/zeit-inc/image/fetch/${framework.logo}`;
|
||||
}
|
||||
|
||||
return framework;
|
||||
});
|
||||
return framework;
|
||||
});
|
||||
|
||||
export default withApiHandler(async function(
|
||||
export default withApiHandler(async function (
|
||||
req: NowRequest,
|
||||
res: NowResponse
|
||||
) {
|
||||
|
||||
@@ -13,12 +13,12 @@
|
||||
"node-fetch": "2.6.0",
|
||||
"parse-github-url": "1.0.2",
|
||||
"tar-fs": "2.0.0",
|
||||
"typescript": "3.7.4",
|
||||
"unzip-stream": "0.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@now/node": "1.3.3",
|
||||
"@types/node": "13.1.4",
|
||||
"@types/node-fetch": "2.5.4"
|
||||
"@types/node-fetch": "2.5.4",
|
||||
"@vercel/node": "1.7.2",
|
||||
"typescript": "3.9.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,6 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@now/node@1.3.3":
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@now/node/-/node-1.3.3.tgz#5407cb6a730d4dd9b6b6b0bc4a316f29086c9feb"
|
||||
integrity sha512-s1qajtQttWhhSs1k6FX0/6eTFYFUplzultrQeKfOPMoYzzz6OxDq5qrQ3elpsGlZlDVmO+x+JOJ7yad+3yBgpg==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@sentry/apm@5.11.1":
|
||||
version "5.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/apm/-/apm-5.11.1.tgz#cc89fa4150056fbf009f92eca94fccc3980db34e"
|
||||
@@ -141,11 +134,25 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@vercel/node@1.7.2":
|
||||
version "1.7.2"
|
||||
resolved "https://registry.yarnpkg.com/@vercel/node/-/node-1.7.2.tgz#85cb8aac661c02dfef6fe752740f5b162e90767b"
|
||||
integrity sha512-XV5lrLC+K/cxsaFj8H2OoGu1zliOqnxcrOnPInI8HmQjR/Tztt+0nzgpt+7sx8wXcrib0Nu7lK303jP7VjSETw==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
ts-node "8.9.1"
|
||||
typescript "3.9.3"
|
||||
|
||||
agent-base@5:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c"
|
||||
integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==
|
||||
|
||||
arg@^4.1.0:
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
|
||||
integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
|
||||
|
||||
binary@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79"
|
||||
@@ -161,6 +168,11 @@ bl@^3.0.0:
|
||||
dependencies:
|
||||
readable-stream "^3.0.1"
|
||||
|
||||
buffer-from@^1.0.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
|
||||
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
|
||||
|
||||
buffers@~0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb"
|
||||
@@ -229,6 +241,11 @@ defer-to-connect@^1.1.1:
|
||||
resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.1.tgz#88ae694b93f67b81815a2c8c769aef6574ac8f2f"
|
||||
integrity sha512-J7thop4u3mRTkYRQ+Vpfwy2G5Ehoy82I14+14W4YMDLKdWloI9gSzRbV30s/NckQGVJtPkWNcW4oMAUigTdqiQ==
|
||||
|
||||
diff@^4.0.1:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
|
||||
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
|
||||
|
||||
duplexer3@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
|
||||
@@ -313,6 +330,11 @@ lru_map@^0.3.3:
|
||||
resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd"
|
||||
integrity sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0=
|
||||
|
||||
make-error@^1.1.1:
|
||||
version "1.3.6"
|
||||
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
|
||||
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
|
||||
|
||||
mimic-response@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
|
||||
@@ -396,6 +418,19 @@ safe-buffer@~5.2.0:
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
|
||||
integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
|
||||
|
||||
source-map-support@^0.5.17:
|
||||
version "0.5.19"
|
||||
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
|
||||
integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
|
||||
dependencies:
|
||||
buffer-from "^1.0.0"
|
||||
source-map "^0.6.0"
|
||||
|
||||
source-map@^0.6.0:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
string_decoder@^1.1.1:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
|
||||
@@ -434,6 +469,17 @@ to-readable-stream@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9"
|
||||
integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=
|
||||
|
||||
ts-node@8.9.1:
|
||||
version "8.9.1"
|
||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.9.1.tgz#2f857f46c47e91dcd28a14e052482eb14cfd65a5"
|
||||
integrity sha512-yrq6ODsxEFTLz0R3BX2myf0WBCSQh9A+py8PBo1dCzWIOcvisbyH6akNKqDHMgXePF2kir5mm5JXJTH3OUJYOQ==
|
||||
dependencies:
|
||||
arg "^4.1.0"
|
||||
diff "^4.0.1"
|
||||
make-error "^1.1.1"
|
||||
source-map-support "^0.5.17"
|
||||
yn "3.1.1"
|
||||
|
||||
tslib@^1.9.3:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
|
||||
@@ -444,10 +490,15 @@ type-fest@^0.8.0:
|
||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
|
||||
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
|
||||
|
||||
typescript@3.7.4:
|
||||
version "3.7.4"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.4.tgz#1743a5ec5fef6a1fa9f3e4708e33c81c73876c19"
|
||||
integrity sha512-A25xv5XCtarLwXpcDNZzCGvW2D1S3/bACratYBx2sax8PefsFhlYmkQicKHvpYflFS8if4zne5zT5kpJ7pzuvw==
|
||||
typescript@3.9.3:
|
||||
version "3.9.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.3.tgz#d3ac8883a97c26139e42df5e93eeece33d610b8a"
|
||||
integrity sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==
|
||||
|
||||
typescript@3.9.6:
|
||||
version "3.9.6"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.6.tgz#8f3e0198a34c3ae17091b35571d3afd31999365a"
|
||||
integrity sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw==
|
||||
|
||||
unzip-stream@0.3.0:
|
||||
version "0.3.0"
|
||||
@@ -466,3 +517,8 @@ wrappy@1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
||||
|
||||
yn@3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
|
||||
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
|
||||
|
||||
@@ -2,16 +2,14 @@
|
||||
|
||||
#### Why This Error Occurred
|
||||
|
||||
The domain you supplied cannot be verified using either the intended set of nameservers or the given verification TXT record.
|
||||
The domain you supplied cannot be verified using the intended nameservers.
|
||||
|
||||
#### Possible Ways to Fix It
|
||||
#### Possible Way to Fix It
|
||||
|
||||
Apply the intended set of nameservers to your domain or add the given TXT verification record through your domain provider.
|
||||
Apply the intended set of nameservers to your domain.
|
||||
|
||||
You can retrieve both the intended nameservers and TXT verification record for the domain you wish to verify by running `vercel domains inspect <domain>`.
|
||||
|
||||
When you have added either verification method to your domain, you can run `vercel domains verify <domain>` again to complete verification for your domain.
|
||||
|
||||
Vercel will also automatically check periodically that your domain has been verified and automatically mark it as such if we detect either verification method on the domain.
|
||||
|
||||
If you would not like to verify your domain, you can remove it from your account using `vercel domains rm <domain>`.
|
||||
|
||||
14
errors/next-functions-config-optimized-lambdas.md
Normal file
14
errors/next-functions-config-optimized-lambdas.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# `@vercel/next` Functions Config Optimized Lambdas Opt-out
|
||||
|
||||
#### Why This Warning Occurred
|
||||
|
||||
`@vercel/next` by default now bundles pages into optimized functions, minimizing bootup time and increasing overall application throughput.
|
||||
When the `functions` config is added in `now.json` or `vercel.json`, it causes conflicts with this optimization, so it is opted-out.
|
||||
|
||||
#### Possible Ways to Fix It
|
||||
|
||||
Remove the `functions` config from your `now.json` or `vercel.json` to take advantage of this optimization.
|
||||
|
||||
### Useful Links
|
||||
|
||||
- [Functions Config Documentation](https://vercel.com/docs/configuration?query=functions#project/functions)
|
||||
16
errors/next-legacy-routes-optimized-lambdas.md
Normal file
16
errors/next-legacy-routes-optimized-lambdas.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# `@vercel/next` Legacy Routes Optimized Lambdas Opt-out
|
||||
|
||||
#### Why This Warning Occurred
|
||||
|
||||
`@vercel/next` by default now bundles pages into optimized functions, minimizing bootup time and increasing overall application throughput.
|
||||
When legacy `routes` are added in `now.json` or `vercel.json`, they cause conflicts with this optimization, so it is opted-out.
|
||||
|
||||
#### Possible Ways to Fix It
|
||||
|
||||
Migrate from using legacy `routes` to the new `rewrites`, `redirects`, and `headers` configurations in your `now.json` or `vercel.json` file or leverage them directly in your `next.config.js` with the built-in [custom routes support](https://github.com/zeit/next.js/issues/9081)
|
||||
|
||||
### Useful Links
|
||||
|
||||
- [Rewrites Documentation](https://vercel.com/docs/configuration?query=rewrites#project/rewrites)
|
||||
- [Redirects Documentation](https://vercel.com/docs/configuration?query=rewrites#project/redirects)
|
||||
- [Headers Documentation](https://vercel.com/docs/configuration?query=rewrites#project/headers)
|
||||
4
examples/blitzjs/.babelrc.js
Normal file
4
examples/blitzjs/.babelrc.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
presets: ["next/babel"],
|
||||
plugins: [],
|
||||
}
|
||||
10
examples/blitzjs/.eslintrc.js
Normal file
10
examples/blitzjs/.eslintrc.js
Normal file
@@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
extends: ["react-app", "plugin:jsx-a11y/recommended"],
|
||||
plugins: ["jsx-a11y"],
|
||||
rules: {
|
||||
"import/no-anonymous-default-export": "error",
|
||||
"import/no-webpack-loader-syntax": "off",
|
||||
"react/react-in-jsx-scope": "off", // React is always in scope with Blitz
|
||||
"jsx-a11y/anchor-is-valid": "off", //Doesn't play well with Blitz/Next <Link> usage
|
||||
},
|
||||
}
|
||||
56
examples/blitzjs/.gitignore
vendored
Normal file
56
examples/blitzjs/.gitignore
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
# dependencies
|
||||
node_modules
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.pnp.*
|
||||
.npm
|
||||
web_modules/
|
||||
|
||||
# blitz
|
||||
/.blitz/
|
||||
/.next/
|
||||
*.sqlite
|
||||
.now
|
||||
.vercel
|
||||
.blitz-console-history
|
||||
blitz-log.log
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
|
||||
# local env files
|
||||
.env
|
||||
.envrc
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Testing
|
||||
coverage
|
||||
*.lcov
|
||||
.nyc_output
|
||||
lib-cov
|
||||
|
||||
# Caches
|
||||
*.tsbuildinfo
|
||||
.eslintcache
|
||||
.node_repl_history
|
||||
.yarn-integrity
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
1
examples/blitzjs/.npmrc
Normal file
1
examples/blitzjs/.npmrc
Normal file
@@ -0,0 +1 @@
|
||||
save-exact=true
|
||||
6
examples/blitzjs/.prettierignore
Normal file
6
examples/blitzjs/.prettierignore
Normal file
@@ -0,0 +1,6 @@
|
||||
.gitkeep
|
||||
.env
|
||||
*.ico
|
||||
*.lock
|
||||
db/migrations
|
||||
|
||||
21
examples/blitzjs/README.md
Normal file
21
examples/blitzjs/README.md
Normal file
@@ -0,0 +1,21 @@
|
||||

|
||||
|
||||
This is a [Blitz.js](https://blitzjs.com/) project bootstrapped with `npx blitz new`.
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npx blitz start
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Blitz.js, view [Blitzjs.com](https://blitzjs.com)
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
View the [documentation on deploying to Vercel](https://blitzjs.com/docs/deploy-vercel)
|
||||
0
examples/blitzjs/app/components/.keep
Normal file
0
examples/blitzjs/app/components/.keep
Normal file
21
examples/blitzjs/app/components/ErrorBoundary.tsx
Normal file
21
examples/blitzjs/app/components/ErrorBoundary.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from "react"
|
||||
|
||||
export default class ErrorBoundary extends React.Component<{
|
||||
fallback: (error: any) => React.ReactNode
|
||||
}> {
|
||||
state = { hasError: false, error: null }
|
||||
|
||||
static getDerivedStateFromError(error: any) {
|
||||
return {
|
||||
hasError: true,
|
||||
error,
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return this.props.fallback(this.state.error)
|
||||
}
|
||||
return this.props.children
|
||||
}
|
||||
}
|
||||
0
examples/blitzjs/app/layouts/.keep
Normal file
0
examples/blitzjs/app/layouts/.keep
Normal file
5
examples/blitzjs/app/pages/_app.tsx
Normal file
5
examples/blitzjs/app/pages/_app.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { AppProps } from "blitz"
|
||||
|
||||
export default function MyApp({ Component, pageProps }: AppProps) {
|
||||
return <Component {...pageProps} />
|
||||
}
|
||||
23
examples/blitzjs/app/pages/_document.tsx
Normal file
23
examples/blitzjs/app/pages/_document.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Document, Html, DocumentHead, Main, BlitzScript /*DocumentContext*/ } from "blitz"
|
||||
|
||||
class MyDocument extends Document {
|
||||
// Only uncomment if you need to customize this behaviour
|
||||
// static async getInitialProps(ctx: DocumentContext) {
|
||||
// const initialProps = await Document.getInitialProps(ctx)
|
||||
// return {...initialProps}
|
||||
// }
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<DocumentHead />
|
||||
<body>
|
||||
<Main />
|
||||
<BlitzScript />
|
||||
</body>
|
||||
</Html>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default MyDocument
|
||||
197
examples/blitzjs/app/pages/index.tsx
Normal file
197
examples/blitzjs/app/pages/index.tsx
Normal file
@@ -0,0 +1,197 @@
|
||||
import { Head, Link } from "blitz"
|
||||
|
||||
const Home = () => (
|
||||
<div className="container">
|
||||
<Head>
|
||||
<title>blitzjs</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
|
||||
<main>
|
||||
<div className="logo">
|
||||
<img src="/logo.png" alt="blitz.js" />
|
||||
</div>
|
||||
<p>1. Run this command in your terminal:</p>
|
||||
<pre>
|
||||
<code>blitz generate all project name:string</code>
|
||||
</pre>
|
||||
<p>2. Then run this command:</p>
|
||||
<pre>
|
||||
<code>blitz db migrate</code>
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
3. Go to{" "}
|
||||
<Link href="/projects">
|
||||
<a>/projects</a>
|
||||
</Link>
|
||||
</p>
|
||||
<div className="buttons">
|
||||
<a
|
||||
className="button"
|
||||
href="https://github.com/blitz-js/blitz/blob/master/USER_GUIDE.md?utm_source=blitz-new&utm_medium=app-template&utm_campaign=blitz-new"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Documentation
|
||||
</a>
|
||||
<a
|
||||
className="button-outline"
|
||||
href="https://github.com/blitz-js/blitz"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Github Repo
|
||||
</a>
|
||||
<a
|
||||
className="button-outline"
|
||||
href="https://slack.blitzjs.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Slack Community
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<a
|
||||
href="https://blitzjs.com?utm_source=blitz-new&utm_medium=app-template&utm_campaign=blitz-new"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Powered by Blitz.js
|
||||
</a>
|
||||
</footer>
|
||||
|
||||
<style jsx>{`
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 5rem 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
main p {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
border-top: 1px solid #eaeaea;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #45009d;
|
||||
}
|
||||
|
||||
footer a {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
footer a {
|
||||
color: #f4f4f4;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.logo img {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
grid-gap: 0.5rem;
|
||||
margin-top: 6rem;
|
||||
}
|
||||
|
||||
a.button {
|
||||
background-color: #6700eb;
|
||||
padding: 1rem 2rem;
|
||||
color: #f4f4f4;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a.button:hover {
|
||||
background-color: #45009d;
|
||||
}
|
||||
|
||||
a.button-outline {
|
||||
border: 2px solid #6700eb;
|
||||
padding: 1rem 2rem;
|
||||
color: #6700eb;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a.button-outline:hover {
|
||||
border-color: #45009d;
|
||||
color: #45009d;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #fafafa;
|
||||
border-radius: 5px;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
code {
|
||||
font-size: 0.9rem;
|
||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
max-width: 800px;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.grid {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<style jsx global>{`
|
||||
@import url("https://fonts.googleapis.com/css2?family=Libre+Franklin:wght@300;700&display=swap");
|
||||
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: "Libre Franklin", -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
}
|
||||
|
||||
* {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default Home
|
||||
15
examples/blitzjs/blitz.config.js
Normal file
15
examples/blitzjs/blitz.config.js
Normal file
@@ -0,0 +1,15 @@
|
||||
module.exports = {
|
||||
/*
|
||||
webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
|
||||
// Note: we provide webpack above so you should not `require` it
|
||||
// Perform customizations to webpack config
|
||||
// Important: return the modified config
|
||||
return config
|
||||
},
|
||||
webpackDevMiddleware: (config) => {
|
||||
// Perform customizations to webpack dev middleware config
|
||||
// Important: return the modified config
|
||||
return config
|
||||
},
|
||||
*/
|
||||
}
|
||||
15
examples/blitzjs/db/index.ts
Normal file
15
examples/blitzjs/db/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { PrismaClient } from "@prisma/client"
|
||||
export * from "@prisma/client"
|
||||
|
||||
let prisma: PrismaClient
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
prisma = new PrismaClient()
|
||||
} else {
|
||||
// Ensure the prisma instance is re-used during hot-reloading
|
||||
// Otherwise, a new client will be created on every reload
|
||||
global["prisma"] = global["prisma"] || new PrismaClient()
|
||||
prisma = global["prisma"]
|
||||
}
|
||||
|
||||
export default prisma
|
||||
0
examples/blitzjs/db/migrations/.keep
Normal file
0
examples/blitzjs/db/migrations/.keep
Normal file
27
examples/blitzjs/db/schema.prisma
Normal file
27
examples/blitzjs/db/schema.prisma
Normal file
@@ -0,0 +1,27 @@
|
||||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
datasource sqlite {
|
||||
provider = "sqlite"
|
||||
url = "file:./db.sqlite"
|
||||
}
|
||||
|
||||
// SQLite is easy to start with, but if you use Postgres in production
|
||||
// you should also use it in development with the following:
|
||||
//datasource postgresql {
|
||||
// provider = "postgresql"
|
||||
// url = env("DATABASE_URL")
|
||||
//}
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------
|
||||
|
||||
//model Project {
|
||||
// id Int @default(autoincrement()) @id
|
||||
// name String
|
||||
//}
|
||||
|
||||
0
examples/blitzjs/integrations/.keep
Normal file
0
examples/blitzjs/integrations/.keep
Normal file
55
examples/blitzjs/package.json
Normal file
55
examples/blitzjs/package.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "blitzjs",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"start": "blitz start",
|
||||
"studio": "blitz db studio",
|
||||
"build": "blitz build",
|
||||
"lint": "eslint --ignore-path .gitignore --ext .js,.ts,.tsx .",
|
||||
"test": "echo \"No tests yet\""
|
||||
},
|
||||
"browserslist": [
|
||||
"defaults"
|
||||
],
|
||||
"prettier": {
|
||||
"semi": false,
|
||||
"printWidth": 100
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged && pretty-quick --staged",
|
||||
"pre-push": "blitz test"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,ts,tsx}": [
|
||||
"eslint --fix"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/cli": "latest",
|
||||
"@prisma/client": "latest",
|
||||
"blitz": "latest",
|
||||
"react": "experimental",
|
||||
"react-dom": "experimental"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "16.9.36",
|
||||
"@typescript-eslint/eslint-plugin": "2.x",
|
||||
"@typescript-eslint/parser": "2.x",
|
||||
"babel-eslint": "10.1.0",
|
||||
"eslint": "6.8.0",
|
||||
"eslint-config-react-app": "5.2.1",
|
||||
"eslint-plugin-flowtype": "4.7.0",
|
||||
"eslint-plugin-import": "2.21.2",
|
||||
"eslint-plugin-jsx-a11y": "6.2.3",
|
||||
"eslint-plugin-react": "7.20.0",
|
||||
"eslint-plugin-react-hooks": "3.0.0",
|
||||
"husky": "4.2.5",
|
||||
"lint-staged": "10.2.10",
|
||||
"prettier": "2.0.5",
|
||||
"pretty-quick": "2.0.1",
|
||||
"typescript": "3.9.5"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
BIN
examples/blitzjs/public/favicon.ico
Executable file
BIN
examples/blitzjs/public/favicon.ico
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 556 B |
BIN
examples/blitzjs/public/logo.png
Normal file
BIN
examples/blitzjs/public/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
20
examples/blitzjs/tsconfig.json
Normal file
20
examples/blitzjs/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"baseUrl": "./",
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve"
|
||||
},
|
||||
"exclude": ["node_modules"],
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
|
||||
}
|
||||
0
examples/blitzjs/utils/.keep
Normal file
0
examples/blitzjs/utils/.keep
Normal file
11913
examples/gatsby/yarn.lock
Normal file
11913
examples/gatsby/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
10
examples/redwoodjs/.editorconfig
Normal file
10
examples/redwoodjs/.editorconfig
Normal file
@@ -0,0 +1,10 @@
|
||||
# editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
7
examples/redwoodjs/.env.defaults
Normal file
7
examples/redwoodjs/.env.defaults
Normal file
@@ -0,0 +1,7 @@
|
||||
# These environment variables will be used by default if you do not create any
|
||||
# yourself in .env. This file should be safe to check into your version control
|
||||
# system. Any custom values should go in .env and .env should *not* be checked
|
||||
# into version control.
|
||||
|
||||
DATABASE_URL=file:./dev.db
|
||||
BINARY_TARGET=native
|
||||
10
examples/redwoodjs/.gitignore
vendored
Normal file
10
examples/redwoodjs/.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
.DS_Store
|
||||
.env
|
||||
.netlify
|
||||
dev.db
|
||||
dist
|
||||
dist-babel
|
||||
node_modules
|
||||
yarn-error.log
|
||||
|
||||
.vercel
|
||||
1
examples/redwoodjs/.nvmrc
Normal file
1
examples/redwoodjs/.nvmrc
Normal file
@@ -0,0 +1 @@
|
||||
lts/*
|
||||
21
examples/redwoodjs/LICENSE
Normal file
21
examples/redwoodjs/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Redwood
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
29
examples/redwoodjs/README.md
Normal file
29
examples/redwoodjs/README.md
Normal file
@@ -0,0 +1,29 @@
|
||||

|
||||
|
||||
# RedwoodJS Example
|
||||
|
||||
This directory is a brief example of a [RedwoodJS](https://redwoodjs.com) app with [Serverless Functions](https://vercel.com/docs/v2/serverless-functions/introduction) that can be deployed with Vercel and zero configuration.
|
||||
|
||||
## Deploy Your Own
|
||||
|
||||
Deploy your own RedwoodJS project, along with Serverless Functions, with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/examples/redwoodjs)
|
||||
|
||||
_Live Example: https://redwoodjs.now-examples.now.sh_
|
||||
|
||||
### How We Created This Example
|
||||
|
||||
To get started with RedwoodJS on Vercel, you can [use Yarn to initialize](https://redwoodjs.com/tutorial/installation-starting-development) the project:
|
||||
|
||||
```shell
|
||||
$ yarn create redwood-app ./my-redwood-app
|
||||
```
|
||||
|
||||
### Deploying From Your Terminal
|
||||
|
||||
You can deploy your new RedwoodJS project, along with [Serverless Functions](https://vercel.com/docs/v2/serverless-functions/introduction), with a single command from your terminal using [Vercel CLI](https://vercel.com/download):
|
||||
|
||||
```shell
|
||||
$ vercel
|
||||
```
|
||||
1
examples/redwoodjs/api/.babelrc.js
Normal file
1
examples/redwoodjs/api/.babelrc.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = { extends: '../babel.config.js' }
|
||||
9
examples/redwoodjs/api/jsconfig.json
Normal file
9
examples/redwoodjs/api/jsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"src/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
8
examples/redwoodjs/api/package.json
Normal file
8
examples/redwoodjs/api/package.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "api",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@redwoodjs/api": "0.14.0"
|
||||
}
|
||||
}
|
||||
18
examples/redwoodjs/api/prisma/schema.prisma
Normal file
18
examples/redwoodjs/api/prisma/schema.prisma
Normal file
@@ -0,0 +1,18 @@
|
||||
datasource DS {
|
||||
provider = "sqlite"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
binaryTargets = env("BINARY_TARGET")
|
||||
}
|
||||
|
||||
// Define your own datamodels here and run `yarn redwood db save` to create
|
||||
// migrations for them.
|
||||
// TODO: Please remove the following example:
|
||||
model UserExample {
|
||||
id Int @id @default(autoincrement())
|
||||
email String @unique
|
||||
name String?
|
||||
}
|
||||
26
examples/redwoodjs/api/prisma/seeds.js
Normal file
26
examples/redwoodjs/api/prisma/seeds.js
Normal file
@@ -0,0 +1,26 @@
|
||||
/* eslint-disable no-console */
|
||||
const { PrismaClient } = require('@prisma/client')
|
||||
const dotenv = require('dotenv')
|
||||
|
||||
dotenv.config()
|
||||
const db = new PrismaClient()
|
||||
|
||||
async function main() {
|
||||
// Seed data is database data that needs to exist for your app to run.
|
||||
// Ideally this file should be idempotent: running it multiple times
|
||||
// will result in the same database state (usually by checking for the
|
||||
// existence of a record before trying to create it). For example:
|
||||
//
|
||||
// const existing = await db.user.findMany({ where: { email: 'admin@email.com' }})
|
||||
// if (!existing.length) {
|
||||
// await db.user.create({ data: { name: 'Admin', email: 'admin@email.com' }})
|
||||
// }
|
||||
|
||||
console.info('No data to seed. See api/prisma/seeds.js for info.')
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => console.error(e))
|
||||
.finally(async () => {
|
||||
await db.disconnect()
|
||||
})
|
||||
19
examples/redwoodjs/api/src/functions/graphql.js
Normal file
19
examples/redwoodjs/api/src/functions/graphql.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import {
|
||||
createGraphQLHandler,
|
||||
makeMergedSchema,
|
||||
makeServices,
|
||||
} from '@redwoodjs/api'
|
||||
import importAll from '@redwoodjs/api/importAll.macro'
|
||||
|
||||
import { db } from 'src/lib/db'
|
||||
|
||||
const schemas = importAll('api', 'graphql')
|
||||
const services = importAll('api', 'services')
|
||||
|
||||
export const handler = createGraphQLHandler({
|
||||
schema: makeMergedSchema({
|
||||
schemas,
|
||||
services: makeServices({ services }),
|
||||
}),
|
||||
db,
|
||||
})
|
||||
0
examples/redwoodjs/api/src/graphql/.keep
Normal file
0
examples/redwoodjs/api/src/graphql/.keep
Normal file
6
examples/redwoodjs/api/src/lib/db.js
Normal file
6
examples/redwoodjs/api/src/lib/db.js
Normal file
@@ -0,0 +1,6 @@
|
||||
// See https://github.com/prisma/prisma2/blob/master/docs/prisma-client-js/api.md#constructor
|
||||
// for options.
|
||||
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
|
||||
export const db = new PrismaClient()
|
||||
0
examples/redwoodjs/api/src/services/.keep
Normal file
0
examples/redwoodjs/api/src/services/.keep
Normal file
3
examples/redwoodjs/babel.config.js
Normal file
3
examples/redwoodjs/babel.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
presets: ['@redwoodjs/core/config/babel-preset'],
|
||||
}
|
||||
19
examples/redwoodjs/package.json
Normal file
19
examples/redwoodjs/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"private": true,
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
"api",
|
||||
"web"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@redwoodjs/core": "0.14.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "@redwoodjs/eslint-config"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
"yarn": ">=1.15"
|
||||
}
|
||||
}
|
||||
9
examples/redwoodjs/prettier.config.js
Normal file
9
examples/redwoodjs/prettier.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
// https://prettier.io/docs/en/options.html
|
||||
module.exports = {
|
||||
trailingComma: 'es5',
|
||||
bracketSpacing: true,
|
||||
tabWidth: 2,
|
||||
semi: false,
|
||||
singleQuote: true,
|
||||
arrowParens: 'always',
|
||||
}
|
||||
7
examples/redwoodjs/redwood.toml
Normal file
7
examples/redwoodjs/redwood.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[web]
|
||||
port = 8910
|
||||
apiProxyPath = "/api"
|
||||
[api]
|
||||
port = 8911
|
||||
[browser]
|
||||
open = false
|
||||
1
examples/redwoodjs/web/.babelrc.js
Normal file
1
examples/redwoodjs/web/.babelrc.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = { extends: '../babel.config.js' }
|
||||
10
examples/redwoodjs/web/jsconfig.json
Normal file
10
examples/redwoodjs/web/jsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"src/*": ["./src/*"]
|
||||
},
|
||||
"jsx": "preserve"
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
15
examples/redwoodjs/web/package.json
Normal file
15
examples/redwoodjs/web/package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "web",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"browserslist": [
|
||||
"defaults"
|
||||
],
|
||||
"dependencies": {
|
||||
"@redwoodjs/router": "0.14.0",
|
||||
"@redwoodjs/web": "0.14.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1"
|
||||
}
|
||||
}
|
||||
36
examples/redwoodjs/web/public/README.md
Normal file
36
examples/redwoodjs/web/public/README.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Static Assets
|
||||
Use this folder to add static files directly to your app. All included files and folders will be copied directly into the `/dist` folder (created when Webpack builds for production). They will also be available during development when you run `yarn rw dev`.
|
||||
>Note: files will *not* hot reload while the development server is running. You'll need to manually stop/start to access file changes.
|
||||
|
||||
### Example Use
|
||||
A file like `favicon.png` will be copied to `/dist/favicon.png`. A folder containing a file such as `static-files/my-logo.jpg` will be copied to `/dist/static-files/my-logo.jpg`. These can be referenced in your code directly without any special handling, e.g.
|
||||
```
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
```
|
||||
and
|
||||
```
|
||||
<img src="/static-files/my-logo.jpg"> alt="Logo" />
|
||||
```
|
||||
|
||||
Behind the scenes, we are using Webpack's ["copy-webpack-plugin"](https://github.com/webpack-contrib/copy-webpack-plugin).
|
||||
|
||||
## Best Practices
|
||||
Because assets in this folder are bypassing the javascript module system, **this folder should be used sparingly** for assets such as favicons, robots.txt, manifests, libraries incompatible with Webpack, etc.
|
||||
|
||||
In general, it's best to import files directly into a template, page, or component. This allows Webpack to include that file in the bundle, which ensures Webpack will correctly process and move assets into the distribution folder, providing error checks and correct paths along the way.
|
||||
|
||||
### Example Asset Import with Webpack
|
||||
Instead of handling our logo image as a static file per the example above, we can do the following:
|
||||
```
|
||||
import React from "react"
|
||||
import logo from "./my-logo.jpg"
|
||||
|
||||
|
||||
function Header() {
|
||||
return <img src={logo} alt="Logo" />
|
||||
}
|
||||
|
||||
export default Header
|
||||
```
|
||||
|
||||
Behind the scenes, we are using Webpack's ["file-loader"](https://webpack.js.org/loaders/file-loader/) and ["url-loader](https://webpack.js.org/loaders/url-loader/) (for files smaller than 10kb).
|
||||
BIN
examples/redwoodjs/web/public/favicon.png
Normal file
BIN
examples/redwoodjs/web/public/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
2
examples/redwoodjs/web/public/robots.txt
Normal file
2
examples/redwoodjs/web/public/robots.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Disallow:
|
||||
22
examples/redwoodjs/web/src/Routes.js
Normal file
22
examples/redwoodjs/web/src/Routes.js
Normal file
@@ -0,0 +1,22 @@
|
||||
// In this file, all Page components from 'src/pages` are auto-imported. Nested
|
||||
// directories are supported, and should be uppercase. Each subdirectory will be
|
||||
// prepended onto the component name.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// 'src/pages/HomePage/HomePage.js' -> HomePage
|
||||
// 'src/pages/Admin/BooksPage/BooksPage.js' -> AdminBooksPage
|
||||
|
||||
import { Router, Route } from '@redwoodjs/router'
|
||||
|
||||
const Routes = () => {
|
||||
return (
|
||||
<Router>
|
||||
<Route path="/" page={HomePage} name="home" />
|
||||
<Route path="/about" page={AboutPage} name="about" />
|
||||
<Route notfound page={NotFoundPage} />
|
||||
</Router>
|
||||
)
|
||||
}
|
||||
|
||||
export default Routes
|
||||
0
examples/redwoodjs/web/src/components/.keep
Normal file
0
examples/redwoodjs/web/src/components/.keep
Normal file
0
examples/redwoodjs/web/src/index.css
Normal file
0
examples/redwoodjs/web/src/index.css
Normal file
12
examples/redwoodjs/web/src/index.html
Normal file
12
examples/redwoodjs/web/src/index.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="redwood-app"></div>
|
||||
</body>
|
||||
</html>
|
||||
16
examples/redwoodjs/web/src/index.js
Normal file
16
examples/redwoodjs/web/src/index.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import ReactDOM from 'react-dom'
|
||||
import { RedwoodProvider, FatalErrorBoundary } from '@redwoodjs/web'
|
||||
import FatalErrorPage from 'src/pages/FatalErrorPage'
|
||||
|
||||
import Routes from 'src/Routes'
|
||||
|
||||
import './index.css'
|
||||
|
||||
ReactDOM.render(
|
||||
<FatalErrorBoundary page={FatalErrorPage}>
|
||||
<RedwoodProvider>
|
||||
<Routes />
|
||||
</RedwoodProvider>
|
||||
</FatalErrorBoundary>,
|
||||
document.getElementById('redwood-app')
|
||||
)
|
||||
0
examples/redwoodjs/web/src/layouts/.keep
Normal file
0
examples/redwoodjs/web/src/layouts/.keep
Normal file
44
examples/redwoodjs/web/src/pages/AboutPage/AboutPage.js
Normal file
44
examples/redwoodjs/web/src/pages/AboutPage/AboutPage.js
Normal file
@@ -0,0 +1,44 @@
|
||||
export default () => (
|
||||
<main>
|
||||
<style
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
html, body {
|
||||
margin: 0;
|
||||
}
|
||||
html * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
|
||||
text-align: center;
|
||||
background-color: #E2E8F0;
|
||||
height: 100vh;
|
||||
}
|
||||
section {
|
||||
background-color: white;
|
||||
border-radius: 0.25rem;
|
||||
width: 32rem;
|
||||
padding: 1rem;
|
||||
margin: 0 auto;
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
margin: 0;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
color: #2D3748;
|
||||
}
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
<section>
|
||||
<h1>
|
||||
<span>About RedwoodJS</span>
|
||||
</h1>
|
||||
</section>
|
||||
</main>
|
||||
)
|
||||
@@ -0,0 +1,53 @@
|
||||
// This page will be rendered when an error makes it all the way to the top of the
|
||||
// application without being handled by a Javascript catch statement or React error
|
||||
// boundary.
|
||||
//
|
||||
// You can modify this page as you wish, but it is important to keep things simple to
|
||||
// avoid the possibility that it will cause its own error. If it does, Redwood will
|
||||
// still render a generic error page, but your users will prefer something a bit more
|
||||
// thoughtful. =)
|
||||
|
||||
export default () => (
|
||||
<main>
|
||||
<style
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
html, body {
|
||||
margin: 0;
|
||||
}
|
||||
html * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
|
||||
text-align: center;
|
||||
background-color: #E2E8F0;
|
||||
height: 100vh;
|
||||
}
|
||||
section {
|
||||
background-color: white;
|
||||
border-radius: 0.25rem;
|
||||
width: 32rem;
|
||||
padding: 1rem;
|
||||
margin: 0 auto;
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
margin: 0;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
color: #2D3748;
|
||||
}
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
<section>
|
||||
<h1>
|
||||
<span>Something went wrong</span>
|
||||
</h1>
|
||||
</section>
|
||||
</main>
|
||||
)
|
||||
44
examples/redwoodjs/web/src/pages/HomePage/HomePage.js
Normal file
44
examples/redwoodjs/web/src/pages/HomePage/HomePage.js
Normal file
@@ -0,0 +1,44 @@
|
||||
export default () => (
|
||||
<main>
|
||||
<style
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
html, body {
|
||||
margin: 0;
|
||||
}
|
||||
html * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
|
||||
text-align: center;
|
||||
background-color: #E2E8F0;
|
||||
height: 100vh;
|
||||
}
|
||||
section {
|
||||
background-color: white;
|
||||
border-radius: 0.25rem;
|
||||
width: 32rem;
|
||||
padding: 1rem;
|
||||
margin: 0 auto;
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
margin: 0;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
color: #2D3748;
|
||||
}
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
<section>
|
||||
<h1>
|
||||
<span>Welcome to RedwoodJS!</span>
|
||||
</h1>
|
||||
</section>
|
||||
</main>
|
||||
)
|
||||
@@ -0,0 +1,44 @@
|
||||
export default () => (
|
||||
<main>
|
||||
<style
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
html, body {
|
||||
margin: 0;
|
||||
}
|
||||
html * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
|
||||
text-align: center;
|
||||
background-color: #E2E8F0;
|
||||
height: 100vh;
|
||||
}
|
||||
section {
|
||||
background-color: white;
|
||||
border-radius: 0.25rem;
|
||||
width: 32rem;
|
||||
padding: 1rem;
|
||||
margin: 0 auto;
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
margin: 0;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
color: #2D3748;
|
||||
}
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
<section>
|
||||
<h1>
|
||||
<span>404 Page Not Found</span>
|
||||
</h1>
|
||||
</section>
|
||||
</main>
|
||||
)
|
||||
@@ -20,6 +20,7 @@
|
||||
"@zeit/ncc": "0.20.4",
|
||||
"async-retry": "1.2.3",
|
||||
"buffer-replace": "1.0.0",
|
||||
"cheerio": "1.0.0-rc.3",
|
||||
"eslint": "6.2.2",
|
||||
"eslint-config-prettier": "6.1.0",
|
||||
"eslint-plugin-jest": "23.8.2",
|
||||
@@ -28,7 +29,7 @@
|
||||
"lint-staged": "9.2.5",
|
||||
"node-fetch": "2.6.0",
|
||||
"npm-package-arg": "6.1.0",
|
||||
"prettier": "1.18.2"
|
||||
"prettier": "2.0.5"
|
||||
},
|
||||
"scripts": {
|
||||
"lerna": "lerna",
|
||||
@@ -63,7 +64,8 @@
|
||||
},
|
||||
"prettier": {
|
||||
"trailingComma": "es5",
|
||||
"singleQuote": true
|
||||
"singleQuote": true,
|
||||
"arrowParens": "avoid"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
|
||||
@@ -1,12 +1,41 @@
|
||||
[
|
||||
{
|
||||
"name": "Blitz.js",
|
||||
"slug": "blitzjs",
|
||||
"demo": "https://blitzjs.now-examples.now.sh",
|
||||
"logo": "https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/blitz.svg",
|
||||
"tagline": "Blitz.js: The Fullstack React Framework",
|
||||
"description": "A brand new Blitz.js app - the result of running `npx blitz new`.",
|
||||
"website": "https://blitzjs.com",
|
||||
"detectors": {
|
||||
"every": [
|
||||
{
|
||||
"path": "package.json",
|
||||
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"blitz\":\\s*\".+?\"[^}]*}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"buildCommand": {
|
||||
"placeholder": "`npm run build` or `blitz build`"
|
||||
},
|
||||
"devCommand": {
|
||||
"value": "blitz start"
|
||||
},
|
||||
"outputDirectory": {
|
||||
"placeholder": "Next.js default"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Next.js",
|
||||
"slug": "nextjs",
|
||||
"demo": "https://nextjs.now-examples.now.sh",
|
||||
"logo": "https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/next.svg",
|
||||
"tagline": "Next.js makes you productive with React instantly — whether you want to build static or dynamic sites. ",
|
||||
"tagline": "Next.js makes you productive with React instantly — whether you want to build static or dynamic sites.",
|
||||
"description": "A Next.js app and a Serverless Function API.",
|
||||
"website": "https://nextjs.org",
|
||||
"sort": 1,
|
||||
"detectors": {
|
||||
"every": [
|
||||
{
|
||||
@@ -35,6 +64,7 @@
|
||||
"tagline": "Gatsby helps developers build blazing fast websites and apps with React.",
|
||||
"description": "A Gatsby app, using the default starter theme and a Serverless Function API.",
|
||||
"website": "https://gatsbyjs.org",
|
||||
"sort": 2,
|
||||
"detectors": {
|
||||
"every": [
|
||||
{
|
||||
@@ -62,7 +92,8 @@
|
||||
"logo": "https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/hexo.svg",
|
||||
"tagline": "Hexo is a fast, simple & powerful blog framework powered by Node.js.",
|
||||
"description": "A Hexo site, created with the Hexo CLI.",
|
||||
"website": "https://hexo.io/",
|
||||
"website": "https://hexo.io",
|
||||
"sort": 3,
|
||||
"detectors": {
|
||||
"every": [
|
||||
{
|
||||
@@ -90,7 +121,8 @@
|
||||
"logo": "https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/eleventy.svg",
|
||||
"tagline": "11ty is a simpler static site generator written in JavaScript, created to be an alternative to Jekyll.",
|
||||
"description": "An Eleventy site, created with npm init.",
|
||||
"website": "https://www.11ty.dev/",
|
||||
"website": "https://www.11ty.dev",
|
||||
"sort": 4,
|
||||
"detectors": {
|
||||
"every": [
|
||||
{
|
||||
@@ -634,13 +666,13 @@
|
||||
"every": [
|
||||
{
|
||||
"path": "package.json",
|
||||
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"nuxt\":\\s*\".+?\"[^}]*}"
|
||||
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"nuxt(-edge)?\":\\s*\".+?\"[^}]*}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"buildCommand": {
|
||||
"placeholder": "`npm run build` or `nuxt build`"
|
||||
"placeholder": "`npm run build` or `nuxt generate`"
|
||||
},
|
||||
"devCommand": {
|
||||
"value": "nuxt"
|
||||
@@ -650,6 +682,34 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "RedwoodJS",
|
||||
"slug": "redwoodjs",
|
||||
"demo": "https://redwoodjs.now-examples.now.sh",
|
||||
"logo": "https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/redwood.svg",
|
||||
"tagline": "RedwoodJS is a full-stack framework for the Jamstack.",
|
||||
"description": "A RedwoodJS app, bootstraped with create-redwood-app.",
|
||||
"website": "https://redwoodjs.com",
|
||||
"detectors": {
|
||||
"every": [
|
||||
{
|
||||
"path": "package.json",
|
||||
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@redwoodjs\\/core\":\\s*\".+?\"[^}]*}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"buildCommand": {
|
||||
"value": "yarn rw db up --no-db-client --auto-approve && yarn rw build"
|
||||
},
|
||||
"devCommand": {
|
||||
"value": "yarn rw dev"
|
||||
},
|
||||
"outputDirectory": {
|
||||
"value": "RedwoodJS default"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Hugo",
|
||||
"slug": "hugo",
|
||||
@@ -658,6 +718,7 @@
|
||||
"tagline": "Hugo is the world’s fastest framework for building websites, written in Go.",
|
||||
"description": "A Hugo site, created with the Hugo CLI.",
|
||||
"website": "https://gohugo.io",
|
||||
"sort": 5,
|
||||
"detectors": {
|
||||
"some": [
|
||||
{
|
||||
@@ -771,7 +832,7 @@
|
||||
"description": "No framework or a unoptimized framework.",
|
||||
"settings": {
|
||||
"buildCommand": {
|
||||
"placeholder": "`npm run now-build` or `npm run build`"
|
||||
"placeholder": "`npm run vercel-build` or `npm run build`"
|
||||
},
|
||||
"devCommand": {
|
||||
"placeholder": "None"
|
||||
|
||||
1
packages/frameworks/index.d.ts
vendored
1
packages/frameworks/index.d.ts
vendored
@@ -19,6 +19,7 @@ export interface Framework {
|
||||
tagline?: string;
|
||||
website?: string;
|
||||
description: string;
|
||||
sort?: number;
|
||||
detectors?: {
|
||||
every?: FrameworkDetectionItem[];
|
||||
some?: FrameworkDetectionItem[];
|
||||
|
||||
30
packages/frameworks/logos/blitz.svg
Normal file
30
packages/frameworks/logos/blitz.svg
Normal file
@@ -0,0 +1,30 @@
|
||||
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0)">
|
||||
<path d="M95.4242 249.857H173.991C203.89 249.857 232.049 263.909 250.026 287.799L327.526 390.789C328.991 392.736 329.212 395.349 328.095 397.513L283.421 484.069C281.278 488.221 275.532 488.71 272.719 484.978L95.4242 249.857Z" fill="url(#paint0_linear)"/>
|
||||
<g filter="url(#filter0_d)">
|
||||
<path d="M404.558 249.991H325.991C296.093 249.991 267.933 235.939 249.956 212.049L172.456 109.059C170.991 107.112 170.771 104.499 171.888 102.335L216.561 15.7794C218.705 11.6267 224.45 11.1382 227.264 14.8695L404.558 249.991Z" fill="url(#paint1_linear)"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d" x="71.1812" y="-39.6553" width="433.377" height="437.646" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dy="48"/>
|
||||
<feGaussianBlur stdDeviation="50"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.270588 0 0 0 0 0 0 0 0 0 0.615686 0 0 0 0.2 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear" x1="163.936" y1="392.775" x2="316.429" y2="155.244" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6700EB"/>
|
||||
<stop offset="1" stop-color="#45009D"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear" x1="336.047" y1="107.073" x2="183.554" y2="344.604" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6700EB"/>
|
||||
<stop offset="1" stop-color="#45009D"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0">
|
||||
<rect width="500" height="500" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
1
packages/frameworks/logos/redwood.svg
Normal file
1
packages/frameworks/logos/redwood.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="none" height="1000" viewBox="0 0 917 1000" width="917" xmlns="http://www.w3.org/2000/svg"><path clip-rule="evenodd" d="m249.557 144.582 194.171 132.54c4.383 2.918 9.502 4.516 14.755 4.606 5.261-.038 10.394-1.641 14.755-4.606l194.319-132.986c7.55-5.406 11.714-14.418 10.957-23.717-.757-9.298-6.322-17.507-14.646-21.6024l-194.171-96.13614c-7.366-3.573948-15.947-3.573948-23.313 0l-193.581 96.13614c-8.474 4.1174-14.113 12.4854-14.783 21.9354-.67 9.451 3.73 18.541 11.537 23.83zm274.879 174.144c.016 8.789 4.318 17.01 11.509 21.991l155.662 106.389c9.965 6.87 23.298 6.012 32.313-2.081l130.579-116.789c5.819-5.199 9.051-12.729 8.823-20.56s-3.892-15.158-10.004-20.005l-124.677-99.702c-9.062-7.199-21.704-7.68-31.28-1.189l-161.416 110.401c-7.064 4.89-11.35 12.914-11.509 21.545zm-387.163 144.724c6.292 5.652 9.526 13.988 8.706 22.437-.817 8.499-5.726 16.052-13.132 20.208l-92.9545 55.72c-9.4227 5.633-21.32 4.82-29.90183-2.041-8.5818-6.861-12.06543-18.346-8.75546-28.865l34.37839-108.172c2.6969-8.57 9.5328-15.175 18.1483-17.533 8.609-2.505 17.8924-.309 24.4928 5.795zm504.168 11.293-168.056-115.007c-8.931-6.01-20.578-6.01-29.509 0l-168.056 115.007c-6.684 4.626-10.919 12.061-11.509 20.208-.435 8.203 2.816 16.169 8.853 21.693l167.909 150.222c4.842 4.319 11.089 6.698 17.558 6.687 6.465-.002 12.708-2.38 17.558-6.687l167.908-150.222c6.056-5.501 9.265-13.5 8.705-21.693-.469-8.146-4.666-15.612-11.361-20.208zm-448.247-29.718-130.4316-116.79c-5.8687-5.331-9.1073-12.995-8.8528-20.95.1419-7.841 3.7705-15.204 9.8856-20.06l124.6768-100.296c9.126-7.179 21.793-7.658 31.428-1.189l161.269 110.401c7.484 4.908 11.998 13.293 11.998 22.288 0 8.994-4.514 17.38-11.998 22.288l-155.515 106.388c-10.025 6.841-23.376 5.985-32.46-2.08zm669.715 167.756-132.792-79.495c-9.862-5.943-22.415-4.739-30.985 2.972l-162.301 144.873c-6.846 6.114-10.062 15.362-8.499 24.441 1.563 9.08 7.681 16.698 16.171 20.135l225.157 91.233c3.088 1.283 6.397 1.939 9.738 1.932 10.449.033 19.936-6.142 24.197-15.751l69.79-156.314c5.68-12.37 1.157-27.062-10.476-34.026zm18.443-190.043 34.379 108.171h-.295c2.542 8.091 1.097 16.919-3.889 23.761-4.986 6.841-12.915 10.876-21.342 10.86-4.728.016-9.37-1.269-13.427-3.715l-93.102-55.72c-7.254-4.243-11.992-11.789-12.689-20.208-.87-8.456 2.373-16.814 8.705-22.436l59.019-52.6c6.668-5.976 15.881-8.156 24.493-5.795 8.609 2.459 15.423 9.098 18.148 17.682zm-492.511 282.761c1.587-9.042-1.597-18.266-8.41-24.368l-162.302-144.873c-8.57-7.711-21.123-8.915-30.985-2.972l-132.7921 79.495c-11.4977 6.995-16.0467 21.502-10.6233 33.878l69.9374 156.314c5.794 13.034 20.774 19.134 33.936 13.818l225.009-91.232c8.492-3.407 14.632-10.995 16.23-20.06zm79.675 44.577 180.598 73.105c8.83 3.779 14.93 12.084 15.935 21.694 1.143 9.729-3.178 19.291-11.214 24.814l-180.745 125.556c-4.331 3.043-9.473 4.7-14.754 4.755-5.277-.082-10.411-1.737-14.755-4.755l-180.597-125.556c-8.066-5.508-12.439-15.061-11.362-24.814 1.206-9.71 7.526-18.006 16.526-21.694l180.597-73.105c6.351-2.532 13.421-2.532 19.771 0z" fill="#bf4722" fill-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/frameworks",
|
||||
"version": "0.0.15-canary.3",
|
||||
"version": "0.0.18-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"
|
||||
}
|
||||
}
|
||||
|
||||
38
packages/frameworks/test/frameworks.unit.test.ts
vendored
38
packages/frameworks/test/frameworks.unit.test.ts
vendored
@@ -1,11 +1,9 @@
|
||||
import Ajv from 'ajv';
|
||||
import path from 'path';
|
||||
import { join } from 'path';
|
||||
import { existsSync } from 'fs';
|
||||
import { isString } from 'util';
|
||||
import { Framework } from '../';
|
||||
|
||||
function isString(arg: any): arg is string {
|
||||
return typeof arg === 'string';
|
||||
}
|
||||
const frameworkList = require('../frameworks.json') as Framework[];
|
||||
|
||||
const SchemaFrameworkDetectionItem = {
|
||||
type: 'array',
|
||||
@@ -60,6 +58,7 @@ const Schema = {
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
slug: { type: ['string', 'null'] },
|
||||
sort: { type: 'number' },
|
||||
logo: { type: 'string' },
|
||||
demo: { type: 'string' },
|
||||
tagline: { type: 'string' },
|
||||
@@ -89,12 +88,10 @@ const Schema = {
|
||||
|
||||
describe('frameworks', () => {
|
||||
it('ensure there is an example for every framework', async () => {
|
||||
const root = path.join(__dirname, '..', '..', '..');
|
||||
const getExample = (name: string) => path.join(root, 'examples', name);
|
||||
const root = join(__dirname, '..', '..', '..');
|
||||
const getExample = (name: string) => join(root, 'examples', name);
|
||||
|
||||
const frameworks = require('../frameworks.json') as Framework[];
|
||||
|
||||
const result = frameworks
|
||||
const result = frameworkList
|
||||
.map(f => f.slug)
|
||||
.filter(isString)
|
||||
.filter(f => existsSync(getExample(f)) === false);
|
||||
@@ -103,10 +100,8 @@ describe('frameworks', () => {
|
||||
});
|
||||
|
||||
it('ensure schema', async () => {
|
||||
const frameworks = require('../frameworks.json') as Framework[];
|
||||
|
||||
const ajv = new Ajv();
|
||||
const result = ajv.validate(Schema, frameworks);
|
||||
const result = ajv.validate(Schema, frameworkList);
|
||||
|
||||
if (ajv.errors) {
|
||||
console.error(ajv.errors);
|
||||
@@ -116,17 +111,26 @@ describe('frameworks', () => {
|
||||
});
|
||||
|
||||
it('ensure logo', async () => {
|
||||
const frameworks = require('../frameworks.json') as Framework[];
|
||||
|
||||
const missing = frameworks
|
||||
const missing = frameworkList
|
||||
.map(f => f.logo)
|
||||
.filter(url => {
|
||||
const prefix =
|
||||
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/';
|
||||
const name = url.replace(prefix, '');
|
||||
return existsSync(path.join(__dirname, '..', 'logos', name)) === false;
|
||||
return existsSync(join(__dirname, '..', 'logos', name)) === false;
|
||||
});
|
||||
|
||||
expect(missing).toEqual([]);
|
||||
});
|
||||
|
||||
it('ensure unique sort number', async () => {
|
||||
const sortNumToSlug = new Map<number, string | null>();
|
||||
frameworkList.forEach(f => {
|
||||
if (f.sort) {
|
||||
const duplicateSlug = sortNumToSlug.get(f.sort);
|
||||
expect(duplicateSlug).toStrictEqual(undefined);
|
||||
sortNumToSlug.set(f.sort, f.slug);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
out="dist"
|
||||
|
||||
rm -rf "$out"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/build-utils",
|
||||
"version": "2.3.2-canary.2",
|
||||
"version": "2.4.3-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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import { isOfficialRuntime } from './';
|
||||
interface ErrorResponse {
|
||||
code: string;
|
||||
message: string;
|
||||
action?: string;
|
||||
link?: string;
|
||||
}
|
||||
|
||||
interface Options {
|
||||
@@ -82,6 +84,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 +102,7 @@ export async function detectBuilders(
|
||||
defaultRoutes: null,
|
||||
redirectRoutes: null,
|
||||
rewriteRoutes: null,
|
||||
errorRoutes: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -154,6 +158,7 @@ export async function detectBuilders(
|
||||
defaultRoutes: null,
|
||||
redirectRoutes: null,
|
||||
rewriteRoutes: null,
|
||||
errorRoutes: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -231,6 +236,7 @@ export async function detectBuilders(
|
||||
redirectRoutes: null,
|
||||
defaultRoutes: null,
|
||||
rewriteRoutes: null,
|
||||
errorRoutes: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -272,6 +278,7 @@ export async function detectBuilders(
|
||||
redirectRoutes: null,
|
||||
defaultRoutes: null,
|
||||
rewriteRoutes: null,
|
||||
errorRoutes: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -302,6 +309,13 @@ export async function detectBuilders(
|
||||
options
|
||||
);
|
||||
|
||||
if (frontendBuilder && framework === 'redwoodjs') {
|
||||
// RedwoodJS uses the /api directory differently so we must
|
||||
// clear any existing builders and only use `@vercel/redwood`.
|
||||
builders.length = 0;
|
||||
builders.push(frontendBuilder);
|
||||
}
|
||||
|
||||
return {
|
||||
warnings,
|
||||
builders: builders.length ? builders : null,
|
||||
@@ -309,6 +323,7 @@ export async function detectBuilders(
|
||||
redirectRoutes: routesResult.redirectRoutes,
|
||||
defaultRoutes: routesResult.defaultRoutes,
|
||||
rewriteRoutes: routesResult.rewriteRoutes,
|
||||
errorRoutes: routesResult.errorRoutes,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -456,10 +471,14 @@ function detectFrontBuilder(
|
||||
});
|
||||
}
|
||||
|
||||
if (framework === 'nextjs') {
|
||||
if (framework === 'nextjs' || framework === 'blitzjs') {
|
||||
return { src: 'package.json', use: `@vercel/next${withTag}`, config };
|
||||
}
|
||||
|
||||
if (framework === 'redwoodjs') {
|
||||
return { src: 'package.json', use: `@vercel/redwood${withTag}`, config };
|
||||
}
|
||||
|
||||
// Entrypoints for other frameworks
|
||||
// TODO - What if just a build script is provided, but no entrypoint.
|
||||
const entrypoints = new Set([
|
||||
@@ -490,7 +509,7 @@ function getMissingBuildScriptError() {
|
||||
code: 'missing_build_script',
|
||||
message:
|
||||
'Your `package.json` file is missing a `build` property inside the `scripts` property.' +
|
||||
'\nMore details: https://vercel.com/docs/v2/platform/frequently-asked-questions#missing-build-script',
|
||||
'\nLearn More: https://vercel.com/docs/v2/platform/frequently-asked-questions#missing-build-script',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -602,20 +621,22 @@ function checkUnusedFunctions(
|
||||
} else {
|
||||
return {
|
||||
code: 'unused_function',
|
||||
message: `The function for ${fnKey} can't be handled by any builder`,
|
||||
message: `The pattern "${fnKey}" defined in \`functions\` doesn't match any Serverless Functions.`,
|
||||
action: 'Learn More',
|
||||
link: 'https://vercel.link/unmatched-function-pattern',
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (unusedFunctions.size) {
|
||||
const [unusedFunction] = Array.from(unusedFunctions);
|
||||
const [fnKey] = Array.from(unusedFunctions);
|
||||
|
||||
return {
|
||||
code: 'unused_function',
|
||||
message:
|
||||
`The function for ${unusedFunction} can't be handled by any builder. ` +
|
||||
`Make sure it is inside the api/ directory.`,
|
||||
message: `The pattern "${fnKey}" defined in \`functions\` doesn't match any Serverless Functions inside the \`api\` directory.`,
|
||||
action: 'Learn More',
|
||||
link: 'https://vercel.link/unmatched-function-pattern',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -898,10 +919,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 +996,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 "Learn More".
|
||||
*/
|
||||
action?: string;
|
||||
}
|
||||
|
||||
export function getPrettyError(obj: {
|
||||
dataPath?: string;
|
||||
message?: string;
|
||||
params: any;
|
||||
}): NowBuildError {
|
||||
const docsUrl = 'https://vercel.com/docs/configuration';
|
||||
try {
|
||||
const { dataPath, params, message: ajvMessage } = obj;
|
||||
const prop = getTopLevelPropertyName(dataPath);
|
||||
let message =
|
||||
dataPath && dataPath.startsWith('.') ? `\`${dataPath.slice(1)}\` ` : '';
|
||||
|
||||
if (params && typeof params.additionalProperty === 'string') {
|
||||
const suggestion = getSuggestion(prop, params.additionalProperty);
|
||||
message += `should NOT have additional property \`${params.additionalProperty}\`. ${suggestion}`;
|
||||
} else if (params && typeof params.missingProperty === 'string') {
|
||||
message += `missing required property \`${params.missingProperty}\`.`;
|
||||
} else {
|
||||
message += `${ajvMessage}.`;
|
||||
}
|
||||
|
||||
return new NowBuildError({
|
||||
code: 'DEV_VALIDATE_CONFIG',
|
||||
message: message,
|
||||
link: prop ? `${docsUrl}#project/${prop.toLowerCase()}` : docsUrl,
|
||||
action: 'View Documentation',
|
||||
});
|
||||
} catch (e) {
|
||||
return new NowBuildError({
|
||||
code: 'DEV_VALIDATE_CONFIG',
|
||||
message: `Failed to validate configuration.`,
|
||||
link: docsUrl,
|
||||
action: 'View Documentation',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the top level property from the dataPath.
|
||||
* `.cleanUrls` => `cleanUrls`
|
||||
* `.headers[0].source` => `headers`
|
||||
* `.headers[0].headers[0]` => `headers`
|
||||
* `` => ``
|
||||
*/
|
||||
function getTopLevelPropertyName(dataPath?: string): string {
|
||||
if (dataPath && dataPath.startsWith('.')) {
|
||||
const lastIndex = dataPath.indexOf('[');
|
||||
return lastIndex > -1 ? dataPath.slice(1, lastIndex) : dataPath.slice(1);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
const mapTypoToSuggestion: { [key: string]: { [key: string]: string } } = {
|
||||
'': {
|
||||
builder: 'builds',
|
||||
'build.env': '{ "build": { "env": {"name": "value"} } }',
|
||||
'builds.env': '{ "build": { "env": {"name": "value"} } }',
|
||||
},
|
||||
rewrites: { src: 'source', dest: 'destination' },
|
||||
redirects: { src: 'source', dest: 'destination', status: 'statusCode' },
|
||||
headers: { src: 'source', header: 'headers' },
|
||||
routes: {
|
||||
source: 'src',
|
||||
destination: 'dest',
|
||||
header: 'headers',
|
||||
method: 'methods',
|
||||
},
|
||||
};
|
||||
|
||||
function getSuggestion(topLevelProp: string, additionalProperty: string) {
|
||||
const choices = mapTypoToSuggestion[topLevelProp];
|
||||
const choice = choices ? choices[additionalProperty] : undefined;
|
||||
return choice ? `Did you mean \`${choice}\`?` : 'Please remove it.';
|
||||
}
|
||||
|
||||
@@ -15,7 +15,9 @@ async function readFileOrNull(file: string) {
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function readConfigFile<T>(files: string | string[]) {
|
||||
export async function readConfigFile<T>(
|
||||
files: string | string[]
|
||||
): Promise<T | null> {
|
||||
files = Array.isArray(files) ? files : [files];
|
||||
|
||||
for (const name of files) {
|
||||
@@ -24,11 +26,11 @@ export async function readConfigFile<T>(files: string | string[]) {
|
||||
if (data) {
|
||||
const str = data.toString('utf8');
|
||||
if (name.endsWith('.json')) {
|
||||
return JSON.parse(str);
|
||||
return JSON.parse(str) as T;
|
||||
} else if (name.endsWith('.toml')) {
|
||||
return (toml.parse(str) as unknown) as T;
|
||||
} else if (name.endsWith('.yaml') || name.endsWith('.yml')) {
|
||||
return yaml.safeLoad(str, { filename: name });
|
||||
return yaml.safeLoad(str, { filename: name }) as T;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,20 +349,35 @@ export async function runPipInstall(
|
||||
);
|
||||
}
|
||||
|
||||
export function getScriptName(
|
||||
pkg: Pick<PackageJson, 'scripts'> | null | undefined,
|
||||
possibleNames: Iterable<string>
|
||||
): string | null {
|
||||
if (pkg && pkg.scripts) {
|
||||
for (const name of possibleNames) {
|
||||
if (name in pkg.scripts) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function runPackageJsonScript(
|
||||
destPath: string,
|
||||
scriptName: string,
|
||||
scriptNames: string | Iterable<string>,
|
||||
spawnOpts?: SpawnOptions
|
||||
) {
|
||||
assert(path.isAbsolute(destPath));
|
||||
const { packageJson, cliType } = await scanParentDirs(destPath, true);
|
||||
const hasScript = Boolean(
|
||||
packageJson &&
|
||||
packageJson.scripts &&
|
||||
scriptName &&
|
||||
packageJson.scripts[scriptName]
|
||||
const scriptName = getScriptName(
|
||||
packageJson,
|
||||
typeof scriptNames === 'string' ? [scriptNames] : scriptNames
|
||||
);
|
||||
if (!hasScript) return false;
|
||||
if (!scriptName) return false;
|
||||
|
||||
debug('Running user script...');
|
||||
const runScriptTime = Date.now();
|
||||
|
||||
if (cliType === 'npm') {
|
||||
const prettyCommand = `npm run ${scriptName}`;
|
||||
@@ -382,6 +397,7 @@ export async function runPackageJsonScript(
|
||||
});
|
||||
}
|
||||
|
||||
debug(`Script complete [${Date.now() - runScriptTime}ms]`);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
execCommand,
|
||||
spawnCommand,
|
||||
walkParentDirs,
|
||||
getScriptName,
|
||||
installDependencies,
|
||||
runPackageJsonScript,
|
||||
runNpmInstall,
|
||||
@@ -27,6 +28,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';
|
||||
@@ -46,6 +48,7 @@ export {
|
||||
rename,
|
||||
execAsync,
|
||||
spawnAsync,
|
||||
getScriptName,
|
||||
installDependencies,
|
||||
runPackageJsonScript,
|
||||
execCommand,
|
||||
@@ -111,9 +114,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,
|
||||
});
|
||||
|
||||
@@ -774,8 +775,9 @@ describe('Test `detectBuilders`', () => {
|
||||
expect(errors).toEqual([
|
||||
{
|
||||
code: 'unused_function',
|
||||
message:
|
||||
"The function for server/**/*.ts can't be handled by any builder. Make sure it is inside the api/ directory.",
|
||||
message: `The pattern "server/**/*.ts" defined in \`functions\` doesn't match any Serverless Functions inside the \`api\` directory.`,
|
||||
action: 'Learn More',
|
||||
link: 'https://vercel.link/unmatched-function-pattern',
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -867,12 +869,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 +891,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 +919,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 +938,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 +949,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 +961,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 +976,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 +1005,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 +1018,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 +1034,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 +1048,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 +1063,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 +1077,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 +1088,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 +1102,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 +1118,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 +1132,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 +1144,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 +1152,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 +1172,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 +1201,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 +1217,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 +1234,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 +1375,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 +1384,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 +1539,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 +1548,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 +1580,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 +1681,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
|
||||
// @ts-ignore
|
||||
const { errors } = await detectBuilders(files, null, {
|
||||
// @ts-ignore
|
||||
functions,
|
||||
featHandleMiss,
|
||||
});
|
||||
@@ -1646,6 +1698,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 +1734,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, null, {
|
||||
projectSettings,
|
||||
featHandleMiss,
|
||||
@@ -1693,6 +1747,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 +1763,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
rewriteRoutes,
|
||||
errorRoutes,
|
||||
} = await detectBuilders(files, null, {
|
||||
projectSettings,
|
||||
featHandleMiss,
|
||||
@@ -1722,6 +1779,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 +1789,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 +1804,38 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
},
|
||||
},
|
||||
]);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('RedwoodJS should only use redwood builder', async () => {
|
||||
const files = [
|
||||
'package.json',
|
||||
'web/index.html',
|
||||
'api/one.js',
|
||||
'api/two.js',
|
||||
];
|
||||
const projectSettings = {
|
||||
framework: 'redwoodjs',
|
||||
};
|
||||
|
||||
const { builders, errorRoutes } = await detectBuilders(files, null, {
|
||||
projectSettings,
|
||||
featHandleMiss,
|
||||
});
|
||||
|
||||
expect(builders).toEqual([
|
||||
{
|
||||
use: '@vercel/redwood',
|
||||
src: 'package.json',
|
||||
config: {
|
||||
zeroConfig: true,
|
||||
framework: 'redwoodjs',
|
||||
},
|
||||
},
|
||||
]);
|
||||
expect(errorRoutes!.length).toBe(1);
|
||||
expect((errorRoutes![0] as Source).status).toBe(404);
|
||||
});
|
||||
|
||||
it('No framework, only package.json', async () => {
|
||||
@@ -1755,7 +1846,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 +1859,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 +1889,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 () => {
|
||||
@@ -1812,8 +1909,9 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
||||
expect(errors).toEqual([
|
||||
{
|
||||
code: 'unused_function',
|
||||
message:
|
||||
"The function for server/**/*.ts can't be handled by any builder. Make sure it is inside the api/ directory.",
|
||||
message: `The pattern "server/**/*.ts" defined in \`functions\` doesn't match any Serverless Functions inside the \`api\` directory.`,
|
||||
action: 'Learn More',
|
||||
link: 'https://vercel.link/unmatched-function-pattern',
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -1843,13 +1941,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 +2113,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 +2129,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 +2151,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 +2425,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 +2472,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 +2483,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 +2724,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 +2973,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.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
72
packages/now-build-utils/test/unit.get-script-name.test.ts
vendored
Normal file
72
packages/now-build-utils/test/unit.get-script-name.test.ts
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
import assert from 'assert';
|
||||
import { getScriptName } from '../src';
|
||||
|
||||
describe('Test `getScriptName()`', () => {
|
||||
it('should return "vercel-*"', () => {
|
||||
const pkg = {
|
||||
scripts: {
|
||||
'vercel-dev': '',
|
||||
'vercel-build': '',
|
||||
dev: '',
|
||||
build: '',
|
||||
},
|
||||
};
|
||||
assert.equal(
|
||||
getScriptName(pkg, ['vercel-dev', 'now-dev', 'dev']),
|
||||
'vercel-dev'
|
||||
);
|
||||
assert.equal(
|
||||
getScriptName(pkg, ['vercel-build', 'now-build', 'build']),
|
||||
'vercel-build'
|
||||
);
|
||||
assert.equal(getScriptName(pkg, ['dev']), 'dev');
|
||||
assert.equal(getScriptName(pkg, ['build']), 'build');
|
||||
});
|
||||
|
||||
it('should return "now-*"', () => {
|
||||
const pkg = {
|
||||
scripts: {
|
||||
'now-dev': '',
|
||||
'now-build': '',
|
||||
dev: '',
|
||||
build: '',
|
||||
},
|
||||
};
|
||||
assert.equal(
|
||||
getScriptName(pkg, ['vercel-dev', 'now-dev', 'dev']),
|
||||
'now-dev'
|
||||
);
|
||||
assert.equal(
|
||||
getScriptName(pkg, ['vercel-build', 'now-build', 'build']),
|
||||
'now-build'
|
||||
);
|
||||
assert.equal(getScriptName(pkg, ['dev']), 'dev');
|
||||
assert.equal(getScriptName(pkg, ['build']), 'build');
|
||||
});
|
||||
|
||||
it('should return base script name', () => {
|
||||
const pkg = {
|
||||
scripts: {
|
||||
dev: '',
|
||||
build: '',
|
||||
},
|
||||
};
|
||||
assert.equal(getScriptName(pkg, ['dev']), 'dev');
|
||||
assert.equal(getScriptName(pkg, ['build']), 'build');
|
||||
});
|
||||
|
||||
it('should return `null`', () => {
|
||||
assert.equal(getScriptName(undefined, ['build']), null);
|
||||
assert.equal(getScriptName({}, ['build']), null);
|
||||
assert.equal(getScriptName({ scripts: {} }, ['build']), null);
|
||||
|
||||
const pkg = {
|
||||
scripts: {
|
||||
dev: '',
|
||||
build: '',
|
||||
},
|
||||
};
|
||||
assert.equal(getScriptName(pkg, ['vercel-dev', 'now-dev']), null);
|
||||
assert.equal(getScriptName(pkg, ['vercel-build', 'now-build']), null);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/cgi",
|
||||
"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": "20.0.0-canary.7",
|
||||
"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,15 @@
|
||||
"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.3-canary.2",
|
||||
"@vercel/go": "1.1.5-canary.0",
|
||||
"@vercel/next": "2.6.15",
|
||||
"@vercel/node": "1.7.4",
|
||||
"@vercel/python": "1.2.2",
|
||||
"@vercel/redwood": "0.0.2-canary.1",
|
||||
"@vercel/ruby": "1.2.3",
|
||||
"@vercel/static-build": "0.17.7-canary.1",
|
||||
"update-notifier": "4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sentry/node": "5.5.0",
|
||||
@@ -106,7 +108,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",
|
||||
@@ -119,7 +121,7 @@
|
||||
"chalk": "2.4.2",
|
||||
"chokidar": "3.3.1",
|
||||
"clipboardy": "2.1.0",
|
||||
"codecov": "3.6.5",
|
||||
"codecov": "3.7.1",
|
||||
"cpy": "7.2.0",
|
||||
"credit-card": "3.0.1",
|
||||
"date-fns": "1.29.0",
|
||||
@@ -136,6 +138,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,9 +185,8 @@
|
||||
"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",
|
||||
"which": "2.0.2",
|
||||
"which-promise": "1.0.0",
|
||||
|
||||
@@ -49,7 +49,13 @@ async function main() {
|
||||
// Do the initial `ncc` build
|
||||
console.log();
|
||||
const src = join(dirRoot, 'src');
|
||||
const args = ['@zeit/ncc', 'build', '--source-map'];
|
||||
const args = [
|
||||
'@zeit/ncc',
|
||||
'build',
|
||||
'--source-map',
|
||||
'--external',
|
||||
'update-notifier',
|
||||
];
|
||||
if (!isDev) {
|
||||
args.push('--minify');
|
||||
}
|
||||
@@ -75,7 +81,18 @@ async function main() {
|
||||
const dest = join(dirRoot, 'dist/runtimes');
|
||||
await cpy('**/*', dest, { parents: true, cwd: runtimes });
|
||||
|
||||
console.log('Finished building `now-cli`');
|
||||
// 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 Vercel CLI');
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', (reason: any, promise: Promise<any>) => {
|
||||
|
||||
@@ -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`
|
||||
);
|
||||
@@ -355,7 +354,7 @@ export default async function main(
|
||||
path,
|
||||
sourcePath,
|
||||
project
|
||||
? `To change your project settings, go to https://vercel.com/${org.slug}/${project.name}/settings`
|
||||
? `To change your Project Settings, go to https://vercel.com/${org.slug}/${project.name}/settings`
|
||||
: ''
|
||||
)) === false
|
||||
) {
|
||||
@@ -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;
|
||||
}
|
||||
@@ -667,7 +669,7 @@ export default async function main(
|
||||
}
|
||||
|
||||
if (err instanceof BuildError) {
|
||||
output.error('Build failed');
|
||||
output.error(err.message || 'Build failed');
|
||||
output.error(
|
||||
`Check your logs at https://${now.url}/_logs or run ${getCommandName(
|
||||
`logs ${now.url}`,
|
||||
@@ -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) {
|
||||
|
||||
@@ -8,11 +8,15 @@ import Client from '../../util/client';
|
||||
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, ProjectEnvTarget } from '../../types';
|
||||
import getDecryptedEnvRecords from '../../util/get-decrypted-env-records';
|
||||
import { Env } from '@vercel/build-utils';
|
||||
import setupAndLink from '../../util/link/setup-and-link';
|
||||
|
||||
type Options = {
|
||||
'--debug'?: boolean;
|
||||
'--listen'?: string;
|
||||
'--confirm': boolean;
|
||||
};
|
||||
|
||||
export default async function dev(
|
||||
@@ -34,37 +38,56 @@ export default async function dev(
|
||||
});
|
||||
|
||||
// retrieve dev command
|
||||
const [link, frameworks] = await Promise.all([
|
||||
let [link, frameworks] = await Promise.all([
|
||||
getLinkedProject(output, client, cwd),
|
||||
getFrameworks(client),
|
||||
]);
|
||||
|
||||
if (link.status === 'not_linked' && !process.env.__VERCEL_SKIP_DEV_CMD) {
|
||||
const autoConfirm = opts['--confirm'];
|
||||
const forceDelete = false;
|
||||
|
||||
link = await setupAndLink(
|
||||
ctx,
|
||||
output,
|
||||
cwd,
|
||||
forceDelete,
|
||||
autoConfirm,
|
||||
'link',
|
||||
'Set up and develop'
|
||||
);
|
||||
|
||||
if (link.status === 'not_linked') {
|
||||
// User aborted project linking questions
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (link.status === 'error') {
|
||||
return link.exitCode;
|
||||
}
|
||||
|
||||
if (link.status === 'not_linked' && !process.env.__VERCEL_SKIP_DEV_CMD) {
|
||||
output.error(
|
||||
`Your codebase isn’t linked to a project on Vercel. Run ${getCommandName()} to link it.`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
let devCommand: undefined | string;
|
||||
let frameworkSlug: null | string = null;
|
||||
let devCommand: string | undefined;
|
||||
let frameworkSlug: string | undefined;
|
||||
let projectSettings: ProjectSettings | undefined;
|
||||
let environmentVars: Env | 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;
|
||||
}
|
||||
@@ -74,6 +97,13 @@ export default async function dev(
|
||||
if (project.rootDirectory) {
|
||||
cwd = join(cwd, project.rootDirectory);
|
||||
}
|
||||
|
||||
environmentVars = await getDecryptedEnvRecords(
|
||||
output,
|
||||
client,
|
||||
project,
|
||||
ProjectEnvTarget.Development
|
||||
);
|
||||
}
|
||||
|
||||
const devServer = new DevServer(cwd, {
|
||||
@@ -81,6 +111,8 @@ export default async function dev(
|
||||
debug,
|
||||
devCommand,
|
||||
frameworkSlug,
|
||||
projectSettings,
|
||||
environmentVars,
|
||||
});
|
||||
|
||||
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';
|
||||
@@ -31,6 +32,7 @@ const help = () => {
|
||||
-d, --debug Debug mode [off]
|
||||
-l, --listen [uri] Specify a URI endpoint on which to listen [0.0.0.0:3000]
|
||||
-t, --token [token] Specify an Authorization Token
|
||||
--confirm Skip questions and use defaults when setting up a new project
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
@@ -55,6 +57,7 @@ export default async function main(ctx: NowContext) {
|
||||
argv = getArgs(ctx.argv.slice(2), {
|
||||
'--listen': String,
|
||||
'-l': '--listen',
|
||||
'--confirm': Boolean,
|
||||
|
||||
// Deprecated
|
||||
'--port': Number,
|
||||
@@ -96,7 +99,7 @@ export default async function main(ctx: NowContext) {
|
||||
'package.json'
|
||||
)} must not contain ${cmd('now dev')}`
|
||||
);
|
||||
output.error(`More details: http://err.sh/now/now-dev-as-dev-script`);
|
||||
output.error(`Learn More: http://err.sh/now/now-dev-as-dev-script`);
|
||||
return 1;
|
||||
}
|
||||
if (scripts && scripts.dev && /\bvercel\b\W+\bdev\b/.test(scripts.dev)) {
|
||||
@@ -105,7 +108,7 @@ export default async function main(ctx: NowContext) {
|
||||
'package.json'
|
||||
)} must not contain ${cmd('vercel dev')}`
|
||||
);
|
||||
output.error(`More details: http://err.sh/now/now-dev-as-dev-script`);
|
||||
output.error(`Learn More: http://err.sh/now/now-dev-as-dev-script`);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -119,7 +122,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;
|
||||
}
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
import chalk from 'chalk';
|
||||
import psl from 'psl';
|
||||
|
||||
import { NowContext } from '../../types';
|
||||
import { Output } from '../../util/output';
|
||||
import * as ERRORS from '../../util/errors-ts';
|
||||
import addDomain from '../../util/domains/add-domain';
|
||||
import Client from '../../util/client';
|
||||
import cmd from '../../util/output/cmd';
|
||||
import formatDnsTable from '../../util/format-dns-table';
|
||||
import formatNSTable from '../../util/format-ns-table';
|
||||
import getScope from '../../util/get-scope';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import param from '../../util/output/param';
|
||||
import { getCommandName, getTitleName } from '../../util/pkg-name';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import { getDomain } from '../../util/domains/get-domain';
|
||||
import { getLinkedProject } from '../../util/projects/link';
|
||||
import { isPublicSuffix } from '../../util/domains/is-public-suffix';
|
||||
import { getDomainConfig } from '../../util/domains/get-domain-config';
|
||||
import { addDomainToProject } from '../../util/projects/add-domain-to-project';
|
||||
import { removeDomainFromProject } from '../../util/projects/remove-domain-from-project';
|
||||
import code from '../../util/output/code';
|
||||
|
||||
type Options = {
|
||||
'--cdn': boolean;
|
||||
'--debug': boolean;
|
||||
'--no-cdn': boolean;
|
||||
'--force': boolean;
|
||||
};
|
||||
|
||||
export default async function add(
|
||||
@@ -33,6 +34,7 @@ export default async function add(
|
||||
const { currentTeam } = config;
|
||||
const { apiUrl } = ctx;
|
||||
const debug = opts['--debug'];
|
||||
const force = opts['--force'];
|
||||
const client = new Client({ apiUrl, token, currentTeam, debug });
|
||||
let contextName = null;
|
||||
|
||||
@@ -47,105 +49,116 @@ export default async function add(
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (opts['--cdn'] !== undefined || opts['--no-cdn'] !== undefined) {
|
||||
output.error(`Toggling CF from ${getTitleName()} CLI is deprecated.`);
|
||||
return 1;
|
||||
}
|
||||
const project = await getLinkedProject(output, client).then(result => {
|
||||
if (result.status === 'linked') {
|
||||
return result.project;
|
||||
}
|
||||
|
||||
if (args.length !== 1) {
|
||||
return null;
|
||||
});
|
||||
|
||||
if (project && args.length !== 1) {
|
||||
output.error(
|
||||
`${getCommandName('domains add <domain>')} expects one argument`
|
||||
`${getCommandName('domains add <domain>')} expects one argument.`
|
||||
);
|
||||
return 1;
|
||||
} else if (!project && args.length !== 2) {
|
||||
output.error(
|
||||
`${getCommandName(
|
||||
'domains add <domain> <project>'
|
||||
)} expects two arguments.`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const domainName = String(args[0]);
|
||||
const parsedDomain = psl.parse(domainName);
|
||||
if (parsedDomain.error) {
|
||||
output.error(`The provided domain name ${param(domainName)} is invalid`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const { domain, subdomain } = parsedDomain;
|
||||
if (!domain) {
|
||||
output.error(`The provided domain '${param(domainName)}' is not valid.`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (subdomain) {
|
||||
output.error(
|
||||
`You are adding '${domainName}' as a domain name containing a subdomain part '${subdomain}'\n` +
|
||||
` This feature is deprecated, please add just the root domain: ${chalk.cyan(
|
||||
`${getCommandName(`domain add ${domain}`)}`
|
||||
)}`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
const projectName = project ? project.name : String(args[1]);
|
||||
|
||||
const addStamp = stamp();
|
||||
const addedDomain = await addDomain(client, domainName, contextName);
|
||||
|
||||
if (addedDomain instanceof ERRORS.InvalidDomain) {
|
||||
output.error(
|
||||
`The provided domain name "${addedDomain.meta.domain}" is invalid`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
let aliasTarget = await addDomainToProject(client, projectName, domainName);
|
||||
|
||||
if (addedDomain instanceof ERRORS.DomainAlreadyExists) {
|
||||
output.error(
|
||||
`The domain ${chalk.underline(
|
||||
addedDomain.meta.domain
|
||||
)} is already registered by a different account.\n` +
|
||||
` If this seems like a mistake, please contact us at support@vercel.com`
|
||||
);
|
||||
return 1;
|
||||
if (aliasTarget instanceof Error) {
|
||||
if (
|
||||
aliasTarget instanceof ERRORS.APIError &&
|
||||
aliasTarget.code === 'ALIAS_DOMAIN_EXIST' &&
|
||||
aliasTarget.project &&
|
||||
aliasTarget.project.id
|
||||
) {
|
||||
if (force) {
|
||||
const removeResponse = await removeDomainFromProject(
|
||||
client,
|
||||
aliasTarget.project.id,
|
||||
domainName
|
||||
);
|
||||
|
||||
if (removeResponse instanceof Error) {
|
||||
output.prettyError(removeResponse);
|
||||
return 1;
|
||||
}
|
||||
|
||||
aliasTarget = await addDomainToProject(client, projectName, domainName);
|
||||
}
|
||||
}
|
||||
|
||||
if (aliasTarget instanceof Error) {
|
||||
output.prettyError(aliasTarget);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// We can cast the information because we've just added the domain and it should be there
|
||||
console.log(
|
||||
`${chalk.cyan('> Success!')} Domain ${chalk.bold(
|
||||
addedDomain.name
|
||||
)} added correctly. ${addStamp()}\n`
|
||||
domainName
|
||||
)} added to project ${chalk.bold(projectName)}. ${addStamp()}`
|
||||
);
|
||||
|
||||
if (!addedDomain.verified) {
|
||||
if (isPublicSuffix(domainName)) {
|
||||
output.log(
|
||||
`The domain will automatically get assigned to your latest production deployment.`
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const domainResponse = await getDomain(client, contextName, domainName);
|
||||
|
||||
if (domainResponse instanceof Error) {
|
||||
output.prettyError(domainResponse);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const domainConfig = await getDomainConfig(client, contextName, domainName);
|
||||
|
||||
if (domainConfig.misconfigured) {
|
||||
output.warn(
|
||||
`The domain was added but it is not verified. To verify it, you should either:`
|
||||
`This domain is not configured properly. To configure it you should either:`
|
||||
);
|
||||
output.print(
|
||||
` ${chalk.gray(
|
||||
'a)'
|
||||
)} Change your domain nameservers to the following intended set: ${chalk.gray(
|
||||
'[recommended]'
|
||||
)}\n`
|
||||
` ${chalk.grey('a)')} ` +
|
||||
`Set the following record on your DNS provider to continue: ` +
|
||||
`${code(`A ${domainName} 76.76.21.21`)} ` +
|
||||
`${chalk.grey('[recommended]')}\n`
|
||||
);
|
||||
output.print(
|
||||
` ${chalk.grey('b)')} ` +
|
||||
`Change your domain nameservers to the intended set`
|
||||
);
|
||||
output.print(
|
||||
`\n${formatNSTable(
|
||||
addedDomain.intendedNameservers,
|
||||
addedDomain.nameservers,
|
||||
domainResponse.intendedNameservers,
|
||||
domainResponse.nameservers,
|
||||
{ extraSpace: ' ' }
|
||||
)}\n\n`
|
||||
);
|
||||
output.print(
|
||||
` ${chalk.gray(
|
||||
'b)'
|
||||
)} Add a DNS TXT record with the name and value shown below.\n`
|
||||
);
|
||||
output.print(
|
||||
`\n${formatDnsTable([['_now', 'TXT', addedDomain.verificationRecord]], {
|
||||
extraSpace: ' ',
|
||||
})}\n\n`
|
||||
);
|
||||
output.print(
|
||||
` We will run a verification for you and you will receive an email upon completion.\n`
|
||||
);
|
||||
output.print(
|
||||
` If you want to force running a verification, you can run ${cmd(
|
||||
`${getCommandName('domains verify <domain>')}`
|
||||
)}\n`
|
||||
output.print(' Read more: https://vercel.link/domain-configuration\n\n');
|
||||
} else {
|
||||
output.log(
|
||||
`The domain will automatically get assigned to your latest production deployment.`
|
||||
);
|
||||
output.print(' Read more: https://err.sh/now/domain-verification\n\n');
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -13,7 +13,6 @@ import transferIn from './transfer-in';
|
||||
import inspect from './inspect';
|
||||
import ls from './ls';
|
||||
import rm from './rm';
|
||||
import verify from './verify';
|
||||
import move from './move';
|
||||
import { getPkgName } from '../../util/pkg-name';
|
||||
|
||||
@@ -25,17 +24,17 @@ const help = () => {
|
||||
|
||||
ls Show all domains in a list
|
||||
inspect [name] Displays information related to a domain
|
||||
add [name] Add a new domain that you already own
|
||||
add [name] [project] Add a new domain that you already own
|
||||
rm [name] Remove a domain
|
||||
buy [name] Buy a domain that you don't yet own
|
||||
move [name] [destination] Move a domain to another user or team.
|
||||
transfer-in [name] Transfer in a domain to Vercel
|
||||
verify [name] Run a verification for a domain
|
||||
|
||||
${chalk.dim('Options:')}
|
||||
|
||||
-h, --help Output usage information
|
||||
-d, --debug Debug mode [off]
|
||||
-f, --force Force a domain on a project and remove it from an existing one
|
||||
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
|
||||
'FILE'
|
||||
)} Path to the local ${'`vercel.json`'} file
|
||||
@@ -82,7 +81,6 @@ const COMMAND_CONFIG = {
|
||||
move: ['move'],
|
||||
rm: ['rm', 'remove'],
|
||||
transferIn: ['transfer-in'],
|
||||
verify: ['verify'],
|
||||
};
|
||||
|
||||
export default async function main(ctx: NowContext) {
|
||||
@@ -90,10 +88,9 @@ export default async function main(ctx: NowContext) {
|
||||
|
||||
try {
|
||||
argv = getArgs(ctx.argv.slice(2), {
|
||||
'--cdn': Boolean,
|
||||
'--code': String,
|
||||
'--no-cdn': Boolean,
|
||||
'--yes': Boolean,
|
||||
'--force': Boolean,
|
||||
'--next': Number,
|
||||
'-N': '--next',
|
||||
});
|
||||
@@ -122,8 +119,6 @@ export default async function main(ctx: NowContext) {
|
||||
return rm(ctx, argv, args, output);
|
||||
case 'transferIn':
|
||||
return transferIn(ctx, argv, args, output);
|
||||
case 'verify':
|
||||
return verify(ctx, argv, args, output);
|
||||
default:
|
||||
return ls(ctx, argv, args, output);
|
||||
}
|
||||
|
||||
@@ -4,13 +4,16 @@ import { NowContext } from '../../types';
|
||||
import { Output } from '../../util/output';
|
||||
import Client from '../../util/client';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import dnsTable from '../../util/format-dns-table';
|
||||
import formatDate from '../../util/format-date';
|
||||
import formatNSTable from '../../util/format-ns-table';
|
||||
import getDomainByName from '../../util/domains/get-domain-by-name';
|
||||
import getScope from '../../util/get-scope';
|
||||
import formatTable from '../../util/format-table';
|
||||
import { findProjectsForDomain } from '../../util/projects/find-projects-for-domain';
|
||||
import getDomainPrice from '../../util/domains/get-domain-price';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import { getDomainConfig } from '../../util/domains/get-domain-config';
|
||||
import code from '../../util/output/code';
|
||||
|
||||
type Options = {
|
||||
'--debug': boolean;
|
||||
@@ -70,7 +73,7 @@ export default async function inspect(
|
||||
.then(res => (res instanceof Error ? null : res.price))
|
||||
.catch(() => null),
|
||||
]);
|
||||
if (domain instanceof DomainNotFound) {
|
||||
if (!domain || domain instanceof DomainNotFound) {
|
||||
output.error(
|
||||
`Domain not found by "${domainName}" under ${chalk.bold(contextName)}`
|
||||
);
|
||||
@@ -88,6 +91,15 @@ export default async function inspect(
|
||||
return 1;
|
||||
}
|
||||
|
||||
const projects = await findProjectsForDomain(client, domainName);
|
||||
|
||||
if (projects instanceof Error) {
|
||||
output.prettyError(projects);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const domainConfig = await getDomainConfig(client, contextName, domainName);
|
||||
|
||||
output.log(
|
||||
`Domain ${domainName} found under ${chalk.bold(contextName)} ${chalk.gray(
|
||||
inspectStamp()
|
||||
@@ -129,6 +141,7 @@ export default async function inspect(
|
||||
domain.txtVerifiedAt
|
||||
)}\n`
|
||||
);
|
||||
|
||||
if (renewalPrice && domain.boughtAt) {
|
||||
output.print(
|
||||
` ${chalk.cyan('Renewal Price')}\t\t$${renewalPrice} USD\n`
|
||||
@@ -145,37 +158,57 @@ export default async function inspect(
|
||||
);
|
||||
output.print('\n');
|
||||
|
||||
output.print(chalk.bold(' Verification Record\n\n'));
|
||||
output.print(
|
||||
`${dnsTable([['_now', 'TXT', domain.verificationRecord]], {
|
||||
extraSpace: ' ',
|
||||
})}\n`
|
||||
);
|
||||
output.print('\n');
|
||||
|
||||
if (!domain.verified) {
|
||||
output.warn(`This domain is not verified. To verify it you should either:`);
|
||||
output.print(
|
||||
` ${chalk.gray(
|
||||
'a)'
|
||||
)} Change your domain nameservers to the intended set detailed above. ${chalk.gray(
|
||||
'[recommended]'
|
||||
)}\n`
|
||||
if (domainConfig.misconfigured) {
|
||||
output.warn(
|
||||
`This domain is not configured properly. To configure it you should either:`
|
||||
);
|
||||
output.print(
|
||||
` ${chalk.gray(
|
||||
'b)'
|
||||
)} Add a DNS TXT record with the name and value shown above.\n\n`
|
||||
` ${chalk.grey('a)')} ` +
|
||||
`Set the following record on your DNS provider to continue: ` +
|
||||
`${code(`A ${domainName} 76.76.21.21`)} ` +
|
||||
`${chalk.grey('[recommended]')}\n`
|
||||
);
|
||||
output.print(
|
||||
` ${chalk.grey('b)')} ` +
|
||||
`Change your domain nameservers to the intended set detailed above.\n\n`
|
||||
);
|
||||
output.print(
|
||||
` We will run a verification for you and you will receive an email upon completion.\n`
|
||||
);
|
||||
output.print(
|
||||
` If you want to force running a verification, you can run ${getCommandName(
|
||||
`domains verify <domain>`
|
||||
)}\n`
|
||||
output.print(' Read more: https://vercel.link/domain-configuration\n\n');
|
||||
}
|
||||
|
||||
if (Array.isArray(projects) && projects.length > 0) {
|
||||
output.print(chalk.bold(' Projects\n'));
|
||||
|
||||
const table = formatTable(
|
||||
['Project', 'Domains'],
|
||||
['l', 'l'],
|
||||
[
|
||||
{
|
||||
rows: projects.map(project => {
|
||||
const name = project.name;
|
||||
|
||||
const domains = (project.alias || [])
|
||||
.map(target => target.domain)
|
||||
.filter(alias => alias.endsWith(domainName));
|
||||
|
||||
const cols = domains.length ? domains.join(', ') : '-';
|
||||
|
||||
return [name, cols];
|
||||
}),
|
||||
},
|
||||
]
|
||||
);
|
||||
output.print(' Read more: https://err.sh/now/domain-verification\n\n');
|
||||
|
||||
output.print(
|
||||
table
|
||||
.split('\n')
|
||||
.map(line => ` ${line}`)
|
||||
.join('\n')
|
||||
);
|
||||
|
||||
output.print('\n\n');
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -1,22 +1,37 @@
|
||||
import ms from 'ms';
|
||||
import psl from 'psl';
|
||||
import chalk from 'chalk';
|
||||
import table from 'text-table';
|
||||
import plural from 'pluralize';
|
||||
|
||||
import Client from '../../util/client';
|
||||
import getDomains from '../../util/domains/get-domains';
|
||||
import getScope from '../../util/get-scope';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import strlen from '../../util/strlen';
|
||||
import { Output } from '../../util/output';
|
||||
import { Domain, NowContext } from '../../types';
|
||||
import formatTable from '../../util/format-table';
|
||||
import { formatDateWithoutTime } from '../../util/format-date';
|
||||
import { Domain, Project, NowContext } from '../../types';
|
||||
import { getProjectsWithDomains } from '../../util/projects/get-projects-with-domains';
|
||||
import getCommandFlags from '../../util/get-command-flags';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import isDomainExternal from '../../util/domains/is-domain-external';
|
||||
import { isPublicSuffix } from '../../util/domains/is-public-suffix';
|
||||
|
||||
type Options = {
|
||||
'--debug': boolean;
|
||||
'--next': number;
|
||||
};
|
||||
|
||||
interface DomainInfo {
|
||||
domain: string;
|
||||
apexDomain: string;
|
||||
projectName: string | null;
|
||||
dns: 'Vercel' | 'External';
|
||||
configured: boolean;
|
||||
expiresAt: number | null;
|
||||
createdAt: number | null;
|
||||
}
|
||||
|
||||
export default async function ls(
|
||||
ctx: NowContext,
|
||||
opts: Options,
|
||||
@@ -60,16 +75,31 @@ export default async function ls(
|
||||
return 1;
|
||||
}
|
||||
|
||||
const { domains, pagination } = await getDomains(
|
||||
client,
|
||||
contextName,
|
||||
nextTimestamp
|
||||
);
|
||||
const [{ domains, pagination }, projects] = await Promise.all([
|
||||
getDomains(client, contextName),
|
||||
getProjectsWithDomains(client),
|
||||
] as const);
|
||||
|
||||
if (projects instanceof Error) {
|
||||
output.prettyError(projects);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const domainsInfo = createDomainsInfo(domains, projects);
|
||||
|
||||
output.log(
|
||||
`Domains found under ${chalk.bold(contextName)} ${chalk.gray(lsStamp())}\n`
|
||||
`${plural(
|
||||
'project domain',
|
||||
domainsInfo.length,
|
||||
true
|
||||
)} found under ${chalk.bold(contextName)} ${chalk.gray(lsStamp())}`
|
||||
);
|
||||
if (domains.length > 0) {
|
||||
console.log(`${formatDomainsTable(domains)}\n`);
|
||||
|
||||
if (domainsInfo.length > 0) {
|
||||
output.print(
|
||||
formatDomainsTable(domainsInfo).replace(/^(.*)/gm, `${' '.repeat(3)}$1`)
|
||||
);
|
||||
output.print('\n\n');
|
||||
}
|
||||
|
||||
if (pagination && pagination.count === 20) {
|
||||
@@ -84,28 +114,92 @@ export default async function ls(
|
||||
return 0;
|
||||
}
|
||||
|
||||
function formatDomainsTable(domains: Domain[]) {
|
||||
const current = new Date();
|
||||
return table(
|
||||
[
|
||||
[
|
||||
'',
|
||||
chalk.gray('domain'),
|
||||
chalk.gray('serviceType'),
|
||||
chalk.gray('verified'),
|
||||
chalk.gray('cdn'),
|
||||
chalk.gray('age'),
|
||||
].map(s => chalk.dim(s)),
|
||||
...domains.map(domain => {
|
||||
const url = chalk.bold(domain.name);
|
||||
const time = chalk.gray(ms(current.getTime() - domain.createdAt));
|
||||
return ['', url, domain.serviceType, domain.verified, true, time];
|
||||
}),
|
||||
],
|
||||
{
|
||||
align: ['l', 'l', 'l', 'l', 'l'],
|
||||
hsep: ' '.repeat(4),
|
||||
stringLength: strlen,
|
||||
function createDomainsInfo(domains: Domain[], projects: Project[]) {
|
||||
const info = new Map<string, DomainInfo>();
|
||||
|
||||
domains.forEach(domain => {
|
||||
info.set(domain.name, {
|
||||
domain: domain.name,
|
||||
apexDomain: domain.name,
|
||||
projectName: null,
|
||||
expiresAt: domain.expiresAt || null,
|
||||
createdAt: domain.createdAt,
|
||||
configured: Boolean(domain.verified),
|
||||
dns: isDomainExternal(domain) ? 'External' : 'Vercel',
|
||||
});
|
||||
|
||||
projects.forEach(project => {
|
||||
(project.alias || []).forEach(target => {
|
||||
if (!target.domain.endsWith(domain.name)) return;
|
||||
|
||||
info.set(target.domain, {
|
||||
domain: target.domain,
|
||||
apexDomain: domain.name,
|
||||
projectName: project.name,
|
||||
expiresAt: domain.expiresAt || null,
|
||||
createdAt: domain.createdAt || target.createdAt || null,
|
||||
configured: Boolean(domain.verified),
|
||||
dns: isDomainExternal(domain) ? 'External' : 'Vercel',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
projects.forEach(project => {
|
||||
(project.alias || []).forEach(target => {
|
||||
if (info.has(target.domain)) return;
|
||||
|
||||
const { domain: apexDomain } = psl.parse(
|
||||
target.domain
|
||||
) as psl.ParsedDomain;
|
||||
|
||||
info.set(target.domain, {
|
||||
domain: target.domain,
|
||||
apexDomain: apexDomain || target.domain,
|
||||
projectName: project.name,
|
||||
expiresAt: null,
|
||||
createdAt: target.createdAt || null,
|
||||
configured: isPublicSuffix(target.domain),
|
||||
dns: isPublicSuffix(target.domain) ? 'Vercel' : 'External',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const list = Array.from(info.values());
|
||||
|
||||
return list.sort((a, b) => {
|
||||
if (a.apexDomain === b.apexDomain) {
|
||||
if (a.apexDomain === a.domain) return -1;
|
||||
if (b.apexDomain === b.domain) return 1;
|
||||
return a.domain.localeCompare(b.domain);
|
||||
}
|
||||
);
|
||||
|
||||
return a.apexDomain.localeCompare(b.apexDomain);
|
||||
});
|
||||
}
|
||||
|
||||
function formatDomainsTable(domainsInfo: DomainInfo[]) {
|
||||
const current = Date.now();
|
||||
|
||||
const rows: string[][] = domainsInfo.map(info => {
|
||||
const expiration = formatDateWithoutTime(info.expiresAt);
|
||||
const age = info.createdAt ? ms(current - info.createdAt) : '-';
|
||||
|
||||
return [
|
||||
info.domain,
|
||||
info.projectName || '-',
|
||||
info.dns,
|
||||
expiration,
|
||||
info.configured.toString(),
|
||||
chalk.gray(age),
|
||||
];
|
||||
});
|
||||
|
||||
const table = formatTable(
|
||||
['domain', 'project', 'dns', 'expiration', 'configured', 'age'],
|
||||
['l', 'l', 'l', 'l', 'l', 'l'],
|
||||
[{ rows }]
|
||||
);
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user