mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-27 11:49:14 +00:00
Compare commits
72 Commits
@now/ruby@
...
@now/next@
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62beb0f78d | ||
|
|
5f9777f4af | ||
|
|
e00db4437a | ||
|
|
b0e4f2590d | ||
|
|
f0d58eac8c | ||
|
|
dae830d2b6 | ||
|
|
e3071e4e29 | ||
|
|
073d7ece23 | ||
|
|
071258ba33 | ||
|
|
c0e00dc69a | ||
|
|
6e5c136337 | ||
|
|
60428cd4cf | ||
|
|
c6b9d80eec | ||
|
|
953bdc10e5 | ||
|
|
becdbd2136 | ||
|
|
da9bb31259 | ||
|
|
8cbf036921 | ||
|
|
8f66e4a308 | ||
|
|
9627b612f2 | ||
|
|
8a9ded6d61 | ||
|
|
d89c772bd5 | ||
|
|
32137586b9 | ||
|
|
9a3e435175 | ||
|
|
01d5a10ebd | ||
|
|
040658fbfa | ||
|
|
51c00286a4 | ||
|
|
c3e274fc2f | ||
|
|
fe633c528a | ||
|
|
5e306d49f8 | ||
|
|
274259b7dd | ||
|
|
230e96c687 | ||
|
|
8c0c6e546d | ||
|
|
5e8541b936 | ||
|
|
300558f24e | ||
|
|
829f7d8aeb | ||
|
|
340f7db68a | ||
|
|
2fe987b5da | ||
|
|
5b3aa48cd6 | ||
|
|
080d96bfa1 | ||
|
|
7d9bf682b4 | ||
|
|
a913a4f59f | ||
|
|
2042c96d98 | ||
|
|
4ca0ff8426 | ||
|
|
554cc42d83 | ||
|
|
3f9e30d031 | ||
|
|
5a9ca8644c | ||
|
|
3f362d4b50 | ||
|
|
7360886c9a | ||
|
|
5b8a1b47b0 | ||
|
|
0506b262d2 | ||
|
|
17c4569f41 | ||
|
|
888ff83309 | ||
|
|
7191421470 | ||
|
|
e9c9aa5ce1 | ||
|
|
d98f3d8140 | ||
|
|
fbf659e4e1 | ||
|
|
6f7069aadd | ||
|
|
cef2c889f6 | ||
|
|
e42d6c422a | ||
|
|
2e9241b454 | ||
|
|
257a3b07fc | ||
|
|
df9322b392 | ||
|
|
28b5476bf3 | ||
|
|
70a61f045f | ||
|
|
238a8a8593 | ||
|
|
6f2d5c0045 | ||
|
|
fc798da398 | ||
|
|
0804483316 | ||
|
|
4a087f842e | ||
|
|
01c7bf0059 | ||
|
|
6c439516db | ||
|
|
0db4ee69ef |
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@@ -2,6 +2,7 @@
|
||||
# https://help.github.com/en/articles/about-code-owners
|
||||
|
||||
* @tootallnate @leo
|
||||
/.github/workflows @AndyBitz @styfle
|
||||
/packages/frameworks @AndyBitz
|
||||
/packages/now-cli/src/commands/dev/ @tootallnate @leo @styfle @AndyBitz
|
||||
/packages/now-cli/src/util/dev/ @tootallnate @leo @styfle @AndyBitz
|
||||
|
||||
17
.github/workflows/cancel.yml
vendored
Normal file
17
.github/workflows/cancel.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
name: Cancel
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
- '!master'
|
||||
jobs:
|
||||
cancel:
|
||||
name: 'Cancel Previous Runs'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 3
|
||||
steps:
|
||||
- uses: styfle/cancel-workflow-action@master
|
||||
with:
|
||||
workflow_id: 435869
|
||||
access_token: ${{ secrets.GITHUB_WORKFLOW_TOKEN }}
|
||||
|
||||
20
.github/workflows/continuous-integration.yml
vendored
20
.github/workflows/continuous-integration.yml
vendored
@@ -11,24 +11,31 @@ on:
|
||||
jobs:
|
||||
test-unit:
|
||||
name: Unit Tests
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
node: [10, 12]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- run: git fetch origin master --depth=10
|
||||
- run: git fetch origin ${{ github.ref }} --depth=10
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 10
|
||||
- run: yarn install && yarn run build
|
||||
- run: yarn run test-lint
|
||||
- run: yarn run test-unit --clean false
|
||||
- uses: actions/upload-artifact@v1
|
||||
- name: Upload Artifact
|
||||
if: matrix.os == 'ubuntu-latest' && matrix.node == 12 # only run once
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: test-unit-output
|
||||
path: packages/now-cli/.nyc_output
|
||||
|
||||
test-integration:
|
||||
name: Integration Tests
|
||||
timeout-minutes: 120
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@@ -39,10 +46,11 @@ jobs:
|
||||
|
||||
test-now-cli:
|
||||
name: Now CLI Tests
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
os: [ubuntu-latest]
|
||||
node: [10, 12]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
@@ -58,6 +66,7 @@ jobs:
|
||||
|
||||
test-now-dev:
|
||||
name: "`now dev` Tests"
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -77,6 +86,7 @@ jobs:
|
||||
|
||||
coverage:
|
||||
name: Coverage
|
||||
timeout-minutes: 10
|
||||
needs: [test-unit, test-now-cli, test-now-dev, test-integration]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||

