diff --git a/commitlint.config.js b/commitlint.config.js index 7c4ff4d9841..ce5afa95b4b 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -1 +1,10 @@ -export default { extends: ['@commitlint/config-conventional'] } +export default { + extends: ['@commitlint/config-conventional'], + parserPreset: { + parserOpts: { + headerPattern: /^(\w+)(?:\(([^)]*)\))?(!)?:\s(.+)$/, + breakingHeaderPattern: /^(\w+)(?:\(([^)]*)\))?(!)?:\s(.+)$/, + headerCorrespondence: ['type', 'scope', 'breaking', 'subject'], + }, + }, +} diff --git a/docs/commands/sites.md b/docs/commands/sites.md index d11c84a135c..c4d3dd38af2 100644 --- a/docs/commands/sites.md +++ b/docs/commands/sites.md @@ -26,7 +26,7 @@ netlify sites | Subcommand | description | |:--------------------------- |:-----| | [`sites:create`](/commands/sites#sitescreate) | Create an empty project (advanced) | -| [`sites:create-template`](/commands/sites#sitescreate-template) | (Beta) Create a project from a starter template | +| [`sites:create-template`](/commands/sites#sitescreate-template) | (Deprecated) Create a project from a starter template | | [`sites:delete`](/commands/sites#sitesdelete) | Delete a project | | [`sites:list`](/commands/sites#siteslist) | List all projects you have access to | @@ -64,8 +64,8 @@ netlify sites:create --- ## `sites:create-template` -(Beta) Create a project from a starter template -Create a project from a starter template. +(Deprecated) Create a project from a starter template +This command has been deprecated. Use the Netlify UI to deploy from a template. **Usage** @@ -73,27 +73,11 @@ Create a project from a starter template. netlify sites:create-template ``` -**Arguments** - -- repository - repository to use as starter template - **Flags** -- `account-slug` (*string*) - account slug to create the project under - `filter` (*string*) - For monorepos, specify the name of the application to run the command in -- `name` (*string*) - name of project -- `url` (*string*) - template url - `debug` (*boolean*) - Print debugging information - `auth` (*string*) - Netlify auth token - can be used to run this command without logging in -- `with-ci` (*boolean*) - initialize CI hooks during project creation - -**Examples** - -```bash -netlify sites:create-template -netlify sites:create-template nextjs-blog-theme -netlify sites:create-template my-github-profile/my-template -``` --- ## `sites:delete` diff --git a/docs/index.md b/docs/index.md index ee86844d08a..bb28aa73791 100644 --- a/docs/index.md +++ b/docs/index.md @@ -165,7 +165,7 @@ Handle various project operations | Subcommand | description | |:--------------------------- |:-----| | [`sites:create`](/commands/sites#sitescreate) | Create an empty project (advanced) | -| [`sites:create-template`](/commands/sites#sitescreate-template) | (Beta) Create a project from a starter template | +| [`sites:create-template`](/commands/sites#sitescreate-template) | (Deprecated) Create a project from a starter template | | [`sites:delete`](/commands/sites#sitesdelete) | Delete a project | | [`sites:list`](/commands/sites#siteslist) | List all projects you have access to | diff --git a/src/commands/sites/sites-create-template.ts b/src/commands/sites/sites-create-template.ts deleted file mode 100644 index 87057e9ea19..00000000000 --- a/src/commands/sites/sites-create-template.ts +++ /dev/null @@ -1,294 +0,0 @@ -import path from 'node:path' -import { fileURLToPath } from 'node:url' - -import type { OptionValues } from 'commander' -import inquirer from 'inquirer' -import pick from 'lodash/pick.js' -import { render } from 'prettyjson' -import { v4 as uuid } from 'uuid' - -import { - chalk, - logAndThrowError, - getTerminalLink, - log, - logJson, - warn, - type APIError, - GitHubAPIError, - type GitHubRepoResponse, -} from '../../utils/command-helpers.js' -import execa from '../../utils/execa.js' -import getRepoData from '../../utils/get-repo-data.js' -import { getGitHubToken } from '../../utils/init/config-github.js' -import { configureRepo } from '../../utils/init/config.js' -import { deployedSiteExists, getGitHubLink, getTemplateName } from '../../utils/sites/create-template.js' -import { callLinkSite, createRepo, validateTemplate } from '../../utils/sites/utils.js' -import { track } from '../../utils/telemetry/index.js' -import type { SiteInfo } from '../../utils/types.js' -import type BaseCommand from '../base-command.js' - -import { getSiteNameInput } from './sites-create.js' - -export const sitesCreateTemplate = async (repository: string, options: OptionValues, command: BaseCommand) => { - const { accounts, api } = command.netlify - await command.authenticate() - - const { globalConfig } = command.netlify - const ghToken = await getGitHubToken({ globalConfig }) - const templateName = await getTemplateName({ ghToken, options, repository }) - const { exists, isTemplate } = await validateTemplate({ templateName, ghToken }) - if (!exists) { - const githubLink = getGitHubLink({ options, templateName }) - return logAndThrowError( - `Could not find template ${chalk.bold(templateName)}. Please verify it exists and you can ${getTerminalLink( - 'access to it on GitHub', - githubLink, - )}`, - ) - } - if (!isTemplate) { - const githubLink = getGitHubLink({ options, templateName }) - return logAndThrowError(`${getTerminalLink(chalk.bold(templateName), githubLink)} is not a valid GitHub template`) - } - - 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 site: SiteInfo - let repoResp: Awaited> - - // Allow the user to reenter project name if selected one isn't available - const inputSiteName = async (name?: string, hasExistingRepo?: boolean): Promise<[SiteInfo, GitHubRepoResponse]> => { - const { name: inputName } = await getSiteNameInput(name) - - const siteName = inputName.trim() - - if (siteName && (await deployedSiteExists(siteName))) { - log('A project with that name already exists') - return inputSiteName() - } - - try { - const sites = await api.listSites({ name: siteName, filter: 'all' }) - const siteFoundByName = sites.find((filteredSite) => filteredSite.name === siteName) - if (siteFoundByName) { - log('A project with that name already exists on your account') - return inputSiteName() - } - } catch (error_) { - return logAndThrowError(error_) - } - - if (!hasExistingRepo) { - try { - // Create new repo from template - let gitHubInputName = siteName || templateName - repoResp = await createRepo(templateName, ghToken, gitHubInputName) - if (repoResp.errors && repoResp.errors[0].includes('Name already exists on this account')) { - if (gitHubInputName === templateName) { - gitHubInputName += `-${uuid().split('-')[0]}` - repoResp = await createRepo(templateName, ghToken, gitHubInputName) - } else { - warn(`It seems you have already created a repository with the name ${gitHubInputName}.`) - return inputSiteName() - } - } - if (!repoResp.id) { - throw new GitHubAPIError((repoResp as GitHubAPIError).status, (repoResp as GitHubAPIError).message) - } - hasExistingRepo = true - } catch (error_) { - if ((error_ as GitHubAPIError).status === '404') { - return logAndThrowError( - `Could not create repository: ${ - (error_ as GitHubAPIError).message - }. Ensure that your GitHub personal access token grants permission to create repositories`, - ) - } else { - return logAndThrowError( - `Something went wrong trying to create the repository. We're getting the following error: '${ - (error_ as GitHubAPIError).message - }'. You can try to re-run this command again or open an issue in our repository: https://github.com/netlify/cli/issues`, - ) - } - } - } - - try { - // FIXME(serhalp): `id` and `name` should be required in `netlify` package type - site = (await api.createSiteInTeam({ - accountSlug, - body: { - repo: { - provider: 'github', - // @ts-expect-error -- FIXME(serhalp): Supposedly this is does not exist. Investigate. - repo: repoResp.full_name, - // FIXME(serhalp): Supposedly this should be `public_repo`. Investigate. - private: repoResp.private, - // FIXME(serhalp): Supposedly this should be `repo_branch`. Investigate. - branch: repoResp.default_branch, - }, - name: siteName, - }, - })) as unknown as SiteInfo - } catch (error_) { - if ((error_ as APIError).status === 422) { - log(`createSiteInTeam error: ${(error_ as APIError).status}: ${(error_ as APIError).message}`) - log('Cannot create a project with that name. Project name may already exist. Please try a new name.') - return inputSiteName(undefined, hasExistingRepo) - } - return logAndThrowError(`createSiteInTeam error: ${(error_ as APIError).status}: ${(error_ as APIError).message}`) - } - return [site, repoResp] - } - - ;[site, repoResp] = await inputSiteName(nameFlag) - - log() - log(chalk.greenBright.bold.underline(`Project Created`)) - log() - - const siteUrl = site.ssl_url || site.url - log( - render({ - 'Admin URL': site.admin_url, - URL: siteUrl, - 'Project ID': site.id, - 'Repo URL': site.build_settings?.repo_url ?? '', - }), - ) - - track('sites_createdFromTemplate', { - siteId: site.id, - adminUrl: site.admin_url, - siteUrl, - }) - - const { cloneConfirm } = await inquirer.prompt({ - type: 'confirm', - name: 'cloneConfirm', - message: `Do you want to clone the repository to your local machine?`, - default: true, - }) - if (cloneConfirm) { - log() - - if (repoResp.clone_url) { - await execa('git', ['clone', repoResp.clone_url, `${repoResp.name}`]) - } - - log(`🚀 Repository cloned successfully. You can find it under the ${chalk.magenta(repoResp.name)} folder`) - - const { linkConfirm } = await inquirer.prompt({ - type: 'confirm', - name: 'linkConfirm', - message: `Do you want to link the cloned directory to the project?`, - default: true, - }) - - if (linkConfirm) { - const __dirname = path.dirname(fileURLToPath(import.meta.url)) - - const cliPath = path.resolve(__dirname, '../../../bin/run.js') - - let stdout - // TODO(serhalp): Why is this condition here? We've asked the user multiple prompts, but we already knew we had - // invalid repo data. Move upstream. - if (repoResp.name) { - stdout = await callLinkSite(cliPath, repoResp.name, '\n') - } else { - return logAndThrowError('Failed to fetch the repo') - } - - const linkedSiteUrlRegex = /Project url:\s+(\S+)/ - const lineMatch = linkedSiteUrlRegex.exec(stdout) - const urlMatch = lineMatch ? lineMatch[1] : undefined - if (urlMatch) { - log(`\nDirectory ${chalk.cyanBright(repoResp.name)} linked to project ${chalk.cyanBright(urlMatch)}\n`) - log( - `${chalk.cyanBright.bold('cd', repoResp.name)} to use other netlify cli commands in the cloned directory.\n`, - ) - } else { - const linkedSiteMatch = /Project already linked to\s+(\S+)/.exec(stdout) - const linkedSiteNameMatch = linkedSiteMatch ? linkedSiteMatch[1] : undefined - if (linkedSiteNameMatch) { - log(`\nThis directory appears to be linked to ${chalk.cyanBright(linkedSiteNameMatch)}`) - log('This can happen if you cloned the template into a subdirectory of an existing Netlify project.') - log( - `You may need to move the ${chalk.cyanBright( - repoResp.name, - )} directory out of its parent directory and then re-run the ${chalk.cyanBright( - 'link', - )} command manually\n`, - ) - } else { - log('A problem occurred linking the project') - log('You can try again manually by running:') - log(chalk.cyanBright(`cd ${repoResp.name} && netlify link\n`)) - } - } - } else { - log('To link the cloned directory manually, run:') - log(chalk.cyanBright(`cd ${repoResp.name} && netlify link\n`)) - } - } - - if (options.withCi) { - log('Configuring CI') - const repoData = await getRepoData({ workingDir: command.workingDir }) - - if ('error' in repoData) { - return logAndThrowError('Failed to get repo data') - } - - 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 -} diff --git a/src/commands/sites/sites.ts b/src/commands/sites/sites.ts index bf6913cfc3c..11cf14b0678 100644 --- a/src/commands/sites/sites.ts +++ b/src/commands/sites/sites.ts @@ -1,5 +1,5 @@ import { OptionValues, InvalidArgumentError } from 'commander' - +import { chalk, log } from '../../utils/command-helpers.js' import BaseCommand from '../base-command.js' const MAX_SITE_NAME_LENGTH = 63 @@ -22,23 +22,21 @@ export const createSitesFromTemplateCommand = (program: BaseCommand) => { program .command('sites:create-template') .description( - `(Beta) Create a project from a starter template -Create a project from a starter template.`, + `(Deprecated) Create a project from a starter template +This command has been deprecated. Use the Netlify UI to deploy from a template.`, ) - .option('-n, --name [name]', 'name of project') - .option('-u, --url [url]', 'template url') - .option('-a, --account-slug [slug]', 'account slug to create the project under') - .option('-c, --with-ci', 'initialize CI hooks during project creation') - .argument('[repository]', 'repository to use as starter template') - .addHelpText('after', `(Beta) Create a project from starter template.`) - .addExamples([ - 'netlify sites:create-template', - 'netlify sites:create-template nextjs-blog-theme', - 'netlify sites:create-template my-github-profile/my-template', - ]) - .action(async (repository: string, options: OptionValues, command: BaseCommand) => { - const { sitesCreateTemplate } = await import('./sites-create-template.js') - await sitesCreateTemplate(repository, options, command) + .addHelpText( + 'after', + `(Deprecated) This command has been deprecated. Learn more: https://docs.netlify.com/start/quickstarts/deploy-from-template/`, + ) + + .action(() => { + log() + log(chalk.yellow.bold('This command has been deprecated.')) + log() + log('To deploy from a template, please use the Netlify UI or API.') + log(`Learn more: ${chalk.cyan('https://docs.netlify.com/start/quickstarts/deploy-from-template/')}`) + log() }) } diff --git a/src/utils/sites/create-template.ts b/src/utils/sites/create-template.ts deleted file mode 100644 index bb8cc98bae1..00000000000 --- a/src/utils/sites/create-template.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { OptionValues } from 'commander' -import inquirer from 'inquirer' -import parseGitHubUrl from 'parse-github-url' - -import { log } from '../command-helpers.js' -import { Template, GitHubRepo } from '../types.js' - -import { getTemplatesFromGitHub } from './utils.js' - -export const fetchTemplates = async (token: string): Promise => { - const templatesFromGitHubOrg: GitHubRepo[] = await getTemplatesFromGitHub(token) - - return ( - templatesFromGitHubOrg - // adding this filter because the react-based-templates has multiple templates in one repo so doesn't work for this command - .filter((repo: GitHubRepo) => !repo.archived && !repo.disabled && repo.name !== 'react-based-templates') - .map((template: GitHubRepo) => ({ - name: template.name, - sourceCodeUrl: template.html_url, - slug: template.full_name, - })) - ) -} - -export const getTemplateName = async ({ - ghToken, - options, - repository, -}: { - ghToken: string - options: OptionValues - repository: string -}) => { - if (repository) { - const parsedUrl = parseGitHubUrl(repository) - return parsedUrl?.repo || `netlify-templates/${repository}` - } - - if (options.url) { - const urlFromOptions = new URL(options.url) - return urlFromOptions.pathname.slice(1) - } - - const templates = await fetchTemplates(ghToken) - - log(`Choose one of our starter templates. Netlify will create a new repo for this template in your GitHub account.`) - - const { templateName } = await inquirer.prompt([ - { - type: 'list', - name: 'templateName', - message: 'Template:', - choices: templates.map((template) => ({ - value: template.slug, - name: template.name, - })), - }, - ]) - - return templateName -} - -export const deployedSiteExists = async (name: string): Promise => { - const resp = await fetch(`https://${name}.netlify.app`, { - method: 'GET', - }) - - return resp.status === 200 -} - -export const getGitHubLink = ({ options, templateName }: { options: OptionValues; templateName: string }): string => - (options.url as string | undefined) || `https://github.com/${templateName}` diff --git a/src/utils/sites/utils.ts b/src/utils/sites/utils.ts index b60111a4b8a..5ac1a8c1220 100644 --- a/src/utils/sites/utils.ts +++ b/src/utils/sites/utils.ts @@ -1,7 +1,6 @@ import fetch from 'node-fetch' -import execa from 'execa' -import { GitHubRepoResponse, logAndThrowError } from '../command-helpers.js' +import { logAndThrowError } from '../command-helpers.js' import { GitHubRepo } from '../types.js' export const getTemplatesFromGitHub = async (token: string): Promise => { @@ -29,51 +28,3 @@ export const getTemplatesFromGitHub = async (token: string): Promise { - const response = await fetch(`https://api.github.com/repos/${templateName}`, { - headers: { - Authorization: `token ${ghToken}`, - }, - }) - - if (response.status === 404) { - return { exists: false } - } - - if (!response.ok) { - throw new Error(`Error fetching template ${templateName}: ${await response.text()}`) - } - - const data = (await response.json()) as GitHubRepoResponse - - return { exists: true, isTemplate: data.is_template } -} - -export const createRepo = async ( - templateName: string, - ghToken: string, - siteName: string, -): Promise => { - const resp = await fetch(`https://api.github.com/repos/${templateName}/generate`, { - method: 'POST', - headers: { - Authorization: `token ${ghToken}`, - }, - body: JSON.stringify({ - name: siteName, - }), - }) - - const data = await resp.json() - - return data as GitHubRepoResponse -} - -export const callLinkSite = async (cliPath: string, repoName: string, input: string) => { - const { stdout } = await execa(cliPath, ['link'], { - input, - cwd: repoName, - }) - return stdout -} diff --git a/tests/integration/commands/sites/sites-create-template.test.ts b/tests/integration/commands/sites/sites-create-template.test.ts deleted file mode 100644 index ccbc2105a34..00000000000 --- a/tests/integration/commands/sites/sites-create-template.test.ts +++ /dev/null @@ -1,249 +0,0 @@ -import process from 'process' - -import inquirer from 'inquirer' -import { beforeEach, afterEach, describe, expect, test, vi, afterAll } from 'vitest' - -import BaseCommand from '../../../../src/commands/base-command.js' -import { createSitesFromTemplateCommand } from '../../../../src/commands/sites/sites.js' -import { deployedSiteExists, fetchTemplates, getTemplateName } from '../../../../src/utils/sites/create-template.js' -import { - getTemplatesFromGitHub, - validateTemplate, - createRepo, - callLinkSite, -} from '../../../../src/utils/sites/utils.js' -import { getEnvironmentVariables, withMockApi } from '../../utils/mock-api.js' -import { chalk } from '../../../../src/utils/command-helpers.js' - -vi.mock('../../../../src/utils/init/config-github.ts') -vi.mock('../../../../src/utils/sites/utils.ts') -vi.mock('../../../../src/utils/sites/create-template.ts') -vi.mock('inquirer') - -inquirer.registerPrompt = vi.fn() -inquirer.prompt.registerPrompt = vi.fn() - -const siteInfo = { - admin_url: 'https://app.netlify.com/projects/site-name/overview', - ssl_url: 'https://site-name.netlify.app/', - id: 'site_id', - name: 'site-name', - build_settings: { repo_url: 'https://github.com/owner/repo' }, -} - -const routes = [ - { - path: 'accounts', - response: [{ slug: 'test-account' }], - }, - { - path: 'sites', - response: [{ name: 'test-name' }], - }, - { - path: 'test-account/sites', - response: siteInfo, - method: 'POST' as const, - }, -] - -const OLD_ENV = process.env - -describe('sites:create-template', () => { - beforeEach(async () => { - inquirer.prompt = Object.assign( - vi - .fn() - .mockImplementationOnce(() => Promise.resolve({ accountSlug: 'test-account' })) - .mockImplementationOnce(() => Promise.resolve({ name: 'test-name' })) - .mockImplementationOnce(() => Promise.resolve({ cloneConfirm: true })) - .mockImplementationOnce(() => Promise.resolve({ linkConfirm: true })), - { - prompts: inquirer.prompt?.prompts || {}, - registerPrompt: inquirer.prompt?.registerPrompt || vi.fn(), - restoreDefaultPrompts: inquirer.prompt?.restoreDefaultPrompts || vi.fn(), - }, - ) - - vi.mocked(fetchTemplates).mockResolvedValue([ - { - name: 'mockTemplateName', - sourceCodeUrl: 'mockUrl', - slug: 'mockSlug', - }, - ]) - vi.mocked(getTemplatesFromGitHub).mockResolvedValue([ - { - name: 'mock-name', - html_url: 'mock-url', - full_name: 'mock-full-name', - archived: false, - disabled: false, - }, - ]) - vi.mocked(getTemplateName).mockResolvedValue('mockTemplateName') - vi.mocked(validateTemplate).mockResolvedValue({ - exists: true, - isTemplate: true, - }) - vi.mocked(createRepo).mockResolvedValue({ - id: 1, - full_name: 'mockName', - private: true, - default_branch: 'mockBranch', - name: 'repoName', - }) - }) - - afterEach(() => { - vi.clearAllMocks() - }) - - afterAll(() => { - vi.resetModules() - vi.restoreAllMocks() - - Object.defineProperty(process, 'env', { - value: OLD_ENV, - }) - }) - - test('it should ask for a new project name if project with that name already exists on a globally deployed project', async (t) => { - const stdoutwriteSpy = vi.spyOn(process.stdout, 'write') - await withMockApi(routes, async ({ apiUrl }) => { - Object.assign(process.env, getEnvironmentVariables({ apiUrl })) - - const program = new BaseCommand('netlify') - - vi.mocked(deployedSiteExists).mockResolvedValue(true) - - createSitesFromTemplateCommand(program) - - await program.parseAsync([ - '', - '', - 'sites:create-template', - '--account-slug', - 'test-account', - '--name', - 'test-name', - ]) - }) - expect(stdoutwriteSpy).toHaveBeenCalledWith('A project with that name already exists\n') - }) - - test('it should ask for a new project name if project with that name already exists on account', async (t) => { - const stdoutwriteSpy = vi.spyOn(process.stdout, 'write') - await withMockApi(routes, async ({ apiUrl }) => { - Object.assign(process.env, getEnvironmentVariables({ apiUrl })) - - const program = new BaseCommand('netlify') - - vi.mocked(deployedSiteExists).mockResolvedValue(false) - - createSitesFromTemplateCommand(program) - - await program.parseAsync([ - '', - '', - 'sites:create-template', - '--account-slug', - 'test-account', - '--name', - 'test-name', - ]) - }) - expect(stdoutwriteSpy).toHaveBeenCalledWith('A project with that name already exists on your account\n') - }) - - test('it should automatically link to the project when the user clones the template repo', async (t) => { - const mockSuccessfulLinkOutput = ` - Directory Linked - - Admin url: https://app.netlify.com/projects/site-name - Project url: https://site-name.netlify.app - - You can now run other \`netlify\` cli commands in this directory - ` - vi.mocked(callLinkSite).mockImplementationOnce(() => Promise.resolve(mockSuccessfulLinkOutput)) - - const autoLinkRoutes = [ - { - path: 'accounts', - response: [{ slug: 'test-account' }], - }, - { - path: 'sites', - response: [{ name: 'test-name-unique' }], - }, - { - path: 'test-account/sites', - response: siteInfo, - method: 'POST' as const, - }, - ] - - const stdoutwriteSpy = vi.spyOn(process.stdout, 'write') - await withMockApi(autoLinkRoutes, async ({ apiUrl }) => { - Object.assign(process.env, getEnvironmentVariables({ apiUrl })) - - const program = new BaseCommand('netlify') - - vi.mocked(deployedSiteExists).mockResolvedValue(false) - - createSitesFromTemplateCommand(program) - - await program.parseAsync(['', '', 'sites:create-template']) - }) - - expect(stdoutwriteSpy).toHaveBeenCalledWith( - `\nDirectory ${chalk.cyanBright('repoName')} linked to project ${chalk.cyanBright( - 'https://site-name.netlify.app', - )}\n\n`, - ) - }) - - test('it should output instructions if a project is already linked', async (t) => { - const mockUnsuccessfulLinkOutput = ` - Project already linked to \"site-name\" - Admin url: https://app.netlify.com/projects/site-name - - To unlink this project, run: netlify unlink - ` - - vi.mocked(callLinkSite).mockImplementationOnce(() => Promise.resolve(mockUnsuccessfulLinkOutput)) - - const autoLinkRoutes = [ - { - path: 'accounts', - response: [{ slug: 'test-account' }], - }, - { - path: 'sites', - response: [{ name: 'test-name-unique' }], - }, - { - path: 'test-account/sites', - response: siteInfo, - method: 'POST' as const, - }, - ] - - const stdoutwriteSpy = vi.spyOn(process.stdout, 'write') - await withMockApi(autoLinkRoutes, async ({ apiUrl }) => { - Object.assign(process.env, getEnvironmentVariables({ apiUrl })) - - const program = new BaseCommand('netlify') - - vi.mocked(deployedSiteExists).mockResolvedValue(false) - - createSitesFromTemplateCommand(program) - - await program.parseAsync(['', '', 'sites:create-template']) - }) - - expect(stdoutwriteSpy).toHaveBeenCalledWith( - `\nThis directory appears to be linked to ${chalk.cyanBright(`"site-name"`)}\n`, - ) - }) -}) diff --git a/tests/integration/commands/sites/sites.test.ts b/tests/integration/commands/sites/sites.test.ts index 4afd03e174a..6ee3909353a 100644 --- a/tests/integration/commands/sites/sites.test.ts +++ b/tests/integration/commands/sites/sites.test.ts @@ -1,14 +1,10 @@ import process from 'process' import inquirer from 'inquirer' -import { render } from 'prettyjson' import { afterAll, beforeEach, describe, expect, test, vi } from 'vitest' import BaseCommand from '../../../../src/commands/base-command.js' -import { createSitesCreateCommand, createSitesFromTemplateCommand } from '../../../../src/commands/sites/sites.js' -import { getGitHubToken } from '../../../../src/utils/init/config-github.js' -import { fetchTemplates } from '../../../../src/utils/sites/create-template.js' -import { createRepo, getTemplatesFromGitHub } from '../../../../src/utils/sites/utils.js' +import { createSitesCreateCommand } from '../../../../src/commands/sites/sites.js' import { getEnvironmentVariables, withMockApi } from '../../utils/mock-api.js' vi.mock('../../../../src/utils/command-helpers.js', async () => ({ @@ -35,16 +31,6 @@ vi.mock('../../../../src/utils/sites/utils.js', () => ({ archived: true, }, ]), - createRepo: vi.fn().mockImplementation(() => ({ - full_name: 'Next starter', - private: false, - branch: 'main', - id: 1, - })), - validateTemplate: vi.fn().mockImplementation(() => ({ - exists: true, - isTemplate: true, - })), })) vi.mock('prettyjson', async () => { @@ -107,87 +93,6 @@ describe('sites command', () => { value: OLD_ENV, }) }) - describe('sites:create-template', () => { - test('basic', async () => { - await withMockApi(routes, async ({ apiUrl }) => { - Object.assign(process.env, getEnvironmentVariables({ apiUrl })) - - const program = new BaseCommand('netlify') - - createSitesFromTemplateCommand(program) - - await program.parseAsync(['', '', 'sites:create-template']) - }) - - expect(getGitHubToken).toHaveBeenCalledOnce() - expect(getTemplatesFromGitHub).toHaveBeenCalledOnce() - expect(createRepo).toHaveBeenCalledOnce() - expect(render).toHaveBeenCalledOnce() - expect(render).toHaveBeenCalledWith({ - 'Admin URL': siteInfo.admin_url, - URL: siteInfo.ssl_url, - 'Project ID': siteInfo.id, - 'Repo URL': siteInfo.build_settings.repo_url, - }) - }) - - test('should not fetch templates if one is passed as option', async () => { - await withMockApi(routes, async ({ apiUrl }) => { - Object.assign(process.env, getEnvironmentVariables({ apiUrl })) - - const program = new BaseCommand('netlify') - - createSitesFromTemplateCommand(program) - - await program.parseAsync([ - '', - '', - 'sites:create-template', - '-u', - 'http://github.com/netlify-templates/next-starter', - ]) - - expect(getTemplatesFromGitHub).not.toHaveBeenCalled() - }) - }) - - test('should throw an error if the URL option is not a valid URL', async () => { - await withMockApi(routes, async ({ apiUrl }) => { - Object.assign(process.env, getEnvironmentVariables({ apiUrl })) - - const program = new BaseCommand('netlify') - - createSitesFromTemplateCommand(program) - - await expect(async () => { - await program.parseAsync(['', '', 'sites:create-template', '-u', 'not-a-url']) - }).rejects.toThrowError('Invalid URL') - }) - }) - }) - - describe('fetchTemplates', () => { - test('should return an array of templates with name, source code url and slug', async () => { - await withMockApi(routes, async ({ apiUrl }) => { - Object.assign(process.env, getEnvironmentVariables({ apiUrl })) - - const program = new BaseCommand('netlify') - - createSitesFromTemplateCommand(program) - - const templates = await fetchTemplates('fake-token') - - expect(getTemplatesFromGitHub).toHaveBeenCalledWith('fake-token') - expect(templates).toEqual([ - { - name: 'next-starter', - sourceCodeUrl: 'http://github.com/netlify-templates/next-starter', - slug: 'netlify-templates/next-starter', - }, - ]) - }) - }) - }) describe('sites:create', () => { test('should throw error when name flag is incorrect', async () => {