Compare commits

..

31 Commits

Author SHA1 Message Date
Steven
13b03c6abd Publish Canary
- @vercel/build-utils@2.11.2-canary.0
 - vercel@23.0.2-canary.1
 - @vercel/client@10.1.2-canary.0
 - @vercel/node@1.11.2-canary.1
2021-06-23 10:54:17 -04:00
Steven
7dd4c629ad [node] Add support for ESM to vc dev (#6385)
Follow up to #6121
2021-06-23 14:48:47 +00:00
Nathan Rajlich
81b3d7f5f1 [cli] Fix vc login hanging for a few seconds before exiting (#6387)
Closing keep-alive HTTP connections was causing the `server.close()` call
to take a few seconds before completing, so set the `Connection: close`
response header in order to make the connections close immediately,
so that `server.close()` is fast.
2021-06-22 20:57:15 -07:00
Steven
3566c32209 [build-utils] Add support for .mjs with zero config (#6386)
Follow up to #6121
2021-06-23 02:21:16 +00:00
Nathan Rajlich
267ca7b379 [cli] Print timestamp for each line for multi-line log entries (#6384) 2021-06-22 16:46:03 -07:00
Steven
7b9d9954b8 Publish Canary
- vercel@23.0.2-canary.0
 - @vercel/node-bridge@2.0.1-canary.0
 - @vercel/node@1.11.2-canary.0
2021-06-22 15:46:21 -04:00
Steven
79675db241 [node][node-bridge] Add support for ESM (#6121) 2021-06-22 15:44:30 -04:00
Nathan Rajlich
3ac8a3f67f [cli] Make vercel dev command exit quickly (#6365)
Considering that it's a development environment, it's not important to wait for ongoing HTTP request connections to complete. Sometimes it takes a long time for all the shutdown operations to complete, which makes the command feel sluggish. So let's just `process.exit()` and exit quickly.
2021-06-18 23:33:37 +00:00
Nathan Rajlich
2f19949133 [cli] Finish executing the original command after login with no credentials (#6364) 2021-06-18 13:10:30 -07:00
Paco
85fd2aed7e Update Next.js example favicon (#6367) 2021-06-18 12:35:01 -07:00
Steven
e659eecf48 Publish Stable
- @vercel/build-utils@2.11.1
 - vercel@23.0.1
 - @vercel/client@10.1.1
 - @vercel/frameworks@0.4.1
 - @vercel/node@1.11.1
 - @vercel/python@2.0.4
 - @vercel/routing-utils@1.11.2
2021-06-15 17:29:53 -04:00
Steven
b428f7ff83 Publish Canary
- vercel@23.0.1-canary.4
2021-06-15 16:37:28 -04:00
Nathan Rajlich
5eb133283d Remove utils/go (#6362)
This code now lives in its own repository, so the code in this repo is not being used.

https://github.com/vercel/go-bridge
2021-06-15 12:49:18 +00:00
Nathan Rajlich
646c29600e [cgi] Remove @vercel/cgi Runtime (#6361)
This Runtime is very old, outdated, unmaintained, and has never been documented.

Additionally, it no longer compiles with the latest version of Go. So
rather than fixing it, let's just remove it since we don't want to
invest any more time into it.
2021-06-14 20:49:05 -07:00
Nathan Rajlich
469eb4315d [cli] Update "open" to v8.2.0 (#6348)
Fixes an issue where the bundled `xdg-open` script would not be used.
2021-06-10 19:14:57 -07:00
JJ Kasper
6dc54d0d64 Publish Canary
- @vercel/build-utils@2.11.1-canary.1
 - vercel@23.0.1-canary.3
 - @vercel/client@10.1.1-canary.1
 - @vercel/frameworks@0.4.1-canary.1
 - @vercel/routing-utils@1.11.2-canary.1
2021-06-08 11:38:01 -05:00
JJ Kasper
adc84d5148 Fix check: true beforeFiles order (#6337) 2021-06-08 11:35:31 -05:00
Nathan Rajlich
88642b1ce8 [cli] Print login URL to terminal (#6336)
In some cases (i.e. when SSH'd to a remote machine) the `open` command will not work reliably. So we need to print the URL to the user as a fallback for those cases when the web browser is not automatically opened.

This also moves where `tokenName` is specified to be in the "verify" endpoint, so that it does not need to be part of the URL that gets printed to the user.

<img width="738" alt="Screen Shot 2021-06-07 at 2 12 47 PM" src="https://user-images.githubusercontent.com/71256/121089239-b5452d00-c79b-11eb-85b2-0e45b817dff0.png">
2021-06-07 22:13:58 +00:00
Steven
4b8d207533 [cli] Warn when vercel.json uses has (#6327) 2021-06-07 14:45:36 -04:00
Steven
36fe5cc4d1 [test] Fix corrupt bmp test (#6328) 2021-06-07 09:31:51 -04:00
JJ Kasper
370b0dbed2 Publish Canary
- vercel@23.0.1-canary.2
 - @vercel/node@1.11.1-canary.0
2021-06-06 15:24:50 -05:00
JJ Kasper
cc7a82fb0a [node] Update nft to 0.13.1 (#6333) 2021-06-06 16:22:56 -04:00
Markoz Peña
6eea26c39e [cli] Convert vc alias to TS (#6325)
* refactor: Remove unncessary file

* feat(cli/alias): Migration to TS

* refactor(cli/alias): Add a line break

* refactor(cli/alias): Remove unnecesary code

* feat(cli): Add `Paginationoptions` type to `pagination`

* feat(cli/commands): Rewrite th `alias ls` command to TS

* refactor: Remove unncessary code

* feat: Create helper function for `getSafeAlias`

* refactor: Remove unnecessary code

* feat: Remove parameter generic, "null" for the fetch

* feat(cli/alias): Rewritten in full TS

* feat: Add Partial to opts

* refactor: Remove comment @ts-ignore

* feat: Add Partial to opts

* feat: Only should be return `alias.uid`

* refactor: Remove `Alias` type from of the parameter `id`

* refactor: Remove destructuring from alias object

* refactor: Remove unnecessary code

* feat: Rename `created` property to `createdAt` of number type

* refactor: Move getSafeAlias function in the same file

* refactor: Simplifying code

* refactor: Intentation did not affect diff on git

* Add null back to type

Co-authored-by: Steven <steven@ceriously.com>
2021-06-04 16:43:39 -04:00
Nathan Rajlich
b8bfae7840 [cli] Fix vc logout command when using Team scope (#6322)
Fixes logout command not working when switched to a Team scope:

```
$ vc login
$ vc switch $some_team
$ vc logout
Failed during logout
```
2021-06-04 19:48:03 +00:00
Nathan Rajlich
dc6a0a1cbb [cli] Upgrade token scopes in vc switch command (#6323)
Pass the `Authorization` request header to the verify endpoint so that the current auth token will be upgraded with the new scope.

[ch22273]
2021-06-04 18:33:49 +00:00
Steven
a6807c9d21 Publish Canary
- vercel@23.0.1-canary.1
 - @vercel/python@2.0.4-canary.0
2021-06-01 14:35:09 -04:00
Steven
c628090d08 [cli] Fix vc projects rm race condition (#6306)
The call to `GET /projects/info` is used to check existence but it can cause a race condition if the project was removed before the `DELETE /v2/projects` is called.

Instead, we rely on the response from `DELETE /v2/projects` to determine if the project exists or not.

This will also allow us to remove a legacy API endpoint in the future (see related API PR)
2021-06-01 18:31:20 +00:00
Hydrophobefireman
4e0b291ed1 [python] Remove imports from werkzeug._compat (#6283) 2021-06-01 09:15:06 -04:00
JJ Kasper
ee0bc9b0c8 Publish Canary
- @vercel/build-utils@2.11.1-canary.0
 - vercel@23.0.1-canary.0
 - @vercel/client@10.1.1-canary.0
 - @vercel/frameworks@0.4.1-canary.0
 - @vercel/routing-utils@1.11.2-canary.0
2021-05-26 13:44:39 -05:00
JJ Kasper
e516c1f49f Ensure beforeFiles rewrites come after redirects when continuing (#6289) 2021-05-26 12:50:51 -05:00
JJ Kasper
01f53f36fc [routing-utils] Ensure header key value casing is normalized (#6284)
This ensures we normalize header `key` values in `has` items to be lower-case as the proxy currently only matches against the lower-case variant. Updated superstatic tests to ensure the header key is normalized correctly. 

### Related Issues

[related thread](https://vercel.slack.com/archives/C01N3RWTE5V/p1621937306006400)

### 📋 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

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2021-05-25 22:46:28 +00:00
84 changed files with 974 additions and 933 deletions

View File

@@ -1,14 +1,7 @@
node_modules
dist
examples
# gatsby-plugin-now
packages/gatsby-plugin-now/test/fixtures
# build-utils
packages/build-utils/test/fixtures
# cli
packages/cli/@types
packages/cli/download
packages/cli/dist
@@ -17,24 +10,9 @@ packages/cli/test/dev/fixtures
packages/cli/bin
packages/cli/link
packages/cli/src/util/dev/templates/*.ts
# client
packages/client/tests/fixtures
packages/client/lib
# next
packages/next/test/fixtures
# node
packages/node/src/bridge.ts
packages/node/test/fixtures
# node-bridge
packages/node-bridge/bridge.*
# static-build
packages/static-build/test/fixtures
packages/static-build/test/build-fixtures
# redwood
packages/redwood/test/fixtures
packages/node-bridge/bridge.js
packages/node-bridge/launcher.js

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/build-utils",
"version": "2.11.0",
"version": "2.11.2-canary.0",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.js",
@@ -29,7 +29,7 @@
"@types/node-fetch": "^2.1.6",
"@types/semver": "6.0.0",
"@types/yazl": "^2.4.1",
"@vercel/frameworks": "0.4.0",
"@vercel/frameworks": "0.4.1",
"@vercel/ncc": "0.24.0",
"aggregate-error": "3.0.1",
"async-retry": "1.2.3",

View File

@@ -424,6 +424,7 @@ function getApiMatches() {
return [
{ src: 'api/**/*.js', use: `@vercel/node`, config },
{ src: 'api/**/*.mjs', use: `@vercel/node`, config },
{ src: 'api/**/*.ts', use: `@vercel/node`, config },
{ src: 'api/**/!(*_test).go', use: `@vercel/go`, config },
{ src: 'api/**/*.py', use: `@vercel/python`, config },

View File

@@ -1346,6 +1346,25 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
expect((errorRoutes![0] as Source).status).toBe(404);
});
it('api detect node mjs files', async () => {
const files = [
'api/index.mjs',
'api/users.mjs',
'api/config/staging.mjs',
'api/config/production.mjs',
'api/src/controllers/health.mjs',
'api/src/controllers/user.module.mjs',
];
const { builders, errorRoutes } = await detectBuilders(files, undefined, {
featHandleMiss,
});
expect(builders!.length).toBe(6);
expect(builders!.every(b => b.src!.endsWith('.mjs'))).toBe(true);
expect(errorRoutes!.length).toBe(1);
expect((errorRoutes![0] as Source).status).toBe(404);
});
it('just public', async () => {
const files = ['public/index.html', 'public/favicon.ico', 'README.md'];

View File

@@ -1 +0,0 @@
handler

View File

@@ -1,25 +0,0 @@
#!/usr/bin/env node
const execa = require('execa');
const { join } = require('path');
const { homedir } = require('os');
async function main() {
process.env.GOOS = 'linux';
process.env.GOARCH = 'amd64';
process.env.GOPATH = join(homedir(), 'go');
await execa('go', ['get', 'github.com/aws/aws-lambda-go/events'], {
stdio: 'inherit',
});
await execa('go', ['get', 'github.com/aws/aws-lambda-go/lambda'], {
stdio: 'inherit',
});
await execa('go', ['build', '-o', 'handler', 'main.go'], {
stdio: 'inherit',
});
}
main().catch(err => {
console.error(err);
process.exit(1);
});

View File

@@ -1,45 +0,0 @@
const path = require('path');
const { mkdirp, copyFile } = require('fs-extra');
const {
glob,
download,
shouldServe,
createLambda,
getWritableDirectory,
} = require('@vercel/build-utils');
exports.analyze = ({ files, entrypoint }) => files[entrypoint].digest;
exports.version = 3;
exports.build = async ({ workPath, files, entrypoint, meta }) => {
const outDir = await getWritableDirectory();
await download(files, workPath, meta);
const handlerPath = path.join(__dirname, 'handler');
await copyFile(handlerPath, path.join(outDir, 'handler'));
const entrypointOutDir = path.join(outDir, path.dirname(entrypoint));
await mkdirp(entrypointOutDir);
// For now only the entrypoint file is copied into the lambda
await copyFile(
path.join(workPath, entrypoint),
path.join(outDir, entrypoint)
);
const lambda = await createLambda({
files: await glob('**', outDir),
handler: 'handler',
runtime: 'go1.x',
environment: {
SCRIPT_FILENAME: entrypoint,
},
});
return { output: lambda };
};
exports.shouldServe = shouldServe;

View File

@@ -1,36 +0,0 @@
package main
import (
now "../../utils/go/bridge"
"net/http"
"net/http/cgi"
"os"
"path/filepath"
)
type CgiHandler struct {
http.Handler
Dir string
Script string
}
func (h *CgiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
handler := cgi.Handler{
Path: h.Script,
Root: "/" + h.Script,
Dir: h.Dir,
Env: []string{
"HTTPS=on",
"SERVER_PORT=443",
"SERVER_SOFTWARE=@vercel/cgi",
},
}
handler.ServeHTTP(w, r)
}
func main() {
workdir, _ := filepath.Abs(".")
script := os.Getenv("SCRIPT_FILENAME")
handler := &CgiHandler{nil, workdir, script}
now.Start(handler)
}

View File

@@ -1,24 +0,0 @@
{
"name": "@vercel/cgi",
"version": "1.0.7",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/vercel/vercel.git",
"directory": "packages/cgi"
},
"scripts": {
"build": "node build",
"prepublishOnly": "node build"
},
"files": [
"index.js",
"handler"
],
"dependencies": {
"fs-extra": "7.0.0"
},
"devDependencies": {
"rmfr": "2.0.0"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "23.0.0",
"version": "23.0.2-canary.1",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -61,10 +61,10 @@
"node": ">= 12"
},
"dependencies": {
"@vercel/build-utils": "2.11.0",
"@vercel/build-utils": "2.11.2-canary.0",
"@vercel/go": "1.2.2",
"@vercel/node": "1.11.0",
"@vercel/python": "2.0.3",
"@vercel/node": "1.11.2-canary.1",
"@vercel/python": "2.0.4",
"@vercel/ruby": "1.2.6",
"update-notifier": "4.1.0"
},
@@ -100,7 +100,7 @@
"@types/universal-analytics": "0.4.2",
"@types/which": "1.3.2",
"@types/write-json-file": "2.2.1",
"@vercel/frameworks": "0.4.0",
"@vercel/frameworks": "0.4.1",
"@vercel/ncc": "0.24.0",
"@zeit/fun": "0.11.2",
"@zeit/source-map-support": "0.6.2",
@@ -148,7 +148,7 @@
"node-fetch": "2.6.1",
"npm-package-arg": "6.1.0",
"nyc": "13.2.0",
"open": "8.0.2",
"open": "8.2.0",
"ora": "3.4.0",
"pcre-to-regexp": "1.0.0",
"pluralize": "7.0.0",

View File

@@ -2,10 +2,11 @@ import chalk from 'chalk';
import { handleError } from '../../util/error';
import Client from '../../util/client';
import getArgs from '../../util/get-args';
import getSubcommand from '../../util/get-subcommand';
import logo from '../../util/output/logo';
import { getPkgName } from '../../util/pkg-name.ts';
import { getPkgName } from '../../util/pkg-name';
import ls from './ls';
import rm from './rm';
@@ -36,6 +37,7 @@ const help = () => {
)} Login token
-S, --scope Set a custom scope
-N, --next Show next page of results
${chalk.dim('Examples:')}
${chalk.gray('')} Add a new alias to ${chalk.underline('my-api.vercel.app')}
@@ -64,13 +66,13 @@ const help = () => {
};
const COMMAND_CONFIG = {
default: 'set',
default: ['set'],
ls: ['ls', 'list'],
rm: ['rm', 'remove'],
set: ['set'],
};
export default async function main(client) {
export default async function main(client: Client) {
let argv;
try {

View File

@@ -1,21 +1,26 @@
import chalk from 'chalk';
import ms from 'ms';
import table from 'text-table';
import Now from '../../util';
import Client from '../../util/client';
import getAliases from '../../util/alias/get-aliases';
import getScope from '../../util/get-scope.ts';
import stamp from '../../util/output/stamp.ts';
import strlen from '../../util/strlen.ts';
import getScope from '../../util/get-scope';
import stamp from '../../util/output/stamp';
import strlen from '../../util/strlen';
import getCommandFlags from '../../util/get-command-flags';
import { getCommandName } from '../../util/pkg-name.ts';
import { getCommandName } from '../../util/pkg-name';
export default async function ls(client, opts, args) {
const {
apiUrl,
authConfig: { token },
output,
config: { currentTeam },
} = client;
import { Alias } from '../../types';
interface Options {
'--next'?: number;
}
export default async function ls(
client: Client,
opts: Options,
args: string[]
) {
const { output } = client;
const { '--next': nextTimestamp } = opts;
let contextName = null;
@@ -36,13 +41,6 @@ export default async function ls(client, opts, args) {
return 1;
}
const now = new Now({
apiUrl,
token,
debug: client.output.isDebugEnabled(),
currentTeam,
output,
});
const lsStamp = stamp();
if (args.length > 0) {
@@ -56,8 +54,9 @@ export default async function ls(client, opts, args) {
output.spinner(`Fetching aliases under ${chalk.bold(contextName)}`);
// Get the list of alias
const { aliases, pagination } = await getAliases(
now,
client,
undefined,
nextTimestamp
);
@@ -73,21 +72,20 @@ export default async function ls(client, opts, args) {
);
}
now.close();
return 0;
}
function printAliasTable(aliases) {
function printAliasTable(aliases: Alias[]) {
return `${table(
[
['source', 'url', 'age'].map(h => chalk.gray(h)),
['source', 'url', 'age'].map(header => chalk.gray(header)),
...aliases.map(a => [
// for legacy reasons, we might have situations
// where the deployment was deleted and the alias
// not collected appropriately, and we need to handle it
a.deployment && a.deployment.url ? a.deployment.url : chalk.gray(''),
a.alias,
ms(Date.now() - new Date(a.createdAt)),
ms(Date.now() - a.createdAt),
]),
],
{

View File

@@ -1,23 +1,29 @@
import chalk from 'chalk';
import ms from 'ms';
import table from 'text-table';
import Now from '../../util';
import getScope from '../../util/get-scope.ts';
import Client from '../../util/client';
import getScope from '../../util/get-scope';
import removeAliasById from '../../util/alias/remove-alias-by-id';
import stamp from '../../util/output/stamp.ts';
import strlen from '../../util/strlen.ts';
import stamp from '../../util/output/stamp';
import strlen from '../../util/strlen';
import confirm from '../../util/input/confirm';
import { isValidName } from '../../util/is-valid-name';
import findAliasByAliasOrId from '../../util/alias/find-alias-by-alias-or-id';
import { getCommandName } from '../../util/pkg-name.ts';
export default async function rm(client, opts, args) {
const {
apiUrl,
authConfig: { token },
output,
config: { currentTeam },
} = client;
import { Alias } from '../../types';
import { Output } from '../../util/output';
import { isValidName } from '../../util/is-valid-name';
import { getCommandName } from '../../util/pkg-name';
type Options = {
'--yes': boolean;
};
export default async function rm(
client: Client,
opts: Partial<Options>,
args: string[]
) {
const { output } = client;
let contextName = null;
@@ -32,13 +38,6 @@ export default async function rm(client, opts, args) {
throw err;
}
const now = new Now({
apiUrl,
token,
debug: client.output.isDebugEnabled(),
currentTeam,
output,
});
const [aliasOrId] = args;
if (args.length !== 1) {
@@ -61,7 +60,8 @@ export default async function rm(client, opts, args) {
return 1;
}
const alias = await findAliasByAliasOrId(output, now, aliasOrId);
const alias = await findAliasByAliasOrId(output, client, aliasOrId);
if (!alias) {
output.error(
`Alias not found by "${aliasOrId}" under ${chalk.bold(contextName)}`
@@ -76,7 +76,7 @@ export default async function rm(client, opts, args) {
return 0;
}
await removeAliasById(now, alias.uid);
await removeAliasById(client, alias.uid);
console.log(
`${chalk.cyan('> Success!')} Alias ${chalk.bold(
alias.alias
@@ -85,7 +85,7 @@ export default async function rm(client, opts, args) {
return 0;
}
async function confirmAliasRemove(output, alias) {
async function confirmAliasRemove(output: Output, alias: Alias) {
const srcUrl = alias.deployment
? chalk.underline(alias.deployment.url)
: null;
@@ -94,7 +94,7 @@ async function confirmAliasRemove(output, alias) {
[
...(srcUrl ? [srcUrl] : []),
chalk.underline(alias.alias),
chalk.gray(`${ms(new Date() - new Date(alias.created))} ago`),
chalk.gray(`${ms(Date.now() - alias.createdAt)} ago`),
],
],
{

View File

@@ -28,7 +28,7 @@ type Options = {
export default async function set(
client: Client,
opts: Options,
opts: Partial<Options>,
args: string[]
) {
const { output, localConfig } = client;

View File

@@ -108,8 +108,5 @@ export default async function dev(
systemEnvValues,
});
process.once('SIGINT', () => devServer.stop());
process.once('SIGTERM', () => devServer.stop());
await devServer.start(...listen);
}

View File

@@ -12,7 +12,6 @@ import { getCommandName, getPkgName } from '../util/pkg-name';
import getGlobalPathConfig from '../util/config/global-path';
import { writeToAuthConfigFile, writeToConfigFile } from '../util/config/files';
import Client from '../util/client';
import { LoginParams } from '../util/login/types';
const help = () => {
console.log(`
@@ -46,7 +45,7 @@ const help = () => {
export default async function login(client: Client): Promise<number> {
let argv;
const { apiUrl, output } = client;
const { output } = client;
try {
argv = getArgs(client.argv.slice(2));
@@ -68,18 +67,17 @@ export default async function login(client: Client): Promise<number> {
const input = argv._[1];
let result: number | string = 1;
const params: LoginParams = { output, apiUrl };
if (input) {
// Email or Team slug was provided via command line
if (validateEmail(input)) {
result = await doEmailLogin(params, input);
result = await doEmailLogin(client, input);
} else {
result = await doSsoLogin(params, input);
result = await doSsoLogin(client, input);
}
} else {
// Interactive mode
result = await prompt(params);
result = await prompt(client);
}
// The login function failed, so it returned an exit code

View File

@@ -1,6 +1,5 @@
import chalk from 'chalk';
import logo from '../util/output/logo';
// @ts-ignore
import { handleError } from '../util/error';
import { writeToConfigFile, writeToAuthConfigFile } from '../util/config/files';
import getArgs from '../util/get-args';
@@ -62,6 +61,7 @@ export default async function main(client: Client): Promise<number> {
try {
await client.fetch(`/v3/user/tokens/current`, {
method: 'DELETE',
useCurrentTeam: false,
});
} catch (err) {
if (err.status === 403) {

View File

@@ -243,7 +243,7 @@ function printLogShort(log: any) {
const date = new Date(log.created).toISOString();
data.split('\n').forEach((line, i) => {
data.split('\n').forEach(line => {
if (
line.includes('START RequestId:') ||
line.includes('END RequestId:') ||
@@ -260,18 +260,9 @@ function printLogShort(log: any) {
}
}
if (i === 0) {
console.log(
`${chalk.dim(date)} ${line.replace('[now-builder-debug] ', '')}`
);
} else {
console.log(
`${' '.repeat(date.length)} ${line.replace(
'[now-builder-debug] ',
''
)}`
);
}
console.log(
`${chalk.dim(date)} ${line.replace('[now-builder-debug] ', '')}`
);
});
return 0;

View File

@@ -187,25 +187,22 @@ async function run({ client, contextName }) {
const name = args[0];
// Check the existence of the project
try {
await client.fetch(`/projects/info/${e(name)}`);
} catch (err) {
if (err.status === 404) {
console.error(error('No such project exists'));
return exit(1);
}
}
const yes = await readConfirmation(name);
if (!yes) {
console.error(error('User abort'));
return exit(0);
}
await client.fetch(`/v2/projects/${name}`, {
method: 'DELETE',
});
try {
await client.fetch(`/v2/projects/${e(name)}`, {
method: 'DELETE',
});
} catch (err) {
if (err.status === 404) {
console.error(error('No such project exists'));
return exit(1);
}
}
const elapsed = ms(new Date() - start);
console.log(
`${chalk.cyan('> Success!')} Project ${chalk.bold(

View File

@@ -50,6 +50,7 @@ import { SENTRY_DSN } from './util/constants.ts';
import getUpdateCommand from './util/get-update-command';
import { metrics, shouldCollectMetrics } from './util/metrics.ts';
import { getCommandName, getTitleName } from './util/pkg-name.ts';
import doLoginPrompt from './util/login/prompt.ts';
const isCanary = pkg.version.includes('canary');
@@ -422,13 +423,24 @@ const main = async () => {
) {
if (isTTY) {
output.log(info(`No existing credentials found. Please log in:`));
const result = await doLoginPrompt(client);
subcommand = 'login';
client.argv[2] = 'login';
// The login function failed, so it returned an exit code
if (typeof result === 'number') {
return result;
}
// Ensure that subcommands lead to login as well, if
// no credentials are defined
client.argv = client.argv.splice(0, 3);
// When `result` is a string it's the user's authentication token.
// It needs to be saved to the configuration file.
client.authConfig.token = result;
// New user, so we can't keep the team
delete client.config.currentTeam;
configFiles.writeToAuthConfigFile(client.authConfig);
configFiles.writeToConfigFile(client.config);
output.debug(`Saved credentials in "${hp(VERCEL_DIR)}"`);
} else {
output.prettyError({
message:

View File

@@ -16,7 +16,7 @@ export interface JSONObject {
}
export interface AuthConfig {
token: string;
token?: string;
skipWrite?: boolean;
}
@@ -145,7 +145,7 @@ export type Deployment = {
export type Alias = {
uid: string;
alias: string;
created: string;
createdAt: number;
deployment: {
id: string;
url: string;

View File

@@ -1,5 +1,6 @@
import { Output } from '../output';
import { Alias } from '../../types';
import Client from '../client';
export default async function findAliasByAliasOrId(
@@ -11,7 +12,6 @@ export default async function findAliasByAliasOrId(
`/now/aliases/${encodeURIComponent(getSafeAlias(aliasOrId))}`
);
}
function getSafeAlias(alias: string) {
return alias
.replace(/^https:\/\//i, '')

View File

@@ -1,8 +1,9 @@
import { Alias } from '../../types';
import { Alias, PaginationOptions } from '../../types';
import Client from '../client';
type Response = {
aliases: Alias[];
pagination: PaginationOptions;
};
export default async function getAliases(

View File

@@ -2,6 +2,6 @@ import Client from '../client';
export default async function removeAliasById(client: Client, id: string) {
return client.fetch(`/now/aliases/${id}`, {
method: 'DELETE'
method: 'DELETE',
});
}

View File

@@ -145,6 +145,7 @@ export default class DevServer {
private devServerPids: Set<number>;
private projectSettings?: ProjectSettings;
private vercelConfigWarning: boolean;
private getVercelConfigPromise: Promise<VercelConfig> | null;
private blockingBuildsPromise: Promise<void> | null;
private updateBuildersPromise: Promise<void> | null;
@@ -181,6 +182,7 @@ export default class DevServer {
this.inProgressBuilds = new Map();
this.devCacheDir = join(getVercelDirectory(cwd), 'cache');
this.vercelConfigWarning = false;
this.getVercelConfigPromise = null;
this.blockingBuildsPromise = null;
this.updateBuildersPromise = null;
@@ -636,6 +638,20 @@ export default class DevServer {
await this.validateVercelConfig(config);
// TODO: temporarily strip and warn since `has` is not implemented yet
config.routes = (config.routes || []).filter(route => {
if ('has' in route) {
if (!this.vercelConfigWarning) {
this.vercelConfigWarning = true;
this.output.warn(
`The "has" property in ${config[fileNameSymbol]} will be ignored during development. Deployments will work as expected.`
);
}
return false;
}
return true;
});
this.caseSensitive = hasNewRoutingProperties(config);
this.apiDir = detectApiDirectory(config.builds || []);
this.apiExtensions = detectApiExtensions(config.builds || []);

View File

@@ -42,7 +42,7 @@ export default async function doEmailLogin(
while (!token) {
try {
await sleep(ms('1s'));
token = await verify(email, verificationToken, params);
token = await verify(email, verificationToken, 'Email', params);
} catch (err) {
if (err.message !== 'Confirmation incomplete') {
output.error(err.message);

View File

@@ -1,8 +1,6 @@
import fetch from 'node-fetch';
import { hostname } from 'os';
import { InvalidEmail, AccountNotFound } from '../errors-ts';
import ua from '../ua';
import { getTitleName } from '../pkg-name';
import { LoginData } from './types';
export default async function login(
@@ -10,20 +8,13 @@ export default async function login(
email: string,
mode: 'login' | 'signup' = 'login'
): Promise<LoginData> {
const hyphens = new RegExp('-', 'g');
const host = hostname().replace(hyphens, ' ').replace('.local', '');
const tokenName = `${getTitleName()} CLI on ${host}`;
const response = await fetch(`${apiUrl}/now/registration?mode=${mode}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'User-Agent': ua,
},
body: JSON.stringify({
tokenName,
email,
}),
body: JSON.stringify({ email }),
});
const body = await response.json();

View File

@@ -2,12 +2,12 @@ import http from 'http';
import open from 'open';
import { URL } from 'url';
import listen from 'async-listen';
import { hostname } from 'os';
import { LoginParams } from './types';
import prompt from './prompt';
import verify from './verify';
import { getTitleName } from '../pkg-name';
import highlight from '../output/highlight';
import link from '../output/link';
import eraseLines from '../output/erase-lines';
export default async function doOauthLogin(
params: LoginParams,
@@ -16,26 +16,24 @@ export default async function doOauthLogin(
): Promise<number | string> {
const { output } = params;
output.spinner(
`Please complete the ${provider} authentication in your web browser`
);
const server = http.createServer();
const address = await listen(server, 0, '127.0.0.1');
const { port } = new URL(address);
url.searchParams.append('mode', 'login');
url.searchParams.append('next', `http://localhost:${port}`);
url.searchParams.set('mode', 'login');
url.searchParams.set('next', `http://localhost:${port}`);
// Append token name param
const hyphens = new RegExp('-', 'g');
const host = hostname().replace(hyphens, ' ').replace('.local', '');
const tokenName = `${getTitleName()} CLI on ${host} via ${provider}`;
url.searchParams.append('tokenName', tokenName);
output.log(`Please visit the following URL in your web browser:`);
output.log(link(url.href));
output.spinner(`Waiting for ${provider} authentication to be completed`);
try {
const [query] = await Promise.all([
new Promise<URL['searchParams']>((resolve, reject) => {
server.once('request', (req, res) => {
// Close the HTTP connection to prevent
// `server.close()` from hanging
res.setHeader('connection', 'close');
const query = new URL(req.url || '/', 'http://localhost')
.searchParams;
resolve(query);
@@ -78,6 +76,9 @@ export default async function doOauthLogin(
open(url.href),
]);
output.stopSpinner();
output.print(eraseLines(3));
const loginError = query.get('loginError');
if (loginError) {
const err = JSON.parse(loginError);
@@ -106,7 +107,7 @@ export default async function doOauthLogin(
}
output.spinner('Verifying authentication token');
const token = await verify(email, verificationToken, params);
const token = await verify(email, verificationToken, provider, params);
output.success(
`${provider} authentication complete for ${highlight(email)}`
);

View File

@@ -4,6 +4,6 @@ import doOauthLogin from './oauth';
export default function doSsoLogin(params: LoginParams, teamIdOrSlug: string) {
const url = new URL('/auth/sso', params.apiUrl);
url.searchParams.append('teamId', teamIdOrSlug);
url.searchParams.set('teamId', teamIdOrSlug);
return doOauthLogin(params, url, 'SAML Single Sign-On');
}

View File

@@ -1,6 +1,8 @@
import { AuthConfig } from '../../types';
import { Output } from '../output';
export interface LoginParams {
authConfig: AuthConfig;
apiUrl: string;
output: Output;
ssoUserId?: string;

View File

@@ -1,24 +1,41 @@
import { URL } from 'url';
import fetch from 'node-fetch';
import fetch, { Headers } from 'node-fetch';
import ua from '../ua';
import { LoginParams } from './types';
import { hostname } from 'os';
import { getTitleName } from '../pkg-name';
export default async function verify(
email: string,
verificationToken: string,
{ apiUrl, ssoUserId }: LoginParams
provider: string,
{ authConfig, apiUrl, ssoUserId }: LoginParams
): Promise<string> {
const url = new URL('/registration/verify', apiUrl);
url.searchParams.append('email', email);
url.searchParams.append('token', verificationToken);
if (ssoUserId) {
url.searchParams.append('ssoUserId', ssoUserId);
url.searchParams.set('email', email);
url.searchParams.set('token', verificationToken);
const headers = new Headers({ 'User-Agent': ua });
if (authConfig.token) {
// If there is already an auth token then it will be
// upgraded, rather than a new token being created
headers.set('Authorization', `Bearer ${authConfig.token}`);
} else {
// Set the "name" of the Token that will be created
const hyphens = new RegExp('-', 'g');
const host = hostname().replace(hyphens, ' ').replace('.local', '');
const tokenName = `${getTitleName()} CLI on ${host} via ${provider}`;
url.searchParams.set('tokenName', tokenName);
}
const res = await fetch(url.href, {
headers: { 'User-Agent': ua },
});
// If `ssoUserId` is defined then this verification
// will complete the SAML two-step login connection
if (ssoUserId) {
url.searchParams.set('ssoUserId', ssoUserId);
}
const res = await fetch(url.href, { headers });
const body = await res.json();
if (!res.ok) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -0,0 +1,11 @@
import { dep1 } from './js/ecmascript-module';
const { dep2 } = require('./js/commonjs-module');
module.exports = (req, res) => {
if (req && typeof dep1 === 'string' && typeof dep2 === 'string') {
res.end('mixed-modules:js');
} else {
res.end('import failed');
}
};

View File

@@ -0,0 +1,12 @@
import { dep1 } from './js/em-jay-ess.mjs';
async function handler(_req, res) {
const cjs = await import('./js/commonjs-module.js');
if (dep1 === 'dep1' && cjs.default && cjs.default.dep2 === 'dep2') {
res.end('mixed-modules:mjs');
} else {
res.end('import failed');
}
};
export default handler;

View File

@@ -0,0 +1,11 @@
import { IncomingMessage, ServerResponse } from 'http';
import { dep1 } from './ts/ecmascript-module';
const { dep2 } = require('./ts/commonjs-module');
module.exports = (req: IncomingMessage, res: ServerResponse) => {
if (req && typeof dep1 === 'string' && typeof dep2 === 'string') {
res.end('mixed-modules:ts');
} else {
res.end('import failed');
}
};

View File

@@ -0,0 +1 @@
module.exports = { dep2: 'dep2' };

View File

@@ -0,0 +1,2 @@
export const dep1 = 'dep1';
export const another = 'another';

View File

@@ -0,0 +1,2 @@
export const dep1 = 'dep1';
export const another = 'another';

View File

@@ -0,0 +1,3 @@
{
"private": true
}

View File

@@ -0,0 +1 @@
module.exports = { dep2: 'dep2' };

View File

@@ -0,0 +1 @@
export const dep1 = 'dep1';

View File

@@ -0,0 +1,3 @@
{
"private": true
}

View File

@@ -0,0 +1,12 @@
import { dep1 } from '../js/em-jay-ess.mjs';
async function handler(_req, res) {
const cjs = await import('../js/commonjs-module.js');
if (dep1 === 'dep1' && cjs.default && cjs.default.dep2 === 'dep2') {
res.end('mixed-modules:auto');
} else {
res.end('import failed');
}
}
export default handler;

View File

@@ -0,0 +1,12 @@
import { dep1 } from '../../js/em-jay-ess.mjs';
async function handler(_req, res) {
const cjs = await import('../../js/commonjs-module.js');
if (dep1 === 'dep1' && cjs.default && cjs.default.dep2 === 'dep2') {
res.end('mixed-modules:also');
} else {
res.end('import failed');
}
}
export default handler;

View File

@@ -0,0 +1,4 @@
{
"private": true,
"type": "module"
}

View File

@@ -0,0 +1,7 @@
{
"version": 2,
"builds": [
{ "src": "entrypoint**", "use": "@vercel/node@canary" },
{ "src": "type-module-package-json/**/*.js", "use": "@vercel/node@canary" }
]
}

View File

@@ -1710,7 +1710,9 @@ test(
expectHeader('image/svg+xml'),
fetchOpts('image/webp')
);
// bmp should bypass: serve as-is
/* Disabled bmp because `next dev` bypasses
* and production will convert. Eventually
* we can enable once `next dev` supports it.
await testPath(
200,
toUrl('/test.bmp', 64, 50),
@@ -1718,6 +1720,7 @@ test(
expectHeader('image/bmp'),
fetchOpts('image/webp')
);
*/
// animated gif should bypass: serve as-is
await testPath(
200,
@@ -1729,6 +1732,25 @@ test(
})
);
test(
'[vercel dev] 40-mixed-modules',
testFixtureStdio('40-mixed-modules', async testPath => {
await testPath(200, '/entrypoint.js', 'mixed-modules:js');
await testPath(200, '/entrypoint.mjs', 'mixed-modules:mjs');
await testPath(200, '/entrypoint.ts', 'mixed-modules:ts');
await testPath(
200,
'/type-module-package-json/auto.js',
'mixed-modules:auto'
);
await testPath(
200,
'/type-module-package-json/nested/also.js',
'mixed-modules:also'
);
})
);
test(
'[vercel dev] Use `@vercel/python` with Flask requirements.txt',
testFixtureStdio('python-flask', async testPath => {

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/client",
"version": "10.1.0",
"version": "10.1.2-canary.0",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"homepage": "https://vercel.com",
@@ -40,7 +40,7 @@
]
},
"dependencies": {
"@vercel/build-utils": "2.11.0",
"@vercel/build-utils": "2.11.2-canary.0",
"@zeit/fetch": "5.2.0",
"async-retry": "1.2.3",
"async-sema": "3.0.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/frameworks",
"version": "0.4.0",
"version": "0.4.1",
"main": "./dist/frameworks.js",
"types": "./dist/frameworks.d.ts",
"files": [
@@ -20,7 +20,7 @@
"@types/js-yaml": "3.12.1",
"@types/node": "12.0.4",
"@types/node-fetch": "2.5.8",
"@vercel/routing-utils": "1.11.1",
"@vercel/routing-utils": "1.11.2",
"ajv": "6.12.2",
"jest": "24.9.0",
"ts-jest": "24.1.0",

View File

@@ -1,2 +0,0 @@
/bridge.*
/launcher.*

18
packages/node-bridge/bridge.d.ts vendored Normal file
View File

@@ -0,0 +1,18 @@
/// <reference types="node" />
import { Server } from 'http';
import {
VercelProxyRequest,
VercelProxyResponse,
VercelProxyEvent,
ServerLike,
} from './types';
export declare class Bridge {
constructor(server?: ServerLike, shouldStoreEvents?: boolean);
setServer(server: ServerLike): void;
setStoreEvents(shouldStoreEvents: boolean): void;
listen(): void | Server;
launcher(event: VercelProxyEvent, context: any): Promise<VercelProxyResponse>;
consumeEvent(reqId: string): VercelProxyRequest;
}
export {};

View File

@@ -1,43 +1,4 @@
/// <reference types="node" />
import { AddressInfo } from 'net';
import { APIGatewayProxyEvent, Context } from 'aws-lambda';
import {
Server,
IncomingHttpHeaders,
OutgoingHttpHeaders,
request,
} from 'http';
interface NowProxyEvent {
Action: string;
body: string;
}
export interface NowProxyRequest {
isApiGateway?: boolean;
method: string;
path: string;
headers: IncomingHttpHeaders;
body: Buffer;
}
export interface NowProxyResponse {
statusCode: number;
headers: OutgoingHttpHeaders;
body: string;
encoding: BufferEncoding;
}
interface ServerLike {
timeout?: number;
listen: (
opts: {
host?: string;
port?: number;
},
callback: (this: Server | null) => void
) => Server | void;
}
const { request } = require('http');
/**
* If the `http.Server` handler function throws an error asynchronously,
@@ -51,8 +12,11 @@ process.on('unhandledRejection', err => {
process.exit(1);
});
function normalizeNowProxyEvent(event: NowProxyEvent): NowProxyRequest {
let bodyBuffer: Buffer | null;
/**
* @param {import('./types').VercelProxyEvent} event
*/
function normalizeProxyEvent(event) {
let bodyBuffer;
const { method, path, headers, encoding, body } = JSON.parse(event.body);
if (body) {
@@ -70,10 +34,11 @@ function normalizeNowProxyEvent(event: NowProxyEvent): NowProxyRequest {
return { isApiGateway: false, method, path, headers, body: bodyBuffer };
}
function normalizeAPIGatewayProxyEvent(
event: APIGatewayProxyEvent
): NowProxyRequest {
let bodyBuffer: Buffer | null;
/**
* @param {import('aws-lambda').APIGatewayProxyEvent} event
*/
function normalizeAPIGatewayProxyEvent(event) {
let bodyBuffer;
const { httpMethod: method, path, headers, body } = event;
if (body) {
@@ -89,12 +54,13 @@ function normalizeAPIGatewayProxyEvent(
return { isApiGateway: true, method, path, headers, body: bodyBuffer };
}
function normalizeEvent(
event: NowProxyEvent | APIGatewayProxyEvent
): NowProxyRequest {
/**
* @param {import('./types').VercelProxyEvent | import('aws-lambda').APIGatewayProxyEvent} event
*/
function normalizeEvent(event) {
if ('Action' in event) {
if (event.Action === 'Invoke') {
return normalizeNowProxyEvent(event);
return normalizeProxyEvent(event);
} else {
throw new Error(`Unexpected event.Action: ${event.Action}`);
}
@@ -103,35 +69,40 @@ function normalizeEvent(
}
}
export class Bridge {
private server: ServerLike | null;
private listening: Promise<AddressInfo>;
private resolveListening: (info: AddressInfo) => void;
private events: { [key: string]: NowProxyRequest } = {};
private reqIdSeed = 1;
private shouldStoreEvents = false;
constructor(server?: ServerLike, shouldStoreEvents = false) {
this.server = null;
class Bridge {
/**
* @param {import('./types').ServerLike | null} server
* @param {boolean} shouldStoreEvents
*/
constructor(server = null, shouldStoreEvents = false) {
this.server = server;
this.shouldStoreEvents = shouldStoreEvents;
if (server) {
this.setServer(server);
}
this.launcher = this.launcher.bind(this);
// This is just to appease TypeScript strict mode, since it doesn't
// understand that the Promise constructor is synchronous
this.resolveListening = (_info: AddressInfo) => {}; // eslint-disable-line @typescript-eslint/no-unused-vars
this.reqIdSeed = 1;
/**
* @type {{ [key: string]: import('./types').VercelProxyRequest }}
*/
this.events = {};
this.listening = new Promise(resolve => {
this.resolveListening = resolve;
});
}
setServer(server: ServerLike) {
/**
* @param {import('./types').ServerLike} server
*/
setServer(server) {
this.server = server;
}
/**
* @param {boolean} shouldStoreEvents
*/
setStoreEvents(shouldStoreEvents) {
this.shouldStoreEvents = shouldStoreEvents;
}
listen() {
const { server, resolveListening } = this;
if (!server) {
@@ -173,10 +144,13 @@ export class Bridge {
);
}
async launcher(
event: NowProxyEvent | APIGatewayProxyEvent,
context: Pick<Context, 'callbackWaitsForEmptyEventLoop'>
): Promise<NowProxyResponse> {
/**
*
* @param {import('./types').VercelProxyEvent | import('aws-lambda').APIGatewayProxyEvent} event
* @param {import('aws-lambda').Context} context
* @return {Promise<{statusCode: number, headers: import('http').IncomingHttpHeaders, body: string, encoding: 'base64'}>}
*/
async launcher(event, context) {
context.callbackWaitsForEmptyEventLoop = false;
const { port } = await this.listening;
@@ -194,7 +168,10 @@ export class Bridge {
const opts = { hostname: '127.0.0.1', port, path, method };
const req = request(opts, res => {
const response = res;
const respBodyChunks: Buffer[] = [];
/**
* @type {Buffer[]}
*/
const respBodyChunks = [];
response.on('data', chunk => respBodyChunks.push(Buffer.from(chunk)));
response.on('error', reject);
response.on('end', () => {
@@ -227,18 +204,14 @@ export class Bridge {
for (const [name, value] of Object.entries(headers)) {
if (value === undefined) {
console.error(
'Skipping HTTP request header %j because value is undefined',
name
`Skipping HTTP request header "${name}" because value is undefined`
);
continue;
}
try {
req.setHeader(name, value);
} catch (err) {
console.error(
'Skipping HTTP request header: %j',
`${name}: ${value}`
);
console.error(`Skipping HTTP request header: "${name}: ${value}"`);
console.error(err.message);
}
}
@@ -248,9 +221,15 @@ export class Bridge {
});
}
consumeEvent(reqId: string) {
/**
* @param {string} reqId
* @return {import('./types').VercelProxyRequest}
*/
consumeEvent(reqId) {
const event = this.events[reqId];
delete this.events[reqId];
return event;
}
}
module.exports = { Bridge };

16
packages/node-bridge/launcher.d.ts vendored Normal file
View File

@@ -0,0 +1,16 @@
import { Bridge } from './bridge';
import { LauncherConfiguration } from './types';
export declare function makeVercelLauncher(
config: LauncherConfiguration
): string;
export declare function getVercelLauncher({
entrypointPath,
helpersPath,
shouldAddHelpers,
}: LauncherConfiguration): () => Bridge;
export declare function makeAwsLauncher(config: LauncherConfiguration): string;
export declare function getAwsLauncher({
entrypointPath,
awsLambdaHandler,
}: LauncherConfiguration): (e: any, context: any, callback: any) => any;
export {};

View File

@@ -0,0 +1,181 @@
const { parse } = require('url');
const { createServer, Server } = require('http');
const { Bridge } = require('./bridge.js');
/**
* @param {import('./types').LauncherConfiguration} config
*/
function makeVercelLauncher(config) {
const {
entrypointPath,
bridgePath,
helpersPath,
sourcemapSupportPath,
shouldAddHelpers = false,
shouldAddSourcemapSupport = false,
} = config;
return `
const { parse } = require('url');
const { createServer, Server } = require('http');
const { Bridge } = require(${JSON.stringify(bridgePath)});
${
shouldAddSourcemapSupport
? `require(${JSON.stringify(sourcemapSupportPath)});`
: ''
}
const entrypointPath = ${JSON.stringify(entrypointPath)};
const shouldAddHelpers = ${JSON.stringify(shouldAddHelpers)};
const helpersPath = ${JSON.stringify(helpersPath)};
const func = (${getVercelLauncher(config).toString()})();
exports.launcher = func.launcher;`;
}
/**
* @param {import('./types').LauncherConfiguration} config
*/
function getVercelLauncher({
entrypointPath,
helpersPath,
shouldAddHelpers = false,
}) {
return function () {
const bridge = new Bridge();
let isServerListening = false;
const originalListen = Server.prototype.listen;
Server.prototype.listen = function listen() {
isServerListening = true;
console.log('Legacy server listening...');
bridge.setServer(this);
Server.prototype.listen = originalListen;
bridge.listen();
return this;
};
if (!process.env.NODE_ENV) {
const region = process.env.VERCEL_REGION || process.env.NOW_REGION;
process.env.NODE_ENV = region === 'dev1' ? 'development' : 'production';
}
import(entrypointPath)
.then(listener => {
// In some cases we might have nested default props
// due to TS => JS
for (let i = 0; i < 5; i++) {
if (listener.default) listener = listener.default;
}
if (typeof listener.listen === 'function') {
Server.prototype.listen = originalListen;
const server = listener;
bridge.setServer(server);
bridge.listen();
} else if (typeof listener === 'function') {
Server.prototype.listen = originalListen;
if (shouldAddHelpers) {
bridge.setStoreEvents(true);
import(helpersPath).then(helper => {
const server = helper.createServerWithHelpers(listener, bridge);
bridge.setServer(server);
bridge.listen();
});
} else {
const server = createServer(listener);
bridge.setServer(server);
bridge.listen();
}
} else if (
typeof listener === 'object' &&
Object.keys(listener).length === 0
) {
setTimeout(() => {
if (!isServerListening) {
console.error('No exports found in module %j.', entrypointPath);
console.error('Did you forget to export a function or a server?');
process.exit(1);
}
}, 5000);
} else {
console.error('Invalid export found in module %j.', entrypointPath);
console.error('The default export must be a function or server.');
}
})
.catch(err => {
if (err.code === 'MODULE_NOT_FOUND') {
console.error(err.message);
console.error(
'Did you forget to add it to "dependencies" in `package.json`?'
);
} else {
console.error(err);
}
process.exit(1);
});
return bridge;
};
}
/**
* @param {import('./types').LauncherConfiguration} config
*/
function makeAwsLauncher(config) {
const { entrypointPath, awsLambdaHandler = '' } = config;
return `const { parse } = require("url");
const funcName = ${JSON.stringify(awsLambdaHandler.split('.').pop())};
const entrypointPath = ${JSON.stringify(entrypointPath)};
exports.launcher = ${getAwsLauncher(config).toString()}`;
}
/**
* @param {import('./types').LauncherConfiguration} config
*/
function getAwsLauncher({ entrypointPath, awsLambdaHandler = '' }) {
const funcName = awsLambdaHandler.split('.').pop() || '';
if (typeof funcName !== 'string') {
throw new TypeError('Expected "string"');
}
/**
* @param {import('aws-lambda').APIGatewayProxyEvent} e
* @param {import('aws-lambda').Context} context
* @param {() => void} callback
*/
function internal(e, context, callback) {
const { path, method: httpMethod, body, headers } = JSON.parse(
e.body || '{}'
);
const { query } = parse(path, true);
/**
* @type {{[key: string]: string}}
*/
const queryStringParameters = {};
for (const [key, value] of Object.entries(query)) {
if (typeof value === 'string') {
queryStringParameters[key] = value;
}
}
const awsGatewayEvent = {
resource: '/{proxy+}',
path: path,
httpMethod: httpMethod,
body: body,
isBase64Encoded: true,
queryStringParameters: queryStringParameters,
multiValueQueryStringParameters: query,
headers: headers,
};
const mod = require(entrypointPath);
return mod[funcName](awsGatewayEvent, context, callback);
}
return internal;
}
module.exports = {
makeVercelLauncher,
getVercelLauncher,
makeAwsLauncher,
getAwsLauncher,
};

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/node-bridge",
"version": "1.3.3",
"version": "2.0.1-canary.0",
"license": "MIT",
"main": "./index.js",
"repository": {
@@ -15,12 +15,11 @@
],
"scripts": {
"build": "tsc",
"test-integration-once": "jest --env node --verbose --runInBand --bail",
"prepublish": "npm run build"
"test-unit": "jest --env node --verbose --runInBand --bail"
},
"devDependencies": {
"@types/aws-lambda": "8.10.19",
"@types/node": "10.x",
"@types/node": "*",
"typescript": "3.9.3"
}
}

View File

@@ -1,159 +0,0 @@
import { parse } from 'url';
import { createServer, Server } from 'http';
import { Bridge } from './bridge';
type LauncherConfiguration = {
entrypointPath: string;
bridgePath: string;
helpersPath: string;
sourcemapSupportPath: string;
shouldAddHelpers?: boolean;
shouldAddSourcemapSupport?: boolean;
awsLambdaHandler?: string;
};
export function makeVercelLauncher(config: LauncherConfiguration): string {
const {
entrypointPath,
bridgePath,
helpersPath,
sourcemapSupportPath,
shouldAddHelpers = false,
shouldAddSourcemapSupport = false,
} = config;
return `const bridge_1 = require(${JSON.stringify(bridgePath)});
const http_1 = require("http");
${
shouldAddSourcemapSupport
? `require(${JSON.stringify(sourcemapSupportPath)});`
: ''
}
const entrypointPath = ${JSON.stringify(entrypointPath)};
const shouldAddHelpers = ${JSON.stringify(shouldAddHelpers)};
const helpersPath = ${JSON.stringify(helpersPath)};
const bridge = (${getNowLauncher(config)})();
exports.launcher = bridge.launcher;`;
}
export function getNowLauncher({
entrypointPath,
helpersPath,
shouldAddHelpers = false,
}: LauncherConfiguration) {
return function (): Bridge {
let bridge = new Bridge();
let isServerListening = false;
const originalListen = Server.prototype.listen;
Server.prototype.listen = function listen() {
isServerListening = true;
console.log('Legacy server listening...');
bridge.setServer(this);
Server.prototype.listen = originalListen;
bridge.listen();
return this;
};
if (!process.env.NODE_ENV) {
const region = process.env.VERCEL_REGION || process.env.NOW_REGION;
process.env.NODE_ENV = region === 'dev1' ? 'development' : 'production';
}
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
let listener = require(entrypointPath);
if (listener.default) listener = listener.default;
if (typeof listener.listen === 'function') {
Server.prototype.listen = originalListen;
const server = listener;
bridge.setServer(server);
bridge.listen();
} else if (typeof listener === 'function') {
Server.prototype.listen = originalListen;
let server: Server;
if (shouldAddHelpers) {
bridge = new Bridge(undefined, true);
server = require(helpersPath).createServerWithHelpers(
listener,
bridge
);
} else {
server = createServer(listener);
}
bridge.setServer(server);
bridge.listen();
} else if (
typeof listener === 'object' &&
Object.keys(listener).length === 0
) {
setTimeout(() => {
if (!isServerListening) {
console.error('No exports found in module %j.', entrypointPath);
console.error('Did you forget to export a function or a server?');
process.exit(1);
}
}, 5000);
} else {
console.error('Invalid export found in module %j.', entrypointPath);
console.error('The default export must be a function or server.');
}
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
console.error(err.message);
console.error(
'Did you forget to add it to "dependencies" in `package.json`?'
);
} else {
console.error(err);
}
process.exit(1);
}
return bridge;
};
}
export function makeAwsLauncher(config: LauncherConfiguration): string {
const { entrypointPath, awsLambdaHandler = '' } = config;
return `const url_1 = require("url");
const funcName = ${JSON.stringify(awsLambdaHandler.split('.').pop())};
const entrypointPath = ${JSON.stringify(entrypointPath)};
exports.launcher = ${getAwsLauncher(config)}`;
}
export function getAwsLauncher({
entrypointPath,
awsLambdaHandler = '',
}: LauncherConfiguration) {
const funcName = awsLambdaHandler.split('.').pop();
if (typeof funcName !== 'string') {
throw new TypeError('Expected "string"');
}
// @ts-ignore
return function (e, context, callback) {
const { path, method: httpMethod, body, headers } = JSON.parse(e.body);
const { query } = parse(path, true);
const queryStringParameters: { [i: string]: string } = {};
for (const [key, value] of Object.entries(query)) {
if (typeof value === 'string') {
queryStringParameters[key] = value;
}
}
const awsGatewayEvent = {
resource: '/{proxy+}',
path: path,
httpMethod: httpMethod,
body: body,
isBase64Encoded: true,
queryStringParameters: queryStringParameters,
multiValueQueryStringParameters: query,
headers: headers,
};
// eslint-disable-next-line @typescript-eslint/no-var-requires
const mod = require(entrypointPath);
return mod[funcName](awsGatewayEvent, context, callback);
};
}

View File

@@ -1,14 +1,13 @@
{
"compilerOptions": {
"esModuleInterop": true,
"allowJs": true,
"checkJs": true,
"lib": ["esnext"],
"target": "esnext",
"module": "commonjs",
"outDir": ".",
"noEmit": true,
"noImplicitReturns": true,
"strict": true,
"sourceMap": true,
"declaration": true
},
"include": ["src/**/*"],
"include": ["bridge.js", "launcher.js"],
"exclude": ["node_modules"]
}

View File

@@ -0,0 +1,38 @@
/// <reference types="node" />
import { Server, IncomingHttpHeaders, OutgoingHttpHeaders } from 'http';
export interface VercelProxyEvent {
Action: string;
body: string;
}
export interface VercelProxyRequest {
isApiGateway?: boolean;
method: string;
path: string;
headers: IncomingHttpHeaders;
body: Buffer;
}
export interface VercelProxyResponse {
statusCode: number;
headers: OutgoingHttpHeaders;
body: string;
encoding: BufferEncoding;
}
export interface ServerLike {
timeout?: number;
listen: (
opts: {
host?: string;
port?: number;
},
callback: (this: Server | null) => void
) => Server | void;
}
export type LauncherConfiguration = {
entrypointPath: string;
bridgePath: string;
helpersPath: string;
sourcemapSupportPath: string;
shouldAddHelpers?: boolean;
shouldAddSourcemapSupport?: boolean;
awsLambdaHandler?: string;
};

View File

@@ -1,5 +1,3 @@
/dist
/src/bridge.ts
/src/launcher.ts
/test/fixtures/**/types.d.ts
/test/fixtures/11-symlinks/symlink

View File

@@ -8,15 +8,6 @@ async function main() {
const outDir = join(__dirname, 'dist');
const bridgeDir = join(__dirname, '../node-bridge');
// Copy shared dependencies
await Promise.all([
fs.copyFile(join(bridgeDir, 'src/bridge.ts'), join(srcDir, 'bridge.ts')),
fs.copyFile(
join(bridgeDir, 'src/launcher.ts'),
join(srcDir, 'launcher.ts')
),
]);
// Start fresh
await fs.remove(outDir);
@@ -25,6 +16,12 @@ async function main() {
stdio: 'inherit',
});
// Copy bridge and launcher as-is
await Promise.all([
fs.copyFile(join(bridgeDir, 'bridge.js'), join(outDir, 'bridge.js')),
fs.copyFile(join(bridgeDir, 'launcher.js'), join(outDir, 'launcher.js')),
]);
// Copy type file for ts test
await fs.copyFile(
join(outDir, 'types.d.ts'),
@@ -53,6 +50,8 @@ async function main() {
'build',
join(srcDir, 'helpers.ts'),
'-e',
'@vercel/node-bridge',
'-e',
'@vercel/build-utils',
'-e',
'@now/build-utils',
@@ -72,6 +71,8 @@ async function main() {
'build',
join(__dirname, '../../node_modules/source-map-support/register'),
'-e',
'@vercel/node-bridge',
'-e',
'@vercel/build-utils',
'-e',
'@now/build-utils',
@@ -93,6 +94,8 @@ async function main() {
'build',
join(srcDir, 'index.ts'),
'-e',
'@vercel/node-bridge',
'-e',
'@vercel/build-utils',
'-e',
'@now/build-utils',

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/node",
"version": "1.11.0",
"version": "1.11.2-canary.1",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
@@ -33,7 +33,8 @@
"@types/etag": "1.8.0",
"@types/test-listen": "1.1.0",
"@vercel/ncc": "0.24.0",
"@vercel/nft": "0.12.2",
"@vercel/nft": "0.13.1",
"@vercel/node-bridge": "2.0.1-canary.0",
"content-type": "1.0.4",
"cookie": "0.4.0",
"etag": "1.8.1",

View File

@@ -13,76 +13,79 @@ import { register } from 'ts-node';
type TypescriptModule = typeof import('typescript');
const resolveTypescript = (p: string): string => {
try {
return require.resolve('typescript', {
paths: [p],
});
} catch (_) {
return '';
}
};
const requireTypescript = (p: string): TypescriptModule => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
return require(p) as TypescriptModule;
};
let ts: TypescriptModule | null = null;
// Assume Node.js 12 as the lowest common denominator
let target = 'ES2019';
const nodeMajor = Number(process.versions.node.split('.')[0]);
if (nodeMajor >= 14) {
target = 'ES2020';
}
// Use the project's version of Typescript if available and supports `target`
let compiler = resolveTypescript(process.cwd());
if (compiler) {
ts = requireTypescript(compiler);
if (!(target in ts.ScriptTarget)) {
ts = null;
}
}
// Otherwise fall back to using the copy that `@vercel/node` uses
if (!ts) {
compiler = resolveTypescript(join(__dirname, '..'));
ts = requireTypescript(compiler);
}
if (tsconfig) {
try {
const { config } = ts.readConfigFile(tsconfig, ts.sys.readFile);
if (config?.compilerOptions?.target) {
target = config.compilerOptions.target;
if (!process.env.VERCEL_DEV_IS_ESM) {
const resolveTypescript = (p: string): string => {
try {
return require.resolve('typescript', {
paths: [p],
});
} catch (_) {
return '';
}
} catch (err) {
if (err.code !== 'ENOENT') {
console.error(`Error while parsing "${tsconfig}"`);
throw err;
};
const requireTypescript = (p: string): TypescriptModule => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
return require(p) as TypescriptModule;
};
let ts: TypescriptModule | null = null;
// Assume Node.js 12 as the lowest common denominator
let target = 'ES2019';
const nodeMajor = Number(process.versions.node.split('.')[0]);
if (nodeMajor >= 14) {
target = 'ES2020';
}
// Use the project's version of Typescript if available and supports `target`
let compiler = resolveTypescript(process.cwd());
if (compiler) {
ts = requireTypescript(compiler);
if (!(target in ts.ScriptTarget)) {
ts = null;
}
}
}
register({
compiler,
compilerOptions: {
allowJs: true,
esModuleInterop: true,
jsx: 'react',
module: 'commonjs',
target,
},
project: tsconfig || undefined, // Resolve `tsconfig.json` from entrypoint dir
transpileOnly: true,
});
// Otherwise fall back to using the copy that `@vercel/node` uses
if (!ts) {
compiler = resolveTypescript(join(__dirname, '..'));
ts = requireTypescript(compiler);
}
if (tsconfig) {
try {
const { config } = ts.readConfigFile(tsconfig, ts.sys.readFile);
if (config?.compilerOptions?.target) {
target = config.compilerOptions.target;
}
} catch (err) {
if (err.code !== 'ENOENT') {
console.error(`Error while parsing "${tsconfig}"`);
throw err;
}
}
}
register({
compiler,
compilerOptions: {
allowJs: true,
esModuleInterop: true,
jsx: 'react',
module: 'commonjs',
target,
},
project: tsconfig || undefined, // Resolve `tsconfig.json` from entrypoint dir
transpileOnly: true,
});
}
import { createServer, Server, IncomingMessage, ServerResponse } from 'http';
import { Readable } from 'stream';
import { Bridge } from './bridge';
import { getNowLauncher } from './launcher';
import type { Bridge } from '@vercel/node-bridge/bridge';
// @ts-ignore - copied to the `dist` output as-is
import { getVercelLauncher } from './launcher.js';
function listen(server: Server, port: number, host: string): Promise<void> {
return new Promise(resolve => {
@@ -105,17 +108,16 @@ async function main() {
config.helpers === false || buildEnv.NODEJS_HELPERS === '0'
);
bridge = getNowLauncher({
entrypointPath: join(process.cwd(), entrypoint!),
helpersPath: './helpers',
shouldAddHelpers,
bridgePath: 'not used',
sourcemapSupportPath: 'not used',
})();
const proxyServer = createServer(onDevRequest);
await listen(proxyServer, 0, '127.0.0.1');
const launcher = getVercelLauncher({
entrypointPath: join(process.cwd(), entrypoint!),
helpersPath: './helpers.js',
shouldAddHelpers,
});
bridge = launcher();
const address = proxyServer.address();
if (typeof process.send === 'function') {
process.send(address);
@@ -156,7 +158,7 @@ export async function onDevRequest(
};
if (!bridge) {
res.statusCode = 500;
res.end('Bridge is not defined');
res.end('Bridge is not ready, please try again');
return;
}
const result = await bridge.launcher(event, {

View File

@@ -6,7 +6,7 @@ import {
VercelRequestBody,
} from './types';
import { Server } from 'http';
import { Bridge } from './bridge';
import type { Bridge } from '@vercel/node-bridge/bridge';
function getBodyParser(req: VercelRequest, body: Buffer) {
return function parseBody(): VercelRequestBody {

View File

@@ -46,7 +46,10 @@ const {
isSymbolicLink,
walkParentDirs,
} = buildUtils;
import { makeVercelLauncher, makeAwsLauncher } from './launcher';
// @ts-ignore - copied to the `dist` output as-is
import { makeVercelLauncher, makeAwsLauncher } from './launcher.js';
import { Register, register } from './typescript';
export { shouldServe };
@@ -81,10 +84,10 @@ const tscPath = resolve(
// eslint-disable-next-line no-useless-escape
const libPathRegEx = /^node_modules|[\/\\]node_modules[\/\\]/;
const LAUNCHER_FILENAME = '___vc_launcher';
const BRIDGE_FILENAME = '___vc_bridge';
const HELPERS_FILENAME = '___vc_helpers';
const SOURCEMAP_SUPPORT_FILENAME = '___vc_sourcemap_support';
const LAUNCHER_FILENAME = '___vc_launcher.js';
const BRIDGE_FILENAME = '___vc_bridge.js';
const HELPERS_FILENAME = '___vc_helpers.js';
const SOURCEMAP_SUPPORT_FILENAME = '___vc_sourcemap_support.js';
async function downloadInstallAndBundle({
files,
@@ -117,6 +120,16 @@ async function downloadInstallAndBundle({
return { entrypointPath, entrypointFsDirname, nodeVersion, spawnOpts };
}
function renameTStoJS(path: string) {
if (path.endsWith('.ts')) {
return path.slice(0, -3) + '.js';
}
if (path.endsWith('.tsx')) {
return path.slice(0, -4) + '.js';
}
return path;
}
async function compile(
workPath: string,
baseDir: string,
@@ -133,6 +146,7 @@ async function compile(
const sourceCache = new Map<string, string | Buffer | null>();
const fsCache = new Map<string, File>();
const tsCompiled = new Set<string>();
const pkgCache = new Map<string, { type?: string }>();
let shouldAddSourcemapSupport = false;
@@ -172,9 +186,7 @@ async function compile(
}
const { code, map } = tsCompile(source, path);
tsCompiled.add(relPath);
preparedFiles[
relPath.slice(0, -3 - Number(path.endsWith('x'))) + '.js.map'
] = new FileBlob({
preparedFiles[renameTStoJS(relPath) + '.map'] = new FileBlob({
data: JSON.stringify(map),
});
source = code;
@@ -256,17 +268,12 @@ async function compile(
}
}
}
// Rename .ts -> .js (except for entry)
// There is a bug on Windows where entrypoint uses forward slashes
// and workPath uses backslashes so we use resolve before comparing.
if (
resolve(baseDir, path) !== resolve(workPath, entrypoint) &&
tsCompiled.has(path)
) {
preparedFiles[
path.slice(0, -3 - Number(path.endsWith('x'))) + '.js'
] = entry;
} else preparedFiles[path] = entry;
if (tsCompiled.has(path)) {
preparedFiles[renameTStoJS(path)] = entry;
} else {
preparedFiles[path] = entry;
}
}
// Compile ES Modules into CommonJS
@@ -274,11 +281,29 @@ async function compile(
file =>
!file.endsWith('.ts') &&
!file.endsWith('.tsx') &&
!file.endsWith('.mjs') &&
!file.match(libPathRegEx)
);
if (esmPaths.length) {
const babelCompile = require('./babel').compile;
for (const path of esmPaths) {
const pathDir = join(workPath, dirname(path));
if (!pkgCache.has(pathDir)) {
const pathToPkg = await walkParentDirs({
base: workPath,
start: pathDir,
filename: 'package.json',
});
const pkg = pathToPkg ? require(pathToPkg) : {};
pkgCache.set(pathDir, pkg);
}
const pkg = pkgCache.get(pathDir) || {};
if (pkg.type === 'module' && path.endsWith('.js')) {
// Found parent package.json indicating this file is already ESM
// so we should not transpile to CJS.
// https://nodejs.org/api/packages.html#packages_type
continue;
}
const filename = basename(path);
const { data: source } = await FileBlob.fromStream({
stream: preparedFiles[path].toStream(),
@@ -368,31 +393,33 @@ export async function build({
const launcher = awsLambdaHandler ? makeAwsLauncher : makeVercelLauncher;
const launcherSource = launcher({
entrypointPath: `./${renameTStoJS(relative(baseDir, entrypointPath))}`,
bridgePath: `./${BRIDGE_FILENAME}`,
helpersPath: `./${HELPERS_FILENAME}`,
sourcemapSupportPath: `./${SOURCEMAP_SUPPORT_FILENAME}`,
shouldAddHelpers,
shouldAddSourcemapSupport,
awsLambdaHandler,
});
const launcherFiles: Files = {
[`${LAUNCHER_FILENAME}.js`]: new FileBlob({
data: launcher({
entrypointPath: `./${relative(baseDir, entrypointPath)}`,
bridgePath: `./${BRIDGE_FILENAME}`,
helpersPath: `./${HELPERS_FILENAME}`,
sourcemapSupportPath: `./${SOURCEMAP_SUPPORT_FILENAME}`,
shouldAddHelpers,
shouldAddSourcemapSupport,
awsLambdaHandler,
}),
[LAUNCHER_FILENAME]: new FileBlob({
data: launcherSource,
}),
[`${BRIDGE_FILENAME}.js`]: new FileFsRef({
[BRIDGE_FILENAME]: new FileFsRef({
fsPath: join(__dirname, 'bridge.js'),
}),
};
if (shouldAddSourcemapSupport) {
launcherFiles[`${SOURCEMAP_SUPPORT_FILENAME}.js`] = new FileFsRef({
launcherFiles[SOURCEMAP_SUPPORT_FILENAME] = new FileFsRef({
fsPath: join(__dirname, 'source-map-support.js'),
});
}
if (shouldAddHelpers) {
launcherFiles[`${HELPERS_FILENAME}.js`] = new FileFsRef({
launcherFiles[HELPERS_FILENAME] = new FileFsRef({
fsPath: join(__dirname, 'helpers.js'),
});
}
@@ -402,7 +429,7 @@ export async function build({
...preparedFiles,
...launcherFiles,
},
handler: `${LAUNCHER_FILENAME}.launcher`,
handler: `${LAUNCHER_FILENAME.slice(0, -3)}.launcher`,
runtime: nodeVersion.runtime,
});
@@ -420,13 +447,21 @@ export async function startDevServer(
opts: StartDevServerOptions
): Promise<StartDevServerResult> {
const { entrypoint, workPath, config, meta = {} } = opts;
// Find the `tsconfig.json` file closest to the entrypoint file
const entryDir = join(workPath, dirname(entrypoint));
const projectTsConfig = await walkParentDirs({
base: workPath,
start: join(workPath, dirname(entrypoint)),
start: entryDir,
filename: 'tsconfig.json',
});
const pathToPkg = await walkParentDirs({
base: workPath,
start: entryDir,
filename: 'package.json',
});
const pkg = pathToPkg ? require(pathToPkg) : {};
const isEsm =
entrypoint.endsWith('.mjs') ||
(pkg.type === 'module' && entrypoint.endsWith('.js'));
const devServerPath = join(__dirname, 'dev-server.js');
const child = fork(devServerPath, [], {
@@ -437,6 +472,7 @@ export async function startDevServer(
...meta.env,
VERCEL_DEV_ENTRYPOINT: entrypoint,
VERCEL_DEV_TSCONFIG: projectTsConfig || '',
VERCEL_DEV_IS_ESM: isEsm ? '1' : undefined,
VERCEL_DEV_CONFIG: JSON.stringify(config),
VERCEL_DEV_BUILD_ENV: JSON.stringify(meta.buildEnv || {}),
},

View File

@@ -0,0 +1,12 @@
import { dep1 } from './js/em-jay-ess.mjs';
async function handler(_req, res) {
const cjs = await import('./js/commonjs-module.js');
if (dep1 === 'dep1' && cjs?.default?.dep2 === 'dep2') {
res.end('mixed-modules:mjs:RANDOMNESS_PLACEHOLDER');
} else {
res.end('import failed');
}
};
export default handler;

View File

@@ -0,0 +1,2 @@
export const dep1 = 'dep1';
export const another = 'another';

View File

@@ -0,0 +1,12 @@
import { dep1 } from '../js/em-jay-ess.mjs';
async function handler(_req, res) {
const cjs = await import('../js/commonjs-module.js');
if (dep1 === 'dep1' && cjs?.default?.dep2 === 'dep2') {
res.end('mixed-modules:auto:RANDOMNESS_PLACEHOLDER');
} else {
res.end('import failed');
}
}
export default handler;

View File

@@ -0,0 +1,12 @@
import { dep1 } from '../../js/em-jay-ess.mjs';
async function handler(_req, res) {
const cjs = await import('../../js/commonjs-module.js');
if (dep1 === 'dep1' && cjs?.default?.dep2 === 'dep2') {
res.end('mixed-modules:also:RANDOMNESS_PLACEHOLDER');
} else {
res.end('import failed');
}
}
export default handler;

View File

@@ -0,0 +1,4 @@
{
"private": true,
"type": "module"
}

View File

@@ -1,14 +1,29 @@
{
"version": 2,
"builds": [{ "src": "entrypoint**", "use": "@vercel/node" }],
"builds": [
{ "src": "entrypoint**", "use": "@vercel/node" },
{ "src": "type-module-package-json/**/*.js", "use": "@vercel/node" }
],
"probes": [
{
"path": "/entrypoint.js",
"mustContain": "mixed-modules:js:RANDOMNESS_PLACEHOLDER"
},
{
"path": "/entrypoint.mjs",
"mustContain": "mixed-modules:mjs:RANDOMNESS_PLACEHOLDER"
},
{
"path": "/entrypoint.ts",
"mustContain": "mixed-modules:ts:RANDOMNESS_PLACEHOLDER"
},
{
"path": "/type-module-package-json/auto.js",
"mustContain": "mixed-modules:auto:RANDOMNESS_PLACEHOLDER"
},
{
"path": "/type-module-package-json/nested/also.js",
"mustContain": "mixed-modules:also:RANDOMNESS_PLACEHOLDER"
}
]
}

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/python",
"version": "2.0.3",
"version": "2.0.4",
"main": "./dist/index.js",
"license": "MIT",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",

View File

@@ -82,14 +82,27 @@ elif 'app' in __vc_variables:
not inspect.iscoroutinefunction(__vc_module.app.__call__)
):
print('using Web Server Gateway Interface (WSGI)')
from io import BytesIO
from urllib.parse import urlparse
from werkzeug._compat import BytesIO
from werkzeug._compat import string_types
from werkzeug._compat import to_bytes
from werkzeug._compat import wsgi_encoding_dance
from werkzeug.datastructures import Headers
from werkzeug.wrappers import Response
string_types = (str,)
def to_bytes(x, charset=sys.getdefaultencoding(), errors="strict"):
if x is None:
return None
if isinstance(x, (bytes, bytearray, memoryview)):
return bytes(x)
if isinstance(x, str):
return x.encode(charset, errors)
raise TypeError("Expected bytes")
def wsgi_encoding_dance(s, charset="utf-8", errors="replace"):
if isinstance(s, str):
s = s.encode(charset)
return s.decode("latin1", errors)
def vc_handler(event, context):
payload = json.loads(event['body'])

View File

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

View File

@@ -37,9 +37,9 @@ function getCheckAndContinue(
route
)}`
);
} else if (route.check) {
} else if (route.check && !route.override) {
checks.push(route);
} else if (route.continue) {
} else if (route.continue && !route.override) {
continues.push(route);
} else {
others.push(route);

View File

@@ -104,6 +104,9 @@ export const routesSchema = {
continue: {
type: 'boolean',
},
override: {
type: 'boolean',
},
check: {
type: 'boolean',
},

View File

@@ -187,6 +187,10 @@ function collectHasSegments(has?: HasField) {
const hasSegments = new Set<string>();
for (const hasItem of has || []) {
if ('key' in hasItem && hasItem.type === 'header') {
hasItem.key = hasItem.key.toLowerCase();
}
if (!hasItem.value && 'key' in hasItem) {
hasSegments.add(hasItem.key);
}

View File

@@ -27,6 +27,7 @@ export type Source = {
headers?: { [name: string]: string };
methods?: string[];
continue?: boolean;
override?: boolean;
check?: boolean;
important?: boolean;
status?: number;

View File

@@ -419,3 +419,97 @@ test('mergeRoutes ensure `handle: error` comes last', () => {
];
deepStrictEqual(actual, expected);
});
test('mergeRoutes ensure beforeFiles comes after redirects (continue)', () => {
const userRoutes = [];
const builds = [
{
use: '@vercel/next',
entrypoint: 'package.json',
routes: [
{
src: '^/home$',
status: 301,
headers: {
Location: '/',
},
},
{
src: '^/hello$',
dest: '/somewhere',
continue: true,
override: true,
},
{
handle: 'filesystem',
},
{
src: '^/404$',
dest: '/404',
status: 404,
check: true,
},
],
},
];
const actual = mergeRoutes({ userRoutes, builds });
const expected = [
{ src: '^/home$', status: 301, headers: { Location: '/' } },
{
src: '^/hello$',
dest: '/somewhere',
continue: true,
override: true,
},
{ handle: 'filesystem' },
{ src: '^/404$', dest: '/404', status: 404, check: true },
];
deepStrictEqual(actual, expected);
});
test('mergeRoutes ensure beforeFiles comes after redirects (check)', () => {
const userRoutes = [];
const builds = [
{
use: '@vercel/next',
entrypoint: 'package.json',
routes: [
{
src: '^/home$',
status: 301,
headers: {
Location: '/',
},
},
{
src: '^/hello$',
dest: '/somewhere',
check: true,
override: true,
},
{
handle: 'filesystem',
},
{
src: '^/404$',
dest: '/404',
status: 404,
check: true,
},
],
},
];
const actual = mergeRoutes({ userRoutes, builds });
const expected = [
{ src: '^/home$', status: 301, headers: { Location: '/' } },
{
src: '^/hello$',
dest: '/somewhere',
check: true,
override: true,
},
{ handle: 'filesystem' },
{ src: '^/404$', dest: '/404', status: 404, check: true },
];
deepStrictEqual(actual, expected);
});

View File

@@ -247,7 +247,7 @@ test('convertRedirects', () => {
},
{
type: 'header',
key: 'x-pathname',
key: 'X-Pathname',
value: '(?<another>hello|world)',
},
],
@@ -549,7 +549,7 @@ test('convertRewrites', () => {
},
{
type: 'header',
key: 'x-pathname',
key: 'X-Pathname',
value: '(?<another>hello|world)',
},
],
@@ -893,7 +893,7 @@ test('convertHeaders', () => {
},
{
type: 'header',
key: 'x-pathname',
key: 'X-Pathname',
value: '(?<another>hello|world)',
},
],

View File

@@ -1,127 +0,0 @@
package bridge
import (
"bytes"
"encoding/base64"
"encoding/json"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
"net/http"
"strconv"
"strings"
)
type Request struct {
Host string `json:"host"`
Path string `json:"path"`
Method string `json:"method"`
Headers map[string]string `json:"headers"`
Encoding string `json:"encoding,omitempty"`
Body string `json:"body"`
}
type Response struct {
StatusCode int `json:"statusCode"`
Headers map[string][]string `json:"headers"`
Encoding string `json:"encoding,omitemtpy"`
Body string `json:"body"`
}
type ResponseWriter struct {
http.ResponseWriter
statusCode int
headers http.Header
body *bytes.Buffer
}
func (w *ResponseWriter) Header() http.Header {
return w.headers
}
func (w *ResponseWriter) Write(p []byte) (n int, err error) {
n, err = w.body.Write(p)
return
}
func (w *ResponseWriter) WriteHeader(statusCode int) {
w.statusCode = statusCode
}
var userHandler http.Handler
func Serve(handler http.Handler, req *Request) (res Response, err error) {
var body []byte
if req.Encoding == "base64" {
body, err = base64.StdEncoding.DecodeString(req.Body)
if err != nil {
return
}
} else {
body = []byte(req.Body)
}
r, err := http.NewRequest(req.Method, req.Path, bytes.NewReader(body))
if err != nil {
return
}
for k, v := range req.Headers {
r.Header.Add(k, v)
switch strings.ToLower(k) {
case "host":
// we need to set `Host` in the request
// because Go likes to ignore the `Host` header
// see https://github.com/golang/go/issues/7682
r.Host = v
case "content-length":
contentLength, _ := strconv.ParseInt(v, 10, 64)
r.ContentLength = contentLength
case "x-forwarded-for":
case "x-real-ip":
r.RemoteAddr = v
}
}
var bodyBuf bytes.Buffer
w := &ResponseWriter{
nil,
http.StatusOK,
make(http.Header),
&bodyBuf,
}
handler.ServeHTTP(w, r)
defer r.Body.Close()
headers := make(map[string][]string)
for k, v := range w.headers {
for _, s := range v {
headers[k] = append(headers[k], s)
}
}
res = Response{
StatusCode: w.statusCode,
Headers: headers,
Encoding: "base64",
Body: base64.StdEncoding.EncodeToString(bodyBuf.Bytes()),
}
return
}
// Maps the `APIGatewayProxyRequest` to a `Request` instance and invokes `Serve()`
func handler(event events.APIGatewayProxyRequest) (res Response, err error) {
var req Request
err = json.Unmarshal([]byte(event.Body), &req)
if err != nil {
return
}
res, err = Serve(userHandler, &req)
return
}
// Starts the Lambda
func Start(h http.Handler) {
userHandler = h
lambda.Start(handler)
}

View File

@@ -1,43 +0,0 @@
package bridge
import (
"encoding/base64"
"fmt"
"net/http"
"testing"
)
type HttpHandler struct {
http.Handler
t *testing.T
}
func (h *HttpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Add("X-Foo", "bar")
w.WriteHeader(404)
w.Write([]byte("test"))
}
func TestServe(t *testing.T) {
h := &HttpHandler{nil, t}
req := &Request{
"test.com",
"/path?foo=bar",
"POST",
map[string]string{"Content-Length": "1", "X-Foo": "bar"},
"",
"a",
}
res, err := Serve(h, req)
if err != nil {
t.Fail()
}
if res.StatusCode != 404 {
t.Fail()
}
fmt.Printf("status code: %d\n", res.StatusCode)
fmt.Printf("header: %v\n", res.Headers)
fmt.Printf("base64 body: %s\n", res.Body)
body, err := base64.StdEncoding.DecodeString(res.Body)
fmt.Printf("body: %s\n", body)
}

1
utils/run.js vendored
View File

@@ -6,7 +6,6 @@ const allPackages = [
'routing-utils',
'frameworks',
'build-utils',
'cgi',
'client',
'node-bridge',
'node',

View File

@@ -1901,11 +1901,6 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67"
integrity sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==
"@types/node@10.x":
version "10.17.25"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.25.tgz#64f64cd3e8641e8163c81045e545d2825d300e37"
integrity sha512-EWPw3jDB0jip4HafDkoezNOwG00TtVZ1TOe74MaxIBWgpyM60UF/LXzFVx9+8AdSYNNOPgx7TuJoRmgnhHZ/7g==
"@types/node@11.11.0":
version "11.11.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-11.11.0.tgz#070e9ce7c90e727aca0e0c14e470f9a93ffe9390"
@@ -2116,13 +2111,13 @@
resolved "https://registry.yarnpkg.com/@vercel/ncc/-/ncc-0.24.0.tgz#a2e8783a185caa99b5d8961a57dfc9665de16296"
integrity sha512-crqItMcIwCkvdXY/V3/TzrHJQx6nbIaRqE1cOopJhgGX6izvNov40SmD//nS5flfEvdK54YGjwVVq+zG6crjOg==
"@vercel/nft@0.12.2":
version "0.12.2"
resolved "https://registry.yarnpkg.com/@vercel/nft/-/nft-0.12.2.tgz#67ea9f231d24639b3783e3e69bef173659972d3b"
integrity sha512-H8n44GboVnJaVVX4+WfuOTAaNLDnUIYH4KpMZcXll7KMNIcg0JTd0IFRsIBe/uvuXisqm6nEANp8Tr3/1dlRQw==
"@vercel/nft@0.13.1":
version "0.13.1"
resolved "https://registry.yarnpkg.com/@vercel/nft/-/nft-0.13.1.tgz#98df07e04620069ba63fff92af490c5842a2f31f"
integrity sha512-7pBTfSkwhhcPAeGVsFml5YX7LCZgtocP+zTAknnRK2u/RsV3GGqOD5yw7CtbgTpfjY8NfXWzwoxF1zOUEVsbww==
dependencies:
"@mapbox/node-pre-gyp" "^1.0.5"
acorn "^8.1.0"
acorn "^8.3.0"
acorn-class-fields "^1.0.0"
acorn-static-class-features "^1.0.0"
bindings "^1.4.0"
@@ -2283,10 +2278,10 @@ acorn@^7.1.1:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.2.0.tgz#17ea7e40d7c8640ff54a694c889c26f31704effe"
integrity sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==
acorn@^8.1.0:
version "8.1.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.1.1.tgz#fb0026885b9ac9f48bac1e185e4af472971149ff"
integrity sha512-xYiIVjNuqtKXMxlRMDc6mZUhXehod4a3gbZ1qRlM7icK4EbxUFNLhWoPblCvFtB2Y9CIqHP3CF/rdxLItaQv8g==
acorn@^8.3.0:
version "8.3.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.3.0.tgz#1193f9b96c4e8232f00b11a9edff81b2c8b98b88"
integrity sha512-tqPKHZ5CaBJw0Xmy0ZZvLs1qTV+BNFSyvn77ASXkpBNfIRk8ev26fKrD9iLGwGA9zedPao52GSHzq8lyZG0NUw==
agent-base@4, agent-base@^4.1.0, agent-base@^4.3.0:
version "4.3.0"
@@ -2448,11 +2443,6 @@ append-transform@^1.0.0:
dependencies:
default-require-extensions "^2.0.0"
append-type@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/append-type/-/append-type-1.0.2.tgz#a492f350e81ddcb46b787fc605becf6dd8bccbf6"
integrity sha512-hac740vT/SAbrFBLgLIWZqVT5PUAcGTWS5UkDDhr+OCizZSw90WKw6sWAEgGaYd2viIblggypMXwpjzHXOvAQg==
aproba@^1.0.3, aproba@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
@@ -2553,11 +2543,6 @@ array-ify@^1.0.0:
resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece"
integrity sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=
array-to-sentence@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/array-to-sentence/-/array-to-sentence-1.1.0.tgz#c804956dafa53232495b205a9452753a258d39fc"
integrity sha1-yASVba+lMjJJWyBalFJ1OiWNOfw=
array-union@^1.0.1, array-union@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39"
@@ -2612,14 +2597,6 @@ assert-plus@1.0.0, assert-plus@^1.0.0:
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
assert-valid-glob-opts@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/assert-valid-glob-opts/-/assert-valid-glob-opts-1.0.0.tgz#ab9b5438ec5e929f5bb08201819affb1227f730a"
integrity sha512-/mttty5Xh7wE4o7ttKaUpBJl0l04xWe3y6muy1j27gyzSsnceK0AYU9owPtUoL9z8+9hnPxztmuhdFZ7jRoyWw==
dependencies:
glob-option-error "^1.0.0"
validate-glob-opts "^1.0.0"
assign-symbols@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
@@ -5362,11 +5339,6 @@ gitconfiglocal@^1.0.0:
dependencies:
ini "^1.3.2"
glob-option-error@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/glob-option-error/-/glob-option-error-1.0.0.tgz#57cc65def9c7d5c1461baf13129bb5403cff6176"
integrity sha1-V8xl3vnH1cFGG68TEpu1QDz/YXY=
glob-parent@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae"
@@ -5855,13 +5827,6 @@ indent-string@^4.0.0:
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
indexed-filter@^1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/indexed-filter/-/indexed-filter-1.0.3.tgz#7911439191cac588188464640a8db4f6b324973d"
integrity sha512-oBIzs6EARNMzrLgVg20fK52H19WcRHBiukiiEkw9rnnI//8rinEBMLrYdwEfJ9d4K7bjV1L6nSGft6H/qzHNgQ==
dependencies:
append-type "^1.0.1"
infer-owner@^1.0.3, infer-owner@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467"
@@ -5942,13 +5907,6 @@ inquirer@^6.2.0, inquirer@^6.4.1:
strip-ansi "^5.1.0"
through "^2.3.6"
inspect-with-kind@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/inspect-with-kind/-/inspect-with-kind-1.0.5.tgz#fce151d4ce89722c82ca8e9860bb96f9167c316c"
integrity sha512-MAQUJuIo7Xqk8EVNP+6d3CKq9c80hi4tjIbIAT6lmGW9W6WzlHiu9PS8uSuUYU+Do+j1baiFp3H25XEVxDIG2g==
dependencies:
kind-of "^6.0.2"
into-stream@5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-5.0.0.tgz#690569d7806b29d7cbd496cb05972fbe725b42a5"
@@ -8293,10 +8251,10 @@ onetime@^5.1.0:
dependencies:
mimic-fn "^2.1.0"
open@8.0.2:
version "8.0.2"
resolved "https://registry.yarnpkg.com/open/-/open-8.0.2.tgz#8c3e95cce93ba2fc8d99968ee8bfefecdb50b84f"
integrity sha512-NV5QmWJrTaNBLHABJyrb+nd5dXI5zfea/suWawBhkHzAbVhLLiJdrqMgxMypGK9Eznp2Ltoh7SAVkQ3XAucX7Q==
open@8.2.0:
version "8.2.0"
resolved "https://registry.yarnpkg.com/open/-/open-8.2.0.tgz#d6a4788b00009a9d60df471ecb89842a15fdcfc1"
integrity sha512-O8uInONB4asyY3qUcEytpgwxQG3O0fJ/hlssoUHsBboOIRVZzT6Wq+Rwj5nffbeUhOdMjpXeISpDDzHCMRDuOQ==
dependencies:
define-lazy-prop "^2.0.0"
is-docker "^2.1.1"
@@ -9496,17 +9454,6 @@ rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3:
dependencies:
glob "^7.1.3"
rmfr@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/rmfr/-/rmfr-2.0.0.tgz#8a42e81332550b3f0019b8fb8ab245bea81b6d1c"
integrity sha512-nQptLCZeyyJfgbpf2x97k5YE8vzDn7bhwx9NlvODdhgbU0mL1ruh71X0HYdRaOEvWC7Cr+SfV0p5p+Ib5yOl7A==
dependencies:
assert-valid-glob-opts "^1.0.0"
glob "^7.1.2"
graceful-fs "^4.1.11"
inspect-with-kind "^1.0.4"
rimraf "^2.6.2"
rollup-pluginutils@^2.8.2:
version "2.8.2"
resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e"
@@ -11011,16 +10958,6 @@ v8-compile-cache@^2.0.3:
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745"
integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==
validate-glob-opts@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/validate-glob-opts/-/validate-glob-opts-1.0.2.tgz#ef9f98977d965537ea4f51fa7d5799e9c6ebca91"
integrity sha512-3PKjRQq/R514lUcG9OEiW0u9f7D4fP09A07kmk1JbNn2tfeQdAHhlT+A4dqERXKu2br2rrxSM3FzagaEeq9w+A==
dependencies:
array-to-sentence "^1.1.0"
indexed-filter "^1.0.0"
inspect-with-kind "^1.0.4"
is-plain-obj "^1.1.0"
validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.3:
version "3.0.4"
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"