Compare commits

...

4 Commits

Author SHA1 Message Date
semantic-release-bot e995b4e40a build(release): 1.6.0 [skip ci]
# [1.6.0](https://github.com/actions/create-github-app-token/compare/v1.5.1...v1.6.0) (2023-11-16)

### Features

* add retry ([#79](https://github.com/actions/create-github-app-token/issues/79)) ([0f3b4d7](https://github.com/actions/create-github-app-token/commit/0f3b4d7df99b1af7cb8596ba4f855d6de4155aa5)), closes [#71](https://github.com/actions/create-github-app-token/issues/71)
2023-11-16 22:53:33 +00:00
Stephane Moser 0f3b4d7df9 feat: add retry (#79)
resolves #71

- Add p-retry library
- Extract logic to new functions to improve the usage of retry logic
2023-11-12 08:00:38 -08:00
Gregor Martynus 9769eb4076 build(.node-version): use latest LTS version (#78)
this should resolve
https://github.com/actions/create-github-app-token/actions/runs/6741605908/job/18326430392
2023-11-06 12:58:23 -08:00
Grant Birkinbine bb368d6a10 General Improvements (#70)
# General Improvements

> This is a classic @GrantBirki drive-by PR 🚗

This pull request does the following:

- Lightly updates and formats a few existing Actions workflows
- Adds a new `package-check` workflow to validate the contents of the
`dist/` directory have been properly built
- Uses a `.node-version` file so that local development and Actions
remain on the same pinned version of node
- Adds status badges to the readme for visual effect  🎨
2023-11-02 22:23:07 -07:00
13 changed files with 653 additions and 92 deletions
+6 -3
View File
@@ -1,5 +1,6 @@
name: release
"on":
on:
push:
branches:
- main
@@ -18,10 +19,12 @@ jobs:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
node-version-file: .node-version
cache: 'npm'
- run: npm ci
- run: npm run build
- uses: ./
+5 -2
View File
@@ -1,4 +1,5 @@
name: test
on:
push:
branches:
@@ -15,10 +16,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
node-version-file: .node-version
cache: 'npm'
- run: npm ci
- run: npm test
+1
View File
@@ -0,0 +1 @@
20.9.0
+3 -1
View File
@@ -1,5 +1,7 @@
# Create GitHub App Token
[![test](https://github.com/actions/create-github-app-token/actions/workflows/test.yml/badge.svg)](https://github.com/actions/create-github-app-token/actions/workflows/test.yml)
GitHub Action for creating a GitHub App installation access token.
## Usage
@@ -46,7 +48,7 @@ jobs:
# required
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
token: ${{ steps.app-token.outputs.token }}
ref: ${{ github.head_ref }}
+25
View File
@@ -0,0 +1,25 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="106"
height="20" role="img" aria-label="Coverage: 100%">
<title>Coverage: 100%</title>
<linearGradient id="s" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1" />
<stop offset="1" stop-opacity=".1" />
</linearGradient>
<clipPath id="r">
<rect width="106" height="20" rx="3" fill="#fff" />
</clipPath>
<g clip-path="url(#r)">
<rect width="63" height="20" fill="#555" />
<rect x="63" width="43" height="20" fill="#4c1" />
<rect width="106" height="20" fill="url(#s)" />
</g>
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif"
text-rendering="geometricPrecision" font-size="110">
<text aria-hidden="true" x="325" y="150" fill="#010101" fill-opacity=".3"
transform="scale(.1)" textLength="530">Coverage</text>
<text x="325" y="140" transform="scale(.1)" fill="#fff" textLength="530">Coverage</text>
<text aria-hidden="true" x="835" y="150" fill="#010101" fill-opacity=".3"
transform="scale(.1)" textLength="330">100%</text>
<text x="835" y="140" transform="scale(.1)" fill="#fff" textLength="330">100%</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+398 -46
View File
@@ -7664,7 +7664,7 @@ var require_lodash = __commonJS({
}
var objectProto = Object.prototype;
var hasOwnProperty = objectProto.hasOwnProperty;
var objectToString = objectProto.toString;
var objectToString2 = objectProto.toString;
var propertyIsEnumerable = objectProto.propertyIsEnumerable;
var nativeKeys = overArg(Object.keys, Object);
var nativeMax = Math.max;
@@ -7708,7 +7708,7 @@ var require_lodash = __commonJS({
return isString(collection) ? fromIndex <= length && collection.indexOf(value, fromIndex) > -1 : !!length && baseIndexOf(collection, value, fromIndex) > -1;
}
function isArguments(value) {
return isArrayLikeObject(value) && hasOwnProperty.call(value, "callee") && (!propertyIsEnumerable.call(value, "callee") || objectToString.call(value) == argsTag);
return isArrayLikeObject(value) && hasOwnProperty.call(value, "callee") && (!propertyIsEnumerable.call(value, "callee") || objectToString2.call(value) == argsTag);
}
var isArray = Array.isArray;
function isArrayLike(value) {
@@ -7718,7 +7718,7 @@ var require_lodash = __commonJS({
return isObjectLike(value) && isArrayLike(value);
}
function isFunction(value) {
var tag = isObject(value) ? objectToString.call(value) : "";
var tag = isObject(value) ? objectToString2.call(value) : "";
return tag == funcTag || tag == genTag;
}
function isLength(value) {
@@ -7732,10 +7732,10 @@ var require_lodash = __commonJS({
return !!value && typeof value == "object";
}
function isString(value) {
return typeof value == "string" || !isArray(value) && isObjectLike(value) && objectToString.call(value) == stringTag;
return typeof value == "string" || !isArray(value) && isObjectLike(value) && objectToString2.call(value) == stringTag;
}
function isSymbol(value) {
return typeof value == "symbol" || isObjectLike(value) && objectToString.call(value) == symbolTag;
return typeof value == "symbol" || isObjectLike(value) && objectToString2.call(value) == symbolTag;
}
function toFinite(value) {
if (!value) {
@@ -7785,9 +7785,9 @@ var require_lodash2 = __commonJS({
"node_modules/lodash.isboolean/index.js"(exports, module2) {
var boolTag = "[object Boolean]";
var objectProto = Object.prototype;
var objectToString = objectProto.toString;
var objectToString2 = objectProto.toString;
function isBoolean(value) {
return value === true || value === false || isObjectLike(value) && objectToString.call(value) == boolTag;
return value === true || value === false || isObjectLike(value) && objectToString2.call(value) == boolTag;
}
function isObjectLike(value) {
return !!value && typeof value == "object";
@@ -7809,7 +7809,7 @@ var require_lodash3 = __commonJS({
var reIsOctal = /^0o[0-7]+$/i;
var freeParseInt = parseInt;
var objectProto = Object.prototype;
var objectToString = objectProto.toString;
var objectToString2 = objectProto.toString;
function isInteger(value) {
return typeof value == "number" && value == toInteger(value);
}
@@ -7821,7 +7821,7 @@ var require_lodash3 = __commonJS({
return !!value && typeof value == "object";
}
function isSymbol(value) {
return typeof value == "symbol" || isObjectLike(value) && objectToString.call(value) == symbolTag;
return typeof value == "symbol" || isObjectLike(value) && objectToString2.call(value) == symbolTag;
}
function toFinite(value) {
if (!value) {
@@ -7865,12 +7865,12 @@ var require_lodash4 = __commonJS({
"node_modules/lodash.isnumber/index.js"(exports, module2) {
var numberTag = "[object Number]";
var objectProto = Object.prototype;
var objectToString = objectProto.toString;
var objectToString2 = objectProto.toString;
function isObjectLike(value) {
return !!value && typeof value == "object";
}
function isNumber(value) {
return typeof value == "number" || isObjectLike(value) && objectToString.call(value) == numberTag;
return typeof value == "number" || isObjectLike(value) && objectToString2.call(value) == numberTag;
}
module2.exports = isNumber;
}
@@ -7900,13 +7900,13 @@ var require_lodash5 = __commonJS({
var funcToString = funcProto.toString;
var hasOwnProperty = objectProto.hasOwnProperty;
var objectCtorString = funcToString.call(Object);
var objectToString = objectProto.toString;
var objectToString2 = objectProto.toString;
var getPrototype = overArg(Object.getPrototypeOf, Object);
function isObjectLike(value) {
return !!value && typeof value == "object";
}
function isPlainObject(value) {
if (!isObjectLike(value) || objectToString.call(value) != objectTag || isHostObject(value)) {
if (!isObjectLike(value) || objectToString2.call(value) != objectTag || isHostObject(value)) {
return false;
}
var proto = getPrototype(value);
@@ -7925,13 +7925,13 @@ var require_lodash6 = __commonJS({
"node_modules/lodash.isstring/index.js"(exports, module2) {
var stringTag = "[object String]";
var objectProto = Object.prototype;
var objectToString = objectProto.toString;
var objectToString2 = objectProto.toString;
var isArray = Array.isArray;
function isObjectLike(value) {
return !!value && typeof value == "object";
}
function isString(value) {
return typeof value == "string" || !isArray(value) && isObjectLike(value) && objectToString.call(value) == stringTag;
return typeof value == "string" || !isArray(value) && isObjectLike(value) && objectToString2.call(value) == stringTag;
}
module2.exports = isString;
}
@@ -7951,7 +7951,7 @@ var require_lodash7 = __commonJS({
var reIsOctal = /^0o[0-7]+$/i;
var freeParseInt = parseInt;
var objectProto = Object.prototype;
var objectToString = objectProto.toString;
var objectToString2 = objectProto.toString;
function before(n, func) {
var result;
if (typeof func != "function") {
@@ -7979,7 +7979,7 @@ var require_lodash7 = __commonJS({
return !!value && typeof value == "object";
}
function isSymbol(value) {
return typeof value == "symbol" || isObjectLike(value) && objectToString.call(value) == symbolTag;
return typeof value == "symbol" || isObjectLike(value) && objectToString2.call(value) == symbolTag;
}
function toFinite(value) {
if (!value) {
@@ -10009,10 +10009,340 @@ var require_dist_node12 = __commonJS({
}
});
// node_modules/retry/lib/retry_operation.js
var require_retry_operation = __commonJS({
"node_modules/retry/lib/retry_operation.js"(exports, module2) {
function RetryOperation(timeouts, options) {
if (typeof options === "boolean") {
options = { forever: options };
}
this._originalTimeouts = JSON.parse(JSON.stringify(timeouts));
this._timeouts = timeouts;
this._options = options || {};
this._maxRetryTime = options && options.maxRetryTime || Infinity;
this._fn = null;
this._errors = [];
this._attempts = 1;
this._operationTimeout = null;
this._operationTimeoutCb = null;
this._timeout = null;
this._operationStart = null;
this._timer = null;
if (this._options.forever) {
this._cachedTimeouts = this._timeouts.slice(0);
}
}
module2.exports = RetryOperation;
RetryOperation.prototype.reset = function() {
this._attempts = 1;
this._timeouts = this._originalTimeouts.slice(0);
};
RetryOperation.prototype.stop = function() {
if (this._timeout) {
clearTimeout(this._timeout);
}
if (this._timer) {
clearTimeout(this._timer);
}
this._timeouts = [];
this._cachedTimeouts = null;
};
RetryOperation.prototype.retry = function(err) {
if (this._timeout) {
clearTimeout(this._timeout);
}
if (!err) {
return false;
}
var currentTime = (/* @__PURE__ */ new Date()).getTime();
if (err && currentTime - this._operationStart >= this._maxRetryTime) {
this._errors.push(err);
this._errors.unshift(new Error("RetryOperation timeout occurred"));
return false;
}
this._errors.push(err);
var timeout = this._timeouts.shift();
if (timeout === void 0) {
if (this._cachedTimeouts) {
this._errors.splice(0, this._errors.length - 1);
timeout = this._cachedTimeouts.slice(-1);
} else {
return false;
}
}
var self = this;
this._timer = setTimeout(function() {
self._attempts++;
if (self._operationTimeoutCb) {
self._timeout = setTimeout(function() {
self._operationTimeoutCb(self._attempts);
}, self._operationTimeout);
if (self._options.unref) {
self._timeout.unref();
}
}
self._fn(self._attempts);
}, timeout);
if (this._options.unref) {
this._timer.unref();
}
return true;
};
RetryOperation.prototype.attempt = function(fn, timeoutOps) {
this._fn = fn;
if (timeoutOps) {
if (timeoutOps.timeout) {
this._operationTimeout = timeoutOps.timeout;
}
if (timeoutOps.cb) {
this._operationTimeoutCb = timeoutOps.cb;
}
}
var self = this;
if (this._operationTimeoutCb) {
this._timeout = setTimeout(function() {
self._operationTimeoutCb();
}, self._operationTimeout);
}
this._operationStart = (/* @__PURE__ */ new Date()).getTime();
this._fn(this._attempts);
};
RetryOperation.prototype.try = function(fn) {
console.log("Using RetryOperation.try() is deprecated");
this.attempt(fn);
};
RetryOperation.prototype.start = function(fn) {
console.log("Using RetryOperation.start() is deprecated");
this.attempt(fn);
};
RetryOperation.prototype.start = RetryOperation.prototype.try;
RetryOperation.prototype.errors = function() {
return this._errors;
};
RetryOperation.prototype.attempts = function() {
return this._attempts;
};
RetryOperation.prototype.mainError = function() {
if (this._errors.length === 0) {
return null;
}
var counts = {};
var mainError = null;
var mainErrorCount = 0;
for (var i = 0; i < this._errors.length; i++) {
var error = this._errors[i];
var message = error.message;
var count = (counts[message] || 0) + 1;
counts[message] = count;
if (count >= mainErrorCount) {
mainError = error;
mainErrorCount = count;
}
}
return mainError;
};
}
});
// node_modules/retry/lib/retry.js
var require_retry = __commonJS({
"node_modules/retry/lib/retry.js"(exports) {
var RetryOperation = require_retry_operation();
exports.operation = function(options) {
var timeouts = exports.timeouts(options);
return new RetryOperation(timeouts, {
forever: options && (options.forever || options.retries === Infinity),
unref: options && options.unref,
maxRetryTime: options && options.maxRetryTime
});
};
exports.timeouts = function(options) {
if (options instanceof Array) {
return [].concat(options);
}
var opts = {
retries: 10,
factor: 2,
minTimeout: 1 * 1e3,
maxTimeout: Infinity,
randomize: false
};
for (var key in options) {
opts[key] = options[key];
}
if (opts.minTimeout > opts.maxTimeout) {
throw new Error("minTimeout is greater than maxTimeout");
}
var timeouts = [];
for (var i = 0; i < opts.retries; i++) {
timeouts.push(this.createTimeout(i, opts));
}
if (options && options.forever && !timeouts.length) {
timeouts.push(this.createTimeout(i, opts));
}
timeouts.sort(function(a, b) {
return a - b;
});
return timeouts;
};
exports.createTimeout = function(attempt, opts) {
var random = opts.randomize ? Math.random() + 1 : 1;
var timeout = Math.round(random * Math.max(opts.minTimeout, 1) * Math.pow(opts.factor, attempt));
timeout = Math.min(timeout, opts.maxTimeout);
return timeout;
};
exports.wrap = function(obj, options, methods) {
if (options instanceof Array) {
methods = options;
options = null;
}
if (!methods) {
methods = [];
for (var key in obj) {
if (typeof obj[key] === "function") {
methods.push(key);
}
}
}
for (var i = 0; i < methods.length; i++) {
var method = methods[i];
var original = obj[method];
obj[method] = function retryWrapper(original2) {
var op = exports.operation(options);
var args = Array.prototype.slice.call(arguments, 1);
var callback = args.pop();
args.push(function(err) {
if (op.retry(err)) {
return;
}
if (err) {
arguments[0] = op.mainError();
}
callback.apply(this, arguments);
});
op.attempt(function() {
original2.apply(obj, args);
});
}.bind(obj, original);
obj[method].options = options;
}
};
}
});
// node_modules/retry/index.js
var require_retry2 = __commonJS({
"node_modules/retry/index.js"(exports, module2) {
module2.exports = require_retry();
}
});
// main.js
var import_core = __toESM(require_core(), 1);
var import_auth_app = __toESM(require_dist_node12(), 1);
// node_modules/p-retry/index.js
var import_retry = __toESM(require_retry2(), 1);
// node_modules/is-network-error/index.js
var objectToString = Object.prototype.toString;
var isError = (value) => objectToString.call(value) === "[object Error]";
var errorMessages = /* @__PURE__ */ new Set([
"Failed to fetch",
// Chrome
"NetworkError when attempting to fetch resource.",
// Firefox
"The Internet connection appears to be offline.",
// Safari 16
"Load failed",
// Safari 17+
"Network request failed",
// `cross-fetch`
"fetch failed"
// Undici (Node.js)
]);
function isNetworkError(error) {
const isValid = error && isError(error) && error.name === "TypeError" && typeof error.message === "string";
if (!isValid) {
return false;
}
if (error.message === "Load failed") {
return error.stack === void 0;
}
return errorMessages.has(error.message);
}
// node_modules/p-retry/index.js
var AbortError = class extends Error {
constructor(message) {
super();
if (message instanceof Error) {
this.originalError = message;
({ message } = message);
} else {
this.originalError = new Error(message);
this.originalError.stack = this.stack;
}
this.name = "AbortError";
this.message = message;
}
};
var decorateErrorWithCounts = (error, attemptNumber, options) => {
const retriesLeft = options.retries - (attemptNumber - 1);
error.attemptNumber = attemptNumber;
error.retriesLeft = retriesLeft;
return error;
};
async function pRetry(input, options) {
return new Promise((resolve, reject) => {
options = {
onFailedAttempt() {
},
retries: 10,
...options
};
const operation = import_retry.default.operation(options);
const abortHandler = () => {
operation.stop();
reject(options.signal?.reason);
};
if (options.signal && !options.signal.aborted) {
options.signal.addEventListener("abort", abortHandler, { once: true });
}
const cleanUp = () => {
options.signal?.removeEventListener("abort", abortHandler);
operation.stop();
};
operation.attempt(async (attemptNumber) => {
try {
const result = await input(attemptNumber);
cleanUp();
resolve(result);
} catch (error) {
try {
if (!(error instanceof Error)) {
throw new TypeError(`Non-error was thrown: "${error}". You should only throw errors.`);
}
if (error instanceof AbortError) {
throw error.originalError;
}
if (error instanceof TypeError && !isNetworkError(error)) {
throw error;
}
await options.onFailedAttempt(decorateErrorWithCounts(error, attemptNumber, options));
if (!operation.retry(error)) {
throw operation.mainError();
}
} catch (finalError) {
decorateErrorWithCounts(finalError, attemptNumber, options);
cleanUp();
reject(finalError);
}
}
});
});
}
// lib/main.js
async function main(appId2, privateKey2, owner2, repositories2, core2, createAppAuth2, request2, skipTokenRevoke2) {
let parsedOwner = "";
@@ -10055,37 +10385,22 @@ async function main(appId2, privateKey2, owner2, repositories2, core2, createApp
});
let authentication;
if (parsedRepositoryNames) {
const response = await request2("GET /repos/{owner}/{repo}/installation", {
owner: parsedOwner,
repo: parsedRepositoryNames.split(",")[0],
headers: {
authorization: `bearer ${appAuthentication.token}`
}
});
authentication = await auth({
type: "installation",
installationId: response.data.id,
repositoryNames: parsedRepositoryNames.split(",")
authentication = await pRetry(() => getTokenFromRepository(request2, auth, parsedOwner, appAuthentication, parsedRepositoryNames), {
onFailedAttempt: (error) => {
core2.info(
`Failed to create token for "${parsedRepositoryNames}" (attempt ${error.attemptNumber}): ${error.message}`
);
},
retries: 3
});
} else {
const response = await request2("GET /orgs/{org}/installation", {
org: parsedOwner,
headers: {
authorization: `bearer ${appAuthentication.token}`
}
}).catch((error) => {
if (error.status !== 404)
throw error;
return request2("GET /users/{username}/installation", {
username: parsedOwner,
headers: {
authorization: `bearer ${appAuthentication.token}`
}
});
});
authentication = await auth({
type: "installation",
installationId: response.data.id
authentication = await pRetry(() => getTokenFromOwner(request2, auth, appAuthentication, parsedOwner), {
onFailedAttempt: (error) => {
core2.info(
`Failed to create token for "${parsedOwner}" (attempt ${error.attemptNumber}): ${error.message}`
);
},
retries: 3
});
}
core2.setSecret(authentication.token);
@@ -10094,6 +10409,43 @@ async function main(appId2, privateKey2, owner2, repositories2, core2, createApp
core2.saveState("token", authentication.token);
}
}
async function getTokenFromOwner(request2, auth, appAuthentication, parsedOwner) {
const response = await request2("GET /orgs/{org}/installation", {
org: parsedOwner,
headers: {
authorization: `bearer ${appAuthentication.token}`
}
}).catch((error) => {
if (error.status !== 404)
throw error;
return request2("GET /users/{username}/installation", {
username: parsedOwner,
headers: {
authorization: `bearer ${appAuthentication.token}`
}
});
});
const authentication = await auth({
type: "installation",
installationId: response.data.id
});
return authentication;
}
async function getTokenFromRepository(request2, auth, parsedOwner, appAuthentication, parsedRepositoryNames) {
const response = await request2("GET /repos/{owner}/{repo}/installation", {
owner: parsedOwner,
repo: parsedRepositoryNames.split(",")[0],
headers: {
authorization: `bearer ${appAuthentication.token}`
}
});
const authentication = await auth({
type: "installation",
installationId: response.data.id,
repositoryNames: parsedRepositoryNames.split(",")
});
return authentication;
}
// lib/request.js
var import_request = __toESM(require_dist_node5(), 1);
+62 -34
View File
@@ -1,3 +1,4 @@
import pRetry from "p-retry";
// @ts-check
/**
@@ -75,47 +76,26 @@ export async function main(
let authentication;
// If at least one repository is set, get installation ID from that repository
// https://docs.github.com/rest/apps/apps?apiVersion=2022-11-28#get-a-repository-installation-for-the-authenticated-app
if (parsedRepositoryNames) {
const response = await request("GET /repos/{owner}/{repo}/installation", {
owner: parsedOwner,
repo: parsedRepositoryNames.split(",")[0],
headers: {
authorization: `bearer ${appAuthentication.token}`,
authentication = await pRetry(() => getTokenFromRepository(request, auth, parsedOwner,appAuthentication, parsedRepositoryNames), {
onFailedAttempt: (error) => {
core.info(
`Failed to create token for "${parsedRepositoryNames}" (attempt ${error.attemptNumber}): ${error.message}`
);
},
retries: 3,
});
// Get token for given repositories
authentication = await auth({
type: "installation",
installationId: response.data.id,
repositoryNames: parsedRepositoryNames.split(","),
});
} else {
// Otherwise get the installation for the owner, which can either be an organization or a user account
// https://docs.github.com/rest/apps/apps?apiVersion=2022-11-28#get-a-repository-installation-for-the-authenticated-app
const response = await request("GET /orgs/{org}/installation", {
org: parsedOwner,
headers: {
authorization: `bearer ${appAuthentication.token}`,
authentication = await pRetry(() => getTokenFromOwner(request, auth, appAuthentication, parsedOwner), {
onFailedAttempt: (error) => {
core.info(
`Failed to create token for "${parsedOwner}" (attempt ${error.attemptNumber}): ${error.message}`
);
},
}).catch((error) => {
/* c8 ignore next */
if (error.status !== 404) throw error;
// https://docs.github.com/rest/apps/apps?apiVersion=2022-11-28#get-a-user-installation-for-the-authenticated-app
return request("GET /users/{username}/installation", {
username: parsedOwner,
headers: {
authorization: `bearer ${appAuthentication.token}`,
},
});
});
// Get token for for all repositories of the given installation
authentication = await auth({
type: "installation",
installationId: response.data.id,
retries: 3,
});
}
@@ -129,3 +109,51 @@ export async function main(
core.saveState("token", authentication.token);
}
}
async function getTokenFromOwner(request, auth, appAuthentication, parsedOwner) {
// https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28#get-an-organization-installation-for-the-authenticated-app
const response = await request("GET /orgs/{org}/installation", {
org: parsedOwner,
headers: {
authorization: `bearer ${appAuthentication.token}`,
},
}).catch((error) => {
/* c8 ignore next */
if (error.status !== 404) throw error;
// https://docs.github.com/rest/apps/apps?apiVersion=2022-11-28#get-a-user-installation-for-the-authenticated-app
return request("GET /users/{username}/installation", {
username: parsedOwner,
headers: {
authorization: `bearer ${appAuthentication.token}`,
},
});
});
// Get token for for all repositories of the given installation
const authentication = await auth({
type: "installation",
installationId: response.data.id,
});
return authentication;
}
async function getTokenFromRepository(request, auth, parsedOwner,appAuthentication, parsedRepositoryNames) {
// https://docs.github.com/rest/apps/apps?apiVersion=2022-11-28#get-a-repository-installation-for-the-authenticated-app
const response = await request("GET /repos/{owner}/{repo}/installation", {
owner: parsedOwner,
repo: parsedRepositoryNames.split(",")[0],
headers: {
authorization: `bearer ${appAuthentication.token}`,
},
});
// Get token for given repositories
const authentication = await auth({
type: "installation",
installationId: response.data.id,
repositoryNames: parsedRepositoryNames.split(","),
});
return authentication;
}
+44 -3
View File
@@ -1,17 +1,18 @@
{
"name": "create-github-app-token",
"version": "1.5.0",
"version": "1.5.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "create-github-app-token",
"version": "1.5.0",
"version": "1.5.1",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.10.1",
"@octokit/auth-app": "^6.0.1",
"@octokit/request": "^8.1.4"
"@octokit/request": "^8.1.4",
"p-retry": "^6.1.0"
},
"devDependencies": {
"ava": "^5.3.1",
@@ -853,6 +854,11 @@
"integrity": "sha512-lqa4UEhhv/2sjjIQgjX8B+RBjj47eo0mzGasklVJ78UKGQY1r0VpB9XHDaZZO9qzEFDdy4MrXLuEaSmPrPSe/A==",
"dev": true
},
"node_modules/@types/retry": {
"version": "0.12.2",
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz",
"integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow=="
},
"node_modules/acorn": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
@@ -2407,6 +2413,17 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-network-error": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.0.0.tgz",
"integrity": "sha512-P3fxi10Aji2FZmHTrMPSNFbNC6nnp4U5juPAIjXPHkUNubi4+qK7vvdsaNpAUwXslhYm9oyjEYTxs1xd/+Ph0w==",
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
@@ -3098,6 +3115,22 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-retry": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.1.0.tgz",
"integrity": "sha512-fJLEQ2KqYBJRuaA/8cKMnqhulqNM+bpcjYtXNex2t3mOXKRYPitAJt9NacSf8XAFzcYahSAbKpobiWDSqHSh2g==",
"dependencies": {
"@types/retry": "0.12.2",
"is-network-error": "^1.0.0",
"retry": "^0.13.1"
},
"engines": {
"node": ">=16.17"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-timeout": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-5.1.0.tgz",
@@ -3465,6 +3498,14 @@
"node": ">=8"
}
},
"node_modules/retry": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
"integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
"engines": {
"node": ">= 4"
}
},
"node_modules/reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+4 -3
View File
@@ -2,10 +2,10 @@
"name": "create-github-app-token",
"private": true,
"type": "module",
"version": "1.5.1",
"version": "1.6.0",
"description": "GitHub Action for creating a GitHub App Installation Access Token",
"scripts": {
"build": "esbuild main.js post.js --bundle --outdir=dist --out-extension:.js=.cjs --platform=node --target=node16.16",
"build": "esbuild main.js post.js --bundle --outdir=dist --out-extension:.js=.cjs --platform=node --target=node20.0.0",
"test": "c8 --100 ava tests/index.js",
"coverage": "c8 report --reporter html",
"postcoverage": "open-cli coverage/index.html"
@@ -14,7 +14,8 @@
"dependencies": {
"@actions/core": "^1.10.1",
"@octokit/auth-app": "^6.0.1",
"@octokit/request": "^8.1.4"
"@octokit/request": "^8.1.4",
"p-retry": "^6.1.0"
},
"devDependencies": {
"ava": "^5.3.1",
@@ -0,0 +1,39 @@
import { test } from "./main.js";
// Verify `main` retry when the GitHub API returns a 500 error.
await test((mockPool) => {
process.env.INPUT_OWNER = 'actions'
process.env.INPUT_REPOSITORIES = 'failed-repo';
const owner = process.env.INPUT_OWNER
const repo = process.env.INPUT_REPOSITORIES
const mockInstallationId = "123456";
mockPool
.intercept({
path: `/repos/${owner}/${repo}/installation`,
method: "GET",
headers: {
accept: "application/vnd.github.v3+json",
"user-agent": "actions/create-github-app-token",
// Intentionally omitting the `authorization` header, since JWT creation is not idempotent.
},
})
.reply(500, 'GitHub API not available')
mockPool
.intercept({
path: `/repos/${owner}/${repo}/installation`,
method: "GET",
headers: {
accept: "application/vnd.github.v3+json",
"user-agent": "actions/create-github-app-token",
// Intentionally omitting the `authorization` header, since JWT creation is not idempotent.
},
})
.reply(
200,
{ id: mockInstallationId },
{ headers: { "content-type": "application/json" } }
);
});
@@ -0,0 +1,36 @@
import { test } from "./main.js";
// Verify `main` successfully obtains a token when the `owner` input is set (to a user), but the `repositories` input isnt set.
await test((mockPool) => {
process.env.INPUT_OWNER = "smockle";
delete process.env.INPUT_REPOSITORIES;
// Mock installation id request
const mockInstallationId = "123456";
mockPool
.intercept({
path: `/orgs/${process.env.INPUT_OWNER}/installation`,
method: "GET",
headers: {
accept: "application/vnd.github.v3+json",
"user-agent": "actions/create-github-app-token",
// Intentionally omitting the `authorization` header, since JWT creation is not idempotent.
},
})
.reply(500, 'GitHub API not available')
mockPool
.intercept({
path: `/orgs/${process.env.INPUT_OWNER}/installation`,
method: "GET",
headers: {
accept: "application/vnd.github.v3+json",
"user-agent": "actions/create-github-app-token",
// Intentionally omitting the `authorization` header, since JWT creation is not idempotent.
},
})
.reply(
200,
{ id: mockInstallationId },
{ headers: { "content-type": "application/json" } }
);
});
+30
View File
@@ -56,6 +56,21 @@ Generated by [AVA](https://avajs.dev).
''
## main-token-get-owner-set-repo-fail-response.test.js
> stderr
''
> stdout
`owner and repositories set, creating token for repositories "failed-repo" owned by "actions"␊
Failed to create token for "failed-repo" (attempt 1): GitHub API not available␊
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a`
## main-token-get-owner-set-repo-set-to-many.test.js
> stderr
@@ -98,6 +113,21 @@ Generated by [AVA](https://avajs.dev).
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a`
## main-token-get-owner-set-to-user-fail-response.test.js
> stderr
''
> stdout
`repositories not set, creating token for all repositories for given owner "smockle"␊
Failed to create token for "smockle" (attempt 1): GitHub API not available␊
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a`
## main-token-get-owner-set-to-user-repo-unset.test.js
> stderr
Binary file not shown.