Browse Source

refactor the `readMetaData()` function (#508)

* refactor the `readMetaData()` function

* add a couple new test cases

* test: restore "build" prefix

Fixes Node.js <= v6

* add default values for `npm`/`docker` get files functions

Makes the logic simpler, since we don't have to check for existence.

* throw an error when missing start/now-start or server.js

* pass in entire `readMetaData()` result to Now.create()

This avoids us reading these files from the filesystem again
master
Nathan Rajlich 8 years ago
committed by GitHub
parent
commit
49a2a645c8
  1. 258
      bin/now-deploy.js
  2. 10
      lib/get-files.js
  3. 5
      lib/git.js
  4. 86
      lib/index.js
  5. 237
      lib/read-metadata.js
  6. 1
      test/_fixtures/multiple-manifests-throws/Dockerfile
  7. 3
      test/_fixtures/multiple-manifests-throws/package.json
  8. 1
      test/_fixtures/now-json-docker/now.json
  9. 1
      test/_fixtures/type-in-package-now-with-dockerfile/Dockerfile
  10. 5
      test/_fixtures/type-in-package-now-with-dockerfile/package.json
  11. 26
      test/index.js

258
bin/now-deploy.js

@ -12,6 +12,7 @@ const minimist = require('minimist')
const ms = require('ms') const ms = require('ms')
const flatten = require('arr-flatten') const flatten = require('arr-flatten')
const dotenv = require('dotenv') const dotenv = require('dotenv')
const retry = require('async-retry')
const { eraseLines } = require('ansi-escapes') const { eraseLines } = require('ansi-escapes')
const { write: copy } = require('clipboardy') const { write: copy } = require('clipboardy')
@ -167,6 +168,7 @@ const gitRepo = {}
// Options // Options
let forceNew = argv.force let forceNew = argv.force
let deploymentName = argv.name
const debug = argv.debug const debug = argv.debug
const clipboard = !argv['no-clipboard'] const clipboard = !argv['no-clipboard']
const forwardNpm = argv['forward-npm'] const forwardNpm = argv['forward-npm']
@ -174,7 +176,6 @@ const forceSync = argv.forceSync
const shouldLogin = argv.login const shouldLogin = argv.login
const followSymlinks = !argv.links const followSymlinks = !argv.links
const wantsPublic = argv.public const wantsPublic = argv.public
const deploymentName = argv.name || false
const apiUrl = argv.url || 'https://api.zeit.co' const apiUrl = argv.url || 'https://api.zeit.co'
const isTTY = process.stdout.isTTY const isTTY = process.stdout.isTTY
const quiet = !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') console.log('Read more about the new way here: http://bit.ly/2l2v5Fg\n')
} }
// Create a new deployment if user changed const stopDeployment = msg => {
// the name or made _src public. error(msg)
// This should just work fine because it doesn't process.exit(1)
// force a new sync, it just forces a new deployment. }
// 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) { if (deploymentName || wantsPublic) {
forceNew = true forceNew = true
} }
let alwaysForwardNpm let alwaysForwardNpm
Promise.resolve().then(async () => { async function main() {
let config = await cfg.read({ token: argv.token }) let config = await cfg.read({ token: argv.token })
alwaysForwardNpm = config.forwardNpm alwaysForwardNpm = config.forwardNpm
if (argv.h || argv.help) { if (argv.h || argv.help) {
help() help()
exit(0) return exit(0)
} else if (argv.v || argv.version) { } else if (argv.v || argv.version) {
console.log(version) console.log(version)
process.exit(0) return exit(0)
} else if (!config.token || shouldLogin) { }
let token
let token = argv.token || config.token
if (!token || shouldLogin) {
try { try {
token = await login(apiUrl) token = await login(apiUrl)
config = await cfg.read() config = await cfg.read()
} catch (err) { } catch (err) {
error(`Authentication error – ${err.message}`) return stopDeployment(`Authentication error – ${err.message}`)
process.exit(1)
} }
if (shouldLogin) { if (shouldLogin) {
console.log('> Logged in successfully. Token saved in ~/.now.json') console.log('> Logged in successfully. Token saved in ~/.now.json')
process.exit(0) return exit(0)
} else {
sync({ token, config }).catch(err => {
error(`Unknown error: ${err}\n${err.stack}`)
process.exit(1)
})
} }
} 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 } }) { async function sync({ token, config: { currentTeam, user } }) {
const start = Date.now() const start = Date.now()
@ -248,19 +251,23 @@ async function sync({ token, config: { currentTeam, user } }) {
currentTeam currentTeam
}).getCurrent() }).getCurrent()
const stopDeployment = msg => {
error(msg)
process.exit(1)
}
const isValidRepo = isRepoPath(rawPath)
try { try {
await fs.stat(path) await fs.stat(path)
} catch (err) { } catch (err) {
let repo 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) const gitParts = gitPathParts(rawPath)
Object.assign(gitRepo, gitParts) Object.assign(gitRepo, gitParts)
@ -282,8 +289,6 @@ async function sync({ token, config: { currentTeam, user } }) {
// Set global variable for deleting tmp dir later // Set global variable for deleting tmp dir later
// once the deployment has finished // once the deployment has finished
Object.assign(gitRepo, repo) 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) { } else if (isValidRepo) {
const gitRef = gitRepo.ref ? `with "${chalk.bold(gitRepo.ref)}" ` : '' const gitRef = gitRepo.ref ? `with "${chalk.bold(gitRepo.ref)}" ` : ''
stopDeployment(`There's no repository named "${chalk.bold(gitRepo.main)}" ${gitRef}on ${gitRepo.type}`) 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 deploymentType
let hasPackage // CLI deployment type explicit overrides
let hasDockerfile
let isStatic
if (argv.docker) { if (argv.docker) {
if (debug) { if (debug) {
console.log(`> [debug] Forcing \`deploymentType\` = \`docker\``) console.log(`> [debug] Forcing \`deploymentType\` = \`docker\``)
@ -322,101 +325,89 @@ async function sync({ token, config: { currentTeam, user } }) {
deploymentType = 'docker' deploymentType = 'docker'
} else if (argv.npm) { } else if (argv.npm) {
deploymentType = 'npm'
} else if (argv.static) {
if (debug) { if (debug) {
console.log(`> [debug] Forcing static deployment`) console.log(`> [debug] Forcing \`deploymentType\` = \`npm\``)
} }
deploymentType = 'npm' deploymentType = 'npm'
isStatic = true } else if (argv.static) {
} else { if (debug) {
try { console.log(`> [debug] Forcing \`deploymentType\` = \`static\``)
await fs.stat(resolve(path, 'package.json'))
} catch (err) {
hasPackage = true
} }
;[hasPackage, hasDockerfile] = await Promise.all([ deploymentType = 'static'
await (async () => { }
try {
await fs.stat(resolve(path, 'package.json')) let meta
} catch (err) { await retry(
return false async bail => {
} try {
return true meta = await readMetaData(path, {
})(), deploymentType,
await (async () => { deploymentName,
try { quiet: true
await fs.stat(resolve(path, 'Dockerfile')) })
} catch (err) {
return false nowConfig = meta.nowConfig
if (!deploymentType) {
deploymentType = meta.type
if (debug) {
console.log(`> [debug] Detected \`deploymentType\` = \`${deploymentType}\``)
}
} }
return true
})()
])
if (hasPackage && hasDockerfile) { if (!deploymentName) {
if (debug) { deploymentName = meta.name
console.log('[debug] multiple manifests found, disambiguating')
}
if (isTTY) { if (debug) {
try { console.log(`> [debug] Detected \`deploymentName\` = "${deploymentName}"`)
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)
} }
} else { } catch (err) {
error( if (err.code === 'MULTIPLE_MANIFESTS') {
'Ambiguous deployment (`package.json` and `Dockerfile` found). ' + if (debug) {
'Please supply `--npm` or `--docker` to disambiguate.' console.log('> [debug] Multiple manifests found, disambiguating')
) }
}
} else if (hasPackage) {
if (debug) {
console.log(
'> [debug] `package.json` found, assuming `deploymentType` = `npm`'
)
}
deploymentType = 'npm' if (isTTY) {
} else if (hasDockerfile) { console.log(`> Two manifests found. Press [${chalk.bold('n')}] to deploy or re-run with --flag`)
if (debug) { try {
console.log( deploymentType = await promptOptions([
'> [debug] `Dockerfile` found, assuming `deploymentType` = `docker`' [
) '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' return stopDeployment(err)
} else {
if (debug) {
console.log(
'> [debug] No manifest files found, assuming static deployment'
)
} }
},
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 }) const now = new Now({ apiUrl, token, debug, currentTeam })
@ -529,18 +520,21 @@ async function sync({ token, config: { currentTeam, user } }) {
}) })
try { try {
await now.create(path, { await now.create(
env, path,
deploymentType, Object.assign(
deploymentName, {
followSymlinks, env,
forceNew, followSymlinks,
forceSync, forceNew,
forwardNpm: alwaysForwardNpm || forwardNpm, forceSync,
quiet, forwardNpm: alwaysForwardNpm || forwardNpm,
wantsPublic, quiet,
isStatic wantsPublic
}) },
meta
)
)
} catch (err) { } catch (err) {
if (debug) { if (debug) {
console.log(`> [debug] error: ${err}\n${err.stack}`) console.log(`> [debug] error: ${err}\n${err.stack}`)
@ -725,3 +719,5 @@ function printLogs(host, token, currentTeam, user) {
process.exit(0) process.exit(0)
}) })
} }
main()

10
lib/get-files.js

@ -77,11 +77,11 @@ const asAbsolute = function(path, parent) {
async function npm( async function npm(
path, path,
pkg, pkg = {},
nowConfig = null, nowConfig = {},
{ limit = null, hasNowJson = false, debug = false } = {} { 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 // The package.json `files` whitelist still
// honors ignores: https://docs.npmjs.com/files/package.json#files // honors ignores: https://docs.npmjs.com/files/package.json#files
@ -172,10 +172,10 @@ async function npm(
async function docker( async function docker(
path, path,
nowConfig = null, nowConfig = {},
{ limit = null, hasNowJson = false, debug = false } = {} { limit = null, hasNowJson = false, debug = false } = {}
) { ) {
const whitelist = nowConfig && nowConfig.files const whitelist = nowConfig.files
// Base search path // Base search path
// the now.json `files` whitelist still // the now.json `files` whitelist still

5
lib/git.js

@ -183,7 +183,10 @@ const isRepoPath = path => {
return true 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) return /[^\s\\]\/[^\s\\]/g.test(path)

86
lib/index.js

@ -20,12 +20,8 @@ const { npm: getNpmFiles, docker: getDockerFiles } = require('./get-files')
const ua = require('./ua') const ua = require('./ua')
const hash = require('./hash') const hash = require('./hash')
const Agent = require('./agent') const Agent = require('./agent')
const readMetaData = require('./read-metadata')
const toHost = require('./to-host') const toHost = require('./to-host')
// Helpers
const { error } = require('./error')
// How many concurrent HTTP/2 stream uploads // How many concurrent HTTP/2 stream uploads
const MAX_CONCURRENT = 10 const MAX_CONCURRENT = 10
@ -54,44 +50,44 @@ module.exports = class Now extends EventEmitter {
forceNew = false, forceNew = false,
forceSync = false, forceSync = false,
forwardNpm = false, forwardNpm = false,
deploymentType = 'npm',
deploymentName, // From readMetaData
isStatic = false name,
description,
type = 'npm',
isStatic = false,
pkg = {},
nowConfig = {},
hasNowJson = false
} }
) { ) {
this._path = path this._path = path
this._static = isStatic this._static = isStatic
let files let files
let engines
const meta = await readMetaData(path, {
deploymentType,
deploymentName,
quiet,
isStatic
})
const { pkg, name, description, nowConfig, hasNowJson } = meta
deploymentType = meta.deploymentType
if (this._debug) { if (this._debug) {
console.time('> [debug] Getting files') console.time('> [debug] Getting files')
} }
const opts = { debug: this._debug, hasNowJson } const opts = { debug: this._debug, hasNowJson }
if (deploymentType === 'npm') { if (type === 'npm') {
files = await getNpmFiles(path, pkg, nowConfig, opts) files = await getNpmFiles(path, pkg, nowConfig, opts)
// A `start` or `now-start` npm script, or a `server.js` file // A `start` or `now-start` npm script, or a `server.js` file
// in the root directory of the deployment are required // in the root directory of the deployment are required
if (!hasNpmStart(pkg) && !hasFile(path, files, 'server.js')) { if (!hasNpmStart(pkg) && !hasFile(path, files, 'server.js')) {
error( const err = new Error(
'Missing `start` (or `now-start`) script in `package.json`. ' + 'Missing `start` (or `now-start`) script in `package.json`. ' +
'See: https://docs.npmjs.com/cli/start.' 'See: https://docs.npmjs.com/cli/start.'
) )
err.userError = true
// eslint-disable-next-line unicorn/no-process-exit throw err
process.exit(1)
} }
engines = nowConfig.engines || pkg.engines
forwardNpm = forwardNpm || nowConfig.forwardNpm
} else { } else {
files = await getDockerFiles(path, nowConfig, opts) files = await getDockerFiles(path, nowConfig, opts)
} }
@ -100,40 +96,18 @@ module.exports = class Now extends EventEmitter {
console.timeEnd('> [debug] Getting files') console.timeEnd('> [debug] Getting files')
} }
forwardNpm = forwardNpm || (nowConfig && nowConfig.forwardNpm) // Read `registry.npmjs.org` authToken from .npmrc
// Read .npmrc
let npmrc = {}
let authToken let authToken
if (deploymentType === 'npm' && forwardNpm) { if (type === 'npm' && forwardNpm) {
try { authToken =
npmrc = await readFile(resolvePath(path, '.npmrc'), 'utf8') (await readAuthToken(path)) || (await readAuthToken(homedir()))
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 (this._debug) { if (this._debug) {
console.time('> [debug] Computing hashes') console.time('> [debug] Computing hashes')
} }
const pkgDetails = {} const pkgDetails = Object.assign({ name }, pkg)
pkgDetails.name = name
Object.assign(pkgDetails, pkg)
const hashes = await hash(files, isStatic, pkgDetails) const hashes = await hash(files, isStatic, pkgDetails)
if (this._debug) { if (this._debug) {
@ -142,8 +116,6 @@ module.exports = class Now extends EventEmitter {
this._files = hashes this._files = hashes
const engines = (nowConfig && nowConfig.engines) || pkg.engines
const deployment = await this.retry(async bail => { const deployment = await this.retry(async bail => {
if (this._debug) { if (this._debug) {
console.time('> [debug] /now/create') console.time('> [debug] /now/create')
@ -198,7 +170,7 @@ module.exports = class Now extends EventEmitter {
forceSync, forceSync,
name, name,
description, description,
deploymentType, deploymentType: type,
registryAuthToken: authToken, registryAuthToken: authToken,
files, files,
engines 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 (engines && engines.node) {
if (missingVersion) { if (missingVersion) {
console.log(`> Using Node.js ${chalk.bold(deployment.nodeVersion)} (default)`) 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)) const relative = files.map(file => toRelative(file, base))
return relative.indexOf(name) !== -1 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
}
}

237
lib/read-metadata.js

@ -3,119 +3,111 @@ const { basename, resolve: resolvePath } = require('path')
// Packages // Packages
const chalk = require('chalk') const chalk = require('chalk')
const { readFile, exists } = require('fs-promise') const { readFile } = require('fs-promise')
const { parse: parseDockerfile } = require('docker-file-parser') const { parse: parseDockerfile } = require('docker-file-parser')
// Helpers // `package.json` used for "static" deployments
const { error } = require('../lib/error') const staticPackage = Object.freeze({
const listPackage = {
scripts: { scripts: {
start: `NODE_ENV='production' serve ./content` start: `NODE_ENV='production' serve ./content`
}, },
dependencies: { dependencies: {
serve: '5.0.4' serve: '5.0.4'
} }
} })
module.exports = readMetaData module.exports = readMetaData
async function readMetaData( async function readMetaData(
path, path,
{ { deploymentType, deploymentName, quiet = false, strict = true }
deploymentType = 'npm',
deploymentName,
quiet = false,
strict = true,
isStatic = false
}
) { ) {
let pkg = {}
let nowConfig = null
let hasNowJson = false
let name
let description let description
let type = deploymentType
let name = deploymentName
try { let isStatic = false
nowConfig = JSON.parse(await readFile(resolvePath(path, 'now.json'))) let pkg = await readJSON(path, 'package.json')
hasNowJson = true let nowConfig = await readJSON(path, 'now.json')
} catch (err) { const dockerfile = await readDockerfile(path)
// If the file doesn't exist then that's fine; any other error bubbles up
if (err.code !== 'ENOENT') { const hasNowJson = Boolean(nowConfig)
const e = Error(`Failed to read JSON in "${path}/now.json"`)
e.userError = true if (pkg && pkg.now) {
throw e // 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) { if (!type) {
// User can specify the type of deployment explicitly in the `now.json` file // `now.json` / `pkg.now` get default type preference
// when both a package.json and Dockerfile exist if (nowConfig) {
if (nowConfig.type) { type = nowConfig.type
deploymentType = nowConfig.type
} else if (
!await exists(resolvePath(path, 'package.json')) &&
!await exists(resolvePath(path, 'Dockerfile'))
) {
deploymentType = 'static'
} }
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') { if (!type && pkg) {
isStatic = true type = 'npm'
deploymentType = 'npm' }
}
if (deploymentType === 'npm') { if (!type && dockerfile) {
if (isStatic) { type = 'docker'
pkg = listPackage }
} else {
try {
pkg = JSON.parse(await readFile(resolvePath(path, 'package.json')))
} catch (err) {
error(`Failed to read JSON in "${path}/package.json"`)
// eslint-disable-next-line unicorn/no-process-exit if (!type) {
process.exit(1) type = 'static'
}
} }
}
if (!deploymentName) { // "static" deployments are (not-so) secretly just
if (typeof pkg.name === 'string' && pkg.name !== '') { // npm deployments under the hood
name = pkg.name if (type === 'static') {
} else { isStatic = true
name = basename(path) type = 'npm'
pkg = Object.assign({ name: pkg && pkg.name }, staticPackage)
}
if (!quiet && !isStatic) { if (!name && nowConfig) {
console.log(`> No \`name\` in \`package.json\`, using ${chalk.bold(name)}`) name = nowConfig.name
} }
}
}
description = pkg.description if (type === 'npm') {
} else if (deploymentType === 'docker') { if (pkg) {
let docker if (!name && pkg.name) {
try { name = String(pkg.name)
const dockerfile = await readFile(resolvePath(path, 'Dockerfile'), 'utf8') }
docker = parseDockerfile(dockerfile, { includeComments: true }) description = pkg.description
} catch (err) {
const e = Error(`Failed to parse "${path}/Dockerfile"`)
e.userError = true
throw e
} }
} else if (type === 'docker') {
if (strict && docker.length <= 0) { if (strict && dockerfile.length <= 0) {
const e = Error('No commands found in `Dockerfile`') const err = new Error('No commands found in `Dockerfile`')
e.userError = true err.userError = true
throw e throw err
} }
const labels = {} const labels = {}
docker.filter(cmd => cmd.name === 'LABEL').forEach(({ args }) => { dockerfile.filter(cmd => cmd.name === 'LABEL').forEach(({ args }) => {
for (const key in args) { for (const key in args) {
if (!{}.hasOwnProperty.call(args, key)) { if (!{}.hasOwnProperty.call(args, key)) {
continue continue
@ -125,61 +117,74 @@ async function readMetaData(
try { try {
labels[key] = args[key] labels[key] = args[key]
} catch (err) { } 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 e.userError = true
throw e throw e
} }
} }
}) })
if (!deploymentName) { if (!name) {
if (labels.name) { name = 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)}`)
}
}
}
} }
description = labels.description description = labels.description
} else { } else {
throw new TypeError(`Unsupported "deploymentType": ${deploymentType}`) throw new TypeError(`Unsupported "deploymentType": ${type}`)
} }
if (deploymentName) { // No name in `package.json` / `now.json`, or "name" label in Dockerfile.
name = deploymentName // Default to the basename of the root dir
} if (!name) {
name = basename(path)
if (pkg.now) { if (!quiet && !isStatic) {
// If the project has both a `now.json` and `now` Object in the `package.json` if (type === 'docker') {
// file, then fail hard and let the user know that they need to pick one or the console.log(`> No \`name\` LABEL in \`Dockerfile\`, using ${chalk.bold(name)}`)
// other } else {
if (hasNowJson) { console.log(`> No \`name\` in \`package.json\`, using ${chalk.bold(name)}`)
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
} }
} }
return { return {
name, name,
description, description,
deploymentType, type,
isStatic,
pkg, pkg,
nowConfig, 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
}
} }
} }

1
test/_fixtures/multiple-manifests-throws/Dockerfile

@ -0,0 +1 @@
CMD echo 'world'

3
test/_fixtures/multiple-manifests-throws/package.json

@ -0,0 +1,3 @@
{
"name": "simple"
}

1
test/_fixtures/now-json-docker/now.json

@ -1,5 +1,4 @@
{ {
"type": "docker",
"files": [ "files": [
"b.js" "b.js"
] ]

1
test/_fixtures/type-in-package-now-with-dockerfile/Dockerfile

@ -0,0 +1 @@
CMD echo 'world'

5
test/_fixtures/type-in-package-now-with-dockerfile/package.json

@ -0,0 +1,5 @@
{
"now": {
"type": "npm"
}
}

26
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) /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)
})

Loading…
Cancel
Save