Compare commits

..

30 Commits

Author SHA1 Message Date
Steven
46c8cb1a68 Publish Stable
- @now/build-utils@1.3.2
 - @now/next@2.3.7
 - @now/node@1.3.3
 - @now/static-build@0.14.3
2020-01-06 18:44:26 -05:00
Steven
faeb053ea6 Publish Canary
- @now/build-utils@1.3.2-canary.1
 - now@16.7.2-canary.3
 - @now/next@2.3.7-canary.1
 - @now/node@1.3.3-canary.1
 - @now/static-build@0.14.3-canary.1
2020-01-06 18:30:34 -05:00
Steven
708a09b86a [now-build-utils] Add usage of package.json engines (#3512)
Add a better upgrade message that shows usage of `engines` in the `package.json` file.
2020-01-06 18:29:08 -05:00
Steven
89403e93e4 Publish Canary
- @now/build-utils@1.3.2-canary.0
 - @now/next@2.3.7-canary.0
 - @now/node@1.3.3-canary.0
 - @now/static-build@0.14.3-canary.0
2020-01-06 17:01:47 -05:00
Steven
ecb0c08fe2 [now-build-utils] Fix now dev to use system node (#3509)
This PR will use the system installed version of Node.js and avoid printing a warning or error if a discontinued version is selected.

This optimization was already in `@now/node` but for some reason it was never add to `@now/next`.

The reason why its relevant today is because the warnings turned into errors due to Node 8 deprecation and we don't have the "Project" in `now dev` so we don't know which version of node to select.

So instead of determining the version, `now dev` will always use `node` in the PATH and avoid printing warnings or errors. This also results in less FS reads since we no longer need to read package.json.
2020-01-06 17:00:46 -05:00
Andy Bitz
0b88c158b9 Publish Stable
- @now/frameworks@0.0.3
2020-01-06 20:15:04 +01:00
Andy
ec3a38107a [frameworks] Add settings to frameworks (#3506)
* [frameworks] Add `settings` to frameworks

* Fix svelte

* Add `outputDirectory`

* Update Next.js outputDirectory placeholder

* Type settings

Co-authored-by: Leo Lamprecht <mindrun@icloud.com>
2020-01-06 20:12:38 +01:00
Steven
0c0f1c6eb5 Publish Stable
- @now/build-utils@1.3.1
 - @now/next@2.3.6
 - @now/node@1.3.2
 - @now/static-build@0.14.2
2020-01-06 13:21:41 -05:00
Steven
ed296ef733 Publish Canary
- @now/build-utils@1.3.1-canary.1
 - now@16.7.2-canary.2
 - @now/static-build@0.14.2-canary.0
2020-01-06 13:10:05 -05:00
Steven
246f47ec95 [now-static-build] Fix distDir when zero config framework is detected (#3507)
There was a regression with framework detection that was preferring the frameworks output directory instead of the `distDir` defined in the `@now/static-build`.

The fix is to only run framework detection when `builds` is not defined (ie zero config).

Related to https://github.com/Ebury/chameleon/pull/63
2020-01-06 18:07:25 +00:00
Steven
9d95b99b72 [now-build-utils] Add support for dynamic handle: miss (#3457)
This extends the behavior of `featHandleMiss: true` flag to do the following:

- Reduce zero config API routes so that only dynamic path segment files (`api/[id].js`) get a route.
- Remove zero config out directory route (`public/`)—the files will be renamed instead.
- Use redirects for API routes when `cleanUrls: true` and use rewrites when `cleanUrls: false` from extension to the extension-less file.
- Normalize existing routes to begin with `^` and end with `$`.
2020-01-06 16:07:47 +00:00
Steven
3de8ae9d7e Publish Canary
- @now/build-utils@1.3.1-canary.0
 - @now/node@1.3.2-canary.0
2020-01-06 10:19:23 -05:00
Steven
44f6e1904e [now-build-utils][now-node] Throw new NowBuildError() (#3496)
This PR does the following

- Add and export class, `NowBuildError`, that can be thrown to stop the build and print an error but it will not print a stack trace.
- Improve logic for discontinued node versions and add more tests
- Remove hack (#3425) for undefined TERM, fixed by updating dependencies
- Rename `silent` variable to `isAuto` which means the node version was automatically selected, not defined in `package.json`
- Rename `test` deployments to `test2020` so that a fresh project is created with latest Node.js 12.x
2020-01-06 15:00:27 +00:00
Max Rovensky
d9c84fc4ce Publish Canary
- now@16.7.2-canary.1
 - @now/python@1.1.1-canary.0
2020-01-06 21:45:01 +08:00
Max
b5142d935b Fix event order in CLI (#3502)
This should fix [PRODUCT-893](https://zeit.atlassian.net/browse/PRODUCT-893) and [PRODUCT-941](https://zeit.atlassian.net/browse/PRODUCT-941)
2020-01-06 13:43:44 +00:00
luc
718a451110 Publish Canary
- now@16.7.2-canary.0
 - now-client@6.0.2-canary.0
2020-01-06 11:24:45 +01:00
Andy
9755847855 [now-client] Ignore .env.* files by default (#3436)
Ignore `.env.*` files by default.

[PRODUCT-804]

[PRODUCT-804]: https://zeit.atlassian.net/browse/PRODUCT-804
2020-01-06 10:19:55 +00:00
Luc
abc417b6b3 [now-client] Use @zeit/fetch instead of node-fetch (#3490)
We want to take advantage of the better defaults and retry policies (among others).

This bumps `@zeit/fetch` and replaces `node-fetch` with `@zeit/fetch` in `now-client`.

Also update retry policy for "uploading" files to:
- base 10ms
- 5 retries
- factor 6

which means the timeouts are 10, 60, 360, 2160, 12960
2020-01-06 09:01:17 +00:00
Steven
d6f71c8d7b Publish Stable
- @now/frameworks@0.0.2
 - @now/build-utils@1.3.0
 - now@16.7.1
 - now-client@6.0.1
 - @now/next@2.3.5
 - @now/node@1.3.1
 - @now/python@1.1.0
 - @now/routing-utils@1.5.0
 - @now/static-build@0.14.1
2020-01-03 12:39:08 -05:00
JJ Kasper
d90892dc9c Publish Stable
- @now/next@2.3.4
2020-01-03 08:45:34 -06:00
Tim Neutkens
60d2f8b96c Publish Canary
- @now/next@2.3.4-canary.1
2020-01-03 12:36:15 +01:00
JJ Kasper
2488adf80d [now-next] Add support for experimental basePath (#3478)
* Add support for experimental basePath

* Update base-path fixture

* Update type

* Update basePath route source and add invariants
2020-01-03 12:32:20 +01:00
Steven
9deb5b31d2 Publish Canary
- now@16.7.1-canary.5
2020-01-02 19:32:01 -05:00
Steven
ae55823c3c Publish Canary
- @now/frameworks@0.0.2-canary.3
 - @now/build-utils@1.2.1-canary.7
 - @now/static-build@0.14.1-canary.8
2020-01-02 18:09:49 -05:00
Andy
d3395553fe [frameworks][now-static-build] Add support for Scully (#3469)
Add support for Scully.
2020-01-02 22:25:57 +00:00
JJ Kasper
e742dd3a48 Publish Canary
- @now/next@2.3.4-canary.0
2020-01-02 13:24:06 -06:00
JJ Kasper
4f0f44e746 Publish Stable
- @now/next@2.3.3
2020-01-02 13:18:38 -06:00
JJ Kasper
0da98a7f5d Revert "[now-next] Implement handle: miss for custom-routes (#3456)" (#3488)
This reverts commit 40bbff9bee.
2020-01-02 13:15:16 -06:00
Steven
685989ae57 Publish Canary
- @now/build-utils@1.2.1-canary.6
 - @now/next@2.3.3-canary.0
 - @now/node@1.3.1-canary.1
 - @now/static-build@0.14.1-canary.7
2020-01-02 12:42:36 -05:00
Steven
6bc121e7b1 [now-build-utils] Add support for getLatestNodeVersion() and config.nodeVersion (#3483)
This PR does a few things:

1. Add functions `getLatestNodeVersion()` and `getOldestNodeVersion()` for use in api-project.
2. Add property `config.nodeVersion` which has precedence over default Node 8.

We want new projects to use the latest node version without setting any configuration but we don't want to break old projects. So during project creation, the value of `getLatestNodeVersion()` will be saved and then each deployment of that project will assign that value to `config.nodeVersion`. 

Implements [PRODUCT-837]

[PRODUCT-837]: https://zeit.atlassian.net/browse/PRODUCT-837
2020-01-02 17:34:31 +00:00
83 changed files with 2172 additions and 348 deletions

View File

@@ -12,6 +12,17 @@
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"next\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`next build` or `build` from `package.json`"
},
"devCommand": {
"value": "next dev --port $PORT"
},
"outputDirectory": {
"placeholder": "Next.js default"
}
}
},
{
@@ -27,6 +38,17 @@
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"gatsby\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`gatsby build` or `build` from `package.json`"
},
"devCommand": {
"value": "gatsby develop --port $PORT"
},
"outputDirectory": {
"value": "public"
}
}
},
{
@@ -41,6 +63,17 @@
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"hexo\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`hexo generate` or `build` from `package.json`"
},
"devCommand": {
"value": "hexo server --port $PORT"
},
"outputDirectory": {
"value": "public"
}
}
},
{
@@ -55,6 +88,17 @@
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@11ty\\/eleventy\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`npx @11ty/eleventy` or `build` from `package.json`"
},
"devCommand": {
"value": "npx @11ty/eleventy --serve --watch --port $PORT"
},
"outputDirectory": {
"value": "_site"
}
}
},
{
@@ -73,6 +117,17 @@
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@docusaurus\\/core\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`docusaurus-build` or `build` from `package.json`"
},
"devCommand": {
"value": "docusaurus-start --port $PORT"
},
"outputDirectory": {
"value": "build"
}
}
},
{
@@ -88,6 +143,17 @@
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"preact-cli\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`preact build` or `build` from `package.json`"
},
"devCommand": {
"value": "preact watch --port $PORT"
},
"outputDirectory": {
"value": "build"
}
}
},
{
@@ -102,6 +168,17 @@
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"ember-cli\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`ember build` or `build` from `package.json`"
},
"devCommand": {
"value": "ember serve --port $PORT"
},
"outputDirectory": {
"value": "dist"
}
}
},
{
@@ -117,6 +194,41 @@
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@vue\\/cli-service\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`vue-cli-service build` or `build` from `package.json`"
},
"devCommand": {
"value": "vue-cli-service serve --port $PORT"
},
"outputDirectory": {
"value": "dist"
}
}
},
{
"name": "Scully",
"slug": "scully",
"tagline": "Scully is a static site generator for Angular.",
"detectors": {
"every": [
{
"file": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@scullyio\\/init\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`ng build && scully` or `build` from `package.json`"
},
"devCommand": {
"value": "ng serve --port $PORT"
},
"outputDirectory": {
"value": "dist"
}
}
},
{
@@ -132,6 +244,17 @@
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@angular\\/cli\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`ng build` or `build` from `package.json`"
},
"devCommand": {
"value": "ng serve --port $PORT"
},
"outputDirectory": {
"value": "dist"
}
}
},
{
@@ -146,6 +269,17 @@
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"polymer-cli\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`polymer build` or `build` from `package.json`"
},
"devCommand": {
"value": "polymer serve --port $PORT"
},
"outputDirectory": {
"value": "build"
}
}
},
{
@@ -161,6 +295,17 @@
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"sirv-cli\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`rollup -c` or `build` from `package.json`"
},
"devCommand": {
"value": "sirv public --single --dev --port $PORT"
},
"outputDirectory": {
"value": "public"
}
}
},
{
@@ -180,6 +325,17 @@
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"react-dev-utils\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`react-scripts build` or `build` from `package.json`"
},
"devCommand": {
"value": "react-scripts start"
},
"outputDirectory": {
"value": "build"
}
}
},
{
@@ -194,6 +350,17 @@
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"gridsome\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`gridsome build` or `build` from `package.json`"
},
"devCommand": {
"value": "gridsome develop -p $PORT"
},
"outputDirectory": {
"value": "dist"
}
}
},
{
@@ -209,6 +376,17 @@
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"umi\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`umi build` or `build` from `package.json`"
},
"devCommand": {
"value": "umi dev --port $PORT"
},
"outputDirectory": {
"value": "dist"
}
}
},
{
@@ -224,6 +402,17 @@
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"sapper\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`sapper export` or `build` from `package.json`"
},
"devCommand": {
"value": "sapper dev --port $PORT"
},
"outputDirectory": {
"value": "__sapper__/export"
}
}
},
{
@@ -238,6 +427,17 @@
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"saber\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`saber build` or `build` from `package.json`"
},
"devCommand": {
"value": "saber --port $PORT"
},
"outputDirectory": {
"value": "public"
}
}
},
{
@@ -252,6 +452,17 @@
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@stencil\\/core\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`stencil build` or `build` from `package.json`"
},
"devCommand": {
"value": "stencil build --dev --watch --serve --port $PORT"
},
"outputDirectory": {
"value": "www"
}
}
},
{
@@ -267,6 +478,17 @@
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"nuxt\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`nuxt build` or `build` from `package.json`"
},
"devCommand": {
"value": "nuxt"
},
"outputDirectory": {
"value": "dist"
}
}
},
{
@@ -287,6 +509,17 @@
"file": "config.json"
}
]
},
"settings": {
"buildCommand": {
"value": "hugo"
},
"devCommand": {
"value": "hugo server -D -w -p $PORT"
},
"outputDirectory": {
"placeholder": "`public` or `publishDir` from the `config` file"
}
}
},
{
@@ -300,6 +533,17 @@
"file": "_config.yml"
}
]
},
"settings": {
"buildCommand": {
"value": "jekyll build"
},
"devCommand": {
"value": "bundle exec jekyll serve --watch --port $PORT"
},
"outputDirectory": {
"placeholder": "`_site` or `destination` from `_config.yml`"
}
}
},
{
@@ -313,6 +557,17 @@
"file": "brunch-config.js"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`brunch build --production` or `build` from `package.json`"
},
"devCommand": {
"value": "brunch watch --server --port $PORT"
},
"outputDirectory": {
"value": "public"
}
}
},
{
@@ -326,6 +581,17 @@
"file": "config.rb"
}
]
},
"settings": {
"buildCommand": {
"value": "bundle exec middleman build"
},
"devCommand": {
"value": "bundle exec middleman server -p $PORT"
},
"outputDirectory": {
"value": "build"
}
}
},
{

View File

@@ -3,6 +3,11 @@ export interface FrameworkDetectionItem {
matchContent?: string;
}
interface Setting {
value?: string;
placeholder?: string;
}
export interface Framework {
name: string;
slug: string;
@@ -13,4 +18,9 @@ export interface Framework {
every?: FrameworkDetectionItem[];
some?: FrameworkDetectionItem[];
};
settings?: {
buildCommand?: Setting;
devCommand?: Setting;
outputDirectory?: Setting;
};
}

View File

@@ -1,6 +1,6 @@
{
"name": "@now/frameworks",
"version": "0.0.2-canary.2",
"version": "0.0.3",
"main": "frameworks.json",
"license": "UNLICENSED"
}

View File

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

View File

@@ -1,5 +1,5 @@
import { parse as parsePath } from 'path';
import { Route, isHandler } from '@now/routing-utils';
import { Route, Source } from '@now/routing-utils';
import { Builder } from './types';
import { getIgnoreApiFilter, sortFiles } from './detect-builders';
@@ -41,20 +41,26 @@ function getSegmentName(segment: string): string | null {
return null;
}
function createRouteFromPath(filePath: string): Route {
function createRouteFromPath(
filePath: string,
featHandleMiss: boolean,
cleanUrls: boolean
): { route: Source; isDynamic: boolean } {
const parts = filePath.split('/');
let counter = 1;
const query: string[] = [];
let isDynamic = false;
const srcParts = parts.map((segment, index): string => {
const srcParts = parts.map((segment, i): string => {
const name = getSegmentName(segment);
const isLast = index === parts.length - 1;
const isLast = i === parts.length - 1;
if (name !== null) {
// We can't use `URLSearchParams` because `$` would get escaped
query.push(`${name}=$${counter++}`);
return `([^\\/]+)`;
isDynamic = true;
return `([^/]+)`;
} else if (isLast) {
const { name: fileName, ext } = parsePath(segment);
const isIndex = fileName === 'index';
@@ -63,27 +69,43 @@ function createRouteFromPath(filePath: string): Route {
const names = [
isIndex ? prefix : `${fileName}\\/`,
prefix + escapeName(fileName),
prefix + escapeName(fileName) + escapeName(ext),
featHandleMiss && cleanUrls
? ''
: prefix + escapeName(fileName) + escapeName(ext),
].filter(Boolean);
// Either filename with extension, filename without extension
// or nothing when the filename is `index`
// or nothing when the filename is `index`.
// When `cleanUrls: true` then do *not* add the filename with extension.
return `(${names.join('|')})${isIndex ? '?' : ''}`;
}
return segment;
});
const { name: fileName } = parsePath(filePath);
const { name: fileName, ext } = parsePath(filePath);
const isIndex = fileName === 'index';
const queryString = `${query.length ? '?' : ''}${query.join('&')}`;
const src = isIndex
? `^/${srcParts.slice(0, -1).join('/')}${srcParts.slice(-1)[0]}$`
: `^/${srcParts.join('/')}$`;
const dest = `/${filePath}${query.length ? '?' : ''}${query.join('&')}`;
return { src, dest };
let route: Source;
if (featHandleMiss) {
const extensionless = ext ? filePath.slice(0, -ext.length) : filePath;
route = {
src,
dest: `/${extensionless}${queryString}`,
check: true,
};
} else {
route = {
src,
dest: `/${filePath}${queryString}`,
};
}
return { route, isDynamic };
}
// Check if the path partially matches and has the same
@@ -193,18 +215,30 @@ function sortFilesBySegmentCount(fileA: string, fileB: string): number {
return 0;
}
interface ApiRoutesResult {
defaultRoutes: Source[] | null;
dynamicRoutes: Source[] | null;
error: { [key: string]: string } | null;
}
interface RoutesResult {
defaultRoutes: Route[] | null;
redirectRoutes: Route[] | null;
error: { [key: string]: string } | null;
}
async function detectApiRoutes(
files: string[],
builders: Builder[],
featHandleMiss: boolean
): Promise<RoutesResult> {
featHandleMiss: boolean,
cleanUrls: boolean
): Promise<ApiRoutesResult> {
if (!files || files.length === 0) {
return { defaultRoutes: null, error: null };
return {
defaultRoutes: null,
dynamicRoutes: null,
error: null,
};
}
// The deepest routes need to be
@@ -214,7 +248,8 @@ async function detectApiRoutes(
.sort(sortFiles)
.sort(sortFilesBySegmentCount);
let defaultRoutes: Route[] = [];
const defaultRoutes: Source[] = [];
const dynamicRoutes: Source[] = [];
for (const file of sortedFiles) {
// We only consider every file in the api directory
@@ -231,6 +266,7 @@ async function detectApiRoutes(
if (conflictingSegment) {
return {
defaultRoutes: null,
dynamicRoutes: null,
error: {
code: 'conflicting_path_segment',
message:
@@ -252,6 +288,7 @@ async function detectApiRoutes(
return {
defaultRoutes: null,
dynamicRoutes: null,
error: {
code: 'conflicting_file_path',
message:
@@ -262,39 +299,14 @@ async function detectApiRoutes(
};
}
defaultRoutes.push(createRouteFromPath(file));
}
// 404 Route to disable directory listing
if (defaultRoutes.length > 0) {
if (featHandleMiss) {
defaultRoutes = [
{ handle: 'miss' },
{
src: '/api/(.+)\\.\\w+',
dest: '/api/$1',
check: true,
},
{
status: 404,
src: '/api(/.*)?$',
continue: true,
},
];
} else if (
defaultRoutes.some(
route =>
!isHandler(route) && route.dest && route.dest.startsWith('/api/')
)
) {
defaultRoutes.push({
status: 404,
src: '/api(/.*)?$',
});
const out = createRouteFromPath(file, featHandleMiss, cleanUrls);
if (out.isDynamic) {
dynamicRoutes.push(out.route);
}
defaultRoutes.push(out.route);
}
return { defaultRoutes, error: null };
return { defaultRoutes, dynamicRoutes, error: null };
}
function getPublicBuilder(builders: Builder[]): Builder | null {
@@ -319,17 +331,81 @@ export function detectOutputDirectory(builders: Builder[]): string | null {
export async function detectRoutes(
files: string[],
builders: Builder[],
featHandleMiss = false
featHandleMiss = false,
cleanUrls = false,
trailingSlash?: boolean
): Promise<RoutesResult> {
const routesResult = await detectApiRoutes(files, builders, featHandleMiss);
const result = await detectApiRoutes(
files,
builders,
featHandleMiss,
cleanUrls
);
const { dynamicRoutes, defaultRoutes: allRoutes, error } = result;
if (error) {
return { defaultRoutes: null, redirectRoutes: null, error };
}
const directory = detectOutputDirectory(builders);
const defaultRoutes: Route[] = [];
const redirectRoutes: Route[] = [];
if (allRoutes && allRoutes.length > 0) {
const hasApiRoutes = allRoutes.some(
r => r.dest && r.dest.startsWith('/api/')
);
if (featHandleMiss) {
defaultRoutes.push({ handle: 'miss' });
if (cleanUrls) {
const extensions = builders
.map(b => parsePath(b.src).ext)
.filter(Boolean);
if (extensions.length > 0) {
const exts = extensions.map(ext => ext.slice(1)).join('|');
const group = `(?:\\.(?:${exts}))`;
redirectRoutes.push({
src: `^/(api(?:.+)?)/index${group}?/?$`,
headers: { Location: trailingSlash ? '/$1/' : '/$1' },
status: 308,
});
redirectRoutes.push({
src: `^/api/(.+)${group}/?$`,
headers: { Location: trailingSlash ? '/api/$1/' : '/api/$1' },
status: 308,
});
}
} else {
defaultRoutes.push({
src: '^/api/(.+)\\.\\w+$',
dest: '/api/$1',
check: true,
});
}
if (dynamicRoutes) {
defaultRoutes.push(...dynamicRoutes);
}
if (hasApiRoutes) {
defaultRoutes.push({
src: '^/api(/.*)?$',
status: 404,
continue: true,
});
}
} else {
defaultRoutes.push(...allRoutes);
if (hasApiRoutes) {
defaultRoutes.push({
status: 404,
src: '^/api(/.*)?$',
});
}
}
}
if (routesResult.defaultRoutes && directory && !featHandleMiss) {
routesResult.defaultRoutes.push({
if (!featHandleMiss && directory) {
defaultRoutes.push({
src: '/(.*)',
dest: `/${directory}/$1`,
});
}
return routesResult;
return { defaultRoutes, redirectRoutes, error };
}

View File

@@ -0,0 +1,18 @@
/**
* This error should be thrown from a Builder in
* order to stop the build and print a message.
* This is necessary to avoid printing a stack trace.
*/
export class NowBuildError extends Error {
public code: string;
constructor({ message, code }: Props) {
super(message);
this.code = code;
}
}
interface Props {
message: string;
code: string;
}

View File

@@ -1,6 +1,7 @@
import { intersects } from 'semver';
import boxen from 'boxen';
import { NodeVersion } from '../types';
import { NowBuildError } from '../errors';
import debug from '../debug';
const allOptions: NodeVersion[] = [
@@ -14,87 +15,90 @@ const allOptions: NodeVersion[] = [
},
];
const supportedOptions = allOptions.filter(o => !isDiscontinued(o));
const pleaseSet =
'Please set "engines": { "node": "' +
getLatestNodeVersion().range +
'" } in your `package.json` file to upgrade to Node.js ' +
getLatestNodeVersion().major;
const upstreamProvider =
'This change is the result of a decision made by an upstream infrastructure provider (AWS).' +
'\nRead more: https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html';
// This version should match Fargate's default in the PATH
// Today that is Node 8
export const defaultSelection = supportedOptions.find(
o => o.major === 8
) as NodeVersion;
export function getOldestNodeVersion(): NodeVersion {
return allOptions[allOptions.length - 1];
}
export function getLatestNodeVersion(): NodeVersion {
return allOptions[0];
}
export async function getSupportedNodeVersion(
engineRange?: string,
silent?: boolean
isAuto?: boolean
): Promise<NodeVersion> {
let selection = defaultSelection;
let selection = getOldestNodeVersion();
if (!engineRange) {
if (!silent) {
debug(
'Missing `engines` in `package.json`, using default range: ' +
selection.range
);
}
} else {
if (engineRange) {
const found = allOptions.some(o => {
// the array is already in order so return the first
// match which will be the newest version of node
selection = o;
return intersects(o.range, engineRange);
});
const discontinued = isDiscontinued(selection);
if (found && !discontinued) {
if (!silent) {
debug(
'Found `engines` in `package.json`, selecting range: ' +
selection.range
);
}
} else {
throw new Error(
'Found `engines` in `package.json` with an unsupported Node.js version range: ' +
engineRange +
'\nPlease use one of the following supported ranges: ' +
JSON.stringify(supportedOptions.map(o => o.range)) +
(discontinued
? '\nThis change is the result of a decision made by an upstream infrastructure provider (AWS).' +
'\nRead more: https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html'
: '')
);
if (!found) {
const intro =
isAuto || !engineRange
? 'This project is using an invalid version of Node.js and must be changed.'
: 'Found `engines` in `package.json` with an invalid Node.js version range: ' +
engineRange;
throw new NowBuildError({
code: 'NOW_BUILD_UTILS_NODE_VERSION_INVALID',
message: intro + '\n' + pleaseSet,
});
}
}
const { range, discontinueDate } = selection;
if (discontinueDate && !isDiscontinued(selection)) {
const d = discontinueDate.toISOString().split('T')[0];
const validRanges = supportedOptions
.filter(o => !o.discontinueDate)
.map(o => o.range);
const prevTerm = process.env.TERM;
if (!prevTerm) {
// workaround for https://github.com/sindresorhus/term-size/issues/13
process.env.TERM = 'xterm';
}
if (isDiscontinued(selection)) {
const intro =
isAuto || !engineRange
? 'This project is using a discontinued version of Node.js and must be upgraded.'
: 'Found `engines` in `package.json` with a discontinued Node.js version range: ' +
engineRange;
throw new NowBuildError({
code: 'NOW_BUILD_UTILS_NODE_VERSION_DISCONTINUED',
message: intro + '\n' + pleaseSet + '\n' + upstreamProvider,
});
}
debug(
isAuto || !engineRange
? 'Using default Node.js range: ' + selection.range
: (engineRange ? 'Found' : 'Missing') +
' `engines` in `package.json`, selecting range: ' +
selection.range
);
if (selection.discontinueDate) {
const d = selection.discontinueDate.toISOString().split('T')[0];
console.warn(
boxen(
'NOTICE' +
'\n' +
`\nNode.js version ${range} has reached end-of-life.` +
`\nNode.js version ${selection.range} has reached end-of-life.` +
`\nAs a result, deployments created on or after ${d} will fail to build.` +
'\nPlease use one of the following supported `engines` in `package.json`: ' +
JSON.stringify(validRanges) +
'\nThis change is the result of a decision made by an upstream infrastructure provider (AWS).' +
'\nRead more: https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html',
'\n' +
pleaseSet +
'\n' +
upstreamProvider,
{ padding: 1 }
)
);
process.env.TERM = prevTerm;
}
return selection;
}
function isDiscontinued({ discontinueDate }: NodeVersion): boolean {
const today = new Date();
return discontinueDate !== undefined && discontinueDate <= today;
const today = Date.now();
return discontinueDate !== undefined && discontinueDate.getTime() <= today;
}

View File

@@ -7,7 +7,7 @@ import { SpawnOptions } from 'child_process';
import { deprecate } from 'util';
import { cpus } from 'os';
import { Meta, PackageJson, NodeVersion, Config } from '../types';
import { getSupportedNodeVersion } from './node-version';
import { getSupportedNodeVersion, getLatestNodeVersion } from './node-version';
export function spawnAsync(
command: string,
@@ -140,22 +140,30 @@ export function getSpawnOptions(
export async function getNodeVersion(
destPath: string,
minNodeVersion?: string,
config?: Config
config?: Config,
meta?: Meta
): Promise<NodeVersion> {
if (meta && meta.isDev) {
// Use the system-installed version of `node` in PATH for `now dev`
const latest = getLatestNodeVersion();
return { ...latest, runtime: 'nodejs' };
}
const { packageJson } = await scanParentDirs(destPath, true);
let range: string | undefined;
let silent = false;
let isAuto = false;
if (packageJson && packageJson.engines && packageJson.engines.node) {
range = packageJson.engines.node;
} else if (minNodeVersion) {
range = minNodeVersion;
silent = true;
isAuto = true;
} else if (config && config.nodeVersion) {
range = config.nodeVersion;
isAuto = true;
} else if (config && config.zeroConfig) {
// Use latest node version zero config detected
range = '10.x';
silent = true;
isAuto = true;
}
return getSupportedNodeVersion(range, silent);
return getSupportedNodeVersion(range, isAuto);
}
async function scanParentDirs(destPath: string, readPackageJson = false) {

View File

@@ -21,6 +21,7 @@ import {
getNodeVersion,
getSpawnOptions,
} from './fs/run-user-scripts';
import { getLatestNodeVersion } from './fs/node-version';
import streamToBuffer from './fs/stream-to-buffer';
import shouldServe from './should-serve';
import debug from './debug';
@@ -48,6 +49,7 @@ export {
runPipInstall,
runShellScript,
getNodeVersion,
getLatestNodeVersion,
getSpawnOptions,
streamToBuffer,
shouldServe,
@@ -64,3 +66,4 @@ export { readConfigFile } from './fs/read-config-file';
export * from './schemas';
export * from './types';
export * from './errors';

View File

@@ -43,6 +43,7 @@ export interface Config {
buildCommand?: string;
devCommand?: string;
framework?: string;
nodeVersion?: string;
}
export interface Meta {

View File

@@ -1,3 +1,4 @@
import { Source, Route } from '@now/routing-utils';
import { detectBuilders, detectRoutes } from '../src';
describe('Test `detectBuilders`', () => {
@@ -892,7 +893,7 @@ it('Test `detectRoutes`', async () => {
const { builders } = await detectBuilders(files);
const { defaultRoutes } = await detectRoutes(files, builders!);
expect((defaultRoutes![2] as any).status).toBe(404);
expect((defaultRoutes![2] as any).src).toBe('/api(/.*)?$');
expect((defaultRoutes![2] as any).src).toBe('^/api(/.*)?$');
expect((defaultRoutes![3] as any).src).toBe('/(.*)');
expect((defaultRoutes![3] as any).dest).toBe('/public/$1');
expect(defaultRoutes!.length).toBe(4);
@@ -908,7 +909,7 @@ it('Test `detectRoutes`', async () => {
const { builders } = await detectBuilders(files, pkg);
const { defaultRoutes } = await detectRoutes(files, builders!);
expect((defaultRoutes![1] as any).status).toBe(404);
expect((defaultRoutes![1] as any).src).toBe('/api(/.*)?$');
expect((defaultRoutes![1] as any).src).toBe('^/api(/.*)?$');
expect(defaultRoutes!.length).toBe(2);
}
@@ -946,7 +947,7 @@ it('Test `detectRoutes`', async () => {
expect(defaultRoutes!.length).toBe(3);
expect((defaultRoutes![0] as any).src).toBe(
'^/api/([^\\/]+)(\\/|\\/index|\\/index\\.js)?$'
'^/api/([^/]+)(\\/|\\/index|\\/index\\.js)?$'
);
expect((defaultRoutes![0] as any).dest).toBe(
'/api/[date]/index.js?date=$1'
@@ -993,20 +994,6 @@ it('Test `detectRoutes`', async () => {
it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
const featHandleMiss = true;
const expectedRoutes = [
{ handle: 'miss' },
{
src: '/api/(.+)\\.\\w+',
dest: '/api/$1',
check: true,
},
{
status: 404,
src: '/api(/.*)?$',
continue: true,
},
];
{
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
@@ -1016,7 +1003,19 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
builders!,
featHandleMiss
);
expect(defaultRoutes).toStrictEqual(expectedRoutes);
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
src: '^/api/(.+)\\.\\w+$',
dest: '/api/$1',
check: true,
},
{
status: 404,
src: '^/api(/.*)?$',
continue: true,
},
]);
}
{
@@ -1065,7 +1064,29 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
builders!,
featHandleMiss
);
expect(defaultRoutes).toStrictEqual(expectedRoutes);
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
src: '^/api/(.+)\\.\\w+$',
dest: '/api/$1',
check: true,
},
{
src: '^/api/([^/]+)/([^/]+)$',
dest: '/api/[endpoint]/[id]?endpoint=$1&id=$2',
check: true,
},
{
src: '^/api/([^/]+)$',
dest: '/api/[endpoint]?endpoint=$1',
check: true,
},
{
status: 404,
src: '^/api(/.*)?$',
continue: true,
},
]);
}
{
@@ -1081,7 +1102,29 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
builders!,
featHandleMiss
);
expect(defaultRoutes).toStrictEqual(expectedRoutes);
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
src: '^/api/(.+)\\.\\w+$',
dest: '/api/$1',
check: true,
},
{
src: '^/api/([^/]+)/([^/]+)$',
dest: '/api/[endpoint]/[id]?endpoint=$1&id=$2',
check: true,
},
{
src: '^/api/([^/]+)$',
dest: '/api/[endpoint]?endpoint=$1',
check: true,
},
{
status: 404,
src: '^/api(/.*)?$',
continue: true,
},
]);
}
{
@@ -1103,7 +1146,24 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
builders!,
featHandleMiss
);
expect(defaultRoutes).toStrictEqual(expectedRoutes);
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
src: '^/api/(.+)\\.\\w+$',
dest: '/api/$1',
check: true,
},
{
src: '^/api/([^/]+)$',
dest: '/api/[endpoint]?endpoint=$1',
check: true,
},
{
status: 404,
src: '^/api(/.*)?$',
continue: true,
},
]);
}
{
@@ -1127,7 +1187,19 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
builders!,
featHandleMiss
);
expect(defaultRoutes).toStrictEqual(expectedRoutes);
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
src: '^/api/(.+)\\.\\w+$',
dest: '/api/$1',
check: true,
},
{
status: 404,
src: '^/api(/.*)?$',
continue: true,
},
]);
}
{
@@ -1139,7 +1211,24 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
builders!,
featHandleMiss
);
expect(defaultRoutes).toStrictEqual(expectedRoutes);
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
src: '^/api/(.+)\\.\\w+$',
dest: '/api/$1',
check: true,
},
{
src: '^/api/([^/]+)(\\/|\\/index|\\/index\\.js)?$',
dest: '/api/[date]/index?date=$1',
check: true,
},
{
status: 404,
src: '^/api(/.*)?$',
continue: true,
},
]);
}
{
@@ -1157,7 +1246,16 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
builders!,
featHandleMiss
);
expect(defaultRoutes).toStrictEqual(expectedRoutes);
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
src: '^/api/(.+)\\.\\w+$',
dest: '/api/$1',
check: true,
},
{ status: 404, src: '^/api(/.*)?$', continue: true },
]);
}
{
@@ -1171,6 +1269,611 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
builders!,
featHandleMiss
);
expect(defaultRoutes).toStrictEqual(expectedRoutes);
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
src: '^/api/(.+)\\.\\w+$',
dest: '/api/$1',
check: true,
},
{ status: 404, src: '^/api(/.*)?$', continue: true },
]);
}
});
it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async () => {
const featHandleMiss = true;
const cleanUrls = true;
const testHeaders = (redirectRoutes: Route[] | null) => {
if (!redirectRoutes || redirectRoutes.length === 0) {
throw new Error('Expected one redirect but found none');
}
expect(redirectRoutes).toBeDefined();
expect(redirectRoutes.length).toBe(2);
};
{
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
const { builders } = await detectBuilders(files);
const { defaultRoutes, redirectRoutes } = await detectRoutes(
files,
builders!,
featHandleMiss,
cleanUrls
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
status: 404,
src: '^/api(/.*)?$',
continue: true,
},
]);
// expected redirect should match inputs
const getLocation = createReplaceLocation(redirectRoutes);
expect(getLocation('/api/index')).toBe('/api');
expect(getLocation('/api/index.js')).toBe('/api');
expect(getLocation('/api/user.js')).toBe('/api/user');
expect(getLocation('/api/user.prod.js')).toBe('/api/user.prod');
expect(getLocation('/api/user/index.js')).toBe('/api/user');
expect(getLocation('/api/index.go')).toBe('/api');
expect(getLocation('/api/user.go')).toBe('/api/user');
expect(getLocation('/api/user.prod.go')).toBe('/api/user.prod');
expect(getLocation('/api/user/index.go')).toBe('/api/user');
expect(getLocation('/api/index.cpp')).toBe(null);
expect(getLocation('/api/user.cpp')).toBe(null);
expect(getLocation('/api/user.prod.cpp')).toBe(null);
expect(getLocation('/api/user/index.cpp')).toBe(null);
expect(getLocation('/api/user')).toBe(null);
expect(getLocation('/api/user/get')).toBe(null);
expect(getLocation('/apiindex')).toBe(null);
expect(getLocation('/api-index')).toBe(null);
expect(getLocation('/apiuserindex')).toBe(null);
expect(getLocation('/apiuser-index')).toBe(null);
}
{
const files = ['api/user.go', 'api/user.js'];
const { builders } = await detectBuilders(files);
const { error } = await detectRoutes(
files,
builders!,
featHandleMiss,
cleanUrls
);
expect(error!.code).toBe('conflicting_file_path');
}
{
const files = ['api/[user].go', 'api/[team]/[id].js'];
const { builders } = await detectBuilders(files);
const { error } = await detectRoutes(
files,
builders!,
featHandleMiss,
cleanUrls
);
expect(error!.code).toBe('conflicting_file_path');
}
{
const files = ['api/[team]/[team].js'];
const { builders } = await detectBuilders(files);
const { error } = await detectRoutes(
files,
builders!,
featHandleMiss,
cleanUrls
);
expect(error!.code).toBe('conflicting_path_segment');
}
{
const files = ['api/date/index.js', 'api/date/index.go'];
const { builders } = await detectBuilders(files);
const { defaultRoutes, error } = await detectRoutes(
files,
builders!,
featHandleMiss,
cleanUrls
);
expect(defaultRoutes).toBe(null);
expect(error!.code).toBe('conflicting_file_path');
}
{
const files = ['api/[endpoint].js', 'api/[endpoint]/[id].js'];
const { builders } = await detectBuilders(files);
const { defaultRoutes, redirectRoutes } = await detectRoutes(
files,
builders!,
featHandleMiss,
cleanUrls
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
src: '^/api/([^/]+)/([^/]+)$',
dest: '/api/[endpoint]/[id]?endpoint=$1&id=$2',
check: true,
},
{
src: '^/api/([^/]+)$',
dest: '/api/[endpoint]?endpoint=$1',
check: true,
},
{
status: 404,
src: '^/api(/.*)?$',
continue: true,
},
]);
}
{
const files = [
'public/index.html',
'api/[endpoint].js',
'api/[endpoint]/[id].js',
];
const { builders } = await detectBuilders(files);
const { defaultRoutes, redirectRoutes } = await detectRoutes(
files,
builders!,
featHandleMiss,
cleanUrls
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
src: '^/api/([^/]+)/([^/]+)$',
dest: '/api/[endpoint]/[id]?endpoint=$1&id=$2',
check: true,
},
{
src: '^/api/([^/]+)$',
dest: '/api/[endpoint]?endpoint=$1',
check: true,
},
{
status: 404,
src: '^/api(/.*)?$',
continue: true,
},
]);
}
{
const pkg = {
scripts: {
build: 'next build',
},
framework: {
slug: 'next',
version: '9.0.0',
},
};
const files = ['public/index.html', 'api/[endpoint].js'];
const { builders } = await detectBuilders(files, pkg);
const { defaultRoutes, redirectRoutes } = await detectRoutes(
files,
builders!,
featHandleMiss,
cleanUrls
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
src: '^/api/([^/]+)$',
dest: '/api/[endpoint]?endpoint=$1',
check: true,
},
{
status: 404,
src: '^/api(/.*)?$',
continue: true,
},
]);
}
{
const files = ['public/index.html'];
const { builders } = await detectBuilders(files);
const { defaultRoutes } = await detectRoutes(
files,
builders!,
featHandleMiss,
cleanUrls
);
expect(defaultRoutes).toStrictEqual([]);
}
{
const files = ['api/date/index.js', 'api/date.js'];
const { builders } = await detectBuilders(files);
const { defaultRoutes, redirectRoutes } = await detectRoutes(
files,
builders!,
featHandleMiss,
cleanUrls
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
status: 404,
src: '^/api(/.*)?$',
continue: true,
},
]);
}
{
const files = ['api/date.js', 'api/[date]/index.js'];
const { builders } = await detectBuilders(files);
const { defaultRoutes, redirectRoutes } = await detectRoutes(
files,
builders!,
featHandleMiss,
cleanUrls
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
src: '^/api/([^/]+)(\\/|\\/index)?$',
dest: '/api/[date]/index?date=$1',
check: true,
},
{
status: 404,
src: '^/api(/.*)?$',
continue: true,
},
]);
}
{
const files = [
'api/index.ts',
'api/index.d.ts',
'api/users/index.ts',
'api/users/index.d.ts',
'api/food.ts',
'api/ts/gold.ts',
];
const { builders } = await detectBuilders(files);
const { defaultRoutes, redirectRoutes } = await detectRoutes(
files,
builders!,
featHandleMiss,
cleanUrls
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{ status: 404, src: '^/api(/.*)?$', continue: true },
]);
}
{
// use a custom runtime
const functions = { 'api/user.php': { runtime: 'now-php@0.0.5' } };
const files = ['api/user.php'];
const { builders } = await detectBuilders(files, null, { functions });
const { defaultRoutes, redirectRoutes } = await detectRoutes(
files,
builders!,
featHandleMiss,
cleanUrls
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{ status: 404, src: '^/api(/.*)?$', continue: true },
]);
}
});
it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingSlash=true`', async () => {
const featHandleMiss = true;
const cleanUrls = true;
const trailingSlash = true;
const testHeaders = (redirectRoutes: Route[] | null) => {
if (!redirectRoutes || redirectRoutes.length === 0) {
throw new Error('Expected one redirect but found none');
}
expect(redirectRoutes).toBeDefined();
expect(redirectRoutes.length).toBe(2);
};
{
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
const { builders } = await detectBuilders(files);
const { defaultRoutes, redirectRoutes } = await detectRoutes(
files,
builders!,
featHandleMiss,
cleanUrls,
trailingSlash
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
status: 404,
src: '^/api(/.*)?$',
continue: true,
},
]);
// expected redirect should match inputs
const getLocation = createReplaceLocation(redirectRoutes);
expect(getLocation('/api/index')).toBe('/api/');
expect(getLocation('/api/index.js')).toBe('/api/');
expect(getLocation('/api/user.js')).toBe('/api/user/');
expect(getLocation('/api/user.prod.js')).toBe('/api/user.prod/');
expect(getLocation('/api/user/index.js')).toBe('/api/user/');
expect(getLocation('/api/index.go')).toBe('/api/');
expect(getLocation('/api/user.go')).toBe('/api/user/');
expect(getLocation('/api/user.prod.go')).toBe('/api/user.prod/');
expect(getLocation('/api/user/index.go')).toBe('/api/user/');
expect(getLocation('/api/index.cpp')).toBe(null);
expect(getLocation('/api/user.cpp')).toBe(null);
expect(getLocation('/api/user.prod.cpp')).toBe(null);
expect(getLocation('/api/user/index.cpp')).toBe(null);
expect(getLocation('/api/user')).toBe(null);
expect(getLocation('/api/user/get')).toBe(null);
expect(getLocation('/apiindex')).toBe(null);
expect(getLocation('/api.index')).toBe(null);
expect(getLocation('/api.index.js')).toBe(null);
expect(getLocation('/api-index')).toBe(null);
expect(getLocation('/apiuser.index')).toBe(null);
expect(getLocation('/apiuser-index')).toBe(null);
expect(getLocation('/apiuser-index')).toBe(null);
}
{
const files = ['api/[endpoint].js', 'api/[endpoint]/[id].js'];
const { builders } = await detectBuilders(files);
const { defaultRoutes, redirectRoutes } = await detectRoutes(
files,
builders!,
featHandleMiss,
cleanUrls,
trailingSlash
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
src: '^/api/([^/]+)/([^/]+)$',
dest: '/api/[endpoint]/[id]?endpoint=$1&id=$2',
check: true,
},
{
src: '^/api/([^/]+)$',
dest: '/api/[endpoint]?endpoint=$1',
check: true,
},
{
status: 404,
src: '^/api(/.*)?$',
continue: true,
},
]);
}
{
const files = [
'public/index.html',
'api/[endpoint].js',
'api/[endpoint]/[id].js',
];
const { builders } = await detectBuilders(files);
const { defaultRoutes, redirectRoutes } = await detectRoutes(
files,
builders!,
featHandleMiss,
cleanUrls,
trailingSlash
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
src: '^/api/([^/]+)/([^/]+)$',
dest: '/api/[endpoint]/[id]?endpoint=$1&id=$2',
check: true,
},
{
src: '^/api/([^/]+)$',
dest: '/api/[endpoint]?endpoint=$1',
check: true,
},
{
status: 404,
src: '^/api(/.*)?$',
continue: true,
},
]);
}
{
const pkg = {
scripts: {
build: 'next build',
},
framework: {
slug: 'next',
version: '9.0.0',
},
};
const files = ['public/index.html', 'api/[endpoint].js'];
const { builders } = await detectBuilders(files, pkg);
const { defaultRoutes, redirectRoutes } = await detectRoutes(
files,
builders!,
featHandleMiss,
cleanUrls,
trailingSlash
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
src: '^/api/([^/]+)$',
dest: '/api/[endpoint]?endpoint=$1',
check: true,
},
{
status: 404,
src: '^/api(/.*)?$',
continue: true,
},
]);
}
{
const files = ['api/date/index.js', 'api/date.js'];
const { builders } = await detectBuilders(files);
const { defaultRoutes, redirectRoutes } = await detectRoutes(
files,
builders!,
featHandleMiss,
cleanUrls,
trailingSlash
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
status: 404,
src: '^/api(/.*)?$',
continue: true,
},
]);
}
{
const files = ['api/date.js', 'api/[date]/index.js'];
const { builders } = await detectBuilders(files);
const { defaultRoutes, redirectRoutes } = await detectRoutes(
files,
builders!,
featHandleMiss,
cleanUrls,
trailingSlash
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
src: '^/api/([^/]+)(\\/|\\/index)?$',
dest: '/api/[date]/index?date=$1',
check: true,
},
{
status: 404,
src: '^/api(/.*)?$',
continue: true,
},
]);
}
{
const files = [
'api/index.ts',
'api/index.d.ts',
'api/users/index.ts',
'api/users/index.d.ts',
'api/food.ts',
'api/ts/gold.ts',
];
const { builders } = await detectBuilders(files);
const { defaultRoutes, redirectRoutes } = await detectRoutes(
files,
builders!,
featHandleMiss,
cleanUrls,
trailingSlash
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{ status: 404, src: '^/api(/.*)?$', continue: true },
]);
}
{
// use a custom runtime
const functions = { 'api/user.php': { runtime: 'now-php@0.0.5' } };
const files = ['api/user.php'];
const { builders } = await detectBuilders(files, null, { functions });
const { defaultRoutes, redirectRoutes } = await detectRoutes(
files,
builders!,
featHandleMiss,
cleanUrls,
trailingSlash
);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{ status: 404, src: '^/api(/.*)?$', continue: true },
]);
}
});
/**
* Create a function that will replace matched redirects
* similar to how it works with `now-proxy` in production.
*/
function createReplaceLocation(redirectRoutes: Route[] | null) {
const redirectSources = (redirectRoutes || []) as Source[];
return (filePath: string): string | null => {
for (const r of redirectSources) {
const m = new RegExp(r.src).exec(filePath);
console.log({ filePath, r, m });
if (m && r.headers) {
const match = m[1] || '';
return r.headers['Location'].replace('$1', match);
}
}
return null;
};
}

View File

@@ -125,4 +125,17 @@ describe('#detectFramework', () => {
expect(await detectFramework({ fs, frameworkList })).toBe('middleman');
});
it('Detect Scully', async () => {
const fs = new VirtualFilesystem({
'package.json': JSON.stringify({
dependencies: {
'@angular/cli': 'latest',
'@scullyio/init': 'latest',
},
}),
});
expect(await detectFramework({ fs, frameworkList })).toBe('scully');
});
});

View File

@@ -3,10 +3,8 @@ const fs = require('fs-extra');
const assert = require('assert');
const { createZip } = require('../dist/lambda');
const { glob, spawnAsync, download } = require('../');
const {
getSupportedNodeVersion,
defaultSelection,
} = require('../dist/fs/node-version');
const { getSupportedNodeVersion } = require('../dist/fs/node-version');
const { getNodeVersion, getLatestNodeVersion } = require('../dist');
it('should re-create symlinks properly', async () => {
const files = await glob('**', path.join(__dirname, 'symlinks'));
@@ -49,15 +47,31 @@ it('should create zip files with symlinks properly', async () => {
});
it('should only match supported node versions', async () => {
expect(await getSupportedNodeVersion('10.x')).toHaveProperty('major', 10);
expect(await getSupportedNodeVersion('8.10.x')).toHaveProperty('major', 8);
expect(getSupportedNodeVersion('8.11.x')).rejects.toThrow();
expect(getSupportedNodeVersion('6.x')).rejects.toThrow();
expect(getSupportedNodeVersion('999.x')).rejects.toThrow();
expect(getSupportedNodeVersion('foo')).rejects.toThrow();
expect(await getSupportedNodeVersion('')).toBe(defaultSelection);
expect(await getSupportedNodeVersion(null)).toBe(defaultSelection);
expect(await getSupportedNodeVersion(undefined)).toBe(defaultSelection);
expect(await getSupportedNodeVersion('10.x', false)).toHaveProperty(
'major',
10
);
expect(await getSupportedNodeVersion('12.x', false)).toHaveProperty(
'major',
12
);
expect(getSupportedNodeVersion('8.11.x', false)).rejects.toThrow();
expect(getSupportedNodeVersion('6.x', false)).rejects.toThrow();
expect(getSupportedNodeVersion('999.x', false)).rejects.toThrow();
expect(getSupportedNodeVersion('foo', false)).rejects.toThrow();
expect(await getSupportedNodeVersion('10.x', true)).toHaveProperty(
'major',
10
);
expect(await getSupportedNodeVersion('12.x', true)).toHaveProperty(
'major',
12
);
expect(getSupportedNodeVersion('8.11.x', true)).rejects.toThrow();
expect(getSupportedNodeVersion('6.x', true)).rejects.toThrow();
expect(getSupportedNodeVersion('999.x', true)).rejects.toThrow();
expect(getSupportedNodeVersion('foo', true)).rejects.toThrow();
});
it('should match all semver ranges', async () => {
@@ -78,6 +92,47 @@ it('should match all semver ranges', async () => {
expect(await getSupportedNodeVersion('^10.5.0')).toHaveProperty('major', 10);
});
it('should select correct node version in getNodeVersion()', async () => {
expect(
await getNodeVersion('/tmp', undefined, { nodeVersion: '12.x' })
).toHaveProperty('major', 12);
expect(
await getNodeVersion('/tmp', undefined, { nodeVersion: '10.x' })
).toHaveProperty('major', 10);
expect(
await getNodeVersion('/tmp', '10.x', { nodeVersion: '12.x' })
).toHaveProperty('major', 10);
});
it('should ignore node version in now dev getNodeVersion()', async () => {
expect(
await getNodeVersion(
'/tmp',
undefined,
{ nodeVersion: '1' },
{ isDev: true }
)
).toHaveProperty('runtime', 'nodejs');
});
it('should get latest node version', async () => {
expect(await getLatestNodeVersion()).toHaveProperty('major', 12);
});
it('should throw for discontinued versions', async () => {
// Mock a future date so that Node 8 becomes discontinued
const realDateNow = Date.now.bind(global.Date);
global.Date.now = () => new Date('2020-02-14').getTime();
expect(getSupportedNodeVersion('', false)).rejects.toThrow();
expect(getSupportedNodeVersion('8.10.x', false)).rejects.toThrow();
expect(getSupportedNodeVersion('', true)).rejects.toThrow();
expect(getSupportedNodeVersion('8.10.x', true)).rejects.toThrow();
global.Date.now = realDateNow;
});
it('should support require by path for legacy builders', () => {
const index = require('@now/build-utils');

View File

@@ -6,7 +6,7 @@
To install the latest version of Now CLI, visit [zeit.co/download](https://zeit.co/download) or run this command:
```bash
```sh
npm i -g now
```

