mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-11 21:07:47 +00:00
Compare commits
56 Commits
@now/php-b
...
@now/pytho
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9767682006 | ||
|
|
3285b31721 | ||
|
|
70353c7fc0 | ||
|
|
f85cf99325 | ||
|
|
8b14a46d04 | ||
|
|
383cbfd82f | ||
|
|
81e268a3c9 | ||
|
|
ac8b33213b | ||
|
|
de12e7b8c8 | ||
|
|
b9346603f0 | ||
|
|
0b793dfc35 | ||
|
|
9dd672c383 | ||
|
|
1b743aeea8 | ||
|
|
d4af4b9f5c | ||
|
|
b734ca3e01 | ||
|
|
f81d753104 | ||
|
|
db31b9a207 | ||
|
|
b80b5182e6 | ||
|
|
268a7c2b81 | ||
|
|
667a16c996 | ||
|
|
7b851f81c0 | ||
|
|
80fbbcd194 | ||
|
|
3108332043 | ||
|
|
7509c82c32 | ||
|
|
c4f5a5b48d | ||
|
|
05314da810 | ||
|
|
5f1cf714c1 | ||
|
|
2623e2e799 | ||
|
|
bac1da09d4 | ||
|
|
5b57f1a3ac | ||
|
|
2e95dd5329 | ||
|
|
215f6367d6 | ||
|
|
e8cd348a79 | ||
|
|
168f373641 | ||
|
|
8c3174be29 | ||
|
|
898de78b63 | ||
|
|
26e33c1c4b | ||
|
|
c2f95de3ec | ||
|
|
6a7de860db | ||
|
|
acb8cadafe | ||
|
|
1a8df7080d | ||
|
|
5a92826eb0 | ||
|
|
e083aa3750 | ||
|
|
941f675657 | ||
|
|
6fad726abb | ||
|
|
dd22051d6b | ||
|
|
7e86cb403f | ||
|
|
d19d557738 | ||
|
|
e4281f698c | ||
|
|
86ff681c6d | ||
|
|
ba97a7cf19 | ||
|
|
0a94397700 | ||
|
|
5c8e2f2ccc | ||
|
|
35d56a34cb | ||
|
|
9dfd37e135 | ||
|
|
6f815f2645 |
@@ -6,12 +6,10 @@
|
|||||||
/packages/now-build-utils/src/*.js
|
/packages/now-build-utils/src/*.js
|
||||||
/packages/now-build-utils/src/fs/*.js
|
/packages/now-build-utils/src/fs/*.js
|
||||||
/packages/now-node/dist/*
|
/packages/now-node/dist/*
|
||||||
/packages/now-layer-node/dist/*
|
|
||||||
/packages/now-layer-npm/dist/*
|
|
||||||
/packages/now-layer-yarn/dist/*
|
|
||||||
/packages/now-next/dist/*
|
/packages/now-next/dist/*
|
||||||
/packages/now-node-bridge/*
|
/packages/now-node-bridge/*
|
||||||
/packages/now-python/dist/*
|
/packages/now-python/dist/*
|
||||||
/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/*
|
||||||
|
|||||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"eslint.enable": false
|
|
||||||
}
|
|
||||||
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.
|
||||||
47
README.md
47
README.md
@@ -2,29 +2,50 @@
|
|||||||
|
|
||||||
This is a monorepo containing the [Official Builders](https://zeit.co/docs/v2/deployments/builders/overview) provided by the ZEIT team.
|
This is a monorepo containing the [Official Builders](https://zeit.co/docs/v2/deployments/builders/overview) provided by the ZEIT team.
|
||||||
|
|
||||||
There are two branches:
|
## Channels
|
||||||
|
|
||||||
- canary - published to npm as `canary` dist-tag, eg `@now/node@canary`
|
There are two Channels:
|
||||||
- master - published to npm as `latest` dist-tag, eg `@now/node@latest`
|
|
||||||
|
| Channel | Git Branch | npm dist-tag | use example |
|
||||||
|
| ------- | ---------- | ------------ | ------------------ |
|
||||||
|
| Canary | `canary` | `@canary` | `@now/node@canary` |
|
||||||
|
| Stable | `master` | `@latest` | `@now/node@latest` |
|
||||||
|
|
||||||
|
All PRs should be submitted to the `canary` branch.
|
||||||
|
|
||||||
|
Once a PR is merged into the `canary` branch, it should be published to npm immediately using the Canary Channel.
|
||||||
|
|
||||||
### Publishing to npm
|
### Publishing to npm
|
||||||
|
|
||||||
Run the following command to publish modified builders to npm:
|
For the Canary Channel, publish the modified Builders to npm with the following:
|
||||||
|
|
||||||
For the stable channel use:
|
|
||||||
|
|
||||||
```
|
|
||||||
yarn publish-stable
|
|
||||||
```
|
|
||||||
|
|
||||||
For the canary channel use:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
yarn publish-canary
|
yarn publish-canary
|
||||||
```
|
```
|
||||||
|
|
||||||
GitHub Actions will take care of publishing the updated packages to npm from there.
|
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 pull # make sure you're up to date
|
||||||
|
git cherry-pick <PR501_COMMIT_SHA>
|
||||||
|
git cherry-pick <PR502_COMMIT_SHA>
|
||||||
|
git cherry-pick <PR503_COMMIT_SHA>
|
||||||
|
git cherry-pick <PR504_COMMIT_SHA>
|
||||||
|
# ... etc ...
|
||||||
|
git diff origin/canary
|
||||||
|
yarn publish-stable
|
||||||
|
```
|
||||||
|
|
||||||
|
After running this publish step, GitHub Actions will take care of publishing the modified Builder packages to npm.
|
||||||
|
|
||||||
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,13 +1,15 @@
|
|||||||
const childProcess = require('child_process');
|
const { execSync } = require('child_process');
|
||||||
const path = require('path');
|
const { relative } = require('path');
|
||||||
|
|
||||||
const command = 'git diff HEAD~1 --name-only';
|
const branch = execSync('git branch | grep "*" | cut -d " " -f2').toString();
|
||||||
const diff = childProcess.execSync(command).toString();
|
console.log(`Running tests on branch "${branch}"`);
|
||||||
|
const base = branch === 'master' ? 'HEAD~1' : 'origin/canary';
|
||||||
|
const diff = execSync(`git diff ${base} --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 => path.relative('packages', item).split('/')[0]);
|
.map(item => relative('packages', item).split('/')[0]);
|
||||||
|
|
||||||
const matches = [];
|
const matches = [];
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,14 @@
|
|||||||
"*.ts": [
|
"*.ts": [
|
||||||
"prettier --write",
|
"prettier --write",
|
||||||
"git add"
|
"git add"
|
||||||
|
],
|
||||||
|
"*.json": [
|
||||||
|
"prettier --write",
|
||||||
|
"git add"
|
||||||
|
],
|
||||||
|
"*.md": [
|
||||||
|
"prettier --write",
|
||||||
|
"git add"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/build-utils",
|
"name": "@now/build-utils",
|
||||||
"version": "0.5.6",
|
"version": "0.7.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.js",
|
"types": "./dist/index.d.js",
|
||||||
@@ -9,8 +9,12 @@
|
|||||||
"url": "https://github.com/zeit/now-builders.git",
|
"url": "https://github.com/zeit/now-builders.git",
|
||||||
"directory": "packages/now-build-utils"
|
"directory": "packages/now-build-utils"
|
||||||
},
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"test": "tsc && jest",
|
||||||
|
"prepublishOnly": "tsc"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/cross-spawn": "6.0.0",
|
|
||||||
"async-retry": "1.2.3",
|
"async-retry": "1.2.3",
|
||||||
"async-sema": "2.1.4",
|
"async-sema": "2.1.4",
|
||||||
"cross-spawn": "6.0.5",
|
"cross-spawn": "6.0.5",
|
||||||
@@ -18,24 +22,21 @@
|
|||||||
"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",
|
||||||
"memory-fs": "0.4.1",
|
|
||||||
"multistream": "2.1.1",
|
"multistream": "2.1.1",
|
||||||
"node-fetch": "2.2.0",
|
"node-fetch": "2.2.0",
|
||||||
|
"semver": "6.1.1",
|
||||||
"yazl": "2.4.3"
|
"yazl": "2.4.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
|
||||||
"build": "tsc",
|
|
||||||
"test": "tsc && jest",
|
|
||||||
"prepublish": "tsc"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/async-retry": "^1.2.1",
|
"@types/async-retry": "^1.2.1",
|
||||||
|
"@types/cross-spawn": "6.0.0",
|
||||||
"@types/end-of-stream": "^1.4.0",
|
"@types/end-of-stream": "^1.4.0",
|
||||||
"@types/fs-extra": "^5.0.5",
|
"@types/fs-extra": "^5.0.5",
|
||||||
"@types/glob": "^7.1.1",
|
"@types/glob": "^7.1.1",
|
||||||
"@types/node-fetch": "^2.1.6",
|
"@types/node-fetch": "^2.1.6",
|
||||||
|
"@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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
45
packages/now-build-utils/src/fs/node-version.ts
Normal file
45
packages/now-build-utils/src/fs/node-version.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
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
|
||||||
|
): Promise<NodeVersion> {
|
||||||
|
let selection = defaultSelection;
|
||||||
|
|
||||||
|
if (!engineRange) {
|
||||||
|
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) {
|
||||||
|
console.log(
|
||||||
|
'found `engines` in `package.json`, selecting range: ' + selection.range
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -3,6 +3,9 @@ import fs from 'fs-extra';
|
|||||||
import path from 'path';
|
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 { Meta, PackageJson, NodeVersion } from '../types';
|
||||||
|
import { getSupportedNodeVersion } from './node-version';
|
||||||
|
|
||||||
function spawnAsync(
|
function spawnAsync(
|
||||||
command: string,
|
command: string,
|
||||||
@@ -51,11 +54,32 @@ export async function runShellScript(fsPath: string) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function scanParentDirs(destPath: string, scriptName?: string) {
|
export function getSpawnOptions(
|
||||||
|
meta: Meta,
|
||||||
|
nodeVersion: NodeVersion
|
||||||
|
): SpawnOptions {
|
||||||
|
const opts = {
|
||||||
|
env: { ...process.env },
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!meta.isDev) {
|
||||||
|
opts.env.PATH = `/node${nodeVersion.major}/bin:${opts.env.PATH}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return opts;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getNodeVersion(destPath: string): Promise<NodeVersion> {
|
||||||
|
const { packageJson } = await scanParentDirs(destPath, true);
|
||||||
|
const range = packageJson && packageJson.engines && packageJson.engines.node;
|
||||||
|
return getSupportedNodeVersion(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function scanParentDirs(destPath: string, readPackageJson = false) {
|
||||||
assert(path.isAbsolute(destPath));
|
assert(path.isAbsolute(destPath));
|
||||||
|
|
||||||
let hasScript = false;
|
|
||||||
let hasPackageLockJson = false;
|
let hasPackageLockJson = false;
|
||||||
|
let packageJson: PackageJson | undefined;
|
||||||
let currentDestPath = destPath;
|
let currentDestPath = destPath;
|
||||||
|
|
||||||
// eslint-disable-next-line no-constant-condition
|
// eslint-disable-next-line no-constant-condition
|
||||||
@@ -64,12 +88,9 @@ async function scanParentDirs(destPath: string, scriptName?: string) {
|
|||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
if (await fs.pathExists(packageJsonPath)) {
|
if (await fs.pathExists(packageJsonPath)) {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
const packageJson = JSON.parse(
|
if (readPackageJson) {
|
||||||
await fs.readFile(packageJsonPath, 'utf8')
|
packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
|
||||||
);
|
}
|
||||||
hasScript = Boolean(
|
|
||||||
packageJson.scripts && scriptName && packageJson.scripts[scriptName]
|
|
||||||
);
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
hasPackageLockJson = await fs.pathExists(
|
hasPackageLockJson = await fs.pathExists(
|
||||||
path.join(currentDestPath, 'package-lock.json')
|
path.join(currentDestPath, 'package-lock.json')
|
||||||
@@ -82,12 +103,13 @@ async function scanParentDirs(destPath: string, scriptName?: string) {
|
|||||||
currentDestPath = newDestPath;
|
currentDestPath = newDestPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { hasScript, hasPackageLockJson };
|
return { hasPackageLockJson, packageJson };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function installDependencies(
|
export async function runNpmInstall(
|
||||||
destPath: string,
|
destPath: string,
|
||||||
args: string[] = []
|
args: string[] = [],
|
||||||
|
spawnOpts?: SpawnOptions
|
||||||
) {
|
) {
|
||||||
assert(path.isAbsolute(destPath));
|
assert(path.isAbsolute(destPath));
|
||||||
|
|
||||||
@@ -95,30 +117,22 @@ export async function installDependencies(
|
|||||||
console.log(`installing to ${destPath}`);
|
console.log(`installing to ${destPath}`);
|
||||||
const { hasPackageLockJson } = await scanParentDirs(destPath);
|
const { hasPackageLockJson } = await scanParentDirs(destPath);
|
||||||
|
|
||||||
const opts = {
|
const opts = spawnOpts || { env: process.env };
|
||||||
env: {
|
|
||||||
...process.env,
|
|
||||||
// This is a little hack to force `node-gyp` to build for the
|
|
||||||
// Node.js version that `@now/node` and `@now/node-server` use
|
|
||||||
npm_config_target: '8.10.0',
|
|
||||||
},
|
|
||||||
stdio: 'pipe',
|
|
||||||
};
|
|
||||||
|
|
||||||
if (hasPackageLockJson) {
|
if (hasPackageLockJson) {
|
||||||
commandArgs = args.filter(a => a !== '--prefer-offline');
|
commandArgs = args.filter(a => a !== '--prefer-offline');
|
||||||
await spawnAsync(
|
await spawnAsync(
|
||||||
'npm',
|
'npm',
|
||||||
['install', '--unsafe-perm'].concat(commandArgs),
|
commandArgs.concat(['install', '--unsafe-perm']),
|
||||||
destPath,
|
destPath,
|
||||||
opts as SpawnOptions
|
opts
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await spawnAsync(
|
await spawnAsync(
|
||||||
'yarn',
|
'yarn',
|
||||||
['--ignore-engines', '--cwd', destPath].concat(commandArgs),
|
commandArgs.concat(['--ignore-engines', '--cwd', destPath]),
|
||||||
destPath,
|
destPath,
|
||||||
opts as SpawnOptions
|
opts
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,9 +143,15 @@ export async function runPackageJsonScript(
|
|||||||
opts?: SpawnOptions
|
opts?: SpawnOptions
|
||||||
) {
|
) {
|
||||||
assert(path.isAbsolute(destPath));
|
assert(path.isAbsolute(destPath));
|
||||||
const { hasScript, hasPackageLockJson } = await scanParentDirs(
|
const { packageJson, hasPackageLockJson } = await scanParentDirs(
|
||||||
destPath,
|
destPath,
|
||||||
scriptName
|
true
|
||||||
|
);
|
||||||
|
const hasScript = Boolean(
|
||||||
|
packageJson &&
|
||||||
|
packageJson.scripts &&
|
||||||
|
scriptName &&
|
||||||
|
packageJson.scripts[scriptName]
|
||||||
);
|
);
|
||||||
if (!hasScript) return false;
|
if (!hasScript) return false;
|
||||||
|
|
||||||
@@ -151,4 +171,11 @@ export async function runPackageJsonScript(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const runNpmInstall = installDependencies;
|
/**
|
||||||
|
* @deprecate installDependencies() is deprecated.
|
||||||
|
* Please use runNpmInstall() instead.
|
||||||
|
*/
|
||||||
|
export const installDependencies = deprecate(
|
||||||
|
runNpmInstall,
|
||||||
|
'installDependencies() is deprecated. Please use runNpmInstall() instead.'
|
||||||
|
);
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import {
|
|||||||
runPackageJsonScript,
|
runPackageJsonScript,
|
||||||
runNpmInstall,
|
runNpmInstall,
|
||||||
runShellScript,
|
runShellScript,
|
||||||
|
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';
|
||||||
@@ -42,6 +44,8 @@ export {
|
|||||||
runPackageJsonScript,
|
runPackageJsonScript,
|
||||||
runNpmInstall,
|
runNpmInstall,
|
||||||
runShellScript,
|
runShellScript,
|
||||||
|
getNodeVersion,
|
||||||
|
getSpawnOptions,
|
||||||
streamToBuffer,
|
streamToBuffer,
|
||||||
AnalyzeOptions,
|
AnalyzeOptions,
|
||||||
BuildOptions,
|
BuildOptions,
|
||||||
|
|||||||
@@ -16,7 +16,14 @@ export interface Files {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Config {
|
export interface Config {
|
||||||
[key: string]: string;
|
[key: string]: string | string[] | boolean | number | undefined;
|
||||||
|
maxLambdaSize?: string;
|
||||||
|
includeFiles?: string | string[];
|
||||||
|
bundle?: boolean;
|
||||||
|
ldsflags?: string;
|
||||||
|
helpers?: boolean;
|
||||||
|
rust?: string;
|
||||||
|
debug?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Meta {
|
export interface Meta {
|
||||||
@@ -155,3 +162,28 @@ 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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ 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,
|
||||||
@@ -64,6 +68,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');
|
||||||
|
|||||||
@@ -128,18 +128,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.includes('go.mod')) {
|
} 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -202,14 +202,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];
|
||||||
goPackageName = `${usrModName}/${packageName}`;
|
|
||||||
|
if (entrypointArr.length > 1 && isGoModInRootDir) {
|
||||||
|
let cleanPackagePath = [...entrypointArr];
|
||||||
|
cleanPackagePath.pop();
|
||||||
|
goPackageName = `${usrModName}/${cleanPackagePath.join('/')}`;
|
||||||
|
} else {
|
||||||
|
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 +266,28 @@ Learn more: https://zeit.co/docs/v2/deployments/official-builders/go-now-go/#ent
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let baseGoModPath = '';
|
||||||
|
if (meta.isDev && isGoModExist && isGoModInRootDir) {
|
||||||
|
baseGoModPath = dirname(downloadedFiles['now.json'].fsPath);
|
||||||
|
} else if (isGoModExist && isGoModInRootDir) {
|
||||||
|
baseGoModPath = srcPath;
|
||||||
|
} else if (isGoModExist && !isGoModInRootDir) {
|
||||||
|
baseGoModPath = goModPath;
|
||||||
|
} else {
|
||||||
|
baseGoModPath = entrypointDirname;
|
||||||
|
}
|
||||||
|
|
||||||
if (meta.isDev) {
|
if (meta.isDev) {
|
||||||
let entrypointDir = entrypointDirname;
|
const isGoModBk = await pathExists(join(baseGoModPath, 'go.mod.bk'));
|
||||||
if (goModPathArr.length > 1) {
|
|
||||||
entrypointDir = goModPath;
|
|
||||||
}
|
|
||||||
const isGoModBk = await pathExists(join(entrypointDir, '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 +304,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,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/go",
|
"name": "@now/go",
|
||||||
"version": "0.5.1",
|
"version": "0.5.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -31,6 +31,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
packages/now-layer-node/.gitignore
vendored
1
packages/now-layer-node/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
/dist
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@now/layer-node",
|
|
||||||
"version": "0.0.2",
|
|
||||||
"main": "./dist/src/index",
|
|
||||||
"license": "MIT",
|
|
||||||
"files": [
|
|
||||||
"dist"
|
|
||||||
],
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/zeit/now-builders.git",
|
|
||||||
"directory": "packages/now-layer-node"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"build": "tsc",
|
|
||||||
"test": "tsc && jest",
|
|
||||||
"prepublishOnly": "tsc"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"fs-extra": "7.0.1",
|
|
||||||
"node-fetch": "2.6.0",
|
|
||||||
"promisepipe": "3.0.0",
|
|
||||||
"stream-to-promise": "2.2.0",
|
|
||||||
"tar": "4.4.6",
|
|
||||||
"yauzl-promise": "2.1.3"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/tar": "4.0.0",
|
|
||||||
"@types/yauzl-promise": "2.1.0",
|
|
||||||
"typescript": "3.3.3"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import { tmpdir } from 'os';
|
|
||||||
import { join } from 'path';
|
|
||||||
import { glob, Files } from '@now/build-utils';
|
|
||||||
import { mkdir, remove, pathExists } from 'fs-extra';
|
|
||||||
import { install } from './install';
|
|
||||||
|
|
||||||
interface BuildLayerConfig {
|
|
||||||
runtimeVersion: string;
|
|
||||||
platform: string;
|
|
||||||
arch: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BuildLayerResult {
|
|
||||||
files: Files;
|
|
||||||
entrypoint: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function buildLayer({
|
|
||||||
runtimeVersion,
|
|
||||||
platform,
|
|
||||||
arch,
|
|
||||||
}: BuildLayerConfig): Promise<BuildLayerResult> {
|
|
||||||
const dir = join(
|
|
||||||
tmpdir(),
|
|
||||||
`now-layer-node-${runtimeVersion}-${platform}-${arch}`
|
|
||||||
);
|
|
||||||
const exists = await pathExists(dir);
|
|
||||||
if (exists) {
|
|
||||||
await remove(dir);
|
|
||||||
}
|
|
||||||
await mkdir(dir);
|
|
||||||
const { entrypoint } = await install(dir, runtimeVersion, platform, arch);
|
|
||||||
const files = await glob('{bin/node,bin/node.exe,include/**}', {
|
|
||||||
cwd: dir,
|
|
||||||
});
|
|
||||||
return { files, entrypoint };
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
import { basename, join } from 'path';
|
|
||||||
import fetch from 'node-fetch';
|
|
||||||
import { extract } from 'tar';
|
|
||||||
import pipe from 'promisepipe';
|
|
||||||
import { createWriteStream } from 'fs-extra';
|
|
||||||
import { unzip, zipFromFile } from './unzip';
|
|
||||||
|
|
||||||
export async function install(
|
|
||||||
dest: string,
|
|
||||||
version: string,
|
|
||||||
platform: string,
|
|
||||||
arch: string
|
|
||||||
) {
|
|
||||||
const tarballUrl = getUrl(version, platform, arch);
|
|
||||||
console.log('Downloading from ' + tarballUrl);
|
|
||||||
console.log('Downloading to ' + dest);
|
|
||||||
const res = await fetch(tarballUrl);
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error(`HTTP request failed: ${res.status}`);
|
|
||||||
}
|
|
||||||
let entrypoint: string;
|
|
||||||
if (platform === 'win32') {
|
|
||||||
// Put it in the `bin` dir for consistency with the tarballs
|
|
||||||
const finalDest = join(dest, 'bin');
|
|
||||||
const zipName = basename(tarballUrl);
|
|
||||||
const zipPath = join(dest, zipName);
|
|
||||||
|
|
||||||
await pipe(
|
|
||||||
res.body,
|
|
||||||
createWriteStream(zipPath)
|
|
||||||
);
|
|
||||||
|
|
||||||
const zipFile = await zipFromFile(zipPath);
|
|
||||||
await unzip(zipFile, finalDest, { strip: 1 });
|
|
||||||
entrypoint = join('bin', 'node.exe');
|
|
||||||
} else {
|
|
||||||
const extractStream = extract({ strip: 1, C: dest });
|
|
||||||
if (!extractStream.destroy) {
|
|
||||||
// If there is an error in promisepipe,
|
|
||||||
// it expects a destroy method
|
|
||||||
extractStream.destroy = () => {};
|
|
||||||
}
|
|
||||||
await pipe(
|
|
||||||
res.body,
|
|
||||||
extractStream
|
|
||||||
);
|
|
||||||
entrypoint = join('bin', 'node');
|
|
||||||
}
|
|
||||||
|
|
||||||
return { entrypoint };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getUrl(
|
|
||||||
version: string,
|
|
||||||
platform: string = process.platform,
|
|
||||||
arch: string = process.arch
|
|
||||||
): string {
|
|
||||||
let ext: string;
|
|
||||||
let plat: string;
|
|
||||||
if (platform === 'win32') {
|
|
||||||
ext = 'zip';
|
|
||||||
plat = 'win';
|
|
||||||
} else {
|
|
||||||
ext = 'tar.gz';
|
|
||||||
plat = platform;
|
|
||||||
}
|
|
||||||
return `https://nodejs.org/dist/v${version}/node-v${version}-${plat}-${arch}.${ext}`;
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
import { tmpdir } from 'os';
|
|
||||||
import pipe from 'promisepipe';
|
|
||||||
import { dirname, join } from 'path';
|
|
||||||
import { createWriteStream, mkdirp, symlink, unlink } from 'fs-extra';
|
|
||||||
import streamToPromise from 'stream-to-promise';
|
|
||||||
import {
|
|
||||||
Entry,
|
|
||||||
ZipFile,
|
|
||||||
open as zipFromFile,
|
|
||||||
fromBuffer as zipFromBuffer,
|
|
||||||
} from 'yauzl-promise';
|
|
||||||
|
|
||||||
export { zipFromFile, zipFromBuffer, ZipFile };
|
|
||||||
|
|
||||||
export async function unzipToTemp(
|
|
||||||
data: Buffer | string,
|
|
||||||
tmpDir: string = tmpdir()
|
|
||||||
): Promise<string> {
|
|
||||||
const dir = join(
|
|
||||||
tmpDir,
|
|
||||||
`zeit-fun-${Math.random()
|
|
||||||
.toString(16)
|
|
||||||
.substring(2)}`
|
|
||||||
);
|
|
||||||
let zip: ZipFile;
|
|
||||||
if (Buffer.isBuffer(data)) {
|
|
||||||
zip = await zipFromBuffer(data);
|
|
||||||
} else {
|
|
||||||
zip = await zipFromFile(data);
|
|
||||||
}
|
|
||||||
await unzip(zip, dir);
|
|
||||||
await zip.close();
|
|
||||||
return dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UnzipOptions {
|
|
||||||
strip?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function unzip(
|
|
||||||
zipFile: ZipFile,
|
|
||||||
dir: string,
|
|
||||||
opts: UnzipOptions = {}
|
|
||||||
): Promise<void> {
|
|
||||||
let entry: Entry;
|
|
||||||
const strip = opts.strip || 0;
|
|
||||||
while ((entry = await zipFile.readEntry()) !== null) {
|
|
||||||
const fileName =
|
|
||||||
strip === 0
|
|
||||||
? entry.fileName
|
|
||||||
: entry.fileName
|
|
||||||
.split('/')
|
|
||||||
.slice(strip)
|
|
||||||
.join('/');
|
|
||||||
const destPath = join(dir, fileName);
|
|
||||||
if (/\/$/.test(entry.fileName)) {
|
|
||||||
await mkdirp(destPath);
|
|
||||||
} else {
|
|
||||||
const [entryStream] = await Promise.all([
|
|
||||||
entry.openReadStream(),
|
|
||||||
// ensure parent directory exists
|
|
||||||
mkdirp(dirname(destPath)),
|
|
||||||
]);
|
|
||||||
const mode = entry.externalFileAttributes >>> 16;
|
|
||||||
if (isSymbolicLink(mode)) {
|
|
||||||
const linkDest = String(await streamToPromise(entryStream));
|
|
||||||
await symlink(linkDest, destPath);
|
|
||||||
} else {
|
|
||||||
const octal = mode & 4095 /* 07777 */;
|
|
||||||
const modeOctal = ('0000' + octal.toString(8)).slice(-4);
|
|
||||||
const modeVal = parseInt(modeOctal, 8);
|
|
||||||
try {
|
|
||||||
await unlink(destPath);
|
|
||||||
} catch (err) {
|
|
||||||
if (err.code !== 'ENOENT') {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const destStream = createWriteStream(destPath, {
|
|
||||||
mode: modeVal,
|
|
||||||
});
|
|
||||||
await pipe(
|
|
||||||
entryStream,
|
|
||||||
destStream
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const S_IFMT = 61440; /* 0170000 type of file */
|
|
||||||
const S_IFLNK = 40960; /* 0120000 symbolic link */
|
|
||||||
|
|
||||||
export function isSymbolicLink(mode: number): boolean {
|
|
||||||
return (mode & S_IFMT) === S_IFLNK;
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
/* global jest, expect, it */
|
|
||||||
jest.setTimeout(30 * 1000);
|
|
||||||
const { buildLayer } = require('../');
|
|
||||||
|
|
||||||
describe('buildLayer', () => {
|
|
||||||
it('should get node 10 and metadata for windows', async () => {
|
|
||||||
const { files, entrypoint } = await buildLayer({
|
|
||||||
runtimeVersion: '10.16.0',
|
|
||||||
platform: 'win32',
|
|
||||||
arch: 'x64',
|
|
||||||
});
|
|
||||||
const names = new Set(Object.keys(files));
|
|
||||||
expect(names).toBeTruthy();
|
|
||||||
expect(names.size).toBeGreaterThan(0);
|
|
||||||
expect(entrypoint).toBe('bin/node.exe');
|
|
||||||
expect(names.has('bin/node.exe')).toBeTruthy();
|
|
||||||
expect(names.has('bin/npm.cmd')).toBeFalsy();
|
|
||||||
expect(names.has('bin/npx.cmd')).toBeFalsy();
|
|
||||||
expect(names.has('bin/node_modules')).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should get node 10 and metadata for macos', async () => {
|
|
||||||
const { files, entrypoint } = await buildLayer({
|
|
||||||
runtimeVersion: '10.16.0',
|
|
||||||
platform: 'darwin',
|
|
||||||
arch: 'x64',
|
|
||||||
});
|
|
||||||
const names = new Set(Object.keys(files));
|
|
||||||
expect(names).toBeTruthy();
|
|
||||||
expect(names.size).toBeGreaterThan(0);
|
|
||||||
expect(entrypoint).toBe('bin/node');
|
|
||||||
expect(names.has('bin/node')).toBeTruthy();
|
|
||||||
expect(names.has('bin/npm')).toBeFalsy();
|
|
||||||
expect(names.has('bin/npx')).toBeFalsy();
|
|
||||||
expect(names.has('lib/node_modules')).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should get node 10 and metadata for linux', async () => {
|
|
||||||
const { files, entrypoint } = await buildLayer({
|
|
||||||
runtimeVersion: '10.16.0',
|
|
||||||
platform: 'linux',
|
|
||||||
arch: 'x64',
|
|
||||||
});
|
|
||||||
const names = new Set(Object.keys(files));
|
|
||||||
expect(names).toBeTruthy();
|
|
||||||
expect(names.size).toBeGreaterThan(0);
|
|
||||||
expect(entrypoint).toBe('bin/node');
|
|
||||||
expect(names.has('bin/node')).toBeTruthy();
|
|
||||||
expect(names.has('include/node/node.h')).toBeTruthy();
|
|
||||||
expect(names.has('bin/npm')).toBeFalsy();
|
|
||||||
expect(names.has('bin/npx')).toBeFalsy();
|
|
||||||
expect(names.has('lib/node_modules')).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
declare module 'promisepipe' {
|
|
||||||
import { Stream } from 'stream';
|
|
||||||
export default function pipe(...args: Stream[]): Promise<void>;
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
declare module 'stream-to-promise' {
|
|
||||||
import { Stream } from 'stream';
|
|
||||||
export default function streamToPromise(
|
|
||||||
stream: NodeJS.ReadableStream
|
|
||||||
): Promise<string>;
|
|
||||||
}
|
|
||||||
1
packages/now-layer-npm/.gitignore
vendored
1
packages/now-layer-npm/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
/dist
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@now/layer-npm",
|
|
||||||
"version": "0.0.2",
|
|
||||||
"main": "./dist/src/index",
|
|
||||||
"license": "MIT",
|
|
||||||
"files": [
|
|
||||||
"dist"
|
|
||||||
],
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/zeit/now-builders.git",
|
|
||||||
"directory": "packages/now-layer-npm"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"build": "tsc",
|
|
||||||
"test": "tsc && jest",
|
|
||||||
"prepublishOnly": "tsc"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"fs-extra": "7.0.1",
|
|
||||||
"node-fetch": "2.6.0",
|
|
||||||
"promisepipe": "3.0.0",
|
|
||||||
"tar": "4.4.6"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/tar": "4.0.0",
|
|
||||||
"typescript": "3.3.3"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import { tmpdir } from 'os';
|
|
||||||
import { join } from 'path';
|
|
||||||
import { glob, Files } from '@now/build-utils';
|
|
||||||
import { mkdir, remove, pathExists } from 'fs-extra';
|
|
||||||
import { install } from './install';
|
|
||||||
|
|
||||||
interface BuildLayerConfig {
|
|
||||||
runtimeVersion: string;
|
|
||||||
platform: string;
|
|
||||||
arch: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BuildLayerResult {
|
|
||||||
files: Files;
|
|
||||||
entrypoint: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function buildLayer({
|
|
||||||
runtimeVersion,
|
|
||||||
platform,
|
|
||||||
arch,
|
|
||||||
}: BuildLayerConfig): Promise<BuildLayerResult> {
|
|
||||||
const dir = join(
|
|
||||||
tmpdir(),
|
|
||||||
`now-layer-npm-${runtimeVersion}-${platform}-${arch}`
|
|
||||||
);
|
|
||||||
const exists = await pathExists(dir);
|
|
||||||
if (exists) {
|
|
||||||
await remove(dir);
|
|
||||||
}
|
|
||||||
await mkdir(dir);
|
|
||||||
const { entrypoint } = await install(dir, runtimeVersion);
|
|
||||||
const files = await glob('{bin/**,lib/**,node_modules/**}', {
|
|
||||||
cwd: dir,
|
|
||||||
});
|
|
||||||
return { files, entrypoint };
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { join } from 'path';
|
|
||||||
import fetch from 'node-fetch';
|
|
||||||
import { extract } from 'tar';
|
|
||||||
import pipe from 'promisepipe';
|
|
||||||
|
|
||||||
export async function install(dest: string, version: string) {
|
|
||||||
const tarballUrl = `https://registry.npmjs.org/npm/-/npm-${version}.tgz`;
|
|
||||||
console.log('Downloading from ' + tarballUrl);
|
|
||||||
console.log('Downloading to ' + dest);
|
|
||||||
const res = await fetch(tarballUrl);
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error(`HTTP request failed: ${res.status}`);
|
|
||||||
}
|
|
||||||
const extractStream = extract({ strip: 1, C: dest });
|
|
||||||
if (!extractStream.destroy) {
|
|
||||||
// If there is an error in promisepipe,
|
|
||||||
// it expects a destroy method
|
|
||||||
extractStream.destroy = () => {};
|
|
||||||
}
|
|
||||||
await pipe(
|
|
||||||
res.body,
|
|
||||||
extractStream
|
|
||||||
);
|
|
||||||
|
|
||||||
const pathToManifest = join(dest, 'package.json');
|
|
||||||
const manifest = require(pathToManifest);
|
|
||||||
const entrypoint = manifest.bin.npm;
|
|
||||||
return { entrypoint };
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
/* global jest, expect, it */
|
|
||||||
jest.setTimeout(30 * 1000);
|
|
||||||
const { buildLayer } = require('../');
|
|
||||||
|
|
||||||
describe('buildLayer', () => {
|
|
||||||
it('should get npm 6 but not npm for windows', async () => {
|
|
||||||
const { files, entrypoint } = await buildLayer({
|
|
||||||
runtimeVersion: '6.9.0',
|
|
||||||
platform: 'win32',
|
|
||||||
arch: 'x64',
|
|
||||||
});
|
|
||||||
const names = new Set(Object.keys(files));
|
|
||||||
expect(names).toBeTruthy();
|
|
||||||
expect(entrypoint).toBe('./bin/npm-cli.js');
|
|
||||||
expect(names.size).toBeGreaterThan(0);
|
|
||||||
expect(names.has('bin/npm.cmd')).toBeTruthy();
|
|
||||||
expect(names.has('bin/npx.cmd')).toBeTruthy();
|
|
||||||
expect(names.has('README.md')).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should get npm 6 but not npm for macos', async () => {
|
|
||||||
const { files, entrypoint } = await buildLayer({
|
|
||||||
runtimeVersion: '6.9.0',
|
|
||||||
platform: 'darwin',
|
|
||||||
arch: 'x64',
|
|
||||||
});
|
|
||||||
const names = new Set(Object.keys(files));
|
|
||||||
expect(names).toBeTruthy();
|
|
||||||
expect(entrypoint).toBe('./bin/npm-cli.js');
|
|
||||||
expect(names.size).toBeGreaterThan(0);
|
|
||||||
expect(names.has('bin/npm')).toBeTruthy();
|
|
||||||
expect(names.has('bin/npx')).toBeTruthy();
|
|
||||||
expect(names.has('README.md')).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should get npm 6 but not npm for linux', async () => {
|
|
||||||
const { files, entrypoint } = await buildLayer({
|
|
||||||
runtimeVersion: '6.9.0',
|
|
||||||
platform: 'linux',
|
|
||||||
arch: 'x64',
|
|
||||||
});
|
|
||||||
const names = new Set(Object.keys(files));
|
|
||||||
expect(names).toBeTruthy();
|
|
||||||
expect(entrypoint).toBe('./bin/npm-cli.js');
|
|
||||||
expect(names.size).toBeGreaterThan(0);
|
|
||||||
expect(names.has('bin/npm')).toBeTruthy();
|
|
||||||
expect(names.has('bin/npx')).toBeTruthy();
|
|
||||||
expect(names.has('README.md')).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"declaration": false,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"lib": ["esnext"],
|
|
||||||
"module": "commonjs",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"noEmitOnError": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"noImplicitReturns": true,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
"outDir": "dist",
|
|
||||||
"types": ["node"],
|
|
||||||
"strict": true,
|
|
||||||
"target": "esnext"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
declare module 'promisepipe' {
|
|
||||||
import { Stream } from 'stream';
|
|
||||||
export default function pipe(...args: Stream[]): Promise<void>;
|
|
||||||
}
|
|
||||||
1
packages/now-layer-yarn/.gitignore
vendored
1
packages/now-layer-yarn/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
/dist
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@now/layer-yarn",
|
|
||||||
"version": "0.0.2",
|
|
||||||
"main": "./dist/src/index",
|
|
||||||
"license": "MIT",
|
|
||||||
"files": [
|
|
||||||
"dist"
|
|
||||||
],
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/zeit/now-builders.git",
|
|
||||||
"directory": "packages/now-layer-yarn"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"build": "tsc",
|
|
||||||
"test": "tsc && jest",
|
|
||||||
"prepublishOnly": "tsc"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"fs-extra": "7.0.1",
|
|
||||||
"node-fetch": "2.6.0",
|
|
||||||
"promisepipe": "3.0.0",
|
|
||||||
"tar": "4.4.6"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/tar": "4.0.0",
|
|
||||||
"typescript": "3.3.3"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import { tmpdir } from 'os';
|
|
||||||
import { join } from 'path';
|
|
||||||
import { glob, Files } from '@now/build-utils';
|
|
||||||
import { mkdir, remove, pathExists } from 'fs-extra';
|
|
||||||
import { install } from './install';
|
|
||||||
|
|
||||||
interface BuildLayerConfig {
|
|
||||||
runtimeVersion: string;
|
|
||||||
platform: string;
|
|
||||||
arch: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BuildLayerResult {
|
|
||||||
files: Files;
|
|
||||||
entrypoint: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function buildLayer({
|
|
||||||
runtimeVersion,
|
|
||||||
platform,
|
|
||||||
arch,
|
|
||||||
}: BuildLayerConfig): Promise<BuildLayerResult> {
|
|
||||||
const dir = join(
|
|
||||||
tmpdir(),
|
|
||||||
`now-layer-yarn-${runtimeVersion}-${platform}-${arch}`
|
|
||||||
);
|
|
||||||
const exists = await pathExists(dir);
|
|
||||||
if (exists) {
|
|
||||||
await remove(dir);
|
|
||||||
}
|
|
||||||
await mkdir(dir);
|
|
||||||
const { entrypoint } = await install(dir, runtimeVersion);
|
|
||||||
const files = await glob('{bin/**,lib/**}', {
|
|
||||||
cwd: dir,
|
|
||||||
});
|
|
||||||
return { files, entrypoint };
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { join } from 'path';
|
|
||||||
import fetch from 'node-fetch';
|
|
||||||
import { extract } from 'tar';
|
|
||||||
import pipe from 'promisepipe';
|
|
||||||
|
|
||||||
export async function install(dest: string, version: string) {
|
|
||||||
const tarballUrl = `https://registry.npmjs.org/yarn/-/yarn-${version}.tgz`;
|
|
||||||
console.log('Downloading from ' + tarballUrl);
|
|
||||||
console.log('Downloading to ' + dest);
|
|
||||||
const res = await fetch(tarballUrl);
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error(`HTTP request failed: ${res.status}`);
|
|
||||||
}
|
|
||||||
const extractStream = extract({ strip: 1, C: dest });
|
|
||||||
if (!extractStream.destroy) {
|
|
||||||
// If there is an error in promisepipe,
|
|
||||||
// it expects a destroy method
|
|
||||||
extractStream.destroy = () => {};
|
|
||||||
}
|
|
||||||
await pipe(
|
|
||||||
res.body,
|
|
||||||
extractStream
|
|
||||||
);
|
|
||||||
|
|
||||||
const pathToManifest = join(dest, 'package.json');
|
|
||||||
const manifest = require(pathToManifest);
|
|
||||||
const entrypoint = manifest.bin.yarn;
|
|
||||||
return { entrypoint };
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
/* global jest, expect, it */
|
|
||||||
jest.setTimeout(30 * 1000);
|
|
||||||
const { buildLayer } = require('../');
|
|
||||||
|
|
||||||
describe('buildLayer', () => {
|
|
||||||
it('should get yarn for windows', async () => {
|
|
||||||
const { files, entrypoint } = await buildLayer({
|
|
||||||
runtimeVersion: '1.16.0',
|
|
||||||
platform: 'win32',
|
|
||||||
arch: 'x64',
|
|
||||||
});
|
|
||||||
const names = new Set(Object.keys(files));
|
|
||||||
expect(names).toBeTruthy();
|
|
||||||
expect(entrypoint).toBe('./bin/yarn.js');
|
|
||||||
expect(names.size).toBeGreaterThan(0);
|
|
||||||
expect(names.has('bin/yarn.cmd')).toBeTruthy();
|
|
||||||
expect(names.has('lib/cli.js')).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should get yarn for macos', async () => {
|
|
||||||
const { files, entrypoint } = await buildLayer({
|
|
||||||
runtimeVersion: '1.16.0',
|
|
||||||
platform: 'darwin',
|
|
||||||
arch: 'x64',
|
|
||||||
});
|
|
||||||
const names = new Set(Object.keys(files));
|
|
||||||
expect(names).toBeTruthy();
|
|
||||||
expect(entrypoint).toBe('./bin/yarn.js');
|
|
||||||
expect(names.size).toBeGreaterThan(0);
|
|
||||||
expect(names.has('bin/yarn')).toBeTruthy();
|
|
||||||
expect(names.has('lib/cli.js')).toBeTruthy();
|
|
||||||
expect(names.has('README.md')).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should get yarn for linux', async () => {
|
|
||||||
const { files, entrypoint } = await buildLayer({
|
|
||||||
runtimeVersion: '1.16.0',
|
|
||||||
platform: 'linux',
|
|
||||||
arch: 'x64',
|
|
||||||
});
|
|
||||||
const names = new Set(Object.keys(files));
|
|
||||||
expect(names).toBeTruthy();
|
|
||||||
expect(entrypoint).toBe('./bin/yarn.js');
|
|
||||||
expect(names.size).toBeGreaterThan(0);
|
|
||||||
expect(names.has('bin/yarn')).toBeTruthy();
|
|
||||||
expect(names.has('lib/cli.js')).toBeTruthy();
|
|
||||||
expect(names.has('README.md')).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"declaration": false,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"lib": ["esnext"],
|
|
||||||
"module": "commonjs",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"noEmitOnError": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"noImplicitReturns": true,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
"outDir": "dist",
|
|
||||||
"types": ["node"],
|
|
||||||
"strict": true,
|
|
||||||
"target": "esnext"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
declare module 'promisepipe' {
|
|
||||||
import { Stream } from 'stream';
|
|
||||||
export default function pipe(...args: Stream[]): Promise<void>;
|
|
||||||
}
|
|
||||||
@@ -39,4 +39,7 @@ exports.build = async ({ files, entrypoint, config }) => {
|
|||||||
return { [replacedEntrypoint]: result };
|
return { [replacedEntrypoint]: result };
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.shouldServe = shouldServe;
|
exports.shouldServe = (options) => {
|
||||||
|
const requestPath = options.requestPath.replace(/\.html$/, '.md');
|
||||||
|
return shouldServe({ ...options, requestPath });
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/md",
|
"name": "@now/md",
|
||||||
"version": "0.5.3",
|
"version": "0.5.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/next",
|
"name": "@now/next",
|
||||||
"version": "0.4.1",
|
"version": "0.5.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index",
|
"main": "./dist/index",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
"directory": "packages/now-next"
|
"directory": "packages/now-next"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@now/node-bridge": "^1.1.4",
|
"@now/node-bridge": "^1.2.1",
|
||||||
"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 +28,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';
|
||||||
@@ -166,11 +168,14 @@ export const build = async ({
|
|||||||
|
|
||||||
const entryDirectory = path.dirname(entrypoint);
|
const entryDirectory = path.dirname(entrypoint);
|
||||||
const entryPath = path.join(workPath, entryDirectory);
|
const entryPath = path.join(workPath, entryDirectory);
|
||||||
const dotNext = path.join(entryPath, '.next');
|
const dotNextStatic = path.join(entryPath, '.next/static');
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
@@ -190,7 +195,7 @@ export const build = async ({
|
|||||||
// 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';
|
||||||
@@ -227,7 +232,7 @@ export const build = async ({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await pathExists(dotNext)) {
|
if (await pathExists(dotNextStatic)) {
|
||||||
console.warn(
|
console.warn(
|
||||||
'WARNING: You should not upload the `.next` directory. See https://zeit.co/docs/v2/deployments/official-builders/next-js-now-next/ for more details.'
|
'WARNING: You should not upload the `.next` directory. See https://zeit.co/docs/v2/deployments/official-builders/next-js-now-next/ for more details.'
|
||||||
);
|
);
|
||||||
@@ -259,14 +264,15 @@ export const build = async ({
|
|||||||
console.log('normalized package.json result: ', packageJson);
|
console.log('normalized package.json result: ', packageJson);
|
||||||
await writePackageJson(entryPath, packageJson);
|
await writePackageJson(entryPath, packageJson);
|
||||||
} else if (!pkg.scripts || !pkg.scripts['now-build']) {
|
} else if (!pkg.scripts || !pkg.scripts['now-build']) {
|
||||||
console.warn(
|
console.log(
|
||||||
'WARNING: "now-build" script not found. Adding \'"now-build": "next build"\' to "package.json" automatically'
|
'Your application is being built using `next build`. ' +
|
||||||
|
'If you need to define a different build step, please create a `now-build` script in your `package.json` ' +
|
||||||
|
'(e.g. `{ "scripts": { "now-build": "npm run prepare && next build" } }`).'
|
||||||
);
|
);
|
||||||
pkg.scripts = {
|
pkg.scripts = {
|
||||||
'now-build': 'next build',
|
'now-build': 'next build',
|
||||||
...(pkg.scripts || {}),
|
...(pkg.scripts || {}),
|
||||||
};
|
};
|
||||||
console.log('normalized package.json result: ', pkg);
|
|
||||||
await writePackageJson(entryPath, pkg);
|
await writePackageJson(entryPath, pkg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,20 +282,22 @@ 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 buildSpawnOptions = { ...spawnOpts };
|
||||||
env: {
|
const env = { ...buildSpawnOptions.env } as any;
|
||||||
...process.env,
|
env.NODE_OPTIONS = `--max_old_space_size=${memoryToConsume}`;
|
||||||
NODE_OPTIONS: `--max_old_space_size=${memoryToConsume}`,
|
await runPackageJsonScript(entryPath, 'now-build', buildSpawnOptions);
|
||||||
},
|
|
||||||
} 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) {
|
||||||
@@ -510,7 +518,7 @@ export const build = async ({
|
|||||||
).map(route => {
|
).map(route => {
|
||||||
// make sure .html is added to dest for now until
|
// make sure .html is added to dest for now until
|
||||||
// outputting static files to clean routes is available
|
// outputting static files to clean routes is available
|
||||||
if (staticPages[`${route.dest}.html`]) {
|
if (staticPages[`${route.dest}.html`.substr(1)]) {
|
||||||
route.dest = `${route.dest}.html`;
|
route.dest = `${route.dest}.html`;
|
||||||
}
|
}
|
||||||
return route;
|
return route;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/node-bridge",
|
"name": "@now/node-bridge",
|
||||||
"version": "1.1.4",
|
"version": "1.2.1",
|
||||||
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,9 +95,13 @@ export class Bridge {
|
|||||||
private server: Server | null;
|
private server: Server | 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 reqIdSeed: number = 1;
|
||||||
|
private shouldStoreEvents: boolean = false;
|
||||||
|
|
||||||
constructor(server?: Server) {
|
constructor(server?: Server, shouldStoreEvents: boolean = false) {
|
||||||
this.server = null;
|
this.server = null;
|
||||||
|
this.shouldStoreEvents = shouldStoreEvents;
|
||||||
if (server) {
|
if (server) {
|
||||||
this.setServer(server);
|
this.setServer(server);
|
||||||
}
|
}
|
||||||
@@ -144,15 +148,16 @@ export class Bridge {
|
|||||||
context.callbackWaitsForEmptyEventLoop = false;
|
context.callbackWaitsForEmptyEventLoop = false;
|
||||||
const { port } = await this.listening;
|
const { port } = await this.listening;
|
||||||
|
|
||||||
const { isApiGateway, method, path, headers, body } = normalizeEvent(event);
|
const normalizedEvent = normalizeEvent(event);
|
||||||
|
const { isApiGateway, method, path, headers, body } = normalizedEvent;
|
||||||
|
|
||||||
const opts = {
|
if (this.shouldStoreEvents) {
|
||||||
hostname: '127.0.0.1',
|
const reqId = `${this.reqIdSeed++}`;
|
||||||
port,
|
this.events[reqId] = normalizedEvent;
|
||||||
path,
|
headers['x-now-bridge-request-id'] = reqId;
|
||||||
method,
|
}
|
||||||
headers,
|
|
||||||
};
|
const opts = { hostname: '127.0.0.1', port, path, method, headers };
|
||||||
|
|
||||||
// eslint-disable-next-line consistent-return
|
// eslint-disable-next-line consistent-return
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -192,4 +197,10 @@ export class Bridge {
|
|||||||
req.end();
|
req.end();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
consumeEvent(reqId: string) {
|
||||||
|
const event = this.events[reqId];
|
||||||
|
delete this.events[reqId];
|
||||||
|
return event;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,3 +83,41 @@ test('`NowProxyEvent` normalizing', async () => {
|
|||||||
|
|
||||||
server.close();
|
server.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('consumeEvent', async () => {
|
||||||
|
const mockListener = jest.fn((req, res) => {
|
||||||
|
res.end('hello');
|
||||||
|
});
|
||||||
|
|
||||||
|
const server = new Server(mockListener);
|
||||||
|
const bridge = new Bridge(server, true);
|
||||||
|
bridge.listen();
|
||||||
|
|
||||||
|
const context = { callbackWaitsForEmptyEventLoop: true };
|
||||||
|
await bridge.launcher(
|
||||||
|
{
|
||||||
|
Action: 'Invoke',
|
||||||
|
body: JSON.stringify({
|
||||||
|
method: 'POST',
|
||||||
|
headers: { foo: 'baz' },
|
||||||
|
path: '/nowproxy',
|
||||||
|
body: 'body=1',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
context
|
||||||
|
);
|
||||||
|
|
||||||
|
const headers = mockListener.mock.calls[0][0].headers;
|
||||||
|
const reqId = headers['x-now-bridge-request-id'];
|
||||||
|
|
||||||
|
expect(reqId).toBeTruthy();
|
||||||
|
|
||||||
|
const event = bridge.consumeEvent(reqId);
|
||||||
|
expect(event.body.toString()).toBe('body=1');
|
||||||
|
|
||||||
|
// an event can't be consumed multiple times
|
||||||
|
// to avoid memory leaks
|
||||||
|
expect(bridge.consumeEvent(reqId)).toBeUndefined();
|
||||||
|
|
||||||
|
server.close();
|
||||||
|
});
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -146,7 +160,7 @@ exports.build = async ({
|
|||||||
const lambda = await createLambda({
|
const lambda = await createLambda({
|
||||||
files: { ...preparedFiles, ...launcherFiles },
|
files: { ...preparedFiles, ...launcherFiles },
|
||||||
handler: 'launcher.launcher',
|
handler: 'launcher.launcher',
|
||||||
runtime: 'nodejs8.10',
|
runtime: nodeVersion.runtime,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { [entrypoint]: lambda };
|
return { [entrypoint]: lambda };
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/node-server",
|
"name": "@now/node-server",
|
||||||
"version": "0.7.4",
|
"version": "0.8.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
"directory": "packages/now-node-server"
|
"directory": "packages/now-node-server"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@now/node-bridge": "^1.1.4",
|
"@now/node-bridge": "^1.2.1",
|
||||||
"@zeit/ncc": "0.18.5",
|
"@zeit/ncc": "0.18.5",
|
||||||
"fs-extra": "7.0.1"
|
"fs-extra": "7.0.1"
|
||||||
},
|
},
|
||||||
|
|||||||
1
packages/now-node/.gitignore
vendored
1
packages/now-node/.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
/dist
|
/dist
|
||||||
/src/bridge.d.ts
|
/src/bridge.d.ts
|
||||||
|
/test/fixtures/**/types.d.ts
|
||||||
|
|||||||
45
packages/now-node/@types/zeit__ncc/index.d.ts
vendored
Normal file
45
packages/now-node/@types/zeit__ncc/index.d.ts
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
declare function ncc(
|
||||||
|
entrypoint: string,
|
||||||
|
options?: ncc.NccOptions
|
||||||
|
): ncc.NccResult;
|
||||||
|
|
||||||
|
declare namespace ncc {
|
||||||
|
export interface NccOptions {
|
||||||
|
watch?: any;
|
||||||
|
sourceMap?: boolean;
|
||||||
|
sourceMapRegister?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Asset {
|
||||||
|
source: Buffer;
|
||||||
|
permissions: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Assets {
|
||||||
|
[name: string]: Asset;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BuildResult {
|
||||||
|
err: Error | null | undefined;
|
||||||
|
code: string;
|
||||||
|
map: string | undefined;
|
||||||
|
assets: Assets | undefined;
|
||||||
|
permissions: number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type HandlerFn = (params: BuildResult) => void;
|
||||||
|
export type HandlerCallback = (fn: HandlerFn) => void;
|
||||||
|
export type RebuildFn = () => void;
|
||||||
|
export type RebuildCallback = (fn: RebuildFn) => void;
|
||||||
|
export type CloseCallback = () => void;
|
||||||
|
|
||||||
|
export interface NccResult {
|
||||||
|
handler: HandlerCallback;
|
||||||
|
rebuild: RebuildCallback;
|
||||||
|
close: CloseCallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@zeit/ncc' {
|
||||||
|
export = ncc;
|
||||||
|
}
|
||||||
@@ -9,4 +9,16 @@ if [ ! -e "$bridge_defs" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
cp -v "$bridge_defs" src
|
cp -v "$bridge_defs" src
|
||||||
|
|
||||||
|
# build ts files
|
||||||
tsc
|
tsc
|
||||||
|
|
||||||
|
# bundle helpers.ts with ncc
|
||||||
|
rm dist/helpers.js
|
||||||
|
ncc build src/helpers.ts -o dist/helpers
|
||||||
|
mv dist/helpers/index.js dist/helpers.js
|
||||||
|
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,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/node",
|
"name": "@now/node",
|
||||||
"version": "0.7.4",
|
"version": "0.10.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index",
|
"main": "./dist/index",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -9,8 +9,10 @@
|
|||||||
"directory": "packages/now-node"
|
"directory": "packages/now-node"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@now/node-bridge": "^1.1.4",
|
"@now/node-bridge": "^1.2.1",
|
||||||
|
"@types/node": "*",
|
||||||
"@zeit/ncc": "0.18.5",
|
"@zeit/ncc": "0.18.5",
|
||||||
|
"@zeit/ncc-watcher": "1.0.3",
|
||||||
"fs-extra": "7.0.1"
|
"fs-extra": "7.0.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -22,8 +24,13 @@
|
|||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "11.9.4",
|
"@types/content-type": "1.1.3",
|
||||||
"jest": "24.1.0",
|
"@types/cookie": "0.3.3",
|
||||||
"typescript": "3.3.3"
|
"@types/test-listen": "1.1.0",
|
||||||
|
"content-type": "1.0.4",
|
||||||
|
"cookie": "0.4.0",
|
||||||
|
"node-fetch": "2.6.0",
|
||||||
|
"test-listen": "1.1.0",
|
||||||
|
"typescript": "3.5.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
packages/now-node/src/example-import.ts
Normal file
3
packages/now-node/src/example-import.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// we intentionally import these types here
|
||||||
|
// to test that they are exported from index
|
||||||
|
import { NowRequest, NowResponse } from './index';
|
||||||
204
packages/now-node/src/helpers.ts
Normal file
204
packages/now-node/src/helpers.ts
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
import {
|
||||||
|
NowRequest,
|
||||||
|
NowResponse,
|
||||||
|
NowRequestCookies,
|
||||||
|
NowRequestQuery,
|
||||||
|
NowRequestBody,
|
||||||
|
} from './types';
|
||||||
|
import { Stream } from 'stream';
|
||||||
|
import { Server } from 'http';
|
||||||
|
import { Bridge } from './bridge';
|
||||||
|
|
||||||
|
function getBodyParser(req: NowRequest, body: Buffer) {
|
||||||
|
return function parseBody(): NowRequestBody {
|
||||||
|
if (!req.headers['content-type']) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { parse: parseCT } = require('content-type');
|
||||||
|
const { type } = parseCT(req.headers['content-type']);
|
||||||
|
|
||||||
|
if (type === 'application/json') {
|
||||||
|
try {
|
||||||
|
return JSON.parse(body.toString());
|
||||||
|
} catch (error) {
|
||||||
|
throw new ApiError(400, 'Invalid JSON');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'application/octet-stream') {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'application/x-www-form-urlencoded') {
|
||||||
|
const { parse: parseQS } = require('querystring');
|
||||||
|
// remark : querystring.parse does not produce an iterable object
|
||||||
|
// https://nodejs.org/api/querystring.html#querystring_querystring_parse_str_sep_eq_options
|
||||||
|
return parseQS(body.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'text/plain') {
|
||||||
|
return body.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getQueryParser({ url = '/' }: NowRequest) {
|
||||||
|
return function parseQuery(): NowRequestQuery {
|
||||||
|
const { URL } = require('url');
|
||||||
|
// we provide a placeholder base url because we only want searchParams
|
||||||
|
const params = new URL(url, 'https://n').searchParams;
|
||||||
|
|
||||||
|
const query: { [key: string]: string | string[] } = {};
|
||||||
|
for (const [key, value] of params) {
|
||||||
|
query[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCookieParser(req: NowRequest) {
|
||||||
|
return function parseCookie(): NowRequestCookies {
|
||||||
|
const header: undefined | string | string[] = req.headers.cookie;
|
||||||
|
|
||||||
|
if (!header) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { parse } = require('cookie');
|
||||||
|
return parse(Array.isArray(header) ? header.join(';') : header);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendStatusCode(res: NowResponse, statusCode: number): NowResponse {
|
||||||
|
res.statusCode = statusCode;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendData(res: NowResponse, body: any): NowResponse {
|
||||||
|
if (body === null) {
|
||||||
|
res.end();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendJson(res: NowResponse, jsonBody: any): NowResponse {
|
||||||
|
// Set header to application/json
|
||||||
|
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
||||||
|
|
||||||
|
// Use send to handle request
|
||||||
|
return res.send(jsonBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ApiError extends Error {
|
||||||
|
readonly statusCode: number;
|
||||||
|
|
||||||
|
constructor(statusCode: number, message: string) {
|
||||||
|
super(message);
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendError(
|
||||||
|
res: NowResponse,
|
||||||
|
statusCode: number,
|
||||||
|
message: string
|
||||||
|
) {
|
||||||
|
res.statusCode = statusCode;
|
||||||
|
res.statusMessage = message;
|
||||||
|
res.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLazyProp<T>(req: NowRequest, prop: string, getter: () => T) {
|
||||||
|
const opts = { configurable: true, enumerable: true };
|
||||||
|
const optsReset = { ...opts, writable: true };
|
||||||
|
|
||||||
|
Object.defineProperty(req, prop, {
|
||||||
|
...opts,
|
||||||
|
get: () => {
|
||||||
|
const value = getter();
|
||||||
|
// we set the property on the object to avoid recalculating it
|
||||||
|
Object.defineProperty(req, prop, { ...optsReset, value });
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
set: value => {
|
||||||
|
Object.defineProperty(req, prop, { ...optsReset, value });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createServerWithHelpers(
|
||||||
|
listener: (req: NowRequest, res: NowResponse) => void | Promise<void>,
|
||||||
|
bridge: Bridge
|
||||||
|
) {
|
||||||
|
const server = new Server(async (_req, _res) => {
|
||||||
|
const req = _req as NowRequest;
|
||||||
|
const res = _res as NowResponse;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const reqId = req.headers['x-now-bridge-request-id'];
|
||||||
|
|
||||||
|
// don't expose this header to the client
|
||||||
|
delete req.headers['x-now-bridge-request-id'];
|
||||||
|
|
||||||
|
if (typeof reqId !== 'string') {
|
||||||
|
throw new ApiError(500, 'Internal Server Error');
|
||||||
|
}
|
||||||
|
|
||||||
|
const event = bridge.consumeEvent(reqId);
|
||||||
|
|
||||||
|
setLazyProp<NowRequestCookies>(req, 'cookies', getCookieParser(req));
|
||||||
|
setLazyProp<NowRequestQuery>(req, 'query', getQueryParser(req));
|
||||||
|
setLazyProp<NowRequestBody>(req, 'body', getBodyParser(req, event.body));
|
||||||
|
|
||||||
|
res.status = statusCode => sendStatusCode(res, statusCode);
|
||||||
|
res.send = data => sendData(res, data);
|
||||||
|
res.json = data => sendJson(res, data);
|
||||||
|
|
||||||
|
await listener(req, res);
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
sendError(res, err.statusCode, err.message);
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return server;
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import { join, dirname, sep } from 'path';
|
|
||||||
import { readFile } from 'fs-extra';
|
import { readFile } from 'fs-extra';
|
||||||
|
import { Assets, NccOptions } from '@zeit/ncc';
|
||||||
|
import { join, dirname, relative, sep } from 'path';
|
||||||
|
import { NccWatcher, WatcherResult } from '@zeit/ncc-watcher';
|
||||||
import {
|
import {
|
||||||
glob,
|
glob,
|
||||||
download,
|
download,
|
||||||
@@ -10,10 +12,13 @@ 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';
|
||||||
|
|
||||||
interface CompilerConfig {
|
interface CompilerConfig {
|
||||||
includeFiles?: string | string[];
|
includeFiles?: string | string[];
|
||||||
@@ -23,8 +28,25 @@ interface DownloadOptions {
|
|||||||
files: Files;
|
files: Files;
|
||||||
entrypoint: string;
|
entrypoint: string;
|
||||||
workPath: string;
|
workPath: string;
|
||||||
meta?: Meta;
|
meta: Meta;
|
||||||
npmArguments?: string[];
|
}
|
||||||
|
|
||||||
|
const watchers: Map<string, NccWatcher> = new Map();
|
||||||
|
|
||||||
|
function getWatcher(entrypoint: string, options: NccOptions): NccWatcher {
|
||||||
|
let watcher = watchers.get(entrypoint);
|
||||||
|
if (!watcher) {
|
||||||
|
watcher = new NccWatcher(entrypoint, options);
|
||||||
|
watchers.set(entrypoint, watcher);
|
||||||
|
}
|
||||||
|
return watcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toBuffer(data: string | Buffer): Buffer {
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
return Buffer.from(data, 'utf8');
|
||||||
|
}
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadInstallAndBundle({
|
async function downloadInstallAndBundle({
|
||||||
@@ -32,32 +54,66 @@ async function downloadInstallAndBundle({
|
|||||||
entrypoint,
|
entrypoint,
|
||||||
workPath,
|
workPath,
|
||||||
meta,
|
meta,
|
||||||
npmArguments = [],
|
|
||||||
}: DownloadOptions) {
|
}: DownloadOptions) {
|
||||||
console.log('downloading user files...');
|
console.log('downloading user files...');
|
||||||
const downloadedFiles = await download(files, workPath, meta);
|
const downloadedFiles = await download(files, workPath, meta);
|
||||||
|
|
||||||
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, npmArguments);
|
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(
|
||||||
|
workPath: string,
|
||||||
entrypointPath: string,
|
entrypointPath: string,
|
||||||
entrypoint: string,
|
entrypoint: string,
|
||||||
config: CompilerConfig
|
config: CompilerConfig,
|
||||||
): Promise<Files> {
|
{ isDev, filesChanged, filesRemoved }: Meta
|
||||||
|
): Promise<{ preparedFiles: Files; watch: string[] }> {
|
||||||
const input = entrypointPath;
|
const input = entrypointPath;
|
||||||
const inputDir = dirname(input);
|
const inputDir = dirname(input);
|
||||||
const rootIncludeFiles = inputDir.split(sep).pop() || '';
|
const rootIncludeFiles = inputDir.split(sep).pop() || '';
|
||||||
const ncc = require('@zeit/ncc');
|
const options: NccOptions = {
|
||||||
const { code, map, assets } = await ncc(input, {
|
|
||||||
sourceMap: true,
|
sourceMap: true,
|
||||||
sourceMapRegister: true,
|
sourceMapRegister: true,
|
||||||
});
|
};
|
||||||
|
let code: string;
|
||||||
|
let map: string | undefined;
|
||||||
|
let assets: Assets | undefined;
|
||||||
|
let watch: string[] = [];
|
||||||
|
if (isDev) {
|
||||||
|
const watcher = getWatcher(entrypointPath, options);
|
||||||
|
const result = await watcher.build(
|
||||||
|
Array.isArray(filesChanged)
|
||||||
|
? filesChanged.map(f => join(workPath, f))
|
||||||
|
: undefined,
|
||||||
|
Array.isArray(filesRemoved)
|
||||||
|
? filesRemoved.map(f => join(workPath, f))
|
||||||
|
: undefined
|
||||||
|
);
|
||||||
|
code = result.code;
|
||||||
|
map = result.map;
|
||||||
|
assets = result.assets;
|
||||||
|
watch = [...result.files, ...result.dirs, ...result.missing]
|
||||||
|
.filter(f => f.startsWith(workPath))
|
||||||
|
.map(f => relative(workPath, f));
|
||||||
|
} else {
|
||||||
|
const ncc = require('@zeit/ncc');
|
||||||
|
const result = await ncc(input, {
|
||||||
|
sourceMap: true,
|
||||||
|
sourceMapRegister: true,
|
||||||
|
});
|
||||||
|
code = result.code;
|
||||||
|
map = result.map;
|
||||||
|
assets = result.assets;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!assets) assets = {};
|
||||||
|
|
||||||
if (config && config.includeFiles) {
|
if (config && config.includeFiles) {
|
||||||
const includeFiles =
|
const includeFiles =
|
||||||
@@ -81,7 +137,7 @@ async function compile(
|
|||||||
}
|
}
|
||||||
|
|
||||||
assets[fullPath] = {
|
assets[fullPath] = {
|
||||||
source: data,
|
source: toBuffer(data),
|
||||||
permissions: mode,
|
permissions: mode,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -89,11 +145,15 @@ async function compile(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const preparedFiles: Files = {};
|
const preparedFiles: Files = {};
|
||||||
// move all user code to 'user' subdirectory
|
|
||||||
preparedFiles[entrypoint] = new FileBlob({ data: code });
|
preparedFiles[entrypoint] = new FileBlob({ data: code });
|
||||||
preparedFiles[`${entrypoint.replace('.ts', '.js')}.map`] = new FileBlob({
|
|
||||||
data: map,
|
if (map) {
|
||||||
});
|
preparedFiles[`${entrypoint.replace('.ts', '.js')}.map`] = new FileBlob({
|
||||||
|
data: toBuffer(map),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// move all user code to 'user' subdirectory
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const assetName of Object.keys(assets)) {
|
for (const assetName of Object.keys(assets)) {
|
||||||
const { source: data, permissions: mode } = assets[assetName];
|
const { source: data, permissions: mode } = assets[assetName];
|
||||||
@@ -101,9 +161,11 @@ async function compile(
|
|||||||
preparedFiles[join(dirname(entrypoint), assetName)] = blob2;
|
preparedFiles[join(dirname(entrypoint), assetName)] = blob2;
|
||||||
}
|
}
|
||||||
|
|
||||||
return preparedFiles;
|
return { preparedFiles, watch };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const version = 2;
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
maxLambdaSize: '5mb',
|
maxLambdaSize: '5mb',
|
||||||
};
|
};
|
||||||
@@ -113,47 +175,76 @@ export async function build({
|
|||||||
entrypoint,
|
entrypoint,
|
||||||
workPath,
|
workPath,
|
||||||
config,
|
config,
|
||||||
meta,
|
meta = {},
|
||||||
}: BuildOptions) {
|
}: BuildOptions) {
|
||||||
|
const shouldAddHelpers = !(config && config.helpers === false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
entrypointPath,
|
entrypointPath,
|
||||||
entrypointFsDirname,
|
entrypointFsDirname,
|
||||||
|
nodeVersion,
|
||||||
|
spawnOpts,
|
||||||
} = await downloadInstallAndBundle({
|
} = await downloadInstallAndBundle({
|
||||||
files,
|
files,
|
||||||
entrypoint,
|
entrypoint,
|
||||||
workPath,
|
workPath,
|
||||||
meta,
|
meta,
|
||||||
npmArguments: ['--prefer-offline'],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
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 = await compile(entrypointPath, entrypoint, config);
|
const { preparedFiles, watch } = await compile(
|
||||||
|
workPath,
|
||||||
|
entrypointPath,
|
||||||
|
entrypoint,
|
||||||
|
config,
|
||||||
|
meta
|
||||||
|
);
|
||||||
const launcherPath = join(__dirname, 'launcher.js');
|
const launcherPath = join(__dirname, 'launcher.js');
|
||||||
let launcherData = await readFile(launcherPath, 'utf8');
|
let launcherData = await readFile(launcherPath, 'utf8');
|
||||||
|
|
||||||
launcherData = launcherData.replace(
|
launcherData = launcherData.replace(
|
||||||
'// PLACEHOLDER',
|
'// PLACEHOLDER:shouldStoreProxyRequests',
|
||||||
|
shouldAddHelpers ? 'shouldStoreProxyRequests = true;' : ''
|
||||||
|
);
|
||||||
|
|
||||||
|
launcherData = launcherData.replace(
|
||||||
|
'// PLACEHOLDER:setServer',
|
||||||
[
|
[
|
||||||
`listener = require("./${entrypoint}");`,
|
`let listener = require("./${entrypoint}");`,
|
||||||
'if (listener.default) listener = listener.default;',
|
'if (listener.default) listener = listener.default;',
|
||||||
|
shouldAddHelpers
|
||||||
|
? 'const server = require("./helpers").createServerWithHelpers(listener, bridge);'
|
||||||
|
: 'const server = require("http").createServer(listener);',
|
||||||
|
'bridge.setServer(server);',
|
||||||
].join(' ')
|
].join(' ')
|
||||||
);
|
);
|
||||||
|
|
||||||
const launcherFiles = {
|
const launcherFiles: Files = {
|
||||||
'launcher.js': new FileBlob({ data: launcherData }),
|
'launcher.js': new FileBlob({ data: launcherData }),
|
||||||
'bridge.js': new FileFsRef({ fsPath: require('@now/node-bridge') }),
|
'bridge.js': new FileFsRef({ fsPath: require('@now/node-bridge') }),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (shouldAddHelpers) {
|
||||||
|
launcherFiles['helpers.js'] = new FileFsRef({
|
||||||
|
fsPath: join(__dirname, 'helpers.js'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const lambda = await createLambda({
|
const lambda = await createLambda({
|
||||||
files: { ...preparedFiles, ...launcherFiles },
|
files: {
|
||||||
|
...preparedFiles,
|
||||||
|
...launcherFiles,
|
||||||
|
},
|
||||||
handler: 'launcher.launcher',
|
handler: 'launcher.launcher',
|
||||||
runtime: 'nodejs8.10',
|
runtime: nodeVersion.runtime,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { [entrypoint]: lambda };
|
const output = { [entrypoint]: lambda };
|
||||||
|
const result = { output, watch };
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function prepareCache({ workPath }: PrepareCacheOptions) {
|
export async function prepareCache({ workPath }: PrepareCacheOptions) {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { Server } from 'http';
|
|
||||||
import { Bridge } from './bridge';
|
import { Bridge } from './bridge';
|
||||||
|
|
||||||
let listener;
|
let shouldStoreProxyRequests: boolean = false;
|
||||||
|
// PLACEHOLDER:shouldStoreProxyRequests
|
||||||
|
|
||||||
|
const bridge = new Bridge(undefined, shouldStoreProxyRequests);
|
||||||
|
|
||||||
if (!process.env.NODE_ENV) {
|
if (!process.env.NODE_ENV) {
|
||||||
process.env.NODE_ENV =
|
process.env.NODE_ENV =
|
||||||
@@ -9,7 +11,7 @@ if (!process.env.NODE_ENV) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// PLACEHOLDER
|
// PLACEHOLDER:setServer
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code === 'MODULE_NOT_FOUND') {
|
if (err.code === 'MODULE_NOT_FOUND') {
|
||||||
console.error(err.message);
|
console.error(err.message);
|
||||||
@@ -23,8 +25,6 @@ try {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const server = new Server(listener);
|
|
||||||
const bridge = new Bridge(server);
|
|
||||||
bridge.listen();
|
bridge.listen();
|
||||||
|
|
||||||
exports.launcher = bridge.launcher;
|
exports.launcher = bridge.launcher;
|
||||||
|
|||||||
17
packages/now-node/src/types.ts
Normal file
17
packages/now-node/src/types.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { ServerResponse, IncomingMessage } from 'http';
|
||||||
|
|
||||||
|
export type NowRequestCookies = { [key: string]: string };
|
||||||
|
export type NowRequestQuery = { [key: string]: string | string[] };
|
||||||
|
export type NowRequestBody = any;
|
||||||
|
|
||||||
|
export type NowRequest = IncomingMessage & {
|
||||||
|
query: NowRequestQuery;
|
||||||
|
cookies: NowRequestCookies;
|
||||||
|
body: NowRequestBody;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type NowResponse = ServerResponse & {
|
||||||
|
send: (body: any) => NowResponse;
|
||||||
|
json: (body: any) => NowResponse;
|
||||||
|
status: (statusCode: number) => NowResponse;
|
||||||
|
};
|
||||||
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
20
packages/now-node/test/fixtures/15-helpers/express-compat/index.js
vendored
Normal file
20
packages/now-node/test/fixtures/15-helpers/express-compat/index.js
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
/* 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`);
|
||||||
|
});
|
||||||
5
packages/now-node/test/fixtures/15-helpers/express-compat/package.json
vendored
Normal file
5
packages/now-node/test/fixtures/15-helpers/express-compat/package.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"express": "4.17.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
17
packages/now-node/test/fixtures/15-helpers/index.js
vendored
Normal file
17
packages/now-node/test/fixtures/15-helpers/index.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/* eslint-disable prefer-destructuring */
|
||||||
|
|
||||||
|
module.exports = (req, res) => {
|
||||||
|
res.status(200);
|
||||||
|
|
||||||
|
let who = 'anonymous';
|
||||||
|
|
||||||
|
if (req.body && req.body.who) {
|
||||||
|
who = req.body.who;
|
||||||
|
} else if (req.query.who) {
|
||||||
|
who = req.query.who;
|
||||||
|
} else if (req.cookies.who) {
|
||||||
|
who = req.cookies.who;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.send(`hello ${who}:RANDOMNESS_PLACEHOLDER`);
|
||||||
|
};
|
||||||
14
packages/now-node/test/fixtures/15-helpers/micro-compat/index.js
vendored
Normal file
14
packages/now-node/test/fixtures/15-helpers/micro-compat/index.js
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/* eslint-disable prefer-destructuring */
|
||||||
|
const { json, send } = require('micro');
|
||||||
|
|
||||||
|
module.exports = async (req, res) => {
|
||||||
|
const body = await json(req);
|
||||||
|
|
||||||
|
let who = 'anonymous';
|
||||||
|
|
||||||
|
if (body && body.who) {
|
||||||
|
who = body.who;
|
||||||
|
}
|
||||||
|
|
||||||
|
send(res, 200, `hello ${who}:RANDOMNESS_PLACEHOLDER`);
|
||||||
|
};
|
||||||
5
packages/now-node/test/fixtures/15-helpers/micro-compat/package.json
vendored
Normal file
5
packages/now-node/test/fixtures/15-helpers/micro-compat/package.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"micro": "9.3.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
5
packages/now-node/test/fixtures/15-helpers/no-helpers/index.js
vendored
Normal file
5
packages/now-node/test/fixtures/15-helpers/no-helpers/index.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = (req, res) => {
|
||||||
|
const areHelpersAvailable = typeof req.query !== 'undefined';
|
||||||
|
|
||||||
|
res.end(`${areHelpersAvailable ? 'yes' : 'no'}:RANDOMNESS_PLACEHOLDER`);
|
||||||
|
};
|
||||||
55
packages/now-node/test/fixtures/15-helpers/now.json
vendored
Normal file
55
packages/now-node/test/fixtures/15-helpers/now.json
vendored
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"builds": [
|
||||||
|
{ "src": "index.js", "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": "no-helpers/index.js",
|
||||||
|
"use": "@now/node",
|
||||||
|
"config": { "helpers": false }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"probes": [
|
||||||
|
{
|
||||||
|
"path": "/",
|
||||||
|
"mustContain": "hello anonymous:RANDOMNESS_PLACEHOLDER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/?who=bill",
|
||||||
|
"mustContain": "hello bill:RANDOMNESS_PLACEHOLDER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/",
|
||||||
|
"method": "POST",
|
||||||
|
"body": { "who": "john" },
|
||||||
|
"mustContain": "hello john:RANDOMNESS_PLACEHOLDER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/",
|
||||||
|
"headers": { "cookie": "who=chris" },
|
||||||
|
"mustContain": "hello chris:RANDOMNESS_PLACEHOLDER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/ts",
|
||||||
|
"mustContain": "hello:RANDOMNESS_PLACEHOLDER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/express-compat",
|
||||||
|
"method": "POST",
|
||||||
|
"body": { "who": "sara" },
|
||||||
|
"mustContain": "hello sara:RANDOMNESS_PLACEHOLDER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/micro-compat",
|
||||||
|
"method": "POST",
|
||||||
|
"body": { "who": "katie" },
|
||||||
|
"mustContain": "hello katie:RANDOMNESS_PLACEHOLDER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/no-helpers/",
|
||||||
|
"mustContain": "no:RANDOMNESS_PLACEHOLDER"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
6
packages/now-node/test/fixtures/15-helpers/ts/index.ts
vendored
Normal file
6
packages/now-node/test/fixtures/15-helpers/ts/index.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { NowRequest, NowResponse } from './types';
|
||||||
|
|
||||||
|
export default function listener(req: NowRequest, res: NowResponse) {
|
||||||
|
res.status(200);
|
||||||
|
res.send('hello:RANDOMNESS_PLACEHOLDER');
|
||||||
|
}
|
||||||
11
packages/now-node/test/fixtures/15-helpers/ts/tsconfig.json
vendored
Normal file
11
packages/now-node/test/fixtures/15-helpers/ts/tsconfig.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"lib": ["esnext"],
|
||||||
|
"target": "esnext",
|
||||||
|
"module": "commonjs"
|
||||||
|
},
|
||||||
|
"include": ["index.ts"]
|
||||||
|
}
|
||||||
317
packages/now-node/test/helpers.test.js
Normal file
317
packages/now-node/test/helpers.test.js
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
/* global beforeAll, beforeEach, afterAll, expect, it, jest */
|
||||||
|
const fetch = require('node-fetch');
|
||||||
|
const listen = require('test-listen');
|
||||||
|
const qs = require('querystring');
|
||||||
|
|
||||||
|
const { createServerWithHelpers } = require('../dist/helpers');
|
||||||
|
|
||||||
|
const mockListener = jest.fn((req, res) => {
|
||||||
|
res.send('hello');
|
||||||
|
});
|
||||||
|
const consumeEventMock = jest.fn(() => ({}));
|
||||||
|
const mockBridge = { consumeEvent: consumeEventMock };
|
||||||
|
|
||||||
|
let server;
|
||||||
|
let url;
|
||||||
|
|
||||||
|
const nowProps = [
|
||||||
|
['query', 0],
|
||||||
|
['cookies', 0],
|
||||||
|
['body', 0],
|
||||||
|
['status', 1],
|
||||||
|
['send', 1],
|
||||||
|
['json', 1],
|
||||||
|
];
|
||||||
|
|
||||||
|
async function fetchWithProxyReq(_url, opts = {}) {
|
||||||
|
if (opts.body) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
opts = { ...opts, body: Buffer.from(opts.body) };
|
||||||
|
}
|
||||||
|
|
||||||
|
consumeEventMock.mockImplementationOnce(() => opts);
|
||||||
|
|
||||||
|
return fetch(_url, {
|
||||||
|
...opts,
|
||||||
|
headers: { ...opts.headers, 'x-now-bridge-request-id': '2' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
server = createServerWithHelpers(mockListener, mockBridge);
|
||||||
|
url = await listen(server);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockListener.mockClear();
|
||||||
|
consumeEventMock.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await server.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call consumeEvent with the correct reqId', async () => {
|
||||||
|
await fetchWithProxyReq(`${url}/`);
|
||||||
|
|
||||||
|
expect(consumeEventMock).toHaveBeenLastCalledWith('2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not expose the request id header', async () => {
|
||||||
|
await fetchWithProxyReq(`${url}/`, { headers: { 'x-test-header': 'ok' } });
|
||||||
|
|
||||||
|
const [{ headers }] = mockListener.mock.calls[0];
|
||||||
|
|
||||||
|
expect(headers['x-now-bridge-request-id']).toBeUndefined();
|
||||||
|
expect(headers['x-test-header']).toBe('ok');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('req.query should reflect querystring in the url', async () => {
|
||||||
|
await fetchWithProxyReq(`${url}/?who=bill&where=us`);
|
||||||
|
|
||||||
|
expect(mockListener.mock.calls[0][0].query).toMatchObject({
|
||||||
|
who: 'bill',
|
||||||
|
where: 'us',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('req.query should be {} when there is no querystring', async () => {
|
||||||
|
await fetchWithProxyReq(url);
|
||||||
|
const [{ query }] = mockListener.mock.calls[0];
|
||||||
|
expect(Object.keys(query).length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('req.cookies should reflect req.cookie header', async () => {
|
||||||
|
await fetchWithProxyReq(url, {
|
||||||
|
headers: {
|
||||||
|
cookie: 'who=bill; where=us',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockListener.mock.calls[0][0].cookies).toMatchObject({
|
||||||
|
who: 'bill',
|
||||||
|
where: 'us',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('req.body should be undefined by default', async () => {
|
||||||
|
await fetchWithProxyReq(url);
|
||||||
|
expect(mockListener.mock.calls[0][0].body).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('req.body should be undefined if content-type is not defined', async () => {
|
||||||
|
await fetchWithProxyReq(url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: 'hello',
|
||||||
|
});
|
||||||
|
expect(mockListener.mock.calls[0][0].body).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('req.body should be a string when content-type is `text/plain`', async () => {
|
||||||
|
await fetchWithProxyReq(url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: 'hello',
|
||||||
|
headers: { 'content-type': 'text/plain' },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockListener.mock.calls[0][0].body).toBe('hello');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('req.body should be a buffer when content-type is `application/octet-stream`', async () => {
|
||||||
|
await fetchWithProxyReq(url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: 'hello',
|
||||||
|
headers: { 'content-type': 'application/octet-stream' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const [{ body }] = mockListener.mock.calls[0];
|
||||||
|
|
||||||
|
const str = body.toString();
|
||||||
|
|
||||||
|
expect(Buffer.isBuffer(body)).toBe(true);
|
||||||
|
expect(str).toBe('hello');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('req.body should be an object when content-type is `application/x-www-form-urlencoded`', async () => {
|
||||||
|
const obj = { who: 'mike' };
|
||||||
|
|
||||||
|
await fetchWithProxyReq(url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: qs.encode(obj),
|
||||||
|
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockListener.mock.calls[0][0].body).toMatchObject(obj);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('req.body should be an object when content-type is `application/json`', async () => {
|
||||||
|
const json = {
|
||||||
|
who: 'bill',
|
||||||
|
where: 'us',
|
||||||
|
};
|
||||||
|
|
||||||
|
await fetchWithProxyReq(url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(json),
|
||||||
|
headers: { 'content-type': 'application/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 () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
console.log(req.body);
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: '',
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
});
|
||||||
|
|
||||||
|
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`, {
|
||||||
|
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(() => {});
|
||||||
|
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
try {
|
||||||
|
if (req.body === undefined) res.status(400);
|
||||||
|
} catch (error) {
|
||||||
|
bodySpy(error);
|
||||||
|
} finally {
|
||||||
|
res.end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await fetchWithProxyReq(url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: '{"wrong":"json"',
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(bodySpy).toHaveBeenCalled();
|
||||||
|
|
||||||
|
const [error] = bodySpy.mock.calls[0];
|
||||||
|
expect(error.message).toMatch(/invalid json/i);
|
||||||
|
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 () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
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) => {
|
||||||
|
res.status(404);
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
|
||||||
|
expect(res.status).toBe(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('res.status().send() should work', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.status(404).send('notfound');
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
|
||||||
|
expect(res.status).toBe(404);
|
||||||
|
expect(await res.text()).toBe('notfound');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('res.status().json() should work', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.status(404).json({ error: 'not found' });
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
|
||||||
|
expect(res.status).toBe(404);
|
||||||
|
expect(await res.json()).toMatchObject({ error: 'not found' });
|
||||||
|
});
|
||||||
@@ -7,7 +7,8 @@
|
|||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"sourceMap": false,
|
"sourceMap": false,
|
||||||
"declaration": false
|
"declaration": true,
|
||||||
|
"typeRoots": ["./@types", "./node_modules/@types"]
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"],
|
"include": ["src/**/*"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/optipng",
|
"name": "@now/optipng",
|
||||||
"version": "0.6.2",
|
"version": "0.6.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index",
|
"main": "./dist/index",
|
||||||
"files": [
|
"files": [
|
||||||
@@ -21,6 +21,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "11.9.4",
|
"@types/node": "11.9.4",
|
||||||
"typescript": "3.3.3"
|
"typescript": "3.5.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,8 +68,9 @@ elif 'app' in __now_variables:
|
|||||||
if isinstance(body, string_types):
|
if isinstance(body, string_types):
|
||||||
body = to_bytes(body, charset='utf-8')
|
body = to_bytes(body, charset='utf-8')
|
||||||
|
|
||||||
path = unquote(payload['path'])
|
url = urlparse(unquote(payload['path']))
|
||||||
query = urlparse(path).query
|
query = url.query
|
||||||
|
path = url.path
|
||||||
|
|
||||||
environ = {
|
environ = {
|
||||||
'CONTENT_LENGTH': str(len(body)),
|
'CONTENT_LENGTH': str(len(body)),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/python",
|
"name": "@now/python",
|
||||||
"version": "0.2.5",
|
"version": "0.2.8",
|
||||||
"main": "index.js",
|
"main": "./dist/index.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"test": "tsc && jest",
|
"test": "tsc && jest",
|
||||||
"prepublish": "tsc"
|
"prepublishOnly": "tsc"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"execa": "^1.0.0",
|
"execa": "^1.0.0",
|
||||||
@@ -23,6 +23,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" }]
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
from flask import Flask, Response, __version__
|
from flask import Flask, Response, request, __version__
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
@app.route('/', defaults={'path': ''})
|
@app.route('/', defaults={'path': ''})
|
||||||
@app.route('/<path:path>')
|
@app.route('/<path:path>')
|
||||||
def catch_all(path):
|
def catch_all(path):
|
||||||
return Response("path=%s" %(path), mimetype='text/html')
|
qs = request.args.to_dict()
|
||||||
|
return Response("path: %s query: %s" %(path, qs), mimetype='text/html')
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
from flask import Flask, Response, __version__
|
from flask import Flask, Response, request, __version__
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
@app.route('/', defaults={'path': ''})
|
@app.route('/', defaults={'path': ''})
|
||||||
@app.route('/<path:path>')
|
@app.route('/<path:path>')
|
||||||
def catch_all(path):
|
def catch_all(path):
|
||||||
return Response("path=%s" %(path), mimetype='text/html')
|
qs = request.args.to_dict()
|
||||||
|
return Response("path: %s query: %s" %(path, qs), mimetype='text/html')
|
||||||
@@ -3,7 +3,10 @@
|
|||||||
"builds": [{ "src": "*.py", "use": "@now/python" }],
|
"builds": [{ "src": "*.py", "use": "@now/python" }],
|
||||||
"routes": [{ "src": "/another", "dest": "custom.py" }],
|
"routes": [{ "src": "/another", "dest": "custom.py" }],
|
||||||
"probes": [
|
"probes": [
|
||||||
{ "path": "/?hello=/", "mustContain": "path=?hello=/" },
|
{ "path": "/?hello=/", "mustContain": "path: query: {'hello': '/'}" },
|
||||||
{ "path": "/another?hello=/", "mustContain": "path=another?hello=/" }
|
{
|
||||||
|
"path": "/another?hello=/",
|
||||||
|
"mustContain": "path: another query: {'hello': '/'}"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
1
packages/now-ruby/.gitignore
vendored
Normal file
1
packages/now-ruby/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/dist
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user