From 497693c2b62f89fd407753279edab825ee122610 Mon Sep 17 00:00:00 2001 From: Mike Frysinger Date: Mon, 26 Oct 2020 04:12:16 -0400 Subject: [PATCH] initial release --- .eslintrc.json | 203 ++++++++++++++++++++++++++++++++++++ .github/workflows/ci.yaml | 27 +++++ .gitignore | 4 + LICENSE | 25 +++++ README.md | 213 ++++++++++++++++++++++++++++++++++++++ package.json | 44 ++++++++ rollup.config.js | 23 ++++ src/index.js | 176 +++++++++++++++++++++++++++++++ test/test.js | 182 ++++++++++++++++++++++++++++++++ 9 files changed, 897 insertions(+) create mode 100644 .eslintrc.json create mode 100644 .github/workflows/ci.yaml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 package.json create mode 100644 rollup.config.js create mode 100644 src/index.js create mode 100644 test/test.js diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..6cd302d --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,203 @@ +// Copyright 2020 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// See https://eslint.org/docs/user-guide/configuring. +// We need to use JSON rather than JS due to Node/ESLint conflicts over ESM: +// https://github.com/eslint/eslint/issues/12319 + +{ + "root": true, + "env": { + "node": true, + // This allows the runtime environment (i.e. objects). + "es6": true + }, + "parserOptions": { + // This sets the syntax parsing level. + "ecmaVersion": 2020, + "sourceType": "module" + }, + + "plugins": [ + "jsdoc" + ], + + // See https://eslint.org/docs/rules/ for details. + // These rules were picked based on the existing codebase. If you find one + // to be too onerous and not required by the styleguide, feel free to discuss. + "rules": { + "array-bracket-spacing": "error", + "arrow-parens": ["error", "always"], + "arrow-spacing": ["error", {"before": true, "after": true}], + "block-spacing": ["error", "always"], + "comma-dangle": ["error", "always-multiline"], + "comma-spacing": "error", + "comma-style": "error", + "curly": "error", + "default-param-last": "error", + "eol-last": "error", + "func-call-spacing": "error", + "generator-star-spacing": ["error", "after"], + // l/I: Depending on the font, these are hard to distinguish. + "id-blacklist": ["error", "l", "I", "self"], + "keyword-spacing": "error", + "lines-between-class-members": "error", + "max-len": ["error", {"code": 80, "ignoreUrls": true}], + "new-parens": "error", + "no-alert": "error", + "no-case-declarations": "error", + "no-cond-assign": "error", + "no-const-assign": "error", + "no-control-regex": "error", + "no-debugger": "error", + "no-dupe-args": "error", + "no-dupe-class-members": "error", + "no-dupe-keys": "error", + "no-duplicate-case": "error", + "no-empty": "error", + "no-empty-character-class": "error", + "no-eval": "error", + "no-ex-assign": "error", + // We want "all" (nestedBinaryExpressions=false), but this breaks + // closure-compiler casts. + "no-extra-parens": ["error", "functions"], + "no-extra-semi": "error", + "no-implied-eval": "error", + "no-invalid-regexp": "error", + "no-irregular-whitespace": "error", + "no-label-var": "error", + "no-mixed-spaces-and-tabs": "error", + "no-multi-spaces": ["error", {"ignoreEOLComments": true}], + "no-multiple-empty-lines": "error", + "no-new": "error", + "no-new-func": "error", + "no-new-object": "error", + "no-new-wrappers": "error", + "no-obj-calls": "error", + "no-octal": "error", + "no-octal-escape": "error", + "no-return-await": "error", + "no-script-url": "error", + "no-self-assign": "error", + "no-self-compare": "error", + "no-sequences": "error", + "no-shadow-restricted-names": "error", + "no-tabs": "error", + "no-template-curly-in-string": "error", + "no-throw-literal": "error", + "no-trailing-spaces": "error", + "no-unmodified-loop-condition": "error", + "no-unneeded-ternary": "error", + "no-unreachable": "error", + "no-useless-call": "error", + "no-useless-concat": "error", + "no-useless-escape": "error", + "no-useless-return": "error", + "no-var": "error", + "no-void": "error", + // We allow TODO comments. + "no-warning-comments": [ + "error", { + "terms": ["fix", "fixme", "xxx"] + } + ], + "no-whitespace-before-property": "error", + "no-with": "error", + "object-curly-newline": ["error", {"consistent": true}], + "object-curly-spacing": "error", + "one-var-declaration-per-line": "error", + "prefer-const": "error", + "prefer-numeric-literals": "error", + "prefer-rest-params": "error", + "quote-props": ["error", "consistent"], + "quotes": ["error", "single", + {"avoidEscape": true, "allowTemplateLiterals": true}], + "radix": "error", + "rest-spread-spacing": "error", + "semi": ["error", "always"], + "semi-spacing": "error", + "semi-style": ["error", "last"], + "space-before-blocks": ["error", "always"], + "space-before-function-paren": [ + "error", { + "anonymous": "never", + "named": "never", + "asyncArrow": "always" + } + ], + "space-in-parens": ["error", "never"], + "space-infix-ops": "error", + "space-unary-ops": "error", + "spaced-comment": ["error", "always"], + "switch-colon-spacing": ["error", {"after": true, "before": false}], + "symbol-description": "error", + "template-curly-spacing": ["error", "never"], + "unicode-bom": ["error", "never"], + "use-isnan": "error", + "valid-typeof": "error", + "yield-star-spacing": ["error", "after"], + "yoda": "error", + + "jsdoc/check-access": "error", + "jsdoc/check-alignment": "error", + "jsdoc/check-examples": "error", + // We want hanging indentation, but this check requires none everywhere. + "jsdoc/check-indentation": "off", + "jsdoc/check-param-names": "error", + "jsdoc/check-property-names": "error", + // Make sure this is disabled as this rejects closure syntax. + "jsdoc/check-syntax": "off", + "jsdoc/check-tag-names": "error", + // This is disabled until this crash is resolved: + // https://github.com/gajus/eslint-plugin-jsdoc/issues/389 + "jsdoc/check-types": "off", + // We don"t use these tags in the project. + "jsdoc/check-values": "off", + "jsdoc/empty-tags": "error", + "jsdoc/implements-on-classes": "error", + "jsdoc/match-description": "error", + "jsdoc/newline-after-description": "error", + // This is only for TypeScript which we don"t care about. + "jsdoc/no-types": "off", + "jsdoc/no-undefined-types": "error", + "jsdoc/require-description": "error", + // TODO(vapier): Turn this on. + "jsdoc/require-description-complete-sentence": "off", + // We don"t want to require examples. + "jsdoc/require-example": "off", + "jsdoc/require-file-overview": "error", + "jsdoc/require-hyphen-before-param-description": ["error", "never"], + "jsdoc/require-jsdoc": "error", + "jsdoc/require-param": "error", + "jsdoc/require-param-description": "error", + "jsdoc/require-param-name": "error", + "jsdoc/require-param-type": "error", + "jsdoc/require-returns": "error", + "jsdoc/require-returns-check": "error", + "jsdoc/require-returns-description": "error", + "jsdoc/require-returns-type": "error", + "jsdoc/valid-types": "error" + }, + + "settings": { + // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc + "jsdoc": { + "mode": "closure", + "preferredTypes": { + "object": "Object" + }, + "tagNamePreference": { + // While not explicitly defined, Google/Chromium JS style guides only + // use these keyword forms, as does the closure compiler docs. + "augments": "extends", + "constant": "const", + "class": "constructor", + "file": "fileoverview", + "returns": "return", + "yields": "yield" + } + } + } +} + diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..84bc2e1 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,27 @@ +# GitHub actions workflow. +# https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions +# https://docs.github.com/en/free-pro-team@latest/actions/guides/building-and-testing-nodejs + +name: Build+Lint+Test CI + +on: [push] + +jobs: + build: + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + node-version: [12.x, 14.x] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm install + - run: npm run build --if-present + - run: npm run lint + - run: npm test + env: + CI: true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..95ea2e3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/dist/ +/.eslintcache +/node_modules/ +/package-lock.json diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..848e9dd --- /dev/null +++ b/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2020 The Chromium OS Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..fb8ccac --- /dev/null +++ b/README.md @@ -0,0 +1,213 @@ +# rollup-plugin-git-info + +Plugin for including git version information into `package.json` imports. + +- [Requirements] +- [Install] +- [Usage] + - [Fields] +- [Options] + - [`abbrev`] + - [`branchCommand`] + - [`commitHashCommand`] + - [`cwd`] + - [`dateFormat`] + - [`dateCommand`] + - [`transformFilename`] + - [`updateVersion`] + - [`versionFormat`] + +## Requirements + +This plugin requires: + +- Node v12+ (the latest [LTS](https://github.com/nodejs/Release) or newer) +- Rollup v2+ (tested against 2.32.0+) + +## Install + +Using npm: + +```console +npm install rollup-plugin-git-info --save-dev +``` + +## Usage + +In your [`rollup.config.js`](https://www.rollupjs.org/guide/en/#configuration-files), +import the plugin: + +```js +import gitInfo from 'rollup-plugin-git-info'; + +export default { + input: 'src/index.js', + output: { + dir: 'dist', + format: 'es', + }, + plugins: [gitInfo()], +}; +``` + +Then in your source code, import the `package.json` file, and use the [fields]: + +```js +// src/index.js +import pkg from './package.json'; +console.log(`running version ${pkg.version}`); +``` + +Then use [`rollup`](https://www.rollupjs.org/) like normal! + +### Fields + +The following fields are injected into the JSON import. + +Note: `version` is updated by default to equal `gitVersion`. + +| Field | Meaning | Example | +| --------------- | -------------------------------- | ------------------------------------------ | +| `buildDate` | Date the bundled was made. | `Mon, 26 Oct 2020 06:58:09 GMT` | +| `gitAbbrevHash` | Abbreviated (short) git hash. | `f347dcd` | +| `gitBranch` | Active git branch. | `main` | +| `gitCommitHash` | Full git hash. | `f347dcd8c8c7b5923fd8459eaf5fddc44f31acc6` | +| `gitDate` | Latest commit date. 1 | `Sat, 24 Oct 2020 01:32:35 -0400` | +| `gitVersion` | [SemVer] version with git info. | `1.0.0-main+gf347dcd` | + +1: By default, the git date is the git commit date which comes from the +git commit itself and might not be the same as the author date or when the +commit was actually pushed to the branch. + +## Options + +Since this loads a JSON file using the [rollup-plugin-json] package, all of its +[options](https://www.npmjs.com/package/@rollup/plugin-json#options) +are respected here too. +They aren't documented here though, so see that plugin's documentation. + +### `abbrev` + +Type: `Number`
+Default: `7` + +How many leading characters to use when creating `gitAbbrevHash`. + +### `branchCommand` + +Type: `String`
+Default: `rev-parse --abbrev-ref HEAD` + +The git command to display the active branch. +If no branch is active (i.e. a detached head due to checking out a specific +commit), then this will simply be `HEAD`. + +### `commitHashCommand` + +Type: `String`
+Default: `rev-parse HEAD` + +Git subcommand to extract the commit hash from the repository. + +### `cwd` + +Type: `String`
+Default: undefined + +The directory to run git commands under. +By default, git information is extracted from the directory where the imported +`package.json` file lives. + +### `dateFormat` + +Type: `String`
+Default: `%cD` ("git commit date, RFC2822 style") + +The [git pretty format](https://git-scm.com/docs/pretty-formats) for extracting +the date of the git repository. +This is appended to `dateCommand` to form the complete git command, but is +broken out for convience as most people only want to tweak the format. + +Another common option is `%aD` for the RFC2822 git author date. + +If none of the stock formats work for you, consider using `%ad` or `%cd` with +[`--date=`](https://git-scm.com/docs/git-log#Documentation/git-log.txt---dateltformatgt). +In particular, the `--date=format:...` gets access to +[strftime](https://man7.org/linux/man-pages/man3/strftime.3.html). + +### `dateCommand` + +Type: `String`
+Default: `log -1 --format=` + +The git command to extract the commit date. +The `dateFormat` option is appended to this command first to control which date +field to extract from the git repository. + +### `enableBuildDate` + +Type: `Boolean`
+Default: `false` + +Whether to export `buildDate` at all. + +This can be useful for development, but it can also be detrimental to caching +and [reproducible builds](https://reproducible-builds.org/). +Hence it is disabled by default. + +### `transformFilename` + +Type: `String`
+Default: `package.json` + +The JSON file to inject git fields into when importing. + +This might be useful if, for some reason, you want to keep the standard +`package.json` content unmodified, and place all the generated git fields in a +unique file/namespace. + +### `updateVersion` + +Type: `Boolean`
+Default: `true` + +Whether to set the `package.json`'s `version` to the generated `gitVersion`. + +Note: The `gitVersion` is always available separately, so this is more of a +convience of just referring to `pkg.version` everywhere. + +### `versionFormat` + +Type: `String`
+Default: `[version]-[branch]+g[abbrevHash]` + +The format of the generated `gitVersion`. + +It is strongly recommended to keep it [SemVer] compliant. + +Note: The `g` prefix (short for "git") on the commit hash is a standard +convention in the git world when using commit hashes in version strings. + +Available placeholders (see the [Fields] section for details): + +- `abbrevHash`: Abbreviated commit hash. +- `commitHash`: Full commit hash. +- `branch`: Git branch. +- `version`: The input file's `version` field (or `0.0.0` if unavailable). + +[requirements]: #requirements +[install]: #install +[usage]: #usage +[fields]: #fields +[options]: #options +[`abbrev`]: #abbrev +[`branchcommand`]: #branchCommand +[`commithashcommand`]: #commitHashCommand +[`cwd`]: #cwd +[`dateformat`]: #dateFormat +[`datecommand`]: #dateCommand +[`transformfilename`]: #transformFilename +[`updateversion`]: #updateVersion +[`versionformat`]: #versionFormat +[rollup-plugin-json]: https://www.npmjs.com/package/@rollup/plugin-json +[semver]: https://semver.org/ diff --git a/package.json b/package.json new file mode 100644 index 0000000..3d33fdc --- /dev/null +++ b/package.json @@ -0,0 +1,44 @@ +{ + "name": "rollup-plugin-git-info", + "version": "1.0.0", + "description": "Rollup plugin to export git/build information", + "license": "BSD-3-Clause", + "repository": "github:vapier/rollup-plugin-git-info", + "author": "Mike Frysinger", + "homepage": "https://github.com/vapier/rollup-plugin-git-info", + "bugs": "https://github.com/vapier/rollup-plugin-git-info/issues", + "main": "dist/rollup-plugin-git-info.cjs", + "module": "dist/rollup-plugin-git-info.js", + "scripts": { + "build": "rollup -c", + "lint": "npm run lint:docs && npm run lint:js && npm run lint:package", + "lint:docs": "prettier --single-quote --arrow-parens avoid --trailing-comma all --write README.md", + "lint:js": "eslint --fix --cache src test rollup.config.js --ext .js", + "lint:package": "prettier --write package.json --plugin=prettier-plugin-package", + "prepare": "npm run build", + "pretest": "npm run build", + "test": "cross-env NODE_ENV=--experimental-vm-modules mocha --require esm test/*.js" + }, + "files": [ + "dist" + ], + "keywords": [ + "git", + "rollup", + "rollup-plugin" + ], + "dependencies": { + "@rollup/plugin-json": "^4.1.0" + }, + "devDependencies": { + "chai": "^4.2.0", + "cross-env": "^7.0.2", + "eslint": "^7.12.0", + "eslint-plugin-jsdoc": "^30.7.3", + "esm": "^3.2.25", + "mocha": "^8.2.0", + "prettier": "^2.1.2", + "prettier-plugin-package": "^1.2.0", + "rollup": "^2.32.0" + } +} diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..181ba62 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,23 @@ +// Copyright 2020 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Bundle for release. + */ + +import pkg from './package.json'; + +export default { + input: 'src/index.js', + output: [ + // We have to provide CommonJS still because rollup requires it :(. + {file: pkg.main, format: 'cjs', exports: 'named'}, + {file: pkg.module, format: 'es'}, + ], + plugins: [], + external: [ + 'child_process', 'path', + ...Object.keys({...pkg.dependencies, ...pkg.peerDependencies}), + ], +}; diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..f33f6d2 --- /dev/null +++ b/src/index.js @@ -0,0 +1,176 @@ +// Copyright 2020 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview The git info rollup plugin! + */ + +import jsonPlugin from '@rollup/plugin-json'; + +import child_process from 'child_process'; +import path from 'path'; + +// Declared once in module scope so it's consistent across multiple outputs. +const buildDate = new Date().toUTCString(); + +/** + * Execute a git subcommand and return the output. + * + * @param {string} cwd The directory to run in. + * @param {string} cmd The git subcommand. + * @return {string} The command's output (stdout). + */ +function runGit(cwd, cmd) { + return child_process.execSync(`git ${cmd}`, { + cwd, + encoding: 'utf-8', + windowsHide: true, + }).trim(); +} + +/** + * Format the placeholders in a string. + * + * @param {string} format The string to format. + * @param {!Object} fields The placeholders dict. + * @return {string} The formatted string. + */ +function formatString(format, fields) { + return format.replace(/\[([^\]]*)\]/g, (match, field) => { + const ret = fields[field]; + if (ret === undefined) { + throw new Error(`"${format}": unknown placeholder '${field}'`); + } + return ret; + }); +} + +/** + * Helper for doing the actual field injection into the parsed object. + * + * @param {!Object} options The plugin options. + * @param {!Object} data The object to inject our fields into. + * @param {!Object} fields The fields we'll be injecting. + */ +function inject(options, data, + {buildDate, abbrevHash, branch, commitHash, commitDate}) { + const version = data.version || '0.0.0'; + if (options.enableBuildDate) { + data.buildDate = buildDate; + } + data.gitAbbrevHash = abbrevHash; + data.gitBranch = branch; + data.gitCommitHash = commitHash; + data.gitDate = commitDate; + data.gitVersion = formatString(options.versionFormat, { + version, branch, commitHash, abbrevHash, + }); + if (options.updateVersion) { + data.version = data.gitVersion; + } +} + +/** + * Options that we pass through to the JSON plugin. + * + * @type {!Array} + */ +const jsonPluginOptions = [ + 'compact', + 'exclude', + 'include', + 'indent', + 'namedExports', + 'preferConst', +]; + +// Constants broken out to help with testing. +const defaultBranchCommand = 'rev-parse --abbrev-ref HEAD'; +const defaultCommitHashCommand = 'rev-parse HEAD'; +const defaultDateCommand = 'log -1 --format='; +const defaultDateFormat = '%cD'; +const defaultVersionFormat = '[version]-[branch]+g[abbrevHash]'; + +/** + * The rollup plugin! + * + * @param {!Object} options The plugin options from the rollup config. + * @return {!Object} The plugin configuration info. + */ +function rollupGitInfo(options = {}) { + // Extract the JSON plugin options only. + const jsonOptions = {}; + jsonPluginOptions.forEach((key) => { + if (options[key] !== undefined) { + jsonOptions[key] = options[key]; + delete options[key]; + } + }); + const jsonTransform = jsonPlugin(jsonOptions).transform; + + // Set up our default options. + options = Object.assign({ + abbrev: 7, + branchCommand: defaultBranchCommand, + commitHashCommand: defaultCommitHashCommand, + cwd: undefined, + dateCommand: defaultDateCommand, + dateFormat: defaultDateFormat, + enableBuildDate: false, + transformFilename: 'package.json', + updateVersion: true, + versionFormat: defaultVersionFormat, + }, options); + + return { + name: 'git-info', + + /** + * Transform the import! + * + * @param {string} json The input file data. + * @param {string} id The input file name. + * @return {?string} The transformed file data. + */ + transform(json, id) { + // Only monkey patch package.json imports. + if (path.basename(id) != options.transformFilename) { + return null; + } + + // Figure out the runtime info to use. + const cwd = options.cwd === undefined ? path.dirname(id) : options.cwd; + const run = TEST_ONLY.runGit.bind(this, cwd); + + // Extract the git details in this dir. + const commitHash = run(options.commitHashCommand); + const branch = run(options.branchCommand); + const abbrevHash = commitHash.slice(0, options.abbrev); + const commitDate = run(options.dateCommand + options.dateFormat); + + // Inject the fields into the JSON content. + const data = JSON.parse(json); + inject(options, data, + {buildDate, abbrevHash, branch, commitHash, commitDate}); + + // Pass the new JSON on to the JSON plugin to output. + const newJson = JSON.stringify(data); + return jsonTransform(newJson, id); + }, + }; +} + +export default rollupGitInfo; + +// Exports only for the test framework. +export const TEST_ONLY = { + defaultBranchCommand, + defaultCommitHashCommand, + defaultDateCommand, + defaultDateFormat, + defaultVersionFormat, + formatString, + inject, + runGit, +}; diff --git a/test/test.js b/test/test.js new file mode 100644 index 0000000..4b38f20 --- /dev/null +++ b/test/test.js @@ -0,0 +1,182 @@ +// Copyright 2020 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Test the plugin. + */ + +import {assert} from 'chai'; +import gitInfo from '../src/index.js'; +import {TEST_ONLY} from '../src/index.js'; + +/** + * Tests for runGit helper. + */ +describe('runGit', () => { + const {runGit} = TEST_ONLY; + it('version', () => { + const ret = runGit('.', 'version'); + assert.include(ret, 'git version'); + }); +}); + +/** + * Tests for formatString helper. + */ +describe('formatString', () => { + const {formatString} = TEST_ONLY; + it('no fields', () => { + assert.equal('foo', formatString('foo', {})); + }); + + it('one field', () => { + assert.equal('bar', formatString('[foo]', {foo: 'bar'})); + }); + + it('repeat field', () => { + assert.equal('bar-bar', formatString('[foo]-[foo]', {foo: 'bar'})); + }); + + it('many fields', () => { + assert.equal('FB', formatString('[foo][bar]', {foo: 'F', bar: 'B'})); + }); + + it('unknown field', () => { + assert.throws(() => formatString('[foo][bar]', {})); + }); +}); + +/** + * Tests for injection. + */ +describe('inject', () => { + const {defaultVersionFormat, inject} = TEST_ONLY; + const defOptions = { + updateVersion: true, + versionFormat: defaultVersionFormat, + }; + const defFields = { + buildDate: 'today!', + abbrevHash: '12345', + branch: 'main', + commitHash: 'myhash', + commitDate: 'long-ago', + }; + + it('normal', () => { + const data = {version: '1.2.3'}; + inject(defOptions, data, defFields); + assert.deepEqual({ + gitAbbrevHash: '12345', + gitBranch: 'main', + gitCommitHash: 'myhash', + gitDate: 'long-ago', + gitVersion: '1.2.3-main+g12345', + version: '1.2.3-main+g12345', + }, data); + }); + + it('missing version', () => { + const data = {}; + inject(defOptions, data, defFields); + assert.equal(data.version, '0.0.0-main+g12345'); + assert.equal(data.gitVersion, '0.0.0-main+g12345'); + }); + + it('options enableBuildDate=true', () => { + const data = {}; + inject({...defOptions, enableBuildDate: true}, data, defFields); + assert.equal(data.buildDate, 'today!'); + }); + + it('options updateVersion=false', () => { + const data = {}; + inject({...defOptions, updateVersion: false}, data, defFields); + assert.isUndefined(data.version); + assert.equal(data.gitVersion, '0.0.0-main+g12345'); + }); +}); + +/** + * Real tests for the plugin itself. + */ +describe('plugin', () => { + const {defaultBranchCommand, defaultCommitHashCommand, defaultDateCommand, + defaultDateFormat} = TEST_ONLY; + + before(function() { + this.oldRunGit = TEST_ONLY.runGit; + }); + after(function() { + TEST_ONLY.runGit = this.oldRunGit; + }); + + it('name', () => { + assert.equal(gitInfo().name, 'git-info'); + }); + + it('instantiate', () => { + const plugin = new gitInfo(); + }); + + it('skip file', () => { + TEST_ONLY.runGit = () => assert.fail('Should not be run'); + const plugin = new gitInfo(); + assert.isNull(plugin.transform('', 'my.json')); + }); + + it('options abbrev', () => { + TEST_ONLY.runGit = () => 'long-output-should-be-abbrev'; + const plugin = new gitInfo({abbrev: 10}); + const {code} = plugin.transform('{}', 'package.json'); + assert.include(code, 'export var gitAbbrevHash = "long-outpu";'); + }); + + it('options cwd', () => { + let callCount = 0; + TEST_ONLY.runGit = (cwd) => { + ++callCount; + assert.equal(cwd, '/foo/bar'); + return 'output'; + }; + const plugin = new gitInfo({cwd: '/foo/bar'}); + plugin.transform('{}', 'package.json'); + assert.notEqual(callCount, 0); + }); + + it('options transformFilename', () => { + TEST_ONLY.runGit = () => 'output'; + const plugin = new gitInfo({transformFilename: 'my.json'}); + const {code} = plugin.transform('{}', 'my.json'); + assert.include(code, 'export var gitVersion = "0.0.0-output+goutput";'); + }); + + it('options versionFormat', () => { + TEST_ONLY.runGit = () => 'output'; + const plugin = new gitInfo({versionFormat: 'fooie!'}); + const {code} = plugin.transform('{}', 'package.json'); + assert.include(code, 'export var version = "fooie!";'); + }); + + it('transform', () => { + TEST_ONLY.runGit = (cwd, cmd) => { + switch (cmd) { + case defaultBranchCommand: + return 'main'; + case defaultCommitHashCommand: + return 'abcdef0123456789a0b1c2d3e4f5069876543210'; + case defaultDateCommand + defaultDateFormat: + return 'today!'; + default: + assert.fail(`mock for "${cmd}" is missing`); + } + }; + + const plugin = new gitInfo(); + const {code} = plugin.transform('{}', 'package.json'); + // Could assert all the fields, but inject() coverage above should be OK. + assert.include(code, 'export var gitCommitHash'); + assert.include(code, 'export var gitDate'); + }); +});