├── .gitignore ├── .npmrc ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── greenkeeper.json ├── index.js ├── package.json └── test ├── fixture-next6 ├── dotgit │ ├── HEAD │ └── refs │ │ └── heads │ │ └── master ├── next.config.js ├── package.json └── pages │ └── index.js ├── fixture-next7 ├── dotgit │ ├── HEAD │ └── refs │ │ └── heads │ │ └── master ├── next.config.js ├── package.json └── pages │ └── index.js ├── fixture-next8 ├── dotgit │ ├── HEAD │ └── refs │ │ └── heads │ │ └── master ├── next.config.js ├── package.json └── pages │ └── index.js ├── fixture-next9 ├── dotgit │ ├── HEAD │ └── refs │ │ └── heads │ │ └── master ├── next.config.js ├── package.json └── pages │ └── index.js ├── git ├── test-next6.js ├── test-next7.js ├── test-next8.js ├── test-next9.js └── utils.js /.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | node_modules/ 3 | .nyc_output/ 4 | .next/ 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 'node' 5 | - '10' 6 | - '8' 7 | after_success: npm run coverage 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [3.0.0](https://github.com/nexdrew/next-build-id/compare/v2.0.1...v3.0.0) (2019-08-16) 6 | 7 | 8 | ### ⚠ BREAKING CHANGES 9 | 10 | * Drops support for Next<6 and Node<8 (now using `async`/`await`), changes return value from object to string (to be more compatible with `generateBuildId`), removes hacky file overwriting and thus the CLI (file overwriting is no longer necessary for modern versions of Next). 11 | 12 | See README for further details. 13 | 14 | ### Features 15 | 16 | * add sync function, return string, add describe option, remove cli ([#23](https://github.com/nexdrew/next-build-id/issues/23)) ([5621290](https://github.com/nexdrew/next-build-id/commit/5621290)) 17 | 18 | 19 | ## [2.0.1](https://github.com/nexdrew/next-build-id/compare/v2.0.0...v2.0.1) (2018-05-25) 20 | 21 | 22 | ### Bug Fixes 23 | 24 | * look for .git dir in parents of input dir ([#7](https://github.com/nexdrew/next-build-id/issues/7)) ([bcc374b](https://github.com/nexdrew/next-build-id/commit/bcc374b)) 25 | 26 | 27 | 28 | 29 | # [2.0.0](https://github.com/nexdrew/next-build-id/compare/v1.2.0...v2.0.0) (2018-05-25) 30 | 31 | 32 | ### Features 33 | 34 | * use next-build-id as module within Next 6 generateBuildId function ([#6](https://github.com/nexdrew/next-build-id/issues/6)) ([502bccc](https://github.com/nexdrew/next-build-id/commit/502bccc)) 35 | 36 | 37 | ### BREAKING CHANGES 38 | 39 | * The next-build-id CLI has not changed, but module usage will no longer manually overwrite a BUILD_ID file unless a `write: true` option is given, so that the module can be used within generateBuildId from next.config.js to just lookup the current git commit hash. 40 | 41 | 42 | 43 | 44 | # [1.2.0](https://github.com/nexdrew/next-build-id/compare/v1.1.0...v1.2.0) (2018-05-24) 45 | 46 | 47 | ### Features 48 | 49 | * support Next.js v5 and "config as a function" ([#5](https://github.com/nexdrew/next-build-id/issues/5)) ([2b02037](https://github.com/nexdrew/next-build-id/commit/2b02037)) 50 | 51 | 52 | 53 | 54 | # [1.1.0](https://github.com/nexdrew/next-build-id/compare/v1.0.0...v1.1.0) (2018-04-08) 55 | 56 | 57 | ### Features 58 | 59 | * support git version <= 1.8.4 ([#2](https://github.com/nexdrew/next-build-id/issues/2)) ([2724677](https://github.com/nexdrew/next-build-id/commit/2724677)) 60 | 61 | 62 | 63 | 64 | # 1.0.0 (2017-12-13) 65 | 66 | 67 | ### Features 68 | 69 | * initial code ([3872c83](https://github.com/nexdrew/next-build-id/commit/3872c83)) 70 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017-2019 Andrew Goode 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose 4 | with or without fee is hereby granted, provided that the above copyright notice 5 | and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 9 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 11 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 12 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 13 | THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # next-build-id 2 | 3 | > Use a consistent, git-based build id for your Next.js app 4 | 5 | [![Build Status](https://travis-ci.org/nexdrew/next-build-id.svg?branch=master)](https://travis-ci.org/nexdrew/next-build-id) 6 | [![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) 7 | [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) 8 | [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org) 9 | [![Greenkeeper badge](https://badges.greenkeeper.io/nexdrew/next-build-id.svg)](https://greenkeeper.io/) 10 | 11 | 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. 12 | 13 | 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. 14 | 15 | By default, it will use the latest git commit hash from the local git repository (equivalent of `git rev-parse HEAD`): 16 | 17 | ```js 18 | // next.config.js 19 | const nextBuildId = require('next-build-id') 20 | module.exports = { 21 | generateBuildId: () => nextBuildId({ dir: __dirname }) 22 | } 23 | // => 'f9fc968afa249d162c924a8d5b4ce6562c164c2e' 24 | ``` 25 | 26 | 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: 27 | 28 | ```js 29 | // next.config.js 30 | const nextBuildId = require('next-build-id') 31 | module.exports = { 32 | generateBuildId: () => nextBuildId({ dir: __dirname, describe: true }) 33 | } 34 | // => 'v1.0.0' (no changes since v1.0.0 tag) 35 | // => 'v1.0.0-19-ga8f7eee' (19 changes since v1.0.0 tag) 36 | ``` 37 | 38 | 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. 39 | 40 | ## Why? 41 | 42 | 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. 43 | 44 | 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. 45 | 46 | ## Install 47 | 48 | ```console 49 | $ npm i next-build-id 50 | ``` 51 | 52 | ## API 53 | 54 | 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. 55 | 56 | The options supported are: 57 | 58 | - `dir` (string, default `process.cwd()`): a directory within the local git repository 59 | 60 | 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. 61 | 62 | - `describe` (boolean, default `false`): use git tag description instead of latest commit sha 63 | 64 | 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`. 65 | 66 | - `fallbackToSha` (boolean, default `true`): fallback to latest commit sha when `describe: true` and no tags exist 67 | 68 | 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. 69 | 70 | 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. 71 | 72 | ## Reference 73 | 74 | - [zeit/next.js#786](https://github.com/zeit/next.js/issues/786) 75 | - [zeit/next.js#2978 (comment)](https://github.com/zeit/next.js/issues/2978#issuecomment-334849384) 76 | - [zeit/next.js#3299 (comment)](https://github.com/zeit/next.js/issues/3299#issuecomment-344973091) 77 | - ["Handle BUILD_ID Mismatch Error" on Next.js wiki](https://github.com/zeit/next.js/wiki/Handle-BUILD_ID-Mismatch-Error) 78 | 79 | ## License 80 | 81 | ISC © Andrew Goode 82 | -------------------------------------------------------------------------------- /greenkeeper.json: -------------------------------------------------------------------------------- 1 | { 2 | "groups": { 3 | "default": { 4 | "packages": [ 5 | "package.json" 6 | ] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // eagerly load libs that are always used 4 | const fs = require('fs') 5 | const path = require('path') 6 | 7 | // lazily load libs that might not be used (depending on async/sync and opts.describe) 8 | let _cp, _util 9 | function cp () { 10 | if (!_cp) _cp = require('child_process') 11 | return _cp 12 | } 13 | function util () { 14 | if (!_util) _util = require('util') 15 | return _util 16 | } 17 | 18 | // lazily load promisified functions that might not be used 19 | let _access, _execFile, _readFile 20 | function access () { 21 | if (!_access) _access = util().promisify(fs.access) 22 | return _access 23 | } 24 | function execFile () { 25 | if (!_execFile) _execFile = util().promisify(cp().execFile) 26 | return _execFile 27 | } 28 | function readFile () { 29 | if (!_readFile) _readFile = util().promisify(fs.readFile) 30 | return _readFile 31 | } 32 | 33 | // functions to execute a git command 34 | function gitArgs (dir, args) { 35 | return [`--git-dir=${path.join(dir, '.git')}`, `--work-tree=${dir}`].concat(args) 36 | } 37 | async function git (dir, args) { 38 | const { stdout, stderr } = await execFile()('git', gitArgs(dir, args)) 39 | if (stderr) throw new Error(String(stderr).trim()) 40 | return String(stdout).trim() 41 | } 42 | function gitSync (dir, args) { 43 | const stdout = cp().execFileSync('git', gitArgs(dir, args)) 44 | return String(stdout).trim() 45 | } 46 | 47 | // functions to read a file 48 | function pathToGitFile (dir, filename) { 49 | return path.join(dir, '.git', filename) 50 | } 51 | async function readGitFile (dir, filename) { 52 | const data = await readFile()(pathToGitFile(dir, filename), 'utf8') 53 | return String(data).trim() 54 | } 55 | function readGitFileSync (dir, filename) { 56 | const data = fs.readFileSync(pathToGitFile(dir, filename), 'utf8') 57 | return String(data).trim() 58 | } 59 | 60 | function getOpts (opts) { 61 | return { fallbackToSha: true, ...opts } 62 | } 63 | 64 | // valid opts: 65 | // - dir (string): in case `process.cwd()` isn't suitable 66 | // - describe (boolean): use `git describe --tags` instead of `git rev-parse HEAD` 67 | // - fallbackToSha (boolean): if opts.describe and no tags found, fallback to latest commit sha 68 | const nextBuildId = async opts => { 69 | opts = getOpts(opts) 70 | 71 | const inputDir = path.resolve(process.cwd(), opts.dir || '.') 72 | let dir = inputDir 73 | 74 | // dir may not be the project root so look for .git dir in parent dirs too 75 | const root = path.parse(dir).root 76 | let attempts = 0 // protect against infinite tight loop if libs misbehave 77 | while (dir !== root && attempts < 999) { 78 | attempts++ 79 | try { 80 | await access()(path.join(dir, '.git'), fs.constants.R_OK) 81 | break 82 | } catch (_) { 83 | dir = path.dirname(dir) 84 | } 85 | } 86 | if (dir === root || attempts >= 999) dir = inputDir 87 | 88 | // if opts.describe, use `git describe --tags` 89 | let id 90 | if (opts.describe) { 91 | try { 92 | id = await git(dir, ['describe', '--tags']) 93 | if (!id) throw new Error('Output of `git describe --tags` was empty!') 94 | return id 95 | } catch (err) { 96 | if (!opts.fallbackToSha) throw err 97 | } 98 | } 99 | 100 | // try file system, suggestion by @sheerun here: https://github.com/nexdrew/next-build-id/issues/17#issuecomment-482799872 101 | // 1. read .git/HEAD to find out ref, the result is something like `ref: refs/heads/master` 102 | // 2. read this file to find our current commit (e.g. .git/refs/heads/master) 103 | try { 104 | const head = await readGitFile(dir, 'HEAD') 105 | let refi = head.indexOf('ref:') 106 | if (refi === -1) refi = 0 107 | const endi = head.indexOf('\n', refi + 4) + 1 108 | const ref = head.slice(refi + 4, endi || undefined).trim() 109 | if (ref) { 110 | id = await readGitFile(dir, ref) 111 | if (id) return id 112 | } 113 | } catch (_) {} 114 | 115 | // fallback to `git rev-parse HEAD` 116 | id = await git(dir, ['rev-parse', 'HEAD']) 117 | if (!id) throw new Error('Output of `git rev-parse HEAD` was empty!') 118 | 119 | return id 120 | } 121 | 122 | nextBuildId.sync = opts => { 123 | opts = getOpts(opts) 124 | 125 | const inputDir = path.resolve(process.cwd(), opts.dir || '.') 126 | let dir = inputDir 127 | 128 | // dir may not be the project root so look for .git dir in parent dirs too 129 | const root = path.parse(dir).root 130 | let attempts = 0 // protect against infinite tight loop if libs misbehave 131 | while (dir !== root && attempts < 999) { 132 | attempts++ 133 | try { 134 | fs.accessSync(path.join(dir, '.git'), fs.constants.R_OK) 135 | break 136 | } catch (_) { 137 | dir = path.dirname(dir) 138 | } 139 | } 140 | if (dir === root || attempts >= 999) dir = inputDir 141 | 142 | // if opts.describe, use `git describe --tags` 143 | let id 144 | if (opts.describe) { 145 | try { 146 | id = gitSync(dir, ['describe', '--tags']) 147 | if (!id) throw new Error('Output of `git describe --tags` was empty!') 148 | return id 149 | } catch (err) { 150 | if (!opts.fallbackToSha) throw err 151 | } 152 | } 153 | 154 | // try file system, suggestion by @sheerun here: https://github.com/nexdrew/next-build-id/issues/17#issuecomment-482799872 155 | // 1. read .git/HEAD to find out ref, the result is something like `ref: refs/heads/master` 156 | // 2. read this file to find our current commit (e.g. .git/refs/heads/master) 157 | try { 158 | const head = readGitFileSync(dir, 'HEAD') 159 | let refi = head.indexOf('ref:') 160 | if (refi === -1) refi = 0 161 | const endi = head.indexOf('\n', refi + 4) + 1 162 | const ref = head.slice(refi + 4, endi || undefined).trim() 163 | if (ref) { 164 | id = readGitFileSync(dir, ref) 165 | if (id) return id 166 | } 167 | } catch (_) {} 168 | 169 | // fallback to `git rev-parse HEAD` 170 | id = gitSync(dir, ['rev-parse', 'HEAD']) 171 | if (!id) throw new Error('Output of `git rev-parse HEAD` was empty!') 172 | 173 | return id 174 | } 175 | 176 | module.exports = nextBuildId 177 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-build-id", 3 | "version": "3.0.0", 4 | "description": "Use a consistent, git-based build id for your Next.js app", 5 | "main": "index.js", 6 | "files": [ 7 | "index.js" 8 | ], 9 | "scripts": { 10 | "pretest": "standard", 11 | "test": "tap --cov --timeout=240 test/test-next6.js test/test-next7.js test/test-next8.js test/test-next9.js", 12 | "coverage": "nyc report --reporter=text-lcov | coveralls", 13 | "release": "standard-version" 14 | }, 15 | "engines": { 16 | "node": ">=8" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/nexdrew/next-build-id.git" 21 | }, 22 | "keywords": [ 23 | "next", 24 | "next.js", 25 | "build", 26 | "id", 27 | "BUILD_ID", 28 | "git", 29 | "head", 30 | "describe", 31 | "tags" 32 | ], 33 | "author": "nexdrew", 34 | "license": "ISC", 35 | "bugs": { 36 | "url": "https://github.com/nexdrew/next-build-id/issues" 37 | }, 38 | "homepage": "https://github.com/nexdrew/next-build-id#readme", 39 | "dependencies": {}, 40 | "devDependencies": { 41 | "coveralls": "^3.0.6", 42 | "rimraf": "^3.0.0", 43 | "standard": "^14.0.0", 44 | "standard-version": "^7.0.0", 45 | "sywac": "^1.2.2", 46 | "tap": "^14.6.1" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test/fixture-next6/dotgit/HEAD: -------------------------------------------------------------------------------- 1 | ref: refs/heads/master 2 | -------------------------------------------------------------------------------- /test/fixture-next6/dotgit/refs/heads/master: -------------------------------------------------------------------------------- 1 | 0123456789abcdef0123456789abcdef0123fff6 2 | -------------------------------------------------------------------------------- /test/fixture-next6/next.config.js: -------------------------------------------------------------------------------- 1 | const nextBuildId = require('../../index') 2 | 3 | module.exports = () => { 4 | const opts = { dir: __dirname } 5 | if (process.env.NBI_TEST_DESCRIBE) opts.describe = true 6 | return { 7 | generateBuildId: () => process.env.NBI_TEST_SYNC ? nextBuildId.sync(opts) : nextBuildId(opts) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/fixture-next6/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fixture", 3 | "version": "1.0.0", 4 | "description": "Fixture to test against Next.js 6", 5 | "private": true, 6 | "main": "index.js", 7 | "scripts": { 8 | "build": "next build" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "next": "^6.1.2", 14 | "react": "^16.4.0", 15 | "react-dom": "^16.4.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/fixture-next6/pages/index.js: -------------------------------------------------------------------------------- 1 | export default () =>
Welcome to next.js!
2 | -------------------------------------------------------------------------------- /test/fixture-next7/dotgit/HEAD: -------------------------------------------------------------------------------- 1 | ref: refs/heads/master 2 | -------------------------------------------------------------------------------- /test/fixture-next7/dotgit/refs/heads/master: -------------------------------------------------------------------------------- 1 | 0123456789abcdef0123456789abcdef0123fff7 2 | -------------------------------------------------------------------------------- /test/fixture-next7/next.config.js: -------------------------------------------------------------------------------- 1 | const nextBuildId = require('../../index') 2 | 3 | module.exports = () => { 4 | const opts = { dir: __dirname } 5 | if (process.env.NBI_TEST_DESCRIBE) opts.describe = true 6 | return { 7 | generateBuildId: () => process.env.NBI_TEST_SYNC ? nextBuildId.sync(opts) : nextBuildId(opts) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/fixture-next7/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fixture", 3 | "version": "1.0.0", 4 | "description": "Fixture to test against Next.js 7", 5 | "private": true, 6 | "main": "index.js", 7 | "scripts": { 8 | "build": "next build" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "next": "^7.0.3", 14 | "react": "^16.0.0", 15 | "react-dom": "^16.0.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/fixture-next7/pages/index.js: -------------------------------------------------------------------------------- 1 | export default () =>
Welcome to next.js!
2 | -------------------------------------------------------------------------------- /test/fixture-next8/dotgit/HEAD: -------------------------------------------------------------------------------- 1 | ref: refs/heads/master 2 | -------------------------------------------------------------------------------- /test/fixture-next8/dotgit/refs/heads/master: -------------------------------------------------------------------------------- 1 | 0123456789abcdef0123456789abcdef0123fff8 2 | -------------------------------------------------------------------------------- /test/fixture-next8/next.config.js: -------------------------------------------------------------------------------- 1 | const nextBuildId = require('../../index') 2 | 3 | module.exports = () => { 4 | const opts = { dir: __dirname } 5 | if (process.env.NBI_TEST_DESCRIBE) opts.describe = true 6 | return { 7 | generateBuildId: () => process.env.NBI_TEST_SYNC ? nextBuildId.sync(opts) : nextBuildId(opts) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/fixture-next8/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fixture", 3 | "version": "1.0.0", 4 | "description": "Fixture to test against Next.js 8", 5 | "private": true, 6 | "main": "index.js", 7 | "scripts": { 8 | "build": "next build" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "next": "^8.1.0", 14 | "react": "^16.6.0", 15 | "react-dom": "^16.6.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/fixture-next8/pages/index.js: -------------------------------------------------------------------------------- 1 | export default () =>
Welcome to next.js!
2 | -------------------------------------------------------------------------------- /test/fixture-next9/dotgit/HEAD: -------------------------------------------------------------------------------- 1 | ref: refs/heads/master 2 | -------------------------------------------------------------------------------- /test/fixture-next9/dotgit/refs/heads/master: -------------------------------------------------------------------------------- 1 | 0123456789abcdef0123456789abcdef0123fff9 2 | -------------------------------------------------------------------------------- /test/fixture-next9/next.config.js: -------------------------------------------------------------------------------- 1 | const nextBuildId = require('../../index') 2 | 3 | module.exports = () => { 4 | const opts = { dir: __dirname } 5 | if (process.env.NBI_TEST_DESCRIBE) opts.describe = true 6 | if (process.env.NBI_TEST_F2S_FALSE) opts.fallbackToSha = false 7 | return { 8 | generateBuildId: () => process.env.NBI_TEST_SYNC ? nextBuildId.sync(opts) : nextBuildId(opts) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/fixture-next9/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fixture", 3 | "version": "1.0.0", 4 | "description": "Fixture to test against Next.js 9", 5 | "private": true, 6 | "main": "index.js", 7 | "scripts": { 8 | "build": "next build" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "next": "^9.0.3", 14 | "react": "^16.9.0", 15 | "react-dom": "^16.9.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/fixture-next9/pages/index.js: -------------------------------------------------------------------------------- 1 | export default () =>
Welcome to next.js!
2 | -------------------------------------------------------------------------------- /test/git: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // --git-dir=${inputDir}/.git --work-tree=${inputDir} 4 | // rev-parse HEAD 5 | // describe --tags 6 | require('sywac') 7 | .dir('--git-dir ', { mustExist: true }) 8 | .dir('--work-tree ', { mustExist: true }) 9 | .outputSettings({ showHelpOnError: false }) 10 | .parseAndExit() 11 | .then(argv => { 12 | if (argv._[0] === 'describe') { 13 | return process.env.NBI_TEST_F2S_FALSE ? null : console.log('v2.0.1-12-g0123456') 14 | } 15 | console.log('0123456789abcdef0123456789abcdef01234567') 16 | }) 17 | -------------------------------------------------------------------------------- /test/test-next6.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const tap = require('tap') 4 | 5 | const utils = require('./utils') 6 | 7 | const exec = utils.exec 8 | const mockedGitEnv = utils.mockedGitEnv 9 | const rmrf = utils.rmrf 10 | const fixturePath = path.resolve(__dirname, 'fixture-next6') 11 | 12 | let npmi = false 13 | tap.beforeEach(async () => { 14 | await rmrf(path.resolve(fixturePath, '.git')) // clean up if test previously failed 15 | const setup = [exec('cp', '-R dotgit .git', fixturePath)] 16 | if (!npmi) { 17 | npmi = true 18 | setup.push(exec('npm', 'i', fixturePath)) 19 | } 20 | return Promise.all(setup) 21 | }) 22 | 23 | tap.afterEach(() => { 24 | return Promise.all(['.next', '.git'].map(dir => rmrf(path.resolve(fixturePath, dir)))) 25 | }) 26 | 27 | tap.test('async > default', async t => { 28 | const io = await exec('npm', 'run build', fixturePath) 29 | t.notOk(io.err) 30 | t.notOk(io.stderr) 31 | 32 | const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) 33 | t.equal(buildId, '0123456789abcdef0123456789abcdef0123fff6') // file system 34 | }) 35 | 36 | tap.test('async > describe: true', async t => { 37 | const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_DESCRIBE: true })) 38 | t.notOk(io.err) 39 | t.notOk(io.stderr) 40 | 41 | const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) 42 | t.equal(buildId, 'v2.0.1-12-g0123456') // mocked git output 43 | }) 44 | 45 | // *Note* only using mockedGitEnv() here to avoid this error during `npm run build`: 46 | // The node binary used for scripts is /Users/abg/.node-spawn-wrap-98929-a7983ca02859/node 47 | // but npm is using /usr/local/Cellar/node/12.7.0/bin/node itself. 48 | // Use the `--scripts-prepend-node-path` option to include the path for 49 | // the node binary npm was executed with. 50 | tap.test('sync > default', async t => { 51 | const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_SYNC: true })) 52 | t.notOk(io.err) 53 | t.notOk(io.stderr) 54 | 55 | const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) 56 | t.equal(buildId, '0123456789abcdef0123456789abcdef0123fff6') // file system 57 | }) 58 | 59 | tap.test('sync > describe: true', async t => { 60 | const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_DESCRIBE: true, NBI_TEST_SYNC: true })) 61 | t.notOk(io.err) 62 | t.notOk(io.stderr) 63 | 64 | const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) 65 | t.equal(buildId, 'v2.0.1-12-g0123456') // mocked git output 66 | }) 67 | 68 | tap.test('async > fallback to rev-parse HEAD', async t => { 69 | await rmrf(path.resolve(fixturePath, '.git', 'refs', 'heads', 'master')) 70 | 71 | const io = await exec('npm', 'run build', fixturePath, mockedGitEnv()) 72 | t.notOk(io.err) 73 | t.notOk(io.stderr) 74 | 75 | const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) 76 | t.equal(buildId, '0123456789abcdef0123456789abcdef01234567') // mocked git output 77 | }) 78 | 79 | tap.test('sync > fallback to rev-parse HEAD', async t => { 80 | await rmrf(path.resolve(fixturePath, '.git', 'refs', 'heads', 'master')) 81 | 82 | const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_SYNC: true })) 83 | t.notOk(io.err) 84 | t.notOk(io.stderr) 85 | 86 | const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) 87 | t.equal(buildId, '0123456789abcdef0123456789abcdef01234567') // mocked git output 88 | }) 89 | -------------------------------------------------------------------------------- /test/test-next7.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const tap = require('tap') 4 | 5 | const utils = require('./utils') 6 | 7 | const exec = utils.exec 8 | const mockedGitEnv = utils.mockedGitEnv 9 | const rmrf = utils.rmrf 10 | const fixturePath = path.resolve(__dirname, 'fixture-next7') 11 | 12 | let npmi = false 13 | tap.beforeEach(async () => { 14 | await rmrf(path.resolve(fixturePath, '.git')) // clean up if test previously failed 15 | const setup = [exec('cp', '-R dotgit .git', fixturePath)] 16 | if (!npmi) { 17 | npmi = true 18 | setup.push(exec('npm', 'i', fixturePath)) 19 | } 20 | return Promise.all(setup) 21 | }) 22 | 23 | tap.afterEach(() => { 24 | return Promise.all(['.next', '.git'].map(dir => rmrf(path.resolve(fixturePath, dir)))) 25 | }) 26 | 27 | tap.test('async > default', async t => { 28 | const io = await exec('npm', 'run build', fixturePath) 29 | t.notOk(io.err) 30 | t.notOk(io.stderr) 31 | 32 | const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) 33 | t.equal(buildId, '0123456789abcdef0123456789abcdef0123fff7') // file system 34 | }) 35 | 36 | tap.test('async > describe: true', async t => { 37 | const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_DESCRIBE: true })) 38 | t.notOk(io.err) 39 | t.notOk(io.stderr) 40 | 41 | const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) 42 | t.equal(buildId, 'v2.0.1-12-g0123456') // mocked git output 43 | }) 44 | 45 | // *Note* only using mockedGitEnv() here to avoid this error during `npm run build`: 46 | // The node binary used for scripts is /Users/abg/.node-spawn-wrap-98929-a7983ca02859/node 47 | // but npm is using /usr/local/Cellar/node/12.7.0/bin/node itself. 48 | // Use the `--scripts-prepend-node-path` option to include the path for 49 | // the node binary npm was executed with. 50 | tap.test('sync > default', async t => { 51 | const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_SYNC: true })) 52 | t.notOk(io.err) 53 | t.notOk(io.stderr) 54 | 55 | const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) 56 | t.equal(buildId, '0123456789abcdef0123456789abcdef0123fff7') // file system 57 | }) 58 | 59 | tap.test('sync > describe: true', async t => { 60 | const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_DESCRIBE: true, NBI_TEST_SYNC: true })) 61 | t.notOk(io.err) 62 | t.notOk(io.stderr) 63 | 64 | const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) 65 | t.equal(buildId, 'v2.0.1-12-g0123456') // mocked git output 66 | }) 67 | 68 | tap.test('async > fallback to rev-parse HEAD', async t => { 69 | await rmrf(path.resolve(fixturePath, '.git', 'refs', 'heads', 'master')) 70 | 71 | const io = await exec('npm', 'run build', fixturePath, mockedGitEnv()) 72 | t.notOk(io.err) 73 | t.notOk(io.stderr) 74 | 75 | const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) 76 | t.equal(buildId, '0123456789abcdef0123456789abcdef01234567') // mocked git output 77 | }) 78 | 79 | tap.test('sync > fallback to rev-parse HEAD', async t => { 80 | await rmrf(path.resolve(fixturePath, '.git', 'refs', 'heads', 'master')) 81 | 82 | const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_SYNC: true })) 83 | t.notOk(io.err) 84 | t.notOk(io.stderr) 85 | 86 | const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) 87 | t.equal(buildId, '0123456789abcdef0123456789abcdef01234567') // mocked git output 88 | }) 89 | -------------------------------------------------------------------------------- /test/test-next8.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const tap = require('tap') 4 | 5 | const utils = require('./utils') 6 | 7 | const exec = utils.exec 8 | const mockedGitEnv = utils.mockedGitEnv 9 | const rmrf = utils.rmrf 10 | const fixturePath = path.resolve(__dirname, 'fixture-next8') 11 | 12 | let npmi = false 13 | tap.beforeEach(async () => { 14 | await rmrf(path.resolve(fixturePath, '.git')) // clean up if test previously failed 15 | const setup = [exec('cp', '-R dotgit .git', fixturePath)] 16 | if (!npmi) { 17 | npmi = true 18 | setup.push(exec('npm', 'i', fixturePath)) 19 | } 20 | return Promise.all(setup) 21 | }) 22 | 23 | tap.afterEach(() => { 24 | return Promise.all(['.next', '.git'].map(dir => rmrf(path.resolve(fixturePath, dir)))) 25 | }) 26 | 27 | tap.test('async > default', async t => { 28 | const io = await exec('npm', 'run build', fixturePath) 29 | t.notOk(io.err) 30 | t.notOk(io.stderr) 31 | 32 | const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) 33 | t.equal(buildId, '0123456789abcdef0123456789abcdef0123fff8') // file system 34 | }) 35 | 36 | tap.test('async > describe: true', async t => { 37 | const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_DESCRIBE: true })) 38 | t.notOk(io.err) 39 | t.notOk(io.stderr) 40 | 41 | const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) 42 | t.equal(buildId, 'v2.0.1-12-g0123456') // mocked git output 43 | }) 44 | 45 | // *Note* only using mockedGitEnv() here to avoid this error during `npm run build`: 46 | // The node binary used for scripts is /Users/abg/.node-spawn-wrap-98929-a7983ca02859/node 47 | // but npm is using /usr/local/Cellar/node/12.7.0/bin/node itself. 48 | // Use the `--scripts-prepend-node-path` option to include the path for 49 | // the node binary npm was executed with. 50 | tap.test('sync > default', async t => { 51 | const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_SYNC: true })) 52 | t.notOk(io.err) 53 | t.notOk(io.stderr) 54 | 55 | const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) 56 | t.equal(buildId, '0123456789abcdef0123456789abcdef0123fff8') // file system 57 | }) 58 | 59 | tap.test('sync > describe: true', async t => { 60 | const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_DESCRIBE: true, NBI_TEST_SYNC: true })) 61 | t.notOk(io.err) 62 | t.notOk(io.stderr) 63 | 64 | const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) 65 | t.equal(buildId, 'v2.0.1-12-g0123456') // mocked git output 66 | }) 67 | 68 | tap.test('async > fallback to rev-parse HEAD', async t => { 69 | await rmrf(path.resolve(fixturePath, '.git', 'refs', 'heads', 'master')) 70 | 71 | const io = await exec('npm', 'run build', fixturePath, mockedGitEnv()) 72 | t.notOk(io.err) 73 | t.notOk(io.stderr) 74 | 75 | const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) 76 | t.equal(buildId, '0123456789abcdef0123456789abcdef01234567') // mocked git output 77 | }) 78 | 79 | tap.test('sync > fallback to rev-parse HEAD', async t => { 80 | await rmrf(path.resolve(fixturePath, '.git', 'refs', 'heads', 'master')) 81 | 82 | const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_SYNC: true })) 83 | t.notOk(io.err) 84 | t.notOk(io.stderr) 85 | 86 | const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) 87 | t.equal(buildId, '0123456789abcdef0123456789abcdef01234567') // mocked git output 88 | }) 89 | -------------------------------------------------------------------------------- /test/test-next9.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const tap = require('tap') 4 | 5 | const utils = require('./utils') 6 | 7 | const exec = utils.exec 8 | const mockedGitEnv = utils.mockedGitEnv 9 | const rmrf = utils.rmrf 10 | const fixturePath = path.resolve(__dirname, 'fixture-next9') 11 | 12 | let npmi = false 13 | tap.beforeEach(async () => { 14 | await rmrf(path.resolve(fixturePath, '.git')) // clean up if test previously failed 15 | const setup = [exec('cp', '-R dotgit .git', fixturePath)] 16 | if (!npmi) { 17 | npmi = true 18 | setup.push(exec('npm', 'i', fixturePath)) 19 | } 20 | return Promise.all(setup) 21 | }) 22 | 23 | tap.afterEach(() => { 24 | return Promise.all(['.next', '.git'].map(dir => rmrf(path.resolve(fixturePath, dir)))) 25 | }) 26 | 27 | tap.test('async > default', async t => { 28 | const io = await exec('npm', 'run build', fixturePath) 29 | t.notOk(io.err) 30 | t.notOk(io.stderr) 31 | 32 | const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) 33 | t.equal(buildId, '0123456789abcdef0123456789abcdef0123fff9') // file system 34 | }) 35 | 36 | tap.test('async > describe: true', async t => { 37 | const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_DESCRIBE: true })) 38 | t.notOk(io.err) 39 | t.notOk(io.stderr) 40 | 41 | const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) 42 | t.equal(buildId, 'v2.0.1-12-g0123456') // mocked git output 43 | }) 44 | 45 | // *Note* only using mockedGitEnv() here to avoid this error during `npm run build`: 46 | // The node binary used for scripts is /Users/abg/.node-spawn-wrap-98929-a7983ca02859/node 47 | // but npm is using /usr/local/Cellar/node/12.7.0/bin/node itself. 48 | // Use the `--scripts-prepend-node-path` option to include the path for 49 | // the node binary npm was executed with. 50 | tap.test('sync > default', async t => { 51 | const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_SYNC: true })) 52 | t.notOk(io.err) 53 | t.notOk(io.stderr) 54 | 55 | const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) 56 | t.equal(buildId, '0123456789abcdef0123456789abcdef0123fff9') // file system 57 | }) 58 | 59 | tap.test('sync > describe: true', async t => { 60 | const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { 61 | NBI_TEST_DESCRIBE: true, 62 | NBI_TEST_SYNC: true 63 | })) 64 | t.notOk(io.err) 65 | t.notOk(io.stderr) 66 | 67 | const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) 68 | t.equal(buildId, 'v2.0.1-12-g0123456') // mocked git output 69 | }) 70 | 71 | tap.test('async > fallback to rev-parse HEAD', async t => { 72 | await rmrf(path.resolve(fixturePath, '.git', 'refs', 'heads', 'master')) 73 | 74 | const io = await exec('npm', 'run build', fixturePath, mockedGitEnv()) 75 | t.notOk(io.err) 76 | t.notOk(io.stderr) 77 | 78 | const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) 79 | t.equal(buildId, '0123456789abcdef0123456789abcdef01234567') // mocked git output 80 | }) 81 | 82 | tap.test('sync > fallback to rev-parse HEAD', async t => { 83 | await rmrf(path.resolve(fixturePath, '.git', 'refs', 'heads', 'master')) 84 | 85 | const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_SYNC: true })) 86 | t.notOk(io.err) 87 | t.notOk(io.stderr) 88 | 89 | const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) 90 | t.equal(buildId, '0123456789abcdef0123456789abcdef01234567') // mocked git output 91 | }) 92 | 93 | tap.test('async > no .git dir', async t => { 94 | await rmrf(path.resolve(fixturePath, '.git')) 95 | 96 | const io = await exec('npm', 'run build', fixturePath, mockedGitEnv()) 97 | t.notOk(io.err) 98 | t.notOk(io.stderr) 99 | 100 | const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) 101 | t.equal(buildId && buildId.length, 40) // latest sha from next-build-id repo 102 | }) 103 | 104 | tap.test('sync > no .git dir', async t => { 105 | await rmrf(path.resolve(fixturePath, '.git')) 106 | 107 | const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { NBI_TEST_SYNC: true })) 108 | t.notOk(io.err) 109 | t.notOk(io.stderr) 110 | 111 | const buildId = await utils.readTextFile(path.resolve(fixturePath, '.next', 'BUILD_ID')) 112 | t.equal(buildId && buildId.length, 40) // latest sha from next-build-id repo 113 | }) 114 | 115 | tap.test('async > describe: true, fallbackToSha: false', async t => { 116 | const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { 117 | NBI_TEST_DESCRIBE: true, 118 | NBI_TEST_F2S_FALSE: true 119 | }), true) 120 | t.ok(io.err) 121 | t.match(io.stderr, /Output of `git describe --tags` was empty!/) 122 | }) 123 | 124 | tap.test('sync > describe: true, fallbackToSha: false', async t => { 125 | const io = await exec('npm', 'run build', fixturePath, Object.assign(mockedGitEnv(), { 126 | NBI_TEST_DESCRIBE: true, 127 | NBI_TEST_F2S_FALSE: true, 128 | NBI_TEST_SYNC: true 129 | }), true) 130 | t.ok(io.err) 131 | t.match(io.stderr, /Output of `git describe --tags` was empty!/) 132 | }) 133 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const cp = require('child_process') 4 | const fs = require('fs') 5 | const path = require('path') 6 | 7 | const rimraf = require('rimraf') 8 | 9 | function exec (file, args, cwd, env, alwaysResolve) { 10 | // console.error(`\nexec: ${file} ${args}`) 11 | return new Promise((resolve, reject) => { 12 | cp.execFile(file, args.split(/\s/), { 13 | cwd: cwd || __dirname, 14 | env: env || Object.assign({}, process.env), 15 | encoding: 'utf8' 16 | }, (err, stdout, stderr) => { 17 | // console.error(`done: ${file} ${args}`) 18 | if (err && !alwaysResolve) return reject(err) 19 | resolve({ err, stdout, stderr }) 20 | }) 21 | }) 22 | } 23 | 24 | function mockedGitEnv (env) { 25 | env = env || {} 26 | if (!env.PATH) env.PATH = [__dirname].concat(process.env.PATH.split(path.delimiter)).join(path.delimiter) 27 | return Object.assign({}, process.env, env) 28 | } 29 | 30 | function rmrf (dirOrFile) { 31 | return new Promise((resolve, reject) => { 32 | rimraf(dirOrFile, err => { 33 | if (err) return reject(err) 34 | resolve() 35 | }) 36 | }) 37 | } 38 | 39 | function readTextFile (file) { 40 | return new Promise((resolve, reject) => { 41 | fs.readFile(file, 'utf8', (err, data) => { 42 | if (err) return reject(err) 43 | resolve(data) 44 | }) 45 | }) 46 | } 47 | 48 | // function writeTextFile (file, data) { 49 | // return new Promise((resolve, reject) => { 50 | // fs.writeFile(file, data, error => { 51 | // if (error) return reject(error) 52 | // resolve() 53 | // }) 54 | // }) 55 | // } 56 | 57 | module.exports = { 58 | exec, 59 | mockedGitEnv, 60 | rmrf, 61 | readTextFile 62 | } 63 | --------------------------------------------------------------------------------