mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-12 04:22:14 +00:00
Compare commits
70 Commits
@now/build
...
@now/php@0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7bc3b3490 | ||
|
|
d83b09ffbd | ||
|
|
0c120f6202 | ||
|
|
48a01415f8 | ||
|
|
9198b72382 | ||
|
|
79048b83de | ||
|
|
f798e2cf19 | ||
|
|
be5057a738 | ||
|
|
059d44fde7 | ||
|
|
11ad481546 | ||
|
|
4061ed2eb7 | ||
|
|
b54c79e619 | ||
|
|
2203ae1a20 | ||
|
|
5e58c1738b | ||
|
|
290577ddfb | ||
|
|
a5e651403e | ||
|
|
df2769717b | ||
|
|
880ef77b7b | ||
|
|
af2616c283 | ||
|
|
4300f4d797 | ||
|
|
4921e541af | ||
|
|
6ce00eda8c | ||
|
|
060e952f6c | ||
|
|
1639127b24 | ||
|
|
ebbb62eaea | ||
|
|
66954e84fe | ||
|
|
15a21bb28c | ||
|
|
96ca1e1d8c | ||
|
|
587cb52191 | ||
|
|
95422ffd46 | ||
|
|
391a883799 | ||
|
|
43d6960df4 | ||
|
|
5c128003d8 | ||
|
|
2f8fd1b14b | ||
|
|
625553c146 | ||
|
|
3b0ce7bad3 | ||
|
|
489ec1dfa5 | ||
|
|
d3f92d7143 | ||
|
|
3072b044ef | ||
|
|
3b4968657f | ||
|
|
cafae4c800 | ||
|
|
64952d24f1 | ||
|
|
72758b6e0d | ||
|
|
4f80bc74d5 | ||
|
|
a6e62ed61c | ||
|
|
d8eecd6172 | ||
|
|
0e70608511 | ||
|
|
da0de150df | ||
|
|
a58c35fb9e | ||
|
|
fe88a69ab7 | ||
|
|
9767682006 | ||
|
|
3285b31721 | ||
|
|
70353c7fc0 | ||
|
|
f85cf99325 | ||
|
|
8b14a46d04 | ||
|
|
383cbfd82f | ||
|
|
81e268a3c9 | ||
|
|
ac8b33213b | ||
|
|
de12e7b8c8 | ||
|
|
b9346603f0 | ||
|
|
0b793dfc35 | ||
|
|
9dd672c383 | ||
|
|
1b743aeea8 | ||
|
|
d4af4b9f5c | ||
|
|
b734ca3e01 | ||
|
|
f81d753104 | ||
|
|
db31b9a207 | ||
|
|
b80b5182e6 | ||
|
|
268a7c2b81 | ||
|
|
667a16c996 |
@@ -12,3 +12,6 @@
|
|||||||
/packages/now-optipng/dist/*
|
/packages/now-optipng/dist/*
|
||||||
/packages/now-go/*
|
/packages/now-go/*
|
||||||
/packages/now-rust/dist/*
|
/packages/now-rust/dist/*
|
||||||
|
/packages/now-ruby/dist/*
|
||||||
|
/packages/now-static-build/dist/*
|
||||||
|
/packages/now-static-build/test/fixtures/**
|
||||||
|
|||||||
4
.github/CODEOWNERS
vendored
4
.github/CODEOWNERS
vendored
@@ -2,8 +2,10 @@
|
|||||||
# https://help.github.com/en/articles/about-code-owners
|
# https://help.github.com/en/articles/about-code-owners
|
||||||
|
|
||||||
* @styfle
|
* @styfle
|
||||||
/packages/now-node @styfle @tootallnate
|
/packages/now-node @styfle @tootallnate @lucleray
|
||||||
|
/packages/now-node-bridge @styfle @tootallnate @lucleray
|
||||||
/packages/now-next @timer @dav-is
|
/packages/now-next @timer @dav-is
|
||||||
/packages/now-go @styfle @sophearak
|
/packages/now-go @styfle @sophearak
|
||||||
/packages/now-python @styfle @sophearak
|
/packages/now-python @styfle @sophearak
|
||||||
/packages/now-rust @styfle @mike-engel @anmonteiro
|
/packages/now-rust @styfle @mike-engel @anmonteiro
|
||||||
|
/packages/now-ruby @styfle @coetry @nathancahill
|
||||||
|
|||||||
85
CONTRIBUTING.md
Normal file
85
CONTRIBUTING.md
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
When contributing to this repository, please first discuss the change you wish to make via issue or [spectrum](https://spectrum.chat/zeit) with the owners of this repository before submitting a Pull Request.
|
||||||
|
|
||||||
|
Please read our [code of conduct](CODE_OF_CONDUCT.md) and follow it in all your interactions with the project.
|
||||||
|
|
||||||
|
## Local development
|
||||||
|
|
||||||
|
This project is configured in a monorepo pattern where one repo contains multiple npm packages. Dependencies are installed and managed with `yarn`, not `npm` CLI.
|
||||||
|
|
||||||
|
To get started, execute the following:
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://github.com/zeit/now-builders
|
||||||
|
yarn install
|
||||||
|
yarn bootstrap
|
||||||
|
yarn build
|
||||||
|
yarn lint
|
||||||
|
yarn test
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure all the tests pass before making changes.
|
||||||
|
|
||||||
|
## Verifying your change
|
||||||
|
|
||||||
|
Once you are done with your changes (we even suggest doing it along the way ), make sure all the test still run by running
|
||||||
|
|
||||||
|
```
|
||||||
|
yarn build && yarn test
|
||||||
|
```
|
||||||
|
|
||||||
|
from the root of the project.
|
||||||
|
|
||||||
|
If any test fails, make sure to fix it along with your changes. See [Interpreting test errors](#Interpreting-test-errors) for more information about how the tests are executed, especially the integration tests.
|
||||||
|
|
||||||
|
## Pull Request Process
|
||||||
|
|
||||||
|
Once you are confident that your changes work properly, open a pull request on the main repository.
|
||||||
|
|
||||||
|
The pull request will be reviewed by the maintainers and the tests will be checked by our continuous integration platform.
|
||||||
|
|
||||||
|
## Interpreting test errors
|
||||||
|
|
||||||
|
There are 2 kinds of tests in this repository – Unit tests and Integration tests.
|
||||||
|
|
||||||
|
Unit tests are run locally with `jest` and execute quickly because they are testing the smallest units of code.
|
||||||
|
|
||||||
|
### Integration tests
|
||||||
|
|
||||||
|
Integration tests create deployments to your ZEIT account using the `test` project name. After each test is deployed, the `probes` key is used to check if the response is the expected value. If the value doesn't match, you'll see a message explaining the difference. If the deployment failed to build, you'll see a more generic message like the following:
|
||||||
|
|
||||||
|
```
|
||||||
|
[Error: Fetched page https://test-8ashcdlew.now.sh/root.js does not contain hello Root!. Instead it contains An error occurred with this application.
|
||||||
|
|
||||||
|
NO_STATUS_CODE_FRO Response headers:
|
||||||
|
cache-control=s-maxage=0
|
||||||
|
connection=close
|
||||||
|
content-type=text/plain; charset=utf-8
|
||||||
|
date=Wed, 19 Jun 2019 18:01:37 GMT
|
||||||
|
server=now
|
||||||
|
strict-transport-security=max-age=63072000
|
||||||
|
transfer-encoding=chunked
|
||||||
|
x-now-id=iad1:hgtzj-1560967297876-44ae12559f95
|
||||||
|
x-now-trace=iad1]
|
||||||
|
```
|
||||||
|
|
||||||
|
In such cases you can visit the URL of the failed deployment and append `/_logs` so see the build error. In the case above, that would be https://test-8ashcdlew.now.sh/_logs
|
||||||
|
|
||||||
|
The logs of this deployment will contain the actual error which may help you to understand what went wrong.
|
||||||
|
|
||||||
|
### @zeit/ncc integration
|
||||||
|
|
||||||
|
Some of the builders use `@zeit/ncc` to bundle files before deployment. If you suspect an error with the bundling mechanism, you can run the `ncc` CLI with a couple modifications to the test.
|
||||||
|
|
||||||
|
For example if an error occurred in `now-node/test/fixtures/08-assets`
|
||||||
|
|
||||||
|
```
|
||||||
|
cd packages/now-node/test/fixtures/08-assets
|
||||||
|
yarn install
|
||||||
|
echo 'require("http").createServer(module.exports).listen(3000)' >> index.js
|
||||||
|
npx @zeit/ncc@0.20.1 build index.js --source-map
|
||||||
|
node dist
|
||||||
|
```
|
||||||
|
|
||||||
|
This will compile the test with the specific version of `ncc` and run the resulting file. If it fails here, then there is likely a bug in `ncc` and not the Builder.
|
||||||
11
README.md
11
README.md
@@ -23,7 +23,11 @@ For the Canary Channel, publish the modified Builders to npm with the following:
|
|||||||
yarn publish-canary
|
yarn publish-canary
|
||||||
```
|
```
|
||||||
|
|
||||||
For the Stable Channel, you must cherry pick each commit from canary to master and then deploy the modified Builders:
|
For the Stable Channel, you must do the following:
|
||||||
|
|
||||||
|
- Cherry pick each commit from canary to master
|
||||||
|
- Verify that you are _in-sync_ with canary (with the exception of the `version` line in `package.json`)
|
||||||
|
- Deploy the modified Builders
|
||||||
|
|
||||||
```
|
```
|
||||||
git checkout master
|
git checkout master
|
||||||
@@ -33,6 +37,7 @@ git cherry-pick <PR502_COMMIT_SHA>
|
|||||||
git cherry-pick <PR503_COMMIT_SHA>
|
git cherry-pick <PR503_COMMIT_SHA>
|
||||||
git cherry-pick <PR504_COMMIT_SHA>
|
git cherry-pick <PR504_COMMIT_SHA>
|
||||||
# ... etc ...
|
# ... etc ...
|
||||||
|
git diff origin/canary
|
||||||
yarn publish-stable
|
yarn publish-stable
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -41,3 +46,7 @@ After running this publish step, GitHub Actions will take care of publishing the
|
|||||||
If for some reason GitHub Actions fails to publish the npm package, you may do so
|
If for some reason GitHub Actions fails to publish the npm package, you may do so
|
||||||
manually by running `npm publish` from the package directory. Make sure to
|
manually by running `npm publish` from the package directory. Make sure to
|
||||||
use `npm publish --tag canary` if you are publishing a canary release!
|
use `npm publish --tag canary` if you are publishing a canary release!
|
||||||
|
|
||||||
|
### Contributing
|
||||||
|
|
||||||
|
See the [Contribution guidelines for this project](CONTRIBUTING.md), it also contains guidance on interpreting tests failures.
|
||||||
|
|||||||
@@ -1,33 +1,29 @@
|
|||||||
const { execSync } = require('child_process');
|
const { execSync } = require('child_process');
|
||||||
const { relative } = require('path');
|
const { relative } = require('path');
|
||||||
|
|
||||||
const branch = execSync('git branch | grep "*" | cut -d " " -f2').toString();
|
const branch = execSync('git branch | grep "*" | cut -d " " -f2')
|
||||||
|
.toString()
|
||||||
|
.trim();
|
||||||
console.log(`Running tests on branch "${branch}"`);
|
console.log(`Running tests on branch "${branch}"`);
|
||||||
const base = branch === 'master' ? 'HEAD~1' : 'origin/canary';
|
const gitPath = branch === 'master' ? 'HEAD~1' : 'origin/canary...HEAD';
|
||||||
const diff = execSync(`git diff ${base} --name-only`).toString();
|
const diff = execSync(`git diff ${gitPath} --name-only`).toString();
|
||||||
|
|
||||||
const changed = diff
|
const changed = diff
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.filter(item => Boolean(item) && item.includes('packages/'))
|
.filter(item => Boolean(item) && item.includes('packages/'))
|
||||||
.map(item => relative('packages', item).split('/')[0]);
|
.map(item => relative('packages', item).split('/')[0]);
|
||||||
|
|
||||||
const matches = [];
|
const matches = Array.from(new Set(changed));
|
||||||
|
|
||||||
if (changed.length > 0) {
|
if (matches.length === 0) {
|
||||||
console.log('The following packages have changed:');
|
|
||||||
|
|
||||||
changed.map((item) => {
|
|
||||||
matches.push(item);
|
|
||||||
console.log(item);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
matches.push('now-node');
|
matches.push('now-node');
|
||||||
console.log(`No packages changed, defaulting to ${matches[0]}`);
|
console.log(`No packages changed, defaulting to ${matches[0]}`);
|
||||||
|
} else {
|
||||||
|
console.log('The following packages have changed:');
|
||||||
|
console.log(matches.join('\n'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const testMatch = Array.from(new Set(matches)).map(
|
const testMatch = matches.map(
|
||||||
item => `**/${item}/**/?(*.)+(spec|test).[jt]s?(x)`,
|
item => `**/${item}/**/?(*.)+(spec|test).[jt]s?(x)`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"lerna": "lerna",
|
"lerna": "lerna",
|
||||||
"bootstrap": "lerna bootstrap",
|
"bootstrap": "lerna bootstrap",
|
||||||
"publish-stable": "git checkout master && git pull && lerna version",
|
"publish-stable": "git checkout master && git pull && lerna version --exact",
|
||||||
"publish-canary": "git checkout canary && git pull && lerna version prerelease --preid canary",
|
"publish-canary": "git checkout canary && git pull && lerna version prerelease --preid canary --exact",
|
||||||
"publish-from-github": "./.circleci/publish.sh",
|
"publish-from-github": "./.circleci/publish.sh",
|
||||||
"build": "./.circleci/build.sh",
|
"build": "./.circleci/build.sh",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
|
|||||||
@@ -13,6 +13,15 @@ exports.config = {
|
|||||||
maxLambdaSize: '10mb',
|
maxLambdaSize: '10mb',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// From this list: https://import.pw/importpw/import/docs/config.md
|
||||||
|
const allowedConfigImports = new Set([
|
||||||
|
'CACHE',
|
||||||
|
'CURL_OPTS',
|
||||||
|
'DEBUG',
|
||||||
|
'RELOAD',
|
||||||
|
'SERVER',
|
||||||
|
]);
|
||||||
|
|
||||||
exports.analyze = ({ files, entrypoint }) => files[entrypoint].digest;
|
exports.analyze = ({ files, entrypoint }) => files[entrypoint].digest;
|
||||||
|
|
||||||
exports.build = async ({
|
exports.build = async ({
|
||||||
@@ -24,10 +33,23 @@ exports.build = async ({
|
|||||||
await download(files, srcDir);
|
await download(files, srcDir);
|
||||||
|
|
||||||
const configEnv = Object.keys(config).reduce((o, v) => {
|
const configEnv = Object.keys(config).reduce((o, v) => {
|
||||||
o[`IMPORT_${snakeCase(v).toUpperCase()}`] = config[v]; // eslint-disable-line no-param-reassign
|
const name = snakeCase(v).toUpperCase();
|
||||||
|
|
||||||
|
if (allowedConfigImports.has(name)) {
|
||||||
|
o[`IMPORT_${name}`] = config[v]; // eslint-disable-line no-param-reassign
|
||||||
|
}
|
||||||
|
|
||||||
return o;
|
return o;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
|
if (config && config.import) {
|
||||||
|
Object.keys(config.import).forEach((key) => {
|
||||||
|
const name = snakeCase(key).toUpperCase();
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
configEnv[`IMPORT_${name}`] = config.import[key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const IMPORT_CACHE = `${workPath}/.import-cache`;
|
const IMPORT_CACHE = `${workPath}/.import-cache`;
|
||||||
const env = Object.assign({}, process.env, configEnv, {
|
const env = Object.assign({}, process.env, configEnv, {
|
||||||
PATH: `${IMPORT_CACHE}/bin:${process.env.PATH}`,
|
PATH: `${IMPORT_CACHE}/bin:${process.env.PATH}`,
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/bash",
|
"name": "@now/bash",
|
||||||
"version": "0.2.3",
|
"version": "1.0.0",
|
||||||
"description": "Now 2.0 builder for HTTP endpoints written in Bash",
|
"description": "Now 2.0 builder for HTTP endpoints written in Bash",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"author": "Nathan Rajlich <nate@zeit.co>",
|
"author": "Nathan Rajlich <nate@zeit.co>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/bash-now-bash",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/zeit/now-builders.git",
|
"url": "https://github.com/zeit/now-builders.git",
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/build-utils",
|
"name": "@now/build-utils",
|
||||||
"version": "0.6.0",
|
"version": "0.8.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.js",
|
"types": "./dist/index.d.js",
|
||||||
|
"homepage": "https://zeit.co/docs/v2/deployments/builders/developer-guide",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/zeit/now-builders.git",
|
"url": "https://github.com/zeit/now-builders.git",
|
||||||
@@ -22,6 +23,7 @@
|
|||||||
"fs-extra": "7.0.0",
|
"fs-extra": "7.0.0",
|
||||||
"glob": "7.1.3",
|
"glob": "7.1.3",
|
||||||
"into-stream": "5.0.0",
|
"into-stream": "5.0.0",
|
||||||
|
"minimatch": "3.0.4",
|
||||||
"multistream": "2.1.1",
|
"multistream": "2.1.1",
|
||||||
"node-fetch": "2.2.0",
|
"node-fetch": "2.2.0",
|
||||||
"semver": "6.1.1",
|
"semver": "6.1.1",
|
||||||
@@ -37,6 +39,6 @@
|
|||||||
"@types/semver": "6.0.0",
|
"@types/semver": "6.0.0",
|
||||||
"@types/yazl": "^2.4.1",
|
"@types/yazl": "^2.4.1",
|
||||||
"execa": "^1.0.0",
|
"execa": "^1.0.0",
|
||||||
"typescript": "3.3.4000"
|
"typescript": "3.5.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
47
packages/now-build-utils/src/detect-builder.ts
Normal file
47
packages/now-build-utils/src/detect-builder.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { PackageJson, Builder, Config } from './types';
|
||||||
|
import minimatch from 'minimatch';
|
||||||
|
|
||||||
|
const src: string = 'package.json';
|
||||||
|
const config: Config = { zeroConfig: true };
|
||||||
|
|
||||||
|
// Static builders are special cased in `@now/static-build`
|
||||||
|
const BUILDERS = new Map<string, Builder>([
|
||||||
|
['next', { src, use: '@now/next', config }],
|
||||||
|
]);
|
||||||
|
|
||||||
|
const API_BUILDERS: Builder[] = [
|
||||||
|
{ src: 'api/**/*.js', use: '@now/node', config },
|
||||||
|
{ src: 'api/**/*.ts', use: '@now/node', config },
|
||||||
|
{ src: 'api/**/*.rs', use: '@now/rust', config },
|
||||||
|
{ src: 'api/**/*.go', use: '@now/go', config },
|
||||||
|
{ src: 'api/**/*.php', use: '@now/php', config },
|
||||||
|
{ src: 'api/**/*.py', use: '@now/python', config },
|
||||||
|
{ src: 'api/**/*.rb', use: '@now/ruby', config },
|
||||||
|
{ src: 'api/**/*.sh', use: '@now/bash', config },
|
||||||
|
];
|
||||||
|
|
||||||
|
export async function detectBuilder(pkg: PackageJson): Promise<Builder> {
|
||||||
|
for (const [dependency, builder] of BUILDERS) {
|
||||||
|
const deps = Object.assign({}, pkg.dependencies, pkg.devDependencies);
|
||||||
|
|
||||||
|
// Return the builder when a dependency matches
|
||||||
|
if (deps[dependency]) {
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// By default we'll choose the `static-build` builder
|
||||||
|
return { src, use: '@now/static-build', config };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function detectApiBuilders(
|
||||||
|
files: string[]
|
||||||
|
): Promise<Builder[] | null> {
|
||||||
|
const builds = files.map(file => {
|
||||||
|
return API_BUILDERS.find(({ src }): boolean => minimatch(file, src));
|
||||||
|
});
|
||||||
|
|
||||||
|
// We can use `new Set` here since `builds` contains references to `API_BUILDERS`
|
||||||
|
const finishedBuilds = Array.from(new Set(builds.filter(Boolean)));
|
||||||
|
return finishedBuilds.length > 0 ? (finishedBuilds as Builder[]) : null;
|
||||||
|
}
|
||||||
239
packages/now-build-utils/src/detect-routes.ts
Normal file
239
packages/now-build-utils/src/detect-routes.ts
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import { Route } from './types';
|
||||||
|
|
||||||
|
function concatArrayOfText(texts: string[]): string {
|
||||||
|
const last = texts.pop();
|
||||||
|
return `${texts.join(', ')}, and ${last}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Takes a filename or foldername, strips the extension
|
||||||
|
// gets the part between the "[]" brackets.
|
||||||
|
// It will return `null` if there are no brackets
|
||||||
|
// and therefore no segment.
|
||||||
|
function getSegmentName(segment: string): string | null {
|
||||||
|
const { name } = path.parse(segment);
|
||||||
|
|
||||||
|
if (name.startsWith('[') && name.endsWith(']')) {
|
||||||
|
return name.slice(1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRouteFromPath(filePath: string): Route {
|
||||||
|
const parts = filePath.split('/');
|
||||||
|
|
||||||
|
let append: string = '';
|
||||||
|
let counter: number = 1;
|
||||||
|
const query: string[] = [];
|
||||||
|
|
||||||
|
const srcParts = parts.map(
|
||||||
|
(segment, index): string => {
|
||||||
|
const name = getSegmentName(segment);
|
||||||
|
const isLast = index === parts.length - 1;
|
||||||
|
|
||||||
|
if (name !== null) {
|
||||||
|
query.push(`${name}=$${counter++}`);
|
||||||
|
|
||||||
|
if (isLast) {
|
||||||
|
// We append this to the last one
|
||||||
|
// to make sure GET params still work
|
||||||
|
// and are just appended to our GET params
|
||||||
|
append += `$${counter++}`;
|
||||||
|
return `([^\\/|\\?]+)\\/?(?:\\?(.*))?`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `([^\\/]+)`;
|
||||||
|
} else if (isLast) {
|
||||||
|
// If it is the last part we want to remove the extension
|
||||||
|
// and capture everything after that as regex group and append it
|
||||||
|
const { name: fileName } = path.parse(segment);
|
||||||
|
append += `$${counter++}`;
|
||||||
|
return `${fileName}(.*)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return segment;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const src = `^/${srcParts.join('/')}$`;
|
||||||
|
const dest = `/${filePath}${query.length ? '?' : ''}${query.join('&')}${
|
||||||
|
query.length ? '&' : ''
|
||||||
|
}${append}`;
|
||||||
|
|
||||||
|
return { src, dest };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the path partially matches and has the same
|
||||||
|
// name for the path segment at the same position
|
||||||
|
function partiallyMatches(pathA: string, pathB: string): boolean {
|
||||||
|
const partsA = pathA.split('/');
|
||||||
|
const partsB = pathB.split('/');
|
||||||
|
|
||||||
|
const long = partsA.length > partsB.length ? partsA : partsB;
|
||||||
|
const short = long === partsA ? partsB : partsA;
|
||||||
|
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
for (const segmentShort of short) {
|
||||||
|
const segmentLong = long[index];
|
||||||
|
|
||||||
|
const nameLong = getSegmentName(segmentLong);
|
||||||
|
const nameShort = getSegmentName(segmentShort);
|
||||||
|
|
||||||
|
// If there are no segments or the paths differ we
|
||||||
|
// return as they are not matching
|
||||||
|
if (segmentShort !== segmentLong && (!nameLong || !nameShort)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nameLong !== nameShort) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Counts how often a path occurres when all placeholders
|
||||||
|
// got resolved, so we can check if they have conflicts
|
||||||
|
function pathOccurrences(filePath: string, files: string[]): string[] {
|
||||||
|
const getAbsolutePath = (unresolvedPath: string): string => {
|
||||||
|
const { dir, name } = path.parse(unresolvedPath);
|
||||||
|
const parts = path.join(dir, name).split('/');
|
||||||
|
return parts.map(part => part.replace(/\[.*\]/, '1')).join('/');
|
||||||
|
};
|
||||||
|
|
||||||
|
const currentAbsolutePath = getAbsolutePath(filePath);
|
||||||
|
|
||||||
|
return files.reduce((prev: string[], file: string): string[] => {
|
||||||
|
const absolutePath = getAbsolutePath(file);
|
||||||
|
|
||||||
|
if (absolutePath === currentAbsolutePath) {
|
||||||
|
prev.push(file);
|
||||||
|
} else if (partiallyMatches(filePath, file)) {
|
||||||
|
prev.push(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
return prev;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if a placeholder with the same name is used
|
||||||
|
// multiple times inside the same path
|
||||||
|
function getConflictingSegment(filePath: string): string | null {
|
||||||
|
const segments = new Set<string>();
|
||||||
|
|
||||||
|
for (const segment of filePath.split('/')) {
|
||||||
|
const name = getSegmentName(segment);
|
||||||
|
|
||||||
|
if (name !== null && segments.has(name)) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name) {
|
||||||
|
segments.add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortFilesBySegmentCount(fileA: string, fileB: string): number {
|
||||||
|
const lengthA = fileA.split('/').length;
|
||||||
|
const lengthB = fileB.split('/').length;
|
||||||
|
|
||||||
|
if (lengthA > lengthB) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lengthA < lengthB) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paths that have the same segment length but
|
||||||
|
// less placeholders are preferred
|
||||||
|
const countSegments = (prev: number, segment: string) =>
|
||||||
|
getSegmentName(segment) ? prev + 1 : 0;
|
||||||
|
const segmentLengthA = fileA.split('/').reduce(countSegments, 0);
|
||||||
|
const segmentLengthB = fileB.split('/').reduce(countSegments, 0);
|
||||||
|
|
||||||
|
if (segmentLengthA > segmentLengthB) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (segmentLengthA < segmentLengthB) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function detectApiRoutes(
|
||||||
|
files: string[]
|
||||||
|
): Promise<{
|
||||||
|
defaultRoutes: Route[] | null;
|
||||||
|
error: { [key: string]: string } | null;
|
||||||
|
}> {
|
||||||
|
if (!files || files.length === 0) {
|
||||||
|
return { defaultRoutes: null, error: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
// The deepest routes need to be
|
||||||
|
// the first ones to get handled
|
||||||
|
const sortedFiles = files.sort(sortFilesBySegmentCount);
|
||||||
|
|
||||||
|
const defaultRoutes: Route[] = [];
|
||||||
|
|
||||||
|
for (const file of sortedFiles) {
|
||||||
|
// We only consider every file in the api directory
|
||||||
|
// as we will strip extensions as well as resolving "[segments]"
|
||||||
|
if (!file.startsWith('api/')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const conflictingSegment = getConflictingSegment(file);
|
||||||
|
|
||||||
|
console.log({ file, conflictingSegment });
|
||||||
|
|
||||||
|
if (conflictingSegment) {
|
||||||
|
return {
|
||||||
|
defaultRoutes: null,
|
||||||
|
error: {
|
||||||
|
code: 'conflicting_path_segment',
|
||||||
|
message:
|
||||||
|
`The segment "${conflictingSegment}" occurres more than ` +
|
||||||
|
`one time in your path "${file}". Please make sure that ` +
|
||||||
|
`every segment in a path is unique`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const occurrences = pathOccurrences(file, sortedFiles).filter(
|
||||||
|
name => name !== file
|
||||||
|
);
|
||||||
|
|
||||||
|
if (occurrences.length > 0) {
|
||||||
|
const messagePaths = concatArrayOfText(
|
||||||
|
occurrences.map(name => `"${name}"`)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
defaultRoutes: null,
|
||||||
|
error: {
|
||||||
|
code: 'conflicting_file_path',
|
||||||
|
message:
|
||||||
|
`Two or more files have conflicting paths or names. ` +
|
||||||
|
`Please make sure path segments and filenames, without their extension, are unique. ` +
|
||||||
|
`The path "${file}" has conflicts with ${messagePaths}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultRoutes.push(createRouteFromPath(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
return { defaultRoutes, error: null };
|
||||||
|
}
|
||||||
@@ -39,10 +39,10 @@ export default async function download(
|
|||||||
basePath: string,
|
basePath: string,
|
||||||
meta?: Meta
|
meta?: Meta
|
||||||
): Promise<DownloadedFiles> {
|
): Promise<DownloadedFiles> {
|
||||||
const { isDev = false, filesChanged = null, filesRemoved = null } =
|
const { isDev = false, skipDownload = false, filesChanged = null, filesRemoved = null } =
|
||||||
meta || {};
|
meta || {};
|
||||||
|
|
||||||
if (isDev) {
|
if (isDev || skipDownload) {
|
||||||
// In `now dev`, the `download()` function is a no-op because
|
// In `now dev`, the `download()` function is a no-op because
|
||||||
// the `basePath` matches the `cwd` of the dev server, so the
|
// the `basePath` matches the `cwd` of the dev server, so the
|
||||||
// source files are already available.
|
// source files are already available.
|
||||||
|
|||||||
53
packages/now-build-utils/src/fs/node-version.ts
Normal file
53
packages/now-build-utils/src/fs/node-version.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { intersects } from 'semver';
|
||||||
|
import { NodeVersion } from '../types';
|
||||||
|
|
||||||
|
const supportedOptions: NodeVersion[] = [
|
||||||
|
{ major: 10, range: '10.x', runtime: 'nodejs10.x' },
|
||||||
|
{ major: 8, range: '8.10.x', runtime: 'nodejs8.10' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// This version should match Fargate's default in the PATH
|
||||||
|
// Today that is Node 8
|
||||||
|
export const defaultSelection = supportedOptions.find(
|
||||||
|
o => o.major === 8
|
||||||
|
) as NodeVersion;
|
||||||
|
|
||||||
|
export async function getSupportedNodeVersion(
|
||||||
|
engineRange?: string,
|
||||||
|
silent?: boolean
|
||||||
|
): Promise<NodeVersion> {
|
||||||
|
let selection = defaultSelection;
|
||||||
|
|
||||||
|
if (!engineRange) {
|
||||||
|
if (!silent) {
|
||||||
|
console.log(
|
||||||
|
'missing `engines` in `package.json`, using default range: ' +
|
||||||
|
selection.range
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const found = supportedOptions.some(o => {
|
||||||
|
// the array is already in order so return the first
|
||||||
|
// match which will be the newest version of node
|
||||||
|
selection = o;
|
||||||
|
return intersects(o.range, engineRange);
|
||||||
|
});
|
||||||
|
if (found) {
|
||||||
|
if (!silent) {
|
||||||
|
console.log(
|
||||||
|
'Found `engines` in `package.json`, selecting range: ' +
|
||||||
|
selection.range
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!silent) {
|
||||||
|
throw new Error(
|
||||||
|
'found `engines` in `package.json` with an unsupported node range: ' +
|
||||||
|
engineRange +
|
||||||
|
'\nplease use `10.x` or `8.10.x` instead'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return selection;
|
||||||
|
}
|
||||||
@@ -4,7 +4,8 @@ import path from 'path';
|
|||||||
import spawn from 'cross-spawn';
|
import spawn from 'cross-spawn';
|
||||||
import { SpawnOptions } from 'child_process';
|
import { SpawnOptions } from 'child_process';
|
||||||
import { deprecate } from 'util';
|
import { deprecate } from 'util';
|
||||||
import { intersects } from 'semver';
|
import { Meta, PackageJson, NodeVersion } from '../types';
|
||||||
|
import { getSupportedNodeVersion } from './node-version';
|
||||||
|
|
||||||
function spawnAsync(
|
function spawnAsync(
|
||||||
command: string,
|
command: string,
|
||||||
@@ -45,42 +46,42 @@ async function chmodPlusX(fsPath: string) {
|
|||||||
await fs.chmod(fsPath, base8);
|
await fs.chmod(fsPath, base8);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runShellScript(fsPath: string) {
|
export async function runShellScript(
|
||||||
|
fsPath: string,
|
||||||
|
args: string[] = [],
|
||||||
|
spawnOpts?: SpawnOptions
|
||||||
|
) {
|
||||||
assert(path.isAbsolute(fsPath));
|
assert(path.isAbsolute(fsPath));
|
||||||
const destPath = path.dirname(fsPath);
|
const destPath = path.dirname(fsPath);
|
||||||
await chmodPlusX(fsPath);
|
await chmodPlusX(fsPath);
|
||||||
await spawnAsync(`./${path.basename(fsPath)}`, [], destPath);
|
await spawnAsync(`./${path.basename(fsPath)}`, args, destPath, spawnOpts);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PackageJson {
|
export function getSpawnOptions(
|
||||||
name: string;
|
meta: Meta,
|
||||||
version: string;
|
nodeVersion: NodeVersion
|
||||||
engines?: {
|
): SpawnOptions {
|
||||||
[key: string]: string;
|
const opts = {
|
||||||
node: string;
|
env: { ...process.env },
|
||||||
npm: string;
|
|
||||||
};
|
|
||||||
scripts?: {
|
|
||||||
[key: string]: string;
|
|
||||||
};
|
|
||||||
dependencies?: {
|
|
||||||
[key: string]: string;
|
|
||||||
};
|
|
||||||
devDependencies?: {
|
|
||||||
[key: string]: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!meta.isDev) {
|
||||||
|
opts.env.PATH = `/node${nodeVersion.major}/bin:${opts.env.PATH}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return opts;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function enginesMatch(
|
export async function getNodeVersion(
|
||||||
destPath: string,
|
destPath: string,
|
||||||
nodeVersion: string
|
minNodeVersion?: string
|
||||||
): Promise<boolean> {
|
): Promise<NodeVersion> {
|
||||||
const { packageJson } = await scanParentDirs(destPath, true);
|
const { packageJson } = await scanParentDirs(destPath, true);
|
||||||
|
const range =
|
||||||
const engineVersion =
|
(packageJson && packageJson.engines && packageJson.engines.node) ||
|
||||||
packageJson && packageJson.engines && packageJson.engines.node;
|
minNodeVersion;
|
||||||
return intersects(nodeVersion, engineVersion || '0.0.0');
|
return getSupportedNodeVersion(range, typeof minNodeVersion !== 'undefined');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function scanParentDirs(destPath: string, readPackageJson = false) {
|
async function scanParentDirs(destPath: string, readPackageJson = false) {
|
||||||
@@ -114,18 +115,18 @@ async function scanParentDirs(destPath: string, readPackageJson = false) {
|
|||||||
return { hasPackageLockJson, packageJson };
|
return { hasPackageLockJson, packageJson };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runNpmInstall(destPath: string, args: string[] = []) {
|
export async function runNpmInstall(
|
||||||
|
destPath: string,
|
||||||
|
args: string[] = [],
|
||||||
|
spawnOpts?: SpawnOptions
|
||||||
|
) {
|
||||||
assert(path.isAbsolute(destPath));
|
assert(path.isAbsolute(destPath));
|
||||||
|
|
||||||
let commandArgs = args;
|
let commandArgs = args;
|
||||||
console.log(`installing to ${destPath}`);
|
console.log(`installing to ${destPath}`);
|
||||||
const { hasPackageLockJson } = await scanParentDirs(destPath);
|
const { hasPackageLockJson } = await scanParentDirs(destPath);
|
||||||
|
|
||||||
const opts: SpawnOptions = {
|
const opts = spawnOpts || { env: process.env };
|
||||||
env: {
|
|
||||||
...process.env,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (hasPackageLockJson) {
|
if (hasPackageLockJson) {
|
||||||
commandArgs = args.filter(a => a !== '--prefer-offline');
|
commandArgs = args.filter(a => a !== '--prefer-offline');
|
||||||
@@ -138,7 +139,13 @@ export async function runNpmInstall(destPath: string, args: string[] = []) {
|
|||||||
} else {
|
} else {
|
||||||
await spawnAsync(
|
await spawnAsync(
|
||||||
'yarn',
|
'yarn',
|
||||||
commandArgs.concat(['--ignore-engines', '--cwd', destPath]),
|
commandArgs.concat([
|
||||||
|
'--ignore-engines',
|
||||||
|
'--mutex',
|
||||||
|
'network',
|
||||||
|
'--cwd',
|
||||||
|
destPath,
|
||||||
|
]),
|
||||||
destPath,
|
destPath,
|
||||||
opts
|
opts
|
||||||
);
|
);
|
||||||
@@ -180,7 +187,7 @@ export async function runPackageJsonScript(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* installDependencies() is deprecated.
|
* @deprecate installDependencies() is deprecated.
|
||||||
* Please use runNpmInstall() instead.
|
* Please use runNpmInstall() instead.
|
||||||
*/
|
*/
|
||||||
export const installDependencies = deprecate(
|
export const installDependencies = deprecate(
|
||||||
|
|||||||
@@ -1,15 +1,6 @@
|
|||||||
import FileBlob from './file-blob';
|
import FileBlob from './file-blob';
|
||||||
import FileFsRef from './file-fs-ref';
|
import FileFsRef from './file-fs-ref';
|
||||||
import FileRef from './file-ref';
|
import FileRef from './file-ref';
|
||||||
import {
|
|
||||||
File,
|
|
||||||
Files,
|
|
||||||
AnalyzeOptions,
|
|
||||||
BuildOptions,
|
|
||||||
PrepareCacheOptions,
|
|
||||||
ShouldServeOptions,
|
|
||||||
Meta,
|
|
||||||
} from './types';
|
|
||||||
import { Lambda, createLambda } from './lambda';
|
import { Lambda, createLambda } from './lambda';
|
||||||
import download, { DownloadedFiles } from './fs/download';
|
import download, { DownloadedFiles } from './fs/download';
|
||||||
import getWriteableDirectory from './fs/get-writable-directory';
|
import getWriteableDirectory from './fs/get-writable-directory';
|
||||||
@@ -20,18 +11,18 @@ import {
|
|||||||
runPackageJsonScript,
|
runPackageJsonScript,
|
||||||
runNpmInstall,
|
runNpmInstall,
|
||||||
runShellScript,
|
runShellScript,
|
||||||
enginesMatch,
|
getNodeVersion,
|
||||||
|
getSpawnOptions,
|
||||||
} from './fs/run-user-scripts';
|
} from './fs/run-user-scripts';
|
||||||
import streamToBuffer from './fs/stream-to-buffer';
|
import streamToBuffer from './fs/stream-to-buffer';
|
||||||
import shouldServe from './should-serve';
|
import shouldServe from './should-serve';
|
||||||
|
import { detectBuilder, detectApiBuilders } from './detect-builder';
|
||||||
|
import { detectApiRoutes } from './detect-routes';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
FileBlob,
|
FileBlob,
|
||||||
FileFsRef,
|
FileFsRef,
|
||||||
FileRef,
|
FileRef,
|
||||||
Files,
|
|
||||||
File,
|
|
||||||
Meta,
|
|
||||||
Lambda,
|
Lambda,
|
||||||
createLambda,
|
createLambda,
|
||||||
download,
|
download,
|
||||||
@@ -43,11 +34,13 @@ export {
|
|||||||
runPackageJsonScript,
|
runPackageJsonScript,
|
||||||
runNpmInstall,
|
runNpmInstall,
|
||||||
runShellScript,
|
runShellScript,
|
||||||
enginesMatch,
|
getNodeVersion,
|
||||||
|
getSpawnOptions,
|
||||||
streamToBuffer,
|
streamToBuffer,
|
||||||
AnalyzeOptions,
|
|
||||||
BuildOptions,
|
|
||||||
PrepareCacheOptions,
|
|
||||||
ShouldServeOptions,
|
|
||||||
shouldServe,
|
shouldServe,
|
||||||
|
detectBuilder,
|
||||||
|
detectApiBuilders,
|
||||||
|
detectApiRoutes,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export * from './types';
|
||||||
|
|||||||
@@ -15,8 +15,24 @@ export interface Files {
|
|||||||
[filePath: string]: File;
|
[filePath: string]: File;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Route {
|
||||||
|
src?: string;
|
||||||
|
dest?: string;
|
||||||
|
handle?: string;
|
||||||
|
type?: string;
|
||||||
|
headers?: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface Config {
|
export interface Config {
|
||||||
[key: string]: string | string[] | boolean | number | undefined;
|
[key: string]:
|
||||||
|
| string
|
||||||
|
| string[]
|
||||||
|
| boolean
|
||||||
|
| number
|
||||||
|
| { [key: string]: string }
|
||||||
|
| undefined;
|
||||||
maxLambdaSize?: string;
|
maxLambdaSize?: string;
|
||||||
includeFiles?: string | string[];
|
includeFiles?: string | string[];
|
||||||
bundle?: boolean;
|
bundle?: boolean;
|
||||||
@@ -24,10 +40,13 @@ export interface Config {
|
|||||||
helpers?: boolean;
|
helpers?: boolean;
|
||||||
rust?: string;
|
rust?: string;
|
||||||
debug?: boolean;
|
debug?: boolean;
|
||||||
|
zeroConfig?: boolean;
|
||||||
|
import?: { [key: string]: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Meta {
|
export interface Meta {
|
||||||
isDev?: boolean;
|
isDev?: boolean;
|
||||||
|
skipDownload?: boolean;
|
||||||
requestPath?: string;
|
requestPath?: string;
|
||||||
filesChanged?: string[];
|
filesChanged?: string[];
|
||||||
filesRemoved?: string[];
|
filesRemoved?: string[];
|
||||||
@@ -162,3 +181,34 @@ export interface ShouldServeOptions {
|
|||||||
*/
|
*/
|
||||||
config: Config;
|
config: Config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PackageJson {
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
engines?: {
|
||||||
|
[key: string]: string;
|
||||||
|
node: string;
|
||||||
|
npm: string;
|
||||||
|
};
|
||||||
|
scripts?: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
dependencies?: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
devDependencies?: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NodeVersion {
|
||||||
|
major: number;
|
||||||
|
range: string;
|
||||||
|
runtime: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Builder {
|
||||||
|
use: string;
|
||||||
|
src: string;
|
||||||
|
config?: Config;
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
{
|
{
|
||||||
"src": "index.js",
|
"src": "index.js",
|
||||||
"use": "@now/node",
|
"use": "@now/node",
|
||||||
"config": { "maxLambdaSize": "15mb" }
|
"config": { "maxLambdaSize": "18mb" }
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"probes": [{ "path": "/", "mustContain": "found:RANDOMNESS_PLACEHOLDER" }]
|
"probes": [{ "path": "/", "mustContain": "found:RANDOMNESS_PLACEHOLDER" }]
|
||||||
|
|||||||
@@ -6,12 +6,22 @@ const execa = require('execa');
|
|||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const { glob, download } = require('../');
|
const { glob, download } = require('../');
|
||||||
const { createZip } = require('../dist/lambda');
|
const { createZip } = require('../dist/lambda');
|
||||||
|
const {
|
||||||
|
getSupportedNodeVersion,
|
||||||
|
defaultSelection,
|
||||||
|
} = require('../dist/fs/node-version');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
packAndDeploy,
|
packAndDeploy,
|
||||||
testDeployment,
|
testDeployment,
|
||||||
} = require('../../../test/lib/deployment/test-deployment.js');
|
} = require('../../../test/lib/deployment/test-deployment.js');
|
||||||
|
|
||||||
|
const {
|
||||||
|
detectBuilder,
|
||||||
|
detectApiBuilders,
|
||||||
|
detectApiRoutes,
|
||||||
|
} = require('../dist');
|
||||||
|
|
||||||
jest.setTimeout(4 * 60 * 1000);
|
jest.setTimeout(4 * 60 * 1000);
|
||||||
const builderUrl = '@canary';
|
const builderUrl = '@canary';
|
||||||
let buildUtilsUrl;
|
let buildUtilsUrl;
|
||||||
@@ -64,6 +74,52 @@ it('should create zip files with symlinks properly', async () => {
|
|||||||
assert(aStat.isFile());
|
assert(aStat.isFile());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should only match supported node versions', () => {
|
||||||
|
expect(getSupportedNodeVersion('10.x')).resolves.toHaveProperty('major', 10);
|
||||||
|
expect(getSupportedNodeVersion('8.10.x')).resolves.toHaveProperty('major', 8);
|
||||||
|
expect(getSupportedNodeVersion('8.11.x')).rejects.toThrow();
|
||||||
|
expect(getSupportedNodeVersion('6.x')).rejects.toThrow();
|
||||||
|
expect(getSupportedNodeVersion('999.x')).rejects.toThrow();
|
||||||
|
expect(getSupportedNodeVersion('foo')).rejects.toThrow();
|
||||||
|
expect(getSupportedNodeVersion('')).resolves.toBe(defaultSelection);
|
||||||
|
expect(getSupportedNodeVersion(null)).resolves.toBe(defaultSelection);
|
||||||
|
expect(getSupportedNodeVersion(undefined)).resolves.toBe(defaultSelection);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should match all semver ranges', () => {
|
||||||
|
// See https://docs.npmjs.com/files/package.json#engines
|
||||||
|
expect(getSupportedNodeVersion('10.0.0')).resolves.toHaveProperty(
|
||||||
|
'major',
|
||||||
|
10,
|
||||||
|
);
|
||||||
|
expect(getSupportedNodeVersion('10.x')).resolves.toHaveProperty('major', 10);
|
||||||
|
expect(getSupportedNodeVersion('>=10')).resolves.toHaveProperty('major', 10);
|
||||||
|
expect(getSupportedNodeVersion('>=10.3.0')).resolves.toHaveProperty(
|
||||||
|
'major',
|
||||||
|
10,
|
||||||
|
);
|
||||||
|
expect(getSupportedNodeVersion('8.5.0 - 10.5.0')).resolves.toHaveProperty(
|
||||||
|
'major',
|
||||||
|
10,
|
||||||
|
);
|
||||||
|
expect(getSupportedNodeVersion('>=9.0.0')).resolves.toHaveProperty(
|
||||||
|
'major',
|
||||||
|
10,
|
||||||
|
);
|
||||||
|
expect(getSupportedNodeVersion('>=9.5.0 <=10.5.0')).resolves.toHaveProperty(
|
||||||
|
'major',
|
||||||
|
10,
|
||||||
|
);
|
||||||
|
expect(getSupportedNodeVersion('~10.5.0')).resolves.toHaveProperty(
|
||||||
|
'major',
|
||||||
|
10,
|
||||||
|
);
|
||||||
|
expect(getSupportedNodeVersion('^10.5.0')).resolves.toHaveProperty(
|
||||||
|
'major',
|
||||||
|
10,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// own fixtures
|
// own fixtures
|
||||||
|
|
||||||
const fixturesPath = path.resolve(__dirname, 'fixtures');
|
const fixturesPath = path.resolve(__dirname, 'fixtures');
|
||||||
@@ -108,3 +164,84 @@ for (const builder of buildersToTestWith) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it('Test `detectBuilder`', async () => {
|
||||||
|
{
|
||||||
|
const pkg = { dependencies: { next: '1.0.0' } };
|
||||||
|
const builder = await detectBuilder(pkg);
|
||||||
|
expect(builder.use).toBe('@now/next');
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const pkg = { devDependencies: { next: '1.0.0' } };
|
||||||
|
const builder = await detectBuilder(pkg);
|
||||||
|
expect(builder.use).toBe('@now/next');
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const pkg = {};
|
||||||
|
const builder = await detectBuilder(pkg);
|
||||||
|
expect(builder.use).toBe('@now/static-build');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Test `detectApiBuilders`', async () => {
|
||||||
|
{
|
||||||
|
const files = ['package.json', 'api/user.js', 'api/team.js'];
|
||||||
|
|
||||||
|
const builders = await detectApiBuilders(files);
|
||||||
|
expect(builders[0].use).toBe('@now/node');
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const files = ['package.json', 'api/user.go', 'api/team.js'];
|
||||||
|
|
||||||
|
const builders = await detectApiBuilders(files);
|
||||||
|
expect(builders.some(({ use }) => use === '@now/go')).toBeTruthy();
|
||||||
|
expect(builders.some(({ use }) => use === '@now/node')).toBeTruthy();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const files = ['package.json'];
|
||||||
|
|
||||||
|
const builders = await detectApiBuilders(files);
|
||||||
|
expect(builders).toBe(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Test `detectApiRoutes`', async () => {
|
||||||
|
{
|
||||||
|
const files = ['api/user.go', 'api/team.js'];
|
||||||
|
|
||||||
|
const { defaultRoutes } = await detectApiRoutes(files);
|
||||||
|
expect(defaultRoutes.length).toBe(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const files = ['api/user.go', 'api/user.js'];
|
||||||
|
|
||||||
|
const { error } = await detectApiRoutes(files);
|
||||||
|
expect(error.code).toBe('conflicting_file_path');
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const files = ['api/[user].go', 'api/[team]/[id].js'];
|
||||||
|
|
||||||
|
const { error } = await detectApiRoutes(files);
|
||||||
|
expect(error.code).toBe('conflicting_file_path');
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const files = ['api/[team]/[team].js'];
|
||||||
|
|
||||||
|
const { error } = await detectApiRoutes(files);
|
||||||
|
expect(error.code).toBe('conflicting_path_segment');
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const files = ['api/[endpoint].js', 'api/[endpoint]/[id].js'];
|
||||||
|
|
||||||
|
const { defaultRoutes } = await detectApiRoutes(files);
|
||||||
|
expect(defaultRoutes.length).toBe(2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@@ -61,6 +61,18 @@ export async function build({
|
|||||||
await initPrivateGit(process.env.GIT_CREDENTIALS);
|
await initPrivateGit(process.env.GIT_CREDENTIALS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.env.GO111MODULE) {
|
||||||
|
console.log(`\nManually assigning 'GO111MODULE' is not recommended.
|
||||||
|
|
||||||
|
By default:
|
||||||
|
- 'GO111MODULE=on' If entrypoint package name is not 'main'
|
||||||
|
- 'GO111MODULE=off' If entrypoint package name is 'main'
|
||||||
|
|
||||||
|
We highly recommend you leverage Go Modules in your project.
|
||||||
|
Learn more: https://github.com/golang/go/wiki/Modules
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
console.log('Downloading user files...');
|
console.log('Downloading user files...');
|
||||||
const entrypointArr = entrypoint.split(sep);
|
const entrypointArr = entrypoint.split(sep);
|
||||||
|
|
||||||
@@ -128,18 +140,18 @@ Learn more: https://zeit.co/docs/v2/deployments/official-builders/go-now-go/#ent
|
|||||||
const entrypointDirname = dirname(downloadedFiles[entrypoint].fsPath);
|
const entrypointDirname = dirname(downloadedFiles[entrypoint].fsPath);
|
||||||
let isGoModExist = false;
|
let isGoModExist = false;
|
||||||
let goModPath = '';
|
let goModPath = '';
|
||||||
let goModPathArr: string[] = [];
|
let isGoModInRootDir = false;
|
||||||
for (const file of Object.keys(downloadedFiles)) {
|
for (const file of Object.keys(downloadedFiles)) {
|
||||||
const fileDirname = dirname(downloadedFiles[file].fsPath);
|
const fileDirname = dirname(downloadedFiles[file].fsPath);
|
||||||
if (file === 'go.mod') {
|
if (file === 'go.mod') {
|
||||||
isGoModExist = true;
|
isGoModExist = true;
|
||||||
|
isGoModInRootDir = true;
|
||||||
goModPath = fileDirname;
|
goModPath = fileDirname;
|
||||||
goModPathArr = goModPath.split(sep);
|
} else if (file.endsWith('go.mod') && !file.endsWith('vendor')) {
|
||||||
} else if (file.includes('go.mod')) {
|
|
||||||
isGoModExist = true;
|
|
||||||
if (entrypointDirname === fileDirname) {
|
if (entrypointDirname === fileDirname) {
|
||||||
|
isGoModExist = true;
|
||||||
goModPath = fileDirname;
|
goModPath = fileDirname;
|
||||||
goModPathArr = goModPath.split(sep);
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,6 +173,10 @@ Learn more: https://zeit.co/docs/v2/deployments/official-builders/go-now-go/#ent
|
|||||||
`Found exported function "${handlerFunctionName}" in "${entrypoint}"`
|
`Found exported function "${handlerFunctionName}" in "${entrypoint}"`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!isGoModExist && 'vendor' in downloadedFiles) {
|
||||||
|
throw new Error('`go.mod` is required to use a `vendor` directory.');
|
||||||
|
}
|
||||||
|
|
||||||
// check if package name other than main
|
// check if package name other than main
|
||||||
// using `go.mod` way building the handler
|
// using `go.mod` way building the handler
|
||||||
const packageName = parsedAnalyzed.packageName;
|
const packageName = parsedAnalyzed.packageName;
|
||||||
@@ -202,14 +218,28 @@ Learn more: https://zeit.co/docs/v2/deployments/official-builders/go-now-go/#ent
|
|||||||
if (isGoModExist) {
|
if (isGoModExist) {
|
||||||
const goModContents = await readFile(join(goModPath, 'go.mod'), 'utf8');
|
const goModContents = await readFile(join(goModPath, 'go.mod'), 'utf8');
|
||||||
const usrModName = goModContents.split('\n')[0].split(' ')[1];
|
const usrModName = goModContents.split('\n')[0].split(' ')[1];
|
||||||
|
|
||||||
|
if (entrypointArr.length > 1 && isGoModInRootDir) {
|
||||||
|
let cleanPackagePath = [...entrypointArr];
|
||||||
|
cleanPackagePath.pop();
|
||||||
|
goPackageName = `${usrModName}/${cleanPackagePath.join('/')}`;
|
||||||
|
} else {
|
||||||
goPackageName = `${usrModName}/${packageName}`;
|
goPackageName = `${usrModName}/${packageName}`;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const mainModGoContents = modMainGoContents
|
const mainModGoContents = modMainGoContents
|
||||||
.replace('__NOW_HANDLER_PACKAGE_NAME', goPackageName)
|
.replace('__NOW_HANDLER_PACKAGE_NAME', goPackageName)
|
||||||
.replace('__NOW_HANDLER_FUNC_NAME', goFuncName);
|
.replace('__NOW_HANDLER_FUNC_NAME', goFuncName);
|
||||||
|
|
||||||
if (goModPathArr.length > 1) {
|
if (meta.isDev && isGoModExist && isGoModInRootDir) {
|
||||||
|
await writeFile(
|
||||||
|
join(dirname(downloadedFiles['now.json'].fsPath), mainModGoFileName),
|
||||||
|
mainModGoContents
|
||||||
|
);
|
||||||
|
} else if (isGoModExist && isGoModInRootDir) {
|
||||||
|
await writeFile(join(srcPath, mainModGoFileName), mainModGoContents);
|
||||||
|
} else if (isGoModExist && !isGoModInRootDir) {
|
||||||
// using `go.mod` path to write main__mod__.go
|
// using `go.mod` path to write main__mod__.go
|
||||||
await writeFile(join(goModPath, mainModGoFileName), mainModGoContents);
|
await writeFile(join(goModPath, mainModGoFileName), mainModGoContents);
|
||||||
} else {
|
} else {
|
||||||
@@ -252,21 +282,28 @@ Learn more: https://zeit.co/docs/v2/deployments/official-builders/go-now-go/#ent
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (meta.isDev) {
|
let baseGoModPath = '';
|
||||||
let entrypointDir = entrypointDirname;
|
if (meta.isDev && isGoModExist && isGoModInRootDir) {
|
||||||
if (goModPathArr.length > 1) {
|
baseGoModPath = dirname(downloadedFiles['now.json'].fsPath);
|
||||||
entrypointDir = goModPath;
|
} else if (isGoModExist && isGoModInRootDir) {
|
||||||
|
baseGoModPath = srcPath;
|
||||||
|
} else if (isGoModExist && !isGoModInRootDir) {
|
||||||
|
baseGoModPath = goModPath;
|
||||||
|
} else {
|
||||||
|
baseGoModPath = entrypointDirname;
|
||||||
}
|
}
|
||||||
const isGoModBk = await pathExists(join(entrypointDir, 'go.mod.bk'));
|
|
||||||
|
if (meta.isDev) {
|
||||||
|
const isGoModBk = await pathExists(join(baseGoModPath, 'go.mod.bk'));
|
||||||
if (isGoModBk) {
|
if (isGoModBk) {
|
||||||
await move(
|
await move(
|
||||||
join(entrypointDir, 'go.mod.bk'),
|
join(baseGoModPath, 'go.mod.bk'),
|
||||||
join(entrypointDir, 'go.mod'),
|
join(baseGoModPath, 'go.mod'),
|
||||||
{ overwrite: true }
|
{ overwrite: true }
|
||||||
);
|
);
|
||||||
await move(
|
await move(
|
||||||
join(entrypointDir, 'go.sum.bk'),
|
join(baseGoModPath, 'go.sum.bk'),
|
||||||
join(entrypointDir, 'go.sum'),
|
join(baseGoModPath, 'go.sum'),
|
||||||
{ overwrite: true }
|
{ overwrite: true }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -283,8 +320,7 @@ Learn more: https://zeit.co/docs/v2/deployments/official-builders/go-now-go/#ent
|
|||||||
|
|
||||||
console.log('Running `go build`...');
|
console.log('Running `go build`...');
|
||||||
const destPath = join(outDir, 'handler');
|
const destPath = join(outDir, 'handler');
|
||||||
const isGoModInRootDir = goModPathArr.length === 1;
|
|
||||||
const baseGoModPath = isGoModInRootDir ? entrypointDirname : goModPath;
|
|
||||||
try {
|
try {
|
||||||
let src = [join(baseGoModPath, mainModGoFileName)];
|
let src = [join(baseGoModPath, mainModGoFileName)];
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/go",
|
"name": "@now/go",
|
||||||
"version": "0.5.1",
|
"version": "0.5.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/go-now-go",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/zeit/now-builders.git",
|
"url": "https://github.com/zeit/now-builders.git",
|
||||||
@@ -31,6 +32,6 @@
|
|||||||
"@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",
|
||||||
"typescript": "^3.4.2"
|
"typescript": "3.5.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
12
packages/now-go/test/fixtures/13-go-mod-nested/api/nested/index.go
vendored
Normal file
12
packages/now-go/test/fixtures/13-go-mod-nested/api/nested/index.go
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package nested
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"with-nested/shared"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler func
|
||||||
|
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Fprintf(w, shared.Say("lol:RANDOMNESS_PLACEHOLDER"))
|
||||||
|
}
|
||||||
3
packages/now-go/test/fixtures/13-go-mod-nested/go.mod
vendored
Normal file
3
packages/now-go/test/fixtures/13-go-mod-nested/go.mod
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module with-nested
|
||||||
|
|
||||||
|
go 1.12
|
||||||
5
packages/now-go/test/fixtures/13-go-mod-nested/now.json
vendored
Normal file
5
packages/now-go/test/fixtures/13-go-mod-nested/now.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"builds": [{ "src": "api/nested/*.go", "use": "@now/go" }],
|
||||||
|
"probes": [{ "path": "/api/nested", "mustContain": "RANDOMNESS_PLACEHOLDER" }]
|
||||||
|
}
|
||||||
6
packages/now-go/test/fixtures/13-go-mod-nested/shared/shared.go
vendored
Normal file
6
packages/now-go/test/fixtures/13-go-mod-nested/shared/shared.go
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package shared
|
||||||
|
|
||||||
|
// Say func
|
||||||
|
func Say(text string) string {
|
||||||
|
return text
|
||||||
|
}
|
||||||
11
packages/now-go/test/fixtures/14-go-mod-sub/api/index.go
vendored
Normal file
11
packages/now-go/test/fixtures/14-go-mod-sub/api/index.go
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler func
|
||||||
|
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Fprintf(w, "hello:RANDOMNESS_PLACEHOLDER")
|
||||||
|
}
|
||||||
5
packages/now-go/test/fixtures/14-go-mod-sub/now.json
vendored
Normal file
5
packages/now-go/test/fixtures/14-go-mod-sub/now.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"builds": [{ "src": "api/**/*.go", "use": "@now/go" }],
|
||||||
|
"probes": [{ "path": "/api", "mustContain": "RANDOMNESS_PLACEHOLDER" }]
|
||||||
|
}
|
||||||
3
packages/now-go/test/fixtures/14-go-mod-sub/other-folder/go.mod
vendored
Normal file
3
packages/now-go/test/fixtures/14-go-mod-sub/other-folder/go.mod
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module other-folder
|
||||||
|
|
||||||
|
go 1.12
|
||||||
11
packages/now-go/test/fixtures/14-go-mod-sub/other-folder/index.go
vendored
Normal file
11
packages/now-go/test/fixtures/14-go-mod-sub/other-folder/index.go
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler func
|
||||||
|
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Fprintf(w, "hello:RANDOMNESS_PLACEHOLDER")
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/html-minifier",
|
"name": "@now/html-minifier",
|
||||||
"version": "1.1.3",
|
"version": "1.1.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/html-minifier-now-html-minifier",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/zeit/now-builders.git",
|
"url": "https://github.com/zeit/now-builders.git",
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/md",
|
"name": "@now/md",
|
||||||
"version": "0.5.4",
|
"version": "0.5.5",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/markdown-now-md",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/zeit/now-builders.git",
|
"url": "https://github.com/zeit/now-builders.git",
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/mdx-deck",
|
"name": "@now/mdx-deck",
|
||||||
"version": "0.5.4",
|
"version": "0.5.5",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/mdx-deck-now-mdx-deck",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/zeit/now-builders.git",
|
"url": "https://github.com/zeit/now-builders.git",
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/next",
|
"name": "@now/next",
|
||||||
"version": "0.4.2",
|
"version": "0.5.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index",
|
"main": "./dist/index",
|
||||||
|
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/next-js-now-next",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "./getBridgeTypes.sh && tsc",
|
"build": "./getBridgeTypes.sh && tsc",
|
||||||
"test": "npm run build && jest",
|
"test": "npm run build && jest",
|
||||||
@@ -14,7 +15,7 @@
|
|||||||
"directory": "packages/now-next"
|
"directory": "packages/now-next"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@now/node-bridge": "^1.2.0",
|
"@now/node-bridge": "1.2.2",
|
||||||
"fs-extra": "^7.0.0",
|
"fs-extra": "^7.0.0",
|
||||||
"get-port": "^5.0.0",
|
"get-port": "^5.0.0",
|
||||||
"resolve-from": "^5.0.0",
|
"resolve-from": "^5.0.0",
|
||||||
@@ -28,6 +29,6 @@
|
|||||||
"@types/resolve-from": "^5.0.1",
|
"@types/resolve-from": "^5.0.1",
|
||||||
"@types/semver": "^6.0.0",
|
"@types/semver": "^6.0.0",
|
||||||
"jest": "^24.7.1",
|
"jest": "^24.7.1",
|
||||||
"typescript": "^3.4.3"
|
"typescript": "3.5.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import {
|
|||||||
PrepareCacheOptions,
|
PrepareCacheOptions,
|
||||||
runNpmInstall,
|
runNpmInstall,
|
||||||
runPackageJsonScript,
|
runPackageJsonScript,
|
||||||
|
getNodeVersion,
|
||||||
|
getSpawnOptions,
|
||||||
} from '@now/build-utils';
|
} from '@now/build-utils';
|
||||||
|
|
||||||
import nextLegacyVersions from './legacy-versions';
|
import nextLegacyVersions from './legacy-versions';
|
||||||
@@ -38,6 +40,7 @@ import {
|
|||||||
validateEntrypoint,
|
validateEntrypoint,
|
||||||
normalizePage,
|
normalizePage,
|
||||||
getDynamicRoutes,
|
getDynamicRoutes,
|
||||||
|
isDynamicRoute,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
|
|
||||||
interface BuildParamsMeta {
|
interface BuildParamsMeta {
|
||||||
@@ -162,6 +165,8 @@ export const build = async ({
|
|||||||
watch?: string[];
|
watch?: string[];
|
||||||
childProcesses: ChildProcess[];
|
childProcesses: ChildProcess[];
|
||||||
}> => {
|
}> => {
|
||||||
|
process.env.__NEXT_BUILDER_EXPERIMENTAL_TARGET = 'serverless';
|
||||||
|
|
||||||
validateEntrypoint(entrypoint);
|
validateEntrypoint(entrypoint);
|
||||||
|
|
||||||
const entryDirectory = path.dirname(entrypoint);
|
const entryDirectory = path.dirname(entrypoint);
|
||||||
@@ -171,6 +176,9 @@ export const build = async ({
|
|||||||
console.log(`${name} Downloading user files...`);
|
console.log(`${name} Downloading user files...`);
|
||||||
await download(files, workPath, meta);
|
await download(files, workPath, meta);
|
||||||
|
|
||||||
|
const nodeVersion = await getNodeVersion(entryPath);
|
||||||
|
const spawnOpts = getSpawnOptions(meta, nodeVersion);
|
||||||
|
|
||||||
const pkg = await readPackageJson(entryPath);
|
const pkg = await readPackageJson(entryPath);
|
||||||
const nextVersion = getNextVersion(pkg);
|
const nextVersion = getNextVersion(pkg);
|
||||||
|
|
||||||
@@ -180,17 +188,13 @@ export const build = async ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
process.env.__NEXT_BUILDER_EXPERIMENTAL_TARGET = 'serverless';
|
|
||||||
|
|
||||||
if (meta.isDev) {
|
if (meta.isDev) {
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
process.env.__NEXT_BUILDER_EXPERIMENTAL_DEBUG = 'true';
|
|
||||||
let childProcess: ChildProcess | undefined;
|
let childProcess: ChildProcess | undefined;
|
||||||
|
|
||||||
// If this is the initial build, we want to start the server
|
// If this is the initial build, we want to start the server
|
||||||
if (!urls[entrypoint]) {
|
if (!urls[entrypoint]) {
|
||||||
console.log(`${name} Installing dependencies...`);
|
console.log(`${name} Installing dependencies...`);
|
||||||
await runNpmInstall(entryPath, ['--prefer-offline']);
|
await runNpmInstall(entryPath, ['--prefer-offline'], spawnOpts);
|
||||||
|
|
||||||
if (!process.env.NODE_ENV) {
|
if (!process.env.NODE_ENV) {
|
||||||
process.env.NODE_ENV = 'development';
|
process.env.NODE_ENV = 'development';
|
||||||
@@ -277,20 +281,21 @@ export const build = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log('installing dependencies...');
|
console.log('installing dependencies...');
|
||||||
await runNpmInstall(entryPath, ['--prefer-offline']);
|
await runNpmInstall(entryPath, ['--prefer-offline'], spawnOpts);
|
||||||
|
|
||||||
console.log('running user script...');
|
console.log('running user script...');
|
||||||
const memoryToConsume = Math.floor(os.totalmem() / 1024 ** 2) - 128;
|
const memoryToConsume = Math.floor(os.totalmem() / 1024 ** 2) - 128;
|
||||||
await runPackageJsonScript(entryPath, 'now-build', {
|
const env = { ...spawnOpts.env } as any;
|
||||||
env: {
|
env.NODE_OPTIONS = `--max_old_space_size=${memoryToConsume}`;
|
||||||
...process.env,
|
await runPackageJsonScript(entryPath, 'now-build', { ...spawnOpts, env });
|
||||||
NODE_OPTIONS: `--max_old_space_size=${memoryToConsume}`,
|
|
||||||
},
|
|
||||||
} as SpawnOptions);
|
|
||||||
|
|
||||||
if (isLegacy) {
|
if (isLegacy) {
|
||||||
console.log('running npm install --production...');
|
console.log('running npm install --production...');
|
||||||
await runNpmInstall(entryPath, ['--prefer-offline', '--production']);
|
await runNpmInstall(
|
||||||
|
entryPath,
|
||||||
|
['--prefer-offline', '--production'],
|
||||||
|
spawnOpts
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.NPM_AUTH_TOKEN) {
|
if (process.env.NPM_AUTH_TOKEN) {
|
||||||
@@ -381,7 +386,7 @@ export const build = async ({
|
|||||||
'now__launcher.js': new FileBlob({ data: launcher }),
|
'now__launcher.js': new FileBlob({ data: launcher }),
|
||||||
},
|
},
|
||||||
handler: 'now__launcher.launcher',
|
handler: 'now__launcher.launcher',
|
||||||
runtime: 'nodejs8.10',
|
runtime: nodeVersion.runtime,
|
||||||
});
|
});
|
||||||
console.log(`Created lambda for page: "${page}"`);
|
console.log(`Created lambda for page: "${page}"`);
|
||||||
})
|
})
|
||||||
@@ -407,7 +412,7 @@ export const build = async ({
|
|||||||
|
|
||||||
const pathname = page.replace(/\.html$/, '');
|
const pathname = page.replace(/\.html$/, '');
|
||||||
|
|
||||||
if (pathname.startsWith('$') || pathname.includes('/$')) {
|
if (isDynamicRoute(pathname)) {
|
||||||
dynamicPages.push(pathname);
|
dynamicPages.push(pathname);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,7 +459,7 @@ export const build = async ({
|
|||||||
|
|
||||||
const pathname = page.replace(/\.js$/, '');
|
const pathname = page.replace(/\.js$/, '');
|
||||||
|
|
||||||
if (pathname.startsWith('$') || pathname.includes('/$')) {
|
if (isDynamicRoute(pathname)) {
|
||||||
dynamicPages.push(normalizePage(pathname));
|
dynamicPages.push(normalizePage(pathname));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -466,7 +471,7 @@ export const build = async ({
|
|||||||
'page.js': pages[page],
|
'page.js': pages[page],
|
||||||
},
|
},
|
||||||
handler: 'now__launcher.launcher',
|
handler: 'now__launcher.launcher',
|
||||||
runtime: 'nodejs8.10',
|
runtime: nodeVersion.runtime,
|
||||||
});
|
});
|
||||||
console.log(`Created lambda for page: "${page}"`);
|
console.log(`Created lambda for page: "${page}"`);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,6 +9,14 @@ export interface EnvConfig {
|
|||||||
[name: string]: string | undefined;
|
[name: string]: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Identify /[param]/ in route string
|
||||||
|
const TEST_DYNAMIC_ROUTE = /\/\[[^\/]+?\](?=\/|$)/;
|
||||||
|
|
||||||
|
function isDynamicRoute(route: string): boolean {
|
||||||
|
route = route.startsWith('/') ? route : `/${route}`;
|
||||||
|
return TEST_DYNAMIC_ROUTE.test(route);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate if the entrypoint is allowed to be used
|
* Validate if the entrypoint is allowed to be used
|
||||||
*/
|
*/
|
||||||
@@ -226,7 +234,7 @@ function getRoutes(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pageName.startsWith('$') || pageName.includes('/$')) {
|
if (isDynamicRoute(pageName)) {
|
||||||
dynamicPages.push(normalizePage(pageName));
|
dynamicPages.push(normalizePage(pageName));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,4 +359,5 @@ export {
|
|||||||
stringMap,
|
stringMap,
|
||||||
syncEnvVars,
|
syncEnvVars,
|
||||||
normalizePage,
|
normalizePage,
|
||||||
|
isDynamicRoute,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ it(
|
|||||||
const {
|
const {
|
||||||
buildResult: { output },
|
buildResult: { output },
|
||||||
} = await runBuildLambda(path.join(__dirname, 'standard'));
|
} = await runBuildLambda(path.join(__dirname, 'standard'));
|
||||||
expect(output.index).toBeDefined();
|
expect(output['index.html']).toBeDefined();
|
||||||
|
expect(output.goodbye).toBeDefined();
|
||||||
const filePaths = Object.keys(output);
|
const filePaths = Object.keys(output);
|
||||||
const serverlessError = filePaths.some(filePath => filePath.match(/_error/));
|
const serverlessError = filePaths.some(filePath => filePath.match(/_error/));
|
||||||
const hasUnderScoreAppStaticFile = filePaths.some(filePath => filePath.match(/static.*\/pages\/_app\.js$/));
|
const hasUnderScoreAppStaticFile = filePaths.some(filePath => filePath.match(/static.*\/pages\/_app\.js$/));
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
target: 'serverless',
|
|
||||||
};
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"version": 2,
|
"version": 2,
|
||||||
"builds": [{ "src": "next.config.js", "use": "@now/next" }]
|
"builds": [{ "src": "package.json", "use": "@now/next" }]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
const F = () => 'Goodbye World!';
|
||||||
|
F.getInitialProps = () => ({});
|
||||||
|
export default F;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/node-bridge",
|
"name": "@now/node-bridge",
|
||||||
"version": "1.2.0",
|
"version": "1.2.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./index.js",
|
"main": "./index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -21,6 +21,6 @@
|
|||||||
"@types/aws-lambda": "8.10.19",
|
"@types/aws-lambda": "8.10.19",
|
||||||
"@types/node": "11.9.4",
|
"@types/node": "11.9.4",
|
||||||
"jest": "24.1.0",
|
"jest": "24.1.0",
|
||||||
"typescript": "3.3.3"
|
"typescript": "3.5.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,16 @@ export interface NowProxyResponse {
|
|||||||
encoding: string;
|
encoding: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ServerLike {
|
||||||
|
listen: (
|
||||||
|
opts: {
|
||||||
|
host?: string;
|
||||||
|
port?: number;
|
||||||
|
},
|
||||||
|
callback: (this: Server | null) => void
|
||||||
|
) => Server | void;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the `http.Server` handler function throws an error asynchronously,
|
* If the `http.Server` handler function throws an error asynchronously,
|
||||||
* then it ends up being an unhandled rejection which doesn't kill the node
|
* then it ends up being an unhandled rejection which doesn't kill the node
|
||||||
@@ -92,14 +102,14 @@ function normalizeEvent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Bridge {
|
export class Bridge {
|
||||||
private server: Server | null;
|
private server: ServerLike | null;
|
||||||
private listening: Promise<AddressInfo>;
|
private listening: Promise<AddressInfo>;
|
||||||
private resolveListening: (info: AddressInfo) => void;
|
private resolveListening: (info: AddressInfo) => void;
|
||||||
private events: { [key: string]: NowProxyRequest } = {};
|
private events: { [key: string]: NowProxyRequest } = {};
|
||||||
private reqIdSeed: number = 1;
|
private reqIdSeed: number = 1;
|
||||||
private shouldStoreEvents: boolean = false;
|
private shouldStoreEvents: boolean = false;
|
||||||
|
|
||||||
constructor(server?: Server, shouldStoreEvents: boolean = false) {
|
constructor(server?: ServerLike, shouldStoreEvents: boolean = false) {
|
||||||
this.server = null;
|
this.server = null;
|
||||||
this.shouldStoreEvents = shouldStoreEvents;
|
this.shouldStoreEvents = shouldStoreEvents;
|
||||||
if (server) {
|
if (server) {
|
||||||
@@ -116,18 +126,8 @@ export class Bridge {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setServer(server: Server) {
|
setServer(server: ServerLike) {
|
||||||
this.server = server;
|
this.server = server;
|
||||||
server.once('listening', () => {
|
|
||||||
const addr = server.address();
|
|
||||||
if (typeof addr === 'string') {
|
|
||||||
throw new Error(`Unexpected string for \`server.address()\`: ${addr}`);
|
|
||||||
} else if (!addr) {
|
|
||||||
throw new Error('`server.address()` returned `null`');
|
|
||||||
} else {
|
|
||||||
this.resolveListening(addr);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
listen() {
|
listen() {
|
||||||
@@ -135,10 +135,35 @@ export class Bridge {
|
|||||||
throw new Error('Server has not been set!');
|
throw new Error('Server has not been set!');
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.server.listen({
|
const resolveListening = this.resolveListening;
|
||||||
|
|
||||||
|
return this.server.listen(
|
||||||
|
{
|
||||||
host: '127.0.0.1',
|
host: '127.0.0.1',
|
||||||
port: 0,
|
port: 0,
|
||||||
});
|
},
|
||||||
|
function listeningCallback() {
|
||||||
|
if (!this || typeof this.address !== 'function') {
|
||||||
|
throw new Error(
|
||||||
|
'Missing server.address() function on `this` in server.listen()'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const addr = this.address();
|
||||||
|
|
||||||
|
if (!addr) {
|
||||||
|
throw new Error('`server.address()` returned `null`');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof addr === 'string') {
|
||||||
|
throw new Error(
|
||||||
|
`Unexpected string for \`server.address()\`: ${addr}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveListening(addr);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async launcher(
|
async launcher(
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
const { createLambda } = require('@now/build-utils/lambda.js'); // eslint-disable-line import/no-extraneous-dependencies
|
|
||||||
const download = require('@now/build-utils/fs/download.js'); // eslint-disable-line import/no-extraneous-dependencies
|
|
||||||
const FileBlob = require('@now/build-utils/file-blob.js'); // eslint-disable-line import/no-extraneous-dependencies
|
|
||||||
const FileFsRef = require('@now/build-utils/file-fs-ref.js'); // eslint-disable-line import/no-extraneous-dependencies
|
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const glob = require('@now/build-utils/fs/glob.js'); // eslint-disable-line import/no-extraneous-dependencies
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const {
|
const {
|
||||||
|
FileBlob,
|
||||||
|
FileFsRef,
|
||||||
|
download,
|
||||||
|
createLambda,
|
||||||
|
glob,
|
||||||
runNpmInstall,
|
runNpmInstall,
|
||||||
runPackageJsonScript,
|
runPackageJsonScript,
|
||||||
} = require('@now/build-utils/fs/run-user-scripts.js'); // eslint-disable-line import/no-extraneous-dependencies
|
getNodeVersion,
|
||||||
const { shouldServe } = require('@now/build-utils'); // eslint-disable-line import/no-extraneous-dependencies
|
getSpawnOptions,
|
||||||
|
shouldServe,
|
||||||
|
} = require('@now/build-utils'); // eslint-disable-line import/no-extraneous-dependencies
|
||||||
|
|
||||||
/** @typedef { import('@now/build-utils/file-ref') } FileRef */
|
/** @typedef { import('@now/build-utils/file-ref') } FileRef */
|
||||||
/** @typedef {{[filePath: string]: FileRef}} Files */
|
/** @typedef {{[filePath: string]: FileRef}} Files */
|
||||||
@@ -38,8 +40,15 @@ async function downloadInstallAndBundle(
|
|||||||
|
|
||||||
console.log("installing dependencies for user's code...");
|
console.log("installing dependencies for user's code...");
|
||||||
const entrypointFsDirname = path.join(workPath, path.dirname(entrypoint));
|
const entrypointFsDirname = path.join(workPath, path.dirname(entrypoint));
|
||||||
await runNpmInstall(entrypointFsDirname, npmArguments);
|
const nodeVersion = await getNodeVersion(entrypointFsDirname);
|
||||||
return [downloadedFiles, entrypointFsDirname];
|
const spawnOpts = getSpawnOptions(meta, nodeVersion);
|
||||||
|
await runNpmInstall(entrypointFsDirname, npmArguments, spawnOpts);
|
||||||
|
return {
|
||||||
|
downloadedFiles,
|
||||||
|
entrypointFsDirname,
|
||||||
|
spawnOpts,
|
||||||
|
nodeVersion,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function compile(workPath, downloadedFiles, entrypoint, config) {
|
async function compile(workPath, downloadedFiles, entrypoint, config) {
|
||||||
@@ -101,9 +110,14 @@ exports.config = {
|
|||||||
* @returns {Promise<Files>}
|
* @returns {Promise<Files>}
|
||||||
*/
|
*/
|
||||||
exports.build = async ({
|
exports.build = async ({
|
||||||
files, entrypoint, config, workPath, meta,
|
files, entrypoint, config, workPath, meta = {},
|
||||||
}) => {
|
}) => {
|
||||||
const [downloadedFiles, entrypointFsDirname] = await downloadInstallAndBundle(
|
const {
|
||||||
|
downloadedFiles,
|
||||||
|
entrypointFsDirname,
|
||||||
|
spawnOptions,
|
||||||
|
nodeVersion,
|
||||||
|
} = await downloadInstallAndBundle(
|
||||||
{
|
{
|
||||||
files,
|
files,
|
||||||
entrypoint,
|
entrypoint,
|
||||||
@@ -114,7 +128,7 @@ exports.build = async ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
console.log('running user script...');
|
console.log('running user script...');
|
||||||
await runPackageJsonScript(entrypointFsDirname, 'now-build');
|
await runPackageJsonScript(entrypointFsDirname, 'now-build', spawnOptions);
|
||||||
|
|
||||||
console.log('preparing lambda files...');
|
console.log('preparing lambda files...');
|
||||||
let preparedFiles;
|
let preparedFiles;
|
||||||
@@ -143,10 +157,13 @@ exports.build = async ({
|
|||||||
'bridge.js': new FileFsRef({ fsPath: require('@now/node-bridge') }),
|
'bridge.js': new FileFsRef({ fsPath: require('@now/node-bridge') }),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Use the system-installed version of `node` when running via `now dev`
|
||||||
|
const runtime = meta.isDev ? 'nodejs' : nodeVersion.runtime;
|
||||||
|
|
||||||
const lambda = await createLambda({
|
const lambda = await createLambda({
|
||||||
files: { ...preparedFiles, ...launcherFiles },
|
files: { ...preparedFiles, ...launcherFiles },
|
||||||
handler: 'launcher.launcher',
|
handler: 'launcher.launcher',
|
||||||
runtime: 'nodejs8.10',
|
runtime,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { [entrypoint]: lambda };
|
return { [entrypoint]: lambda };
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/node-server",
|
"name": "@now/node-server",
|
||||||
"version": "0.7.5",
|
"version": "0.8.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/node-js-server-now-node-server",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/zeit/now-builders.git",
|
"url": "https://github.com/zeit/now-builders.git",
|
||||||
"directory": "packages/now-node-server"
|
"directory": "packages/now-node-server"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@now/node-bridge": "^1.2.0",
|
"@now/node-bridge": "1.2.2",
|
||||||
"@zeit/ncc": "0.18.5",
|
"@zeit/ncc": "0.18.5",
|
||||||
"fs-extra": "7.0.1"
|
"fs-extra": "7.0.1"
|
||||||
},
|
},
|
||||||
|
|||||||
1
packages/now-node/bench/.gitignore
vendored
Normal file
1
packages/now-node/bench/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
lambda
|
||||||
19
packages/now-node/bench/entrypoint-express.js
Normal file
19
packages/now-node/bench/entrypoint-express.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
const express = require('express');
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
module.exports = app;
|
||||||
|
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
app.post('*', (req, res) => {
|
||||||
|
if (req.body == null) {
|
||||||
|
return res.status(400).send({ error: 'no JSON object in the request' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send(JSON.stringify(req.body, null, 4));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.all('*', (req, res) => {
|
||||||
|
res.status(405).send({ error: 'only POST requests are accepted' });
|
||||||
|
});
|
||||||
7
packages/now-node/bench/entrypoint-helpers.js
Normal file
7
packages/now-node/bench/entrypoint-helpers.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
module.exports = (req, res) => {
|
||||||
|
if (req.body == null) {
|
||||||
|
return res.status(400).send({ error: 'no JSON object in the request' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send(JSON.stringify(req.body, null, 4));
|
||||||
|
};
|
||||||
9
packages/now-node/bench/entrypoint-load-helpers.js
Normal file
9
packages/now-node/bench/entrypoint-load-helpers.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
function doNothing() {}
|
||||||
|
|
||||||
|
module.exports = (req, res) => {
|
||||||
|
doNothing(req.query.who);
|
||||||
|
doNothing(req.body);
|
||||||
|
doNothing(req.cookies);
|
||||||
|
|
||||||
|
res.end('hello');
|
||||||
|
};
|
||||||
3
packages/now-node/bench/entrypoint-notload-helpers.js
Normal file
3
packages/now-node/bench/entrypoint-notload-helpers.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = (req, res) => {
|
||||||
|
res.end('hello');
|
||||||
|
};
|
||||||
10
packages/now-node/bench/package.json
Normal file
10
packages/now-node/bench/package.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "bench",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"express": "4.17.1",
|
||||||
|
"fs-extra": "8.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
91
packages/now-node/bench/run.js
Normal file
91
packages/now-node/bench/run.js
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
const fs = require('fs-extra');
|
||||||
|
const { join } = require('path');
|
||||||
|
const { makeLauncher } = require('../dist/launcher');
|
||||||
|
|
||||||
|
const setupFiles = async (entrypoint, shouldAddHelpers) => {
|
||||||
|
await fs.remove(join(__dirname, 'lambda'));
|
||||||
|
await fs.ensureDir(join(__dirname, 'lambda'));
|
||||||
|
|
||||||
|
await fs.copyFile(
|
||||||
|
join(__dirname, '../dist/helpers.js'),
|
||||||
|
join(__dirname, 'lambda/helpers.js'),
|
||||||
|
);
|
||||||
|
await fs.copyFile(
|
||||||
|
require.resolve('@now/node-bridge/bridge'),
|
||||||
|
join(__dirname, 'lambda/bridge.js'),
|
||||||
|
);
|
||||||
|
await fs.copyFile(
|
||||||
|
join(process.cwd(), entrypoint),
|
||||||
|
join(__dirname, 'lambda/entrypoint.js'),
|
||||||
|
);
|
||||||
|
|
||||||
|
let launcher = makeLauncher('./entrypoint', shouldAddHelpers);
|
||||||
|
launcher += '\nexports.bridge=bridge';
|
||||||
|
|
||||||
|
await fs.writeFile(join(__dirname, 'lambda/launcher.js'), launcher);
|
||||||
|
};
|
||||||
|
|
||||||
|
const createBigJSONObj = () => {
|
||||||
|
const obj = {};
|
||||||
|
for (let i = 0; i < 1000; i += 1) {
|
||||||
|
obj[`idx${i}`] = `val${i}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const createEvent = () => ({
|
||||||
|
Action: 'Invoke',
|
||||||
|
body: JSON.stringify({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/',
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
encoding: undefined,
|
||||||
|
body: createBigJSONObj(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const runTests = async (entrypoint, shouldAddHelpers = true, nb) => {
|
||||||
|
console.log(
|
||||||
|
`setting up files with entrypoint ${entrypoint} and ${
|
||||||
|
shouldAddHelpers ? 'helpers' : 'no helpers'
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
await setupFiles(entrypoint, shouldAddHelpers);
|
||||||
|
|
||||||
|
console.log('importing launcher');
|
||||||
|
const launcher = require('./lambda/launcher');
|
||||||
|
|
||||||
|
const event = createEvent();
|
||||||
|
const context = {};
|
||||||
|
|
||||||
|
const start = process.hrtime();
|
||||||
|
|
||||||
|
console.log(`throwing ${nb} events at lambda`);
|
||||||
|
for (let i = 0; i < nb; i += 1) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
await launcher.launcher(event, context);
|
||||||
|
}
|
||||||
|
const timer = process.hrtime(start);
|
||||||
|
const ms = (timer[0] * 1e9 + timer[1]) / 1e6;
|
||||||
|
|
||||||
|
await launcher.bridge.server.close();
|
||||||
|
delete require.cache[require.resolve('./lambda/launcher')];
|
||||||
|
|
||||||
|
console.log({ nb, sum: ms, avg: ms / nb });
|
||||||
|
};
|
||||||
|
|
||||||
|
const main = async () => {
|
||||||
|
if (process.argv.length !== 5) {
|
||||||
|
console.log(
|
||||||
|
'usage : node run.js <entrypoint-file> <add-helpers> <nb-of-request>',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [, , entrypoint, helpers, nbRequests] = process.argv;
|
||||||
|
const shouldAddHelpers = helpers !== 'false' && helpers !== 'no';
|
||||||
|
const nb = Number(nbRequests);
|
||||||
|
|
||||||
|
await runTests(entrypoint, shouldAddHelpers, nb);
|
||||||
|
};
|
||||||
|
|
||||||
|
main();
|
||||||
378
packages/now-node/bench/yarn.lock
Normal file
378
packages/now-node/bench/yarn.lock
Normal file
@@ -0,0 +1,378 @@
|
|||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
accepts@~1.3.7:
|
||||||
|
version "1.3.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
|
||||||
|
integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
|
||||||
|
dependencies:
|
||||||
|
mime-types "~2.1.24"
|
||||||
|
negotiator "0.6.2"
|
||||||
|
|
||||||
|
array-flatten@1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
|
||||||
|
integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
|
||||||
|
|
||||||
|
body-parser@1.19.0:
|
||||||
|
version "1.19.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
|
||||||
|
integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
|
||||||
|
dependencies:
|
||||||
|
bytes "3.1.0"
|
||||||
|
content-type "~1.0.4"
|
||||||
|
debug "2.6.9"
|
||||||
|
depd "~1.1.2"
|
||||||
|
http-errors "1.7.2"
|
||||||
|
iconv-lite "0.4.24"
|
||||||
|
on-finished "~2.3.0"
|
||||||
|
qs "6.7.0"
|
||||||
|
raw-body "2.4.0"
|
||||||
|
type-is "~1.6.17"
|
||||||
|
|
||||||
|
bytes@3.1.0:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
|
||||||
|
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
|
||||||
|
|
||||||
|
content-disposition@0.5.3:
|
||||||
|
version "0.5.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
|
||||||
|
integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==
|
||||||
|
dependencies:
|
||||||
|
safe-buffer "5.1.2"
|
||||||
|
|
||||||
|
content-type@~1.0.4:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
|
||||||
|
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
|
||||||
|
|
||||||
|
cookie-signature@1.0.6:
|
||||||
|
version "1.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
|
||||||
|
integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
|
||||||
|
|
||||||
|
cookie@0.4.0:
|
||||||
|
version "0.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
|
||||||
|
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
|
||||||
|
|
||||||
|
debug@2.6.9:
|
||||||
|
version "2.6.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||||
|
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
|
||||||
|
dependencies:
|
||||||
|
ms "2.0.0"
|
||||||
|
|
||||||
|
depd@~1.1.2:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
||||||
|
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
|
||||||
|
|
||||||
|
destroy@~1.0.4:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
|
||||||
|
integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
|
||||||
|
|
||||||
|
ee-first@1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||||
|
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
|
||||||
|
|
||||||
|
encodeurl@~1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
||||||
|
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
|
||||||
|
|
||||||
|
escape-html@~1.0.3:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
|
||||||
|
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
|
||||||
|
|
||||||
|
etag@~1.8.1:
|
||||||
|
version "1.8.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
|
||||||
|
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
|
||||||
|
|
||||||
|
express@4.17.1:
|
||||||
|
version "4.17.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
|
||||||
|
integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==
|
||||||
|
dependencies:
|
||||||
|
accepts "~1.3.7"
|
||||||
|
array-flatten "1.1.1"
|
||||||
|
body-parser "1.19.0"
|
||||||
|
content-disposition "0.5.3"
|
||||||
|
content-type "~1.0.4"
|
||||||
|
cookie "0.4.0"
|
||||||
|
cookie-signature "1.0.6"
|
||||||
|
debug "2.6.9"
|
||||||
|
depd "~1.1.2"
|
||||||
|
encodeurl "~1.0.2"
|
||||||
|
escape-html "~1.0.3"
|
||||||
|
etag "~1.8.1"
|
||||||
|
finalhandler "~1.1.2"
|
||||||
|
fresh "0.5.2"
|
||||||
|
merge-descriptors "1.0.1"
|
||||||
|
methods "~1.1.2"
|
||||||
|
on-finished "~2.3.0"
|
||||||
|
parseurl "~1.3.3"
|
||||||
|
path-to-regexp "0.1.7"
|
||||||
|
proxy-addr "~2.0.5"
|
||||||
|
qs "6.7.0"
|
||||||
|
range-parser "~1.2.1"
|
||||||
|
safe-buffer "5.1.2"
|
||||||
|
send "0.17.1"
|
||||||
|
serve-static "1.14.1"
|
||||||
|
setprototypeof "1.1.1"
|
||||||
|
statuses "~1.5.0"
|
||||||
|
type-is "~1.6.18"
|
||||||
|
utils-merge "1.0.1"
|
||||||
|
vary "~1.1.2"
|
||||||
|
|
||||||
|
finalhandler@~1.1.2:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
|
||||||
|
integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
|
||||||
|
dependencies:
|
||||||
|
debug "2.6.9"
|
||||||
|
encodeurl "~1.0.2"
|
||||||
|
escape-html "~1.0.3"
|
||||||
|
on-finished "~2.3.0"
|
||||||
|
parseurl "~1.3.3"
|
||||||
|
statuses "~1.5.0"
|
||||||
|
unpipe "~1.0.0"
|
||||||
|
|
||||||
|
forwarded@~0.1.2:
|
||||||
|
version "0.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
|
||||||
|
integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=
|
||||||
|
|
||||||
|
fresh@0.5.2:
|
||||||
|
version "0.5.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
||||||
|
integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
|
||||||
|
|
||||||
|
fs-extra@8.0.1:
|
||||||
|
version "8.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.0.1.tgz#90294081f978b1f182f347a440a209154344285b"
|
||||||
|
integrity sha512-W+XLrggcDzlle47X/XnS7FXrXu9sDo+Ze9zpndeBxdgv88FHLm1HtmkhEwavruS6koanBjp098rUpHs65EmG7A==
|
||||||
|
dependencies:
|
||||||
|
graceful-fs "^4.1.2"
|
||||||
|
jsonfile "^4.0.0"
|
||||||
|
universalify "^0.1.0"
|
||||||
|
|
||||||
|
graceful-fs@^4.1.2, graceful-fs@^4.1.6:
|
||||||
|
version "4.1.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
|
||||||
|
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
|
||||||
|
|
||||||
|
http-errors@1.7.2, http-errors@~1.7.2:
|
||||||
|
version "1.7.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
|
||||||
|
integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
|
||||||
|
dependencies:
|
||||||
|
depd "~1.1.2"
|
||||||
|
inherits "2.0.3"
|
||||||
|
setprototypeof "1.1.1"
|
||||||
|
statuses ">= 1.5.0 < 2"
|
||||||
|
toidentifier "1.0.0"
|
||||||
|
|
||||||
|
iconv-lite@0.4.24:
|
||||||
|
version "0.4.24"
|
||||||
|
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||||
|
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
|
||||||
|
dependencies:
|
||||||
|
safer-buffer ">= 2.1.2 < 3"
|
||||||
|
|
||||||
|
inherits@2.0.3:
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||||
|
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
|
||||||
|
|
||||||
|
ipaddr.js@1.9.0:
|
||||||
|
version "1.9.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65"
|
||||||
|
integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==
|
||||||
|
|
||||||
|
jsonfile@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
|
||||||
|
integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=
|
||||||
|
optionalDependencies:
|
||||||
|
graceful-fs "^4.1.6"
|
||||||
|
|
||||||
|
media-typer@0.3.0:
|
||||||
|
version "0.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||||
|
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
|
||||||
|
|
||||||
|
merge-descriptors@1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
|
||||||
|
integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
|
||||||
|
|
||||||
|
methods@~1.1.2:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
|
||||||
|
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
|
||||||
|
|
||||||
|
mime-db@1.40.0:
|
||||||
|
version "1.40.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32"
|
||||||
|
integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==
|
||||||
|
|
||||||
|
mime-types@~2.1.24:
|
||||||
|
version "2.1.24"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81"
|
||||||
|
integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==
|
||||||
|
dependencies:
|
||||||
|
mime-db "1.40.0"
|
||||||
|
|
||||||
|
mime@1.6.0:
|
||||||
|
version "1.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
||||||
|
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
||||||
|
|
||||||
|
ms@2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||||
|
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
|
||||||
|
|
||||||
|
ms@2.1.1:
|
||||||
|
version "2.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
|
||||||
|
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
|
||||||
|
|
||||||
|
negotiator@0.6.2:
|
||||||
|
version "0.6.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
|
||||||
|
integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
|
||||||
|
|
||||||
|
on-finished@~2.3.0:
|
||||||
|
version "2.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
|
||||||
|
integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
|
||||||
|
dependencies:
|
||||||
|
ee-first "1.1.1"
|
||||||
|
|
||||||
|
parseurl@~1.3.3:
|
||||||
|
version "1.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
|
||||||
|
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
|
||||||
|
|
||||||
|
path-to-regexp@0.1.7:
|
||||||
|
version "0.1.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
|
||||||
|
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
|
||||||
|
|
||||||
|
proxy-addr@~2.0.5:
|
||||||
|
version "2.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34"
|
||||||
|
integrity sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==
|
||||||
|
dependencies:
|
||||||
|
forwarded "~0.1.2"
|
||||||
|
ipaddr.js "1.9.0"
|
||||||
|
|
||||||
|
qs@6.7.0:
|
||||||
|
version "6.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
|
||||||
|
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
|
||||||
|
|
||||||
|
range-parser@~1.2.1:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
|
||||||
|
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
|
||||||
|
|
||||||
|
raw-body@2.4.0:
|
||||||
|
version "2.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332"
|
||||||
|
integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==
|
||||||
|
dependencies:
|
||||||
|
bytes "3.1.0"
|
||||||
|
http-errors "1.7.2"
|
||||||
|
iconv-lite "0.4.24"
|
||||||
|
unpipe "1.0.0"
|
||||||
|
|
||||||
|
safe-buffer@5.1.2:
|
||||||
|
version "5.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||||
|
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||||
|
|
||||||
|
"safer-buffer@>= 2.1.2 < 3":
|
||||||
|
version "2.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||||
|
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||||
|
|
||||||
|
send@0.17.1:
|
||||||
|
version "0.17.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
|
||||||
|
integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==
|
||||||
|
dependencies:
|
||||||
|
debug "2.6.9"
|
||||||
|
depd "~1.1.2"
|
||||||
|
destroy "~1.0.4"
|
||||||
|
encodeurl "~1.0.2"
|
||||||
|
escape-html "~1.0.3"
|
||||||
|
etag "~1.8.1"
|
||||||
|
fresh "0.5.2"
|
||||||
|
http-errors "~1.7.2"
|
||||||
|
mime "1.6.0"
|
||||||
|
ms "2.1.1"
|
||||||
|
on-finished "~2.3.0"
|
||||||
|
range-parser "~1.2.1"
|
||||||
|
statuses "~1.5.0"
|
||||||
|
|
||||||
|
serve-static@1.14.1:
|
||||||
|
version "1.14.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9"
|
||||||
|
integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==
|
||||||
|
dependencies:
|
||||||
|
encodeurl "~1.0.2"
|
||||||
|
escape-html "~1.0.3"
|
||||||
|
parseurl "~1.3.3"
|
||||||
|
send "0.17.1"
|
||||||
|
|
||||||
|
setprototypeof@1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
|
||||||
|
integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
|
||||||
|
|
||||||
|
"statuses@>= 1.5.0 < 2", statuses@~1.5.0:
|
||||||
|
version "1.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
|
||||||
|
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
|
||||||
|
|
||||||
|
toidentifier@1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
|
||||||
|
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
|
||||||
|
|
||||||
|
type-is@~1.6.17, type-is@~1.6.18:
|
||||||
|
version "1.6.18"
|
||||||
|
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
|
||||||
|
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
|
||||||
|
dependencies:
|
||||||
|
media-typer "0.3.0"
|
||||||
|
mime-types "~2.1.24"
|
||||||
|
|
||||||
|
universalify@^0.1.0:
|
||||||
|
version "0.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
||||||
|
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
||||||
|
|
||||||
|
unpipe@1.0.0, unpipe@~1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
||||||
|
integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
|
||||||
|
|
||||||
|
utils-merge@1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
||||||
|
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
|
||||||
|
|
||||||
|
vary@~1.1.2:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
||||||
|
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
|
||||||
@@ -13,12 +13,17 @@ cp -v "$bridge_defs" src
|
|||||||
# build ts files
|
# build ts files
|
||||||
tsc
|
tsc
|
||||||
|
|
||||||
|
# todo: improve
|
||||||
|
# copy type file for ts test
|
||||||
|
cp dist/types.d.ts test/fixtures/15-helpers/ts/types.d.ts
|
||||||
|
|
||||||
|
# use types.d.ts as the main types export
|
||||||
|
mv dist/types.d.ts dist/types
|
||||||
|
rm dist/*.d.ts
|
||||||
|
mv dist/types dist/index.d.ts
|
||||||
|
|
||||||
# bundle helpers.ts with ncc
|
# bundle helpers.ts with ncc
|
||||||
rm dist/helpers.js
|
rm dist/helpers.js
|
||||||
ncc build src/helpers.ts -o dist/helpers
|
ncc build src/helpers.ts -o dist/helpers
|
||||||
mv dist/helpers/index.js dist/helpers.js
|
mv dist/helpers/index.js dist/helpers.js
|
||||||
rm -rf dist/helpers
|
rm -rf dist/helpers
|
||||||
|
|
||||||
# todo: improve
|
|
||||||
# copy type file for ts test
|
|
||||||
cp dist/types.d.ts test/fixtures/15-helpers/ts/types.d.ts
|
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/node",
|
"name": "@now/node",
|
||||||
"version": "0.9.0",
|
"version": "0.11.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index",
|
"main": "./dist/index",
|
||||||
|
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/node-js-now-node",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/zeit/now-builders.git",
|
"url": "https://github.com/zeit/now-builders.git",
|
||||||
"directory": "packages/now-node"
|
"directory": "packages/now-node"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@now/node-bridge": "^1.2.0",
|
"@now/node-bridge": "1.2.2",
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
"@zeit/ncc": "0.18.5",
|
"@zeit/ncc": "0.18.5",
|
||||||
"@zeit/ncc-watcher": "1.0.3",
|
"@zeit/ncc-watcher": "1.0.3"
|
||||||
"fs-extra": "7.0.1"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "./build.sh",
|
"build": "./build.sh",
|
||||||
@@ -26,11 +26,13 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/content-type": "1.1.3",
|
"@types/content-type": "1.1.3",
|
||||||
"@types/cookie": "0.3.3",
|
"@types/cookie": "0.3.3",
|
||||||
|
"@types/etag": "1.8.0",
|
||||||
"@types/test-listen": "1.1.0",
|
"@types/test-listen": "1.1.0",
|
||||||
"content-type": "1.0.4",
|
"content-type": "1.0.4",
|
||||||
"cookie": "0.4.0",
|
"cookie": "0.4.0",
|
||||||
|
"etag": "1.8.1",
|
||||||
"node-fetch": "2.6.0",
|
"node-fetch": "2.6.0",
|
||||||
"test-listen": "1.1.0",
|
"test-listen": "1.1.0",
|
||||||
"typescript": "3.3.3"
|
"typescript": "3.5.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
NowRequestQuery,
|
NowRequestQuery,
|
||||||
NowRequestBody,
|
NowRequestBody,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { Stream } from 'stream';
|
|
||||||
import { Server } from 'http';
|
import { Server } from 'http';
|
||||||
import { Bridge } from './bridge';
|
import { Bridge } from './bridge';
|
||||||
|
|
||||||
@@ -73,56 +72,127 @@ function getCookieParser(req: NowRequest) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendStatusCode(res: NowResponse, statusCode: number): NowResponse {
|
function status(res: NowResponse, statusCode: number): NowResponse {
|
||||||
res.statusCode = statusCode;
|
res.statusCode = statusCode;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendData(res: NowResponse, body: any): NowResponse {
|
function setCharset(type: string, charset: string) {
|
||||||
if (body === null) {
|
const { parse, format } = require('content-type');
|
||||||
|
const parsed = parse(type);
|
||||||
|
parsed.parameters.charset = charset;
|
||||||
|
return format(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createETag(body: any, encoding: string | undefined) {
|
||||||
|
const etag = require('etag');
|
||||||
|
const buf = !Buffer.isBuffer(body) ? Buffer.from(body, encoding) : body;
|
||||||
|
return etag(buf, { weak: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
function send(req: NowRequest, res: NowResponse, body: any): NowResponse {
|
||||||
|
let chunk: unknown = body;
|
||||||
|
let encoding: string | undefined;
|
||||||
|
|
||||||
|
switch (typeof chunk) {
|
||||||
|
// string defaulting to html
|
||||||
|
case 'string':
|
||||||
|
if (!res.getHeader('content-type')) {
|
||||||
|
res.setHeader('content-type', 'text/html');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'boolean':
|
||||||
|
case 'number':
|
||||||
|
case 'object':
|
||||||
|
if (chunk === null) {
|
||||||
|
chunk = '';
|
||||||
|
} else if (Buffer.isBuffer(chunk)) {
|
||||||
|
if (!res.getHeader('content-type')) {
|
||||||
|
res.setHeader('content-type', 'application/octet-stream');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return json(req, res, chunk);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// write strings in utf-8
|
||||||
|
if (typeof chunk === 'string') {
|
||||||
|
encoding = 'utf8';
|
||||||
|
|
||||||
|
// reflect this in content-type
|
||||||
|
const type = res.getHeader('content-type');
|
||||||
|
if (typeof type === 'string') {
|
||||||
|
res.setHeader('content-type', setCharset(type, 'utf-8'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// populate Content-Length
|
||||||
|
let len: number | undefined;
|
||||||
|
if (chunk !== undefined) {
|
||||||
|
if (Buffer.isBuffer(chunk)) {
|
||||||
|
// get length of Buffer
|
||||||
|
len = chunk.length;
|
||||||
|
} else if (typeof chunk === 'string') {
|
||||||
|
if (chunk.length < 1000) {
|
||||||
|
// just calculate length small chunk
|
||||||
|
len = Buffer.byteLength(chunk, encoding);
|
||||||
|
} else {
|
||||||
|
// convert chunk to Buffer and calculate
|
||||||
|
const buf = Buffer.from(chunk, encoding);
|
||||||
|
len = buf.length;
|
||||||
|
chunk = buf;
|
||||||
|
encoding = undefined;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
'`body` is not a valid string, object, boolean, number, Stream, or Buffer'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len !== undefined) {
|
||||||
|
res.setHeader('content-length', len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// populate ETag
|
||||||
|
let etag: string | undefined;
|
||||||
|
if (
|
||||||
|
!res.getHeader('etag') &&
|
||||||
|
len !== undefined &&
|
||||||
|
(etag = createETag(chunk, encoding))
|
||||||
|
) {
|
||||||
|
res.setHeader('etag', etag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// strip irrelevant headers
|
||||||
|
if (204 === res.statusCode || 304 === res.statusCode) {
|
||||||
|
res.removeHeader('Content-Type');
|
||||||
|
res.removeHeader('Content-Length');
|
||||||
|
res.removeHeader('Transfer-Encoding');
|
||||||
|
chunk = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.method === 'HEAD') {
|
||||||
|
// skip body for HEAD
|
||||||
res.end();
|
res.end();
|
||||||
return res;
|
} else {
|
||||||
|
// respond
|
||||||
|
res.end(chunk, encoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentType = res.getHeader('Content-Type');
|
|
||||||
|
|
||||||
if (Buffer.isBuffer(body)) {
|
|
||||||
if (!contentType) {
|
|
||||||
res.setHeader('Content-Type', 'application/octet-stream');
|
|
||||||
}
|
|
||||||
res.setHeader('Content-Length', body.length);
|
|
||||||
res.end(body);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (body instanceof Stream) {
|
|
||||||
if (!contentType) {
|
|
||||||
res.setHeader('Content-Type', 'application/octet-stream');
|
|
||||||
}
|
|
||||||
body.pipe(res);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
let str = body;
|
|
||||||
|
|
||||||
// Stringify JSON body
|
|
||||||
if (typeof body === 'object' || typeof body === 'number') {
|
|
||||||
str = JSON.stringify(body);
|
|
||||||
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
||||||
}
|
|
||||||
|
|
||||||
res.setHeader('Content-Length', Buffer.byteLength(str));
|
|
||||||
res.end(str);
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendJson(res: NowResponse, jsonBody: any): NowResponse {
|
function json(req: NowRequest, res: NowResponse, jsonBody: any): NowResponse {
|
||||||
// Set header to application/json
|
const body = JSON.stringify(jsonBody);
|
||||||
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
||||||
|
|
||||||
// Use send to handle request
|
// content-type
|
||||||
return res.send(jsonBody);
|
if (!res.getHeader('content-type')) {
|
||||||
|
res.setHeader('content-type', 'application/json; charset=utf-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
return send(req, res, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ApiError extends Error {
|
export class ApiError extends Error {
|
||||||
@@ -186,9 +256,9 @@ export function createServerWithHelpers(
|
|||||||
setLazyProp<NowRequestQuery>(req, 'query', getQueryParser(req));
|
setLazyProp<NowRequestQuery>(req, 'query', getQueryParser(req));
|
||||||
setLazyProp<NowRequestBody>(req, 'body', getBodyParser(req, event.body));
|
setLazyProp<NowRequestBody>(req, 'body', getBodyParser(req, event.body));
|
||||||
|
|
||||||
res.status = statusCode => sendStatusCode(res, statusCode);
|
res.status = statusCode => status(res, statusCode);
|
||||||
res.send = data => sendData(res, data);
|
res.send = body => send(req, res, body);
|
||||||
res.json = data => sendJson(res, data);
|
res.json = jsonBody => json(req, res, jsonBody);
|
||||||
|
|
||||||
await listener(req, res);
|
await listener(req, res);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { readFile } from 'fs-extra';
|
|
||||||
import { Assets, NccOptions } from '@zeit/ncc';
|
import { Assets, NccOptions } from '@zeit/ncc';
|
||||||
import { join, dirname, relative, sep } from 'path';
|
import { join, dirname, relative, sep } from 'path';
|
||||||
import { NccWatcher, WatcherResult } from '@zeit/ncc-watcher';
|
import { NccWatcher, WatcherResult } from '@zeit/ncc-watcher';
|
||||||
@@ -12,11 +11,14 @@ import {
|
|||||||
createLambda,
|
createLambda,
|
||||||
runNpmInstall,
|
runNpmInstall,
|
||||||
runPackageJsonScript,
|
runPackageJsonScript,
|
||||||
|
getNodeVersion,
|
||||||
|
getSpawnOptions,
|
||||||
PrepareCacheOptions,
|
PrepareCacheOptions,
|
||||||
BuildOptions,
|
BuildOptions,
|
||||||
shouldServe,
|
shouldServe,
|
||||||
} from '@now/build-utils';
|
} from '@now/build-utils';
|
||||||
export { NowRequest, NowResponse } from './types';
|
export { NowRequest, NowResponse } from './types';
|
||||||
|
import { makeLauncher } from './launcher';
|
||||||
|
|
||||||
interface CompilerConfig {
|
interface CompilerConfig {
|
||||||
includeFiles?: string | string[];
|
includeFiles?: string | string[];
|
||||||
@@ -31,6 +33,10 @@ interface DownloadOptions {
|
|||||||
|
|
||||||
const watchers: Map<string, NccWatcher> = new Map();
|
const watchers: Map<string, NccWatcher> = new Map();
|
||||||
|
|
||||||
|
const LAUNCHER_FILENAME = '___now_launcher';
|
||||||
|
const BRIDGE_FILENAME = '___now_bridge';
|
||||||
|
const HELPERS_FILENAME = '___now_helpers';
|
||||||
|
|
||||||
function getWatcher(entrypoint: string, options: NccOptions): NccWatcher {
|
function getWatcher(entrypoint: string, options: NccOptions): NccWatcher {
|
||||||
let watcher = watchers.get(entrypoint);
|
let watcher = watchers.get(entrypoint);
|
||||||
if (!watcher) {
|
if (!watcher) {
|
||||||
@@ -58,10 +64,12 @@ async function downloadInstallAndBundle({
|
|||||||
|
|
||||||
console.log("installing dependencies for user's code...");
|
console.log("installing dependencies for user's code...");
|
||||||
const entrypointFsDirname = join(workPath, dirname(entrypoint));
|
const entrypointFsDirname = join(workPath, dirname(entrypoint));
|
||||||
await runNpmInstall(entrypointFsDirname, ['--prefer-offline']);
|
const nodeVersion = await getNodeVersion(entrypointFsDirname);
|
||||||
|
const spawnOpts = getSpawnOptions(meta, nodeVersion);
|
||||||
|
await runNpmInstall(entrypointFsDirname, ['--prefer-offline'], spawnOpts);
|
||||||
|
|
||||||
const entrypointPath = downloadedFiles[entrypoint].fsPath;
|
const entrypointPath = downloadedFiles[entrypoint].fsPath;
|
||||||
return { entrypointPath, entrypointFsDirname };
|
return { entrypointPath, entrypointFsDirname, nodeVersion, spawnOpts };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function compile(
|
async function compile(
|
||||||
@@ -97,7 +105,8 @@ async function compile(
|
|||||||
assets = result.assets;
|
assets = result.assets;
|
||||||
watch = [...result.files, ...result.dirs, ...result.missing]
|
watch = [...result.files, ...result.dirs, ...result.missing]
|
||||||
.filter(f => f.startsWith(workPath))
|
.filter(f => f.startsWith(workPath))
|
||||||
.map(f => relative(workPath, f));
|
.map(f => relative(workPath, f))
|
||||||
|
.concat(Object.keys(assets || {}));
|
||||||
} else {
|
} else {
|
||||||
const ncc = require('@zeit/ncc');
|
const ncc = require('@zeit/ncc');
|
||||||
const result = await ncc(input, {
|
const result = await ncc(input, {
|
||||||
@@ -178,6 +187,8 @@ export async function build({
|
|||||||
const {
|
const {
|
||||||
entrypointPath,
|
entrypointPath,
|
||||||
entrypointFsDirname,
|
entrypointFsDirname,
|
||||||
|
nodeVersion,
|
||||||
|
spawnOpts,
|
||||||
} = await downloadInstallAndBundle({
|
} = await downloadInstallAndBundle({
|
||||||
files,
|
files,
|
||||||
entrypoint,
|
entrypoint,
|
||||||
@@ -186,7 +197,7 @@ export async function build({
|
|||||||
});
|
});
|
||||||
|
|
||||||
console.log('running user script...');
|
console.log('running user script...');
|
||||||
await runPackageJsonScript(entrypointFsDirname, 'now-build');
|
await runPackageJsonScript(entrypointFsDirname, 'now-build', spawnOpts);
|
||||||
|
|
||||||
console.log('compiling entrypoint with ncc...');
|
console.log('compiling entrypoint with ncc...');
|
||||||
const { preparedFiles, watch } = await compile(
|
const { preparedFiles, watch } = await compile(
|
||||||
@@ -196,44 +207,37 @@ export async function build({
|
|||||||
config,
|
config,
|
||||||
meta
|
meta
|
||||||
);
|
);
|
||||||
const launcherPath = join(__dirname, 'launcher.js');
|
|
||||||
let launcherData = await readFile(launcherPath, 'utf8');
|
|
||||||
|
|
||||||
launcherData = launcherData.replace(
|
|
||||||
'// PLACEHOLDER:shouldStoreProxyRequests',
|
|
||||||
shouldAddHelpers ? 'shouldStoreProxyRequests = true;' : ''
|
|
||||||
);
|
|
||||||
|
|
||||||
launcherData = launcherData.replace(
|
|
||||||
'// PLACEHOLDER:setServer',
|
|
||||||
[
|
|
||||||
`let listener = require("./${entrypoint}");`,
|
|
||||||
'if (listener.default) listener = listener.default;',
|
|
||||||
shouldAddHelpers
|
|
||||||
? 'const server = require("./helpers").createServerWithHelpers(listener, bridge);'
|
|
||||||
: 'const server = require("http").createServer(listener);',
|
|
||||||
'bridge.setServer(server);',
|
|
||||||
].join(' ')
|
|
||||||
);
|
|
||||||
|
|
||||||
const launcherFiles: Files = {
|
const launcherFiles: Files = {
|
||||||
'launcher.js': new FileBlob({ data: launcherData }),
|
[`${LAUNCHER_FILENAME}.js`]: new FileBlob({
|
||||||
'bridge.js': new FileFsRef({ fsPath: require('@now/node-bridge') }),
|
data: makeLauncher({
|
||||||
|
entrypointPath: `./${entrypoint}`,
|
||||||
|
bridgePath: `./${BRIDGE_FILENAME}`,
|
||||||
|
helpersPath: `./${HELPERS_FILENAME}`,
|
||||||
|
shouldAddHelpers,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
[`${BRIDGE_FILENAME}.js`]: new FileFsRef({
|
||||||
|
fsPath: require('@now/node-bridge'),
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (shouldAddHelpers) {
|
if (shouldAddHelpers) {
|
||||||
launcherFiles['helpers.js'] = new FileFsRef({
|
launcherFiles[`${HELPERS_FILENAME}.js`] = new FileFsRef({
|
||||||
fsPath: join(__dirname, 'helpers.js'),
|
fsPath: join(__dirname, 'helpers.js'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use the system-installed version of `node` when running via `now dev`
|
||||||
|
const runtime = meta.isDev ? 'nodejs' : nodeVersion.runtime;
|
||||||
|
|
||||||
const lambda = await createLambda({
|
const lambda = await createLambda({
|
||||||
files: {
|
files: {
|
||||||
...preparedFiles,
|
...preparedFiles,
|
||||||
...launcherFiles,
|
...launcherFiles,
|
||||||
},
|
},
|
||||||
handler: 'launcher.launcher',
|
handler: `${LAUNCHER_FILENAME}.launcher`,
|
||||||
runtime: 'nodejs8.10',
|
runtime,
|
||||||
});
|
});
|
||||||
|
|
||||||
const output = { [entrypoint]: lambda };
|
const output = { [entrypoint]: lambda };
|
||||||
|
|||||||
@@ -1,9 +1,29 @@
|
|||||||
import { Bridge } from './bridge';
|
type LauncherConfiguration = {
|
||||||
|
entrypointPath: string;
|
||||||
|
bridgePath: string;
|
||||||
|
helpersPath: string;
|
||||||
|
shouldAddHelpers?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
let shouldStoreProxyRequests: boolean = false;
|
export function makeLauncher({
|
||||||
// PLACEHOLDER:shouldStoreProxyRequests
|
entrypointPath,
|
||||||
|
bridgePath,
|
||||||
|
helpersPath,
|
||||||
|
shouldAddHelpers = false,
|
||||||
|
}: LauncherConfiguration): string {
|
||||||
|
return `const { Bridge } = require("${bridgePath}");
|
||||||
|
const { Server } = require("http");
|
||||||
|
|
||||||
const bridge = new Bridge(undefined, shouldStoreProxyRequests);
|
let isServerListening = false;
|
||||||
|
let bridge = new Bridge();
|
||||||
|
const saveListen = Server.prototype.listen;
|
||||||
|
Server.prototype.listen = function listen() {
|
||||||
|
isServerListening = true;
|
||||||
|
console.log('Legacy server listening...');
|
||||||
|
bridge.setServer(this);
|
||||||
|
Server.prototype.listen = saveListen;
|
||||||
|
return bridge.listen();
|
||||||
|
};
|
||||||
|
|
||||||
if (!process.env.NODE_ENV) {
|
if (!process.env.NODE_ENV) {
|
||||||
process.env.NODE_ENV =
|
process.env.NODE_ENV =
|
||||||
@@ -11,13 +31,43 @@ if (!process.env.NODE_ENV) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// PLACEHOLDER:setServer
|
let listener = require("${entrypointPath}");
|
||||||
|
if (listener.default) listener = listener.default;
|
||||||
|
|
||||||
|
if (typeof listener.listen === 'function') {
|
||||||
|
Server.prototype.listen = saveListen;
|
||||||
|
const server = listener;
|
||||||
|
bridge.setServer(server);
|
||||||
|
bridge.listen();
|
||||||
|
} else if (typeof listener === 'function') {
|
||||||
|
Server.prototype.listen = saveListen;
|
||||||
|
let server;
|
||||||
|
${
|
||||||
|
shouldAddHelpers
|
||||||
|
? [
|
||||||
|
'bridge = new Bridge(undefined, true);',
|
||||||
|
`server = require("${helpersPath}").createServerWithHelpers(listener, bridge);`,
|
||||||
|
].join('\n')
|
||||||
|
: ['server = require("http").createServer(listener);'].join('\n')
|
||||||
|
}
|
||||||
|
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 "${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 "${entrypointPath}".');
|
||||||
|
console.error('The default export must be a function or server.');
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code === 'MODULE_NOT_FOUND') {
|
if (err.code === 'MODULE_NOT_FOUND') {
|
||||||
console.error(err.message);
|
console.error(err.message);
|
||||||
console.error(
|
console.error('Did you forget to add it to "dependencies" in \`package.json\`?');
|
||||||
'Did you forget to add it to "dependencies" in `package.json`?'
|
|
||||||
);
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
} else {
|
} else {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@@ -25,6 +75,5 @@ try {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bridge.listen();
|
exports.launcher = bridge.launcher;`;
|
||||||
|
}
|
||||||
exports.launcher = bridge.launcher;
|
|
||||||
|
|||||||
@@ -12,6 +12,6 @@ export type NowRequest = IncomingMessage & {
|
|||||||
|
|
||||||
export type NowResponse = ServerResponse & {
|
export type NowResponse = ServerResponse & {
|
||||||
send: (body: any) => NowResponse;
|
send: (body: any) => NowResponse;
|
||||||
json: (body: any) => NowResponse;
|
json: (jsonBody: any) => NowResponse;
|
||||||
status: (statusCode: number) => NowResponse;
|
status: (statusCode: number) => NowResponse;
|
||||||
};
|
};
|
||||||
|
|||||||
25
packages/now-node/test/fixtures/02-node-server/hapi-async/index.js
vendored
Normal file
25
packages/now-node/test/fixtures/02-node-server/hapi-async/index.js
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
const Hapi = require('@hapi/hapi');
|
||||||
|
|
||||||
|
const init = async () => {
|
||||||
|
const server = Hapi.server({
|
||||||
|
port: 3000,
|
||||||
|
host: 'localhost',
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: 'GET',
|
||||||
|
path: '/{p*}',
|
||||||
|
handler: () => 'hapi-async:RANDOMNESS_PLACEHOLDER',
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.start();
|
||||||
|
console.log('Hapi server running on %s', server.info.uri);
|
||||||
|
};
|
||||||
|
|
||||||
|
process.on('unhandledRejection', (err) => {
|
||||||
|
console.log('Hapi failed in an unexpected way');
|
||||||
|
console.log(err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
init();
|
||||||
5
packages/now-node/test/fixtures/02-node-server/hapi-async/package.json
vendored
Normal file
5
packages/now-node/test/fixtures/02-node-server/hapi-async/package.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"@hapi/hapi": "18.3.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
7
packages/now-node/test/fixtures/02-node-server/index.js
vendored
Normal file
7
packages/now-node/test/fixtures/02-node-server/index.js
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
const http = require('http');
|
||||||
|
|
||||||
|
const server = http.createServer((req, resp) => {
|
||||||
|
resp.end('root:RANDOMNESS_PLACEHOLDER');
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen();
|
||||||
15
packages/now-node/test/fixtures/02-node-server/now.json
vendored
Normal file
15
packages/now-node/test/fixtures/02-node-server/now.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"builds": [{ "src": "**/*.js", "use": "@now/node" }],
|
||||||
|
"probes": [
|
||||||
|
{ "path": "/", "mustContain": "root:RANDOMNESS_PLACEHOLDER" },
|
||||||
|
{
|
||||||
|
"path": "/subdirectory/",
|
||||||
|
"mustContain": "subdir:RANDOMNESS_PLACEHOLDER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/hapi-async/",
|
||||||
|
"mustContain": "hapi-async:RANDOMNESS_PLACEHOLDER"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
7
packages/now-node/test/fixtures/02-node-server/subdirectory/index.js
vendored
Normal file
7
packages/now-node/test/fixtures/02-node-server/subdirectory/index.js
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
const http = require('http');
|
||||||
|
|
||||||
|
const server = http.createServer((req, resp) => {
|
||||||
|
resp.end('subdir:RANDOMNESS_PLACEHOLDER');
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen();
|
||||||
3
packages/now-node/test/fixtures/10-node-engines/empty/index.js
vendored
Normal file
3
packages/now-node/test/fixtures/10-node-engines/empty/index.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = (req, res) => {
|
||||||
|
res.end(`RANDOMNESS_PLACEHOLDER:${process.versions.node}`);
|
||||||
|
};
|
||||||
3
packages/now-node/test/fixtures/10-node-engines/empty/package.json
vendored
Normal file
3
packages/now-node/test/fixtures/10-node-engines/empty/package.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"name": "missing-engines-key-on-purpose"
|
||||||
|
}
|
||||||
3
packages/now-node/test/fixtures/10-node-engines/exact/index.js
vendored
Normal file
3
packages/now-node/test/fixtures/10-node-engines/exact/index.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = (req, res) => {
|
||||||
|
res.end(`RANDOMNESS_PLACEHOLDER:${process.versions.node}`);
|
||||||
|
};
|
||||||
5
packages/now-node/test/fixtures/10-node-engines/exact/package.json
vendored
Normal file
5
packages/now-node/test/fixtures/10-node-engines/exact/package.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"engines": {
|
||||||
|
"node": "10.5.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
3
packages/now-node/test/fixtures/10-node-engines/greater/index.js
vendored
Normal file
3
packages/now-node/test/fixtures/10-node-engines/greater/index.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = (req, res) => {
|
||||||
|
res.end(`RANDOMNESS_PLACEHOLDER:${process.versions.node}`);
|
||||||
|
};
|
||||||
5
packages/now-node/test/fixtures/10-node-engines/greater/package.json
vendored
Normal file
5
packages/now-node/test/fixtures/10-node-engines/greater/package.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
3
packages/now-node/test/fixtures/10-node-engines/major/index.js
vendored
Normal file
3
packages/now-node/test/fixtures/10-node-engines/major/index.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = (req, res) => {
|
||||||
|
res.end(`RANDOMNESS_PLACEHOLDER:${process.versions.node}`);
|
||||||
|
};
|
||||||
5
packages/now-node/test/fixtures/10-node-engines/major/package.json
vendored
Normal file
5
packages/now-node/test/fixtures/10-node-engines/major/package.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"engines": {
|
||||||
|
"node": "10.x"
|
||||||
|
}
|
||||||
|
}
|
||||||
11
packages/now-node/test/fixtures/10-node-engines/now.json
vendored
Normal file
11
packages/now-node/test/fixtures/10-node-engines/now.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"builds": [{ "src": "**/*.js", "use": "@now/node" }],
|
||||||
|
"probes": [
|
||||||
|
{ "path": "/empty", "mustContain": "RANDOMNESS_PLACEHOLDER:8" },
|
||||||
|
{ "path": "/exact", "mustContain": "RANDOMNESS_PLACEHOLDER:10" },
|
||||||
|
{ "path": "/greater", "mustContain": "RANDOMNESS_PLACEHOLDER:10" },
|
||||||
|
{ "path": "/major", "mustContain": "RANDOMNESS_PLACEHOLDER:10" },
|
||||||
|
{ "path": "/range", "mustContain": "RANDOMNESS_PLACEHOLDER:10" }
|
||||||
|
]
|
||||||
|
}
|
||||||
3
packages/now-node/test/fixtures/10-node-engines/range/index.js
vendored
Normal file
3
packages/now-node/test/fixtures/10-node-engines/range/index.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = (req, res) => {
|
||||||
|
res.end(`RANDOMNESS_PLACEHOLDER:${process.versions.node}`);
|
||||||
|
};
|
||||||
5
packages/now-node/test/fixtures/10-node-engines/range/package.json
vendored
Normal file
5
packages/now-node/test/fixtures/10-node-engines/range/package.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"engines": {
|
||||||
|
"node": "10.x"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
/* eslint-disable prefer-destructuring */
|
|
||||||
const express = require('express');
|
|
||||||
|
|
||||||
const app = express();
|
|
||||||
|
|
||||||
module.exports = app;
|
|
||||||
|
|
||||||
app.use(express.json());
|
|
||||||
|
|
||||||
app.all('*', (req, res) => {
|
|
||||||
res.status(200);
|
|
||||||
|
|
||||||
let who = 'anonymous';
|
|
||||||
|
|
||||||
if (req.body && req.body.who) {
|
|
||||||
who = req.body.who;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.send(`hello ${who}:RANDOMNESS_PLACEHOLDER`);
|
|
||||||
});
|
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
"builds": [
|
"builds": [
|
||||||
{ "src": "index.js", "use": "@now/node" },
|
{ "src": "index.js", "use": "@now/node" },
|
||||||
{ "src": "ts/index.ts", "use": "@now/node" },
|
{ "src": "ts/index.ts", "use": "@now/node" },
|
||||||
{ "src": "express-compat/index.js", "use": "@now/node" },
|
|
||||||
{ "src": "micro-compat/index.js", "use": "@now/node" },
|
{ "src": "micro-compat/index.js", "use": "@now/node" },
|
||||||
{
|
{
|
||||||
"src": "no-helpers/index.js",
|
"src": "no-helpers/index.js",
|
||||||
@@ -35,12 +34,6 @@
|
|||||||
"path": "/ts",
|
"path": "/ts",
|
||||||
"mustContain": "hello:RANDOMNESS_PLACEHOLDER"
|
"mustContain": "hello:RANDOMNESS_PLACEHOLDER"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"path": "/express-compat",
|
|
||||||
"method": "POST",
|
|
||||||
"body": { "who": "sara" },
|
|
||||||
"mustContain": "hello sara:RANDOMNESS_PLACEHOLDER"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"path": "/micro-compat",
|
"path": "/micro-compat",
|
||||||
"method": "POST",
|
"method": "POST",
|
||||||
|
|||||||
9
packages/now-node/test/fixtures/16-servers/express/index.js
vendored
Normal file
9
packages/now-node/test/fixtures/16-servers/express/index.js
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
const express = require('express');
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
app.all('*', (req, res) => {
|
||||||
|
res.send('hello from express:RANDOMNESS_PLACEHOLDER');
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = app;
|
||||||
17
packages/now-node/test/fixtures/16-servers/hapi/index.js
vendored
Normal file
17
packages/now-node/test/fixtures/16-servers/hapi/index.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
const Hapi = require('@hapi/hapi');
|
||||||
|
|
||||||
|
const server = Hapi.server({
|
||||||
|
port: 3000,
|
||||||
|
host: 'localhost',
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: 'GET',
|
||||||
|
path: '/{p*}',
|
||||||
|
handler: () => 'hello from hapi:RANDOMNESS_PLACEHOLDER',
|
||||||
|
});
|
||||||
|
|
||||||
|
// server.listener is a node's http.Server
|
||||||
|
// server does not have the `listen` method so we need to export this instead
|
||||||
|
|
||||||
|
module.exports = server.listener;
|
||||||
5
packages/now-node/test/fixtures/16-servers/hapi/package.json
vendored
Normal file
5
packages/now-node/test/fixtures/16-servers/hapi/package.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"@hapi/hapi": "18.3.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
7
packages/now-node/test/fixtures/16-servers/index.js
vendored
Normal file
7
packages/now-node/test/fixtures/16-servers/index.js
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
const http = require('http');
|
||||||
|
|
||||||
|
const server = http.createServer((req, res) => {
|
||||||
|
res.end('hello:RANDOMNESS_PLACEHOLDER');
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = server;
|
||||||
9
packages/now-node/test/fixtures/16-servers/koa/index.js
vendored
Normal file
9
packages/now-node/test/fixtures/16-servers/koa/index.js
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
const Koa = require('koa');
|
||||||
|
|
||||||
|
const app = new Koa();
|
||||||
|
|
||||||
|
app.use(async (ctx) => {
|
||||||
|
ctx.body = 'hello from koa:RANDOMNESS_PLACEHOLDER';
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = app;
|
||||||
5
packages/now-node/test/fixtures/16-servers/koa/package.json
vendored
Normal file
5
packages/now-node/test/fixtures/16-servers/koa/package.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"koa": "2.7.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
22
packages/now-node/test/fixtures/16-servers/now.json
vendored
Normal file
22
packages/now-node/test/fixtures/16-servers/now.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"builds": [{ "src": "**/*.js", "use": "@now/node" }],
|
||||||
|
"probes": [
|
||||||
|
{
|
||||||
|
"path": "/",
|
||||||
|
"mustContain": "hello:RANDOMNESS_PLACEHOLDER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/express",
|
||||||
|
"mustContain": "hello from express:RANDOMNESS_PLACEHOLDER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/koa",
|
||||||
|
"mustContain": "hello from koa:RANDOMNESS_PLACEHOLDER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/hapi",
|
||||||
|
"mustContain": "hello from hapi:RANDOMNESS_PLACEHOLDER"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
3
packages/now-node/test/fixtures/17-entrypoint-name-conflict/bridge.js
vendored
Normal file
3
packages/now-node/test/fixtures/17-entrypoint-name-conflict/bridge.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = (req, res) => {
|
||||||
|
res.end('bridge:RANDOMNESS_PLACEHOLDER');
|
||||||
|
};
|
||||||
3
packages/now-node/test/fixtures/17-entrypoint-name-conflict/helpers.js
vendored
Normal file
3
packages/now-node/test/fixtures/17-entrypoint-name-conflict/helpers.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = (req, res) => {
|
||||||
|
res.end('helpers:RANDOMNESS_PLACEHOLDER');
|
||||||
|
};
|
||||||
3
packages/now-node/test/fixtures/17-entrypoint-name-conflict/launcher.js
vendored
Normal file
3
packages/now-node/test/fixtures/17-entrypoint-name-conflict/launcher.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = (req, res) => {
|
||||||
|
res.end('launcher:RANDOMNESS_PLACEHOLDER');
|
||||||
|
};
|
||||||
18
packages/now-node/test/fixtures/17-entrypoint-name-conflict/now.json
vendored
Normal file
18
packages/now-node/test/fixtures/17-entrypoint-name-conflict/now.json
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"builds": [{ "src": "*.js", "use": "@now/node" }],
|
||||||
|
"probes": [
|
||||||
|
{
|
||||||
|
"path": "/helpers.js",
|
||||||
|
"mustContain": "helpers:RANDOMNESS_PLACEHOLDER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/bridge.js",
|
||||||
|
"mustContain": "bridge:RANDOMNESS_PLACEHOLDER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/launcher.js",
|
||||||
|
"mustContain": "launcher:RANDOMNESS_PLACEHOLDER"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,28 +1,17 @@
|
|||||||
/* global beforeAll, beforeEach, afterAll, expect, it, jest */
|
/* global beforeEach, afterEach, expect, it, jest */
|
||||||
const fetch = require('node-fetch');
|
const fetch = require('node-fetch');
|
||||||
const listen = require('test-listen');
|
const listen = require('test-listen');
|
||||||
const qs = require('querystring');
|
const qs = require('querystring');
|
||||||
|
|
||||||
const { createServerWithHelpers } = require('../dist/helpers');
|
const { createServerWithHelpers } = require('../dist/helpers');
|
||||||
|
|
||||||
const mockListener = jest.fn((req, res) => {
|
const mockListener = jest.fn();
|
||||||
res.send('hello');
|
const consumeEventMock = jest.fn();
|
||||||
});
|
|
||||||
const consumeEventMock = jest.fn(() => ({}));
|
|
||||||
const mockBridge = { consumeEvent: consumeEventMock };
|
const mockBridge = { consumeEvent: consumeEventMock };
|
||||||
|
|
||||||
let server;
|
let server;
|
||||||
let url;
|
let url;
|
||||||
|
|
||||||
const nowProps = [
|
|
||||||
['query', 0],
|
|
||||||
['cookies', 0],
|
|
||||||
['body', 0],
|
|
||||||
['status', 1],
|
|
||||||
['send', 1],
|
|
||||||
['json', 1],
|
|
||||||
];
|
|
||||||
|
|
||||||
async function fetchWithProxyReq(_url, opts = {}) {
|
async function fetchWithProxyReq(_url, opts = {}) {
|
||||||
if (opts.body) {
|
if (opts.body) {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
@@ -37,51 +26,131 @@ async function fetchWithProxyReq(_url, opts = {}) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeEach(async () => {
|
||||||
|
mockListener.mockClear();
|
||||||
|
consumeEventMock.mockClear();
|
||||||
|
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send('hello');
|
||||||
|
});
|
||||||
|
consumeEventMock.mockImplementation(() => ({}));
|
||||||
|
|
||||||
server = createServerWithHelpers(mockListener, mockBridge);
|
server = createServerWithHelpers(mockListener, mockBridge);
|
||||||
url = await listen(server);
|
url = await listen(server);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
afterEach(async () => {
|
||||||
mockListener.mockClear();
|
|
||||||
consumeEventMock.mockClear();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await server.close();
|
await server.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call consumeEvent with the correct reqId', async () => {
|
describe('contract with @now/node-bridge', () => {
|
||||||
|
test('should call consumeEvent with the correct reqId', async () => {
|
||||||
await fetchWithProxyReq(`${url}/`);
|
await fetchWithProxyReq(`${url}/`);
|
||||||
|
|
||||||
expect(consumeEventMock).toHaveBeenLastCalledWith('2');
|
expect(consumeEventMock).toHaveBeenLastCalledWith('2');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not expose the request id header', async () => {
|
test('should not expose the request id header', async () => {
|
||||||
await fetchWithProxyReq(`${url}/`, { headers: { 'x-test-header': 'ok' } });
|
await fetchWithProxyReq(`${url}/`, { headers: { 'x-test-header': 'ok' } });
|
||||||
|
|
||||||
const [{ headers }] = mockListener.mock.calls[0];
|
const [{ headers }] = mockListener.mock.calls[0];
|
||||||
|
|
||||||
expect(headers['x-now-bridge-request-id']).toBeUndefined();
|
expect(headers['x-now-bridge-request-id']).toBeUndefined();
|
||||||
expect(headers['x-test-header']).toBe('ok');
|
expect(headers['x-test-header']).toBe('ok');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('req.query should reflect querystring in the url', async () => {
|
describe('all helpers', () => {
|
||||||
|
const nowHelpers = [
|
||||||
|
['query', 0],
|
||||||
|
['cookies', 0],
|
||||||
|
['body', 0],
|
||||||
|
['status', 1],
|
||||||
|
['send', 1],
|
||||||
|
['json', 1],
|
||||||
|
];
|
||||||
|
|
||||||
|
test('should not recalculate req properties twice', async () => {
|
||||||
|
const spy = jest.fn(() => {});
|
||||||
|
|
||||||
|
const nowReqHelpers = nowHelpers.filter(([, i]) => i === 0);
|
||||||
|
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
spy(...nowReqHelpers.map(h => req[h]));
|
||||||
|
spy(...nowReqHelpers.map(h => req[h]));
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
await fetchWithProxyReq(`${url}/?who=bill`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ who: 'mike' }),
|
||||||
|
headers: { 'content-type': 'application/json', cookie: 'who=jim' },
|
||||||
|
});
|
||||||
|
|
||||||
|
// here we test that bodySpy is called twice with exactly the same arguments
|
||||||
|
for (let i = 0; i < 3; i += 1) {
|
||||||
|
expect(spy.mock.calls[0][i]).toBe(spy.mock.calls[1][i]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should be able to overwrite request properties', async () => {
|
||||||
|
const spy = jest.fn(() => {});
|
||||||
|
|
||||||
|
mockListener.mockImplementation((...args) => {
|
||||||
|
nowHelpers.forEach(([prop, n]) => {
|
||||||
|
/* eslint-disable */
|
||||||
|
args[n][prop] = 'ok';
|
||||||
|
args[n][prop] = 'ok2';
|
||||||
|
spy(args[n][prop]);
|
||||||
|
});
|
||||||
|
|
||||||
|
args[1].end();
|
||||||
|
});
|
||||||
|
|
||||||
|
await fetchWithProxyReq(url);
|
||||||
|
|
||||||
|
nowHelpers.forEach((_, i) => expect(spy.mock.calls[i][0]).toBe('ok2'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should be able to reconfig request properties', async () => {
|
||||||
|
const spy = jest.fn(() => {});
|
||||||
|
|
||||||
|
mockListener.mockImplementation((...args) => {
|
||||||
|
nowHelpers.forEach(([prop, n]) => {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
Object.defineProperty(args[n], prop, { value: 'ok' });
|
||||||
|
Object.defineProperty(args[n], prop, { value: 'ok2' });
|
||||||
|
spy(args[n][prop]);
|
||||||
|
});
|
||||||
|
|
||||||
|
args[1].end();
|
||||||
|
});
|
||||||
|
|
||||||
|
await fetchWithProxyReq(url);
|
||||||
|
|
||||||
|
nowHelpers.forEach((_, i) => expect(spy.mock.calls[i][0]).toBe('ok2'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('req.query', () => {
|
||||||
|
test('req.query should reflect querystring in the url', async () => {
|
||||||
await fetchWithProxyReq(`${url}/?who=bill&where=us`);
|
await fetchWithProxyReq(`${url}/?who=bill&where=us`);
|
||||||
|
|
||||||
expect(mockListener.mock.calls[0][0].query).toMatchObject({
|
expect(mockListener.mock.calls[0][0].query).toMatchObject({
|
||||||
who: 'bill',
|
who: 'bill',
|
||||||
where: 'us',
|
where: 'us',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('req.query should be {} when there is no querystring', async () => {
|
test('req.query should be {} when there is no querystring', async () => {
|
||||||
await fetchWithProxyReq(url);
|
await fetchWithProxyReq(url);
|
||||||
const [{ query }] = mockListener.mock.calls[0];
|
const [{ query }] = mockListener.mock.calls[0];
|
||||||
expect(Object.keys(query).length).toBe(0);
|
expect(Object.keys(query).length).toBe(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('req.cookies should reflect req.cookie header', async () => {
|
describe('req.cookies', () => {
|
||||||
|
test('req.cookies should reflect req.cookie header', async () => {
|
||||||
await fetchWithProxyReq(url, {
|
await fetchWithProxyReq(url, {
|
||||||
headers: {
|
headers: {
|
||||||
cookie: 'who=bill; where=us',
|
cookie: 'who=bill; where=us',
|
||||||
@@ -92,22 +161,24 @@ it('req.cookies should reflect req.cookie header', async () => {
|
|||||||
who: 'bill',
|
who: 'bill',
|
||||||
where: 'us',
|
where: 'us',
|
||||||
});
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('req.body should be undefined by default', async () => {
|
describe('req.body', () => {
|
||||||
|
test('req.body should be undefined by default', async () => {
|
||||||
await fetchWithProxyReq(url);
|
await fetchWithProxyReq(url);
|
||||||
expect(mockListener.mock.calls[0][0].body).toBe(undefined);
|
expect(mockListener.mock.calls[0][0].body).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('req.body should be undefined if content-type is not defined', async () => {
|
test('req.body should be undefined if content-type is not defined', async () => {
|
||||||
await fetchWithProxyReq(url, {
|
await fetchWithProxyReq(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: 'hello',
|
body: 'hello',
|
||||||
});
|
});
|
||||||
expect(mockListener.mock.calls[0][0].body).toBe(undefined);
|
expect(mockListener.mock.calls[0][0].body).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('req.body should be a string when content-type is `text/plain`', async () => {
|
test('req.body should be a string when content-type is `text/plain`', async () => {
|
||||||
await fetchWithProxyReq(url, {
|
await fetchWithProxyReq(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: 'hello',
|
body: 'hello',
|
||||||
@@ -115,9 +186,9 @@ it('req.body should be a string when content-type is `text/plain`', async () =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(mockListener.mock.calls[0][0].body).toBe('hello');
|
expect(mockListener.mock.calls[0][0].body).toBe('hello');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('req.body should be a buffer when content-type is `application/octet-stream`', async () => {
|
test('req.body should be a buffer when content-type is `application/octet-stream`', async () => {
|
||||||
await fetchWithProxyReq(url, {
|
await fetchWithProxyReq(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: 'hello',
|
body: 'hello',
|
||||||
@@ -130,9 +201,9 @@ it('req.body should be a buffer when content-type is `application/octet-stream`'
|
|||||||
|
|
||||||
expect(Buffer.isBuffer(body)).toBe(true);
|
expect(Buffer.isBuffer(body)).toBe(true);
|
||||||
expect(str).toBe('hello');
|
expect(str).toBe('hello');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('req.body should be an object when content-type is `application/x-www-form-urlencoded`', async () => {
|
test('req.body should be an object when content-type is `application/x-www-form-urlencoded`', async () => {
|
||||||
const obj = { who: 'mike' };
|
const obj = { who: 'mike' };
|
||||||
|
|
||||||
await fetchWithProxyReq(url, {
|
await fetchWithProxyReq(url, {
|
||||||
@@ -142,9 +213,9 @@ it('req.body should be an object when content-type is `application/x-www-form-ur
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(mockListener.mock.calls[0][0].body).toMatchObject(obj);
|
expect(mockListener.mock.calls[0][0].body).toMatchObject(obj);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('req.body should be an object when content-type is `application/json`', async () => {
|
test('req.body should be an object when content-type is `application/json`', async () => {
|
||||||
const json = {
|
const json = {
|
||||||
who: 'bill',
|
who: 'bill',
|
||||||
where: 'us',
|
where: 'us',
|
||||||
@@ -157,9 +228,9 @@ it('req.body should be an object when content-type is `application/json`', async
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(mockListener.mock.calls[0][0].body).toMatchObject(json);
|
expect(mockListener.mock.calls[0][0].body).toMatchObject(json);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw error when body is empty and content-type is `application/json`', async () => {
|
test('should throw error when body is empty and content-type is `application/json`', async () => {
|
||||||
mockListener.mockImplementation((req, res) => {
|
mockListener.mockImplementation((req, res) => {
|
||||||
console.log(req.body);
|
console.log(req.body);
|
||||||
res.end();
|
res.end();
|
||||||
@@ -172,70 +243,9 @@ it('should throw error when body is empty and content-type is `application/json`
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(res.status).toBe(400);
|
expect(res.status).toBe(400);
|
||||||
});
|
|
||||||
|
|
||||||
it('should not recalculate req properties twice', async () => {
|
|
||||||
const bodySpy = jest.fn(() => {});
|
|
||||||
|
|
||||||
mockListener.mockImplementation((req, res) => {
|
|
||||||
bodySpy(req.body, req.query, req.cookies);
|
|
||||||
bodySpy(req.body, req.query, req.cookies);
|
|
||||||
res.end();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await fetchWithProxyReq(`${url}/?who=bill`, {
|
test('should be able to try/catch parse errors', async () => {
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({ who: 'mike' }),
|
|
||||||
headers: { 'content-type': 'application/json', cookie: 'who=jim' },
|
|
||||||
});
|
|
||||||
|
|
||||||
// here we test that bodySpy is called twice with exactly the same arguments
|
|
||||||
for (let i = 0; i < 3; i += 1) {
|
|
||||||
expect(bodySpy.mock.calls[0][i]).toBe(bodySpy.mock.calls[1][i]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to overwrite request properties', async () => {
|
|
||||||
const spy = jest.fn(() => {});
|
|
||||||
|
|
||||||
mockListener.mockImplementation((...args) => {
|
|
||||||
nowProps.forEach(([prop, n]) => {
|
|
||||||
/* eslint-disable */
|
|
||||||
args[n][prop] = 'ok';
|
|
||||||
args[n][prop] = 'ok2';
|
|
||||||
spy(args[n][prop]);
|
|
||||||
});
|
|
||||||
|
|
||||||
args[1].end();
|
|
||||||
});
|
|
||||||
|
|
||||||
await fetchWithProxyReq(url);
|
|
||||||
|
|
||||||
nowProps.forEach((_, i) => expect(spy.mock.calls[i][0]).toBe('ok2'));
|
|
||||||
});
|
|
||||||
|
|
||||||
// we test that properties are configurable
|
|
||||||
// because expressjs (or some other api frameworks) needs that to work
|
|
||||||
it('should be able to reconfig request properties', async () => {
|
|
||||||
const spy = jest.fn(() => {});
|
|
||||||
|
|
||||||
mockListener.mockImplementation((...args) => {
|
|
||||||
nowProps.forEach(([prop, n]) => {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
Object.defineProperty(args[n], prop, { value: 'ok' });
|
|
||||||
Object.defineProperty(args[n], prop, { value: 'ok2' });
|
|
||||||
spy(args[n][prop]);
|
|
||||||
});
|
|
||||||
|
|
||||||
args[1].end();
|
|
||||||
});
|
|
||||||
|
|
||||||
await fetchWithProxyReq(url);
|
|
||||||
|
|
||||||
nowProps.forEach((_, i) => expect(spy.mock.calls[i][0]).toBe('ok2'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to try/catch parse errors', async () => {
|
|
||||||
const bodySpy = jest.fn(() => {});
|
const bodySpy = jest.fn(() => {});
|
||||||
|
|
||||||
mockListener.mockImplementation((req, res) => {
|
mockListener.mockImplementation((req, res) => {
|
||||||
@@ -259,31 +269,11 @@ it('should be able to try/catch parse errors', async () => {
|
|||||||
const [error] = bodySpy.mock.calls[0];
|
const [error] = bodySpy.mock.calls[0];
|
||||||
expect(error.message).toMatch(/invalid json/i);
|
expect(error.message).toMatch(/invalid json/i);
|
||||||
expect(error.statusCode).toBe(400);
|
expect(error.statusCode).toBe(400);
|
||||||
});
|
|
||||||
|
|
||||||
it('res.send() should send text', async () => {
|
|
||||||
mockListener.mockImplementation((req, res) => {
|
|
||||||
res.send('hello world');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = await fetchWithProxyReq(url);
|
|
||||||
|
|
||||||
expect(await res.text()).toBe('hello world');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('res.json() should send json', async () => {
|
describe('res.status', () => {
|
||||||
mockListener.mockImplementation((req, res) => {
|
test('res.status() should set the status code', async () => {
|
||||||
res.json({ who: 'bill' });
|
|
||||||
});
|
|
||||||
|
|
||||||
const res = await fetchWithProxyReq(url);
|
|
||||||
const contentType = res.headers.get('content-type') || '';
|
|
||||||
|
|
||||||
expect(contentType.includes('application/json')).toBe(true);
|
|
||||||
expect(await res.json()).toMatchObject({ who: 'bill' });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('res.status() should set the status code', async () => {
|
|
||||||
mockListener.mockImplementation((req, res) => {
|
mockListener.mockImplementation((req, res) => {
|
||||||
res.status(404);
|
res.status(404);
|
||||||
res.end();
|
res.end();
|
||||||
@@ -292,26 +282,499 @@ it('res.status() should set the status code', async () => {
|
|||||||
const res = await fetchWithProxyReq(url);
|
const res = await fetchWithProxyReq(url);
|
||||||
|
|
||||||
expect(res.status).toBe(404);
|
expect(res.status).toBe(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('res.status() should be chainable', async () => {
|
||||||
|
const spy = jest.fn();
|
||||||
|
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
spy(res, res.status(404));
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
await fetchWithProxyReq(url);
|
||||||
|
|
||||||
|
const [a, b] = spy.mock.calls[0];
|
||||||
|
expect(a).toBe(b);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('res.status().send() should work', async () => {
|
// tests based on expressjs test suite
|
||||||
|
// see https://github.com/expressjs/express/blob/master/test/res.send.js
|
||||||
|
describe('res.send', () => {
|
||||||
|
test('should be chainable', async () => {
|
||||||
|
const spy = jest.fn();
|
||||||
|
|
||||||
mockListener.mockImplementation((req, res) => {
|
mockListener.mockImplementation((req, res) => {
|
||||||
res.status(404).send('notfound');
|
spy(res, res.send('hello'));
|
||||||
|
});
|
||||||
|
|
||||||
|
await fetchWithProxyReq(url);
|
||||||
|
|
||||||
|
const [a, b] = spy.mock.calls[0];
|
||||||
|
expect(a).toBe(b);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('res.send()', () => {
|
||||||
|
test('should set body to ""', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send();
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = await fetchWithProxyReq(url);
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(await res.text()).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
expect(res.status).toBe(404);
|
describe('.send(null)', () => {
|
||||||
expect(await res.text()).toBe('notfound');
|
test('should set body to ""', async () => {
|
||||||
});
|
|
||||||
|
|
||||||
it('res.status().json() should work', async () => {
|
|
||||||
mockListener.mockImplementation((req, res) => {
|
mockListener.mockImplementation((req, res) => {
|
||||||
res.status(404).json({ error: 'not found' });
|
res.send(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = await fetchWithProxyReq(url);
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('content-length')).toBe('0');
|
||||||
|
expect(await res.text()).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
expect(res.status).toBe(404);
|
describe('.send(undefined)', () => {
|
||||||
expect(await res.json()).toMatchObject({ error: 'not found' });
|
test('should set body to ""', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(await res.text()).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('.send(String)', () => {
|
||||||
|
test('should send as html', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send('<p>hey</p>');
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.headers.get('content-type')).toBe('text/html; charset=utf-8');
|
||||||
|
expect(await res.text()).toBe('<p>hey</p>');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should set Content-Length', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send('½ + ¼ = ¾');
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(Number(res.headers.get('content-length'))).toBe(12);
|
||||||
|
expect(await res.text()).toBe('½ + ¼ = ¾');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should set ETag', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send(Array(1000).join('-'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('ETag')).toBe(
|
||||||
|
'W/"3e7-qPnkJ3CVdVhFJQvUBfF10TmVA7g"'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not override Content-Type', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.setHeader('Content-Type', 'text/plain');
|
||||||
|
res.send('hey');
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('Content-Type')).toBe('text/plain; charset=utf-8');
|
||||||
|
expect(await res.text()).toBe('hey');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should override charset in Content-Type', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.setHeader('Content-Type', 'text/plain; charset=iso-8859-1');
|
||||||
|
res.send('hey');
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('Content-Type')).toBe('text/plain; charset=utf-8');
|
||||||
|
expect(await res.text()).toBe('hey');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('.send(Buffer)', () => {
|
||||||
|
test('should keep charset in Content-Type', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.setHeader('Content-Type', 'text/plain; charset=iso-8859-1');
|
||||||
|
res.send(Buffer.from('hi'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('Content-Type')).toBe(
|
||||||
|
'text/plain; charset=iso-8859-1'
|
||||||
|
);
|
||||||
|
expect(await res.text()).toBe('hi');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should set Content-Length', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send(Buffer.from('½ + ¼ = ¾'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(Number(res.headers.get('content-length'))).toBe(12);
|
||||||
|
expect(await res.text()).toBe('½ + ¼ = ¾');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should send as octet-stream', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send(Buffer.from('hello'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('Content-Type')).toBe('application/octet-stream');
|
||||||
|
expect((await res.buffer()).toString('hex')).toBe(
|
||||||
|
Buffer.from('hello').toString('hex')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should set ETag', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send(Buffer.alloc(999, '-'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('ETag')).toBe(
|
||||||
|
'W/"3e7-qPnkJ3CVdVhFJQvUBfF10TmVA7g"'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not override Content-Type', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
||||||
|
res.send(Buffer.from('hey'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('Content-Type')).toBe('text/plain; charset=utf-8');
|
||||||
|
expect(await res.text()).toBe('hey');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not override ETag', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.setHeader('ETag', '"foo"');
|
||||||
|
res.send(Buffer.from('hey'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('ETag')).toBe('"foo"');
|
||||||
|
expect(await res.text()).toBe('hey');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('.send(Object)', () => {
|
||||||
|
test('should send as application/json', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send({ name: 'tobi' });
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('Content-Type')).toBe(
|
||||||
|
'application/json; charset=utf-8'
|
||||||
|
);
|
||||||
|
expect(await res.text()).toBe('{"name":"tobi"}');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the request method is HEAD', () => {
|
||||||
|
test('should ignore the body', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send('yay');
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: fix this test
|
||||||
|
// node-fetch is automatically ignoring the body so this test will never fail
|
||||||
|
const res = await fetchWithProxyReq(url, { method: 'HEAD' });
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect((await res.buffer()).toString()).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when .statusCode is 204', () => {
|
||||||
|
test('should strip Content-* fields, Transfer-Encoding field, and body', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.statusCode = 204;
|
||||||
|
res.setHeader('Transfer-Encoding', 'chunked');
|
||||||
|
res.send('foo');
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(204);
|
||||||
|
expect(res.headers.get('Content-Type')).toBe(null);
|
||||||
|
expect(res.headers.get('Content-Length')).toBe(null);
|
||||||
|
expect(res.headers.get('Transfer-Encoding')).toBe(null);
|
||||||
|
expect(await res.text()).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when .statusCode is 304', () => {
|
||||||
|
test('should strip Content-* fields, Transfer-Encoding field, and body', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.statusCode = 304;
|
||||||
|
res.setHeader('Transfer-Encoding', 'chunked');
|
||||||
|
res.send('foo');
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(304);
|
||||||
|
expect(res.headers.get('Content-Type')).toBe(null);
|
||||||
|
expect(res.headers.get('Content-Length')).toBe(null);
|
||||||
|
expect(res.headers.get('Transfer-Encoding')).toBe(null);
|
||||||
|
expect(await res.text()).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// test('should always check regardless of length', async () => {
|
||||||
|
// const etag = '"asdf"';
|
||||||
|
|
||||||
|
// mockListener.mockImplementation((req, res) => {
|
||||||
|
// res.setHeader('ETag', etag);
|
||||||
|
// res.send('hey');
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const res = await fetchWithProxyReq(url, {
|
||||||
|
// headers: { 'If-None-Match': etag },
|
||||||
|
// });
|
||||||
|
// expect(res.status).toBe(304);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// test('should respond with 304 Not Modified when fresh', async () => {
|
||||||
|
// const etag = '"asdf"';
|
||||||
|
|
||||||
|
// mockListener.mockImplementation((req, res) => {
|
||||||
|
// res.setHeader('ETag', etag);
|
||||||
|
// res.send(Array(1000).join('-'));
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const res = await fetchWithProxyReq(url, {
|
||||||
|
// headers: { 'If-None-Match': etag },
|
||||||
|
// });
|
||||||
|
// expect(res.status).toBe(304);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// test('should not perform freshness check unless 2xx or 304', async () => {
|
||||||
|
// const etag = '"asdf"';
|
||||||
|
|
||||||
|
// mockListener.mockImplementation((req, res) => {
|
||||||
|
// res.status(500);
|
||||||
|
// res.setHeader('ETag', etag);
|
||||||
|
// res.send('hey');
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const res = await fetchWithProxyReq(url, {
|
||||||
|
// headers: { 'If-None-Match': etag },
|
||||||
|
// });
|
||||||
|
// expect(res.status).toBe(500);
|
||||||
|
// expect(await res.text()).toBe('hey');
|
||||||
|
// });
|
||||||
|
|
||||||
|
describe('etag', () => {
|
||||||
|
test('should send ETag', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send('kajdslfkasdf');
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('ETag')).toBe('W/"c-IgR/L5SF7CJQff4wxKGF/vfPuZ0"');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should send ETag for empty string response', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send('');
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('ETag')).toBe('W/"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should send ETag for long response', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send(Array(1000).join('-'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('ETag')).toBe(
|
||||||
|
'W/"3e7-qPnkJ3CVdVhFJQvUBfF10TmVA7g"'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not override ETag when manually set', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.setHeader('etag', '"asdf"');
|
||||||
|
res.send('hello');
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('ETag')).toBe('"asdf"');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not send ETag for res.send()', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send();
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('ETag')).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// tests based on expressjs test suite
|
||||||
|
// see https://github.com/expressjs/express/blob/master/test/res.json.js
|
||||||
|
describe('res.json', () => {
|
||||||
|
test('should send be chainable', async () => {
|
||||||
|
const spy = jest.fn();
|
||||||
|
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
spy(res, res.json({ hello: 'world' }));
|
||||||
|
});
|
||||||
|
|
||||||
|
await fetchWithProxyReq(url);
|
||||||
|
|
||||||
|
const [a, b] = spy.mock.calls[0];
|
||||||
|
expect(a).toBe(b);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('res.json() should send an empty body', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.json();
|
||||||
|
});
|
||||||
|
|
||||||
|
await fetchWithProxyReq(url);
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('content-type')).toBe(
|
||||||
|
'application/json; charset=utf-8'
|
||||||
|
);
|
||||||
|
expect(await res.text()).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('.json(object)', () => {
|
||||||
|
test('should not override previous Content-Types', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.setHeader('content-type', 'application/vnd.example+json');
|
||||||
|
res.json({ hello: 'world' });
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('content-type')).toBe(
|
||||||
|
'application/vnd.example+json; charset=utf-8'
|
||||||
|
);
|
||||||
|
expect(await res.text()).toBe('{"hello":"world"}');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should set Content-Length and Content-Type', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.json({ hello: '½ + ¼ = ¾' });
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('content-type')).toBe(
|
||||||
|
'application/json; charset=utf-8'
|
||||||
|
);
|
||||||
|
expect(Number(res.headers.get('content-length'))).toBe(24);
|
||||||
|
expect(await res.text()).toBe('{"hello":"½ + ¼ = ¾"}');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when given primitives', () => {
|
||||||
|
test('should respond with json for null', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.json(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('content-type')).toBe(
|
||||||
|
'application/json; charset=utf-8'
|
||||||
|
);
|
||||||
|
expect(await res.text()).toBe('null');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should respond with json for Number', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.json(300);
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('content-type')).toBe(
|
||||||
|
'application/json; charset=utf-8'
|
||||||
|
);
|
||||||
|
expect(await res.text()).toBe('300');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should respond with json for String', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.json('str');
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('content-type')).toBe(
|
||||||
|
'application/json; charset=utf-8'
|
||||||
|
);
|
||||||
|
expect(await res.text()).toBe('"str"');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should respond with json when given an array', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.json(['foo', 'bar', 'baz']);
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('content-type')).toBe(
|
||||||
|
'application/json; charset=utf-8'
|
||||||
|
);
|
||||||
|
expect(await res.text()).toBe('["foo","bar","baz"]');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should respond with json when given an object', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.json({ name: 'tobi' });
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('content-type')).toBe(
|
||||||
|
'application/json; charset=utf-8'
|
||||||
|
);
|
||||||
|
expect(await res.text()).toBe('{"name":"tobi"}');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/optipng",
|
"name": "@now/optipng",
|
||||||
"version": "0.6.2",
|
"version": "0.6.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index",
|
"main": "./dist/index",
|
||||||
|
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/optipng-now-optipng",
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
@@ -21,6 +22,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "11.9.4",
|
"@types/node": "11.9.4",
|
||||||
"typescript": "3.3.3"
|
"typescript": "3.5.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/php",
|
"name": "@now/php",
|
||||||
"version": "0.5.5",
|
"version": "0.5.6",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/php-now-php",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/zeit/now-builders.git",
|
"url": "https://github.com/zeit/now-builders.git",
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ from http.server import BaseHTTPRequestHandler
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
|
import inspect
|
||||||
|
|
||||||
import __NOW_HANDLER_FILENAME
|
import __NOW_HANDLER_FILENAME
|
||||||
__now_variables = dir(__NOW_HANDLER_FILENAME)
|
__now_variables = dir(__NOW_HANDLER_FILENAME)
|
||||||
|
|
||||||
|
|
||||||
if 'handler' in __now_variables or 'Handler' in __now_variables:
|
if 'handler' in __now_variables or 'Handler' in __now_variables:
|
||||||
base = __NOW_HANDLER_FILENAME.handler if ('handler' in __now_variables) else __NOW_HANDLER_FILENAME.Handler
|
base = __NOW_HANDLER_FILENAME.handler if ('handler' in __now_variables) else __NOW_HANDLER_FILENAME.Handler
|
||||||
if not issubclass(base, BaseHTTPRequestHandler):
|
if not issubclass(base, BaseHTTPRequestHandler):
|
||||||
@@ -47,6 +47,7 @@ if 'handler' in __now_variables or 'Handler' in __now_variables:
|
|||||||
'body': res.text,
|
'body': res.text,
|
||||||
}
|
}
|
||||||
elif 'app' in __now_variables:
|
elif 'app' in __now_variables:
|
||||||
|
if not inspect.iscoroutinefunction(__NOW_HANDLER_FILENAME.app.__call__):
|
||||||
print('using Web Server Gateway Interface (WSGI)')
|
print('using Web Server Gateway Interface (WSGI)')
|
||||||
import sys
|
import sys
|
||||||
from urllib.parse import urlparse, unquote
|
from urllib.parse import urlparse, unquote
|
||||||
@@ -117,6 +118,131 @@ elif 'app' in __now_variables:
|
|||||||
return_dict['encoding'] = 'base64'
|
return_dict['encoding'] = 'base64'
|
||||||
|
|
||||||
return return_dict
|
return return_dict
|
||||||
|
else:
|
||||||
|
print('using Asynchronous Server Gateway Interface (ASGI)')
|
||||||
|
import asyncio
|
||||||
|
import enum
|
||||||
|
from urllib.parse import urlparse, unquote, urlencode
|
||||||
|
|
||||||
|
|
||||||
|
class ASGICycleState(enum.Enum):
|
||||||
|
REQUEST = enum.auto()
|
||||||
|
RESPONSE = enum.auto()
|
||||||
|
|
||||||
|
|
||||||
|
class ASGICycle:
|
||||||
|
def __init__(self, scope):
|
||||||
|
self.scope = scope
|
||||||
|
self.body = b''
|
||||||
|
self.state = ASGICycleState.REQUEST
|
||||||
|
self.app_queue = None
|
||||||
|
self.response = {}
|
||||||
|
|
||||||
|
def __call__(self, app, body):
|
||||||
|
"""
|
||||||
|
Receives the application and any body included in the request, then builds the
|
||||||
|
ASGI instance using the connection scope.
|
||||||
|
Runs until the response is completely read from the application.
|
||||||
|
"""
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
self.app_queue = asyncio.Queue(loop=loop)
|
||||||
|
self.put_message({'type': 'http.request', 'body': body, 'more_body': False})
|
||||||
|
|
||||||
|
asgi_instance = app(self.scope, self.receive, self.send)
|
||||||
|
|
||||||
|
asgi_task = loop.create_task(asgi_instance)
|
||||||
|
loop.run_until_complete(asgi_task)
|
||||||
|
return self.response
|
||||||
|
|
||||||
|
def put_message(self, message):
|
||||||
|
self.app_queue.put_nowait(message)
|
||||||
|
|
||||||
|
async def receive(self):
|
||||||
|
"""
|
||||||
|
Awaited by the application to receive messages in the queue.
|
||||||
|
"""
|
||||||
|
message = await self.app_queue.get()
|
||||||
|
return message
|
||||||
|
|
||||||
|
async def send(self, message):
|
||||||
|
"""
|
||||||
|
Awaited by the application to send messages to the current cycle instance.
|
||||||
|
"""
|
||||||
|
message_type = message['type']
|
||||||
|
|
||||||
|
if self.state is ASGICycleState.REQUEST:
|
||||||
|
if message_type != 'http.response.start':
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Expected 'http.response.start', received: {message_type}"
|
||||||
|
)
|
||||||
|
|
||||||
|
status_code = message['status']
|
||||||
|
headers = {k: v for k, v in message.get('headers', [])}
|
||||||
|
|
||||||
|
self.on_request(headers, status_code)
|
||||||
|
self.state = ASGICycleState.RESPONSE
|
||||||
|
|
||||||
|
elif self.state is ASGICycleState.RESPONSE:
|
||||||
|
if message_type != 'http.response.body':
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Expected 'http.response.body', received: {message_type}"
|
||||||
|
)
|
||||||
|
|
||||||
|
body = message.get('body', b'')
|
||||||
|
more_body = message.get('more_body', False)
|
||||||
|
|
||||||
|
# The body must be completely read before returning the response.
|
||||||
|
self.body += body
|
||||||
|
|
||||||
|
if not more_body:
|
||||||
|
self.on_response()
|
||||||
|
self.put_message({'type': 'http.disconnect'})
|
||||||
|
|
||||||
|
def on_request(self, headers, status_code):
|
||||||
|
self.response['statusCode'] = status_code
|
||||||
|
self.response['headers'] = {k.decode(): v.decode() for k, v in headers.items()}
|
||||||
|
|
||||||
|
def on_response(self):
|
||||||
|
if self.body:
|
||||||
|
self.response['body'] = base64.b64encode(self.body).decode('utf-8')
|
||||||
|
self.response['encoding'] = 'base64'
|
||||||
|
|
||||||
|
def now_handler(event, context):
|
||||||
|
payload = json.loads(event['body'])
|
||||||
|
|
||||||
|
headers = payload.get('headers', {})
|
||||||
|
|
||||||
|
body = payload.get('body', b'')
|
||||||
|
if payload.get('encoding') == 'base64':
|
||||||
|
body = base64.b64decode(body)
|
||||||
|
elif not isinstance(body, bytes):
|
||||||
|
body = body.encode()
|
||||||
|
|
||||||
|
url = urlparse(unquote(payload['path']))
|
||||||
|
query = url.query.encode()
|
||||||
|
path = url.path
|
||||||
|
|
||||||
|
scope = {
|
||||||
|
'server': (headers.get('host', 'lambda'), headers.get('x-forwarded-port', 80)),
|
||||||
|
'client': (headers.get(
|
||||||
|
'x-forwarded-for', headers.get(
|
||||||
|
'x-real-ip', payload.get(
|
||||||
|
'true-client-ip', ''))), 0),
|
||||||
|
'scheme': headers.get('x-forwarded-proto', 'http'),
|
||||||
|
'root_path': '',
|
||||||
|
'query_string': query,
|
||||||
|
'headers': [[k.lower().encode(), v.encode()] for k, v in headers.items()],
|
||||||
|
'type': 'http',
|
||||||
|
'http_version': '1.1',
|
||||||
|
'method': payload['method'],
|
||||||
|
'path': path,
|
||||||
|
'raw_path': path.encode(),
|
||||||
|
}
|
||||||
|
|
||||||
|
asgi_cycle = ASGICycle(scope)
|
||||||
|
response = asgi_cycle(__NOW_HANDLER_FILENAME.app, body)
|
||||||
|
return response
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print('Missing variable `handler` or `app` in file __NOW_HANDLER_FILENAME.py')
|
print('Missing variable `handler` or `app` in file __NOW_HANDLER_FILENAME.py')
|
||||||
print('See the docs https://zeit.co/docs/v2/deployments/official-builders/python-now-python')
|
print('See the docs https://zeit.co/docs/v2/deployments/official-builders/python-now-python')
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/python",
|
"name": "@now/python",
|
||||||
"version": "0.2.7",
|
"version": "0.2.10",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/python-now-python",
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
"now_init.py"
|
"now_init.py"
|
||||||
@@ -23,6 +24,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/execa": "^0.9.0",
|
"@types/execa": "^0.9.0",
|
||||||
"typescript": "3.3.4000"
|
"typescript": "3.5.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,7 +104,6 @@ export const build = async ({
|
|||||||
workPath = destNow;
|
workPath = destNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
const foundLockFile = 'Pipfile.lock' in downloadedFiles;
|
|
||||||
const pyUserBase = await getWriteableDirectory();
|
const pyUserBase = await getWriteableDirectory();
|
||||||
process.env.PYTHONUSERBASE = pyUserBase;
|
process.env.PYTHONUSERBASE = pyUserBase;
|
||||||
const pipPath = 'pip3';
|
const pipPath = 'pip3';
|
||||||
@@ -129,17 +128,25 @@ export const build = async ({
|
|||||||
await pipInstall(pipPath, workPath, 'werkzeug');
|
await pipInstall(pipPath, workPath, 'werkzeug');
|
||||||
await pipInstall(pipPath, workPath, 'requests');
|
await pipInstall(pipPath, workPath, 'requests');
|
||||||
|
|
||||||
if (foundLockFile) {
|
let fsFiles = await glob('**', workPath);
|
||||||
|
const entryDirectory = dirname(entrypoint);
|
||||||
|
|
||||||
|
const pipfileLockDir = fsFiles[join(entryDirectory, 'Pipfile.lock')]
|
||||||
|
? join(workPath, entryDirectory)
|
||||||
|
: fsFiles['Pipfile.lock']
|
||||||
|
? workPath
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (pipfileLockDir) {
|
||||||
console.log('found "Pipfile.lock"');
|
console.log('found "Pipfile.lock"');
|
||||||
|
|
||||||
// Install pipenv.
|
// Install pipenv.
|
||||||
await pipInstallUser(pipPath, ' pipenv_to_requirements');
|
await pipInstallUser(pipPath, ' pipenv_to_requirements');
|
||||||
|
|
||||||
await pipenvInstall(pyUserBase, workPath);
|
await pipenvInstall(pyUserBase, pipfileLockDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fsFiles = await glob('**', workPath);
|
fsFiles = await glob('**', workPath);
|
||||||
const entryDirectory = dirname(entrypoint);
|
|
||||||
const requirementsTxt = join(entryDirectory, 'requirements.txt');
|
const requirementsTxt = join(entryDirectory, 'requirements.txt');
|
||||||
|
|
||||||
if (fsFiles[requirementsTxt]) {
|
if (fsFiles[requirementsTxt]) {
|
||||||
|
|||||||
12
packages/now-python/test/fixtures/04-cowsay-pipfile/Pipfile
vendored
Normal file
12
packages/now-python/test/fixtures/04-cowsay-pipfile/Pipfile
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[[source]]
|
||||||
|
name = "pypi"
|
||||||
|
url = "https://pypi.org/simple"
|
||||||
|
verify_ssl = true
|
||||||
|
|
||||||
|
[dev-packages]
|
||||||
|
|
||||||
|
[packages]
|
||||||
|
cowpy = "*"
|
||||||
|
|
||||||
|
[requires]
|
||||||
|
python_version = "3.7"
|
||||||
29
packages/now-python/test/fixtures/04-cowsay-pipfile/Pipfile.lock
generated
vendored
Normal file
29
packages/now-python/test/fixtures/04-cowsay-pipfile/Pipfile.lock
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"_meta": {
|
||||||
|
"hash": {
|
||||||
|
"sha256": "841b49dd1836f7373490faa2a1d6abb4ecd53c0b45e8196df7c6e852e21f458b"
|
||||||
|
},
|
||||||
|
"pipfile-spec": 6,
|
||||||
|
"requires": {
|
||||||
|
"python_version": "3.7"
|
||||||
|
},
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"name": "pypi",
|
||||||
|
"url": "https://pypi.org/simple",
|
||||||
|
"verify_ssl": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"cowpy": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:1bdc61d107df02fd34a9241f2220d0704a01d8ce16bed8bff3512a34a0efa56a",
|
||||||
|
"sha256:91a861bfbfa644dfdba5b2250d141b2227a94f61d4dcbeaf7653524d048935a9"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"develop": {}
|
||||||
|
}
|
||||||
13
packages/now-python/test/fixtures/04-cowsay-pipfile/index.py
vendored
Normal file
13
packages/now-python/test/fixtures/04-cowsay-pipfile/index.py
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
from http.server import BaseHTTPRequestHandler
|
||||||
|
from cowpy import cow
|
||||||
|
|
||||||
|
|
||||||
|
class handler(BaseHTTPRequestHandler):
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'text/plain')
|
||||||
|
self.end_headers()
|
||||||
|
message = cow.Cowacter().milk('pip:RANDOMNESS_PLACEHOLDER')
|
||||||
|
self.wfile.write(message.encode())
|
||||||
|
return
|
||||||
5
packages/now-python/test/fixtures/04-cowsay-pipfile/now.json
vendored
Normal file
5
packages/now-python/test/fixtures/04-cowsay-pipfile/now.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"builds": [{ "src": "**/**.py", "use": "@now/python" }],
|
||||||
|
"probes": [{ "path": "/", "mustContain": "pip:RANDOMNESS_PLACEHOLDER" }]
|
||||||
|
}
|
||||||
12
packages/now-python/test/fixtures/05-cowsay-pipfile-local/local/Pipfile
vendored
Normal file
12
packages/now-python/test/fixtures/05-cowsay-pipfile-local/local/Pipfile
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[[source]]
|
||||||
|
name = "pypi"
|
||||||
|
url = "https://pypi.org/simple"
|
||||||
|
verify_ssl = true
|
||||||
|
|
||||||
|
[dev-packages]
|
||||||
|
|
||||||
|
[packages]
|
||||||
|
cowpy = "*"
|
||||||
|
|
||||||
|
[requires]
|
||||||
|
python_version = "3.7"
|
||||||
29
packages/now-python/test/fixtures/05-cowsay-pipfile-local/local/Pipfile.lock
generated
vendored
Normal file
29
packages/now-python/test/fixtures/05-cowsay-pipfile-local/local/Pipfile.lock
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"_meta": {
|
||||||
|
"hash": {
|
||||||
|
"sha256": "841b49dd1836f7373490faa2a1d6abb4ecd53c0b45e8196df7c6e852e21f458b"
|
||||||
|
},
|
||||||
|
"pipfile-spec": 6,
|
||||||
|
"requires": {
|
||||||
|
"python_version": "3.7"
|
||||||
|
},
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"name": "pypi",
|
||||||
|
"url": "https://pypi.org/simple",
|
||||||
|
"verify_ssl": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"cowpy": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:1bdc61d107df02fd34a9241f2220d0704a01d8ce16bed8bff3512a34a0efa56a",
|
||||||
|
"sha256:91a861bfbfa644dfdba5b2250d141b2227a94f61d4dcbeaf7653524d048935a9"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"develop": {}
|
||||||
|
}
|
||||||
13
packages/now-python/test/fixtures/05-cowsay-pipfile-local/local/index.py
vendored
Normal file
13
packages/now-python/test/fixtures/05-cowsay-pipfile-local/local/index.py
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
from http.server import BaseHTTPRequestHandler
|
||||||
|
from cowpy import cow
|
||||||
|
|
||||||
|
|
||||||
|
class handler(BaseHTTPRequestHandler):
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'text/plain')
|
||||||
|
self.end_headers()
|
||||||
|
message = cow.Cowacter().milk('pip:RANDOMNESS_PLACEHOLDER')
|
||||||
|
self.wfile.write(message.encode())
|
||||||
|
return
|
||||||
5
packages/now-python/test/fixtures/05-cowsay-pipfile-local/now.json
vendored
Normal file
5
packages/now-python/test/fixtures/05-cowsay-pipfile-local/now.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"builds": [{ "src": "**/**.py", "use": "@now/python" }],
|
||||||
|
"probes": [{ "path": "/local", "mustContain": "pip:RANDOMNESS_PLACEHOLDER" }]
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user