diff --git a/packages/create-app/src/createApp.test.ts b/packages/create-app/src/createApp.test.ts index 36552ae306..76b5d24b98 100644 --- a/packages/create-app/src/createApp.test.ts +++ b/packages/create-app/src/createApp.test.ts @@ -33,7 +33,7 @@ const checkPathExistsMock = jest.spyOn(tasks, 'checkPathExistsTask'); const templatingMock = jest.spyOn(tasks, 'templatingTask'); const checkAppExistsMock = jest.spyOn(tasks, 'checkAppExistsTask'); const initGitRepositoryMock = jest.spyOn(tasks, 'initGitRepository'); -const checkForGitSetup = jest.spyOn(tasks, 'checkForGitSetup'); +const readGitConfig = jest.spyOn(tasks, 'readGitConfig'); const createTemporaryAppFolderMock = jest.spyOn( tasks, 'createTemporaryAppFolderTask', @@ -58,7 +58,11 @@ describe('command entrypoint', () => { name: 'MyApp', dbType: 'PostgreSQL', }); - checkForGitSetup.mockResolvedValue(true); + readGitConfig.mockResolvedValue({ + name: 'git-user', + email: 'git-email', + defaultBranch: 'git-default-branch', + }); }); afterEach(() => { @@ -91,9 +95,9 @@ describe('command entrypoint', () => { expect(buildAppMock).not.toHaveBeenCalled(); }); - it('should not call `initGitRepository` when `isGitConfigured` is false', async () => { + it('should not call `initGitRepository` when `gitConfig` is undefined', async () => { const cmd = {} as unknown as Command; - checkForGitSetup.mockResolvedValue(false); + readGitConfig.mockResolvedValue(undefined); await createApp(cmd); expect(initGitRepositoryMock).not.toHaveBeenCalled(); }); diff --git a/packages/create-app/src/createApp.ts b/packages/create-app/src/createApp.ts index 74e32eeba2..fd70a4c238 100644 --- a/packages/create-app/src/createApp.ts +++ b/packages/create-app/src/createApp.ts @@ -29,9 +29,11 @@ import { moveAppTask, templatingTask, initGitRepository, - checkForGitSetup, + readGitConfig, } from './lib/tasks'; +const DEFAULT_BRANCH = 'master'; + export default async (opts: OptionValues): Promise => { /* eslint-disable-next-line no-restricted-syntax */ const paths = findPaths(__dirname); @@ -78,7 +80,7 @@ export default async (opts: OptionValues): Promise => { Task.log('Creating the app...'); try { - const isGitConfigured = await checkForGitSetup(); + const gitConfig = await readGitConfig(); if (opts.path) { // Template directly to specified path @@ -86,13 +88,11 @@ export default async (opts: OptionValues): Promise => { Task.section('Checking that supplied path exists'); await checkPathExistsTask(appDir); - if (isGitConfigured) { - Task.section('Initializing git repository'); - await initGitRepository(appDir, answers); - } - Task.section('Preparing files'); - await templatingTask(templateDir, opts.path, answers); + await templatingTask(templateDir, opts.path, { + ...answers, + defaultBranch: gitConfig?.defaultBranch ?? DEFAULT_BRANCH, + }); } else { // Template to temporary location, and then move files @@ -102,18 +102,21 @@ export default async (opts: OptionValues): Promise => { Task.section('Creating a temporary app directory'); await createTemporaryAppFolderTask(tempDir); - if (isGitConfigured) { - Task.section('Initializing git repository'); - await initGitRepository(tempDir, answers); - } - Task.section('Preparing files'); - await templatingTask(templateDir, tempDir, answers); + await templatingTask(templateDir, tempDir, { + ...answers, + defaultBranch: gitConfig?.defaultBranch ?? DEFAULT_BRANCH, + }); Task.section('Moving to final location'); await moveAppTask(tempDir, appDir, answers.name); } + if (gitConfig) { + Task.section('Initializing git repository'); + await initGitRepository(appDir); + } + if (!opts.skipInstall) { Task.section('Building the app'); await buildAppTask(appDir); diff --git a/packages/create-app/src/lib/tasks.test.ts b/packages/create-app/src/lib/tasks.test.ts index b1cdb293fc..b8d3202976 100644 --- a/packages/create-app/src/lib/tasks.test.ts +++ b/packages/create-app/src/lib/tasks.test.ts @@ -27,7 +27,7 @@ import { moveAppTask, templatingTask, initGitRepository, - checkForGitSetup, + readGitConfig, } from './tasks'; const commandExists = jest.fn(); @@ -286,17 +286,22 @@ describe('tasks', () => { }); }); - describe('checkForGitSetup', () => { - it('should return true if git package is installed and git credentials are set', async () => { + describe('readGitConfig', () => { + it('should return git config if git package is installed and git credentials are set', async () => { mockExec.mockImplementation((_command, callback) => { callback(null, { stdout: 'main' }, 'standard error'); }); commandExists.mockResolvedValue(true); - const isGitConfigured = await checkForGitSetup(); + const gitConfig = await readGitConfig(); - expect(isGitConfigured).toBe(true); + expect(gitConfig).toBeTruthy(); + expect(gitConfig).toEqual({ + name: 'main', + email: 'main', + defaultBranch: 'main', + }); expect(mockExec).toHaveBeenCalledWith( 'git config user.name', expect.any(Function), @@ -305,14 +310,19 @@ describe('tasks', () => { 'git config user.email', expect.any(Function), ); + expect(mockExec).toHaveBeenCalledWith('git init', expect.any(Function)); + expect(mockExec).toHaveBeenCalledWith( + 'git commit --allow-empty -m "Initial commit"', + expect.any(Function), + ); }); it('should return false if git package is not installed', async () => { commandExists.mockResolvedValue(false); - const isGitConfigured = await checkForGitSetup(); + const gitConfig = await readGitConfig(); - expect(isGitConfigured).toBe(false); + expect(gitConfig).toBeUndefined(); }); it('should return false if git package is installed but git credentials are not set', async () => { @@ -322,9 +332,9 @@ describe('tasks', () => { commandExists.mockResolvedValue(true); - const isGitConfigured = await checkForGitSetup(); + const gitConfig = await readGitConfig(); - expect(isGitConfigured).toBe(false); + expect(gitConfig).toBeUndefined(); expect(mockExec).toHaveBeenCalledWith( 'git config user.name', expect.any(Function), @@ -339,18 +349,14 @@ describe('tasks', () => { describe('initGitRepository', () => { it('should initialize a git repository at the given path', async () => { const destinationDir = 'tmp/mockApp/'; - const context = { - defaultBranch: '', - }; mockExec.mockImplementation((_command, callback) => { callback(null, { stdout: 'main' }, 'standard error'); }); - await initGitRepository(destinationDir, context); + await initGitRepository(destinationDir); - expect(context.defaultBranch).toBe('main'); - expect(mockExec).toHaveBeenCalledTimes(3); + expect(mockExec).toHaveBeenCalledTimes(2); expect(mockExec).toHaveBeenNthCalledWith( 1, 'git init', @@ -361,11 +367,6 @@ describe('tasks', () => { 'git commit --allow-empty -m "Initial commit"', expect.any(Function), ); - expect(mockExec).toHaveBeenNthCalledWith( - 3, - 'git branch --format="%(refname:short)"', - expect.any(Function), - ); }); }); }); diff --git a/packages/create-app/src/lib/tasks.ts b/packages/create-app/src/lib/tasks.ts index d1c7b125f4..6b40ecac63 100644 --- a/packages/create-app/src/lib/tasks.ts +++ b/packages/create-app/src/lib/tasks.ts @@ -29,10 +29,17 @@ import { exec as execCb } from 'child_process'; import { packageVersions } from './versions'; import { promisify } from 'util'; import commandExists from 'command-exists'; +import os from 'os'; const TASK_NAME_MAX_LENGTH = 14; const exec = promisify(execCb); +export type GitConfig = { + name?: string; + email?: string; + defaultBranch?: string; +}; + export class Task { static log(name: string = '') { process.stdout.write(`${chalk.green(name)}\n`); @@ -239,11 +246,13 @@ export async function moveAppTask( } /** - * Checks if git package is installed and git credentials exists + * Read git configs by creating a temp folder and initializing a repo * * @throws if `exec` fails */ -export async function checkForGitSetup(): Promise { +export async function readGitConfig(): Promise { + const tempDir = resolvePath(os.tmpdir(), 'git-temp-dir'); + const runCmd = (cmd: string) => exec(cmd).catch(error => { process.stdout.write(error.stderr); @@ -251,26 +260,52 @@ export async function checkForGitSetup(): Promise { throw new Error(`Could not execute command ${chalk.cyan(cmd)}`); }); - const gitCommandExists = await commandExists('git'); + const isGitAvailable = await commandExists('git').catch(() => false); - if (!gitCommandExists) return false; + if (!isGitAvailable) return; - const [gitUsername, gitEmail] = await Promise.all([ - runCmd('git config user.name'), - runCmd('git config user.email'), - ]); + try { + await fs.mkdir(tempDir); - return Boolean(gitUsername.stdout?.trim() && gitEmail.stdout?.trim()); + process.chdir(tempDir); + + const [gitUsername, gitEmail] = await Promise.all([ + runCmd('git config user.name'), + runCmd('git config user.email'), + ]); + + const gitCredentials = Boolean( + gitUsername.stdout?.trim() && gitEmail.stdout?.trim(), + ); + + if (!gitCredentials) return; + + await runCmd('git init'); + await runCmd('git commit --allow-empty -m "Initial commit"'); + + const gitDefaultBranch = await runCmd( + 'git branch --format="%(refname:short)"', + ); + + return { + name: gitUsername.stdout?.trim(), + email: gitEmail.stdout?.trim(), + defaultBranch: gitDefaultBranch.stdout?.trim(), + }; + } catch (error) { + throw new Error(`Failed to read git config, ${error}`); + } finally { + await fs.rm(tempDir, { recursive: true }); + } } /** * Initializes a git repository in the destination folder * * @param dir - source path to initialize git repository in - * @param context - template parameters * @throws if `exec` fails */ -export async function initGitRepository(dir: string, context: any) { +export async function initGitRepository(dir: string) { const runCmd = (cmd: string) => exec(cmd).catch(error => { process.stdout.write(error.stderr); @@ -283,11 +318,5 @@ export async function initGitRepository(dir: string, context: any) { await runCmd('git init'); await runCmd('git commit --allow-empty -m "Initial commit"'); - - const defaultBranch = await runCmd( - 'git branch --format="%(refname:short)"', - ); - - context.defaultBranch = defaultBranch.stdout?.trim() || 'master'; }); }