Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 14 additions & 12 deletions packages/cli/command-prompts/getProjectNamePrompt.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import inquirer from 'inquirer';
import Enquirer from 'enquirer';
import { LEFT_PADDING } from 'stplr-utils';

export const getProjectNamePrompt = async (): Promise<string> =>
(
await inquirer.prompt([
{
type: 'input',
name: 'name',
message: 'What is your project named?',
default: 'my-stapled-app',
},
])
).name;
export const getProjectNamePrompt = async (): Promise<string> => {
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;
};
24 changes: 14 additions & 10 deletions packages/cli/command-prompts/overwriteDirectoryPrompt.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
};
24 changes: 14 additions & 10 deletions packages/cli/command-prompts/shouldUsePayloadPrompt.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -8,12 +9,15 @@ import inquirer from 'inquirer';
*
**/

export const shouldUsePayloadPrompt = async (): Promise<{ usePayload: boolean }> =>
await inquirer.prompt([
{
type: 'confirm',
name: 'usePayload',
message: 'Would you like to add Payload to your app?',
default: true,
},
]);
export const shouldUsePayloadPrompt = async (): Promise<{ usePayload: boolean }> => {
const payloadEnquirer = new Enquirer();
const response = (await payloadEnquirer.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;
};
82 changes: 64 additions & 18 deletions packages/cli/command-prompts/unfinishedProjectsChoicePrompt.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import inquirer from 'inquirer';
import Enquirer from 'enquirer';

import { ProjectChoice, UnfinishedProject } from '../utils/findUnfinishedProjects';
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;
};
Expand All @@ -18,21 +26,59 @@ export type UnfinishedProjectsChoiceAnswers = {
export const unfinishedProjectsChoice = async (
unfinishedProjects: UnfinishedProject[],
projectChoices: ProjectChoice[],
): Promise<UnfinishedProjectsChoiceAnswers> =>
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<UnfinishedProjectsChoiceResponse> => {
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;
};
18 changes: 8 additions & 10 deletions packages/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,9 @@
import fs from 'fs';
import chalk from 'chalk';
import { Command } from 'commander';
import { logger } from 'stplr-utils';
import { createProject } from 'stplr-core';
import {
checkAuthentication,
checkTools,
displayHeader,
findUnfinishedProjects,
getProjectChoices,
UnfinishedProject,
} from './utils';
import { checkAuthentication, checkTools, findUnfinishedProjects, getProjectChoices, UnfinishedProject } from './utils';
import {
getProjectNamePrompt,
overwriteDirectoryPrompt,
Expand All @@ -33,7 +27,7 @@ program
)
.version('0.1.0')
.hook('preAction', () => {
displayHeader();
logger.displayHeader();
})
.option('-n, --name <name>', 'Set the name of the project')
.option(
Expand All @@ -53,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);
Expand Down Expand Up @@ -104,15 +99,18 @@ 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.log(chalk.yellow(`The directory "${projectName}" has been cleared.`));
}

// Skip Payload if specified by the flag
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();
}

