From 39dd3e02e1db61005646b85b8aaac45e12d0ea8f Mon Sep 17 00:00:00 2001 From: Przemyslaw Mejna Date: Wed, 11 Dec 2024 13:13:04 +0100 Subject: [PATCH 1/8] feat(logger): create base label implementation --- packages/cli/index.ts | 25 +--- packages/utils/logger.ts | 259 +++++++++++++++++++++++++++------------ 2 files changed, 185 insertions(+), 99 deletions(-) diff --git a/packages/cli/index.ts b/packages/cli/index.ts index 94b254c..79e04c2 100644 --- a/packages/cli/index.ts +++ b/packages/cli/index.ts @@ -4,33 +4,12 @@ import chalk from 'chalk'; import { Command } from 'commander'; import gradient from 'gradient-string'; import inquirer from 'inquirer'; +import { logger } from 'stplr-utils'; import { createProject } from 'stplr-core'; import { checkAuthentication } from './utils/checkAuthentication'; import { checkTools } from './utils/checkTools'; import { findUnfinishedProjects, UnfinishedProject } from './utils/findUnfinishedProjects'; -const asciiArt = ` -.&&&% &&&& -.&&&% &&&& -.&&&&&&&&* (&&&&&&&&&&* (&&&.&&&&&&&&&& *&&&# &&&& #&&&&, -.&&&&((((, %&&&&(, .,#&&&&# (&&&&&%(**(%&&&&( *&&&# &&&& (&&&% -.&&&% %&&&. ,&&&% (&&&/ &&&&. *&&&# &&&& #&&&# -.&&&% ,&&&* (&&&. (&&&/ %&&&, *&&&# &&&&&&&&&&. -.&&&% %&&&. *&&&# (&&&/ %&&&, *&&&# &&&&&* *&&&% -.&&&% %&&&&%. ,&&&&&# (&&&/ %&&&, *&&&# &&&& #&&&/ -.&&&% (&&&&&&&&&%* (&&&/ %&&&, *&&&# &&&& .&&&&, -`; - -const displayHeader = () => { - const metalGradient = gradient([ - { color: '#3C3C3C', pos: 0 }, - { color: '#FFFFFF', pos: 1 }, - ]); - - console.log(metalGradient(asciiArt)); - console.log(chalk.bold('\nWelcome to Stapler!\n')); -}; - const program = new Command(); program @@ -40,7 +19,7 @@ program ) .version('0.1.0') .hook('preAction', () => { - displayHeader(); + logger.displayHeader(); }) .option('-n, --name ', 'Set the name of the project') .option('--skip-payload', 'Skip adding Payload to the app') diff --git a/packages/utils/logger.ts b/packages/utils/logger.ts index cf10ba8..6e59e91 100644 --- a/packages/utils/logger.ts +++ b/packages/utils/logger.ts @@ -1,8 +1,21 @@ import chalk from 'chalk'; -import gradient from 'gradient-string'; import ora, { Ora } from 'ora'; +const LABEL_WIDTH = 8; +const LABEL_BG_COLOR = '#FAD400'; +const LABEL_TEXT_COLOR = '#000000'; +const DIMMED_LABEL_BG_COLOR = '#5C4D00'; +const CHECK_MARK_COLOR = '#FAD400'; +const ACTIVE_TEXT_COLOR = '#FFFFFF'; +const COMPLETED_TEXT_COLOR = '#666666'; +const SPACING = 2; + type Name = + | 'dir' + | 'cms' + | 'git' + | 'db' + | 'vrcl' | 'stapler' | 'turborepo' | 'supabase' @@ -15,99 +28,142 @@ type Name = | 'postgres' | 'error'; -type NameProps = { - name: Name; - prefix: string; - colors: string[]; +interface LabelConfig { + text: string; + align?: 'left' | 'right'; +} + +const labels: Record = { + dir: { text: 'dir' }, + cms: { text: 'cms' }, + git: { text: 'git' }, + db: { text: 'db' }, + vrcl: { text: 'vrcl' }, + stapler: { text: 'stplr' }, + turborepo: { text: 'turbo', align: 'right' }, + supabase: { text: 'supa', align: 'right' }, + tailwind: { text: 'tw', align: 'right' }, + payload: { text: 'cms', align: 'right' }, + github: { text: 'git', align: 'right' }, + prettier: { text: 'fmt', align: 'right' }, + vercel: { text: 'vrcl', align: 'right' }, + docker: { text: 'dock', align: 'right' }, + postgres: { text: 'pg', align: 'right' }, + error: { text: 'error', align: 'right' }, }; -const names: NameProps[] = [ - { - name: 'stapler', - prefix: 'Stapler', - colors: ['#FAD400', '#FAD400'], - }, - { - name: 'turborepo', - prefix: 'Turbo', - colors: ['#0099F7', '#F11712'], - }, - { - name: 'supabase', - prefix: 'Supabase', - colors: ['#3ABC82', '#259764'], - }, - { - name: 'tailwind', - prefix: 'Tailwind', - colors: ['#38B2AC', '#0099F7'], - }, - { - name: 'payload', - prefix: 'Payload', - colors: ['#12324A', '#E5AA5F'], - }, - { - name: 'github', - prefix: 'GitHub', - colors: ['#3B8640', '#8256D0'], - }, - { - name: 'prettier', - prefix: 'Prettier', - colors: ['#F11D28', '#FFA12C'], - }, - { - name: 'vercel', - prefix: 'Vercel', - colors: ['#FFF', '#FFF'], - }, - { - name: 'docker', - prefix: 'Docker', - colors: ['#0db7ed', '#0db7ed'], - }, - { - name: 'postgres', - prefix: 'PostgreSQL', - colors: ['#0064a5', '#008bb9'], - }, - { - name: 'error', - prefix: 'Error', - colors: ['#990000', '#FF0000'], - }, -]; - -const getPrefix = (name: Name): string => { - const color = names.find((color) => color.name === name); - if (!color) { - return chalk.red('[Error]'); - } +let currentStep: Name | null = null; +const completedSteps = new Set(); +const stepOutputs = new Map(); + +const formatLabel = (name: Name): string => { + const config = labels[name]; + const label = config.text.padEnd(LABEL_WIDTH); + const isDimmed = currentStep !== null && name !== currentStep && !completedSteps.has(name); + const bgColor = isDimmed ? DIMMED_LABEL_BG_COLOR : LABEL_BG_COLOR; + + return chalk.bgHex(bgColor).hex(LABEL_TEXT_COLOR)(` ${label} `); +}; + +const formatCheckMark = (): string => { + return chalk.hex(CHECK_MARK_COLOR)('✓'); +}; - const gradientColor = gradient(color.colors); - return name === 'vercel' ? chalk.bgBlack(gradientColor(`[▲ ${color.prefix}]`)) : gradientColor(`[${color.prefix}]`); +const formatMessage = (message: string, isCompleted: boolean = false): string => { + return isCompleted ? chalk.hex(COMPLETED_TEXT_COLOR)(message) : chalk.hex(ACTIVE_TEXT_COLOR)(message); }; -const log = (name: Name, messages: string[] | string): void => { - const prefix = getPrefix(name); - console.log(prefix, typeof messages === 'string' ? messages : messages.join(' ')); +const reprintPreviousOutput = () => { + stepOutputs.forEach((outputs, step) => { + outputs.forEach((output) => { + const label = formatLabel(step); + const checkmark = formatCheckMark() + ' '; + const padding = ' '.repeat(SPACING); + const isCompleted = step !== currentStep; + const formattedOutput = formatMessage(output, isCompleted); + + const labelConfig = labels[step]; + if (labelConfig.align === 'right') { + const leftPadding = ' '.repeat(LABEL_WIDTH + SPACING); + console.log(`${leftPadding}${label}${padding}${checkmark}${formattedOutput}`); + } else { + console.log(`${label}${padding}${checkmark}${formattedOutput}`); + } + }); + }); +}; + +const log = (name: Name, messages: string[] | string, showCheck: boolean = true): void => { + // Update current step + if (currentStep !== name) { + if (currentStep) { + completedSteps.add(currentStep); + } + currentStep = name; + + // Clear console and reprint previous outputs + console.clear(); + displayHeader(); + reprintPreviousOutput(); + } + + const messageText = typeof messages === 'string' ? messages : messages.join(' '); + + // Store output for reprinting + if (!stepOutputs.has(name)) { + stepOutputs.set(name, []); + } + stepOutputs.get(name)?.push(messageText); + + // Print current message + const label = formatLabel(name); + const checkmark = showCheck ? formatCheckMark() + ' ' : ' '; + const padding = ' '.repeat(SPACING); + const isCompleted = name !== currentStep; + + const labelConfig = labels[name]; + if (labelConfig.align === 'right') { + const leftPadding = ' '.repeat(LABEL_WIDTH + SPACING); + console.log(`${leftPadding}${label}${padding}${checkmark}${formatMessage(messageText, isCompleted)}`); + } else { + console.log(`${label}${padding}${checkmark}${formatMessage(messageText, isCompleted)}`); + } }; const createSpinner = (name: Name, initialText?: string): Ora => { - const prefix = getPrefix(name); + const label = formatLabel(name); + const padding = ' '.repeat(SPACING); + return ora({ - prefixText: prefix, + prefixText: `${label}${padding}`, text: initialText, spinner: 'dots', + color: 'yellow', }); }; const withSpinner = async (name: Name, initialText: string, action: (spinner: Ora) => Promise): Promise => { + if (currentStep !== name) { + if (currentStep) { + completedSteps.add(currentStep); + } + currentStep = name; + } + const spinner = createSpinner(name, initialText); try { spinner.start(); const result = await action(spinner); + + // Store and display completion message + if (!stepOutputs.has(name)) { + stepOutputs.set(name, []); + } + stepOutputs.get(name)?.push(initialText); + + spinner.stop(); + log(name, initialText, true); + return result; } catch (error) { spinner.fail(); @@ -115,9 +171,60 @@ const withSpinner = async (name: Name, initialText: string, action: (spinner: } }; -// Example usage with named exports +const logUserInput = (step: Name, input: string) => { + if (!stepOutputs.has(step)) { + stepOutputs.set(step, []); + } + stepOutputs.get(step)?.push(input); +}; + +// Reset all state (useful for testing or restarting the process) +const reset = () => { + currentStep = null; + completedSteps.clear(); + stepOutputs.clear(); +}; + +const displayHeader = () => { + const block = chalk.hex(LABEL_BG_COLOR)('█'); + const stplrText = chalk.bgHex(LABEL_BG_COLOR).hex(LABEL_TEXT_COLOR)('stplr'); + const stplrLeft = chalk.bgHex(LABEL_BG_COLOR).hex(LABEL_TEXT_COLOR)(String.fromCharCode(9484)); + const stplrMiddle = chalk.bgHex(LABEL_BG_COLOR).hex(LABEL_TEXT_COLOR)(String.fromCharCode(9472)); + const stplrRight = chalk.bgHex(LABEL_BG_COLOR).hex(LABEL_TEXT_COLOR)(String.fromCharCode(9488)); + const triangle = chalk.hex(LABEL_BG_COLOR)(String.fromCharCode(9701)); + + const logoLines = [ + `${block}${block}${block}${stplrLeft}${stplrMiddle}${stplrRight}${block}`, + `${block}${stplrText}${block}`, + ` ${triangle}`, + ` `, + ]; + + const title = 'Stapler setup initialized'; + const subText1 = ' '; + const subText2 = 'Everything is fine.'; + const subText3 = "I've done this like a million times."; + + const logoWidth = logoLines[0].length; + const padding = 4; + + const output = [ + '', // Initial newline + ` ${logoLines[0]}${' '.repeat(padding)}${chalk.hex(ACTIVE_TEXT_COLOR)(title)}`, + ` ${logoLines[1]}${' '.repeat(padding)}${chalk.gray(subText1)}`, + ` ${logoLines[2]}${' '.repeat(padding)}${chalk.gray(subText2)}`, + ` ${logoLines[3]}${' '.repeat(padding)}${chalk.gray(subText3)}`, + '', + ].join('\n'); + + console.log(output); +}; + export const logger = { log, createSpinner, withSpinner, + displayHeader, + reset, + logUserInput, }; From 816327e2e177d6394eb42316b4060b9b97b984d5 Mon Sep 17 00:00:00 2001 From: KarolinaKopacz Date: Thu, 12 Dec 2024 13:44:07 +0100 Subject: [PATCH 2/8] feat: add background color --- packages/utils/logger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/utils/logger.ts b/packages/utils/logger.ts index 6e59e91..b36f562 100644 --- a/packages/utils/logger.ts +++ b/packages/utils/logger.ts @@ -186,7 +186,7 @@ const reset = () => { }; const displayHeader = () => { - const block = chalk.hex(LABEL_BG_COLOR)('█'); + const block = chalk.bgHex(LABEL_BG_COLOR).hex(LABEL_BG_COLOR)('█'); const stplrText = chalk.bgHex(LABEL_BG_COLOR).hex(LABEL_TEXT_COLOR)('stplr'); const stplrLeft = chalk.bgHex(LABEL_BG_COLOR).hex(LABEL_TEXT_COLOR)(String.fromCharCode(9484)); const stplrMiddle = chalk.bgHex(LABEL_BG_COLOR).hex(LABEL_TEXT_COLOR)(String.fromCharCode(9472)); From acbb4ac733e89a477f6a67b8c2fcf8f3117e4a2a Mon Sep 17 00:00:00 2001 From: Przemyslaw Mejna Date: Thu, 12 Dec 2024 15:11:55 +0100 Subject: [PATCH 3/8] feat(logger): remove stepsCompleted --- packages/utils/logger.ts | 74 ++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 45 deletions(-) diff --git a/packages/utils/logger.ts b/packages/utils/logger.ts index 410e8de..bdeab01 100644 --- a/packages/utils/logger.ts +++ b/packages/utils/logger.ts @@ -1,7 +1,7 @@ import chalk from 'chalk'; import ora, { Ora } from 'ora'; -const LABEL_WIDTH = 8; +const LABEL_WIDTH = 7; const LABEL_BG_COLOR = '#FAD400'; const LABEL_TEXT_COLOR = '#000000'; const DIMMED_LABEL_BG_COLOR = '#5C4D00'; @@ -41,27 +41,26 @@ const labels: Record = { db: { text: 'db' }, vrcl: { text: 'vrcl' }, stapler: { text: 'stplr' }, - turborepo: { text: 'turbo', align: 'right' }, - supabase: { text: 'supa', align: 'right' }, - tailwind: { text: 'tw', align: 'right' }, - payload: { text: 'cms', align: 'right' }, - github: { text: 'git', align: 'right' }, - prettier: { text: 'fmt', align: 'right' }, - vercel: { text: 'vrcl', align: 'right' }, - docker: { text: 'dock', align: 'right' }, - postgres: { text: 'pg', align: 'right' }, - error: { text: 'error', align: 'right' }, + turborepo: { text: 'turbo' }, + supabase: { text: 'supa' }, + tailwind: { text: 'tw' }, + payload: { text: 'cms' }, + github: { text: 'git' }, + prettier: { text: 'fmt' }, + deployment: { text: 'dep' }, + vercel: { text: 'vrcl' }, + docker: { text: 'dock' }, + postgres: { text: 'pg' }, + error: { text: 'error' }, }; -let currentStep: Name | null = null; const completedSteps = new Set(); const stepOutputs = new Map(); const formatLabel = (name: Name): string => { const config = labels[name]; - const label = config.text.padEnd(LABEL_WIDTH); - const isDimmed = currentStep !== null && name !== currentStep && !completedSteps.has(name); - const bgColor = isDimmed ? DIMMED_LABEL_BG_COLOR : LABEL_BG_COLOR; + const label = config.text.padStart(LABEL_WIDTH); + const bgColor = LABEL_BG_COLOR; return chalk.bgHex(bgColor).hex(LABEL_TEXT_COLOR)(` ${label} `); }; @@ -80,10 +79,11 @@ const reprintPreviousOutput = () => { const label = formatLabel(step); const checkmark = formatCheckMark() + ' '; const padding = ' '.repeat(SPACING); - const isCompleted = step !== currentStep; - const formattedOutput = formatMessage(output, isCompleted); + const formattedOutput = formatMessage(output); const labelConfig = labels[step]; + labelConfig.align = labelConfig.align || 'right'; + console.log(labelConfig.align); if (labelConfig.align === 'right') { const leftPadding = ' '.repeat(LABEL_WIDTH + SPACING); console.log(`${leftPadding}${label}${padding}${checkmark}${formattedOutput}`); @@ -96,17 +96,8 @@ const reprintPreviousOutput = () => { const log = (name: Name, messages: string[] | string, showCheck: boolean = true): void => { // Update current step - if (currentStep !== name) { - if (currentStep) { - completedSteps.add(currentStep); - } - currentStep = name; - - // Clear console and reprint previous outputs - console.clear(); - displayHeader(); - reprintPreviousOutput(); - } + console.clear(); + reprintPreviousOutput(); const messageText = typeof messages === 'string' ? messages : messages.join(' '); @@ -120,38 +111,33 @@ const log = (name: Name, messages: string[] | string, showCheck: boolean = true) const label = formatLabel(name); const checkmark = showCheck ? formatCheckMark() + ' ' : ' '; const padding = ' '.repeat(SPACING); - const isCompleted = name !== currentStep; const labelConfig = labels[name]; if (labelConfig.align === 'right') { const leftPadding = ' '.repeat(LABEL_WIDTH + SPACING); - console.log(`${leftPadding}${label}${padding}${checkmark}${formatMessage(messageText, isCompleted)}`); + console.log(`${leftPadding}${label}${padding}${checkmark}${formatMessage(messageText)}`); } else { - console.log(`${label}${padding}${checkmark}${formatMessage(messageText, isCompleted)}`); + console.log(`${label}${padding}${checkmark}${formatMessage(messageText)}`); } }; -const createSpinner = (name: Name, initialText?: string): Ora => { - const label = formatLabel(name); +const createSpinner = (initialText?: string): Ora => { const padding = ' '.repeat(SPACING); return ora({ - prefixText: `${label}${padding}`, + prefixText: `${padding}`, text: initialText, spinner: 'dots', color: 'yellow', }); }; -const withSpinner = async (name: Name, initialText: string, action: (spinner: Ora) => Promise): Promise => { - if (currentStep !== name) { - if (currentStep) { - completedSteps.add(currentStep); - } - currentStep = name; - } - - const spinner = createSpinner(name, initialText); +const withSpinner = async ( + initialText: string, + action: (spinner: Ora) => Promise, + label?: string, +): Promise => { + const spinner = createSpinner(initialText); try { spinner.start(); const result = await action(spinner); @@ -181,7 +167,6 @@ const logUserInput = (step: Name, input: string) => { // Reset all state (useful for testing or restarting the process) const reset = () => { - currentStep = null; completedSteps.clear(); stepOutputs.clear(); }; @@ -206,7 +191,6 @@ const displayHeader = () => { const subText2 = 'Everything is fine.'; const subText3 = "I've done this like a million times."; - const logoWidth = logoLines[0].length; const padding = 4; const output = [ From 5760357da134eb832ddb7bcdd4c866334f79514e Mon Sep 17 00:00:00 2001 From: Przemyslaw Mejna Date: Thu, 19 Dec 2024 09:45:19 +0100 Subject: [PATCH 4/8] feat(ui): add label formatting and paddings --- .../command-prompts/getProjectNamePrompt.ts | 5 +- .../command-prompts/shouldUsePayloadPrompt.ts | 2 +- .../unfinishedProjectsChoicePrompt.ts | 8 +- packages/cli/index.ts | 2 + packages/core/installMachine/index.ts | 14 ++ .../installSteps/stapler/prepareDrink.ts | 4 +- packages/utils/logger.ts | 215 ------------------ packages/utils/logger/displayHeader.ts | 37 +++ packages/utils/logger/index.ts | 1 + packages/utils/logger/logger.ts | 129 +++++++++++ 10 files changed, 196 insertions(+), 221 deletions(-) delete mode 100644 packages/utils/logger.ts create mode 100644 packages/utils/logger/displayHeader.ts create mode 100644 packages/utils/logger/index.ts create mode 100644 packages/utils/logger/logger.ts diff --git a/packages/cli/command-prompts/getProjectNamePrompt.ts b/packages/cli/command-prompts/getProjectNamePrompt.ts index 63a28fa..fbcdc31 100644 --- a/packages/cli/command-prompts/getProjectNamePrompt.ts +++ b/packages/cli/command-prompts/getProjectNamePrompt.ts @@ -1,7 +1,7 @@ import inquirer from 'inquirer'; -export const getProjectNamePrompt = async (): Promise => - ( +export const getProjectNamePrompt = async (): Promise => { + return ( await inquirer.prompt([ { type: 'input', @@ -11,3 +11,4 @@ export const getProjectNamePrompt = async (): Promise => }, ]) ).name; +}; diff --git a/packages/cli/command-prompts/shouldUsePayloadPrompt.ts b/packages/cli/command-prompts/shouldUsePayloadPrompt.ts index 4d3e9e8..f2e0315 100644 --- a/packages/cli/command-prompts/shouldUsePayloadPrompt.ts +++ b/packages/cli/command-prompts/shouldUsePayloadPrompt.ts @@ -13,7 +13,7 @@ export const shouldUsePayloadPrompt = async (): Promise<{ usePayload: boolean }> { type: 'confirm', name: 'usePayload', - message: 'Would you like to add Payload to your app?', + message: '', default: true, }, ]); diff --git a/packages/cli/command-prompts/unfinishedProjectsChoicePrompt.ts b/packages/cli/command-prompts/unfinishedProjectsChoicePrompt.ts index 1a74cda..9586a99 100644 --- a/packages/cli/command-prompts/unfinishedProjectsChoicePrompt.ts +++ b/packages/cli/command-prompts/unfinishedProjectsChoicePrompt.ts @@ -1,5 +1,6 @@ import inquirer from 'inquirer'; import { ProjectChoice, UnfinishedProject } from '../utils/findUnfinishedProjects'; +import { LABEL_WIDTH, SPACING } from 'stplr-utils'; export type UnfinishedProjectsChoiceAnswers = { resume: boolean; @@ -15,11 +16,13 @@ export type UnfinishedProjectsChoiceAnswers = { * **/ +const leftPadding = ' '.repeat(LABEL_WIDTH + SPACING); + export const unfinishedProjectsChoice = async ( unfinishedProjects: UnfinishedProject[], projectChoices: ProjectChoice[], -): Promise => - await inquirer.prompt([ +): Promise => { + return await inquirer.prompt([ { type: 'confirm', name: 'resume', @@ -36,3 +39,4 @@ export const unfinishedProjectsChoice = async ( when: (answers) => answers.resume && unfinishedProjects.length > 1, }, ]); +}; diff --git a/packages/cli/index.ts b/packages/cli/index.ts index 06cd577..382898d 100644 --- a/packages/cli/index.ts +++ b/packages/cli/index.ts @@ -47,6 +47,7 @@ const createAction = async (options: Flags) => { const unfinishedProjects: UnfinishedProject[] = findUnfinishedProjects(currentDir); + logger.withLabel('dir', 'Your project name and location'); // If no project name is provided, and there are unfinished projects, we prompt the user to resume one of them if (!options.name && unfinishedProjects.length > 0) { const projectChoices = getProjectChoices(unfinishedProjects); @@ -102,6 +103,7 @@ const createAction = async (options: Flags) => { } // Skip Payload if specified by the flag + logger.withLabel('cms', 'Want to use Payload?'); const payloadAnswer = options.skipPayload ? { usePayload: false } : await shouldUsePayloadPrompt(); const finalOptions = { name: projectName, shouldDeploy, ...payloadAnswer }; diff --git a/packages/core/installMachine/index.ts b/packages/core/installMachine/index.ts index 64872c9..7bfee0d 100644 --- a/packages/core/installMachine/index.ts +++ b/packages/core/installMachine/index.ts @@ -17,6 +17,7 @@ import { updateVercelProjectSettings, } from './installSteps/vercel'; import { shouldDeploy } from './installSteps/shouldDeploy'; +import { logger } from 'stplr-utils'; const isStepCompleted = (stepName: keyof StepsCompleted) => { return ({ context }: { context: InstallMachineContext; event: AnyEventObject }) => { @@ -349,6 +350,7 @@ const createInstallMachine = (initialContext: InstallMachineContext) => { createTurboActor: createStepMachine( fromPromise(async ({ input }) => { try { + logger.withLabel('turborepo', 'Creating Turbo project'); await createTurbo(input.stateData.options.name); process.chdir(input.projectDir); input.stateData.stepsCompleted.createTurbo = true; @@ -362,6 +364,7 @@ const createInstallMachine = (initialContext: InstallMachineContext) => { modifyGitignoreActor: createStepMachine( fromPromise(async ({ input }) => { try { + logger.withLabel('git', 'Modifying .gitignore'); await modifyGitignore('.initializeRcFile'); input.stateData.stepsCompleted.modifyGitignore = true; saveStateToRcFile(input.stateData, input.projectDir); @@ -374,6 +377,7 @@ const createInstallMachine = (initialContext: InstallMachineContext) => { installTailwindActor: createStepMachine( fromPromise(async ({ input }) => { try { + logger.withLabel('tailwind', 'Installing Tailwind CSS'); const currentDir = process.cwd(); await installTailwind(currentDir); input.stateData.stepsCompleted.installTailwind = true; @@ -399,6 +403,7 @@ const createInstallMachine = (initialContext: InstallMachineContext) => { installSupabaseActor: createStepMachine( fromPromise(async ({ input }) => { try { + logger.withLabel('supabase', 'Installing Supabase'); const currentDir = process.cwd(); await installSupabase(currentDir); input.stateData.stepsCompleted.installSupabase = true; @@ -412,6 +417,7 @@ const createInstallMachine = (initialContext: InstallMachineContext) => { installPayloadActor: createStepMachine( fromPromise(async ({ input }) => { try { + logger.withLabel('payload', 'Preparing Payload'); await preparePayload(); input.stateData.stepsCompleted.installPayload = true; saveStateToRcFile(input.stateData, input.projectDir); @@ -424,6 +430,7 @@ const createInstallMachine = (initialContext: InstallMachineContext) => { createDocFilesActor: createStepMachine( fromPromise(async ({ input }) => { try { + logger.withLabel('stapler', 'Creating documentation files'); await createDocFiles(); input.stateData.stepsCompleted.createDocFiles = true; saveStateToRcFile(input.stateData, input.projectDir); @@ -436,6 +443,7 @@ const createInstallMachine = (initialContext: InstallMachineContext) => { prettifyCodeActor: createStepMachine( fromPromise(async ({ input }) => { try { + logger.withLabel('prettier', 'Prettifying code'); await prettify(); input.stateData.stepsCompleted.prettifyCode = true; saveStateToRcFile(input.stateData, input.projectDir); @@ -459,6 +467,7 @@ const createInstallMachine = (initialContext: InstallMachineContext) => { initializeRepositoryActor: createStepMachine( fromPromise(async ({ input }) => { try { + logger.withLabel('github', 'Initializing GitHub repository'); await initializeRepository({ projectName: input.stateData.options.name, stateData: input.stateData, @@ -486,6 +495,7 @@ const createInstallMachine = (initialContext: InstallMachineContext) => { createSupabaseProjectActor: createStepMachine( fromPromise(async ({ input }) => { try { + logger.withLabel('supabase', 'Creating Supabase project'); await createSupabaseProject(input.stateData.githubCandidateName); input.stateData.stepsCompleted.createSupabaseProject = true; saveStateToRcFile(input.stateData, input.projectDir); @@ -498,6 +508,7 @@ const createInstallMachine = (initialContext: InstallMachineContext) => { chooseVercelTeamActor: createStepMachine( fromPromise(async ({ input }) => { try { + logger.withLabel('vercel', 'Managing your Vercel'); await chooseVercelTeam(); input.stateData.stepsCompleted.chooseVercelTeam = true; saveStateToRcFile(input.stateData, input.projectDir); @@ -534,6 +545,7 @@ const createInstallMachine = (initialContext: InstallMachineContext) => { connectSupabaseProjectActor: createStepMachine( fromPromise(async ({ input }) => { try { + logger.withLabel('supabase', 'Connecting Supabase project'); const currentDir = process.cwd(); await connectSupabaseProject(input.stateData.githubCandidateName, currentDir); input.stateData.stepsCompleted.connectSupabaseProject = true; @@ -547,6 +559,7 @@ const createInstallMachine = (initialContext: InstallMachineContext) => { deployVercelProjectActor: createStepMachine( fromPromise(async ({ input }) => { try { + logger.withLabel('vercel', 'Deploying Vercel project'); await deployVercelProject(input.stateData); input.stateData.stepsCompleted.deployVercelProject = true; saveStateToRcFile(input.stateData, input.projectDir); @@ -559,6 +572,7 @@ const createInstallMachine = (initialContext: InstallMachineContext) => { prepareDrinkActor: createStepMachine( fromPromise(async ({ input }) => { try { + logger.withLabel('stapler', 'Preparing your drink'); const { projectName, prettyDeploymentUrl, diff --git a/packages/core/installMachine/installSteps/stapler/prepareDrink.ts b/packages/core/installMachine/installSteps/stapler/prepareDrink.ts index d4af8b9..2e92783 100644 --- a/packages/core/installMachine/installSteps/stapler/prepareDrink.ts +++ b/packages/core/installMachine/installSteps/stapler/prepareDrink.ts @@ -1,5 +1,6 @@ import chalk from 'chalk'; import { delay } from '../../../utils/delay'; +import { LABEL_WIDTH, SPACING } from 'stplr-utils'; const getMessages = (name: string, prettyDeploymentUrl: string, shouldDeploy: boolean) => { const messages = [ @@ -21,10 +22,11 @@ const getMessages = (name: string, prettyDeploymentUrl: string, shouldDeploy: bo }; export const prepareDrink = async (name: string, prettyDeploymentUrl: string, shouldDeploy: boolean) => { + const leftPadding = ' '.repeat(SPACING + LABEL_WIDTH); const messages = getMessages(name, prettyDeploymentUrl, shouldDeploy); for (const message of messages) { - console.log(message); + console.log(`${leftPadding}${message}`); await delay(1000); } }; diff --git a/packages/utils/logger.ts b/packages/utils/logger.ts deleted file mode 100644 index bdeab01..0000000 --- a/packages/utils/logger.ts +++ /dev/null @@ -1,215 +0,0 @@ -import chalk from 'chalk'; -import ora, { Ora } from 'ora'; - -const LABEL_WIDTH = 7; -const LABEL_BG_COLOR = '#FAD400'; -const LABEL_TEXT_COLOR = '#000000'; -const DIMMED_LABEL_BG_COLOR = '#5C4D00'; -const CHECK_MARK_COLOR = '#FAD400'; -const ACTIVE_TEXT_COLOR = '#FFFFFF'; -const COMPLETED_TEXT_COLOR = '#666666'; -const SPACING = 2; - -type Name = - | 'dir' - | 'cms' - | 'git' - | 'db' - | 'vrcl' - | 'stapler' - | 'turborepo' - | 'supabase' - | 'tailwind' - | 'payload' - | 'github' - | 'prettier' - | 'deployment' - | 'vercel' - | 'docker' - | 'postgres' - | 'error'; - -interface LabelConfig { - text: string; - align?: 'left' | 'right'; -} - -const labels: Record = { - dir: { text: 'dir' }, - cms: { text: 'cms' }, - git: { text: 'git' }, - db: { text: 'db' }, - vrcl: { text: 'vrcl' }, - stapler: { text: 'stplr' }, - turborepo: { text: 'turbo' }, - supabase: { text: 'supa' }, - tailwind: { text: 'tw' }, - payload: { text: 'cms' }, - github: { text: 'git' }, - prettier: { text: 'fmt' }, - deployment: { text: 'dep' }, - vercel: { text: 'vrcl' }, - docker: { text: 'dock' }, - postgres: { text: 'pg' }, - error: { text: 'error' }, -}; - -const completedSteps = new Set(); -const stepOutputs = new Map(); - -const formatLabel = (name: Name): string => { - const config = labels[name]; - const label = config.text.padStart(LABEL_WIDTH); - const bgColor = LABEL_BG_COLOR; - - return chalk.bgHex(bgColor).hex(LABEL_TEXT_COLOR)(` ${label} `); -}; - -const formatCheckMark = (): string => { - return chalk.hex(CHECK_MARK_COLOR)('✓'); -}; - -const formatMessage = (message: string, isCompleted: boolean = false): string => { - return isCompleted ? chalk.hex(COMPLETED_TEXT_COLOR)(message) : chalk.hex(ACTIVE_TEXT_COLOR)(message); -}; - -const reprintPreviousOutput = () => { - stepOutputs.forEach((outputs, step) => { - outputs.forEach((output) => { - const label = formatLabel(step); - const checkmark = formatCheckMark() + ' '; - const padding = ' '.repeat(SPACING); - const formattedOutput = formatMessage(output); - - const labelConfig = labels[step]; - labelConfig.align = labelConfig.align || 'right'; - console.log(labelConfig.align); - if (labelConfig.align === 'right') { - const leftPadding = ' '.repeat(LABEL_WIDTH + SPACING); - console.log(`${leftPadding}${label}${padding}${checkmark}${formattedOutput}`); - } else { - console.log(`${label}${padding}${checkmark}${formattedOutput}`); - } - }); - }); -}; - -const log = (name: Name, messages: string[] | string, showCheck: boolean = true): void => { - // Update current step - console.clear(); - reprintPreviousOutput(); - - const messageText = typeof messages === 'string' ? messages : messages.join(' '); - - // Store output for reprinting - if (!stepOutputs.has(name)) { - stepOutputs.set(name, []); - } - stepOutputs.get(name)?.push(messageText); - - // Print current message - const label = formatLabel(name); - const checkmark = showCheck ? formatCheckMark() + ' ' : ' '; - const padding = ' '.repeat(SPACING); - - const labelConfig = labels[name]; - if (labelConfig.align === 'right') { - const leftPadding = ' '.repeat(LABEL_WIDTH + SPACING); - console.log(`${leftPadding}${label}${padding}${checkmark}${formatMessage(messageText)}`); - } else { - console.log(`${label}${padding}${checkmark}${formatMessage(messageText)}`); - } -}; - -const createSpinner = (initialText?: string): Ora => { - const padding = ' '.repeat(SPACING); - - return ora({ - prefixText: `${padding}`, - text: initialText, - spinner: 'dots', - color: 'yellow', - }); -}; - -const withSpinner = async ( - initialText: string, - action: (spinner: Ora) => Promise, - label?: string, -): Promise => { - const spinner = createSpinner(initialText); - try { - spinner.start(); - const result = await action(spinner); - - // Store and display completion message - if (!stepOutputs.has(name)) { - stepOutputs.set(name, []); - } - stepOutputs.get(name)?.push(initialText); - - spinner.stop(); - log(name, initialText, true); - - return result; - } catch (error) { - spinner.fail(); - throw error; - } -}; - -const logUserInput = (step: Name, input: string) => { - if (!stepOutputs.has(step)) { - stepOutputs.set(step, []); - } - stepOutputs.get(step)?.push(input); -}; - -// Reset all state (useful for testing or restarting the process) -const reset = () => { - completedSteps.clear(); - stepOutputs.clear(); -}; - -const displayHeader = () => { - const block = chalk.bgHex(LABEL_BG_COLOR).hex(LABEL_BG_COLOR)('█'); - const stplrText = chalk.bgHex(LABEL_BG_COLOR).hex(LABEL_TEXT_COLOR)('stplr'); - const stplrLeft = chalk.bgHex(LABEL_BG_COLOR).hex(LABEL_TEXT_COLOR)(String.fromCharCode(9484)); - const stplrMiddle = chalk.bgHex(LABEL_BG_COLOR).hex(LABEL_TEXT_COLOR)(String.fromCharCode(9472)); - const stplrRight = chalk.bgHex(LABEL_BG_COLOR).hex(LABEL_TEXT_COLOR)(String.fromCharCode(9488)); - const triangle = chalk.hex(LABEL_BG_COLOR)(String.fromCharCode(9701)); - - const logoLines = [ - `${block}${block}${block}${stplrLeft}${stplrMiddle}${stplrRight}${block}`, - `${block}${stplrText}${block}`, - ` ${triangle}`, - ` `, - ]; - - const title = 'Stapler setup initialized'; - const subText1 = ' '; - const subText2 = 'Everything is fine.'; - const subText3 = "I've done this like a million times."; - - const padding = 4; - - const output = [ - '', // Initial newline - ` ${logoLines[0]}${' '.repeat(padding)}${chalk.hex(ACTIVE_TEXT_COLOR)(title)}`, - ` ${logoLines[1]}${' '.repeat(padding)}${chalk.gray(subText1)}`, - ` ${logoLines[2]}${' '.repeat(padding)}${chalk.gray(subText2)}`, - ` ${logoLines[3]}${' '.repeat(padding)}${chalk.gray(subText3)}`, - '', - ].join('\n'); - - console.log(output); -}; - -export const logger = { - log, - createSpinner, - withSpinner, - displayHeader, - reset, - logUserInput, -}; diff --git a/packages/utils/logger/displayHeader.ts b/packages/utils/logger/displayHeader.ts new file mode 100644 index 0000000..24c09ab --- /dev/null +++ b/packages/utils/logger/displayHeader.ts @@ -0,0 +1,37 @@ +import chalk from 'chalk'; + +type DisplayHeaderType = (labelBgColor: string, labelTextColor: string, activeTextColor: string) => void; + +export const displayHeader: DisplayHeaderType = (labelBgColor, labelTextColor, activeTextColor) => { + const block = chalk.bgHex(labelBgColor).hex(labelBgColor)('█'); + const stplrText = chalk.bgHex(labelBgColor).hex(labelTextColor)('stplr'); + const stplrLeft = chalk.bgHex(labelBgColor).hex(labelTextColor)(String.fromCharCode(9484)); + const stplrMiddle = chalk.bgHex(labelBgColor).hex(labelTextColor)(String.fromCharCode(9472)); + const stplrRight = chalk.bgHex(labelBgColor).hex(labelTextColor)(String.fromCharCode(9488)); + const triangle = chalk.hex(labelBgColor)(String.fromCharCode(9701)); + + const logoLines = [ + `${block}${block}${block}${stplrLeft}${stplrMiddle}${stplrRight}${block}`, + `${block}${stplrText}${block}`, + ` ${triangle}`, + ` `, + ]; + + const title = 'Stapler setup initialized'; + const subText1 = ' '; + const subText2 = 'Everything is fine.'; + const subText3 = "I've done this like a million times."; + + const padding = 2; + + const output = [ + '', // Initial newline + ` ${logoLines[0]}${' '.repeat(padding)}${chalk.hex(activeTextColor)(title)}`, + ` ${logoLines[1]}${' '.repeat(padding)}${chalk.gray(subText1)}`, + ` ${logoLines[2]}${' '.repeat(padding)}${chalk.gray(subText2)}`, + ` ${logoLines[3]}${' '.repeat(padding)}${chalk.gray(subText3)}`, + '', + ].join('\n'); + + console.log(output); +}; diff --git a/packages/utils/logger/index.ts b/packages/utils/logger/index.ts new file mode 100644 index 0000000..1ff09ef --- /dev/null +++ b/packages/utils/logger/index.ts @@ -0,0 +1 @@ +export * from './logger'; diff --git a/packages/utils/logger/logger.ts b/packages/utils/logger/logger.ts new file mode 100644 index 0000000..e63c558 --- /dev/null +++ b/packages/utils/logger/logger.ts @@ -0,0 +1,129 @@ +import chalk from 'chalk'; +import ora, { Ora } from 'ora'; +import { displayHeader } from './displayHeader'; + +export const SPACING = 2; +export const LABEL_WIDTH = 9; +export const LABEL_BG_COLOR = '#FAD400'; +export const LABEL_TEXT_COLOR = '#000000'; +export const DIMMED_COLOR = '#5C4D00'; +export const CHECK_MARK_COLOR = '#FAD400'; +export const ACTIVE_TEXT_COLOR = '#FFFFFF'; +export const COMPLETED_TEXT_COLOR = '#666666'; + +type Name = + | 'dir' + | 'cms' + | 'git' + | 'db' + | 'stapler' + | 'turborepo' + | 'supabase' + | 'tailwind' + | 'payload' + | 'github' + | 'prettier' + | 'deployment' + | 'vercel' + | 'docker' + | 'postgres' + | 'error'; + +interface LabelConfig { + text: string; +} + +const labels: Record = { + dir: { text: 'dir' }, + cms: { text: 'cms' }, + git: { text: 'git' }, + db: { text: 'db' }, + stapler: { text: 'stplr' }, + turborepo: { text: 'turbo' }, + supabase: { text: 'supa' }, + tailwind: { text: 'tw' }, + payload: { text: 'cms' }, + github: { text: 'git' }, + prettier: { text: 'fmt' }, + deployment: { text: 'dep' }, + vercel: { text: 'vrcl' }, + docker: { text: 'dock' }, + postgres: { text: 'pg' }, + error: { text: 'error' }, +}; + +const padding = ' '.repeat(SPACING); +const leftPadding = ' '.repeat(LABEL_WIDTH); + +const formatLabel = (name: Name): string => { + const labelText = ` ${labels[name].text} `; + const label = chalk.bgHex(LABEL_BG_COLOR).hex(LABEL_TEXT_COLOR)(labelText); + const padding = ' '.repeat(LABEL_WIDTH - labelText.length); + + return `${padding}${label}`; +}; + +const formatCheckMark = (): string => { + return chalk.hex(CHECK_MARK_COLOR)('✓'); +}; + +const formatMessage = (message: string, isCompleted: boolean = false): string => { + return isCompleted ? chalk.hex(COMPLETED_TEXT_COLOR)(message) : chalk.hex(ACTIVE_TEXT_COLOR)(message); +}; + +const logMessage = (name: Name, message: string, showCheck: boolean = true): void => { + // Print current message + const checkmark = showCheck ? formatCheckMark() + ' ' : ' '; + + console.log(`${leftPadding}${padding}${checkmark}${formatMessage(message)}`); +}; + +const withLabel = (label: Name, message: string): void => { + const formattedLabel = formatLabel(label); + console.log(` `); + console.log(`${formattedLabel}${padding}${formatMessage(message)}`); + console.log(` `); +}; + +const createSpinner = (initialText?: string): Ora => { + const padding = ' '.repeat(SPACING); + + const spinner = { + frames: [ + `${chalk.hex(LABEL_BG_COLOR)('[')} ${chalk.hex(DIMMED_COLOR)(']')}`, + ` ${chalk.hex(LABEL_BG_COLOR)('[')}${chalk.hex(DIMMED_COLOR)(']')} `, + ` ${chalk.hex(DIMMED_COLOR)(']')}${chalk.hex(LABEL_BG_COLOR)('[')} `, + `${chalk.hex(DIMMED_COLOR)(']')} ${chalk.hex(LABEL_BG_COLOR)('[')}`, + ` ${chalk.hex(DIMMED_COLOR)(']')}${chalk.hex(LABEL_BG_COLOR)('[')} `, + ` ${chalk.hex(LABEL_BG_COLOR)('[')}${chalk.hex(DIMMED_COLOR)(']')} `, + ], + interval: 140, + }; + return ora({ + suffixText: `${padding}`, + text: initialText, + spinner: spinner, + color: 'yellow', + indent: 6, + }); +}; + +const withSpinner = async (name: Name, initialText: string, action: (spinner: Ora) => Promise): Promise => { + const spinner = createSpinner(initialText); + try { + spinner.start(); + const result = await action(spinner); + return result; + } catch (error) { + spinner.fail(); + throw error; + } +}; + +export const logger = { + createSpinner, + logMessage, + withSpinner, + withLabel, + displayHeader: () => displayHeader(LABEL_BG_COLOR, LABEL_TEXT_COLOR, ACTIVE_TEXT_COLOR), +}; From ed14c906a29f4799b06d82fa44e56256e3ca2614 Mon Sep 17 00:00:00 2001 From: Przemyslaw Mejna Date: Thu, 19 Dec 2024 15:36:49 +0100 Subject: [PATCH 5/8] feat(inquirer): replace inquirer with enquirer --- .../command-prompts/getProjectNamePrompt.ts | 23 ++--- .../overwriteDirectoryPrompt.ts | 24 +++--- .../command-prompts/shouldUsePayloadPrompt.ts | 24 +++--- .../unfinishedProjectsChoicePrompt.ts | 84 ++++++++++++++----- packages/cli/index.ts | 6 +- packages/cli/package.json | 5 +- .../installSteps/github/checkGitHubCLI.ts | 14 ++-- .../installSteps/github/ghInstaller.ts | 10 +-- .../installSteps/github/repositoryManager.ts | 49 +++++++---- .../installSteps/shouldDeploy/shouldDeploy.ts | 11 ++- .../installSteps/supabase/checkSupabaseCLI.ts | 8 +- .../installSteps/supabase/connectProject.ts | 6 +- .../installSteps/supabase/createProject.ts | 2 +- .../installSteps/vercel/authenticateVercel.ts | 4 +- .../installSteps/vercel/checkVercelCLI.ts | 6 +- .../installSteps/vercel/deploy.ts | 4 +- packages/core/package.json | 3 +- packages/utils/logger/logger.ts | 15 ++-- pnpm-lock.yaml | 6 ++ 19 files changed, 197 insertions(+), 107 deletions(-) diff --git a/packages/cli/command-prompts/getProjectNamePrompt.ts b/packages/cli/command-prompts/getProjectNamePrompt.ts index fbcdc31..ee02e0f 100644 --- a/packages/cli/command-prompts/getProjectNamePrompt.ts +++ b/packages/cli/command-prompts/getProjectNamePrompt.ts @@ -1,14 +1,15 @@ -import inquirer from 'inquirer'; +import Enquirer from 'enquirer'; +import { LEFT_PADDING } from 'stplr-utils'; export const getProjectNamePrompt = async (): Promise => { - return ( - await inquirer.prompt([ - { - type: 'input', - name: 'name', - message: 'What is your project named?', - default: 'my-stapled-app', - }, - ]) - ).name; + const enquirer = new Enquirer(); + const response = (await enquirer.prompt({ + type: 'input', + name: 'name', + message: 'What is your project named?', + initial: 'my-stapled-app', + prefix: LEFT_PADDING, + })) as { name: string }; + + return response.name; }; diff --git a/packages/cli/command-prompts/overwriteDirectoryPrompt.ts b/packages/cli/command-prompts/overwriteDirectoryPrompt.ts index 4530cb6..dd8288c 100644 --- a/packages/cli/command-prompts/overwriteDirectoryPrompt.ts +++ b/packages/cli/command-prompts/overwriteDirectoryPrompt.ts @@ -1,4 +1,5 @@ -import inquirer from 'inquirer'; +import Enquirer from 'enquirer'; +import { LEFT_PADDING } from 'stplr-utils'; /** * Prompts the user to confirm whether they want to overwrite an existing project directory. @@ -8,12 +9,15 @@ import inquirer from 'inquirer'; * **/ -export const overwriteDirectoryPrompt = async (projectName: string): Promise<{ overwrite: boolean }> => - await inquirer.prompt([ - { - type: 'confirm', - name: 'overwrite', - message: `The directory "${projectName}" already exists. Do you want to overwrite it?`, - default: false, - }, - ]); +export const overwriteDirectoryPrompt = async (projectName: string): Promise<{ overwrite: boolean }> => { + const enquirer = new Enquirer(); + const response = (await enquirer.prompt({ + type: 'confirm', + name: 'overwrite', + message: `The directory "${projectName}" already exists. Do you want to overwrite it?`, + initial: false, + prefix: LEFT_PADDING, + })) as { overwrite: boolean }; + + return response; +}; diff --git a/packages/cli/command-prompts/shouldUsePayloadPrompt.ts b/packages/cli/command-prompts/shouldUsePayloadPrompt.ts index f2e0315..d3bf3f5 100644 --- a/packages/cli/command-prompts/shouldUsePayloadPrompt.ts +++ b/packages/cli/command-prompts/shouldUsePayloadPrompt.ts @@ -1,4 +1,5 @@ -import inquirer from 'inquirer'; +import Enquirer from 'enquirer'; +import { LEFT_PADDING } from 'stplr-utils'; /** * Prompts the user to confirm whether they want to overwrite an existing project directory. @@ -8,12 +9,15 @@ import inquirer from 'inquirer'; * **/ -export const shouldUsePayloadPrompt = async (): Promise<{ usePayload: boolean }> => - await inquirer.prompt([ - { - type: 'confirm', - name: 'usePayload', - message: '', - default: true, - }, - ]); +export const shouldUsePayloadPrompt = async (): Promise<{ usePayload: boolean }> => { + const enquirer = new Enquirer(); + const response = (await enquirer.prompt({ + type: 'confirm', + name: 'usePayload', + message: 'Would you like to use Payload?', + initial: true, // Default value + prefix: LEFT_PADDING, // Removes the default '?' prefix + })) as { usePayload: boolean }; + + return response; +}; diff --git a/packages/cli/command-prompts/unfinishedProjectsChoicePrompt.ts b/packages/cli/command-prompts/unfinishedProjectsChoicePrompt.ts index 9586a99..35ade1a 100644 --- a/packages/cli/command-prompts/unfinishedProjectsChoicePrompt.ts +++ b/packages/cli/command-prompts/unfinishedProjectsChoicePrompt.ts @@ -1,8 +1,15 @@ -import inquirer from 'inquirer'; +import Enquirer from 'enquirer'; + import { ProjectChoice, UnfinishedProject } from '../utils/findUnfinishedProjects'; -import { LABEL_WIDTH, SPACING } from 'stplr-utils'; +import { LABEL_BG_COLOR, LEFT_PADDING } from 'stplr-utils'; +import chalk from 'chalk'; export type UnfinishedProjectsChoiceAnswers = { + resume: boolean; + unfinishedSelectedProject: string; +}; + +export type UnfinishedProjectsChoiceResponse = { resume: boolean; unfinishedSelectedProject: UnfinishedProject; }; @@ -16,27 +23,62 @@ export type UnfinishedProjectsChoiceAnswers = { * **/ -const leftPadding = ' '.repeat(LABEL_WIDTH + SPACING); - export const unfinishedProjectsChoice = async ( unfinishedProjects: UnfinishedProject[], projectChoices: ProjectChoice[], -): Promise => { - return await inquirer.prompt([ - { - type: 'confirm', - name: 'resume', - message: `We found the following unfinished project(s):\n${unfinishedProjects - .map((p) => `- ${p.projectName}`) - .join('\n')}\nWould you like to resume one of them?`, - default: true, - }, - { - type: 'list', - name: 'unfinishedSelectedProject', - message: 'Select a project to resume:', - choices: projectChoices, - when: (answers) => answers.resume && unfinishedProjects.length > 1, +): Promise => { + const enquirer = new Enquirer(); + const formattedProjectChoices = projectChoices.map((choice) => { + return { + name: choice.name, + value: choice.name, + message: `${chalk.hex(LABEL_BG_COLOR)(LEFT_PADDING + choice.name)}`, + }; + }); + + // we might need to create custom prompt format like in: https://www.npmjs.com/package/enquirer#-custom-prompts + const shouldResume = (await enquirer.prompt({ + type: 'confirm', + name: 'resume', + message: `We found the following unfinished project(s):\n${unfinishedProjects + .map((p) => `${LEFT_PADDING} - ${p.projectName}`) + .join('\n')}\n${LEFT_PADDING} Would you like to resume one of them?`, + initial: true, + prefix: LEFT_PADDING, + })) as { resume: boolean }; + + if (!shouldResume.resume) { + return { + resume: false, + unfinishedSelectedProject: unfinishedProjects[0], + }; + } + + const selectProjectAnswer = (await enquirer.prompt({ + type: 'select', + name: 'unfinishedSelectedProject', + message: 'Select a project to resume:', + choices: formattedProjectChoices, + prefix: LEFT_PADDING, + // use it only when we want to resume a project + skip: (state: unknown) => { + if (typeof state === 'object' && state !== null && 'resume' in state) { + console.log('state', state); + return !(state as UnfinishedProjectsChoiceAnswers).resume; + } + console.log('state', state); + return false; // default fallback if not sure }, - ]); + })) as UnfinishedProjectsChoiceAnswers; + + const selectedProject = unfinishedProjects.find( + (project) => project.projectName === selectProjectAnswer.unfinishedSelectedProject, + ); + + const response = { + resume: selectProjectAnswer.resume, + unfinishedSelectedProject: selectedProject, + } as UnfinishedProjectsChoiceResponse; + + return response; }; diff --git a/packages/cli/index.ts b/packages/cli/index.ts index 382898d..90c1557 100644 --- a/packages/cli/index.ts +++ b/packages/cli/index.ts @@ -98,8 +98,10 @@ const createAction = async (options: Flags) => { } // Clear the directory if overwrite is confirmed - fs.rmSync(projectDir, { recursive: true, force: true }); - console.log(chalk.yellow(`The directory "${projectName}" has been cleared.`)); + logger.withSpinner('dir', 'Clearing existing directory...', async () => { + fs.rmSync(projectDir, { recursive: true, force: true }); + }); + logger.log(chalk.yellow(`The directory "${projectName}" has been cleared.`)); } // Skip Payload if specified by the flag diff --git a/packages/cli/package.json b/packages/cli/package.json index b088715..89440d0 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -35,14 +35,15 @@ "dependencies": { "chalk": "^5.3.0", "commander": "^12.1.0", + "enquirer": "^2.4.1", "gradient-string": "^3.0.0", "inquirer": "^10.2.2" }, "devDependencies": { - "stplr-core": "workspace:*", - "stplr-utils": "workspace:*", "@types/inquirer": "^9.0.7", "@types/node": "^22.5.4", + "stplr-core": "workspace:*", + "stplr-utils": "workspace:*", "tsup": "^8.2.4", "typescript": "^5.6.2" } diff --git a/packages/core/installMachine/installSteps/github/checkGitHubCLI.ts b/packages/core/installMachine/installSteps/github/checkGitHubCLI.ts index 8000210..05b8ef6 100644 --- a/packages/core/installMachine/installSteps/github/checkGitHubCLI.ts +++ b/packages/core/installMachine/installSteps/github/checkGitHubCLI.ts @@ -1,19 +1,21 @@ -import inquirer from 'inquirer'; -import { logger } from 'stplr-utils'; +import Enquirer from 'enquirer'; +import { LEFT_PADDING, logger } from 'stplr-utils'; import { isGitHubCLIInstalled, installGitHubCLI } from './ghInstaller'; export const checkGitHubCLI = async () => { await logger.withSpinner('github', 'Checking if GitHub CLI is installed...', async (spinner) => { if (!isGitHubCLIInstalled()) { - logger.log('github', 'GitHub CLI is not installed.'); - const { shouldInstallGitHubCLI } = await inquirer.prompt([ + logger.log('GitHub CLI is not installed.'); + const enquirer = new Enquirer(); + const { shouldInstallGitHubCLI } = (await enquirer.prompt([ { type: 'confirm', name: 'shouldInstallGitHubCLI', message: 'Would you like us to install GitHub CLI?', - default: true, + initial: true, + prefix: LEFT_PADDING, }, - ]); + ])) as { shouldInstallGitHubCLI: boolean }; if (shouldInstallGitHubCLI) { const installed = await installGitHubCLI(); diff --git a/packages/core/installMachine/installSteps/github/ghInstaller.ts b/packages/core/installMachine/installSteps/github/ghInstaller.ts index d1a9a40..45b72c6 100644 --- a/packages/core/installMachine/installSteps/github/ghInstaller.ts +++ b/packages/core/installMachine/installSteps/github/ghInstaller.ts @@ -23,7 +23,7 @@ export const installGitHubCLI = async (): Promise => { case 'linux': // Linux const linuxDistro = await getLinuxDistro(); if (linuxDistro === 'unknown') { - logger.log('github', 'Automatic installation is not supported for your Linux distribution.'); + logger.log('Automatic installation is not supported for your Linux distribution.'); } if (linuxDistro === 'ubuntu' || linuxDistro === 'debian') { installCommand = @@ -32,7 +32,7 @@ export const installGitHubCLI = async (): Promise => { installCommand = 'sudo dnf config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo && sudo dnf install gh'; } else { - logger.log('github', [ + logger.log([ 'Automatic installation is not supported for your Linux distribution.', '\n Please visit https://github.com/cli/cli#installation for installation instructions.', ]); @@ -43,20 +43,20 @@ export const installGitHubCLI = async (): Promise => { installCommand = 'winget install --id GitHub.cli'; break; default: - logger.log('github', [ + logger.log([ 'Automatic installation is not supported for your operating system.', '\nPlease visit https://github.com/cli/cli#installation for installation instructions.', ]); return false; } - logger.log('github', 'Installing GitHub CLI...'); + logger.log('Installing GitHub CLI...'); try { await execAsync(installCommand); return true; } catch (error) { console.error('Failed to install GitHub CLI.'); - logger.log('github', 'Please install it manually from: https://github.com/cli/cli#installation'); + logger.log('Please install it manually from: https://github.com/cli/cli#installation'); return false; } }; diff --git a/packages/core/installMachine/installSteps/github/repositoryManager.ts b/packages/core/installMachine/installSteps/github/repositoryManager.ts index 5b8d330..4ce7ca1 100644 --- a/packages/core/installMachine/installSteps/github/repositoryManager.ts +++ b/packages/core/installMachine/installSteps/github/repositoryManager.ts @@ -1,23 +1,28 @@ import { execSync, spawnSync } from 'child_process'; -import inquirer from 'inquirer'; +import Enquirer from 'enquirer'; import chalk from 'chalk'; -import { logger } from 'stplr-utils'; +import { LEFT_PADDING, logger } from 'stplr-utils'; import { execAsync } from '../../../utils/execAsync'; import { InstallMachineContext } from '../../../types'; import { fetchOrganizations } from './fetchOrganizations'; +export interface ProjectChoice { + name: string; + value: string; +} + const generateUniqueRepoName = async (baseName: string): Promise => { const cleanBaseName = baseName.replace(/-\d+$/, ''); // Clean base name try { await execAsync(`gh repo view ${cleanBaseName}`); - logger.log('github', `Repository "${cleanBaseName}" already exists.`); + logger.log(`Repository "${cleanBaseName}" already exists.`); let counter = 2; while (true) { const candidateName = `${cleanBaseName}-v${counter}`; try { await execAsync(`gh repo view ${candidateName}`); - logger.log('github', `Repository "${candidateName}" already exists.`); + logger.log(`Repository "${candidateName}" already exists.`); counter++; } catch { return candidateName; @@ -100,14 +105,16 @@ export const createGitHubRepository = async ( ]; // Prompt the user to select an account or organization - const { selectedAccount } = await inquirer.prompt([ + const enquirer = new Enquirer(); + const { selectedAccount } = (await enquirer.prompt([ { - type: 'list', + type: 'select', name: 'selectedAccount', message: 'Select the account or organization to create the repository under:', choices: accountChoices, + prefix: LEFT_PADDING, }, - ]); + ])) as { selectedAccount: string }; stateData.selectedAccount = selectedAccount; // Update state with selected account await logger.withSpinner('github', 'Checking if repository already exists...', async (spinner) => { @@ -118,14 +125,16 @@ export const createGitHubRepository = async ( if (repoExists) { spinner.stop(); const newRepoName = await generateUniqueRepoName(projectName); - const { confirmedName } = await inquirer.prompt([ + const enquirer = new Enquirer(); + const { confirmedName } = (await enquirer.prompt([ { type: 'input', name: 'confirmedName', message: 'The repository already exists. Please confirm or modify the repository name:', - default: newRepoName, + initial: newRepoName, + prefix: LEFT_PADDING, }, - ]); + ])) as { confirmedName: string }; repoName = confirmedName; stateData.githubCandidateName = confirmedName; // Update state with confirmed name } @@ -139,15 +148,25 @@ export const createGitHubRepository = async ( await logger.withSpinner('github', `Creating repository: ${selectedAccount}/${repoName}...`, async (spinner) => { try { spinner.stop(); - const { repositoryVisibility } = await inquirer.prompt([ + const enquirer = new Enquirer(); + const questions = [ { - type: 'list', + type: 'select' as const, name: 'repositoryVisibility', message: 'Choose the repository visibility:', - choices: ['public', 'private'], - default: 'public', + choices: [ + { name: 'public', value: 'public' }, + { name: 'private', value: 'private' }, + ], + initial: 'public', }, - ]); + ]; + + const response = (await enquirer.prompt(questions)) as { repositoryVisibility: string }; + console.log(response); + + const { repositoryVisibility } = response; + const visibilityFlag = repositoryVisibility === 'public' ? '--public' : '--private'; const command = `gh repo create ${selectedAccount}/${repoName} ${visibilityFlag}`; await execAsync(command); diff --git a/packages/core/installMachine/installSteps/shouldDeploy/shouldDeploy.ts b/packages/core/installMachine/installSteps/shouldDeploy/shouldDeploy.ts index 2f6065d..fa329db 100644 --- a/packages/core/installMachine/installSteps/shouldDeploy/shouldDeploy.ts +++ b/packages/core/installMachine/installSteps/shouldDeploy/shouldDeploy.ts @@ -1,5 +1,5 @@ -import inquirer from 'inquirer'; -import { logger } from 'stplr-utils'; +import Enquirer from 'enquirer'; +import { LEFT_PADDING, logger } from 'stplr-utils'; export const shouldDeploy = async (shouldContinue: boolean): Promise => { return await logger.withSpinner('deployment', 'Deciding next steps...', async (spinner) => { @@ -10,15 +10,18 @@ export const shouldDeploy = async (shouldContinue: boolean): Promise => try { spinner.stop(); - const answers = (await inquirer.prompt([ + const enquirer = new Enquirer(); + const answers = (await enquirer.prompt([ { type: 'confirm', name: 'continue', message: 'Local installation completed. Would you like to continue with remote setup (GitHub, Supabase, Vercel)?', - default: true, + initial: true, + prefix: LEFT_PADDING, }, ])) as { continue: boolean }; + spinner.start(); const spinnerMessage = answers.continue ? 'Continuing with remote setup...' : 'Local deployment completed'; spinner.succeed(spinnerMessage); diff --git a/packages/core/installMachine/installSteps/supabase/checkSupabaseCLI.ts b/packages/core/installMachine/installSteps/supabase/checkSupabaseCLI.ts index cb55f7a..55f090b 100644 --- a/packages/core/installMachine/installSteps/supabase/checkSupabaseCLI.ts +++ b/packages/core/installMachine/installSteps/supabase/checkSupabaseCLI.ts @@ -27,20 +27,20 @@ const installSupabaseCLI = async (): Promise => { 'scoop bucket add supabase https://github.com/supabase/scoop-bucket.git && scoop install supabase'; break; default: - logger.log('supabase', [ + logger.log([ 'Automatic installation is not supported for your operating system.', '\nPlease visit https://supabase.com/docs/guides/cli/getting-started for installation instructions.', ]); return false; } - logger.log('supabase', 'Installing Supabase CLI...'); + logger.log('Installing Supabase CLI...'); try { await execAsync(installCommand); return true; } catch (error) { console.error('Failed to install Supabase CLI.'); - logger.log('supabase', 'Please install it manually from: https://supabase.com/docs/guides/cli/getting-started'); + logger.log('Please install it manually from: https://supabase.com/docs/guides/cli/getting-started'); return false; } }; @@ -48,7 +48,7 @@ const installSupabaseCLI = async (): Promise => { export const checkSupabaseCLI = async () => { await logger.withSpinner('supabase', 'Checking if Supabase CLI is installed...', async (spinner) => { if (!isSupabaseCLIInstalled()) { - logger.log('supabase', 'Supabase CLI is not installed.'); + logger.log('Supabase CLI is not installed.'); const installed = await installSupabaseCLI(); if (!installed) { diff --git a/packages/core/installMachine/installSteps/supabase/connectProject.ts b/packages/core/installMachine/installSteps/supabase/connectProject.ts index a05592f..04b77b7 100644 --- a/packages/core/installMachine/installSteps/supabase/connectProject.ts +++ b/packages/core/installMachine/installSteps/supabase/connectProject.ts @@ -48,7 +48,7 @@ export const connectSupabaseProject = async (projectName: string, currentDir: st ); // Link project - logger.log('supabase', 'Linking project...'); + logger.log('Linking project...'); execSync(`npx supabase link --project-ref ${newProject.refId}`, { stdio: 'inherit', }); @@ -74,7 +74,7 @@ export const connectSupabaseProject = async (projectName: string, currentDir: st ); // Countdown and open dashboard - const spinner = logger.createSpinner('supabase', 'Preparing to open dashboard'); + const spinner = logger.createSpinner('Preparing to open dashboard'); spinner.start(); for (let i = 3; i > 0; i--) { @@ -140,7 +140,7 @@ export const connectSupabaseProject = async (projectName: string, currentDir: st return false; }); } catch (error) { - logger.log('error', error instanceof Error ? error.message : 'An unknown error occurred'); + logger.log(error instanceof Error ? error.message : 'An unknown error occurred', false); throw error; } }; diff --git a/packages/core/installMachine/installSteps/supabase/createProject.ts b/packages/core/installMachine/installSteps/supabase/createProject.ts index ce9c1bd..4d476ae 100644 --- a/packages/core/installMachine/installSteps/supabase/createProject.ts +++ b/packages/core/installMachine/installSteps/supabase/createProject.ts @@ -2,7 +2,7 @@ import { execSync } from 'child_process'; import { logger } from 'stplr-utils'; export const createSupabaseProject = async (name: string) => { - logger.log('supabase', 'Creating Supabase project...'); + logger.log('Creating Supabase project...'); execSync(`npx supabase projects create ${name}`, { stdio: 'inherit', diff --git a/packages/core/installMachine/installSteps/vercel/authenticateVercel.ts b/packages/core/installMachine/installSteps/vercel/authenticateVercel.ts index 9ea2c43..256e9f3 100644 --- a/packages/core/installMachine/installSteps/vercel/authenticateVercel.ts +++ b/packages/core/installMachine/installSteps/vercel/authenticateVercel.ts @@ -13,7 +13,7 @@ export const authenticateVercel = async () => { spinner.succeed('Logged in successfully.'); } catch { spinner.fail('Failed to log in.'); - logger.log('vercel', [ + logger.log([ 'Oops! Something went wrong while logging in to Vercel...', '\nYou might already be logged in with this email in another project.', '\nIn this case, select "Continue with Email" and enter the email you\'re already logged in with.\n', @@ -24,7 +24,7 @@ export const authenticateVercel = async () => { spinner.succeed('Logged in'); } catch { spinner.fail('Failed to log in.'); - logger.log('vercel', [ + logger.log([ 'Please check the error above and try again.', '\nAfter successfully logging in with "vercel login", please run stplr again.\n', ]); diff --git a/packages/core/installMachine/installSteps/vercel/checkVercelCLI.ts b/packages/core/installMachine/installSteps/vercel/checkVercelCLI.ts index 3b079ad..5b4068b 100644 --- a/packages/core/installMachine/installSteps/vercel/checkVercelCLI.ts +++ b/packages/core/installMachine/installSteps/vercel/checkVercelCLI.ts @@ -11,13 +11,13 @@ const isVercelCLIInstalled = async (): Promise => { }; const installVercelCLI = async (): Promise => { - logger.log('vercel', 'Installing Vercel CLI...'); + logger.log('Installing Vercel CLI...'); try { await execAsync('npm i -g vercel@latest'); return true; } catch (error) { console.error('Failed to install Vercel CLI.'); - logger.log('vercel', 'Please install it manually from: https://vercel.com/docs/cli'); + logger.log('Please install it manually from: https://vercel.com/docs/cli'); return false; } }; @@ -25,7 +25,7 @@ const installVercelCLI = async (): Promise => { export const checkVercelCLI = async () => { await logger.withSpinner('vercel', 'Checking if Vercel CLI is installed...', async (spinner) => { if (!isVercelCLIInstalled()) { - logger.log('vercel', 'Vercel CLI is not installed.'); + logger.log('Vercel CLI is not installed.'); const installed = await installVercelCLI(); if (!installed) { diff --git a/packages/core/installMachine/installSteps/vercel/deploy.ts b/packages/core/installMachine/installSteps/vercel/deploy.ts index 9aa08ef..bda779e 100644 --- a/packages/core/installMachine/installSteps/vercel/deploy.ts +++ b/packages/core/installMachine/installSteps/vercel/deploy.ts @@ -17,7 +17,7 @@ export const deployVercelProject = async (stateData: InstallMachineContext['stat } }); - logger.log('vercel', 'Creating production deployment...'); + logger.log('Creating production deployment...'); const productionUrl = execSync('npx vercel --prod', { stdio: ['inherit', 'pipe', 'inherit'], @@ -26,7 +26,7 @@ export const deployVercelProject = async (stateData: InstallMachineContext['stat const shortestVercelAlias = await getShortestVercelAlias(productionUrl); - if (!productionUrl) logger.log('vercel', 'Failed to create production deployment.'); + if (!productionUrl) logger.log('Failed to create production deployment.'); stateData.prettyDeploymentUrl = productionUrl; diff --git a/packages/core/package.json b/packages/core/package.json index e2658d8..6c443a9 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -10,15 +10,16 @@ "dependencies": { "boxen": "^8.0.1", "chalk": "^5.3.0", + "enquirer": "^2.4.1", "fs-extra": "^11.2.0", "gradient-string": "^3.0.0", "inquirer": "^10.2.2", "xstate": "^5.18.2" }, "devDependencies": { - "stplr-utils": "workspace:*", "@types/fs-extra": "^11.0.4", "@types/node": "^22.5.4", + "stplr-utils": "workspace:*", "tsup": "^8.3.0", "typescript": "^5.6.2" } diff --git a/packages/utils/logger/logger.ts b/packages/utils/logger/logger.ts index e63c558..22cc195 100644 --- a/packages/utils/logger/logger.ts +++ b/packages/utils/logger/logger.ts @@ -11,6 +11,8 @@ export const CHECK_MARK_COLOR = '#FAD400'; export const ACTIVE_TEXT_COLOR = '#FFFFFF'; export const COMPLETED_TEXT_COLOR = '#666666'; +export const LEFT_PADDING = ' '.repeat(SPACING + LABEL_WIDTH - 1); + type Name = | 'dir' | 'cms' @@ -52,7 +54,7 @@ const labels: Record = { error: { text: 'error' }, }; -const padding = ' '.repeat(SPACING); +const gap = ' '.repeat(SPACING); const leftPadding = ' '.repeat(LABEL_WIDTH); const formatLabel = (name: Name): string => { @@ -71,17 +73,20 @@ const formatMessage = (message: string, isCompleted: boolean = false): string => return isCompleted ? chalk.hex(COMPLETED_TEXT_COLOR)(message) : chalk.hex(ACTIVE_TEXT_COLOR)(message); }; -const logMessage = (name: Name, message: string, showCheck: boolean = true): void => { +const log = (messages: string[] | string, showCheck: boolean = true): void => { // Print current message const checkmark = showCheck ? formatCheckMark() + ' ' : ' '; - console.log(`${leftPadding}${padding}${checkmark}${formatMessage(message)}`); + messages = Array.isArray(messages) ? messages : [messages]; + messages.forEach((message) => { + console.log(`${leftPadding}${gap}${checkmark}${formatMessage(message)}`); + }); }; const withLabel = (label: Name, message: string): void => { const formattedLabel = formatLabel(label); console.log(` `); - console.log(`${formattedLabel}${padding}${formatMessage(message)}`); + console.log(`${formattedLabel}${gap}${formatMessage(message)}`); console.log(` `); }; @@ -122,7 +127,7 @@ const withSpinner = async (name: Name, initialText: string, action: (spinner: export const logger = { createSpinner, - logMessage, + log, withSpinner, withLabel, displayHeader: () => displayHeader(LABEL_BG_COLOR, LABEL_TEXT_COLOR, ACTIVE_TEXT_COLOR), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bc874ca..ae283ec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -117,6 +117,9 @@ importers: commander: specifier: ^12.1.0 version: 12.1.0 + enquirer: + specifier: ^2.4.1 + version: 2.4.1 gradient-string: specifier: ^3.0.0 version: 3.0.0 @@ -151,6 +154,9 @@ importers: chalk: specifier: ^5.3.0 version: 5.3.0 + enquirer: + specifier: ^2.4.1 + version: 2.4.1 fs-extra: specifier: ^11.2.0 version: 11.2.0 From d3f33ad0894b9be841e079a0cdfbe097dec82269 Mon Sep 17 00:00:00 2001 From: Przemyslaw Mejna Date: Fri, 20 Dec 2024 10:55:29 +0100 Subject: [PATCH 6/8] feat(cli): add fixes to enquirer, adjust further the styles --- .../command-prompts/shouldUsePayloadPrompt.ts | 4 +-- packages/cli/index.ts | 8 ++--- .../installSteps/github/checkGitHubCLI.ts | 2 +- .../github/ensureGitHubAuthentication.ts | 2 +- .../installSteps/github/fetchOrganizations.ts | 2 +- .../installSteps/github/repositoryManager.ts | 18 ++++++----- .../homepage/__tests__/install.test.ts | 2 +- .../installSteps/homepage/install.ts | 2 +- .../installSteps/payload/moveFilesToAppDir.ts | 2 +- .../payload/preparePayloadConfig.ts | 2 +- .../installSteps/payload/prepareTsConfig.ts | 2 +- .../installSteps/payload/removeTurboFlag.ts | 2 +- .../installSteps/payload/runInstallCommand.ts | 2 +- .../installSteps/payload/updatePackages.ts | 4 +-- .../installSteps/prettier/prettify.ts | 2 +- .../installSteps/shouldDeploy/shouldDeploy.ts | 2 +- .../installSteps/stapler/createDocFiles.ts | 2 +- .../installSteps/stapler/initializeStapler.ts | 2 +- .../installSteps/stapler/modifyGitignore.ts | 2 +- .../installSteps/supabase/addTemplateFiles.ts | 2 +- .../supabase/authenticateSupabase.ts | 2 +- .../installSteps/supabase/checkSupabaseCLI.ts | 2 +- .../installSteps/supabase/connectProject.ts | 32 ++++++++----------- .../installSteps/supabase/createEnvFile.ts | 2 +- .../supabase/initializeSupabaseProject.ts | 2 +- .../supabase/installDependencies.ts | 2 +- .../installSteps/tailwind/install.ts | 2 +- .../installSteps/turbo/install.ts | 4 +-- .../installSteps/vercel/authenticateVercel.ts | 2 +- .../installSteps/vercel/checkVercelCLI.ts | 2 +- .../installSteps/vercel/deploy.ts | 2 +- .../installSteps/vercel/link.ts | 2 +- .../vercel/updateProjectSettings.ts | 2 +- packages/utils/logger/logger.ts | 7 ++-- 34 files changed, 66 insertions(+), 65 deletions(-) diff --git a/packages/cli/command-prompts/shouldUsePayloadPrompt.ts b/packages/cli/command-prompts/shouldUsePayloadPrompt.ts index d3bf3f5..ded0f0d 100644 --- a/packages/cli/command-prompts/shouldUsePayloadPrompt.ts +++ b/packages/cli/command-prompts/shouldUsePayloadPrompt.ts @@ -10,8 +10,8 @@ import { LEFT_PADDING } from 'stplr-utils'; **/ export const shouldUsePayloadPrompt = async (): Promise<{ usePayload: boolean }> => { - const enquirer = new Enquirer(); - const response = (await enquirer.prompt({ + const payloadEnquirer = new Enquirer(); + const response = (await payloadEnquirer.prompt({ type: 'confirm', name: 'usePayload', message: 'Would you like to use Payload?', diff --git a/packages/cli/index.ts b/packages/cli/index.ts index 90c1557..d742970 100644 --- a/packages/cli/index.ts +++ b/packages/cli/index.ts @@ -98,19 +98,19 @@ const createAction = async (options: Flags) => { } // Clear the directory if overwrite is confirmed - logger.withSpinner('dir', 'Clearing existing directory...', async () => { - fs.rmSync(projectDir, { recursive: true, force: true }); - }); + fs.rmSync(projectDir, { recursive: true, force: true }); logger.log(chalk.yellow(`The directory "${projectName}" has been cleared.`)); } // Skip Payload if specified by the flag - logger.withLabel('cms', 'Want to use Payload?'); + logger.withLabel('cms', 'CMS setup'); const payloadAnswer = options.skipPayload ? { usePayload: false } : await shouldUsePayloadPrompt(); const finalOptions = { name: projectName, shouldDeploy, ...payloadAnswer }; if (shouldDeploy) { + logger.withLabel('auth', 'Authentication status'); await checkAuthentication(); + logger.withLabel('stapler', 'Tooling status'); await checkTools(); } diff --git a/packages/core/installMachine/installSteps/github/checkGitHubCLI.ts b/packages/core/installMachine/installSteps/github/checkGitHubCLI.ts index 05b8ef6..fdb6dd7 100644 --- a/packages/core/installMachine/installSteps/github/checkGitHubCLI.ts +++ b/packages/core/installMachine/installSteps/github/checkGitHubCLI.ts @@ -3,7 +3,7 @@ import { LEFT_PADDING, logger } from 'stplr-utils'; import { isGitHubCLIInstalled, installGitHubCLI } from './ghInstaller'; export const checkGitHubCLI = async () => { - await logger.withSpinner('github', 'Checking if GitHub CLI is installed...', async (spinner) => { + await logger.withSpinner('Checking if GitHub CLI is installed...', async (spinner) => { if (!isGitHubCLIInstalled()) { logger.log('GitHub CLI is not installed.'); const enquirer = new Enquirer(); diff --git a/packages/core/installMachine/installSteps/github/ensureGitHubAuthentication.ts b/packages/core/installMachine/installSteps/github/ensureGitHubAuthentication.ts index edd770b..f26f7dd 100644 --- a/packages/core/installMachine/installSteps/github/ensureGitHubAuthentication.ts +++ b/packages/core/installMachine/installSteps/github/ensureGitHubAuthentication.ts @@ -2,7 +2,7 @@ import { logger } from 'stplr-utils'; import { authenticateGitHub, isGitHubAuthenticated } from './repositoryManager'; export const ensureGitHubAuthentication = async () => { - await logger.withSpinner('github', 'Checking authentication status...', async (spinner) => { + await logger.withSpinner('Checking authentication status...', async (spinner) => { if (isGitHubAuthenticated()) { spinner.succeed('Logged in'); return; diff --git a/packages/core/installMachine/installSteps/github/fetchOrganizations.ts b/packages/core/installMachine/installSteps/github/fetchOrganizations.ts index 34b023f..a96c6c8 100644 --- a/packages/core/installMachine/installSteps/github/fetchOrganizations.ts +++ b/packages/core/installMachine/installSteps/github/fetchOrganizations.ts @@ -2,7 +2,7 @@ import { execAsync } from '../../../utils/execAsync'; import { logger } from 'stplr-utils'; export const fetchOrganizations = async (): Promise<{ name: string; writable: boolean }[]> => { - return await logger.withSpinner('github', 'Fetching organizations you belong to...', async (spinner) => { + return await logger.withSpinner('Fetching organizations you belong to...', async (spinner) => { try { // Fetch all organizations the user belongs to const orgsOutput = await execAsync(`gh api user/orgs --jq '[.[] | {name: .login, repos_url: .repos_url}]'`); diff --git a/packages/core/installMachine/installSteps/github/repositoryManager.ts b/packages/core/installMachine/installSteps/github/repositoryManager.ts index 4ce7ca1..6c10abc 100644 --- a/packages/core/installMachine/installSteps/github/repositoryManager.ts +++ b/packages/core/installMachine/installSteps/github/repositoryManager.ts @@ -43,7 +43,7 @@ export const isGitHubAuthenticated = (): boolean => { }; export const authenticateGitHub = async () => { - await logger.withSpinner('github', 'Attempting to authenticate...', async (spinner) => { + await logger.withSpinner('Attempting to authenticate...', async (spinner) => { try { spinner.start('Authenticating...'); const isAuthenticated = isGitHubAuthenticated(); @@ -96,10 +96,11 @@ export const createGitHubRepository = async ( // Fetch organizations and build choices for the prompt const organizations = await fetchOrganizations(); const accountChoices = [ - { name: `${username} (personal account)`, value: username }, + { name: username, value: username, message: `${username} (personal account)` }, ...organizations.map((org: { writable: any; name: any }) => ({ - name: org.writable ? org.name : chalk.gray(`${org.name} (read-only)`), + name: org.name, value: org.name, + message: org.writable ? org.name : chalk.gray(`${org.name} (read-only)`), disabled: org.writable ? false : 'No write access', })), ]; @@ -115,9 +116,10 @@ export const createGitHubRepository = async ( prefix: LEFT_PADDING, }, ])) as { selectedAccount: string }; + stateData.selectedAccount = selectedAccount; // Update state with selected account - await logger.withSpinner('github', 'Checking if repository already exists...', async (spinner) => { + await logger.withSpinner('Checking if repository already exists...', async (spinner) => { try { const repoNameJSON = await execAsync(`echo "$(gh repo view ${selectedAccount}/${projectName} --json name)"`); const repoExists = repoNameJSON.stdout.trim().includes(`{"name":"${projectName}"}`); @@ -145,7 +147,7 @@ export const createGitHubRepository = async ( } }); - await logger.withSpinner('github', `Creating repository: ${selectedAccount}/${repoName}...`, async (spinner) => { + await logger.withSpinner(`Creating repository: ${selectedAccount}/${repoName}...`, async (spinner) => { try { spinner.stop(); const enquirer = new Enquirer(); @@ -154,6 +156,7 @@ export const createGitHubRepository = async ( type: 'select' as const, name: 'repositoryVisibility', message: 'Choose the repository visibility:', + prefix: LEFT_PADDING, choices: [ { name: 'public', value: 'public' }, { name: 'private', value: 'private' }, @@ -163,7 +166,6 @@ export const createGitHubRepository = async ( ]; const response = (await enquirer.prompt(questions)) as { repositoryVisibility: string }; - console.log(response); const { repositoryVisibility } = response; @@ -194,7 +196,7 @@ const executeCommands = async (commands: string[]) => { }; export const setupGitRepository = async () => { - await logger.withSpinner('github', `Setting up Git for the repository...`, async (spinner) => { + await logger.withSpinner(`Setting up Git for the repository...`, async (spinner) => { const commands = [`git init`, `git add .`]; await executeCommands(commands); spinner.succeed('Git setup complete.'); @@ -202,7 +204,7 @@ export const setupGitRepository = async () => { }; export const pushToGitHub = async (selectedAccount: string, githubCandidateName: string) => { - await logger.withSpinner('github', 'Pushing changes...', async (spinner) => { + await logger.withSpinner('Pushing changes...', async (spinner) => { const commands = [ `git add .`, `git branch -M main`, diff --git a/packages/core/installMachine/installSteps/homepage/__tests__/install.test.ts b/packages/core/installMachine/installSteps/homepage/__tests__/install.test.ts index 037da7e..d693a4e 100644 --- a/packages/core/installMachine/installSteps/homepage/__tests__/install.test.ts +++ b/packages/core/installMachine/installSteps/homepage/__tests__/install.test.ts @@ -9,7 +9,7 @@ vi.mock('../../../../utils/generator/generator'); vi.mock('../../../../utils/getTemplateDirectory'); vi.mock('../../../../utils/logger', () => ({ logger: { - withSpinner: vi.fn((_, __, callback) => callback({ succeed: vi.fn(), fail: vi.fn() })), + withSpinner: vi.fn((__, callback) => callback({ succeed: vi.fn(), fail: vi.fn() })), }, })); diff --git a/packages/core/installMachine/installSteps/homepage/install.ts b/packages/core/installMachine/installSteps/homepage/install.ts index 09938ae..ea4dd6f 100644 --- a/packages/core/installMachine/installSteps/homepage/install.ts +++ b/packages/core/installMachine/installSteps/homepage/install.ts @@ -4,7 +4,7 @@ import { getTemplateDirectory } from '../../../utils/getTemplateDirectory'; import { logger } from 'stplr-utils'; export const modifyHomepage = async (destinationDirectory: string) => { - await logger.withSpinner('tailwind', 'Setting up your welcome homepage...', async (spinner) => { + await logger.withSpinner('Setting up your welcome homepage...', async (spinner) => { try { const templateDirectory = getTemplateDirectory(`/templates/homepage/files/`); templateGenerator(homepageFiles, templateDirectory, destinationDirectory); diff --git a/packages/core/installMachine/installSteps/payload/moveFilesToAppDir.ts b/packages/core/installMachine/installSteps/payload/moveFilesToAppDir.ts index e3e8af4..2731a8f 100644 --- a/packages/core/installMachine/installSteps/payload/moveFilesToAppDir.ts +++ b/packages/core/installMachine/installSteps/payload/moveFilesToAppDir.ts @@ -2,7 +2,7 @@ import { logger } from 'stplr-utils'; import { execAsync } from '../../../utils/execAsync'; export const moveFilesToAppDir = async () => { - await logger.withSpinner('payload', 'Moving files to (app) directory...', async (spinner) => { + await logger.withSpinner('Moving files to (app) directory...', async (spinner) => { try { await execAsync( `mkdir -p ./app/\\(app\\) && find ./app -maxdepth 1 ! -path './app' ! -path './app/\\(app\\)' -exec mv {} ./app/\\(app\\)/ \\;`, diff --git a/packages/core/installMachine/installSteps/payload/preparePayloadConfig.ts b/packages/core/installMachine/installSteps/payload/preparePayloadConfig.ts index 9f04fbb..1fc26d4 100644 --- a/packages/core/installMachine/installSteps/payload/preparePayloadConfig.ts +++ b/packages/core/installMachine/installSteps/payload/preparePayloadConfig.ts @@ -11,7 +11,7 @@ export const preparePayloadConfig = async () => { return; } - await logger.withSpinner('payload', 'Preparing config...', async (spinner) => { + await logger.withSpinner('Preparing config...', async (spinner) => { try { // Read the payload.config.ts file const data = await fs.readFile(payloadConfigPath, 'utf8'); diff --git a/packages/core/installMachine/installSteps/payload/prepareTsConfig.ts b/packages/core/installMachine/installSteps/payload/prepareTsConfig.ts index d902223..2eecf76 100644 --- a/packages/core/installMachine/installSteps/payload/prepareTsConfig.ts +++ b/packages/core/installMachine/installSteps/payload/prepareTsConfig.ts @@ -7,7 +7,7 @@ const readFileAsync = promisify(fs.readFile); const writeFileAsync = promisify(fs.writeFile); export const prepareTsConfig = async () => { - await logger.withSpinner('payload', 'Preparing TypeScript config..', async (spinner) => { + await logger.withSpinner('Preparing TypeScript config..', async (spinner) => { const tsconfigPath = path.join(process.cwd(), 'tsconfig.json'); try { diff --git a/packages/core/installMachine/installSteps/payload/removeTurboFlag.ts b/packages/core/installMachine/installSteps/payload/removeTurboFlag.ts index 28df30c..bb73aa0 100644 --- a/packages/core/installMachine/installSteps/payload/removeTurboFlag.ts +++ b/packages/core/installMachine/installSteps/payload/removeTurboFlag.ts @@ -7,7 +7,7 @@ const readFileAsync = promisify(fs.readFile); const writeFileAsync = promisify(fs.writeFile); export const removeTurboFlag = async () => { - await logger.withSpinner('payload', 'Removing --turbo flag from dev script...', async (spinner) => { + await logger.withSpinner('Removing --turbo flag from dev script...', async (spinner) => { const packageJsonPath = path.join(process.cwd(), 'package.json'); try { diff --git a/packages/core/installMachine/installSteps/payload/runInstallCommand.ts b/packages/core/installMachine/installSteps/payload/runInstallCommand.ts index c8abdaf..988a7aa 100644 --- a/packages/core/installMachine/installSteps/payload/runInstallCommand.ts +++ b/packages/core/installMachine/installSteps/payload/runInstallCommand.ts @@ -4,7 +4,7 @@ import { loadEnvFile } from './utils/loadEnvFile'; export const runInstallCommand = async () => { loadEnvFile('../../supabase/.env'); - await logger.withSpinner('payload', 'Installing to Next.js...', async (spinner) => { + await logger.withSpinner('Installing to Next.js...', async (spinner) => { try { await execAsync(`echo y | npx create-payload-app --db postgres --db-connection-string ${process.env.DB_URL}`); diff --git a/packages/core/installMachine/installSteps/payload/updatePackages.ts b/packages/core/installMachine/installSteps/payload/updatePackages.ts index 2e9ed1b..d0ef1d5 100644 --- a/packages/core/installMachine/installSteps/payload/updatePackages.ts +++ b/packages/core/installMachine/installSteps/payload/updatePackages.ts @@ -2,7 +2,7 @@ import { logger } from 'stplr-utils'; import { execAsync } from '../../../utils/execAsync'; export const updatePackages = async () => { - await logger.withSpinner('payload', `Updating React to version 19...`, async (spinner) => { + await logger.withSpinner(`Updating React to version 19...`, async (spinner) => { try { await execAsync(`pnpm up react@19 react-dom@19 --reporter silent`); spinner.succeed(`Updated React to version 19.`); @@ -12,7 +12,7 @@ export const updatePackages = async () => { } }); - await logger.withSpinner('payload', 'Installing necessary packages...', async (spinner) => { + await logger.withSpinner('Installing necessary packages...', async (spinner) => { try { await execAsync(`pnpm i pg sharp --reporter silent`); spinner.succeed('Installed necessary packages.'); diff --git a/packages/core/installMachine/installSteps/prettier/prettify.ts b/packages/core/installMachine/installSteps/prettier/prettify.ts index c3ffbe1..619cfbd 100644 --- a/packages/core/installMachine/installSteps/prettier/prettify.ts +++ b/packages/core/installMachine/installSteps/prettier/prettify.ts @@ -2,7 +2,7 @@ import { execSync } from 'child_process'; import { logger } from 'stplr-utils'; export const prettify = async () => { - await logger.withSpinner('prettier', 'Prettifying...', async (spinner) => { + await logger.withSpinner('Prettifying...', async (spinner) => { try { const ignorePatterns = [ 'node_modules/', diff --git a/packages/core/installMachine/installSteps/shouldDeploy/shouldDeploy.ts b/packages/core/installMachine/installSteps/shouldDeploy/shouldDeploy.ts index fa329db..3713218 100644 --- a/packages/core/installMachine/installSteps/shouldDeploy/shouldDeploy.ts +++ b/packages/core/installMachine/installSteps/shouldDeploy/shouldDeploy.ts @@ -2,7 +2,7 @@ import Enquirer from 'enquirer'; import { LEFT_PADDING, logger } from 'stplr-utils'; export const shouldDeploy = async (shouldContinue: boolean): Promise => { - return await logger.withSpinner('deployment', 'Deciding next steps...', async (spinner) => { + return await logger.withSpinner('Deciding next steps...', async (spinner) => { if (!shouldContinue) { spinner.succeed('Local deployment completed'); return false; diff --git a/packages/core/installMachine/installSteps/stapler/createDocFiles.ts b/packages/core/installMachine/installSteps/stapler/createDocFiles.ts index 5bf0b91..2872bdc 100644 --- a/packages/core/installMachine/installSteps/stapler/createDocFiles.ts +++ b/packages/core/installMachine/installSteps/stapler/createDocFiles.ts @@ -4,7 +4,7 @@ import { logger } from 'stplr-utils'; import { getTemplateDirectory } from '../../../utils/getTemplateDirectory'; export const createDocFiles = async () => { - await logger.withSpinner('stapler', 'Writing docs...', async (spinner) => { + await logger.withSpinner('Writing docs...', async (spinner) => { try { const templateDirectory = getTemplateDirectory(`/templates/docs/files`); const destinationDirectory = process.cwd(); diff --git a/packages/core/installMachine/installSteps/stapler/initializeStapler.ts b/packages/core/installMachine/installSteps/stapler/initializeStapler.ts index db6de5a..80182dc 100644 --- a/packages/core/installMachine/installSteps/stapler/initializeStapler.ts +++ b/packages/core/installMachine/installSteps/stapler/initializeStapler.ts @@ -2,7 +2,7 @@ import { execAsync } from '../../../utils/execAsync'; import { logger } from 'stplr-utils'; export const initializeStapler = async (name: string) => { - await logger.withSpinner('stapler', 'Initializing...', async (spinner) => { + await logger.withSpinner('Initializing...', async (spinner) => { try { await execAsync(`mkdir ${name}`); spinner.succeed('Initialized.'); diff --git a/packages/core/installMachine/installSteps/stapler/modifyGitignore.ts b/packages/core/installMachine/installSteps/stapler/modifyGitignore.ts index 90dd592..fb18ad3 100644 --- a/packages/core/installMachine/installSteps/stapler/modifyGitignore.ts +++ b/packages/core/installMachine/installSteps/stapler/modifyGitignore.ts @@ -7,7 +7,7 @@ const readFileAsync = promisify(fs.readFile); const writeFileAsync = promisify(fs.writeFile); export const modifyGitignore = async (entry: string) => { - await logger.withSpinner('stapler', `Adding entries to .gitignore..`, async (spinner) => { + await logger.withSpinner(`Adding entries to .gitignore..`, async (spinner) => { const gitignorePath = path.join(process.cwd(), '.gitignore'); try { diff --git a/packages/core/installMachine/installSteps/supabase/addTemplateFiles.ts b/packages/core/installMachine/installSteps/supabase/addTemplateFiles.ts index 5b9c16a..51008fb 100644 --- a/packages/core/installMachine/installSteps/supabase/addTemplateFiles.ts +++ b/packages/core/installMachine/installSteps/supabase/addTemplateFiles.ts @@ -6,7 +6,7 @@ import { getTemplateDirectory } from '../../../utils/getTemplateDirectory'; import { logger } from 'stplr-utils'; export const addTemplateFiles = async (destinationDirectory: string) => { - await logger.withSpinner('supabase', 'Adding files from template...', async (spinner) => { + await logger.withSpinner('Adding files from template...', async (spinner) => { const templateDirectory = getTemplateDirectory(`/templates/supabase/files/`); templateGenerator(supabaseFiles, templateDirectory, destinationDirectory); diff --git a/packages/core/installMachine/installSteps/supabase/authenticateSupabase.ts b/packages/core/installMachine/installSteps/supabase/authenticateSupabase.ts index 0b32a25..a1282ca 100644 --- a/packages/core/installMachine/installSteps/supabase/authenticateSupabase.ts +++ b/packages/core/installMachine/installSteps/supabase/authenticateSupabase.ts @@ -3,7 +3,7 @@ import { execAsync } from '../../../utils/execAsync'; import { logger } from 'stplr-utils/logger'; export const authenticateSupabase = async () => { - await logger.withSpinner('supabase', 'Checking Supabase authentication...', async (spinner) => { + await logger.withSpinner('Checking Supabase authentication...', async (spinner) => { try { await execAsync('npx supabase projects list'); spinner.succeed('Logged in'); diff --git a/packages/core/installMachine/installSteps/supabase/checkSupabaseCLI.ts b/packages/core/installMachine/installSteps/supabase/checkSupabaseCLI.ts index 55f090b..3a19f0a 100644 --- a/packages/core/installMachine/installSteps/supabase/checkSupabaseCLI.ts +++ b/packages/core/installMachine/installSteps/supabase/checkSupabaseCLI.ts @@ -46,7 +46,7 @@ const installSupabaseCLI = async (): Promise => { }; export const checkSupabaseCLI = async () => { - await logger.withSpinner('supabase', 'Checking if Supabase CLI is installed...', async (spinner) => { + await logger.withSpinner('Checking if Supabase CLI is installed...', async (spinner) => { if (!isSupabaseCLIInstalled()) { logger.log('Supabase CLI is not installed.'); diff --git a/packages/core/installMachine/installSteps/supabase/connectProject.ts b/packages/core/installMachine/installSteps/supabase/connectProject.ts index 04b77b7..8ec207c 100644 --- a/packages/core/installMachine/installSteps/supabase/connectProject.ts +++ b/packages/core/installMachine/installSteps/supabase/connectProject.ts @@ -11,7 +11,7 @@ import { delay } from '../../../utils/delay'; export const connectSupabaseProject = async (projectName: string, currentDir: string) => { try { // Get project information - const newProject = await logger.withSpinner('supabase', 'Getting project information...', async (spinner) => { + const newProject = await logger.withSpinner('Getting project information...', async (spinner) => { const { stdout: projectsList } = await execAsync('npx supabase projects list'); const projects = parseProjectsList(projectsList); const project = projects.find((p) => p.name === projectName); @@ -28,24 +28,20 @@ export const connectSupabaseProject = async (projectName: string, currentDir: st }); // Get API keys - const { anonKey, serviceRoleKey } = await logger.withSpinner( - 'supabase', - 'Getting project API keys...', - async (spinner) => { - const { stdout: projectAPIKeys } = await execAsync( - `npx supabase projects api-keys --project-ref ${newProject.refId}`, - ); + const { anonKey, serviceRoleKey } = await logger.withSpinner('Getting project API keys...', async (spinner) => { + const { stdout: projectAPIKeys } = await execAsync( + `npx supabase projects api-keys --project-ref ${newProject.refId}`, + ); - const keys = getSupabaseKeys(projectAPIKeys); - if (!keys.anonKey || !keys.serviceRoleKey) { - spinner.fail('Failed to retrieve API keys'); - throw new Error('Failed to retrieve Supabase API keys. Please check your project configuration.'); - } + const keys = getSupabaseKeys(projectAPIKeys); + if (!keys.anonKey || !keys.serviceRoleKey) { + spinner.fail('Failed to retrieve API keys'); + throw new Error('Failed to retrieve Supabase API keys. Please check your project configuration.'); + } - spinner.succeed('API keys retrieved.'); - return keys; - }, - ); + spinner.succeed('API keys retrieved.'); + return keys; + }); // Link project logger.log('Linking project...'); @@ -87,7 +83,7 @@ export const connectSupabaseProject = async (projectName: string, currentDir: st spinner.succeed('Dashboard opened.'); // Check Vercel integration - await logger.withSpinner('vercel', 'Checking integration...', async (spinner) => { + await logger.withSpinner('Checking integration...', async (spinner) => { const token = await getVercelTokenFromAuthFile(); const { projectId: vercelProjectId, orgId: vercelTeamId } = await getDataFromVercelConfig(); let attempts = 0; diff --git a/packages/core/installMachine/installSteps/supabase/createEnvFile.ts b/packages/core/installMachine/installSteps/supabase/createEnvFile.ts index 3437301..0e6e16f 100644 --- a/packages/core/installMachine/installSteps/supabase/createEnvFile.ts +++ b/packages/core/installMachine/installSteps/supabase/createEnvFile.ts @@ -2,7 +2,7 @@ import fs from 'fs'; import { logger } from 'stplr-utils'; export const createEnvFile = async () => { - await logger.withSpinner('supabase', 'Writing local variables to .env file...', async (spinner) => { + await logger.withSpinner('Writing local variables to .env file...', async (spinner) => { const envData = `ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0 API_URL=http://127.0.0.1:54321 DB_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres diff --git a/packages/core/installMachine/installSteps/supabase/initializeSupabaseProject.ts b/packages/core/installMachine/installSteps/supabase/initializeSupabaseProject.ts index 184edbf..3fdd836 100644 --- a/packages/core/installMachine/installSteps/supabase/initializeSupabaseProject.ts +++ b/packages/core/installMachine/installSteps/supabase/initializeSupabaseProject.ts @@ -2,7 +2,7 @@ import { execAsync } from '../../../utils/execAsync'; import { logger } from 'stplr-utils'; export const initializeSupabaseProject = async () => { - await logger.withSpinner('supabase', 'Initializing project...', async (spinner) => { + await logger.withSpinner('Initializing project...', async (spinner) => { try { await execAsync(`npx supabase init`); spinner.succeed('Project initialized.'); diff --git a/packages/core/installMachine/installSteps/supabase/installDependencies.ts b/packages/core/installMachine/installSteps/supabase/installDependencies.ts index de1290d..e306305 100644 --- a/packages/core/installMachine/installSteps/supabase/installDependencies.ts +++ b/packages/core/installMachine/installSteps/supabase/installDependencies.ts @@ -2,7 +2,7 @@ import { execAsync } from '../../../utils/execAsync'; import { logger } from 'stplr-utils'; export const installDependencies = async () => { - await logger.withSpinner('supabase', 'Installing dependencies...', async (spinner) => { + await logger.withSpinner('Installing dependencies...', async (spinner) => { await execAsync('pnpm i --reporter silent'); spinner.succeed('Dependencies installed.'); }); diff --git a/packages/core/installMachine/installSteps/tailwind/install.ts b/packages/core/installMachine/installSteps/tailwind/install.ts index f8fcbd0..da108c4 100644 --- a/packages/core/installMachine/installSteps/tailwind/install.ts +++ b/packages/core/installMachine/installSteps/tailwind/install.ts @@ -23,7 +23,7 @@ const installTailwindPackage = async (currentDir: string) => { }; export const installTailwind = async (currentDir: string) => { - await logger.withSpinner('tailwind', 'Adding Tailwind...', async (spinner) => { + await logger.withSpinner('Adding Tailwind...', async (spinner) => { try { await installTailwindPackage(currentDir); copyTailwindFiles(currentDir); diff --git a/packages/core/installMachine/installSteps/turbo/install.ts b/packages/core/installMachine/installSteps/turbo/install.ts index 5435588..3afeb59 100644 --- a/packages/core/installMachine/installSteps/turbo/install.ts +++ b/packages/core/installMachine/installSteps/turbo/install.ts @@ -2,7 +2,7 @@ import { execAsync } from '../../../utils/execAsync'; import { logger } from 'stplr-utils'; const checkPnpmVersion = async () => { - await logger.withSpinner('turborepo', 'Checking pnpm version...', async (spinner) => { + await logger.withSpinner('Checking pnpm version...', async (spinner) => { try { // Run the command to check the pnpm version const { stdout } = await execAsync('pnpm --version'); @@ -15,7 +15,7 @@ const checkPnpmVersion = async () => { }; export const createTurbo = async (name: string) => { - await logger.withSpinner('turborepo', 'Initializing...', async (spinner) => { + await logger.withSpinner('Initializing...', async (spinner) => { try { // Check the pnpm version await checkPnpmVersion(); diff --git a/packages/core/installMachine/installSteps/vercel/authenticateVercel.ts b/packages/core/installMachine/installSteps/vercel/authenticateVercel.ts index 256e9f3..dc3cd0c 100644 --- a/packages/core/installMachine/installSteps/vercel/authenticateVercel.ts +++ b/packages/core/installMachine/installSteps/vercel/authenticateVercel.ts @@ -2,7 +2,7 @@ import { execSync } from 'child_process'; import { logger } from 'stplr-utils'; export const authenticateVercel = async () => { - await logger.withSpinner('vercel', 'Logging in to Vercel...', async (spinner) => { + await logger.withSpinner('Logging in to Vercel...', async (spinner) => { try { execSync('npx vercel whoami', { stdio: 'pipe' }); diff --git a/packages/core/installMachine/installSteps/vercel/checkVercelCLI.ts b/packages/core/installMachine/installSteps/vercel/checkVercelCLI.ts index 5b4068b..c87581d 100644 --- a/packages/core/installMachine/installSteps/vercel/checkVercelCLI.ts +++ b/packages/core/installMachine/installSteps/vercel/checkVercelCLI.ts @@ -23,7 +23,7 @@ const installVercelCLI = async (): Promise => { }; export const checkVercelCLI = async () => { - await logger.withSpinner('vercel', 'Checking if Vercel CLI is installed...', async (spinner) => { + await logger.withSpinner('Checking if Vercel CLI is installed...', async (spinner) => { if (!isVercelCLIInstalled()) { logger.log('Vercel CLI is not installed.'); diff --git a/packages/core/installMachine/installSteps/vercel/deploy.ts b/packages/core/installMachine/installSteps/vercel/deploy.ts index bda779e..e07beb1 100644 --- a/packages/core/installMachine/installSteps/vercel/deploy.ts +++ b/packages/core/installMachine/installSteps/vercel/deploy.ts @@ -5,7 +5,7 @@ import { getShortestVercelAlias } from './utils/getShortestVercelAlias'; import { type InstallMachineContext } from '../../../types'; export const deployVercelProject = async (stateData: InstallMachineContext['stateData']) => { - await logger.withSpinner('vercel', 'Connecting Vercel to Git...', async (spinner) => { + await logger.withSpinner('Connecting Vercel to Git...', async (spinner) => { try { // Execute 'vercel git connect' and capture the output await execAsync('npx vercel git connect'); diff --git a/packages/core/installMachine/installSteps/vercel/link.ts b/packages/core/installMachine/installSteps/vercel/link.ts index 834e93f..1ab1512 100644 --- a/packages/core/installMachine/installSteps/vercel/link.ts +++ b/packages/core/installMachine/installSteps/vercel/link.ts @@ -2,7 +2,7 @@ import { execAsync } from '../../../utils/execAsync'; import { logger } from 'stplr-utils'; export const linkVercelProject = async (projectName: string) => { - await logger.withSpinner('vercel', 'Linking project...', async (spinner) => { + await logger.withSpinner('Linking project...', async (spinner) => { try { await execAsync(`npx vercel link --yes --project ${projectName}`); spinner.succeed('Project linked successfully.'); diff --git a/packages/core/installMachine/installSteps/vercel/updateProjectSettings.ts b/packages/core/installMachine/installSteps/vercel/updateProjectSettings.ts index c817d8f..4ca3022 100644 --- a/packages/core/installMachine/installSteps/vercel/updateProjectSettings.ts +++ b/packages/core/installMachine/installSteps/vercel/updateProjectSettings.ts @@ -3,7 +3,7 @@ import { getDataFromVercelConfig } from '../../../utils/getDataFromVercelConfig' import { getVercelTokenFromAuthFile } from '../../../utils/getVercelTokenFromAuthFile'; export const updateVercelProjectSettings = async () => { - await logger.withSpinner('vercel', 'Changing project settings...', async (spinner) => { + await logger.withSpinner('Changing project settings...', async (spinner) => { try { const token = await getVercelTokenFromAuthFile(); if (!token) { diff --git a/packages/utils/logger/logger.ts b/packages/utils/logger/logger.ts index 22cc195..b19dfc8 100644 --- a/packages/utils/logger/logger.ts +++ b/packages/utils/logger/logger.ts @@ -18,6 +18,7 @@ type Name = | 'cms' | 'git' | 'db' + | 'auth' | 'stapler' | 'turborepo' | 'supabase' @@ -40,6 +41,7 @@ const labels: Record = { cms: { text: 'cms' }, git: { text: 'git' }, db: { text: 'db' }, + auth: { text: 'auth' }, stapler: { text: 'stplr' }, turborepo: { text: 'turbo' }, supabase: { text: 'supa' }, @@ -109,15 +111,16 @@ const createSpinner = (initialText?: string): Ora => { text: initialText, spinner: spinner, color: 'yellow', - indent: 6, + indent: LABEL_WIDTH + SPACING, }); }; -const withSpinner = async (name: Name, initialText: string, action: (spinner: Ora) => Promise): Promise => { +const withSpinner = async (initialText: string, action: (spinner: Ora) => Promise): Promise => { const spinner = createSpinner(initialText); try { spinner.start(); const result = await action(spinner); + spinner.succeed(); return result; } catch (error) { spinner.fail(); From ecfc20571addb63292d15ba8673bf8e8acf3598d Mon Sep 17 00:00:00 2001 From: KarolinaKopacz Date: Thu, 9 Jan 2025 10:33:40 +0100 Subject: [PATCH 7/8] feat(logger): overwrite checkmark color when spinner succeed --- packages/utils/logger/logger.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/utils/logger/logger.ts b/packages/utils/logger/logger.ts index b19dfc8..68cab41 100644 --- a/packages/utils/logger/logger.ts +++ b/packages/utils/logger/logger.ts @@ -119,6 +119,14 @@ const withSpinner = async (initialText: string, action: (spinner: Ora) => Pro const spinner = createSpinner(initialText); try { spinner.start(); + + // overwrite success checkmark + spinner.succeed = (text: string) => { + return spinner.stopAndPersist({ + text, + symbol: chalk.hex(CHECK_MARK_COLOR)('✔'), + }); + }; const result = await action(spinner); spinner.succeed(); return result; From a32df1d14feb7c1af8749bb49b18fd880a7c3b92 Mon Sep 17 00:00:00 2001 From: KarolinaKopacz Date: Wed, 22 Jan 2025 16:16:27 +0100 Subject: [PATCH 8/8] feat(ui): change prompt selects and text style, change boxen style, overwrite ansi pointer style --- package.json | 7 +++- .../command-prompts/getProjectNamePrompt.ts | 8 +++-- .../overwriteDirectoryPrompt.ts | 8 +++-- .../command-prompts/shouldUsePayloadPrompt.ts | 8 +++-- .../unfinishedProjectsChoicePrompt.ts | 24 +++++++++----- .../installSteps/github/repositoryManager.ts | 33 ++++++++++++------- .../installSteps/shouldDeploy/shouldDeploy.ts | 23 +++++++++++-- .../installSteps/stapler/prepareDrink.ts | 8 ++--- .../installSteps/supabase/connectProject.ts | 27 +++++++-------- .../installSteps/vercel/chooseTeam.ts | 12 +++---- packages/utils/logger/logger.ts | 9 +++++ patches/ansi-colors@4.1.3.patch | 30 +++++++++++++++++ pnpm-lock.yaml | 29 +++++++++++----- 13 files changed, 159 insertions(+), 67 deletions(-) create mode 100644 patches/ansi-colors@4.1.3.patch diff --git a/package.json b/package.json index 8d6821c..4e832ef 100644 --- a/package.json +++ b/package.json @@ -57,5 +57,10 @@ }, "keywords": [], "author": "", - "license": "ISC" + "license": "ISC", + "pnpm": { + "patchedDependencies": { + "ansi-colors@4.1.3": "patches/ansi-colors@4.1.3.patch" + } + } } diff --git a/packages/cli/command-prompts/getProjectNamePrompt.ts b/packages/cli/command-prompts/getProjectNamePrompt.ts index ee02e0f..b5fd376 100644 --- a/packages/cli/command-prompts/getProjectNamePrompt.ts +++ b/packages/cli/command-prompts/getProjectNamePrompt.ts @@ -1,14 +1,18 @@ +import chalk from 'chalk'; import Enquirer from 'enquirer'; -import { LEFT_PADDING } from 'stplr-utils'; +import { CHECK_MARK_COLOR, LEFT_PADDING } from 'stplr-utils'; export const getProjectNamePrompt = async (): Promise => { const enquirer = new Enquirer(); const response = (await enquirer.prompt({ type: 'input', name: 'name', - message: 'What is your project named?', + message: chalk.whiteBright('What is your project named?'), initial: 'my-stapled-app', prefix: LEFT_PADDING, + format(value) { + return `${chalk.hex(CHECK_MARK_COLOR)(value)}`; + }, })) as { name: string }; return response.name; diff --git a/packages/cli/command-prompts/overwriteDirectoryPrompt.ts b/packages/cli/command-prompts/overwriteDirectoryPrompt.ts index dd8288c..a7f9791 100644 --- a/packages/cli/command-prompts/overwriteDirectoryPrompt.ts +++ b/packages/cli/command-prompts/overwriteDirectoryPrompt.ts @@ -1,5 +1,6 @@ +import chalk from 'chalk'; import Enquirer from 'enquirer'; -import { LEFT_PADDING } from 'stplr-utils'; +import { CHECK_MARK_COLOR, LEFT_PADDING } from 'stplr-utils'; /** * Prompts the user to confirm whether they want to overwrite an existing project directory. @@ -14,9 +15,12 @@ export const overwriteDirectoryPrompt = async (projectName: string): Promise<{ o const response = (await enquirer.prompt({ type: 'confirm', name: 'overwrite', - message: `The directory "${projectName}" already exists. Do you want to overwrite it?`, + message: chalk.whiteBright(`The directory "${projectName}" already exists. Do you want to overwrite it?`), initial: false, prefix: LEFT_PADDING, + format(value) { + return `${chalk.hex(CHECK_MARK_COLOR)(value)}`; + }, })) as { overwrite: boolean }; return response; diff --git a/packages/cli/command-prompts/shouldUsePayloadPrompt.ts b/packages/cli/command-prompts/shouldUsePayloadPrompt.ts index ded0f0d..aae8c5d 100644 --- a/packages/cli/command-prompts/shouldUsePayloadPrompt.ts +++ b/packages/cli/command-prompts/shouldUsePayloadPrompt.ts @@ -1,5 +1,6 @@ +import chalk from 'chalk'; import Enquirer from 'enquirer'; -import { LEFT_PADDING } from 'stplr-utils'; +import { CHECK_MARK_COLOR, LEFT_PADDING } from 'stplr-utils'; /** * Prompts the user to confirm whether they want to overwrite an existing project directory. @@ -14,9 +15,12 @@ export const shouldUsePayloadPrompt = async (): Promise<{ usePayload: boolean }> const response = (await payloadEnquirer.prompt({ type: 'confirm', name: 'usePayload', - message: 'Would you like to use Payload?', + message: chalk.whiteBright('Would you like to use Payload?'), initial: true, // Default value prefix: LEFT_PADDING, // Removes the default '?' prefix + format(value) { + return `${chalk.hex(CHECK_MARK_COLOR)(value)}`; + }, })) as { usePayload: boolean }; return response; diff --git a/packages/cli/command-prompts/unfinishedProjectsChoicePrompt.ts b/packages/cli/command-prompts/unfinishedProjectsChoicePrompt.ts index 35ade1a..105697b 100644 --- a/packages/cli/command-prompts/unfinishedProjectsChoicePrompt.ts +++ b/packages/cli/command-prompts/unfinishedProjectsChoicePrompt.ts @@ -1,8 +1,8 @@ import Enquirer from 'enquirer'; -import { ProjectChoice, UnfinishedProject } from '../utils/findUnfinishedProjects'; -import { LABEL_BG_COLOR, LEFT_PADDING } from 'stplr-utils'; import chalk from 'chalk'; +import { CHECK_MARK_COLOR, LEFT_PADDING, QUESTION_MARK } from 'stplr-utils'; +import { ProjectChoice, UnfinishedProject } from '../utils/findUnfinishedProjects'; export type UnfinishedProjectsChoiceAnswers = { resume: boolean; @@ -32,7 +32,7 @@ export const unfinishedProjectsChoice = async ( return { name: choice.name, value: choice.name, - message: `${chalk.hex(LABEL_BG_COLOR)(LEFT_PADDING + choice.name)}`, + message: chalk.whiteBright(choice.name), }; }); @@ -40,11 +40,16 @@ export const unfinishedProjectsChoice = async ( const shouldResume = (await enquirer.prompt({ type: 'confirm', name: 'resume', - message: `We found the following unfinished project(s):\n${unfinishedProjects - .map((p) => `${LEFT_PADDING} - ${p.projectName}`) - .join('\n')}\n${LEFT_PADDING} Would you like to resume one of them?`, + message: chalk.whiteBright( + `We found the following unfinished project(s):\n${unfinishedProjects + .map((p) => `${LEFT_PADDING} - ${p.projectName}`) + .join('\n')}\n ${LEFT_PADDING}${QUESTION_MARK} Would you like to resume one of them?`, + ), initial: true, prefix: LEFT_PADDING, + format(value) { + return `${chalk.hex(CHECK_MARK_COLOR)(value)}`; + }, })) as { resume: boolean }; if (!shouldResume.resume) { @@ -57,9 +62,9 @@ export const unfinishedProjectsChoice = async ( const selectProjectAnswer = (await enquirer.prompt({ type: 'select', name: 'unfinishedSelectedProject', - message: 'Select a project to resume:', + message: chalk.whiteBright('Select a project to resume:'), choices: formattedProjectChoices, - prefix: LEFT_PADDING, + prefix: ' ' + LEFT_PADDING + QUESTION_MARK, // use it only when we want to resume a project skip: (state: unknown) => { if (typeof state === 'object' && state !== null && 'resume' in state) { @@ -69,6 +74,9 @@ export const unfinishedProjectsChoice = async ( console.log('state', state); return false; // default fallback if not sure }, + format(value) { + return chalk.hex(CHECK_MARK_COLOR)(value); + }, })) as UnfinishedProjectsChoiceAnswers; const selectedProject = unfinishedProjects.find( diff --git a/packages/core/installMachine/installSteps/github/repositoryManager.ts b/packages/core/installMachine/installSteps/github/repositoryManager.ts index 6c10abc..1ac6308 100644 --- a/packages/core/installMachine/installSteps/github/repositoryManager.ts +++ b/packages/core/installMachine/installSteps/github/repositoryManager.ts @@ -1,9 +1,9 @@ +import chalk from 'chalk'; import { execSync, spawnSync } from 'child_process'; import Enquirer from 'enquirer'; -import chalk from 'chalk'; -import { LEFT_PADDING, logger } from 'stplr-utils'; -import { execAsync } from '../../../utils/execAsync'; +import { CHECK_MARK_COLOR, LEFT_PADDING, logger, QUESTION_MARK } from 'stplr-utils'; import { InstallMachineContext } from '../../../types'; +import { execAsync } from '../../../utils/execAsync'; import { fetchOrganizations } from './fetchOrganizations'; export interface ProjectChoice { @@ -96,11 +96,11 @@ export const createGitHubRepository = async ( // Fetch organizations and build choices for the prompt const organizations = await fetchOrganizations(); const accountChoices = [ - { name: username, value: username, message: `${username} (personal account)` }, + { name: username, value: username, message: chalk.whiteBright(username + 'personal account') }, ...organizations.map((org: { writable: any; name: any }) => ({ name: org.name, - value: org.name, - message: org.writable ? org.name : chalk.gray(`${org.name} (read-only)`), + value: chalk.hex(CHECK_MARK_COLOR)(LEFT_PADDING + org.name), + message: org.writable ? chalk.whiteBright(org.name) : chalk.gray(`${org.name} (read-only)`), disabled: org.writable ? false : 'No write access', })), ]; @@ -111,9 +111,12 @@ export const createGitHubRepository = async ( { type: 'select', name: 'selectedAccount', - message: 'Select the account or organization to create the repository under:', + message: chalk.whiteBright('Select the account or organization to create the repository under:'), choices: accountChoices, - prefix: LEFT_PADDING, + prefix: ' ' + LEFT_PADDING + QUESTION_MARK, + format(value) { + return chalk.hex(CHECK_MARK_COLOR)(value); + }, }, ])) as { selectedAccount: string }; @@ -132,9 +135,12 @@ export const createGitHubRepository = async ( { type: 'input', name: 'confirmedName', - message: 'The repository already exists. Please confirm or modify the repository name:', + message: chalk.whiteBright('The repository already exists. Please confirm or modify the repository name:'), initial: newRepoName, prefix: LEFT_PADDING, + format(value) { + return chalk.hex(CHECK_MARK_COLOR)(value); + }, }, ])) as { confirmedName: string }; repoName = confirmedName; @@ -155,13 +161,16 @@ export const createGitHubRepository = async ( { type: 'select' as const, name: 'repositoryVisibility', - message: 'Choose the repository visibility:', + message: chalk.whiteBright('Choose the repository visibility:'), prefix: LEFT_PADDING, choices: [ - { name: 'public', value: 'public' }, - { name: 'private', value: 'private' }, + { name: 'public', value: 'public', message: chalk.whiteBright('public') }, + { name: 'private', value: 'private', message: chalk.whiteBright('private') }, ], initial: 'public', + format(value: string) { + return `${chalk.hex(CHECK_MARK_COLOR)(value)}`; + }, }, ]; diff --git a/packages/core/installMachine/installSteps/shouldDeploy/shouldDeploy.ts b/packages/core/installMachine/installSteps/shouldDeploy/shouldDeploy.ts index 3713218..43aba7f 100644 --- a/packages/core/installMachine/installSteps/shouldDeploy/shouldDeploy.ts +++ b/packages/core/installMachine/installSteps/shouldDeploy/shouldDeploy.ts @@ -1,5 +1,6 @@ +import chalk from 'chalk'; import Enquirer from 'enquirer'; -import { LEFT_PADDING, logger } from 'stplr-utils'; +import { CHECK_MARK_COLOR, logger, QUESTION_MARK } from 'stplr-utils'; export const shouldDeploy = async (shouldContinue: boolean): Promise => { return await logger.withSpinner('Deciding next steps...', async (spinner) => { @@ -15,10 +16,26 @@ export const shouldDeploy = async (shouldContinue: boolean): Promise => { type: 'confirm', name: 'continue', - message: + message: chalk.whiteBright( 'Local installation completed. Would you like to continue with remote setup (GitHub, Supabase, Vercel)?', + ), initial: true, - prefix: LEFT_PADDING, + prefix: QUESTION_MARK, + format(value) { + return `${chalk.hex(CHECK_MARK_COLOR)(value)}`; + }, + result(value) { + process.stdout.write('\x1B[1A'); + process.stdout.write('\x1B[2K'); + process.stdout.write('\x1B[1A'); + process.stdout.write('\x1B[2K'); + + logger.log( + `Local installation completed. Would you like to continue with remote setup (GitHub, Supabase, Vercel)? (Y/n) · ${chalk.hex(CHECK_MARK_COLOR)(value)}`, + ); + + return value; + }, }, ])) as { continue: boolean }; diff --git a/packages/core/installMachine/installSteps/stapler/prepareDrink.ts b/packages/core/installMachine/installSteps/stapler/prepareDrink.ts index 2e92783..ff7e47d 100644 --- a/packages/core/installMachine/installSteps/stapler/prepareDrink.ts +++ b/packages/core/installMachine/installSteps/stapler/prepareDrink.ts @@ -1,6 +1,6 @@ import chalk from 'chalk'; +import { CHECK_MARK_COLOR, LABEL_WIDTH, SPACING } from 'stplr-utils'; import { delay } from '../../../utils/delay'; -import { LABEL_WIDTH, SPACING } from 'stplr-utils'; const getMessages = (name: string, prettyDeploymentUrl: string, shouldDeploy: boolean) => { const messages = [ @@ -8,12 +8,12 @@ const getMessages = (name: string, prettyDeploymentUrl: string, shouldDeploy: bo '🍸 Adding gin and lime juice...', `🍸 Topping with ${chalk.blue('Tonik')}...`, '🍸 Garnishing with lime wedge...', - `🍸 ${chalk.green(`Your Stapled ${name} is ready!`)}`, - `🍸 Ready to explore? Jump into your project with: ${chalk.cyan(`cd ${name} && pnpm dev`)}`, + `🍸 ${chalk.hex(CHECK_MARK_COLOR)(`Your Stapled ${chalk.hex(CHECK_MARK_COLOR)(name)} is ready!`)}`, + `🍸 Ready to explore? Jump into your project with: ${chalk.hex(CHECK_MARK_COLOR)(`cd ${name} && pnpm dev`)}`, ]; if (shouldDeploy) { - messages.push(`🍸 Prefer to see it online? Check it out here: ${chalk.cyan(prettyDeploymentUrl)}`); + messages.push(`🍸 Prefer to see it online? Check it out here: ${chalk.hex(CHECK_MARK_COLOR)(prettyDeploymentUrl)}`); } else { messages.push('🍸 Want to deploy your project? Run `stplr` within your project directory.'); } diff --git a/packages/core/installMachine/installSteps/supabase/connectProject.ts b/packages/core/installMachine/installSteps/supabase/connectProject.ts index 8ec207c..f3dc913 100644 --- a/packages/core/installMachine/installSteps/supabase/connectProject.ts +++ b/packages/core/installMachine/installSteps/supabase/connectProject.ts @@ -1,12 +1,12 @@ -import { execSync } from 'child_process'; +import boxen, { Options } from 'boxen'; import chalk from 'chalk'; -import boxen from 'boxen'; -import { getSupabaseKeys, parseProjectsList } from './utils'; -import { logger } from 'stplr-utils'; -import { getVercelTokenFromAuthFile } from '../../../utils/getVercelTokenFromAuthFile'; -import { getDataFromVercelConfig } from '../../../utils/getDataFromVercelConfig'; -import { execAsync } from '../../../utils/execAsync'; +import { execSync } from 'child_process'; +import { BOXEN_SETTINGS, LABEL_SECONDARY_TEXT_COLOR, logger } from 'stplr-utils'; import { delay } from '../../../utils/delay'; +import { execAsync } from '../../../utils/execAsync'; +import { getDataFromVercelConfig } from '../../../utils/getDataFromVercelConfig'; +import { getVercelTokenFromAuthFile } from '../../../utils/getVercelTokenFromAuthFile'; +import { getSupabaseKeys, parseProjectsList } from './utils'; export const connectSupabaseProject = async (projectName: string, currentDir: string) => { try { @@ -53,19 +53,14 @@ export const connectSupabaseProject = async (projectName: string, currentDir: st console.log( boxen( chalk.bold('Supabase Integration Setup\n\n') + - chalk.hex('#259764')('1.') + + chalk.hex(LABEL_SECONDARY_TEXT_COLOR)('1.') + ' You will be redirected to your project dashboard\n' + - chalk.hex('#259764')('2.') + + chalk.hex(LABEL_SECONDARY_TEXT_COLOR)('2.') + ' Connect Vercel: "Add new project connection"\n' + - chalk.hex('#259764')('3.') + + chalk.hex(LABEL_SECONDARY_TEXT_COLOR)('3.') + ' (Optional) Connect GitHub: "Add new project connection"\n\n' + chalk.dim('Tip: Keep this terminal open to track the integration status'), - { - padding: 1, - margin: 1, - borderStyle: 'round', - borderColor: '#3ABC82', - }, + BOXEN_SETTINGS as Options, ), ); diff --git a/packages/core/installMachine/installSteps/vercel/chooseTeam.ts b/packages/core/installMachine/installSteps/vercel/chooseTeam.ts index c3f676d..95a0203 100644 --- a/packages/core/installMachine/installSteps/vercel/chooseTeam.ts +++ b/packages/core/installMachine/installSteps/vercel/chooseTeam.ts @@ -1,18 +1,14 @@ -import { execSync } from 'child_process'; -import boxen from 'boxen'; +import boxen, { Options } from 'boxen'; import chalk from 'chalk'; +import { execSync } from 'child_process'; +import { BOXEN_SETTINGS } from 'stplr-utils'; export const chooseVercelTeam = async () => { console.log( boxen( chalk.bold('Choose a Vercel team to link your project to.\n\n') + 'If you are not sure, you can skip this step by choosing "Cancel". This will link the project to the current logged-in user.', - { - padding: 1, - margin: 1, - borderStyle: 'round', - borderColor: '#FFFFFF', - }, + BOXEN_SETTINGS as Options, ), ); diff --git a/packages/utils/logger/logger.ts b/packages/utils/logger/logger.ts index 68cab41..1dc5599 100644 --- a/packages/utils/logger/logger.ts +++ b/packages/utils/logger/logger.ts @@ -6,12 +6,21 @@ export const SPACING = 2; export const LABEL_WIDTH = 9; export const LABEL_BG_COLOR = '#FAD400'; export const LABEL_TEXT_COLOR = '#000000'; +export const LABEL_SECONDARY_TEXT_COLOR = '#FAD400'; export const DIMMED_COLOR = '#5C4D00'; export const CHECK_MARK_COLOR = '#FAD400'; export const ACTIVE_TEXT_COLOR = '#FFFFFF'; export const COMPLETED_TEXT_COLOR = '#666666'; +export const BORDER_COLOR = '#FAD400'; export const LEFT_PADDING = ' '.repeat(SPACING + LABEL_WIDTH - 1); +export const QUESTION_MARK = chalk.hex(CHECK_MARK_COLOR)('?'); +export const BOXEN_SETTINGS = { + padding: 1, + margin: { left: 11, right: 11, top: 1, bottom: 1 }, + borderStyle: 'round', + borderColor: BORDER_COLOR, +}; type Name = | 'dir' diff --git a/patches/ansi-colors@4.1.3.patch b/patches/ansi-colors@4.1.3.patch new file mode 100644 index 0000000..b3c01ec --- /dev/null +++ b/patches/ansi-colors@4.1.3.patch @@ -0,0 +1,30 @@ +diff --git a/symbols.js b/symbols.js +index 02ab25799d8846be9861569f4edf5295dd6247ae..b8ed609981a538e940c63f0ded65ba2c527bc23c 100644 +--- a/symbols.js ++++ b/symbols.js +@@ -33,6 +33,7 @@ const common = { + upDownArrow: '↕' + }; + ++ + const windows = Object.assign({}, common, { + check: '√', + cross: '×', +@@ -47,6 +48,7 @@ const windows = Object.assign({}, common, { + warning: '‼' + }); + ++const hex = (color) => `\x1b[38;2;${parseInt(color.slice(1, 3), 16)};${parseInt(color.slice(3, 5), 16)};${parseInt(color.slice(5, 7), 16)}m`; + const other = Object.assign({}, common, { + ballotCross: '✘', + check: '✔', +@@ -56,7 +58,8 @@ const other = Object.assign({}, common, { + info: 'ℹ', + questionFull: '?', + questionSmall: '﹖', +- pointer: isLinux ? '▸' : '❯', ++ pointer: isLinux ? '▸' : ` ${hex('#FAD400')}❯ \x1b[0m` ++ , + pointerSmall: isLinux ? '‣' : '›', + radioOff: '◯', + radioOn: '◉', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ae283ec..946125f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,11 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +patchedDependencies: + ansi-colors@4.1.3: + hash: vgudubycctmxevfq2gz364c7qu + path: patches/ansi-colors@4.1.3.patch + importers: .: @@ -25,7 +30,7 @@ importers: version: 2.1.3(eslint@8.57.1) eslint-import-resolver-alias: specifier: ^1.1.2 - version: 1.1.2(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.8.1(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)) + version: 1.1.2(eslint-plugin-import@2.31.0) eslint-import-resolver-node: specifier: ^0.3.9 version: 0.3.9 @@ -34,7 +39,7 @@ importers: version: 3.6.3(@typescript-eslint/parser@8.8.1(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) eslint-module-utils: specifier: ^2.8.0 - version: 2.12.0(@typescript-eslint/parser@8.8.1(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.8.1(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) + version: 2.12.0(@typescript-eslint/parser@8.8.1(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) eslint-plugin-eslint-comments: specifier: ^3.2.0 version: 3.2.0(eslint@8.57.1) @@ -111,6 +116,12 @@ importers: packages/cli: dependencies: + ansi-colors: + specifier: 4.1.3 + version: 4.1.3(patch_hash=vgudubycctmxevfq2gz364c7qu) + boxen: + specifier: ^8.0.1 + version: 8.0.1 chalk: specifier: ^5.3.0 version: 5.3.0 @@ -3134,7 +3145,7 @@ snapshots: '@changesets/types': 6.0.0 '@changesets/write': 0.3.2 '@manypkg/get-packages': 1.1.3 - ansi-colors: 4.1.3 + ansi-colors: 4.1.3(patch_hash=vgudubycctmxevfq2gz364c7qu) ci-info: 3.9.0 enquirer: 2.4.1 external-editor: 3.1.0 @@ -3884,7 +3895,7 @@ snapshots: dependencies: string-width: 4.2.3 - ansi-colors@4.1.3: {} + ansi-colors@4.1.3(patch_hash=vgudubycctmxevfq2gz364c7qu): {} ansi-escapes@4.3.2: dependencies: @@ -4238,7 +4249,7 @@ snapshots: enquirer@2.4.1: dependencies: - ansi-colors: 4.1.3 + ansi-colors: 4.1.3(patch_hash=vgudubycctmxevfq2gz364c7qu) strip-ansi: 6.0.1 error-ex@1.3.2: @@ -4437,7 +4448,7 @@ snapshots: eslint: 8.57.1 eslint-plugin-turbo: 2.1.3(eslint@8.57.1) - eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.8.1(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)): + eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.31.0): dependencies: eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.8.1(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) @@ -4455,7 +4466,7 @@ snapshots: debug: 4.3.7 enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.8.1(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.8.1(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.8.1(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 @@ -4468,7 +4479,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.8.1(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.8.1(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.8.1(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: @@ -4496,7 +4507,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.8.1(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.8.1(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.8.1(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3