Skip to content

Commit

Permalink
Creating wonder-stuff-ci package! (#628)
Browse files Browse the repository at this point in the history
## Summary:
- Created a new package `wonder-stuff-ci`. This package will contain typescript functions that are used throughout our scripts and automations ( OLC, Bitrise, Github Actions, etc.). I found a need for this when needing common functions for interacting with mobile releases.
- Ported over three functions that I need and will continue adding functions into this package as they arise.

Issue: FEI-5058

## Test plan:
- Wrote Jest test for the functions.

Author: jlauzy

Reviewers: jlauzy, jeresig, kevinbarabash, somewhatabstract

Required Reviewers:

Approved By: kevinbarabash, jeresig, somewhatabstract

Checks: ✅ codecov/project, ✅ Test (macos-latest, 16.x), ✅ CodeQL, ✅ Lint, typecheck, and coverage check (ubuntu-latest, 16.x), ⏭  dependabot, ✅ gerald, ✅ Prime node_modules cache for primary configuration (ubuntu-latest, 16.x), ✅ Analyze (javascript)

Pull Request URL: #628
  • Loading branch information
jlauzy authored Apr 12, 2023
1 parent 4c6457b commit d2fbdcc
Show file tree
Hide file tree
Showing 19 changed files with 309 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/clean-houses-press.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@khanacademy/wonder-stuff-ci": patch
---

Adding a new wonder-stuff-ci package that contains functions that are useful for the mobile release.
3 changes: 3 additions & 0 deletions packages/wonder-stuff-ci/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# wonder-stuff-ci

This Wonder Stuff package contains functions for automation and scripts.
23 changes: 23 additions & 0 deletions packages/wonder-stuff-ci/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"publishConfig": {
"access": "public"
},
"engines": {
"node": ">=16"
},
"name": "@khanacademy/wonder-stuff-ci",
"version": "0.0.1",
"description": "Functions for automation and scripts.",
"module": "dist/es/index.js",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"test": "bash -c 'yarn --silent --cwd \"../..\" test ${@:0} $($([[ ${@: -1} = -* ]] || [[ ${@: -1} = bash ]]) && echo $PWD)'"
},
"devDependencies": {
"@types/node": "^18.15.11",
"ws-dev-build-settings": "^1.1.0"
},
"author": "",
"license": "MIT"
}
17 changes: 17 additions & 0 deletions packages/wonder-stuff-ci/src/__tests__/buffer-to-string.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {bufferToString} from "../buffer-to-string";

describe("#bufferToString", () => {
it.each(["testing", Buffer.from("testing")])(
"the buffer to string function returns the correct value",
(testCase: string | Buffer) => {
// Arrange
const input = testCase;

// Act
const result = bufferToString(input);

// Assert
expect(result).toBe("testing");
},
);
});
45 changes: 45 additions & 0 deletions packages/wonder-stuff-ci/src/__tests__/compare-versions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {compareVersions} from "../compare-versions";

