netlify-cli
Version:
Netlify command line tool
214 lines • 8.71 kB
JavaScript
import { Octokit } from '@octokit/rest';
import { chalk, logAndThrowError, log } from '../command-helpers.js';
import { getGitHubToken as ghauth } from '../gh-auth.js';
import { createDeployKey, formatErrorMessage, getBuildSettings, saveNetlifyToml, setupSite } from './utils.js';
const formatRepoAndOwner = ({ repoName, repoOwner }) => ({
name: chalk.magenta(repoName),
owner: chalk.magenta(repoOwner),
});
const PAGE_SIZE = 100;
/**
* Get a valid GitHub token
*/
export const getGitHubToken = async ({ globalConfig }) => {
const userId = globalConfig.get('userId');
const githubToken = globalConfig.get(`users.${userId}.auth.github`);
if (githubToken?.user && githubToken.token) {
try {
const octokit = getGitHubClient(githubToken.token);
const { status } = await octokit.rest.users.getAuthenticated();
if (status < 400) {
return githubToken.token;
}
}
catch {
log(chalk.yellow('Token is expired or invalid!'));
log('Generating a new Github token...');
}
}
const newToken = await ghauth();
globalConfig.set(`users.${userId}.auth.github`, newToken);
return newToken.token;
};
const getGitHubClient = (token) => new Octokit({
auth: `token ${token}`,
});
const addDeployKey = async ({ api, octokit, repoName, repoOwner, }) => {
log('Adding deploy key to repository...');
const key = await createDeployKey({ api });
try {
await octokit.repos.createDeployKey({
title: 'Netlify Deploy Key',
key: key.public_key ?? '',
owner: repoOwner,
repo: repoName,
read_only: true,
});
log('Deploy key added!');
return key;
}
catch (error) {
let message = formatErrorMessage({ message: 'Failed adding GitHub deploy key', error });
// @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'.
if (error.status === 404) {
const { name, owner } = formatRepoAndOwner({ repoName, repoOwner });
message = `${message}. Does the repository ${name} exist and do ${owner} has the correct permissions to set up deploy keys?`;
}
return logAndThrowError(message);
}
};
const getGitHubRepo = async ({ octokit, repoName, repoOwner, }) => {
try {
const { data } = await octokit.repos.get({
owner: repoOwner,
repo: repoName,
});
return data;
}
catch (error) {
let message = formatErrorMessage({ message: 'Failed retrieving GitHub repository information', error });
// @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'.
if (error.status === 404) {
const { name, owner } = formatRepoAndOwner({ repoName, repoOwner });
message = `${message}. Does the repository ${name} exist and accessible by ${owner}`;
}
return logAndThrowError(message);
}
};
// @ts-expect-error TS(7031) FIXME: Binding element 'deployHook' implicitly has an 'an... Remove this comment to see the full error message
const hookExists = async ({ deployHook, octokit, repoName, repoOwner }) => {
try {
const { data: hooks } = await octokit.repos.listWebhooks({
owner: repoOwner,
repo: repoName,
per_page: PAGE_SIZE,
});
// @ts-expect-error TS(7006) FIXME: Parameter 'hook' implicitly has an 'any' type.
const exists = hooks.some((hook) => hook.config.url === deployHook);
return exists;
}
catch {
// we don't need to fail if listHooks errors out
return false;
}
};
// @ts-expect-error TS(7031) FIXME: Binding element 'deployHook' implicitly has an 'an... Remove this comment to see the full error message
const addDeployHook = async ({ deployHook, octokit, repoName, repoOwner }) => {
const exists = await hookExists({ deployHook, octokit, repoOwner, repoName });
if (!exists) {
try {
await octokit.repos.createWebhook({
owner: repoOwner,
repo: repoName,
name: 'web',
config: {
url: deployHook,
content_type: 'json',
},
events: ['push', 'pull_request', 'delete'],
active: true,
});
}
catch (error) {
// Ignore exists error if the list doesn't return all installed hooks
// @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'.
if (!error.message.includes('Hook already exists on this repository')) {
let message = formatErrorMessage({ message: 'Failed creating repo hook', error });
// @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'.
if (error.status === 404) {
const { name, owner } = formatRepoAndOwner({ repoName, repoOwner });
message = `${message}. Does the repository ${name} and do ${owner} has the correct permissions to set up hooks`;
}
return logAndThrowError(message);
}
}
}
};
const GITHUB_HOOK_EVENTS = ['deploy_created', 'deploy_failed', 'deploy_building'];
const GITHUB_HOOK_TYPE = 'github_commit_status';
// @ts-expect-error TS(7031) FIXME: Binding element 'api' implicitly has an 'any' type... Remove this comment to see the full error message
const upsertHook = async ({ api, event, ntlHooks, siteId, token }) => {
// @ts-expect-error TS(7006) FIXME: Parameter 'hook' implicitly has an 'any' type.
const ntlHook = ntlHooks.find((hook) => hook.type === GITHUB_HOOK_TYPE && hook.event === event);
if (!ntlHook || ntlHook.disabled) {
return await api.createHookBySiteId({
site_id: siteId,
body: {
type: GITHUB_HOOK_TYPE,
event,
data: {
access_token: token,
},
},
});
}
return await api.updateHook({
hook_id: ntlHook.id,
body: {
data: {
access_token: token,
},
},
});
};
// @ts-expect-error TS(7031) FIXME: Binding element 'api' implicitly has an 'any' type... Remove this comment to see the full error message
const addNotificationHooks = async ({ api, siteId, token }) => {
log(`Creating Netlify GitHub Notification Hooks...`);
let ntlHooks;
try {
ntlHooks = await api.listHooksBySiteId({ siteId });
}
catch (error) {
const message = formatErrorMessage({ message: 'Failed retrieving Netlify hooks', error });
return logAndThrowError(message);
}
await Promise.all(GITHUB_HOOK_EVENTS.map(async (event) => {
try {
await upsertHook({ ntlHooks, event, api, siteId, token });
}
catch (error) {
const message = formatErrorMessage({ message: `Failed settings Netlify hook ${chalk.magenta(event)}`, error });
return logAndThrowError(message);
}
}));
log(`Netlify Notification Hooks configured!`);
};
export const configGithub = async ({ command, repoName, repoOwner, siteId, }) => {
const { netlify } = command;
const { api, cachedConfig: { configPath }, config, globalConfig, repositoryRoot, } = netlify;
const token = await getGitHubToken({ globalConfig });
const { baseDir, buildCmd, buildDir, functionsDir, pluginsToInstall } = await getBuildSettings({
config,
command,
});
await saveNetlifyToml({ repositoryRoot, config, configPath, baseDir, buildCmd, buildDir, functionsDir });
log();
const octokit = getGitHubClient(token);
const [deployKey, githubRepo] = await Promise.all([
addDeployKey({ api, octokit, repoOwner, repoName }),
getGitHubRepo({ octokit, repoOwner, repoName }),
]);
const repo = {
id: githubRepo.id,
provider: 'github',
repo_path: githubRepo.full_name,
repo_branch: githubRepo.default_branch,
allowed_branches: [githubRepo.default_branch],
deploy_key_id: deployKey.id,
base: baseDir,
dir: buildDir,
functions_dir: functionsDir,
...(buildCmd && { cmd: buildCmd }),
};
const updatedSite = await setupSite({
api,
siteId,
repo,
configPlugins: config.plugins ?? [],
pluginsToInstall,
});
await addDeployHook({ deployHook: updatedSite.deploy_hook, octokit, repoOwner, repoName });
log();
await addNotificationHooks({ siteId, api, token });
};
//# sourceMappingURL=config-github.js.map