Compare commits

..

18 Commits

Author SHA1 Message Date
Sophearak Tha
372a674625 Publish
- @now/build-utils@0.11.1
 - now@16.4.1
 - now-client@5.2.2
 - @now/next@1.0.5
 - @now/python@0.3.3
2019-10-25 19:34:00 +07:00
Sophearak Tha
fafeadb7ba update now-cli readme (#3202) 2019-10-25 14:14:24 +02:00
Max
966b1e763f Add apiUrl debug log to now-client (#3201) 2019-10-25 14:14:19 +02:00
Luc
bb60e1a5fe Improve styling of 1.0 message (#3200) 2019-10-25 12:47:19 +02:00
Nathan Rajlich
cac9f807cc [now-cli] Gracefully exit when the cwd does not exist (#3194)
There have been Sentry errors where `process.cwd()` fails and throws an error. This patch handles that scenario gracefully by printing a more clear error message to the user and avoids sending a report to Sentry.

Fixes #3193.
2019-10-25 11:49:45 +02:00
Steven
a0b1254820 Fix circle ci xcode version (#3197) 2019-10-25 11:48:27 +02:00
Nathan Rajlich
0faff4132b [now-build-utils] Add more complete PackageJson typings (#3198)
* [now-build-utils] Add more complete `PackageJson` typings

Planning on using this in `now-cli`.

* Make "name" optional

* Use a namespace
2019-10-25 11:47:36 +02:00
Joe Haddad
1793a1287d [now-next] Cache All Next.js Directories (#3190)
Next.js now uses the `css` and `media` folders for its build-in CSS support. These files should be cached forever.
2019-10-25 11:45:37 +02:00
Max
5b572239c1 [now-client] Yield an error event if path provided is not absolute (#3173)
This adds an `error` event when provided path to `now-client` isn't absolute
2019-10-25 11:43:34 +02:00
Steven
f6a66d937e [now-client] Change npm publish to use files key (#3181)
- remove `.npmignore`
- use [`files`](https://docs.npmjs.com/files/package.json#files) key in `package.json`
- update metadata in `package.json`
- fix test harness to generate a token for each test deployment

This PR will prevent publishing [tests](https://cdn.jsdelivr.net/npm/now-client@5.2.0/tests/) to npm and any other unused files.
2019-10-25 11:43:29 +02:00
Sophearak Tha
2cf9a2f489 [now-client] [now-cli] Update version of deployment API (#3188)
* Update version of deployment API

* Add `alias-assigned` event and handling

* Replace v9 api with v10

* Don't return on immediate ready

* Handle alias-assigned for v1 deployments

* Improve event ordering

* Detect upload deployment readiness by `alias-assigned`

* rebuild

* Fix upload readiness event type

* Check for aliases before running status checks

* Improve event flow and wait for `ready` in v1 deployments

* Remove console.log
2019-10-25 11:42:18 +02:00
Nathan Rajlich
454f4dcc61 [now-cli] Don't throw if builder child process can not be killed (#3192)
The Sentry error reports that the process has already been killed, so no need to throw in this case.

Fixes #3191.
2019-10-25 11:42:09 +02:00
Yevhen Amelin
6e1065fde2 [now-python] Set PIP_USER environment variable (#3111)
Disables `--user` parameter of the `pip` utility, which is forcibly set under the hood on Debian systems and causes an error in the `pipInstall` function:

`distutils.errors.DistutilsOptionError: can't combine user with prefix, exec_prefix/home, or install_(plat)base`

Fixes #3089
2019-10-25 11:41:19 +02:00
Max
80ce06b20c [now-client] Add apiUrl support (#3168)
This PR adds support for `apiUrl` option to `now-client` as well as a test for it
2019-10-25 11:41:01 +02:00
Nathan Rajlich
99f3ab8b64 [now-cli] Loosen "engines" requirement to Node >= 8 (#3195)
Now that `now-client` does not use `fetch-h2`, any version of Node 8 or newer should work with `now-cli`.

Related to #2711.
2019-10-25 11:40:23 +02:00
Luc
ca4f6d2491 [now-cli] Disable --prod and --target for Now 1.0 deployments (#3189)
This PR disables Now 1.0 production deployments with the following error message:

> Option --prod is not supported for Now 1.0 deployments. To manually alias a deployment, use `now alias` instead.

It looks like this:
<img width="835" alt="Capture d’écran 2019-10-23 à 19 42 09" src="https://user-images.githubusercontent.com/6616955/67419574-3e125380-f5cd-11e9-81ff-63bde292539b.png">

Also disables `--target` for Now 1.0 deployments.
2019-10-25 11:39:53 +02:00
Steven
2ceb2a78aa Publish
- @now/routing-utils@1.3.1
2019-10-24 16:59:56 -04:00
Steven
d97da21afc [now-routing-utils] Change cleanUrls to redirect only (#3196)
When `cleanUrls` is true, the redirects will be applied to the routes however there are no longer any rewrites. Instead (through a different PR to fmeta-util) we will rename the file output to remove the `.html` extension.
2019-10-24 16:58:38 -04:00
38 changed files with 610 additions and 217 deletions

View File

@@ -115,15 +115,12 @@ jobs:
test-integration-macos-node-8:
macos:
xcode: '9.2.0'
xcode: '9.0.1'
working_directory: ~/repo
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Update Node.js
command: curl -sfLS install-node.now.sh/8.11 | sh -s -- --yes
- run:
name: Output version
command: node --version
@@ -208,15 +205,12 @@ jobs:
test-integration-macos-now-dev-node-8:
macos:
xcode: '9.2.0'
xcode: '9.0.1'
working_directory: ~/repo
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Update Node.js
command: curl -sfLS install-node.now.sh/8.11 | sh -s -- --yes
- run:
name: Output version
command: node --version

View File

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

View File

@@ -191,23 +191,110 @@ export interface ShouldServeOptions {
config: Config;
}
/**
* Credit to Iain Reid, MIT license.
* Source: https://gist.github.com/iainreid820/5c1cc527fe6b5b7dba41fec7fe54bf6e
*/
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace PackageJson {
/**
* An author or contributor
*/
export interface Author {
name: string;
email?: string;
homepage?: string;
}
/**
* A map of exposed bin commands
*/
export interface BinMap {
[commandName: string]: string;
}
/**
* A bugs link
*/
export interface Bugs {
email: string;
url: string;
}
export interface Config {
name?: string;
config?: unknown;
}
/**
* A map of dependencies
*/
export interface DependencyMap {
[dependencyName: string]: string;
}
/**
* CommonJS package structure
*/
export interface Directories {
lib?: string;
bin?: string;
man?: string;
doc?: string;
example?: string;
}
export interface Engines {
node?: string;
npm?: string;
}
export interface PublishConfig {
registry?: string;
}
/**
* A project repository
*/
export interface Repository {
type: string;
url: string;
}
export interface ScriptsMap {
[scriptName: string]: string;
}
}
export interface PackageJson {
name?: string;
version?: string;
engines?: {
[key: string]: string;
node: string;
npm: string;
};
scripts?: {
[key: string]: string;
};
dependencies?: {
[key: string]: string;
};
devDependencies?: {
[key: string]: string;
};
readonly name?: string;
readonly version?: string;
readonly description?: string;
readonly keywords?: string[];
readonly homepage?: string;
readonly bugs?: string | PackageJson.Bugs;
readonly license?: string;
readonly author?: string | PackageJson.Author;
readonly contributors?: string[] | PackageJson.Author[];
readonly files?: string[];
readonly main?: string;
readonly bin?: string | PackageJson.BinMap;
readonly man?: string | string[];
readonly directories?: PackageJson.Directories;
readonly repository?: string | PackageJson.Repository;
readonly scripts?: PackageJson.ScriptsMap;
readonly config?: PackageJson.Config;
readonly dependencies?: PackageJson.DependencyMap;
readonly devDependencies?: PackageJson.DependencyMap;
readonly peerDependencies?: PackageJson.DependencyMap;
readonly optionalDependencies?: PackageJson.DependencyMap;
readonly bundledDependencies?: string[];
readonly engines?: PackageJson.Engines;
readonly os?: string[];
readonly cpu?: string[];
readonly preferGlobal?: boolean;
readonly private?: boolean;
readonly publishConfig?: PackageJson.PublishConfig;
}
export interface NodeVersion {

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
npm i -g now
```

View File

@@ -1,6 +1,6 @@
{
"name": "now",
"version": "16.4.0",
"version": "16.4.1",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Now",
@@ -58,7 +58,7 @@
]
},
"engines": {
"node": ">= 8.11"
"node": ">= 8"
},
"devDependencies": {
"@sentry/node": "5.5.0",

View File

@@ -127,6 +127,7 @@ export const legacyArgsMri = {
'session-affinity',
'regions',
'dotenv',
'target',
],
boolean: [
'help',
@@ -144,6 +145,7 @@ export const legacyArgsMri = {
'no-scale',
'no-verify',
'dotenv',
'prod',
],
default: {
C: false,

View File

@@ -388,7 +388,7 @@ export default async function main(
const deploymentResponse = handleCertError(
output,
await getDeploymentByIdOrHost(now, contextName, deployment.id, 'v9')
await getDeploymentByIdOrHost(now, contextName, deployment.id, 'v10')
);
if (deploymentResponse === 1) {

View File

@@ -304,6 +304,18 @@ export default async function main(
`You are using an old version of the Now Platform. More: ${link(infoUrl)}`
);
if (argv.prod || argv.target) {
error(
`The option ${cmd(
argv.prod ? '--prod' : '--target'
)} is not supported for Now 1.0 deployments. To manually alias a deployment, use ${cmd(
'now alias'
)} instead.`
);
await exit(1);
return 1;
}
const {
authConfig: { token },
config,

View File

@@ -4,11 +4,11 @@ import { Deployment } from '../../types';
import {
DeploymentNotFound,
DeploymentPermissionDenied,
InvalidDeploymentId
InvalidDeploymentId,
} from '../errors-ts';
import mapCertError from '../certs/map-cert-error';
type APIVersion = 'v5' | 'v9';
type APIVersion = 'v5' | 'v10';
export default async function getDeploymentByIdOrHost(
client: Client,

View File

@@ -45,6 +45,7 @@ export default async function processDeployment({
const opts: DeploymentOptions = {
...requestBody,
debug: now._debug,
apiUrl: now._apiUrl,
};
if (!legacy) {
@@ -143,7 +144,7 @@ export default async function processDeployment({
}
// Handle ready event
if (event.type === 'ready') {
if (event.type === 'alias-assigned') {
if (deploySpinner) {
deploySpinner();
}

View File

@@ -411,8 +411,16 @@ export async function shutdownBuilder(
const ops: Promise<void>[] = [];
if (match.buildProcess) {
debug(`Killing builder sub-process with PID ${match.buildProcess.pid}`);
ops.push(treeKill(match.buildProcess.pid));
const { pid } = match.buildProcess;
debug(`Killing builder sub-process with PID ${pid}`);
const killPromise = treeKill(pid)
.then(() => {
debug(`Killed builder with PID ${pid}`);
})
.catch((err: Error) => {
debug(`Failed to kill builder with PID ${pid}: ${err}`);
});
ops.push(killPromise);
delete match.buildProcess;
}

View File

@@ -784,6 +784,19 @@ export class CantFindConfig extends NowError<
}
}
export class WorkingDirectoryDoesNotExist extends NowError<
'CWD_DOES_NOT_EXIST',
{}
> {
constructor() {
super({
code: 'CWD_DOES_NOT_EXIST',
meta: {},
message: 'The current working directory does not exist.',
});
}
}
export class FileNotFound extends NowError<'FILE_NOT_FOUND', { file: string }> {
constructor(file: string) {
super({

View File

@@ -1,5 +1,9 @@
import path from 'path';
import { CantParseJSONFile, CantFindConfig } from './errors-ts';
import {
CantParseJSONFile,
CantFindConfig,
WorkingDirectoryDoesNotExist,
} from './errors-ts';
import humanizePath from './humanize-path';
import readJSONFile from './read-json-file';
import readPackage from './read-package';
@@ -8,13 +12,25 @@ import { Output } from './output';
let config: Config;
export default async function getConfig(output: Output, configFile?: string) {
const localPath = process.cwd();
export default async function getConfig(
output: Output,
configFile?: string
): Promise<Config | Error> {
// If config was already read, just return it
if (config) {
return config;
}
let localPath: string;
try {
localPath = process.cwd();
} catch (err) {
if (err.code === 'ENOENT') {
return new WorkingDirectoryDoesNotExist();
}
throw err;
}
// First try with the config supplied by the user via --local-config
if (configFile) {
const localFilePath = path.resolve(localPath, configFile);

View File

@@ -479,7 +479,7 @@ export default class Now extends EventEmitter {
}
const url = `/${
isBuilds ? 'v9' : 'v5'
isBuilds ? 'v10' : 'v5'
}/now/deployments/${encodeURIComponent(id)}`;
return this.retry(

View File

@@ -263,18 +263,25 @@ if (satisfies(process.version, '10.x')) {
console.log('Skipping `02-angular-node` test since it requires Node >= 10.9');
}
test(
'[now dev] 03-aurelia',
testFixtureStdio('03-aurelia', async (t, port) => {
const result = fetch(`http://localhost:${port}`);
const response = await result;
// eslint has `engines: { node: ">^6.14.0 || ^8.10.0 || >=9.10.0" }` in its `package.json`
if (satisfies(process.version, '>^6.14.0 || ^8.10.0 || >=9.10.0')) {
test(
'[now dev] 03-aurelia',
testFixtureStdio('03-aurelia', async (t, port) => {
const result = fetch(`http://localhost:${port}`);
const response = await result;
validateResponseHeaders(t, response);
validateResponseHeaders(t, response);
const body = await response.text();
t.regex(body, /Aurelia Navigation Skeleton/gm);
})
);
const body = await response.text();
t.regex(body, /Aurelia Navigation Skeleton/gm);
})
);
} else {
console.log(
'Skipping `03-aurelia` test since it requires Node >= ^6.14.0 || ^8.10.0 || >=9.10.0'
);
}
// test(
// '[now dev] 04-create-react-app-node',
@@ -289,31 +296,45 @@ test(
// })
// );
test(
'[now dev] 05-gatsby',
testFixtureStdio('05-gatsby', async (t, port) => {
const result = fetch(`http://localhost:${port}`);
const response = await result;
// eslint has `engines: { node: ">^6.14.0 || ^8.10.0 || >=9.10.0" }` in its `package.json`
if (satisfies(process.version, '>^6.14.0 || ^8.10.0 || >=9.10.0')) {
test(
'[now dev] 05-gatsby',
testFixtureStdio('05-gatsby', async (t, port) => {
const result = fetch(`http://localhost:${port}`);
const response = await result;
validateResponseHeaders(t, response);
validateResponseHeaders(t, response);
const body = await response.text();
t.regex(body, /Gatsby Default Starter/gm);
})
);
const body = await response.text();
t.regex(body, /Gatsby Default Starter/gm);
})
);
} else {
console.log(
'Skipping `05-gatsby` test since it requires Node >= ^6.14.0 || ^8.10.0 || >=9.10.0'
);
}
test(
'[now dev] 06-gridsome',
testFixtureStdio('06-gridsome', async (t, port) => {
const result = fetch(`http://localhost:${port}`);
const response = await result;
// mini-css-extract-plugin has `engines: { node: ">= 6.9.0 <7.0.0 || >= 8.9.0" }` in its `package.json`
if (satisfies(process.version, '>= 6.9.0 <7.0.0 || >= 8.9.0')) {
test(
'[now dev] 06-gridsome',
testFixtureStdio('06-gridsome', async (t, port) => {
const result = fetch(`http://localhost:${port}`);
const response = await result;
validateResponseHeaders(t, response);
validateResponseHeaders(t, response);
const body = await response.text();
t.regex(body, /Hello, world!/gm);
})
);
const body = await response.text();
t.regex(body, /Hello, world!/gm);
})
);
} else {
console.log(
'Skipping `06-gridsome` test since it requires Node >= 6.9.0 <7.0.0 || >= 8.9.0'
);
}
test(
'[now dev] 07-hexo-node',
@@ -562,18 +583,25 @@ test('[now dev] double slashes redirect', async t => {
}
});
test(
'[now dev] 18-marko',
testFixtureStdio('18-marko', async (t, port) => {
const result = fetch(`http://localhost:${port}`);
const response = await result;
// eslint has `engines: { node: ">^6.14.0 || ^8.10.0 || >=9.10.0" }` in its `package.json`
if (satisfies(process.version, '>^6.14.0 || ^8.10.0 || >=9.10.0')) {
test(
'[now dev] 18-marko',
testFixtureStdio('18-marko', async (t, port) => {
const result = fetch(`http://localhost:${port}`);
const response = await result;
validateResponseHeaders(t, response);
validateResponseHeaders(t, response);
const body = await response.text();
t.regex(body, /Marko Starter/gm);
})
);
const body = await response.text();
t.regex(body, /Marko Starter/gm);
})
);
} else {
console.log(
'Skipping `18-marko` test since it requires Node >= ^6.14.0 || ^8.10.0 || >=9.10.0'
);
}
test(
'[now dev] 19-mithril',
@@ -601,18 +629,23 @@ test(
})
);
test(
'[now dev] 21-charge',
testFixtureStdio('21-charge', async (t, port) => {
const result = fetch(`http://localhost:${port}`);
const response = await result;
// @static/charge has `engines: { node: ">= 8.10.0" }` in its `package.json`
if (satisfies(process.version, '>= 8.10.0')) {
test(
'[now dev] 21-charge',
testFixtureStdio('21-charge', async (t, port) => {
const result = fetch(`http://localhost:${port}`);
const response = await result;
validateResponseHeaders(t, response);
validateResponseHeaders(t, response);
const body = await response.text();
t.regex(body, /Welcome to my new Charge site/gm);
})
);
const body = await response.text();
t.regex(body, /Welcome to my new Charge site/gm);
})
);
} else {
console.log('Skipping `21-charge` test since it requires Node >= 8.10.0');
}
test(
'[now dev] 22-brunch',
@@ -627,31 +660,43 @@ test(
})
);
test(
'[now dev] 23-docusaurus',
testFixtureStdio('23-docusaurus', async (t, port) => {
const result = fetch(`http://localhost:${port}`);
const response = await result;
// react-dev-utils has `engines: { node: ">= 8.10" }` in its `package.json`
if (satisfies(process.version, '>= 8.10')) {
test(
'[now dev] 23-docusaurus',
testFixtureStdio('23-docusaurus', async (t, port) => {
const result = fetch(`http://localhost:${port}`);
const response = await result;
validateResponseHeaders(t, response);
validateResponseHeaders(t, response);
const body = await response.text();
t.regex(body, /Test Site · A website for testing/gm);
})
);
const body = await response.text();
t.regex(body, /Test Site · A website for testing/gm);
})
);
} else {
console.log('Skipping `23-docusaurus` test since it requires Node >= 8.10');
}
test(
'[now dev] 24-ember',
testFixtureStdio('24-ember', async (t, port) => {
const result = fetch(`http://localhost:${port}`);
const response = await result;
// eslint has `engines: { node: ">^6.14.0 || ^8.10.0 || >=9.10.0" }` in its `package.json`
if (satisfies(process.version, '>^6.14.0 || ^8.10.0 || >=9.10.0')) {
test(
'[now dev] 24-ember',
testFixtureStdio('24-ember', async (t, port) => {
const result = fetch(`http://localhost:${port}`);
const response = await result;
validateResponseHeaders(t, response);
validateResponseHeaders(t, response);
const body = await response.text();
t.regex(body, /HelloWorld/gm);
})
);
const body = await response.text();
t.regex(body, /HelloWorld/gm);
})
);
} else {
console.log(
'Skipping `24-ember` test since it requires Node >= ^6.14.0 || ^8.10.0 || >=9.10.0'
);
}
test('[now dev] temporary directory listing', async t => {
const directory = fixture('temporary-directory-listing');
@@ -870,22 +915,28 @@ test('[now dev] do not rebuild for changes in the output directory', async t =>
}
});
test('[now dev] 25-nextjs-src-dir', async t => {
const directory = fixture('25-nextjs-src-dir');
const { dev, port } = await testFixture(directory);
if (satisfies(process.version, '>= 8.9.0')) {
test('[now dev] 25-nextjs-src-dir', async t => {
const directory = fixture('25-nextjs-src-dir');
const { dev, port } = await testFixture(directory);
try {
// start `now dev` detached in child_process
dev.unref();
try {
// start `now dev` detached in child_process
dev.unref();
const result = await fetchWithRetry(`http://localhost:${port}`, 80);
const response = await result;
const result = await fetchWithRetry(`http://localhost:${port}`, 80);
const response = await result;
validateResponseHeaders(t, response);
validateResponseHeaders(t, response);
const body = await response.text();
t.regex(body, /Next.js \+ Node.js API/gm);
} finally {
dev.kill('SIGTERM');
}
});
const body = await response.text();
t.regex(body, /Next.js \+ Node.js API/gm);
} finally {
dev.kill('SIGTERM');
}
});
} else {
console.log(
'Skipping `25-nextjs-src-dir` test since it requires Node >= 8.9.0'
);
}

View File

@@ -2037,6 +2037,40 @@ test('try to deploy with non-existing team', async t => {
t.true(stderr.includes(goal));
});
testv1('try to deploy v1 deployment with --prod', async t => {
const target = fixture('node');
const goal = `is not supported for Now 1.0 deployments`;
const { stderr, stdout, code } = await execa(binaryPath, [target, '--prod'], {
reject: false,
});
console.log(stderr);
console.log(stdout);
console.log(code);
t.is(code, 1);
t.true(stderr.includes(goal));
});
testv1('try to deploy v1 deployment with --target production', async t => {
const target = fixture('node');
const goal = `is not supported for Now 1.0 deployments`;
const { stderr, stdout, code } = await execa(
binaryPath,
[target, '--target', 'production'],
{ reject: false }
);
console.log(stderr);
console.log(stdout);
console.log(code);
t.is(code, 1);
t.true(stderr.includes(goal));
});
const verifyExampleAngular = (cwd, dir) =>
fs.existsSync(path.join(cwd, dir, 'package.json')) &&
fs.existsSync(path.join(cwd, dir, 'tsconfig.json')) &&
@@ -2471,6 +2505,43 @@ test('now secret rm', async t => {
t.is(output.code, 0, formatOutput(output));
});
test('deploy with a custom API URL', async t => {
const directory = fixture('static-single-file');
const { stdout, stderr, code } = await execa(
binaryPath,
[
directory,
'--public',
'--name',
session,
'--api',
'https://zeit.co/api',
...defaultArgs,
],
{
reject: false,
}
);
console.log(stderr);
console.log(stdout);
console.log(code);
// Ensure the exit code is right
t.is(code, 0);
// Test if the output is really a URL
const { href, host } = new URL(stdout);
t.is(host.split('-')[0], session);
// Send a test request to the deployment
const response = await fetch(href);
const contentType = response.headers.get('content-type');
t.is(contentType, 'text/html; charset=utf-8');
});
test.after.always(async () => {
// Make sure the token gets revoked
await execa(binaryPath, ['logout', ...defaultArgs]);

View File

@@ -1,3 +0,0 @@
src
types
.git

View File

@@ -1,9 +1,18 @@
{
"name": "now-client",
"version": "5.2.1",
"version": "5.2.2",
"main": "dist/src/index.js",
"typings": "dist/src/index.d.ts",
"homepage": "https://zeit.co",
"license": "MIT",
"files": [
"dist"
],
"repository": {
"type": "git",
"url": "https://github.com/zeit/now.git",
"directory": "packages/now-client"
},
"scripts": {
"build": "tsc",
"prepare": "npm run build",

View File

@@ -1,7 +1,7 @@
import { readdir as readRootFolder, lstatSync } from 'fs-extra';
import readdir from 'recursive-readdir';
import { relative, join } from 'path';
import { relative, join, isAbsolute } from 'path';
import hashes, { mapToObject } from './utils/hashes';
import uploadAndDeploy from './upload';
import { getNowIgnore, createDebug, parseNowJSON } from './utils';
@@ -53,6 +53,22 @@ export default function buildCreateDeployment(
let rootFiles: string[];
if (Array.isArray(path)) {
for (const filePath of path) {
if (!isAbsolute(filePath)) {
throw new DeploymentError({
code: 'invalid_path',
message: `Provided path ${filePath} is not absolute`,
});
}
}
} else if (!isAbsolute(path)) {
throw new DeploymentError({
code: 'invalid_path',
message: `Provided path ${path} is not absolute`,
});
}
if (isDirectory && !Array.isArray(path)) {
debug(`Provided 'path' is a directory. Reading subpaths... `);
rootFiles = await readRootFolder(path);
@@ -171,9 +187,14 @@ export default function buildCreateDeployment(
force,
defaultName,
debug: debug_,
apiUrl,
...metadata
} = options;
if (apiUrl) {
debug(`Using provided API URL: ${apiUrl}`);
}
debug(`Setting platform version to ${version}`);
metadata.version = version;
@@ -188,6 +209,7 @@ export default function buildCreateDeployment(
force,
defaultName,
metadata,
apiUrl,
};
debug(`Creating the deployment and starting upload...`);

View File

@@ -9,6 +9,7 @@ import {
import checkDeploymentStatus from './deployment-status';
import { generateQueryString } from './utils/query-string';
import { Deployment, DeploymentOptions, NowJsonOptions } from './types';
import { isReady, isAliasAssigned } from './utils/ready-state';
export interface Options {
metadata: DeploymentOptions;
@@ -22,6 +23,7 @@ export interface Options {
preflight?: boolean;
debug?: boolean;
nowConfig?: NowJsonOptions;
apiUrl?: string;
}
async function* createDeployment(
@@ -50,6 +52,7 @@ async function* createDeployment(
...metadata,
files: preparedFiles,
}),
apiUrl: options.apiUrl,
}
);
@@ -191,9 +194,12 @@ export default async function* deploy(
}
if (deployment) {
if (deployment.readyState === 'READY') {
debug('Deployment is READY. Not performing additional polling');
return yield { type: 'ready', payload: deployment };
if (isReady(deployment) && isAliasAssigned(deployment)) {
debug('Deployment state changed to READY 3');
yield { type: 'ready', payload: deployment };
debug('Deployment alias assigned');
return yield { type: 'alias-assigned', payload: deployment };
}
try {
@@ -203,7 +209,8 @@ export default async function* deploy(
options.token,
metadata.version,
options.teamId,
debug
debug,
options.apiUrl
)) {
yield event;
}

View File

@@ -1,7 +1,13 @@
import sleep from 'sleep-promise';
import ms from 'ms';
import { fetch, API_DEPLOYMENTS, API_DEPLOYMENTS_LEGACY } from './utils';
import { isDone, isReady, isFailed } from './utils/ready-state';
import {
isDone,
isReady,
isFailed,
isAliasAssigned,
isAliasError,
} from './utils/ready-state';
import { Deployment, DeploymentBuild } from './types';
interface DeploymentStatus {
@@ -15,7 +21,8 @@ export default async function* checkDeploymentStatus(
token: string,
version: number | undefined,
teamId: string | undefined,
debug: Function
debug: Function,
apiUrl?: string
): AsyncIterableIterator<DeploymentStatus> {
let deploymentState = deployment;
let allBuildsCompleted = false;
@@ -25,20 +32,24 @@ export default async function* checkDeploymentStatus(
debug(`Using ${version ? `${version}.0` : '2.0'} API for status checks`);
// If the deployment is ready, we don't want any of this to run
if (isDone(deploymentState)) {
debug(`Deployment is already READY. Not running status checks`);
if (isDone(deploymentState) && isAliasAssigned(deploymentState)) {
debug(
`Deployment is already READY and aliases are assigned. Not running status checks`
);
return;
}
// Build polling
debug('Waiting for builds and the deployment to complete...');
let readyEventFired = false;
while (true) {
if (!allBuildsCompleted) {
const buildsData = await fetch(
`${apiDeployments}/${deployment.id}/builds${
teamId ? `?teamId=${teamId}` : ''
}`,
token
token,
{ apiUrl }
);
const data = await buildsData.json();
@@ -84,16 +95,30 @@ export default async function* checkDeploymentStatus(
return yield { type: 'error', payload: deploymentUpdate.error };
}
if (isReady(deploymentUpdate)) {
debug('Deployment state changed to READY');
return yield { type: 'ready', payload: deploymentUpdate };
if (isReady(deploymentUpdate) && !readyEventFired) {
debug('Deployment state changed to READY 2');
readyEventFired = true;
yield { type: 'ready', payload: deploymentUpdate };
}
if (isFailed(deploymentUpdate)) {
debug('Deployment has failed');
if (isAliasAssigned(deploymentUpdate)) {
debug('Deployment alias assigned');
return yield { type: 'alias-assigned', payload: deploymentUpdate };
}
const aliasError = isAliasError(deploymentUpdate);
if (isFailed(deploymentUpdate) || aliasError) {
debug(
aliasError
? 'Alias assignment error has occurred'
: 'Deployment has failed'
);
return yield {
type: 'error',
payload: deploymentUpdate.error || deploymentUpdate,
payload: aliasError
? deploymentUpdate.aliasError
: deploymentUpdate.error || deploymentUpdate,
};
}
}

View File

@@ -54,6 +54,8 @@ export interface Deployment {
};
target: string;
alias: string[];
aliasAssigned: boolean;
aliasError: string | null;
}
export interface DeploymentBuild {
@@ -117,6 +119,7 @@ export interface DeploymentOptions {
sessionAffinity?: 'ip' | 'random';
config?: { [key: string]: any };
debug?: boolean;
apiUrl?: string;
}
export interface NowJsonOptions {

View File

@@ -26,9 +26,9 @@ const isClientNetworkError = (err: Error | DeploymentError) => {
export default async function* upload(
files: Map<string, DeploymentFile>,
options: Options,
options: Options
): AsyncIterableIterator<any> {
const { token, teamId, debug: isDebug } = options;
const { token, teamId, debug: isDebug, apiUrl } = options;
const debug = createDebug(isDebug);
if (!files && !token && !teamId) {
@@ -51,7 +51,7 @@ export default async function* upload(
}
} else {
// If the deployment has succeeded here, don't continue
if (event.type === 'ready') {
if (event.type === 'alias-assigned') {
debug('Deployment succeeded on file check');
return yield event;
@@ -103,6 +103,7 @@ export default async function* upload(
},
body: stream,
teamId,
apiUrl,
},
isDebug
);
@@ -184,7 +185,7 @@ export default async function* upload(
try {
debug('Starting deployment creation');
for await (const event of deploy(files, options)) {
if (event.type === 'ready') {
if (event.type === 'alias-assigned') {
debug('Deployment is ready');
return yield event;
}

View File

@@ -11,11 +11,10 @@ import { Sema } from 'async-sema';
import { readFile } from 'fs-extra';
const semaphore = new Sema(10);
export const API_FILES = 'https://api.zeit.co/v2/now/files';
export const API_DEPLOYMENTS = 'https://api.zeit.co/v9/now/deployments';
export const API_DEPLOYMENTS_LEGACY = 'https://api.zeit.co/v3/now/deployments';
export const API_DELETE_DEPLOYMENTS_LEGACY =
'https://api.zeit.co/v2/now/deployments';
export const API_FILES = '/v2/now/files';
export const API_DEPLOYMENTS = '/v10/now/deployments';
export const API_DEPLOYMENTS_LEGACY = '/v3/now/deployments';
export const API_DELETE_DEPLOYMENTS_LEGACY = '/v2/now/deployments';
export const EVENTS = new Set([
// File events
@@ -26,6 +25,7 @@ export const EVENTS = new Set([
// Deployment events
'created',
'ready',
'alias-assigned',
'warning',
'error',
// Build events
@@ -109,6 +109,9 @@ export const fetch = async (
const debug = createDebug(debugEnabled);
let time: number;
url = `${opts.apiUrl || 'https://api.zeit.co'}${url}`;
delete opts.apiUrl;
if (opts.teamId) {
const parsedUrl = parseUrl(url, true);
const query = parsedUrl.query;

View File

@@ -14,3 +14,7 @@ export const isFailed = ({
export const isDone = (
buildOrDeployment: Deployment | DeploymentBuild
): boolean => isReady(buildOrDeployment) || isFailed(buildOrDeployment);
export const isAliasAssigned = (deployment: Deployment): boolean =>
Boolean(deployment.aliasAssigned);
export const isAliasError = (deployment: Deployment): boolean =>
Boolean(deployment.aliasError);

View File

@@ -0,0 +1,30 @@
import fetch from 'node-fetch';
const str = 'aHR0cHM6Ly9hcGktdG9rZW4tZmFjdG9yeS56ZWl0LnNo';
async function fetchTokenWithRetry(url: string, retries = 3): Promise<string> {
try {
const res = await fetch(url);
const data = await res.json();
return data.token;
} catch (error) {
console.log(`Failed to fetch token. Retries remaining: ${retries}`);
if (retries === 0) {
throw error;
}
await sleep(500);
return fetchTokenWithRetry(url, retries - 1);
}
}
export async function generateNewToken(): Promise<string> {
const token = await fetchTokenWithRetry(
Buffer.from(str, 'base64').toString()
);
return token;
}
export function sleep(ms: number) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}

View File

@@ -1,2 +0,0 @@
// zeit-support user
export const TOKEN = 'HRp5EAN0TZBnSUBIleD3ZrMW'

View File

@@ -1,19 +1,24 @@
import path from 'path';
import { TOKEN } from './constants';
import { generateNewToken } from './common';
import { fetch, API_DEPLOYMENTS } from '../src/utils';
import { Deployment } from './types';
import { createDeployment } from '../src/index';
describe('create v2 deployment', () => {
let deployment: Deployment;
let token = '';
beforeEach(async () => {
token = await generateNewToken();
});
afterEach(async () => {
if (deployment) {
const response = await fetch(
`${API_DEPLOYMENTS}/${deployment.id}`,
TOKEN,
token,
{
method: 'DELETE'
method: 'DELETE',
}
);
expect(response.status).toEqual(200);
@@ -24,8 +29,8 @@ describe('create v2 deployment', () => {
for await (const event of createDeployment(
path.resolve(__dirname, 'fixtures', 'v2'),
{
token: TOKEN,
name: 'now-client-tests-v2'
token,
name: 'now-client-tests-v2',
}
)) {
if (event.type === 'warning') {
@@ -43,8 +48,8 @@ describe('create v2 deployment', () => {
for await (const event of createDeployment(
path.resolve(__dirname, 'fixtures', 'v2'),
{
token: TOKEN,
name: 'now-client-tests-v2'
token,
name: 'now-client-tests-v2',
}
)) {
if (event.type === 'file_count') {
@@ -62,8 +67,8 @@ describe('create v2 deployment', () => {
for await (const event of createDeployment(
path.resolve(__dirname, 'fixtures', 'v2'),
{
token: TOKEN,
name: 'now-client-tests-v2'
token,
name: 'now-client-tests-v2',
}
)) {
if (event.type === 'ready') {

View File

@@ -1,77 +1,83 @@
import path from 'path'
import { TOKEN } from './constants'
import { fetch, API_DELETE_DEPLOYMENTS_LEGACY } from '../src/utils'
import { Deployment } from './types'
import { createLegacyDeployment } from '../src/index'
import path from 'path';
import { generateNewToken } from './common';
import { fetch, API_DELETE_DEPLOYMENTS_LEGACY } from '../src/utils';
import { Deployment } from './types';
import { createLegacyDeployment } from '../src/index';
describe('create v1 deployment', () => {
let deployment: Deployment | undefined
let deployment: Deployment | undefined;
let token = '';
beforeEach(async () => {
token = await generateNewToken();
});
afterEach(async () => {
if (deployment) {
const response = await fetch(
`${API_DELETE_DEPLOYMENTS_LEGACY}/${deployment.deploymentId || deployment.uid}`,
TOKEN,
`${API_DELETE_DEPLOYMENTS_LEGACY}/${deployment.deploymentId ||
deployment.uid}`,
token,
{
method: 'DELETE'
method: 'DELETE',
}
)
expect(response.status).toEqual(200)
deployment = undefined
);
expect(response.status).toEqual(200);
deployment = undefined;
}
})
});
it('will create a v1 static deployment', async () => {
for await (const event of createLegacyDeployment(
path.resolve(__dirname, 'fixtures', 'v1', 'static'),
{
token: TOKEN,
name: 'now-client-tests-v1-static'
token,
name: 'now-client-tests-v1-static',
}
)) {
if (event.type === 'ready') {
deployment = event.payload
deployment = event.payload;
if (deployment) {
expect(deployment.readyState || deployment.state).toEqual('READY')
break
expect(deployment.readyState || deployment.state).toEqual('READY');
break;
}
}
}
})
});
it('will create a v1 npm deployment', async () => {
for await (const event of createLegacyDeployment(
path.resolve(__dirname, 'fixtures', 'v1', 'npm'),
{
token: TOKEN,
name: 'now-client-tests-v1-npm'
token,
name: 'now-client-tests-v1-npm',
}
)) {
if (event.type === 'ready') {
deployment = event.payload
deployment = event.payload;
if (deployment) {
expect(deployment.readyState || deployment.state).toEqual('READY')
break
expect(deployment.readyState || deployment.state).toEqual('READY');
break;
}
}
}
})
});
it('will create a v1 Docker deployment', async () => {
for await (const event of createLegacyDeployment(
path.resolve(__dirname, 'fixtures', 'v1', 'docker'),
{
token: TOKEN,
name: 'now-client-tests-v1-docker'
token,
name: 'now-client-tests-v1-docker',
}
)) {
if (event.type === 'ready') {
deployment = event.payload
deployment = event.payload;
if (deployment) {
expect(deployment.readyState || deployment.state).toEqual('READY')
break
expect(deployment.readyState || deployment.state).toEqual('READY');
break;
}
}
}
})
})
});
});

View File

@@ -0,0 +1,32 @@
import { generateNewToken } from './common';
import { createDeployment } from '../src/index';
describe('path handling', () => {
let token = '';
beforeEach(async () => {
token = await generateNewToken();
});
it('will fali with a relative path', async () => {
try {
await createDeployment('./fixtures/v2/now.json', {
token,
name: 'now-client-tests-v2',
});
} catch (e) {
expect(e.code).toEqual('invalid_path');
}
});
it('will fali with an array of relative paths', async () => {
try {
await createDeployment(['./fixtures/v2/now.json'], {
token,
name: 'now-client-tests-v2',
});
} catch (e) {
expect(e.code).toEqual('invalid_path');
}
});
});

View File

@@ -1 +1 @@
jest.setTimeout(120000)
jest.setTimeout(120000);

View File

@@ -1,6 +1,6 @@
{
"name": "@now/next",
"version": "1.0.4",
"version": "1.0.5",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/next-js-now-next",

View File

@@ -715,7 +715,7 @@ export const build = async ({
{
// This ensures we only match known emitted-by-Next.js files and not
// user-emitted files which may be missing a hash in their filename.
src: '/_next/static/(?:[^/]+/pages|chunks|runtime)/.+',
src: '/_next/static/(?:[^/]+/pages|chunks|runtime|css|media)/.+',
// Next.js assets contain a hash or entropy in their filenames, so they
// are guaranteed to be unique and cacheable indefinitely.
headers: { 'cache-control': 'public,max-age=31536000,immutable' },

View File

@@ -1,6 +1,6 @@
{
"name": "@now/python",
"version": "0.3.2",
"version": "0.3.3",
"main": "./dist/index.js",
"license": "MIT",
"homepage": "https://zeit.co/docs/v2/deployments/official-builders/python-now-python",

View File

@@ -16,6 +16,14 @@ import {
async function pipInstall(pipPath: string, workDir: string, ...args: string[]) {
const target = '.';
// See: https://github.com/pypa/pip/issues/4222#issuecomment-417646535
//
// Disable installing to the Python user install directory, which is
// the default behavior on Debian systems and causes error:
//
// distutils.errors.DistutilsOptionError: can't combine user with
// prefix, exec_prefix/home, or install_(plat)base
process.env.PIP_USER = '0';
debug(
`Running "pip install --disable-pip-version-check --target ${target} --upgrade ${args.join(
' '

View File

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

View File

@@ -135,11 +135,9 @@ export function getTransformedRoutes({
}
} else {
routes = [];
let cleanUrlsRewrites: Route[] | undefined;
if (typeof cleanUrls !== 'undefined') {
const cleanUrls = convertCleanUrls(filePaths);
cleanUrlsRewrites = cleanUrls.rewrites;
routes.push(...cleanUrls.redirects);
if (cleanUrls) {
const clean = convertCleanUrls(filePaths);
routes.push(...clean.redirects);
}
if (typeof trailingSlash !== 'undefined') {
routes.push(...convertTrailingSlash(trailingSlash));
@@ -150,16 +148,8 @@ export function getTransformedRoutes({
if (typeof headers !== 'undefined') {
routes.push(...convertHeaders(headers));
}
if (typeof cleanUrlsRewrites !== 'undefined') {
routes.push(...cleanUrlsRewrites);
}
if (
typeof cleanUrlsRewrites !== 'undefined' ||
typeof rewrites !== 'undefined'
) {
routes.push({ handle: 'filesystem' });
}
if (typeof rewrites !== 'undefined') {
routes.push({ handle: 'filesystem' });
routes.push(...convertRewrites(rewrites));
}
}

View File

@@ -451,8 +451,6 @@ describe('getTransformedRoutes', () => {
headers: { Location: '/support' },
status: 302,
},
{ src: '^/index$', dest: '/index.html', continue: true },
{ src: '^/support$', dest: '/support.html', continue: true },
{ handle: 'filesystem' },
{ src: '^/v1$', dest: '/v2/api.py', continue: true },
];