diff --git a/.gitignore b/.gitignore index 6719e04..df63095 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,4 @@ package-lock.json node_modules/ .nyc_output/ -test/fixture/.next/ -test/fixture/nextoutput/ -test/fixture/next.config.js -test/fixture2/.next/BUILD_ID -test/fixture-next5/build/ -test/fixture-next6/.next/ +.next/ diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..43c97e7 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/.travis.yml b/.travis.yml index 768ce0a..6cf9957 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,5 +4,4 @@ node_js: - 'node' - '10' - '8' - - '6' after_success: npm run coverage diff --git a/LICENSE b/LICENSE index bac57a4..6c504cd 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2017 Andrew Goode +Copyright 2017-2019 Andrew Goode Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice diff --git a/README.md b/README.md index a1075b0..5cf9ac9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # next-build-id -> Override `next build` output to use a consistent build id +> Use a consistent, git-based build id for your Next.js app [![Build Status](https://travis-ci.org/nexdrew/next-build-id.svg?branch=master)](https://travis-ci.org/nexdrew/next-build-id) [![Coverage Status](https://coveralls.io/repos/github/nexdrew/next-build-id/badge.svg?branch=master)](https://coveralls.io/github/nexdrew/next-build-id?branch=master) @@ -8,123 +8,66 @@ [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org) [![Greenkeeper badge](https://badges.greenkeeper.io/nexdrew/next-build-id.svg)](https://greenkeeper.io/) -Simple CLI and module that lets you define your own build id when using Next.js. +Small package to generate a consistent, git-based build id for your Next.js app when running `next build` on each server in a multi-server deployment. -## New in version 2! +This module exports a function that you can use as your [generateBuildId](https://github.com/zeit/next.js#configuring-the-build-id) config option in next.config.js. -When using Next.js 6+ (which introduced the [generateBuildId](https://github.com/zeit/next.js#configuring-the-build-id) config prop), you can use `next-build-id` as a module within your next.config.js logic to set the BUILD_ID to the most recent git commit hash. This approach means you don't need to use the `next-build-id` CLI - just use `next build` as normal and you'll get the build id you want! +By default, it will use the latest git commit hash from the local git repository (equivalent of `git rev-parse HEAD`): ```js // next.config.js const nextBuildId = require('next-build-id') module.exports = { - generateBuildId: async () => { - const fromGit = await nextBuildId({ dir: __dirname }) - return fromGit.id - } + generateBuildId: () => nextBuildId({ dir: __dirname }) } +// => 'f9fc968afa249d162c924a8d5b4ce6562c164c2e' ``` -## Intro +If you'd rather use a build id relative to the most recent tag in your git repo, pass `describe: true` as an option and the output of `git describe --tags` will be used instead: -This tool is necessary if you're running multiple instances of your Next.js app on different servers sitting behind a load balancer without session affinity. Otherwise, if your Next.js builds end up with different build ids, a client loading content from different servers can result in [this Next.js error](https://github.com/zeit/next.js/blob/52ccc14059673508803f96ef1c74eecdf27fe096/server/index.js#L444), which causes the app to blow up for that client. - -This module updates/overrides the following: - -- uuid defined in `.next/BUILD_ID` -- hashes for all chunks defined in `.next/build-stats.json` (Next.js 4 or below) - -By default, this CLI/module will overwrite those values with the hash of the latest git commit (`git rev-parse HEAD`), but it will also allow you to define your own id. - -If you have `distDir` defined in a `next.config.js` file, it will be respected. Otherwise, this module assumes the Next.js build output is in a relative `.next` directory. - -## Install - -```console -$ npm i --save next-build-id -``` - -## CLI Usage - -Modify your build script to run `next-build-id` after `next build` (only needed for production builds). - -For instance, if you have an npm run script in package.json that looks like this: - -```json -{ - "scripts": { - "build": "next build" - } +```js +// next.config.js +const nextBuildId = require('next-build-id') +module.exports = { + generateBuildId: () => nextBuildId({ dir: __dirname, describe: true }) } +// => 'v1.0.0' (no changes since v1.0.0 tag) +// => 'v1.0.0-19-ga8f7eee' (19 changes since v1.0.0 tag) ``` -You can change it to this: +This module also exposes a synchronous version for custom needs, e.g. passing the build id directly to a Sentry configuration. Just call `nextBuildId.sync({ dir: __dirname })` instead. -```json -{ - "scripts": { - "build": "next build && next-build-id" - } -} -``` +## Why? -The above example will use the hash of the latest git commit as the build id. If you'd like to define your own build id, pass it to the CLI using the `--id` flag: +If you're running multiple instances of your app sitting behind a load balancer without session affinity (and you're building your app directly on each production server instead of pre-packaging it), a tool like this is necessary to avoid Next.js errors like ["invalid build file hash"](https://github.com/zeit/next.js/blob/52ccc14059673508803f96ef1c74eecdf27fe096/server/index.js#L444), which happens when the same client (browser code) talks to multiple server backends (Node server) that have different build ids. -```json -{ - "scripts": { - "build": "next build && next-build-id --id $MY_CUSTOM_ID" - } -} -``` +The build id used by your app is stored on the file system in a `BUILD_ID` text file in your build directory, which is `.next` by default. -If you are building a directory other than the project root, you can pass that as an argument, just like you do with `next build`: +## Install -```json -{ - "scripts": { - "build": "next build client && next-build-id client" - } -} +```console +$ npm i next-build-id ``` -## Module Usage +## API -This module exports a single function that accepts an options object and returns a `Promise`. +This module exports two functions, one that is asynchronous (`nextBuildId()` primary export) and one that is synchronous (`nextBuildId.sync()`). Both functions accept a single options object, supporting the same options listed below. Both functions return (or resolve to) a string, representing the git-based build id. The options supported are: -- `dir` (string): the directory built by `next build` -- `write` (boolean): whether to overwrite the BUILD_ID in the dist dir (not needed when using `generateBuildId` in next.config.js) -- `id` (string): define a custom id instead of deferring to `git rev-parse HEAD` +- `dir` (string, default `process.cwd()`): a directory within the local git repository -The returned `Promise` resolves to a result object containing: + Using `__dirname` from your next.config.js module is generally safe. The default value is assumed to be the directory from which you are running the `next build` command, but this may not be correct based on how you build your Next.js app. -- `inputDir` (string): the resolved path of the Next.js app -- `outputDir` (string): the resolved path of the `next build` output -- `id` (string): the build id used -- `files` (array of strings): resolved paths of each file updated with the build id +- `describe` (boolean, default `false`): use git tag description instead of latest commit sha -Example: + Specify this as `true` to use `git describe --tags` instead of `git rev-parse HEAD` for generating the build id. If there are no tags in your local git repository, the latest commit sha will be used instead, unless you also specify `fallbackToSha: false`. -```js -const nextBuildId = require('next-build-id') +- `fallbackToSha` (boolean, default `true`): fallback to latest commit sha when `describe: true` and no tags exist -const opts = {} -// opts.dir = '/path/to/input/dir' -// opts.write = true -// opts.id = 'my_custom_id' - -nextBuildId(opts).then(result => { - console.log('success!') - console.log('input dir:', result.inputDir) - console.log('output dir:', result.outputDir) - console.log('build id:', result.id) - console.log('updated files:', result.files) -}).catch(err => { - console.error('you broke it', err) -}) -``` + Only applies when using `describe: true`. If you want to be strict about requiring the use (and presence) of tags, then disable this with `fallbackToSha: false`, in which case an error will be thrown if no tags exist. + +Note that this module really provides a generic way to get an id or status string for any local git repository, meaning it is not directly tied to Next.js in any way - it just depends on how you use it. ## Reference diff --git a/cli.js b/cli.js deleted file mode 100755 index 6db05f9..0000000 --- a/cli.js +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env node -'use strict' - -const path = require('path') - -require('sywac') - .positional('[dir]', { - paramsDesc: 'The directory containing the Next.js app' - }) - .string('-i, --id ', { desc: 'Specify the build id to use' }) - .help('-h, --help', { implicitCommand: false }) - .version('-v, --version', { implicitCommand: false }) - .outputSettings({ maxWidth: 60 }) - .parseAndExit() - .then(argv => { - argv.write = true - argv.dir = path.resolve(process.cwd(), argv.dir || '.') - const nextBuildId = require('./index') - return nextBuildId(argv) - }) - .then(result => { - // if (!result.id || !result.files.length) { - // console.error(result.id ? 'Could not find files to update' : 'Could not determine build id') - // process.exitCode = 1 - // return - // } - console.log(`Build ID: ${result.id}`) - result.files.forEach(file => console.log(` Updated: ${path.relative(process.cwd(), file)}`)) - }) - .catch(err => { - if (typeof err === 'string') console.error(err) - else console.error('Unexpected error:', err) - process.exitCode = 1 - }) diff --git a/index.js b/index.js index f009010..7852f60 100644 --- a/index.js +++ b/index.js @@ -1,151 +1,176 @@ 'use strict' +// eagerly load libs that are always used const fs = require('fs') const path = require('path') -const buildIdFiles = ['BUILD_ID', 'build-stats.json'] - -// from https://github.com/zeit/next.js/blob/canary/server/config.js -const defaultConfig = { - webpack: null, - webpackDevMiddleware: null, - poweredByHeader: true, - distDir: '.next', - assetPrefix: '', - configOrigin: 'default', - useFileSystemPublicRoutes: true, - generateBuildId: () => '9f2a37be-4545-445e-91bd-' + String(new Date().getTime()).slice(1, 13), - generateEtags: true, - pageExtensions: ['jsx', 'js'] +// lazily load libs that might not be used (depending on async/sync and opts.describe) +let _cp, _util +function cp () { + if (!_cp) _cp = require('child_process') + return _cp +} +function util () { + if (!_util) _util = require('util') + return _util } -module.exports = function nextBuildId (opts) { - // TODO support opts.conf object similar to next(opts) ?? - opts = opts || {} - const result = {} - return resolveInputDir(opts.dir) - .then(inputDir => { - result.inputDir = inputDir - return opts.write && resolveOutputDir(inputDir) - }) - .then(outputDir => { - result.outputDir = outputDir || null - return opts.write && getFiles(outputDir) - }) - .then(files => { - result.files = files || null - return determineBuildId(opts.id, result.inputDir) - }) - .then(id => { - result.id = id - return opts.write && updateFiles(id, result.files) - }) - .then(() => result) +// lazily load promisified functions that might not be used +let _access, _execFile, _readFile +function access () { + if (!_access) _access = util().promisify(fs.access) + return _access +} +function execFile () { + if (!_execFile) _execFile = util().promisify(cp().execFile) + return _execFile +} +function readFile () { + if (!_readFile) _readFile = util().promisify(fs.readFile) + return _readFile } -/* eslint-disable prefer-promise-reject-errors */ -function resolveInputDir (dir) { - let inputDir = dir || '.' - if (!path.isAbsolute(inputDir)) inputDir = path.resolve(path.dirname(require.main.filename), inputDir) - if (!fs.existsSync(inputDir)) return Promise.reject(`Input directory does not exist: ${inputDir}`) - return Promise.resolve(inputDir) +// functions to execute a git command +function gitArgs (dir, args) { + return [`--git-dir=${path.join(dir, '.git')}`, `--work-tree=${dir}`].concat(args) +} +async function git (dir, args) { + const { stdout, stderr } = await execFile()('git', gitArgs(dir, args)) + if (stderr) throw new Error(String(stderr).trim()) + return String(stdout).trim() +} +function gitSync (dir, args) { + const stdout = cp().execFileSync('git', gitArgs(dir, args)) + return String(stdout).trim() } -function resolveOutputDir (inputDir) { - let outputDir = defaultConfig.distDir - const nextConfigFile = path.join(inputDir, 'next.config.js') - // avoid slow require if file doesn't exist where it should - if (fs.existsSync(nextConfigFile)) { - try { - const userConfigModule = require(nextConfigFile) - let userConfig = userConfigModule.default || userConfigModule - if (typeof userConfigModule === 'function') { - userConfig = userConfigModule('phase-production-build', { defaultConfig }) - } - if (userConfig && userConfig.distDir) outputDir = userConfig.distDir - } catch (e) {} - } - outputDir = path.resolve(inputDir, outputDir) - if (!fs.existsSync(outputDir)) return Promise.reject(`Output directory does not exist: ${outputDir}`) - return Promise.resolve(outputDir) +// functions to read a file +function pathToGitFile (dir, filename) { + return path.join(dir, '.git', filename) +} +async function readGitFile (dir, filename) { + const data = await readFile()(pathToGitFile(dir, filename), 'utf8') + return String(data).trim() +} +function readGitFileSync (dir, filename) { + const data = fs.readFileSync(pathToGitFile(dir, filename), 'utf8') + return String(data).trim() } -function getFiles (outputDir) { - const files = buildIdFiles.map(file => { - file = path.join(outputDir, file) - return fs.existsSync(file) ? file : null - }).filter(Boolean) - if (!files.length) return Promise.reject(`Could not find ${buildIdFiles.join(' or ')} in output directory: ${outputDir}`) - return Promise.resolve(files) +function getOpts (opts) { + return { fallbackToSha: true, ...opts } } -function determineBuildId (id, inputDir) { - if (id) return Promise.resolve(id) - return new Promise((resolve, reject) => { - const cp = require('child_process') - - // inputDir may not be the project root so look for .git dir in parent dirs too - let dir = inputDir - const root = path.parse(dir).root - let attempts = 0 // protect against infinite tight loop if libs misbehave - while (dir !== root && attempts < 999) { - attempts++ - try { - fs.accessSync(path.join(dir, '.git'), (fs.constants || fs).R_OK) - break - } catch (_) { - dir = path.dirname(dir) - } +// valid opts: +// - dir (string): in case `process.cwd()` isn't suitable +// - describe (boolean): use `git describe --tags` instead of `git rev-parse HEAD` +// - fallbackToSha (boolean): if opts.describe and no tags found, fallback to latest commit sha +const nextBuildId = async opts => { + opts = getOpts(opts) + + const inputDir = path.resolve(process.cwd(), opts.dir || '.') + let dir = inputDir + + // dir may not be the project root so look for .git dir in parent dirs too + const root = path.parse(dir).root + let attempts = 0 // protect against infinite tight loop if libs misbehave + while (dir !== root && attempts < 999) { + attempts++ + try { + await access()(path.join(dir, '.git'), fs.constants.R_OK) + break + } catch (_) { + dir = path.dirname(dir) } - if (dir === root || attempts >= 999) dir = inputDir - - cp.execFile('git', [`--git-dir=${path.join(dir, '.git')}`, `--work-tree=${dir}`, 'rev-parse', 'HEAD'], (err, stdout, stderr) => { - if (err) return reject(err) - if (stderr) return reject(String(stderr).trim()) - if (stdout) return resolve(String(stdout).trim()) - reject(`No output from command: git --git-dir=${path.join(dir, '.git')} --work-tree=${dir} rev-parse HEAD`) - }) - }) -} + } + if (dir === root || attempts >= 999) dir = inputDir -function updateFiles (id, files) { - return Promise.all(files.map(file => { - return path.extname(file) === '.json' ? updateJsonFile(id, file) : updateTextFile(id, file) - })) -} + // if opts.describe, use `git describe --tags` + let id + if (opts.describe) { + try { + id = await git(dir, ['describe', '--tags']) + if (!id) throw new Error('Output of `git describe --tags` was empty!') + return id + } catch (err) { + if (!opts.fallbackToSha) throw err + } + } -function updateJsonFile (id, file) { - return new Promise((resolve, reject) => { - fs.readFile(file, 'utf8', (err, data) => { - if (err) return reject(err) - let write = false - let json - try { - json = JSON.parse(data) - } catch (e) { - return reject(e) - } - Object.keys(json).forEach(key => { - if (json[key] && json[key].hash) { - write = true - json[key].hash = id - } - }) - if (!write) return reject(`No hash values found in ${file}`) - fs.writeFile(file, JSON.stringify(json), error => { - if (error) return reject(error) - resolve() - }) - }) - }) + // try file system, suggestion by @sheerun here: https://github.com/nexdrew/next-build-id/issues/17#issuecomment-482799872 + // 1. read .git/HEAD to find out ref, the result is something like `ref: refs/heads/master` + // 2. read this file to find our current commit (e.g. .git/refs/heads/master) + try { + const head = await readGitFile(dir, 'HEAD') + let refi = head.indexOf('ref:') + if (refi === -1) refi = 0 + const endi = head.indexOf('\n', refi + 4) + 1 + const ref = head.slice(refi + 4, endi || undefined).trim() + if (ref) { + id = await readGitFile(dir, ref) + if (id) return id + } + } catch (_) {} + + // fallback to `git rev-parse HEAD` + id = await git(dir, ['rev-parse', 'HEAD']) + if (!id) throw new Error('Output of `git rev-parse HEAD` was empty!') + + return id } -function updateTextFile (id, file) { - return new Promise((resolve, reject) => { - fs.writeFile(file, id, err => { - if (err) return reject(err) - resolve() - }) - }) +nextBuildId.sync = opts => { + opts = getOpts(opts) + + const inputDir = path.resolve(process.cwd(), opts.dir || '.') + let dir = inputDir + + // dir may not be the project root so look for .git dir in parent dirs too + const root = path.parse(dir).root + let attempts = 0 // protect against infinite tight loop if libs misbehave + while (dir !== root && attempts < 999) { + attempts++ + try { + fs.accessSync(path.join(dir, '.git'), fs.constants.R_OK) + break + } catch (_) { + dir = path.dirname(dir) + } + } + if (dir === root || attempts >= 999) dir = inputDir + + // if opts.describe, use `git describe --tags` + let id + if (opts.describe) { + try { + id = gitSync(dir, ['describe', '--tags']) + if (!id) throw new Error('Output of `git describe --tags` was empty!') + return id + } catch (err) { + if (!opts.fallbackToSha) throw err + } + } + + // try file system, suggestion by @sheerun here: https://github.com/nexdrew/next-build-id/issues/17#issuecomment-482799872 + // 1. read .git/HEAD to find out ref, the result is something like `ref: refs/heads/master` + // 2. read this file to find our current commit (e.g. .git/refs/heads/master) + try { + const head = readGitFileSync(dir, 'HEAD') + let refi = head.indexOf('ref:') + if (refi === -1) refi = 0 + const endi = head.indexOf('\n', refi + 4) + 1 + const ref = head.slice(refi + 4, endi || undefined).trim() + if (ref) { + id = readGitFileSync(dir, ref) + if (id) return id + } + } catch (_) {} + + // fallback to `git rev-parse HEAD` + id = gitSync(dir, ['rev-parse', 'HEAD']) + if (!id) throw new Error('Output of `git rev-parse HEAD` was empty!') + + return id } -/* eslint-enable prefer-promise-reject-errors */ + +module.exports = nextBuildId diff --git a/package.json b/package.json index 2dfcb65..5c55b74 100644 --- a/package.json +++ b/package.json @@ -1,21 +1,19 @@ { "name": "next-build-id", "version": "2.0.1", - "description": "Override `next build` output to use a consistent build id", + "description": "Use a consistent, git-based build id for your Next.js app", "main": "index.js", - "bin": "cli.js", "files": [ - "cli.js", "index.js" ], "scripts": { "pretest": "standard", - "test": "tap --cov test/test*.js", + "test": "tap --cov --timeout=240 test/test-next6.js test/test-next7.js test/test-next8.js test/test-next9.js", "coverage": "nyc report --reporter=text-lcov | coveralls", "release": "standard-version" }, "engines": { - "node": ">=4" + "node": ">=8" }, "repository": { "type": "git", @@ -26,11 +24,11 @@ "next.js", "build", "id", - "build-stats", "BUILD_ID", - "fix", - "hack", - "cli" + "git", + "head", + "describe", + "tags" ], "author": "nexdrew", "license": "ISC", @@ -38,14 +36,13 @@ "url": "https://github.com/nexdrew/next-build-id/issues" }, "homepage": "https://github.com/nexdrew/next-build-id#readme", - "dependencies": { - "sywac": "^1.2.0" - }, + "dependencies": {}, "devDependencies": { - "coveralls": "^3.0.1", - "rimraf": "^2.6.2", - "standard": "^13.0.1", - "standard-version": "^6.0.1", - "tap": "^12.0.1" + "coveralls": "^3.0.6", + "rimraf": "^3.0.0", + "standard": "^13.1.0", + "standard-version": "^7.0.0", + "sywac": "^1.2.2", + "tap": "^14.6.1" } } diff --git a/test/fixture-next5/next.config.js b/test/fixture-next5/next.config.js deleted file mode 100644 index 0ef9778..0000000 --- a/test/fixture-next5/next.config.js +++ /dev/null @@ -1,24 +0,0 @@ -const withPlugins = require('next-compose-plugins') -const images = require('next-images') -const sass = require('@zeit/next-sass') - -// next.js configuration -const nextConfig = { - useFileSystemPublicRoutes: false, - distDir: 'build' -} - -module.exports = withPlugins([ - - // add a plugin with specific configuration - [sass, { - cssModules: true, - cssLoaderOptions: { - localIdentName: '[local]___[hash:base64:5]' - } - }], - - // add a plugin without a configuration - images - -], nextConfig) diff --git a/test/fixture-next5/package.json b/test/fixture-next5/package.json deleted file mode 100644 index 08ce8eb..0000000 --- a/test/fixture-next5/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "fixture", - "version": "1.0.0", - "description": "Fixture to test against Next.js 5", - "private": true, - "main": "index.js", - "scripts": { - "build": "next build" - }, - "author": "", - "license": "ISC", - "dependencies": { - "@zeit/next-sass": "^0.2.0", - "next": "^5.1.0", - "next-compose-plugins": "^2.1.1", - "next-images": "^0.10.2", - "react": "^16.3.2", - "react-dom": "^16.3.2" - } -} diff --git a/test/fixture-next6/dotgit/HEAD b/test/fixture-next6/dotgit/HEAD new file mode 100644 index 0000000..cb089cd --- /dev/null +++ b/test/fixture-next6/dotgit/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/test/fixture-next6/dotgit/refs/heads/master b/test/fixture-next6/dotgit/refs/heads/master new file mode 100644 index 0000000..bb3cec6 --- /dev/null +++ b/test/fixture-next6/dotgit/refs/heads/master @@ -0,0 +1 @@ +0123456789abcdef0123456789abcdef0123fff6 diff --git a/test/fixture-next6/next.config.js b/test/fixture-next6/next.config.js index 58100a8..d565242 100644 --- a/test/fixture-next6/next.config.js +++ b/test/fixture-next6/next.config.js @@ -1,8 +1,9 @@ const nextBuildId = require('../../index') -module.exports = (phase, nextConfig) => { - if (process.env.NBI_TEST_CALL_DEFAULT_GENERATEBUILDID) console.log(nextConfig.defaultConfig.generateBuildId()) +module.exports = () => { + const opts = { dir: __dirname } + if (process.env.NBI_TEST_DESCRIBE) opts.describe = true return { - generateBuildId: () => nextBuildId({ dir: __dirname }).then(result => result.id) + generateBuildId: () => process.env.NBI_TEST_SYNC ? nextBuildId.sync(opts) : nextBuildId(opts) } } diff --git a/test/fixture-next6/package.json b/test/fixture-next6/package.json index 069c286..1d965f5 100644 --- a/test/fixture-next6/package.json +++ b/test/fixture-next6/package.json @@ -10,7 +10,7 @@ "author": "", "license": "ISC", "dependencies": { - "next": "^6.0.3", + "next": "^6.1.2", "react": "^16.4.0", "react-dom": "^16.4.0" } diff --git a/test/fixture-next7/dotgit/HEAD b/test/fixture-next7/dotgit/HEAD new file mode 100644 index 0000000..cb089cd --- /dev/null +++ b/test/fixture-next7/dotgit/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/test/fixture-next7/dotgit/refs/heads/master b/test/fixture-next7/dotgit/refs/heads/master new file mode 100644 index 0000000..dcc1c71 --- /dev/null +++ b/test/fixture-next7/dotgit/refs/heads/master @@ -0,0 +1 @@ +0123456789abcdef0123456789abcdef0123fff7 diff --git a/test/fixture-next7/next.config.js b/test/fixture-next7/next.config.js new file mode 100644 index 0000000..d565242 --- /dev/null +++ b/test/fixture-next7/next.config.js @@ -0,0 +1,9 @@ +const nextBuildId = require('../../index') + +module.exports = () => { + const opts = { dir: __dirname } + if (process.env.NBI_TEST_DESCRIBE) opts.describe = true + return { + generateBuildId: () => process.env.NBI_TEST_SYNC ? nextBuildId.sync(opts) : nextBuildId(opts) + } +} diff --git a/test/fixture/package.json b/test/fixture-next7/package.json similarity index 59% rename from test/fixture/package.json rename to test/fixture-next7/package.json index 2f058f8..d293153 100644 --- a/test/fixture/package.json +++ b/test/fixture-next7/package.json @@ -1,7 +1,7 @@ { "name": "fixture", "version": "1.0.0", - "description": "Fixture web app, don't worry about it", + "description": "Fixture to test against Next.js 7", "private": true, "main": "index.js", "scripts": { @@ -10,8 +10,8 @@ "author": "", "license": "ISC", "dependencies": { - "next": "^4.2.3", - "react": "^16.3.1", - "react-dom": "^16.3.1" + "next": "^7.0.3", + "react": "^16.0.0", + "react-dom": "^16.0.0" } } diff --git a/test/fixture-next5/pages/index.js b/test/fixture-next7/pages/index.js similarity index 100% rename from test/fixture-next5/pages/index.js rename to test/fixture-next7/pages/index.js diff --git a/test/fixture-next8/dotgit/HEAD b/test/fixture-next8/dotgit/HEAD new file mode 100644 index 0000000..cb089cd --- /dev/null +++ b/test/fixture-next8/dotgit/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/test/fixture-next8/dotgit/refs/heads/master b/test/fixture-next8/dotgit/refs/heads/master new file mode 100644 index 0000000..b85e1aa --- /dev/null +++ b/test/fixture-next8/dotgit/refs/heads/master @@ -0,0 +1 @@ +0123456789abcdef0123456789abcdef0123fff8 diff --git a/test/fixture-next8/next.config.js b/test/fixture-next8/next.config.js new file mode 100644 index 0000000..d565242 --- /dev/null +++ b/test/fixture-next8/next.config.js @@ -0,0 +1,9 @@ +const nextBuildId = require('../../index') + +module.exports = () => { + const opts = { dir: __dirname } + if (process.env.NBI_TEST_DESCRIBE) opts.describe = true + return { + generateBuildId: () => process.env.NBI_TEST_SYNC ? nextBuildId.sync(opts) : nextBuildId(opts) + } +} diff --git a/test/fixture-next8/package.json b/test/fixture-next8/package.json new file mode 100644 index 0000000..e5df218 --- /dev/null +++ b/test/fixture-next8/package.json @@ -0,0 +1,17 @@ +{ + "name": "fixture", + "version": "1.0.0", + "description": "Fixture to test against Next.js 8", + "private": true, + "main": "index.js", + "scripts": { + "build": "next build" + }, + "author": "", + "license": "ISC", + "dependencies": { + "next": "^8.1.0", + "react": "^16.6.0", + "react-dom": "^16.6.0" + } +} diff --git a/test/fixture/pages/index.js b/test/fixture-next8/pages/index.js similarity index 100% rename from test/fixture/pages/index.js rename to test/fixture-next8/pages/index.js diff --git a/test/fixture-next9/dotgit/HEAD b/test/fixture-next9/dotgit/HEAD new file mode 100644 index 0000000..cb089cd --- /dev/null +++ b/test/fixture-next9/dotgit/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/test/fixture-next9/dotgit/refs/heads/master b/test/fixture-next9/dotgit/refs/heads/master new file mode 100644 index 0000000..06d2797 --- /dev/null +++ b/test/fixture-next9/dotgit/refs/heads/master @@ -0,0 +1 @@ +0123456789abcdef0123456789abcdef0123fff9 diff --git a/test/fixture-next9/next.config.js b/test/fixture-next9/next.config.js new file mode 100644 index 0000000..08c562e --- /dev/null +++ b/test/fixture-next9/next.config.js @@ -0,0 +1,10 @@ +const nextBuildId = require('../../index') + +module.exports = () => { + const opts = { dir: __dirname } + if (process.env.NBI_TEST_DESCRIBE) opts.describe = true + if (process.env.NBI_TEST_F2S_FALSE) opts.fallbackToSha = false + return { + generateBuildId: () => process.env.NBI_TEST_SYNC ? nextBuildId.sync(opts) : nextBuildId(opts) + } +} diff --git a/test/fixture-next9/package.json b/test/fixture-next9/package.json new file mode 100644 index 0000000..764e702 --- /dev/null +++ b/test/fixture-next9/package.json @@ -0,0 +1,17 @@ +{ + "name": "fixture", + "version": "1.0.0", + "description": "Fixture to test against Next.js 9", + "private": true, + "main": "index.js", + "scripts": { + "build": "next build" + }, + "author": "", + "license": "ISC", + "dependencies": { + "next": "^9.0.3", + "react": "^16.9.0", + "react-dom": "^16.9.0" + } +} diff --git a/test/fixture-next9/pages/index.js b/test/fixture-next9/pages/index.js new file mode 100644 index 0000000..e346f85 --- /dev/null +++ b/test/fixture-next9/pages/index.js @@ -0,0 +1 @@ +export default () =>
Welcome to next.js!
diff --git a/test/fixture2/.next/build-stats.json b/test/fixture2/.next/build-stats.json deleted file mode 100644 index e69de29..0000000 diff --git a/test/fixture3/.next/BUILD_ID b/test/fixture3/.next/BUILD_ID deleted file mode 100644 index e69de29..0000000 diff --git a/test/fixture3/.next/build-stats.json b/test/fixture3/.next/build-stats.json deleted file mode 100644 index e69de29..0000000 diff --git a/test/git b/test/git index 61e90b6..ec3f370 100755 --- a/test/git +++ b/test/git @@ -1,12 +1,16 @@ #!/usr/bin/env node // --git-dir=${inputDir}/.git --work-tree=${inputDir} +// rev-parse HEAD +// describe --tags require('sywac') .dir('--git-dir ', { mustExist: true }) + .dir('--work-tree ', { mustExist: true }) .outputSettings({ showHelpOnError: false }) .parseAndExit() .then(argv => { - if (!process.env.NBI_TEST_FIXTURE || !String(process.env.NBI_TEST_FIXTURE).includes('fixture3')) { - console.log('0123456789abcdef0123456789abcdef01234567') + if (argv._[0] === 'describe') { + return process.env.NBI_TEST_F2S_FALSE ? null : console.log('v2.0.1-12-g0123456') } + console.log('0123456789abcdef0123456789abcdef01234567') }) diff --git a/test/test-fixture.js b/test/test-fixture.js deleted file mode 100644 index 27ab5d4..0000000 --- a/test/test-fixture.js +++ /dev/null @@ -1,126 +0,0 @@ -'use strict' - -const path = require('path') - -const tap = require('tap') -const rimraf = require('rimraf') - -const utils = require('./utils') - -const exec = utils.exec -const cli = utils.cli -const readTextFile = utils.readTextFile -const readJsonFile = utils.readJsonFile -const writeTextFile = utils.writeTextFile -const fixturePath = path.resolve(__dirname, 'fixture') - -let npmi = false -tap.beforeEach(() => { - if (npmi) return Promise.resolve() - npmi = true - return exec('npm', 'i', fixturePath) -}) - -tap.afterEach(() => { - return Promise.all(['.next', 'nextoutput', 'next.config.js'].map(dir => { - return new Promise((resolve, reject) => { - rimraf(path.resolve(fixturePath, dir), err => { - if (err) return reject(err) - resolve() - }) - }) - })) -}) - -tap.test('updates .next in cwd by default', t => { - return exec('npm', 'run build', fixturePath).then(() => { - return cli('', fixturePath) - }).then(io => { - t.notOk(io.err) - t.notOk(io.stderr) - t.match(io.stdout, /Build ID: 0123456789abcdef0123456789abcdef01234567/) - t.match(io.stdout, /Updated:.*BUILD_ID/) - t.match(io.stdout, /Updated:.*build-stats.json/) - return readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) - }).then(buildId => { - t.equal(buildId, '0123456789abcdef0123456789abcdef01234567') - return readJsonFile(path.resolve(fixturePath, '.next', 'build-stats.json')) - }).then(stats => { - t.equal(stats['app.js'].hash, '0123456789abcdef0123456789abcdef01234567') - }) -}) - -tap.test('supports relative input dir', t => { - return exec('npm', 'run build', fixturePath).then(() => { - return cli('fixture') - }).then(io => { - t.notOk(io.err) - t.notOk(io.stderr) - t.match(io.stdout, /Build ID: 0123456789abcdef0123456789abcdef01234567/) - t.match(io.stdout, /Updated:.*BUILD_ID/) - t.match(io.stdout, /Updated:.*build-stats.json/) - return readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) - }).then(buildId => { - t.equal(buildId, '0123456789abcdef0123456789abcdef01234567') - return readJsonFile(path.resolve(fixturePath, '.next', 'build-stats.json')) - }).then(stats => { - t.equal(stats['app.js'].hash, '0123456789abcdef0123456789abcdef01234567') - }) -}) - -tap.test('supports custom --id', t => { - return exec('npm', 'run build', fixturePath).then(() => { - return cli('--id abcxyz', fixturePath) - }).then(io => { - t.notOk(io.err) - t.notOk(io.stderr) - t.match(io.stdout, /Build ID: abcxyz/) - t.match(io.stdout, /Updated:.*BUILD_ID/) - t.match(io.stdout, /Updated:.*build-stats.json/) - return readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) - }).then(buildId => { - t.equal(buildId, 'abcxyz') - return readJsonFile(path.resolve(fixturePath, '.next', 'build-stats.json')) - }).then(stats => { - t.equal(stats['app.js'].hash, 'abcxyz') - }) -}) - -tap.test('does not need git with custom --id', t => { - return exec('npm', 'run build', fixturePath).then(() => { - return cli('--id 123456', fixturePath, { PATH: '/dne' }) - }).then(io => { - t.notOk(io.err) - t.notOk(io.stderr) - t.match(io.stdout, /Build ID: 123456/) - t.match(io.stdout, /Updated:.*BUILD_ID/) - t.match(io.stdout, /Updated:.*build-stats.json/) - return readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) - }).then(buildId => { - t.equal(buildId, '123456') - return readJsonFile(path.resolve(fixturePath, '.next', 'build-stats.json')) - }).then(stats => { - t.equal(stats['app.js'].hash, '123456') - }) -}) - -tap.test('with next.config.js that defines distDir', t => { - return writeTextFile(path.resolve(fixturePath, 'next.config.js'), `module.exports = { distDir: 'nextoutput' }`) - .then(() => exec('npm', 'run build', fixturePath)) - .then(() => cli('', fixturePath)) - .then(io => { - t.notOk(io.err) - t.notOk(io.stderr) - t.match(io.stdout, /Build ID: 0123456789abcdef0123456789abcdef01234567/) - t.match(io.stdout, /Updated:.*BUILD_ID/) - t.match(io.stdout, /Updated:.*build-stats.json/) - return readTextFile(path.resolve(fixturePath, 'nextoutput', 'BUILD_ID')) - }) - .then(buildId => { - t.equal(buildId, '0123456789abcdef0123456789abcdef01234567') - return readJsonFile(path.resolve(fixturePath, 'nextoutput', 'build-stats.json')) - }) - .then(stats => { - t.equal(stats['app.js'].hash, '0123456789abcdef0123456789abcdef01234567') - }) -}) diff --git a/test/test-next5.js b/test/test-next5.js deleted file mode 100644 index e4553e4..0000000 --- a/test/test-next5.js +++ /dev/null @@ -1,43 +0,0 @@ -const path = require('path') - -const tap = require('tap') -const rimraf = require('rimraf') - -const utils = require('./utils') - -const exec = utils.exec -const cli = utils.cli -const readTextFile = utils.readTextFile -const fixturePath = path.resolve(__dirname, 'fixture-next5') - -let npmi = false -tap.beforeEach(() => { - if (npmi) return Promise.resolve() - npmi = true - return exec('npm', 'i', fixturePath) -}) - -tap.afterEach(() => { - return Promise.all(['build'].map(dir => { - return new Promise((resolve, reject) => { - rimraf(path.resolve(fixturePath, dir), err => { - if (err) return reject(err) - resolve() - }) - }) - })) -}) - -tap.test('with next.config.js that exports a function', t => { - return exec('npm', 'run build', fixturePath).then(() => { - return cli('', fixturePath) - }).then(io => { - t.notOk(io.err) - t.notOk(io.stderr) - t.match(io.stdout, /Build ID: 0123456789abcdef0123456789abcdef01234567/) - t.match(io.stdout, /Updated:.*BUILD_ID/) - return readTextFile(path.resolve(fixturePath, 'build', 'BUILD_ID')) - }).then(buildId => { - t.equal(buildId, '0123456789abcdef0123456789abcdef01234567') - }) -}) diff --git a/test/test-next6.js b/test/test-next6.js index 95ae8af..71f4394 100644 --- a/test/test-next6.js +++ b/test/test-next6.js @@ -1,58 +1,88 @@ const path = require('path') const tap = require('tap') -const rimraf = require('rimraf') const utils = require('./utils') const exec = utils.exec const mockedGitEnv = utils.mockedGitEnv +const rmrf = utils.rmrf const fixturePath = path.resolve(__dirname, 'fixture-next6') let npmi = false -tap.beforeEach(() => { - if (npmi) return Promise.resolve() - npmi = true - return exec('npm', 'i', fixturePath) +tap.beforeEach(async () => { + await rmrf(path.resolve(fixturePath, '.git')) // clean up if test previously failed + const setup = [exec('cp', '-R dotgit .git', fixturePath)] + if (!npmi) { + npmi = true + setup.push(exec('npm', 'i', fixturePath)) + } + return Promise.all(setup) }) tap.afterEach(() => { - return Promise.all(['.next'].map(dir => { - return new Promise((resolve, reject) => { - rimraf(path.resolve(fixturePath, dir), err => { - if (err) return reject(err) - resolve() - }) - }) - })) -}) - -tap.test('with next.config.js that uses a generateBuildId function', t => { - return exec('npm', 'run build', fixturePath, mockedGitEnv()) - .then(io => { - t.notOk(io.err) - t.notOk(io.stderr) - return utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) - }) - .then(buildId => { - t.equal(buildId, '0123456789abcdef0123456789abcdef01234567') - }) -}) - -tap.test('call stupid default generateBuildId for code coverage', t => { - return exec('npm', 'run build', fixturePath, mockedGitEnv()) - .then(() => { - return exec(path.resolve(__dirname, '..', 'cli.js'), '', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_CALL_DEFAULT_GENERATEBUILDID: true })) - }) - .then(io => { - t.notOk(io.err) - t.notOk(io.stderr) - t.match(io.stdout, /9f2a37be-4545-445e-91bd/) - t.match(io.stdout, /Build ID: 0123456789abcdef0123456789abcdef01234567/) - t.match(io.stdout, /Updated:.*BUILD_ID/) - return utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) - }) - .then(buildId => { - t.equal(buildId, '0123456789abcdef0123456789abcdef01234567') - }) + return Promise.all(['.next', '.git'].map(dir => rmrf(path.resolve(fixturePath, dir)))) +}) + +tap.test('async > default', async t => { + const io = await exec('npm', 'run build', fixturePath) + t.notOk(io.err) + t.notOk(io.stderr) + + const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) + t.equal(buildId, '0123456789abcdef0123456789abcdef0123fff6') // file system +}) + +tap.test('async > describe: true', async t => { + const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_DESCRIBE: true })) + t.notOk(io.err) + t.notOk(io.stderr) + + const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) + t.equal(buildId, 'v2.0.1-12-g0123456') // mocked git output +}) + +// *Note* only using mockedGitEnv() here to avoid this error during `npm run build`: +// The node binary used for scripts is /Users/abg/.node-spawn-wrap-98929-a7983ca02859/node +// but npm is using /usr/local/Cellar/node/12.7.0/bin/node itself. +// Use the `--scripts-prepend-node-path` option to include the path for +// the node binary npm was executed with. +tap.test('sync > default', async t => { + const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_SYNC: true })) + t.notOk(io.err) + t.notOk(io.stderr) + + const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) + t.equal(buildId, '0123456789abcdef0123456789abcdef0123fff6') // file system +}) + +tap.test('sync > describe: true', async t => { + const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_DESCRIBE: true, NBI_TEST_SYNC: true })) + t.notOk(io.err) + t.notOk(io.stderr) + + const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) + t.equal(buildId, 'v2.0.1-12-g0123456') // mocked git output +}) + +tap.test('async > fallback to rev-parse HEAD', async t => { + await rmrf(path.resolve(fixturePath, '.git', 'refs', 'heads', 'master')) + + const io = await exec('npm', 'run build', fixturePath, mockedGitEnv()) + t.notOk(io.err) + t.notOk(io.stderr) + + const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) + t.equal(buildId, '0123456789abcdef0123456789abcdef01234567') // mocked git output +}) + +tap.test('sync > fallback to rev-parse HEAD', async t => { + await rmrf(path.resolve(fixturePath, '.git', 'refs', 'heads', 'master')) + + const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_SYNC: true })) + t.notOk(io.err) + t.notOk(io.stderr) + + const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) + t.equal(buildId, '0123456789abcdef0123456789abcdef01234567') // mocked git output }) diff --git a/test/test-next7.js b/test/test-next7.js new file mode 100644 index 0000000..6707f32 --- /dev/null +++ b/test/test-next7.js @@ -0,0 +1,88 @@ +const path = require('path') + +const tap = require('tap') + +const utils = require('./utils') + +const exec = utils.exec +const mockedGitEnv = utils.mockedGitEnv +const rmrf = utils.rmrf +const fixturePath = path.resolve(__dirname, 'fixture-next7') + +let npmi = false +tap.beforeEach(async () => { + await rmrf(path.resolve(fixturePath, '.git')) // clean up if test previously failed + const setup = [exec('cp', '-R dotgit .git', fixturePath)] + if (!npmi) { + npmi = true + setup.push(exec('npm', 'i', fixturePath)) + } + return Promise.all(setup) +}) + +tap.afterEach(() => { + return Promise.all(['.next', '.git'].map(dir => rmrf(path.resolve(fixturePath, dir)))) +}) + +tap.test('async > default', async t => { + const io = await exec('npm', 'run build', fixturePath) + t.notOk(io.err) + t.notOk(io.stderr) + + const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) + t.equal(buildId, '0123456789abcdef0123456789abcdef0123fff7') // file system +}) + +tap.test('async > describe: true', async t => { + const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_DESCRIBE: true })) + t.notOk(io.err) + t.notOk(io.stderr) + + const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) + t.equal(buildId, 'v2.0.1-12-g0123456') // mocked git output +}) + +// *Note* only using mockedGitEnv() here to avoid this error during `npm run build`: +// The node binary used for scripts is /Users/abg/.node-spawn-wrap-98929-a7983ca02859/node +// but npm is using /usr/local/Cellar/node/12.7.0/bin/node itself. +// Use the `--scripts-prepend-node-path` option to include the path for +// the node binary npm was executed with. +tap.test('sync > default', async t => { + const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_SYNC: true })) + t.notOk(io.err) + t.notOk(io.stderr) + + const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) + t.equal(buildId, '0123456789abcdef0123456789abcdef0123fff7') // file system +}) + +tap.test('sync > describe: true', async t => { + const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_DESCRIBE: true, NBI_TEST_SYNC: true })) + t.notOk(io.err) + t.notOk(io.stderr) + + const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) + t.equal(buildId, 'v2.0.1-12-g0123456') // mocked git output +}) + +tap.test('async > fallback to rev-parse HEAD', async t => { + await rmrf(path.resolve(fixturePath, '.git', 'refs', 'heads', 'master')) + + const io = await exec('npm', 'run build', fixturePath, mockedGitEnv()) + t.notOk(io.err) + t.notOk(io.stderr) + + const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) + t.equal(buildId, '0123456789abcdef0123456789abcdef01234567') // mocked git output +}) + +tap.test('sync > fallback to rev-parse HEAD', async t => { + await rmrf(path.resolve(fixturePath, '.git', 'refs', 'heads', 'master')) + + const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_SYNC: true })) + t.notOk(io.err) + t.notOk(io.stderr) + + const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) + t.equal(buildId, '0123456789abcdef0123456789abcdef01234567') // mocked git output +}) diff --git a/test/test-next8.js b/test/test-next8.js new file mode 100644 index 0000000..9cd27cd --- /dev/null +++ b/test/test-next8.js @@ -0,0 +1,88 @@ +const path = require('path') + +const tap = require('tap') + +const utils = require('./utils') + +const exec = utils.exec +const mockedGitEnv = utils.mockedGitEnv +const rmrf = utils.rmrf +const fixturePath = path.resolve(__dirname, 'fixture-next8') + +let npmi = false +tap.beforeEach(async () => { + await rmrf(path.resolve(fixturePath, '.git')) // clean up if test previously failed + const setup = [exec('cp', '-R dotgit .git', fixturePath)] + if (!npmi) { + npmi = true + setup.push(exec('npm', 'i', fixturePath)) + } + return Promise.all(setup) +}) + +tap.afterEach(() => { + return Promise.all(['.next', '.git'].map(dir => rmrf(path.resolve(fixturePath, dir)))) +}) + +tap.test('async > default', async t => { + const io = await exec('npm', 'run build', fixturePath) + t.notOk(io.err) + t.notOk(io.stderr) + + const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) + t.equal(buildId, '0123456789abcdef0123456789abcdef0123fff8') // file system +}) + +tap.test('async > describe: true', async t => { + const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_DESCRIBE: true })) + t.notOk(io.err) + t.notOk(io.stderr) + + const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) + t.equal(buildId, 'v2.0.1-12-g0123456') // mocked git output +}) + +// *Note* only using mockedGitEnv() here to avoid this error during `npm run build`: +// The node binary used for scripts is /Users/abg/.node-spawn-wrap-98929-a7983ca02859/node +// but npm is using /usr/local/Cellar/node/12.7.0/bin/node itself. +// Use the `--scripts-prepend-node-path` option to include the path for +// the node binary npm was executed with. +tap.test('sync > default', async t => { + const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_SYNC: true })) + t.notOk(io.err) + t.notOk(io.stderr) + + const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) + t.equal(buildId, '0123456789abcdef0123456789abcdef0123fff8') // file system +}) + +tap.test('sync > describe: true', async t => { + const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_DESCRIBE: true, NBI_TEST_SYNC: true })) + t.notOk(io.err) + t.notOk(io.stderr) + + const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) + t.equal(buildId, 'v2.0.1-12-g0123456') // mocked git output +}) + +tap.test('async > fallback to rev-parse HEAD', async t => { + await rmrf(path.resolve(fixturePath, '.git', 'refs', 'heads', 'master')) + + const io = await exec('npm', 'run build', fixturePath, mockedGitEnv()) + t.notOk(io.err) + t.notOk(io.stderr) + + const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) + t.equal(buildId, '0123456789abcdef0123456789abcdef01234567') // mocked git output +}) + +tap.test('sync > fallback to rev-parse HEAD', async t => { + await rmrf(path.resolve(fixturePath, '.git', 'refs', 'heads', 'master')) + + const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_SYNC: true })) + t.notOk(io.err) + t.notOk(io.stderr) + + const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) + t.equal(buildId, '0123456789abcdef0123456789abcdef01234567') // mocked git output +}) diff --git a/test/test-next9.js b/test/test-next9.js new file mode 100644 index 0000000..430b65e --- /dev/null +++ b/test/test-next9.js @@ -0,0 +1,132 @@ +const path = require('path') + +const tap = require('tap') + +const utils = require('./utils') + +const exec = utils.exec +const mockedGitEnv = utils.mockedGitEnv +const rmrf = utils.rmrf +const fixturePath = path.resolve(__dirname, 'fixture-next9') + +let npmi = false +tap.beforeEach(async () => { + await rmrf(path.resolve(fixturePath, '.git')) // clean up if test previously failed + const setup = [exec('cp', '-R dotgit .git', fixturePath)] + if (!npmi) { + npmi = true + setup.push(exec('npm', 'i', fixturePath)) + } + return Promise.all(setup) +}) + +tap.afterEach(() => { + return Promise.all(['.next', '.git'].map(dir => rmrf(path.resolve(fixturePath, dir)))) +}) + +tap.test('async > default', async t => { + const io = await exec('npm', 'run build', fixturePath) + t.notOk(io.err) + t.notOk(io.stderr) + + const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) + t.equal(buildId, '0123456789abcdef0123456789abcdef0123fff9') // file system +}) + +tap.test('async > describe: true', async t => { + const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_DESCRIBE: true })) + t.notOk(io.err) + t.notOk(io.stderr) + + const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) + t.equal(buildId, 'v2.0.1-12-g0123456') // mocked git output +}) + +// *Note* only using mockedGitEnv() here to avoid this error during `npm run build`: +// The node binary used for scripts is /Users/abg/.node-spawn-wrap-98929-a7983ca02859/node +// but npm is using /usr/local/Cellar/node/12.7.0/bin/node itself. +// Use the `--scripts-prepend-node-path` option to include the path for +// the node binary npm was executed with. +tap.test('sync > default', async t => { + const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_SYNC: true })) + t.notOk(io.err) + t.notOk(io.stderr) + + const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) + t.equal(buildId, '0123456789abcdef0123456789abcdef0123fff9') // file system +}) + +tap.test('sync > describe: true', async t => { + const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { + NBI_TEST_DESCRIBE: true, + NBI_TEST_SYNC: true + })) + t.notOk(io.err) + t.notOk(io.stderr) + + const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) + t.equal(buildId, 'v2.0.1-12-g0123456') // mocked git output +}) + +tap.test('async > fallback to rev-parse HEAD', async t => { + await rmrf(path.resolve(fixturePath, '.git', 'refs', 'heads', 'master')) + + const io = await exec('npm', 'run build', fixturePath, mockedGitEnv()) + t.notOk(io.err) + t.notOk(io.stderr) + + const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) + t.equal(buildId, '0123456789abcdef0123456789abcdef01234567') // mocked git output +}) + +tap.test('sync > fallback to rev-parse HEAD', async t => { + await rmrf(path.resolve(fixturePath, '.git', 'refs', 'heads', 'master')) + + const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_SYNC: true })) + t.notOk(io.err) + t.notOk(io.stderr) + + const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) + t.equal(buildId, '0123456789abcdef0123456789abcdef01234567') // mocked git output +}) + +tap.test('async > no .git dir', async t => { + await rmrf(path.resolve(fixturePath, '.git')) + + const io = await exec('npm', 'run build', fixturePath, mockedGitEnv()) + t.notOk(io.err) + t.notOk(io.stderr) + + const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) + t.equal(buildId && buildId.length, 40) // latest sha from next-build-id repo +}) + +tap.test('sync > no .git dir', async t => { + await rmrf(path.resolve(fixturePath, '.git')) + + const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_SYNC: true })) + t.notOk(io.err) + t.notOk(io.stderr) + + const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) + t.equal(buildId && buildId.length, 40) // latest sha from next-build-id repo +}) + +tap.test('async > describe: true, fallbackToSha: false', async t => { + const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { + NBI_TEST_DESCRIBE: true, + NBI_TEST_F2S_FALSE: true + }), true) + t.ok(io.err) + t.match(io.stderr, /Output of `git describe --tags` was empty!/) +}) + +tap.test('sync > describe: true, fallbackToSha: false', async t => { + const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { + NBI_TEST_DESCRIBE: true, + NBI_TEST_F2S_FALSE: true, + NBI_TEST_SYNC: true + }), true) + t.ok(io.err) + t.match(io.stderr, /Output of `git describe --tags` was empty!/) +}) diff --git a/test/test-others.js b/test/test-others.js deleted file mode 100644 index ee61606..0000000 --- a/test/test-others.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict' - -const path = require('path') - -const tap = require('tap') - -const utils = require('./utils') - -const cli = utils.cli -const writeTextFile = utils.writeTextFile - -tap.test('errs on invalid json', t => { - const fixture2Path = path.resolve(__dirname, 'fixture2') - return writeTextFile(path.resolve(fixture2Path, '.next', 'BUILD_ID'), '') - .then(() => cli('', fixture2Path)) - .then(io => { - t.equal(io.err.code, 1) - t.match(io.stderr, /Unexpected error/) - t.notOk(io.stdout) - }) -}) - -tap.test('errs on no git output', t => { - return cli('', path.resolve(__dirname, 'fixture3'), { NBI_TEST_FIXTURE: 'fixture3' }).then(io => { - t.equal(io.err.code, 1) - t.match(io.stderr, /No output from command: git/) - t.notOk(io.stdout) - }) -}) - -tap.test('errs without --id and without git', t => { - return cli('', path.resolve(__dirname, 'fixture3'), { NBI_TEST_FIXTURE: 'fixture3', PATH: '/dne' }).then(io => { - t.equal(io.err.code, 1) - t.match(io.stderr, /Unexpected error/) - t.notOk(io.stdout) - }) -}) diff --git a/test/utils.js b/test/utils.js index 8addc82..5b0a663 100644 --- a/test/utils.js +++ b/test/utils.js @@ -4,7 +4,7 @@ const cp = require('child_process') const fs = require('fs') const path = require('path') -const cliPath = path.resolve(__dirname, '..', 'cli.js') +const rimraf = require('rimraf') function exec (file, args, cwd, env, alwaysResolve) { // console.error(`\nexec: ${file} ${args}`) @@ -27,42 +27,36 @@ function mockedGitEnv (env) { return Object.assign({}, process.env, env) } -function cli (args, cwd, env) { - return exec(cliPath, args, cwd, mockedGitEnv(env), true) -} - -function readTextFile (file) { +function rmrf (dirOrFile) { return new Promise((resolve, reject) => { - fs.readFile(file, 'utf8', (err, data) => { + rimraf(dirOrFile, err => { if (err) return reject(err) - resolve(data) + resolve() }) }) } -function readJsonFile (file) { +function readTextFile (file) { return new Promise((resolve, reject) => { fs.readFile(file, 'utf8', (err, data) => { if (err) return reject(err) - resolve(JSON.parse(data)) + resolve(data) }) }) } -function writeTextFile (file, data) { - return new Promise((resolve, reject) => { - fs.writeFile(file, data, error => { - if (error) return reject(error) - resolve() - }) - }) -} +// function writeTextFile (file, data) { +// return new Promise((resolve, reject) => { +// fs.writeFile(file, data, error => { +// if (error) return reject(error) +// resolve() +// }) +// }) +// } module.exports = { exec, mockedGitEnv, - cli, - readTextFile, - readJsonFile, - writeTextFile + rmrf, + readTextFile }