mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-06-13 11:46:08 -05:00
Switch from bot PAT to GitHub App token via Azure Key Vault (#63538)
This commit is contained in:
614
scripts/create-github-app-token.yml
Normal file
614
scripts/create-github-app-token.yml
Normal file
@@ -0,0 +1,614 @@
|
||||
# Step template for creating a GitHub App token via Azure Key Vault.
|
||||
# This template can be used in release jobs where checkout is not available.
|
||||
# The CLI script from microsoft/create-github-app-token-via-key-vault is inlined below.
|
||||
#
|
||||
# The token is created and stored in an Azure Pipelines variable (default: GH_TOKEN).
|
||||
# A revocation step runs at the end with condition: always().
|
||||
# To use the token, add your steps between this template's create and revoke steps
|
||||
# by setting insertSteps.
|
||||
|
||||
parameters:
|
||||
- name: azureSubscription
|
||||
type: string
|
||||
default: 'TypeScript Public CI'
|
||||
- name: owner
|
||||
type: string
|
||||
default: 'microsoft'
|
||||
- name: repositories
|
||||
type: string
|
||||
- name: permissions
|
||||
type: string
|
||||
- name: tokenVariable
|
||||
type: string
|
||||
default: 'GH_TOKEN'
|
||||
- name: insertSteps
|
||||
type: stepList
|
||||
default: []
|
||||
|
||||
steps:
|
||||
- task: AzureCLI@2
|
||||
displayName: Create GitHub App token
|
||||
inputs:
|
||||
azureSubscription: ${{ parameters.azureSubscription }}
|
||||
scriptType: bash
|
||||
scriptLocation: inlineScript
|
||||
inlineScript: |
|
||||
cat << 'GITHUB_APP_TOKEN_CLI_EOF' > /tmp/create-github-app-token.cjs
|
||||
"use strict";
|
||||
|
||||
// src/api.ts
|
||||
var defaultRefreshWindowMs = 5 * 60 * 1e3;
|
||||
var defaultGitHubApiUrl = "https://api.github.com";
|
||||
var transientRetryCount = 3;
|
||||
var GitHubRequestError = class extends Error {
|
||||
status;
|
||||
constructor(message, status) {
|
||||
super(message);
|
||||
this.status = status;
|
||||
}
|
||||
};
|
||||
function assertValue(value, message) {
|
||||
if (!value) {
|
||||
throw new Error(message);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
function base64url(value) {
|
||||
return Buffer.from(value).toString("base64url");
|
||||
}
|
||||
async function sleep(ms) {
|
||||
await new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
function isRetryableError(error) {
|
||||
return error instanceof GitHubRequestError ? error.status >= 500 : error instanceof TypeError;
|
||||
}
|
||||
async function retryTransient(operation) {
|
||||
for (let attempt = 0; ; attempt++) {
|
||||
try {
|
||||
return await operation();
|
||||
} catch (error) {
|
||||
if (attempt >= transientRetryCount || !isRetryableError(error)) {
|
||||
throw error;
|
||||
}
|
||||
await sleep(2 ** attempt * 1e3);
|
||||
}
|
||||
}
|
||||
}
|
||||
function splitRepositoryNames(repositories) {
|
||||
if (Array.isArray(repositories)) {
|
||||
return repositories.map((repo) => `${repo}`.trim()).filter(Boolean);
|
||||
}
|
||||
if (typeof repositories === "string") {
|
||||
return repositories.split(/[,\n]/).map((repo) => repo.trim()).filter(Boolean);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
function stableObject(value) {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
return value;
|
||||
}
|
||||
return Object.fromEntries(
|
||||
Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, stableObject(entry)])
|
||||
);
|
||||
}
|
||||
function githubHeaders(token, json = false) {
|
||||
return {
|
||||
"Accept": "application/vnd.github+json",
|
||||
"Authorization": `Bearer ${token}`,
|
||||
...json ? { "Content-Type": "application/json" } : {},
|
||||
"X-GitHub-Api-Version": "2022-11-28"
|
||||
};
|
||||
}
|
||||
function isRecord(value) {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
function requiredIntegerProperty(value, property, failureMessage) {
|
||||
const propertyValue = isRecord(value) ? value[property] : void 0;
|
||||
if (typeof propertyValue !== "number" || !Number.isInteger(propertyValue)) {
|
||||
throw new Error(failureMessage);
|
||||
}
|
||||
return propertyValue;
|
||||
}
|
||||
function requiredStringProperty(value, property, failureMessage) {
|
||||
const propertyValue = isRecord(value) ? value[property] : void 0;
|
||||
if (typeof propertyValue !== "string" || !propertyValue) {
|
||||
throw new Error(failureMessage);
|
||||
}
|
||||
return propertyValue;
|
||||
}
|
||||
function validatePermissionName(key) {
|
||||
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
||||
throw new Error(`Invalid permission name: ${key}`);
|
||||
}
|
||||
}
|
||||
function validatePermissionLevel(key, level) {
|
||||
if (level !== "read" && level !== "write" && level !== "admin") {
|
||||
throw new Error(`Invalid permission level for ${key}: ${level}`);
|
||||
}
|
||||
return level;
|
||||
}
|
||||
function validatePermissions(value) {
|
||||
if (value === void 0) {
|
||||
return void 0;
|
||||
}
|
||||
if (!isRecord(value)) {
|
||||
throw new Error("permissions must be an object");
|
||||
}
|
||||
const permissions = {};
|
||||
for (const [key, level] of Object.entries(value)) {
|
||||
validatePermissionName(key);
|
||||
permissions[key] = validatePermissionLevel(key, level);
|
||||
}
|
||||
return Object.keys(permissions).length === 0 ? void 0 : permissions;
|
||||
}
|
||||
async function requestJson(url, init, failureMessage) {
|
||||
const response = await fetch(url, init);
|
||||
const body = await response.text();
|
||||
if (!response.ok) {
|
||||
throw new GitHubRequestError(
|
||||
`${failureMessage}: ${response.status} ${response.statusText}: ${body}`,
|
||||
response.status
|
||||
);
|
||||
}
|
||||
try {
|
||||
return JSON.parse(body);
|
||||
} catch {
|
||||
throw new Error(`${failureMessage}: GitHub returned invalid JSON`);
|
||||
}
|
||||
}
|
||||
async function requestNoContent(url, init, failureMessage) {
|
||||
const response = await fetch(url, init);
|
||||
if (!response.ok) {
|
||||
const body = await response.text();
|
||||
throw new GitHubRequestError(
|
||||
`${failureMessage}: ${response.status} ${response.statusText}: ${body}`,
|
||||
response.status
|
||||
);
|
||||
}
|
||||
}
|
||||
function parseRepositoryInput(input) {
|
||||
const parts = input.split("/");
|
||||
if (parts.length === 1 && parts[0]) {
|
||||
return { input, name: parts[0] };
|
||||
}
|
||||
if (parts.length === 2 && parts[0] && parts[1]) {
|
||||
return { input, owner: parts[0], name: parts[1] };
|
||||
}
|
||||
throw new Error(`Invalid repository '${input}'. Expected 'repository' or 'owner/repository'.`);
|
||||
}
|
||||
function normalizeRepositoryTarget(owner, repositories, defaultOwner) {
|
||||
const parsedRepositories = repositories.map(parseRepositoryInput);
|
||||
const repositoryOwner = parsedRepositories.find((repository) => repository.owner)?.owner;
|
||||
const parsedOwner = owner || defaultOwner || repositoryOwner;
|
||||
if (!parsedOwner) {
|
||||
throw new Error("owner is required when repositories are provided");
|
||||
}
|
||||
const mismatchedRepository = parsedRepositories.find(
|
||||
(repository) => repository.owner && repository.owner.toLowerCase() !== parsedOwner.toLowerCase()
|
||||
);
|
||||
if (mismatchedRepository) {
|
||||
throw new Error(
|
||||
`Repository '${mismatchedRepository.input}' includes owner '${mismatchedRepository.owner}', which does not match the resolved owner '${parsedOwner}'.`
|
||||
);
|
||||
}
|
||||
return {
|
||||
owner: parsedOwner,
|
||||
repositories: parsedRepositories.map((repository) => repository.name)
|
||||
};
|
||||
}
|
||||
function resolveInstallationTarget(options, defaultOwner) {
|
||||
const repositories = splitRepositoryNames(options.repositories ?? options.repositoryNames);
|
||||
if (options.enterprise) {
|
||||
if (options.owner || repositories.length > 0) {
|
||||
throw new Error("Cannot use 'enterprise' with 'owner' or 'repositories'");
|
||||
}
|
||||
return { type: "enterprise", enterprise: options.enterprise };
|
||||
}
|
||||
const owner = assertValue(options.owner ?? defaultOwner, "owner is required to discover installation ID");
|
||||
if (repositories.length === 0) {
|
||||
return { type: "owner", owner };
|
||||
}
|
||||
return { type: "repository", owner, repositories };
|
||||
}
|
||||
function createGitHubAppAuth(options) {
|
||||
assertValue(options.appClientId, "appClientId is required");
|
||||
assertValue(options.signer, "signer is required");
|
||||
const appClientId = options.appClientId;
|
||||
const signer = options.signer;
|
||||
const defaultOwner = options.defaultOwner;
|
||||
const refreshWindowMs = options.refreshWindowMs ?? defaultRefreshWindowMs;
|
||||
const githubApiUrl = options.githubApiUrl ?? defaultGitHubApiUrl;
|
||||
const installationCache = /* @__PURE__ */ new Map();
|
||||
const tokenCache = /* @__PURE__ */ new Map();
|
||||
async function createJwt() {
|
||||
const now = Math.floor(Date.now() / 1e3);
|
||||
const iat = now - 60;
|
||||
const exp = now + 9 * 60;
|
||||
const header = base64url(JSON.stringify({ typ: "JWT", alg: "RS256" }));
|
||||
const payload = base64url(JSON.stringify({ iat, exp, iss: appClientId }));
|
||||
const signingInput = `${header}.${payload}`;
|
||||
const signature = await signer(signingInput);
|
||||
return `${signingInput}.${signature}`;
|
||||
}
|
||||
async function discoverInstallation(target) {
|
||||
const cacheKey = JSON.stringify(target);
|
||||
const cached = installationCache.get(cacheKey);
|
||||
if (cached !== void 0) {
|
||||
return cached;
|
||||
}
|
||||
const jwt = await createJwt();
|
||||
let installation;
|
||||
switch (target.type) {
|
||||
case "enterprise":
|
||||
installation = await requestJson(
|
||||
`${githubApiUrl}/enterprises/${target.enterprise}/installation`,
|
||||
{ headers: githubHeaders(jwt) },
|
||||
"Could not discover GitHub App installation ID"
|
||||
);
|
||||
break;
|
||||
case "owner":
|
||||
try {
|
||||
installation = await requestJson(
|
||||
`${githubApiUrl}/orgs/${target.owner}/installation`,
|
||||
{ headers: githubHeaders(jwt) },
|
||||
"Could not discover GitHub App installation ID"
|
||||
);
|
||||
} catch (error) {
|
||||
if (!(error instanceof GitHubRequestError) || error.status !== 404) {
|
||||
throw error;
|
||||
}
|
||||
installation = await requestJson(
|
||||
`${githubApiUrl}/users/${target.owner}/installation`,
|
||||
{ headers: githubHeaders(jwt) },
|
||||
"Could not discover GitHub App installation ID"
|
||||
);
|
||||
}
|
||||
break;
|
||||
case "repository":
|
||||
installation = await requestJson(
|
||||
`${githubApiUrl}/repos/${target.owner}/${assertValue(target.repositories[0], "repository is required")}/installation`,
|
||||
{ headers: githubHeaders(jwt) },
|
||||
"Could not discover GitHub App installation ID"
|
||||
);
|
||||
break;
|
||||
}
|
||||
const result = {
|
||||
id: requiredIntegerProperty(installation, "id", "GitHub did not return an installation ID"),
|
||||
appSlug: requiredStringProperty(installation, "app_slug", "GitHub did not return an App slug")
|
||||
};
|
||||
installationCache.set(cacheKey, result);
|
||||
return result;
|
||||
}
|
||||
async function getInstallationToken(options2) {
|
||||
const target = resolveInstallationTarget(options2, defaultOwner);
|
||||
const permissions = validatePermissions(options2.permissions);
|
||||
return retryTransient(async () => {
|
||||
const installation = await discoverInstallation(target);
|
||||
const repositories = target.type === "repository" ? target.repositories : [];
|
||||
const cacheKey = JSON.stringify({
|
||||
installationId: installation.id,
|
||||
repositories: [...repositories].sort(),
|
||||
permissions: stableObject(permissions)
|
||||
});
|
||||
const cached = tokenCache.get(cacheKey);
|
||||
if (cached && Date.now() < new Date(cached.expiresAt).getTime() - refreshWindowMs) {
|
||||
return cached;
|
||||
}
|
||||
const jwt = await createJwt();
|
||||
const body = {
|
||||
...repositories.length > 0 ? { repositories } : {},
|
||||
...permissions ? { permissions } : {}
|
||||
};
|
||||
const token = await requestJson(
|
||||
`${githubApiUrl}/app/installations/${installation.id}/access_tokens`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: githubHeaders(jwt, true),
|
||||
body: JSON.stringify(body)
|
||||
},
|
||||
"Could not create GitHub App installation token"
|
||||
);
|
||||
const result = {
|
||||
token: requiredStringProperty(token, "token", "GitHub did not return an installation token"),
|
||||
expiresAt: requiredStringProperty(
|
||||
token,
|
||||
"expires_at",
|
||||
"GitHub did not return an installation token expiration"
|
||||
),
|
||||
installationId: installation.id,
|
||||
appSlug: installation.appSlug,
|
||||
repositories,
|
||||
permissions: isRecord(token) && isRecord(token["permissions"]) ? token["permissions"] : permissions ?? {}
|
||||
};
|
||||
tokenCache.set(cacheKey, result);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
async function getToken(options2) {
|
||||
return (await getInstallationToken(options2)).token;
|
||||
}
|
||||
async function revokeToken(token) {
|
||||
await requestNoContent(
|
||||
`${githubApiUrl}/installation/token`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: githubHeaders(token)
|
||||
},
|
||||
"Could not revoke GitHub App installation token"
|
||||
);
|
||||
}
|
||||
return {
|
||||
getInstallationToken,
|
||||
getToken,
|
||||
revokeToken
|
||||
};
|
||||
}
|
||||
|
||||
// src/azureCliSigner.ts
|
||||
var import_node_child_process = require("node:child_process");
|
||||
var import_node_crypto = require("node:crypto");
|
||||
var cachedAzCommand;
|
||||
function isRecord2(value) {
|
||||
return typeof value === "object" && value !== null;
|
||||
}
|
||||
function base64ToBase64url(value) {
|
||||
return value.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
||||
}
|
||||
function commandExists(command) {
|
||||
try {
|
||||
(0, import_node_child_process.execFileSync)("where.exe", [command], {
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "ignore", "ignore"]
|
||||
});
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function azCommand() {
|
||||
if (cachedAzCommand) {
|
||||
return cachedAzCommand;
|
||||
}
|
||||
if (process.platform !== "win32") {
|
||||
cachedAzCommand = { command: "az", argsPrefix: [] };
|
||||
return cachedAzCommand;
|
||||
}
|
||||
for (const command of ["az.exe", "az.cmd", "az.bat", "az"]) {
|
||||
if (commandExists(command)) {
|
||||
cachedAzCommand = command.endsWith(".exe") ? { command, argsPrefix: [] } : { command: process.env["ComSpec"] || "cmd.exe", argsPrefix: ["/d", "/s", "/c", command] };
|
||||
return cachedAzCommand;
|
||||
}
|
||||
}
|
||||
throw new Error("Azure CLI (`az`) was not found on PATH");
|
||||
}
|
||||
function signDigest(keyId, digest) {
|
||||
const { command, argsPrefix } = azCommand();
|
||||
try {
|
||||
return (0, import_node_child_process.execFileSync)(command, [
|
||||
...argsPrefix,
|
||||
"keyvault",
|
||||
"key",
|
||||
"sign",
|
||||
"--id",
|
||||
keyId,
|
||||
"--algorithm",
|
||||
"RS256",
|
||||
"--digest",
|
||||
digest,
|
||||
"--query",
|
||||
"signature",
|
||||
"--output",
|
||||
"tsv",
|
||||
"--only-show-errors"
|
||||
], {
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "pipe"]
|
||||
}).trim();
|
||||
} catch (error) {
|
||||
if (isRecord2(error)) {
|
||||
if (error["code"] === "ENOENT") {
|
||||
throw new Error("Azure CLI (`az`) was not found on PATH");
|
||||
}
|
||||
const errorStderr = error["stderr"];
|
||||
const stderr = typeof errorStderr === "string" ? errorStderr.trim() : "";
|
||||
if (typeof error["status"] === "number") {
|
||||
throw new Error(
|
||||
`Azure Key Vault signing failed with exit code ${error["status"]}${stderr ? `: ${stderr}` : ""}`
|
||||
);
|
||||
}
|
||||
}
|
||||
throw new Error("Azure Key Vault signing failed");
|
||||
}
|
||||
}
|
||||
function createAzureCliKeyVaultSigner(keyId) {
|
||||
if (!keyId) {
|
||||
throw new Error("keyId is required");
|
||||
}
|
||||
return async (signingInput) => {
|
||||
const digest = (0, import_node_crypto.createHash)("sha256").update(signingInput).digest("base64");
|
||||
const signature = signDigest(keyId, digest);
|
||||
if (!signature) {
|
||||
throw new Error("Azure Key Vault did not return a signature");
|
||||
}
|
||||
return base64ToBase64url(signature);
|
||||
};
|
||||
}
|
||||
|
||||
// src/proxy.ts
|
||||
var proxyEnvironmentKeys = [
|
||||
"https_proxy",
|
||||
"HTTPS_PROXY",
|
||||
"http_proxy",
|
||||
"HTTP_PROXY"
|
||||
];
|
||||
function proxyEnvironmentConfigured() {
|
||||
return proxyEnvironmentKeys.some((key) => process.env[key]);
|
||||
}
|
||||
function nativeProxySupportEnabled() {
|
||||
return process.env["NODE_USE_ENV_PROXY"] === "1";
|
||||
}
|
||||
function ensureNativeProxySupport() {
|
||||
if (!proxyEnvironmentConfigured() || nativeProxySupportEnabled()) {
|
||||
return;
|
||||
}
|
||||
throw new Error(
|
||||
"A proxy environment variable is set, but Node.js native proxy support is not enabled. Set NODE_USE_ENV_PROXY=1 before running this tool."
|
||||
);
|
||||
}
|
||||
|
||||
// src/cli.ts
|
||||
function requiredEnv(name) {
|
||||
const value = process.env[name];
|
||||
if (!value) {
|
||||
throw new Error(`${name} must be set`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
function getAppClientId() {
|
||||
const appClientId = process.env["APP_CLIENT_ID"];
|
||||
if (!appClientId) {
|
||||
throw new Error("APP_CLIENT_ID must be set");
|
||||
}
|
||||
return appClientId;
|
||||
}
|
||||
function validatePermissionName2(key) {
|
||||
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
||||
throw new Error(`Invalid permission name: ${key}`);
|
||||
}
|
||||
}
|
||||
function validatePermissionLevel2(key, level) {
|
||||
if (level !== "read" && level !== "write" && level !== "admin") {
|
||||
throw new Error(`Invalid permission level for ${key}: ${level}`);
|
||||
}
|
||||
return level;
|
||||
}
|
||||
function parsePermissions(value) {
|
||||
if (!value) {
|
||||
return void 0;
|
||||
}
|
||||
const permissions = {};
|
||||
for (const entry of splitRepositoryNames(value)) {
|
||||
const parts = entry.split(":");
|
||||
if (parts.length !== 2) {
|
||||
throw new Error(`Permission entry must include an explicit level: ${entry}`);
|
||||
}
|
||||
const key = parts[0]?.trim();
|
||||
const rawLevel = parts[1]?.trim();
|
||||
if (!key) {
|
||||
throw new Error(`Permission entry must include a permission name: ${entry}`);
|
||||
}
|
||||
validatePermissionName2(key);
|
||||
if (Object.hasOwn(permissions, key)) {
|
||||
throw new Error(`Duplicate permission: ${key}`);
|
||||
}
|
||||
permissions[key] = validatePermissionLevel2(key, rawLevel);
|
||||
}
|
||||
return Object.keys(permissions).length === 0 ? void 0 : permissions;
|
||||
}
|
||||
function validateVariableName(name, envName) {
|
||||
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
||||
throw new Error(`${envName} must be an environment-style variable name`);
|
||||
}
|
||||
}
|
||||
function parseOutputMode(value) {
|
||||
const output = (value || "stdout").trim().toLowerCase();
|
||||
if (output === "azure" || output === "azure-pipelines" || output === "stdout") {
|
||||
return output;
|
||||
}
|
||||
throw new Error('OUTPUT must be "azure", "azure-pipelines", or "stdout"');
|
||||
}
|
||||
function getTokenOptions() {
|
||||
const enterprise = process.env["ENTERPRISE"];
|
||||
const owner = process.env["OWNER"];
|
||||
const repositories = splitRepositoryNames(process.env["REPOSITORIES"]);
|
||||
const permissions = parsePermissions(process.env["PERMISSIONS"]);
|
||||
if (enterprise) {
|
||||
if (owner || repositories.length > 0) {
|
||||
throw new Error("Cannot use ENTERPRISE with OWNER or REPOSITORIES");
|
||||
}
|
||||
return { enterprise, permissions };
|
||||
}
|
||||
if (repositories.length > 0) {
|
||||
return { ...normalizeRepositoryTarget(owner, repositories, void 0), permissions };
|
||||
}
|
||||
if (owner) {
|
||||
return { owner, permissions };
|
||||
}
|
||||
throw new Error("OWNER, REPOSITORIES, or ENTERPRISE must be set");
|
||||
}
|
||||
function writeAzurePipelinesOutput(installationToken) {
|
||||
const variableName = requiredEnv("AZURE_TOKEN_VARIABLE");
|
||||
validateVariableName(variableName, "AZURE_TOKEN_VARIABLE");
|
||||
process.stdout.write(`##vso[task.setvariable variable=${variableName};isSecret=true]${installationToken.token}
|
||||
`);
|
||||
}
|
||||
function writeOutput(installationToken, output) {
|
||||
switch (output) {
|
||||
case "azure":
|
||||
case "azure-pipelines":
|
||||
writeAzurePipelinesOutput(installationToken);
|
||||
break;
|
||||
case "stdout":
|
||||
process.stdout.write(`${installationToken.token}
|
||||
`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
function reportError(error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
console.error(`error: ${message}`);
|
||||
}
|
||||
async function main() {
|
||||
ensureNativeProxySupport();
|
||||
const githubApiUrl = process.env["GITHUB_API_URL"] || "https://api.github.com";
|
||||
const revokeTokenValue = process.env["REVOKE_TOKEN"];
|
||||
if (revokeTokenValue) {
|
||||
const response = await fetch(`${githubApiUrl}/installation/token`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Accept": "application/vnd.github+json",
|
||||
"Authorization": `Bearer ${revokeTokenValue}`,
|
||||
"X-GitHub-Api-Version": "2022-11-28"
|
||||
}
|
||||
});
|
||||
if (!response.ok) {
|
||||
const body = await response.text();
|
||||
throw new Error(`Could not revoke token: ${response.status} ${response.statusText}: ${body}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const githubAuth = createGitHubAppAuth({
|
||||
appClientId: getAppClientId(),
|
||||
signer: createAzureCliKeyVaultSigner(requiredEnv("KEY_ID")),
|
||||
githubApiUrl
|
||||
});
|
||||
const installationToken = await githubAuth.getInstallationToken(getTokenOptions());
|
||||
writeOutput(installationToken, parseOutputMode(process.env["OUTPUT"]));
|
||||
}
|
||||
void main().catch((error) => {
|
||||
reportError(error);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
GITHUB_APP_TOKEN_CLI_EOF
|
||||
node /tmp/create-github-app-token.cjs
|
||||
env:
|
||||
APP_CLIENT_ID: $(TYPESCRIPT_AUTOMATION_GITHUB_APP_CLIENT_ID)
|
||||
KEY_ID: $(TYPESCRIPT_AUTOMATION_GITHUB_APP_KEY_ID)
|
||||
OWNER: ${{ parameters.owner }}
|
||||
REPOSITORIES: ${{ parameters.repositories }}
|
||||
PERMISSIONS: ${{ parameters.permissions }}
|
||||
OUTPUT: azure-pipelines
|
||||
AZURE_TOKEN_VARIABLE: ${{ parameters.tokenVariable }}
|
||||
|
||||
- ${{ each step in parameters.insertSteps }}:
|
||||
- ${{ step }}
|
||||
|
||||
- task: Bash@3
|
||||
displayName: Revoke GitHub App token
|
||||
condition: always()
|
||||
continueOnError: true
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: node /tmp/create-github-app-token.cjs
|
||||
env:
|
||||
REVOKE_TOKEN: $(${{ parameters.tokenVariable }})
|
||||
Reference in New Issue
Block a user