|
||||
|
||||
[](https://circleci.com/gh/zeit/workflows/now/tree/master)
|
||||
[](https://github.com/zeit/now/actions?workflow=CI)
|
||||
[](https://spectrum.chat/zeit)
|
||||
|
||||
## Usage
|
||||
@@ -16,7 +16,6 @@ To quickly start a new project, run the following commands:
|
||||
```
|
||||
now init # Pick an example project to clone
|
||||
cd <PROJECT> # Change directory to the newly created project
|
||||
now dev # Run locally during development
|
||||
now # Deploy to the cloud
|
||||
```
|
||||
|
||||
|
||||
2
examples/hugo/themes/ananke/.gitignore
vendored
2
examples/hugo/themes/ananke/.gitignore
vendored
@@ -28,3 +28,5 @@ npm-debug.log
|
||||
|
||||
/junit.xml
|
||||
partials/structure/stylesheet.html
|
||||
|
||||
!dist
|
||||
|
||||
3
examples/hugo/themes/ananke/static/dist/css/app.d98f2eb6bcd1eaedb7edf166bd16af26.css
vendored
Normal file
3
examples/hugo/themes/ananke/static/dist/css/app.d98f2eb6bcd1eaedb7edf166bd16af26.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
examples/hugo/themes/ananke/static/dist/js/app.3fc0f988d21662902933.js
vendored
Normal file
1
examples/hugo/themes/ananke/static/dist/js/app.3fc0f988d21662902933.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!function(n){function t(e){if(r[e])return r[e].exports;var o=r[e]={i:e,l:!1,exports:{}};return n[e].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var r={};t.m=n,t.c=r,t.i=function(n){return n},t.d=function(n,r,e){t.o(n,r)||Object.defineProperty(n,r,{configurable:!1,enumerable:!0,get:e})},t.n=function(n){var r=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(r,"a",r),r},t.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},t.p="",t(t.s=1)}([function(n,t){},function(n,t,r){"use strict";var e=r(0);!function(n){n&&n.__esModule}(e)}]);
|
||||
4
now.json
4
now.json
@@ -10,6 +10,10 @@
|
||||
"GITHUB_ACCESS_TOKEN": "@now-api-examples-github-token",
|
||||
"SENTRY_DSN": "@sentry-product-dsn"
|
||||
},
|
||||
"github": {
|
||||
"silent": true,
|
||||
"autoJobCancelation": true
|
||||
},
|
||||
"headers": [
|
||||
{
|
||||
"source": "/api/frameworks",
|
||||
|
||||
@@ -91,6 +91,7 @@
|
||||
"@typescript-eslint/camelcase": 0,
|
||||
"@typescript-eslint/explicit-function-return-type": 0,
|
||||
"@typescript-eslint/no-empty-function": 0,
|
||||
"@typescript-eslint/no-unused-vars": 2,
|
||||
"@typescript-eslint/no-use-before-define": 0
|
||||
},
|
||||
"overrides": [
|
||||
|
||||
@@ -808,5 +808,22 @@
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/foundation.svg",
|
||||
"tagline": "Foundation is the most advanced responsive front-end framework in the world.",
|
||||
"description": "A Foundation app, created with the Foundation CLI."
|
||||
},
|
||||
{
|
||||
"name": "Other",
|
||||
"slug": null,
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/other.svg",
|
||||
"description": "No framework or a unoptimized framework.",
|
||||
"settings": {
|
||||
"buildCommand": {
|
||||
"placeholder": "`build` or `now-build` from `package.json` if it exists"
|
||||
},
|
||||
"devCommand": {
|
||||
"placeholder": "None"
|
||||
},
|
||||
"outputDirectory": {
|
||||
"placeholder": "`public` if it exists, or `.`"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
8
packages/frameworks/index.d.ts
vendored
8
packages/frameworks/index.d.ts
vendored
@@ -13,11 +13,11 @@ export type Setting = SettingValue | SettingPlaceholder;
|
||||
|
||||
export interface Framework {
|
||||
name: string;
|
||||
slug: string;
|
||||
slug: string | null;
|
||||
logo: string;
|
||||
demo: string;
|
||||
tagline: string;
|
||||
website: string;
|
||||
demo?: string;
|
||||
tagline?: string;
|
||||
website?: string;
|
||||
description: string;
|
||||
detectors?: {
|
||||
every?: FrameworkDetectionItem[];
|
||||
|
||||
3
packages/frameworks/logos/other.svg
Normal file
3
packages/frameworks/logos/other.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 4L16 15H4L10 4Z" stroke="#666666" stroke-dasharray="2 2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 215 B |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/frameworks",
|
||||
"version": "0.0.7",
|
||||
"version": "0.0.9",
|
||||
"main": "frameworks.json",
|
||||
"license": "UNLICENSED"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/build-utils",
|
||||
"version": "1.3.7-canary.1",
|
||||
"version": "1.3.9",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
|
||||
@@ -28,7 +28,7 @@ function getApiBuilders({ tag }: Pick<Options, 'tag'> = {}): Builder[] {
|
||||
return [
|
||||
{ src: 'api/**/*.js', use: `@now/node${withTag}`, config },
|
||||
{ src: 'api/**/*.ts', use: `@now/node${withTag}`, config },
|
||||
{ src: 'api/**/*.go', use: `@now/go${withTag}`, config },
|
||||
{ src: 'api/**/!(*_test).go', use: `@now/go${withTag}`, config },
|
||||
{ src: 'api/**/*.py', use: `@now/python${withTag}`, config },
|
||||
{ src: 'api/**/*.rb', use: `@now/ruby${withTag}`, config },
|
||||
];
|
||||
@@ -423,10 +423,18 @@ export async function detectBuilders(
|
||||
|
||||
let frontendBuilder: Builder | null = null;
|
||||
|
||||
if (hasBuildScript(pkg) || buildCommand || framework) {
|
||||
// Everything will be static if either is an empty string
|
||||
const ignoreBuild = buildCommand === '' || outputDirectory === '';
|
||||
|
||||
if (!ignoreBuild && (hasBuildScript(pkg) || buildCommand || framework)) {
|
||||
frontendBuilder = detectFrontBuilder(pkg, builders, files, options);
|
||||
} else {
|
||||
if (!options.ignoreBuildScript && pkg && builders.length === 0) {
|
||||
if (
|
||||
!ignoreBuild &&
|
||||
!options.ignoreBuildScript &&
|
||||
pkg &&
|
||||
builders.length === 0
|
||||
) {
|
||||
// We only show this error when there are no api builders
|
||||
// since the dependencies of the pkg could be used for those
|
||||
errors.push({
|
||||
@@ -442,7 +450,9 @@ export async function detectBuilders(
|
||||
// when there are no build steps
|
||||
const outDir = outputDirectory || 'public';
|
||||
|
||||
if (hasDirectory(outDir, files)) {
|
||||
// If `outputDirectory` is an empty string,
|
||||
// we'll default to the root directory.
|
||||
if (hasDirectory(outDir, files) && outputDirectory !== '') {
|
||||
frontendBuilder = {
|
||||
use: '@now/static',
|
||||
src: `${outDir}/**/*`,
|
||||
|
||||
@@ -5,7 +5,7 @@ import { promisify } from 'util';
|
||||
import { lstat, Stats } from 'fs-extra';
|
||||
import FileFsRef from '../file-fs-ref';
|
||||
|
||||
type GlobOptions = vanillaGlob_.IOptions;
|
||||
export type GlobOptions = vanillaGlob_.IOptions;
|
||||
|
||||
interface FsFiles {
|
||||
[filePath: string]: FileFsRef;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Lambda, createLambda, getLambdaOptionsFromFunction } from './lambda';
|
||||
import { Prerender } from './prerender';
|
||||
import download, { DownloadedFiles, isSymbolicLink } from './fs/download';
|
||||
import getWriteableDirectory from './fs/get-writable-directory';
|
||||
import glob from './fs/glob';
|
||||
import glob, { GlobOptions } from './fs/glob';
|
||||
import rename from './fs/rename';
|
||||
import {
|
||||
execAsync,
|
||||
@@ -40,6 +40,7 @@ export {
|
||||
DownloadedFiles,
|
||||
getWriteableDirectory,
|
||||
glob,
|
||||
GlobOptions,
|
||||
rename,
|
||||
execAsync,
|
||||
spawnAsync,
|
||||
|
||||
@@ -31,6 +31,7 @@ export interface Config {
|
||||
| undefined;
|
||||
maxLambdaSize?: string;
|
||||
includeFiles?: string | string[];
|
||||
excludeFiles?: string | string[];
|
||||
bundle?: boolean;
|
||||
ldsflags?: string;
|
||||
helpers?: boolean;
|
||||
|
||||
@@ -151,6 +151,27 @@ describe('Test `detectBuilders`', () => {
|
||||
expect(builders!.length).toBe(2);
|
||||
});
|
||||
|
||||
it('api go with test files', async () => {
|
||||
const files = [
|
||||
'api/index.go',
|
||||
'api/index_test.go',
|
||||
'api/test.go',
|
||||
'api/testing_another.go',
|
||||
'api/readme.md',
|
||||
'api/config/staging.go',
|
||||
'api/config/staging_test.go',
|
||||
'api/config/production.go',
|
||||
'api/config/production_test.go',
|
||||
'api/src/controllers/health.go',
|
||||
'api/src/controllers/user.module.go',
|
||||
'api/src/controllers/user.module_test.go',
|
||||
];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
expect(builders!.length).toBe(7);
|
||||
expect(builders!.some(b => b.src.endsWith('_test.go'))).toBe(false);
|
||||
});
|
||||
|
||||
it('just public', async () => {
|
||||
const files = ['public/index.html', 'public/favicon.ico', 'README.md'];
|
||||
|
||||
@@ -745,6 +766,53 @@ describe('Test `detectBuilders`', () => {
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('All static if `buildCommand` is an empty string', async () => {
|
||||
const files = ['index.html'];
|
||||
const projectSettings = { buildCommand: '' };
|
||||
const { builders, errors } = await detectBuilders(files, null, {
|
||||
projectSettings,
|
||||
});
|
||||
expect(errors).toBe(null);
|
||||
expect(builders).toBe(null);
|
||||
});
|
||||
|
||||
it('All static if `outputDirectory` is an empty string', async () => {
|
||||
const files = ['index.html'];
|
||||
const projectSettings = { outputDirectory: '' };
|
||||
const { builders, errors } = await detectBuilders(files, null, {
|
||||
projectSettings,
|
||||
});
|
||||
expect(errors).toBe(null);
|
||||
expect(builders).toBe(null);
|
||||
});
|
||||
|
||||
it('All static if `buildCommand` is an empty string with an `outputDirectory`', async () => {
|
||||
const files = ['out/index.html'];
|
||||
const projectSettings = { buildCommand: '', outputDirectory: 'out' };
|
||||
const { builders, errors } = await detectBuilders(files, null, {
|
||||
projectSettings,
|
||||
});
|
||||
expect(errors).toBe(null);
|
||||
expect(builders![0]!.use).toBe('@now/static');
|
||||
expect(builders![0]!.src).toBe('out/**/*');
|
||||
});
|
||||
|
||||
it('do not require build script when `buildCommand` is an empty string', async () => {
|
||||
const files = ['index.html', 'about.html', 'package.json'];
|
||||
const projectSettings = { buildCommand: '', outputDirectory: '' };
|
||||
const pkg = {
|
||||
scripts: {
|
||||
build: 'false',
|
||||
},
|
||||
};
|
||||
|
||||
const { builders, errors } = await detectBuilders(files, pkg, {
|
||||
projectSettings,
|
||||
});
|
||||
expect(builders).toBe(null);
|
||||
expect(errors).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
it('Test `detectRoutes`', async () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/cgi",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.3",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
"@zeit/best@0.4.3":
|
||||
version "0.4.3"
|
||||
resolved "http://registry.npmjs.org/@zeit/best/-/best-0.4.3.tgz#eaebdfa8b24121a97b1753501ea8c9330d549b30"
|
||||
resolved "https://registry.npmjs.org/@zeit/best/-/best-0.4.3.tgz#eaebdfa8b24121a97b1753501ea8c9330d549b30"
|
||||
dependencies:
|
||||
arg "1.0.0"
|
||||
chalk "2.3.1"
|
||||
@@ -144,7 +144,7 @@ call-me-maybe@^1.0.1:
|
||||
|
||||
chalk@2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "http://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz#523fe2678aec7b04e8041909292fe8b17059b796"
|
||||
resolved "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz#523fe2678aec7b04e8041909292fe8b17059b796"
|
||||
dependencies:
|
||||
ansi-styles "^3.2.0"
|
||||
escape-string-regexp "^1.0.5"
|
||||
@@ -585,8 +585,8 @@ minimatch@^3.0.4:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
mixin-deep@^1.2.0:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe"
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
|
||||
dependencies:
|
||||
for-in "^1.0.2"
|
||||
is-extendable "^1.0.1"
|
||||
@@ -704,7 +704,7 @@ rmfr@2.0.0:
|
||||
|
||||
safe-regex@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"
|
||||
resolved "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"
|
||||
dependencies:
|
||||
ret "~0.1.10"
|
||||
|
||||
|
||||
10
packages/now-cli/@types/is-port-reachable/index.d.ts
vendored
Normal file
10
packages/now-cli/@types/is-port-reachable/index.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
declare module 'is-port-reachable' {
|
||||
export interface IsPortReachableOptions {
|
||||
timeout?: number | undefined;
|
||||
host?: string;
|
||||
}
|
||||
export default function(
|
||||
port: number | undefined,
|
||||
options?: IsPortReachableOptions
|
||||
): Promise<boolean>;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "now",
|
||||
"version": "17.0.0-canary.20",
|
||||
"version": "17.0.0",
|
||||
"preferGlobal": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "The command-line interface for Now",
|
||||
@@ -63,6 +63,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sentry/node": "5.5.0",
|
||||
"@sindresorhus/slugify": "0.10.0",
|
||||
"@types/ansi-escapes": "3.0.0",
|
||||
"@types/ansi-regex": "4.0.0",
|
||||
"@types/async-retry": "1.2.1",
|
||||
@@ -89,10 +90,10 @@
|
||||
"@types/tar-fs": "1.16.1",
|
||||
"@types/text-table": "0.2.0",
|
||||
"@types/universal-analytics": "0.4.2",
|
||||
"@types/which": "1.3.1",
|
||||
"@types/which": "1.3.2",
|
||||
"@types/write-json-file": "2.2.1",
|
||||
"@zeit/dockerignore": "0.0.5",
|
||||
"@zeit/fun": "0.11.0",
|
||||
"@zeit/fun": "0.11.2",
|
||||
"@zeit/ncc": "0.18.5",
|
||||
"@zeit/source-map-support": "0.6.2",
|
||||
"ajv": "6.10.2",
|
||||
@@ -126,11 +127,13 @@
|
||||
"esm": "3.1.4",
|
||||
"execa": "3.2.0",
|
||||
"fs-extra": "7.0.1",
|
||||
"get-port": "5.1.1",
|
||||
"glob": "7.1.2",
|
||||
"http-proxy": "1.17.0",
|
||||
"ignore": "4.0.6",
|
||||
"ini": "1.3.4",
|
||||
"inquirer": "3.3.0",
|
||||
"inquirer": "7.0.4",
|
||||
"is-port-reachable": "3.0.0",
|
||||
"is-url": "1.2.2",
|
||||
"jaro-winkler": "0.2.8",
|
||||
"jsonlines": "0.1.1",
|
||||
@@ -172,7 +175,7 @@
|
||||
"universal-analytics": "0.4.20",
|
||||
"update-check": "1.5.3",
|
||||
"utility-types": "2.1.0",
|
||||
"which": "1.3.1",
|
||||
"which": "2.0.2",
|
||||
"which-promise": "1.0.0",
|
||||
"write-json-file": "2.2.0",
|
||||
"xdg-app-paths": "5.1.0",
|
||||
|
||||
@@ -8,12 +8,11 @@ import getAliases from '../../util/alias/get-aliases';
|
||||
import getScope from '../../util/get-scope.ts';
|
||||
import stamp from '../../util/output/stamp.ts';
|
||||
import strlen from '../../util/strlen.ts';
|
||||
import wait from '../../util/output/wait';
|
||||
|
||||
export default async function ls(ctx, opts, args, output) {
|
||||
const {
|
||||
authConfig: { token },
|
||||
config
|
||||
config,
|
||||
} = ctx;
|
||||
const { currentTeam } = config;
|
||||
const { apiUrl } = ctx;
|
||||
@@ -22,7 +21,7 @@ export default async function ls(ctx, opts, args, output) {
|
||||
apiUrl,
|
||||
token,
|
||||
currentTeam,
|
||||
debug: debugEnabled
|
||||
debug: debugEnabled,
|
||||
});
|
||||
let contextName = null;
|
||||
|
||||
@@ -51,7 +50,7 @@ export default async function ls(ctx, opts, args, output) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
cancelWait = wait(
|
||||
cancelWait = output.spinner(
|
||||
args[0]
|
||||
? `Fetching alias details for "${args[0]}" under ${chalk.bold(
|
||||
contextName
|
||||
@@ -112,13 +111,13 @@ function printAliasTable(aliases) {
|
||||
? a.deployment.url
|
||||
: chalk.gray('–'),
|
||||
a.alias,
|
||||
ms(Date.now() - new Date(a.created))
|
||||
])
|
||||
ms(Date.now() - new Date(a.created)),
|
||||
]),
|
||||
],
|
||||
{
|
||||
align: ['l', 'l', 'r'],
|
||||
hsep: ' '.repeat(4),
|
||||
stringLength: strlen
|
||||
stringLength: strlen,
|
||||
}
|
||||
).replace(/^/gm, ' ')}\n\n`;
|
||||
}
|
||||
@@ -130,13 +129,13 @@ function printPathAliasTable(rules) {
|
||||
rules.map(rule => [
|
||||
rule.pathname ? rule.pathname : chalk.cyan('[fallthrough]'),
|
||||
rule.method ? rule.method : '*',
|
||||
rule.dest
|
||||
rule.dest,
|
||||
])
|
||||
),
|
||||
{
|
||||
align: ['l', 'l', 'l', 'l'],
|
||||
hsep: ' '.repeat(6),
|
||||
stringLength: strlen
|
||||
stringLength: strlen,
|
||||
}
|
||||
).replace(/^(.*)/gm, ' $1')}\n`;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export default async function({ creditCards, clear = false, contextName }) {
|
||||
name: {
|
||||
label: rightPad('Full Name', 12),
|
||||
placeholder: 'John Appleseed',
|
||||
validateValue: data => data.trim().length > 0
|
||||
validateValue: data => data.trim().length > 0,
|
||||
},
|
||||
|
||||
cardNumber: {
|
||||
@@ -36,7 +36,7 @@ export default async function({ creditCards, clear = false, contextName }) {
|
||||
return false;
|
||||
}
|
||||
return ccValidator.isValidCardNumber(data, type);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
ccv: {
|
||||
@@ -46,7 +46,7 @@ export default async function({ creditCards, clear = false, contextName }) {
|
||||
validateValue: data => {
|
||||
const brand = state.cardNumber.brand.toLowerCase();
|
||||
return ccValidator.doesCvvMatchType(data, brand);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
expDate: {
|
||||
@@ -54,8 +54,8 @@ export default async function({ creditCards, clear = false, contextName }) {
|
||||
mask: 'expDate',
|
||||
placeholder: 'mm / yyyy',
|
||||
middleware: expDateMiddleware,
|
||||
validateValue: data => !ccValidator.isExpired(...data.split(' / '))
|
||||
}
|
||||
validateValue: data => !ccValidator.isExpired(...data.split(' / ')),
|
||||
},
|
||||
};
|
||||
|
||||
async function render() {
|
||||
@@ -80,7 +80,7 @@ export default async function({ creditCards, clear = false, contextName }) {
|
||||
mask: piece.mask,
|
||||
validateKeypress: piece.validateKeypress,
|
||||
validateValue: piece.validateValue,
|
||||
autoComplete: piece.autoComplete
|
||||
autoComplete: piece.autoComplete,
|
||||
});
|
||||
|
||||
piece.value = result;
|
||||
@@ -135,7 +135,7 @@ export default async function({ creditCards, clear = false, contextName }) {
|
||||
name: state.name.value,
|
||||
cardNumber: state.cardNumber.value,
|
||||
ccv: state.ccv.value,
|
||||
expDate: state.expDate.value
|
||||
expDate: state.expDate.value,
|
||||
});
|
||||
|
||||
stopSpinner();
|
||||
@@ -156,9 +156,9 @@ export default async function({ creditCards, clear = false, contextName }) {
|
||||
stopSpinner();
|
||||
const linesToClear = state.error ? 15 : 14;
|
||||
process.stdout.write(ansiEscapes.eraseLines(linesToClear));
|
||||
state.error = `${chalk.red(
|
||||
'> Error!'
|
||||
)} ${err.message} Please make sure the info is correct`;
|
||||
state.error = `${chalk.red('> Error!')} ${
|
||||
err.message
|
||||
} Please make sure the info is correct`;
|
||||
await render();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import Now from '../../util';
|
||||
import Client from '../../util/client';
|
||||
import getScope from '../../util/get-scope';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import wait from '../../util/output/wait';
|
||||
import createCertFromFile from '../../util/certs/create-cert-from-file';
|
||||
import createCertForCns from '../../util/certs/create-cert-for-cns';
|
||||
import { NowContext } from '../../types';
|
||||
@@ -110,7 +109,7 @@ async function add(
|
||||
(res, item) => res.concat(item.split(',')),
|
||||
[]
|
||||
);
|
||||
const cancelWait = wait(
|
||||
const cancelWait = output.spinner(
|
||||
`Generating a certificate for ${chalk.bold(cns.join(', '))}`
|
||||
);
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ export const latestHelp = () => `
|
||||
-h, --help Output usage information
|
||||
-v, --version Output the version number
|
||||
-V, --platform-version Set the platform version to deploy to
|
||||
-n, --name Set the project name of the deployment
|
||||
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
|
||||
'FILE'
|
||||
)} Path to the local ${'`now.json`'} file
|
||||
@@ -97,7 +96,6 @@ export const latestHelp = () => `
|
||||
`;
|
||||
|
||||
export const latestArgs = {
|
||||
'--name': String,
|
||||
'--force': Boolean,
|
||||
'--public': Boolean,
|
||||
'--no-clipboard': Boolean,
|
||||
@@ -108,10 +106,8 @@ export const latestArgs = {
|
||||
// This is not an array in favor of matching
|
||||
// the config property name.
|
||||
'--regions': String,
|
||||
'--target': String,
|
||||
'--prod': Boolean,
|
||||
'--confirm': Boolean,
|
||||
'-n': '--name',
|
||||
'-f': '--force',
|
||||
'-p': '--public',
|
||||
'-e': '--env',
|
||||
@@ -119,6 +115,11 @@ export const latestArgs = {
|
||||
'-C': '--no-clipboard',
|
||||
'-m': '--meta',
|
||||
'-c': '--confirm',
|
||||
|
||||
// deprecated
|
||||
'--name': String,
|
||||
'-n': '--name',
|
||||
'--target': String,
|
||||
};
|
||||
|
||||
export const legacyArgsMri = {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import ms from 'ms';
|
||||
import bytes from 'bytes';
|
||||
import { join } from 'path';
|
||||
import { write as copy } from 'clipboardy';
|
||||
import chalk from 'chalk';
|
||||
import title from 'title';
|
||||
@@ -47,8 +48,11 @@ import {
|
||||
import getProjectName from '../../util/get-project-name';
|
||||
import selectOrg from '../../util/input/select-org';
|
||||
import inputProject from '../../util/input/input-project';
|
||||
import validatePaths from '../../util/validate-paths';
|
||||
import { prependEmoji, emoji } from '../../util/emoji';
|
||||
import { inputRootDirectory } from '../../util/input/input-root-directory';
|
||||
import validatePaths, {
|
||||
validateRootDirectory,
|
||||
} from '../../util/validate-paths';
|
||||
|
||||
const addProcessEnv = async (log, env) => {
|
||||
let val;
|
||||
@@ -90,7 +94,8 @@ const printDeploymentStatus = async (
|
||||
},
|
||||
deployStamp,
|
||||
isClipboardEnabled,
|
||||
quiet
|
||||
quiet,
|
||||
isFile
|
||||
) => {
|
||||
const isProdDeployment = target === 'production';
|
||||
|
||||
@@ -113,7 +118,7 @@ const printDeploymentStatus = async (
|
||||
// print preview/production url
|
||||
let previewUrl;
|
||||
let isWildcard;
|
||||
if (Array.isArray(aliasList) && aliasList.length > 0) {
|
||||
if (!isFile && Array.isArray(aliasList) && aliasList.length > 0) {
|
||||
// search for a non now.sh/non wildcard domain
|
||||
// but fallback to the first alias in the list
|
||||
const mainAlias =
|
||||
@@ -130,10 +135,13 @@ const printDeploymentStatus = async (
|
||||
}
|
||||
|
||||
// copy to clipboard
|
||||
let isCopiedToClipboard = false;
|
||||
if (isClipboardEnabled && !isWildcard) {
|
||||
await copy(previewUrl).catch(error =>
|
||||
output.debug(`Error copying to clipboard: ${error}`)
|
||||
);
|
||||
await copy(previewUrl)
|
||||
.then(() => {
|
||||
isCopiedToClipboard = true;
|
||||
})
|
||||
.catch(error => output.debug(`Error copying to clipboard: ${error}`));
|
||||
}
|
||||
|
||||
// write to stdout
|
||||
@@ -145,7 +153,9 @@ const printDeploymentStatus = async (
|
||||
prependEmoji(
|
||||
`${isProdDeployment ? 'Production' : 'Preview'}: ${chalk.bold(
|
||||
previewUrl
|
||||
)} ${deployStamp()}`,
|
||||
)}${
|
||||
isCopiedToClipboard ? chalk.gray(` [copied to clipboard]`) : ''
|
||||
} ${deployStamp()}`,
|
||||
emoji('success')
|
||||
) + `\n`
|
||||
);
|
||||
@@ -234,19 +244,6 @@ export default async function main(
|
||||
const { isFile, path } = pathValidation;
|
||||
const autoConfirm = argv['--confirm'] || isFile;
|
||||
|
||||
// check env variables options
|
||||
const { NOW_ORG_ID, NOW_PROJECT_ID } = process.env;
|
||||
if ((NOW_ORG_ID && !NOW_PROJECT_ID) || (!NOW_ORG_ID && NOW_PROJECT_ID)) {
|
||||
output.print(
|
||||
`${chalk.red('Error!')} You specified ${
|
||||
NOW_ORG_ID ? '`NOW_ORG_ID`' : '`NOW_PROJECT_ID`'
|
||||
} but you forgot to specify ${
|
||||
NOW_ORG_ID ? '`NOW_PROJECT_ID`' : '`NOW_ORG_ID`'
|
||||
}. You need to specify both to deploy to a custom project.\n`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// build `meta`
|
||||
const meta = Object.assign(
|
||||
{},
|
||||
@@ -259,6 +256,27 @@ export default async function main(
|
||||
warn(`The option --no-scale is only supported on Now 1.0 deployments`);
|
||||
}
|
||||
|
||||
// deprecate --name
|
||||
if (argv['--name']) {
|
||||
output.print(
|
||||
`${prependEmoji(
|
||||
`The ${param('--name')} flag is deprecated (https://zeit.ink/1B)`,
|
||||
emoji('warning')
|
||||
)}\n`
|
||||
);
|
||||
}
|
||||
|
||||
if (localConfig && localConfig.name) {
|
||||
output.print(
|
||||
`${prependEmoji(
|
||||
`The ${code('name')} property in ${highlight(
|
||||
'now.json'
|
||||
)} is deprecated (https://zeit.ink/5F)`,
|
||||
emoji('warning')
|
||||
)}\n`
|
||||
);
|
||||
}
|
||||
|
||||
// build `env`
|
||||
const isObject = item =>
|
||||
Object.prototype.toString.call(item) === '[object Object]';
|
||||
@@ -362,21 +380,17 @@ export default async function main(
|
||||
});
|
||||
|
||||
// retrieve `project` and `org` from .now
|
||||
let [org, project] = await getLinkedProject(output, client, path);
|
||||
const link = await getLinkedProject(output, client, path);
|
||||
|
||||
if (link.status === 'error') {
|
||||
return link.exitCode;
|
||||
}
|
||||
|
||||
let { org, project, status } = link;
|
||||
let newProjectName = null;
|
||||
let rootDirectory = project ? project.rootDirectory : null;
|
||||
|
||||
if (!org || !project) {
|
||||
const { NOW_PROJECT_ID, NOW_ORG_ID } = process.env;
|
||||
if (NOW_PROJECT_ID && NOW_ORG_ID) {
|
||||
output.print(
|
||||
`${chalk.red('Error!')} Project not found (${JSON.stringify({
|
||||
NOW_PROJECT_ID,
|
||||
NOW_ORG_ID,
|
||||
})})\n`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (status === 'not_linked') {
|
||||
const shouldStartSetup =
|
||||
autoConfirm ||
|
||||
(await confirm(
|
||||
@@ -390,6 +404,7 @@ export default async function main(
|
||||
}
|
||||
|
||||
org = await selectOrg(
|
||||
output,
|
||||
'Which scope do you want to deploy to?',
|
||||
client,
|
||||
ctx.config.currentTeam,
|
||||
@@ -413,8 +428,10 @@ export default async function main(
|
||||
|
||||
if (typeof projectOrNewProjectName === 'string') {
|
||||
newProjectName = projectOrNewProjectName;
|
||||
rootDirectory = await inputRootDirectory(path, output, autoConfirm);
|
||||
} else {
|
||||
project = projectOrNewProjectName;
|
||||
rootDirectory = project.rootDirectory;
|
||||
|
||||
// we can already link the project
|
||||
await linkFolderToProject(
|
||||
@@ -427,9 +444,26 @@ export default async function main(
|
||||
project.name,
|
||||
org.slug
|
||||
);
|
||||
status = 'linked';
|
||||
}
|
||||
}
|
||||
|
||||
const sourcePath = rootDirectory ? join(path, rootDirectory) : path;
|
||||
|
||||
if (
|
||||
rootDirectory &&
|
||||
(await validateRootDirectory(
|
||||
output,
|
||||
path,
|
||||
sourcePath,
|
||||
project
|
||||
? `To change your project settings, go to https://zeit.co/${org.slug}/${project.name}/settings`
|
||||
: ''
|
||||
)) === false
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const currentTeam = org.type === 'team' ? org.id : undefined;
|
||||
const now = new Now({ apiUrl, token, debug: debugEnabled, currentTeam });
|
||||
let deployStamp = stamp();
|
||||
@@ -457,11 +491,11 @@ export default async function main(
|
||||
output,
|
||||
now,
|
||||
contextName,
|
||||
[path],
|
||||
[sourcePath],
|
||||
createArgs,
|
||||
org,
|
||||
autoConfirm && !isFile,
|
||||
!!newProjectName
|
||||
!project && !isFile,
|
||||
path
|
||||
);
|
||||
|
||||
if (
|
||||
@@ -470,6 +504,10 @@ export default async function main(
|
||||
) {
|
||||
let { projectSettings, framework } = deployment;
|
||||
|
||||
if (rootDirectory) {
|
||||
projectSettings.rootDirectory = rootDirectory;
|
||||
}
|
||||
|
||||
const settings = await editProjectSettings(
|
||||
output,
|
||||
projectSettings,
|
||||
@@ -485,11 +523,11 @@ export default async function main(
|
||||
output,
|
||||
now,
|
||||
contextName,
|
||||
[path],
|
||||
[sourcePath],
|
||||
createArgs,
|
||||
org,
|
||||
!!newProjectName,
|
||||
false
|
||||
false,
|
||||
path
|
||||
);
|
||||
}
|
||||
|
||||
@@ -498,6 +536,14 @@ export default async function main(
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (deployment instanceof Error) {
|
||||
output.error(
|
||||
`${deployment.message ||
|
||||
'An unexpected error occurred while deploying your project'} (http://zeit.ink/P4)`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const deploymentResponse = await getDeploymentByIdOrHost(
|
||||
now,
|
||||
contextName,
|
||||
@@ -613,7 +659,8 @@ export default async function main(
|
||||
deployment,
|
||||
deployStamp,
|
||||
!argv['--no-clipboard'],
|
||||
quiet
|
||||
quiet,
|
||||
isFile
|
||||
);
|
||||
}
|
||||
|
||||
@@ -661,6 +708,11 @@ function handleCreateDeployError(output, error) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (dataPath === '.name') {
|
||||
output.error(message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (keyword === 'type') {
|
||||
const prop = dataPath.substr(1, dataPath.length);
|
||||
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import path from 'path';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import DevServer from '../../util/dev/server';
|
||||
import parseListen from '../../util/dev/parse-listen';
|
||||
import { Output } from '../../util/output';
|
||||
import { NowContext } from '../../types';
|
||||
import Client from '../../util/client';
|
||||
import { getLinkedProject } from '../../util/projects/link';
|
||||
import { getFrameworks } from '../../util/get-frameworks';
|
||||
import { isSettingValue } from '../../util/is-setting-value';
|
||||
import cmd from '../../util/output/cmd';
|
||||
|
||||
type Options = {
|
||||
'--debug'?: boolean;
|
||||
@@ -17,10 +23,62 @@ export default async function dev(
|
||||
output: Output
|
||||
) {
|
||||
const [dir = '.'] = args;
|
||||
const cwd = path.resolve(dir);
|
||||
let cwd = path.resolve(dir);
|
||||
const listen = parseListen(opts['--listen'] || '3000');
|
||||
const debug = opts['--debug'] || false;
|
||||
const devServer = new DevServer(cwd, { output, debug });
|
||||
|
||||
const client = new Client({
|
||||
apiUrl: ctx.apiUrl,
|
||||
token: ctx.authConfig.token,
|
||||
currentTeam: ctx.config.currentTeam,
|
||||
debug,
|
||||
});
|
||||
|
||||
// retrieve dev command
|
||||
const [link, frameworks] = await Promise.all([
|
||||
getLinkedProject(output, client, cwd),
|
||||
getFrameworks(),
|
||||
]);
|
||||
|
||||
if (link.status === 'error') {
|
||||
return link.exitCode;
|
||||
}
|
||||
|
||||
if (link.status === 'not_linked' && !process.env.__NOW_SKIP_DEV_COMMAND) {
|
||||
output.print(
|
||||
`${chalk.red(
|
||||
'Error!'
|
||||
)} Your codebase isn’t linked to a project on ZEIT Now. Run ${cmd(
|
||||
'now'
|
||||
)} to link it.\n`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
let devCommand: undefined | string;
|
||||
if (link.status === 'linked') {
|
||||
const { project } = link;
|
||||
|
||||
if (project.devCommand) {
|
||||
devCommand = project.devCommand;
|
||||
} else if (project.framework) {
|
||||
const framework = frameworks.find(f => f.slug === project.framework);
|
||||
|
||||
if (framework) {
|
||||
const defaults = framework.settings.devCommand;
|
||||
|
||||
if (isSettingValue(defaults)) {
|
||||
devCommand = defaults.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (project.rootDirectory) {
|
||||
cwd = path.join(cwd, project.rootDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
const devServer = new DevServer(cwd, { output, debug, devCommand });
|
||||
|
||||
process.once('SIGINT', () => devServer.stop());
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import param from '../../util/output/param';
|
||||
import promptBool from '../../util/input/prompt-bool';
|
||||
import purchaseDomain from '../../util/domains/purchase-domain';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import wait from '../../util/output/wait';
|
||||
|
||||
type Options = {
|
||||
'--debug': boolean;
|
||||
@@ -27,7 +26,7 @@ export default async function buy(
|
||||
) {
|
||||
const {
|
||||
authConfig: { token },
|
||||
config
|
||||
config,
|
||||
} = ctx;
|
||||
const { currentTeam } = config;
|
||||
const { apiUrl } = ctx;
|
||||
@@ -100,7 +99,7 @@ export default async function buy(
|
||||
|
||||
let buyResult;
|
||||
const purchaseStamp = stamp();
|
||||
const stopPurchaseSpinner = wait('Purchasing');
|
||||
const stopPurchaseSpinner = output.spinner('Purchasing');
|
||||
|
||||
try {
|
||||
buyResult = await purchaseDomain(client, domainName, price);
|
||||
|
||||
@@ -9,7 +9,6 @@ import listInput from '../../util/input/list';
|
||||
import listItem from '../../util/output/list-item';
|
||||
import promptBool from '../../util/input/prompt-bool';
|
||||
import toHumanPath from '../../util/humanize-path';
|
||||
import wait from '../../util/output/wait';
|
||||
import { Output } from '../../util/output';
|
||||
import { NowContext } from '../../types';
|
||||
import success from '../../util/output/success';
|
||||
@@ -24,10 +23,10 @@ type Options = {
|
||||
};
|
||||
|
||||
type Example = {
|
||||
name: string,
|
||||
visible: boolean,
|
||||
suggestions: string[]
|
||||
}
|
||||
name: string;
|
||||
visible: boolean;
|
||||
suggestions: string[];
|
||||
};
|
||||
|
||||
const EXAMPLE_API = 'https://now-example-files.zeit.sh';
|
||||
|
||||
@@ -40,7 +39,7 @@ export default async function init(
|
||||
const [name, dir] = args;
|
||||
const force = opts['-f'] || opts['--force'];
|
||||
|
||||
const examples = await fetchExampleList();
|
||||
const examples = await fetchExampleList(output);
|
||||
|
||||
if (!examples) {
|
||||
throw new Error(`Could not fetch example list.`);
|
||||
@@ -56,22 +55,22 @@ export default async function init(
|
||||
return 0;
|
||||
}
|
||||
|
||||
return extractExample(chosen, dir, force);
|
||||
return extractExample(output, chosen, dir, force);
|
||||
}
|
||||
|
||||
if (exampleList.includes(name)) {
|
||||
return extractExample(name, dir, force);
|
||||
return extractExample(output, name, dir, force);
|
||||
}
|
||||
|
||||
const oldExample = examples.find(x => !x.visible && x.name === name);
|
||||
if (oldExample) {
|
||||
return extractExample(name, dir, force, 'v1');
|
||||
return extractExample(output, name, dir, force, 'v1');
|
||||
}
|
||||
|
||||
const found = await guess(exampleList, name);
|
||||
|
||||
if (typeof found === 'string') {
|
||||
return extractExample(found, dir, force);
|
||||
return extractExample(output, found, dir, force);
|
||||
}
|
||||
|
||||
console.log(info('No changes made.'));
|
||||
@@ -81,8 +80,8 @@ export default async function init(
|
||||
/**
|
||||
* Fetch example list json
|
||||
*/
|
||||
async function fetchExampleList() {
|
||||
const stopSpinner = wait('Fetching examples');
|
||||
async function fetchExampleList(output: Output) {
|
||||
const stopSpinner = output.spinner('Fetching examples');
|
||||
const url = `${EXAMPLE_API}/v2/list.json`;
|
||||
|
||||
try {
|
||||
@@ -93,7 +92,7 @@ async function fetchExampleList() {
|
||||
throw new Error(`Failed fetching list.json (${resp.statusText}).`);
|
||||
}
|
||||
|
||||
return await resp.json() as Example[];
|
||||
return (await resp.json()) as Example[];
|
||||
} catch (e) {
|
||||
stopSpinner();
|
||||
}
|
||||
@@ -106,22 +105,28 @@ async function chooseFromDropdown(message: string, exampleList: string[]) {
|
||||
const choices = exampleList.map(name => ({
|
||||
name,
|
||||
value: name,
|
||||
short: name
|
||||
short: name,
|
||||
}));
|
||||
|
||||
return listInput({
|
||||
message,
|
||||
separator: false,
|
||||
choices
|
||||
choices,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract example to directory
|
||||
*/
|
||||
async function extractExample(name: string, dir: string, force?: boolean, ver: string = 'v2') {
|
||||
async function extractExample(
|
||||
output: Output,
|
||||
name: string,
|
||||
dir: string,
|
||||
force?: boolean,
|
||||
ver: string = 'v2'
|
||||
) {
|
||||
const folder = prepareFolder(process.cwd(), dir || name, force);
|
||||
const stopSpinner = wait(`Fetching ${name}`);
|
||||
const stopSpinner = output.spinner(`Fetching ${name}`);
|
||||
|
||||
const url = `${EXAMPLE_API}/${ver}/download/${name}.tar.gz`;
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import createOutput from '../util/output';
|
||||
import Now from '../util';
|
||||
import logo from '../util/output/logo';
|
||||
import elapsed from '../util/output/elapsed.ts';
|
||||
import wait from '../util/output/wait';
|
||||
import { handleError } from '../util/error';
|
||||
import strlen from '../util/strlen.ts';
|
||||
import Client from '../util/client.ts';
|
||||
@@ -79,13 +78,16 @@ export default async function main(ctx) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const { authConfig: { token }, config } = ctx;
|
||||
const {
|
||||
authConfig: { token },
|
||||
config,
|
||||
} = ctx;
|
||||
const { currentTeam } = config;
|
||||
const client = new Client({
|
||||
apiUrl,
|
||||
token,
|
||||
currentTeam,
|
||||
debug: debugEnabled
|
||||
debug: debugEnabled,
|
||||
});
|
||||
let contextName = null;
|
||||
|
||||
@@ -104,7 +106,7 @@ export default async function main(ctx) {
|
||||
|
||||
// resolve the deployment, since we might have been given an alias
|
||||
const depFetchStart = Date.now();
|
||||
const cancelWait = wait(
|
||||
const cancelWait = output.spinner(
|
||||
`Fetching deployment "${id}" in ${chalk.bold(contextName)}`
|
||||
);
|
||||
|
||||
@@ -140,7 +142,7 @@ export default async function main(ctx) {
|
||||
limits,
|
||||
version,
|
||||
routes,
|
||||
readyState
|
||||
readyState,
|
||||
} = deployment;
|
||||
|
||||
const isBuilds = version === 2;
|
||||
@@ -159,7 +161,7 @@ export default async function main(ctx) {
|
||||
)}/events?types=event`
|
||||
)
|
||||
),
|
||||
isBuilds ? now.fetch(buildsUrl) : { builds: [] }
|
||||
isBuilds ? now.fetch(buildsUrl) : { builds: [] },
|
||||
]);
|
||||
|
||||
cancelWait();
|
||||
@@ -174,7 +176,9 @@ export default async function main(ctx) {
|
||||
print(` ${chalk.cyan('version')}\t${version}\n`);
|
||||
print(` ${chalk.cyan('id')}\t\t${finalId}\n`);
|
||||
print(` ${chalk.cyan('name')}\t${name}\n`);
|
||||
print(` ${chalk.cyan('readyState')}\t${stateString(state || readyState)}\n`);
|
||||
print(
|
||||
` ${chalk.cyan('readyState')}\t${stateString(state || readyState)}\n`
|
||||
);
|
||||
if (!isBuilds) {
|
||||
print(` ${chalk.cyan('type')}\t${type}\n`);
|
||||
}
|
||||
@@ -255,7 +259,7 @@ export default async function main(ctx) {
|
||||
`${table(t, {
|
||||
align: ['l', 'c', 'c', 'c'],
|
||||
hsep: ' '.repeat(8),
|
||||
stringLength: strlen
|
||||
stringLength: strlen,
|
||||
}).replace(/^(.*)/gm, ' $1')}\n`
|
||||
);
|
||||
print('\n');
|
||||
@@ -269,9 +273,9 @@ export default async function main(ctx) {
|
||||
events.forEach(data => {
|
||||
if (!data.event) return; // keepalive
|
||||
print(
|
||||
` ${chalk.gray(
|
||||
new Date(data.created).toISOString()
|
||||
)} ${data.event} ${getEventMetadata(data)}\n`
|
||||
` ${chalk.gray(new Date(data.created).toISOString())} ${
|
||||
data.event
|
||||
} ${getEventMetadata(data)}\n`
|
||||
);
|
||||
});
|
||||
print('\n');
|
||||
|
||||
@@ -8,10 +8,7 @@ import chalk from 'chalk';
|
||||
import ua from '../util/ua.ts';
|
||||
import getArgs from '../util/get-args';
|
||||
import error from '../util/output/error';
|
||||
import aborted from '../util/output/aborted';
|
||||
import wait from '../util/output/wait';
|
||||
import highlight from '../util/output/highlight';
|
||||
import info from '../util/output/info';
|
||||
import ok from '../util/output/ok';
|
||||
import cmd from '../util/output/cmd.ts';
|
||||
import param from '../util/output/param.ts';
|
||||
@@ -24,7 +21,8 @@ import hp from '../util/humanize-path';
|
||||
import logo from '../util/output/logo';
|
||||
import exit from '../util/exit';
|
||||
import createOutput from '../util/output';
|
||||
import executeLogin from '../util/login/login.ts'
|
||||
import executeLogin from '../util/login/login.ts';
|
||||
import { prependEmoji, emoji } from '../util/emoji';
|
||||
|
||||
const debug = debugFactory('now:sh:login');
|
||||
|
||||
@@ -57,7 +55,7 @@ const help = () => {
|
||||
const verify = async ({ apiUrl, email, verificationToken }) => {
|
||||
const query = {
|
||||
email,
|
||||
token: verificationToken
|
||||
token: verificationToken,
|
||||
};
|
||||
|
||||
debug('GET /now/registration/verify');
|
||||
@@ -68,7 +66,7 @@ const verify = async ({ apiUrl, email, verificationToken }) => {
|
||||
res = await fetch(
|
||||
`${apiUrl}/now/registration/verify?${stringifyQuery(query)}`,
|
||||
{
|
||||
headers: { 'User-Agent': ua }
|
||||
headers: { 'User-Agent': ua },
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
@@ -105,12 +103,12 @@ const readEmail = async () => {
|
||||
let email;
|
||||
|
||||
try {
|
||||
email = await promptEmail({ start: info('Enter your email: ') });
|
||||
email = await promptEmail({ start: `Enter your email: ` });
|
||||
} catch (err) {
|
||||
console.log(); // \n
|
||||
|
||||
if (err.message === 'User abort') {
|
||||
throw new Error(aborted('No changes made.'));
|
||||
throw new Error(`${chalk.red('Aborted!')} No changes made`);
|
||||
}
|
||||
|
||||
if (err.message === 'stdin lacks setRawMode support') {
|
||||
@@ -192,7 +190,7 @@ const login = async ctx => {
|
||||
let verificationToken;
|
||||
let securityCode;
|
||||
|
||||
stopSpinner = wait('Sending you an email');
|
||||
stopSpinner = output.spinner('Sending you an email');
|
||||
|
||||
try {
|
||||
const data = await executeLogin(apiUrl, email);
|
||||
@@ -200,7 +198,7 @@ const login = async ctx => {
|
||||
securityCode = data.securityCode;
|
||||
} catch (err) {
|
||||
stopSpinner();
|
||||
console.log(error(err.message))
|
||||
console.log(error(err.message));
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -209,18 +207,15 @@ const login = async ctx => {
|
||||
// Clear up `Sending email` success message
|
||||
process.stdout.write(eraseLines(possibleAddress ? 1 : 2));
|
||||
|
||||
console.log(
|
||||
info(
|
||||
`We sent an email to ${highlight(
|
||||
email
|
||||
)}. Please follow the steps provided`,
|
||||
` inside it and make sure the security code matches ${highlight(
|
||||
securityCode
|
||||
)}.`
|
||||
)
|
||||
output.print(
|
||||
`We sent an email to ${highlight(
|
||||
email
|
||||
)}. Please follow the steps provided inside it and make sure the security code matches ${highlight(
|
||||
securityCode
|
||||
)}.\n`
|
||||
);
|
||||
|
||||
stopSpinner = wait('Waiting for your confirmation');
|
||||
stopSpinner = output.spinner('Waiting for your confirmation');
|
||||
|
||||
let token;
|
||||
|
||||
@@ -256,8 +251,15 @@ const login = async ctx => {
|
||||
output.debug(`Saved credentials in "${hp(getNowDir())}"`);
|
||||
|
||||
console.log(
|
||||
`${chalk.cyan('> Congratulations!')} ` +
|
||||
`You are now logged in. In order to deploy something, run ${cmd('now')}.`
|
||||
`${chalk.cyan('Congratulations!')} ` +
|
||||
`You are now logged in. In order to deploy something, run ${cmd('now')}.`
|
||||
);
|
||||
|
||||
output.print(
|
||||
`${prependEmoji(
|
||||
`Connect your Git Repositories to deploy every branch push automatically (https://zeit.ink/1X).`,
|
||||
emoji('tip')
|
||||
)}\n`
|
||||
);
|
||||
|
||||
return ctx;
|
||||
|
||||
@@ -6,7 +6,6 @@ import logo from '../util/output/logo';
|
||||
import elapsed from '../util/output/elapsed.ts';
|
||||
import { maybeURL, normalizeURL, parseInstanceURL } from '../util/url';
|
||||
import printEvents from '../util/events';
|
||||
import wait from '../util/output/wait';
|
||||
import Client from '../util/client.ts';
|
||||
import getScope from '../util/get-scope.ts';
|
||||
|
||||
@@ -165,7 +164,7 @@ export default async function main(ctx) {
|
||||
const id = deploymentIdOrURL;
|
||||
|
||||
const depFetchStart = Date.now();
|
||||
const cancelWait = wait(
|
||||
const cancelWait = output.spinner(
|
||||
`Fetching deployment "${id}" in ${chalk.bold(contextName)}`
|
||||
);
|
||||
|
||||
@@ -287,12 +286,12 @@ function printLogShort(log) {
|
||||
data = JSON.stringify(obj, null, 2);
|
||||
} else {
|
||||
data = (log.text || '')
|
||||
.replace(/\n$/, '')
|
||||
.replace(/^\n/, '')
|
||||
// eslint-disable-next-line no-control-regex
|
||||
.replace(/\x1b\[1000D/g, '')
|
||||
.replace(/\x1b\[0K/g, '')
|
||||
.replace(/\x1b\[1A/g, '');
|
||||
.replace(/\n$/, '')
|
||||
.replace(/^\n/, '')
|
||||
// eslint-disable-next-line no-control-regex
|
||||
.replace(/\x1b\[1000D/g, '')
|
||||
.replace(/\x1b\[0K/g, '')
|
||||
.replace(/\x1b\[1A/g, '');
|
||||
if (/warning/i.test(data)) {
|
||||
data = chalk.yellow(data);
|
||||
} else if (log.type === 'stderr') {
|
||||
|
||||
@@ -6,7 +6,6 @@ import table from 'text-table';
|
||||
import Now from '../util';
|
||||
import getAliases from '../util/alias/get-aliases';
|
||||
import createOutput from '../util/output';
|
||||
import wait from '../util/output/wait';
|
||||
import logo from '../util/output/logo';
|
||||
import cmd from '../util/output/cmd.ts';
|
||||
import elapsed from '../util/output/elapsed.ts';
|
||||
@@ -132,7 +131,7 @@ export default async function main(ctx) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
const cancelWait = wait(
|
||||
const cancelWait = output.spinner(
|
||||
`Fetching deployment(s) ${ids
|
||||
.map(id => `"${id}"`)
|
||||
.join(' ')} in ${chalk.bold(contextName)}`
|
||||
|
||||
@@ -11,6 +11,7 @@ import logo from '../util/output/logo';
|
||||
import Client from '../util/client.ts';
|
||||
import getScope from '../util/get-scope.ts';
|
||||
import createOutput from '../util/output';
|
||||
import confirm from '../util/input/confirm';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
@@ -144,7 +145,7 @@ async function run({ output, token, contextName, currentTeam }) {
|
||||
const elapsed = ms(new Date() - start);
|
||||
|
||||
console.log(
|
||||
`> ${plural('secret', list.length, true)} found under ${chalk.bold(
|
||||
`${plural('secret', list.length, true)} found under ${chalk.bold(
|
||||
contextName
|
||||
)} ${chalk.gray(`[${elapsed}]`)}`
|
||||
);
|
||||
@@ -189,22 +190,29 @@ async function run({ output, token, contextName, currentTeam }) {
|
||||
const theSecret = list.find(secret => secret.name === args[0]);
|
||||
|
||||
if (theSecret) {
|
||||
const yes = argv.yes || (await readConfirmation(theSecret));
|
||||
const yes =
|
||||
argv.yes || (await readConfirmation(output, theSecret, contextName));
|
||||
if (!yes) {
|
||||
console.error(error('User abort'));
|
||||
output.print(`Aborted. Secret not deleted.\n`);
|
||||
return exit(0);
|
||||
}
|
||||
} else {
|
||||
console.error(error(`No secret found by name "${args[0]}"`));
|
||||
console.error(
|
||||
error(
|
||||
`No secret found by name "${args[0]}" under ${chalk.bold(
|
||||
contextName
|
||||
)}`
|
||||
)
|
||||
);
|
||||
return exit(1);
|
||||
}
|
||||
|
||||
const secret = await secrets.rm(args[0]);
|
||||
const elapsed = ms(new Date() - start);
|
||||
console.log(
|
||||
`${chalk.cyan('> Success!')} Secret ${chalk.bold(
|
||||
`${chalk.cyan('Success!')} Secret ${chalk.bold(
|
||||
secret.name
|
||||
)} removed ${chalk.gray(`[${elapsed}]`)}`
|
||||
)} under ${chalk.bold(contextName)} removed ${chalk.gray(`[${elapsed}]`)}`
|
||||
);
|
||||
return secrets.close();
|
||||
}
|
||||
@@ -223,9 +231,11 @@ async function run({ output, token, contextName, currentTeam }) {
|
||||
const secret = await secrets.rename(args[0], args[1]);
|
||||
const elapsed = ms(new Date() - start);
|
||||
console.log(
|
||||
`${chalk.cyan('> Success!')} Secret ${chalk.bold(
|
||||
`${chalk.cyan('Success!')} Secret ${chalk.bold(
|
||||
secret.oldName
|
||||
)} renamed to ${chalk.bold(args[1])} ${chalk.gray(`[${elapsed}]`)}`
|
||||
)} renamed to ${chalk.bold(args[1])} under ${chalk.bold(
|
||||
contextName
|
||||
)} ${chalk.gray(`[${elapsed}]`)}`
|
||||
);
|
||||
return secrets.close();
|
||||
}
|
||||
@@ -243,7 +253,7 @@ async function run({ output, token, contextName, currentTeam }) {
|
||||
if (args.length > 2) {
|
||||
const example = chalk.cyan(`$ now secret add -- "${args[0]}"`);
|
||||
console.log(
|
||||
`> If your secret has spaces or starts with '-', make sure to terminate command options with double dash and wrap it in quotes. Example: \n ${example} `
|
||||
`If your secret has spaces or starts with '-', make sure to terminate command options with double dash and wrap it in quotes. Example: \n ${example} `
|
||||
);
|
||||
}
|
||||
|
||||
@@ -259,9 +269,9 @@ async function run({ output, token, contextName, currentTeam }) {
|
||||
}
|
||||
|
||||
console.log(
|
||||
`${chalk.cyan('> Success!')} Secret ${chalk.bold(
|
||||
`${chalk.cyan('Success!')} Secret ${chalk.bold(
|
||||
name.toLowerCase()
|
||||
)} added (${chalk.bold(contextName)}) ${chalk.gray(`[${elapsed}]`)}`
|
||||
)} added under ${chalk.bold(contextName)} ${chalk.gray(`[${elapsed}]`)}`
|
||||
);
|
||||
return secrets.close();
|
||||
}
|
||||
@@ -278,33 +288,19 @@ process.on('uncaughtException', err => {
|
||||
exit(1);
|
||||
});
|
||||
|
||||
function readConfirmation(secret) {
|
||||
return new Promise(resolve => {
|
||||
const time = chalk.gray(`${ms(new Date() - new Date(secret.created))} ago`);
|
||||
const tbl = table([[chalk.bold(secret.name), time]], {
|
||||
align: ['r', 'l'],
|
||||
hsep: ' '.repeat(6),
|
||||
});
|
||||
|
||||
process.stdout.write(
|
||||
'> The following secret will be removed permanently\n'
|
||||
);
|
||||
process.stdout.write(` ${tbl}\n`);
|
||||
|
||||
process.stdout.write(
|
||||
`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[y/N] ')}`
|
||||
);
|
||||
|
||||
process.stdin
|
||||
.on('data', d => {
|
||||
process.stdin.pause();
|
||||
resolve(
|
||||
d
|
||||
.toString()
|
||||
.trim()
|
||||
.toLowerCase() === 'y'
|
||||
);
|
||||
})
|
||||
.resume();
|
||||
async function readConfirmation(output, secret, contextName) {
|
||||
const time = chalk.gray(`${ms(new Date() - new Date(secret.created))} ago`);
|
||||
const tbl = table([[chalk.bold(secret.name), time]], {
|
||||
align: ['r', 'l'],
|
||||
hsep: ' '.repeat(6),
|
||||
});
|
||||
|
||||
output.print(
|
||||
`The following secret will be removed permanently from ${chalk.bold(
|
||||
contextName
|
||||
)}\n`
|
||||
);
|
||||
output.print(` ${tbl}\n`);
|
||||
|
||||
return confirm(`${chalk.bold.red('Are you sure?')}`, false);
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ export default async function({ apiUrl, token, teams, config }) {
|
||||
validateKeypress: validateSlugKeypress,
|
||||
initialValue: slug,
|
||||
valid: team,
|
||||
forceLowerCase: true
|
||||
forceLowerCase: true,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.message === 'USER_ABORT') {
|
||||
@@ -95,7 +95,7 @@ export default async function({ apiUrl, token, teams, config }) {
|
||||
try {
|
||||
name = await textInput({
|
||||
label: `- ${teamNamePrefix}`,
|
||||
validateKeypress: validateNameKeypress
|
||||
validateKeypress: validateNameKeypress,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.message === 'USER_ABORT') {
|
||||
@@ -153,7 +153,7 @@ export default async function({ apiUrl, token, teams, config }) {
|
||||
introMsg: 'Invite your teammates! When done, press enter on an empty field',
|
||||
noopMsg: `You can invite teammates later by running ${cmd(
|
||||
'now teams invite'
|
||||
)}`
|
||||
)}`,
|
||||
});
|
||||
|
||||
gracefulExit();
|
||||
|
||||
@@ -30,7 +30,7 @@ const domains = Array.from(
|
||||
'inbox.com',
|
||||
'mail.com',
|
||||
'gmx.com',
|
||||
'icloud.com'
|
||||
'icloud.com',
|
||||
])
|
||||
);
|
||||
|
||||
@@ -56,17 +56,15 @@ const emailAutoComplete = (value, teamSlug) => {
|
||||
return false;
|
||||
};
|
||||
|
||||
export default async function(
|
||||
{
|
||||
teams,
|
||||
args,
|
||||
config,
|
||||
introMsg,
|
||||
noopMsg = 'No changes made',
|
||||
apiUrl,
|
||||
token
|
||||
} = {}
|
||||
) {
|
||||
export default async function({
|
||||
teams,
|
||||
args,
|
||||
config,
|
||||
introMsg,
|
||||
noopMsg = 'No changes made',
|
||||
apiUrl,
|
||||
token,
|
||||
} = {}) {
|
||||
const { currentTeam: currentTeamId } = config;
|
||||
|
||||
const stopSpinner = wait('Fetching teams');
|
||||
@@ -86,7 +84,11 @@ export default async function(
|
||||
|
||||
if (!currentTeam) {
|
||||
// We specifically need a team scope here
|
||||
let err = `You can't run this command under ${param(user.username || user.email)}.\nPlease select a team scope using ${cmd('now switch')} or use ${cmd('--scope')}`;
|
||||
let err = `You can't run this command under ${param(
|
||||
user.username || user.email
|
||||
)}.\nPlease select a team scope using ${cmd('now switch')} or use ${cmd(
|
||||
'--scope'
|
||||
)}`;
|
||||
return fatalError(err);
|
||||
}
|
||||
|
||||
@@ -107,7 +109,9 @@ export default async function(
|
||||
userInfo = res.name || res.username;
|
||||
} catch (err) {
|
||||
if (err.code === 'user_not_found') {
|
||||
console.error(error(`No user exists with the email address "${email}".`));
|
||||
console.error(
|
||||
error(`No user exists with the email address "${email}".`)
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -115,7 +119,11 @@ export default async function(
|
||||
}
|
||||
|
||||
stopSpinner();
|
||||
console.log(`${chalk.cyan(chars.tick)} ${email}${userInfo ? ` (${userInfo})` : ''} ${elapsed()}`);
|
||||
console.log(
|
||||
`${chalk.cyan(chars.tick)} ${email}${
|
||||
userInfo ? ` (${userInfo})` : ''
|
||||
} ${elapsed()}`
|
||||
);
|
||||
} else {
|
||||
console.log(`${chalk.red(`✖ ${email}`)} ${chalk.gray('[invalid]')}`);
|
||||
}
|
||||
@@ -135,7 +143,7 @@ export default async function(
|
||||
email = await textInput({
|
||||
label: `- ${inviteUserPrefix}`,
|
||||
validateValue: validateEmail,
|
||||
autoComplete: value => emailAutoComplete(value, currentTeam.slug)
|
||||
autoComplete: value => emailAutoComplete(value, currentTeam.slug),
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.message !== 'USER_ABORT') {
|
||||
@@ -149,7 +157,10 @@ export default async function(
|
||||
stopSpinner = wait(inviteUserPrefix + email);
|
||||
try {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const { name, username } = await teams.inviteUser({ teamId: currentTeam.id, email });
|
||||
const { name, username } = await teams.inviteUser({
|
||||
teamId: currentTeam.id,
|
||||
email,
|
||||
});
|
||||
stopSpinner();
|
||||
const userInfo = name || username;
|
||||
email = `${email}${userInfo ? ` (${userInfo})` : ''} ${elapsed()}`;
|
||||
|
||||
@@ -27,20 +27,20 @@ export default async function({ teams, config, apiUrl, token }) {
|
||||
|
||||
if (accountIsCurrent) {
|
||||
currentTeam = {
|
||||
slug: user.username || user.email
|
||||
slug: user.username || user.email,
|
||||
};
|
||||
}
|
||||
|
||||
const teamList = list.map(({ slug, name }) => ({
|
||||
name,
|
||||
value: slug,
|
||||
current: slug === currentTeam.slug ? chars.tick : ''
|
||||
current: slug === currentTeam.slug ? chars.tick : '',
|
||||
}));
|
||||
|
||||
teamList.unshift({
|
||||
name: user.email,
|
||||
value: user.username || user.email,
|
||||
current: (accountIsCurrent && chars.tick) || ''
|
||||
current: (accountIsCurrent && chars.tick) || '',
|
||||
});
|
||||
|
||||
// Let's bring the current team to the beginning of the list
|
||||
|
||||
@@ -40,8 +40,8 @@ const main = async ctx => {
|
||||
boolean: ['help', 'debug', 'all'],
|
||||
alias: {
|
||||
help: 'h',
|
||||
debug: 'd'
|
||||
}
|
||||
debug: 'd',
|
||||
},
|
||||
});
|
||||
|
||||
argv._ = argv._.slice(1);
|
||||
@@ -52,7 +52,10 @@ const main = async ctx => {
|
||||
}
|
||||
|
||||
const debug = argv['--debug'];
|
||||
const { authConfig: { token }, apiUrl } = ctx;
|
||||
const {
|
||||
authConfig: { token },
|
||||
apiUrl,
|
||||
} = ctx;
|
||||
const output = createOutput({ debug });
|
||||
const client = new Client({ apiUrl, token, debug });
|
||||
let contextName = null;
|
||||
|
||||
@@ -48,6 +48,7 @@ import { NowError } from './util/now-error';
|
||||
import { SENTRY_DSN } from './util/constants.ts';
|
||||
import getUpdateCommand from './util/get-update-command';
|
||||
import { metrics, shouldCollectMetrics } from './util/metrics.ts';
|
||||
import { getLinkedOrg } from './util/projects/link';
|
||||
|
||||
const NOW_DIR = getNowDir();
|
||||
const NOW_CONFIG_PATH = configFiles.getConfigFilePath();
|
||||
@@ -504,9 +505,6 @@ const main = async argv_ => {
|
||||
}
|
||||
}
|
||||
|
||||
const scope = argv['--scope'] || argv['--team'] || localConfig.scope;
|
||||
const targetCommand = commands.get(subcommand);
|
||||
|
||||
if (argv['--team']) {
|
||||
output.warn(
|
||||
`The ${param('--team')} flag is deprecated. Please use ${param(
|
||||
@@ -515,18 +513,35 @@ const main = async argv_ => {
|
||||
);
|
||||
}
|
||||
|
||||
const {
|
||||
authConfig: { token },
|
||||
} = ctx;
|
||||
|
||||
let scope = argv['--scope'] || argv['--team'] || localConfig.scope;
|
||||
|
||||
if (process.env.NOW_ORG_ID || !scope) {
|
||||
const client = new Client({ apiUrl, token });
|
||||
const link = await getLinkedOrg(client, output);
|
||||
|
||||
if (link.status === 'error') {
|
||||
return link.exitCode;
|
||||
}
|
||||
|
||||
if (link.status === 'linked') {
|
||||
scope = link.org.slug;
|
||||
}
|
||||
}
|
||||
|
||||
const targetCommand = commands.get(subcommand);
|
||||
|
||||
if (
|
||||
typeof scope === 'string' &&
|
||||
targetCommand !== 'login' &&
|
||||
targetCommand !== 'dev' &&
|
||||
!(targetCommand === 'teams' && argv._[3] !== 'invite')
|
||||
) {
|
||||
const {
|
||||
authConfig: { token },
|
||||
} = ctx;
|
||||
const client = new Client({ apiUrl, token });
|
||||
|
||||
let user = null;
|
||||
const client = new Client({ apiUrl, token });
|
||||
|
||||
try {
|
||||
user = await getUser(client);
|
||||
|
||||
@@ -222,6 +222,9 @@ export interface Project {
|
||||
accountId: string;
|
||||
updatedAt: number;
|
||||
createdAt: number;
|
||||
devCommand?: string | null;
|
||||
framework?: string | null;
|
||||
rootDirectory?: string | null;
|
||||
}
|
||||
|
||||
export interface Org {
|
||||
@@ -233,6 +236,4 @@ export interface Org {
|
||||
export interface ProjectLink {
|
||||
projectId: string;
|
||||
orgId: string;
|
||||
orgSlug?: string;
|
||||
projectName?: string;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import { Output } from '../output';
|
||||
import * as ERRORS from '../errors-ts';
|
||||
import Client from '../client';
|
||||
import createCertForAlias from '../certs/create-cert-for-alias';
|
||||
import wait from '../output/wait';
|
||||
|
||||
export type AliasRecord = {
|
||||
uid: string;
|
||||
@@ -20,7 +19,7 @@ export default async function createAlias(
|
||||
alias: string,
|
||||
externalDomain: boolean
|
||||
) {
|
||||
let cancelMessage = wait(`Creating alias`);
|
||||
let cancelMessage = output.spinner(`Creating alias`);
|
||||
const result = await performCreateAlias(
|
||||
client,
|
||||
contextName,
|
||||
@@ -41,7 +40,7 @@ export default async function createAlias(
|
||||
return cert;
|
||||
}
|
||||
|
||||
let cancelMessage = wait(`Creating alias`);
|
||||
let cancelMessage = output.spinner(`Creating alias`);
|
||||
const secondTry = await performCreateAlias(
|
||||
client,
|
||||
contextName,
|
||||
@@ -66,7 +65,7 @@ async function performCreateAlias(
|
||||
`/now/deployments/${deployment.uid}/aliases`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: { alias }
|
||||
body: { alias },
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
@@ -77,7 +76,10 @@ async function performCreateAlias(
|
||||
return { uid: error.uid, alias: error.alias } as AliasRecord;
|
||||
}
|
||||
if (error.code === 'deployment_not_found') {
|
||||
return new ERRORS.DeploymentNotFound({ context: contextName, id: deployment.uid });
|
||||
return new ERRORS.DeploymentNotFound({
|
||||
context: contextName,
|
||||
id: deployment.uid,
|
||||
});
|
||||
}
|
||||
if (error.code === 'gone') {
|
||||
return new ERRORS.DeploymentFailedAliasImpossible();
|
||||
@@ -94,7 +96,7 @@ async function performCreateAlias(
|
||||
}
|
||||
}
|
||||
if (error.status === 400) {
|
||||
return new ERRORS.DeploymentNotReady({url: deployment.url })
|
||||
return new ERRORS.DeploymentNotReady({ url: deployment.url });
|
||||
}
|
||||
|
||||
throw error;
|
||||
|
||||
@@ -3,7 +3,6 @@ import chalk from 'chalk';
|
||||
import getAppLastDeployment from '../deploy/get-app-last-deployment';
|
||||
import getAppName from '../deploy/get-app-name';
|
||||
import fetchDeploymentByIdOrHost from '../deploy/get-deployment-by-id-or-host';
|
||||
import wait from '../output/wait';
|
||||
import Client from '../client';
|
||||
import { Output } from '../output';
|
||||
import { User, Config } from '../../types';
|
||||
@@ -17,7 +16,7 @@ export default async function getDeploymentForAlias(
|
||||
contextName: string,
|
||||
localConfig: Config
|
||||
) {
|
||||
const cancelWait = wait(
|
||||
const cancelWait = output.spinner(
|
||||
`Fetching deployment to alias in ${chalk.bold(contextName)}`
|
||||
);
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import * as ERRORS from '../errors-ts';
|
||||
import Client from '../client';
|
||||
import createCertForAlias from '../certs/create-cert-for-alias';
|
||||
import setupDomain from '../domains/setup-domain';
|
||||
import wait from '../output/wait';
|
||||
|
||||
const NOW_SH_REGEX = /\.now\.sh$/;
|
||||
|
||||
@@ -34,6 +33,7 @@ export default async function upsertPathAlias(
|
||||
}
|
||||
|
||||
const result = await performUpsertPathAlias(
|
||||
output,
|
||||
client,
|
||||
alias,
|
||||
rules,
|
||||
@@ -51,23 +51,26 @@ export default async function upsertPathAlias(
|
||||
return cert;
|
||||
}
|
||||
|
||||
return performUpsertPathAlias(client, alias, rules, contextName);
|
||||
return performUpsertPathAlias(output, client, alias, rules, contextName);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function performUpsertPathAlias(
|
||||
output: Output,
|
||||
client: Client,
|
||||
alias: string,
|
||||
rules: PathRule[],
|
||||
contextName: string
|
||||
) {
|
||||
const cancelMessage = wait(`Updating path alias rules for ${alias}`);
|
||||
const cancelMessage = output.spinner(
|
||||
`Updating path alias rules for ${alias}`
|
||||
);
|
||||
try {
|
||||
const record = await client.fetch<AliasRecord>(`/now/aliases`, {
|
||||
body: { alias, rules },
|
||||
method: 'POST'
|
||||
method: 'POST',
|
||||
});
|
||||
cancelMessage();
|
||||
return record;
|
||||
|
||||
@@ -5,7 +5,6 @@ import createCertForCns from './create-cert-for-cns';
|
||||
import getWildcardCnsForAlias from './get-wildcard-cns-for-alias';
|
||||
import joinWords from '../output/join-words';
|
||||
import stamp from '../output/stamp';
|
||||
import wait from '../output/wait';
|
||||
|
||||
export default async function createCertificateForAlias(
|
||||
output: Output,
|
||||
@@ -15,7 +14,7 @@ export default async function createCertificateForAlias(
|
||||
shouldBeWildcard: boolean
|
||||
) {
|
||||
const cns = shouldBeWildcard ? getWildcardCnsForAlias(alias) : [alias];
|
||||
const cancelMessage = wait(`Generating a certificate...`);
|
||||
const cancelMessage = output.spinner(`Generating a certificate...`);
|
||||
const certStamp = stamp();
|
||||
const cert = await createCertForCns(client, cns, context);
|
||||
if (cert instanceof NowError) {
|
||||
@@ -25,9 +24,9 @@ export default async function createCertificateForAlias(
|
||||
|
||||
cancelMessage();
|
||||
output.log(
|
||||
`Certificate for ${joinWords(
|
||||
cert.cns
|
||||
)} (${cert.uid}) created ${certStamp()}`
|
||||
`Certificate for ${joinWords(cert.cns)} (${
|
||||
cert.uid
|
||||
}) created ${certStamp()}`
|
||||
);
|
||||
return cert;
|
||||
}
|
||||
|
||||
@@ -11,17 +11,11 @@ export default async function createDeploy(
|
||||
paths,
|
||||
createArgs,
|
||||
org,
|
||||
shouldLinkFolder,
|
||||
isDetectingFramework
|
||||
isSettingUpProject,
|
||||
cwd
|
||||
) {
|
||||
try {
|
||||
return await now.create(
|
||||
paths,
|
||||
createArgs,
|
||||
org,
|
||||
shouldLinkFolder,
|
||||
isDetectingFramework
|
||||
);
|
||||
return await now.create(paths, createArgs, org, isSettingUpProject, cwd);
|
||||
} catch (error) {
|
||||
if (error.code === 'rate_limited') {
|
||||
throw new ERRORS_TS.DeploymentsRateLimited(error.message);
|
||||
@@ -104,8 +98,7 @@ export default async function createDeploy(
|
||||
paths,
|
||||
createArgs,
|
||||
org,
|
||||
shouldLinkFolder,
|
||||
isDetectingFramework
|
||||
isSettingUpProject
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import { Output } from '../output';
|
||||
import Client from '../client';
|
||||
import createCertForCns from '../certs/create-cert-for-cns';
|
||||
import setupDomain from '../domains/setup-domain';
|
||||
import wait from '../output/wait';
|
||||
import { InvalidDomain } from '../errors-ts';
|
||||
|
||||
export default async function generateCertForDeploy(
|
||||
@@ -23,7 +22,9 @@ export default async function generateCertForDeploy(
|
||||
return new InvalidDomain(deployURL);
|
||||
}
|
||||
|
||||
const cancelSetupWait = wait(`Setting custom suffix domain ${domain}`);
|
||||
const cancelSetupWait = output.spinner(
|
||||
`Setting custom suffix domain ${domain}`
|
||||
);
|
||||
const result = await setupDomain(output, client, domain, contextName);
|
||||
cancelSetupWait();
|
||||
if (result instanceof NowError) {
|
||||
@@ -31,7 +32,7 @@ export default async function generateCertForDeploy(
|
||||
}
|
||||
|
||||
// Generate the certificate with the given parameters
|
||||
const cancelCertWait = wait(
|
||||
const cancelCertWait = output.spinner(
|
||||
`Generating a wildcard certificate for ${domain}`
|
||||
);
|
||||
const cert = await createCertForCns(
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
DeploymentOptions,
|
||||
NowClientOptions,
|
||||
} from 'now-client';
|
||||
import wait from '../output/wait';
|
||||
import { Output } from '../output';
|
||||
// @ts-ignore
|
||||
import Now from '../../util';
|
||||
@@ -50,9 +49,9 @@ function printInspectUrl(
|
||||
export default async function processDeployment({
|
||||
isLegacy,
|
||||
org,
|
||||
cwd,
|
||||
projectName,
|
||||
shouldLinkFolder,
|
||||
isDetectingFramework,
|
||||
isSettingUpProject,
|
||||
skipAutoDetectionConfirmation,
|
||||
...args
|
||||
}: {
|
||||
@@ -69,9 +68,9 @@ export default async function processDeployment({
|
||||
force?: boolean;
|
||||
org: Org;
|
||||
projectName: string;
|
||||
shouldLinkFolder: boolean;
|
||||
isDetectingFramework: boolean;
|
||||
isSettingUpProject: boolean;
|
||||
skipAutoDetectionConfirmation?: boolean;
|
||||
cwd?: string;
|
||||
}) {
|
||||
if (isLegacy) return processLegacyDeployment(args);
|
||||
|
||||
@@ -106,8 +105,8 @@ export default async function processDeployment({
|
||||
let buildSpinner = null;
|
||||
let deploySpinner = null;
|
||||
|
||||
let deployingSpinner = wait(
|
||||
isDetectingFramework
|
||||
let deployingSpinner = output.spinner(
|
||||
isSettingUpProject
|
||||
? `Setting up project`
|
||||
: `Deploying ${chalk.bold(`${org.slug}/${projectName}`)}`,
|
||||
0
|
||||
@@ -167,26 +166,24 @@ export default async function processDeployment({
|
||||
|
||||
now._host = event.payload.url;
|
||||
|
||||
if (shouldLinkFolder) {
|
||||
await linkFolderToProject(
|
||||
output,
|
||||
paths[0],
|
||||
{
|
||||
orgId: org.id,
|
||||
projectId: event.payload.projectId,
|
||||
},
|
||||
projectName,
|
||||
org.slug
|
||||
);
|
||||
}
|
||||
await linkFolderToProject(
|
||||
output,
|
||||
cwd || paths[0],
|
||||
{
|
||||
orgId: org.id,
|
||||
projectId: event.payload.projectId,
|
||||
},
|
||||
projectName,
|
||||
org.slug
|
||||
);
|
||||
|
||||
printInspectUrl(output, event.payload.url, deployStamp, org.slug);
|
||||
|
||||
if (queuedSpinner === null) {
|
||||
queuedSpinner =
|
||||
event.payload.readyState === 'QUEUED'
|
||||
? wait('Queued', 0)
|
||||
: wait('Building', 0);
|
||||
? output.spinner('Queued', 0)
|
||||
: output.spinner('Building', 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +193,7 @@ export default async function processDeployment({
|
||||
}
|
||||
|
||||
if (buildSpinner === null) {
|
||||
buildSpinner = wait('Building', 0);
|
||||
buildSpinner = output.spinner('Building', 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,7 +205,7 @@ export default async function processDeployment({
|
||||
buildSpinner();
|
||||
}
|
||||
|
||||
deploySpinner = wait('Completing', 0);
|
||||
deploySpinner = output.spinner('Completing', 0);
|
||||
}
|
||||
|
||||
// Handle error events
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
import pkg from '../../../package.json';
|
||||
|
||||
import { NoBuilderCacheError } from '../errors-ts';
|
||||
import wait from '../output/wait';
|
||||
import { Output } from '../output';
|
||||
import { getDistTag } from '../get-dist-tag';
|
||||
|
||||
@@ -246,7 +245,7 @@ export async function installBuilders(
|
||||
return;
|
||||
}
|
||||
|
||||
const stopSpinner = wait(
|
||||
const stopSpinner = output.spinner(
|
||||
`Installing ${pluralize(
|
||||
'Runtime',
|
||||
packagesToInstall.length
|
||||
|
||||
@@ -3,18 +3,10 @@
|
||||
import ms from 'ms';
|
||||
import bytes from 'bytes';
|
||||
import { promisify } from 'util';
|
||||
import { delimiter, dirname, extname, join } from 'path';
|
||||
import { delimiter, dirname, join } from 'path';
|
||||
import { fork, ChildProcess } from 'child_process';
|
||||
import { createFunction } from '@zeit/fun';
|
||||
import {
|
||||
Builder,
|
||||
File,
|
||||
Lambda,
|
||||
FileBlob,
|
||||
FileFsRef,
|
||||
detectApiDirectory,
|
||||
detectApiExtensions,
|
||||
} from '@now/build-utils';
|
||||
import { Builder, File, Lambda, FileBlob, FileFsRef } from '@now/build-utils';
|
||||
import which from 'which';
|
||||
import plural from 'pluralize';
|
||||
import minimatch from 'minimatch';
|
||||
@@ -128,7 +120,7 @@ export async function executeBuild(
|
||||
const {
|
||||
builderWithPkg: { runInProcess, builder, package: pkg },
|
||||
} = match;
|
||||
const { src: entrypoint } = match;
|
||||
const { entrypoint } = match;
|
||||
const { env, debug, buildEnv, yarnPath, cwd: workPath } = devServer;
|
||||
|
||||
const startTime = Date.now();
|
||||
@@ -282,8 +274,8 @@ export async function executeBuild(
|
||||
}
|
||||
|
||||
const { output } = result;
|
||||
|
||||
const { cleanUrls } = nowConfig;
|
||||
|
||||
// Mimic fmeta-util and perform file renaming
|
||||
Object.entries(output).forEach(([path, value]) => {
|
||||
if (cleanUrls && path.endsWith('.html')) {
|
||||
@@ -294,6 +286,11 @@ export async function executeBuild(
|
||||
}
|
||||
}
|
||||
|
||||
const extensionless = devServer.getExtensionlessFile(path);
|
||||
if (extensionless) {
|
||||
path = extensionless;
|
||||
}
|
||||
|
||||
delete output[path];
|
||||
output[path] = value;
|
||||
});
|
||||
@@ -403,6 +400,7 @@ export async function getBuildMatches(
|
||||
cwd: string,
|
||||
yarnDir: string,
|
||||
output: Output,
|
||||
devServer: DevServer,
|
||||
fileList: string[]
|
||||
): Promise<BuildMatch[]> {
|
||||
const matches: BuildMatch[] = [];
|
||||
@@ -415,9 +413,6 @@ export async function getBuildMatches(
|
||||
|
||||
const noMatches: Builder[] = [];
|
||||
const builds = nowConfig.builds || [{ src: '**', use: '@now/static' }];
|
||||
const apiDir = detectApiDirectory(builds || []);
|
||||
const apiExtensions = detectApiExtensions(builds || []);
|
||||
const apiMatch = apiDir + '/';
|
||||
|
||||
for (const buildConfig of builds) {
|
||||
let { src, use } = buildConfig;
|
||||
@@ -436,10 +431,13 @@ export async function getBuildMatches(
|
||||
// We need to escape brackets since `glob` will
|
||||
// try to find a group otherwise
|
||||
src = src.replace(/(\[|\])/g, '[$1]');
|
||||
const ext = extname(src);
|
||||
if (apiDir && src.startsWith(apiMatch) && apiExtensions.has(ext)) {
|
||||
// lambda function files are trimmed of their file extension
|
||||
src = src.slice(0, -ext.length);
|
||||
|
||||
// lambda function files are trimmed of their file extension
|
||||
const mapToEntrypoint = new Map<string, string>();
|
||||
const extensionless = devServer.getExtensionlessFile(src);
|
||||
if (extensionless) {
|
||||
mapToEntrypoint.set(extensionless, src);
|
||||
src = extensionless;
|
||||
}
|
||||
|
||||
const files = fileList
|
||||
@@ -456,6 +454,7 @@ export async function getBuildMatches(
|
||||
matches.push({
|
||||
...buildConfig,
|
||||
src,
|
||||
entrypoint: mapToEntrypoint.get(src) || src,
|
||||
builderWithPkg,
|
||||
buildOutput: {},
|
||||
buildResults: new Map(),
|
||||
|
||||
@@ -11,9 +11,13 @@ import { randomBytes } from 'crypto';
|
||||
import serveHandler from 'serve-handler';
|
||||
import { watch, FSWatcher } from 'chokidar';
|
||||
import { parse as parseDotenv } from 'dotenv';
|
||||
import { basename, dirname, extname, join } from 'path';
|
||||
import { basename, dirname, extname, join, delimiter } from 'path';
|
||||
import { getTransformedRoutes, HandleValue } from '@now/routing-utils';
|
||||
import directoryTemplate from 'serve-handler/src/directory';
|
||||
import getPort from 'get-port';
|
||||
import { ChildProcess } from 'child_process';
|
||||
import isPortReachable from 'is-port-reachable';
|
||||
import which from 'which';
|
||||
|
||||
import {
|
||||
Builder,
|
||||
@@ -23,6 +27,8 @@ import {
|
||||
detectRoutes,
|
||||
detectApiDirectory,
|
||||
detectApiExtensions,
|
||||
execAsync,
|
||||
spawnCommand,
|
||||
} from '@now/build-utils';
|
||||
|
||||
import { once } from '../once';
|
||||
@@ -107,6 +113,8 @@ export default class DevServer {
|
||||
public address: string;
|
||||
|
||||
private cachedNowConfig: NowConfig | null;
|
||||
private apiDir: string | null;
|
||||
private apiExtensions: Set<string>;
|
||||
private server: http.Server;
|
||||
private stopping: boolean;
|
||||
private serverUrlPrinted: boolean;
|
||||
@@ -118,6 +126,9 @@ export default class DevServer {
|
||||
private watchAggregationTimeout: number;
|
||||
private filter: (path: string) => boolean;
|
||||
private podId: string;
|
||||
private devCommand?: string;
|
||||
private devProcess?: ChildProcess;
|
||||
private devProcessPort?: number;
|
||||
|
||||
private getNowConfigPromise: Promise<NowConfig> | null;
|
||||
private blockingBuildsPromise: Promise<void> | null;
|
||||
@@ -131,11 +142,14 @@ export default class DevServer {
|
||||
this.buildEnv = {};
|
||||
this.files = {};
|
||||
this.address = '';
|
||||
this.devCommand = options.devCommand;
|
||||
|
||||
// This gets updated when `start()` is invoked
|
||||
this.yarnPath = '/';
|
||||
|
||||
this.cachedNowConfig = null;
|
||||
this.apiDir = null;
|
||||
this.apiExtensions = new Set<string>();
|
||||
this.server = http.createServer(this.devServerHandler);
|
||||
this.server.timeout = 0; // Disable timeout
|
||||
this.serverUrlPrinted = false;
|
||||
@@ -282,6 +296,10 @@ export default class DevServer {
|
||||
const name = relative(this.cwd, fsPath);
|
||||
try {
|
||||
this.files[name] = await FileFsRef.fromFsPath({ fsPath });
|
||||
const extensionless = this.getExtensionlessFile(name);
|
||||
if (extensionless) {
|
||||
this.files[extensionless] = await FileFsRef.fromFsPath({ fsPath });
|
||||
}
|
||||
fileChanged(name, changed, removed);
|
||||
this.output.debug(`File created: ${name}`);
|
||||
} catch (err) {
|
||||
@@ -302,6 +320,11 @@ export default class DevServer {
|
||||
const name = relative(this.cwd, fsPath);
|
||||
this.output.debug(`File deleted: ${name}`);
|
||||
fileRemoved(name, this.files, changed, removed);
|
||||
const extensionless = this.getExtensionlessFile(name);
|
||||
if (extensionless) {
|
||||
this.output.debug(`File deleted: ${extensionless}`);
|
||||
fileRemoved(extensionless, this.files, changed, removed);
|
||||
}
|
||||
}
|
||||
|
||||
async handleFileModified(
|
||||
@@ -334,6 +357,7 @@ export default class DevServer {
|
||||
this.cwd,
|
||||
this.yarnPath,
|
||||
this.output,
|
||||
this,
|
||||
fileList
|
||||
);
|
||||
const sources = matches.map(m => m.src);
|
||||
@@ -525,7 +549,7 @@ export default class DevServer {
|
||||
const featHandleMiss = true; // enable for zero config
|
||||
const { projectSettings, cleanUrls, trailingSlash } = config;
|
||||
|
||||
const { builders, warnings, errors } = await detectBuilders(files, pkg, {
|
||||
let { builders, warnings, errors } = await detectBuilders(files, pkg, {
|
||||
tag: getDistTag(cliVersion) === 'canary' ? 'canary' : 'latest',
|
||||
functions: config.functions,
|
||||
...(projectSettings ? { projectSettings } : {}),
|
||||
@@ -541,6 +565,10 @@ export default class DevServer {
|
||||
}
|
||||
|
||||
if (builders) {
|
||||
if (this.devCommand) {
|
||||
builders = builders.filter(filterFrontendBuilds);
|
||||
}
|
||||
|
||||
const {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
@@ -571,10 +599,14 @@ export default class DevServer {
|
||||
}
|
||||
|
||||
if (!config.builds || config.builds.length === 0) {
|
||||
if (isInitialLoad) {
|
||||
if (isInitialLoad && !this.devCommand) {
|
||||
this.output.note(`Serving all files as static`);
|
||||
}
|
||||
} else if (Array.isArray(config.builds)) {
|
||||
if (this.devCommand) {
|
||||
config.builds = config.builds.filter(filterFrontendBuilds);
|
||||
}
|
||||
|
||||
// `@now/static-build` needs to be the last builder
|
||||
// since it might catch all other requests
|
||||
config.builds.sort(sortBuilders);
|
||||
@@ -583,6 +615,8 @@ export default class DevServer {
|
||||
await this.validateNowConfig(config);
|
||||
|
||||
this.cachedNowConfig = config;
|
||||
this.apiDir = detectApiDirectory(config.builds || []);
|
||||
this.apiExtensions = detectApiExtensions(config.builds || []);
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -722,26 +756,21 @@ export default class DevServer {
|
||||
|
||||
const opts = { output: this.output, isBuilds: true };
|
||||
const files = await getFiles(this.cwd, nowConfig, opts);
|
||||
const results: { [filePath: string]: FileFsRef } = {};
|
||||
const apiDir = detectApiDirectory(nowConfig.builds || []);
|
||||
const apiExtensions = detectApiExtensions(nowConfig.builds || []);
|
||||
const apiMatch = apiDir + '/';
|
||||
this.files = {};
|
||||
for (const fsPath of files) {
|
||||
let path = relative(this.cwd, fsPath);
|
||||
const { mode } = await fs.stat(fsPath);
|
||||
const ext = extname(path);
|
||||
if (apiDir && path.startsWith(apiMatch) && apiExtensions.has(ext)) {
|
||||
// lambda function files are trimmed of their file extension
|
||||
path = path.slice(0, -ext.length);
|
||||
this.files[path] = new FileFsRef({ mode, fsPath });
|
||||
const extensionless = this.getExtensionlessFile(path);
|
||||
if (extensionless) {
|
||||
this.files[extensionless] = new FileFsRef({ mode, fsPath });
|
||||
}
|
||||
results[path] = new FileFsRef({ mode, fsPath });
|
||||
}
|
||||
this.files = results;
|
||||
|
||||
const builders: Set<string> = new Set(
|
||||
const builders = new Set<string>(
|
||||
(nowConfig.builds || [])
|
||||
.filter((b: Builder) => b.use)
|
||||
.map((b: Builder) => b.use as string)
|
||||
.map((b: Builder) => b.use)
|
||||
);
|
||||
|
||||
await installBuilders(builders, this.yarnPath, this.output);
|
||||
@@ -806,6 +835,8 @@ export default class DevServer {
|
||||
// Wait for "ready" event of the watcher
|
||||
await once(this.watcher, 'ready');
|
||||
|
||||
const devCommandPromise = this.runDevCommand();
|
||||
|
||||
let address: string | null = null;
|
||||
while (typeof address !== 'string') {
|
||||
try {
|
||||
@@ -837,6 +868,8 @@ export default class DevServer {
|
||||
.replace('[::]', 'localhost')
|
||||
.replace('127.0.0.1', 'localhost');
|
||||
|
||||
await devCommandPromise;
|
||||
|
||||
this.output.ready(`Available at ${link(this.address)}`);
|
||||
this.serverUrlPrinted = true;
|
||||
}
|
||||
@@ -861,6 +894,18 @@ export default class DevServer {
|
||||
ops.push(shutdownBuilder(match, this.output));
|
||||
}
|
||||
|
||||
ops.push(
|
||||
new Promise(resolve => {
|
||||
if (!this.devProcess) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
this.devProcess.on('exit', () => resolve());
|
||||
process.kill(this.devProcess.pid, exitCode);
|
||||
})
|
||||
);
|
||||
|
||||
ops.push(close(this.server));
|
||||
|
||||
if (this.watcher) {
|
||||
@@ -1052,7 +1097,9 @@ export default class DevServer {
|
||||
// If the requested asset wasn't found in the match's
|
||||
// outputs then trigger a build
|
||||
const buildKey =
|
||||
requestPath === null ? match.src : `${match.src}-${requestPath}`;
|
||||
requestPath === null
|
||||
? match.entrypoint
|
||||
: `${match.entrypoint}-${requestPath}`;
|
||||
let buildPromise = this.inProgressBuilds.get(buildKey);
|
||||
if (buildPromise) {
|
||||
// A build for `buildKey` is already in progress, so don't trigger
|
||||
@@ -1094,6 +1141,19 @@ export default class DevServer {
|
||||
}
|
||||
}
|
||||
|
||||
getExtensionlessFile = (path: string) => {
|
||||
const ext = extname(path);
|
||||
if (
|
||||
this.apiDir &&
|
||||
path.startsWith(this.apiDir + '/') &&
|
||||
this.apiExtensions.has(ext)
|
||||
) {
|
||||
// lambda function files are trimmed of their file extension
|
||||
return path.slice(0, -ext.length);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* DevServer HTTP handler
|
||||
*/
|
||||
@@ -1291,6 +1351,18 @@ export default class DevServer {
|
||||
const requestPath = dest.replace(/^\//, '');
|
||||
|
||||
if (!match) {
|
||||
// if the dev command is started, proxy to it
|
||||
if (this.devProcessPort) {
|
||||
this.output.debug('Proxy to dev command server');
|
||||
return proxyPass(
|
||||
req,
|
||||
res,
|
||||
`http://localhost:${this.devProcessPort}`,
|
||||
this.output,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
(statusCode === 404 && routeResult.phase === 'miss') ||
|
||||
!this.renderDirectoryListing(req, res, requestPath, nowRequestId)
|
||||
@@ -1348,6 +1420,18 @@ export default class DevServer {
|
||||
}
|
||||
|
||||
if (!foundAsset) {
|
||||
// if the dev command is started, proxy to it
|
||||
if (this.devProcessPort) {
|
||||
this.output.debug('Proxy to dev command server');
|
||||
return proxyPass(
|
||||
req,
|
||||
res,
|
||||
`http://localhost:${this.devProcessPort}`,
|
||||
this.output,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
await this.send404(req, res, nowRequestId);
|
||||
return;
|
||||
}
|
||||
@@ -1547,6 +1631,67 @@ export default class DevServer {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async runDevCommand() {
|
||||
if (!this.devCommand) return;
|
||||
|
||||
const cwd = this.cwd;
|
||||
|
||||
const { stdout: yarnBinStdout } = await execAsync('yarn', ['bin'], {
|
||||
cwd,
|
||||
});
|
||||
|
||||
const yarnBinPath = yarnBinStdout.trim();
|
||||
|
||||
this.output.log(
|
||||
`Running Dev Command ${chalk.cyan.bold(`“${this.devCommand}”`)}`
|
||||
);
|
||||
|
||||
const port = await getPort();
|
||||
|
||||
const env: EnvConfig = {
|
||||
...process.env,
|
||||
...this.buildEnv,
|
||||
NOW_REGION: 'dev1',
|
||||
};
|
||||
|
||||
const devCommand = this.devCommand
|
||||
.replace(/\$PORT/g, `${port}`)
|
||||
.replace(/%PORT%/g, `${port}`);
|
||||
|
||||
this.output.debug(
|
||||
`Starting dev command with parameters : ${JSON.stringify({
|
||||
cwd: this.cwd,
|
||||
devCommand,
|
||||
port,
|
||||
})}`
|
||||
);
|
||||
|
||||
const isNpxAvailable = await which('npx')
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
|
||||
if (!isNpxAvailable) {
|
||||
env.PATH = `${yarnBinPath}${delimiter}${env.PATH}`;
|
||||
}
|
||||
|
||||
this.output.debug('Spawning dev command');
|
||||
this.output.debug(`PATH is ${env.PATH}`);
|
||||
|
||||
const p = spawnCommand(
|
||||
isNpxAvailable ? `npx --no-install ${devCommand}` : devCommand,
|
||||
{ stdio: 'inherit', cwd, env }
|
||||
);
|
||||
|
||||
p.on('exit', () => {
|
||||
this.devProcessPort = undefined;
|
||||
});
|
||||
|
||||
await checkForPort(port, 1000 * 60 * 5);
|
||||
|
||||
this.devProcessPort = port;
|
||||
this.devProcess = p;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1556,13 +1701,14 @@ function proxyPass(
|
||||
req: http.IncomingMessage,
|
||||
res: http.ServerResponse,
|
||||
dest: string,
|
||||
output: Output
|
||||
output: Output,
|
||||
ignorePath: boolean = true
|
||||
): void {
|
||||
const proxy = httpProxy.createProxyServer({
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
xfwd: true,
|
||||
ignorePath: true,
|
||||
ignorePath,
|
||||
target: dest,
|
||||
});
|
||||
|
||||
@@ -1801,3 +1947,27 @@ function needsBlockingBuild(buildMatch: BuildMatch): boolean {
|
||||
const { builder } = buildMatch.builderWithPkg;
|
||||
return typeof builder.shouldServe !== 'function';
|
||||
}
|
||||
|
||||
async function sleep(n: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, n));
|
||||
}
|
||||
|
||||
async function checkForPort(
|
||||
port: number | undefined,
|
||||
timeout: number
|
||||
): Promise<void> {
|
||||
const start = Date.now();
|
||||
while (!(await isPortReachable(port))) {
|
||||
if (Date.now() - start > timeout) {
|
||||
throw new Error(`Detecting port ${port} timed out after ${timeout}ms`);
|
||||
}
|
||||
await sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
function filterFrontendBuilds(build: Builder) {
|
||||
return (
|
||||
!build.use.startsWith('@now/static-build') &&
|
||||
!build.use.startsWith('@now/next')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ export { NowConfig };
|
||||
export interface DevServerOptions {
|
||||
output: Output;
|
||||
debug: boolean;
|
||||
devCommand?: string;
|
||||
}
|
||||
|
||||
export interface EnvConfig {
|
||||
@@ -25,6 +26,7 @@ export interface EnvConfig {
|
||||
}
|
||||
|
||||
export interface BuildMatch extends BuildConfig {
|
||||
entrypoint: string;
|
||||
builderWithPkg: BuilderWithPackage;
|
||||
buildOutput: BuilderOutputs;
|
||||
buildResults: Map<string | null, BuildResult>;
|
||||
|
||||
@@ -9,7 +9,6 @@ import getDomainStatus from './get-domain-status';
|
||||
import promptBool from '../input/prompt-bool';
|
||||
import purchaseDomain from './purchase-domain';
|
||||
import stamp from '../output/stamp';
|
||||
import wait from '../output/wait';
|
||||
import * as ERRORS from '../errors-ts';
|
||||
|
||||
const isTTY = process.stdout.isTTY;
|
||||
@@ -20,7 +19,7 @@ export default async function purchaseDomainIfAvailable(
|
||||
domain: string,
|
||||
contextName: string
|
||||
) {
|
||||
const cancelWait = wait(`Checking status of ${chalk.bold(domain)}`);
|
||||
const cancelWait = output.spinner(`Checking status of ${chalk.bold(domain)}`);
|
||||
const buyDomainStamp = stamp();
|
||||
const { available } = await getDomainStatus(client, domain);
|
||||
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
export function emoji(label: string): string | undefined {
|
||||
export type EmojiLabel =
|
||||
| 'notice'
|
||||
| 'tip'
|
||||
| 'warning'
|
||||
| 'link'
|
||||
| 'inspect'
|
||||
| 'success';
|
||||
|
||||
export function emoji(label: EmojiLabel): string | undefined {
|
||||
switch (label) {
|
||||
case 'notice':
|
||||
return '📝';
|
||||
|
||||
14
packages/now-cli/src/util/get-frameworks.ts
Normal file
14
packages/now-cli/src/util/get-frameworks.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import fetch from 'node-fetch';
|
||||
import { Framework } from '@now/frameworks';
|
||||
|
||||
export async function getFrameworks(): Promise<Framework[]> {
|
||||
const res = await fetch('https://api-frameworks.zeit.sh/api/frameworks');
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error('Could not retrieve frameworks');
|
||||
}
|
||||
|
||||
const json: Framework[] = await res.json();
|
||||
|
||||
return json;
|
||||
}
|
||||
@@ -1,15 +1,24 @@
|
||||
import Client from './client';
|
||||
import { APIError, InvalidToken } from './errors-ts';
|
||||
import { Team } from '../types';
|
||||
// @ts-ignore
|
||||
import NowTeams from './teams.js';
|
||||
|
||||
type Response = {
|
||||
teams: Team[];
|
||||
};
|
||||
let teams: Team[] | undefined;
|
||||
|
||||
export default async function getTeams(client: Client) {
|
||||
if (teams) return teams;
|
||||
|
||||
try {
|
||||
const { teams } = await client.fetch<Response>(`/teams`);
|
||||
return teams;
|
||||
// we're using NowTeams because `client.fetch` hangs on windows
|
||||
const teamClient = new NowTeams({
|
||||
apiUrl: client._apiUrl,
|
||||
token: client._token,
|
||||
debug: client._debug,
|
||||
});
|
||||
|
||||
const teams = (await teamClient.ls()).teams;
|
||||
return teams as Team[];
|
||||
} catch (error) {
|
||||
if (error instanceof APIError && error.status === 403) {
|
||||
throw new InvalidToken();
|
||||
|
||||
@@ -2,23 +2,27 @@ import Client from './client';
|
||||
import { User } from '../types';
|
||||
import { APIError, InvalidToken, MissingUser } from './errors-ts';
|
||||
|
||||
let user: User | undefined;
|
||||
|
||||
export default async function getUser(client: Client) {
|
||||
let user;
|
||||
if (user) return user;
|
||||
|
||||
try {
|
||||
({ user } = await client.fetch<{ user: User }>('/www/user', {
|
||||
useCurrentTeam: false
|
||||
}));
|
||||
const res = await client.fetch<{ user: User }>('/www/user', {
|
||||
useCurrentTeam: false,
|
||||
});
|
||||
|
||||
if (!res.user) {
|
||||
throw new MissingUser();
|
||||
}
|
||||
|
||||
user = res.user;
|
||||
return user;
|
||||
} catch (error) {
|
||||
if (error instanceof APIError && error.status === 403) {
|
||||
throw new InvalidToken();
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
throw new MissingUser();
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
@@ -70,8 +70,8 @@ export default class Now extends EventEmitter {
|
||||
skipAutoDetectionConfirmation,
|
||||
},
|
||||
org,
|
||||
shouldLinkFolder,
|
||||
isDetectingFramework
|
||||
isSettingUpProject,
|
||||
cwd
|
||||
) {
|
||||
const opts = { output: this._output, hasNowJson };
|
||||
const { log, warn } = this._output;
|
||||
@@ -182,9 +182,9 @@ export default class Now extends EventEmitter {
|
||||
force: forceNew,
|
||||
org,
|
||||
projectName: name,
|
||||
shouldLinkFolder,
|
||||
isDetectingFramework,
|
||||
isSettingUpProject,
|
||||
skipAutoDetectionConfirmation,
|
||||
cwd,
|
||||
});
|
||||
|
||||
// We report about files whose sizes are too big
|
||||
|
||||
@@ -2,7 +2,8 @@ import inquirer from 'inquirer';
|
||||
import confirm from './confirm';
|
||||
import chalk from 'chalk';
|
||||
import { Output } from '../output';
|
||||
import { Framework, SettingValue } from '@now/frameworks';
|
||||
import { Framework } from '@now/frameworks';
|
||||
import { isSettingValue } from '../is-setting-value';
|
||||
|
||||
export interface ProjectSettings {
|
||||
buildCommand: string | null;
|
||||
@@ -10,37 +11,45 @@ export interface ProjectSettings {
|
||||
devCommand: string | null;
|
||||
}
|
||||
|
||||
export interface ProjectSettingsWithFramework extends ProjectSettings {
|
||||
framework: string | null;
|
||||
}
|
||||
|
||||
const fields: { name: string; value: keyof ProjectSettings }[] = [
|
||||
{ name: 'Build Command', value: 'buildCommand' },
|
||||
{ name: 'Output Directory', value: 'outputDirectory' },
|
||||
{ name: 'Development Command', value: 'devCommand' },
|
||||
];
|
||||
|
||||
function isSettingValue(setting: any): setting is SettingValue {
|
||||
return setting && typeof setting.value === 'string';
|
||||
}
|
||||
|
||||
export default async function editProjectSettings(
|
||||
output: Output,
|
||||
projectSettings: ProjectSettings | null,
|
||||
framework: Framework | null
|
||||
) {
|
||||
// create new settings object filled with "null" values
|
||||
const settings: Partial<ProjectSettings> = {};
|
||||
// create new settings object, missing values will be filled with `null`
|
||||
const settings: Partial<ProjectSettingsWithFramework> = {
|
||||
...projectSettings,
|
||||
};
|
||||
|
||||
for (let field of fields) {
|
||||
settings[field.value] =
|
||||
(projectSettings && projectSettings[field.value]) || null;
|
||||
}
|
||||
|
||||
// skip editing project settings if no framework is detected
|
||||
if (!framework) {
|
||||
settings.framework = null;
|
||||
return settings;
|
||||
}
|
||||
|
||||
output.print(
|
||||
`Auto-detected project settings (${chalk.bold(framework.name)}):\n`
|
||||
!framework.slug
|
||||
? `No framework detected. Default project settings:\n`
|
||||
: `Auto-detected project settings (${chalk.bold(framework.name)}):\n`
|
||||
);
|
||||
|
||||
settings.framework = framework.slug;
|
||||
|
||||
for (let field of fields) {
|
||||
const defaults = framework.settings[field.value];
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import chalk from 'chalk';
|
||||
import { ProjectNotFound } from '../../util/errors-ts';
|
||||
import { Output } from '../output';
|
||||
import { Project, Org } from '../../types';
|
||||
import wait from '../output/wait';
|
||||
import slugify from '@sindresorhus/slugify';
|
||||
|
||||
export default async function inputProject(
|
||||
output: Output,
|
||||
@@ -19,16 +19,26 @@ export default async function inputProject(
|
||||
return detectedProjectName;
|
||||
}
|
||||
|
||||
const slugifiedName = slugify(detectedProjectName);
|
||||
|
||||
// attempt to auto-detect a project to link
|
||||
let detectedProject = null;
|
||||
const existingProjectSpinner = wait('Searching for existing projects…', 1000);
|
||||
const existingProjectSpinner = output.spinner(
|
||||
'Searching for existing projects…',
|
||||
1000
|
||||
);
|
||||
try {
|
||||
const project = await getProjectByIdOrName(
|
||||
client,
|
||||
detectedProjectName,
|
||||
org.id
|
||||
);
|
||||
detectedProject = project instanceof ProjectNotFound ? null : project;
|
||||
const [project, slugifiedProject] = await Promise.all([
|
||||
getProjectByIdOrName(client, detectedProjectName, org.id),
|
||||
slugifiedName !== detectedProjectName
|
||||
? getProjectByIdOrName(client, slugifiedName, org.id)
|
||||
: null,
|
||||
]);
|
||||
detectedProject = !(project instanceof ProjectNotFound)
|
||||
? project
|
||||
: !(slugifiedProject instanceof ProjectNotFound)
|
||||
? slugifiedProject
|
||||
: null;
|
||||
} catch (error) {}
|
||||
existingProjectSpinner();
|
||||
|
||||
@@ -42,7 +52,7 @@ export default async function inputProject(
|
||||
if (
|
||||
await confirm(
|
||||
`Found project ${chalk.cyan(
|
||||
`“${org.slug}/${detectedProjectName}”`
|
||||
`“${org.slug}/${detectedProject.name}”`
|
||||
)}. Link to it?`,
|
||||
true
|
||||
)
|
||||
@@ -74,7 +84,7 @@ export default async function inputProject(
|
||||
continue;
|
||||
}
|
||||
|
||||
const spinner = wait('Verifying project name…', 1000);
|
||||
const spinner = output.spinner('Verifying project name…', 1000);
|
||||
try {
|
||||
project = await getProjectByIdOrName(client, projectName, org.id);
|
||||
} finally {
|
||||
@@ -97,7 +107,7 @@ export default async function inputProject(
|
||||
type: 'input',
|
||||
name: 'newProjectName',
|
||||
message: `What’s your project’s name?`,
|
||||
default: !detectedProject ? detectedProjectName : undefined,
|
||||
default: !detectedProject ? slugifiedName : undefined,
|
||||
});
|
||||
newProjectName = answers.newProjectName as string;
|
||||
|
||||
@@ -106,7 +116,7 @@ export default async function inputProject(
|
||||
continue;
|
||||
}
|
||||
|
||||
const spinner = wait('Verifying project name…', 1000);
|
||||
const spinner = output.spinner('Verifying project name…', 1000);
|
||||
let existingProject: Project | ProjectNotFound;
|
||||
try {
|
||||
existingProject = await getProjectByIdOrName(
|
||||
|
||||
54
packages/now-cli/src/util/input/input-root-directory.ts
Normal file
54
packages/now-cli/src/util/input/input-root-directory.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import path from 'path';
|
||||
import chalk from 'chalk';
|
||||
import inquirer from 'inquirer';
|
||||
import { Output } from '../output';
|
||||
import { validateRootDirectory } from '../validate-paths';
|
||||
|
||||
export async function inputRootDirectory(
|
||||
cwd: string,
|
||||
output: Output,
|
||||
autoConfirm: boolean
|
||||
) {
|
||||
if (autoConfirm) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const basename = path.basename(cwd);
|
||||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const { rootDirectory } = await inquirer.prompt({
|
||||
type: 'input',
|
||||
name: 'rootDirectory',
|
||||
message: `In which directory is your code located?`,
|
||||
transformer: (input: string) => {
|
||||
return `${chalk.dim(`${basename}/`)}${input}`;
|
||||
},
|
||||
});
|
||||
|
||||
if (!rootDirectory) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normal = path.normalize(rootDirectory);
|
||||
|
||||
if (normal === '.' || normal === './') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const fullPath = path.join(cwd, normal);
|
||||
|
||||
if (
|
||||
(await validateRootDirectory(
|
||||
output,
|
||||
cwd,
|
||||
fullPath,
|
||||
'Please choose a different one.'
|
||||
)) === false
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return normal;
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,12 @@ import inquirer from 'inquirer';
|
||||
import getUser from '../get-user';
|
||||
import getTeams from '../get-teams';
|
||||
import { User, Team, Org } from '../../types';
|
||||
import wait from '../output/wait';
|
||||
import { Output } from '../output';
|
||||
|
||||
type Choice = { name: string; value: Org };
|
||||
|
||||
export default async function selectProject(
|
||||
export default async function selectOrg(
|
||||
output: Output,
|
||||
question: string,
|
||||
client: Client,
|
||||
currentTeam?: string,
|
||||
@@ -15,7 +16,7 @@ export default async function selectProject(
|
||||
): Promise<Org> {
|
||||
require('./patch-inquirer');
|
||||
|
||||
const spinner = wait('Loading scopes…', 1000);
|
||||
const spinner = output.spinner('Loading scopes…', 1000);
|
||||
let user: User;
|
||||
let teams: Team[];
|
||||
try {
|
||||
|
||||
5
packages/now-cli/src/util/is-setting-value.ts
Normal file
5
packages/now-cli/src/util/is-setting-value.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { SettingValue } from '@now/frameworks';
|
||||
|
||||
export function isSettingValue(setting: any): setting is SettingValue {
|
||||
return setting && typeof setting.value === 'string';
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { red } from 'chalk';
|
||||
|
||||
const error = msg => `${red('> Aborted!')} ${msg}`;
|
||||
|
||||
export default error;
|
||||
@@ -2,6 +2,7 @@ import chalk from 'chalk';
|
||||
import boxen from 'boxen';
|
||||
import { format } from 'util';
|
||||
import { Console } from 'console';
|
||||
import wait from './wait';
|
||||
|
||||
export type Output = ReturnType<typeof createOutput>;
|
||||
|
||||
@@ -76,6 +77,20 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
function spinner(message: string, delay: number = 300) {
|
||||
if (debugEnabled) {
|
||||
debug(`Spinner invoked (${message}) with a ${delay}ms delay`);
|
||||
let isEnded = false;
|
||||
return () => {
|
||||
if (isEnded) return;
|
||||
isEnded = true;
|
||||
debug(`Spinner ended (${message})`);
|
||||
};
|
||||
}
|
||||
|
||||
return wait(message, delay);
|
||||
}
|
||||
|
||||
// This is pretty hacky, but since we control the version of Node.js
|
||||
// being used because of `pkg` it's safe to do in this case.
|
||||
const c = {
|
||||
@@ -109,5 +124,6 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
|
||||
dim,
|
||||
time,
|
||||
note,
|
||||
spinner,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import ora from 'ora';
|
||||
import chalk from 'chalk';
|
||||
import eraseLines from './erase-lines';
|
||||
|
||||
export default function wait(msg: string, timeout: number = 300, _ora = ora) {
|
||||
export default function wait(msg: string, delay: number = 300, _ora = ora) {
|
||||
let spinner: ReturnType<typeof _ora>;
|
||||
let running = false;
|
||||
|
||||
@@ -11,7 +11,7 @@ export default function wait(msg: string, timeout: number = 300, _ora = ora) {
|
||||
spinner.color = 'gray';
|
||||
spinner.start();
|
||||
running = true;
|
||||
}, timeout);
|
||||
}, delay);
|
||||
|
||||
const cancel = () => {
|
||||
clearTimeout(planned);
|
||||
|
||||
11
packages/now-cli/src/util/projects/NOW_DIR_README.txt
Normal file
11
packages/now-cli/src/util/projects/NOW_DIR_README.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
> Why do I have a folder named ".now" in my project?
|
||||
The ".now" folder is created when you link a directory to a ZEIT Now project.
|
||||
|
||||
> What does the "project.json" file contain?
|
||||
The "project.json" file contains:
|
||||
- The ID of the ZEIT Now project that you linked ("projectId")
|
||||
- The ID of the user or team your ZEIT Now project is owned by ("orgId")
|
||||
|
||||
> Should I commit the ".now" folder?
|
||||
No, you should not share the ".now" folder with anyone.
|
||||
Upon creation, it will be automatically added to your ".gitignore" file.
|
||||
@@ -12,15 +12,42 @@ import { Project } from '../../types';
|
||||
import { Org, ProjectLink } from '../../types';
|
||||
import chalk from 'chalk';
|
||||
import { prependEmoji, emoji } from '../emoji';
|
||||
import wait from '../output/wait';
|
||||
|
||||
const readFile = promisify(fs.readFile);
|
||||
const writeFile = promisify(fs.writeFile);
|
||||
|
||||
export const NOW_FOLDER = '.now';
|
||||
export const NOW_FOLDER_README = 'README.txt';
|
||||
export const NOW_PROJECT_LINK_FILE = 'project.json';
|
||||
|
||||
async function getOrg(client: Client, orgId: string): Promise<Org | null> {
|
||||
async function getLink(path?: string): Promise<ProjectLink | null> {
|
||||
try {
|
||||
const json = await readFile(
|
||||
join(path || process.cwd(), NOW_FOLDER, NOW_PROJECT_LINK_FILE),
|
||||
{ encoding: 'utf8' }
|
||||
);
|
||||
|
||||
const link: ProjectLink = JSON.parse(json);
|
||||
|
||||
return link;
|
||||
} catch (error) {
|
||||
// link file does not exists, project is not linked
|
||||
if (['ENOENT', 'ENOTDIR'].includes(error.code)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// link file can't be read
|
||||
if (error.name === 'SyntaxError') {
|
||||
throw new Error(
|
||||
'Now project settings could not be retrieved. To link your project again, remove the `.now` directory.'
|
||||
);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function getOrgById(client: Client, orgId: string): Promise<Org | null> {
|
||||
if (orgId.startsWith('team_')) {
|
||||
const team = await getTeamById(client, orgId);
|
||||
if (!team) return null;
|
||||
@@ -32,70 +59,121 @@ async function getOrg(client: Client, orgId: string): Promise<Org | null> {
|
||||
return { type: 'user', id: orgId, slug: user.username };
|
||||
}
|
||||
|
||||
export async function getLinkedOrg(
|
||||
client: Client,
|
||||
output: Output,
|
||||
path?: string
|
||||
): Promise<
|
||||
| { status: 'linked'; org: Org }
|
||||
| { status: 'not_linked'; org: null }
|
||||
| { status: 'error'; exitCode: number }
|
||||
> {
|
||||
const { NOW_ORG_ID } = process.env;
|
||||
|
||||
let orgId: string | null = null;
|
||||
if (NOW_ORG_ID) {
|
||||
orgId = NOW_ORG_ID;
|
||||
} else {
|
||||
const link = await getLink(path);
|
||||
|
||||
if (link) {
|
||||
orgId = link.orgId;
|
||||
}
|
||||
}
|
||||
|
||||
if (!orgId) {
|
||||
return { status: 'not_linked', org: null };
|
||||
}
|
||||
|
||||
const spinner = output.spinner('Retrieving scope…', 1000);
|
||||
try {
|
||||
const org = await getOrgById(client, orgId);
|
||||
|
||||
if (!org && NOW_ORG_ID) {
|
||||
output.print(
|
||||
`${chalk.red('Error!')} Organization not found (${JSON.stringify({
|
||||
NOW_ORG_ID,
|
||||
})})\n`
|
||||
);
|
||||
return { status: 'error', exitCode: 1 };
|
||||
}
|
||||
|
||||
if (!org) {
|
||||
return { status: 'not_linked', org: null };
|
||||
}
|
||||
|
||||
return { status: 'linked', org };
|
||||
} finally {
|
||||
spinner();
|
||||
}
|
||||
}
|
||||
|
||||
export async function getLinkedProject(
|
||||
output: Output,
|
||||
client: Client,
|
||||
path: string
|
||||
): Promise<[Org | null, Project | null]> {
|
||||
try {
|
||||
let link: ProjectLink;
|
||||
path?: string
|
||||
): Promise<
|
||||
| { status: 'linked'; org: Org; project: Project }
|
||||
| { status: 'not_linked'; org: null; project: null }
|
||||
| { status: 'error'; exitCode: number }
|
||||
> {
|
||||
const { NOW_ORG_ID, NOW_PROJECT_ID } = process.env;
|
||||
const shouldUseEnv = Boolean(NOW_ORG_ID && NOW_PROJECT_ID);
|
||||
|
||||
const { NOW_ORG_ID, NOW_PROJECT_ID } = process.env;
|
||||
if (NOW_ORG_ID && NOW_PROJECT_ID) {
|
||||
link = {
|
||||
orgId: NOW_ORG_ID,
|
||||
projectId: NOW_PROJECT_ID,
|
||||
};
|
||||
} else {
|
||||
const json = await readFile(
|
||||
join(path, NOW_FOLDER, NOW_PROJECT_LINK_FILE),
|
||||
{ encoding: 'utf8' }
|
||||
);
|
||||
|
||||
link = JSON.parse(json);
|
||||
}
|
||||
|
||||
const spinner = wait('Retrieving project…', 1000);
|
||||
let org: Org | null;
|
||||
let project: Project | ProjectNotFound | null;
|
||||
try {
|
||||
[org, project] = await Promise.all([
|
||||
getOrg(client, link.orgId),
|
||||
getProjectByIdOrName(client, link.projectId, link.orgId),
|
||||
]);
|
||||
} finally {
|
||||
spinner();
|
||||
}
|
||||
|
||||
if (project instanceof ProjectNotFound || org === null) {
|
||||
if (!(NOW_ORG_ID && NOW_PROJECT_ID)) {
|
||||
output.print(
|
||||
prependEmoji(
|
||||
'Your project was either removed from ZEIT Now or you’re not a member of it anymore.\n',
|
||||
emoji('warning')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
return [org, project];
|
||||
} catch (error) {
|
||||
// link file does not exists, project is not linked
|
||||
if (['ENOENT', 'ENOTDIR'].includes(error.code)) {
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
// link file can't be read
|
||||
if (error.name === 'SyntaxError') {
|
||||
throw new Error(
|
||||
'Now project settings could not be retrieved. To link your project again, remove .now'
|
||||
);
|
||||
}
|
||||
|
||||
throw error;
|
||||
if ((NOW_ORG_ID || NOW_PROJECT_ID) && !shouldUseEnv) {
|
||||
output.print(
|
||||
`${chalk.red('Error!')} You specified ${
|
||||
NOW_ORG_ID ? '`NOW_ORG_ID`' : '`NOW_PROJECT_ID`'
|
||||
} but you forgot to specify ${
|
||||
NOW_ORG_ID ? '`NOW_PROJECT_ID`' : '`NOW_ORG_ID`'
|
||||
}. You need to specify both to deploy to a custom project.\n`
|
||||
);
|
||||
return { status: 'error', exitCode: 1 };
|
||||
}
|
||||
|
||||
const link =
|
||||
NOW_ORG_ID && NOW_PROJECT_ID
|
||||
? { orgId: NOW_ORG_ID, projectId: NOW_PROJECT_ID }
|
||||
: await getLink(path);
|
||||
|
||||
if (!link) {
|
||||
return { status: 'not_linked', org: null, project: null };
|
||||
}
|
||||
|
||||
const spinner = output.spinner('Retrieving project…', 1000);
|
||||
let org: Org | null;
|
||||
let project: Project | ProjectNotFound | null;
|
||||
try {
|
||||
[org, project] = await Promise.all([
|
||||
getOrgById(client, link.orgId),
|
||||
getProjectByIdOrName(client, link.projectId, link.orgId),
|
||||
]);
|
||||
} finally {
|
||||
spinner();
|
||||
}
|
||||
|
||||
if (!org || !project || project instanceof ProjectNotFound) {
|
||||
if (shouldUseEnv) {
|
||||
output.print(
|
||||
`${chalk.red('Error!')} Project not found (${JSON.stringify({
|
||||
NOW_PROJECT_ID,
|
||||
NOW_ORG_ID,
|
||||
})})\n`
|
||||
);
|
||||
return { status: 'error', exitCode: 1 };
|
||||
} else {
|
||||
output.print(
|
||||
prependEmoji(
|
||||
'Your project was either removed from ZEIT Now or you’re not a member of it anymore.\n',
|
||||
emoji('warning')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return { status: 'not_linked', org: null, project: null };
|
||||
}
|
||||
|
||||
return { status: 'linked', org, project };
|
||||
}
|
||||
|
||||
export async function linkFolderToProject(
|
||||
@@ -105,6 +183,22 @@ export async function linkFolderToProject(
|
||||
projectName: string,
|
||||
orgSlug: string
|
||||
) {
|
||||
// if NOW_ORG_ID or NOW_PROJECT_ID are used, we skip linking
|
||||
const { NOW_ORG_ID, NOW_PROJECT_ID } = process.env;
|
||||
if (NOW_ORG_ID || NOW_PROJECT_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if the project is already linked, we skip linking
|
||||
const link = await getLink(path);
|
||||
if (
|
||||
link &&
|
||||
link.orgId === projectLink.orgId &&
|
||||
link.projectId === projectLink.projectId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await ensureDir(join(path, NOW_FOLDER));
|
||||
} catch (error) {
|
||||
@@ -122,6 +216,12 @@ export async function linkFolderToProject(
|
||||
{ encoding: 'utf8' }
|
||||
);
|
||||
|
||||
await writeFile(
|
||||
join(path, NOW_FOLDER, NOW_FOLDER_README),
|
||||
await readFile(join(__dirname, 'NOW_DIR_README.txt'), 'utf-8'),
|
||||
{ encoding: 'utf-8' }
|
||||
);
|
||||
|
||||
// update .gitignore
|
||||
let isGitIgnoreUpdated = false;
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import chalk from 'chalk';
|
||||
import wait from '../output/wait';
|
||||
import joinWords from '../output/join-words';
|
||||
import * as Errors from '../errors-ts';
|
||||
import { Output } from '../output';
|
||||
@@ -17,7 +16,7 @@ export default async function patchDeploymentScale(
|
||||
scaleArgs: ScaleArgs,
|
||||
url: string
|
||||
) {
|
||||
const cancelWait = wait(
|
||||
const cancelWait = output.spinner(
|
||||
`Setting scale rules for ${joinWords(
|
||||
Object.keys(scaleArgs).map(dc => `${chalk.bold(dc)}`)
|
||||
)}`
|
||||
@@ -28,7 +27,7 @@ export default async function patchDeploymentScale(
|
||||
`/v3/now/deployments/${encodeURIComponent(deploymentId)}/instances`,
|
||||
{
|
||||
method: 'PATCH',
|
||||
body: scaleArgs
|
||||
body: scaleArgs,
|
||||
}
|
||||
);
|
||||
cancelWait();
|
||||
|
||||
@@ -4,7 +4,6 @@ import { Output } from '../output';
|
||||
import * as ERRORS from '../errors-ts';
|
||||
import Client from '../client';
|
||||
import joinWords from '../output/join-words';
|
||||
import wait from '../output/wait';
|
||||
|
||||
type ScaleArgs = {
|
||||
min: number;
|
||||
@@ -18,7 +17,7 @@ export default async function setScale(
|
||||
scaleArgs: ScaleArgs | DeploymentScale,
|
||||
url: string
|
||||
) {
|
||||
const cancelWait = wait(
|
||||
const cancelWait = output.spinner(
|
||||
`Setting scale rules for ${joinWords(
|
||||
Object.keys(scaleArgs).map(dc => `${chalk.bold(dc)}`)
|
||||
)}`
|
||||
@@ -29,7 +28,7 @@ export default async function setScale(
|
||||
`/v3/now/deployments/${encodeURIComponent(deploymentId)}/instances`,
|
||||
{
|
||||
method: 'PUT',
|
||||
body: scaleArgs
|
||||
body: scaleArgs,
|
||||
}
|
||||
);
|
||||
cancelWait();
|
||||
|
||||
@@ -9,7 +9,6 @@ import Client from '../client';
|
||||
import joinWords from '../output/join-words';
|
||||
import stamp from '../output/stamp';
|
||||
import verifyDeploymentScale from './verify-deployment-scale';
|
||||
import wait from '../output/wait';
|
||||
|
||||
export default async function waitForScale(
|
||||
output: Output,
|
||||
@@ -19,7 +18,7 @@ export default async function waitForScale(
|
||||
) {
|
||||
const remainingDCs = new Set(Object.keys(scale));
|
||||
const scaleStamp = stamp();
|
||||
let cancelWait = renderWaitDcs(Array.from(remainingDCs.keys()));
|
||||
let cancelWait = renderWaitDcs(output, Array.from(remainingDCs.keys()));
|
||||
|
||||
for await (const dcReady of verifyDeploymentScale(
|
||||
output,
|
||||
@@ -43,13 +42,13 @@ export default async function waitForScale(
|
||||
}
|
||||
|
||||
if (remainingDCs.size > 0) {
|
||||
cancelWait = renderWaitDcs(Array.from(remainingDCs.keys()));
|
||||
cancelWait = renderWaitDcs(output, Array.from(remainingDCs.keys()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderWaitDcs(dcs: string[]) {
|
||||
return wait(
|
||||
function renderWaitDcs(output: Output, dcs: string[]) {
|
||||
return output.spinner(
|
||||
`Waiting for instances in ${joinWords(
|
||||
dcs.map(dc => chalk.bold(dc))
|
||||
)} to be ready`
|
||||
|
||||
@@ -9,6 +9,48 @@ import toHumanPath from './humanize-path';
|
||||
|
||||
const stat = promisify(lstatRaw);
|
||||
|
||||
/**
|
||||
* A helper function to validate the `rootDirectory` input.
|
||||
*/
|
||||
export async function validateRootDirectory(
|
||||
output: Output,
|
||||
cwd: string,
|
||||
path: string,
|
||||
errorSuffix: string
|
||||
) {
|
||||
const pathStat = await stat(path).catch(() => null);
|
||||
const suffix = errorSuffix ? ` ${errorSuffix}` : '';
|
||||
|
||||
if (!pathStat) {
|
||||
output.print(
|
||||
`${chalk.red('Error!')} The provided path ${chalk.cyan(
|
||||
`“${toHumanPath(path)}”`
|
||||
)} does not exist.${suffix}\n`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!pathStat.isDirectory()) {
|
||||
output.print(
|
||||
`${chalk.red('Error!')} The provided path ${chalk.cyan(
|
||||
`“${toHumanPath(path)}”`
|
||||
)} is a file, but expected a directory.${suffix}\n`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!path.startsWith(cwd)) {
|
||||
output.print(
|
||||
`${chalk.red('Error!')} The provided path ${chalk.cyan(
|
||||
`“${toHumanPath(path)}”`
|
||||
)} is outside of the project.${suffix}\n`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export default async function validatePaths(
|
||||
output: Output,
|
||||
paths: string[]
|
||||
@@ -25,10 +67,7 @@ export default async function validatePaths(
|
||||
const path = paths[0];
|
||||
|
||||
// can only deploy a directory
|
||||
let pathStat;
|
||||
try {
|
||||
pathStat = await stat(path);
|
||||
} catch (error) {}
|
||||
const pathStat = await stat(path).catch(() => null);
|
||||
|
||||
if (!pathStat) {
|
||||
output.print(
|
||||
@@ -43,7 +82,7 @@ export default async function validatePaths(
|
||||
if (isFile) {
|
||||
output.print(
|
||||
`${prependEmoji(
|
||||
'Deploying files with ZEIT Now is deprecated',
|
||||
'Deploying files with ZEIT Now is deprecated (https://zeit.ink/3Z)',
|
||||
emoji('warning')
|
||||
)}\n`
|
||||
);
|
||||
|
||||
1
packages/now-cli/test/dev/fixtures/custom-runtime/.gitignore
vendored
Normal file
1
packages/now-cli/test/dev/fixtures/custom-runtime/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.now
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"functions": {
|
||||
"api/user": {
|
||||
"runtime": "now-bash@1.0.3"
|
||||
"api/user.sh": {
|
||||
"runtime": "now-bash@3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1
packages/now-cli/test/dev/fixtures/python-flask/.gitignore
vendored
Normal file
1
packages/now-cli/test/dev/fixtures/python-flask/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.now
|
||||
@@ -0,0 +1,9 @@
|
||||
from flask import Flask, Response
|
||||
from datetime import datetime
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/', defaults={'path': ''})
|
||||
@app.route('/<path:path>')
|
||||
def date(path):
|
||||
d = datetime.now().isoformat()
|
||||
return Response("Current date is %s" % (d), mimetype='text/html')
|
||||
@@ -0,0 +1,8 @@
|
||||
from flask import Flask, Response, request
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/', defaults={'path': ''})
|
||||
@app.route('/<path:path>')
|
||||
def user(path):
|
||||
name = request.args.get('name')
|
||||
return Response("Hello %s" % (name), mimetype='text/html')
|
||||
@@ -0,0 +1 @@
|
||||
Home Page for Python
|
||||
@@ -0,0 +1 @@
|
||||
Flask==1.0.3
|
||||
@@ -109,6 +109,7 @@ function validateResponseHeaders(t, res) {
|
||||
async function exec(directory, args = []) {
|
||||
return execa(binaryPath, ['dev', directory, ...args], {
|
||||
reject: false,
|
||||
env: { __NOW_SKIP_DEV_COMMAND: 1 },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -161,6 +162,7 @@ async function testFixture(directory, opts = {}, args = []) {
|
||||
detached: true,
|
||||
stdio: 'pipe',
|
||||
...opts,
|
||||
env: { ...opts.env, __NOW_SKIP_DEV_COMMAND: 1 },
|
||||
}
|
||||
);
|
||||
|
||||
@@ -223,7 +225,9 @@ function testFixtureStdio(directory, fn) {
|
||||
let stderr = '';
|
||||
let printedOutput = false;
|
||||
|
||||
dev = execa(binaryPath, ['dev', dir, '-l', port]);
|
||||
dev = execa(binaryPath, ['dev', dir, '-l', port], {
|
||||
env: { __NOW_SKIP_DEV_COMMAND: 1 },
|
||||
});
|
||||
|
||||
dev.stdout.on('data', data => {
|
||||
stdoutList.push(data);
|
||||
@@ -1363,13 +1367,40 @@ test('[now dev] 25-nextjs-src-dir', async t => {
|
||||
});
|
||||
|
||||
test(
|
||||
'[now dev] Use runtime from the functions property',
|
||||
testFixtureStdio('custom-runtime', async (t, port) => {
|
||||
const response = await fetchWithRetry(`http://localhost:${port}/api/user`);
|
||||
'[now dev] Use `@now/python` with Flask requirements.txt',
|
||||
testFixtureStdio('python-flask', async (t, port) => {
|
||||
const name = 'Alice';
|
||||
const year = new Date().getFullYear();
|
||||
const user = await fetchWithRetry(
|
||||
`http://localhost:${port}/api/user?name=${name}`
|
||||
);
|
||||
const date = await fetchWithRetry(`http://localhost:${port}/api/date`);
|
||||
const ext = await fetchWithRetry(`http://localhost:${port}/api/date.py`);
|
||||
|
||||
validateResponseHeaders(t, response);
|
||||
validateResponseHeaders(t, user);
|
||||
validateResponseHeaders(t, date);
|
||||
validateResponseHeaders(t, ext);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Hello, from Bash!/gm);
|
||||
t.regex(await user.text(), new RegExp(`Hello ${name}`));
|
||||
t.regex(await date.text(), new RegExp(`Current date is ${year}`));
|
||||
t.regex(await ext.text(), new RegExp(`Current date is ${year}`));
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] Use runtime from the functions property',
|
||||
testFixtureStdio('custom-runtime', async (t, port) => {
|
||||
const extensionless = await fetchWithRetry(
|
||||
`http://localhost:${port}/api/user`
|
||||
);
|
||||
const extension = await fetchWithRetry(
|
||||
`http://localhost:${port}/api/user.sh`
|
||||
);
|
||||
|
||||
validateResponseHeaders(t, extensionless);
|
||||
validateResponseHeaders(t, extension);
|
||||
|
||||
t.regex(await extensionless.text(), /Hello, from Bash!/gm);
|
||||
t.regex(await extension.text(), /Hello, from Bash!/gm);
|
||||
})
|
||||
);
|
||||
|
||||
@@ -485,6 +485,9 @@ CMD ["node", "index.js"]`,
|
||||
},
|
||||
}),
|
||||
},
|
||||
'project-root-directory': {
|
||||
'src/index.html': '<h1>I am a website.</h1>',
|
||||
},
|
||||
};
|
||||
|
||||
for (const typeName of Object.keys(spec)) {
|
||||
|
||||
503
packages/now-cli/test/integration.js
vendored
503
packages/now-cli/test/integration.js
vendored
@@ -27,12 +27,11 @@ function execa(file, args, options) {
|
||||
return _execa(file, args, options);
|
||||
}
|
||||
|
||||
const str = 'aHR0cHM6Ly9hcGktdG9rZW4tZmFjdG9yeS56ZWl0LnNo';
|
||||
const binaryPath = path.resolve(__dirname, `../scripts/start.js`);
|
||||
const fixture = name => path.join(__dirname, 'fixtures', 'integration', name);
|
||||
const deployHelpMessage = `${logo} now [options] <command | path>`;
|
||||
const session = Math.random()
|
||||
.toString(36)
|
||||
.split('.')[1];
|
||||
let session = 'temp-session';
|
||||
|
||||
const isCanary = pkg.version.includes('canary');
|
||||
|
||||
@@ -176,32 +175,37 @@ const getDeploymentBuildsByUrl = async url => {
|
||||
return builds;
|
||||
};
|
||||
|
||||
const createUser = async () => {
|
||||
await retry(
|
||||
async () => {
|
||||
const location = getConfigPath();
|
||||
const url = Buffer.from(str, 'base64').toString();
|
||||
token = await fetchTokenWithRetry(url);
|
||||
|
||||
if (!fs.existsSync(location)) {
|
||||
await createDirectory(location);
|
||||
}
|
||||
|
||||
await fs.writeJSON(getConfigAuthPath(), { token });
|
||||
|
||||
const user = await fetchTokenInformation(token);
|
||||
|
||||
email = user.email;
|
||||
contextName = user.email.split('@')[0];
|
||||
session = Math.random()
|
||||
.toString(36)
|
||||
.split('.')[1];
|
||||
},
|
||||
{ retries: 3, factor: 1 }
|
||||
);
|
||||
};
|
||||
|
||||
const getConfigPath = () => path.join(tmpDir ? tmpDir.name : homedir(), '.now');
|
||||
const getConfigAuthPath = () => path.join(getConfigPath(), 'auth.json');
|
||||
|
||||
test.before(async () => {
|
||||
try {
|
||||
await retry(
|
||||
async () => {
|
||||
const location = path.join(tmpDir ? tmpDir.name : homedir(), '.now');
|
||||
const str = 'aHR0cHM6Ly9hcGktdG9rZW4tZmFjdG9yeS56ZWl0LnNo';
|
||||
const url = Buffer.from(str, 'base64').toString();
|
||||
token = await fetchTokenWithRetry(url);
|
||||
|
||||
if (!fs.existsSync(location)) {
|
||||
await createDirectory(location);
|
||||
}
|
||||
|
||||
await writeFile(
|
||||
path.join(location, `auth.json`),
|
||||
JSON.stringify({ token })
|
||||
);
|
||||
|
||||
const user = await fetchTokenInformation(token);
|
||||
|
||||
email = user.email;
|
||||
contextName = user.email.split('@')[0];
|
||||
},
|
||||
{ retries: 3, factor: 1 }
|
||||
);
|
||||
|
||||
await createUser();
|
||||
await prepareFixtures(contextName);
|
||||
} catch (err) {
|
||||
console.log('Failed `test.before`');
|
||||
@@ -209,6 +213,18 @@ test.before(async () => {
|
||||
}
|
||||
});
|
||||
|
||||
test.after.always(async () => {
|
||||
// Make sure the token gets revoked
|
||||
await execa(binaryPath, ['logout', ...defaultArgs]);
|
||||
|
||||
if (!tmpDir) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove config directory entirely
|
||||
tmpDir.removeCallback();
|
||||
});
|
||||
|
||||
test('login', async t => {
|
||||
// Delete the current token
|
||||
const logoutOutput = await execute(['logout']);
|
||||
@@ -228,8 +244,7 @@ test('login', async t => {
|
||||
);
|
||||
|
||||
// Save the new token
|
||||
const location = path.join(tmpDir ? tmpDir.name : homedir(), '.now');
|
||||
const auth = JSON.parse(await readFile(path.join(location, 'auth.json')));
|
||||
const auth = await fs.readJSON(getConfigAuthPath());
|
||||
|
||||
token = auth.token;
|
||||
|
||||
@@ -1174,42 +1189,6 @@ test('create a builds deployments without platform version flag', async t => {
|
||||
t.is(contentType, 'text/html; charset=utf-8');
|
||||
});
|
||||
|
||||
test('deploy multiple static files', async t => {
|
||||
const directory = fixture('static-multiple-files');
|
||||
|
||||
const { stdout, stderr, exitCode } = await execa(
|
||||
binaryPath,
|
||||
[directory, '--public', '--name', session, ...defaultArgs],
|
||||
{
|
||||
reject: false,
|
||||
}
|
||||
);
|
||||
|
||||
console.log(stderr);
|
||||
console.log(stdout);
|
||||
console.log(exitCode);
|
||||
|
||||
// Ensure the exit code is right
|
||||
t.is(exitCode, 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, {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
const contentType = response.headers.get('content-type');
|
||||
t.is(contentType, 'application/json; charset=utf-8');
|
||||
|
||||
const content = await response.json();
|
||||
t.is(content.files.length, 3);
|
||||
});
|
||||
|
||||
test('create a staging deployment', async t => {
|
||||
const directory = fixture('static-deployment');
|
||||
|
||||
@@ -1297,102 +1276,6 @@ test('create a production deployment', async t => {
|
||||
t.is(deployment.target, 'production', JSON.stringify(deployment, null, 2));
|
||||
});
|
||||
|
||||
test('ensure we are getting a warning for the old team flag', async t => {
|
||||
const directory = fixture('static-multiple-files');
|
||||
|
||||
const { stderr, stdout, exitCode } = await execa(
|
||||
binaryPath,
|
||||
[
|
||||
directory,
|
||||
'--public',
|
||||
'--name',
|
||||
session,
|
||||
'--team',
|
||||
email,
|
||||
...defaultArgs,
|
||||
'--confirm',
|
||||
],
|
||||
{
|
||||
reject: false,
|
||||
}
|
||||
);
|
||||
|
||||
console.log(stderr);
|
||||
console.log(stdout);
|
||||
console.log(exitCode);
|
||||
|
||||
// Ensure the warning is printed
|
||||
t.true(
|
||||
stderr.includes(
|
||||
'WARN! The "--team" flag is deprecated. Please use "--scope" instead.'
|
||||
)
|
||||
);
|
||||
|
||||
// Ensure the exit code is right
|
||||
t.is(exitCode, 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, {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
const contentType = response.headers.get('content-type');
|
||||
t.is(contentType, 'application/json; charset=utf-8');
|
||||
|
||||
const content = await response.json();
|
||||
t.is(content.files.length, 3);
|
||||
});
|
||||
|
||||
test('deploy multiple static files with custom scope', async t => {
|
||||
const directory = fixture('static-multiple-files');
|
||||
|
||||
const { stdout, stderr, exitCode } = await execa(
|
||||
binaryPath,
|
||||
[
|
||||
directory,
|
||||
'--public',
|
||||
'--name',
|
||||
session,
|
||||
'--scope',
|
||||
email,
|
||||
...defaultArgs,
|
||||
],
|
||||
{
|
||||
reject: false,
|
||||
}
|
||||
);
|
||||
|
||||
console.log(stderr);
|
||||
console.log(stdout);
|
||||
console.log(exitCode);
|
||||
|
||||
// Ensure the exit code is right
|
||||
t.is(exitCode, 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, {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
const contentType = response.headers.get('content-type');
|
||||
t.is(contentType, 'application/json; charset=utf-8');
|
||||
|
||||
const content = await response.json();
|
||||
t.is(content.files.length, 3);
|
||||
});
|
||||
|
||||
test('deploying a file should not show prompts and display deprecation', async t => {
|
||||
const file = fixture('static-single-file/first.png');
|
||||
|
||||
@@ -1446,35 +1329,6 @@ test('deploying more than 1 path should fail', async t => {
|
||||
t.true(stderr.trim().endsWith(`Can't deploy more than one path.`));
|
||||
});
|
||||
|
||||
test('deploy a static directory', async t => {
|
||||
const directory = fixture('static-single-file');
|
||||
|
||||
const { stdout, stderr, exitCode } = await execa(
|
||||
binaryPath,
|
||||
[directory, '--public', '--name', session, ...defaultArgs, '--confirm'],
|
||||
{
|
||||
reject: false,
|
||||
}
|
||||
);
|
||||
|
||||
console.log(stderr);
|
||||
console.log(stdout);
|
||||
console.log(exitCode);
|
||||
|
||||
// Ensure the exit code is right
|
||||
t.is(exitCode, 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('use build-env', async t => {
|
||||
const directory = fixture('build-env');
|
||||
|
||||
@@ -2077,44 +1931,6 @@ test('now secret rm', async t => {
|
||||
t.is(output.exitCode, 0, formatOutput(output));
|
||||
});
|
||||
|
||||
test('deploy with a custom API URL', async t => {
|
||||
const directory = fixture('static-single-file');
|
||||
|
||||
const { stdout, stderr, exitCode } = await execa(
|
||||
binaryPath,
|
||||
[
|
||||
directory,
|
||||
'--public',
|
||||
'--name',
|
||||
session,
|
||||
'--api',
|
||||
'https://zeit.co/api',
|
||||
...defaultArgs,
|
||||
'--confirm',
|
||||
],
|
||||
{
|
||||
reject: false,
|
||||
}
|
||||
);
|
||||
|
||||
console.log(stderr);
|
||||
console.log(stdout);
|
||||
console.log(exitCode);
|
||||
|
||||
// Ensure the exit code is right
|
||||
t.is(exitCode, 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('deploy a Lambda with 128MB of memory', async t => {
|
||||
const directory = fixture('lambda-with-128-memory');
|
||||
const output = await execute([directory, '--confirm']);
|
||||
@@ -2216,6 +2032,32 @@ test('ensure `github` and `scope` are not sent to the API', async t => {
|
||||
t.is(output.exitCode, 0, formatOutput(output));
|
||||
});
|
||||
|
||||
test('change user', async t => {
|
||||
const { stdout: prevUser } = await execute(['whoami']);
|
||||
|
||||
// Delete the current token
|
||||
const logoutOutput = await execute(['logout']);
|
||||
t.is(logoutOutput.exitCode, 0, formatOutput(logoutOutput));
|
||||
|
||||
await createUser();
|
||||
|
||||
const loginOutput = await execute(['login', email]);
|
||||
t.is(loginOutput.exitCode, 0, formatOutput(loginOutput));
|
||||
|
||||
const auth = await fs.readJSON(getConfigAuthPath());
|
||||
|
||||
token = auth.token;
|
||||
|
||||
const { stdout: nextUser } = await execute(['whoami']);
|
||||
|
||||
console.log('prev user', prevUser);
|
||||
console.log('next user', nextUser);
|
||||
|
||||
t.is(typeof prevUser, 'string', prevUser);
|
||||
t.is(typeof nextUser, 'string', nextUser);
|
||||
t.not(prevUser, nextUser, JSON.stringify({ prevUser, nextUser }));
|
||||
});
|
||||
|
||||
test('should show prompts to set up project', async t => {
|
||||
const directory = fixture('project-link');
|
||||
const projectName = `project-link-${
|
||||
@@ -2247,6 +2089,11 @@ test('should show prompts to set up project', async t => {
|
||||
);
|
||||
now.stdin.write(`${projectName}\n`);
|
||||
|
||||
await waitForPrompt(now, chunk =>
|
||||
chunk.includes('In which directory is your code located?')
|
||||
);
|
||||
now.stdin.write('\n');
|
||||
|
||||
await waitForPrompt(now, chunk =>
|
||||
chunk.includes('Want to override the settings?')
|
||||
);
|
||||
@@ -2284,55 +2131,24 @@ test('should show prompts to set up project', async t => {
|
||||
// Ensure .gitignore is created
|
||||
t.is((await readFile(path.join(directory, '.gitignore'))).toString(), '.now');
|
||||
|
||||
// Ensure .now/project.json and .now/README.txt are created
|
||||
t.is(
|
||||
await exists(path.join(directory, '.now', 'project.json')),
|
||||
true,
|
||||
'project.json should be created'
|
||||
);
|
||||
t.is(
|
||||
await exists(path.join(directory, '.now', 'README.txt')),
|
||||
true,
|
||||
'README.txt should be created'
|
||||
);
|
||||
|
||||
// Send a test request to the deployment
|
||||
const response = await fetch(new URL(output.stdout).href);
|
||||
const text = await response.text();
|
||||
t.is(text.includes('<h1>custom hello</h1>'), true, text);
|
||||
});
|
||||
|
||||
test('should not prompt "project settings overwrite" for undetected projects', async t => {
|
||||
const directory = fixture('static-deployment');
|
||||
const projectName = `static-deployment-${
|
||||
Math.random()
|
||||
.toString(36)
|
||||
.split('.')[1]
|
||||
}`;
|
||||
|
||||
// remove previously linked project if it exists
|
||||
await remove(path.join(directory, '.now'));
|
||||
|
||||
const now = execa(binaryPath, [directory, ...defaultArgs]);
|
||||
|
||||
await waitForPrompt(now, chunk => /Set up and deploy [^?]+\?/.test(chunk));
|
||||
now.stdin.write('yes\n');
|
||||
|
||||
await waitForPrompt(now, chunk =>
|
||||
chunk.includes('Which scope do you want to deploy to?')
|
||||
);
|
||||
now.stdin.write('\n');
|
||||
|
||||
await waitForPrompt(now, chunk =>
|
||||
chunk.includes('Link to existing project?')
|
||||
);
|
||||
now.stdin.write('no\n');
|
||||
|
||||
await waitForPrompt(now, chunk =>
|
||||
chunk.includes('What’s your project’s name?')
|
||||
);
|
||||
now.stdin.write(`${projectName}\n`);
|
||||
|
||||
await waitForPrompt(now, chunk => {
|
||||
t.false(
|
||||
chunk.includes('Want to override the settings?'),
|
||||
'Should not ask to override'
|
||||
);
|
||||
return chunk.includes('Linked to');
|
||||
});
|
||||
|
||||
const output = await now;
|
||||
t.is(output.exitCode, 0, formatOutput(output));
|
||||
});
|
||||
|
||||
test('should prefill "project name" prompt with folder name', async t => {
|
||||
const projectName = `static-deployment-${
|
||||
Math.random()
|
||||
@@ -2368,6 +2184,16 @@ test('should prefill "project name" prompt with folder name', async t => {
|
||||
);
|
||||
now.stdin.write(`\n`);
|
||||
|
||||
await waitForPrompt(now, chunk =>
|
||||
chunk.includes('In which directory is your code located?')
|
||||
);
|
||||
now.stdin.write('\n');
|
||||
|
||||
await waitForPrompt(now, chunk =>
|
||||
chunk.includes('Want to override the settings?')
|
||||
);
|
||||
now.stdin.write('no\n');
|
||||
|
||||
const output = await now;
|
||||
t.is(output.exitCode, 0, formatOutput(output));
|
||||
});
|
||||
@@ -2390,9 +2216,19 @@ test('should prefill "project name" prompt with --name', async t => {
|
||||
...defaultArgs,
|
||||
]);
|
||||
|
||||
await waitForPrompt(now, chunk => /Set up and deploy [^?]+\?/.test(chunk));
|
||||
let isDeprecated = false;
|
||||
|
||||
await waitForPrompt(now, chunk => {
|
||||
if (chunk.includes('The "--name" flag is deprecated')) {
|
||||
isDeprecated = true;
|
||||
}
|
||||
|
||||
return /Set up and deploy [^?]+\?/.test(chunk);
|
||||
});
|
||||
now.stdin.write('yes\n');
|
||||
|
||||
t.is(isDeprecated, true);
|
||||
|
||||
await waitForPrompt(now, chunk =>
|
||||
chunk.includes('Which scope do you want to deploy to?')
|
||||
);
|
||||
@@ -2408,6 +2244,16 @@ test('should prefill "project name" prompt with --name', async t => {
|
||||
);
|
||||
now.stdin.write(`\n`);
|
||||
|
||||
await waitForPrompt(now, chunk =>
|
||||
chunk.includes('In which directory is your code located?')
|
||||
);
|
||||
now.stdin.write('\n');
|
||||
|
||||
await waitForPrompt(now, chunk =>
|
||||
chunk.includes('Want to override the settings?')
|
||||
);
|
||||
now.stdin.write('no\n');
|
||||
|
||||
const output = await now;
|
||||
t.is(output.exitCode, 0, formatOutput(output));
|
||||
});
|
||||
@@ -2431,9 +2277,19 @@ test('should prefill "project name" prompt with now.json `name`', async t => {
|
||||
|
||||
const now = execa(binaryPath, [directory, ...defaultArgs]);
|
||||
|
||||
await waitForPrompt(now, chunk => /Set up and deploy [^?]+\?/.test(chunk));
|
||||
let isDeprecated = false;
|
||||
|
||||
await waitForPrompt(now, chunk => {
|
||||
if (chunk.includes('The `name` property in now.json is deprecated')) {
|
||||
isDeprecated = true;
|
||||
}
|
||||
|
||||
return /Set up and deploy [^?]+\?/.test(chunk);
|
||||
});
|
||||
now.stdin.write('yes\n');
|
||||
|
||||
t.is(isDeprecated, true);
|
||||
|
||||
await waitForPrompt(now, chunk =>
|
||||
chunk.includes('Which scope do you want to deploy to?')
|
||||
);
|
||||
@@ -2449,6 +2305,16 @@ test('should prefill "project name" prompt with now.json `name`', async t => {
|
||||
);
|
||||
now.stdin.write(`\n`);
|
||||
|
||||
await waitForPrompt(now, chunk =>
|
||||
chunk.includes('In which directory is your code located?')
|
||||
);
|
||||
now.stdin.write('\n');
|
||||
|
||||
await waitForPrompt(now, chunk =>
|
||||
chunk.includes('Want to override the settings?')
|
||||
);
|
||||
now.stdin.write('no\n');
|
||||
|
||||
const output = await now;
|
||||
t.is(output.exitCode, 0, formatOutput(output));
|
||||
|
||||
@@ -2456,12 +2322,13 @@ test('should prefill "project name" prompt with now.json `name`', async t => {
|
||||
await remove(path.join(directory, 'now.json'));
|
||||
});
|
||||
|
||||
test('deploy with unknown `NOW_ORG_ID` and `NOW_PROJECT_ID` should fail', async t => {
|
||||
test('deploy with unknown `NOW_PROJECT_ID` should fail', async t => {
|
||||
const directory = fixture('static-deployment');
|
||||
const user = await fetchTokenInformation(token);
|
||||
|
||||
const output = await execute([directory], {
|
||||
env: {
|
||||
NOW_ORG_ID: 'asdf',
|
||||
NOW_ORG_ID: user.uid,
|
||||
NOW_PROJECT_ID: 'asdf',
|
||||
},
|
||||
});
|
||||
@@ -2472,9 +2339,10 @@ test('deploy with unknown `NOW_ORG_ID` and `NOW_PROJECT_ID` should fail', async
|
||||
|
||||
test('deploy with `NOW_ORG_ID` but without `NOW_PROJECT_ID` should fail', async t => {
|
||||
const directory = fixture('static-deployment');
|
||||
const user = await fetchTokenInformation(token);
|
||||
|
||||
const output = await execute([directory], {
|
||||
env: { NOW_ORG_ID: 'asdf' },
|
||||
env: { NOW_ORG_ID: user.uid },
|
||||
});
|
||||
|
||||
t.is(output.exitCode, 1, formatOutput(output));
|
||||
@@ -2556,14 +2424,81 @@ test('deploy shows notice when project in `.now` does not exists', async t => {
|
||||
t.is(detectedNotice, true, 'did not detect notice');
|
||||
});
|
||||
|
||||
test.after.always(async () => {
|
||||
// Make sure the token gets revoked
|
||||
await execa(binaryPath, ['logout', ...defaultArgs]);
|
||||
test('use `rootDirectory` from project when deploying', async t => {
|
||||
const directory = fixture('project-root-directory');
|
||||
|
||||
if (!tmpDir) {
|
||||
return;
|
||||
}
|
||||
const firstResult = await execute([directory, '--confirm', '--public']);
|
||||
t.is(firstResult.exitCode, 0, formatOutput(firstResult));
|
||||
|
||||
// Remove config directory entirely
|
||||
tmpDir.removeCallback();
|
||||
const { host: firstHost } = new URL(firstResult.stdout);
|
||||
const response = await apiFetch(`/v12/now/deployments/get?url=${firstHost}`);
|
||||
t.is(response.status, 200);
|
||||
const { projectId } = await response.json();
|
||||
t.is(typeof projectId, 'string', projectId);
|
||||
|
||||
const projectResponse = await apiFetch(`/v2/projects/${projectId}`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({
|
||||
rootDirectory: 'src',
|
||||
}),
|
||||
});
|
||||
console.log('response', await projectResponse.text());
|
||||
t.is(projectResponse.status, 200);
|
||||
|
||||
const secondResult = await execute([directory, '--public']);
|
||||
t.is(secondResult.exitCode, 0, formatOutput(secondResult));
|
||||
|
||||
const pageResponse = await fetch(secondResult.stdout);
|
||||
t.is(pageResponse.status, 200);
|
||||
t.regex(await pageResponse.text(), /I am a website/gm);
|
||||
|
||||
await apiFetch(`/v2/projects/${projectId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
});
|
||||
|
||||
test('whoami with unknown `NOW_ORG_ID` should error', async t => {
|
||||
const output = await execute(['whoami'], {
|
||||
env: { NOW_ORG_ID: 'asdf' },
|
||||
});
|
||||
|
||||
t.is(output.exitCode, 1, formatOutput(output));
|
||||
t.is(
|
||||
output.stderr.includes('Organization not found'),
|
||||
true,
|
||||
formatOutput(output)
|
||||
);
|
||||
});
|
||||
|
||||
test('whoami with `NOW_ORG_ID`', async t => {
|
||||
const user = await fetchTokenInformation(token);
|
||||
|
||||
const output = await execute(['whoami', '--scope', 'asdf'], {
|
||||
env: { NOW_ORG_ID: user.uid },
|
||||
});
|
||||
|
||||
t.is(output.exitCode, 0, formatOutput(output));
|
||||
t.is(output.stdout.includes(contextName), true, formatOutput(output));
|
||||
});
|
||||
|
||||
test('whoami with local .now scope', async t => {
|
||||
const directory = fixture('static-deployment');
|
||||
const user = await fetchTokenInformation(token);
|
||||
|
||||
// create local .now
|
||||
await ensureDir(path.join(directory, '.now'));
|
||||
await fs.writeFile(
|
||||
path.join(directory, '.now', 'project.json'),
|
||||
JSON.stringify({ orgId: user.uid })
|
||||
);
|
||||
|
||||
const output = await execute(['whoami'], {
|
||||
cwd: directory,
|
||||
});
|
||||
|
||||
t.is(output.exitCode, 0, formatOutput(output));
|
||||
t.is(output.stdout.includes(contextName), true, formatOutput(output));
|
||||
|
||||
// clean up
|
||||
await remove(path.join(directory, '.now'));
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"noEmitOnError": true,
|
||||
"moduleResolution": "node",
|
||||
"module": "CommonJS",
|
||||
"target": "ES2018",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "now-client",
|
||||
"version": "7.0.0-canary.2",
|
||||
"version": "7.0.0",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"homepage": "https://zeit.co",
|
||||
@@ -24,7 +24,7 @@
|
||||
"@types/jest": "24.0.16",
|
||||
"@types/ms": "0.7.30",
|
||||
"@types/node": "12.0.4",
|
||||
"@types/node-fetch": "2.3.4",
|
||||
"@types/node-fetch": "2.5.4",
|
||||
"@types/recursive-readdir": "2.2.0",
|
||||
"@zeit/ncc": "0.18.5",
|
||||
"typescript": "3.5.1"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DeploymentFile } from './hashes';
|
||||
import { parse as parseUrl } from 'url';
|
||||
import { RequestInit } from 'node-fetch';
|
||||
import { FetchOptions } from '@zeit/fetch';
|
||||
import { nodeFetch, zeitFetch } from './fetch';
|
||||
import { join, sep } from 'path';
|
||||
import qs from 'querystring';
|
||||
@@ -115,7 +115,7 @@ export async function getNowIgnore(path: string | string[]): Promise<any> {
|
||||
return { ig, ignores };
|
||||
}
|
||||
|
||||
interface FetchOpts extends RequestInit {
|
||||
interface FetchOpts extends FetchOptions {
|
||||
apiUrl?: string;
|
||||
method?: string;
|
||||
teamId?: string;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"moduleResolution": "node",
|
||||
"outDir": "dist",
|
||||
"strictNullChecks": true,
|
||||
"noEmitOnError": true,
|
||||
"noImplicitAny": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
|
||||
@@ -12,7 +12,8 @@ const platformMap = new Map([['win32', 'windows']]);
|
||||
|
||||
// Location where the `go` binary will be installed after `postinstall`
|
||||
const GO_DIR = join(__dirname, 'go');
|
||||
const GO_BIN = join(GO_DIR, 'bin/go');
|
||||
const GO_BIN = join(GO_DIR, 'bin', 'go');
|
||||
const GO_FLAGS = process.platform === 'win32' ? [] : ['-ldflags', '-s -w'];
|
||||
|
||||
const getPlatform = (p: string) => platformMap.get(p) || p;
|
||||
const getArch = (a: string) => archMap.get(a) || a;
|
||||
@@ -23,16 +24,17 @@ const getGoUrl = (version: string, platform: string, arch: string) => {
|
||||
return `https://dl.google.com/go/go${version}.${goPlatform}-${goArch}.${ext}`;
|
||||
};
|
||||
|
||||
export const OUT_EXTENSION = process.platform === 'win32' ? '.exe' : '';
|
||||
|
||||
export async function getAnalyzedEntrypoint(filePath: string, modulePath = '') {
|
||||
debug('Analyzing entrypoint %o', filePath);
|
||||
const bin = join(__dirname, 'analyze');
|
||||
const bin = join(__dirname, `analyze${OUT_EXTENSION}`);
|
||||
|
||||
const isAnalyzeExist = await pathExists(bin);
|
||||
if (!isAnalyzeExist) {
|
||||
const src = join(__dirname, 'util', 'analyze.go');
|
||||
const dest = join(__dirname, 'analyze');
|
||||
const go = await downloadGo();
|
||||
await go.build(src, dest);
|
||||
await go.build(src, bin);
|
||||
}
|
||||
|
||||
const args = [`-modpath=${modulePath}`, filePath];
|
||||
@@ -49,7 +51,7 @@ function createGoPathTree(goPath: string, platform: string, arch: string) {
|
||||
debug('Creating GOPATH directory structure for %o (%s)', goPath, tuple);
|
||||
return Promise.all([
|
||||
mkdirp(join(goPath, 'bin')),
|
||||
mkdirp(join(goPath, 'pkg', tuple))
|
||||
mkdirp(join(goPath, 'pkg', tuple)),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -92,7 +94,7 @@ class GoWrapper {
|
||||
|
||||
const flags = process.env.GO_BUILD_FLAGS
|
||||
? stringArgv(process.env.GO_BUILD_FLAGS)
|
||||
: ['-ldflags', '-s -w'];
|
||||
: GO_FLAGS;
|
||||
|
||||
return this.execute('build', ...flags, '-o', dest, ...sources);
|
||||
}
|
||||
@@ -110,7 +112,7 @@ export async function createGo(
|
||||
...process.env,
|
||||
PATH: path,
|
||||
GOPATH: goPath,
|
||||
...opts.env
|
||||
...opts.env,
|
||||
};
|
||||
if (goMod) {
|
||||
env.GO111MODULE = 'on';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { join, sep, dirname, basename } from 'path';
|
||||
import { join, sep, dirname, basename, normalize } from 'path';
|
||||
import { readFile, writeFile, pathExists, move } from 'fs-extra';
|
||||
import { homedir } from 'os';
|
||||
import execa from 'execa';
|
||||
@@ -14,7 +14,8 @@ import {
|
||||
debug,
|
||||
} from '@now/build-utils';
|
||||
|
||||
import { createGo, getAnalyzedEntrypoint } from './go-helpers';
|
||||
import { createGo, getAnalyzedEntrypoint, OUT_EXTENSION } from './go-helpers';
|
||||
const handlerFileName = `handler${OUT_EXTENSION}`;
|
||||
|
||||
interface Analyzed {
|
||||
found?: boolean;
|
||||
@@ -321,7 +322,7 @@ Learn more: https://zeit.co/docs/v2/advanced/builders/#go
|
||||
}
|
||||
|
||||
debug('Running `go build`...');
|
||||
const destPath = join(outDir, 'handler');
|
||||
const destPath = join(outDir, handlerFileName);
|
||||
|
||||
try {
|
||||
const src = [join(baseGoModPath, mainModGoFileName)];
|
||||
@@ -385,12 +386,12 @@ Learn more: https://zeit.co/docs/v2/advanced/builders/#go
|
||||
}
|
||||
|
||||
debug('Running `go build`...');
|
||||
const destPath = join(outDir, 'handler');
|
||||
const destPath = join(outDir, handlerFileName);
|
||||
try {
|
||||
const src = [
|
||||
join(entrypointDirname, mainGoFileName),
|
||||
downloadedFiles[entrypoint].fsPath,
|
||||
];
|
||||
].map(file => normalize(file));
|
||||
await go.build(src, destPath);
|
||||
} catch (err) {
|
||||
console.log('failed to `go build`');
|
||||
@@ -400,7 +401,7 @@ Learn more: https://zeit.co/docs/v2/advanced/builders/#go
|
||||
|
||||
const lambda = await createLambda({
|
||||
files: { ...(await glob('**', outDir)), ...includedFiles },
|
||||
handler: 'handler',
|
||||
handler: handlerFileName,
|
||||
runtime: 'go1.x',
|
||||
environment: {},
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/go",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.3",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/go",
|
||||
|
||||
14
packages/now-go/test/test.js
vendored
14
packages/now-go/test/test.js
vendored
@@ -3,14 +3,24 @@ const path = require('path');
|
||||
|
||||
const {
|
||||
packAndDeploy,
|
||||
testDeployment
|
||||
testDeployment,
|
||||
} = require('../../../test/lib/deployment/test-deployment.js');
|
||||
|
||||
jest.setTimeout(4 * 60 * 1000);
|
||||
const buildUtilsUrl = '@canary';
|
||||
let buildUtilsUrl;
|
||||
let builderUrl;
|
||||
|
||||
beforeAll(async () => {
|
||||
if (!buildUtilsUrl) {
|
||||
const buildUtilsPath = path.resolve(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'now-build-utils'
|
||||
);
|
||||
buildUtilsUrl = await packAndDeploy(buildUtilsPath);
|
||||
console.log('buildUtilsUrl', buildUtilsUrl);
|
||||
}
|
||||
const builderPath = path.resolve(__dirname, '..');
|
||||
builderUrl = await packAndDeploy(builderPath);
|
||||
console.log('builderUrl', builderUrl);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/next",
|
||||
"version": "2.3.12",
|
||||
"version": "2.3.13",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/next-js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/node",
|
||||
"version": "1.3.6-canary.0",
|
||||
"version": "1.4.1",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/node-js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/python",
|
||||
"version": "1.1.1-canary.0",
|
||||
"version": "1.1.3",
|
||||
"main": "./dist/index.js",
|
||||
"license": "MIT",
|
||||
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/python",
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
getWriteableDirectory,
|
||||
download,
|
||||
glob,
|
||||
GlobOptions,
|
||||
createLambda,
|
||||
shouldServe,
|
||||
BuildOptions,
|
||||
@@ -174,8 +175,16 @@ export const build = async ({
|
||||
// Use the system-installed version of `python3` when running via `now dev`
|
||||
const runtime = meta.isDev ? 'python3' : 'python3.6';
|
||||
|
||||
const globOptions: GlobOptions = {
|
||||
cwd: workPath,
|
||||
ignore:
|
||||
config && typeof config.excludeFiles === 'string'
|
||||
? config.excludeFiles
|
||||
: 'node_modules/**',
|
||||
};
|
||||
|
||||
const lambda = await createLambda({
|
||||
files: await glob('**', workPath),
|
||||
files: await glob('**', globOptions),
|
||||
handler: `${nowHandlerPyFilename}.now_handler`,
|
||||
runtime,
|
||||
environment: {},
|
||||
|
||||
@@ -2,13 +2,24 @@ import execa from 'execa';
|
||||
import { debug, Meta } from '@now/build-utils';
|
||||
const pipPath = 'pip3';
|
||||
|
||||
const makeDependencyCheckCode = (dependency: string) => `
|
||||
from importlib import util
|
||||
dep = '${dependency}'.replace('-', '_')
|
||||
spec = util.find_spec(dep)
|
||||
print(spec.origin)
|
||||
`;
|
||||
|
||||
async function isInstalled(dependency: string, cwd: string) {
|
||||
try {
|
||||
await execa('python3', ['-c', `"import ${dependency}"`], {
|
||||
stdio: 'pipe',
|
||||
cwd,
|
||||
});
|
||||
return true;
|
||||
const { stdout } = await execa(
|
||||
'python3',
|
||||
['-c', makeDependencyCheckCode(dependency)],
|
||||
{
|
||||
stdio: 'pipe',
|
||||
cwd,
|
||||
}
|
||||
);
|
||||
return stdout.startsWith(cwd);
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
@@ -92,8 +103,10 @@ export async function installRequirement({
|
||||
meta,
|
||||
args = [],
|
||||
}: InstallRequirementArg) {
|
||||
if (meta.isDev || !(await isInstalled(dependency, workPath))) {
|
||||
debug(`Skipping ${dependency} dependency installation, already installed`);
|
||||
if (meta.isDev && (await isInstalled(dependency, workPath))) {
|
||||
debug(
|
||||
`Skipping ${dependency} dependency installation, already installed in ${workPath}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
await pipInstall(workPath, [dependency, ...args]);
|
||||
|
||||
15
packages/now-python/test/fixtures/25-exclude-files/api/no.py
vendored
Normal file
15
packages/now-python/test/fixtures/25-exclude-files/api/no.py
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
from http.server import BaseHTTPRequestHandler
|
||||
from cowpy import cow
|
||||
from os import path
|
||||
|
||||
class handler(BaseHTTPRequestHandler):
|
||||
|
||||
def do_GET(self):
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/plain')
|
||||
self.end_headers()
|
||||
first = path.exists('ignoreme/first.txt')
|
||||
second = path.exists('ignoreme/second.txt')
|
||||
message = cow.Cowacter().milk(('%s:%s:RANDOMNESS_PLACEHOLDER') % (first, second))
|
||||
self.wfile.write(message.encode())
|
||||
return
|
||||
15
packages/now-python/test/fixtures/25-exclude-files/api/yes.py
vendored
Normal file
15
packages/now-python/test/fixtures/25-exclude-files/api/yes.py
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
from http.server import BaseHTTPRequestHandler
|
||||
from cowpy import cow
|
||||
from os import path
|
||||
|
||||
class handler(BaseHTTPRequestHandler):
|
||||
|
||||
def do_GET(self):
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/plain')
|
||||
self.end_headers()
|
||||
first = path.exists('ignoreme/first.txt')
|
||||
second = path.exists('ignoreme/second.txt')
|
||||
message = cow.Cowacter().milk(('%s:%s:RANDOMNESS_PLACEHOLDER') % (first, second))
|
||||
self.wfile.write(message.encode())
|
||||
return
|
||||
1
packages/now-python/test/fixtures/25-exclude-files/ignoreme/first.txt
vendored
Normal file
1
packages/now-python/test/fixtures/25-exclude-files/ignoreme/first.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
First file to ignore
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user