Compare commits

...

36 Commits

Author SHA1 Message Date
Leo Lamprecht
f7bc3b3490 Publish
- @now/bash@1.0.0
 - @now/build-utils@0.8.0
 - @now/go@0.5.4
 - @now/html-minifier@1.1.4
 - @now/md@0.5.5
 - @now/mdx-deck@0.5.5
 - @now/next@0.5.3
 - @now/node-server@0.8.2
 - @now/node@0.11.1
 - @now/optipng@0.6.4
 - @now/php@0.5.6
 - @now/python@0.2.10
 - @now/ruby@0.1.2
 - @now/rust@0.2.8
 - @now/static-build@0.7.0
2019-07-07 15:54:56 +00:00
Leo Lamprecht
d83b09ffbd Revert "Fixed yarn.lock (#717)"
This reverts commit 0c120f6202.
2019-07-07 15:53:06 +00:00
Leo Lamprecht
0c120f6202 Fixed yarn.lock (#717) 2019-07-07 14:54:41 +00:00
Leo Lamprecht
48a01415f8 Complete cleanup (#716) 2019-07-07 16:48:28 +02:00
Leo Lamprecht
9198b72382 Revert "Bumps ncc to 0.20.x (#641)"
This reverts commit cd45dce724405968e9e6f58ae4fad983c5d35f20.
2019-07-07 14:28:26 +00:00
Andy
79048b83de [now-build-utils] Add methods to detect builders and routes from files and package.json (#705)
* [now-build-utils] Add methods to detect builders and routes from files
and package.json

* Update packages/now-build-utils/src/detect-routes.ts

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

* Update packages/now-build-utils/src/detect-routes.ts

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

* Update packages/now-build-utils/src/detect-routes.ts

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

* Added tests for new functions

* Adjusted tests

* Tests

* Tests

* Fix routes detection

* Fix routes and tests

* Update concatArrayOfText
2019-07-07 13:30:53 +00:00
Leo Lamprecht
f798e2cf19 [now-static-build] Optimize more frameworks out of the box (#704)
* Added Preact CLI

* Added Hexo

* Added Aurelia

* Correctly check for projects

* Added Gridsome

* Added UmiJS

* Added Polymer

* Added Polymer tests

* Better umi test

* Correctly test for 404

* Make Polymer tests work perfectly

* Removed aurelia

* Removed tests from preact

* Removed aurelia from the framework list

* Removed broken probe from hexo
2019-07-07 13:30:48 +00:00
Steven
be5057a738 [now-static-build] Add support for Node 10 in shell scripts (#708) 2019-07-07 13:30:44 +00:00
Steven
059d44fde7 [now-build-utils] Add shell script args / opts (#707)
* [now-build-utils] Add shell script args / opts

* Remove dead code
2019-07-07 13:30:40 +00:00
Igor Klopov
11ad481546 [now-build-utils] Add option to skip download + yarn mutex (#700)
* dontDownload in Meta

* yarn --mutex network

* dontDownload -> skipDownload
2019-07-07 13:30:34 +00:00
Luc
4061ed2eb7 [now-node] Make types.d.ts the main types export (#706) 2019-07-07 13:30:30 +00:00
dependabot[bot]
b54c79e619 Bump fstream from 1.0.11 to 1.0.12 (#702)
Bumps [fstream](https://github.com/npm/fstream) from 1.0.11 to 1.0.12.
- [Release notes](https://github.com/npm/fstream/releases)
- [Commits](https://github.com/npm/fstream/compare/v1.0.11...v1.0.12)

Signed-off-by: dependabot[bot] <support@github.com>
2019-07-07 13:30:24 +00:00
Luc
2203ae1a20 [now-node] Reduce risks of conflict with entrypoint's name (#626)
* helper -> ___help3rs , bridge -> ___bridg3

* launcher -> ___launch3r

* use const to configure filenames

* revert yarn.lock

* add `now` in file names

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

* add test to prevent regression

* remove 3 in filenames

* rename fixtures folder

* Apply suggestions from code review

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

* fix errors
2019-07-07 13:30:16 +00:00
Steven
5e58c1738b Add lucleray to codeowners (#695) 2019-07-07 13:30:12 +00:00
Luc
290577ddfb [now-node] Benchmark helpers (#613)
* add bench

* remove yarn.lock

* refactor and extract launcher logic

* add entrypoints to bench

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

Co-Authored-By: Steven <steven@ceriously.com>
2019-07-07 13:30:08 +00:00
Steven
a5e651403e [now-node] Add support for server.listen() (#691)
* [now-node] Add support for listener

* Fix listener

* Add hapi-async test

* Add timeout for listening

* Add additional case for invalid export

* Increase timeout to 5 seconds
2019-07-07 13:30:03 +00:00
ywg-jean
df2769717b Bumps ncc to 0.20.x (#641)
* bumps ncc to 0.20.0

* upgrades to ncc@0.20.2

this includes the fixes for https://github.com/zeit/ncc/issues/434

* defines filterAssetBase when calling ncc

This prevents assets from being evicted by ncc's filtering.

* scopes includeFiles under the package root

it was previously scoped under the directory of the input file currently
being processed.
This should fix #522 along with fixing the 09-include-files tests for
now-node

* Bump to ncc 0.20.3

* Bump ncc-watcher to 1.1.0
2019-07-07 13:29:57 +00:00
Tim Craft
880ef77b7b [now-ruby] Use BUNDLE_JOBS=4 for parallel Ruby gem downloads (#689) 2019-07-07 13:29:23 +00:00
Tim Craft
af2616c283 [now-ruby] Use ruby 2.5.5 (#690) 2019-07-07 13:29:19 +00:00
Steven
4300f4d797 Add homepage to all packages (#685) 2019-07-07 13:29:13 +00:00
Nathan Rajlich
4921e541af [now-node] Use the local version of node when running via now dev (#683)
This is a companion to https://github.com/zeit/now-cli/pull/2480.
2019-07-07 13:29:07 +00:00
Leo Lamprecht
6ce00eda8c [now-static-build] Optimize static frameworks (#696)
* Optimize frameworks in @now/static-build

* Make it shorter

* Disable build.sh for zero config

* Adjust error message according to zero config prop

* Renamed wrong fixtures

* Added Gatsby fixture

* Ignore static-build fixtures when linting

* Fix test

* Support Svelte

* Added Create React app and Svelte defaults

* Added comma

* Merge routes properly

* Removed test file

* Polished test

* Fixed types

* Added type

* Added correct type

* Added default routes for Svelte

* Extended route type

* Temporarily removed check

* Revert "Temporarily removed check"

This reverts commit bb5e2843eca60753228d499efbb6dd4aa1c28c5b.

* Enable routes

* Added package.json type

* Added Framework type

* Added Route type

* Increase size

* Export Route type

* Added Vue

* Fix test

* Fixed all tests

* Added Angular app

* Removed garbage

* Ignore ts stuff

* Added type

* Added type for function

* Made it async

* Require minimum node version

* Push pretter polishment

* Add support for node version

* Make minNodeVersion optional

* Make node version silent

* Added optional type

* Log everything

* Fixed tests

* Changed version

* Pushed Node.js version update

* Update packages/now-build-utils/src/fs/node-version.ts

Co-Authored-By: Andy <AndyBitz@users.noreply.github.com>

* Cleaner syntax

* Silence more messages

* Hide more text

* Update packages/now-build-utils/src/fs/run-user-scripts.ts

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

* Update packages/now-build-utils/src/fs/run-user-scripts.ts

Co-Authored-By: Steven <steven@ceriously.com>
2019-07-07 13:27:38 +00:00
Andy Bitz
060e952f6c Publish
- @now/build-utils@0.7.4
2019-07-04 22:58:06 +02:00
Andy Bitz
1639127b24 Publish
- @now/next@0.5.2
2019-07-01 18:45:01 +02:00
Joe Haddad
ebbb62eaea [now-next] Reorder process overrides (#682)
* Reorder process overrides

* Remove next.config.js to test the feature

* Correct src

* Fix more broken code

* Fix tests
2019-07-01 18:44:43 +02:00
Andy Bitz
66954e84fe Publish
- @now/bash@0.3.0
 - @now/build-utils@0.7.3
 - @now/node@0.11.0
 - @now/ruby@0.1.1
2019-07-01 13:10:01 +02:00
Andy
15a21bb28c [now-bash] Add import config prop (#681)
* [now-bash] Add imports config prop

* Fixed type

* Rename to import
2019-07-01 13:08:18 +02:00
Nathan Rajlich
96ca1e1d8c [now-node] Watch consumed assets when running in now dev (#612)
Watch assets that `ncc` reports as part of the compiled bundle.

For example, "pug" template files:

```js
app.set("views", path.join(__dirname, "../../", "/views/"));
```
2019-07-01 13:07:47 +02:00
Luc
587cb52191 [now-node] Add support for server instance exports (#657)
* check for listen method on export

* revert setting helpers on __proto__

* fix launcher

* add missing semicolon

* fix missing bridge parameter

* add server test fixture

* move express test fixtures to servers

* add missing entrypoints in fixtures

* temporary fix before we update node-bridge

* refactor express test fixture

* add fixtures for hapi, fastify, koa

* fix now.json in servers fixtures

* remove fastify as it is not yet supported

* remove fs-extra as a dependency
2019-07-01 13:07:38 +02:00
Luc
95422ffd46 [now-node] Make helpers 100% match expressjs API (#678)
* remove Stream support

* text/plain -> text/html

* add etags

* bring test suite from expressjs

* add TODO comment

* remove body for 204 and 304

* do not send body when req.method is HEAD

* fix tests

* lazy load etag

* add type safeguards

* avoid type casting
2019-07-01 13:07:33 +02:00
Steven
391a883799 [docs] Add codeowners for ruby (#676) 2019-07-01 13:07:26 +02:00
Steven
43d6960df4 [now-ruby] Move typescript to devDependency (#675) 2019-07-01 13:07:06 +02:00
Andy Bitz
5c128003d8 Publish
- @now/build-utils@0.7.2
 - @now/static-build@0.6.2
2019-06-30 00:30:37 +02:00
Andy
2f8fd1b14b [now-static-build] Default to now-dev when zeroConfig is false (#679)
* [now-static-build] Default to `now-dev` when `zeroConfig` is false

* Adjust tests

* Fix build

* Return nowCmd

* Adjusted type

* Changed type

* Removed type

* Cast type

* [now-build-utils] Export config
2019-06-30 00:30:00 +02:00
Andy Bitz
625553c146 Publish
- @now/build-utils@0.7.1
2019-06-29 23:12:38 +02:00
Andy
3b0ce7bad3 [now-builds-util] Add zeroConfig to config type (#680) 2019-06-29 23:11:41 +02:00
350 changed files with 79326 additions and 575 deletions

View File

@@ -14,3 +14,4 @@
/packages/now-rust/dist/*
/packages/now-ruby/dist/*
/packages/now-static-build/dist/*
/packages/now-static-build/test/fixtures/**

14
.github/CODEOWNERS vendored
View File

@@ -1,9 +1,11 @@
# Documentation
# https://help.github.com/en/articles/about-code-owners
* @styfle
/packages/now-node @styfle @tootallnate
/packages/now-next @timer @dav-is
/packages/now-go @styfle @sophearak
/packages/now-python @styfle @sophearak
/packages/now-rust @styfle @mike-engel @anmonteiro
* @styfle
/packages/now-node @styfle @tootallnate @lucleray
/packages/now-node-bridge @styfle @tootallnate @lucleray
/packages/now-next @timer @dav-is
/packages/now-go @styfle @sophearak
/packages/now-python @styfle @sophearak
/packages/now-rust @styfle @mike-engel @anmonteiro
/packages/now-ruby @styfle @coetry @nathancahill

View File

@@ -13,6 +13,15 @@ exports.config = {
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.build = async ({
@@ -24,10 +33,23 @@ exports.build = async ({
await download(files, srcDir);
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;
}, {});
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 env = Object.assign({}, process.env, configEnv, {
PATH: `${IMPORT_CACHE}/bin:${process.env.PATH}`,

View File

@@ -1,10 +1,11 @@
{
"name": "@now/bash",
"version": "0.2.3",
"version": "1.0.0",
"description": "Now 2.0 builder for HTTP endpoints written in Bash",
"main": "index.js",
"author": "Nathan Rajlich <nate@zeit.co>",
"license": "MIT",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/bash-now-bash",
"repository": {
"type": "git",
"url": "https://github.com/zeit/now-builders.git",

View File

@@ -1,9 +1,10 @@
{
"name": "@now/build-utils",
"version": "0.7.0",
"version": "0.8.0",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.js",
"homepage": "https://zeit.co/docs/v2/deployments/builders/developer-guide",
"repository": {
"type": "git",
"url": "https://github.com/zeit/now-builders.git",
@@ -22,6 +23,7 @@
"fs-extra": "7.0.0",
"glob": "7.1.3",
"into-stream": "5.0.0",
"minimatch": "3.0.4",
"multistream": "2.1.1",
"node-fetch": "2.2.0",
"semver": "6.1.1",

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

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

View File

@@ -39,10 +39,10 @@ export default async function download(
basePath: string,
meta?: Meta
): Promise<DownloadedFiles> {
const { isDev = false, filesChanged = null, filesRemoved = null } =
const { isDev = false, skipDownload = false, filesChanged = null, filesRemoved = null } =
meta || {};
if (isDev) {
if (isDev || skipDownload) {
// In `now dev`, the `download()` function is a no-op because
// the `basePath` matches the `cwd` of the dev server, so the
// source files are already available.

View File

@@ -13,15 +13,18 @@ export const defaultSelection = supportedOptions.find(
) as NodeVersion;
export async function getSupportedNodeVersion(
engineRange?: string
engineRange?: string,
silent?: boolean
): Promise<NodeVersion> {
let selection = defaultSelection;
if (!engineRange) {
console.log(
'missing `engines` in `package.json`, using default range: ' +
selection.range
);
if (!silent) {
console.log(
'missing `engines` in `package.json`, using default range: ' +
selection.range
);
}
} else {
const found = supportedOptions.some(o => {
// the array is already in order so return the first
@@ -30,15 +33,20 @@ export async function getSupportedNodeVersion(
return intersects(o.range, engineRange);
});
if (found) {
console.log(
'found `engines` in `package.json`, selecting range: ' + selection.range
);
if (!silent) {
console.log(
'Found `engines` in `package.json`, selecting range: ' +
selection.range
);
}
} else {
throw new Error(
'found `engines` in `package.json` with an unsupported node range: ' +
engineRange +
'\nplease use `10.x` or `8.10.x` instead'
);
if (!silent) {
throw new Error(
'found `engines` in `package.json` with an unsupported node range: ' +
engineRange +
'\nplease use `10.x` or `8.10.x` instead'
);
}
}
}
return selection;

View File

@@ -46,11 +46,15 @@ async function chmodPlusX(fsPath: string) {
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));
const destPath = path.dirname(fsPath);
await chmodPlusX(fsPath);
await spawnAsync(`./${path.basename(fsPath)}`, [], destPath);
await spawnAsync(`./${path.basename(fsPath)}`, args, destPath, spawnOpts);
return true;
}
@@ -69,10 +73,15 @@ export function getSpawnOptions(
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 range = packageJson && packageJson.engines && packageJson.engines.node;
return getSupportedNodeVersion(range);
const range =
(packageJson && packageJson.engines && packageJson.engines.node) ||
minNodeVersion;
return getSupportedNodeVersion(range, typeof minNodeVersion !== 'undefined');
}
async function scanParentDirs(destPath: string, readPackageJson = false) {
@@ -130,7 +139,13 @@ export async function runNpmInstall(
} else {
await spawnAsync(
'yarn',
commandArgs.concat(['--ignore-engines', '--cwd', destPath]),
commandArgs.concat([
'--ignore-engines',
'--mutex',
'network',
'--cwd',
destPath,
]),
destPath,
opts
);

View File

@@ -1,15 +1,6 @@
import FileBlob from './file-blob';
import FileFsRef from './file-fs-ref';
import FileRef from './file-ref';
import {
File,
Files,
AnalyzeOptions,
BuildOptions,
PrepareCacheOptions,
ShouldServeOptions,
Meta,
} from './types';
import { Lambda, createLambda } from './lambda';
import download, { DownloadedFiles } from './fs/download';
import getWriteableDirectory from './fs/get-writable-directory';
@@ -25,14 +16,13 @@ import {
} from './fs/run-user-scripts';
import streamToBuffer from './fs/stream-to-buffer';
import shouldServe from './should-serve';
import { detectBuilder, detectApiBuilders } from './detect-builder';
import { detectApiRoutes } from './detect-routes';
export {
FileBlob,
FileFsRef,
FileRef,
Files,
File,
Meta,
Lambda,
createLambda,
download,
@@ -47,9 +37,10 @@ export {
getNodeVersion,
getSpawnOptions,
streamToBuffer,
AnalyzeOptions,
BuildOptions,
PrepareCacheOptions,
ShouldServeOptions,
shouldServe,
detectBuilder,
detectApiBuilders,
detectApiRoutes,
};
export * from './types';

View File

@@ -15,8 +15,24 @@ export interface Files {
[filePath: string]: File;
}
export interface Route {
src?: string;
dest?: string;
handle?: string;
type?: string;
headers?: {
[key: string]: string;
};
}
export interface Config {
[key: string]: string | string[] | boolean | number | undefined;
[key: string]:
| string
| string[]
| boolean
| number
| { [key: string]: string }
| undefined;
maxLambdaSize?: string;
includeFiles?: string | string[];
bundle?: boolean;
@@ -24,10 +40,13 @@ export interface Config {
helpers?: boolean;
rust?: string;
debug?: boolean;
zeroConfig?: boolean;
import?: { [key: string]: string };
}
export interface Meta {
isDev?: boolean;
skipDownload?: boolean;
requestPath?: string;
filesChanged?: string[];
filesRemoved?: string[];
@@ -187,3 +206,9 @@ export interface NodeVersion {
range: string;
runtime: string;
}
export interface Builder {
use: string;
src: string;
config?: Config;
}

View File

@@ -4,7 +4,7 @@
{
"src": "index.js",
"use": "@now/node",
"config": { "maxLambdaSize": "15mb" }
"config": { "maxLambdaSize": "18mb" }
}
],
"probes": [{ "path": "/", "mustContain": "found:RANDOMNESS_PLACEHOLDER" }]

View File

@@ -16,6 +16,12 @@ const {
testDeployment,
} = require('../../../test/lib/deployment/test-deployment.js');
const {
detectBuilder,
detectApiBuilders,
detectApiRoutes,
} = require('../dist');
jest.setTimeout(4 * 60 * 1000);
const builderUrl = '@canary';
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);
}
});

View File

@@ -1,7 +1,8 @@
{
"name": "@now/go",
"version": "0.5.3",
"version": "0.5.4",
"license": "MIT",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/go-now-go",
"repository": {
"type": "git",
"url": "https://github.com/zeit/now-builders.git",

View File

@@ -1,7 +1,8 @@
{
"name": "@now/html-minifier",
"version": "1.1.3",
"version": "1.1.4",
"license": "MIT",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/html-minifier-now-html-minifier",
"repository": {
"type": "git",
"url": "https://github.com/zeit/now-builders.git",

View File

@@ -1,7 +1,8 @@
{
"name": "@now/md",
"version": "0.5.4",
"version": "0.5.5",
"license": "MIT",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/markdown-now-md",
"repository": {
"type": "git",
"url": "https://github.com/zeit/now-builders.git",

View File

@@ -1,7 +1,8 @@
{
"name": "@now/mdx-deck",
"version": "0.5.4",
"version": "0.5.5",
"license": "MIT",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/mdx-deck-now-mdx-deck",
"repository": {
"type": "git",
"url": "https://github.com/zeit/now-builders.git",

View File

@@ -1,8 +1,9 @@
{
"name": "@now/next",
"version": "0.5.1",
"version": "0.5.3",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/next-js-now-next",
"scripts": {
"build": "./getBridgeTypes.sh && tsc",
"test": "npm run build && jest",

View File

@@ -165,6 +165,8 @@ export const build = async ({
watch?: string[];
childProcesses: ChildProcess[];
}> => {
process.env.__NEXT_BUILDER_EXPERIMENTAL_TARGET = 'serverless';
validateEntrypoint(entrypoint);
const entryDirectory = path.dirname(entrypoint);
@@ -186,11 +188,7 @@ export const build = async ({
);
}
process.env.__NEXT_BUILDER_EXPERIMENTAL_TARGET = 'serverless';
if (meta.isDev) {
// eslint-disable-next-line no-underscore-dangle
process.env.__NEXT_BUILDER_EXPERIMENTAL_DEBUG = 'true';
let childProcess: ChildProcess | undefined;
// If this is the initial build, we want to start the server
@@ -287,10 +285,9 @@ export const build = async ({
console.log('running user script...');
const memoryToConsume = Math.floor(os.totalmem() / 1024 ** 2) - 128;
const buildSpawnOptions = { ...spawnOpts };
const env = { ...buildSpawnOptions.env } as any;
const env = { ...spawnOpts.env } as any;
env.NODE_OPTIONS = `--max_old_space_size=${memoryToConsume}`;
await runPackageJsonScript(entryPath, 'now-build', buildSpawnOptions);
await runPackageJsonScript(entryPath, 'now-build', { ...spawnOpts, env });
if (isLegacy) {
console.log('running npm install --production...');
@@ -389,7 +386,7 @@ export const build = async ({
'now__launcher.js': new FileBlob({ data: launcher }),
},
handler: 'now__launcher.launcher',
runtime: 'nodejs8.10',
runtime: nodeVersion.runtime,
});
console.log(`Created lambda for page: "${page}"`);
})
@@ -474,7 +471,7 @@ export const build = async ({
'page.js': pages[page],
},
handler: 'now__launcher.launcher',
runtime: 'nodejs8.10',
runtime: nodeVersion.runtime,
});
console.log(`Created lambda for page: "${page}"`);
})

View File

@@ -10,7 +10,8 @@ it(
const {
buildResult: { output },
} = 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 serverlessError = filePaths.some(filePath => filePath.match(/_error/));
const hasUnderScoreAppStaticFile = filePaths.some(filePath => filePath.match(/static.*\/pages\/_app\.js$/));

View File

@@ -1,3 +0,0 @@
module.exports = {
target: 'serverless',
};

View File

@@ -1,4 +1,4 @@
{
"version": 2,
"builds": [{ "src": "next.config.js", "use": "@now/next" }]
"builds": [{ "src": "package.json", "use": "@now/next" }]
}

View File

@@ -0,0 +1,3 @@
const F = () => 'Goodbye World!';
F.getInitialProps = () => ({});
export default F;

View File

@@ -157,10 +157,13 @@ exports.build = async ({
'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({
files: { ...preparedFiles, ...launcherFiles },
handler: 'launcher.launcher',
runtime: nodeVersion.runtime,
runtime,
});
return { [entrypoint]: lambda };

View File

@@ -1,7 +1,8 @@
{
"name": "@now/node-server",
"version": "0.8.1",
"version": "0.8.2",
"license": "MIT",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/node-js-server-now-node-server",
"repository": {
"type": "git",
"url": "https://github.com/zeit/now-builders.git",

1
packages/now-node/bench/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
lambda

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

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

View File

@@ -0,0 +1,9 @@
function doNothing() {}
module.exports = (req, res) => {
doNothing(req.query.who);
doNothing(req.body);
doNothing(req.cookies);
res.end('hello');
};

View File

@@ -0,0 +1,3 @@
module.exports = (req, res) => {
res.end('hello');
};

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

View 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();

View 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=

View File

@@ -13,12 +13,17 @@ cp -v "$bridge_defs" src
# build ts files
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
rm dist/helpers.js
ncc build src/helpers.ts -o dist/helpers
mv dist/helpers/index.js dist/helpers.js
rm -rf dist/helpers
# todo: improve
# copy type file for ts test
cp dist/types.d.ts test/fixtures/15-helpers/ts/types.d.ts

View File

@@ -1,8 +1,9 @@
{
"name": "@now/node",
"version": "0.10.1",
"version": "0.11.1",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/node-js-now-node",
"repository": {
"type": "git",
"url": "https://github.com/zeit/now-builders.git",
@@ -12,8 +13,7 @@
"@now/node-bridge": "1.2.2",
"@types/node": "*",
"@zeit/ncc": "0.18.5",
"@zeit/ncc-watcher": "1.0.3",
"fs-extra": "7.0.1"
"@zeit/ncc-watcher": "1.0.3"
},
"scripts": {
"build": "./build.sh",
@@ -26,9 +26,11 @@
"devDependencies": {
"@types/content-type": "1.1.3",
"@types/cookie": "0.3.3",
"@types/etag": "1.8.0",
"@types/test-listen": "1.1.0",
"content-type": "1.0.4",
"cookie": "0.4.0",
"etag": "1.8.1",
"node-fetch": "2.6.0",
"test-listen": "1.1.0",
"typescript": "3.5.2"

View File

@@ -5,7 +5,6 @@ import {
NowRequestQuery,
NowRequestBody,
} from './types';
import { Stream } from 'stream';
import { Server } from 'http';
import { Bridge } from './bridge';
@@ -78,83 +77,122 @@ function status(res: NowResponse, statusCode: number): NowResponse {
return res;
}
function setContentHeaders(
res: NowResponse,
type: string,
length?: number
): void {
if (!res.getHeader('content-type')) {
res.setHeader('content-type', type);
}
if (length !== undefined) {
res.setHeader('content-length', length);
}
function setCharset(type: string, charset: string) {
const { parse, format } = require('content-type');
const parsed = parse(type);
parsed.parameters.charset = charset;
return format(parsed);
}
function send(res: NowResponse, body: any) {
const t = typeof body;
if (body === null || t === 'undefined') {
res.end();
return res;
}
if (t === 'string') {
setContentHeaders(
res,
'text/plain; charset=utf-8',
Buffer.byteLength(body)
);
res.end(body);
return res;
}
if (Buffer.isBuffer(body)) {
setContentHeaders(res, 'application/octet-stream', body.length);
res.end(body);
return res;
}
if (body instanceof Stream) {
setContentHeaders(res, 'application/octet-stream');
body.pipe(res);
return res;
}
switch (t) {
case 'boolean':
case 'number':
case 'bigint':
case 'object':
return json(res, body);
}
throw new Error(
'`body` is not a valid string, object, boolean, number, Stream, or Buffer'
);
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 json(res: NowResponse, jsonBody: any): NowResponse {
switch (typeof jsonBody) {
case 'object':
case 'boolean':
case 'number':
case 'bigint':
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':
const body = JSON.stringify(jsonBody);
setContentHeaders(
res,
'application/json; charset=utf-8',
Buffer.byteLength(body)
);
res.end(body);
return res;
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;
}
throw new Error(
'`jsonBody` is not a valid object, boolean, string, number, or null'
);
// 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();
} else {
// respond
res.end(chunk, encoding);
}
return res;
}
function json(req: NowRequest, res: NowResponse, jsonBody: any): NowResponse {
const body = JSON.stringify(jsonBody);
// content-type
if (!res.getHeader('content-type')) {
res.setHeader('content-type', 'application/json; charset=utf-8');
}
return send(req, res, body);
}
export class ApiError extends Error {
@@ -219,8 +257,8 @@ export function createServerWithHelpers(
setLazyProp<NowRequestBody>(req, 'body', getBodyParser(req, event.body));
res.status = statusCode => status(res, statusCode);
res.send = body => send(res, body);
res.json = jsonBody => json(res, jsonBody);
res.send = body => send(req, res, body);
res.json = jsonBody => json(req, res, jsonBody);
await listener(req, res);
} catch (err) {

View File

@@ -1,4 +1,3 @@
import { readFile } from 'fs-extra';
import { Assets, NccOptions } from '@zeit/ncc';
import { join, dirname, relative, sep } from 'path';
import { NccWatcher, WatcherResult } from '@zeit/ncc-watcher';
@@ -19,6 +18,7 @@ import {
shouldServe,
} from '@now/build-utils';
export { NowRequest, NowResponse } from './types';
import { makeLauncher } from './launcher';
interface CompilerConfig {
includeFiles?: string | string[];
@@ -33,6 +33,10 @@ interface DownloadOptions {
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 {
let watcher = watchers.get(entrypoint);
if (!watcher) {
@@ -101,7 +105,8 @@ async function compile(
assets = result.assets;
watch = [...result.files, ...result.dirs, ...result.missing]
.filter(f => f.startsWith(workPath))
.map(f => relative(workPath, f));
.map(f => relative(workPath, f))
.concat(Object.keys(assets || {}));
} else {
const ncc = require('@zeit/ncc');
const result = await ncc(input, {
@@ -202,44 +207,37 @@ export async function build({
config,
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 = {
'launcher.js': new FileBlob({ data: launcherData }),
'bridge.js': new FileFsRef({ fsPath: require('@now/node-bridge') }),
[`${LAUNCHER_FILENAME}.js`]: new FileBlob({
data: makeLauncher({
entrypointPath: `./${entrypoint}`,
bridgePath: `./${BRIDGE_FILENAME}`,
helpersPath: `./${HELPERS_FILENAME}`,
shouldAddHelpers,
}),
}),
[`${BRIDGE_FILENAME}.js`]: new FileFsRef({
fsPath: require('@now/node-bridge'),
}),
};
if (shouldAddHelpers) {
launcherFiles['helpers.js'] = new FileFsRef({
launcherFiles[`${HELPERS_FILENAME}.js`] = new FileFsRef({
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({
files: {
...preparedFiles,
...launcherFiles,
},
handler: 'launcher.launcher',
runtime: nodeVersion.runtime,
handler: `${LAUNCHER_FILENAME}.launcher`,
runtime,
});
const output = { [entrypoint]: lambda };

View File

@@ -1,9 +1,29 @@
import { Bridge } from './bridge';
type LauncherConfiguration = {
entrypointPath: string;
bridgePath: string;
helpersPath: string;
shouldAddHelpers?: boolean;
};
let shouldStoreProxyRequests: boolean = false;
// PLACEHOLDER:shouldStoreProxyRequests
export function makeLauncher({
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) {
process.env.NODE_ENV =
@@ -11,13 +31,43 @@ if (!process.env.NODE_ENV) {
}
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) {
if (err.code === 'MODULE_NOT_FOUND') {
console.error(err.message);
console.error(
'Did you forget to add it to "dependencies" in `package.json`?'
);
console.error('Did you forget to add it to "dependencies" in \`package.json\`?');
process.exit(1);
} else {
console.error(err);
@@ -25,6 +75,5 @@ try {
}
}
bridge.listen();
exports.launcher = bridge.launcher;
exports.launcher = bridge.launcher;`;
}

View 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();

View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"@hapi/hapi": "18.3.1"
}
}

View File

@@ -0,0 +1,7 @@
const http = require('http');
const server = http.createServer((req, resp) => {
resp.end('root:RANDOMNESS_PLACEHOLDER');
});
server.listen();

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

View File

@@ -0,0 +1,7 @@
const http = require('http');
const server = http.createServer((req, resp) => {
resp.end('subdir:RANDOMNESS_PLACEHOLDER');
});
server.listen();

View File

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

View File

@@ -3,7 +3,6 @@
"builds": [
{ "src": "index.js", "use": "@now/node" },
{ "src": "ts/index.ts", "use": "@now/node" },
{ "src": "express-compat/index.js", "use": "@now/node" },
{ "src": "micro-compat/index.js", "use": "@now/node" },
{
"src": "no-helpers/index.js",
@@ -35,12 +34,6 @@
"path": "/ts",
"mustContain": "hello:RANDOMNESS_PLACEHOLDER"
},
{
"path": "/express-compat",
"method": "POST",
"body": { "who": "sara" },
"mustContain": "hello sara:RANDOMNESS_PLACEHOLDER"
},
{
"path": "/micro-compat",
"method": "POST",

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

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

View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"@hapi/hapi": "18.3.1"
}
}

View File

@@ -0,0 +1,7 @@
const http = require('http');
const server = http.createServer((req, res) => {
res.end('hello:RANDOMNESS_PLACEHOLDER');
});
module.exports = server;

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

View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"koa": "2.7.0"
}
}

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

View File

@@ -0,0 +1,3 @@
module.exports = (req, res) => {
res.end('bridge:RANDOMNESS_PLACEHOLDER');
};

View File

@@ -0,0 +1,3 @@
module.exports = (req, res) => {
res.end('helpers:RANDOMNESS_PLACEHOLDER');
};

View File

@@ -0,0 +1,3 @@
module.exports = (req, res) => {
res.end('launcher:RANDOMNESS_PLACEHOLDER');
};

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

View File

@@ -272,304 +272,7 @@ describe('req.body', () => {
});
});
describe('res.send()', () => {
test('res.send() should set body to ""', async () => {
mockListener.mockImplementation((req, res) => {
res.send();
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(await res.text()).toBe('');
});
test('res.send(null) should set body to ""', async () => {
mockListener.mockImplementation((req, res) => {
res.send(null);
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(await res.text()).toBe('');
});
test('res.send(undefined) 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('');
});
test('res.send(String) should send as text/plain', async () => {
mockListener.mockImplementation((req, res) => {
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('res.send(String) should not override Content-Type', async () => {
mockListener.mockImplementation((req, res) => {
res.setHeader('Content-Type', 'text/html');
res.send('hey');
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe('text/html');
expect(await res.text()).toBe('hey');
});
test('res.send(String) 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('res.send(Buffer) 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.text()).toBe('hello');
});
test('res.send(Buffer) should not override Content-Type', async () => {
mockListener.mockImplementation((req, res) => {
res.setHeader('Content-Type', 'text/plain');
res.send(Buffer.from('hello'));
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe('text/plain');
expect(await res.text()).toBe('hello');
});
test('res.send(Buffer) 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('res.send(Object) 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"}');
});
test('res.send(Stream) should send as application/octet-stream', async () => {
const { PassThrough } = require('stream');
mockListener.mockImplementation((req, res) => {
const stream = new PassThrough();
res.send(stream);
stream.push('hello');
stream.end();
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe('application/octet-stream');
expect(await res.text()).toBe('hello');
});
test('res.send() should send be chainable', async () => {
const spy = jest.fn();
mockListener.mockImplementation((req, res) => {
spy(res, res.send('hello'));
});
await fetchWithProxyReq(url);
const [a, b] = spy.mock.calls[0];
expect(a).toBe(b);
});
});
describe('res.json()', () => {
test('res.json() should not override previous Content-Type', 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'
);
expect(await res.text()).toBe('{"hello":"world"}');
});
test('res.json() should send as application/json', async () => {
mockListener.mockImplementation((req, res) => {
res.json({ hello: 'world' });
});
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('{"hello":"world"}');
});
test('res.json() 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":"½ + ¼ = ¾"}');
});
test('res.json(null) 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('res.json() should throw an error', async () => {
let _err;
mockListener.mockImplementation((req, res) => {
try {
res.json();
} catch (err) {
_err = err;
} finally {
res.end();
}
});
await fetchWithProxyReq(url);
expect(_err).toBeDefined();
expect(_err.message).toMatch(/not a valid object/);
});
test('res.json(undefined) should throw an error', async () => {
let _err;
mockListener.mockImplementation((req, res) => {
try {
res.json(undefined);
} catch (err) {
_err = err;
} finally {
res.end();
}
});
await fetchWithProxyReq(url);
expect(_err).toBeDefined();
expect(_err.message).toMatch(/not a valid object/);
});
test('res.json(Number) 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('res.json(String) 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('res.json(Array) should respond with json for 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('res.json(Object) should respond with json for 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"}');
});
test('res.json() 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);
});
});
describe('res.status()', () => {
describe('res.status', () => {
test('res.status() should set the status code', async () => {
mockListener.mockImplementation((req, res) => {
res.status(404);
@@ -595,3 +298,483 @@ describe('res.status()', () => {
expect(a).toBe(b);
});
});
// 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) => {
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);
expect(res.status).toBe(200);
expect(await res.text()).toBe('');
});
});
describe('.send(null)', () => {
test('should set body to ""', async () => {
mockListener.mockImplementation((req, res) => {
res.send(null);
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-length')).toBe('0');
expect(await res.text()).toBe('');
});
});
describe('.send(undefined)', () => {
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"}');
});
});
});

View File

@@ -1,8 +1,9 @@
{
"name": "@now/optipng",
"version": "0.6.3",
"version": "0.6.4",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/optipng-now-optipng",
"files": [
"dist"
],

View File

@@ -1,7 +1,8 @@
{
"name": "@now/php",
"version": "0.5.5",
"version": "0.5.6",
"license": "MIT",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/php-now-php",
"repository": {
"type": "git",
"url": "https://github.com/zeit/now-builders.git",

View File

@@ -1,8 +1,9 @@
{
"name": "@now/python",
"version": "0.2.9",
"version": "0.2.10",
"main": "./dist/index.js",
"license": "MIT",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/python-now-python",
"files": [
"dist",
"now_init.py"

View File

@@ -64,6 +64,7 @@ async function bundleInstall(
env: {
BUNDLE_SILENCE_ROOT_WARNING: '1',
BUNDLE_APP_CONFIG: bundleAppConfig,
BUNDLE_JOBS: '4',
},
}
);

View File

@@ -2,7 +2,7 @@ import { join } from 'path';
import execa from 'execa';
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) {
const baseDir = await getWriteableDirectory();

View File

@@ -1,8 +1,10 @@
{
"name": "@now/ruby",
"author": "Nathan Cahill <nathan@nathancahill.com>",
"version": "0.1.0",
"version": "0.1.2",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/ruby-now-ruby",
"files": [
"dist",
"now_init.rb"
@@ -12,7 +14,6 @@
"url": "https://github.com/zeit/now-builders.git",
"directory": "packages/now-ruby"
},
"license": "MIT",
"scripts": {
"build": "tsc",
"test": "tsc && jest",
@@ -20,7 +21,9 @@
},
"dependencies": {
"execa": "^1.0.0",
"fs-extra": "^7.0.1",
"fs-extra": "^7.0.1"
},
"devDependencies": {
"typescript": "3.5.2"
}
}

View File

@@ -1,8 +1,9 @@
{
"name": "@now/rust",
"version": "0.2.7",
"version": "0.2.8",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/now-rust",
"repository": {
"type": "git",
"url": "https://github.com/zeit/now-builders.git",

View File

@@ -1,8 +1,9 @@
{
"name": "@now/static-build",
"version": "0.6.1",
"version": "0.7.0",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/static-build-now-static-build",
"files": [
"dist"
],

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

View File

@@ -3,6 +3,7 @@ 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,
@@ -12,13 +13,29 @@ import {
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) {
@@ -50,9 +67,16 @@ function validateDistDir(distDir: string, isDev: boolean | undefined) {
}
}
function getCommand(pkg: PackageJson, cmd: string) {
const scripts = (pkg && pkg.scripts) || {};
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;
@@ -69,6 +93,19 @@ 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,
@@ -80,10 +117,9 @@ export async function build({
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(
const entrypointDir = path.join(workPath, mountpoint);
let distPath = path.join(
workPath,
path.dirname(entrypoint),
(config && (config.distDir as string)) || 'dist'
@@ -91,25 +127,56 @@ export async function build({
const entrypointName = path.basename(entrypoint);
if (entrypointName.endsWith('.sh')) {
console.log(`Running build script "${entrypoint}"`);
await runShellScript(path.join(workPath, entrypoint));
validateDistDir(distPath, meta.isDev);
return glob('**', distPath, mountpoint);
}
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: Files = {};
const routes: { src: string; dest: string }[] = [];
const devScript = getCommand(pkg, 'dev');
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 = nowDevScriptPorts.get(entrypoint);
let devPort: number | undefined = nowDevScriptPorts.get(entrypoint);
if (typeof devPort === 'number') {
console.log(
'`%s` server already running for %j',
@@ -121,10 +188,12 @@ export async function build({
// it will launch a dev server that never "completes"
devPort = await getPort();
nowDevScriptPorts.set(entrypoint, devPort);
const opts = {
cwd: entrypointFsDirname,
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');
@@ -160,13 +229,23 @@ export async function build({
}
let srcBase = mountpoint.replace(/^\.\/?/, '');
if (srcBase.length > 0) {
srcBase = `/${srcBase}`;
}
routes.push({
src: `${srcBase}/(.*)`,
dest: `http://localhost:${devPort}/$1`,
});
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');
@@ -174,26 +253,56 @@ export async function build({
'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');
const buildScript = getCommand(pkg, 'build', config as Config);
console.log(`Running "${buildScript}" script in "${entrypoint}"`);
const found = await runPackageJsonScript(
entrypointFsDirname,
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 };
}
throw new Error(
`Build "src" is "${entrypoint}" but expected "package.json" or "build.sh"`
);
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);
}

View 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

View File

@@ -0,0 +1,5 @@
{
"engines": {
"node": "10.x"
}
}

View File

@@ -2,10 +2,12 @@
"version": 2,
"builds": [
{ "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" }
],
"probes": [
{ "path": "/", "mustContain": "cow:RANDOMNESS_PLACEHOLDER" },
{ "path": "/node10sh/", "mustContain": "node:v10" },
{ "path": "/subdirectory/", "mustContain": "yoda:RANDOMNESS_PLACEHOLDER" }
]
}

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

View 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

View File

@@ -0,0 +1,7 @@
{
"endOfLine": "lf",
"semi": false,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5"
}

View 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.

View 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 sites 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 youd 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 wont change this file directly).**
11. **`package.json`**: A manifest file for Node.js projects, which includes things like metadata (the projects 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 -->

View File

@@ -0,0 +1,7 @@
/**
* Implement Gatsby's Browser APIs in this file.
*
* See: https://www.gatsbyjs.org/docs/browser-apis/
*/
// You can delete this file if you're not using it

View File

@@ -0,0 +1,34 @@
module.exports = {
siteMetadata: {
title: `Gatsby Default Starter`,
description: `Kick off your next, great Gatsby project with this default starter. This barebones starter ships with the main Gatsby configuration files you might need.`,
author: `@gatsbyjs`,
},
plugins: [
`gatsby-plugin-react-helmet`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
`gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `gatsby-starter-default`,
short_name: `starter`,
start_url: `/`,
background_color: `#663399`,
theme_color: `#663399`,
display: `minimal-ui`,
icon: `src/images/gatsby-icon.png`, // This path is relative to the root of the site.
},
},
// this (optional) plugin enables Progressive Web App + Offline functionality
// To learn more, visit: https://gatsby.dev/offline
// `gatsby-plugin-offline`,
],
}

View File

@@ -0,0 +1,7 @@
/**
* Implement Gatsby's Node APIs in this file.
*
* See: https://www.gatsbyjs.org/docs/node-apis/
*/
// You can delete this file if you're not using it

View File

@@ -0,0 +1,7 @@
/**
* Implement Gatsby's SSR (Server Side Rendering) APIs in this file.
*
* See: https://www.gatsbyjs.org/docs/ssr-apis/
*/
// You can delete this file if you're not using it

View File

@@ -0,0 +1,9 @@
{
"version": 2,
"builds": [
{ "src": "package.json", "use": "@now/static-build", "config": { "zeroConfig": true } }
],
"probes": [
{ "path": "/", "mustContain": "Welcome to your new Gatsby site" }
]
}

View File

@@ -0,0 +1,43 @@
{
"name": "gatsby-starter-default",
"private": true,
"description": "A simple starter to get up and developing quickly with Gatsby",
"version": "0.1.0",
"author": "Kyle Mathews <mathews.kyle@gmail.com>",
"dependencies": {
"gatsby": "^2.13.3",
"gatsby-image": "^2.2.4",
"gatsby-plugin-manifest": "^2.2.1",
"gatsby-plugin-offline": "^2.2.0",
"gatsby-plugin-react-helmet": "^3.1.0",
"gatsby-plugin-sharp": "^2.2.2",
"gatsby-source-filesystem": "^2.1.2",
"gatsby-transformer-sharp": "^2.2.1",
"prop-types": "^15.7.2",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-helmet": "^5.2.1"
},
"devDependencies": {
"prettier": "^1.18.2"
},
"keywords": [
"gatsby"
],
"license": "MIT",
"scripts": {
"build": "gatsby build",
"develop": "gatsby develop",
"format": "prettier --write src/**/*.{js,jsx}",
"start": "npm run develop",
"serve": "gatsby serve",
"test": "echo \"Write tests! -> https://gatsby.dev/unit-testing\""
},
"repository": {
"type": "git",
"url": "https://github.com/gatsbyjs/gatsby-starter-default"
},
"bugs": {
"url": "https://github.com/gatsbyjs/gatsby/issues"
}
}

View File

@@ -0,0 +1,42 @@
import { Link } from "gatsby"
import PropTypes from "prop-types"
import React from "react"
const Header = ({ siteTitle }) => (
<header
style={{
background: `rebeccapurple`,
marginBottom: `1.45rem`,
}}
>
<div
style={{
margin: `0 auto`,
maxWidth: 960,
padding: `1.45rem 1.0875rem`,
}}
>
<h1 style={{ margin: 0 }}>
<Link
to="/"
style={{
color: `white`,
textDecoration: `none`,
}}
>
{siteTitle}
</Link>
</h1>
</div>
</header>
)
Header.propTypes = {
siteTitle: PropTypes.string,
}
Header.defaultProps = {
siteTitle: ``,
}
export default Header

View File

@@ -0,0 +1,32 @@
import React from "react"
import { useStaticQuery, graphql } from "gatsby"
import Img from "gatsby-image"
/*
* This component is built using `gatsby-image` to automatically serve optimized
* images with lazy loading and reduced file sizes. The image is loaded using a
* `useStaticQuery`, which allows us to load the image from directly within this
* component, rather than having to pass the image data down from pages.
*
* For more information, see the docs:
* - `gatsby-image`: https://gatsby.dev/gatsby-image
* - `useStaticQuery`: https://www.gatsbyjs.org/docs/use-static-query/
*/
const Image = () => {
const data = useStaticQuery(graphql`
query {
placeholderImage: file(relativePath: { eq: "gatsby-astronaut.png" }) {
childImageSharp {
fluid(maxWidth: 300) {
...GatsbyImageSharpFluid
}
}
}
}
`)
return <Img fluid={data.placeholderImage.childImageSharp.fluid} />
}
export default Image

View File

@@ -0,0 +1,622 @@
html {
font-family: sans-serif;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
article,
aside,
details,
figcaption,
figure,
footer,
header,
main,
menu,
nav,
section,
summary {
display: block;
}
audio,
canvas,
progress,
video {
display: inline-block;
}
audio:not([controls]) {
display: none;
height: 0;
}
progress {
vertical-align: baseline;
}
[hidden],
template {
display: none;
}
a {
background-color: transparent;
-webkit-text-decoration-skip: objects;
}
a:active,
a:hover {
outline-width: 0;
}
abbr[title] {
border-bottom: none;
text-decoration: underline;
text-decoration: underline dotted;
}
b,
strong {
font-weight: inherit;
font-weight: bolder;
}
dfn {
font-style: italic;
}
h1 {
font-size: 2em;
margin: 0.67em 0;
}
mark {
background-color: #ff0;
color: #000;
}
small {
font-size: 80%;
}
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
img {
border-style: none;
}
svg:not(:root) {
overflow: hidden;
}
code,
kbd,
pre,
samp {
font-family: monospace, monospace;
font-size: 1em;
}
figure {
margin: 1em 40px;
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible;
}
button,
input,
optgroup,
select,
textarea {
font: inherit;
margin: 0;
}
optgroup {
font-weight: 700;
}
button,
input {
overflow: visible;
}
button,
select {
text-transform: none;
}
[type="reset"],
[type="submit"],
button,
html [type="button"] {
-webkit-appearance: button;
}
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner,
button::-moz-focus-inner {
border-style: none;
padding: 0;
}
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring,
button:-moz-focusring {
outline: 1px dotted ButtonText;
}
fieldset {
border: 1px solid silver;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
legend {
box-sizing: border-box;
color: inherit;
display: table;
max-width: 100%;
padding: 0;
white-space: normal;
}
textarea {
overflow: auto;
}
[type="checkbox"],
[type="radio"] {
box-sizing: border-box;
padding: 0;
}
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
[type="search"] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
[type="search"]::-webkit-search-cancel-button,
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-input-placeholder {
color: inherit;
opacity: 0.54;
}
::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit;
}
html {
font: 112.5%/1.45em georgia, serif;
box-sizing: border-box;
overflow-y: scroll;
}
* {
box-sizing: inherit;
}
*:before {
box-sizing: inherit;
}
*:after {
box-sizing: inherit;
}
body {
color: hsla(0, 0%, 0%, 0.8);
font-family: georgia, serif;
font-weight: normal;
word-wrap: break-word;
font-kerning: normal;
-moz-font-feature-settings: "kern", "liga", "clig", "calt";
-ms-font-feature-settings: "kern", "liga", "clig", "calt";
-webkit-font-feature-settings: "kern", "liga", "clig", "calt";
font-feature-settings: "kern", "liga", "clig", "calt";
}
img {
max-width: 100%;
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
h1 {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
color: inherit;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
font-weight: bold;
text-rendering: optimizeLegibility;
font-size: 2.25rem;
line-height: 1.1;
}
h2 {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
color: inherit;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
font-weight: bold;
text-rendering: optimizeLegibility;
font-size: 1.62671rem;
line-height: 1.1;
}
h3 {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
color: inherit;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
font-weight: bold;
text-rendering: optimizeLegibility;
font-size: 1.38316rem;
line-height: 1.1;
}
h4 {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
color: inherit;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
font-weight: bold;
text-rendering: optimizeLegibility;
font-size: 1rem;
line-height: 1.1;
}
h5 {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
color: inherit;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
font-weight: bold;
text-rendering: optimizeLegibility;
font-size: 0.85028rem;
line-height: 1.1;
}
h6 {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
color: inherit;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
font-weight: bold;
text-rendering: optimizeLegibility;
font-size: 0.78405rem;
line-height: 1.1;
}
hgroup {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
ul {
margin-left: 1.45rem;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
list-style-position: outside;
list-style-image: none;
}
ol {
margin-left: 1.45rem;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
list-style-position: outside;
list-style-image: none;
}
dl {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
dd {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
p {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
figure {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
pre {
margin-left: 0;
margin-right: 0;
margin-top: 0;
margin-bottom: 1.45rem;
font-size: 0.85rem;
line-height: 1.42;
background: hsla(0, 0%, 0%, 0.04);
border-radius: 3px;
overflow: auto;
word-wrap: normal;
padding: 1.45rem;
}
table {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
font-size: 1rem;
line-height: 1.45rem;
border-collapse: collapse;
width: 100%;
}
fieldset {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
blockquote {
margin-left: 1.45rem;
margin-right: 1.45rem;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
form {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
noscript {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
iframe {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
hr {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: calc(1.45rem - 1px);
background: hsla(0, 0%, 0%, 0.2);
border: none;
height: 1px;
}
address {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
b {
font-weight: bold;
}
strong {
font-weight: bold;
}
dt {
font-weight: bold;
}
th {
font-weight: bold;
}
li {
margin-bottom: calc(1.45rem / 2);
}
ol li {
padding-left: 0;
}
ul li {
padding-left: 0;
}
li > ol {
margin-left: 1.45rem;
margin-bottom: calc(1.45rem / 2);
margin-top: calc(1.45rem / 2);
}
li > ul {
margin-left: 1.45rem;
margin-bottom: calc(1.45rem / 2);
margin-top: calc(1.45rem / 2);
}
blockquote *:last-child {
margin-bottom: 0;
}
li *:last-child {
margin-bottom: 0;
}
p *:last-child {
margin-bottom: 0;
}
li > p {
margin-bottom: calc(1.45rem / 2);
}
code {
font-size: 0.85rem;
line-height: 1.45rem;
}
kbd {
font-size: 0.85rem;
line-height: 1.45rem;
}
samp {
font-size: 0.85rem;
line-height: 1.45rem;
}
abbr {
border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5);
cursor: help;
}
acronym {
border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5);
cursor: help;
}
abbr[title] {
border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5);
cursor: help;
text-decoration: none;
}
thead {
text-align: left;
}
td,
th {
text-align: left;
border-bottom: 1px solid hsla(0, 0%, 0%, 0.12);
font-feature-settings: "tnum";
-moz-font-feature-settings: "tnum";
-ms-font-feature-settings: "tnum";
-webkit-font-feature-settings: "tnum";
padding-left: 0.96667rem;
padding-right: 0.96667rem;
padding-top: 0.725rem;
padding-bottom: calc(0.725rem - 1px);
}
th:first-child,
td:first-child {
padding-left: 0;
}
th:last-child,
td:last-child {
padding-right: 0;
}
tt,
code {
background-color: hsla(0, 0%, 0%, 0.04);
border-radius: 3px;
font-family: "SFMono-Regular", Consolas, "Roboto Mono", "Droid Sans Mono",
"Liberation Mono", Menlo, Courier, monospace;
padding: 0;
padding-top: 0.2em;
padding-bottom: 0.2em;
}
pre code {
background: none;
line-height: 1.42;
}
code:before,
code:after,
tt:before,
tt:after {
letter-spacing: -0.2em;
content: " ";
}
pre code:before,
pre code:after,
pre tt:before,
pre tt:after {
content: "";
}
@media only screen and (max-width: 480px) {
html {
font-size: 100%;
}
}

View File

@@ -0,0 +1,52 @@
/**
* Layout component that queries for data
* with Gatsby's useStaticQuery component
*
* See: https://www.gatsbyjs.org/docs/use-static-query/
*/
import React from "react"
import PropTypes from "prop-types"
import { useStaticQuery, graphql } from "gatsby"
import Header from "./header"
import "./layout.css"
const Layout = ({ children }) => {
const data = useStaticQuery(graphql`
query SiteTitleQuery {
site {
siteMetadata {
title
}
}
}
`)
return (
<>
<Header siteTitle={data.site.siteMetadata.title} />
<div
style={{
margin: `0 auto`,
maxWidth: 960,
padding: `0px 1.0875rem 1.45rem`,
paddingTop: 0,
}}
>
<main>{children}</main>
<footer>
© {new Date().getFullYear()}, Built with
{` `}
<a href="https://www.gatsbyjs.org">Gatsby</a>
</footer>
</div>
</>
)
}
Layout.propTypes = {
children: PropTypes.node.isRequired,
}
export default Layout

View File

@@ -0,0 +1,88 @@
/**
* SEO component that queries for data with
* Gatsby's useStaticQuery React hook
*
* See: https://www.gatsbyjs.org/docs/use-static-query/
*/
import React from "react"
import PropTypes from "prop-types"
import Helmet from "react-helmet"
import { useStaticQuery, graphql } from "gatsby"
function SEO({ description, lang, meta, title }) {
const { site } = useStaticQuery(
graphql`
query {
site {
siteMetadata {
title
description
author
}
}
}
`
)
const metaDescription = description || site.siteMetadata.description
return (
<Helmet
htmlAttributes={{
lang,
}}
title={title}
titleTemplate={`%s | ${site.siteMetadata.title}`}
meta={[
{
name: `description`,
content: metaDescription,
},
{
property: `og:title`,
content: title,
},
{
property: `og:description`,
content: metaDescription,
},
{
property: `og:type`,
content: `website`,
},
{
name: `twitter:card`,
content: `summary`,
},
{
name: `twitter:creator`,
content: site.siteMetadata.author,
},
{
name: `twitter:title`,
content: title,
},
{
name: `twitter:description`,
content: metaDescription,
},
].concat(meta)}
/>
)
}
SEO.defaultProps = {
lang: `en`,
meta: [],
description: ``,
}
SEO.propTypes = {
description: PropTypes.string,
lang: PropTypes.string,
meta: PropTypes.arrayOf(PropTypes.object),
title: PropTypes.string.isRequired,
}
export default SEO

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -0,0 +1,14 @@
import React from "react"
import Layout from "../components/layout"
import SEO from "../components/seo"
const NotFoundPage = () => (
<Layout>
<SEO title="404: Not found" />
<h1>NOT FOUND</h1>
<p>You just hit a route that doesn&#39;t exist... the sadness.</p>
</Layout>
)
export default NotFoundPage

View File

@@ -0,0 +1,21 @@
import React from "react"
import { Link } from "gatsby"
import Layout from "../components/layout"
import Image from "../components/image"
import SEO from "../components/seo"
const IndexPage = () => (
<Layout>
<SEO title="Home" />
<h1>Hi people</h1>
<p>Welcome to your new Gatsby site.</p>
<p>Now go build something great.</p>
<div style={{ maxWidth: `300px`, marginBottom: `1.45rem` }}>
<Image />
</div>
<Link to="/page-2/">Go to page 2</Link>
</Layout>
)
export default IndexPage

View File

@@ -0,0 +1,16 @@
import React from "react"
import { Link } from "gatsby"
import Layout from "../components/layout"
import SEO from "../components/seo"
const SecondPage = () => (
<Layout>
<SEO title="Page two" />
<h1>Hi from the second page</h1>
<p>Welcome to page 2</p>
<Link to="/">Go back to the homepage</Link>
</Layout>
)
export default SecondPage

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +0,0 @@
{
"version": 2,
"builds": [
{ "src": "package.json", "use": "@now/static-build" },
{ "src": "subdirectory/package.json", "use": "@now/static-build" }
],
"probes": [
{ "path": "/", "mustContain": "cow:RANDOMNESS_PLACEHOLDER" },
{ "path": "/subdirectory/", "mustContain": "yoda:RANDOMNESS_PLACEHOLDER" }
]
}

Some files were not shown because too many files have changed in this diff Show More