diff --git a/bin/now-deploy.js b/bin/now-deploy.js index 281b232..335124a 100755 --- a/bin/now-deploy.js +++ b/bin/now-deploy.js @@ -12,6 +12,7 @@ const minimist = require('minimist') const ms = require('ms') const flatten = require('arr-flatten') const dotenv = require('dotenv') +const retry = require('async-retry') const { eraseLines } = require('ansi-escapes') const { write: copy } = require('clipboardy') @@ -167,6 +168,7 @@ const gitRepo = {} // Options let forceNew = argv.force +let deploymentName = argv.name const debug = argv.debug const clipboard = !argv['no-clipboard'] const forwardNpm = argv['forward-npm'] @@ -174,7 +176,6 @@ const forceSync = argv.forceSync const shouldLogin = argv.login const followSymlinks = !argv.links const wantsPublic = argv.public -const deploymentName = argv.name || false const apiUrl = argv.url || 'https://api.zeit.co' const isTTY = process.stdout.isTTY const quiet = !isTTY @@ -191,51 +192,53 @@ if (Array.isArray(autoAliases)) { console.log('Read more about the new way here: http://bit.ly/2l2v5Fg\n') } -// Create a new deployment if user changed -// the name or made _src public. -// This should just work fine because it doesn't -// force a new sync, it just forces a new deployment. +const stopDeployment = msg => { + error(msg) + process.exit(1) +} + +// Create a new deployment if user changed the name or made `_src` public. +// This works fine because it doesn't force a new sync, +// it just forces a new deployment. if (deploymentName || wantsPublic) { forceNew = true } let alwaysForwardNpm -Promise.resolve().then(async () => { +async function main() { let config = await cfg.read({ token: argv.token }) alwaysForwardNpm = config.forwardNpm if (argv.h || argv.help) { help() - exit(0) + return exit(0) } else if (argv.v || argv.version) { console.log(version) - process.exit(0) - } else if (!config.token || shouldLogin) { - let token + return exit(0) + } + + let token = argv.token || config.token + if (!token || shouldLogin) { try { token = await login(apiUrl) config = await cfg.read() } catch (err) { - error(`Authentication error – ${err.message}`) - process.exit(1) + return stopDeployment(`Authentication error – ${err.message}`) } if (shouldLogin) { console.log('> Logged in successfully. Token saved in ~/.now.json') - process.exit(0) - } else { - sync({ token, config }).catch(err => { - error(`Unknown error: ${err}\n${err.stack}`) - process.exit(1) - }) + return exit(0) } - } else { - sync({ token: argv.token || config.token, config }).catch(err => { - error(`Unknown error: ${err}\n${err.stack}`) - process.exit(1) - }) } -}) + + // If we got to here then `token` should be set + try { + await sync({ token, config }) + } catch (err) { + return stopDeployment(`Unknown error: ${err}\n${err.stack}`) + } +} async function sync({ token, config: { currentTeam, user } }) { const start = Date.now() @@ -248,19 +251,23 @@ async function sync({ token, config: { currentTeam, user } }) { currentTeam }).getCurrent() - const stopDeployment = msg => { - error(msg) - process.exit(1) - } - - const isValidRepo = isRepoPath(rawPath) - try { await fs.stat(path) } catch (err) { let repo + let isValidRepo = false + + try { + isValidRepo = isRepoPath(rawPath) + } catch (err) { + if (err.code === 'INVALID_URL') { + stopDeployment(err) + } else { + throw err + } + } - if (isValidRepo && isValidRepo !== 'no-valid-url') { + if (isValidRepo) { const gitParts = gitPathParts(rawPath) Object.assign(gitRepo, gitParts) @@ -282,8 +289,6 @@ async function sync({ token, config: { currentTeam, user } }) { // Set global variable for deleting tmp dir later // once the deployment has finished Object.assign(gitRepo, repo) - } else if (isValidRepo === 'no-valid-url') { - stopDeployment(`This URL is neither a valid repository from GitHub, nor from GitLab.`) } else if (isValidRepo) { const gitRef = gitRepo.ref ? `with "${chalk.bold(gitRepo.ref)}" ` : '' stopDeployment(`There's no repository named "${chalk.bold(gitRepo.main)}" ${gitRef}on ${gitRepo.type}`) @@ -309,12 +314,10 @@ async function sync({ token, config: { currentTeam, user } }) { } } + let nowConfig let deploymentType - let hasPackage - let hasDockerfile - let isStatic - + // CLI deployment type explicit overrides if (argv.docker) { if (debug) { console.log(`> [debug] Forcing \`deploymentType\` = \`docker\``) @@ -322,101 +325,89 @@ async function sync({ token, config: { currentTeam, user } }) { deploymentType = 'docker' } else if (argv.npm) { - deploymentType = 'npm' - } else if (argv.static) { if (debug) { - console.log(`> [debug] Forcing static deployment`) + console.log(`> [debug] Forcing \`deploymentType\` = \`npm\``) } deploymentType = 'npm' - isStatic = true - } else { - try { - await fs.stat(resolve(path, 'package.json')) - } catch (err) { - hasPackage = true + } else if (argv.static) { + if (debug) { + console.log(`> [debug] Forcing \`deploymentType\` = \`static\``) } - ;[hasPackage, hasDockerfile] = await Promise.all([ - await (async () => { - try { - await fs.stat(resolve(path, 'package.json')) - } catch (err) { - return false - } - return true - })(), - await (async () => { - try { - await fs.stat(resolve(path, 'Dockerfile')) - } catch (err) { - return false + deploymentType = 'static' + } + + let meta + await retry( + async bail => { + try { + meta = await readMetaData(path, { + deploymentType, + deploymentName, + quiet: true + }) + + nowConfig = meta.nowConfig + + if (!deploymentType) { + deploymentType = meta.type + + if (debug) { + console.log(`> [debug] Detected \`deploymentType\` = \`${deploymentType}\``) + } } - return true - })() - ]) - if (hasPackage && hasDockerfile) { - if (debug) { - console.log('[debug] multiple manifests found, disambiguating') - } + if (!deploymentName) { + deploymentName = meta.name - if (isTTY) { - try { - console.log(`> Two manifests found. Press [${chalk.bold('n')}] to deploy or re-run with --flag`) - deploymentType = await promptOptions([ - [ - 'npm', - `${chalk.bold('package.json')}\t${chalk.gray(' --npm')} ` - ], - [ - 'docker', - `${chalk.bold('Dockerfile')}\t${chalk.gray('--docker')} ` - ] - ]) - } catch (err) { - error(err.message) - process.exit(1) + if (debug) { + console.log(`> [debug] Detected \`deploymentName\` = "${deploymentName}"`) + } } - } else { - error( - 'Ambiguous deployment (`package.json` and `Dockerfile` found). ' + - 'Please supply `--npm` or `--docker` to disambiguate.' - ) - } - } else if (hasPackage) { - if (debug) { - console.log( - '> [debug] `package.json` found, assuming `deploymentType` = `npm`' - ) - } + } catch (err) { + if (err.code === 'MULTIPLE_MANIFESTS') { + if (debug) { + console.log('> [debug] Multiple manifests found, disambiguating') + } - deploymentType = 'npm' - } else if (hasDockerfile) { - if (debug) { - console.log( - '> [debug] `Dockerfile` found, assuming `deploymentType` = `docker`' - ) - } + if (isTTY) { + console.log(`> Two manifests found. Press [${chalk.bold('n')}] to deploy or re-run with --flag`) + try { + deploymentType = await promptOptions([ + [ + 'npm', + `${chalk.bold('package.json')}\t${chalk.gray(' --npm')} ` + ], + [ + 'docker', + `${chalk.bold('Dockerfile')}\t${chalk.gray('--docker')} ` + ] + ]) + } catch (err) { + console.error(err) + return bail() + } + + if (debug) { + console.log(`> [debug] Selected \`deploymentType\` = "${deploymentType}"`) + } + + // Invoke async-retry and try again with the explicit deployment type + throw err + } + } - deploymentType = 'docker' - } else { - if (debug) { - console.log( - '> [debug] No manifest files found, assuming static deployment' - ) + return stopDeployment(err) } - - isStatic = true + }, + { + retries: 1, + minTimeout: 0, + maxTimeout: 0, + onRetry: console.log } - } - - const { nowConfig } = await readMetaData(path, { - deploymentType, - deploymentName, - isStatic, - quiet: true - }) + ) const now = new Now({ apiUrl, token, debug, currentTeam }) @@ -529,18 +520,21 @@ async function sync({ token, config: { currentTeam, user } }) { }) try { - await now.create(path, { - env, - deploymentType, - deploymentName, - followSymlinks, - forceNew, - forceSync, - forwardNpm: alwaysForwardNpm || forwardNpm, - quiet, - wantsPublic, - isStatic - }) + await now.create( + path, + Object.assign( + { + env, + followSymlinks, + forceNew, + forceSync, + forwardNpm: alwaysForwardNpm || forwardNpm, + quiet, + wantsPublic + }, + meta + ) + ) } catch (err) { if (debug) { console.log(`> [debug] error: ${err}\n${err.stack}`) @@ -725,3 +719,5 @@ function printLogs(host, token, currentTeam, user) { process.exit(0) }) } + +main() diff --git a/lib/get-files.js b/lib/get-files.js index 1faa9ba..0116832 100644 --- a/lib/get-files.js +++ b/lib/get-files.js @@ -77,11 +77,11 @@ const asAbsolute = function(path, parent) { async function npm( path, - pkg, - nowConfig = null, + pkg = {}, + nowConfig = {}, { limit = null, hasNowJson = false, debug = false } = {} ) { - const whitelist = (nowConfig && nowConfig.files) || pkg.files + const whitelist = nowConfig.files || pkg.files // The package.json `files` whitelist still // honors ignores: https://docs.npmjs.com/files/package.json#files @@ -172,10 +172,10 @@ async function npm( async function docker( path, - nowConfig = null, + nowConfig = {}, { limit = null, hasNowJson = false, debug = false } = {} ) { - const whitelist = nowConfig && nowConfig.files + const whitelist = nowConfig.files // Base search path // the now.json `files` whitelist still diff --git a/lib/git.js b/lib/git.js index 43bf479..a6eeb53 100644 --- a/lib/git.js +++ b/lib/git.js @@ -183,7 +183,10 @@ const isRepoPath = path => { return true } - return 'no-valid-url' + const err = new Error(`Host "${urlParts.host}" is unsupported.`) + err.code = 'INVALID_URL' + err.userError = true + throw err } return /[^\s\\]\/[^\s\\]/g.test(path) diff --git a/lib/index.js b/lib/index.js index 2a4aa94..ea6c199 100644 --- a/lib/index.js +++ b/lib/index.js @@ -20,12 +20,8 @@ const { npm: getNpmFiles, docker: getDockerFiles } = require('./get-files') const ua = require('./ua') const hash = require('./hash') const Agent = require('./agent') -const readMetaData = require('./read-metadata') const toHost = require('./to-host') -// Helpers -const { error } = require('./error') - // How many concurrent HTTP/2 stream uploads const MAX_CONCURRENT = 10 @@ -54,44 +50,44 @@ module.exports = class Now extends EventEmitter { forceNew = false, forceSync = false, forwardNpm = false, - deploymentType = 'npm', - deploymentName, - isStatic = false + + // From readMetaData + name, + description, + type = 'npm', + isStatic = false, + pkg = {}, + nowConfig = {}, + hasNowJson = false } ) { this._path = path this._static = isStatic let files - - const meta = await readMetaData(path, { - deploymentType, - deploymentName, - quiet, - isStatic - }) - const { pkg, name, description, nowConfig, hasNowJson } = meta - deploymentType = meta.deploymentType + let engines if (this._debug) { console.time('> [debug] Getting files') } const opts = { debug: this._debug, hasNowJson } - if (deploymentType === 'npm') { + if (type === 'npm') { files = await getNpmFiles(path, pkg, nowConfig, opts) // A `start` or `now-start` npm script, or a `server.js` file // in the root directory of the deployment are required if (!hasNpmStart(pkg) && !hasFile(path, files, 'server.js')) { - error( + const err = new Error( 'Missing `start` (or `now-start`) script in `package.json`. ' + 'See: https://docs.npmjs.com/cli/start.' ) - - // eslint-disable-next-line unicorn/no-process-exit - process.exit(1) + err.userError = true + throw err } + + engines = nowConfig.engines || pkg.engines + forwardNpm = forwardNpm || nowConfig.forwardNpm } else { files = await getDockerFiles(path, nowConfig, opts) } @@ -100,40 +96,18 @@ module.exports = class Now extends EventEmitter { console.timeEnd('> [debug] Getting files') } - forwardNpm = forwardNpm || (nowConfig && nowConfig.forwardNpm) - - // Read .npmrc - let npmrc = {} + // Read `registry.npmjs.org` authToken from .npmrc let authToken - if (deploymentType === 'npm' && forwardNpm) { - try { - npmrc = await readFile(resolvePath(path, '.npmrc'), 'utf8') - npmrc = parseIni(npmrc) - authToken = npmrc['//registry.npmjs.org/:_authToken'] - } catch (err) { - // Do nothing - } - - if (!authToken) { - try { - npmrc = await readFile(resolvePath(homedir(), '.npmrc'), 'utf8') - npmrc = parseIni(npmrc) - authToken = npmrc['//registry.npmjs.org/:_authToken'] - } catch (err) { - // Do nothing - } - } + if (type === 'npm' && forwardNpm) { + authToken = + (await readAuthToken(path)) || (await readAuthToken(homedir())) } if (this._debug) { console.time('> [debug] Computing hashes') } - const pkgDetails = {} - pkgDetails.name = name - - Object.assign(pkgDetails, pkg) - + const pkgDetails = Object.assign({ name }, pkg) const hashes = await hash(files, isStatic, pkgDetails) if (this._debug) { @@ -142,8 +116,6 @@ module.exports = class Now extends EventEmitter { this._files = hashes - const engines = (nowConfig && nowConfig.engines) || pkg.engines - const deployment = await this.retry(async bail => { if (this._debug) { console.time('> [debug] /now/create') @@ -198,7 +170,7 @@ module.exports = class Now extends EventEmitter { forceSync, name, description, - deploymentType, + deploymentType: type, registryAuthToken: authToken, files, engines @@ -270,7 +242,7 @@ module.exports = class Now extends EventEmitter { } } - if (!quiet && deploymentType === 'npm' && deployment.nodeVersion) { + if (!quiet && type === 'npm' && deployment.nodeVersion) { if (engines && engines.node) { if (missingVersion) { console.log(`> Using Node.js ${chalk.bold(deployment.nodeVersion)} (default)`) @@ -997,3 +969,13 @@ function hasFile(base, files, name) { const relative = files.map(file => toRelative(file, base)) return relative.indexOf(name) !== -1 } + +async function readAuthToken(path, name = '.npmrc') { + try { + const contents = await readFile(resolvePath(path, name), 'utf8') + const npmrc = parseIni(contents) + return npmrc['//registry.npmjs.org/:_authToken'] + } catch (err) { + // Do nothing + } +} diff --git a/lib/read-metadata.js b/lib/read-metadata.js index e88bc34..05047db 100644 --- a/lib/read-metadata.js +++ b/lib/read-metadata.js @@ -3,119 +3,111 @@ const { basename, resolve: resolvePath } = require('path') // Packages const chalk = require('chalk') -const { readFile, exists } = require('fs-promise') +const { readFile } = require('fs-promise') const { parse: parseDockerfile } = require('docker-file-parser') -// Helpers -const { error } = require('../lib/error') - -const listPackage = { +// `package.json` used for "static" deployments +const staticPackage = Object.freeze({ scripts: { start: `NODE_ENV='production' serve ./content` }, dependencies: { serve: '5.0.4' } -} +}) module.exports = readMetaData async function readMetaData( path, - { - deploymentType = 'npm', - deploymentName, - quiet = false, - strict = true, - isStatic = false - } + { deploymentType, deploymentName, quiet = false, strict = true } ) { - let pkg = {} - let nowConfig = null - let hasNowJson = false - - let name let description + let type = deploymentType + let name = deploymentName - try { - nowConfig = JSON.parse(await readFile(resolvePath(path, 'now.json'))) - hasNowJson = true - } catch (err) { - // If the file doesn't exist then that's fine; any other error bubbles up - if (err.code !== 'ENOENT') { - const e = Error(`Failed to read JSON in "${path}/now.json"`) - e.userError = true - throw e + let isStatic = false + let pkg = await readJSON(path, 'package.json') + let nowConfig = await readJSON(path, 'now.json') + const dockerfile = await readDockerfile(path) + + const hasNowJson = Boolean(nowConfig) + + if (pkg && pkg.now) { + // If the project has both a `now.json` and `now` Object in the `package.json` + // file, then fail hard and let the user know that they need to pick one or the + // other + if (nowConfig) { + const err = new Error( + 'You have a `now` configuration field inside `package.json` ' + + 'but configuration is also present in `now.json`! ' + + "Please ensure there's a single source of configuration by removing one." + ) + err.userError = true + throw err + } else { + nowConfig = pkg.now } } - if (hasNowJson) { - // User can specify the type of deployment explicitly in the `now.json` file - // when both a package.json and Dockerfile exist - if (nowConfig.type) { - deploymentType = nowConfig.type - } else if ( - !await exists(resolvePath(path, 'package.json')) && - !await exists(resolvePath(path, 'Dockerfile')) - ) { - deploymentType = 'static' + if (!type) { + // `now.json` / `pkg.now` get default type preference + if (nowConfig) { + type = nowConfig.type } - if (nowConfig.name) { - deploymentName = nowConfig.name + + // Both `package.json` and `Dockerfile` exist! Prompt the user to pick one. + if (!type && pkg && dockerfile) { + const err = new Error( + 'Ambiguous deployment (`package.json` and `Dockerfile` found). ' + + 'Please supply `--npm` or `--docker` to disambiguate.' + ) + err.userError = true + err.code = 'MULTIPLE_MANIFESTS' + throw err } - } - if (deploymentType === 'static') { - isStatic = true - deploymentType = 'npm' - } + if (!type && pkg) { + type = 'npm' + } - if (deploymentType === 'npm') { - if (isStatic) { - pkg = listPackage - } else { - try { - pkg = JSON.parse(await readFile(resolvePath(path, 'package.json'))) - } catch (err) { - error(`Failed to read JSON in "${path}/package.json"`) + if (!type && dockerfile) { + type = 'docker' + } - // eslint-disable-next-line unicorn/no-process-exit - process.exit(1) - } + if (!type) { + type = 'static' } + } - if (!deploymentName) { - if (typeof pkg.name === 'string' && pkg.name !== '') { - name = pkg.name - } else { - name = basename(path) + // "static" deployments are (not-so) secretly just + // npm deployments under the hood + if (type === 'static') { + isStatic = true + type = 'npm' + pkg = Object.assign({ name: pkg && pkg.name }, staticPackage) + } - if (!quiet && !isStatic) { - console.log(`> No \`name\` in \`package.json\`, using ${chalk.bold(name)}`) - } - } - } + if (!name && nowConfig) { + name = nowConfig.name + } - description = pkg.description - } else if (deploymentType === 'docker') { - let docker - try { - const dockerfile = await readFile(resolvePath(path, 'Dockerfile'), 'utf8') - docker = parseDockerfile(dockerfile, { includeComments: true }) - } catch (err) { - const e = Error(`Failed to parse "${path}/Dockerfile"`) - e.userError = true - throw e + if (type === 'npm') { + if (pkg) { + if (!name && pkg.name) { + name = String(pkg.name) + } + description = pkg.description } - - if (strict && docker.length <= 0) { - const e = Error('No commands found in `Dockerfile`') - e.userError = true - throw e + } else if (type === 'docker') { + if (strict && dockerfile.length <= 0) { + const err = new Error('No commands found in `Dockerfile`') + err.userError = true + throw err } const labels = {} - docker.filter(cmd => cmd.name === 'LABEL').forEach(({ args }) => { + dockerfile.filter(cmd => cmd.name === 'LABEL').forEach(({ args }) => { for (const key in args) { if (!{}.hasOwnProperty.call(args, key)) { continue @@ -125,61 +117,74 @@ async function readMetaData( try { labels[key] = args[key] } catch (err) { - const e = Error(`Error parsing value for LABEL ${key} in \`Dockerfile\``) + const e = new Error( + `Error parsing value for LABEL ${key} in \`Dockerfile\`` + ) e.userError = true throw e } } }) - if (!deploymentName) { - if (labels.name) { - name = labels.name - } else { - name = basename(path) - - if (!quiet) { - if (hasNowJson) { - console.log(`> No \`name\` LABEL in \`Dockerfile\` or \`name\` field in \`now.json\`, using ${chalk.bold(name)}`) - } else { - console.log(`> No \`name\` LABEL in \`Dockerfile\`, using ${chalk.bold(name)}`) - } - } - } + if (!name) { + name = labels.name } description = labels.description } else { - throw new TypeError(`Unsupported "deploymentType": ${deploymentType}`) + throw new TypeError(`Unsupported "deploymentType": ${type}`) } - if (deploymentName) { - name = deploymentName - } + // No name in `package.json` / `now.json`, or "name" label in Dockerfile. + // Default to the basename of the root dir + if (!name) { + name = basename(path) - if (pkg.now) { - // If the project has both a `now.json` and `now` Object in the `package.json` - // file, then fail hard and let the user know that they need to pick one or the - // other - if (hasNowJson) { - const e = new Error( - 'You have a `now` configuration field' + - 'inside `package.json`, but configuration is also present' + - "in `now.json`! Please ensure there's a single source of configuration by removing one" - ) - e.userError = true - throw e - } else { - nowConfig = pkg.now + if (!quiet && !isStatic) { + if (type === 'docker') { + console.log(`> No \`name\` LABEL in \`Dockerfile\`, using ${chalk.bold(name)}`) + } else { + console.log(`> No \`name\` in \`package.json\`, using ${chalk.bold(name)}`) + } } } return { name, description, - deploymentType, + type, + isStatic, pkg, nowConfig, - hasNowJson + hasNowJson, + + // XXX: legacy + deploymentType: type + } +} + +async function readJSON(path, name) { + try { + const contents = await readFile(resolvePath(path, name), 'utf8') + return JSON.parse(contents) + } catch (err) { + // If the file doesn't exist then that's fine; any other error bubbles up + if (err.code !== 'ENOENT') { + err.userError = true + throw err + } + } +} + +async function readDockerfile(path, name = 'Dockerfile') { + try { + const contents = await readFile(resolvePath(path, name), 'utf8') + return parseDockerfile(contents, { includeComments: true }) + } catch (err) { + // If the file doesn't exist then that's fine; any other error bubbles up + if (err.code !== 'ENOENT') { + err.userError = true + throw err + } } } diff --git a/test/_fixtures/multiple-manifests-throws/Dockerfile b/test/_fixtures/multiple-manifests-throws/Dockerfile new file mode 100644 index 0000000..a86da2e --- /dev/null +++ b/test/_fixtures/multiple-manifests-throws/Dockerfile @@ -0,0 +1 @@ +CMD echo 'world' diff --git a/test/_fixtures/multiple-manifests-throws/package.json b/test/_fixtures/multiple-manifests-throws/package.json new file mode 100644 index 0000000..6d41fe6 --- /dev/null +++ b/test/_fixtures/multiple-manifests-throws/package.json @@ -0,0 +1,3 @@ +{ + "name": "simple" +} diff --git a/test/_fixtures/now-json-docker/now.json b/test/_fixtures/now-json-docker/now.json index 88faec5..5acf2cb 100644 --- a/test/_fixtures/now-json-docker/now.json +++ b/test/_fixtures/now-json-docker/now.json @@ -1,5 +1,4 @@ { - "type": "docker", "files": [ "b.js" ] diff --git a/test/_fixtures/type-in-package-now-with-dockerfile/Dockerfile b/test/_fixtures/type-in-package-now-with-dockerfile/Dockerfile new file mode 100644 index 0000000..a86da2e --- /dev/null +++ b/test/_fixtures/type-in-package-now-with-dockerfile/Dockerfile @@ -0,0 +1 @@ +CMD echo 'world' diff --git a/test/_fixtures/type-in-package-now-with-dockerfile/package.json b/test/_fixtures/type-in-package-now-with-dockerfile/package.json new file mode 100644 index 0000000..a48ec2d --- /dev/null +++ b/test/_fixtures/type-in-package-now-with-dockerfile/package.json @@ -0,0 +1,5 @@ +{ + "now": { + "type": "npm" + } +} diff --git a/test/index.js b/test/index.js index c312f58..e8677d8 100644 --- a/test/index.js +++ b/test/index.js @@ -212,3 +212,29 @@ test('throws when both `now.json` and `package.json:now` exist', async t => { /please ensure there's a single source of configuration/i.test(e.message) ) }) + +test('throws when `package.json` and `Dockerfile` exist', async t => { + let e + try { + await readMetadata(fixture('multiple-manifests-throws'), { + quiet: true, + strict: false + }) + } catch (err) { + e = err + } + t.is(e.userError, true) + t.is(e.code, 'MULTIPLE_MANIFESTS') + t.pass(/ambiguous deployment/i.test(e.message)) +}) + +test('support `package.json:now.type` to bypass multiple manifests error', async t => { + const f = fixture('type-in-package-now-with-dockerfile') + const { type, nowConfig, hasNowJson } = await readMetadata(f, { + quiet: true, + strict: false + }) + t.is(type, 'npm') + t.is(nowConfig.type, 'npm') + t.is(hasNowJson, false) +})