Compare commits

...

31 Commits

Author SHA1 Message Date
luc
3108332043 Publish
- @now/build-utils@0.5.8
 - @now/md@0.5.4
 - @now/next@0.4.2
 - @now/node-bridge@1.2.0
 - @now/node-server@0.7.5
 - @now/node@0.9.0
 - @now/python@0.2.7
 - @now/static-build@0.5.9
2019-06-17 18:19:38 +02:00
Steven
7509c82c32 [tests] Fix yarn.lock (#628) 2019-06-17 16:40:33 +02:00
Steven
c4f5a5b48d [now-node] Remove layers (#617) 2019-06-17 16:05:31 +02:00
Luc
05314da810 [now-node] Fix faulty behavior when request's body is empty and content-type is application/json (#615)
* add test for empty body and `application/json`

* fix test
2019-06-17 16:01:16 +02:00
Steven
5f1cf714c1 [tests] Run against all commits in the branch (#611) 2019-06-17 16:01:09 +02:00
Steven
2623e2e799 [now-node] Bump layer versions (#610) 2019-06-17 16:01:00 +02:00
Steven
bac1da09d4 [now-build-utils] Add types for Layers (#604)
* [now-build-utils] Add PrepareLayersOptions

* [now-build-utils] Add types for layers

* Add layer types to lambda

* Add any values to a layer function

* Change props to optional

* Update layers per call

* Add missing getFiles function

* Fix types

* Improve error message

* Change createLambda() api

* Remove node-gyp hack

* [now-layer-node] Add bootstrap

* Add fallback to empty object

* Add config.useLayers

* Remove config, change to layers check

* Fix typo

* Add deprecation message
2019-06-17 16:00:47 +02:00
Luc
5b57f1a3ac [now-node] Improve @now/node helpers (#609)
* lazy load everything 

* do not read charset in content-type to set encoding

* add tests

* add tests for express compat

* add test for `res.status().send()`

* update after PR comments
2019-06-17 16:00:39 +02:00
Nathan Rajlich
2e95dd5329 [now-md] Fix shouldServe() logic (#608)
* [now-md] Fix `shouldServe()` logic

Since this builder is not a 1-1 mapping of the input -> output names,
the default `shouldServe()` function needs to be augmented such that
the `.html` requestPath is "considered like" a `.md` file.

* Remove "ends with .html" optimization

It breaks index files, i.e. `GET /`
2019-06-17 16:00:30 +02:00
Joe Haddad
215f6367d6 [now-next] Humanize message about now-build (#607)
Some users read this message as a warning that required action (the captain caps lock WARNING didn't help).

Users should never need to define a `now-build` script unless they grow out of running `next build`.
2019-06-17 16:00:24 +02:00
Joe Haddad
e8cd348a79 [now-next] Fix erroneous .next folder message (#606)
We now store the `.next/cache` folder so we need to check for the `static` folder to know if the user uploaded something they should've excluded.
2019-06-17 16:00:17 +02:00
Luc
168f373641 [now-node] Assign type for req.headers.cookie (#603) 2019-06-17 16:00:00 +02:00
Luc
8c3174be29 [now-node] Move @types/node to dependencies (#602)
* move @types/node to dependencies

* fix types
2019-06-17 15:59:44 +02:00
Luc
898de78b63 [now-node] Make helpers opt-out instead of opt-in (#601)
* set helpers to true by default

* update tests
2019-06-17 15:58:00 +02:00
Luc
26e33c1c4b Pin now-node-bridge dependencies in now-next and now-node-server (#600)
* pin dependencies in now-next

* pin dependencies in now-node-server

* regenerate yarn.lock
2019-06-17 15:57:33 +02:00
Luc
c2f95de3ec [now-node] Helpers compatibility with express, micro, etc (#594)
* copy bridge into now-node

* pass body buffer to listener

* only send addon when helpers are added

* ship bridge.js to deployment

* remove raw-body deps

* fix not waiting for server `listening` event

* add test for express compat

* add test for micro compat

* update now-node-bridge

* remove unnecessary yarn.lock

* fix wrong replacement in launcher.ts

* fix listener not defined

* fix unit tests

* add "test" for exports

* add console.log

* add test in node-bridge

* log error before throwing

* revert now-node-bridge to canary state

* remove unused code

* refactor consumeProxyRequests -> consumeEvent

* remove ts-jest

* update tests

* do not transform body to string if not necessary

* fix tests

* remove jest from deps in now-node

* x-bridge-reqid -> x-now-bridge-request-id

* add test for consumeEvent

* do not expose request id header to the client

* pin node-bridge version

* update node-bridge deps to 1.2.0-canary.1

* update yarn.lock

* add await for user's listener

* refactor

* pass async function to `Server`
2019-06-17 15:56:09 +02:00
Luc
6a7de860db [now-node-bridge] Make normalized events consumable by server (#598)
* add `consumeProxyRequest` to bridge

* refactor

* fix tests

* Update packages/now-node-bridge/test/bridge.test.js

Co-Authored-By: Leo Lamprecht <mindrun@icloud.com>

* Update packages/now-node-bridge/src/bridge.ts

Co-Authored-By: Leo Lamprecht <mindrun@icloud.com>
2019-06-17 15:28:05 +02:00
Steven
acb8cadafe [now-static-build] Should fail build when distDir is empty (#597) 2019-06-17 15:27:58 +02:00
Kai Richard König
1a8df7080d [now-python] Do not append query string to path (#580)
* Do not append query string to path - fixes \#545

* Avoid creating multiple parsed urls

* Fix missing whitespace

* Fix handler

* Remove unused imports
2019-06-17 15:27:50 +02:00
JJ Kasper
5a92826eb0 [now-next] Update appending .html to exported routes (#592) 2019-06-17 15:27:24 +02:00
Steven
e083aa3750 Publish
- @now/node@0.8.1
2019-06-08 14:56:27 -04:00
Steven
941f675657 [now-node] Change helpers to opt-in (#591)
* [now-node] Change helpers to opt-in

* Fix tests

* Fix tests
2019-06-08 14:55:56 -04:00
Steven
6fad726abb Publish
- @now/build-utils@0.5.7
 - @now/node@0.8.0
2019-06-08 10:38:13 -04:00
Luc
dd22051d6b [now-node] Add tests for types exports (#590)
* helpers.test.js -> helpers.test.ts

* import NowRequest and NowResponse in test

* fix addHelpers return type

* add comments
2019-06-08 10:36:05 -04:00
Luc
7e86cb403f [now-node] Add Express-like API (#577)
* first iteration of express-like api for @now/node

* fix error when config is undefined

* add integration test for helpers

* add `res.status()` to helpers integration test

* fix error caused by config values being strings

* add helpers opt-out integration test

* add `helpers.js` to deployed files

* add boolean and number to config values types

* update config.helpers to work with boolean

* add unit tests

* fix type error in config type

* add unit test for req.body

* ignore errors not generated in helpers

* Update packages/now-node/test/fixtures/15-helpers/no-helpers/index.js

Co-Authored-By: Steven <steven@ceriously.com>

* update config type

* use ternary instead of filtering with `Boolean`

* add probe in 15-helpers

* add probe for ts function

* ncc `helpers.js`

* fix Config type

* fix @now/rust type issue

* add comment in build.sh

* test that content-type header is correctly added

* add missing tsconfig.json in fixtures

* add `body` to fix `Invalid JSON` errors

* Revert "add `body` to fix `Invalid JSON` errors"

This reverts commit 9b2ff55409501140f0d7411d121fc3a4dfd34ccc.

* make `helpers` false by default

* add method POST to probe for helpers

* Revert "make `helpers` false by default"

This reverts commit d029a432a0bf2463e1613e6cfd76929ce6e45073.

* replace POST requests by GET in probes

* remove unnecessary comments

* destructure in parseQuery

* keep @now/rust unchanged

* Request -> NowRequest and Response -> NowResponse

* improve config types

* add NowListener type

* export NowRequest and NowResponse

* generate `.d.ts` files

* add types to helpers/ts fixtures

* Update packages/now-build-utils/src/types.ts

Co-Authored-By: Steven <steven@ceriously.com>

* Update packages/now-node/build.sh

Co-Authored-By: Steven <steven@ceriously.com>
2019-06-08 10:35:45 -04:00
Nathan Rajlich
d19d557738 [now-node] Add watch array for Builder v2 API (#582)
* [now-node] Add `watch` array

For `now dev`

* Use ncc-watcher from npm registry

* Update `yarn.lock`

* Fix integration tests

* Apply @styfle's suggestions

* Update `@zeit/ncc-watcher` to v1.0.3
2019-06-08 10:35:23 -04:00
Steven
e4281f698c Publish
- @now/rust@0.2.6
2019-06-07 12:56:50 -04:00
Steven
86ff681c6d Use prettier on .json files (#588) 2019-06-07 12:56:21 -04:00
Steven
ba97a7cf19 [now-rust] Move ts files into /src (#585) 2019-06-07 12:56:14 -04:00
Luc
0a94397700 [now-rust] config.includeFiles can be string[] (#584) 2019-06-07 12:56:04 -04:00
Luc
5c8e2f2ccc Fix content-type: application/json header being added even if body is undefined (#583) 2019-06-07 12:55:47 -04:00
76 changed files with 1115 additions and 848 deletions

View File

@@ -6,9 +6,6 @@
/packages/now-build-utils/src/*.js
/packages/now-build-utils/src/fs/*.js
/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-node-bridge/*
/packages/now-python/dist/*

View File

@@ -1,3 +0,0 @@
{
"eslint.enable": false
}

View File

@@ -1,13 +1,15 @@
const childProcess = require('child_process');
const path = require('path');
const { execSync } = require('child_process');
const { relative } = require('path');
const command = 'git diff HEAD~1 --name-only';
const diff = childProcess.execSync(command).toString();
const branch = execSync('git branch | grep "*" | cut -d " " -f2').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
.split('\n')
.filter(item => Boolean(item) && item.includes('packages/'))
.map(item => path.relative('packages', item).split('/')[0]);
.map(item => relative('packages', item).split('/')[0]);
const matches = [];

View File

@@ -33,6 +33,10 @@
"prettier --write",
"git add"
],
"*.json": [
"prettier --write",
"git add"
],
"*.md": [
"prettier --write",
"git add"

View File

@@ -1,6 +1,6 @@
{
"name": "@now/build-utils",
"version": "0.5.6",
"version": "0.5.8",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.js",

View File

@@ -3,6 +3,7 @@ import fs from 'fs-extra';
import path from 'path';
import spawn from 'cross-spawn';
import { SpawnOptions } from 'child_process';
import { deprecate } from 'util';
function spawnAsync(
command: string,
@@ -64,12 +65,14 @@ async function scanParentDirs(destPath: string, scriptName?: string) {
// eslint-disable-next-line no-await-in-loop
if (await fs.pathExists(packageJsonPath)) {
// eslint-disable-next-line no-await-in-loop
const packageJson = JSON.parse(
await fs.readFile(packageJsonPath, 'utf8')
);
hasScript = Boolean(
packageJson.scripts && scriptName && packageJson.scripts[scriptName]
);
if (scriptName) {
const packageJson = JSON.parse(
await fs.readFile(packageJsonPath, 'utf8')
);
hasScript = Boolean(
packageJson.scripts && scriptName && packageJson.scripts[scriptName]
);
}
// eslint-disable-next-line no-await-in-loop
hasPackageLockJson = await fs.pathExists(
path.join(currentDestPath, 'package-lock.json')
@@ -85,9 +88,10 @@ async function scanParentDirs(destPath: string, scriptName?: string) {
return { hasScript, hasPackageLockJson };
}
export async function installDependencies(
export async function runNpmInstall(
destPath: string,
args: string[] = []
args: string[] = [],
cmd?: string
) {
assert(path.isAbsolute(destPath));
@@ -95,30 +99,26 @@ export async function installDependencies(
console.log(`installing to ${destPath}`);
const { hasPackageLockJson } = await scanParentDirs(destPath);
const opts = {
const opts: SpawnOptions = {
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) {
commandArgs = args.filter(a => a !== '--prefer-offline');
await spawnAsync(
'npm',
['install', '--unsafe-perm'].concat(commandArgs),
cmd || 'npm',
commandArgs.concat(['install', '--unsafe-perm']),
destPath,
opts as SpawnOptions
opts
);
} else {
await spawnAsync(
'yarn',
['--ignore-engines', '--cwd', destPath].concat(commandArgs),
cmd || 'yarn',
commandArgs.concat(['--ignore-engines', '--cwd', destPath]),
destPath,
opts as SpawnOptions
opts
);
}
}
@@ -151,4 +151,11 @@ export async function runPackageJsonScript(
return true;
}
export const runNpmInstall = installDependencies;
/**
* installDependencies() is deprecated.
* Please use runNpmInstall() instead.
*/
export const installDependencies = deprecate(
runNpmInstall,
'installDependencies() is deprecated. Please use runNpmInstall() instead.'
);

View File

@@ -16,7 +16,14 @@ export interface Files {
}
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 {

View File

@@ -1 +0,0 @@
/dist

View File

@@ -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"
}
}

View File

@@ -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 };
}

View File

@@ -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}`;
}

View File

@@ -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;
}

View File

@@ -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();
});
});

View File

@@ -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"
}
}

View File

@@ -1,4 +0,0 @@
declare module 'promisepipe' {
import { Stream } from 'stream';
export default function pipe(...args: Stream[]): Promise<void>;
}

View File

@@ -1,6 +0,0 @@
declare module 'stream-to-promise' {
import { Stream } from 'stream';
export default function streamToPromise(
stream: NodeJS.ReadableStream
): Promise<string>;
}

View File

@@ -1 +0,0 @@
/dist

View File

@@ -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"
}
}

View File

@@ -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 };
}

View File

@@ -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 };
}

View File

@@ -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();
});
});

View File

@@ -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"
}
}

View File

@@ -1,4 +0,0 @@
declare module 'promisepipe' {
import { Stream } from 'stream';
export default function pipe(...args: Stream[]): Promise<void>;
}

View File

@@ -1 +0,0 @@
/dist

View File

@@ -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"
}
}

View File

@@ -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 };
}

View File

@@ -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 };
}

View File

@@ -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();
});
});

View File

@@ -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"
}
}

View File

@@ -1,4 +0,0 @@
declare module 'promisepipe' {
import { Stream } from 'stream';
export default function pipe(...args: Stream[]): Promise<void>;
}

View File

@@ -39,4 +39,7 @@ exports.build = async ({ files, entrypoint, config }) => {
return { [replacedEntrypoint]: result };
};
exports.shouldServe = shouldServe;
exports.shouldServe = (options) => {
const requestPath = options.requestPath.replace(/\.html$/, '.md');
return shouldServe({ ...options, requestPath });
};

View File

@@ -1,6 +1,6 @@
{
"name": "@now/md",
"version": "0.5.3",
"version": "0.5.4",
"license": "MIT",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@now/next",
"version": "0.4.1",
"version": "0.4.2",
"license": "MIT",
"main": "./dist/index",
"scripts": {
@@ -14,7 +14,7 @@
"directory": "packages/now-next"
},
"dependencies": {
"@now/node-bridge": "^1.1.4",
"@now/node-bridge": "^1.2.0",
"fs-extra": "^7.0.0",
"get-port": "^5.0.0",
"resolve-from": "^5.0.0",

View File

@@ -166,7 +166,7 @@ export const build = async ({
const entryDirectory = path.dirname(entrypoint);
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...`);
await download(files, workPath, meta);
@@ -227,7 +227,7 @@ export const build = async ({
};
}
if (await pathExists(dotNext)) {
if (await pathExists(dotNextStatic)) {
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.'
);
@@ -259,14 +259,15 @@ export const build = async ({
console.log('normalized package.json result: ', packageJson);
await writePackageJson(entryPath, packageJson);
} else if (!pkg.scripts || !pkg.scripts['now-build']) {
console.warn(
'WARNING: "now-build" script not found. Adding \'"now-build": "next build"\' to "package.json" automatically'
console.log(
'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 = {
'now-build': 'next build',
...(pkg.scripts || {}),
};
console.log('normalized package.json result: ', pkg);
await writePackageJson(entryPath, pkg);
}
@@ -510,7 +511,7 @@ export const build = async ({
).map(route => {
// make sure .html is added to dest for now until
// 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`;
}
return route;

View File

@@ -1,6 +1,6 @@
{
"name": "@now/node-bridge",
"version": "1.1.4",
"version": "1.2.0",
"license": "MIT",
"main": "./index.js",
"repository": {

View File

@@ -95,9 +95,13 @@ export class Bridge {
private server: Server | null;
private listening: Promise<AddressInfo>;
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.shouldStoreEvents = shouldStoreEvents;
if (server) {
this.setServer(server);
}
@@ -144,15 +148,16 @@ export class Bridge {
context.callbackWaitsForEmptyEventLoop = false;
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 = {
hostname: '127.0.0.1',
port,
path,
method,
headers,
};
if (this.shouldStoreEvents) {
const reqId = `${this.reqIdSeed++}`;
this.events[reqId] = normalizedEvent;
headers['x-now-bridge-request-id'] = reqId;
}
const opts = { hostname: '127.0.0.1', port, path, method, headers };
// eslint-disable-next-line consistent-return
return new Promise((resolve, reject) => {
@@ -192,4 +197,10 @@ export class Bridge {
req.end();
});
}
consumeEvent(reqId: string) {
const event = this.events[reqId];
delete this.events[reqId];
return event;
}
}

View File

@@ -83,3 +83,41 @@ test('`NowProxyEvent` normalizing', async () => {
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();
});

View File

@@ -1,6 +1,6 @@
{
"name": "@now/node-server",
"version": "0.7.4",
"version": "0.7.5",
"license": "MIT",
"repository": {
"type": "git",
@@ -8,7 +8,7 @@
"directory": "packages/now-node-server"
},
"dependencies": {
"@now/node-bridge": "^1.1.4",
"@now/node-bridge": "^1.2.0",
"@zeit/ncc": "0.18.5",
"fs-extra": "7.0.1"
},

View File

@@ -1,2 +1,3 @@
/dist
/src/bridge.d.ts
/test/fixtures/**/types.d.ts

View 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;
}

View File

@@ -9,4 +9,16 @@ if [ ! -e "$bridge_defs" ]; then
fi
cp -v "$bridge_defs" src
# build ts files
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

View File

@@ -1,6 +1,6 @@
{
"name": "@now/node",
"version": "0.7.4",
"version": "0.9.0",
"license": "MIT",
"main": "./dist/index",
"repository": {
@@ -9,8 +9,10 @@
"directory": "packages/now-node"
},
"dependencies": {
"@now/node-bridge": "^1.1.4",
"@now/node-bridge": "^1.2.0",
"@types/node": "*",
"@zeit/ncc": "0.18.5",
"@zeit/ncc-watcher": "1.0.3",
"fs-extra": "7.0.1"
},
"scripts": {
@@ -22,8 +24,13 @@
"dist"
],
"devDependencies": {
"@types/node": "11.9.4",
"jest": "24.1.0",
"@types/content-type": "1.1.3",
"@types/cookie": "0.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.3.3"
}
}

View File

@@ -0,0 +1,3 @@
// we intentionally import these types here
// to test that they are exported from index
import { NowRequest, NowResponse } from './index';

View 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;
}

View File

@@ -1,5 +1,7 @@
import { join, dirname, sep } from 'path';
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 {
glob,
download,
@@ -14,6 +16,7 @@ import {
BuildOptions,
shouldServe,
} from '@now/build-utils';
export { NowRequest, NowResponse } from './types';
interface CompilerConfig {
includeFiles?: string | string[];
@@ -23,8 +26,25 @@ interface DownloadOptions {
files: Files;
entrypoint: string;
workPath: string;
meta?: Meta;
npmArguments?: string[];
meta: Meta;
}
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({
@@ -32,32 +52,64 @@ async function downloadInstallAndBundle({
entrypoint,
workPath,
meta,
npmArguments = [],
}: DownloadOptions) {
console.log('downloading user files...');
const downloadedFiles = await download(files, workPath, meta);
console.log("installing dependencies for user's code...");
const entrypointFsDirname = join(workPath, dirname(entrypoint));
await runNpmInstall(entrypointFsDirname, npmArguments);
await runNpmInstall(entrypointFsDirname, ['--prefer-offline']);
const entrypointPath = downloadedFiles[entrypoint].fsPath;
return { entrypointPath, entrypointFsDirname };
}
async function compile(
workPath: string,
entrypointPath: string,
entrypoint: string,
config: CompilerConfig
): Promise<Files> {
config: CompilerConfig,
{ isDev, filesChanged, filesRemoved }: Meta
): Promise<{ preparedFiles: Files; watch: string[] }> {
const input = entrypointPath;
const inputDir = dirname(input);
const rootIncludeFiles = inputDir.split(sep).pop() || '';
const ncc = require('@zeit/ncc');
const { code, map, assets } = await ncc(input, {
const options: NccOptions = {
sourceMap: 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) {
const includeFiles =
@@ -81,7 +133,7 @@ async function compile(
}
assets[fullPath] = {
source: data,
source: toBuffer(data),
permissions: mode,
};
}
@@ -89,11 +141,15 @@ async function compile(
}
const preparedFiles: Files = {};
// move all user code to 'user' subdirectory
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
for (const assetName of Object.keys(assets)) {
const { source: data, permissions: mode } = assets[assetName];
@@ -101,9 +157,11 @@ async function compile(
preparedFiles[join(dirname(entrypoint), assetName)] = blob2;
}
return preparedFiles;
return { preparedFiles, watch };
}
export const version = 2;
export const config = {
maxLambdaSize: '5mb',
};
@@ -113,8 +171,10 @@ export async function build({
entrypoint,
workPath,
config,
meta,
meta = {},
}: BuildOptions) {
const shouldAddHelpers = !(config && config.helpers === false);
const {
entrypointPath,
entrypointFsDirname,
@@ -123,37 +183,62 @@ export async function build({
entrypoint,
workPath,
meta,
npmArguments: ['--prefer-offline'],
});
console.log('running user script...');
await runPackageJsonScript(entrypointFsDirname, 'now-build');
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');
let launcherData = await readFile(launcherPath, 'utf8');
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;',
shouldAddHelpers
? 'const server = require("./helpers").createServerWithHelpers(listener, bridge);'
: 'const server = require("http").createServer(listener);',
'bridge.setServer(server);',
].join(' ')
);
const launcherFiles = {
const launcherFiles: Files = {
'launcher.js': new FileBlob({ data: launcherData }),
'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({
files: { ...preparedFiles, ...launcherFiles },
files: {
...preparedFiles,
...launcherFiles,
},
handler: 'launcher.launcher',
runtime: 'nodejs8.10',
});
return { [entrypoint]: lambda };
const output = { [entrypoint]: lambda };
const result = { output, watch };
return result;
}
export async function prepareCache({ workPath }: PrepareCacheOptions) {

View File

@@ -1,7 +1,9 @@
import { Server } from 'http';
import { Bridge } from './bridge';
let listener;
let shouldStoreProxyRequests: boolean = false;
// PLACEHOLDER:shouldStoreProxyRequests
const bridge = new Bridge(undefined, shouldStoreProxyRequests);
if (!process.env.NODE_ENV) {
process.env.NODE_ENV =
@@ -9,7 +11,7 @@ if (!process.env.NODE_ENV) {
}
try {
// PLACEHOLDER
// PLACEHOLDER:setServer
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
console.error(err.message);
@@ -23,8 +25,6 @@ try {
}
}
const server = new Server(listener);
const bridge = new Bridge(server);
bridge.listen();
exports.launcher = bridge.launcher;

View 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;
};

View 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`);
});

View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"express": "4.17.1"
}
}

View 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`);
};

View 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`);
};

View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"micro": "9.3.4"
}
}

View File

@@ -0,0 +1,5 @@
module.exports = (req, res) => {
const areHelpersAvailable = typeof req.query !== 'undefined';
res.end(`${areHelpersAvailable ? 'yes' : 'no'}:RANDOMNESS_PLACEHOLDER`);
};

View 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"
}
]
}

View 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');
}

View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"sourceMap": true,
"lib": ["esnext"],
"target": "esnext",
"module": "commonjs"
},
"include": ["index.ts"]
}

View 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' });
});

View File

@@ -7,7 +7,8 @@
"module": "commonjs",
"outDir": "dist",
"sourceMap": false,
"declaration": false
"declaration": true,
"typeRoots": ["./@types", "./node_modules/@types"]
},
"include": ["src/**/*"],
"exclude": ["node_modules"]

View File

@@ -68,8 +68,9 @@ elif 'app' in __now_variables:
if isinstance(body, string_types):
body = to_bytes(body, charset='utf-8')
path = unquote(payload['path'])
query = urlparse(path).query
url = urlparse(unquote(payload['path']))
query = url.query
path = url.path
environ = {
'CONTENT_LENGTH': str(len(body)),

View File

@@ -1,6 +1,6 @@
{
"name": "@now/python",
"version": "0.2.6",
"version": "0.2.7",
"main": "./dist/index.js",
"license": "MIT",
"files": [

View File

@@ -1,7 +1,8 @@
from flask import Flask, Response, __version__
from flask import Flask, Response, request, __version__
app = Flask(__name__)
@app.route('/', defaults={'path': ''})
@app.route('/<path: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')

View File

@@ -1,7 +1,8 @@
from flask import Flask, Response, __version__
from flask import Flask, Response, request, __version__
app = Flask(__name__)
@app.route('/', defaults={'path': ''})
@app.route('/<path: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')

View File

@@ -3,7 +3,7 @@
"builds": [{ "src": "*.py", "use": "@now/python" }],
"routes": [{ "src": "/another", "dest": "custom.py" }],
"probes": [
{ "path": "/?hello=/", "mustContain": "path=?hello=/" },
{ "path": "/another?hello=/", "mustContain": "path=another?hello=/" }
{ "path": "/?hello=/", "mustContain": "path: query: {'hello': '/'}" },
{ "path": "/another?hello=/", "mustContain": "path: another query: {'hello': '/'}" }
]
}

View File

@@ -1,6 +1,6 @@
{
"name": "@now/rust",
"version": "0.2.5",
"version": "0.2.6",
"license": "MIT",
"main": "./dist/index",
"repository": {

View File

@@ -119,7 +119,10 @@ async function buildWholeProject(
return lambdas;
}
async function gatherExtraFiles(globMatcher: string, entrypoint: string) {
async function gatherExtraFiles(
globMatcher: string | string[] | undefined,
entrypoint: string
) {
if (!globMatcher) return {};
console.log('gathering extra files for the fs...');
@@ -176,7 +179,7 @@ async function buildSingleFile(
rustEnv: Record<string, string>
) {
console.log('building single file');
const launcherPath = path.join(__dirname, 'launcher.rs');
const launcherPath = path.join(__dirname, '..', 'launcher.rs');
let launcherData = await fs.readFile(launcherPath, 'utf8');
const entrypointPath = downloadedFiles[entrypoint].fsPath;

View File

@@ -2,7 +2,9 @@ const path = require('path');
const { spawn } = require('child_process');
const getPort = require('get-port');
const { timeout } = require('promise-timeout');
const { existsSync, readFileSync } = require('fs');
const {
existsSync, readFileSync, statSync, readdirSync,
} = require('fs');
const {
glob,
download,
@@ -11,11 +13,28 @@ const {
runShellScript,
} = require('@now/build-utils'); // eslint-disable-line import/no-extraneous-dependencies
function validateDistDir(distDir) {
function validateDistDir(distDir, isDev) {
const hash = isDev
? '#local-development'
: '#configuring-the-build-output-directory';
const docsUrl = `https://zeit.co/docs/v2/deployments/official-builders/static-build-now-static-build${hash}`;
const distDirName = path.basename(distDir);
if (!existsSync(distDir)) {
const message = `Build was unable to create the distDir: ${distDirName}.`
+ '\nMake sure you mentioned the correct dist directory: https://zeit.co/docs/v2/deployments/official-builders/static-build-now-static-build/#local-development';
const message = `Build was unable to create the distDir: "${distDirName}".`
+ `\nMake sure you configure the the correct distDir: ${docsUrl}`;
throw new Error(message);
}
const stat = statSync(distDir);
if (!stat.isDirectory()) {
const message = `Build failed because distDir is not a directory: "${distDirName}".`
+ `\nMake sure you configure the the correct distDir: ${docsUrl}`;
throw new Error(message);
}
const contents = readdirSync(distDir);
if (contents.length === 0) {
const message = `Build failed because distDir is empty: "${distDirName}".`
+ `\nMake sure you configure the the correct distDir: ${docsUrl}`;
throw new Error(message);
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@now/static-build",
"version": "0.5.8",
"version": "0.5.9",
"license": "MIT",
"repository": {
"type": "git",

View File

@@ -0,0 +1,9 @@
{
"version": 2,
"builds": [
{
"src": "package.json",
"use": "@now/static-build"
}
]
}

View File

@@ -0,0 +1,5 @@
{
"scripts": {
"now-build": "mkdir dist"
}
}

View File

@@ -0,0 +1,9 @@
{
"version": 2,
"builds": [
{
"src": "package.json",
"use": "@now/static-build"
}
]
}

View File

@@ -0,0 +1,5 @@
{
"scripts": {
"example": "echo 'nothing here'"
}
}

View File

@@ -18,10 +18,15 @@ beforeAll(async () => {
});
const fixturesPath = path.resolve(__dirname, 'fixtures');
const testsThatFailToBuild = new Set([
'04-wrong-dist-dir',
'05-empty-dist-dir',
'06-missing-script',
]);
// eslint-disable-next-line no-restricted-syntax
for (const fixture of fs.readdirSync(fixturesPath)) {
if (fixture === '04-wrong-dist-dir') {
if (testsThatFailToBuild.has(fixture)) {
// eslint-disable-next-line no-loop-func
it(`should not build ${fixture}`, async () => {
try {

View File

@@ -12,7 +12,7 @@ it(
'Should build the airtable folder',
async () => {
const { buildResult } = await runBuildForFolder('airtable');
expect(buildResult['index.js']).toBeDefined();
expect(buildResult.output['index.js']).toBeDefined();
},
TWO_MINUTES,
);
@@ -21,7 +21,7 @@ it(
'Should build the aws-sdk folder',
async () => {
const { buildResult } = await runBuildForFolder('aws-sdk');
expect(buildResult['index.js']).toBeDefined();
expect(buildResult.output['index.js']).toBeDefined();
},
TWO_MINUTES,
);
@@ -30,7 +30,7 @@ it(
'Should build the axios folder',
async () => {
const { buildResult } = await runBuildForFolder('axios');
expect(buildResult['index.js']).toBeDefined();
expect(buildResult.output['index.js']).toBeDefined();
},
TWO_MINUTES,
);
@@ -39,7 +39,7 @@ it(
'Should build the mongoose folder',
async () => {
const { buildResult } = await runBuildForFolder('mongoose');
expect(buildResult['index.js']).toBeDefined();
expect(buildResult.output['index.js']).toBeDefined();
},
TWO_MINUTES,
);

View File

@@ -82,13 +82,13 @@ async function testDeployment (
for (const probe of nowJson.probes || []) {
console.log('testing', JSON.stringify(probe));
const probeUrl = `https://${deploymentUrl}${probe.path}`;
const { text, resp } = await fetchDeploymentUrl(probeUrl, {
method: probe.method,
body: probe.body ? JSON.stringify(probe.body) : undefined,
headers: {
'content-type': 'application/json',
},
});
const fetchOpts = { method: probe.method, headers: { ...probe.headers } };
if (probe.body) {
fetchOpts.headers['content-type'] = 'application/json';
fetchOpts.body = JSON.stringify(probe.body);
}
const { text, resp } = await fetchDeploymentUrl(probeUrl, fetchOpts);
if (probe.mustContain) {
if (!text.includes(probe.mustContain)) {
await fs.writeFile(path.join(__dirname, 'failed-page.txt'), text);

121
yarn.lock
View File

@@ -940,6 +940,16 @@
dependencies:
"@babel/types" "^7.3.0"
"@types/content-type@1.1.3":
version "1.1.3"
resolved "https://registry.yarnpkg.com/@types/content-type/-/content-type-1.1.3.tgz#3688bd77fc12f935548eef102a4e34c512b03a07"
integrity sha512-pv8VcFrZ3fN93L4rTNIbbUzdkzjEyVMp5mPVjsFfOYTDOZMZiZ8P1dhu+kEv3faYyKzZgLlSvnyQNFg+p/v5ug==
"@types/cookie@0.3.3":
version "0.3.3"
resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.3.3.tgz#85bc74ba782fb7aa3a514d11767832b0e3bc6803"
integrity sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==
"@types/cross-spawn@6.0.0":
version "6.0.0"
resolved "https://registry.yarnpkg.com/@types/cross-spawn/-/cross-spawn-6.0.0.tgz#320aaf1d1a12979f1b84fe7a5590a7e860bf3a80"
@@ -1094,13 +1104,20 @@
resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.4.tgz#b4ffc7dc97b498c969b360a41eee247f82616370"
integrity sha512-78AdXtlhpCHT0K3EytMpn4JNxaf5tbqbLcbIRoQIHzpTIyjpxLQKRoxU55ujBXAtg3Nl2h/XWvfDa9dsMOd0pQ==
"@types/tar@4.0.0", "@types/tar@^4.0.0":
"@types/tar@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/tar/-/tar-4.0.0.tgz#e3239d969eeb693a012200613860d0eb871c94f0"
integrity sha512-YybbEHNngcHlIWVCYsoj7Oo1JU9JqONuAlt1LlTH/lmL8BMhbzdFUgReY87a05rY1j8mfK47Del+TCkaLAXwLw==
dependencies:
"@types/node" "*"
"@types/test-listen@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@types/test-listen/-/test-listen-1.1.0.tgz#db7e5b0e277b4a3ee7b90770dae1a41b186458af"
integrity sha512-y6ZfbSzYHniCeY6ZAzsQjSAdJInNVoEz4Uhsb81W+RCoNYA59yoG/+XbqPqCPj2KCU3Wa6RFWSozutkGIHIsNQ==
dependencies:
"@types/node" "*"
"@types/uglify-js@*":
version "3.0.4"
resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.0.4.tgz#96beae23df6f561862a830b4288a49e86baac082"
@@ -1124,22 +1141,6 @@
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.12.tgz#45dd1d0638e8c8f153e87d296907659296873916"
integrity sha512-SOhuU4wNBxhhTHxYaiG5NY4HBhDIDnJF60GU+2LqHAdKKer86//e4yg69aENCtQ04n0ovz+tq2YPME5t5yp4pw==
"@types/yauzl-promise@2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@types/yauzl-promise/-/yauzl-promise-2.1.0.tgz#a813ca317cd897f286fdfefb9b202463d315313a"
integrity sha512-7PkQ5UtElDsanzjdUQzXnstCqxx6KAOTMURuHwOuqC6YO2WaYQ6ItLnLy3TiEVNQMO/pD+QSOcfnAkeSX4hsTA==
dependencies:
"@types/events" "*"
"@types/node" "*"
"@types/yauzl" "*"
"@types/yauzl@*":
version "2.9.1"
resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af"
integrity sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==
dependencies:
"@types/node" "*"
"@types/yazl@^2.4.1":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@types/yazl/-/yazl-2.4.1.tgz#0441a6ee151bf8be9307a2318b89df50f174ea00"
@@ -1158,7 +1159,14 @@
globby "8.0.0"
signal-exit "3.0.2"
"@zeit/ncc@0.18.5":
"@zeit/ncc-watcher@1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@zeit/ncc-watcher/-/ncc-watcher-1.0.3.tgz#c4569d13db7b80a96a7672047c069400e69913ae"
integrity sha512-367wm9bGMBCwT8kHQckw8UY/iYhC9rdzj357FBoM0SjVvWv53yziEmd12RkeF1EsBs0F2a1uBYdUaUvGo6LfkA==
dependencies:
"@zeit/ncc" "^0.18.5"
"@zeit/ncc@0.18.5", "@zeit/ncc@^0.18.5":
version "0.18.5"
resolved "https://registry.yarnpkg.com/@zeit/ncc/-/ncc-0.18.5.tgz#5687df6c32f1a2e2486aa110b3454ccebda4fb9c"
integrity sha512-F+SbvEAh8rchiRXqQbmD1UmbePY7dCOKTbvfFtbVbK2xMH/tyri5YKfNxXKK7eL9EWkkbqB3NTVQO6nokApeBA==
@@ -1282,11 +1290,6 @@ any-observable@^0.3.0:
resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.3.0.tgz#af933475e5806a67d0d7df090dd5e8bef65d119b"
integrity sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==
any-promise@^1.1.0, any-promise@~1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
integrity sha1-q8av7tzqUugJzcA3au0845Y10X8=
anymatch@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb"
@@ -2424,6 +2427,11 @@ contains-path@^0.1.0:
resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"
integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=
content-type@1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
conventional-changelog-angular@^5.0.2:
version "5.0.2"
resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.2.tgz#39d945635e03b6d0c9d4078b1df74e06163dc66a"
@@ -2514,6 +2522,11 @@ convert-source-map@^1.1.0, convert-source-map@^1.1.1, convert-source-map@^1.4.0,
dependencies:
safe-buffer "~5.1.1"
cookie@0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
copy-concurrently@^1.0.0:
version "1.0.5"
resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0"
@@ -3055,13 +3068,6 @@ end-of-stream@1.4.1, end-of-stream@^1.0.0, end-of-stream@^1.1.0:
dependencies:
once "^1.4.0"
end-of-stream@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.1.0.tgz#e9353258baa9108965efc41cb0ef8ade2f3cfb07"
integrity sha1-6TUyWLqpEIll78QcsO+K3i88+wc=
dependencies:
once "~1.3.0"
err-code@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/err-code/-/err-code-1.1.2.tgz#06e0116d3028f6aef4806849eb0ea6a748ae6960"
@@ -3288,11 +3294,6 @@ esutils@^2.0.2:
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=
events-intercept@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/events-intercept/-/events-intercept-2.0.0.tgz#adbf38681c5a4b2011c41ee41f61a34cba448897"
integrity sha1-rb84aBxaSyARxB7kH2GjTLpEiJc=
exec-series@^1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/exec-series/-/exec-series-1.0.3.tgz#6d257a9beac482a872c7783bc8615839fc77143a"
@@ -7488,13 +7489,6 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0:
dependencies:
wrappy "1"
once@~1.3.0:
version "1.3.3"
resolved "https://registry.yarnpkg.com/once/-/once-1.3.3.tgz#b2e261557ce4c314ec8304f3fa82663e4297ca20"
integrity sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=
dependencies:
wrappy "1"
onetime@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789"
@@ -8035,11 +8029,6 @@ promise-timeout@1.3.0:
resolved "https://registry.yarnpkg.com/promise-timeout/-/promise-timeout-1.3.0.tgz#d1c78dd50a607d5f0a5207410252a3a0914e1014"
integrity sha512-5yANTE0tmi5++POym6OgtFmwfDvOXABD9oj/jLQr5GPEyuNEb7jH4wbbANJceJid49jwhi1RddxnhnEAb/doqg==
promisepipe@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/promisepipe/-/promisepipe-3.0.0.tgz#c9b6e5aa861ef5fcce6134f6f75e14f8f30bd3b2"
integrity sha512-V6TbZDJ/ZswevgkDNpGt/YqNCiZP9ASfgU+p83uJE6NrGtvSGoOcHLiDCqkMs2+yg7F5qHdLV8d0aS8O26G/KA==
prompts@^0.1.9:
version "0.1.14"
resolved "https://registry.yarnpkg.com/prompts/-/prompts-0.1.14.tgz#a8e15c612c5c9ec8f8111847df3337c9cbd443b2"
@@ -9132,22 +9121,6 @@ stream-shift@^1.0.0:
resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=
stream-to-array@~2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/stream-to-array/-/stream-to-array-2.3.0.tgz#bbf6b39f5f43ec30bc71babcb37557acecf34353"
integrity sha1-u/azn19D7DC8cbq8s3VXrOzzQ1M=
dependencies:
any-promise "^1.1.0"
stream-to-promise@2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/stream-to-promise/-/stream-to-promise-2.2.0.tgz#b1edb2e1c8cb11289d1b503c08d3f2aef51e650f"
integrity sha1-se2y4cjLESidG1A8CNPyrvUeZQ8=
dependencies:
any-promise "~1.3.0"
end-of-stream "~1.1.0"
stream-to-array "~2.3.0"
string-argv@^0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.0.2.tgz#dac30408690c21f3c3630a3ff3a05877bdcbd736"
@@ -9457,6 +9430,11 @@ test-exclude@^5.0.0:
read-pkg-up "^4.0.0"
require-main-filename "^1.0.1"
test-listen@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/test-listen/-/test-listen-1.1.0.tgz#2ba614d96c3bc9157469003027b42a495dd83b6a"
integrity sha512-OyEVi981C1sb9NX1xayfgZls3p8QTDRwp06EcgxSgd1kktaENBW8dO15i8v/7Fi15j0IYQctJzk5J+hyEBId2w==
text-extensions@^1.0.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26"
@@ -10312,22 +10290,7 @@ yargs@^12.0.1, yargs@^12.0.2:
y18n "^3.2.1 || ^4.0.0"
yargs-parser "^11.1.1"
yauzl-clone@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/yauzl-clone/-/yauzl-clone-1.0.4.tgz#8bc6d293b17cc98802bbbed2e289d18e7697c96c"
integrity sha1-i8bSk7F8yYgCu77S4onRjnaXyWw=
dependencies:
events-intercept "^2.0.0"
yauzl-promise@2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/yauzl-promise/-/yauzl-promise-2.1.3.tgz#17467845db89fc6592ca987ca2ecfee8c381ae3d"
integrity sha1-F0Z4RduJ/GWSyph8ouz+6MOBrj0=
dependencies:
yauzl "^2.9.1"
yauzl-clone "^1.0.4"
yauzl@2.10.0, yauzl@^2.2.1, yauzl@^2.9.1:
yauzl@2.10.0, yauzl@^2.2.1:
version "2.10.0"
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=