Skip to content

Commit

Permalink
refactor(describe-ref): Add JSDoc types, remove test-only export
Browse files Browse the repository at this point in the history
BREAKING CHANGE: The test-only 'parse()' export has been removed.
  • Loading branch information
evocateur committed Dec 8, 2020
1 parent 8f788b2 commit e5cf30c
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 16 deletions.
49 changes: 38 additions & 11 deletions utils/describe-ref/__tests__/describe-ref.test.js
Expand Up @@ -7,10 +7,11 @@ const { describeRef, describeRefSync } = require("../lib/describe-ref");

const DEFAULT_ARGS = ["describe", "--always", "--long", "--dirty", "--first-parent"];

childProcess.exec.mockResolvedValue({ stdout: "v1.2.3-4-g567890a" });
childProcess.execSync.mockReturnValue("v1.2.3-4-g567890a");

describe("describeRef()", () => {
beforeEach(() => {
childProcess.exec.mockResolvedValueOnce({ stdout: "v1.2.3-4-g567890a" });
});

it("resolves parsed metadata", async () => {
const result = await describeRef();

Expand Down Expand Up @@ -54,6 +55,10 @@ describe("describeRef()", () => {
});

describe("describeRefSync()", () => {
beforeEach(() => {
childProcess.execSync.mockReturnValueOnce("v1.2.3-4-g567890a");
});

it("returns parsed metadata", () => {
const result = describeRefSync();

Expand Down Expand Up @@ -84,31 +89,49 @@ describe("describeRefSync()", () => {
options
);
});

it("accepts includeMergedTags argument", async () => {
const includeMergedTags = true;

describeRefSync({}, includeMergedTags);

const newArgs = [...DEFAULT_ARGS];
newArgs.pop();
expect(childProcess.execSync).toHaveBeenLastCalledWith("git", newArgs, {});
});
});

describe("describeRef.parse()", () => {
describe("parser", () => {
it("matches independent tags", () => {
const result = describeRef.parse("pkg-name@1.2.3-4-g567890a");
childProcess.execSync.mockReturnValueOnce("pkg-name@1.2.3-4-g567890a");

const result = describeRefSync();

expect(result.lastTagName).toBe("pkg-name@1.2.3");
expect(result.lastVersion).toBe("1.2.3");
});

it("matches independent tags for scoped packages", () => {
const result = describeRef.parse("@scope/pkg-name@1.2.3-4-g567890a");
childProcess.execSync.mockReturnValueOnce("@scope/pkg-name@1.2.3-4-g567890a");

const result = describeRefSync();

expect(result.lastTagName).toBe("@scope/pkg-name@1.2.3");
expect(result.lastVersion).toBe("1.2.3");
});

it("matches dirty annotations", () => {
const result = describeRef.parse("pkg-name@1.2.3-4-g567890a-dirty");
childProcess.execSync.mockReturnValueOnce("pkg-name@1.2.3-4-g567890a-dirty");

const result = describeRefSync();

expect(result.isDirty).toBe(true);
});

it("handles non-matching strings safely", () => {
const result = describeRef.parse("poopy-pants");
childProcess.execSync.mockReturnValueOnce("poopy-pants");

const result = describeRefSync();

expect(result).toEqual({
isDirty: false,
Expand All @@ -120,10 +143,11 @@ describe("describeRef.parse()", () => {
});

it("detects fallback and returns partial metadata", () => {
childProcess.execSync.mockReturnValueOnce("a1b2c3d");
childProcess.execSync.mockReturnValueOnce("123");

const options = { cwd: "bar" };
const result = describeRef.parse("a1b2c3d", options);
const result = describeRefSync(options);

expect(childProcess.execSync).toHaveBeenLastCalledWith(
"git",
Expand All @@ -138,9 +162,10 @@ describe("describeRef.parse()", () => {
});

it("detects dirty fallback and returns partial metadata", () => {
childProcess.execSync.mockReturnValueOnce("a1b2c3d-dirty");
childProcess.execSync.mockReturnValueOnce("456");

const result = describeRef.parse("a1b2c3d-dirty");
const result = describeRefSync();

expect(childProcess.execSync).toHaveBeenLastCalledWith("git", ["rev-list", "--count", "a1b2c3d"], {});
expect(result).toEqual({
Expand All @@ -151,7 +176,9 @@ describe("describeRef.parse()", () => {
});

it("should return metadata for tag names that are sha-like", () => {
const result = describeRef.parse("20190104-5-g6fb4e3293");
childProcess.execSync.mockReturnValueOnce("20190104-5-g6fb4e3293");

const result = describeRefSync();

expect(result).toEqual({
isDirty: false,
Expand Down
51 changes: 46 additions & 5 deletions utils/describe-ref/lib/describe-ref.js
Expand Up @@ -5,10 +5,36 @@ const childProcess = require("@lerna/child-process");

module.exports = describeRef;
module.exports.describeRef = describeRef;
module.exports.parse = parse;
module.exports.sync = describeRefSync;
module.exports.describeRefSync = describeRefSync;

/**
* @typedef {object} DescribeRefOptions
* @property {string} [cwd] Defaults to `process.cwd()`
* @property {string} [match] Glob passed to `--match` flag
*/

/**
* @typedef {object} DescribeRefFallbackResult When annotated release tags are missing
* @property {boolean} isDirty
* @property {string} refCount
* @property {string} sha
*/

/**
* @typedef {object} DescribeRefDetailedResult When annotated release tags are present
* @property {string} lastTagName
* @property {string} lastVersion
* @property {boolean} isDirty
* @property {string} refCount
* @property {string} sha
*/

/**
* Build `git describe` args.
* @param {DescribeRefOptions} options
* @param {boolean} [includeMergedTags]
*/
function getArgs(options, includeMergedTags) {
let args = [
"describe",
Expand All @@ -34,11 +60,16 @@ function getArgs(options, includeMergedTags) {
return args;
}

/**
* @param {DescribeRefOptions} [options]
* @param {boolean} [includeMergedTags]
* @returns {Promise<DescribeRefFallbackResult|DescribeRefDetailedResult>}
*/
function describeRef(options = {}, includeMergedTags) {
const promise = childProcess.exec("git", getArgs(options, includeMergedTags), options);

return promise.then(({ stdout }) => {
const result = parse(stdout, options);
const result = parse(stdout, options.cwd);

log.verbose("git-describe", "%j => %j", options && options.match, stdout);
log.silly("git-describe", "parsed => %j", result);
Expand All @@ -47,25 +78,35 @@ function describeRef(options = {}, includeMergedTags) {
});
}

/**
* @param {DescribeRefOptions} [options]
* @param {boolean} [includeMergedTags]
*/
function describeRefSync(options = {}, includeMergedTags) {
const stdout = childProcess.execSync("git", getArgs(options, includeMergedTags), options);
const result = parse(stdout, options);
const result = parse(stdout, options.cwd);

// only called by collect-updates with no matcher
log.silly("git-describe.sync", "%j => %j", stdout, result);

return result;
}

function parse(stdout, options = {}) {
/**
* Parse git output and return relevant metadata.
* @param {string} stdout Result of `git describe`
* @param {string} [cwd] Defaults to `process.cwd()`
* @returns {DescribeRefFallbackResult|DescribeRefDetailedResult}
*/
function parse(stdout, cwd) {
const minimalShaRegex = /^([0-9a-f]{7,40})(-dirty)?$/;
// when git describe fails to locate tags, it returns only the minimal sha
if (minimalShaRegex.test(stdout)) {
// repo might still be dirty
const [, sha, isDirty] = minimalShaRegex.exec(stdout);

// count number of commits since beginning of time
const refCount = childProcess.execSync("git", ["rev-list", "--count", sha], options);
const refCount = childProcess.execSync("git", ["rev-list", "--count", sha], { cwd });

return { refCount, sha, isDirty: Boolean(isDirty) };
}
Expand Down

0 comments on commit e5cf30c

Please sign in to comment.