From 3022af044041b69984dac8bde8b0dbdcf0d9d462 Mon Sep 17 00:00:00 2001 From: Charlie Gerard Date: Tue, 4 Jan 2022 14:46:12 -0800 Subject: [PATCH] feat: implement create site from template feature --- src/commands/sites/sites-create-template.js | 214 ++++++++++++++++++++ src/commands/sites/sites.js | 2 + 2 files changed, 216 insertions(+) create mode 100644 src/commands/sites/sites-create-template.js diff --git a/src/commands/sites/sites-create-template.js b/src/commands/sites/sites-create-template.js new file mode 100644 index 00000000000..543012252b8 --- /dev/null +++ b/src/commands/sites/sites-create-template.js @@ -0,0 +1,214 @@ +// @ts-check + +const slugify = require('@sindresorhus/slugify') +const inquirer = require('inquirer') +const pick = require('lodash/pick') +const sample = require('lodash/sample') +const prettyjson = require('prettyjson') +const { v4: uuidv4 } = require('uuid') + +const { chalk, error, getRepoData, log, logJson, track, warn } = require('../../utils') +const { configureRepo } = require('../../utils/init/config') + +const SITE_NAME_SUGGESTION_SUFFIX_LENGTH = 5 + +const templates = [ + { + name: 'Next.js Starter', + sourceCodeUrl: 'https://github.com/netlify-templates/next-netlify-starter', + }, + { + name: 'Nuxt.js Starter', + sourceCodeUrl: 'https://github.com/Gomah/bluise', + }, + { + name: 'Hugo Blog', + sourceCodeUrl: 'https://github.com/netlify-templates/one-click-hugo-cms', + }, +] + +/** + * The sites:create-template command + * @param {import('commander').OptionValues} options + * @param {import('../base-command').BaseCommand} command + */ +const sitesCreate = async (options, command) => { + const { api } = command.netlify + + await command.authenticate() + + // List templates + + const templateUrl = await inquirer.prompt([ + { + type: 'list', + name: 'templateName', + message: 'Template:', + choices: templates.map((template) => ({ + value: template.sourceCodeUrl, + name: template.name, + })), + }, + ]) + + const accounts = await api.listAccountsForUser() + + let { accountSlug } = options + if (!accountSlug) { + const { accountSlug: accountSlugInput } = await inquirer.prompt([ + { + type: 'list', + name: 'accountSlug', + message: 'Team:', + choices: accounts.map((account) => ({ + value: account.slug, + name: account.name, + })), + }, + ]) + accountSlug = accountSlugInput + } + + const { name: nameFlag } = options + let user + let site + + // Allow the user to reenter site name if selected one isn't available + const inputSiteName = async (name) => { + if (!user) user = await api.getCurrentUser() + + if (!name) { + let { slug } = user + let suffix = '' + + // If the user doesn't have a slug, we'll compute one. Because `full_name` is not guaranteed to be unique, we + // append a short randomly-generated ID to reduce the likelihood of a conflict. + if (!slug) { + slug = slugify(user.full_name || user.email) + suffix = `-${uuidv4().slice(0, SITE_NAME_SUGGESTION_SUFFIX_LENGTH)}` + } + + const suggestions = [ + `super-cool-site-by-${slug}${suffix}`, + `the-awesome-${slug}-site${suffix}`, + `${slug}-makes-great-sites${suffix}`, + `netlify-thinks-${slug}-is-great${suffix}`, + `the-great-${slug}-site${suffix}`, + `isnt-${slug}-awesome${suffix}`, + ] + const siteSuggestion = sample(suggestions) + + console.log( + `Choose a unique site name (e.g. ${siteSuggestion}.netlify.app) or leave it blank for a random name. You can update the site name later.`, + ) + const { name: nameInput } = await inquirer.prompt([ + { + type: 'input', + name: 'name', + message: 'Site name (optional):', + filter: (val) => (val === '' ? undefined : val), + validate: (input) => /^[a-zA-Z\d-]+$/.test(input) || 'Only alphanumeric characters and hyphens are allowed', + }, + ]) + name = nameInput + } + + const body = {} + if (typeof name === 'string') { + body.name = name.trim() + } + try { + console.log(templateUrl) + // site = await api.createSiteInTeam({ + // accountSlug, + // body, + // }) + } catch (error_) { + if (error_.status === 422) { + warn(`${name}.netlify.app already exists. Please try a different slug.`) + await inputSiteName() + } else { + error(`createSiteInTeam error: ${error_.status}: ${error_.message}`) + } + } + } + await inputSiteName(nameFlag) + + log() + log(chalk.greenBright.bold.underline(`Site Created`)) + log() + + const siteUrl = site.ssl_url || site.url + log( + prettyjson.render({ + 'Admin URL': site.admin_url, + URL: siteUrl, + 'Site ID': site.id, + }), + ) + + track('sites_created', { + siteId: site.id, + adminUrl: site.admin_url, + siteUrl, + }) + + if (options.withCi) { + log('Configuring CI') + const repoData = await getRepoData() + await configureRepo({ command, siteId: site.id, repoData, manual: options.manual }) + } + + if (options.json) { + logJson( + pick(site, [ + 'id', + 'state', + 'plan', + 'name', + 'custom_domain', + 'domain_aliases', + 'url', + 'ssl_url', + 'admin_url', + 'screenshot_url', + 'created_at', + 'updated_at', + 'user_id', + 'ssl', + 'force_ssl', + 'managed_dns', + 'deploy_url', + 'account_name', + 'account_slug', + 'git_provider', + 'deploy_hook', + 'capabilities', + 'id_domain', + ]), + ) + } + + return site +} + +/** + * Creates the `netlify sites:create-template` command + * @param {import('../base-command').BaseCommand} program + * @returns + */ +const createSitesFromTemplateCommand = (program) => + program + .command('sites:create-template') + .description( + `Create a site from a starter template +Create a site from a starter template.`, + ) + .option('-n, --name [name]', 'name of site') + .option('-a, --account-slug [slug]', 'account slug to create the site under') + .option('-c, --with-ci', 'initialize CI hooks during site creation') + .option('-m, --manual', 'force manual CI setup. Used --with-ci flag') + .addHelpText('after', `Create a site from starter template.`) + .action(sitesCreate) + +module.exports = { createSitesFromTemplateCommand, sitesCreate } diff --git a/src/commands/sites/sites.js b/src/commands/sites/sites.js index 278e8c8a90a..cdc33332822 100644 --- a/src/commands/sites/sites.js +++ b/src/commands/sites/sites.js @@ -1,5 +1,6 @@ // @ts-check const { createSitesCreateCommand } = require('./sites-create') +const { createSitesFromTemplateCommand } = require('./sites-create-template') const { createSitesDeleteCommand } = require('./sites-delete') const { createSitesListCommand } = require('./sites-list') @@ -19,6 +20,7 @@ const sites = (options, command) => { */ const createSitesCommand = (program) => { createSitesCreateCommand(program) + createSitesFromTemplateCommand(program) createSitesListCommand(program) createSitesDeleteCommand(program)