View File

@@ -1,6 +1,6 @@
{
"name": "now",
"version": "16.7.1-canary.4",
"version": "16.7.2-canary.3",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Now",

View File

@@ -17,9 +17,7 @@ export default async function dev(
args: string[],
output: Output
) {
output.dim(
`Now CLI ${pkg.version} dev (beta) — https://zeit.co/feedback`
);
output.dim(`Now CLI ${pkg.version} dev (beta) — https://zeit.co/feedback`);
const [dir = '.'] = args;
const cwd = path.resolve(dir);

View File

@@ -18,7 +18,10 @@ import getMinFromArgs from '../util/scale/get-min-from-args';
import patchDeploymentScale from '../util/scale/patch-deployment-scale';
import waitVerifyDeploymentScale from '../util/scale/wait-verify-deployment-scale';
import { handleError } from '../util/error';
import { VerifyScaleTimeout, DeploymentTypeUnsupported } from '../util/errors-ts';
import {
VerifyScaleTimeout,
DeploymentTypeUnsupported,
} from '../util/errors-ts';
import {
DeploymentNotFound,
DeploymentPermissionDenied,
@@ -28,7 +31,7 @@ import {
InvalidMaxForScale,
InvalidMinForScale,
InvalidScaleMinMaxRelation,
NotSupportedMinScaleSlots
NotSupportedMinScaleSlots,
} from '../util/errors-ts';
import { InvalidAllForScale, InvalidRegionOrDCForScale } from '../util/errors';
import handleCertError from '../util/certs/handle-cert-error';
@@ -56,7 +59,9 @@ const help = () => {
${chalk.dim('Examples:')}
${chalk.gray('')} Enable your deployment in all datacenters (min: 0, max: auto)
${chalk.gray(
''
)} Enable your deployment in all datacenters (min: 0, max: auto)
${chalk.cyan('$ now scale my-deployment-123.now.sh all')}
@@ -87,7 +92,7 @@ export default async function main(ctx) {
argv = getArgs(ctx.argv.slice(2), {
'--verify-timeout': Number,
'--no-verify': Boolean,
'-n': '--no-verify'
'-n': '--no-verify',
});
} catch (err) {
handleError(err);
@@ -100,7 +105,10 @@ export default async function main(ctx) {
}
// Prepare the context
const { authConfig: { token }, config } = ctx;
const {
authConfig: { token },
config,
} = ctx;
const { currentTeam } = config;
const { apiUrl } = ctx;
const debug = argv['--debug'];
@@ -155,8 +163,7 @@ export default async function main(ctx) {
}
if (dcs instanceof InvalidRegionOrDCForScale) {
output.error(
`The value "${dcs.meta
.regionOrDC}" is not a valid region or DC identifier`
`The value "${dcs.meta.regionOrDC}" is not a valid region or DC identifier`
);
now.close();
return 1;
@@ -165,8 +172,7 @@ export default async function main(ctx) {
const min = getMinFromArgs(argv._);
if (min instanceof InvalidMinForScale) {
output.error(
`Invalid <min> parameter "${min.meta
.value}". A number or "auto" were expected`
`Invalid <min> parameter "${min.meta.value}". A number or "auto" were expected`
);
now.close();
return 1;
@@ -175,24 +181,21 @@ export default async function main(ctx) {
const max = getMaxFromArgs(argv._);
if (max instanceof InvalidMinForScale) {
output.error(
`Invalid <min> parameter "${max.meta
.value}". A number or "auto" were expected`
`Invalid <min> parameter "${max.meta.value}". A number or "auto" were expected`
);
now.close();
return 1;
}
if (max instanceof InvalidArgsForMinMaxScale) {
output.error(
`Invalid number of arguments: expected <min> ("${max.meta
.min}") and [max]`
`Invalid number of arguments: expected <min> ("${max.meta.min}") and [max]`
);
now.close();
return 1;
}
if (max instanceof InvalidMaxForScale) {
output.error(
`Invalid <max> parameter "${max.meta
.value}". A number or "auto" were expected`
`Invalid <max> parameter "${max.meta.value}". A number or "auto" were expected`
);
now.close();
return 1;
@@ -262,14 +265,15 @@ export default async function main(ctx) {
deployment.url
);
if (result instanceof ForbiddenScaleMinInstances) {
output.error(`You can't scale to more than ${result.meta.max} min instances with your current plan.`);
output.error(
`You can't scale to more than ${result.meta.max} min instances with your current plan.`
);
now.close();
return 1;
}
if (result instanceof ForbiddenScaleMaxInstances) {
output.error(
`You can't scale to more than ${result.meta
.max} max instances with your current plan.`
`You can't scale to more than ${result.meta.max} max instances with your current plan.`
);
now.close();
return 1;

View File

@@ -82,16 +82,6 @@ export default async function processDeployment({
`Total files ${event.payload.total.size}, ${event.payload.missing.length} changed`
);
if (!quiet) {
log(
`Synced ${pluralize(
'file',
event.payload.missing.length,
true
)} ${uploadStamp()}`
);
}
const missingSize = event.payload.missing
.map((sha: string) => event.payload.total.get(sha).data.length)
.reduce((a: number, b: number) => a + b, 0);
@@ -121,6 +111,13 @@ export default async function processDeployment({
now._host = event.payload.url;
if (!quiet) {
log(
`Synced ${pluralize(
'file',
event.payload.missing.length,
true
)} ${uploadStamp()}`
);
const version = isLegacy ? `${chalk.grey('[v1]')} ` : '';
log(`https://${event.payload.url} ${version}${deployStamp()}`);
} else {
@@ -128,7 +125,10 @@ export default async function processDeployment({
}
if (queuedSpinner === null) {
queuedSpinner = wait('Queued...');
queuedSpinner =
event.payload.readyState === 'QUEUED'
? wait('Queued...')
: wait('Building...');
}
}
@@ -200,15 +200,6 @@ export default async function processDeployment({
debug(
`Total files ${event.payload.total.size}, ${event.payload.missing.length} changed`
);
if (!quiet) {
log(
`Synced ${pluralize(
'file',
event.payload.missing.length,
true
)} ${uploadStamp()}`
);
}
const missingSize = event.payload.missing
.map((sha: string) => event.payload.total.get(sha).data.length)
@@ -239,6 +230,13 @@ export default async function processDeployment({
now._host = event.payload.url;
if (!quiet) {
log(
`Synced ${pluralize(
'file',
event.payload.missing.length,
true
)} ${uploadStamp()}`
);
const version = isLegacy ? `${chalk.grey('[v1]')} ` : '';
log(`${event.payload.url} ${version}${deployStamp()}`);
} else {

View File

@@ -544,10 +544,11 @@ export default class DevServer {
}
if (builders) {
const { defaultRoutes, error: routesError } = await detectRoutes(
files,
builders
);
const {
defaultRoutes,
redirectRoutes,
error: routesError,
} = await detectRoutes(files, builders);
config.builds = config.builds || [];
config.builds.push(...builders);
@@ -556,8 +557,12 @@ export default class DevServer {
this.output.error(routesError.message);
await this.exit();
} else {
config.routes = config.routes || [];
config.routes.push(...(defaultRoutes as RouteConfig[]));
const routes: RouteConfig[] = [];
const { routes: nowConfigRoutes } = config;
routes.push(...(redirectRoutes || []));
routes.push(...(nowConfigRoutes || []));
routes.push(...(defaultRoutes || []));
config.routes = routes;
}
}
}

View File

@@ -15,7 +15,7 @@ export default `.hg
.wafpicke-*
.lock-wscript
.env
.env.build
.env.*
.venv
npm-debug.log
config.gypi

View File

@@ -1,6 +1,6 @@
{
"name": "now-client",
"version": "6.0.1-canary.1",
"version": "6.0.2-canary.0",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"homepage": "https://zeit.co",
@@ -38,7 +38,7 @@
]
},
"dependencies": {
"@zeit/fetch": "5.1.0",
"@zeit/fetch": "5.2.0",
"async-retry": "1.2.3",
"async-sema": "3.0.0",
"fs-extra": "8.0.1",

View File

@@ -115,7 +115,8 @@ export async function* upload(
apiUrl,
userAgent,
},
clientOptions.debug
clientOptions.debug,
true
);
if (res.status === 200) {
@@ -168,8 +169,9 @@ export async function* upload(
return result;
},
{
retries: 3,
factor: 2,
retries: 5,
factor: 6,
minTimeout: 10,
}
);
});

View File

@@ -0,0 +1,6 @@
import nodeFetch from 'node-fetch';
import setupZeitFetch from '@zeit/fetch';
const zeitFetch = setupZeitFetch(nodeFetch);
export { zeitFetch, nodeFetch };

View File

@@ -1,6 +1,7 @@
import { DeploymentFile } from './hashes';
import { parse as parseUrl } from 'url';
import fetch_, { RequestInit } from 'node-fetch';
import { RequestInit } from 'node-fetch';
import { nodeFetch, zeitFetch } from './fetch';
import { join, sep } from 'path';
import qs from 'querystring';
import ignore from 'ignore';
@@ -85,7 +86,7 @@ export async function getNowIgnore(path: string | string[]): Promise<any> {
'.wafpicke-*',
'.lock-wscript',
'.env',
'.env.build',
'.env.*',
'.venv',
'npm-debug.log',
'config.gypi',
@@ -122,7 +123,8 @@ export const fetch = async (
url: string,
token: string,
opts: FetchOpts = {},
debugEnabled?: boolean
debugEnabled?: boolean,
useNodeFetch?: boolean
): Promise<any> => {
semaphore.acquire();
const debug = createDebug(debugEnabled);
@@ -152,7 +154,9 @@ export const fetch = async (
debug(`${opts.method || 'GET'} ${url}`);
time = Date.now();
const res = await fetch_(url, opts);
const res = useNodeFetch
? await nodeFetch(url, opts)
: await zeitFetch(url, opts);
debug(`DONE in ${Date.now() - time}ms: ${opts.method || 'GET'} ${url}`);
semaphore.release();

View File

@@ -1,6 +1,6 @@
{
"name": "@now/next",
"version": "2.3.2",
"version": "2.3.7",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/next-js",

View File

@@ -202,7 +202,7 @@ export const build = async ({
const pkg = await readPackageJson(entryPath);
const nextVersion = getNextVersion(pkg);
const nodeVersion = await getNodeVersion(entryPath, undefined, config);
const nodeVersion = await getNodeVersion(entryPath, undefined, config, meta);
const spawnOpts = getSpawnOptions(meta, nodeVersion);
if (!nextVersion) {
@@ -336,6 +336,8 @@ export const build = async ({
const routesManifest = await getRoutesManifest(entryPath, realNextVersion);
const rewrites: Route[] = [];
const redirects: Route[] = [];
const nextBasePathRoute: Route[] = [];
let nextBasePath: string | undefined;
if (routesManifest) {
switch (routesManifest.version) {
@@ -343,6 +345,26 @@ export const build = async ({
case 2: {
redirects.push(...convertRedirects(routesManifest.redirects));
rewrites.push(...convertRewrites(routesManifest.rewrites));
if (routesManifest.basePath && routesManifest.basePath !== '/') {
nextBasePath = routesManifest.basePath;
if (!nextBasePath.startsWith('/')) {
throw new Error(
'basePath must start with `/`. Please upgrade your `@now/next` builder and try again. Contact support if this continues to happen.'
);
}
if (nextBasePath.endsWith('/')) {
throw new Error(
'basePath must not end with `/`. Please upgrade your `@now/next` builder and try again. Contact support if this continues to happen.'
);
}
nextBasePathRoute.push({
src: `^${nextBasePath}(?:$|/(.*))$`,
dest: `/$1`,
continue: true,
});
}
break;
}
default: {
@@ -400,6 +422,9 @@ export const build = async ({
routes: [
// TODO: low priority: handle trailingSlash
// Add top level rewrite for basePath if provided
...nextBasePathRoute,
// redirects take the highest priority
...redirects,
// Before we handle static files we need to set proper caching headers
@@ -450,11 +475,7 @@ export const build = async ({
const prerenders: { [key: string]: Prerender | FileFsRef } = {};
const staticPages: { [key: string]: FileFsRef } = {};
const dynamicPages: string[] = [];
const dynamicDataRoutes: Array<{
src: string;
dest: string;
check: true;
}> = [];
const dynamicDataRoutes: Array<{ src: string; dest: string }> = [];
const appMountPrefixNoTrailingSlash = path.posix
.join('/', entryDirectory)
@@ -880,7 +901,6 @@ export const build = async ({
src: dataRouteRegex.replace(/^\^/, `^${appMountPrefixNoTrailingSlash}`),
// Location of lambda in builder output
dest: path.posix.join(entryDirectory, dataRoute),
check: true,
});
});
}
@@ -946,30 +966,11 @@ export const build = async ({
...staticDirectoryFiles,
},
routes: [
// Add top level rewrite for basePath if provided
...nextBasePathRoute,
// redirects take the highest priority
...redirects,
// Next.js page lambdas, `static/` folder, reserved assets, and `public/`
// folder
{ handle: 'filesystem' },
...rewrites,
// Custom Next.js 404 page (TODO: do we want to remove this?)
...(isLegacy
? []
: [
{
src: path.join('/', entryDirectory, '.*'),
dest: path.join('/', entryDirectory, '_error'),
status: 404,
check: true,
},
]),
// Routes that are checked after each rewrite and no filesystem match
{ handle: 'miss' },
// Dynamic routes
...dynamicRoutes,
...dynamicDataRoutes,
// Routes that are checked after filesystem match
{ handle: 'hit' },
// Before we handle static files we need to set proper caching headers
{
// This ensures we only match known emitted-by-Next.js files and not
@@ -984,6 +985,24 @@ export const build = async ({
headers: { 'cache-control': 'public,max-age=31536000,immutable' },
continue: true,
},
{ src: path.join('/', entryDirectory, '_next(?!/data(?:/|$))(?:/.*)?') },
// Next.js page lambdas, `static/` folder, reserved assets, and `public/`
// folder
{ handle: 'filesystem' },
...rewrites,
// Dynamic routes
...dynamicRoutes,
...dynamicDataRoutes,
// Custom Next.js 404 page (TODO: do we want to remove this?)
...(isLegacy
? []
: [
{
src: path.join('/', entryDirectory, '.*'),
dest: path.join('/', entryDirectory, '_error'),
status: 404,
},
]),
],
watch: [],
childProcesses: [],

View File

@@ -308,6 +308,7 @@ type RoutesManifestRegex = {
};
export type RoutesManifest = {
basePath: string | undefined;
redirects: (Redirect & RoutesManifestRegex)[];
rewrites: (Rewrite & RoutesManifestRegex)[];
dynamicRoutes: {
@@ -353,7 +354,7 @@ export async function getDynamicRoutes(
dynamicPages: string[],
isDev?: boolean,
routesManifest?: RoutesManifest
): Promise<{ src: string; dest: string; check: true }[]> {
): Promise<{ src: string; dest: string }[]> {
if (!dynamicPages.length) {
return [];
}
@@ -367,7 +368,6 @@ export async function getDynamicRoutes(
return {
src: regex,
dest: !isDev ? path.join('/', entryDirectory, page) : page,
check: true,
};
}
);
@@ -424,7 +424,7 @@ export async function getDynamicRoutes(
matcher: getRouteRegex && getRouteRegex(pageName).re,
}));
const routes: { src: string; dest: string; check: true }[] = [];
const routes: { src: string; dest: string }[] = [];
pageMatchers.forEach(pageMatcher => {
// in `now dev` we don't need to prefix the destination
const dest = !isDev
@@ -435,7 +435,6 @@ export async function getDynamicRoutes(
routes.push({
src: pageMatcher.matcher.source,
dest,
check: true,
});
}
});

View File

@@ -1,6 +1,6 @@
{
"dependencies": {
"next": "9.1.7-canary.11",
"next": "^9.1.4-canary.1",
"react": "^16.8.6",
"react-dom": "^16.8.6"
}

View File

@@ -1,6 +1,6 @@
{
"dependencies": {
"next": "9.1.7-canary.11",
"next": "^9.1.4-canary.1",
"react": "^16.8.6",
"react-dom": "^16.8.6"
}

View File

@@ -1,19 +0,0 @@
module.exports = {
generateBuildId() {
return 'testing-build-id';
},
experimental: {
async rewrites() {
return [
{
source: '/blog/post-1',
destination: '/blog/post-2',
},
{
source: '/blog/post-2',
destination: '/404',
},
];
},
},
};

View File

@@ -1,10 +0,0 @@
{
"version": 2,
"builds": [{ "src": "package.json", "use": "@now/next" }],
"probes": [
{
"path": "/blog/post-1",
"mustContain": "post-2"
}
]
}

View File

@@ -1,11 +0,0 @@
import { useRouter } from 'next/router'
const Page = () => (
<>
<p>Post: {useRouter().query.post}</p>
</>
)
Page.getInitialProps = () => ({ hello: 'world' })
export default Page

View File

@@ -0,0 +1,8 @@
module.exports = {
generateBuildId() {
return 'testing-build-id';
},
experimental: {
basePath: '/docs',
},
};

View File

@@ -0,0 +1,24 @@
{
"version": 2,
"builds": [{ "src": "package.json", "use": "@now/next" }],
"probes": [
{
"path": "/docs/_next/static/testing-build-id/pages/index.js",
"responseHeaders": {
"cache-control": "public,max-age=31536000,immutable"
}
},
{
"path": "/docs/",
"mustContain": "hello from index"
},
{
"path": "/docs",
"mustContain": "hello from index"
},
{
"path": "/docs/another",
"mustContain": "hello from another"
}
]
}

View File

@@ -1,6 +1,6 @@
{
"dependencies": {
"next": "9.1.7-canary.11",
"next": "canary",
"react": "^16.8.6",
"react-dom": "^16.8.6"
}

View File

@@ -0,0 +1,5 @@
const Page = () => 'hello from another';
Page.getInitialProps = () => ({ hello: 'world' });
export default Page;

View File

@@ -0,0 +1 @@
export default () => 'hello from index';

View File

@@ -117,7 +117,6 @@ describe('build meta dev', () => {
{
src: '^/(nested\\/([^\\/]+?)(?:\\/)?)$',
dest: 'http://localhost:5000/$1',
check: true,
},
{ src: '/data.txt', dest: 'http://localhost:5000/data.txt' },
]);

View File

@@ -1,6 +1,6 @@
{
"name": "@now/node",
"version": "1.3.1-canary.0",
"version": "1.3.3",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/node-js",

View File

@@ -71,7 +71,8 @@ async function downloadInstallAndBundle({
const nodeVersion = await getNodeVersion(
entrypointFsDirname,
undefined,
config
config,
meta
);
const spawnOpts = getSpawnOptions(meta, nodeVersion);
await runNpmInstall(
@@ -369,16 +370,13 @@ export async function build({
});
}
// 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_FILENAME}.launcher`,
runtime,
runtime: nodeVersion.runtime,
});
return { output: lambda, watch };

View File

@@ -1,5 +1,6 @@
import { relative, basename, resolve, dirname } from 'path';
import _ts from 'typescript';
import { NowBuildError } from '@now/build-utils';
/*
* Fork of TS-Node - https://github.com/TypeStrong/ts-node
@@ -180,8 +181,8 @@ export function register(opts: Options = {}): Register {
};
function createTSError(diagnostics: ReadonlyArray<_ts.Diagnostic>) {
const diagnosticText = formatDiagnostics(diagnostics, diagnosticHost);
return new Error(diagnosticText);
const message = formatDiagnostics(diagnostics, diagnosticHost);
return new NowBuildError({ code: 'NOW_NODE_TYPESCRIPT_ERROR', message });
}
function reportTSError(

View File

@@ -7,7 +7,7 @@
}
],
"probes": [
{ "path": "/empty", "mustContain": "RANDOMNESS_PLACEHOLDER:8" },
{ "path": "/empty", "mustContain": "RANDOMNESS_PLACEHOLDER:12" },
{ "path": "/exact", "mustContain": "RANDOMNESS_PLACEHOLDER:10" },
{ "path": "/greater", "mustContain": "RANDOMNESS_PLACEHOLDER:12" },
{ "path": "/major", "mustContain": "RANDOMNESS_PLACEHOLDER:10" },

View File

@@ -1,7 +1,10 @@
{
"engines": {
"node": "10.x"
},
"dependencies": {
"chrome-aws-lambda": "1.11.1",
"lighthouse": "4.3.1",
"puppeteer-core": "1.11.0"
"chrome-aws-lambda": "1.20.4",
"lighthouse": "5.6.0",
"puppeteer-core": "1.20.0"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@now/python",
"version": "1.0.2-canary.0",
"version": "1.1.1-canary.0",
"main": "./dist/index.js",
"license": "MIT",
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/python",

View File

@@ -36,11 +36,10 @@ export async function downloadFilesInWorkPath({
files,
meta,
config,
}: BuildOptions) {
debug('Downloading user files...');
let downloadedFiles = await download(files, workPath, meta);
if (meta && meta.isDev) {
debug('Downloading user files...');
let downloadedFiles = await download(files, workPath, meta);
if (meta && meta.isDev) {
let base = null;
if (config && config.zeroConfig) {

View File

@@ -1,6 +1,6 @@
{
"name": "@now/routing-utils",
"version": "1.4.1-canary.3",
"version": "1.5.0",
"description": "ZEIT Now routing utilities",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",

View File

@@ -1,6 +1,6 @@
{
"name": "@now/static-build",
"version": "0.14.1-canary.6",
"version": "0.14.3",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/static-builds",

View File

@@ -121,6 +121,14 @@ export const frameworks: Framework[] = [
},
],
},
{
name: 'Scully',
slug: 'scully',
dependency: '@scullyio/init',
minNodeRange: '10.x',
buildCommand: 'ng build && scully',
getOutputDirName: async () => 'dist/static',
},
{
name: 'Angular',
slug: 'angular',

View File

@@ -162,7 +162,13 @@ function getPkg(entrypoint: string, workPath: string) {
return pkg;
}
function getFramework(config: Config | null, pkg?: PackageJson | null) {
function getFramework(
config: Config | null,
pkg?: PackageJson | null
): Framework | undefined {
if (!config || !config.zeroConfig) {
return;
}
const { framework: configFramework = null } = config || {};
if (configFramework) {
@@ -294,7 +300,8 @@ export async function build({
const nodeVersion = await getNodeVersion(
entrypointDir,
minNodeRange,
config
config,
meta
);
const spawnOpts = getSpawnOptions(meta, nodeVersion);
@@ -447,7 +454,12 @@ export async function build({
if (!config.zeroConfig && entrypoint.endsWith('.sh')) {
debug(`Running build script "${entrypoint}"`);
const nodeVersion = await getNodeVersion(entrypointDir, undefined, config);
const nodeVersion = await getNodeVersion(
entrypointDir,
undefined,
config,
meta
);
const spawnOpts = getSpawnOptions(meta, nodeVersion);
await runShellScript(path.join(workPath, entrypoint), [], spawnOpts);
validateDistDir(distPath, meta.isDev, config);

View File

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

View File

@@ -0,0 +1,9 @@
{
"private": true,
"scripts": {
"now-build": "mkdir out && echo 'output:RANDOMNESS_PLACEHOLDER' > out/index.txt"
},
"dependencies": {
"@vue/cli-service": "3.11.0"
}
}

View File

@@ -0,0 +1 @@
Should not use this file

View File

@@ -0,0 +1,13 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false

View File

@@ -0,0 +1,46 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# Only exists if Bazel was run
/bazel-out
# dependencies
/node_modules
# profiling files
chrome-profiler-events*.json
speed-measure-plugin*.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db

View File

@@ -0,0 +1,27 @@
# Scully
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.3.21.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

View File

@@ -0,0 +1,114 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"scully": {
"projectType": "application",
"schematics": {},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/scully",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"aot": false,
"assets": ["src/favicon.ico", "src/assets"],
"styles": ["src/styles.css"],
"scripts": []
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "scully:build"
},
"configurations": {
"production": {
"browserTarget": "scully:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "scully:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": ["src/favicon.ico", "src/assets"],
"styles": ["src/styles.css"],
"scripts": []
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json",
"e2e/tsconfig.json"
],
"exclude": ["**/node_modules/**"]
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "scully:serve"
},
"configurations": {
"production": {
"devServerTarget": "scully:serve:production"
}
}
}
}
}
},
"defaultProject": "scully"
}

View File

@@ -0,0 +1,12 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# You can see what browsers were selected by your queries by running:
# npx browserslist
> 0.5%
last 2 versions
Firefox ESR
not dead
not IE 9-11 # For IE 9-11 support, remove 'not'.

View File

@@ -0,0 +1,32 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function(config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma'),
],
client: {
clearContext: false, // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, './coverage/scully'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true,
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true,
});
};

View File

@@ -0,0 +1,12 @@
{
"builds": [
{
"src": "package.json",
"use": "@now/static-build",
"config": {
"zeroConfig": true
}
}
],
"probes": [{ "path": "/", "mustContain": "Scully" }]
}

View File

@@ -0,0 +1,51 @@
{
"name": "scully",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"scully": "scully",
"scully:serve": "scully serve"
},
"private": true,
"dependencies": {
"@angular/animations": "~8.2.14",
"@angular/common": "~8.2.14",
"@angular/compiler": "~8.2.14",
"@angular/core": "~8.2.14",
"@angular/forms": "~8.2.14",
"@angular/platform-browser": "~8.2.14",
"@angular/platform-browser-dynamic": "~8.2.14",
"@angular/router": "~8.2.14",
"@scullyio/init": "0.0.9",
"@scullyio/ng-lib": "latest",
"@scullyio/scully": "latest",
"rxjs": "~6.4.0",
"tslib": "^1.10.0",
"zone.js": "~0.9.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.803.21",
"@angular/cli": "~8.3.21",
"@angular/compiler-cli": "~8.2.14",
"@angular/language-service": "~8.2.14",
"@types/node": "~8.9.4",
"@types/jasmine": "~3.3.8",
"@types/jasminewd2": "~2.0.3",
"codelyzer": "^5.0.0",
"jasmine-core": "~3.4.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~4.1.0",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "~2.0.1",
"karma-jasmine": "~2.0.1",
"karma-jasmine-html-reporter": "^1.4.0",
"protractor": "~5.4.0",
"ts-node": "~7.0.0",
"tslint": "~5.15.0",
"typescript": "~3.5.3"
}
}

View File

@@ -0,0 +1,4 @@
exports.config = {
projectRoot: './src/app',
routes: {},
};

View File

@@ -0,0 +1,3 @@
<div>
Scully on ZEIT Now.
</div>

View File

@@ -0,0 +1,31 @@
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [AppComponent],
}).compileComponents();
}));
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'scully'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('scully');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('.content span').textContent).toContain(
'scully app is running!'
);
});
});

View File

@@ -0,0 +1,12 @@
import { IdleMonitorService } from '@scullyio/ng-lib';
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
constructor() {}
title = 'scully';
}

View File

@@ -0,0 +1,13 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { HttpClientModule } from '@angular/common/http';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, HttpClientModule],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,3 @@
export const environment = {
production: true,
};

View File

@@ -0,0 +1,16 @@
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false,
};
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.

Binary file not shown.

After

Width:  |  Height:  |  Size: 948 B

View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Scully</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

View File

@@ -0,0 +1,13 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch(err => console.error(err));

View File

@@ -0,0 +1,71 @@
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/**
* Web Animations `@angular/platform-browser/animations`
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
*/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
* because those flags need to be set before `zone.js` being loaded, and webpack
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
* import './zone-flags.ts';
*
* The flags allowed in zone-flags.ts are listed here.
*
* The following flags will work for all browsers.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*
* (window as any).__Zone_enable_cross_context_check = true;
*
*/
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/
/***************************************************************************************************
* SCULLY IMPORTS
*/
// tslint:disable-next-line: align
import 'zone.js/dist/task-tracking';

View File

@@ -0,0 +1 @@
/* You can add global styles to this file, and also import other style files */

View File

@@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": ["src/main.ts", "src/polyfills.ts"],
"include": ["src/**/*.ts"],
"exclude": ["src/test.ts", "src/**/*.spec.ts"]
}

View File

@@ -0,0 +1,21 @@
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"module": "esnext",
"moduleResolution": "node",
"importHelpers": true,
"target": "es2015",
"typeRoots": ["node_modules/@types"],
"lib": ["es2018", "dom"]
},
"angularCompilerOptions": {
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true
}
}

View File

@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": ["jasmine", "node"]
},
"files": ["src/test.ts", "src/polyfills.ts"],
"include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
}

View File

@@ -0,0 +1,57 @@
{
"extends": "tslint:recommended",
"rules": {
"array-type": false,
"arrow-parens": false,
"deprecation": {
"severity": "warning"
},
"component-class-suffix": true,
"contextual-lifecycle": true,
"directive-class-suffix": true,
"directive-selector": [true, "attribute", "app", "camelCase"],
"component-selector": [true, "element", "app", "kebab-case"],
"import-blacklist": [true, "rxjs/Rx"],
"interface-name": false,
"max-classes-per-file": false,
"max-line-length": [true, 140],
"member-access": false,
"member-ordering": [
true,
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
}
],
"no-consecutive-blank-lines": false,
"no-console": [true, "debug", "info", "time", "timeEnd", "trace"],
"no-empty": false,
"no-inferrable-types": [true, "ignore-params"],
"no-non-null-assertion": true,
"no-redundant-jsdoc": true,
"no-switch-case-fall-through": true,
"no-var-requires": false,
"object-literal-key-quotes": [true, "as-needed"],
"object-literal-sort-keys": false,
"ordered-imports": false,
"quotemark": [true, "single"],
"trailing-comma": false,
"no-conflicting-lifecycle": true,
"no-host-metadata-property": true,
"no-input-rename": true,
"no-inputs-metadata-property": true,
"no-output-native": true,
"no-output-on-prefix": true,
"no-output-rename": true,
"no-outputs-metadata-property": true,
"template-banana-in-box": true,
"template-no-negated-async": true,
"use-lifecycle-interface": true,
"use-pipe-transform-interface": true
},
"rulesDirectory": ["codelyzer"]
}

View File

@@ -4,6 +4,7 @@ const path = require('path');
describe('prepareCache', () => {
test('should cache node_modules', async () => {
const files = await prepareCache({
config: { zeroConfig: true },
workPath: path.resolve(__dirname, './cache-fixtures/default'),
entrypoint: 'index.js',
});
@@ -14,6 +15,7 @@ describe('prepareCache', () => {
test('should cache node_modules and `.cache` folder for gatsby deployments', async () => {
const files = await prepareCache({
config: { zeroConfig: true },
workPath: path.resolve(__dirname, './cache-fixtures/gatsby'),
entrypoint: 'package.json',
});

View File

@@ -1,6 +1,5 @@
const assert = require('assert');
const { createHash } = require('crypto');
const { homedir } = require('os');
const path = require('path');
const fetch = require('./fetch-retry.js');
@@ -28,7 +27,7 @@ async function nowDeploy (bodies, randomness) {
RANDOMNESS_BUILD_ENV_VAR: randomness,
},
},
name: 'test',
name: 'test2020',
files,
builds: nowJson.builds,
routes: nowJson.routes || [],
@@ -133,20 +132,12 @@ async function fetchWithAuth (url, opts = {}) {
if (!opts.headers) opts.headers = {};
if (!opts.headers.Authorization) {
const { NOW_TOKEN, CIRCLECI } = process.env;
currentCount += 1;
if (!token || currentCount === MAX_COUNT) {
currentCount = 0;
if (NOW_TOKEN) {
token = NOW_TOKEN;
} else if (CIRCLECI) {
token = await fetchTokenWithRetry(
Buffer.from(str, 'base64').toString()
);
} else {
const authJsonPath = path.join(homedir(), '.now/auth.json');
token = require(authJsonPath).token;
}
token = await fetchTokenWithRetry(
Buffer.from(str, 'base64').toString()
);
}
opts.headers.Authorization = `Bearer ${token}`;

View File

@@ -1,5 +1,8 @@
const getWritableDirectory = require('../../packages/now-build-utils/fs/get-writable-directory.js');
const glob = require('../../packages/now-build-utils/fs/glob.js');
const {
getLatestNodeVersion,
glob,
getWriteableDirectory,
} = require('@now/build-utils');
function runAnalyze(wrapper, context) {
if (wrapper.analyze) {
@@ -16,6 +19,11 @@ async function runBuildLambda(inputPath) {
const nowJson = require(nowJsonRef.fsPath);
expect(nowJson.builds.length).toBe(1);
const build = nowJson.builds[0];
if (!build.config || !build.config.nodeVersion) {
// Mimic api-deployments when a new project is created
const nodeVersion = getLatestNodeVersion().range;
build.config = { ...build.config, nodeVersion };
}
expect(build.src.includes('*')).toBeFalsy();
const entrypoint = build.src.replace(/^\//, ''); // strip leftmost slash
expect(inputFiles[entrypoint]).toBeDefined();
@@ -26,15 +34,15 @@ async function runBuildLambda(inputPath) {
const analyzeResult = runAnalyze(wrapper, {
files: inputFiles,
entrypoint,
config: build.config
config: build.config,
});
const workPath = await getWritableDirectory();
const workPath = await getWriteableDirectory();
const buildResult = await wrapper.build({
files: inputFiles,
entrypoint,
config: build.config,
workPath
workPath,
});
const { output } = buildResult;
@@ -43,7 +51,7 @@ async function runBuildLambda(inputPath) {
buildResult.output = Object.keys(output).reduce(
(result, path) => ({
...result,
[path.replace(/\\/g, '/')]: output[path]
[path.replace(/\\/g, '/')]: output[path],
}),
{}
);
@@ -52,7 +60,7 @@ async function runBuildLambda(inputPath) {
return {
analyzeResult,
buildResult,
workPath
workPath,
};
}

View File

@@ -2023,22 +2023,22 @@
dependencies:
"@zeit/dns-cached-resolve" "2.1.0"
"@zeit/fetch-retry@4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@zeit/fetch-retry/-/fetch-retry-4.0.0.tgz#ad7fe06c4ceb3dcbd76c04db95b1b624ed6fcf3f"
integrity sha512-ALXnrCPpiVWha/L3Mm1klPhqmVTKmPQ2dmb5YIsSCrMBJugfhDb42kacVsvQ11vAFRE1LRaJ9Pmw16zEMvQnbw==
"@zeit/fetch-retry@^4.1.0":
version "4.1.0"
resolved "https://registry.yarnpkg.com/@zeit/fetch-retry/-/fetch-retry-4.1.0.tgz#a960e335b3ecf01f219e045a4b8819b6ef34b1f4"
integrity sha512-2/vtTzIs3/Bp5tNOmCpICCRooa+v0wL4e+3Afl4etyqZrjOmCMfKshWPSHTxLe20qfsn010qXy/hK6yqD4t0zg==
dependencies:
async-retry "^1.1.3"
async-retry "^1.3.1"
debug "^3.1.0"
"@zeit/fetch@5.1.0":
version "5.1.0"
resolved "https://registry.yarnpkg.com/@zeit/fetch/-/fetch-5.1.0.tgz#5a24c03ce690f63a81543a016e54aa59f8ca71a0"
integrity sha512-e+ZClpgyP8AlOcewSNrpJzXLjPLG+dXBnBg3vYXPYdYItj2dWaI1mRjiyBriH/U9Gt48wZJukz9Q3uhMYS4X6w==
"@zeit/fetch@5.2.0":
version "5.2.0"
resolved "https://registry.yarnpkg.com/@zeit/fetch/-/fetch-5.2.0.tgz#7a8bfa31284e33a4e3fe7940351b0d48264d167d"
integrity sha512-S4TFaT210j/lgzHjaxN2SiaMxXGeoB4hghNlhlvSqwTFSDNIqyJIDKFZQy+r3NKJOQU+RB5yGKJt8LS1pPHUrw==
dependencies:
"@types/async-retry" "1.2.1"
"@zeit/fetch-cached-dns" "1.2.0"
"@zeit/fetch-retry" "4.0.0"
"@zeit/fetch-retry" "^4.1.0"
agentkeepalive "3.4.1"
debug "3.1.0"
@@ -2341,9 +2341,9 @@ ansi-styles@^4.0.0:
color-convert "^2.0.1"
ansi-styles@^4.1.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.0.tgz#5681f0dcf7ae5880a7841d8831c4724ed9cc0172"
integrity sha512-7kFQgnEaMdRtwf6uSfUnVr9gSGC7faurn+J/Mv90/W+iTtN0405/nLdopfMWwchyxhbGYl6TC4Sccn9TUkGAgg==
version "4.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359"
integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==
dependencies:
"@types/color-name" "^1.1.1"
color-convert "^2.0.1"
@@ -2590,13 +2590,20 @@ async-retry@1.1.3:
dependencies:
retry "0.10.1"
async-retry@1.2.3, async-retry@^1.1.3:
async-retry@1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.2.3.tgz#a6521f338358d322b1a0012b79030c6f411d1ce0"
integrity sha512-tfDb02Th6CE6pJUF2gjW5ZVjsgwlucVXOEQMvEX9JgSJMs9gAX+Nz3xRuJBKuUYjTSYORqvDBORdAQ3LU59g7Q==
dependencies:
retry "0.12.0"
async-retry@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.3.1.tgz#139f31f8ddce50c0870b0ba558a6079684aaed55"
integrity sha512-aiieFW/7h3hY0Bq5d+ktDBejxuwR78vRu9hDUdR8rNhSaQ29VzPL4AoIRG7D/c7tdenwOcKvgPM6tIxB3cB6HA==
dependencies:
retry "0.12.0"
async-sema@2.1.4:
version "2.1.4"
resolved "https://registry.yarnpkg.com/async-sema/-/async-sema-2.1.4.tgz#3f5aa091d0a763354045ee899a5d17ffb69251af"
@@ -9731,7 +9738,7 @@ shellwords@^0.1.1:
resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b"
integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==
signal-exit@3.0.2, signal-exit@TooTallNate/signal-exit#update/sighub-to-sigint-on-windows, signal-exit@^3.0.0, signal-exit@^3.0.2:
signal-exit@3.0.2, signal-exit@^3.0.0, signal-exit@^3.0.2:
version "3.0.2"
resolved "https://codeload.github.com/TooTallNate/signal-exit/tar.gz/58088fa7f715149f8411e089a4a6e3fe6ed265ec"
@@ -10411,9 +10418,9 @@ term-size@^1.2.0:
execa "^0.7.0"
term-size@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.1.0.tgz#3aec444c07a7cf936e157c1dc224b590c3c7eef2"
integrity sha512-I42EWhJ+2aeNQawGx1VtpO0DFI9YcfuvAMNIdKyf/6sRbHJ4P+ZQ/zIT87tE+ln1ymAGcCJds4dolfSAS0AcNg==
version "2.1.1"
resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.1.1.tgz#f81ec25854af91a480d2f9d0c77ffcb26594ed1a"
integrity sha512-UqvQSch04R+69g4RDhrslmGvGL3ucDRX/U+snYW0Mab4uCAyKSndUksaoqlJ81QKSpRnIsuOYQCbC2ZWx2896A==
test-exclude@^5.1.0, test-exclude@^5.2.3:
version "5.2.3"