describe("#compareVersions", () => {
it.each(["7.8.1", "7.9.0", "7.10.0", "8.0.0"])(
"return 1 if version 1 is greater than version 2",
(testCase: string) => {
// Arrange
const version1 = testCase;
const version2 = "7.8.0";

// Act
const result = compareVersions(version1, version2);

// Assert
expect(result).toBe(1);
},
);

it.each(["7.7.1", "7.6.0", "6.10.0"])(
"return -1 if version 1 is less than version 2",
(testCase: string) => {
// Arrange
const version1 = testCase;
const version2 = "7.8.0";

// Act
const result = compareVersions(version1, version2);

// Assert
expect(result).toBe(-1);
},
);

it("return 0 if version 1 is equal to version 2", () => {
// Arrange
const version1 = "7.8.0";
const version2 = "7.8.0";

// Act
const result = compareVersions(version1, version2);

// Assert
expect(result).toBe(0);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {extractMobileReleaseInfoFromBranchName} from "../extract-mobile-release-info-from-branch-name";

describe("#extractMobileReleaseInfoFromBranchName", () => {
it.each([
"release/unified/7.8.0",
"release/android/7.8.0",
"release/ios/7.8.0",
])("get the version from the release branch", (testCase: string | null) => {
// Arrange
const releaseBranch = testCase;

// Act
const result = extractMobileReleaseInfoFromBranchName(releaseBranch);

// Assert
expect(result?.version).toBe("7.8.0");
});

it.each(["release/testing", "android/7.8.0", "ios/7.8.0", null])(
"return null if the branch is not a release branch",
(testCase: string | null) => {
// Arrange
const releaseBranch = testCase;

// Act
const result =
extractMobileReleaseInfoFromBranchName(releaseBranch);

// Assert
expect(result).toBe(null);
},
);

it("get the correct prefix string", () => {
// Arrange
const branchName = "release/unified/7.8.0";

// Act
const result = extractMobileReleaseInfoFromBranchName(branchName);

// Assert
expect(result?.prefix).toBe("release/unified/");
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {getMobileReleaseTags} from "../get-mobile-release-tags";

jest.mock("../get-tags-from-git");

describe("#getMobileReleaseTags", () => {
it("get the tags in sorted order by ascending version value", async () => {
// Arrange
jest.spyOn(
require("../get-tags-from-git"),
"getTagsFromGit",
).mockReturnValue(["android-7.10.0", "unified-7.8.0", "unified-7.9.0"]);

// Act
const result = await getMobileReleaseTags();

// Assert
expect(result).toStrictEqual([
"unified-7.8.0",
"unified-7.9.0",
"android-7.10.0",
]);
});
});
24 changes: 24 additions & 0 deletions packages/wonder-stuff-ci/src/__tests__/get-tags-from-git.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {getTagsFromGit} from "../get-tags-from-git";

import {execAsync} from "../exec-async";

jest.mock("../exec-async");

describe("#getTagsFromGit", () => {
it("return an array of release tags split by new line", async () => {
// Arrange
jest.spyOn(require("../exec-async"), "execAsync").mockReturnValue({
stdout: "android-7.10.0\nunified-7.8.0\nunified-7.9.0",
});

// Act
const result = await getTagsFromGit();

// Assert
expect(result).toStrictEqual([
"android-7.10.0",
"unified-7.8.0",
"unified-7.9.0",
]);
});
});
10 changes: 10 additions & 0 deletions packages/wonder-stuff-ci/src/buffer-to-string.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Coerce a buffer or string into a string.
*
* @param {Buffer | string} input The `Buffer` or `string`
* that should be coerced to a string.
* @returns {string} The `string` representation of the given parameter.
*/
export const bufferToString = (input: Buffer | string): string => {
return typeof input === "string" ? input : input.toString("utf8");
};
20 changes: 20 additions & 0 deletions packages/wonder-stuff-ci/src/compare-versions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Compare two versions or tags
* @param {string} v1 A version of the form `<num>.<num>.<num>` or tag of the form `<tag>-<num>.<num>.<num>`.
* @param {string} v2 A version of the form `<num>.<num>.<num>` or tag of the form `<tag>-<num>.<num>.<num>`.
* @returns {number} 1 if v1 > v2, -1 if v1 < v2, 0 if v1 == v2
*/
export const compareVersions = (v1: string, v2: string) => {
const v1v = v1.includes("-") ? v1.split("-")[1] : v1;
const v2v = v2.includes("-") ? v2.split("-")[1] : v2;
const v1p = v1v.replace(/^v/g, "").split(".");
const v2p = v2v.replace(/^v/g, "").split(".");
for (let i = 0; i < v1p.length || i < v2p.length; i++) {
const p1 = +v1p[i] || 0;
const p2 = +v2p[i] || 0;
if (+p1 !== +p2) {
return p1 > p2 ? 1 : -1;
}
}
return 0;
};
7 changes: 7 additions & 0 deletions packages/wonder-stuff-ci/src/exec-async.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {exec} from "child_process";
import util from "util";

/**
* A simple promisified version of child_process.exec, so we can `await` it
*/
export const execAsync = util.promisify(exec);
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Extract release version and prefix from mobile release branch.
*
* Example, given the branch `release/unified/v7.8.0`:
* {
* prefix: "release/unified/",
* version: "7.8.0"
* }
*
* @param {string} The release branch of the form `[release/[unified|ios|android]]/[v]<num>.<num>.<num>[-extra]`.
* @returns {MobileReleaseInfo} The release version and prefix, if found; otherwise, `null`.
*/
export const extractMobileReleaseInfoFromBranchName = (
arg: string | null,
): MobileReleaseBranchInfo | null => {
if (!arg) {
return null;
}

const match = arg.match(
/^(release\/(ios|android|unified)\/)?v?(\d+\.\d+\.\d+(-\w*)*)$/i,
);
return match && match.length >= 3 && match[3]
? {prefix: match[1] || "release/unified/", version: match[3]}
: null;
};
20 changes: 20 additions & 0 deletions packages/wonder-stuff-ci/src/get-mobile-release-tags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {compareVersions} from "./compare-versions";
import {getTagsFromGit} from "./get-tags-from-git";

/**
* Get mobile release tags.
*
* Tags are filtered to only include those matching our tag version format
* (`<tag>-<num>.<num>.<num>`), then they are sorted by the version information from
* earliest version to most recent.
*
* @returns {Promise<Array<string>>} all release tags sorted creation time ascending
*/
export const getMobileReleaseTags = async (): Promise<Array<string>> => {
const tags = await getTagsFromGit();
return tags
.filter((tag) =>
tag.match(/^(ios|android|unified)-(\d+\.\d+\.\d+(-\w*)*)$/i),
)
.sort(compareVersions);
};
13 changes: 13 additions & 0 deletions packages/wonder-stuff-ci/src/get-tags-from-git.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {execAsync} from "./exec-async";

/**
* Get all tags from git.
*
* @returns {Promise<Array<string>>} A promise of all git tags sorted by creation time ascending.
*/
export const getTagsFromGit = async (): Promise<Array<string>> => {
// Why not use simple-git here? Because for some reason it takes like 100x as long.
await execAsync("git fetch --tags");
const {stdout} = await execAsync("git tag");
return stdout.split("\n").filter(Boolean);
};
6 changes: 6 additions & 0 deletions packages/wonder-stuff-ci/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export {compareVersions} from "./compare-versions";
export {extractMobileReleaseInfoFromBranchName} from "./extract-mobile-release-info-from-branch-name";
export {getMobileReleaseTags} from "./get-mobile-release-tags";
export {getTagsFromGit} from "./get-tags-from-git";
export {execAsync} from "./exec-async";
export {bufferToString} from "./buffer-to-string";
7 changes: 7 additions & 0 deletions packages/wonder-stuff-ci/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* The information about a release branch.
*/
type MobileReleaseBranchInfo = {
prefix: string;
version: string;
};
10 changes: 10 additions & 0 deletions packages/wonder-stuff-ci/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"exclude": ["dist"],
"extends": "../tsconfig-shared.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
},
"references": [
]
}
1 change: 1 addition & 0 deletions tsconfig-build.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@
{"path": "./packages/wonder-stuff-sentry"},
{"path": "./packages/wonder-stuff-server"},
{"path": "./packages/wonder-stuff-testing"},
{"path": "./packages/wonder-stuff-ci"},
],
}
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2289,6 +2289,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240"
integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==

"@types/node@^18.15.11":
version "18.15.11"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f"
integrity sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==

"@types/normalize-package-data@^2.4.0":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301"
Expand Down

0 comments on commit d2fbdcc

Please sign in to comment.