-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Creating wonder-stuff-ci package! #628
Changes from 8 commits
8fd79c5
b4a779e
ccc5a16
afb8d7d
c9a0fed
062cbe3
637aa46
74c1788
3ef0056
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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. |
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. |
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": { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you're missing a critical
And then make a |
||
"test": "bash -c 'yarn --silent --cwd \"../..\" test ${@:0} $($([[ ${@: -1} = -* ]] || [[ ${@: -1} = bash ]]) && echo $PWD)'" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was trying to get the test to run with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah yeah - I think this handles some of the trickiness with being in a monorepo, if I remember correctly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typically, you have to run |
||
}, | ||
"devDependencies": { | ||
"@types/node": "^18.15.11", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I needed to add this to resolve some types in the functions i need. |
||
"ws-dev-build-settings": "^1.1.0" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the ws-dev-build-settings? Should I be adding the @types/node into this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is internal to wonder-stuff itself, these are the shared tools that we use to help with test running, builds, etc. I'm not sure if it makes sense to have the node types, or not! I'd say "no" only because not everything in wonder-stuff is designed to run in Node. |
||
}, | ||
"author": "", | ||
"license": "MIT" | ||
} |
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"); | ||
}, | ||
); | ||
}); |
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", | ||
]); | ||
}); | ||
}); |
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", | ||
]); | ||
}); | ||
}); |
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"); | ||
}; |
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; | ||
}; |
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,24 @@ | ||||||||
/** | ||||||||
* 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 {Object} The release version and prefix, if found; otherwise, `null`. | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: If you create the type as suggested below and then replace |
||||||||
*/ | ||||||||
export const extractMobileReleaseInfoFromBranchName = (arg: string | null) => { | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: Currently, the return type is inferred from the code and as such, there isn't a concrete way to refer to this type. It would be helpful for calling code if there was one as it can be used by them to type their own arguments and variables. Something like:
And then:
Suggested change
|
||||||||
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; | ||||||||
}; |
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); | ||
}; |
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); | ||
}; |
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"; |
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": [ | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,5 +12,6 @@ | |
{"path": "./packages/wonder-stuff-sentry"}, | ||
{"path": "./packages/wonder-stuff-server"}, | ||
{"path": "./packages/wonder-stuff-testing"}, | ||
{"path": "./packages/wonder-stuff-ci"}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was curious how the tsconfig-build.json is used in wonderstuff? What else is needed for me to get this new package published to npm? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All of it is managed by Changesets - once you changes are approved and landed then a Changesets PR will be generated, once that's approved and landed it'll publish to NPM! |
||
], | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
praise: Thanks!