├── .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 | [](https://travis-ci.org/nexdrew/next-build-id)
6 | [](https://coveralls.io/github/nexdrew/next-build-id?branch=master)
7 | [](https://standardjs.com)
8 | [](https://conventionalcommits.org)
9 | [](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 |
--------------------------------------------------------------------------------