@api3/commons 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,6 +25,7 @@ Read the documentation and sources of each module how to use it in the project.
25
25
  - [http](./src/http/README.md)
26
26
  - [logger](./src/logger/README.md)
27
27
  - [processing](./src/processing/README.md)
28
+ - [release-scripts](./src/release-scripts/README.md)
28
29
  - [run-in-loop](./src/run-in-loop/README.md)
29
30
  - [utils](./src/utils/README.md)
30
31
 
@@ -3,4 +3,5 @@ export * from './logger';
3
3
  export * from './processing';
4
4
  export * from './config-parsing';
5
5
  export * from './config-hash';
6
+ export * from './release-scripts';
6
7
  //# sourceMappingURL=node-index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"node-index.d.ts","sourceRoot":"","sources":["../src/node-index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC"}
1
+ {"version":3,"file":"node-index.d.ts","sourceRoot":"","sources":["../src/node-index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC"}
@@ -19,4 +19,5 @@ __exportStar(require("./logger"), exports);
19
19
  __exportStar(require("./processing"), exports);
20
20
  __exportStar(require("./config-parsing"), exports);
21
21
  __exportStar(require("./config-hash"), exports);
22
+ __exportStar(require("./release-scripts"), exports);
22
23
  //# sourceMappingURL=node-index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"node-index.js","sourceRoot":"","sources":["../src/node-index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,oDAAkC;AAClC,2CAAyB;AACzB,+CAA6B;AAC7B,mDAAiC;AACjC,gDAA8B"}
1
+ {"version":3,"file":"node-index.js","sourceRoot":"","sources":["../src/node-index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,oDAAkC;AAClC,2CAAyB;AACzB,+CAA6B;AAC7B,mDAAiC;AACjC,gDAA8B;AAC9B,oDAAkC"}
@@ -0,0 +1,2 @@
1
+ export * from './tag-and-release';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/release-scripts/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./tag-and-release"), exports);
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/release-scripts/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,oDAAkC"}
@@ -0,0 +1,2 @@
1
+ export declare const tagAndRelease: (repo: string, branch?: string) => Promise<void>;
2
+ //# sourceMappingURL=tag-and-release.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tag-and-release.d.ts","sourceRoot":"","sources":["../../src/release-scripts/tag-and-release.ts"],"names":[],"mappings":"AA4DA,eAAO,MAAM,aAAa,SAAgB,MAAM,WAAU,MAAM,kBAgC/D,CAAC"}
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ // ########################################################################################
3
+ // This script uses the version defined in the root package.json to determine if a
4
+ // new Git tag and Github release should be created. The Github release notes are
5
+ // generated automatically using the commit information
6
+ //
7
+ // The following secrets are required:
8
+ //
9
+ // 1. GH_ACCESS_TOKEN - A "fine-grained personal access token" generated through the
10
+ // Github UI. It seems like these tokens are scoped to a user, rather than an
11
+ // organisation.
12
+ //
13
+ // The following minimum permissions are required:
14
+ // Read - access to metadata
15
+ // Read & write - access to actions and code
16
+ // #######################################################################################
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.tagAndRelease = void 0;
19
+ const node_child_process_1 = require("node:child_process");
20
+ const node_fs_1 = require("node:fs");
21
+ const node_path_1 = require("node:path");
22
+ const promise_utils_1 = require("@api3/promise-utils");
23
+ const rest_1 = require("@octokit/rest");
24
+ const execSyncWithErrorHandling = (command) => {
25
+ // eslint-disable-next-line functional/no-try-statements
26
+ try {
27
+ return (0, node_child_process_1.execSync)(command).toString();
28
+ }
29
+ catch (error) {
30
+ console.info(error.message);
31
+ console.info('STDOUT', error.stdout.toString());
32
+ console.info('STDERR', error.stderr.toString());
33
+ process.exit(1);
34
+ }
35
+ };
36
+ const createGithubRelease = async (repo, tagName) => {
37
+ if (!process.env.GH_ACCESS_TOKEN) {
38
+ console.info(`GH_ACCESS_TOKEN not set. Skipping release creation`);
39
+ return null;
40
+ }
41
+ // Ensure the GH_ACCESS_TOKEN secret is set on Github and has the relevant permissions
42
+ const octokit = new rest_1.Octokit({ auth: process.env.GH_ACCESS_TOKEN });
43
+ const createRelease = async () => octokit.rest.repos.createRelease({
44
+ owner: 'api3dao',
45
+ repo,
46
+ tag_name: tagName, // eslint-disable-line camelcase
47
+ generate_release_notes: true, // eslint-disable-line camelcase
48
+ });
49
+ console.info(`Creating Github release...`);
50
+ const goRes = await (0, promise_utils_1.go)(createRelease, { totalTimeoutMs: 15_000 });
51
+ if (!goRes.success) {
52
+ // We don't want to fail CI if the release fails to create. This can be done manually through Github's UI
53
+ console.info(`Unable to create Github release`);
54
+ console.info(goRes.error.message);
55
+ return null;
56
+ }
57
+ return goRes.data;
58
+ };
59
+ const tagAndRelease = async (repo, branch = 'main') => {
60
+ console.info('Ensuring working directory is clean...');
61
+ const gitStatus = execSyncWithErrorHandling('git status --porcelain');
62
+ if (gitStatus !== '')
63
+ throw new Error('Working directory is not clean');
64
+ console.info(`Ensuring we are on the ${branch} branch...`);
65
+ const currentBranch = execSyncWithErrorHandling('git branch --show-current');
66
+ if (currentBranch !== `${branch}\n`)
67
+ throw new Error(`Not on the ${branch} branch`);
68
+ console.info('Ensuring we are up to date with the remote...');
69
+ execSyncWithErrorHandling('git fetch');
70
+ const gitDiff = execSyncWithErrorHandling(`git diff origin/${branch}`);
71
+ if (gitDiff !== '')
72
+ throw new Error('Not up to date with the remote');
73
+ const packageJson = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(__dirname, '../../package.json'), 'utf8'));
74
+ const { version } = packageJson;
75
+ console.info(`Version set to ${version}...`);
76
+ const gitTag = execSyncWithErrorHandling(`git tag -l '*v${version}*'`);
77
+ if (gitTag !== '')
78
+ throw new Error(`git tag v${version} already exists`);
79
+ console.info('Creating new annotated git tag...');
80
+ execSyncWithErrorHandling(`git tag -a v${version} -m "v${version}"`);
81
+ console.info('Pushing git tag...');
82
+ // NOTE: in order to push, a valid access token is expected as GH_ACCESS_TOKEN
83
+ execSyncWithErrorHandling(`git push origin v${version} --no-verify`);
84
+ await createGithubRelease(repo, `v${version}`);
85
+ console.info(`Done!`);
86
+ };
87
+ exports.tagAndRelease = tagAndRelease;
88
+ //# sourceMappingURL=tag-and-release.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tag-and-release.js","sourceRoot":"","sources":["../../src/release-scripts/tag-and-release.ts"],"names":[],"mappings":"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"}
@@ -40,5 +40,7 @@ export interface RunInLoopOptions {
40
40
  */
41
41
  initialDelayMs?: number;
42
42
  }
