netlify-cli
Version:
Netlify command line tool
296 lines (293 loc) • 11.7 kB
JavaScript
import assert from 'node:assert';
import inquirer from 'inquirer';
import isEmpty from 'lodash/isEmpty.js';
import { listSites } from '../../lib/api.js';
import { chalk, logAndThrowError, exit, log } from '../../utils/command-helpers.js';
import getRepoData from '../../utils/get-repo-data.js';
import { ensureNetlifyIgnore } from '../../utils/gitignore.js';
import { track } from '../../utils/telemetry/index.js';
const linkPrompt = async (command, options) => {
const { api, state } = command.netlify;
const SITE_NAME_PROMPT = 'Search by full or partial site name';
const SITE_LIST_PROMPT = 'Choose from a list of your recently updated sites';
const SITE_ID_PROMPT = 'Enter a site ID';
let GIT_REMOTE_PROMPT = 'Use the current git remote origin URL';
let site;
// Get git remote data if exists
const repoData = await getRepoData({ workingDir: command.workingDir, remoteName: options.gitRemoteName });
let linkChoices = [SITE_NAME_PROMPT, SITE_LIST_PROMPT, SITE_ID_PROMPT];
if (!('error' in repoData)) {
// Add git GIT_REMOTE_PROMPT if in a repo
GIT_REMOTE_PROMPT = `Use current git remote origin (${repoData.httpsUrl})`;
linkChoices = [GIT_REMOTE_PROMPT, ...linkChoices];
}
log();
log(`${chalk.cyanBright('netlify link')} will connect this folder to a site on Netlify`);
log();
const { linkType } = await inquirer.prompt([
{
type: 'list',
name: 'linkType',
message: 'How do you want to link this folder to a site?',
choices: linkChoices,
},
]);
let kind;
switch (linkType) {
case GIT_REMOTE_PROMPT: {
// TODO(serhalp): Refactor function to avoid this. We can only be here if `repoData` is not an error.
assert(!('error' in repoData));
kind = 'gitRemote';
log();
log(`Looking for sites connected to '${repoData.httpsUrl}'...`);
log();
const sites = await listSites({ api, options: { filter: 'all' } });
if (sites.length === 0) {
return logAndThrowError(`You don't have any sites yet. Run ${chalk.cyanBright('netlify sites:create')} to create a site.`);
}
const matchingSites = sites.filter(({ build_settings: buildSettings = {} }) => repoData.httpsUrl === buildSettings.repo_url);
// If no remote matches. Throw error
if (matchingSites.length === 0) {
log(chalk.redBright.bold.underline(`No Matching Site Found`));
log();
log(`No site found with the remote ${repoData.httpsUrl}.
Double check you are in the correct working directory and a remote origin repo is configured.
Run ${chalk.cyanBright('git remote -v')} to see a list of your git remotes.`);
exit();
}
// Matches a single site hooray!
if (matchingSites.length === 1) {
const [firstSite] = matchingSites;
site = firstSite;
}
else if (matchingSites.length > 1) {
// Matches multiple sites. Users must choose which to link.
log(`Found ${matchingSites.length} matching sites!`);
// Prompt which options
const { selectedSite } = await inquirer.prompt([
{
type: 'list',
name: 'selectedSite',
message: 'Which site do you want to link?',
choices: matchingSites.map((matchingSite) => ({
name: `${matchingSite.name} - ${matchingSite.ssl_url}`,
value: matchingSite,
})),
},
]);
if (!selectedSite) {
return logAndThrowError('No site selected');
}
site = selectedSite;
}
break;
}
case SITE_NAME_PROMPT: {
kind = 'byName';
const { searchTerm } = await inquirer.prompt([
{
type: 'input',
name: 'searchTerm',
message: 'Enter the site name (or just part of it):',
},
]);
log(`Looking for sites with names containing '${searchTerm}'...`);
log();
let matchingSites = [];
try {
matchingSites = await listSites({
api,
options: { name: searchTerm, filter: 'all' },
});
}
catch (error_) {
if (error_.status === 404) {
return logAndThrowError(`'${searchTerm}' not found`);
}
else {
return logAndThrowError(error_);
}
}
if (!matchingSites || matchingSites.length === 0) {
return logAndThrowError(`No site names found containing '${searchTerm}'.
Run ${chalk.cyanBright('netlify link')} again to try a new search,
or run ${chalk.cyanBright('netlify sites:create')} to create a site.`);
}
if (matchingSites.length > 1) {
log(`Found ${matchingSites.length} matching sites!`);
const { selectedSite } = await inquirer.prompt([
{
type: 'list',
name: 'selectedSite',
message: 'Which site do you want to link?',
paginated: true,
choices: matchingSites.map((matchingSite) => ({ name: matchingSite.name, value: matchingSite })),
},
]);
if (!selectedSite) {
return logAndThrowError('No site selected');
}
site = selectedSite;
}
else {
const [firstSite] = matchingSites;
site = firstSite;
}
break;
}
case SITE_LIST_PROMPT: {
kind = 'fromList';
log(`Fetching recently updated sites...`);
log();
let sites;
try {
sites = await listSites({ api, options: { maxPages: 1, filter: 'all' } });
}
catch (error_) {
return logAndThrowError(error_);
}
if (!sites || sites.length === 0) {
return logAndThrowError(`You don't have any sites yet. Run ${chalk.cyanBright('netlify sites:create')} to create a site.`);
}
const { selectedSite } = await inquirer.prompt([
{
type: 'list',
name: 'selectedSite',
message: 'Which site do you want to link?',
paginated: true,
choices: sites.map((matchingSite) => ({ name: matchingSite.name, value: matchingSite })),
},
]);
if (!selectedSite) {
return logAndThrowError('No site selected');
}
site = selectedSite;
break;
}
case SITE_ID_PROMPT: {
kind = 'bySiteId';
const { siteId } = await inquirer.prompt([
{
type: 'input',
name: 'siteId',
message: 'What is the site ID?',
},
]);
try {
site = await api.getSite({ siteId });
}
catch (error_) {
if (error_.status === 404) {
return logAndThrowError(`Site ID '${siteId}' not found`);
}
else {
return logAndThrowError(error_);
}
}
break;
}
default:
return;
}
if (!site) {
return logAndThrowError(new Error(`No site found`));
}
// Save site ID to config
state.set('siteId', site.id);
await track('sites_linked', {
siteId: site.id,
linkType: 'prompt',
kind,
});
// Log output
log();
log(chalk.greenBright.bold.underline(`Directory Linked`));
log();
log(`Admin url: ${chalk.magentaBright(site.admin_url)}`);
log(`Site url: ${chalk.cyanBright(site.ssl_url || site.url)}`);
log();
log(`You can now run other \`netlify\` cli commands in this directory`);
return site;
};
export const link = async (options, command) => {
await command.authenticate();
const { api, repositoryRoot, site: { id: siteId }, siteInfo, state, } = command.netlify;
let initialSiteData;
let newSiteData;
// Add .netlify to .gitignore file
await ensureNetlifyIgnore(repositoryRoot);
// Site id is incorrect
if (siteId && isEmpty(siteInfo)) {
log(`"${siteId}" was not found in your Netlify account.`);
log(`Please double check your siteID and which account you are logged into via \`netlify status\`.`);
return exit();
}
if (!isEmpty(siteInfo)) {
// If already linked to site, exit and prompt for unlink
initialSiteData = siteInfo;
log(`Site already linked to "${initialSiteData.name}"`);
log(`Admin url: ${initialSiteData.admin_url}`);
log();
log(`To unlink this site, run: ${chalk.cyanBright('netlify unlink')}`);
}
else if (options.id) {
try {
// @ts-expect-error(serhalp) -- Mismatch between hardcoded `SiteInfo` and new generated Netlify API types.
newSiteData = await api.getSite({ site_id: options.id });
}
catch (error_) {
if (error_.status === 404) {
return logAndThrowError(new Error(`Site id ${options.id} not found`));
}
else {
return logAndThrowError(error_);
}
}
// Save site ID
state.set('siteId', newSiteData.id);
log(`Linked to ${newSiteData.name}`);
await track('sites_linked', {
siteId: newSiteData.id,
linkType: 'manual',
kind: 'byId',
});
}
else if (options.name) {
let results = [];
try {
results = await listSites({
api,
options: {
name: options.name,
filter: 'all',
},
});
}
catch (error_) {
if (error_.status === 404) {
return logAndThrowError(new Error(`${options.name} not found`));
}
else {
return logAndThrowError(error_);
}
}
if (results.length === 0) {
return logAndThrowError(new Error(`No sites found named ${options.name}`));
}
const matchingSiteData = results.find((site) => site.name === options.name) || results[0];
state.set('siteId', matchingSiteData.id);
log(`Linked to ${matchingSiteData.name}`);
await track('sites_linked', {
siteId: (matchingSiteData && matchingSiteData.id) || siteId,
linkType: 'manual',
kind: 'byName',
});
}
else {
newSiteData = await linkPrompt(command, options);
}
// FIXME(serhalp): All the cases above except one (look up by site name) end up *returning*
// the site data. This is probably not intentional and may result in bugs in deploy/init. Investigate.
return initialSiteData || newSiteData;
};
//# sourceMappingURL=link.js.map