Expand Down
5 changes: 3 additions & 2 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand Down
14 changes: 14 additions & 0 deletions packages/core/installMachine/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => {
Expand Down Expand Up @@ -349,6 +350,7 @@ const createInstallMachine = (initialContext: InstallMachineContext) => {
createTurboActor: createStepMachine(
fromPromise<void, InstallMachineContext, AnyEventObject>(async ({ input }) => {
try {
logger.withLabel('turborepo', 'Creating Turbo project');
await createTurbo(input.stateData.options.name);
process.chdir(input.projectDir);
input.stateData.stepsCompleted.createTurbo = true;
Expand All @@ -362,6 +364,7 @@ const createInstallMachine = (initialContext: InstallMachineContext) => {
modifyGitignoreActor: createStepMachine(
fromPromise<void, InstallMachineContext, AnyEventObject>(async ({ input }) => {
try {
logger.withLabel('git', 'Modifying .gitignore');
await modifyGitignore('.initializeRcFile');
input.stateData.stepsCompleted.modifyGitignore = true;
saveStateToRcFile(input.stateData, input.projectDir);
Expand All @@ -374,6 +377,7 @@ const createInstallMachine = (initialContext: InstallMachineContext) => {
installTailwindActor: createStepMachine(
fromPromise<void, InstallMachineContext, AnyEventObject>(async ({ input }) => {
try {
logger.withLabel('tailwind', 'Installing Tailwind CSS');
const currentDir = process.cwd();
await installTailwind(currentDir);
input.stateData.stepsCompleted.installTailwind = true;
Expand All @@ -399,6 +403,7 @@ const createInstallMachine = (initialContext: InstallMachineContext) => {
installSupabaseActor: createStepMachine(
fromPromise<void, InstallMachineContext, AnyEventObject>(async ({ input }) => {
try {
logger.withLabel('supabase', 'Installing Supabase');
const currentDir = process.cwd();
await installSupabase(currentDir);
input.stateData.stepsCompleted.installSupabase = true;
Expand All @@ -412,6 +417,7 @@ const createInstallMachine = (initialContext: InstallMachineContext) => {
installPayloadActor: createStepMachine(
fromPromise<void, InstallMachineContext, AnyEventObject>(async ({ input }) => {
try {
logger.withLabel('payload', 'Preparing Payload');
await preparePayload();
input.stateData.stepsCompleted.installPayload = true;
saveStateToRcFile(input.stateData, input.projectDir);
Expand All @@ -424,6 +430,7 @@ const createInstallMachine = (initialContext: InstallMachineContext) => {
createDocFilesActor: createStepMachine(
fromPromise<void, InstallMachineContext, AnyEventObject>(async ({ input }) => {
try {
logger.withLabel('stapler', 'Creating documentation files');
await createDocFiles();
input.stateData.stepsCompleted.createDocFiles = true;
saveStateToRcFile(input.stateData, input.projectDir);
Expand All @@ -436,6 +443,7 @@ const createInstallMachine = (initialContext: InstallMachineContext) => {
prettifyCodeActor: createStepMachine(
fromPromise<void, InstallMachineContext, AnyEventObject>(async ({ input }) => {
try {
logger.withLabel('prettier', 'Prettifying code');
await prettify();
input.stateData.stepsCompleted.prettifyCode = true;
saveStateToRcFile(input.stateData, input.projectDir);
Expand All @@ -459,6 +467,7 @@ const createInstallMachine = (initialContext: InstallMachineContext) => {
initializeRepositoryActor: createStepMachine(
fromPromise<void, InstallMachineContext, AnyEventObject>(async ({ input }) => {
try {
logger.withLabel('github', 'Initializing GitHub repository');
await initializeRepository({
projectName: input.stateData.options.name,
stateData: input.stateData,
Expand Down Expand Up @@ -486,6 +495,7 @@ const createInstallMachine = (initialContext: InstallMachineContext) => {
createSupabaseProjectActor: createStepMachine(
fromPromise<void, InstallMachineContext, AnyEventObject>(async ({ input }) => {
try {
logger.withLabel('supabase', 'Creating Supabase project');
await createSupabaseProject(input.stateData.githubCandidateName);
input.stateData.stepsCompleted.createSupabaseProject = true;
saveStateToRcFile(input.stateData, input.projectDir);
Expand All @@ -498,6 +508,7 @@ const createInstallMachine = (initialContext: InstallMachineContext) => {
chooseVercelTeamActor: createStepMachine(
fromPromise<void, InstallMachineContext, AnyEventObject>(async ({ input }) => {
try {
logger.withLabel('vercel', 'Managing your Vercel');
await chooseVercelTeam();
input.stateData.stepsCompleted.chooseVercelTeam = true;
saveStateToRcFile(input.stateData, input.projectDir);
Expand Down Expand Up @@ -534,6 +545,7 @@ const createInstallMachine = (initialContext: InstallMachineContext) => {
connectSupabaseProjectActor: createStepMachine(
fromPromise<void, InstallMachineContext, AnyEventObject>(async ({ input }) => {
try {
logger.withLabel('supabase', 'Connecting Supabase project');
const currentDir = process.cwd();
await connectSupabaseProject(input.stateData.githubCandidateName, currentDir);
input.stateData.stepsCompleted.connectSupabaseProject = true;
Expand All @@ -547,6 +559,7 @@ const createInstallMachine = (initialContext: InstallMachineContext) => {
deployVercelProjectActor: createStepMachine(
fromPromise<void, InstallMachineContext, AnyEventObject>(async ({ input }) => {
try {
logger.withLabel('vercel', 'Deploying Vercel project');
await deployVercelProject(input.stateData);
input.stateData.stepsCompleted.deployVercelProject = true;
saveStateToRcFile(input.stateData, input.projectDir);
Expand All @@ -559,6 +572,7 @@ const createInstallMachine = (initialContext: InstallMachineContext) => {
prepareDrinkActor: createStepMachine(
fromPromise<void, InstallMachineContext, AnyEventObject>(async ({ input }) => {
try {
logger.withLabel('stapler', 'Preparing your drink');
const {
projectName,
prettyDeploymentUrl,
Expand Down
Original file line number Diff line number Diff line change
@@ -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) => {
await logger.withSpinner('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();
Expand Down
Loading