43
- export declare const runInLoop: (fn: () => Promise<void>, options: RunInLoopOptions) => Promise<never>;
43
+ export declare const runInLoop: (fn: () => Promise<{
44
+ shouldContinueRunning: boolean;
45
+ } | void>, options: RunInLoopOptions) => Promise<void>;
44
46
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/run-in-loop/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAGxC,MAAM,WAAW,gBAAgB;IAC/B,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAC;IACf,kDAAkD;IAClD,QAAQ,CAAC,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;IAC7B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,eAAO,MAAM,SAAS,OAAc,MAAM,OAAO,CAAC,IAAI,CAAC,WAAW,gBAAgB,mBAoDjF,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/run-in-loop/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAGxC,MAAM,WAAW,gBAAgB;IAC/B,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAC;IACf,kDAAkD;IAClD,QAAQ,CAAC,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;IAC7B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,eAAO,MAAM,SAAS,OAChB,MAAM,OAAO,CAAC;IAAE,qBAAqB,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAAC,WACnD,gBAAgB,kBA4D1B,CAAC"}
@@ -18,7 +18,7 @@ const runInLoop = async (fn, options) => {
18
18
  const executionId = (0, utils_1.generateRandomBytes32)();
19
19
  if (enabled) {
20
20
  const context = logLabel ? { executionId, label: logLabel } : { executionId };
21
- await logger.runWithContext(context, async () => {
21
+ const shouldContinueRunning = await logger.runWithContext(context, async () => {
22
22
  const goRes = await (0, promise_utils_1.go)(fn, hardTimeoutMs ? { totalTimeoutMs: hardTimeoutMs } : {}); // NOTE: This is a safety net to prevent the loop from hanging
23
23
  if (!goRes.success) {
24
24
  logger.error(`Unexpected runInLoop error`, goRes.error);
@@ -30,7 +30,12 @@ const runInLoop = async (fn, options) => {
30
30
  else {
31
31
  logger.info(`Execution finished`, { executionTimeMs });
32
32
  }
33
+ return goRes.data?.shouldContinueRunning === false ? false : true;
33
34
  });
35
+ // Stop loop execution if the callback return value indicates it should stop
36
+ if (!shouldContinueRunning) {
37
+ break;
38
+ }
34
39
  }
35
40
  else {
36
41
  // If the bot is disabled, we still want to run the loop to prevent the process from hanging. We also want to
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/run-in-loop/index.ts"],"names":[],"mappings":";;;AAAA,uDAAyC;AAGzC,oCAAwD;AA4CjD,MAAM,SAAS,GAAG,KAAK,EAAE,EAAuB,EAAE,OAAyB,EAAE,EAAE;IACpF,MAAM,EACJ,MAAM,EACN,QAAQ,EACR,WAAW,GAAG,CAAC,EACf,aAAa,GAAG,CAAC,EACjB,aAAa,EACb,aAAa,EACb,aAAa,EACb,OAAO,GAAG,IAAI,EACd,cAAc,GACf,GAAG,OAAO,CAAC;IAEZ,IAAI,aAAa,IAAI,aAAa,IAAI,aAAa,GAAG,aAAa,EAAE,CAAC;QACpE,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;IAC1E,CAAC;IACD,IAAI,aAAa,IAAI,aAAa,IAAI,aAAa,GAAG,aAAa,EAAE,CAAC;QACpE,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;IAC1E,CAAC;IAED,IAAI,cAAc;QAAE,MAAM,IAAA,aAAK,EAAC,cAAc,CAAC,CAAC;IAEhD,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QACzC,MAAM,WAAW,GAAG,IAAA,6BAAqB,GAAE,CAAC;QAE5C,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;YAC9E,MAAM,MAAM,CAAC,cAAc,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE;gBAC9C,MAAM,KAAK,GAAG,MAAM,IAAA,kBAAE,EAAC,EAAE,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,8DAA8D;gBAClJ,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;oBACnB,MAAM,CAAC,KAAK,CAAC,4BAA4B,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;gBAC1D,CAAC;gBAED,MAAM,eAAe,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,cAAc,CAAC;gBAC3D,IAAI,eAAe,IAAI,aAAc,EAAE,CAAC;oBACtC,MAAM,CAAC,IAAI,CAAC,yCAAyC,EAAE,EAAE,eAAe,EAAE,CAAC,CAAC;gBAC9E,CAAC;qBAAM,CAAC;oBACN,MAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE,EAAE,eAAe,EAAE,CAAC,CAAC;gBACzD,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,6GAA6G;YAC7G,0CAA0C;YAC1C,MAAM,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;QAC7C,CAAC;QAED,MAAM,iBAAiB,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,GAAG,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,cAAc,CAAC,EAAE,CAAC,CAAC,CAAC;QAC1F,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,iBAAiB,CAAC,CAAC;QAC5D,MAAM,cAAc,GAAG,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QACpF,IAAI,cAAc,GAAG,CAAC;YAAE,MAAM,IAAA,aAAK,EAAC,cAAc,CAAC,CAAC;IACtD,CAAC;AACH,CAAC,CAAC;AApDW,QAAA,SAAS,aAoDpB"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/run-in-loop/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@api3/commons",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "keywords": [],
5
5
  "license": "MIT",
6
6
  "engines": {
@@ -26,11 +26,13 @@
26
26
  "dependencies": {
27
27
  "@api3/ois": "^2.3.2",
28
28
  "@api3/promise-utils": "^0.4.0",
29
- "axios": "^1.7.2",
29
+ "@octokit/rest": "^20.1.1",
30
+ "@octokit/types": "^13.5.0",
31
+ "axios": "^1.7.7",
30
32
  "dotenv": "^16.4.5",
31
33
  "ethers": "^5.7.2",
32
34
  "lodash": "^4.17.21",
33
- "winston": "^3.13.1",
35
+ "winston": "^3.14.2",
34
36
  "winston-console-format": "^1.0.8",
35
37
  "zod": "^3.23.8"
36
38
  },
@@ -38,12 +40,13 @@
38
40
  "@api3/eslint-plugin-commons": "^2.0.1",
39
41
  "@types/jest": "^29.5.12",
40
42
  "@types/lodash": "^4.17.7",
41
- "@types/node": "^20.14.13",
43
+ "@types/node": "^20.16.3",
42
44
  "eslint": "^8.57.0",
43
- "husky": "^9.1.3",
45
+ "husky": "^9.1.5",
44
46
  "jest": "^29.7.0",
45
47
  "prettier": "^3.3.3",
46
- "ts-jest": "^29.2.3",
48
+ "ts-jest": "^29.2.5",
49
+ "ts-node": "^10.9.2",
47
50
  "typescript": "^5.5.4"
48
51
  },
49
52
  "scripts": {
@@ -53,6 +56,7 @@
53
56
  "eslint:fix": "pnpm run eslint:check --fix",
54
57
  "prettier:check": "prettier --check \"./**/*.{js,ts,md,json}\"",
55
58
  "prettier:fix": "prettier --write \"./**/*.{js,ts,md,json}\"",
59
+ "release:tag": "ts-node scripts/tag-and-release.ts",
56
60
  "test": "jest",
57
61
  "tsc": "tsc --project ."
58
62
  }
package/src/node-index.ts CHANGED
@@ -3,3 +3,4 @@ export * from './logger';
3
3
  export * from './processing';
4
4
  export * from './config-parsing';
5
5
  export * from './config-hash';
6
+ export * from './release-scripts';
@@ -0,0 +1,85 @@
1
+ # Release Scripts
2
+
3
+ The following scripts are exposed for use:
4
+
5
+ ## tagAndRelease
6
+
7
+ This script creates a Git tag and Github release for a given version. It is expected to be run as part of CI and only
8
+ once for the given version defined in `package.json`.
9
+
10
+ Git tags are created with the following naming scheme: `v1.2.3`. i.e. a `v` is prepended to the version defined in
11
+ `package.json`.
12
+
13
+ ### Usage
14
+
15
+ ```ts
16
+ // The following environment variable is expected. See the script itself for more details
17
+ //
18
+ // GH_ACCESS_TOKEN - created through the Github UI with relevant permissions to the repo. See the tag-and-release source for more information
19
+
20
+ import { tagAndRelease } from '@api3/commons';
21
+
22
+ await tagAndRelease('my-repo-name'); // defaults to using the 'main' branch
23
+ await tagAndRelease('my-repo-name', 'release-branch');
24
+ ```
25
+
26
+ It is advised to create a script that imports and uses the `tagAndRelease` function, setup a script in `package.json`
27
+ and then call that in CI.
28
+
29
+ ```ts
30
+ // scripts/tag-and-release.ts
31
+ import { tagAndRelease } from '@api3/commons';
32
+
33
+ const main = async () => {
34
+ await tagAndRelease('my-repo-name');
35
+ };
36
+
37
+ main()
38
+ .then(() => process.exit(0))
39
+ .catch((error: unknown) => {
40
+ console.info(error);
41
+ process.exitCode = 1;
42
+ });
43
+
44
+ // package.json
45
+ {
46
+ "scripts": {
47
+ "release:tag": "ts-node scripts/tag-and-release.ts",
48
+ }
49
+ }
50
+ ```
51
+
52
+ It's also recommended to setup a step in CI that checks if the Git tag already exists before executing.
53
+
54
+ ```yml
55
+ # NOTE: irrelevant names and steps have been omitted such cloning, installing dependencies etc.
56
+ tag-and-release:
57
+ # Only tag and release on pushes to main (or the release branch)
58
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
59
+ steps:
60
+ - name: Clone repo
61
+ - name: Install pnpm
62
+ - name: Setup Node
63
+ - name: Install Dependencies
64
+ # Configure the Git user
65
+ - name: Configure Git credentials
66
+ run: |
67
+ git config --global user.name '${{ secrets.GH_USER_NAME }}'
68
+ git config --global user.email '${{ secrets.GH_USER_EMAIL }}'
69
+ # Get the version as defined in package.json
70
+ - name: Get package.json version
71
+ id: get-version
72
+ run: echo "version=$(cat package.json | jq -r '.version' | sed 's/^/v/')" >> $GITHUB_OUTPUT
73
+ # Check if a Git tag already exists with the pattern: `v{version}`
74
+ - name: Validate tag
75
+ id: validate-tag
76
+ run:
77
+ test "$(git tag -l '${{ steps.get-version.outputs.version }}' | awk '{print $NF}')" = "${{
78
+ steps.get-version.outputs.version }}" || echo "new-tag=true" >> $GITHUB_OUTPUT
79
+ # Run the tag-and-release script only if the tag does *not* already exist
80
+ - name: Tag and release on Github
81
+ if: ${{ steps.validate-tag.outputs.new-tag }}
82
+ run: pnpm run release:tag
83
+ env:
84
+ GH_ACCESS_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }}
85
+ ```
@@ -0,0 +1 @@
1
+ export * from './tag-and-release';
@@ -0,0 +1,93 @@
1
+ // ########################################################################################
2
+ // This script uses the version defined in the root package.json to determine if a
3
+ // new Git tag and Github release should be created. The Github release notes are
4
+ // generated automatically using the commit information
5
+ //
6
+ // The following secrets are required:
7
+ //
8
+ // 1. GH_ACCESS_TOKEN - A "fine-grained personal access token" generated through the
9
+ // Github UI. It seems like these tokens are scoped to a user, rather than an
10
+ // organisation.
11
+ //
12
+ // The following minimum permissions are required:
13
+ // Read - access to metadata
14
+ // Read & write - access to actions and code
15
+ // #######################################################################################
16
+
17
+ import { execSync } from 'node:child_process';
18
+ import { readFileSync } from 'node:fs';
19
+ import { join } from 'node:path';
20
+
21
+ import { go } from '@api3/promise-utils';
22
+ import { Octokit } from '@octokit/rest';
23
+
24
+ const execSyncWithErrorHandling = (command: string) => {
25
+ // eslint-disable-next-line functional/no-try-statements
26
+ try {
27
+ return execSync(command).toString();
28
+ } catch (error: any) {
29
+ console.info(error.message);
30
+ console.info('STDOUT', error.stdout.toString());
31
+ console.info('STDERR', error.stderr.toString());
32
+ process.exit(1);
33
+ }
34
+ };
35
+
36
+ const createGithubRelease = async (repo: string, tagName: `v${string}`) => {
37
+ if (!process.env.GH_ACCESS_TOKEN) {
38
+ console.info(`GH_ACCESS_TOKEN not set. Skipping release creation`);
39
+ return null;
40
+ }
41
+ // Ensure the GH_ACCESS_TOKEN secret is set on Github and has the relevant permissions
42
+ const octokit = new Octokit({ auth: process.env.GH_ACCESS_TOKEN });
43
+ const createRelease = async () =>
44
+ octokit.rest.repos.createRelease({
45
+ owner: 'api3dao',
46
+ repo,
47
+ tag_name: tagName, // eslint-disable-line camelcase
48
+ generate_release_notes: true, // eslint-disable-line camelcase
49
+ });
50
+ console.info(`Creating Github release...`);
51
+ const goRes = await go(createRelease, { totalTimeoutMs: 15_000 });
52
+ if (!goRes.success) {
53
+ // We don't want to fail CI if the release fails to create. This can be done manually through Github's UI
54
+ console.info(`Unable to create Github release`);
55
+ console.info(goRes.error.message);
56
+ return null;
57
+ }
58
+ return goRes.data;
59
+ };
60
+
61
+ export const tagAndRelease = async (repo: string, branch: string = 'main') => {
62
+ console.info('Ensuring working directory is clean...');
63
+ const gitStatus = execSyncWithErrorHandling('git status --porcelain');
64
+ if (gitStatus !== '') throw new Error('Working directory is not clean');
65
+
66
+ console.info(`Ensuring we are on the ${branch} branch...`);
67
+ const currentBranch = execSyncWithErrorHandling('git branch --show-current');
68
+ if (currentBranch !== `${branch}\n`) throw new Error(`Not on the ${branch} branch`);
69
+
70
+ console.info('Ensuring we are up to date with the remote...');
71
+ execSyncWithErrorHandling('git fetch');
72
+
73
+ const gitDiff = execSyncWithErrorHandling(`git diff origin/${branch}`);
74
+ if (gitDiff !== '') throw new Error('Not up to date with the remote');
75
+
76
+ const packageJson = JSON.parse(readFileSync(join(__dirname, '../../package.json'), 'utf8')) as any;
77
+ const { version } = packageJson;
78
+ console.info(`Version set to ${version}...`);
79
+
80
+ const gitTag = execSyncWithErrorHandling(`git tag -l '*v${version}*'`);
81
+ if (gitTag !== '') throw new Error(`git tag v${version} already exists`);
82
+
83
+ console.info('Creating new annotated git tag...');
84
+ execSyncWithErrorHandling(`git tag -a v${version} -m "v${version}"`);
85
+
86
+ console.info('Pushing git tag...');
87
+ // NOTE: in order to push, a valid access token is expected as GH_ACCESS_TOKEN
88
+ execSyncWithErrorHandling(`git push origin v${version} --no-verify`);
89
+
90
+ await createGithubRelease(repo, `v${version}`);
91
+
92
+ console.info(`Done!`);
93
+ };
@@ -0,0 +1,23 @@
1
+ import { createLogger } from '../logger';
2
+
3
+ import { runInLoop } from './index';
4
+
5
+ describe(runInLoop.name, () => {
6
+ const logger = createLogger({
7
+ colorize: true,
8
+ enabled: true,
9
+ minLevel: 'info',
10
+ format: 'json',
11
+ });
12
+
13
+ it('stops the loop after getting the stop signal', async () => {
14
+ const fn = async () => ({ shouldContinueRunning: false });
15
+ const fnSpy = jest
16
+ .spyOn({ fn }, 'fn')
17
+ .mockImplementationOnce(async () => ({ shouldContinueRunning: true }))
18
+ .mockImplementationOnce(async () => ({ shouldContinueRunning: true }))
19
+ .mockImplementationOnce(async () => ({ shouldContinueRunning: false }));
20
+ await runInLoop(fnSpy as any, { logger });
21
+ expect(fnSpy).toHaveBeenCalledTimes(3);
22
+ });
23
+ });
@@ -45,7 +45,10 @@ export interface RunInLoopOptions {
45
45
  initialDelayMs?: number;
46
46
  }
47
47
 
48
- export const runInLoop = async (fn: () => Promise<void>, options: RunInLoopOptions) => {
48
+ export const runInLoop = async (
49
+ fn: () => Promise<{ shouldContinueRunning: boolean } | void>,
50
+ options: RunInLoopOptions
51
+ ) => {
49
52
  const {
50
53
  logger,
51
54
  logLabel,
@@ -73,7 +76,7 @@ export const runInLoop = async (fn: () => Promise<void>, options: RunInLoopOptio
73
76
 
74
77
  if (enabled) {
75
78
  const context = logLabel ? { executionId, label: logLabel } : { executionId };
76
- await logger.runWithContext(context, async () => {
79
+ const shouldContinueRunning = await logger.runWithContext(context, async () => {
77
80
  const goRes = await go(fn, hardTimeoutMs ? { totalTimeoutMs: hardTimeoutMs } : {}); // NOTE: This is a safety net to prevent the loop from hanging
78
81
  if (!goRes.success) {
79
82
  logger.error(`Unexpected runInLoop error`, goRes.error);
@@ -85,7 +88,14 @@ export const runInLoop = async (fn: () => Promise<void>, options: RunInLoopOptio
85
88
  } else {
86
89
  logger.info(`Execution finished`, { executionTimeMs });
87
90
  }
91
+
92
+ return goRes.data?.shouldContinueRunning === false ? false : true;
88
93
  });
94
+
95
+ // Stop loop execution if the callback return value indicates it should stop
96
+ if (!shouldContinueRunning) {
97
+ break;
98
+ }
89
99
  } else {
90
100
  // If the bot is disabled, we still want to run the loop to prevent the process from hanging. We also want to
91
101
  // sleep according to the wait time logic.