Compare commits

..

34 Commits

Author SHA1 Message Date
Steven
a400b9b29d Publish Stable
- @vercel/build-utils@2.16.0
 - vercel@24.2.0
 - @vercel/client@11.0.0
 - @vercel/frameworks@0.8.0
 - @vercel/go@1.4.0
 - @vercel/node-bridge@2.2.1
 - @vercel/node@1.15.0
 - @vercel/python@2.3.0
 - @vercel/redwood@0.8.0
 - @vercel/routing-utils@1.13.2
 - @vercel/ruby@1.3.3
 - @vercel/static-build@0.24.0
 - @vercel/static-config@1.0.0
2022-04-29 14:11:32 -04:00
Steven
b549c37149 [build-utils][static-build] Replace 00a0 with space (#7732) 2022-04-28 22:25:24 -04:00
Nathan Rajlich
30d5e64291 Publish Canary
- @vercel/build-utils@2.15.2-canary.6
 - vercel@24.1.1-canary.8
 - @vercel/client@10.4.2-canary.7
 - @vercel/go@1.3.3-canary.6
 - @vercel/node@1.14.2-canary.7
 - @vercel/python@2.2.3-canary.6
 - @vercel/redwood@0.7.1-canary.6
 - @vercel/ruby@1.3.3-canary.6
 - @vercel/static-build@0.23.2-canary.6
2022-04-28 17:50:59 -07:00
Nathan Rajlich
47c2c361d2 [build-utils] Update "yazl" dependency (#7734)
The older version of "yazl" was using `new Buffer()` which causes
deprecation warnings to be printed. The latest version avoids that.
2022-04-28 17:47:10 -07:00
Nathan Rajlich
438576fc7c Publish Canary
- @vercel/build-utils@2.15.2-canary.5
 - vercel@24.1.1-canary.7
 - @vercel/client@10.4.2-canary.6
 - @vercel/frameworks@0.7.2-canary.1
 - @vercel/go@1.3.3-canary.5
 - @vercel/node@1.14.2-canary.6
 - @vercel/python@2.2.3-canary.5
 - @vercel/redwood@0.7.1-canary.5
 - @vercel/ruby@1.3.3-canary.5
 - @vercel/static-build@0.23.2-canary.5
 - @vercel/static-config@1.0.0-canary.1
2022-04-28 11:53:39 -07:00
Steven
b30343ef7b [tests] Bump dependencies for jest/eslint/prettier/turbo/etc (#7727) 2022-04-28 14:52:46 -04:00
Ethan Arrowood
2dc0dfa572 [node][static-build][redwood] Add root path pattern to prepareCache() (#7710) 2022-04-27 21:17:44 -07:00
Steven
9ee54b3dd6 [static-build] Resolve git.io links (#7722)
https://github.blog/changelog/2022-04-25-git-io-deprecation/
2022-04-26 21:57:12 -04:00
Steven
9d67e0bc06 [python] Add discontinue date for Python 3.6 (#7709)
This PR does a few things:
- Changes the existing warning message for Python 3.6 to print a discontinue date
- Will automatically fail new Python 3.6 deployments created after that date
- Consolidates logic to make Python version selection work in a similar manner to Node.js version selection
- Changes tests from JS to TS
2022-04-26 15:49:19 -04:00
Steven
466135cf84 Publish Canary
- @vercel/build-utils@2.15.2-canary.4
 - vercel@24.1.1-canary.6
 - @vercel/client@10.4.2-canary.5
 - @vercel/go@1.3.3-canary.4
 - @vercel/node@1.14.2-canary.5
 - @vercel/python@2.2.3-canary.4
 - @vercel/redwood@0.7.1-canary.4
 - @vercel/ruby@1.3.3-canary.4
 - @vercel/static-build@0.23.2-canary.4
2022-04-25 12:49:34 -04:00
Steven
eab2e229dc [build-utils] Add warning for experimental Node.js (#7717) 2022-04-25 12:49:04 -04:00
Steven
698b89a2ba Publish Canary
- @vercel/build-utils@2.15.2-canary.3
 - vercel@24.1.1-canary.5
 - @vercel/client@10.4.2-canary.4
 - @vercel/frameworks@0.7.2-canary.0
 - @vercel/go@1.3.3-canary.3
 - @vercel/node@1.14.2-canary.4
 - @vercel/python@2.2.3-canary.3
 - @vercel/redwood@0.7.1-canary.3
 - @vercel/ruby@1.3.3-canary.3
 - @vercel/static-build@0.23.2-canary.3
2022-04-25 12:02:13 -04:00
Steven
bae2a2e4df [python] Upgrade tests (#7711)
* [python] Upgrade tests

* Fix latest sanic asgi

* Bump flask

* Change requirements.txt to Pipfile

* Fix dev test

* Use verbose requirements.txt

* Flip requirements
2022-04-25 12:01:49 -04:00
Steven
57916bb712 [build-utils] Add env var ENABLE_EXPERIMENTAL_NODE16 (#7489)
This PR uses an environment variable since this feature is not available to all accounts yet.
2022-04-25 11:07:16 -04:00
Sean Massa
12bbd4e8eb [cli] Update language of prebuilt error (#7702) 2022-04-22 09:55:27 -07:00
Aaron Morris
4e4c7023dc [frameworks] Add opt-in darkModeLogo to Framework in packages/frameworks (#7693)
Supply light mode logos over the frameworks API to be consumed by front

### Related Issues

### 📋 Checklist

<!--
  Please keep your PR as a Draft until the checklist is complete
-->

#### Tests

- [ ] The code changed/added as part of this PR has been covered with tests
- [ ] All tests pass locally with `yarn test-unit`

#### Code Review

- [x] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2022-04-22 16:49:39 +00:00
Nathan Rajlich
41805790e7 [static-build] Support Build Output API v3 cache property in prepareCache() (#7704)
When a framework that outputs Build Output API v3 specifies the `cache`
property in the `config.json` file then static-build will include those
files in the `prepareCache()` function result.
2022-04-21 18:04:21 -07:00
Ethan Arrowood
6ad77ae8e1 Publish Canary
- vercel@24.1.1-canary.4
 - @vercel/client@10.4.2-canary.3
2022-04-21 16:01:41 -06:00
Ethan Arrowood
e8daf36cd7 only revert override feature (#7701) 2022-04-21 15:59:55 -06:00
Nathan Rajlich
c319a2c499 [client] Remove single file deployment root path / route (#7699)
This is strange behavior, and also inconsistent compared to how a Git deployment is created. Let's remove it.
2022-04-21 00:35:25 +00:00
Sean Massa
a410baa797 [cli] check for prebuilt directory (#7697)
When using `vc deploy --prebuilt`, we need to check for an actual ".vercel/output" before attempting to deploy. This PR adds a a check for that. Now...

In a directory without `.vercel/output`:

```
$ vc deploy --prebuilt
Error! Option `--prebuilt` was used, but no prebuilt deploy found in ".vercel/output"
```

In a direcotry with `.vercel/output` (where I've not linked it yet):

```
$ vc deploy --prebuilt
? Set up and deploy “~/source/vercel/examples/build-output-api/serverless-function”? [Y/n]
```
2022-04-20 19:54:47 +00:00
Dominik Ferber
625568e659 fix ts-eager (#7677)
Switches await import() to require().default so that ts-eager understands them.

closes #7676
2022-04-20 10:30:48 -07:00
Nathan Rajlich
41868c1fe0 [cli] Ensure .vercel directory is created in vc pull (#7695)
When the `.vercel` directory does not exist and the env vars
(`VERCEL_PROJECT_ID`/`VERCEL_ORG_ID`) are used for project
linking, the `vercel pull` command was throwing an error:

```
$ VERCEL_PROJECT_ID=xxxxxxxx VERCEL_ORG_ID=xxxxxxxxxx vercel pull
Vercel CLI 24.1.1-canary.3 — https://vercel.com/feedback
Downloading "development" Environment Variables for Project t
Error! ENOENT: no such file or directory, open '/Code/t/.vercel/.env.development.local'
```
2022-04-19 20:49:44 -07:00
Steven
1b644f1218 [docs] Update available runtimes (#7692) 2022-04-19 12:08:46 -04:00
Sean Massa
29ea0fb06b [examples] Remove amp example (#7686) 2022-04-17 19:29:56 -04:00
Ethan Arrowood
b61f049f11 Publish Canary
- @vercel/build-utils@2.15.2-canary.2
 - vercel@24.1.1-canary.3
 - @vercel/client@10.4.2-canary.2
 - @vercel/go@1.3.3-canary.2
 - @vercel/node@1.14.2-canary.3
 - @vercel/python@2.2.3-canary.2
 - @vercel/redwood@0.7.1-canary.2
 - @vercel/ruby@1.3.3-canary.2
 - @vercel/static-build@0.23.2-canary.2
2022-04-15 15:26:55 -06:00
Ethan Arrowood
16e28f326b [cli] Allow projectSettings override in vercel.json (#7619)
This PR adds the required changes to the CLI to support overridable `vercel.json` `projectSettings` values. This PR is linked with changes on the API side too.

- The core changes are in `packages/cli/src/commands/deploy/index.ts`
- An unused code path was removed from `packages/client/src/create-deployment.ts`, `packages/cli/src/util/deploy/process-deployment.ts` and `packages/cli/src/util/index.ts`.

This will only work for *existing* deployment (not *new* ones). This is implemented by checking the `status` of the deployment (is it linked or not). An error is thrown if the user attempts to send overrided settings to a new project. 

A warning is outputted when overrided settings are discovered and shares what settings are being overridden.

### 📋 Checklist

<!--
  Please keep your PR as a Draft until the checklist is complete
-->

#### Tests

- [x] The code changed/added as part of this PR has been covered with tests
- [x] All tests pass locally with `yarn test-unit`

#### Code Review

- [x] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2022-04-15 21:20:00 +00:00
Sean Massa
276397c940 [examples] fixed vercel deploy link (#7684) 2022-04-15 13:55:42 -05:00
Nathan Rajlich
85d7311199 [static-build] Misc TypeScript cleanup (#7682)
This was just sitting on my local checkout so might as well push it.
2022-04-15 09:42:32 -07:00
Sean Massa
b8114b8b39 [cli] set environment properly in vc pull (#7656)
This PR sets environment properly in `vc pull`. The system env vars were not respecting the target `--environment`.

Adds tests for related `vc env` logic.
2022-04-15 04:41:29 +00:00
QianyuPan
d63e8d3187 [go] Add support for version 1.18 (#7590)
Go 1.18 has been released, but I find that I can't deploy my project on Vercel when `go.mod` has `go 1.18` in it.

1.18 adds some very useful features. It would be very valuable for developers to have Vercel support 1.18.
2022-04-14 19:37:01 -04:00
JJ Kasper
11d3dd04aa Publish Canary
- vercel@24.1.1-canary.2
 - @vercel/node-bridge@2.2.1-canary.0
 - @vercel/node@1.14.2-canary.2
2022-04-14 17:04:05 -05:00
JJ Kasper
46bf95ee36 [node-bridge] Ensure content-type is always set with multi-payloads (#7681)
### Related Issues

This ensures a content-type is always set inside of each part of the multi-part payload as it's needed for proper parsing. This also ensures non-200 status codes/headers are passed back separate when they differ so that they can be handled independently without assuming they all match. 

x-ref: https://vercel.slack.com/archives/C03AYHB6MA9
x-ref: https://github.com/vercel/vercel/pull/7507

### 📋 Checklist

<!--
  Please keep your PR as a Draft until the checklist is complete
-->

#### Tests

- [ ] The code changed/added as part of this PR has been covered with tests
- [ ] All tests pass locally with `yarn test-unit`

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2022-04-14 22:00:10 +00:00
Nathan Rajlich
92252468c2 [tests] Add turbo.json and bump to turbo 1.2.2 (#7620)
* Add `turbo.json`

Ran `npx @turbo/codemod create-turbo-config` like the warnings have been telling us to.

* [tests] Bump `turbo` and allow `turbo.json` in `.vercelignore` (#7623)

Co-authored-by: Jared Palmer <jared@jaredpalmer.com>
Co-authored-by: Steven <steven@ceriously.com>
2022-04-13 11:09:47 -04:00
110 changed files with 2265 additions and 1673 deletions

View File

@@ -6,6 +6,7 @@
!.yarnrc !.yarnrc
!yarn.lock !yarn.lock
!package.json !package.json
!turbo.json
# api # api
!api/ !api/

View File

@@ -307,15 +307,15 @@ This is a [class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refere
This is an abstract enumeration type that is implemented by one of the following possible `String` values: This is an abstract enumeration type that is implemented by one of the following possible `String` values:
- `nodejs14.x`
- `nodejs12.x` - `nodejs12.x`
- `nodejs10.x`
- `go1.x` - `go1.x`
- `java11` - `java11`
- `python3.9` - `python3.9`
- `python3.6` - `dotnet6`
- `dotnetcore2.1` - `dotnetcore3.1`
- `ruby2.5` - `ruby2.7`
- `provided` - `provided.al2`
## `@vercel/build-utils` Helper Functions ## `@vercel/build-utils` Helper Functions

View File

@@ -1 +0,0 @@
.env

View File

@@ -1,19 +0,0 @@
# AMP Example
This directory is a brief example of an [AMP](https://amp.dev/) site that can be deployed to Vercel with zero configuration.
## Deploy Your Own
Deploy your own AMP project with Vercel.
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/vercel/vercel/tree/main/examples/amp)
_Live Example: https://amp-template.vercel.app_
### How We Created This Example
To get started deploying AMP with Vercel, you can use the [Vercel CLI](https://vercel.com/download) to initialize the project:
```shell
$ vercel init amp
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -1,72 +0,0 @@
<!DOCTYPE html>
<html >
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,minimum-scale=1" />
<link rel="shortcut icon" href="favicon.png">
<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
<link rel="canonical" href="index.html" />
<title>AMP Website</title>
<script async src="https://cdn.ampproject.org/v0.js"></script>
<style amp-custom>
body > * {
margin: 3rem 1rem;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
color: #525252;
}
h3 {
font-size: 2rem;
}
h4 {
margin-top: 2rem;
}
p {
font-size: 1.2rem;
line-height: 2rem;
}
.links {
display: flex;
justify-content: center;
margin-bottom: 3rem;
}
.links a {
margin: 0 10px;
font-size: 1rem;
color: #005af0;
}
</style>
</head>
<body>
<center>
<amp-img width=150 height=150 layout="fixed" class="logo" src="logo.png"></amp-img>
<h3>Welcome to your AMP page</h3>
<p>AMP is a web component framework to <br> easily create user-first websites, stories, ads and emails.</p>
<h4>Links</h4>
<div class="links">
<a href="https://amp.dev/">Homepage</a>
<a href="https://amp.dev/documentation/guides-and-tutorials/?format=websites">Tutorials</a>
<a href="https://amp.dev/documentation/examples/">Examples</a>
<a href="https://blog.amp.dev">Blog</a>
</div>
<h4>Ready to get started?</h4>
<div class="links">
<a href="https://amp.dev/documentation/guides-and-tutorials/start/create/?format=websites">Create your first AMP page</a>
</div>
<h4>Get involved</h4>
<div class="links">
<a href="https://twitter.com/amphtml">Twitter</a>
<a href="https://amphtml.slack.com">Slack</a>
<a href="https://amp.dev/events/amp-conf-2019">AMP Conf</a>
<a href="https://amp.dev/events/amp-roadshow">AMP Roadshow</a>
</div>
</center>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

View File

@@ -6,7 +6,7 @@ This directory is a brief example of a [Dojo](https://dojo.io) site that can be
Deploy your own Dojo project with Vercel. Deploy your own Dojo project with Vercel.
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/vercel/vercel/tree/main/dojo&template=dojo) [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/vercel/vercel/tree/main/examples/dojo&template=dojo)
### How We Created This Example ### How We Created This Example

View File

@@ -15,7 +15,7 @@ cache:
env: env:
global: global:
# See https://git.io/vdao3 for details. # See https://github.com/ember-cli/ember-cli/blob/master/docs/build-concurrency.md
- JOBS=1 - JOBS=1
script: script:

View File

@@ -14,37 +14,24 @@
"dependencies": { "dependencies": {
"lerna": "3.16.4" "lerna": "3.16.4"
}, },
"turbo": {
"baseBranch": "origin/main",
"pipeline": {
"build": {
"dependsOn": [
"^build"
],
"outputs": [
"dist/**"
]
}
}
},
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "4.28.0", "@typescript-eslint/eslint-plugin": "5.21.0",
"@typescript-eslint/parser": "4.28.0", "@typescript-eslint/parser": "5.21.0",
"async-retry": "1.2.3", "async-retry": "1.2.3",
"buffer-replace": "1.0.0", "buffer-replace": "1.0.0",
"eslint": "7.29.0", "eslint": "8.14.0",
"eslint-config-prettier": "8.3.0", "eslint-config-prettier": "8.5.0",
"eslint-plugin-jest": "24.3.6", "eslint-plugin-jest": "26.1.5",
"husky": "6.0.0", "husky": "7.0.4",
"jest": "27.3.1", "jest": "28.0.2",
"json5": "2.1.1", "json5": "2.1.1",
"lint-staged": "9.2.5", "lint-staged": "9.2.5",
"node-fetch": "2.6.1", "node-fetch": "2.6.1",
"npm-package-arg": "6.1.0", "npm-package-arg": "6.1.0",
"prettier": "2.3.1", "prettier": "2.6.2",
"ts-eager": "2.0.2", "ts-eager": "2.0.2",
"ts-jest": "27.0.4", "ts-jest": "28.0.0-next.1",
"turbo": "1.1.9" "turbo": "1.2.5"
}, },
"scripts": { "scripts": {
"lerna": "lerna", "lerna": "lerna",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/build-utils", "name": "@vercel/build-utils",
"version": "2.15.2-canary.1", "version": "2.16.0",
"license": "MIT", "license": "MIT",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.js", "types": "./dist/index.d.js",
@@ -23,14 +23,14 @@
"@types/end-of-stream": "^1.4.0", "@types/end-of-stream": "^1.4.0",
"@types/fs-extra": "9.0.13", "@types/fs-extra": "9.0.13",
"@types/glob": "^7.1.1", "@types/glob": "^7.1.1",
"@types/jest": "27.0.1", "@types/jest": "27.4.1",
"@types/js-yaml": "3.12.1", "@types/js-yaml": "3.12.1",
"@types/ms": "0.7.31", "@types/ms": "0.7.31",
"@types/multistream": "2.1.1", "@types/multistream": "2.1.1",
"@types/node-fetch": "^2.1.6", "@types/node-fetch": "^2.1.6",
"@types/semver": "6.0.0", "@types/semver": "6.0.0",
"@types/yazl": "^2.4.1", "@types/yazl": "2.4.2",
"@vercel/frameworks": "0.7.1", "@vercel/frameworks": "0.8.0",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"aggregate-error": "3.0.1", "aggregate-error": "3.0.1",
"async-retry": "1.2.3", "async-retry": "1.2.3",
@@ -47,6 +47,6 @@
"node-fetch": "2.6.1", "node-fetch": "2.6.1",
"semver": "6.1.1", "semver": "6.1.1",
"typescript": "4.3.4", "typescript": "4.3.4",
"yazl": "2.4.3" "yazl": "2.5.1"
} }
} }

View File

@@ -222,6 +222,12 @@ export async function getNodeVersion(
const latest = getLatestNodeVersion(); const latest = getLatestNodeVersion();
return { ...latest, runtime: 'nodejs' }; return { ...latest, runtime: 'nodejs' };
} }
if (process.env.ENABLE_EXPERIMENTAL_NODE16 === '1') {
console.warn(
'Warning: Using experimental Node.js 16.x due to ENABLE_EXPERIMENTAL_NODE16=1'
);
return { major: 16, range: '16.x', runtime: 'nodejs16.x' };
}
const { packageJson } = await scanParentDirs(destPath, true); const { packageJson } = await scanParentDirs(destPath, true);
let { nodeVersion } = config; let { nodeVersion } = config;
let isAuto = true; let isAuto = true;
@@ -544,7 +550,7 @@ export async function runPipInstall(
meta?: Meta meta?: Meta
) { ) {
if (meta && meta.isDev) { if (meta && meta.isDev) {
debug('Skipping dependency installation because dev mode is enabled'); debug('Skipping dependency installation because dev mode is enabled');
return; return;
} }

View File

@@ -332,6 +332,7 @@ export interface ProjectSettings {
sourceFilesOutsideRootDirectory?: boolean; sourceFilesOutsideRootDirectory?: boolean;
directoryListing?: boolean; directoryListing?: boolean;
gitForkProtection?: boolean; gitForkProtection?: boolean;
commandForIgnoringBuildStep?: string | null;
} }
export interface BuilderV2 { export interface BuilderV2 {

View File

@@ -277,6 +277,16 @@ it('should not warn when package.json engines matches project setting from confi
expect(warningMessages).toStrictEqual([]); expect(warningMessages).toStrictEqual([]);
}); });
it('should select nodejs16.x with ENABLE_EXPERIMENTAL_NODE16', async () => {
process.env.ENABLE_EXPERIMENTAL_NODE16 = '1';
const result = await getNodeVersion('/tmp', undefined, {}, {});
delete process.env.ENABLE_EXPERIMENTAL_NODE16;
expect(result).toEqual({ major: 16, range: '16.x', runtime: 'nodejs16.x' });
expect(warningMessages).toStrictEqual([
'Warning: Using experimental Node.js 16.x due to ENABLE_EXPERIMENTAL_NODE16=1',
]);
});
it('should get latest node version', async () => { it('should get latest node version', async () => {
expect(getLatestNodeVersion()).toHaveProperty('major', 14); expect(getLatestNodeVersion()).toHaveProperty('major', 14);
}); });

View File

@@ -1,6 +1,6 @@
{ {
"name": "vercel", "name": "vercel",
"version": "24.1.1-canary.1", "version": "24.2.0",
"preferGlobal": true, "preferGlobal": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"description": "The command-line interface for Vercel", "description": "The command-line interface for Vercel",
@@ -43,14 +43,15 @@
"node": ">= 12" "node": ">= 12"
}, },
"dependencies": { "dependencies": {
"@vercel/build-utils": "2.15.2-canary.1", "@vercel/build-utils": "2.16.0",
"@vercel/go": "1.3.3-canary.1", "@vercel/go": "1.4.0",
"@vercel/node": "1.14.2-canary.1", "@vercel/node": "1.15.0",
"@vercel/python": "2.2.3-canary.1", "@vercel/python": "2.3.0",
"@vercel/ruby": "1.3.3-canary.1", "@vercel/ruby": "1.3.3",
"update-notifier": "4.1.0" "update-notifier": "4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@alex_neo/jest-expect-message": "1.0.5",
"@next/env": "11.1.2", "@next/env": "11.1.2",
"@sentry/node": "5.5.0", "@sentry/node": "5.5.0",
"@sindresorhus/slugify": "0.11.0", "@sindresorhus/slugify": "0.11.0",
@@ -68,7 +69,8 @@
"@types/glob": "7.1.1", "@types/glob": "7.1.1",
"@types/http-proxy": "1.16.2", "@types/http-proxy": "1.16.2",
"@types/inquirer": "7.3.1", "@types/inquirer": "7.3.1",
"@types/jest": "27.0.1", "@types/jest": "27.4.1",
"@types/jest-expect-message": "1.0.3",
"@types/load-json-file": "2.0.7", "@types/load-json-file": "2.0.7",
"@types/mime-types": "2.1.0", "@types/mime-types": "2.1.0",
"@types/minimatch": "3.0.3", "@types/minimatch": "3.0.3",
@@ -88,9 +90,9 @@
"@types/update-notifier": "5.1.0", "@types/update-notifier": "5.1.0",
"@types/which": "1.3.2", "@types/which": "1.3.2",
"@types/write-json-file": "2.2.1", "@types/write-json-file": "2.2.1",
"@vercel/client": "10.4.2-canary.1", "@vercel/client": "11.0.0",
"@vercel/fetch-retry": "5.0.3", "@vercel/fetch-retry": "5.0.3",
"@vercel/frameworks": "0.7.1", "@vercel/frameworks": "0.8.0",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"@zeit/fun": "0.11.2", "@zeit/fun": "0.11.2",
"@zeit/source-map-support": "0.6.2", "@zeit/source-map-support": "0.6.2",
@@ -176,6 +178,9 @@
"isolatedModules": true "isolatedModules": true
} }
}, },
"setupFilesAfterEnv": [
"@alex_neo/jest-expect-message"
],
"verbose": false, "verbose": false,
"testEnvironment": "node", "testEnvironment": "node",
"testMatch": [ "testMatch": [

View File

@@ -181,6 +181,21 @@ export default async (client: Client) => {
); );
} }
// build `--prebuilt`
if (argv['--prebuilt']) {
const prebuiltExists = await fs.pathExists(join(path, '.vercel/output'));
if (!prebuiltExists) {
error(
`The ${param(
'--prebuilt'
)} option was used, but no prebuilt output found in ".vercel/output". Run ${getCommandName(
'build'
)} to generate a local build.`
);
return 1;
}
}
// retrieve `project` and `org` from .vercel // retrieve `project` and `org` from .vercel
const link = await getLinkedProject(client, path); const link = await getLinkedProject(client, path);

View File

@@ -115,9 +115,11 @@ export default async function main(client: Client) {
return 2; return 2;
} }
const { subcommand, args } = getSubcommand(argv._.slice(1), COMMAND_CONFIG); const cwd = argv['--cwd'] || process.cwd();
const subArgs = argv._.slice(1);
const { subcommand, args } = getSubcommand(subArgs, COMMAND_CONFIG);
const { output, config } = client; const { output, config } = client;
const link = await getLinkedProject(client); const link = await getLinkedProject(client, cwd);
if (link.status === 'error') { if (link.status === 'error') {
return link.exitCode; return link.exitCode;
} else if (link.status === 'not_linked') { } else if (link.status === 'not_linked') {
@@ -144,7 +146,8 @@ export default async function main(client: Client) {
ProjectEnvTarget.Development, ProjectEnvTarget.Development,
argv, argv,
args, args,
output output,
cwd
); );
default: default:
output.error(getInvalidSubcommand(COMMAND_CONFIG)); output.error(getInvalidSubcommand(COMMAND_CONFIG));

View File

@@ -1,5 +1,6 @@
import chalk from 'chalk'; import chalk from 'chalk';
import { closeSync, openSync, promises, readSync } from 'fs'; import { outputFile } from 'fs-extra';
import { closeSync, openSync, readSync } from 'fs';
import { resolve } from 'path'; import { resolve } from 'path';
import { Project, ProjectEnvTarget } from '../../types'; import { Project, ProjectEnvTarget } from '../../types';
import Client from '../../util/client'; import Client from '../../util/client';
@@ -12,7 +13,6 @@ import { Output } from '../../util/output';
import param from '../../util/output/param'; import param from '../../util/output/param';
import stamp from '../../util/output/stamp'; import stamp from '../../util/output/stamp';
import { getCommandName } from '../../util/pkg-name'; import { getCommandName } from '../../util/pkg-name';
const { writeFile } = promises;
const CONTENTS_PREFIX = '# Created by Vercel CLI\n'; const CONTENTS_PREFIX = '# Created by Vercel CLI\n';
@@ -49,7 +49,7 @@ export default async function pull(
opts: Partial<Options>, opts: Partial<Options>,
args: string[], args: string[],
output: Output, output: Output,
cwd: string = process.cwd() cwd: string
) { ) {
if (args.length > 1) { if (args.length > 1) {
output.error( output.error(
@@ -81,7 +81,7 @@ export default async function pull(
} }
output.print( output.print(
`Downloading Development Environment Variables for Project ${chalk.bold( `Downloading "${environment}" Environment Variables for Project ${chalk.bold(
project.name project.name
)}\n` )}\n`
); );
@@ -99,7 +99,9 @@ export default async function pull(
const records = exposeSystemEnvs( const records = exposeSystemEnvs(
projectEnvs, projectEnvs,
systemEnvValues, systemEnvValues,
project.autoExposeSystemEnvs project.autoExposeSystemEnvs,
undefined,
environment
); );
const contents = const contents =
@@ -109,7 +111,7 @@ export default async function pull(
.join('\n') + .join('\n') +
'\n'; '\n';
await writeFile(fullPath, contents, 'utf8'); await outputFile(fullPath, contents, 'utf8');
output.print( output.print(
`${prependEmoji( `${prependEmoji(

View File

@@ -4,7 +4,6 @@ import Client from '../util/client';
import { ProjectEnvTarget } from '../types'; import { ProjectEnvTarget } from '../types';
import { emoji, prependEmoji } from '../util/emoji'; import { emoji, prependEmoji } from '../util/emoji';
import getArgs from '../util/get-args'; import getArgs from '../util/get-args';
import handleError from '../util/handle-error';
import setupAndLink from '../util/link/setup-and-link'; import setupAndLink from '../util/link/setup-and-link';
import logo from '../util/output/logo'; import logo from '../util/output/logo';
import stamp from '../util/output/stamp'; import stamp from '../util/output/stamp';
@@ -69,7 +68,6 @@ function processArgs(client: Client) {
} }
function parseArgs(client: Client) { function parseArgs(client: Client) {
try {
const argv = processArgs(client); const argv = processArgs(client);
if (argv['--help']) { if (argv['--help']) {
@@ -78,10 +76,6 @@ function parseArgs(client: Client) {
} }
return argv; return argv;
} catch (err) {
handleError(err);
return 1;
}
} }
type LinkResult = { type LinkResult = {

View File

@@ -585,73 +585,73 @@ const main = async () => {
let func: any; let func: any;
switch (targetCommand) { switch (targetCommand) {
case 'alias': case 'alias':
func = await import('./commands/alias'); func = require('./commands/alias').default;
break; break;
case 'billing': case 'billing':
func = await import('./commands/billing'); func = require('./commands/billing').default;
break; break;
case 'bisect': case 'bisect':
func = await import('./commands/bisect'); func = require('./commands/bisect').default;
break; break;
case 'certs': case 'certs':
func = await import('./commands/certs'); func = require('./commands/certs').default;
break; break;
case 'deploy': case 'deploy':
func = await import('./commands/deploy'); func = require('./commands/deploy').default;
break; break;
case 'dev': case 'dev':
func = await import('./commands/dev'); func = require('./commands/dev').default;
break; break;
case 'dns': case 'dns':
func = await import('./commands/dns'); func = require('./commands/dns').default;
break; break;
case 'domains': case 'domains':
func = await import('./commands/domains'); func = require('./commands/domains').default;
break; break;
case 'env': case 'env':
func = await import('./commands/env'); func = require('./commands/env').default;
break; break;
case 'init': case 'init':
func = await import('./commands/init'); func = require('./commands/init').default;
break; break;
case 'inspect': case 'inspect':
func = await import('./commands/inspect'); func = require('./commands/inspect').default;
break; break;
case 'link': case 'link':
func = await import('./commands/link'); func = require('./commands/link').default;
break; break;
case 'list': case 'list':
func = await import('./commands/list'); func = require('./commands/list').default;
break; break;
case 'logs': case 'logs':
func = await import('./commands/logs'); func = require('./commands/logs').default;
break; break;
case 'login': case 'login':
func = await import('./commands/login'); func = require('./commands/login').default;
break; break;
case 'logout': case 'logout':
func = await import('./commands/logout'); func = require('./commands/logout').default;
break; break;
case 'projects': case 'projects':
func = await import('./commands/projects'); func = require('./commands/projects').default;
break; break;
case 'pull': case 'pull':
func = await import('./commands/pull'); func = require('./commands/pull').default;
break; break;
case 'remove': case 'remove':
func = await import('./commands/remove'); func = require('./commands/remove').default;
break; break;
case 'secrets': case 'secrets':
func = await import('./commands/secrets'); func = require('./commands/secrets').default;
break; break;
case 'teams': case 'teams':
func = await import('./commands/teams'); func = require('./commands/teams').default;
break; break;
case 'update': case 'update':
func = await import('./commands/update'); func = require('./commands/update').default;
break; break;
case 'whoami': case 'whoami':
func = await import('./commands/whoami'); func = require('./commands/whoami').default;
break; break;
default: default:
func = null; func = null;

View File

@@ -9,7 +9,6 @@ import {
import { Output } from '../output'; import { Output } from '../output';
// @ts-ignore // @ts-ignore
import Now from '../../util'; import Now from '../../util';
import { VercelConfig } from '../dev/types';
import { Org } from '../../types'; import { Org } from '../../types';
import ua from '../ua'; import ua from '../ua';
import { linkFolderToProject } from '../projects/link'; import { linkFolderToProject } from '../projects/link';
@@ -43,7 +42,6 @@ export default async function processDeployment({
uploadStamp: () => string; uploadStamp: () => string;
deployStamp: () => string; deployStamp: () => string;
quiet: boolean; quiet: boolean;
nowConfig?: VercelConfig;
force?: boolean; force?: boolean;
withCache?: boolean; withCache?: boolean;
org: Org; org: Org;
@@ -62,7 +60,6 @@ export default async function processDeployment({
deployStamp, deployStamp,
force, force,
withCache, withCache,
nowConfig,
quiet, quiet,
prebuilt, prebuilt,
rootDirectory, rootDirectory,
@@ -104,11 +101,7 @@ export default async function processDeployment({
const indications = []; const indications = [];
try { try {
for await (const event of createDeployment( for await (const event of createDeployment(clientOptions, requestBody)) {
clientOptions,
requestBody,
nowConfig
)) {
if (['tip', 'notice', 'warning'].includes(event.type)) { if (['tip', 'notice', 'warning'].includes(event.type)) {
indications.push(event); indications.push(event);
} }

View File

@@ -160,7 +160,6 @@ export default class Now extends EventEmitter {
uploadStamp, uploadStamp,
deployStamp, deployStamp,
quiet, quiet,
nowConfig,
force: forceNew, force: forceNew,
withCache, withCache,
org, org,

View File

@@ -227,6 +227,7 @@ export default async function setupAndLink(
return { status: 'linked', org, project }; return { status: 'linked', org, project };
} catch (err) { } catch (err) {
handleError(err); handleError(err);
return { status: 'error', exitCode: 1 }; return { status: 'error', exitCode: 1 };
} }
} }

View File

@@ -1,4 +1,4 @@
import { writeFile } from 'fs-extra'; import { outputJSON } from 'fs-extra';
import { Org, Project, ProjectLink } from '../../types'; import { Org, Project, ProjectLink } from '../../types';
import { getLinkFromDir, VERCEL_DIR, VERCEL_DIR_PROJECT } from './link'; import { getLinkFromDir, VERCEL_DIR, VERCEL_DIR_PROJECT } from './link';
import { join } from 'path'; import { join } from 'path';
@@ -22,9 +22,7 @@ export async function writeProjectSettings(
project: Project, project: Project,
org: Org org: Org
) { ) {
return await writeFile( const data = {
join(cwd, VERCEL_DIR, VERCEL_DIR_PROJECT),
JSON.stringify({
projectId: project.id, projectId: project.id,
orgId: org.id, orgId: org.id,
settings: { settings: {
@@ -35,8 +33,11 @@ export async function writeProjectSettings(
rootDirectory: project.rootDirectory, rootDirectory: project.rootDirectory,
framework: project.framework, framework: project.framework,
}, },
}) };
); const path = join(cwd, VERCEL_DIR, VERCEL_DIR_PROJECT);
return await outputJSON(path, data, {
spaces: 2,
});
} }
export async function readProjectSettings(cwd: string) { export async function readProjectSettings(cwd: string) {

View File

@@ -31,6 +31,15 @@ describe('deploy', () => {
); );
}); });
it('should reject deploying a directory that does not contain ".vercel/output" when `--prebuilt` is used', async () => {
client.setArgv('deploy', __dirname, '--prebuilt');
const exitCode = await deploy(client);
expect(exitCode).toEqual(1);
expect(client.outputBuffer).toEqual(
'Error! The "--prebuilt" option was used, but no prebuilt output found in ".vercel/output". Run `vercel build` to generate a local build.\n'
);
});
it('should reject deploying "version: 1"', async () => { it('should reject deploying "version: 1"', async () => {
client.setArgv('deploy'); client.setArgv('deploy');
client.localConfig = { client.localConfig = {

View File

@@ -0,0 +1,75 @@
import fs from 'fs-extra';
import path from 'path';
import env from '../../src/commands/env';
import { setupFixture } from '../helpers/setup-fixture';
import { client } from '../mocks/client';
import { defaultProject, useProject } from '../mocks/project';
import { useTeams } from '../mocks/team';
import { useUser } from '../mocks/user';
describe('env', () => {
describe('pull', () => {
it('should handle pulling', async () => {
const cwd = setupFixture('vercel-env-pull');
useUser();
useTeams('team_dummy');
useProject({
...defaultProject,
id: 'vercel-env-pull',
name: 'vercel-env-pull',
});
client.setArgv('env', 'pull', '--yes', '--cwd', cwd);
const exitCode = await env(client);
expect(exitCode, client.outputBuffer).toEqual(0);
const rawDevEnv = await fs.readFile(path.join(cwd, '.env'));
// check for development env value
const devFileHasDevEnv = rawDevEnv.toString().includes('SPECIAL_FLAG');
expect(devFileHasDevEnv).toBeTruthy();
});
it('should handle alternate filename', async () => {
const cwd = setupFixture('vercel-env-pull');
useUser();
useTeams('team_dummy');
useProject({
...defaultProject,
id: 'vercel-env-pull',
name: 'vercel-env-pull',
});
client.setArgv('env', 'pull', 'other.env', '--yes', '--cwd', cwd);
const exitCode = await env(client);
expect(exitCode, client.outputBuffer).toEqual(0);
const rawDevEnv = await fs.readFile(path.join(cwd, 'other.env'));
// check for development env value
const devFileHasDevEnv = rawDevEnv.toString().includes('SPECIAL_FLAG');
expect(devFileHasDevEnv).toBeTruthy();
});
it('should expose production system env variables', async () => {
const cwd = setupFixture('vercel-env-pull');
useUser();
useTeams('team_dummy');
useProject({
...defaultProject,
id: 'vercel-env-pull',
name: 'vercel-env-pull',
autoExposeSystemEnvs: true,
});
client.setArgv('env', 'pull', 'other.env', '--yes', '--cwd', cwd);
const exitCode = await env(client);
expect(exitCode, client.outputBuffer).toEqual(0);
const rawDevEnv = await fs.readFile(path.join(cwd, 'other.env'));
const productionFileHasVercelEnv = rawDevEnv
.toString()
.includes('VERCEL_ENV="development"');
expect(productionFileHasVercelEnv).toBeTruthy();
});
});
});

View File

@@ -11,7 +11,7 @@ describe('pull', () => {
it('should handle pulling', async () => { it('should handle pulling', async () => {
const cwd = setupFixture('vercel-pull-next'); const cwd = setupFixture('vercel-pull-next');
useUser(); useUser();
useTeams(); useTeams('team_dummy');
useProject({ useProject({
...defaultProject, ...defaultProject,
id: 'vercel-pull-next', id: 'vercel-pull-next',
@@ -19,7 +19,7 @@ describe('pull', () => {
}); });
client.setArgv('pull', '--yes', cwd); client.setArgv('pull', '--yes', cwd);
const exitCode = await pull(client); const exitCode = await pull(client);
expect(exitCode).toEqual(0); expect(exitCode, client.outputBuffer).toEqual(0);
const rawDevEnv = await fs.readFile( const rawDevEnv = await fs.readFile(
path.join(cwd, '.vercel', '.env.development.local') path.join(cwd, '.vercel', '.env.development.local')
@@ -28,10 +28,46 @@ describe('pull', () => {
expect(devFileHasDevEnv).toBeTruthy(); expect(devFileHasDevEnv).toBeTruthy();
}); });
it('should handle pulling with env vars (headless mode)', async () => {
try {
process.env.VERCEL_PROJECT_ID = 'vercel-pull-next';
process.env.VERCEL_ORG_ID = 'team_dummy';
const cwd = setupFixture('vercel-pull-next');
// Remove the `.vercel` dir to ensure that the `pull`
// command creates a new one based on env vars
await fs.remove(path.join(cwd, '.vercel'));
useUser();
useTeams('team_dummy');
useProject({
...defaultProject,
id: 'vercel-pull-next',
name: 'vercel-pull-next',
});
client.setArgv('pull', cwd);
const exitCode = await pull(client);
expect(exitCode, client.outputBuffer).toEqual(0);
const config = await fs.readJSON(path.join(cwd, '.vercel/project.json'));
expect(config).toMatchInlineSnapshot(`
Object {
"orgId": "team_dummy",
"projectId": "vercel-pull-next",
"settings": Object {},
}
`);
} finally {
delete process.env.VERCEL_PROJECT_ID;
delete process.env.VERCEL_ORG_ID;
}
});
it('should handle --environment=preview flag', async () => { it('should handle --environment=preview flag', async () => {
const cwd = setupFixture('vercel-pull-next'); const cwd = setupFixture('vercel-pull-next');
useUser(); useUser();
useTeams(); useTeams('team_dummy');
useProject({ useProject({
...defaultProject, ...defaultProject,
id: 'vercel-pull-next', id: 'vercel-pull-next',
@@ -53,7 +89,7 @@ describe('pull', () => {
it('should handle --environment=production flag', async () => { it('should handle --environment=production flag', async () => {
const cwd = setupFixture('vercel-pull-next'); const cwd = setupFixture('vercel-pull-next');
useUser(); useUser();
useTeams(); useTeams('team_dummy');
useProject({ useProject({
...defaultProject, ...defaultProject,
id: 'vercel-pull-next', id: 'vercel-pull-next',

View File

@@ -15,7 +15,7 @@ cache:
env: env:
global: global:
# See https://git.io/vdao3 for details. # See https://github.com/ember-cli/ember-cli/blob/master/docs/build-concurrency.md
- JOBS=1 - JOBS=1
script: script:

View File

@@ -0,0 +1,2 @@
.next
yarn.lock

View File

@@ -0,0 +1,4 @@
{
"orgId": "team_dummy",
"projectId": "vercel-env-pull"
}

View File

@@ -0,0 +1,12 @@
{
"scripts": {
"build": "next build",
"dev": "next",
"now-build": "next build"
},
"dependencies": {
"next": "^8.0.0",
"react": "^16.7.0",
"react-dom": "^16.7.0"
}
}

View File

@@ -0,0 +1,11 @@
import { withRouter } from 'next/router';
function Index({ router }) {
const data = {
pathname: router.pathname,
query: router.query,
};
return <div>{JSON.stringify(data)}</div>;
}
export default withRouter(Index);

View File

@@ -0,0 +1,10 @@
{
"version": 2,
"name": "vercel-env-pull",
"routes": [
{
"src": "/(.*)",
"dest": "/index?route-param=b"
}
]
}

View File

@@ -1,3 +1,2 @@
.next .next
yarn.lock yarn.lock
.vercel

View File

@@ -0,0 +1,4 @@
{
"orgId": "team_dummy",
"projectId": "vercel-pull-next"
}

View File

@@ -1,7 +1,6 @@
{ {
"version": 2, "version": 2,
"name": "vercel-pull-next", "name": "vercel-pull-next",
"builds": [{ "src": "package.json", "use": "@vercel/next@canary" }],
"routes": [ "routes": [
{ {
"src": "/(.*)", "src": "/(.*)",

View File

@@ -796,13 +796,16 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
t.regex(stderr, /Created .env file/gm); t.regex(stderr, /Created .env file/gm);
const contents = fs.readFileSync(path.join(target, '.env'), 'utf8'); const contents = fs.readFileSync(path.join(target, '.env'), 'utf8');
t.true(contents.startsWith('# Created by Vercel CLI\n')); t.regex(contents, /^# Created by Vercel CLI\n/);
const lines = new Set(contents.split('\n')); const lines = new Set(contents.split('\n'));
t.true(lines.has('MY_NEW_ENV_VAR="my plaintext value"')); t.true(lines.has('MY_NEW_ENV_VAR="my plaintext value"'), 'MY_NEW_ENV_VAR');
t.true(lines.has('MY_STDIN_VAR="{"expect":"quotes"}"')); t.true(lines.has('MY_STDIN_VAR="{"expect":"quotes"}"'), 'MY_STDIN_VAR');
t.true(lines.has('MY_DECRYPTABLE_SECRET_ENV="decryptable value"')); t.true(
t.false(lines.has('MY_PREVIEW')); lines.has('MY_DECRYPTABLE_SECRET_ENV="decryptable value"'),
'MY_DECRYPTABLE_SECRET_ENV'
);
t.false(lines.has('MY_PREVIEW'), 'MY_PREVIEW');
} }
async function vcEnvPullOverwrite() { async function vcEnvPullOverwrite() {
@@ -974,11 +977,12 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
const contents = fs.readFileSync(path.join(target, '.env'), 'utf8'); const contents = fs.readFileSync(path.join(target, '.env'), 'utf8');
const lines = new Set(contents.split('\n')); const lines = new Set(contents.split('\n'));
t.true(lines.has('VERCEL="1"'));
t.true(lines.has('VERCEL_URL=""')); t.true(lines.has('VERCEL="1"'), 'VERCEL');
t.true(lines.has('VERCEL_ENV="development"')); t.true(lines.has('VERCEL_URL=""'), 'VERCEL_URL');
t.true(lines.has('VERCEL_GIT_PROVIDER=""')); t.true(lines.has('VERCEL_ENV="development"'), 'VERCEL_ENV');
t.true(lines.has('VERCEL_GIT_REPO_SLUG=""')); t.true(lines.has('VERCEL_GIT_PROVIDER=""'), 'VERCEL_GIT_PROVIDER');
t.true(lines.has('VERCEL_GIT_REPO_SLUG=""'), 'VERCEL_GIT_REPO_SLUG');
} }
async function vcDevAndFetchSystemVars() { async function vcDevAndFetchSystemVars() {
@@ -2144,11 +2148,6 @@ const verifyExampleAngular = (cwd, dir) =>
fs.existsSync(path.join(cwd, dir, 'tsconfig.json')) && fs.existsSync(path.join(cwd, dir, 'tsconfig.json')) &&
fs.existsSync(path.join(cwd, dir, 'angular.json')); fs.existsSync(path.join(cwd, dir, 'angular.json'));
const verifyExampleAmp = (cwd, dir) =>
fs.existsSync(path.join(cwd, dir, 'index.html')) &&
fs.existsSync(path.join(cwd, dir, 'logo.png')) &&
fs.existsSync(path.join(cwd, dir, 'favicon.png'));
test('initialize example "angular"', async t => { test('initialize example "angular"', async t => {
tmpDir = tmp.dirSync({ unsafeCleanup: true }); tmpDir = tmp.dirSync({ unsafeCleanup: true });
const cwd = tmpDir.name; const cwd = tmpDir.name;
@@ -2183,21 +2182,6 @@ test('initialize example ("angular") to specified directory', async t => {
t.true(verifyExampleAngular(cwd, 'ang'), formatOutput({ stdout, stderr })); t.true(verifyExampleAngular(cwd, 'ang'), formatOutput({ stdout, stderr }));
}); });
test('initialize selected example ("amp")', async t => {
tmpDir = tmp.dirSync({ unsafeCleanup: true });
const cwd = tmpDir.name;
const goal = '> Success! Initialized "amp" example in';
const { stdout, stderr, exitCode } = await execute(['init'], {
cwd,
input: '\n',
});
t.is(exitCode, 0, formatOutput({ stdout, stderr }));
t.true(stderr.includes(goal), formatOutput({ stdout, stderr }));
t.true(verifyExampleAmp(cwd, 'amp'), formatOutput({ stdout, stderr }));
});
test('initialize example to existing directory with "-f"', async t => { test('initialize example to existing directory with "-f"', async t => {
tmpDir = tmp.dirSync({ unsafeCleanup: true }); tmpDir = tmp.dirSync({ unsafeCleanup: true });
const cwd = tmpDir.name; const cwd = tmpDir.name;
@@ -2274,6 +2258,8 @@ test('try to revert a deployment and assign the automatic aliases', async t => {
const { name } = JSON.parse( const { name } = JSON.parse(
fs.readFileSync(path.join(firstDeployment, 'now.json')) fs.readFileSync(path.join(firstDeployment, 'now.json'))
); );
t.true(!!name, 'name has a value');
const url = `https://${name}.user.vercel.app`; const url = `https://${name}.user.vercel.app`;
{ {
@@ -2862,7 +2848,7 @@ test('should show prompts to set up project during first deploy', async t => {
); );
// Send a test request to the deployment // Send a test request to the deployment
const response = await fetch(new URL(output.stdout).href); const response = await fetch(new URL(output.stdout));
const text = await response.text(); const text = await response.text();
t.is(text.includes('<h1>custom hello</h1>'), true, text); t.is(text.includes('<h1>custom hello</h1>'), true, text);

View File

@@ -87,7 +87,20 @@ export class MockClient extends Client {
} }
stopMockServer() { stopMockServer() {
this.mockServer?.close(); return new Promise<void>((resolve, reject) => {
if (!this.mockServer?.close) {
reject(new Error(`mockServer did not exist when closing`));
return;
}
this.mockServer.close(error => {
if (error) {
reject(error);
return;
}
resolve();
});
});
} }
setArgv(...argv: string[]) { setArgv(...argv: string[]) {
@@ -109,6 +122,6 @@ beforeEach(() => {
client.reset(); client.reset();
}); });
afterAll(() => { afterAll(async () => {
client.stopMockServer(); await client.stopMockServer();
}); });

View File

@@ -1,4 +1,5 @@
import { client } from './client'; import { client } from './client';
import { Project } from '../../src/types';
const envs = [ const envs = [
{ {
@@ -36,6 +37,42 @@ const envs = [
}, },
]; ];
const systemEnvs = [
{
type: 'encrypted',
id: 'a235l6frtu25df32',
key: 'SYSTEM_ENV_FOR_DEV',
value: 'development',
target: ['development'],
gitBranch: null,
configurationId: null,
updatedAt: 1557241361445,
createdAt: 1557241361445,
},
{
type: 'encrypted',
id: 'a235l6frtu25df32',
key: 'SYSTEM_ENV_FOR_PREV',
value: 'preview',
target: ['preview'],
gitBranch: null,
configurationId: null,
updatedAt: 1557241361445,
createdAt: 1557241361445,
},
{
type: 'encrypted',
id: 'a235l6frtu25df32',
key: 'SYSTEM_ENV_FOR_PROD',
value: 'production',
target: ['production'],
gitBranch: null,
configurationId: null,
updatedAt: 1557241361445,
createdAt: 1557241361445,
},
];
export const defaultProject = { export const defaultProject = {
id: 'foo', id: 'foo',
name: 'cli', name: 'cli',
@@ -80,25 +117,42 @@ export const defaultProject = {
requestedAt: 1571239348998, requestedAt: 1571239348998,
target: 'production', target: 'production',
teamId: null, teamId: null,
type: 'LAMBDAS', type: undefined,
url: 'a-project-name-rjtr4pz3f.vercel.app', url: 'a-project-name-rjtr4pz3f.vercel.app',
userId: 'K4amb7K9dAt5R2vBJWF32bmY', userId: 'K4amb7K9dAt5R2vBJWF32bmY',
}, },
], ],
}; };
export function useProject(project = defaultProject) { export function useProject(project: Partial<Project> = defaultProject) {
client.scenario.get(`/v8/projects/${project.name}`, (_req, res) => { client.scenario.get(`/v8/projects/${project.name}`, (_req, res) => {
res.json(project); res.json(project);
}); });
client.scenario.get(`/v8/projects/${project.id}`, (_req, res) => { client.scenario.get(`/v8/projects/${project.id}`, (_req, res) => {
res.json(project); res.json(project);
}); });
client.scenario.get(
`/v6/projects/${project.id}/system-env-values`,
(_req, res) => {
const target = _req.query.target || 'development';
if (typeof target !== 'string') {
throw new Error(
`/v6/projects/${project.id}/system-env-values was given a query param of "target=${target}", which is not a valid environment.`
);
}
const targetEnvs = systemEnvs.filter(env => env.target.includes(target));
res.json({
systemEnvValues: targetEnvs,
});
}
);
client.scenario.get(`/v8/projects/${project.id}/env`, (_req, res) => { client.scenario.get(`/v8/projects/${project.id}/env`, (_req, res) => {
const target = _req.query.target; const target = _req.query.target;
if (typeof target === 'string') { if (typeof target === 'string') {
const targetEnvs = envs.filter(env => env.target.includes(target)); const targetEnvs = envs.filter(env => env.target.includes(target));
res.json({ envs: targetEnvs }); res.json({ envs: targetEnvs });
return;
} }
res.json({ envs }); res.json({ envs });

View File

@@ -1,10 +1,11 @@
import chance from 'chance'; import chance from 'chance';
import { client } from './client'; import { client } from './client';
export function useTeams() { export function useTeams(teamId?: string) {
const id = teamId || chance().guid();
const teams = [ const teams = [
{ {
id: chance().guid(), id,
slug: chance().string({ length: 5, casing: 'lower' }), slug: chance().string({ length: 5, casing: 'lower' }),
name: chance().company(), name: chance().company(),
creatorId: chance().guid(), creatorId: chance().guid(),
@@ -14,7 +15,7 @@ export function useTeams() {
]; ];
for (let team of teams) { for (let team of teams) {
client.scenario.get(`/v1/team/${team.id}`, (_req, res) => { client.scenario.get(`/teams/${team.id}`, (_req, res) => {
res.json(team); res.json(team);
}); });
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/client", "name": "@vercel/client",
"version": "10.4.2-canary.1", "version": "11.0.0",
"main": "dist/index.js", "main": "dist/index.js",
"typings": "dist/index.d.ts", "typings": "dist/index.d.ts",
"homepage": "https://vercel.com", "homepage": "https://vercel.com",
@@ -24,7 +24,7 @@
"devDependencies": { "devDependencies": {
"@types/async-retry": "1.4.1", "@types/async-retry": "1.4.1",
"@types/fs-extra": "7.0.0", "@types/fs-extra": "7.0.0",
"@types/jest": "27.0.1", "@types/jest": "27.4.1",
"@types/minimatch": "3.0.5", "@types/minimatch": "3.0.5",
"@types/ms": "0.7.30", "@types/ms": "0.7.30",
"@types/node": "12.0.4", "@types/node": "12.0.4",
@@ -41,7 +41,7 @@
] ]
}, },
"dependencies": { "dependencies": {
"@vercel/build-utils": "2.15.2-canary.1", "@vercel/build-utils": "2.16.0",
"@zeit/fetch": "5.2.0", "@zeit/fetch": "5.2.0",
"async-retry": "1.2.3", "async-retry": "1.2.3",
"async-sema": "3.0.0", "async-sema": "3.0.0",

View File

@@ -1,12 +1,10 @@
import { lstatSync } from 'fs-extra'; import { lstatSync } from 'fs-extra';
import { isAbsolute } from 'path';
import { relative, isAbsolute } from 'path';
import { hashes, mapToObject } from './utils/hashes'; import { hashes, mapToObject } from './utils/hashes';
import { upload } from './upload'; import { upload } from './upload';
import { buildFileTree, createDebug, parseVercelConfig } from './utils'; import { buildFileTree, createDebug } from './utils';
import { DeploymentError } from './errors'; import { DeploymentError } from './errors';
import { import {
VercelConfig,
VercelClientOptions, VercelClientOptions,
DeploymentOptions, DeploymentOptions,
DeploymentEventType, DeploymentEventType,
@@ -15,13 +13,11 @@ import {
export default function buildCreateDeployment() { export default function buildCreateDeployment() {
return async function* createDeployment( return async function* createDeployment(
clientOptions: VercelClientOptions, clientOptions: VercelClientOptions,
deploymentOptions: DeploymentOptions = {}, deploymentOptions: DeploymentOptions = {}
nowConfig: VercelConfig = {}
): AsyncIterableIterator<{ type: DeploymentEventType; payload: any }> { ): AsyncIterableIterator<{ type: DeploymentEventType; payload: any }> {
const { path } = clientOptions; const { path } = clientOptions;
const debug = createDebug(clientOptions.debug); const debug = createDebug(clientOptions.debug);
const cwd = process.cwd();
debug('Creating deployment...'); debug('Creating deployment...');
@@ -76,29 +72,6 @@ export default function buildCreateDeployment() {
const { fileList } = await buildFileTree(path, clientOptions, debug); const { fileList } = await buildFileTree(path, clientOptions, debug);
let configPath: string | undefined;
if (!nowConfig) {
// If the user did not provide a config file, use the one in the root directory.
const relativePaths = fileList.map(f => relative(cwd, f));
const hasVercelConfig = relativePaths.includes('vercel.json');
const hasNowConfig = relativePaths.includes('now.json');
if (hasVercelConfig) {
if (hasNowConfig) {
throw new DeploymentError({
code: 'conflicting_config',
message:
'Cannot use both a `vercel.json` and `now.json` file. Please delete the `now.json` file.',
});
}
configPath = 'vercel.json';
} else if (hasNowConfig) {
configPath = 'now.json';
}
nowConfig = await parseVercelConfig(configPath);
}
// This is a useful warning because it prevents people // This is a useful warning because it prevents people
// from getting confused about a deployment that renders 404. // from getting confused about a deployment that renders 404.
if (fileList.length === 0) { if (fileList.length === 0) {

View File

@@ -126,27 +126,6 @@ export async function* deploy(
} }
} }
if (
files.size === 1 &&
deploymentOptions.builds === undefined &&
deploymentOptions.routes === undefined &&
deploymentOptions.cleanUrls === undefined &&
deploymentOptions.rewrites === undefined &&
deploymentOptions.redirects === undefined &&
deploymentOptions.headers === undefined &&
deploymentOptions.trailingSlash === undefined
) {
debug(`Assigning '/' route for single file deployment`);
const filePath = Array.from(files.values())[0].names[0];
deploymentOptions.routes = [
{
src: '/',
dest: `/${filePath.split('/').pop()}`,
},
];
}
if (!deploymentOptions.name) { if (!deploymentOptions.name) {
deploymentOptions.name = deploymentOptions.name =
clientOptions.defaultName || getDefaultName(files, clientOptions); clientOptions.defaultName || getDefaultName(files, clientOptions);

View File

@@ -0,0 +1 @@
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M22.428.013c-.103.01-.431.042-.727.066C14.883.693 8.497 4.37 4.453 10.024A23.754 23.754 0 0 0 .216 20.51C.023 21.828 0 22.217 0 24.005c0 1.787.023 2.177.216 3.495 1.304 9.012 7.718 16.584 16.417 19.39 1.558.501 3.2.844 5.068 1.05.727.08 3.87.08 4.598 0 3.224-.356 5.954-1.154 8.648-2.529.412-.21.492-.267.436-.314-.038-.028-1.797-2.388-3.909-5.24l-3.838-5.184-4.809-7.117c-2.646-3.913-4.824-7.112-4.842-7.112-.019-.005-.038 3.157-.047 7.018-.014 6.76-.019 7.033-.103 7.192-.122.23-.216.324-.413.427-.15.075-.282.09-.99.09h-.812l-.216-.137a.878.878 0 0 1-.314-.342l-.099-.211.01-9.407.014-9.41.145-.184c.075-.098.235-.225.347-.286.193-.094.268-.103 1.08-.103.957 0 1.116.038 1.365.31.07.075 2.674 3.997 5.79 8.721s7.376 11.175 9.469 14.342l3.8 5.756.192-.127c1.704-1.107 3.505-2.683 4.932-4.325a23.888 23.888 0 0 0 5.65-12.268c.192-1.319.215-1.708.215-3.495 0-1.788-.023-2.177-.216-3.495-1.304-9.013-7.718-16.584-16.417-19.39C29.832.623 28.199.28 26.369.074c-.45-.047-3.551-.099-3.94-.061zm9.825 14.515a.947.947 0 0 1 .474.554c.038.122.047 2.73.038 8.608l-.014 8.436-1.488-2.28-1.492-2.28v-6.132c0-3.964.019-6.193.047-6.3a.957.957 0 0 1 .465-.592c.192-.098.262-.108 1-.108.694 0 .816.01.97.094z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/frameworks", "name": "@vercel/frameworks",
"version": "0.7.1", "version": "0.8.0",
"main": "./dist/frameworks.js", "main": "./dist/frameworks.js",
"types": "./dist/frameworks.d.ts", "types": "./dist/frameworks.d.ts",
"files": [ "files": [
@@ -16,11 +16,11 @@
"js-yaml": "3.13.1" "js-yaml": "3.13.1"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "27.0.1", "@types/jest": "27.4.1",
"@types/js-yaml": "3.12.1", "@types/js-yaml": "3.12.1",
"@types/node": "12.0.4", "@types/node": "12.0.4",
"@types/node-fetch": "2.5.8", "@types/node-fetch": "2.5.8",
"@vercel/routing-utils": "1.13.1", "@vercel/routing-utils": "1.13.2",
"ajv": "6.12.2", "ajv": "6.12.2",
"typescript": "4.3.4" "typescript": "4.3.4"
} }

View File

@@ -60,6 +60,7 @@ export const frameworks = [
slug: 'nextjs', slug: 'nextjs',
demo: 'https://nextjs-template.vercel.app', demo: 'https://nextjs-template.vercel.app',
logo: 'https://raw.githubusercontent.com/vercel/vercel/main/packages/frameworks/logos/next.svg', logo: 'https://raw.githubusercontent.com/vercel/vercel/main/packages/frameworks/logos/next.svg',
darkModeLogo: 'https://raw.githubusercontent.com/vercel/vercel/main/packages/frameworks/logos/next-dark.svg',
screenshot: screenshot:
'https://assets.vercel.com/image/upload/v1647366075/front/import/nextjs.png', 'https://assets.vercel.com/image/upload/v1647366075/front/import/nextjs.png',
tagline: tagline:

View File

@@ -60,6 +60,11 @@ export interface Framework {
* @example "https://raw.githubusercontent.com/vercel/vercel/main/packages/frameworks/logos/next.svg" * @example "https://raw.githubusercontent.com/vercel/vercel/main/packages/frameworks/logos/next.svg"
*/ */
logo: string; logo: string;
/**
* An additional URL to the logo of the framework optimized for dark mode
* @example "https://raw.githubusercontent.com/vercel/vercel/main/packages/frameworks/logos/next-dark.svg"
*/
darkModeLogo?: string;
/** /**
* A URL to a screenshot of the demo * A URL to a screenshot of the demo
* @example "https://assets.vercel.com/image/upload/v1647366075/front/import/nextjs.png" * @example "https://assets.vercel.com/image/upload/v1647366075/front/import/nextjs.png"

View File

@@ -6,6 +6,7 @@ import { join } from 'path';
import stringArgv from 'string-argv'; import stringArgv from 'string-argv';
import { debug } from '@vercel/build-utils'; import { debug } from '@vercel/build-utils';
const versionMap = new Map([ const versionMap = new Map([
['1.18', '1.18.1'],
['1.17', '1.17.3'], ['1.17', '1.17.3'],
['1.16', '1.16.10'], ['1.16', '1.16.10'],
['1.15', '1.15.8'], ['1.15', '1.15.8'],

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/go", "name": "@vercel/go",
"version": "1.3.3-canary.1", "version": "1.4.0",
"license": "MIT", "license": "MIT",
"main": "./dist/index", "main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/go", "homepage": "https://vercel.com/docs/runtimes#official-runtimes/go",
@@ -24,7 +24,7 @@
"@types/fs-extra": "^5.0.5", "@types/fs-extra": "^5.0.5",
"@types/node-fetch": "^2.3.0", "@types/node-fetch": "^2.3.0",
"@types/tar": "^4.0.0", "@types/tar": "^4.0.0",
"@vercel/build-utils": "2.15.2-canary.1", "@vercel/build-utils": "2.16.0",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"async-retry": "1.3.1", "async-retry": "1.3.1",
"execa": "^1.0.0", "execa": "^1.0.0",

View File

@@ -5,10 +5,10 @@
{ "src": "subdirectory/index.go", "use": "@vercel/go" } { "src": "subdirectory/index.go", "use": "@vercel/go" }
], ],
"probes": [ "probes": [
{ "path": "/", "mustContain": "cow:go1.17.3:RANDOMNESS_PLACEHOLDER" }, { "path": "/", "mustContain": "cow:go1.18.1:RANDOMNESS_PLACEHOLDER" },
{ {
"path": "/subdirectory", "path": "/subdirectory",
"mustContain": "subcow:go1.17.3:RANDOMNESS_PLACEHOLDER" "mustContain": "subcow:go1.18.1:RANDOMNESS_PLACEHOLDER"
} }
] ]
} }

View File

@@ -186,9 +186,6 @@ class Bridge {
'payloads' in normalizedEvent && 'payloads' in normalizedEvent &&
Array.isArray(normalizedEvent.payloads) Array.isArray(normalizedEvent.payloads)
) { ) {
// statusCode and headers are required to match when using
// multiple payloads in a single invocation so we can use
// the first
let statusCode = 200; let statusCode = 200;
/** /**
* @type {import('http').IncomingHttpHeaders} * @type {import('http').IncomingHttpHeaders}
@@ -200,6 +197,14 @@ class Bridge {
let combinedBody = ''; let combinedBody = '';
const multipartBoundary = 'payload-separator'; const multipartBoundary = 'payload-separator';
const CLRF = '\r\n'; const CLRF = '\r\n';
/**
* @type {Record<string, any>[]}
*/
const separateHeaders = [];
/**
* @type {Set<string>}
*/
const allHeaderKeys = new Set();
// we execute the payloads one at a time to ensure // we execute the payloads one at a time to ensure
// lambda semantics // lambda semantics
@@ -209,19 +214,48 @@ class Bridge {
// build a combined body using multipart // build a combined body using multipart
// https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html // https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html
combinedBody += `--${multipartBoundary}${CLRF}`; combinedBody += `--${multipartBoundary}${CLRF}`;
if (response.headers['content-type']) { combinedBody += `content-type: ${
combinedBody += `content-type: ${response.headers['content-type']}${CLRF}${CLRF}`; response.headers['content-type'] || 'text/plain'
} }${CLRF}${CLRF}`;
combinedBody += response.body; combinedBody += response.body || '';
combinedBody += CLRF; combinedBody += CLRF;
if (i === normalizedEvent.payloads.length - 1) { if (i === normalizedEvent.payloads.length - 1) {
combinedBody += `--${multipartBoundary}--${CLRF}`; combinedBody += `--${multipartBoundary}--${CLRF}`;
} }
// pass non-200 status code in header so it can be handled
statusCode = response.statusCode; // separately from other payloads e.g. HTML payload redirects
headers = response.headers; // (307) but data payload does not (200)
if (response.statusCode !== 200) {
headers[`x-vercel-payload-${i + 1}-status`] =
response.statusCode + '';
} }
separateHeaders.push(response.headers);
Object.keys(response.headers).forEach(key => allHeaderKeys.add(key));
}
allHeaderKeys.forEach(curKey => {
/**
* @type string | string[] | undefined
*/
const curValue = separateHeaders[0] && separateHeaders[0][curKey];
const canDedupe = separateHeaders.every(
headers => headers[curKey] === curValue
);
if (canDedupe) {
headers[curKey] = curValue;
} else {
// if a header is unique per payload ensure it is prefixed
// so it can be parsed and provided separately
separateHeaders.forEach((curHeaders, idx) => {
if (curHeaders[curKey]) {
headers[`x-vercel-payload-${idx + 1}-${curKey}`] =
curHeaders[curKey];
}
});
}
});
headers[ headers[
'content-type' 'content-type'

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/node-bridge", "name": "@vercel/node-bridge",
"version": "2.2.0", "version": "2.2.1",
"license": "MIT", "license": "MIT",
"main": "./index.js", "main": "./index.js",
"repository": { "repository": {

View File

@@ -86,6 +86,12 @@ test('`NowProxyEvent` normalizing', async () => {
test('multi-payload handling', async () => { test('multi-payload handling', async () => {
const server = new Server((req, res) => { const server = new Server((req, res) => {
if (req.url === '/redirect') {
res.setHeader('Location', '/somewhere');
res.statusCode = 307;
res.end('/somewhere');
return;
}
res.setHeader( res.setHeader(
'content-type', 'content-type',
req.url.includes('_next/data') ? 'application/json' : 'text/html' req.url.includes('_next/data') ? 'application/json' : 'text/html'
@@ -117,6 +123,11 @@ test('multi-payload handling', async () => {
headers: { foo: 'baz' }, headers: { foo: 'baz' },
path: '/_next/data/build-id/nowproxy.json', path: '/_next/data/build-id/nowproxy.json',
}, },
{
method: 'GET',
headers: { foo: 'baz' },
path: '/redirect',
},
], ],
}), }),
}, },
@@ -137,20 +148,36 @@ test('multi-payload handling', async () => {
!item.startsWith('content-type:') && !item.startsWith('content-type:') &&
!item.startsWith('--payload') !item.startsWith('--payload')
) { ) {
bodies.push( const content = Buffer.from(
JSON.parse( item.split('--payload-separator')[0],
Buffer.from(item.split('--payload-separator')[0], 'base64').toString() 'base64'
) ).toString();
); bodies.push(content.startsWith('{') ? JSON.parse(content) : content);
} }
}); });
// ensure content-type is always specified as is required for
// proper parsing of the multipart body
assert(payloadParts.some(part => part.includes('content-type: text/plain')));
assert.equal(bodies[0].method, 'GET'); assert.equal(bodies[0].method, 'GET');
assert.equal(bodies[0].path, '/nowproxy'); assert.equal(bodies[0].path, '/nowproxy');
assert.equal(bodies[0].headers.foo, 'baz'); assert.equal(bodies[0].headers.foo, 'baz');
assert.equal(bodies[1].method, 'GET'); assert.equal(bodies[1].method, 'GET');
assert.equal(bodies[1].path, '/_next/data/build-id/nowproxy.json'); assert.equal(bodies[1].path, '/_next/data/build-id/nowproxy.json');
assert.equal(bodies[1].headers.foo, 'baz'); assert.equal(bodies[1].headers.foo, 'baz');
assert.equal(bodies[2], '/somewhere');
assert.equal(result.headers['x-vercel-payload-3-status'], '307');
assert.equal(result.headers['x-vercel-payload-2-status'], undefined);
assert.equal(result.headers['x-vercel-payload-1-status'], undefined);
assert.equal(result.headers['x-vercel-payload-1-content-type'], 'text/html');
assert.equal(
result.headers['x-vercel-payload-2-content-type'],
'application/json'
);
assert.equal(result.headers['x-vercel-payload-3-content-type'], undefined);
assert.equal(result.headers['x-vercel-payload-3-location'], '/somewhere');
assert.equal(result.headers['x-vercel-payload-2-location'], undefined);
assert.equal(context.callbackWaitsForEmptyEventLoop, false); assert.equal(context.callbackWaitsForEmptyEventLoop, false);
server.close(); server.close();

View File

@@ -1,3 +1,4 @@
/dist /dist
/test/fixtures/**/types.d.ts /test/fixtures/**/types.d.ts
/test/fixtures/11-symlinks/symlink /test/fixtures/11-symlinks/symlink
!test/cache-fixtures/**

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/node", "name": "@vercel/node",
"version": "1.14.2-canary.1", "version": "1.15.0",
"license": "MIT", "license": "MIT",
"main": "./dist/index", "main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js", "homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
@@ -12,6 +12,7 @@
"scripts": { "scripts": {
"build": "node build", "build": "node build",
"test-integration-once": "jest --env node --verbose --runInBand --bail test/integration.test.js", "test-integration-once": "jest --env node --verbose --runInBand --bail test/integration.test.js",
"test-unit": "jest --env node --verbose --bail test/prepare-cache.test.js",
"prepublishOnly": "node build" "prepublishOnly": "node build"
}, },
"files": [ "files": [
@@ -19,7 +20,7 @@
], ],
"dependencies": { "dependencies": {
"@types/node": "*", "@types/node": "*",
"@vercel/node-bridge": "2.2.0", "@vercel/node-bridge": "2.2.1",
"ts-node": "8.9.1", "ts-node": "8.9.1",
"typescript": "4.3.4" "typescript": "4.3.4"
}, },
@@ -32,7 +33,7 @@
"@types/cookie": "0.3.3", "@types/cookie": "0.3.3",
"@types/etag": "1.8.0", "@types/etag": "1.8.0",
"@types/test-listen": "1.1.0", "@types/test-listen": "1.1.0",
"@vercel/build-utils": "2.15.2-canary.1", "@vercel/build-utils": "2.16.0",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"@vercel/nft": "0.18.1", "@vercel/nft": "0.18.1",
"content-type": "1.0.4", "content-type": "1.0.4",

View File

@@ -380,9 +380,8 @@ export const build: BuildV3 = async ({
return { output: lambda }; return { output: lambda };
}; };
export const prepareCache: PrepareCache = async ({ workPath }) => { export const prepareCache: PrepareCache = ({ repoRootPath, workPath }) => {
const cache = await glob('node_modules/**', workPath); return glob('**/node_modules/**', repoRootPath || workPath);
return cache;
}; };
export const startDevServer: StartDevServer = async opts => { export const startDevServer: StartDevServer = async opts => {

View File

0
packages/node/test/cache-fixtures/node_modules/file generated vendored Normal file
View File

View File

@@ -0,0 +1,28 @@
const path = require('path');
const { prepareCache } = require('../dist');
describe('prepareCache()', () => {
test('should cache **/node_modules/**', async () => {
const files = await prepareCache({
repoRootPath: path.resolve(__dirname, './cache-fixtures/'),
});
expect(files['foo/node_modules/file']).toBeDefined();
expect(files['node_modules/file']).toBeDefined();
expect(files['index.js']).toBeUndefined();
});
test('should ignore root modules', async () => {
const files = await prepareCache({
workPath: path.resolve(__dirname, './cache-fixtures/foo/'),
});
expect(files['node_modules/file']).toBeDefined();
expect(
files['node_modules/file'].fsPath.includes(
'cache-fixtures/foo/node_modules/file'
)
).toBeTruthy();
expect(files['index.js']).toBeUndefined();
});
});

View File

@@ -0,0 +1,5 @@
/** @type {import('@ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/python", "name": "@vercel/python",
"version": "2.2.3-canary.1", "version": "2.3.0",
"main": "./dist/index.js", "main": "./dist/index.js",
"license": "MIT", "license": "MIT",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/python", "homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",
@@ -15,12 +15,14 @@
}, },
"scripts": { "scripts": {
"build": "node build", "build": "node build",
"test-integration-once": "jest --env node --verbose --runInBand --bail", "test-unit": "jest --env node --verbose --runInBand --bail test/unit.test.ts ",
"test-integration-once": "jest --env node --verbose --runInBand --bail test/integration.test.ts",
"prepublishOnly": "node build" "prepublishOnly": "node build"
}, },
"devDependencies": { "devDependencies": {
"@types/execa": "^0.9.0", "@types/execa": "^0.9.0",
"@vercel/build-utils": "2.15.2-canary.1", "@types/jest": "27.4.1",
"@vercel/build-utils": "2.16.0",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"execa": "^1.0.0", "execa": "^1.0.0",
"typescript": "4.3.4" "typescript": "4.3.4"

View File

@@ -16,6 +16,7 @@ import {
NowBuildError, NowBuildError,
} from '@vercel/build-utils'; } from '@vercel/build-utils';
import { installRequirement, installRequirementsFile } from './install'; import { installRequirement, installRequirementsFile } from './install';
import { getLatestPythonVersion, getSupportedPythonVersion } from './version';
async function pipenvConvert(cmd: string, srcDir: string) { async function pipenvConvert(cmd: string, srcDir: string) {
debug('Running pipfile2req...'); debug('Running pipfile2req...');
@@ -59,9 +60,7 @@ export const build = async ({
meta = {}, meta = {},
config, config,
}: BuildOptions) => { }: BuildOptions) => {
let pipPath = meta.isDev ? 'pip3' : 'pip3.9'; let pythonVersion = getLatestPythonVersion(meta);
let pythonPath = meta.isDev ? 'python3' : 'python3.9';
let pythonRuntime = meta.isDev ? 'python3' : 'python3.9';
workPath = await downloadFilesInWorkPath({ workPath = await downloadFilesInWorkPath({
workPath, workPath,
@@ -91,8 +90,8 @@ export const build = async ({
console.log('Installing required dependencies...'); console.log('Installing required dependencies...');
await installRequirement({ await installRequirement({
pythonPath, pythonPath: pythonVersion.pythonPath,
pipPath, pipPath: pythonVersion.pipPath,
dependency: 'werkzeug', dependency: 'werkzeug',
version: '1.0.1', version: '1.0.1',
workPath, workPath,
@@ -114,25 +113,10 @@ export const build = async ({
try { try {
const json = await readFile(join(pipfileLockDir, 'Pipfile.lock'), 'utf8'); const json = await readFile(join(pipfileLockDir, 'Pipfile.lock'), 'utf8');
const obj = JSON.parse(json); const obj = JSON.parse(json);
const version = obj?._meta?.requires?.python_version; pythonVersion = getSupportedPythonVersion({
if (!meta.isDev) { isDev: meta.isDev,
if (version === '3.6') { pipLockPythonVersion: obj?._meta?.requires?.python_version,
pipPath = 'pip3.6'; });
pythonPath = 'python3.6';
pythonRuntime = 'python3.6';
console.warn(
`Warning: Python version "${version}" detected in Pipfile.lock will reach End-Of-Life December 2021. Please upgrade. http://vercel.link/python-version`
);
} else if (version === '3.9') {
pipPath = 'pip3.9';
pythonPath = 'python3.9';
pythonRuntime = 'python3.9';
} else {
console.warn(
`Warning: Invalid Python version "${version}" detected in Pipfile.lock will be ignored. http://vercel.link/python-version`
);
}
}
} catch (err) { } catch (err) {
throw new NowBuildError({ throw new NowBuildError({
code: 'INVALID_PIPFILE_LOCK', code: 'INVALID_PIPFILE_LOCK',
@@ -146,8 +130,8 @@ export const build = async ({
// it into a separate folder. // it into a separate folder.
const tempDir = await getWriteableDirectory(); const tempDir = await getWriteableDirectory();
await installRequirement({ await installRequirement({
pythonPath, pythonPath: pythonVersion.pythonPath,
pipPath, pipPath: pythonVersion.pipPath,
dependency: 'pipfile-requirements', dependency: 'pipfile-requirements',
version: '0.3.0', version: '0.3.0',
workPath: tempDir, workPath: tempDir,
@@ -169,8 +153,8 @@ export const build = async ({
debug('Found local "requirements.txt"'); debug('Found local "requirements.txt"');
const requirementsTxtPath = fsFiles[requirementsTxt].fsPath; const requirementsTxtPath = fsFiles[requirementsTxt].fsPath;
await installRequirementsFile({ await installRequirementsFile({
pythonPath, pythonPath: pythonVersion.pythonPath,
pipPath, pipPath: pythonVersion.pipPath,
filePath: requirementsTxtPath, filePath: requirementsTxtPath,
workPath, workPath,
meta, meta,
@@ -179,8 +163,8 @@ export const build = async ({
debug('Found global "requirements.txt"'); debug('Found global "requirements.txt"');
const requirementsTxtPath = fsFiles['requirements.txt'].fsPath; const requirementsTxtPath = fsFiles['requirements.txt'].fsPath;
await installRequirementsFile({ await installRequirementsFile({
pythonPath, pythonPath: pythonVersion.pythonPath,
pipPath, pipPath: pythonVersion.pipPath,
filePath: requirementsTxtPath, filePath: requirementsTxtPath,
workPath, workPath,
meta, meta,
@@ -205,9 +189,6 @@ export const build = async ({
await writeFile(join(workPath, `${handlerPyFilename}.py`), handlerPyContents); await writeFile(join(workPath, `${handlerPyFilename}.py`), handlerPyContents);
// Use the system-installed version of `python3` when running via `vercel dev`
const runtime = meta.isDev ? 'python3' : pythonRuntime;
const globOptions: GlobOptions = { const globOptions: GlobOptions = {
cwd: workPath, cwd: workPath,
ignore: ignore:
@@ -219,7 +200,7 @@ export const build = async ({
const lambda = await createLambda({ const lambda = await createLambda({
files: await glob('**', globOptions), files: await glob('**', globOptions),
handler: `${handlerPyFilename}.vc_handler`, handler: `${handlerPyFilename}.vc_handler`,
runtime, runtime: pythonVersion.runtime,
environment: {}, environment: {},
}); });

View File

@@ -0,0 +1,95 @@
import { NowBuildError } from '@vercel/build-utils';
interface PythonVersion {
version: string;
pipPath: string;
pythonPath: string;
runtime: string;
discontinueDate?: Date;
}
// The order must be most recent first
const allOptions: PythonVersion[] = [
{
version: '3.9',
pipPath: 'pip3.9',
pythonPath: 'python3.9',
runtime: 'python3.9',
},
{
version: '3.6',
pipPath: 'pip3.6',
pythonPath: 'python3.6',
runtime: 'python3.6',
discontinueDate: new Date('2022-07-18'),
},
];
const upstreamProvider =
'This change is the result of a decision made by an upstream infrastructure provider (AWS)';
function getDevPythonVersion(): PythonVersion {
// Use the system-installed version of `python3` when running `vercel dev`
return {
version: '3',
pipPath: 'pip3',
pythonPath: 'python3',
runtime: 'python3',
};
}
export function getLatestPythonVersion({
isDev,
}: {
isDev?: boolean;
}): PythonVersion {
if (isDev) {
return getDevPythonVersion();
}
return allOptions[0];
}
export function getSupportedPythonVersion({
isDev,
pipLockPythonVersion,
}: {
isDev?: boolean;
pipLockPythonVersion: string | undefined;
}): PythonVersion {
if (isDev) {
return getDevPythonVersion();
}
let selection = getLatestPythonVersion({ isDev: false });
if (typeof pipLockPythonVersion === 'string') {
const found = allOptions.find(o => o.version === pipLockPythonVersion);
if (found) {
selection = found;
} else {
console.warn(
`Warning: Python version "${pipLockPythonVersion}" detected in Pipfile.lock is invalid and will be ignored. http://vercel.link/python-version`
);
}
}
if (isDiscontinued(selection)) {
throw new NowBuildError({
code: 'BUILD_UTILS_PYTHON_VERSION_DISCONTINUED',
link: 'http://vercel.link/python-version',
message: `Python version "${selection.version}" detected in Pipfile.lock is discontinued and must be upgraded. ${upstreamProvider}.`,
});
}
if (selection.discontinueDate) {
const d = selection.discontinueDate.toISOString().split('T')[0];
console.warn(
`Error: Python version "${selection.version}" detected in Pipfile.lock is deprecated. Deployments created on or after ${d} will fail to build. ${upstreamProvider}. http://vercel.link/python-version`
);
}
return selection;
}
function isDiscontinued({ discontinueDate }: PythonVersion): boolean {
const today = Date.now();
return discontinueDate !== undefined && discontinueDate.getTime() <= today;
}

View File

@@ -1,6 +1,6 @@
from sanic import Sanic from sanic import Sanic
from sanic.response import json from sanic.response import json
app = Sanic() app = Sanic(name='test')
@app.route('/') @app.route('/')
@app.route('/<path:path>') @app.route('/<path:path>')

View File

@@ -1,2 +1,23 @@
sanic==19.6.0 sanic==20.12.6
flask==1.0.2 flask==2.1.1
# below is meant to lock the version of transitive deps
aiofiles==0.8.0; python_version >= "3.6" and python_version < "4.0"
certifi==2021.10.8
click==8.1.2; python_version >= "3.7"
h11==0.9.0
httpcore==0.11.1; python_version >= "3.6"
httptools==0.4.0; python_version >= "3.5"
httpx==0.15.4; python_version >= "3.6"
idna==3.3
importlib-metadata==4.11.3; python_version < "3.10"
itsdangerous==2.1.2; python_version >= "3.7"
jinja2==3.1.1; python_version >= "3.7"
markupsafe==2.1.1; python_version >= "3.7"
multidict==5.2.0; python_version >= "3.6"
rfc3986[idna2008]==1.5.0
sniffio==1.2.0; python_version >= "3.5"
ujson==5.2.0; sys_platform != "win32" and implementation_name == "cpython"
uvloop==0.16.0; sys_platform != "win32" and implementation_name == "cpython"
websockets==9.1; python_full_version >= "3.6.1"
werkzeug==2.1.1; python_version >= "3.7"
zipp==3.8.0; python_version >= "3.7"

View File

@@ -5,7 +5,7 @@
{ {
"path": "/", "path": "/",
"mustContain": "wsgi:RANDOMNESS_PLACEHOLDER", "mustContain": "wsgi:RANDOMNESS_PLACEHOLDER",
"logMustContain": "Warning: Python version \"3.6\" detected in Pipfile.lock will reach End-Of-Life December 2021. Please upgrade." "logMustContain": "Python version \"3.6\" detected in Pipfile.lock is deprecated. Deployments created on or after 2022-07-18 will fail to build"
} }
] ]
} }

View File

@@ -8,7 +8,7 @@
{ {
"path": "/", "path": "/",
"mustContain": "RANDOMNESS_PLACEHOLDER:env", "mustContain": "RANDOMNESS_PLACEHOLDER:env",
"logMustContain": "Warning: Python version \"3.6\" detected in Pipfile.lock will reach End-Of-Life December 2021. Please upgrade." "logMustContain": "Python version \"3.6\" detected in Pipfile.lock is deprecated. Deployments created on or after 2022-07-18 will fail to build"
} }
] ]
} }

View File

@@ -5,7 +5,7 @@
{ {
"path": "/", "path": "/",
"mustContain": "pip:RANDOMNESS_PLACEHOLDER", "mustContain": "pip:RANDOMNESS_PLACEHOLDER",
"logMustContain": "Warning: Invalid Python version \"3.7\" detected in Pipfile.lock will be ignored." "logMustContain": "Python version \"3.7\" detected in Pipfile.lock is invalid and will be ignored."
} }
] ]
} }

View File

@@ -0,0 +1,12 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
flask = "*"
[dev-packages]
[requires]
python_version = "3.9"

View File

@@ -0,0 +1,123 @@
{
"_meta": {
"hash": {
"sha256": "f7f1cea682a03d85328caf2f88382c4380283d3892a9ba31b374784fb29536c4"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.9"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"click": {
"hashes": [
"sha256:24e1a4a9ec5bf6299411369b208c1df2188d9eb8d916302fe6bf03faed227f1e",
"sha256:479707fe14d9ec9a0757618b7a100a0ae4c4e236fac5b7f80ca68028141a1a72"
],
"markers": "python_version >= '3.7'",
"version": "==8.1.2"
},
"flask": {
"hashes": [
"sha256:8a4cf32d904cf5621db9f0c9fbcd7efabf3003f22a04e4d0ce790c7137ec5264",
"sha256:a8c9bd3e558ec99646d177a9739c41df1ded0629480b4c8d2975412f3c9519c8"
],
"index": "pypi",
"version": "==2.1.1"
},
"importlib-metadata": {
"hashes": [
"sha256:1208431ca90a8cca1a6b8af391bb53c1a2db74e5d1cef6ddced95d4b2062edc6",
"sha256:ea4c597ebf37142f827b8f39299579e31685c31d3a438b59f469406afd0f2539"
],
"markers": "python_version < '3.10'",
"version": "==4.11.3"
},
"itsdangerous": {
"hashes": [
"sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44",
"sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"
],
"markers": "python_version >= '3.7'",
"version": "==2.1.2"
},
"jinja2": {
"hashes": [
"sha256:539835f51a74a69f41b848a9645dbdc35b4f20a3b601e2d9a7e22947b15ff119",
"sha256:640bed4bb501cbd17194b3cace1dc2126f5b619cf068a726b98192a0fde74ae9"
],
"markers": "python_version >= '3.7'",
"version": "==3.1.1"
},
"markupsafe": {
"hashes": [
"sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003",
"sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88",
"sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5",
"sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7",
"sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a",
"sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603",
"sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1",
"sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135",
"sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247",
"sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6",
"sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601",
"sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77",
"sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02",
"sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e",
"sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63",
"sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f",
"sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980",
"sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b",
"sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812",
"sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff",
"sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96",
"sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1",
"sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925",
"sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a",
"sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6",
"sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e",
"sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f",
"sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4",
"sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f",
"sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3",
"sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c",
"sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a",
"sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417",
"sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a",
"sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a",
"sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37",
"sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452",
"sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933",
"sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a",
"sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"
],
"markers": "python_version >= '3.7'",
"version": "==2.1.1"
},
"werkzeug": {
"hashes": [
"sha256:3c5493ece8268fecdcdc9c0b112211acd006354723b280d643ec732b6d4063d6",
"sha256:f8e89a20aeabbe8a893c24a461d3ee5dad2123b05cc6abd73ceed01d39c3ae74"
],
"markers": "python_version >= '3.7'",
"version": "==2.1.1"
},
"zipp": {
"hashes": [
"sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad",
"sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"
],
"markers": "python_version >= '3.7'",
"version": "==3.8.0"
}
},
"develop": {}
}

View File

@@ -1 +0,0 @@
Flask==1.0.2

View File

@@ -0,0 +1,12 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
flask = "*"
[dev-packages]
[requires]
python_version = "3.9"

View File

@@ -0,0 +1,123 @@
{
"_meta": {
"hash": {
"sha256": "f7f1cea682a03d85328caf2f88382c4380283d3892a9ba31b374784fb29536c4"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.9"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"click": {
"hashes": [
"sha256:24e1a4a9ec5bf6299411369b208c1df2188d9eb8d916302fe6bf03faed227f1e",
"sha256:479707fe14d9ec9a0757618b7a100a0ae4c4e236fac5b7f80ca68028141a1a72"
],
"markers": "python_version >= '3.7'",
"version": "==8.1.2"
},
"flask": {
"hashes": [
"sha256:8a4cf32d904cf5621db9f0c9fbcd7efabf3003f22a04e4d0ce790c7137ec5264",
"sha256:a8c9bd3e558ec99646d177a9739c41df1ded0629480b4c8d2975412f3c9519c8"
],
"index": "pypi",
"version": "==2.1.1"
},
"importlib-metadata": {
"hashes": [
"sha256:1208431ca90a8cca1a6b8af391bb53c1a2db74e5d1cef6ddced95d4b2062edc6",
"sha256:ea4c597ebf37142f827b8f39299579e31685c31d3a438b59f469406afd0f2539"
],
"markers": "python_version < '3.10'",
"version": "==4.11.3"
},
"itsdangerous": {
"hashes": [
"sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44",
"sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"
],
"markers": "python_version >= '3.7'",
"version": "==2.1.2"
},
"jinja2": {
"hashes": [
"sha256:539835f51a74a69f41b848a9645dbdc35b4f20a3b601e2d9a7e22947b15ff119",
"sha256:640bed4bb501cbd17194b3cace1dc2126f5b619cf068a726b98192a0fde74ae9"
],
"markers": "python_version >= '3.7'",
"version": "==3.1.1"
},
"markupsafe": {
"hashes": [
"sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003",
"sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88",
"sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5",
"sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7",
"sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a",
"sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603",
"sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1",
"sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135",
"sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247",
"sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6",
"sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601",
"sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77",
"sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02",
"sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e",
"sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63",
"sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f",
"sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980",
"sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b",
"sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812",
"sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff",
"sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96",
"sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1",
"sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925",
"sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a",
"sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6",
"sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e",
"sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f",
"sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4",
"sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f",
"sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3",
"sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c",
"sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a",
"sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417",
"sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a",
"sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a",
"sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37",
"sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452",
"sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933",
"sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a",
"sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"
],
"markers": "python_version >= '3.7'",
"version": "==2.1.1"
},
"werkzeug": {
"hashes": [
"sha256:3c5493ece8268fecdcdc9c0b112211acd006354723b280d643ec732b6d4063d6",
"sha256:f8e89a20aeabbe8a893c24a461d3ee5dad2123b05cc6abd73ceed01d39c3ae74"
],
"markers": "python_version >= '3.7'",
"version": "==2.1.1"
},
"zipp": {
"hashes": [
"sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad",
"sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"
],
"markers": "python_version >= '3.7'",
"version": "==3.8.0"
}
},
"develop": {}
}

View File

@@ -1 +0,0 @@
Flask==1.0.2

View File

@@ -12,7 +12,7 @@ const {
jest.setTimeout(4 * 60 * 1000); jest.setTimeout(4 * 60 * 1000);
const buildUtilsUrl = '@canary'; const buildUtilsUrl = '@canary';
let builderUrl; let builderUrl: string;
beforeAll(async () => { beforeAll(async () => {
const builderPath = path.resolve(__dirname, '..'); const builderPath = path.resolve(__dirname, '..');

4
packages/python/test/tsconfig.json vendored Normal file
View File

@@ -0,0 +1,4 @@
{
"extends": "../tsconfig.json",
"include": ["*.test.ts"]
}

75
packages/python/test/unit.test.ts vendored Normal file
View File

@@ -0,0 +1,75 @@
import { getSupportedPythonVersion } from '../src/version';
let warningMessages: string[];
const originalConsoleWarn = console.warn;
const realDateNow = Date.now.bind(global.Date);
beforeEach(() => {
warningMessages = [];
console.warn = m => {
warningMessages.push(m);
};
});
afterEach(() => {
console.warn = originalConsoleWarn;
global.Date.now = realDateNow;
});
it('should only match supported versions, otherwise throw an error', async () => {
expect(
getSupportedPythonVersion({ pipLockPythonVersion: '3.9' })
).toHaveProperty('runtime', 'python3.9');
expect(
getSupportedPythonVersion({ pipLockPythonVersion: '3.6' })
).toHaveProperty('runtime', 'python3.6');
});
it('should ignore minor version in vercel dev', async () => {
expect(
getSupportedPythonVersion({ pipLockPythonVersion: '3.9', isDev: true })
).toHaveProperty('runtime', 'python3');
expect(
getSupportedPythonVersion({ pipLockPythonVersion: '3.6', isDev: true })
).toHaveProperty('runtime', 'python3');
expect(
getSupportedPythonVersion({ pipLockPythonVersion: '999', isDev: true })
).toHaveProperty('runtime', 'python3');
expect(warningMessages).toStrictEqual([]);
});
it('should select latest version when no Piplock detected', async () => {
expect(
getSupportedPythonVersion({ pipLockPythonVersion: undefined })
).toHaveProperty('runtime', 'python3.9');
expect(warningMessages).toStrictEqual([]);
});
it('should select latest version and warn when invalid Piplock detected', async () => {
expect(
getSupportedPythonVersion({ pipLockPythonVersion: '999' })
).toHaveProperty('runtime', 'python3.9');
expect(warningMessages).toStrictEqual([
'Warning: Python version "999" detected in Pipfile.lock is invalid and will be ignored. http://vercel.link/python-version',
]);
});
it('should throw for discontinued versions', async () => {
global.Date.now = () => new Date('2022-07-31').getTime();
expect(() =>
getSupportedPythonVersion({ pipLockPythonVersion: '3.6' })
).toThrow(
'Python version "3.6" detected in Pipfile.lock is discontinued and must be upgraded. This change is the result of a decision made by an upstream infrastructure provider (AWS).'
);
expect(warningMessages).toStrictEqual([]);
});
it('should warn for deprecated versions, soon to be discontinued', async () => {
global.Date.now = () => new Date('2021-07-01').getTime();
expect(
getSupportedPythonVersion({ pipLockPythonVersion: '3.6' })
).toHaveProperty('runtime', 'python3.6');
expect(warningMessages).toStrictEqual([
'Error: Python version "3.6" detected in Pipfile.lock is deprecated. Deployments created on or after 2022-07-18 will fail to build. This change is the result of a decision made by an upstream infrastructure provider (AWS). http://vercel.link/python-version',
]);
});

View File

@@ -11,8 +11,10 @@
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": true, "noUnusedParameters": true,
"outDir": "dist", "outDir": "dist",
"types": ["node"], "types": ["node", "jest"],
"strict": true, "strict": true,
"target": "es2018" "target": "es2018"
} },
"include": ["src/**/*"],
"exclude": ["node_modules"]
} }

View File

@@ -16,7 +16,7 @@ __vc_variables = dir(__vc_module)
def format_headers(headers, decode=False): def format_headers(headers, decode=False):
keyToList = {} keyToList = {}
for key, value in headers.items(): for key, value in headers.items():
if decode: if decode and 'decode' in dir(key) and 'decode' in dir(value):
key = key.decode() key = key.decode()
value = value.decode() value = value.decode()
if key not in keyToList: if key not in keyToList:

1
packages/redwood/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
!test/cache-fixtures/**

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/redwood", "name": "@vercel/redwood",
"version": "0.7.1-canary.1", "version": "0.8.0",
"main": "./dist/index.js", "main": "./dist/index.js",
"license": "MIT", "license": "MIT",
"homepage": "https://vercel.com/docs", "homepage": "https://vercel.com/docs",
@@ -15,17 +15,18 @@
"scripts": { "scripts": {
"build": "node build.js", "build": "node build.js",
"test-integration-once": "jest --env node --verbose --runInBand --bail test/test.js", "test-integration-once": "jest --env node --verbose --runInBand --bail test/test.js",
"test-unit": "jest --env node --verbose --bail test/prepare-cache.test.js",
"prepublishOnly": "node build.js" "prepublishOnly": "node build.js"
}, },
"dependencies": { "dependencies": {
"@vercel/nft": "0.18.1", "@vercel/nft": "0.18.1",
"@vercel/routing-utils": "1.13.1", "@vercel/routing-utils": "1.13.2",
"semver": "6.1.1" "semver": "6.1.1"
}, },
"devDependencies": { "devDependencies": {
"@types/aws-lambda": "8.10.19", "@types/aws-lambda": "8.10.19",
"@types/node": "*", "@types/node": "*",
"@types/semver": "6.0.0", "@types/semver": "6.0.0",
"@vercel/build-utils": "2.15.2-canary.1" "@vercel/build-utils": "2.16.0"
} }
} }

View File

@@ -316,7 +316,6 @@ function hasScript(scriptName: string, pkg: PackageJson | null) {
return typeof scripts[scriptName] === 'string'; return typeof scripts[scriptName] === 'string';
} }
export const prepareCache: PrepareCache = async ({ workPath }) => { export const prepareCache: PrepareCache = ({ repoRootPath, workPath }) => {
const cache = await glob('**/node_modules/**', workPath); return glob('**/node_modules/**', repoRootPath || workPath);
return cache;
}; };

View File

View File

View File

@@ -0,0 +1,28 @@
const path = require('path');
const { prepareCache } = require('../dist');
describe('prepareCache()', () => {
test('should cache **/node_modules/**', async () => {
const files = await prepareCache({
repoRootPath: path.resolve(__dirname, './cache-fixtures/'),
});
expect(files['foo/node_modules/file']).toBeDefined();
expect(files['node_modules/file']).toBeDefined();
expect(files['index.js']).toBeUndefined();
});
test('should ignore root modules', async () => {
const files = await prepareCache({
workPath: path.resolve(__dirname, './cache-fixtures/foo/'),
});
expect(files['node_modules/file']).toBeDefined();
expect(
files['node_modules/file'].fsPath.includes(
'cache-fixtures/foo/node_modules/file'
)
).toBeTruthy();
expect(files['index.js']).toBeUndefined();
});
});

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/routing-utils", "name": "@vercel/routing-utils",
"version": "1.13.1", "version": "1.13.2",
"description": "Vercel routing utilities", "description": "Vercel routing utilities",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",

View File

@@ -1,7 +1,7 @@
{ {
"name": "@vercel/ruby", "name": "@vercel/ruby",
"author": "Nathan Cahill <nathan@nathancahill.com>", "author": "Nathan Cahill <nathan@nathancahill.com>",
"version": "1.3.3-canary.1", "version": "1.3.3",
"license": "MIT", "license": "MIT",
"main": "./dist/index", "main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/ruby", "homepage": "https://vercel.com/docs/runtimes#official-runtimes/ruby",
@@ -22,7 +22,7 @@
"devDependencies": { "devDependencies": {
"@types/fs-extra": "8.0.0", "@types/fs-extra": "8.0.0",
"@types/semver": "6.0.0", "@types/semver": "6.0.0",
"@vercel/build-utils": "2.15.2-canary.1", "@vercel/build-utils": "2.16.0",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"execa": "2.0.4", "execa": "2.0.4",
"fs-extra": "^7.0.1", "fs-extra": "^7.0.1",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/static-build", "name": "@vercel/static-build",
"version": "0.23.2-canary.1", "version": "0.24.0",
"license": "MIT", "license": "MIT",
"main": "./dist/index", "main": "./dist/index",
"homepage": "https://vercel.com/docs/build-step", "homepage": "https://vercel.com/docs/build-step",
@@ -35,10 +35,10 @@
"@types/ms": "0.7.31", "@types/ms": "0.7.31",
"@types/node-fetch": "2.5.4", "@types/node-fetch": "2.5.4",
"@types/promise-timeout": "1.3.0", "@types/promise-timeout": "1.3.0",
"@vercel/build-utils": "2.15.2-canary.1", "@vercel/build-utils": "2.16.0",
"@vercel/frameworks": "0.7.1", "@vercel/frameworks": "0.8.0",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"@vercel/routing-utils": "1.13.1", "@vercel/routing-utils": "1.13.2",
"get-port": "5.0.0", "get-port": "5.0.0",
"is-port-reachable": "2.0.1", "is-port-reachable": "2.0.1",
"ms": "2.1.2", "ms": "2.1.2",

View File

@@ -210,7 +210,7 @@ function getPkg(entrypoint: string, workPath: string) {
try { try {
const pkgPath = path.join(workPath, entrypoint); const pkgPath = path.join(workPath, entrypoint);
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as PackageJson; const pkg: PackageJson = JSON.parse(readFileSync(pkgPath, 'utf8'));
return pkg; return pkg;
} catch (err: any) { } catch (err: any) {
if (err.code !== 'ENOENT') throw err; if (err.code !== 'ENOENT') throw err;
@@ -276,9 +276,7 @@ export const build: BuildV2 = async ({
let distPath = path.join( let distPath = path.join(
workPath, workPath,
path.dirname(entrypoint), path.dirname(entrypoint),
(config && (config.distDir as string)) || (config.distDir as string) || config.outputDirectory || 'dist'
(config.outputDirectory as string) ||
'dist'
); );
const pkg = getPkg(entrypoint, workPath); const pkg = getPkg(entrypoint, workPath);
@@ -326,7 +324,7 @@ export const build: BuildV2 = async ({
distPath = path.join( distPath = path.join(
workPath, workPath,
path.dirname(entrypoint), path.dirname(entrypoint),
(config.outputDirectory as string) || 'public' config.outputDirectory || 'public'
); );
} }
@@ -387,7 +385,7 @@ export const build: BuildV2 = async ({
https://github.com/facebook/create-react-app/pull/2501 https://github.com/facebook/create-react-app/pull/2501
https://github.com/vercel/community/discussions/30 https://github.com/vercel/community/discussions/30
*/ */
if (framework && framework.slug === 'create-react-app') { if (framework?.slug === 'create-react-app') {
if (!spawnOpts.env) { if (!spawnOpts.env) {
spawnOpts.env = {}; spawnOpts.env = {};
} }
@@ -453,7 +451,7 @@ export const build: BuildV2 = async ({
const opts = { const opts = {
env: { env: {
...process.env, ...process.env,
// See more: https://git.io/JtDwx // See more: https://github.com/rubygems/rubygems/blob/a82d04856deba58be6b90f681a5e42a7c0f2baa7/bundler/lib/bundler/man/bundle-config.1.ronn
BUNDLE_BIN: 'vendor/bin', BUNDLE_BIN: 'vendor/bin',
BUNDLE_CACHE_PATH: 'vendor/cache', BUNDLE_CACHE_PATH: 'vendor/cache',
BUNDLE_PATH: 'vendor/bundle', BUNDLE_PATH: 'vendor/bundle',
@@ -468,7 +466,7 @@ export const build: BuildV2 = async ({
isBundleInstall = true; isBundleInstall = true;
} }
if (existsSync(requirementsPath)) { if (existsSync(requirementsPath)) {
debug('Detected requirements.txt'); debug('Detected requirements.txt');
printInstall(); printInstall();
await runPipInstall( await runPipInstall(
workPath, workPath,
@@ -756,18 +754,28 @@ export const build: BuildV2 = async ({
export const prepareCache: PrepareCache = async ({ export const prepareCache: PrepareCache = async ({
entrypoint, entrypoint,
repoRootPath,
workPath, workPath,
config, config,
}) => { }) => {
const cacheFiles: Files = {}; const cacheFiles: Files = {};
// Build Output API v3 cache files
const configV3 = await BuildOutputV3.readConfig(workPath);
if (configV3?.cache && Array.isArray(configV3.cache)) {
for (const cacheGlob of configV3.cache) {
Object.assign(cacheFiles, await glob(cacheGlob, workPath));
}
return cacheFiles;
}
// File System API v1 cache files // File System API v1 cache files
const buildConfig = await BuildOutputV1.readBuildOutputConfig<BuildConfig>({ const buildConfigV1 = await BuildOutputV1.readBuildOutputConfig<BuildConfig>({
workPath, workPath,
configFileName: 'build.json', configFileName: 'build.json',
}); });
if (buildConfig?.cache && Array.isArray(buildConfig.cache)) { if (buildConfigV1?.cache && Array.isArray(buildConfigV1.cache)) {
for (const cacheGlob of buildConfig.cache) { for (const cacheGlob of buildConfigV1.cache) {
Object.assign(cacheFiles, await glob(cacheGlob, workPath)); Object.assign(cacheFiles, await glob(cacheGlob, workPath));
} }
return cacheFiles; return cacheFiles;
@@ -776,7 +784,7 @@ export const prepareCache: PrepareCache = async ({
// Default cache files // Default cache files
Object.assign( Object.assign(
cacheFiles, cacheFiles,
await glob('{.shadow-cljs,node_modules}/**', workPath) await glob('**/{.shadow-cljs,node_modules}/**', repoRootPath || workPath)
); );
// Framework cache files // Framework cache files

View File

@@ -21,3 +21,16 @@ export async function getBuildOutputDirectory(
} }
return undefined; return undefined;
} }
export async function readConfig(
path: string
): Promise<{ cache?: string[] } | undefined> {
try {
const outputDir = join(path, BUILD_OUTPUT_DIR);
const configPath = join(outputDir, 'config.json');
return JSON.parse(await fs.readFile(configPath, 'utf8'));
} catch (err: any) {
if (err.code !== 'ENOENT') throw err;
}
return undefined;
}

View File

@@ -0,0 +1 @@
!.vercel

View File

@@ -0,0 +1,4 @@
{
"version": 3,
"cache": ["some-dir/**", "another.txt"]
}

View File

@@ -0,0 +1 @@
another

View File

@@ -0,0 +1 @@
File that shouldn't be cached

Some files were not shown because too many files have changed in this diff Show More