mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-12 12:57:47 +00:00
Compare commits
50 Commits
@now/pytho
...
@now/pytho
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7bc3b3490 | ||
|
|
d83b09ffbd | ||
|
|
0c120f6202 | ||
|
|
48a01415f8 | ||
|
|
9198b72382 | ||
|
|
79048b83de | ||
|
|
f798e2cf19 | ||
|
|
be5057a738 | ||
|
|
059d44fde7 | ||
|
|
11ad481546 | ||
|
|
4061ed2eb7 | ||
|
|
b54c79e619 | ||
|
|
2203ae1a20 | ||
|
|
5e58c1738b | ||
|
|
290577ddfb | ||
|
|
a5e651403e | ||
|
|
df2769717b | ||
|
|
880ef77b7b | ||
|
|
af2616c283 | ||
|
|
4300f4d797 | ||
|
|
4921e541af | ||
|
|
6ce00eda8c | ||
|
|
060e952f6c | ||
|
|
1639127b24 | ||
|
|
ebbb62eaea | ||
|
|
66954e84fe | ||
|
|
15a21bb28c | ||
|
|
96ca1e1d8c | ||
|
|
587cb52191 | ||
|
|
95422ffd46 | ||
|
|
391a883799 | ||
|
|
43d6960df4 | ||
|
|
5c128003d8 | ||
|
|
2f8fd1b14b | ||
|
|
625553c146 | ||
|
|
3b0ce7bad3 | ||
|
|
489ec1dfa5 | ||
|
|
d3f92d7143 | ||
|
|
3072b044ef | ||
|
|
3b4968657f | ||
|
|
cafae4c800 | ||
|
|
64952d24f1 | ||
|
|
72758b6e0d | ||
|
|
4f80bc74d5 | ||
|
|
a6e62ed61c | ||
|
|
d8eecd6172 | ||
|
|
0e70608511 | ||
|
|
da0de150df | ||
|
|
a58c35fb9e | ||
|
|
fe88a69ab7 |
@@ -13,3 +13,5 @@
|
|||||||
/packages/now-go/*
|
/packages/now-go/*
|
||||||
/packages/now-rust/dist/*
|
/packages/now-rust/dist/*
|
||||||
/packages/now-ruby/dist/*
|
/packages/now-ruby/dist/*
|
||||||
|
/packages/now-static-build/dist/*
|
||||||
|
/packages/now-static-build/test/fixtures/**
|
||||||
|
|||||||
4
.github/CODEOWNERS
vendored
4
.github/CODEOWNERS
vendored
@@ -2,8 +2,10 @@
|
|||||||
# https://help.github.com/en/articles/about-code-owners
|
# https://help.github.com/en/articles/about-code-owners
|
||||||
|
|
||||||
* @styfle
|
* @styfle
|
||||||
/packages/now-node @styfle @tootallnate
|
/packages/now-node @styfle @tootallnate @lucleray
|
||||||
|
/packages/now-node-bridge @styfle @tootallnate @lucleray
|
||||||
/packages/now-next @timer @dav-is
|
/packages/now-next @timer @dav-is
|
||||||
/packages/now-go @styfle @sophearak
|
/packages/now-go @styfle @sophearak
|
||||||
/packages/now-python @styfle @sophearak
|
/packages/now-python @styfle @sophearak
|
||||||
/packages/now-rust @styfle @mike-engel @anmonteiro
|
/packages/now-rust @styfle @mike-engel @anmonteiro
|
||||||
|
/packages/now-ruby @styfle @coetry @nathancahill
|
||||||
|
|||||||
@@ -24,8 +24,9 @@ yarn publish-canary
|
|||||||
```
|
```
|
||||||
|
|
||||||
For the Stable Channel, you must do the following:
|
For the Stable Channel, you must do the following:
|
||||||
|
|
||||||
- Cherry pick each commit from canary to master
|
- 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`)
|
- Verify that you are _in-sync_ with canary (with the exception of the `version` line in `package.json`)
|
||||||
- Deploy the modified Builders
|
- Deploy the modified Builders
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,33 +1,29 @@
|
|||||||
const { execSync } = require('child_process');
|
const { execSync } = require('child_process');
|
||||||
const { relative } = require('path');
|
const { relative } = require('path');
|
||||||
|
|
||||||
const branch = execSync('git branch | grep "*" | cut -d " " -f2').toString();
|
const branch = execSync('git branch | grep "*" | cut -d " " -f2')
|
||||||
|
.toString()
|
||||||
|
.trim();
|
||||||
console.log(`Running tests on branch "${branch}"`);
|
console.log(`Running tests on branch "${branch}"`);
|
||||||
const base = branch === 'master' ? 'HEAD~1' : 'origin/canary';
|
const gitPath = branch === 'master' ? 'HEAD~1' : 'origin/canary...HEAD';
|
||||||
const diff = execSync(`git diff ${base} --name-only`).toString();
|
const diff = execSync(`git diff ${gitPath} --name-only`).toString();
|
||||||
|
|
||||||
const changed = diff
|
const changed = diff
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.filter(item => Boolean(item) && item.includes('packages/'))
|
.filter(item => Boolean(item) && item.includes('packages/'))
|
||||||
.map(item => relative('packages', item).split('/')[0]);
|
.map(item => relative('packages', item).split('/')[0]);
|
||||||
|
|
||||||
const matches = [];
|
const matches = Array.from(new Set(changed));
|
||||||
|
|
||||||
if (changed.length > 0) {
|
if (matches.length === 0) {
|
||||||
console.log('The following packages have changed:');
|
|
||||||
|
|
||||||
changed.map((item) => {
|
|
||||||
matches.push(item);
|
|
||||||
console.log(item);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
matches.push('now-node');
|
matches.push('now-node');
|
||||||
console.log(`No packages changed, defaulting to ${matches[0]}`);
|
console.log(`No packages changed, defaulting to ${matches[0]}`);
|
||||||
|
} else {
|
||||||
|
console.log('The following packages have changed:');
|
||||||
|
console.log(matches.join('\n'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const testMatch = Array.from(new Set(matches)).map(
|
const testMatch = matches.map(
|
||||||
item => `**/${item}/**/?(*.)+(spec|test).[jt]s?(x)`,
|
item => `**/${item}/**/?(*.)+(spec|test).[jt]s?(x)`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"lerna": "lerna",
|
"lerna": "lerna",
|
||||||
"bootstrap": "lerna bootstrap",
|
"bootstrap": "lerna bootstrap",
|
||||||
"publish-stable": "git checkout master && git pull && lerna version",
|
"publish-stable": "git checkout master && git pull && lerna version --exact",
|
||||||
"publish-canary": "git checkout canary && git pull && lerna version prerelease --preid canary",
|
"publish-canary": "git checkout canary && git pull && lerna version prerelease --preid canary --exact",
|
||||||
"publish-from-github": "./.circleci/publish.sh",
|
"publish-from-github": "./.circleci/publish.sh",
|
||||||
"build": "./.circleci/build.sh",
|
"build": "./.circleci/build.sh",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
|
|||||||
@@ -13,6 +13,15 @@ exports.config = {
|
|||||||
maxLambdaSize: '10mb',
|
maxLambdaSize: '10mb',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// From this list: https://import.pw/importpw/import/docs/config.md
|
||||||
|
const allowedConfigImports = new Set([
|
||||||
|
'CACHE',
|
||||||
|
'CURL_OPTS',
|
||||||
|
'DEBUG',
|
||||||
|
'RELOAD',
|
||||||
|
'SERVER',
|
||||||
|
]);
|
||||||
|
|
||||||
exports.analyze = ({ files, entrypoint }) => files[entrypoint].digest;
|
exports.analyze = ({ files, entrypoint }) => files[entrypoint].digest;
|
||||||
|
|
||||||
exports.build = async ({
|
exports.build = async ({
|
||||||
@@ -24,10 +33,23 @@ exports.build = async ({
|
|||||||
await download(files, srcDir);
|
await download(files, srcDir);
|
||||||
|
|
||||||
const configEnv = Object.keys(config).reduce((o, v) => {
|
const configEnv = Object.keys(config).reduce((o, v) => {
|
||||||
o[`IMPORT_${snakeCase(v).toUpperCase()}`] = config[v]; // eslint-disable-line no-param-reassign
|
const name = snakeCase(v).toUpperCase();
|
||||||
|
|
||||||
|
if (allowedConfigImports.has(name)) {
|
||||||
|
o[`IMPORT_${name}`] = config[v]; // eslint-disable-line no-param-reassign
|
||||||
|
}
|
||||||
|
|
||||||
return o;
|
return o;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
|
if (config && config.import) {
|
||||||
|
Object.keys(config.import).forEach((key) => {
|
||||||
|
const name = snakeCase(key).toUpperCase();
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
configEnv[`IMPORT_${name}`] = config.import[key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const IMPORT_CACHE = `${workPath}/.import-cache`;
|
const IMPORT_CACHE = `${workPath}/.import-cache`;
|
||||||
const env = Object.assign({}, process.env, configEnv, {
|
const env = Object.assign({}, process.env, configEnv, {
|
||||||
PATH: `${IMPORT_CACHE}/bin:${process.env.PATH}`,
|
PATH: `${IMPORT_CACHE}/bin:${process.env.PATH}`,
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/bash",
|
"name": "@now/bash",
|
||||||
"version": "0.2.3",
|
"version": "1.0.0",
|
||||||
"description": "Now 2.0 builder for HTTP endpoints written in Bash",
|
"description": "Now 2.0 builder for HTTP endpoints written in Bash",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"author": "Nathan Rajlich <nate@zeit.co>",
|
"author": "Nathan Rajlich <nate@zeit.co>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/bash-now-bash",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/zeit/now-builders.git",
|
"url": "https://github.com/zeit/now-builders.git",
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/build-utils",
|
"name": "@now/build-utils",
|
||||||
"version": "0.7.0",
|
"version": "0.8.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.js",
|
"types": "./dist/index.d.js",
|
||||||
|
"homepage": "https://zeit.co/docs/v2/deployments/builders/developer-guide",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/zeit/now-builders.git",
|
"url": "https://github.com/zeit/now-builders.git",
|
||||||
@@ -22,6 +23,7 @@
|
|||||||
"fs-extra": "7.0.0",
|
"fs-extra": "7.0.0",
|
||||||
"glob": "7.1.3",
|
"glob": "7.1.3",
|
||||||
"into-stream": "5.0.0",
|
"into-stream": "5.0.0",
|
||||||
|
"minimatch": "3.0.4",
|
||||||
"multistream": "2.1.1",
|
"multistream": "2.1.1",
|
||||||
"node-fetch": "2.2.0",
|
"node-fetch": "2.2.0",
|
||||||
"semver": "6.1.1",
|
"semver": "6.1.1",
|
||||||
|
|||||||
47
packages/now-build-utils/src/detect-builder.ts
Normal file
47
packages/now-build-utils/src/detect-builder.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { PackageJson, Builder, Config } from './types';
|
||||||
|
import minimatch from 'minimatch';
|
||||||
|
|
||||||
|
const src: string = 'package.json';
|
||||||
|
const config: Config = { zeroConfig: true };
|
||||||
|
|
||||||
|
// Static builders are special cased in `@now/static-build`
|
||||||
|
const BUILDERS = new Map<string, Builder>([
|
||||||
|
['next', { src, use: '@now/next', config }],
|
||||||
|
]);
|
||||||
|
|
||||||
|
const API_BUILDERS: Builder[] = [
|
||||||
|
{ src: 'api/**/*.js', use: '@now/node', config },
|
||||||
|
{ src: 'api/**/*.ts', use: '@now/node', config },
|
||||||
|
{ src: 'api/**/*.rs', use: '@now/rust', config },
|
||||||
|
{ src: 'api/**/*.go', use: '@now/go', config },
|
||||||
|
{ src: 'api/**/*.php', use: '@now/php', config },
|
||||||
|
{ src: 'api/**/*.py', use: '@now/python', config },
|
||||||
|
{ src: 'api/**/*.rb', use: '@now/ruby', config },
|
||||||
|
{ src: 'api/**/*.sh', use: '@now/bash', config },
|
||||||
|
];
|
||||||
|
|
||||||
|
export async function detectBuilder(pkg: PackageJson): Promise<Builder> {
|
||||||
|
for (const [dependency, builder] of BUILDERS) {
|
||||||
|
const deps = Object.assign({}, pkg.dependencies, pkg.devDependencies);
|
||||||
|
|
||||||
|
// Return the builder when a dependency matches
|
||||||
|
if (deps[dependency]) {
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// By default we'll choose the `static-build` builder
|
||||||
|
return { src, use: '@now/static-build', config };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function detectApiBuilders(
|
||||||
|
files: string[]
|
||||||
|
): Promise<Builder[] | null> {
|
||||||
|
const builds = files.map(file => {
|
||||||
|
return API_BUILDERS.find(({ src }): boolean => minimatch(file, src));
|
||||||
|
});
|
||||||
|
|
||||||
|
// We can use `new Set` here since `builds` contains references to `API_BUILDERS`
|
||||||
|
const finishedBuilds = Array.from(new Set(builds.filter(Boolean)));
|
||||||
|
return finishedBuilds.length > 0 ? (finishedBuilds as Builder[]) : null;
|
||||||
|
}
|
||||||
239
packages/now-build-utils/src/detect-routes.ts
Normal file
239
packages/now-build-utils/src/detect-routes.ts
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import { Route } from './types';
|
||||||
|
|
||||||
|
function concatArrayOfText(texts: string[]): string {
|
||||||
|
const last = texts.pop();
|
||||||
|
return `${texts.join(', ')}, and ${last}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Takes a filename or foldername, strips the extension
|
||||||
|
// gets the part between the "[]" brackets.
|
||||||
|
// It will return `null` if there are no brackets
|
||||||
|
// and therefore no segment.
|
||||||
|
function getSegmentName(segment: string): string | null {
|
||||||
|
const { name } = path.parse(segment);
|
||||||
|
|
||||||
|
if (name.startsWith('[') && name.endsWith(']')) {
|
||||||
|
return name.slice(1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRouteFromPath(filePath: string): Route {
|
||||||
|
const parts = filePath.split('/');
|
||||||
|
|
||||||
|
let append: string = '';
|
||||||
|
let counter: number = 1;
|
||||||
|
const query: string[] = [];
|
||||||
|
|
||||||
|
const srcParts = parts.map(
|
||||||
|
(segment, index): string => {
|
||||||
|
const name = getSegmentName(segment);
|
||||||
|
const isLast = index === parts.length - 1;
|
||||||
|
|
||||||
|
if (name !== null) {
|
||||||
|
query.push(`${name}=$${counter++}`);
|
||||||
|
|
||||||
|
if (isLast) {
|
||||||
|
// We append this to the last one
|
||||||
|
// to make sure GET params still work
|
||||||
|
// and are just appended to our GET params
|
||||||
|
append += `$${counter++}`;
|
||||||
|
return `([^\\/|\\?]+)\\/?(?:\\?(.*))?`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `([^\\/]+)`;
|
||||||
|
} else if (isLast) {
|
||||||
|
// If it is the last part we want to remove the extension
|
||||||
|
// and capture everything after that as regex group and append it
|
||||||
|
const { name: fileName } = path.parse(segment);
|
||||||
|
append += `$${counter++}`;
|
||||||
|
return `${fileName}(.*)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return segment;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const src = `^/${srcParts.join('/')}$`;
|
||||||
|
const dest = `/${filePath}${query.length ? '?' : ''}${query.join('&')}${
|
||||||
|
query.length ? '&' : ''
|
||||||
|
}${append}`;
|
||||||
|
|
||||||
|
return { src, dest };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the path partially matches and has the same
|
||||||
|
// name for the path segment at the same position
|
||||||
|
function partiallyMatches(pathA: string, pathB: string): boolean {
|
||||||
|
const partsA = pathA.split('/');
|
||||||
|
const partsB = pathB.split('/');
|
||||||
|
|
||||||
|
const long = partsA.length > partsB.length ? partsA : partsB;
|
||||||
|
const short = long === partsA ? partsB : partsA;
|
||||||
|
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
for (const segmentShort of short) {
|
||||||
|
const segmentLong = long[index];
|
||||||
|
|
||||||
|
const nameLong = getSegmentName(segmentLong);
|
||||||
|
const nameShort = getSegmentName(segmentShort);
|
||||||
|
|
||||||
|
// If there are no segments or the paths differ we
|
||||||
|
// return as they are not matching
|
||||||
|
if (segmentShort !== segmentLong && (!nameLong || !nameShort)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nameLong !== nameShort) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Counts how often a path occurres when all placeholders
|
||||||
|
// got resolved, so we can check if they have conflicts
|
||||||
|
function pathOccurrences(filePath: string, files: string[]): string[] {
|
||||||
|
const getAbsolutePath = (unresolvedPath: string): string => {
|
||||||
|
const { dir, name } = path.parse(unresolvedPath);
|
||||||
|
const parts = path.join(dir, name).split('/');
|
||||||
|
return parts.map(part => part.replace(/\[.*\]/, '1')).join('/');
|
||||||
|
};
|
||||||
|
|
||||||
|
const currentAbsolutePath = getAbsolutePath(filePath);
|
||||||
|
|
||||||
|
return files.reduce((prev: string[], file: string): string[] => {
|
||||||
|
const absolutePath = getAbsolutePath(file);
|
||||||
|
|
||||||
|
if (absolutePath === currentAbsolutePath) {
|
||||||
|
prev.push(file);
|
||||||
|
} else if (partiallyMatches(filePath, file)) {
|
||||||
|
prev.push(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
return prev;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if a placeholder with the same name is used
|
||||||
|
// multiple times inside the same path
|
||||||
|
function getConflictingSegment(filePath: string): string | null {
|
||||||
|
const segments = new Set<string>();
|
||||||
|
|
||||||
|
for (const segment of filePath.split('/')) {
|
||||||
|
const name = getSegmentName(segment);
|
||||||
|
|
||||||
|
if (name !== null && segments.has(name)) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name) {
|
||||||
|
segments.add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortFilesBySegmentCount(fileA: string, fileB: string): number {
|
||||||
|
const lengthA = fileA.split('/').length;
|
||||||
|
const lengthB = fileB.split('/').length;
|
||||||
|
|
||||||
|
if (lengthA > lengthB) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lengthA < lengthB) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paths that have the same segment length but
|
||||||
|
// less placeholders are preferred
|
||||||
|
const countSegments = (prev: number, segment: string) =>
|
||||||
|
getSegmentName(segment) ? prev + 1 : 0;
|
||||||
|
const segmentLengthA = fileA.split('/').reduce(countSegments, 0);
|
||||||
|
const segmentLengthB = fileB.split('/').reduce(countSegments, 0);
|
||||||
|
|
||||||
|
if (segmentLengthA > segmentLengthB) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (segmentLengthA < segmentLengthB) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function detectApiRoutes(
|
||||||
|
files: string[]
|
||||||
|
): Promise<{
|
||||||
|
defaultRoutes: Route[] | null;
|
||||||
|
error: { [key: string]: string } | null;
|
||||||
|
}> {
|
||||||
|
if (!files || files.length === 0) {
|
||||||
|
return { defaultRoutes: null, error: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
// The deepest routes need to be
|
||||||
|
// the first ones to get handled
|
||||||
|
const sortedFiles = files.sort(sortFilesBySegmentCount);
|
||||||
|
|
||||||
|
const defaultRoutes: Route[] = [];
|
||||||
|
|
||||||
|
for (const file of sortedFiles) {
|
||||||
|
// We only consider every file in the api directory
|
||||||
|
// as we will strip extensions as well as resolving "[segments]"
|
||||||
|
if (!file.startsWith('api/')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const conflictingSegment = getConflictingSegment(file);
|
||||||
|
|
||||||
|
console.log({ file, conflictingSegment });
|
||||||
|
|
||||||
|
if (conflictingSegment) {
|
||||||
|
return {
|
||||||
|
defaultRoutes: null,
|
||||||
|
error: {
|
||||||
|
code: 'conflicting_path_segment',
|
||||||
|
message:
|
||||||
|
`The segment "${conflictingSegment}" occurres more than ` +
|
||||||
|
`one time in your path "${file}". Please make sure that ` +
|
||||||
|
`every segment in a path is unique`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const occurrences = pathOccurrences(file, sortedFiles).filter(
|
||||||
|
name => name !== file
|
||||||
|
);
|
||||||
|
|
||||||
|
if (occurrences.length > 0) {
|
||||||
|
const messagePaths = concatArrayOfText(
|
||||||
|
occurrences.map(name => `"${name}"`)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
defaultRoutes: null,
|
||||||
|
error: {
|
||||||
|
code: 'conflicting_file_path',
|
||||||
|
message:
|
||||||
|
`Two or more files have conflicting paths or names. ` +
|
||||||
|
`Please make sure path segments and filenames, without their extension, are unique. ` +
|
||||||
|
`The path "${file}" has conflicts with ${messagePaths}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultRoutes.push(createRouteFromPath(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
return { defaultRoutes, error: null };
|
||||||
|
}
|
||||||
@@ -39,10 +39,10 @@ export default async function download(
|
|||||||
basePath: string,
|
basePath: string,
|
||||||
meta?: Meta
|
meta?: Meta
|
||||||
): Promise<DownloadedFiles> {
|
): Promise<DownloadedFiles> {
|
||||||
const { isDev = false, filesChanged = null, filesRemoved = null } =
|
const { isDev = false, skipDownload = false, filesChanged = null, filesRemoved = null } =
|
||||||
meta || {};
|
meta || {};
|
||||||
|
|
||||||
if (isDev) {
|
if (isDev || skipDownload) {
|
||||||
// In `now dev`, the `download()` function is a no-op because
|
// In `now dev`, the `download()` function is a no-op because
|
||||||
// the `basePath` matches the `cwd` of the dev server, so the
|
// the `basePath` matches the `cwd` of the dev server, so the
|
||||||
// source files are already available.
|
// source files are already available.
|
||||||
|
|||||||
@@ -13,15 +13,18 @@ export const defaultSelection = supportedOptions.find(
|
|||||||
) as NodeVersion;
|
) as NodeVersion;
|
||||||
|
|
||||||
export async function getSupportedNodeVersion(
|
export async function getSupportedNodeVersion(
|
||||||
engineRange?: string
|
engineRange?: string,
|
||||||
|
silent?: boolean
|
||||||
): Promise<NodeVersion> {
|
): Promise<NodeVersion> {
|
||||||
let selection = defaultSelection;
|
let selection = defaultSelection;
|
||||||
|
|
||||||
if (!engineRange) {
|
if (!engineRange) {
|
||||||
|
if (!silent) {
|
||||||
console.log(
|
console.log(
|
||||||
'missing `engines` in `package.json`, using default range: ' +
|
'missing `engines` in `package.json`, using default range: ' +
|
||||||
selection.range
|
selection.range
|
||||||
);
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const found = supportedOptions.some(o => {
|
const found = supportedOptions.some(o => {
|
||||||
// the array is already in order so return the first
|
// the array is already in order so return the first
|
||||||
@@ -30,10 +33,14 @@ export async function getSupportedNodeVersion(
|
|||||||
return intersects(o.range, engineRange);
|
return intersects(o.range, engineRange);
|
||||||
});
|
});
|
||||||
if (found) {
|
if (found) {
|
||||||
|
if (!silent) {
|
||||||
console.log(
|
console.log(
|
||||||
'found `engines` in `package.json`, selecting range: ' + selection.range
|
'Found `engines` in `package.json`, selecting range: ' +
|
||||||
|
selection.range
|
||||||
);
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (!silent) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'found `engines` in `package.json` with an unsupported node range: ' +
|
'found `engines` in `package.json` with an unsupported node range: ' +
|
||||||
engineRange +
|
engineRange +
|
||||||
@@ -41,5 +48,6 @@ export async function getSupportedNodeVersion(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return selection;
|
return selection;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,11 +46,15 @@ async function chmodPlusX(fsPath: string) {
|
|||||||
await fs.chmod(fsPath, base8);
|
await fs.chmod(fsPath, base8);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runShellScript(fsPath: string) {
|
export async function runShellScript(
|
||||||
|
fsPath: string,
|
||||||
|
args: string[] = [],
|
||||||
|
spawnOpts?: SpawnOptions
|
||||||
|
) {
|
||||||
assert(path.isAbsolute(fsPath));
|
assert(path.isAbsolute(fsPath));
|
||||||
const destPath = path.dirname(fsPath);
|
const destPath = path.dirname(fsPath);
|
||||||
await chmodPlusX(fsPath);
|
await chmodPlusX(fsPath);
|
||||||
await spawnAsync(`./${path.basename(fsPath)}`, [], destPath);
|
await spawnAsync(`./${path.basename(fsPath)}`, args, destPath, spawnOpts);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,10 +73,15 @@ export function getSpawnOptions(
|
|||||||
return opts;
|
return opts;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getNodeVersion(destPath: string): Promise<NodeVersion> {
|
export async function getNodeVersion(
|
||||||
|
destPath: string,
|
||||||
|
minNodeVersion?: string
|
||||||
|
): Promise<NodeVersion> {
|
||||||
const { packageJson } = await scanParentDirs(destPath, true);
|
const { packageJson } = await scanParentDirs(destPath, true);
|
||||||
const range = packageJson && packageJson.engines && packageJson.engines.node;
|
const range =
|
||||||
return getSupportedNodeVersion(range);
|
(packageJson && packageJson.engines && packageJson.engines.node) ||
|
||||||
|
minNodeVersion;
|
||||||
|
return getSupportedNodeVersion(range, typeof minNodeVersion !== 'undefined');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function scanParentDirs(destPath: string, readPackageJson = false) {
|
async function scanParentDirs(destPath: string, readPackageJson = false) {
|
||||||
@@ -130,7 +139,13 @@ export async function runNpmInstall(
|
|||||||
} else {
|
} else {
|
||||||
await spawnAsync(
|
await spawnAsync(
|
||||||
'yarn',
|
'yarn',
|
||||||
commandArgs.concat(['--ignore-engines', '--cwd', destPath]),
|
commandArgs.concat([
|
||||||
|
'--ignore-engines',
|
||||||
|
'--mutex',
|
||||||
|
'network',
|
||||||
|
'--cwd',
|
||||||
|
destPath,
|
||||||
|
]),
|
||||||
destPath,
|
destPath,
|
||||||
opts
|
opts
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,15 +1,6 @@
|
|||||||
import FileBlob from './file-blob';
|
import FileBlob from './file-blob';
|
||||||
import FileFsRef from './file-fs-ref';
|
import FileFsRef from './file-fs-ref';
|
||||||
import FileRef from './file-ref';
|
import FileRef from './file-ref';
|
||||||
import {
|
|
||||||
File,
|
|
||||||
Files,
|
|
||||||
AnalyzeOptions,
|
|
||||||
BuildOptions,
|
|
||||||
PrepareCacheOptions,
|
|
||||||
ShouldServeOptions,
|
|
||||||
Meta,
|
|
||||||
} from './types';
|
|
||||||
import { Lambda, createLambda } from './lambda';
|
import { Lambda, createLambda } from './lambda';
|
||||||
import download, { DownloadedFiles } from './fs/download';
|
import download, { DownloadedFiles } from './fs/download';
|
||||||
import getWriteableDirectory from './fs/get-writable-directory';
|
import getWriteableDirectory from './fs/get-writable-directory';
|
||||||
@@ -25,14 +16,13 @@ import {
|
|||||||
} from './fs/run-user-scripts';
|
} from './fs/run-user-scripts';
|
||||||
import streamToBuffer from './fs/stream-to-buffer';
|
import streamToBuffer from './fs/stream-to-buffer';
|
||||||
import shouldServe from './should-serve';
|
import shouldServe from './should-serve';
|
||||||
|
import { detectBuilder, detectApiBuilders } from './detect-builder';
|
||||||
|
import { detectApiRoutes } from './detect-routes';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
FileBlob,
|
FileBlob,
|
||||||
FileFsRef,
|
FileFsRef,
|
||||||
FileRef,
|
FileRef,
|
||||||
Files,
|
|
||||||
File,
|
|
||||||
Meta,
|
|
||||||
Lambda,
|
Lambda,
|
||||||
createLambda,
|
createLambda,
|
||||||
download,
|
download,
|
||||||
@@ -47,9 +37,10 @@ export {
|
|||||||
getNodeVersion,
|
getNodeVersion,
|
||||||
getSpawnOptions,
|
getSpawnOptions,
|
||||||
streamToBuffer,
|
streamToBuffer,
|
||||||
AnalyzeOptions,
|
|
||||||
BuildOptions,
|
|
||||||
PrepareCacheOptions,
|
|
||||||
ShouldServeOptions,
|
|
||||||
shouldServe,
|
shouldServe,
|
||||||
|
detectBuilder,
|
||||||
|
detectApiBuilders,
|
||||||
|
detectApiRoutes,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export * from './types';
|
||||||
|
|||||||
@@ -15,8 +15,24 @@ export interface Files {
|
|||||||
[filePath: string]: File;
|
[filePath: string]: File;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Route {
|
||||||
|
src?: string;
|
||||||
|
dest?: string;
|
||||||
|
handle?: string;
|
||||||
|
type?: string;
|
||||||
|
headers?: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface Config {
|
export interface Config {
|
||||||
[key: string]: string | string[] | boolean | number | undefined;
|
[key: string]:
|
||||||
|
| string
|
||||||
|
| string[]
|
||||||
|
| boolean
|
||||||
|
| number
|
||||||
|
| { [key: string]: string }
|
||||||
|
| undefined;
|
||||||
maxLambdaSize?: string;
|
maxLambdaSize?: string;
|
||||||
includeFiles?: string | string[];
|
includeFiles?: string | string[];
|
||||||
bundle?: boolean;
|
bundle?: boolean;
|
||||||
@@ -24,10 +40,13 @@ export interface Config {
|
|||||||
helpers?: boolean;
|
helpers?: boolean;
|
||||||
rust?: string;
|
rust?: string;
|
||||||
debug?: boolean;
|
debug?: boolean;
|
||||||
|
zeroConfig?: boolean;
|
||||||
|
import?: { [key: string]: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Meta {
|
export interface Meta {
|
||||||
isDev?: boolean;
|
isDev?: boolean;
|
||||||
|
skipDownload?: boolean;
|
||||||
requestPath?: string;
|
requestPath?: string;
|
||||||
filesChanged?: string[];
|
filesChanged?: string[];
|
||||||
filesRemoved?: string[];
|
filesRemoved?: string[];
|
||||||
@@ -187,3 +206,9 @@ export interface NodeVersion {
|
|||||||
range: string;
|
range: string;
|
||||||
runtime: string;
|
runtime: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Builder {
|
||||||
|
use: string;
|
||||||
|
src: string;
|
||||||
|
config?: Config;
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
{
|
{
|
||||||
"src": "index.js",
|
"src": "index.js",
|
||||||
"use": "@now/node",
|
"use": "@now/node",
|
||||||
"config": { "maxLambdaSize": "15mb" }
|
"config": { "maxLambdaSize": "18mb" }
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"probes": [{ "path": "/", "mustContain": "found:RANDOMNESS_PLACEHOLDER" }]
|
"probes": [{ "path": "/", "mustContain": "found:RANDOMNESS_PLACEHOLDER" }]
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ const {
|
|||||||
testDeployment,
|
testDeployment,
|
||||||
} = require('../../../test/lib/deployment/test-deployment.js');
|
} = require('../../../test/lib/deployment/test-deployment.js');
|
||||||
|
|
||||||
|
const {
|
||||||
|
detectBuilder,
|
||||||
|
detectApiBuilders,
|
||||||
|
detectApiRoutes,
|
||||||
|
} = require('../dist');
|
||||||
|
|
||||||
jest.setTimeout(4 * 60 * 1000);
|
jest.setTimeout(4 * 60 * 1000);
|
||||||
const builderUrl = '@canary';
|
const builderUrl = '@canary';
|
||||||
let buildUtilsUrl;
|
let buildUtilsUrl;
|
||||||
@@ -158,3 +164,84 @@ for (const builder of buildersToTestWith) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it('Test `detectBuilder`', async () => {
|
||||||
|
{
|
||||||
|
const pkg = { dependencies: { next: '1.0.0' } };
|
||||||
|
const builder = await detectBuilder(pkg);
|
||||||
|
expect(builder.use).toBe('@now/next');
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const pkg = { devDependencies: { next: '1.0.0' } };
|
||||||
|
const builder = await detectBuilder(pkg);
|
||||||
|
expect(builder.use).toBe('@now/next');
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const pkg = {};
|
||||||
|
const builder = await detectBuilder(pkg);
|
||||||
|
expect(builder.use).toBe('@now/static-build');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Test `detectApiBuilders`', async () => {
|
||||||
|
{
|
||||||
|
const files = ['package.json', 'api/user.js', 'api/team.js'];
|
||||||
|
|
||||||
|
const builders = await detectApiBuilders(files);
|
||||||
|
expect(builders[0].use).toBe('@now/node');
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const files = ['package.json', 'api/user.go', 'api/team.js'];
|
||||||
|
|
||||||
|
const builders = await detectApiBuilders(files);
|
||||||
|
expect(builders.some(({ use }) => use === '@now/go')).toBeTruthy();
|
||||||
|
expect(builders.some(({ use }) => use === '@now/node')).toBeTruthy();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const files = ['package.json'];
|
||||||
|
|
||||||
|
const builders = await detectApiBuilders(files);
|
||||||
|
expect(builders).toBe(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Test `detectApiRoutes`', async () => {
|
||||||
|
{
|
||||||
|
const files = ['api/user.go', 'api/team.js'];
|
||||||
|
|
||||||
|
const { defaultRoutes } = await detectApiRoutes(files);
|
||||||
|
expect(defaultRoutes.length).toBe(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const files = ['api/user.go', 'api/user.js'];
|
||||||
|
|
||||||
|
const { error } = await detectApiRoutes(files);
|
||||||
|
expect(error.code).toBe('conflicting_file_path');
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const files = ['api/[user].go', 'api/[team]/[id].js'];
|
||||||
|
|
||||||
|
const { error } = await detectApiRoutes(files);
|
||||||
|
expect(error.code).toBe('conflicting_file_path');
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const files = ['api/[team]/[team].js'];
|
||||||
|
|
||||||
|
const { error } = await detectApiRoutes(files);
|
||||||
|
expect(error.code).toBe('conflicting_path_segment');
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const files = ['api/[endpoint].js', 'api/[endpoint]/[id].js'];
|
||||||
|
|
||||||
|
const { defaultRoutes } = await detectApiRoutes(files);
|
||||||
|
expect(defaultRoutes.length).toBe(2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@@ -61,6 +61,18 @@ export async function build({
|
|||||||
await initPrivateGit(process.env.GIT_CREDENTIALS);
|
await initPrivateGit(process.env.GIT_CREDENTIALS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.env.GO111MODULE) {
|
||||||
|
console.log(`\nManually assigning 'GO111MODULE' is not recommended.
|
||||||
|
|
||||||
|
By default:
|
||||||
|
- 'GO111MODULE=on' If entrypoint package name is not 'main'
|
||||||
|
- 'GO111MODULE=off' If entrypoint package name is 'main'
|
||||||
|
|
||||||
|
We highly recommend you leverage Go Modules in your project.
|
||||||
|
Learn more: https://github.com/golang/go/wiki/Modules
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
console.log('Downloading user files...');
|
console.log('Downloading user files...');
|
||||||
const entrypointArr = entrypoint.split(sep);
|
const entrypointArr = entrypoint.split(sep);
|
||||||
|
|
||||||
@@ -135,7 +147,7 @@ Learn more: https://zeit.co/docs/v2/deployments/official-builders/go-now-go/#ent
|
|||||||
isGoModExist = true;
|
isGoModExist = true;
|
||||||
isGoModInRootDir = true;
|
isGoModInRootDir = true;
|
||||||
goModPath = fileDirname;
|
goModPath = fileDirname;
|
||||||
} else if (file.includes('go.mod')) {
|
} else if (file.endsWith('go.mod') && !file.endsWith('vendor')) {
|
||||||
if (entrypointDirname === fileDirname) {
|
if (entrypointDirname === fileDirname) {
|
||||||
isGoModExist = true;
|
isGoModExist = true;
|
||||||
goModPath = fileDirname;
|
goModPath = fileDirname;
|
||||||
@@ -161,6 +173,10 @@ Learn more: https://zeit.co/docs/v2/deployments/official-builders/go-now-go/#ent
|
|||||||
`Found exported function "${handlerFunctionName}" in "${entrypoint}"`
|
`Found exported function "${handlerFunctionName}" in "${entrypoint}"`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!isGoModExist && 'vendor' in downloadedFiles) {
|
||||||
|
throw new Error('`go.mod` is required to use a `vendor` directory.');
|
||||||
|
}
|
||||||
|
|
||||||
// check if package name other than main
|
// check if package name other than main
|
||||||
// using `go.mod` way building the handler
|
// using `go.mod` way building the handler
|
||||||
const packageName = parsedAnalyzed.packageName;
|
const packageName = parsedAnalyzed.packageName;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/go",
|
"name": "@now/go",
|
||||||
"version": "0.5.2",
|
"version": "0.5.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/go-now-go",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/zeit/now-builders.git",
|
"url": "https://github.com/zeit/now-builders.git",
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/html-minifier",
|
"name": "@now/html-minifier",
|
||||||
"version": "1.1.3",
|
"version": "1.1.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/html-minifier-now-html-minifier",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/zeit/now-builders.git",
|
"url": "https://github.com/zeit/now-builders.git",
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/md",
|
"name": "@now/md",
|
||||||
"version": "0.5.4",
|
"version": "0.5.5",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/markdown-now-md",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/zeit/now-builders.git",
|
"url": "https://github.com/zeit/now-builders.git",
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/mdx-deck",
|
"name": "@now/mdx-deck",
|
||||||
"version": "0.5.4",
|
"version": "0.5.5",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/mdx-deck-now-mdx-deck",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/zeit/now-builders.git",
|
"url": "https://github.com/zeit/now-builders.git",
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/next",
|
"name": "@now/next",
|
||||||
"version": "0.5.0",
|
"version": "0.5.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index",
|
"main": "./dist/index",
|
||||||
|
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/next-js-now-next",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "./getBridgeTypes.sh && tsc",
|
"build": "./getBridgeTypes.sh && tsc",
|
||||||
"test": "npm run build && jest",
|
"test": "npm run build && jest",
|
||||||
@@ -14,7 +15,7 @@
|
|||||||
"directory": "packages/now-next"
|
"directory": "packages/now-next"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@now/node-bridge": "^1.2.1",
|
"@now/node-bridge": "1.2.2",
|
||||||
"fs-extra": "^7.0.0",
|
"fs-extra": "^7.0.0",
|
||||||
"get-port": "^5.0.0",
|
"get-port": "^5.0.0",
|
||||||
"resolve-from": "^5.0.0",
|
"resolve-from": "^5.0.0",
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import {
|
|||||||
validateEntrypoint,
|
validateEntrypoint,
|
||||||
normalizePage,
|
normalizePage,
|
||||||
getDynamicRoutes,
|
getDynamicRoutes,
|
||||||
|
isDynamicRoute,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
|
|
||||||
interface BuildParamsMeta {
|
interface BuildParamsMeta {
|
||||||
@@ -164,6 +165,8 @@ export const build = async ({
|
|||||||
watch?: string[];
|
watch?: string[];
|
||||||
childProcesses: ChildProcess[];
|
childProcesses: ChildProcess[];
|
||||||
}> => {
|
}> => {
|
||||||
|
process.env.__NEXT_BUILDER_EXPERIMENTAL_TARGET = 'serverless';
|
||||||
|
|
||||||
validateEntrypoint(entrypoint);
|
validateEntrypoint(entrypoint);
|
||||||
|
|
||||||
const entryDirectory = path.dirname(entrypoint);
|
const entryDirectory = path.dirname(entrypoint);
|
||||||
@@ -185,11 +188,7 @@ export const build = async ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
process.env.__NEXT_BUILDER_EXPERIMENTAL_TARGET = 'serverless';
|
|
||||||
|
|
||||||
if (meta.isDev) {
|
if (meta.isDev) {
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
process.env.__NEXT_BUILDER_EXPERIMENTAL_DEBUG = 'true';
|
|
||||||
let childProcess: ChildProcess | undefined;
|
let childProcess: ChildProcess | undefined;
|
||||||
|
|
||||||
// If this is the initial build, we want to start the server
|
// If this is the initial build, we want to start the server
|
||||||
@@ -286,10 +285,9 @@ export const build = async ({
|
|||||||
|
|
||||||
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;
|
||||||
const buildSpawnOptions = { ...spawnOpts };
|
const env = { ...spawnOpts.env } as any;
|
||||||
const env = { ...buildSpawnOptions.env } as any;
|
|
||||||
env.NODE_OPTIONS = `--max_old_space_size=${memoryToConsume}`;
|
env.NODE_OPTIONS = `--max_old_space_size=${memoryToConsume}`;
|
||||||
await runPackageJsonScript(entryPath, 'now-build', buildSpawnOptions);
|
await runPackageJsonScript(entryPath, 'now-build', { ...spawnOpts, env });
|
||||||
|
|
||||||
if (isLegacy) {
|
if (isLegacy) {
|
||||||
console.log('running npm install --production...');
|
console.log('running npm install --production...');
|
||||||
@@ -388,7 +386,7 @@ export const build = async ({
|
|||||||
'now__launcher.js': new FileBlob({ data: launcher }),
|
'now__launcher.js': new FileBlob({ data: launcher }),
|
||||||
},
|
},
|
||||||
handler: 'now__launcher.launcher',
|
handler: 'now__launcher.launcher',
|
||||||
runtime: 'nodejs8.10',
|
runtime: nodeVersion.runtime,
|
||||||
});
|
});
|
||||||
console.log(`Created lambda for page: "${page}"`);
|
console.log(`Created lambda for page: "${page}"`);
|
||||||
})
|
})
|
||||||
@@ -414,7 +412,7 @@ export const build = async ({
|
|||||||
|
|
||||||
const pathname = page.replace(/\.html$/, '');
|
const pathname = page.replace(/\.html$/, '');
|
||||||
|
|
||||||
if (pathname.startsWith('$') || pathname.includes('/$')) {
|
if (isDynamicRoute(pathname)) {
|
||||||
dynamicPages.push(pathname);
|
dynamicPages.push(pathname);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,7 +459,7 @@ export const build = async ({
|
|||||||
|
|
||||||
const pathname = page.replace(/\.js$/, '');
|
const pathname = page.replace(/\.js$/, '');
|
||||||
|
|
||||||
if (pathname.startsWith('$') || pathname.includes('/$')) {
|
if (isDynamicRoute(pathname)) {
|
||||||
dynamicPages.push(normalizePage(pathname));
|
dynamicPages.push(normalizePage(pathname));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -473,7 +471,7 @@ export const build = async ({
|
|||||||
'page.js': pages[page],
|
'page.js': pages[page],
|
||||||
},
|
},
|
||||||
handler: 'now__launcher.launcher',
|
handler: 'now__launcher.launcher',
|
||||||
runtime: 'nodejs8.10',
|
runtime: nodeVersion.runtime,
|
||||||
});
|
});
|
||||||
console.log(`Created lambda for page: "${page}"`);
|
console.log(`Created lambda for page: "${page}"`);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,6 +9,14 @@ export interface EnvConfig {
|
|||||||
[name: string]: string | undefined;
|
[name: string]: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Identify /[param]/ in route string
|
||||||
|
const TEST_DYNAMIC_ROUTE = /\/\[[^\/]+?\](?=\/|$)/;
|
||||||
|
|
||||||
|
function isDynamicRoute(route: string): boolean {
|
||||||
|
route = route.startsWith('/') ? route : `/${route}`;
|
||||||
|
return TEST_DYNAMIC_ROUTE.test(route);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate if the entrypoint is allowed to be used
|
* Validate if the entrypoint is allowed to be used
|
||||||
*/
|
*/
|
||||||
@@ -226,7 +234,7 @@ function getRoutes(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pageName.startsWith('$') || pageName.includes('/$')) {
|
if (isDynamicRoute(pageName)) {
|
||||||
dynamicPages.push(normalizePage(pageName));
|
dynamicPages.push(normalizePage(pageName));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,4 +359,5 @@ export {
|
|||||||
stringMap,
|
stringMap,
|
||||||
syncEnvVars,
|
syncEnvVars,
|
||||||
normalizePage,
|
normalizePage,
|
||||||
|
isDynamicRoute,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ it(
|
|||||||
const {
|
const {
|
||||||
buildResult: { output },
|
buildResult: { output },
|
||||||
} = await runBuildLambda(path.join(__dirname, 'standard'));
|
} = await runBuildLambda(path.join(__dirname, 'standard'));
|
||||||
expect(output.index).toBeDefined();
|
expect(output['index.html']).toBeDefined();
|
||||||
|
expect(output.goodbye).toBeDefined();
|
||||||
const filePaths = Object.keys(output);
|
const filePaths = Object.keys(output);
|
||||||
const serverlessError = filePaths.some(filePath => filePath.match(/_error/));
|
const serverlessError = filePaths.some(filePath => filePath.match(/_error/));
|
||||||
const hasUnderScoreAppStaticFile = filePaths.some(filePath => filePath.match(/static.*\/pages\/_app\.js$/));
|
const hasUnderScoreAppStaticFile = filePaths.some(filePath => filePath.match(/static.*\/pages\/_app\.js$/));
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
target: 'serverless',
|
|
||||||
};
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"version": 2,
|
"version": 2,
|
||||||
"builds": [{ "src": "next.config.js", "use": "@now/next" }]
|
"builds": [{ "src": "package.json", "use": "@now/next" }]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
const F = () => 'Goodbye World!';
|
||||||
|
F.getInitialProps = () => ({});
|
||||||
|
export default F;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/node-bridge",
|
"name": "@now/node-bridge",
|
||||||
"version": "1.2.1",
|
"version": "1.2.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./index.js",
|
"main": "./index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@@ -27,6 +27,16 @@ export interface NowProxyResponse {
|
|||||||
encoding: string;
|
encoding: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ServerLike {
|
||||||
|
listen: (
|
||||||
|
opts: {
|
||||||
|
host?: string;
|
||||||
|
port?: number;
|
||||||
|
},
|
||||||
|
callback: (this: Server | null) => void
|
||||||
|
) => Server | void;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the `http.Server` handler function throws an error asynchronously,
|
* If the `http.Server` handler function throws an error asynchronously,
|
||||||
* then it ends up being an unhandled rejection which doesn't kill the node
|
* then it ends up being an unhandled rejection which doesn't kill the node
|
||||||
@@ -92,14 +102,14 @@ function normalizeEvent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Bridge {
|
export class Bridge {
|
||||||
private server: Server | null;
|
private server: ServerLike | null;
|
||||||
private listening: Promise<AddressInfo>;
|
private listening: Promise<AddressInfo>;
|
||||||
private resolveListening: (info: AddressInfo) => void;
|
private resolveListening: (info: AddressInfo) => void;
|
||||||
private events: { [key: string]: NowProxyRequest } = {};
|
private events: { [key: string]: NowProxyRequest } = {};
|
||||||
private reqIdSeed: number = 1;
|
private reqIdSeed: number = 1;
|
||||||
private shouldStoreEvents: boolean = false;
|
private shouldStoreEvents: boolean = false;
|
||||||
|
|
||||||
constructor(server?: Server, shouldStoreEvents: boolean = false) {
|
constructor(server?: ServerLike, shouldStoreEvents: boolean = false) {
|
||||||
this.server = null;
|
this.server = null;
|
||||||
this.shouldStoreEvents = shouldStoreEvents;
|
this.shouldStoreEvents = shouldStoreEvents;
|
||||||
if (server) {
|
if (server) {
|
||||||
@@ -116,18 +126,8 @@ export class Bridge {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setServer(server: Server) {
|
setServer(server: ServerLike) {
|
||||||
this.server = server;
|
this.server = server;
|
||||||
server.once('listening', () => {
|
|
||||||
const addr = server.address();
|
|
||||||
if (typeof addr === 'string') {
|
|
||||||
throw new Error(`Unexpected string for \`server.address()\`: ${addr}`);
|
|
||||||
} else if (!addr) {
|
|
||||||
throw new Error('`server.address()` returned `null`');
|
|
||||||
} else {
|
|
||||||
this.resolveListening(addr);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
listen() {
|
listen() {
|
||||||
@@ -135,10 +135,35 @@ export class Bridge {
|
|||||||
throw new Error('Server has not been set!');
|
throw new Error('Server has not been set!');
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.server.listen({
|
const resolveListening = this.resolveListening;
|
||||||
|
|
||||||
|
return this.server.listen(
|
||||||
|
{
|
||||||
host: '127.0.0.1',
|
host: '127.0.0.1',
|
||||||
port: 0,
|
port: 0,
|
||||||
});
|
},
|
||||||
|
function listeningCallback() {
|
||||||
|
if (!this || typeof this.address !== 'function') {
|
||||||
|
throw new Error(
|
||||||
|
'Missing server.address() function on `this` in server.listen()'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const addr = this.address();
|
||||||
|
|
||||||
|
if (!addr) {
|
||||||
|
throw new Error('`server.address()` returned `null`');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof addr === 'string') {
|
||||||
|
throw new Error(
|
||||||
|
`Unexpected string for \`server.address()\`: ${addr}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveListening(addr);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async launcher(
|
async launcher(
|
||||||
|
|||||||
@@ -157,10 +157,13 @@ exports.build = async ({
|
|||||||
'bridge.js': new FileFsRef({ fsPath: require('@now/node-bridge') }),
|
'bridge.js': new FileFsRef({ fsPath: require('@now/node-bridge') }),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Use the system-installed version of `node` when running via `now dev`
|
||||||
|
const runtime = meta.isDev ? 'nodejs' : nodeVersion.runtime;
|
||||||
|
|
||||||
const lambda = await createLambda({
|
const lambda = await createLambda({
|
||||||
files: { ...preparedFiles, ...launcherFiles },
|
files: { ...preparedFiles, ...launcherFiles },
|
||||||
handler: 'launcher.launcher',
|
handler: 'launcher.launcher',
|
||||||
runtime: nodeVersion.runtime,
|
runtime,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { [entrypoint]: lambda };
|
return { [entrypoint]: lambda };
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/node-server",
|
"name": "@now/node-server",
|
||||||
"version": "0.8.0",
|
"version": "0.8.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/node-js-server-now-node-server",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/zeit/now-builders.git",
|
"url": "https://github.com/zeit/now-builders.git",
|
||||||
"directory": "packages/now-node-server"
|
"directory": "packages/now-node-server"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@now/node-bridge": "^1.2.1",
|
"@now/node-bridge": "1.2.2",
|
||||||
"@zeit/ncc": "0.18.5",
|
"@zeit/ncc": "0.18.5",
|
||||||
"fs-extra": "7.0.1"
|
"fs-extra": "7.0.1"
|
||||||
},
|
},
|
||||||
|
|||||||
1
packages/now-node/bench/.gitignore
vendored
Normal file
1
packages/now-node/bench/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
lambda
|
||||||
19
packages/now-node/bench/entrypoint-express.js
Normal file
19
packages/now-node/bench/entrypoint-express.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
const express = require('express');
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
module.exports = app;
|
||||||
|
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
app.post('*', (req, res) => {
|
||||||
|
if (req.body == null) {
|
||||||
|
return res.status(400).send({ error: 'no JSON object in the request' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send(JSON.stringify(req.body, null, 4));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.all('*', (req, res) => {
|
||||||
|
res.status(405).send({ error: 'only POST requests are accepted' });
|
||||||
|
});
|
||||||
7
packages/now-node/bench/entrypoint-helpers.js
Normal file
7
packages/now-node/bench/entrypoint-helpers.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
module.exports = (req, res) => {
|
||||||
|
if (req.body == null) {
|
||||||
|
return res.status(400).send({ error: 'no JSON object in the request' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send(JSON.stringify(req.body, null, 4));
|
||||||
|
};
|
||||||
9
packages/now-node/bench/entrypoint-load-helpers.js
Normal file
9
packages/now-node/bench/entrypoint-load-helpers.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
function doNothing() {}
|
||||||
|
|
||||||
|
module.exports = (req, res) => {
|
||||||
|
doNothing(req.query.who);
|
||||||
|
doNothing(req.body);
|
||||||
|
doNothing(req.cookies);
|
||||||
|
|
||||||
|
res.end('hello');
|
||||||
|
};
|
||||||
3
packages/now-node/bench/entrypoint-notload-helpers.js
Normal file
3
packages/now-node/bench/entrypoint-notload-helpers.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = (req, res) => {
|
||||||
|
res.end('hello');
|
||||||
|
};
|
||||||
10
packages/now-node/bench/package.json
Normal file
10
packages/now-node/bench/package.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "bench",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"express": "4.17.1",
|
||||||
|
"fs-extra": "8.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
91
packages/now-node/bench/run.js
Normal file
91
packages/now-node/bench/run.js
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
const fs = require('fs-extra');
|
||||||
|
const { join } = require('path');
|
||||||
|
const { makeLauncher } = require('../dist/launcher');
|
||||||
|
|
||||||
|
const setupFiles = async (entrypoint, shouldAddHelpers) => {
|
||||||
|
await fs.remove(join(__dirname, 'lambda'));
|
||||||
|
await fs.ensureDir(join(__dirname, 'lambda'));
|
||||||
|
|
||||||
|
await fs.copyFile(
|
||||||
|
join(__dirname, '../dist/helpers.js'),
|
||||||
|
join(__dirname, 'lambda/helpers.js'),
|
||||||
|
);
|
||||||
|
await fs.copyFile(
|
||||||
|
require.resolve('@now/node-bridge/bridge'),
|
||||||
|
join(__dirname, 'lambda/bridge.js'),
|
||||||
|
);
|
||||||
|
await fs.copyFile(
|
||||||
|
join(process.cwd(), entrypoint),
|
||||||
|
join(__dirname, 'lambda/entrypoint.js'),
|
||||||
|
);
|
||||||
|
|
||||||
|
let launcher = makeLauncher('./entrypoint', shouldAddHelpers);
|
||||||
|
launcher += '\nexports.bridge=bridge';
|
||||||
|
|
||||||
|
await fs.writeFile(join(__dirname, 'lambda/launcher.js'), launcher);
|
||||||
|
};
|
||||||
|
|
||||||
|
const createBigJSONObj = () => {
|
||||||
|
const obj = {};
|
||||||
|
for (let i = 0; i < 1000; i += 1) {
|
||||||
|
obj[`idx${i}`] = `val${i}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const createEvent = () => ({
|
||||||
|
Action: 'Invoke',
|
||||||
|
body: JSON.stringify({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/',
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
encoding: undefined,
|
||||||
|
body: createBigJSONObj(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const runTests = async (entrypoint, shouldAddHelpers = true, nb) => {
|
||||||
|
console.log(
|
||||||
|
`setting up files with entrypoint ${entrypoint} and ${
|
||||||
|
shouldAddHelpers ? 'helpers' : 'no helpers'
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
await setupFiles(entrypoint, shouldAddHelpers);
|
||||||
|
|
||||||
|
console.log('importing launcher');
|
||||||
|
const launcher = require('./lambda/launcher');
|
||||||
|
|
||||||
|
const event = createEvent();
|
||||||
|
const context = {};
|
||||||
|
|
||||||
|
const start = process.hrtime();
|
||||||
|
|
||||||
|
console.log(`throwing ${nb} events at lambda`);
|
||||||
|
for (let i = 0; i < nb; i += 1) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
await launcher.launcher(event, context);
|
||||||
|
}
|
||||||
|
const timer = process.hrtime(start);
|
||||||
|
const ms = (timer[0] * 1e9 + timer[1]) / 1e6;
|
||||||
|
|
||||||
|
await launcher.bridge.server.close();
|
||||||
|
delete require.cache[require.resolve('./lambda/launcher')];
|
||||||
|
|
||||||
|
console.log({ nb, sum: ms, avg: ms / nb });
|
||||||
|
};
|
||||||
|
|
||||||
|
const main = async () => {
|
||||||
|
if (process.argv.length !== 5) {
|
||||||
|
console.log(
|
||||||
|
'usage : node run.js <entrypoint-file> <add-helpers> <nb-of-request>',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [, , entrypoint, helpers, nbRequests] = process.argv;
|
||||||
|
const shouldAddHelpers = helpers !== 'false' && helpers !== 'no';
|
||||||
|
const nb = Number(nbRequests);
|
||||||
|
|
||||||
|
await runTests(entrypoint, shouldAddHelpers, nb);
|
||||||
|
};
|
||||||
|
|
||||||
|
main();
|
||||||
378
packages/now-node/bench/yarn.lock
Normal file
378
packages/now-node/bench/yarn.lock
Normal file
@@ -0,0 +1,378 @@
|
|||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
accepts@~1.3.7:
|
||||||
|
version "1.3.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
|
||||||
|
integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
|
||||||
|
dependencies:
|
||||||
|
mime-types "~2.1.24"
|
||||||
|
negotiator "0.6.2"
|
||||||
|
|
||||||
|
array-flatten@1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
|
||||||
|
integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
|
||||||
|
|
||||||
|
body-parser@1.19.0:
|
||||||
|
version "1.19.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
|
||||||
|
integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
|
||||||
|
dependencies:
|
||||||
|
bytes "3.1.0"
|
||||||
|
content-type "~1.0.4"
|
||||||
|
debug "2.6.9"
|
||||||
|
depd "~1.1.2"
|
||||||
|
http-errors "1.7.2"
|
||||||
|
iconv-lite "0.4.24"
|
||||||
|
on-finished "~2.3.0"
|
||||||
|
qs "6.7.0"
|
||||||
|
raw-body "2.4.0"
|
||||||
|
type-is "~1.6.17"
|
||||||
|
|
||||||
|
bytes@3.1.0:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
|
||||||
|
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
|
||||||
|
|
||||||
|
content-disposition@0.5.3:
|
||||||
|
version "0.5.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
|
||||||
|
integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==
|
||||||
|
dependencies:
|
||||||
|
safe-buffer "5.1.2"
|
||||||
|
|
||||||
|
content-type@~1.0.4:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
|
||||||
|
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
|
||||||
|
|
||||||
|
cookie-signature@1.0.6:
|
||||||
|
version "1.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
|
||||||
|
integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
|
||||||
|
|
||||||
|
cookie@0.4.0:
|
||||||
|
version "0.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
|
||||||
|
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
|
||||||
|
|
||||||
|
debug@2.6.9:
|
||||||
|
version "2.6.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||||
|
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
|
||||||
|
dependencies:
|
||||||
|
ms "2.0.0"
|
||||||
|
|
||||||
|
depd@~1.1.2:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
||||||
|
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
|
||||||
|
|
||||||
|
destroy@~1.0.4:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
|
||||||
|
integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
|
||||||
|
|
||||||
|
ee-first@1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||||
|
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
|
||||||
|
|
||||||
|
encodeurl@~1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
||||||
|
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
|
||||||
|
|
||||||
|
escape-html@~1.0.3:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
|
||||||
|
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
|
||||||
|
|
||||||
|
etag@~1.8.1:
|
||||||
|
version "1.8.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
|
||||||
|
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
|
||||||
|
|
||||||
|
express@4.17.1:
|
||||||
|
version "4.17.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
|
||||||
|
integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==
|
||||||
|
dependencies:
|
||||||
|
accepts "~1.3.7"
|
||||||
|
array-flatten "1.1.1"
|
||||||
|
body-parser "1.19.0"
|
||||||
|
content-disposition "0.5.3"
|
||||||
|
content-type "~1.0.4"
|
||||||
|
cookie "0.4.0"
|
||||||
|
cookie-signature "1.0.6"
|
||||||
|
debug "2.6.9"
|
||||||
|
depd "~1.1.2"
|
||||||
|
encodeurl "~1.0.2"
|
||||||
|
escape-html "~1.0.3"
|
||||||
|
etag "~1.8.1"
|
||||||
|
finalhandler "~1.1.2"
|
||||||
|
fresh "0.5.2"
|
||||||
|
merge-descriptors "1.0.1"
|
||||||
|
methods "~1.1.2"
|
||||||
|
on-finished "~2.3.0"
|
||||||
|
parseurl "~1.3.3"
|
||||||
|
path-to-regexp "0.1.7"
|
||||||
|
proxy-addr "~2.0.5"
|
||||||
|
qs "6.7.0"
|
||||||
|
range-parser "~1.2.1"
|
||||||
|
safe-buffer "5.1.2"
|
||||||
|
send "0.17.1"
|
||||||
|
serve-static "1.14.1"
|
||||||
|
setprototypeof "1.1.1"
|
||||||
|
statuses "~1.5.0"
|
||||||
|
type-is "~1.6.18"
|
||||||
|
utils-merge "1.0.1"
|
||||||
|
vary "~1.1.2"
|
||||||
|
|
||||||
|
finalhandler@~1.1.2:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
|
||||||
|
integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
|
||||||
|
dependencies:
|
||||||
|
debug "2.6.9"
|
||||||
|
encodeurl "~1.0.2"
|
||||||
|
escape-html "~1.0.3"
|
||||||
|
on-finished "~2.3.0"
|
||||||
|
parseurl "~1.3.3"
|
||||||
|
statuses "~1.5.0"
|
||||||
|
unpipe "~1.0.0"
|
||||||
|
|
||||||
|
forwarded@~0.1.2:
|
||||||
|
version "0.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
|
||||||
|
integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=
|
||||||
|
|
||||||
|
fresh@0.5.2:
|
||||||
|
version "0.5.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
||||||
|
integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
|
||||||
|
|
||||||
|
fs-extra@8.0.1:
|
||||||
|
version "8.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.0.1.tgz#90294081f978b1f182f347a440a209154344285b"
|
||||||
|
integrity sha512-W+XLrggcDzlle47X/XnS7FXrXu9sDo+Ze9zpndeBxdgv88FHLm1HtmkhEwavruS6koanBjp098rUpHs65EmG7A==
|
||||||
|
dependencies:
|
||||||
|
graceful-fs "^4.1.2"
|
||||||
|
jsonfile "^4.0.0"
|
||||||
|
universalify "^0.1.0"
|
||||||
|
|
||||||
|
graceful-fs@^4.1.2, graceful-fs@^4.1.6:
|
||||||
|
version "4.1.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
|
||||||
|
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
|
||||||
|
|
||||||
|
http-errors@1.7.2, http-errors@~1.7.2:
|
||||||
|
version "1.7.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
|
||||||
|
integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
|
||||||
|
dependencies:
|
||||||
|
depd "~1.1.2"
|
||||||
|
inherits "2.0.3"
|
||||||
|
setprototypeof "1.1.1"
|
||||||
|
statuses ">= 1.5.0 < 2"
|
||||||
|
toidentifier "1.0.0"
|
||||||
|
|
||||||
|
iconv-lite@0.4.24:
|
||||||
|
version "0.4.24"
|
||||||
|
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||||
|
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
|
||||||
|
dependencies:
|
||||||
|
safer-buffer ">= 2.1.2 < 3"
|
||||||
|
|
||||||
|
inherits@2.0.3:
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||||
|
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
|
||||||
|
|
||||||
|
ipaddr.js@1.9.0:
|
||||||
|
version "1.9.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65"
|
||||||
|
integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==
|
||||||
|
|
||||||
|
jsonfile@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
|
||||||
|
integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=
|
||||||
|
optionalDependencies:
|
||||||
|
graceful-fs "^4.1.6"
|
||||||
|
|
||||||
|
media-typer@0.3.0:
|
||||||
|
version "0.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||||
|
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
|
||||||
|
|
||||||
|
merge-descriptors@1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
|
||||||
|
integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
|
||||||
|
|
||||||
|
methods@~1.1.2:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
|
||||||
|
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
|
||||||
|
|
||||||
|
mime-db@1.40.0:
|
||||||
|
version "1.40.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32"
|
||||||
|
integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==
|
||||||
|
|
||||||
|
mime-types@~2.1.24:
|
||||||
|
version "2.1.24"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81"
|
||||||
|
integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==
|
||||||
|
dependencies:
|
||||||
|
mime-db "1.40.0"
|
||||||
|
|
||||||
|
mime@1.6.0:
|
||||||
|
version "1.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
||||||
|
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
||||||
|
|
||||||
|
ms@2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||||
|
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
|
||||||
|
|
||||||
|
ms@2.1.1:
|
||||||
|
version "2.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
|
||||||
|
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
|
||||||
|
|
||||||
|
negotiator@0.6.2:
|
||||||
|
version "0.6.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
|
||||||
|
integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
|
||||||
|
|
||||||
|
on-finished@~2.3.0:
|
||||||
|
version "2.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
|
||||||
|
integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
|
||||||
|
dependencies:
|
||||||
|
ee-first "1.1.1"
|
||||||
|
|
||||||
|
parseurl@~1.3.3:
|
||||||
|
version "1.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
|
||||||
|
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
|
||||||
|
|
||||||
|
path-to-regexp@0.1.7:
|
||||||
|
version "0.1.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
|
||||||
|
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
|
||||||
|
|
||||||
|
proxy-addr@~2.0.5:
|
||||||
|
version "2.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34"
|
||||||
|
integrity sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==
|
||||||
|
dependencies:
|
||||||
|
forwarded "~0.1.2"
|
||||||
|
ipaddr.js "1.9.0"
|
||||||
|
|
||||||
|
qs@6.7.0:
|
||||||
|
version "6.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
|
||||||
|
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
|
||||||
|
|
||||||
|
range-parser@~1.2.1:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
|
||||||
|
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
|
||||||
|
|
||||||
|
raw-body@2.4.0:
|
||||||
|
version "2.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332"
|
||||||
|
integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==
|
||||||
|
dependencies:
|
||||||
|
bytes "3.1.0"
|
||||||
|
http-errors "1.7.2"
|
||||||
|
iconv-lite "0.4.24"
|
||||||
|
unpipe "1.0.0"
|
||||||
|
|
||||||
|
safe-buffer@5.1.2:
|
||||||
|
version "5.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||||
|
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||||
|
|
||||||
|
"safer-buffer@>= 2.1.2 < 3":
|
||||||
|
version "2.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||||
|
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||||
|
|
||||||
|
send@0.17.1:
|
||||||
|
version "0.17.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
|
||||||
|
integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==
|
||||||
|
dependencies:
|
||||||
|
debug "2.6.9"
|
||||||
|
depd "~1.1.2"
|
||||||
|
destroy "~1.0.4"
|
||||||
|
encodeurl "~1.0.2"
|
||||||
|
escape-html "~1.0.3"
|
||||||
|
etag "~1.8.1"
|
||||||
|
fresh "0.5.2"
|
||||||
|
http-errors "~1.7.2"
|
||||||
|
mime "1.6.0"
|
||||||
|
ms "2.1.1"
|
||||||
|
on-finished "~2.3.0"
|
||||||
|
range-parser "~1.2.1"
|
||||||
|
statuses "~1.5.0"
|
||||||
|
|
||||||
|
serve-static@1.14.1:
|
||||||
|
version "1.14.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9"
|
||||||
|
integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==
|
||||||
|
dependencies:
|
||||||
|
encodeurl "~1.0.2"
|
||||||
|
escape-html "~1.0.3"
|
||||||
|
parseurl "~1.3.3"
|
||||||
|
send "0.17.1"
|
||||||
|
|
||||||
|
setprototypeof@1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
|
||||||
|
integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
|
||||||
|
|
||||||
|
"statuses@>= 1.5.0 < 2", statuses@~1.5.0:
|
||||||
|
version "1.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
|
||||||
|
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
|
||||||
|
|
||||||
|
toidentifier@1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
|
||||||
|
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
|
||||||
|
|
||||||
|
type-is@~1.6.17, type-is@~1.6.18:
|
||||||
|
version "1.6.18"
|
||||||
|
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
|
||||||
|
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
|
||||||
|
dependencies:
|
||||||
|
media-typer "0.3.0"
|
||||||
|
mime-types "~2.1.24"
|
||||||
|
|
||||||
|
universalify@^0.1.0:
|
||||||
|
version "0.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
||||||
|
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
||||||
|
|
||||||
|
unpipe@1.0.0, unpipe@~1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
||||||
|
integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
|
||||||
|
|
||||||
|
utils-merge@1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
||||||
|
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
|
||||||
|
|
||||||
|
vary@~1.1.2:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
||||||
|
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
|
||||||
@@ -13,12 +13,17 @@ cp -v "$bridge_defs" src
|
|||||||
# build ts files
|
# build ts files
|
||||||
tsc
|
tsc
|
||||||
|
|
||||||
|
# todo: improve
|
||||||
|
# copy type file for ts test
|
||||||
|
cp dist/types.d.ts test/fixtures/15-helpers/ts/types.d.ts
|
||||||
|
|
||||||
|
# use types.d.ts as the main types export
|
||||||
|
mv dist/types.d.ts dist/types
|
||||||
|
rm dist/*.d.ts
|
||||||
|
mv dist/types dist/index.d.ts
|
||||||
|
|
||||||
# bundle helpers.ts with ncc
|
# bundle helpers.ts with ncc
|
||||||
rm dist/helpers.js
|
rm dist/helpers.js
|
||||||
ncc build src/helpers.ts -o dist/helpers
|
ncc build src/helpers.ts -o dist/helpers
|
||||||
mv dist/helpers/index.js dist/helpers.js
|
mv dist/helpers/index.js dist/helpers.js
|
||||||
rm -rf dist/helpers
|
rm -rf dist/helpers
|
||||||
|
|
||||||
# todo: improve
|
|
||||||
# copy type file for ts test
|
|
||||||
cp dist/types.d.ts test/fixtures/15-helpers/ts/types.d.ts
|
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/node",
|
"name": "@now/node",
|
||||||
"version": "0.10.0",
|
"version": "0.11.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index",
|
"main": "./dist/index",
|
||||||
|
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/node-js-now-node",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/zeit/now-builders.git",
|
"url": "https://github.com/zeit/now-builders.git",
|
||||||
"directory": "packages/now-node"
|
"directory": "packages/now-node"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@now/node-bridge": "^1.2.1",
|
"@now/node-bridge": "1.2.2",
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
"@zeit/ncc": "0.18.5",
|
"@zeit/ncc": "0.18.5",
|
||||||
"@zeit/ncc-watcher": "1.0.3",
|
"@zeit/ncc-watcher": "1.0.3"
|
||||||
"fs-extra": "7.0.1"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "./build.sh",
|
"build": "./build.sh",
|
||||||
@@ -26,9 +26,11 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/content-type": "1.1.3",
|
"@types/content-type": "1.1.3",
|
||||||
"@types/cookie": "0.3.3",
|
"@types/cookie": "0.3.3",
|
||||||
|
"@types/etag": "1.8.0",
|
||||||
"@types/test-listen": "1.1.0",
|
"@types/test-listen": "1.1.0",
|
||||||
"content-type": "1.0.4",
|
"content-type": "1.0.4",
|
||||||
"cookie": "0.4.0",
|
"cookie": "0.4.0",
|
||||||
|
"etag": "1.8.1",
|
||||||
"node-fetch": "2.6.0",
|
"node-fetch": "2.6.0",
|
||||||
"test-listen": "1.1.0",
|
"test-listen": "1.1.0",
|
||||||
"typescript": "3.5.2"
|
"typescript": "3.5.2"
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
NowRequestQuery,
|
NowRequestQuery,
|
||||||
NowRequestBody,
|
NowRequestBody,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { Stream } from 'stream';
|
|
||||||
import { Server } from 'http';
|
import { Server } from 'http';
|
||||||
import { Bridge } from './bridge';
|
import { Bridge } from './bridge';
|
||||||
|
|
||||||
@@ -73,56 +72,127 @@ function getCookieParser(req: NowRequest) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendStatusCode(res: NowResponse, statusCode: number): NowResponse {
|
function status(res: NowResponse, statusCode: number): NowResponse {
|
||||||
res.statusCode = statusCode;
|
res.statusCode = statusCode;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendData(res: NowResponse, body: any): NowResponse {
|
function setCharset(type: string, charset: string) {
|
||||||
if (body === null) {
|
const { parse, format } = require('content-type');
|
||||||
|
const parsed = parse(type);
|
||||||
|
parsed.parameters.charset = charset;
|
||||||
|
return format(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createETag(body: any, encoding: string | undefined) {
|
||||||
|
const etag = require('etag');
|
||||||
|
const buf = !Buffer.isBuffer(body) ? Buffer.from(body, encoding) : body;
|
||||||
|
return etag(buf, { weak: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
function send(req: NowRequest, res: NowResponse, body: any): NowResponse {
|
||||||
|
let chunk: unknown = body;
|
||||||
|
let encoding: string | undefined;
|
||||||
|
|
||||||
|
switch (typeof chunk) {
|
||||||
|
// string defaulting to html
|
||||||
|
case 'string':
|
||||||
|
if (!res.getHeader('content-type')) {
|
||||||
|
res.setHeader('content-type', 'text/html');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'boolean':
|
||||||
|
case 'number':
|
||||||
|
case 'object':
|
||||||
|
if (chunk === null) {
|
||||||
|
chunk = '';
|
||||||
|
} else if (Buffer.isBuffer(chunk)) {
|
||||||
|
if (!res.getHeader('content-type')) {
|
||||||
|
res.setHeader('content-type', 'application/octet-stream');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return json(req, res, chunk);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// write strings in utf-8
|
||||||
|
if (typeof chunk === 'string') {
|
||||||
|
encoding = 'utf8';
|
||||||
|
|
||||||
|
// reflect this in content-type
|
||||||
|
const type = res.getHeader('content-type');
|
||||||
|
if (typeof type === 'string') {
|
||||||
|
res.setHeader('content-type', setCharset(type, 'utf-8'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// populate Content-Length
|
||||||
|
let len: number | undefined;
|
||||||
|
if (chunk !== undefined) {
|
||||||
|
if (Buffer.isBuffer(chunk)) {
|
||||||
|
// get length of Buffer
|
||||||
|
len = chunk.length;
|
||||||
|
} else if (typeof chunk === 'string') {
|
||||||
|
if (chunk.length < 1000) {
|
||||||
|
// just calculate length small chunk
|
||||||
|
len = Buffer.byteLength(chunk, encoding);
|
||||||
|
} else {
|
||||||
|
// convert chunk to Buffer and calculate
|
||||||
|
const buf = Buffer.from(chunk, encoding);
|
||||||
|
len = buf.length;
|
||||||
|
chunk = buf;
|
||||||
|
encoding = undefined;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
'`body` is not a valid string, object, boolean, number, Stream, or Buffer'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len !== undefined) {
|
||||||
|
res.setHeader('content-length', len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// populate ETag
|
||||||
|
let etag: string | undefined;
|
||||||
|
if (
|
||||||
|
!res.getHeader('etag') &&
|
||||||
|
len !== undefined &&
|
||||||
|
(etag = createETag(chunk, encoding))
|
||||||
|
) {
|
||||||
|
res.setHeader('etag', etag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// strip irrelevant headers
|
||||||
|
if (204 === res.statusCode || 304 === res.statusCode) {
|
||||||
|
res.removeHeader('Content-Type');
|
||||||
|
res.removeHeader('Content-Length');
|
||||||
|
res.removeHeader('Transfer-Encoding');
|
||||||
|
chunk = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.method === 'HEAD') {
|
||||||
|
// skip body for HEAD
|
||||||
res.end();
|
res.end();
|
||||||
return res;
|
} else {
|
||||||
|
// respond
|
||||||
|
res.end(chunk, encoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentType = res.getHeader('Content-Type');
|
|
||||||
|
|
||||||
if (Buffer.isBuffer(body)) {
|
|
||||||
if (!contentType) {
|
|
||||||
res.setHeader('Content-Type', 'application/octet-stream');
|
|
||||||
}
|
|
||||||
res.setHeader('Content-Length', body.length);
|
|
||||||
res.end(body);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (body instanceof Stream) {
|
|
||||||
if (!contentType) {
|
|
||||||
res.setHeader('Content-Type', 'application/octet-stream');
|
|
||||||
}
|
|
||||||
body.pipe(res);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
let str = body;
|
|
||||||
|
|
||||||
// Stringify JSON body
|
|
||||||
if (typeof body === 'object' || typeof body === 'number') {
|
|
||||||
str = JSON.stringify(body);
|
|
||||||
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
||||||
}
|
|
||||||
|
|
||||||
res.setHeader('Content-Length', Buffer.byteLength(str));
|
|
||||||
res.end(str);
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendJson(res: NowResponse, jsonBody: any): NowResponse {
|
function json(req: NowRequest, res: NowResponse, jsonBody: any): NowResponse {
|
||||||
// Set header to application/json
|
const body = JSON.stringify(jsonBody);
|
||||||
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
||||||
|
|
||||||
// Use send to handle request
|
// content-type
|
||||||
return res.send(jsonBody);
|
if (!res.getHeader('content-type')) {
|
||||||
|
res.setHeader('content-type', 'application/json; charset=utf-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
return send(req, res, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ApiError extends Error {
|
export class ApiError extends Error {
|
||||||
@@ -186,9 +256,9 @@ export function createServerWithHelpers(
|
|||||||
setLazyProp<NowRequestQuery>(req, 'query', getQueryParser(req));
|
setLazyProp<NowRequestQuery>(req, 'query', getQueryParser(req));
|
||||||
setLazyProp<NowRequestBody>(req, 'body', getBodyParser(req, event.body));
|
setLazyProp<NowRequestBody>(req, 'body', getBodyParser(req, event.body));
|
||||||
|
|
||||||
res.status = statusCode => sendStatusCode(res, statusCode);
|
res.status = statusCode => status(res, statusCode);
|
||||||
res.send = data => sendData(res, data);
|
res.send = body => send(req, res, body);
|
||||||
res.json = data => sendJson(res, data);
|
res.json = jsonBody => json(req, res, jsonBody);
|
||||||
|
|
||||||
await listener(req, res);
|
await listener(req, res);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { readFile } from 'fs-extra';
|
|
||||||
import { Assets, NccOptions } from '@zeit/ncc';
|
import { Assets, NccOptions } from '@zeit/ncc';
|
||||||
import { join, dirname, relative, sep } from 'path';
|
import { join, dirname, relative, sep } from 'path';
|
||||||
import { NccWatcher, WatcherResult } from '@zeit/ncc-watcher';
|
import { NccWatcher, WatcherResult } from '@zeit/ncc-watcher';
|
||||||
@@ -19,6 +18,7 @@ import {
|
|||||||
shouldServe,
|
shouldServe,
|
||||||
} from '@now/build-utils';
|
} from '@now/build-utils';
|
||||||
export { NowRequest, NowResponse } from './types';
|
export { NowRequest, NowResponse } from './types';
|
||||||
|
import { makeLauncher } from './launcher';
|
||||||
|
|
||||||
interface CompilerConfig {
|
interface CompilerConfig {
|
||||||
includeFiles?: string | string[];
|
includeFiles?: string | string[];
|
||||||
@@ -33,6 +33,10 @@ interface DownloadOptions {
|
|||||||
|
|
||||||
const watchers: Map<string, NccWatcher> = new Map();
|
const watchers: Map<string, NccWatcher> = new Map();
|
||||||
|
|
||||||
|
const LAUNCHER_FILENAME = '___now_launcher';
|
||||||
|
const BRIDGE_FILENAME = '___now_bridge';
|
||||||
|
const HELPERS_FILENAME = '___now_helpers';
|
||||||
|
|
||||||
function getWatcher(entrypoint: string, options: NccOptions): NccWatcher {
|
function getWatcher(entrypoint: string, options: NccOptions): NccWatcher {
|
||||||
let watcher = watchers.get(entrypoint);
|
let watcher = watchers.get(entrypoint);
|
||||||
if (!watcher) {
|
if (!watcher) {
|
||||||
@@ -101,7 +105,8 @@ async function compile(
|
|||||||
assets = result.assets;
|
assets = result.assets;
|
||||||
watch = [...result.files, ...result.dirs, ...result.missing]
|
watch = [...result.files, ...result.dirs, ...result.missing]
|
||||||
.filter(f => f.startsWith(workPath))
|
.filter(f => f.startsWith(workPath))
|
||||||
.map(f => relative(workPath, f));
|
.map(f => relative(workPath, f))
|
||||||
|
.concat(Object.keys(assets || {}));
|
||||||
} else {
|
} else {
|
||||||
const ncc = require('@zeit/ncc');
|
const ncc = require('@zeit/ncc');
|
||||||
const result = await ncc(input, {
|
const result = await ncc(input, {
|
||||||
@@ -202,44 +207,37 @@ export async function build({
|
|||||||
config,
|
config,
|
||||||
meta
|
meta
|
||||||
);
|
);
|
||||||
const launcherPath = join(__dirname, 'launcher.js');
|
|
||||||
let launcherData = await readFile(launcherPath, 'utf8');
|
|
||||||
|
|
||||||
launcherData = launcherData.replace(
|
|
||||||
'// PLACEHOLDER:shouldStoreProxyRequests',
|
|
||||||
shouldAddHelpers ? 'shouldStoreProxyRequests = true;' : ''
|
|
||||||
);
|
|
||||||
|
|
||||||
launcherData = launcherData.replace(
|
|
||||||
'// PLACEHOLDER:setServer',
|
|
||||||
[
|
|
||||||
`let listener = require("./${entrypoint}");`,
|
|
||||||
'if (listener.default) listener = listener.default;',
|
|
||||||
shouldAddHelpers
|
|
||||||
? 'const server = require("./helpers").createServerWithHelpers(listener, bridge);'
|
|
||||||
: 'const server = require("http").createServer(listener);',
|
|
||||||
'bridge.setServer(server);',
|
|
||||||
].join(' ')
|
|
||||||
);
|
|
||||||
|
|
||||||
const launcherFiles: Files = {
|
const launcherFiles: Files = {
|
||||||
'launcher.js': new FileBlob({ data: launcherData }),
|
[`${LAUNCHER_FILENAME}.js`]: new FileBlob({
|
||||||
'bridge.js': new FileFsRef({ fsPath: require('@now/node-bridge') }),
|
data: makeLauncher({
|
||||||
|
entrypointPath: `./${entrypoint}`,
|
||||||
|
bridgePath: `./${BRIDGE_FILENAME}`,
|
||||||
|
helpersPath: `./${HELPERS_FILENAME}`,
|
||||||
|
shouldAddHelpers,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
[`${BRIDGE_FILENAME}.js`]: new FileFsRef({
|
||||||
|
fsPath: require('@now/node-bridge'),
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (shouldAddHelpers) {
|
if (shouldAddHelpers) {
|
||||||
launcherFiles['helpers.js'] = new FileFsRef({
|
launcherFiles[`${HELPERS_FILENAME}.js`] = new FileFsRef({
|
||||||
fsPath: join(__dirname, 'helpers.js'),
|
fsPath: join(__dirname, 'helpers.js'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use the system-installed version of `node` when running via `now dev`
|
||||||
|
const runtime = meta.isDev ? 'nodejs' : nodeVersion.runtime;
|
||||||
|
|
||||||
const lambda = await createLambda({
|
const lambda = await createLambda({
|
||||||
files: {
|
files: {
|
||||||
...preparedFiles,
|
...preparedFiles,
|
||||||
...launcherFiles,
|
...launcherFiles,
|
||||||
},
|
},
|
||||||
handler: 'launcher.launcher',
|
handler: `${LAUNCHER_FILENAME}.launcher`,
|
||||||
runtime: nodeVersion.runtime,
|
runtime,
|
||||||
});
|
});
|
||||||
|
|
||||||
const output = { [entrypoint]: lambda };
|
const output = { [entrypoint]: lambda };
|
||||||
|
|||||||
@@ -1,9 +1,29 @@
|
|||||||
import { Bridge } from './bridge';
|
type LauncherConfiguration = {
|
||||||
|
entrypointPath: string;
|
||||||
|
bridgePath: string;
|
||||||
|
helpersPath: string;
|
||||||
|
shouldAddHelpers?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
let shouldStoreProxyRequests: boolean = false;
|
export function makeLauncher({
|
||||||
// PLACEHOLDER:shouldStoreProxyRequests
|
entrypointPath,
|
||||||
|
bridgePath,
|
||||||
|
helpersPath,
|
||||||
|
shouldAddHelpers = false,
|
||||||
|
}: LauncherConfiguration): string {
|
||||||
|
return `const { Bridge } = require("${bridgePath}");
|
||||||
|
const { Server } = require("http");
|
||||||
|
|
||||||
const bridge = new Bridge(undefined, shouldStoreProxyRequests);
|
let isServerListening = false;
|
||||||
|
let bridge = new Bridge();
|
||||||
|
const saveListen = Server.prototype.listen;
|
||||||
|
Server.prototype.listen = function listen() {
|
||||||
|
isServerListening = true;
|
||||||
|
console.log('Legacy server listening...');
|
||||||
|
bridge.setServer(this);
|
||||||
|
Server.prototype.listen = saveListen;
|
||||||
|
return bridge.listen();
|
||||||
|
};
|
||||||
|
|
||||||
if (!process.env.NODE_ENV) {
|
if (!process.env.NODE_ENV) {
|
||||||
process.env.NODE_ENV =
|
process.env.NODE_ENV =
|
||||||
@@ -11,13 +31,43 @@ if (!process.env.NODE_ENV) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// PLACEHOLDER:setServer
|
let listener = require("${entrypointPath}");
|
||||||
|
if (listener.default) listener = listener.default;
|
||||||
|
|
||||||
|
if (typeof listener.listen === 'function') {
|
||||||
|
Server.prototype.listen = saveListen;
|
||||||
|
const server = listener;
|
||||||
|
bridge.setServer(server);
|
||||||
|
bridge.listen();
|
||||||
|
} else if (typeof listener === 'function') {
|
||||||
|
Server.prototype.listen = saveListen;
|
||||||
|
let server;
|
||||||
|
${
|
||||||
|
shouldAddHelpers
|
||||||
|
? [
|
||||||
|
'bridge = new Bridge(undefined, true);',
|
||||||
|
`server = require("${helpersPath}").createServerWithHelpers(listener, bridge);`,
|
||||||
|
].join('\n')
|
||||||
|
: ['server = require("http").createServer(listener);'].join('\n')
|
||||||
|
}
|
||||||
|
bridge.setServer(server);
|
||||||
|
bridge.listen();
|
||||||
|
} else if (typeof listener === 'object' && Object.keys(listener).length === 0) {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!isServerListening) {
|
||||||
|
console.error('No exports found in module "${entrypointPath}".');
|
||||||
|
console.error('Did you forget to export a function or a server?');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
} else {
|
||||||
|
console.error('Invalid export found in module "${entrypointPath}".');
|
||||||
|
console.error('The default export must be a function or server.');
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code === 'MODULE_NOT_FOUND') {
|
if (err.code === 'MODULE_NOT_FOUND') {
|
||||||
console.error(err.message);
|
console.error(err.message);
|
||||||
console.error(
|
console.error('Did you forget to add it to "dependencies" in \`package.json\`?');
|
||||||
'Did you forget to add it to "dependencies" in `package.json`?'
|
|
||||||
);
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
} else {
|
} else {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@@ -25,6 +75,5 @@ try {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bridge.listen();
|
exports.launcher = bridge.launcher;`;
|
||||||
|
}
|
||||||
exports.launcher = bridge.launcher;
|
|
||||||
|
|||||||
@@ -12,6 +12,6 @@ export type NowRequest = IncomingMessage & {
|
|||||||
|
|
||||||
export type NowResponse = ServerResponse & {
|
export type NowResponse = ServerResponse & {
|
||||||
send: (body: any) => NowResponse;
|
send: (body: any) => NowResponse;
|
||||||
json: (body: any) => NowResponse;
|
json: (jsonBody: any) => NowResponse;
|
||||||
status: (statusCode: number) => NowResponse;
|
status: (statusCode: number) => NowResponse;
|
||||||
};
|
};
|
||||||
|
|||||||
25
packages/now-node/test/fixtures/02-node-server/hapi-async/index.js
vendored
Normal file
25
packages/now-node/test/fixtures/02-node-server/hapi-async/index.js
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
const Hapi = require('@hapi/hapi');
|
||||||
|
|
||||||
|
const init = async () => {
|
||||||
|
const server = Hapi.server({
|
||||||
|
port: 3000,
|
||||||
|
host: 'localhost',
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: 'GET',
|
||||||
|
path: '/{p*}',
|
||||||
|
handler: () => 'hapi-async:RANDOMNESS_PLACEHOLDER',
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.start();
|
||||||
|
console.log('Hapi server running on %s', server.info.uri);
|
||||||
|
};
|
||||||
|
|
||||||
|
process.on('unhandledRejection', (err) => {
|
||||||
|
console.log('Hapi failed in an unexpected way');
|
||||||
|
console.log(err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
init();
|
||||||
5
packages/now-node/test/fixtures/02-node-server/hapi-async/package.json
vendored
Normal file
5
packages/now-node/test/fixtures/02-node-server/hapi-async/package.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"@hapi/hapi": "18.3.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
7
packages/now-node/test/fixtures/02-node-server/index.js
vendored
Normal file
7
packages/now-node/test/fixtures/02-node-server/index.js
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
const http = require('http');
|
||||||
|
|
||||||
|
const server = http.createServer((req, resp) => {
|
||||||
|
resp.end('root:RANDOMNESS_PLACEHOLDER');
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen();
|
||||||
15
packages/now-node/test/fixtures/02-node-server/now.json
vendored
Normal file
15
packages/now-node/test/fixtures/02-node-server/now.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"builds": [{ "src": "**/*.js", "use": "@now/node" }],
|
||||||
|
"probes": [
|
||||||
|
{ "path": "/", "mustContain": "root:RANDOMNESS_PLACEHOLDER" },
|
||||||
|
{
|
||||||
|
"path": "/subdirectory/",
|
||||||
|
"mustContain": "subdir:RANDOMNESS_PLACEHOLDER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/hapi-async/",
|
||||||
|
"mustContain": "hapi-async:RANDOMNESS_PLACEHOLDER"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
7
packages/now-node/test/fixtures/02-node-server/subdirectory/index.js
vendored
Normal file
7
packages/now-node/test/fixtures/02-node-server/subdirectory/index.js
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
const http = require('http');
|
||||||
|
|
||||||
|
const server = http.createServer((req, resp) => {
|
||||||
|
resp.end('subdir:RANDOMNESS_PLACEHOLDER');
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen();
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
/* eslint-disable prefer-destructuring */
|
|
||||||
const express = require('express');
|
|
||||||
|
|
||||||
const app = express();
|
|
||||||
|
|
||||||
module.exports = app;
|
|
||||||
|
|
||||||
app.use(express.json());
|
|
||||||
|
|
||||||
app.all('*', (req, res) => {
|
|
||||||
res.status(200);
|
|
||||||
|
|
||||||
let who = 'anonymous';
|
|
||||||
|
|
||||||
if (req.body && req.body.who) {
|
|
||||||
who = req.body.who;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.send(`hello ${who}:RANDOMNESS_PLACEHOLDER`);
|
|
||||||
});
|
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
"builds": [
|
"builds": [
|
||||||
{ "src": "index.js", "use": "@now/node" },
|
{ "src": "index.js", "use": "@now/node" },
|
||||||
{ "src": "ts/index.ts", "use": "@now/node" },
|
{ "src": "ts/index.ts", "use": "@now/node" },
|
||||||
{ "src": "express-compat/index.js", "use": "@now/node" },
|
|
||||||
{ "src": "micro-compat/index.js", "use": "@now/node" },
|
{ "src": "micro-compat/index.js", "use": "@now/node" },
|
||||||
{
|
{
|
||||||
"src": "no-helpers/index.js",
|
"src": "no-helpers/index.js",
|
||||||
@@ -35,12 +34,6 @@
|
|||||||
"path": "/ts",
|
"path": "/ts",
|
||||||
"mustContain": "hello:RANDOMNESS_PLACEHOLDER"
|
"mustContain": "hello:RANDOMNESS_PLACEHOLDER"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"path": "/express-compat",
|
|
||||||
"method": "POST",
|
|
||||||
"body": { "who": "sara" },
|
|
||||||
"mustContain": "hello sara:RANDOMNESS_PLACEHOLDER"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"path": "/micro-compat",
|
"path": "/micro-compat",
|
||||||
"method": "POST",
|
"method": "POST",
|
||||||
|
|||||||
9
packages/now-node/test/fixtures/16-servers/express/index.js
vendored
Normal file
9
packages/now-node/test/fixtures/16-servers/express/index.js
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
const express = require('express');
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
app.all('*', (req, res) => {
|
||||||
|
res.send('hello from express:RANDOMNESS_PLACEHOLDER');
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = app;
|
||||||
17
packages/now-node/test/fixtures/16-servers/hapi/index.js
vendored
Normal file
17
packages/now-node/test/fixtures/16-servers/hapi/index.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
const Hapi = require('@hapi/hapi');
|
||||||
|
|
||||||
|
const server = Hapi.server({
|
||||||
|
port: 3000,
|
||||||
|
host: 'localhost',
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: 'GET',
|
||||||
|
path: '/{p*}',
|
||||||
|
handler: () => 'hello from hapi:RANDOMNESS_PLACEHOLDER',
|
||||||
|
});
|
||||||
|
|
||||||
|
// server.listener is a node's http.Server
|
||||||
|
// server does not have the `listen` method so we need to export this instead
|
||||||
|
|
||||||
|
module.exports = server.listener;
|
||||||
5
packages/now-node/test/fixtures/16-servers/hapi/package.json
vendored
Normal file
5
packages/now-node/test/fixtures/16-servers/hapi/package.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"@hapi/hapi": "18.3.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
7
packages/now-node/test/fixtures/16-servers/index.js
vendored
Normal file
7
packages/now-node/test/fixtures/16-servers/index.js
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
const http = require('http');
|
||||||
|
|
||||||
|
const server = http.createServer((req, res) => {
|
||||||
|
res.end('hello:RANDOMNESS_PLACEHOLDER');
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = server;
|
||||||
9
packages/now-node/test/fixtures/16-servers/koa/index.js
vendored
Normal file
9
packages/now-node/test/fixtures/16-servers/koa/index.js
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
const Koa = require('koa');
|
||||||
|
|
||||||
|
const app = new Koa();
|
||||||
|
|
||||||
|
app.use(async (ctx) => {
|
||||||
|
ctx.body = 'hello from koa:RANDOMNESS_PLACEHOLDER';
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = app;
|
||||||
5
packages/now-node/test/fixtures/16-servers/koa/package.json
vendored
Normal file
5
packages/now-node/test/fixtures/16-servers/koa/package.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"koa": "2.7.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
22
packages/now-node/test/fixtures/16-servers/now.json
vendored
Normal file
22
packages/now-node/test/fixtures/16-servers/now.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"builds": [{ "src": "**/*.js", "use": "@now/node" }],
|
||||||
|
"probes": [
|
||||||
|
{
|
||||||
|
"path": "/",
|
||||||
|
"mustContain": "hello:RANDOMNESS_PLACEHOLDER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/express",
|
||||||
|
"mustContain": "hello from express:RANDOMNESS_PLACEHOLDER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/koa",
|
||||||
|
"mustContain": "hello from koa:RANDOMNESS_PLACEHOLDER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/hapi",
|
||||||
|
"mustContain": "hello from hapi:RANDOMNESS_PLACEHOLDER"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
3
packages/now-node/test/fixtures/17-entrypoint-name-conflict/bridge.js
vendored
Normal file
3
packages/now-node/test/fixtures/17-entrypoint-name-conflict/bridge.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = (req, res) => {
|
||||||
|
res.end('bridge:RANDOMNESS_PLACEHOLDER');
|
||||||
|
};
|
||||||
3
packages/now-node/test/fixtures/17-entrypoint-name-conflict/helpers.js
vendored
Normal file
3
packages/now-node/test/fixtures/17-entrypoint-name-conflict/helpers.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = (req, res) => {
|
||||||
|
res.end('helpers:RANDOMNESS_PLACEHOLDER');
|
||||||
|
};
|
||||||
3
packages/now-node/test/fixtures/17-entrypoint-name-conflict/launcher.js
vendored
Normal file
3
packages/now-node/test/fixtures/17-entrypoint-name-conflict/launcher.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = (req, res) => {
|
||||||
|
res.end('launcher:RANDOMNESS_PLACEHOLDER');
|
||||||
|
};
|
||||||
18
packages/now-node/test/fixtures/17-entrypoint-name-conflict/now.json
vendored
Normal file
18
packages/now-node/test/fixtures/17-entrypoint-name-conflict/now.json
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"builds": [{ "src": "*.js", "use": "@now/node" }],
|
||||||
|
"probes": [
|
||||||
|
{
|
||||||
|
"path": "/helpers.js",
|
||||||
|
"mustContain": "helpers:RANDOMNESS_PLACEHOLDER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/bridge.js",
|
||||||
|
"mustContain": "bridge:RANDOMNESS_PLACEHOLDER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/launcher.js",
|
||||||
|
"mustContain": "launcher:RANDOMNESS_PLACEHOLDER"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,28 +1,17 @@
|
|||||||
/* global beforeAll, beforeEach, afterAll, expect, it, jest */
|
/* global beforeEach, afterEach, expect, it, jest */
|
||||||
const fetch = require('node-fetch');
|
const fetch = require('node-fetch');
|
||||||
const listen = require('test-listen');
|
const listen = require('test-listen');
|
||||||
const qs = require('querystring');
|
const qs = require('querystring');
|
||||||
|
|
||||||
const { createServerWithHelpers } = require('../dist/helpers');
|
const { createServerWithHelpers } = require('../dist/helpers');
|
||||||
|
|
||||||
const mockListener = jest.fn((req, res) => {
|
const mockListener = jest.fn();
|
||||||
res.send('hello');
|
const consumeEventMock = jest.fn();
|
||||||
});
|
|
||||||
const consumeEventMock = jest.fn(() => ({}));
|
|
||||||
const mockBridge = { consumeEvent: consumeEventMock };
|
const mockBridge = { consumeEvent: consumeEventMock };
|
||||||
|
|
||||||
let server;
|
let server;
|
||||||
let url;
|
let url;
|
||||||
|
|
||||||
const nowProps = [
|
|
||||||
['query', 0],
|
|
||||||
['cookies', 0],
|
|
||||||
['body', 0],
|
|
||||||
['status', 1],
|
|
||||||
['send', 1],
|
|
||||||
['json', 1],
|
|
||||||
];
|
|
||||||
|
|
||||||
async function fetchWithProxyReq(_url, opts = {}) {
|
async function fetchWithProxyReq(_url, opts = {}) {
|
||||||
if (opts.body) {
|
if (opts.body) {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
@@ -37,51 +26,131 @@ async function fetchWithProxyReq(_url, opts = {}) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeEach(async () => {
|
||||||
|
mockListener.mockClear();
|
||||||
|
consumeEventMock.mockClear();
|
||||||
|
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send('hello');
|
||||||
|
});
|
||||||
|
consumeEventMock.mockImplementation(() => ({}));
|
||||||
|
|
||||||
server = createServerWithHelpers(mockListener, mockBridge);
|
server = createServerWithHelpers(mockListener, mockBridge);
|
||||||
url = await listen(server);
|
url = await listen(server);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
afterEach(async () => {
|
||||||
mockListener.mockClear();
|
|
||||||
consumeEventMock.mockClear();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await server.close();
|
await server.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call consumeEvent with the correct reqId', async () => {
|
describe('contract with @now/node-bridge', () => {
|
||||||
|
test('should call consumeEvent with the correct reqId', async () => {
|
||||||
await fetchWithProxyReq(`${url}/`);
|
await fetchWithProxyReq(`${url}/`);
|
||||||
|
|
||||||
expect(consumeEventMock).toHaveBeenLastCalledWith('2');
|
expect(consumeEventMock).toHaveBeenLastCalledWith('2');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not expose the request id header', async () => {
|
test('should not expose the request id header', async () => {
|
||||||
await fetchWithProxyReq(`${url}/`, { headers: { 'x-test-header': 'ok' } });
|
await fetchWithProxyReq(`${url}/`, { headers: { 'x-test-header': 'ok' } });
|
||||||
|
|
||||||
const [{ headers }] = mockListener.mock.calls[0];
|
const [{ headers }] = mockListener.mock.calls[0];
|
||||||
|
|
||||||
expect(headers['x-now-bridge-request-id']).toBeUndefined();
|
expect(headers['x-now-bridge-request-id']).toBeUndefined();
|
||||||
expect(headers['x-test-header']).toBe('ok');
|
expect(headers['x-test-header']).toBe('ok');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('req.query should reflect querystring in the url', async () => {
|
describe('all helpers', () => {
|
||||||
|
const nowHelpers = [
|
||||||
|
['query', 0],
|
||||||
|
['cookies', 0],
|
||||||
|
['body', 0],
|
||||||
|
['status', 1],
|
||||||
|
['send', 1],
|
||||||
|
['json', 1],
|
||||||
|
];
|
||||||
|
|
||||||
|
test('should not recalculate req properties twice', async () => {
|
||||||
|
const spy = jest.fn(() => {});
|
||||||
|
|
||||||
|
const nowReqHelpers = nowHelpers.filter(([, i]) => i === 0);
|
||||||
|
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
spy(...nowReqHelpers.map(h => req[h]));
|
||||||
|
spy(...nowReqHelpers.map(h => req[h]));
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
await fetchWithProxyReq(`${url}/?who=bill`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ who: 'mike' }),
|
||||||
|
headers: { 'content-type': 'application/json', cookie: 'who=jim' },
|
||||||
|
});
|
||||||
|
|
||||||
|
// here we test that bodySpy is called twice with exactly the same arguments
|
||||||
|
for (let i = 0; i < 3; i += 1) {
|
||||||
|
expect(spy.mock.calls[0][i]).toBe(spy.mock.calls[1][i]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should be able to overwrite request properties', async () => {
|
||||||
|
const spy = jest.fn(() => {});
|
||||||
|
|
||||||
|
mockListener.mockImplementation((...args) => {
|
||||||
|
nowHelpers.forEach(([prop, n]) => {
|
||||||
|
/* eslint-disable */
|
||||||
|
args[n][prop] = 'ok';
|
||||||
|
args[n][prop] = 'ok2';
|
||||||
|
spy(args[n][prop]);
|
||||||
|
});
|
||||||
|
|
||||||
|
args[1].end();
|
||||||
|
});
|
||||||
|
|
||||||
|
await fetchWithProxyReq(url);
|
||||||
|
|
||||||
|
nowHelpers.forEach((_, i) => expect(spy.mock.calls[i][0]).toBe('ok2'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should be able to reconfig request properties', async () => {
|
||||||
|
const spy = jest.fn(() => {});
|
||||||
|
|
||||||
|
mockListener.mockImplementation((...args) => {
|
||||||
|
nowHelpers.forEach(([prop, n]) => {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
Object.defineProperty(args[n], prop, { value: 'ok' });
|
||||||
|
Object.defineProperty(args[n], prop, { value: 'ok2' });
|
||||||
|
spy(args[n][prop]);
|
||||||
|
});
|
||||||
|
|
||||||
|
args[1].end();
|
||||||
|
});
|
||||||
|
|
||||||
|
await fetchWithProxyReq(url);
|
||||||
|
|
||||||
|
nowHelpers.forEach((_, i) => expect(spy.mock.calls[i][0]).toBe('ok2'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('req.query', () => {
|
||||||
|
test('req.query should reflect querystring in the url', async () => {
|
||||||
await fetchWithProxyReq(`${url}/?who=bill&where=us`);
|
await fetchWithProxyReq(`${url}/?who=bill&where=us`);
|
||||||
|
|
||||||
expect(mockListener.mock.calls[0][0].query).toMatchObject({
|
expect(mockListener.mock.calls[0][0].query).toMatchObject({
|
||||||
who: 'bill',
|
who: 'bill',
|
||||||
where: 'us',
|
where: 'us',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('req.query should be {} when there is no querystring', async () => {
|
test('req.query should be {} when there is no querystring', async () => {
|
||||||
await fetchWithProxyReq(url);
|
await fetchWithProxyReq(url);
|
||||||
const [{ query }] = mockListener.mock.calls[0];
|
const [{ query }] = mockListener.mock.calls[0];
|
||||||
expect(Object.keys(query).length).toBe(0);
|
expect(Object.keys(query).length).toBe(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('req.cookies should reflect req.cookie header', async () => {
|
describe('req.cookies', () => {
|
||||||
|
test('req.cookies should reflect req.cookie header', async () => {
|
||||||
await fetchWithProxyReq(url, {
|
await fetchWithProxyReq(url, {
|
||||||
headers: {
|
headers: {
|
||||||
cookie: 'who=bill; where=us',
|
cookie: 'who=bill; where=us',
|
||||||
@@ -92,22 +161,24 @@ it('req.cookies should reflect req.cookie header', async () => {
|
|||||||
who: 'bill',
|
who: 'bill',
|
||||||
where: 'us',
|
where: 'us',
|
||||||
});
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('req.body should be undefined by default', async () => {
|
describe('req.body', () => {
|
||||||
|
test('req.body should be undefined by default', async () => {
|
||||||
await fetchWithProxyReq(url);
|
await fetchWithProxyReq(url);
|
||||||
expect(mockListener.mock.calls[0][0].body).toBe(undefined);
|
expect(mockListener.mock.calls[0][0].body).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('req.body should be undefined if content-type is not defined', async () => {
|
test('req.body should be undefined if content-type is not defined', async () => {
|
||||||
await fetchWithProxyReq(url, {
|
await fetchWithProxyReq(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: 'hello',
|
body: 'hello',
|
||||||
});
|
});
|
||||||
expect(mockListener.mock.calls[0][0].body).toBe(undefined);
|
expect(mockListener.mock.calls[0][0].body).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('req.body should be a string when content-type is `text/plain`', async () => {
|
test('req.body should be a string when content-type is `text/plain`', async () => {
|
||||||
await fetchWithProxyReq(url, {
|
await fetchWithProxyReq(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: 'hello',
|
body: 'hello',
|
||||||
@@ -115,9 +186,9 @@ it('req.body should be a string when content-type is `text/plain`', async () =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(mockListener.mock.calls[0][0].body).toBe('hello');
|
expect(mockListener.mock.calls[0][0].body).toBe('hello');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('req.body should be a buffer when content-type is `application/octet-stream`', async () => {
|
test('req.body should be a buffer when content-type is `application/octet-stream`', async () => {
|
||||||
await fetchWithProxyReq(url, {
|
await fetchWithProxyReq(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: 'hello',
|
body: 'hello',
|
||||||
@@ -130,9 +201,9 @@ it('req.body should be a buffer when content-type is `application/octet-stream`'
|
|||||||
|
|
||||||
expect(Buffer.isBuffer(body)).toBe(true);
|
expect(Buffer.isBuffer(body)).toBe(true);
|
||||||
expect(str).toBe('hello');
|
expect(str).toBe('hello');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('req.body should be an object when content-type is `application/x-www-form-urlencoded`', async () => {
|
test('req.body should be an object when content-type is `application/x-www-form-urlencoded`', async () => {
|
||||||
const obj = { who: 'mike' };
|
const obj = { who: 'mike' };
|
||||||
|
|
||||||
await fetchWithProxyReq(url, {
|
await fetchWithProxyReq(url, {
|
||||||
@@ -142,9 +213,9 @@ it('req.body should be an object when content-type is `application/x-www-form-ur
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(mockListener.mock.calls[0][0].body).toMatchObject(obj);
|
expect(mockListener.mock.calls[0][0].body).toMatchObject(obj);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('req.body should be an object when content-type is `application/json`', async () => {
|
test('req.body should be an object when content-type is `application/json`', async () => {
|
||||||
const json = {
|
const json = {
|
||||||
who: 'bill',
|
who: 'bill',
|
||||||
where: 'us',
|
where: 'us',
|
||||||
@@ -157,9 +228,9 @@ it('req.body should be an object when content-type is `application/json`', async
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(mockListener.mock.calls[0][0].body).toMatchObject(json);
|
expect(mockListener.mock.calls[0][0].body).toMatchObject(json);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw error when body is empty and content-type is `application/json`', async () => {
|
test('should throw error when body is empty and content-type is `application/json`', async () => {
|
||||||
mockListener.mockImplementation((req, res) => {
|
mockListener.mockImplementation((req, res) => {
|
||||||
console.log(req.body);
|
console.log(req.body);
|
||||||
res.end();
|
res.end();
|
||||||
@@ -172,70 +243,9 @@ it('should throw error when body is empty and content-type is `application/json`
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(res.status).toBe(400);
|
expect(res.status).toBe(400);
|
||||||
});
|
|
||||||
|
|
||||||
it('should not recalculate req properties twice', async () => {
|
|
||||||
const bodySpy = jest.fn(() => {});
|
|
||||||
|
|
||||||
mockListener.mockImplementation((req, res) => {
|
|
||||||
bodySpy(req.body, req.query, req.cookies);
|
|
||||||
bodySpy(req.body, req.query, req.cookies);
|
|
||||||
res.end();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await fetchWithProxyReq(`${url}/?who=bill`, {
|
test('should be able to try/catch parse errors', async () => {
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({ who: 'mike' }),
|
|
||||||
headers: { 'content-type': 'application/json', cookie: 'who=jim' },
|
|
||||||
});
|
|
||||||
|
|
||||||
// here we test that bodySpy is called twice with exactly the same arguments
|
|
||||||
for (let i = 0; i < 3; i += 1) {
|
|
||||||
expect(bodySpy.mock.calls[0][i]).toBe(bodySpy.mock.calls[1][i]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to overwrite request properties', async () => {
|
|
||||||
const spy = jest.fn(() => {});
|
|
||||||
|
|
||||||
mockListener.mockImplementation((...args) => {
|
|
||||||
nowProps.forEach(([prop, n]) => {
|
|
||||||
/* eslint-disable */
|
|
||||||
args[n][prop] = 'ok';
|
|
||||||
args[n][prop] = 'ok2';
|
|
||||||
spy(args[n][prop]);
|
|
||||||
});
|
|
||||||
|
|
||||||
args[1].end();
|
|
||||||
});
|
|
||||||
|
|
||||||
await fetchWithProxyReq(url);
|
|
||||||
|
|
||||||
nowProps.forEach((_, i) => expect(spy.mock.calls[i][0]).toBe('ok2'));
|
|
||||||
});
|
|
||||||
|
|
||||||
// we test that properties are configurable
|
|
||||||
// because expressjs (or some other api frameworks) needs that to work
|
|
||||||
it('should be able to reconfig request properties', async () => {
|
|
||||||
const spy = jest.fn(() => {});
|
|
||||||
|
|
||||||
mockListener.mockImplementation((...args) => {
|
|
||||||
nowProps.forEach(([prop, n]) => {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
Object.defineProperty(args[n], prop, { value: 'ok' });
|
|
||||||
Object.defineProperty(args[n], prop, { value: 'ok2' });
|
|
||||||
spy(args[n][prop]);
|
|
||||||
});
|
|
||||||
|
|
||||||
args[1].end();
|
|
||||||
});
|
|
||||||
|
|
||||||
await fetchWithProxyReq(url);
|
|
||||||
|
|
||||||
nowProps.forEach((_, i) => expect(spy.mock.calls[i][0]).toBe('ok2'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to try/catch parse errors', async () => {
|
|
||||||
const bodySpy = jest.fn(() => {});
|
const bodySpy = jest.fn(() => {});
|
||||||
|
|
||||||
mockListener.mockImplementation((req, res) => {
|
mockListener.mockImplementation((req, res) => {
|
||||||
@@ -259,31 +269,11 @@ it('should be able to try/catch parse errors', async () => {
|
|||||||
const [error] = bodySpy.mock.calls[0];
|
const [error] = bodySpy.mock.calls[0];
|
||||||
expect(error.message).toMatch(/invalid json/i);
|
expect(error.message).toMatch(/invalid json/i);
|
||||||
expect(error.statusCode).toBe(400);
|
expect(error.statusCode).toBe(400);
|
||||||
});
|
|
||||||
|
|
||||||
it('res.send() should send text', async () => {
|
|
||||||
mockListener.mockImplementation((req, res) => {
|
|
||||||
res.send('hello world');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = await fetchWithProxyReq(url);
|
|
||||||
|
|
||||||
expect(await res.text()).toBe('hello world');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('res.json() should send json', async () => {
|
describe('res.status', () => {
|
||||||
mockListener.mockImplementation((req, res) => {
|
test('res.status() should set the status code', async () => {
|
||||||
res.json({ who: 'bill' });
|
|
||||||
});
|
|
||||||
|
|
||||||
const res = await fetchWithProxyReq(url);
|
|
||||||
const contentType = res.headers.get('content-type') || '';
|
|
||||||
|
|
||||||
expect(contentType.includes('application/json')).toBe(true);
|
|
||||||
expect(await res.json()).toMatchObject({ who: 'bill' });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('res.status() should set the status code', async () => {
|
|
||||||
mockListener.mockImplementation((req, res) => {
|
mockListener.mockImplementation((req, res) => {
|
||||||
res.status(404);
|
res.status(404);
|
||||||
res.end();
|
res.end();
|
||||||
@@ -292,26 +282,499 @@ it('res.status() should set the status code', async () => {
|
|||||||
const res = await fetchWithProxyReq(url);
|
const res = await fetchWithProxyReq(url);
|
||||||
|
|
||||||
expect(res.status).toBe(404);
|
expect(res.status).toBe(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('res.status() should be chainable', async () => {
|
||||||
|
const spy = jest.fn();
|
||||||
|
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
spy(res, res.status(404));
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
await fetchWithProxyReq(url);
|
||||||
|
|
||||||
|
const [a, b] = spy.mock.calls[0];
|
||||||
|
expect(a).toBe(b);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('res.status().send() should work', async () => {
|
// tests based on expressjs test suite
|
||||||
|
// see https://github.com/expressjs/express/blob/master/test/res.send.js
|
||||||
|
describe('res.send', () => {
|
||||||
|
test('should be chainable', async () => {
|
||||||
|
const spy = jest.fn();
|
||||||
|
|
||||||
mockListener.mockImplementation((req, res) => {
|
mockListener.mockImplementation((req, res) => {
|
||||||
res.status(404).send('notfound');
|
spy(res, res.send('hello'));
|
||||||
|
});
|
||||||
|
|
||||||
|
await fetchWithProxyReq(url);
|
||||||
|
|
||||||
|
const [a, b] = spy.mock.calls[0];
|
||||||
|
expect(a).toBe(b);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('res.send()', () => {
|
||||||
|
test('should set body to ""', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send();
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = await fetchWithProxyReq(url);
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(await res.text()).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
expect(res.status).toBe(404);
|
describe('.send(null)', () => {
|
||||||
expect(await res.text()).toBe('notfound');
|
test('should set body to ""', async () => {
|
||||||
});
|
|
||||||
|
|
||||||
it('res.status().json() should work', async () => {
|
|
||||||
mockListener.mockImplementation((req, res) => {
|
mockListener.mockImplementation((req, res) => {
|
||||||
res.status(404).json({ error: 'not found' });
|
res.send(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = await fetchWithProxyReq(url);
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('content-length')).toBe('0');
|
||||||
|
expect(await res.text()).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
expect(res.status).toBe(404);
|
describe('.send(undefined)', () => {
|
||||||
expect(await res.json()).toMatchObject({ error: 'not found' });
|
test('should set body to ""', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(await res.text()).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('.send(String)', () => {
|
||||||
|
test('should send as html', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send('<p>hey</p>');
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.headers.get('content-type')).toBe('text/html; charset=utf-8');
|
||||||
|
expect(await res.text()).toBe('<p>hey</p>');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should set Content-Length', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send('½ + ¼ = ¾');
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(Number(res.headers.get('content-length'))).toBe(12);
|
||||||
|
expect(await res.text()).toBe('½ + ¼ = ¾');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should set ETag', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send(Array(1000).join('-'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('ETag')).toBe(
|
||||||
|
'W/"3e7-qPnkJ3CVdVhFJQvUBfF10TmVA7g"'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not override Content-Type', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.setHeader('Content-Type', 'text/plain');
|
||||||
|
res.send('hey');
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('Content-Type')).toBe('text/plain; charset=utf-8');
|
||||||
|
expect(await res.text()).toBe('hey');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should override charset in Content-Type', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.setHeader('Content-Type', 'text/plain; charset=iso-8859-1');
|
||||||
|
res.send('hey');
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('Content-Type')).toBe('text/plain; charset=utf-8');
|
||||||
|
expect(await res.text()).toBe('hey');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('.send(Buffer)', () => {
|
||||||
|
test('should keep charset in Content-Type', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.setHeader('Content-Type', 'text/plain; charset=iso-8859-1');
|
||||||
|
res.send(Buffer.from('hi'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('Content-Type')).toBe(
|
||||||
|
'text/plain; charset=iso-8859-1'
|
||||||
|
);
|
||||||
|
expect(await res.text()).toBe('hi');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should set Content-Length', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send(Buffer.from('½ + ¼ = ¾'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(Number(res.headers.get('content-length'))).toBe(12);
|
||||||
|
expect(await res.text()).toBe('½ + ¼ = ¾');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should send as octet-stream', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send(Buffer.from('hello'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('Content-Type')).toBe('application/octet-stream');
|
||||||
|
expect((await res.buffer()).toString('hex')).toBe(
|
||||||
|
Buffer.from('hello').toString('hex')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should set ETag', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send(Buffer.alloc(999, '-'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('ETag')).toBe(
|
||||||
|
'W/"3e7-qPnkJ3CVdVhFJQvUBfF10TmVA7g"'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not override Content-Type', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
||||||
|
res.send(Buffer.from('hey'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('Content-Type')).toBe('text/plain; charset=utf-8');
|
||||||
|
expect(await res.text()).toBe('hey');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not override ETag', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.setHeader('ETag', '"foo"');
|
||||||
|
res.send(Buffer.from('hey'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('ETag')).toBe('"foo"');
|
||||||
|
expect(await res.text()).toBe('hey');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('.send(Object)', () => {
|
||||||
|
test('should send as application/json', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send({ name: 'tobi' });
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('Content-Type')).toBe(
|
||||||
|
'application/json; charset=utf-8'
|
||||||
|
);
|
||||||
|
expect(await res.text()).toBe('{"name":"tobi"}');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the request method is HEAD', () => {
|
||||||
|
test('should ignore the body', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send('yay');
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: fix this test
|
||||||
|
// node-fetch is automatically ignoring the body so this test will never fail
|
||||||
|
const res = await fetchWithProxyReq(url, { method: 'HEAD' });
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect((await res.buffer()).toString()).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when .statusCode is 204', () => {
|
||||||
|
test('should strip Content-* fields, Transfer-Encoding field, and body', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.statusCode = 204;
|
||||||
|
res.setHeader('Transfer-Encoding', 'chunked');
|
||||||
|
res.send('foo');
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(204);
|
||||||
|
expect(res.headers.get('Content-Type')).toBe(null);
|
||||||
|
expect(res.headers.get('Content-Length')).toBe(null);
|
||||||
|
expect(res.headers.get('Transfer-Encoding')).toBe(null);
|
||||||
|
expect(await res.text()).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when .statusCode is 304', () => {
|
||||||
|
test('should strip Content-* fields, Transfer-Encoding field, and body', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.statusCode = 304;
|
||||||
|
res.setHeader('Transfer-Encoding', 'chunked');
|
||||||
|
res.send('foo');
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(304);
|
||||||
|
expect(res.headers.get('Content-Type')).toBe(null);
|
||||||
|
expect(res.headers.get('Content-Length')).toBe(null);
|
||||||
|
expect(res.headers.get('Transfer-Encoding')).toBe(null);
|
||||||
|
expect(await res.text()).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// test('should always check regardless of length', async () => {
|
||||||
|
// const etag = '"asdf"';
|
||||||
|
|
||||||
|
// mockListener.mockImplementation((req, res) => {
|
||||||
|
// res.setHeader('ETag', etag);
|
||||||
|
// res.send('hey');
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const res = await fetchWithProxyReq(url, {
|
||||||
|
// headers: { 'If-None-Match': etag },
|
||||||
|
// });
|
||||||
|
// expect(res.status).toBe(304);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// test('should respond with 304 Not Modified when fresh', async () => {
|
||||||
|
// const etag = '"asdf"';
|
||||||
|
|
||||||
|
// mockListener.mockImplementation((req, res) => {
|
||||||
|
// res.setHeader('ETag', etag);
|
||||||
|
// res.send(Array(1000).join('-'));
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const res = await fetchWithProxyReq(url, {
|
||||||
|
// headers: { 'If-None-Match': etag },
|
||||||
|
// });
|
||||||
|
// expect(res.status).toBe(304);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// test('should not perform freshness check unless 2xx or 304', async () => {
|
||||||
|
// const etag = '"asdf"';
|
||||||
|
|
||||||
|
// mockListener.mockImplementation((req, res) => {
|
||||||
|
// res.status(500);
|
||||||
|
// res.setHeader('ETag', etag);
|
||||||
|
// res.send('hey');
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const res = await fetchWithProxyReq(url, {
|
||||||
|
// headers: { 'If-None-Match': etag },
|
||||||
|
// });
|
||||||
|
// expect(res.status).toBe(500);
|
||||||
|
// expect(await res.text()).toBe('hey');
|
||||||
|
// });
|
||||||
|
|
||||||
|
describe('etag', () => {
|
||||||
|
test('should send ETag', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send('kajdslfkasdf');
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('ETag')).toBe('W/"c-IgR/L5SF7CJQff4wxKGF/vfPuZ0"');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should send ETag for empty string response', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send('');
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('ETag')).toBe('W/"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should send ETag for long response', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send(Array(1000).join('-'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('ETag')).toBe(
|
||||||
|
'W/"3e7-qPnkJ3CVdVhFJQvUBfF10TmVA7g"'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not override ETag when manually set', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.setHeader('etag', '"asdf"');
|
||||||
|
res.send('hello');
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('ETag')).toBe('"asdf"');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not send ETag for res.send()', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.send();
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('ETag')).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// tests based on expressjs test suite
|
||||||
|
// see https://github.com/expressjs/express/blob/master/test/res.json.js
|
||||||
|
describe('res.json', () => {
|
||||||
|
test('should send be chainable', async () => {
|
||||||
|
const spy = jest.fn();
|
||||||
|
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
spy(res, res.json({ hello: 'world' }));
|
||||||
|
});
|
||||||
|
|
||||||
|
await fetchWithProxyReq(url);
|
||||||
|
|
||||||
|
const [a, b] = spy.mock.calls[0];
|
||||||
|
expect(a).toBe(b);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('res.json() should send an empty body', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.json();
|
||||||
|
});
|
||||||
|
|
||||||
|
await fetchWithProxyReq(url);
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('content-type')).toBe(
|
||||||
|
'application/json; charset=utf-8'
|
||||||
|
);
|
||||||
|
expect(await res.text()).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('.json(object)', () => {
|
||||||
|
test('should not override previous Content-Types', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.setHeader('content-type', 'application/vnd.example+json');
|
||||||
|
res.json({ hello: 'world' });
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('content-type')).toBe(
|
||||||
|
'application/vnd.example+json; charset=utf-8'
|
||||||
|
);
|
||||||
|
expect(await res.text()).toBe('{"hello":"world"}');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should set Content-Length and Content-Type', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.json({ hello: '½ + ¼ = ¾' });
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('content-type')).toBe(
|
||||||
|
'application/json; charset=utf-8'
|
||||||
|
);
|
||||||
|
expect(Number(res.headers.get('content-length'))).toBe(24);
|
||||||
|
expect(await res.text()).toBe('{"hello":"½ + ¼ = ¾"}');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when given primitives', () => {
|
||||||
|
test('should respond with json for null', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.json(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('content-type')).toBe(
|
||||||
|
'application/json; charset=utf-8'
|
||||||
|
);
|
||||||
|
expect(await res.text()).toBe('null');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should respond with json for Number', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.json(300);
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('content-type')).toBe(
|
||||||
|
'application/json; charset=utf-8'
|
||||||
|
);
|
||||||
|
expect(await res.text()).toBe('300');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should respond with json for String', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.json('str');
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('content-type')).toBe(
|
||||||
|
'application/json; charset=utf-8'
|
||||||
|
);
|
||||||
|
expect(await res.text()).toBe('"str"');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should respond with json when given an array', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.json(['foo', 'bar', 'baz']);
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('content-type')).toBe(
|
||||||
|
'application/json; charset=utf-8'
|
||||||
|
);
|
||||||
|
expect(await res.text()).toBe('["foo","bar","baz"]');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should respond with json when given an object', async () => {
|
||||||
|
mockListener.mockImplementation((req, res) => {
|
||||||
|
res.json({ name: 'tobi' });
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithProxyReq(url);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.headers.get('content-type')).toBe(
|
||||||
|
'application/json; charset=utf-8'
|
||||||
|
);
|
||||||
|
expect(await res.text()).toBe('{"name":"tobi"}');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/optipng",
|
"name": "@now/optipng",
|
||||||
"version": "0.6.3",
|
"version": "0.6.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index",
|
"main": "./dist/index",
|
||||||
|
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/optipng-now-optipng",
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/php",
|
"name": "@now/php",
|
||||||
"version": "0.5.5",
|
"version": "0.5.6",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/php-now-php",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/zeit/now-builders.git",
|
"url": "https://github.com/zeit/now-builders.git",
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ from http.server import BaseHTTPRequestHandler
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
|
import inspect
|
||||||
|
|
||||||
import __NOW_HANDLER_FILENAME
|
import __NOW_HANDLER_FILENAME
|
||||||
__now_variables = dir(__NOW_HANDLER_FILENAME)
|
__now_variables = dir(__NOW_HANDLER_FILENAME)
|
||||||
|
|
||||||
|
|
||||||
if 'handler' in __now_variables or 'Handler' in __now_variables:
|
if 'handler' in __now_variables or 'Handler' in __now_variables:
|
||||||
base = __NOW_HANDLER_FILENAME.handler if ('handler' in __now_variables) else __NOW_HANDLER_FILENAME.Handler
|
base = __NOW_HANDLER_FILENAME.handler if ('handler' in __now_variables) else __NOW_HANDLER_FILENAME.Handler
|
||||||
if not issubclass(base, BaseHTTPRequestHandler):
|
if not issubclass(base, BaseHTTPRequestHandler):
|
||||||
@@ -47,6 +47,7 @@ if 'handler' in __now_variables or 'Handler' in __now_variables:
|
|||||||
'body': res.text,
|
'body': res.text,
|
||||||
}
|
}
|
||||||
elif 'app' in __now_variables:
|
elif 'app' in __now_variables:
|
||||||
|
if not inspect.iscoroutinefunction(__NOW_HANDLER_FILENAME.app.__call__):
|
||||||
print('using Web Server Gateway Interface (WSGI)')
|
print('using Web Server Gateway Interface (WSGI)')
|
||||||
import sys
|
import sys
|
||||||
from urllib.parse import urlparse, unquote
|
from urllib.parse import urlparse, unquote
|
||||||
@@ -117,6 +118,131 @@ elif 'app' in __now_variables:
|
|||||||
return_dict['encoding'] = 'base64'
|
return_dict['encoding'] = 'base64'
|
||||||
|
|
||||||
return return_dict
|
return return_dict
|
||||||
|
else:
|
||||||
|
print('using Asynchronous Server Gateway Interface (ASGI)')
|
||||||
|
import asyncio
|
||||||
|
import enum
|
||||||
|
from urllib.parse import urlparse, unquote, urlencode
|
||||||
|
|
||||||
|
|
||||||
|
class ASGICycleState(enum.Enum):
|
||||||
|
REQUEST = enum.auto()
|
||||||
|
RESPONSE = enum.auto()
|
||||||
|
|
||||||
|
|
||||||
|
class ASGICycle:
|
||||||
|
def __init__(self, scope):
|
||||||
|
self.scope = scope
|
||||||
|
self.body = b''
|
||||||
|
self.state = ASGICycleState.REQUEST
|
||||||
|
self.app_queue = None
|
||||||
|
self.response = {}
|
||||||
|
|
||||||
|
def __call__(self, app, body):
|
||||||
|
"""
|
||||||
|
Receives the application and any body included in the request, then builds the
|
||||||
|
ASGI instance using the connection scope.
|
||||||
|
Runs until the response is completely read from the application.
|
||||||
|
"""
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
self.app_queue = asyncio.Queue(loop=loop)
|
||||||
|
self.put_message({'type': 'http.request', 'body': body, 'more_body': False})
|
||||||
|
|
||||||
|
asgi_instance = app(self.scope, self.receive, self.send)
|
||||||
|
|
||||||
|
asgi_task = loop.create_task(asgi_instance)
|
||||||
|
loop.run_until_complete(asgi_task)
|
||||||
|
return self.response
|
||||||
|
|
||||||
|
def put_message(self, message):
|
||||||
|
self.app_queue.put_nowait(message)
|
||||||
|
|
||||||
|
async def receive(self):
|
||||||
|
"""
|
||||||
|
Awaited by the application to receive messages in the queue.
|
||||||
|
"""
|
||||||
|
message = await self.app_queue.get()
|
||||||
|
return message
|
||||||
|
|
||||||
|
async def send(self, message):
|
||||||
|
"""
|
||||||
|
Awaited by the application to send messages to the current cycle instance.
|
||||||
|
"""
|
||||||
|
message_type = message['type']
|
||||||
|
|
||||||
|
if self.state is ASGICycleState.REQUEST:
|
||||||
|
if message_type != 'http.response.start':
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Expected 'http.response.start', received: {message_type}"
|
||||||
|
)
|
||||||
|
|
||||||
|
status_code = message['status']
|
||||||
|
headers = {k: v for k, v in message.get('headers', [])}
|
||||||
|
|
||||||
|
self.on_request(headers, status_code)
|
||||||
|
self.state = ASGICycleState.RESPONSE
|
||||||
|
|
||||||
|
elif self.state is ASGICycleState.RESPONSE:
|
||||||
|
if message_type != 'http.response.body':
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Expected 'http.response.body', received: {message_type}"
|
||||||
|
)
|
||||||
|
|
||||||
|
body = message.get('body', b'')
|
||||||
|
more_body = message.get('more_body', False)
|
||||||
|
|
||||||
|
# The body must be completely read before returning the response.
|
||||||
|
self.body += body
|
||||||
|
|
||||||
|
if not more_body:
|
||||||
|
self.on_response()
|
||||||
|
self.put_message({'type': 'http.disconnect'})
|
||||||
|
|
||||||
|
def on_request(self, headers, status_code):
|
||||||
|
self.response['statusCode'] = status_code
|
||||||
|
self.response['headers'] = {k.decode(): v.decode() for k, v in headers.items()}
|
||||||
|
|
||||||
|
def on_response(self):
|
||||||
|
if self.body:
|
||||||
|
self.response['body'] = base64.b64encode(self.body).decode('utf-8')
|
||||||
|
self.response['encoding'] = 'base64'
|
||||||
|
|
||||||
|
def now_handler(event, context):
|
||||||
|
payload = json.loads(event['body'])
|
||||||
|
|
||||||
|
headers = payload.get('headers', {})
|
||||||
|
|
||||||
|
body = payload.get('body', b'')
|
||||||
|
if payload.get('encoding') == 'base64':
|
||||||
|
body = base64.b64decode(body)
|
||||||
|
elif not isinstance(body, bytes):
|
||||||
|
body = body.encode()
|
||||||
|
|
||||||
|
url = urlparse(unquote(payload['path']))
|
||||||
|
query = url.query.encode()
|
||||||
|
path = url.path
|
||||||
|
|
||||||
|
scope = {
|
||||||
|
'server': (headers.get('host', 'lambda'), headers.get('x-forwarded-port', 80)),
|
||||||
|
'client': (headers.get(
|
||||||
|
'x-forwarded-for', headers.get(
|
||||||
|
'x-real-ip', payload.get(
|
||||||
|
'true-client-ip', ''))), 0),
|
||||||
|
'scheme': headers.get('x-forwarded-proto', 'http'),
|
||||||
|
'root_path': '',
|
||||||
|
'query_string': query,
|
||||||
|
'headers': [[k.lower().encode(), v.encode()] for k, v in headers.items()],
|
||||||
|
'type': 'http',
|
||||||
|
'http_version': '1.1',
|
||||||
|
'method': payload['method'],
|
||||||
|
'path': path,
|
||||||
|
'raw_path': path.encode(),
|
||||||
|
}
|
||||||
|
|
||||||
|
asgi_cycle = ASGICycle(scope)
|
||||||
|
response = asgi_cycle(__NOW_HANDLER_FILENAME.app, body)
|
||||||
|
return response
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print('Missing variable `handler` or `app` in file __NOW_HANDLER_FILENAME.py')
|
print('Missing variable `handler` or `app` in file __NOW_HANDLER_FILENAME.py')
|
||||||
print('See the docs https://zeit.co/docs/v2/deployments/official-builders/python-now-python')
|
print('See the docs https://zeit.co/docs/v2/deployments/official-builders/python-now-python')
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/python",
|
"name": "@now/python",
|
||||||
"version": "0.2.8",
|
"version": "0.2.10",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/python-now-python",
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
"now_init.py"
|
"now_init.py"
|
||||||
|
|||||||
12
packages/now-python/test/fixtures/11-asgi/Pipfile
vendored
Normal file
12
packages/now-python/test/fixtures/11-asgi/Pipfile
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[[source]]
|
||||||
|
name = "pypi"
|
||||||
|
url = "https://pypi.org/simple"
|
||||||
|
verify_ssl = true
|
||||||
|
|
||||||
|
[dev-packages]
|
||||||
|
|
||||||
|
[packages]
|
||||||
|
sanic = "*"
|
||||||
|
|
||||||
|
[requires]
|
||||||
|
python_version = "3.6"
|
||||||
207
packages/now-python/test/fixtures/11-asgi/Pipfile.lock
generated
vendored
Normal file
207
packages/now-python/test/fixtures/11-asgi/Pipfile.lock
generated
vendored
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
{
|
||||||
|
"_meta": {
|
||||||
|
"hash": {
|
||||||
|
"sha256": "93dcd591e5690d3a71cb02979cbe317e83e3c03ec020867bf1554a480ef5cd8a"
|
||||||
|
},
|
||||||
|
"pipfile-spec": 6,
|
||||||
|
"requires": {
|
||||||
|
"python_version": "3.6"
|
||||||
|
},
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"name": "pypi",
|
||||||
|
"url": "https://pypi.org/simple",
|
||||||
|
"verify_ssl": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"aiofiles": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:021ea0ba314a86027c166ecc4b4c07f2d40fc0f4b3a950d1868a0f2571c2bbee",
|
||||||
|
"sha256:1e644c2573f953664368de28d2aa4c89dfd64550429d0c27c4680ccd3aa4985d"
|
||||||
|
],
|
||||||
|
"version": "==0.4.0"
|
||||||
|
},
|
||||||
|
"certifi": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939",
|
||||||
|
"sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695"
|
||||||
|
],
|
||||||
|
"version": "==2019.6.16"
|
||||||
|
},
|
||||||
|
"chardet": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||||
|
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||||
|
],
|
||||||
|
"version": "==3.0.4"
|
||||||
|
},
|
||||||
|
"h11": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:acca6a44cb52a32ab442b1779adf0875c443c689e9e028f8d831a3769f9c5208",
|
||||||
|
"sha256:f2b1ca39bfed357d1f19ac732913d5f9faa54a5062eca7d2ec3a916cfb7ae4c7"
|
||||||
|
],
|
||||||
|
"version": "==0.8.1"
|
||||||
|
},
|
||||||
|
"h2": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:c8f387e0e4878904d4978cd688a3195f6b169d49b1ffa572a3d347d7adc5e09f",
|
||||||
|
"sha256:fd07e865a3272ac6ef195d8904de92dc7b38dc28297ec39cfa22716b6d62e6eb"
|
||||||
|
],
|
||||||
|
"version": "==3.1.0"
|
||||||
|
},
|
||||||
|
"hpack": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0edd79eda27a53ba5be2dfabf3b15780928a0dff6eb0c60a3d6767720e970c89",
|
||||||
|
"sha256:8eec9c1f4bfae3408a3f30500261f7e6a65912dc138526ea054f9ad98892e9d2"
|
||||||
|
],
|
||||||
|
"version": "==3.0.0"
|
||||||
|
},
|
||||||
|
"httpcore": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:96f910b528d47b683242ec207050c7bbaa99cd1b9a07f78ea80cf61e55556b58"
|
||||||
|
],
|
||||||
|
"version": "==0.3.0"
|
||||||
|
},
|
||||||
|
"httptools": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:e00cbd7ba01ff748e494248183abc6e153f49181169d8a3d41bb49132ca01dfc"
|
||||||
|
],
|
||||||
|
"version": "==0.0.13"
|
||||||
|
},
|
||||||
|
"hyperframe": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:5187962cb16dcc078f23cb5a4b110098d546c3f41ff2d4038a9896893bbd0b40",
|
||||||
|
"sha256:a9f5c17f2cc3c719b917c4f33ed1c61bd1f8dfac4b1bd23b7c80b3400971b41f"
|
||||||
|
],
|
||||||
|
"version": "==5.2.0"
|
||||||
|
},
|
||||||
|
"idna": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
|
||||||
|
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
|
||||||
|
],
|
||||||
|
"version": "==2.8"
|
||||||
|
},
|
||||||
|
"multidict": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:024b8129695a952ebd93373e45b5d341dbb87c17ce49637b34000093f243dd4f",
|
||||||
|
"sha256:041e9442b11409be5e4fc8b6a97e4bcead758ab1e11768d1e69160bdde18acc3",
|
||||||
|
"sha256:045b4dd0e5f6121e6f314d81759abd2c257db4634260abcfe0d3f7083c4908ef",
|
||||||
|
"sha256:047c0a04e382ef8bd74b0de01407e8d8632d7d1b4db6f2561106af812a68741b",
|
||||||
|
"sha256:068167c2d7bbeebd359665ac4fff756be5ffac9cda02375b5c5a7c4777038e73",
|
||||||
|
"sha256:148ff60e0fffa2f5fad2eb25aae7bef23d8f3b8bdaf947a65cdbe84a978092bc",
|
||||||
|
"sha256:1d1c77013a259971a72ddaa83b9f42c80a93ff12df6a4723be99d858fa30bee3",
|
||||||
|
"sha256:1d48bc124a6b7a55006d97917f695effa9725d05abe8ee78fd60d6588b8344cd",
|
||||||
|
"sha256:31dfa2fc323097f8ad7acd41aa38d7c614dd1960ac6681745b6da124093dc351",
|
||||||
|
"sha256:34f82db7f80c49f38b032c5abb605c458bac997a6c3142e0d6c130be6fb2b941",
|
||||||
|
"sha256:3d5dd8e5998fb4ace04789d1d008e2bb532de501218519d70bb672c4c5a2fc5d",
|
||||||
|
"sha256:4a6ae52bd3ee41ee0f3acf4c60ceb3f44e0e3bc52ab7da1c2b2aa6703363a3d1",
|
||||||
|
"sha256:4b02a3b2a2f01d0490dd39321c74273fed0568568ea0e7ea23e02bd1fb10a10b",
|
||||||
|
"sha256:4b843f8e1dd6a3195679d9838eb4670222e8b8d01bc36c9894d6c3538316fa0a",
|
||||||
|
"sha256:5de53a28f40ef3c4fd57aeab6b590c2c663de87a5af76136ced519923d3efbb3",
|
||||||
|
"sha256:61b2b33ede821b94fa99ce0b09c9ece049c7067a33b279f343adfe35108a4ea7",
|
||||||
|
"sha256:6a3a9b0f45fd75dc05d8e93dc21b18fc1670135ec9544d1ad4acbcf6b86781d0",
|
||||||
|
"sha256:76ad8e4c69dadbb31bad17c16baee61c0d1a4a73bed2590b741b2e1a46d3edd0",
|
||||||
|
"sha256:7ba19b777dc00194d1b473180d4ca89a054dd18de27d0ee2e42a103ec9b7d014",
|
||||||
|
"sha256:7c1b7eab7a49aa96f3db1f716f0113a8a2e93c7375dd3d5d21c4941f1405c9c5",
|
||||||
|
"sha256:7fc0eee3046041387cbace9314926aa48b681202f8897f8bff3809967a049036",
|
||||||
|
"sha256:8ccd1c5fff1aa1427100ce188557fc31f1e0a383ad8ec42c559aabd4ff08802d",
|
||||||
|
"sha256:8e08dd76de80539d613654915a2f5196dbccc67448df291e69a88712ea21e24a",
|
||||||
|
"sha256:c18498c50c59263841862ea0501da9f2b3659c00db54abfbf823a80787fde8ce",
|
||||||
|
"sha256:c49db89d602c24928e68c0d510f4fcf8989d77defd01c973d6cbe27e684833b1",
|
||||||
|
"sha256:ce20044d0317649ddbb4e54dab3c1bcc7483c78c27d3f58ab3d0c7e6bc60d26a",
|
||||||
|
"sha256:d1071414dd06ca2eafa90c85a079169bfeb0e5f57fd0b45d44c092546fcd6fd9",
|
||||||
|
"sha256:d3be11ac43ab1a3e979dac80843b42226d5d3cccd3986f2e03152720a4297cd7",
|
||||||
|
"sha256:db603a1c235d110c860d5f39988ebc8218ee028f07a7cbc056ba6424372ca31b"
|
||||||
|
],
|
||||||
|
"version": "==4.5.2"
|
||||||
|
},
|
||||||
|
"requests": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4",
|
||||||
|
"sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"
|
||||||
|
],
|
||||||
|
"version": "==2.22.0"
|
||||||
|
},
|
||||||
|
"requests-async": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:8731420451383196ecf2fd96082bfc8ae5103ada90aba185888499d7784dde6f"
|
||||||
|
],
|
||||||
|
"version": "==0.5.0"
|
||||||
|
},
|
||||||
|
"rfc3986": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0344d0bd428126ce554e7ca2b61787b6a28d2bbd19fc70ed2dd85efe31176405",
|
||||||
|
"sha256:df4eba676077cefb86450c8f60121b9ae04b94f65f85b69f3f731af0516b7b18"
|
||||||
|
],
|
||||||
|
"version": "==1.3.2"
|
||||||
|
},
|
||||||
|
"sanic": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:cc64978266025afb0e7c0f8be928e2b81670c5d58ddac290d04c9d0da6ec2112",
|
||||||
|
"sha256:ebd806298782400db811ea9d63e8096e835e67f0b5dc5e66e507532984a82bb3"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==19.6.0"
|
||||||
|
},
|
||||||
|
"ujson": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:f66073e5506e91d204ab0c614a148d5aa938bdbf104751be66f8ad7a222f5f86"
|
||||||
|
],
|
||||||
|
"markers": "sys_platform != 'win32' and implementation_name == 'cpython'",
|
||||||
|
"version": "==1.35"
|
||||||
|
},
|
||||||
|
"urllib3": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1",
|
||||||
|
"sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232"
|
||||||
|
],
|
||||||
|
"version": "==1.25.3"
|
||||||
|
},
|
||||||
|
"uvloop": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0fcd894f6fc3226a962ee7ad895c4f52e3f5c3c55098e21efb17c071849a0573",
|
||||||
|
"sha256:2f31de1742c059c96cb76b91c5275b22b22b965c886ee1fced093fa27dde9e64",
|
||||||
|
"sha256:459e4649fcd5ff719523de33964aa284898e55df62761e7773d088823ccbd3e0",
|
||||||
|
"sha256:67867aafd6e0bc2c30a079603a85d83b94f23c5593b3cc08ec7e58ac18bf48e5",
|
||||||
|
"sha256:8c200457e6847f28d8bb91c5e5039d301716f5f2fce25646f5fb3fd65eda4a26",
|
||||||
|
"sha256:958906b9ca39eb158414fbb7d6b8ef1b7aee4db5c8e8e5d00fcbb69a1ce9dca7",
|
||||||
|
"sha256:ac1dca3d8f3ef52806059e81042ee397ac939e5a86c8a3cea55d6b087db66115",
|
||||||
|
"sha256:b284c22d8938866318e3b9d178142b8be316c52d16fcfe1560685a686718a021",
|
||||||
|
"sha256:c48692bf4587ce281d641087658eca275a5ad3b63c78297bbded96570ae9ce8f",
|
||||||
|
"sha256:fefc3b2b947c99737c348887db2c32e539160dcbeb7af9aa6b53db7a283538fe"
|
||||||
|
],
|
||||||
|
"markers": "sys_platform != 'win32' and implementation_name == 'cpython'",
|
||||||
|
"version": "==0.12.2"
|
||||||
|
},
|
||||||
|
"websockets": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0e2f7d6567838369af074f0ef4d0b802d19fa1fee135d864acc656ceefa33136",
|
||||||
|
"sha256:2a16dac282b2fdae75178d0ed3d5b9bc3258dabfae50196cbb30578d84b6f6a6",
|
||||||
|
"sha256:5a1fa6072405648cb5b3688e9ed3b94be683ce4a4e5723e6f5d34859dee495c1",
|
||||||
|
"sha256:5c1f55a1274df9d6a37553fef8cff2958515438c58920897675c9bc70f5a0538",
|
||||||
|
"sha256:669d1e46f165e0ad152ed8197f7edead22854a6c90419f544e0f234cc9dac6c4",
|
||||||
|
"sha256:695e34c4dbea18d09ab2c258994a8bf6a09564e762655408241f6a14592d2908",
|
||||||
|
"sha256:6b2e03d69afa8d20253455e67b64de1a82ff8612db105113cccec35d3f8429f0",
|
||||||
|
"sha256:79ca7cdda7ad4e3663ea3c43bfa8637fc5d5604c7737f19a8964781abbd1148d",
|
||||||
|
"sha256:7fd2dd9a856f72e6ed06f82facfce01d119b88457cd4b47b7ae501e8e11eba9c",
|
||||||
|
"sha256:82c0354ac39379d836719a77ee360ef865377aa6fdead87909d50248d0f05f4d",
|
||||||
|
"sha256:8f3b956d11c5b301206382726210dc1d3bee1a9ccf7aadf895aaf31f71c3716c",
|
||||||
|
"sha256:91ec98640220ae05b34b79ee88abf27f97ef7c61cf525eec57ea8fcea9f7dddb",
|
||||||
|
"sha256:952be9540d83dba815569d5cb5f31708801e0bbfc3a8c5aef1890b57ed7e58bf",
|
||||||
|
"sha256:99ac266af38ba1b1fe13975aea01ac0e14bb5f3a3200d2c69f05385768b8568e",
|
||||||
|
"sha256:9fa122e7adb24232247f8a89f2d9070bf64b7869daf93ac5e19546b409e47e96",
|
||||||
|
"sha256:a0873eadc4b8ca93e2e848d490809e0123eea154aa44ecd0109c4d0171869584",
|
||||||
|
"sha256:cb998bd4d93af46b8b49ecf5a72c0a98e5cc6d57fdca6527ba78ad89d6606484",
|
||||||
|
"sha256:e02e57346f6a68523e3c43bbdf35dde5c440318d1f827208ae455f6a2ace446d",
|
||||||
|
"sha256:e79a5a896bcee7fff24a788d72e5c69f13e61369d055f28113e71945a7eb1559",
|
||||||
|
"sha256:ee55eb6bcf23ecc975e6b47c127c201b913598f38b6a300075f84eeef2d3baff",
|
||||||
|
"sha256:f1414e6cbcea8d22843e7eafdfdfae3dd1aba41d1945f6ca66e4806c07c4f454"
|
||||||
|
],
|
||||||
|
"version": "==6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"develop": {}
|
||||||
|
}
|
||||||
8
packages/now-python/test/fixtures/11-asgi/index.py
vendored
Normal file
8
packages/now-python/test/fixtures/11-asgi/index.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from sanic import Sanic
|
||||||
|
from sanic import response
|
||||||
|
app = Sanic()
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
async def index(request):
|
||||||
|
return response.text("asgi:RANDOMNESS_PLACEHOLDER")
|
||||||
11
packages/now-python/test/fixtures/11-asgi/now.json
vendored
Normal file
11
packages/now-python/test/fixtures/11-asgi/now.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"builds": [
|
||||||
|
{
|
||||||
|
"src": "index.py",
|
||||||
|
"use": "@now/python",
|
||||||
|
"config": { "maxLambdaSize": "10mb" }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"probes": [{ "path": "/", "mustContain": "asgi:RANDOMNESS_PLACEHOLDER" }]
|
||||||
|
}
|
||||||
@@ -64,6 +64,7 @@ async function bundleInstall(
|
|||||||
env: {
|
env: {
|
||||||
BUNDLE_SILENCE_ROOT_WARNING: '1',
|
BUNDLE_SILENCE_ROOT_WARNING: '1',
|
||||||
BUNDLE_APP_CONFIG: bundleAppConfig,
|
BUNDLE_APP_CONFIG: bundleAppConfig,
|
||||||
|
BUNDLE_JOBS: '4',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { join } from 'path';
|
|||||||
import execa from 'execa';
|
import execa from 'execa';
|
||||||
import { getWriteableDirectory } from '@now/build-utils';
|
import { getWriteableDirectory } from '@now/build-utils';
|
||||||
|
|
||||||
const RUBY_VERSION = '2.5.3';
|
const RUBY_VERSION = '2.5.5';
|
||||||
|
|
||||||
async function installRuby(version: string = RUBY_VERSION) {
|
async function installRuby(version: string = RUBY_VERSION) {
|
||||||
const baseDir = await getWriteableDirectory();
|
const baseDir = await getWriteableDirectory();
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/ruby",
|
"name": "@now/ruby",
|
||||||
"author": "Nathan Cahill <nathan@nathancahill.com>",
|
"author": "Nathan Cahill <nathan@nathancahill.com>",
|
||||||
"version": "0.1.0",
|
"version": "0.1.2",
|
||||||
|
"license": "MIT",
|
||||||
"main": "./dist/index",
|
"main": "./dist/index",
|
||||||
|
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/ruby-now-ruby",
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
"now_init.rb"
|
"now_init.rb"
|
||||||
@@ -12,7 +14,6 @@
|
|||||||
"url": "https://github.com/zeit/now-builders.git",
|
"url": "https://github.com/zeit/now-builders.git",
|
||||||
"directory": "packages/now-ruby"
|
"directory": "packages/now-ruby"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"test": "tsc && jest",
|
"test": "tsc && jest",
|
||||||
@@ -20,7 +21,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"execa": "^1.0.0",
|
"execa": "^1.0.0",
|
||||||
"fs-extra": "^7.0.1",
|
"fs-extra": "^7.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
"typescript": "3.5.2"
|
"typescript": "3.5.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/rust",
|
"name": "@now/rust",
|
||||||
"version": "0.2.7",
|
"version": "0.2.8",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index",
|
"main": "./dist/index",
|
||||||
|
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/now-rust",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/zeit/now-builders.git",
|
"url": "https://github.com/zeit/now-builders.git",
|
||||||
|
|||||||
1
packages/now-static-build/.gitignore
vendored
Normal file
1
packages/now-static-build/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
dist/
|
||||||
@@ -1 +0,0 @@
|
|||||||
/test
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
const path = require('path');
|
|
||||||
const spawn = require('cross-spawn');
|
|
||||||
const getPort = require('get-port');
|
|
||||||
const { timeout } = require('promise-timeout');
|
|
||||||
const {
|
|
||||||
existsSync, readFileSync, statSync, readdirSync,
|
|
||||||
} = require('fs');
|
|
||||||
const {
|
|
||||||
glob,
|
|
||||||
download,
|
|
||||||
runNpmInstall,
|
|
||||||
runPackageJsonScript,
|
|
||||||
runShellScript,
|
|
||||||
getNodeVersion,
|
|
||||||
getSpawnOptions,
|
|
||||||
} = require('@now/build-utils'); // eslint-disable-line import/no-extraneous-dependencies
|
|
||||||
|
|
||||||
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 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.version = 2;
|
|
||||||
|
|
||||||
const nowDevScriptPorts = new Map();
|
|
||||||
|
|
||||||
exports.build = async ({
|
|
||||||
files, entrypoint, workPath, config, meta = {},
|
|
||||||
}) => {
|
|
||||||
console.log('downloading user files...');
|
|
||||||
await download(files, workPath, meta);
|
|
||||||
|
|
||||||
const mountpoint = path.dirname(entrypoint);
|
|
||||||
const entrypointFsDirname = path.join(workPath, mountpoint);
|
|
||||||
const nodeVersion = await getNodeVersion(entrypointFsDirname);
|
|
||||||
const spawnOpts = getSpawnOptions(meta, nodeVersion);
|
|
||||||
const distPath = path.join(
|
|
||||||
workPath,
|
|
||||||
path.dirname(entrypoint),
|
|
||||||
(config && config.distDir) || 'dist',
|
|
||||||
);
|
|
||||||
|
|
||||||
const entrypointName = path.basename(entrypoint);
|
|
||||||
if (entrypointName === 'package.json') {
|
|
||||||
await runNpmInstall(entrypointFsDirname, ['--prefer-offline'], spawnOpts);
|
|
||||||
|
|
||||||
const pkgPath = path.join(workPath, entrypoint);
|
|
||||||
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
||||||
|
|
||||||
let output = {};
|
|
||||||
const routes = [];
|
|
||||||
|
|
||||||
if (meta.isDev && pkg.scripts && pkg.scripts['now-dev']) {
|
|
||||||
let devPort = nowDevScriptPorts.get(entrypoint);
|
|
||||||
if (typeof devPort === 'number') {
|
|
||||||
console.log('`now-dev` server already running for %j', entrypoint);
|
|
||||||
} else {
|
|
||||||
// Run the `now-dev` script out-of-bounds, since it is assumed that
|
|
||||||
// it will launch a dev server that never "completes"
|
|
||||||
devPort = await getPort();
|
|
||||||
nowDevScriptPorts.set(entrypoint, devPort);
|
|
||||||
const opts = {
|
|
||||||
cwd: entrypointFsDirname,
|
|
||||||
env: { ...process.env, PORT: String(devPort) },
|
|
||||||
};
|
|
||||||
const child = spawn('npm', ['run', 'now-dev'], opts);
|
|
||||||
child.on('exit', () => nowDevScriptPorts.delete(entrypoint));
|
|
||||||
child.stdout.setEncoding('utf8');
|
|
||||||
child.stdout.pipe(process.stdout);
|
|
||||||
child.stderr.setEncoding('utf8');
|
|
||||||
child.stderr.pipe(process.stderr);
|
|
||||||
|
|
||||||
// Now wait for the server to have listened on `$PORT`, after which we
|
|
||||||
// will ProxyPass any requests to that development server that come in
|
|
||||||
// for this builder.
|
|
||||||
try {
|
|
||||||
await timeout(
|
|
||||||
new Promise((resolve) => {
|
|
||||||
const checkForPort = (data) => {
|
|
||||||
// Check the logs for the URL being printed with the port number
|
|
||||||
// (i.e. `http://localhost:47521`).
|
|
||||||
if (data.indexOf(`:${devPort}`) !== -1) {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
child.stdout.on('data', checkForPort);
|
|
||||||
child.stderr.on('data', checkForPort);
|
|
||||||
}),
|
|
||||||
5 * 60 * 1000,
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
throw new Error(
|
|
||||||
`Failed to detect a server running on port ${devPort}.\nDetails: https://err.sh/zeit/now-builders/now-static-build-failed-to-detect-a-server`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Detected dev server for %j', entrypoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
let srcBase = mountpoint.replace(/^\.\/?/, '');
|
|
||||||
if (srcBase.length > 0) {
|
|
||||||
srcBase = `/${srcBase}`;
|
|
||||||
}
|
|
||||||
routes.push({
|
|
||||||
src: `${srcBase}/(.*)`,
|
|
||||||
dest: `http://localhost:${devPort}/$1`,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (meta.isDev) {
|
|
||||||
console.log('WARN: "now-dev" script is missing from package.json');
|
|
||||||
console.log(
|
|
||||||
'See the local development docs: https://zeit.co/docs/v2/deployments/official-builders/static-build-now-static-build/#local-development',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Run the `now-build` script and wait for completion to collect the build
|
|
||||||
// outputs
|
|
||||||
console.log('running user "now-build" script from `package.json`...');
|
|
||||||
if (
|
|
||||||
!(await runPackageJsonScript(
|
|
||||||
entrypointFsDirname,
|
|
||||||
'now-build',
|
|
||||||
spawnOpts,
|
|
||||||
))
|
|
||||||
) {
|
|
||||||
throw new Error(
|
|
||||||
`An error running "now-build" script in "${entrypoint}"`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
validateDistDir(distPath);
|
|
||||||
output = await glob('**', distPath, mountpoint);
|
|
||||||
}
|
|
||||||
const watch = [path.join(mountpoint.replace(/^\.\/?/, ''), '**/*')];
|
|
||||||
return { routes, watch, output };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path.extname(entrypoint) === '.sh') {
|
|
||||||
await runShellScript(path.join(workPath, entrypoint));
|
|
||||||
validateDistDir(distPath);
|
|
||||||
return glob('**', distPath, mountpoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error('Proper build script must be specified as entrypoint');
|
|
||||||
};
|
|
||||||
@@ -1,18 +1,29 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/static-build",
|
"name": "@now/static-build",
|
||||||
"version": "0.6.0",
|
"version": "0.7.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"main": "./dist/index",
|
||||||
|
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/static-build-now-static-build",
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/zeit/now-builders.git",
|
"url": "https://github.com/zeit/now-builders.git",
|
||||||
"directory": "packages/now-static-build"
|
"directory": "packages/now-static-build"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "jest"
|
"build": "tsc",
|
||||||
|
"test": "tsc && jest",
|
||||||
|
"prepublishOnly": "tsc"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cross-spawn": "6.0.5",
|
"cross-spawn": "6.0.5",
|
||||||
"get-port": "5.0.0",
|
"get-port": "5.0.0",
|
||||||
"promise-timeout": "1.3.0"
|
"promise-timeout": "1.3.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/promise-timeout": "1.3.0",
|
||||||
|
"typescript": "3.5.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
187
packages/now-static-build/src/frameworks.ts
Normal file
187
packages/now-static-build/src/frameworks.ts
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
import { readdir } from 'fs';
|
||||||
|
import { promisify } from 'util';
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
|
const readirPromise = promisify(readdir);
|
||||||
|
|
||||||
|
// Please note that is extremely important
|
||||||
|
// that the `dependency` property needs
|
||||||
|
// to reference a CLI. This is needed because
|
||||||
|
// you might want (for example) a Gatsby
|
||||||
|
// site that is powered by Preact, so you
|
||||||
|
// can't look for the `preact` dependency.
|
||||||
|
// Instead, you need to look for `preact-cli`
|
||||||
|
// when optimizing Preact CLI projects.
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
name: 'Gatsby.js',
|
||||||
|
dependency: 'gatsby',
|
||||||
|
getOutputDirName: async () => 'public',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Hexo',
|
||||||
|
dependency: 'hexo',
|
||||||
|
getOutputDirName: async () => 'public',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Preact',
|
||||||
|
dependency: 'preact-cli',
|
||||||
|
getOutputDirName: async () => 'build',
|
||||||
|
defaultRoutes: [
|
||||||
|
{
|
||||||
|
handle: 'filesystem',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: '/(.*)',
|
||||||
|
dest: '/index.html',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Vue.js',
|
||||||
|
dependency: '@vue/cli-service',
|
||||||
|
getOutputDirName: async () => 'dist',
|
||||||
|
defaultRoutes: [
|
||||||
|
{
|
||||||
|
handle: 'filesystem',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: '^/js/(.*)',
|
||||||
|
headers: { 'cache-control': 'max-age=31536000, immutable' },
|
||||||
|
dest: '/js/$1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: '^/css/(.*)',
|
||||||
|
headers: { 'cache-control': 'max-age=31536000, immutable' },
|
||||||
|
dest: '/css/$1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: '^/img/(.*)',
|
||||||
|
headers: { 'cache-control': 'max-age=31536000, immutable' },
|
||||||
|
dest: '/img/$1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: '/(.*)',
|
||||||
|
dest: '/index.html',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Angular',
|
||||||
|
dependency: '@angular/cli',
|
||||||
|
minNodeRange: '10.x',
|
||||||
|
getOutputDirName: async (dirPrefix: string) => {
|
||||||
|
const base = 'dist';
|
||||||
|
const location = join(dirPrefix, base);
|
||||||
|
const content = await readirPromise(location);
|
||||||
|
|
||||||
|
return join(base, content[0]);
|
||||||
|
},
|
||||||
|
defaultRoutes: [
|
||||||
|
{
|
||||||
|
handle: 'filesystem',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: '/(.*)',
|
||||||
|
dest: '/index.html',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Polymer',
|
||||||
|
dependency: 'polymer-cli',
|
||||||
|
getOutputDirName: async (dirPrefix: string) => {
|
||||||
|
const base = 'build';
|
||||||
|
const location = join(dirPrefix, base);
|
||||||
|
const content = await readirPromise(location);
|
||||||
|
const paths = content.filter(item => !item.includes('.'));
|
||||||
|
|
||||||
|
return join(base, paths[0]);
|
||||||
|
},
|
||||||
|
defaultRoutes: [
|
||||||
|
{
|
||||||
|
handle: 'filesystem',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: '/(.*)',
|
||||||
|
dest: '/index.html',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Svelte',
|
||||||
|
dependency: 'sirv-cli',
|
||||||
|
getOutputDirName: async () => 'public',
|
||||||
|
defaultRoutes: [
|
||||||
|
{
|
||||||
|
handle: 'filesystem',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: '/(.*)',
|
||||||
|
dest: '/index.html',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Create React App',
|
||||||
|
dependency: 'react-scripts',
|
||||||
|
getOutputDirName: async () => 'build',
|
||||||
|
defaultRoutes: [
|
||||||
|
{
|
||||||
|
src: '/static/(.*)',
|
||||||
|
headers: { 'cache-control': 's-maxage=31536000, immutable' },
|
||||||
|
dest: '/static/$1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: '/favicon.ico',
|
||||||
|
dest: '/favicon.ico',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: '/asset-manifest.json',
|
||||||
|
dest: '/asset-manifest.json',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: '/manifest.json',
|
||||||
|
dest: '/manifest.json',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: '/precache-manifest.(.*)',
|
||||||
|
dest: '/precache-manifest.$1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: '/service-worker.js',
|
||||||
|
headers: { 'cache-control': 's-maxage=0' },
|
||||||
|
dest: '/service-worker.js',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: '/sockjs-node/(.*)',
|
||||||
|
dest: '/sockjs-node/$1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: '/(.*)',
|
||||||
|
headers: { 'cache-control': 's-maxage=0' },
|
||||||
|
dest: '/index.html',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Gridsome',
|
||||||
|
dependency: 'gridsome',
|
||||||
|
getOutputDirName: async () => 'dist',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'UmiJS',
|
||||||
|
dependency: 'umi',
|
||||||
|
getOutputDirName: async () => 'dist',
|
||||||
|
defaultRoutes: [
|
||||||
|
{
|
||||||
|
handle: 'filesystem',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: '/(.*)',
|
||||||
|
dest: '/index.html',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
308
packages/now-static-build/src/index.ts
Normal file
308
packages/now-static-build/src/index.ts
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import spawn from 'cross-spawn';
|
||||||
|
import getPort from 'get-port';
|
||||||
|
import { timeout } from 'promise-timeout';
|
||||||
|
import { existsSync, readFileSync, statSync, readdirSync } from 'fs';
|
||||||
|
import frameworks from './frameworks';
|
||||||
|
import {
|
||||||
|
glob,
|
||||||
|
download,
|
||||||
|
runNpmInstall,
|
||||||
|
runPackageJsonScript,
|
||||||
|
runShellScript,
|
||||||
|
getNodeVersion,
|
||||||
|
getSpawnOptions,
|
||||||
|
Files,
|
||||||
|
Route,
|
||||||
|
BuildOptions,
|
||||||
|
Config,
|
||||||
|
} from '@now/build-utils';
|
||||||
|
|
||||||
|
interface PackageJson {
|
||||||
|
scripts?: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
dependencies?: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
devDependencies?: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Framework {
|
||||||
|
name: string;
|
||||||
|
dependency: string;
|
||||||
|
getOutputDirName: (dirPrefix: string) => Promise<string>;
|
||||||
|
defaultRoutes?: Route[];
|
||||||
|
minNodeRange?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateDistDir(distDir: string, isDev: boolean | undefined) {
|
||||||
|
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 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCommand(pkg: PackageJson, cmd: string, config: Config) {
|
||||||
|
// The `dev` script can be `now dev`
|
||||||
|
const nowCmd = `now-${cmd}`;
|
||||||
|
const { zeroConfig } = config;
|
||||||
|
|
||||||
|
if (!zeroConfig && cmd === 'dev') {
|
||||||
|
return nowCmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scripts = (pkg && pkg.scripts) || {};
|
||||||
|
|
||||||
|
if (scripts[nowCmd]) {
|
||||||
|
return nowCmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scripts[cmd]) {
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nowCmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const version = 2;
|
||||||
|
|
||||||
|
const nowDevScriptPorts = new Map();
|
||||||
|
|
||||||
|
const getDevRoute = (srcBase: string, devPort: number, route: Route) => {
|
||||||
|
const basic: Route = {
|
||||||
|
src: `${srcBase}${route.src}`,
|
||||||
|
dest: `http://localhost:${devPort}${route.dest}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (route.headers) {
|
||||||
|
basic.headers = route.headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
return basic;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function build({
|
||||||
|
files,
|
||||||
|
entrypoint,
|
||||||
|
workPath,
|
||||||
|
config,
|
||||||
|
meta = {},
|
||||||
|
}: BuildOptions) {
|
||||||
|
console.log('Downloading user files...');
|
||||||
|
await download(files, workPath, meta);
|
||||||
|
|
||||||
|
const mountpoint = path.dirname(entrypoint);
|
||||||
|
const entrypointDir = path.join(workPath, mountpoint);
|
||||||
|
|
||||||
|
let distPath = path.join(
|
||||||
|
workPath,
|
||||||
|
path.dirname(entrypoint),
|
||||||
|
(config && (config.distDir as string)) || 'dist'
|
||||||
|
);
|
||||||
|
|
||||||
|
const entrypointName = path.basename(entrypoint);
|
||||||
|
|
||||||
|
if (entrypointName === 'package.json') {
|
||||||
|
const pkgPath = path.join(workPath, entrypoint);
|
||||||
|
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
||||||
|
|
||||||
|
let output: Files = {};
|
||||||
|
let framework: Framework | undefined = undefined;
|
||||||
|
let minNodeRange: string | undefined = undefined;
|
||||||
|
|
||||||
|
const routes: Route[] = [];
|
||||||
|
const devScript = getCommand(pkg, 'dev', config as Config);
|
||||||
|
|
||||||
|
if (config.zeroConfig) {
|
||||||
|
const dependencies = Object.assign(
|
||||||
|
{},
|
||||||
|
pkg.dependencies,
|
||||||
|
pkg.devDependencies
|
||||||
|
);
|
||||||
|
|
||||||
|
framework = frameworks.find(({ dependency }) => dependencies[dependency]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (framework) {
|
||||||
|
console.log(
|
||||||
|
`Detected ${framework.name} framework. Optimizing your deployment...`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (framework.minNodeRange) {
|
||||||
|
minNodeRange = framework.minNodeRange;
|
||||||
|
console.log(
|
||||||
|
`${framework.name} requires Node.js ${
|
||||||
|
framework.minNodeRange
|
||||||
|
}. Switching...`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
`${
|
||||||
|
framework.name
|
||||||
|
} does not require a specific Node.js version. Continuing ...`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeVersion = await getNodeVersion(entrypointDir, minNodeRange);
|
||||||
|
const spawnOpts = getSpawnOptions(meta, nodeVersion);
|
||||||
|
|
||||||
|
await runNpmInstall(entrypointDir, ['--prefer-offline'], spawnOpts);
|
||||||
|
|
||||||
|
if (meta.isDev && pkg.scripts && pkg.scripts[devScript]) {
|
||||||
|
let devPort: number | undefined = nowDevScriptPorts.get(entrypoint);
|
||||||
|
|
||||||
|
if (typeof devPort === 'number') {
|
||||||
|
console.log(
|
||||||
|
'`%s` server already running for %j',
|
||||||
|
devScript,
|
||||||
|
entrypoint
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Run the `now-dev` or `dev` script out-of-bounds, since it is assumed that
|
||||||
|
// it will launch a dev server that never "completes"
|
||||||
|
devPort = await getPort();
|
||||||
|
nowDevScriptPorts.set(entrypoint, devPort);
|
||||||
|
|
||||||
|
const opts = {
|
||||||
|
cwd: entrypointDir,
|
||||||
|
env: { ...process.env, PORT: String(devPort) },
|
||||||
|
};
|
||||||
|
|
||||||
|
const child = spawn('npm', ['run', devScript], opts);
|
||||||
|
child.on('exit', () => nowDevScriptPorts.delete(entrypoint));
|
||||||
|
child.stdout.setEncoding('utf8');
|
||||||
|
child.stdout.pipe(process.stdout);
|
||||||
|
child.stderr.setEncoding('utf8');
|
||||||
|
child.stderr.pipe(process.stderr);
|
||||||
|
|
||||||
|
// Now wait for the server to have listened on `$PORT`, after which we
|
||||||
|
// will ProxyPass any requests to that development server that come in
|
||||||
|
// for this builder.
|
||||||
|
try {
|
||||||
|
await timeout(
|
||||||
|
new Promise(resolve => {
|
||||||
|
const checkForPort = (data: string) => {
|
||||||
|
// Check the logs for the URL being printed with the port number
|
||||||
|
// (i.e. `http://localhost:47521`).
|
||||||
|
if (data.indexOf(`:${devPort}`) !== -1) {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
child.stdout.on('data', checkForPort);
|
||||||
|
child.stderr.on('data', checkForPort);
|
||||||
|
}),
|
||||||
|
5 * 60 * 1000
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to detect a server running on port ${devPort}.\nDetails: https://err.sh/zeit/now-builders/now-static-build-failed-to-detect-a-server`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Detected dev server for %j', entrypoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
let srcBase = mountpoint.replace(/^\.\/?/, '');
|
||||||
|
|
||||||
|
if (srcBase.length > 0) {
|
||||||
|
srcBase = `/${srcBase}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (framework && framework.defaultRoutes) {
|
||||||
|
for (const route of framework.defaultRoutes) {
|
||||||
|
routes.push(getDevRoute(srcBase, devPort, route));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
routes.push(
|
||||||
|
getDevRoute(srcBase, devPort, {
|
||||||
|
src: '/(.*)',
|
||||||
|
dest: '/$1',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (meta.isDev) {
|
||||||
|
console.log('WARN: "${devScript}" script is missing from package.json');
|
||||||
|
console.log(
|
||||||
|
'See the local development docs: https://zeit.co/docs/v2/deployments/official-builders/static-build-now-static-build/#local-development'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildScript = getCommand(pkg, 'build', config as Config);
|
||||||
|
console.log(`Running "${buildScript}" script in "${entrypoint}"`);
|
||||||
|
|
||||||
|
const found = await runPackageJsonScript(
|
||||||
|
entrypointDir,
|
||||||
|
buildScript,
|
||||||
|
spawnOpts
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
throw new Error(
|
||||||
|
`Missing required "${buildScript}" script in "${entrypoint}"`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (framework) {
|
||||||
|
const outputDirPrefix = path.join(workPath, path.dirname(entrypoint));
|
||||||
|
const outputDirName = await framework.getOutputDirName(outputDirPrefix);
|
||||||
|
|
||||||
|
distPath = path.join(outputDirPrefix, outputDirName);
|
||||||
|
}
|
||||||
|
|
||||||
|
validateDistDir(distPath, meta.isDev);
|
||||||
|
output = await glob('**', distPath, mountpoint);
|
||||||
|
|
||||||
|
if (framework && framework.defaultRoutes) {
|
||||||
|
routes.push(...framework.defaultRoutes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const watch = [path.join(mountpoint.replace(/^\.\/?/, ''), '**/*')];
|
||||||
|
return { routes, watch, output };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.zeroConfig && entrypointName.endsWith('.sh')) {
|
||||||
|
console.log(`Running build script "${entrypoint}"`);
|
||||||
|
const nodeVersion = await getNodeVersion(entrypointDir);
|
||||||
|
const spawnOpts = getSpawnOptions(meta, nodeVersion);
|
||||||
|
await runShellScript(path.join(workPath, entrypoint), [], spawnOpts);
|
||||||
|
validateDistDir(distPath, meta.isDev);
|
||||||
|
|
||||||
|
return glob('**', distPath, mountpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
let message = `Build "src" is "${entrypoint}" but expected "package.json"`;
|
||||||
|
|
||||||
|
if (!config.zeroConfig) {
|
||||||
|
message += ' or "build.sh"';
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
7
packages/now-static-build/test/fixtures/02-cowsay-sh/node10sh/build.sh
vendored
Normal file
7
packages/now-static-build/test/fixtures/02-cowsay-sh/node10sh/build.sh
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
NODEVERSION=$(node --version)
|
||||||
|
NPMVERSION=$(npm --version)
|
||||||
|
|
||||||
|
mkdir dist
|
||||||
|
echo "node:$NODEVERSION:RANDOMNESS_PLACEHOLDER" >> dist/index.txt
|
||||||
|
echo "npm:$NPMVERSION:RANDOMNESS_PLACEHOLDER" >> dist/index.txt
|
||||||
|
|
||||||
5
packages/now-static-build/test/fixtures/02-cowsay-sh/node10sh/package.json
vendored
Normal file
5
packages/now-static-build/test/fixtures/02-cowsay-sh/node10sh/package.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"engines": {
|
||||||
|
"node": "10.x"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,10 +2,12 @@
|
|||||||
"version": 2,
|
"version": 2,
|
||||||
"builds": [
|
"builds": [
|
||||||
{ "src": "some-build.sh", "use": "@now/static-build" },
|
{ "src": "some-build.sh", "use": "@now/static-build" },
|
||||||
|
{ "src": "node10sh/build.sh", "use": "@now/static-build" },
|
||||||
{ "src": "subdirectory/some-build.sh", "use": "@now/static-build" }
|
{ "src": "subdirectory/some-build.sh", "use": "@now/static-build" }
|
||||||
],
|
],
|
||||||
"probes": [
|
"probes": [
|
||||||
{ "path": "/", "mustContain": "cow:RANDOMNESS_PLACEHOLDER" },
|
{ "path": "/", "mustContain": "cow:RANDOMNESS_PLACEHOLDER" },
|
||||||
|
{ "path": "/node10sh/", "mustContain": "node:v10" },
|
||||||
{ "path": "/subdirectory/", "mustContain": "yoda:RANDOMNESS_PLACEHOLDER" }
|
{ "path": "/subdirectory/", "mustContain": "yoda:RANDOMNESS_PLACEHOLDER" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
2
packages/now-static-build/test/fixtures/07-nonzero-sh/build.sh
vendored
Executable file
2
packages/now-static-build/test/fixtures/07-nonzero-sh/build.sh
vendored
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
echo 'non-zero exit code should fail the build RANDOMNESS_PLACEHOLDER'
|
||||||
|
exit 1
|
||||||
4
packages/now-static-build/test/fixtures/07-nonzero-sh/now.json
vendored
Normal file
4
packages/now-static-build/test/fixtures/07-nonzero-sh/now.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"builds": [{ "src": "build.sh", "use": "@now/static-build" }]
|
||||||
|
}
|
||||||
19
packages/now-static-build/test/fixtures/09-cowsay-build/now.json
vendored
Normal file
19
packages/now-static-build/test/fixtures/09-cowsay-build/now.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"builds": [
|
||||||
|
{
|
||||||
|
"src": "package.json",
|
||||||
|
"use": "@now/static-build",
|
||||||
|
"config": { "zeroConfig": true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "subdirectory/package.json",
|
||||||
|
"use": "@now/static-build",
|
||||||
|
"config": { "zeroConfig": true }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"probes": [
|
||||||
|
{ "path": "/", "mustContain": "cow:RANDOMNESS_PLACEHOLDER" },
|
||||||
|
{ "path": "/subdirectory/", "mustContain": "yoda:RANDOMNESS_PLACEHOLDER" }
|
||||||
|
]
|
||||||
|
}
|
||||||
8
packages/now-static-build/test/fixtures/09-cowsay-build/package.json
vendored
Normal file
8
packages/now-static-build/test/fixtures/09-cowsay-build/package.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"cowsay": "^1.3.1"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "mkdir dist && cowsay cow:RANDOMNESS_PLACEHOLDER > dist/index.txt"
|
||||||
|
}
|
||||||
|
}
|
||||||
8
packages/now-static-build/test/fixtures/09-cowsay-build/subdirectory/package.json
vendored
Normal file
8
packages/now-static-build/test/fixtures/09-cowsay-build/subdirectory/package.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"yodasay": "^1.1.6"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "mkdir dist && yodasay yoda:RANDOMNESS_PLACEHOLDER > dist/index.txt"
|
||||||
|
}
|
||||||
|
}
|
||||||
69
packages/now-static-build/test/fixtures/10-gatsby/.gitignore
vendored
Normal file
69
packages/now-static-build/test/fixtures/10-gatsby/.gitignore
vendored
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Typescript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
|
||||||
|
# gatsby files
|
||||||
|
.cache/
|
||||||
|
public
|
||||||
|
|
||||||
|
# Mac files
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Yarn
|
||||||
|
yarn-error.log
|
||||||
|
.pnp/
|
||||||
|
.pnp.js
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
7
packages/now-static-build/test/fixtures/10-gatsby/.prettierrc
vendored
Normal file
7
packages/now-static-build/test/fixtures/10-gatsby/.prettierrc
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": false,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"trailingComma": "es5"
|
||||||
|
}
|
||||||
22
packages/now-static-build/test/fixtures/10-gatsby/LICENSE
vendored
Normal file
22
packages/now-static-build/test/fixtures/10-gatsby/LICENSE
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 gatsbyjs
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
93
packages/now-static-build/test/fixtures/10-gatsby/README.md
vendored
Normal file
93
packages/now-static-build/test/fixtures/10-gatsby/README.md
vendored
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<!-- AUTO-GENERATED-CONTENT:START (STARTER) -->
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://www.gatsbyjs.org">
|
||||||
|
<img alt="Gatsby" src="https://www.gatsbyjs.org/monogram.svg" width="60" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<h1 align="center">
|
||||||
|
Gatsby's default starter
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
Kick off your project with this default boilerplate. This starter ships with the main Gatsby configuration files you might need to get up and running blazing fast with the blazing fast app generator for React.
|
||||||
|
|
||||||
|
_Have another more specific idea? You may want to check out our vibrant collection of [official and community-created starters](https://www.gatsbyjs.org/docs/gatsby-starters/)._
|
||||||
|
|
||||||
|
## 🚀 Quick start
|
||||||
|
|
||||||
|
1. **Create a Gatsby site.**
|
||||||
|
|
||||||
|
Use the Gatsby CLI to create a new site, specifying the default starter.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# create a new Gatsby site using the default starter
|
||||||
|
gatsby new my-default-starter https://github.com/gatsbyjs/gatsby-starter-default
|
||||||
|
```
|
||||||
|
|
||||||
|
1. **Start developing.**
|
||||||
|
|
||||||
|
Navigate into your new site’s directory and start it up.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd my-default-starter/
|
||||||
|
gatsby develop
|
||||||
|
```
|
||||||
|
|
||||||
|
1. **Open the source code and start editing!**
|
||||||
|
|
||||||
|
Your site is now running at `http://localhost:8000`!
|
||||||
|
|
||||||
|
_Note: You'll also see a second link: _`http://localhost:8000/___graphql`_. This is a tool you can use to experiment with querying your data. Learn more about using this tool in the [Gatsby tutorial](https://www.gatsbyjs.org/tutorial/part-five/#introducing-graphiql)._
|
||||||
|
|
||||||
|
Open the `my-default-starter` directory in your code editor of choice and edit `src/pages/index.js`. Save your changes and the browser will update in real time!
|
||||||
|
|
||||||
|
## 🧐 What's inside?
|
||||||
|
|
||||||
|
A quick look at the top-level files and directories you'll see in a Gatsby project.
|
||||||
|
|
||||||
|
.
|
||||||
|
├── node_modules
|
||||||
|
├── src
|
||||||
|
├── .gitignore
|
||||||
|
├── .prettierrc
|
||||||
|
├── gatsby-browser.js
|
||||||
|
├── gatsby-config.js
|
||||||
|
├── gatsby-node.js
|
||||||
|
├── gatsby-ssr.js
|
||||||
|
├── LICENSE
|
||||||
|
├── package-lock.json
|
||||||
|
├── package.json
|
||||||
|
└── README.md
|
||||||
|
|
||||||
|
1. **`/node_modules`**: This directory contains all of the modules of code that your project depends on (npm packages) are automatically installed.
|
||||||
|
|
||||||
|
2. **`/src`**: This directory will contain all of the code related to what you will see on the front-end of your site (what you see in the browser) such as your site header or a page template. `src` is a convention for “source code”.
|
||||||
|
|
||||||
|
3. **`.gitignore`**: This file tells git which files it should not track / not maintain a version history for.
|
||||||
|
|
||||||
|
4. **`.prettierrc`**: This is a configuration file for [Prettier](https://prettier.io/). Prettier is a tool to help keep the formatting of your code consistent.
|
||||||
|
|
||||||
|
5. **`gatsby-browser.js`**: This file is where Gatsby expects to find any usage of the [Gatsby browser APIs](https://www.gatsbyjs.org/docs/browser-apis/) (if any). These allow customization/extension of default Gatsby settings affecting the browser.
|
||||||
|
|
||||||
|
6. **`gatsby-config.js`**: This is the main configuration file for a Gatsby site. This is where you can specify information about your site (metadata) like the site title and description, which Gatsby plugins you’d like to include, etc. (Check out the [config docs](https://www.gatsbyjs.org/docs/gatsby-config/) for more detail).
|
||||||
|
|
||||||
|
7. **`gatsby-node.js`**: This file is where Gatsby expects to find any usage of the [Gatsby Node APIs](https://www.gatsbyjs.org/docs/node-apis/) (if any). These allow customization/extension of default Gatsby settings affecting pieces of the site build process.
|
||||||
|
|
||||||
|
8. **`gatsby-ssr.js`**: This file is where Gatsby expects to find any usage of the [Gatsby server-side rendering APIs](https://www.gatsbyjs.org/docs/ssr-apis/) (if any). These allow customization of default Gatsby settings affecting server-side rendering.
|
||||||
|
|
||||||
|
9. **`LICENSE`**: Gatsby is licensed under the MIT license.
|
||||||
|
|
||||||
|
10. **`package-lock.json`** (See `package.json` below, first). This is an automatically generated file based on the exact versions of your npm dependencies that were installed for your project. **(You won’t change this file directly).**
|
||||||
|
|
||||||
|
11. **`package.json`**: A manifest file for Node.js projects, which includes things like metadata (the project’s name, author, etc). This manifest is how npm knows which packages to install for your project.
|
||||||
|
|
||||||
|
12. **`README.md`**: A text file containing useful reference information about your project.
|
||||||
|
|
||||||
|
## 🎓 Learning Gatsby
|
||||||
|
|
||||||
|
Looking for more guidance? Full documentation for Gatsby lives [on the website](https://www.gatsbyjs.org/). Here are some places to start:
|
||||||
|
|
||||||
|
- **For most developers, we recommend starting with our [in-depth tutorial for creating a site with Gatsby](https://www.gatsbyjs.org/tutorial/).** It starts with zero assumptions about your level of ability and walks through every step of the process.
|
||||||
|
|
||||||
|
- **To dive straight into code samples, head [to our documentation](https://www.gatsbyjs.org/docs/).** In particular, check out the _Guides_, _API Reference_, and _Advanced Tutorials_ sections in the sidebar.
|
||||||
|
|
||||||
|
<!-- AUTO-GENERATED-CONTENT:END -->
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user