diff --git a/bin/now-alias.js b/bin/now-alias.js index a66a7c9..4814f5f 100755 --- a/bin/now-alias.js +++ b/bin/now-alias.js @@ -296,13 +296,12 @@ function findAlias(alias, list) { async function realias(alias) { const path = process.cwd() - const {pkg, name} = await readMetaData(path, { + const {nowConfig, name} = await readMetaData(path, { deploymentType: 'npm', // hard coding settingsā€¦ quiet: true // `quiet` }) - const pkgConfig = pkg ? pkg.now || {} : {} - const target = pkgConfig.alias + const target = nowConfig && nowConfig.alias // the user never intended to support aliases from the package if (!target) { diff --git a/bin/now-deploy.js b/bin/now-deploy.js index 540b832..08ecc53 100755 --- a/bin/now-deploy.js +++ b/bin/now-deploy.js @@ -363,7 +363,7 @@ async function sync(token) { } } - const {pkg: {now: pkgConfig = {}} = {}} = await readMetaData(path, { + const {nowConfig} = await readMetaData(path, { deploymentType, deploymentName, isStatic, @@ -373,7 +373,7 @@ async function sync(token) { const now = new Now(apiUrl, token, {debug}) // Merge `now.env` from package.json with `-e` arguments. - const pkgEnv = pkgConfig.env + const pkgEnv = nowConfig && nowConfig.env const envs = [ ...Object.keys(pkgEnv || {}).map(k => `${k}=${pkgEnv[k]}`), ...[].concat(argv.env || []) diff --git a/lib/get-files.js b/lib/get-files.js index a225344..f797b6e 100644 --- a/lib/get-files.js +++ b/lib/get-files.js @@ -24,11 +24,15 @@ const IGNORED = require('./ignored') * @return {Array} comprehensive list of paths to sync */ -async function npm(path, pkg, { +async function npm(path, pkg, nowConfig = null, { limit = null, + hasNowJson = false, debug = false } = {}) { - const whitelist = pkg.now && pkg.now.files ? pkg.now.files : pkg.files + const whitelist = (nowConfig && nowConfig.files) || pkg.files + + // the package.json `files` whitelist still + // honors ignores: https://docs.npmjs.com/files/package.json#files const search_ = whitelist || ['.'] // convert all filenames into absolute paths const search = Array.prototype.concat.apply([], (await Promise.all(search_.map(file => glob(file, {cwd: path, absolute: true, dot: true}))))) @@ -92,6 +96,10 @@ async function npm(path, pkg, { // source: https://docs.npmjs.com/files/package.json#files files.push(asAbsolute('package.json', path)) + if (hasNowJson) { + files.push(asAbsolute('now.json', path)) + } + // get files return unique(files) } @@ -125,12 +133,17 @@ const asAbsolute = function (path, parent) { * @return {Array} comprehensive list of paths to sync */ -async function docker(path, { +async function docker(path, nowConfig = null, { limit = null, + hasNowJson = false, debug = false } = {}) { + const whitelist = nowConfig && nowConfig.files + // base search path - const search_ = ['.'] + // the now.json `files` whitelist still + // honors ignores: https://docs.npmjs.com/files/package.json#files + const search_ = whitelist || ['.'] // convert all filenames into absolute paths const search = search_.map(file => asAbsolute(file, path)) @@ -176,6 +189,10 @@ async function docker(path, { // source: https://docs.npmjs.com/files/package.json#files files.push(asAbsolute('Dockerfile', path)) + if (hasNowJson) { + files.push(asAbsolute('now.json', path)) + } + // get files return unique(files) } @@ -206,7 +223,7 @@ const glob = async function (pattern, options) { * @return {Array} of {String}s of full paths */ -const explode = async function (paths, {accepts, debug}) { +async function explode(paths, {accepts, debug}) { const list = async file => { let path = file let s @@ -262,7 +279,7 @@ const explode = async function (paths, {accepts, debug}) { const maybeRead = async function (path, default_ = '') { try { - return (await readFile(path, 'utf8')) + return await readFile(path, 'utf8') } catch (err) { return default_ } diff --git a/lib/index.js b/lib/index.js index 4cd6e99..9359eee 100644 --- a/lib/index.js +++ b/lib/index.js @@ -53,30 +53,31 @@ module.exports = class Now extends EventEmitter { let files - const {pkg, name, description} = await readMetaData(path, { + const meta = await readMetaData(path, { deploymentType, deploymentName, quiet, isStatic }) + const {pkg, name, description, nowConfig, hasNowJson} = meta + deploymentType = meta.deploymentType if (this._debug) { console.time('> [debug] Getting files') } + const opts = {debug: this._debug, hasNowJson} if (deploymentType === 'npm') { - files = await getNpmFiles(path, pkg, {debug: this._debug}) + files = await getNpmFiles(path, pkg, nowConfig, opts) } else { - files = await getDockerFiles(path, {debug: this._debug}) + files = await getDockerFiles(path, nowConfig, opts) } if (this._debug) { console.timeEnd('> [debug] Getting files') } - const nowProperties = pkg ? pkg.now || {} : {} - - forwardNpm = forwardNpm || nowProperties.forwardNpm + forwardNpm = forwardNpm || (nowConfig && nowConfig.forwardNpm) // Read .npmrc let npmrc = {} @@ -118,7 +119,7 @@ module.exports = class Now extends EventEmitter { this._files = hashes - const engines = nowProperties.engines || pkg.engines + const engines = (nowConfig && nowConfig.engines) || pkg.engines const deployment = await this.retry(async bail => { if (this._debug) { diff --git a/lib/read-metadata.js b/lib/read-metadata.js index 4b9f626..011eee9 100644 --- a/lib/read-metadata.js +++ b/lib/read-metadata.js @@ -12,24 +12,51 @@ const listPackage = { } } -module.exports = async function (path, { +module.exports = readMetaData + +async function readMetaData(path, { deploymentType = 'npm', deploymentName, quiet = false, + strict = true, isStatic = false }) { let pkg = {} + let nowConfig = null + let hasNowJson = false let name let description + 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 + } + } + + 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 + } + if (nowConfig.name) { + deploymentName = nowConfig.name + } + } + if (deploymentType === 'npm') { if (isStatic) { pkg = listPackage } else { try { - pkg = await readFile(resolvePath(path, 'package.json')) - pkg = JSON.parse(pkg) + pkg = JSON.parse(await readFile(resolvePath(path, 'package.json'))) } catch (err) { const e = Error(`Failed to read JSON in "${path}/package.json"`) e.userError = true @@ -37,7 +64,7 @@ module.exports = async function (path, { } } - if (!pkg.scripts || (!pkg.scripts.start && !pkg.scripts['now-start'])) { + if (strict && (!pkg.scripts || (!pkg.scripts.start && !pkg.scripts['now-start']))) { const e = Error('Missing `start` (or `now-start`) script in `package.json`. ' + 'See: https://docs.npmjs.com/cli/start.') e.userError = true @@ -68,7 +95,7 @@ module.exports = async function (path, { throw e } - if (docker.length <= 0) { + if (strict && docker.length <= 0) { const e = Error('No commands found in `Dockerfile`') e.userError = true throw e @@ -101,21 +128,43 @@ module.exports = async function (path, { name = basename(path) if (!quiet) { - console.log(`> No \`name\` LABEL in \`Dockerfile\`, using ${chalk.bold(name)}`) + 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 + } else { + throw new TypeError(`Unsupported "deploymentType": ${deploymentType}`) } if (deploymentName) { name = deploymentName } + 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 + } + } + return { name, description, - pkg + deploymentType, + pkg, + nowConfig, + hasNowJson } } diff --git a/test/_fixtures/now-json-docker/Dockerfile b/test/_fixtures/now-json-docker/Dockerfile new file mode 100644 index 0000000..a86da2e --- /dev/null +++ b/test/_fixtures/now-json-docker/Dockerfile @@ -0,0 +1 @@ +CMD echo 'world' diff --git a/test/_fixtures/now-json-docker/a.js b/test/_fixtures/now-json-docker/a.js new file mode 100644 index 0000000..95c1988 --- /dev/null +++ b/test/_fixtures/now-json-docker/a.js @@ -0,0 +1 @@ +// should not be included diff --git a/test/_fixtures/now-json-docker/b.js b/test/_fixtures/now-json-docker/b.js new file mode 100644 index 0000000..dcdb2da --- /dev/null +++ b/test/_fixtures/now-json-docker/b.js @@ -0,0 +1 @@ +// should be included diff --git a/test/_fixtures/now-json-docker/now.json b/test/_fixtures/now-json-docker/now.json new file mode 100644 index 0000000..88faec5 --- /dev/null +++ b/test/_fixtures/now-json-docker/now.json @@ -0,0 +1,6 @@ +{ + "type": "docker", + "files": [ + "b.js" + ] +} diff --git a/test/_fixtures/now-json-throws/now.json b/test/_fixtures/now-json-throws/now.json new file mode 100644 index 0000000..087ce94 --- /dev/null +++ b/test/_fixtures/now-json-throws/now.json @@ -0,0 +1,3 @@ +{ + "alias": "bar.com" +} diff --git a/test/_fixtures/now-json-throws/package.json b/test/_fixtures/now-json-throws/package.json new file mode 100644 index 0000000..350ad6c --- /dev/null +++ b/test/_fixtures/now-json-throws/package.json @@ -0,0 +1,9 @@ +{ + "name": "woot", + "version": "0.0.1", + "description": "", + "dependencies": {}, + "now": { + "alias": "foo.com" + } +} diff --git a/test/_fixtures/now-json/a.js b/test/_fixtures/now-json/a.js new file mode 100644 index 0000000..95c1988 --- /dev/null +++ b/test/_fixtures/now-json/a.js @@ -0,0 +1 @@ +// should not be included diff --git a/test/_fixtures/now-json/b.js b/test/_fixtures/now-json/b.js new file mode 100644 index 0000000..dcdb2da --- /dev/null +++ b/test/_fixtures/now-json/b.js @@ -0,0 +1 @@ +// should be included diff --git a/test/_fixtures/now-json/now.json b/test/_fixtures/now-json/now.json new file mode 100644 index 0000000..5acf2cb --- /dev/null +++ b/test/_fixtures/now-json/now.json @@ -0,0 +1,5 @@ +{ + "files": [ + "b.js" + ] +} diff --git a/test/_fixtures/now-json/package.json b/test/_fixtures/now-json/package.json new file mode 100644 index 0000000..c679e7f --- /dev/null +++ b/test/_fixtures/now-json/package.json @@ -0,0 +1,9 @@ +{ + "name": "woot", + "version": "0.0.1", + "description": "", + "dependencies": {}, + "files": [ + "a.js" + ] +} diff --git a/test/index.js b/test/index.js index 33ea08f..11b1f99 100644 --- a/test/index.js +++ b/test/index.js @@ -4,25 +4,20 @@ const {join, resolve} = require('path') // Packages const test = require('ava') const {asc: alpha} = require('alpha-sort') -const {readFile} = require('fs-promise') // Ours -const {npm: getNpmFiles_, docker: getDockerFiles} = require('../lib/get-files') const hash = require('../lib/hash') +const readMetadata = require('../lib/read-metadata') +const {npm: getNpmFiles_, docker: getDockerFiles} = require('../lib/get-files') const prefix = join(__dirname, '_fixtures') + '/' const base = path => path.replace(prefix, '') const fixture = name => resolve(`./test/_fixtures/${name}`) -const readJSON = async file => { - const data = await readFile(file) - return JSON.parse(data) -} - // overload to force debugging const getNpmFiles = async dir => { - const pkg = await readJSON(resolve(dir, 'package.json')) - return getNpmFiles_(dir, pkg) + const {pkg, nowConfig, hasNowJson} = await readMetadata(dir, {quiet: true, strict: false}) + return getNpmFiles_(dir, pkg, nowConfig, {hasNowJson}) } test('`files`', async t => { @@ -170,3 +165,37 @@ test('prefix regression', async t => { t.is(base(files[0]), 'prefix-regression/package.json') t.is(base(files[1]), 'prefix-regression/woot.js') }) + +test('support `now.json` files with package.json', async t => { + let files = await getNpmFiles(fixture('now-json')) + files = files.sort(alpha) + t.is(files.length, 3) + t.is(base(files[0]), 'now-json/b.js') + t.is(base(files[1]), 'now-json/now.json') + t.is(base(files[2]), 'now-json/package.json') +}) + +test('support `now.json` files with Dockerfile', async t => { + const f = fixture('now-json-docker') + const {deploymentType, nowConfig, hasNowJson} = await readMetadata(f, {quiet: true, strict: false}) + t.is(deploymentType, 'docker') + + let files = await getDockerFiles(f, nowConfig, {hasNowJson}) + files = files.sort(alpha) + t.is(files.length, 3) + t.is(base(files[0]), 'now-json-docker/Dockerfile') + t.is(base(files[1]), 'now-json-docker/b.js') + t.is(base(files[2]), 'now-json-docker/now.json') +}) + +test('throws when both `now.json` and `package.json:now` exist', async t => { + let e + try { + await readMetadata(fixture('now-json-throws'), {quiet: true, strict: false}) + } catch (err) { + e = err + } + t.is(e.name, 'Error') + t.is(e.userError, true) + t.pass(/please ensure there's a single source of configuration/i.test(e.message)) +})