From ae95b609468d5a311a0efcda1faa1a1a93667859 Mon Sep 17 00:00:00 2001 From: bryantgillespie Date: Wed, 13 May 2026 16:48:54 -0400 Subject: [PATCH] fix: respect explicit init boolean flags --- src/commands/init.ts | 207 ++++++++++++++++++++++--------------------- 1 file changed, 106 insertions(+), 101 deletions(-) diff --git a/src/commands/init.ts b/src/commands/init.ts index e22d6a5..b45ac7f 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -1,5 +1,5 @@ import {cancel, log as clackLog, confirm, intro, isCancel, select, text} from '@clack/prompts' -import {Args, Flags, ux} from '@oclif/core' +import {Args, Flags} from '@oclif/core' import chalk from 'chalk' import {downloadTemplate} from 'giget' import fs from 'node:fs' @@ -12,7 +12,7 @@ import {init} from '../lib/init/index.js' import {animatedBunny} from '../lib/utils/animated-bunny.js' import {createGigetString, parseGitHubUrl} from '../lib/utils/parse-github-url.js' import {readTemplateConfig} from '../lib/utils/template-config.js' -import {createGitHub} from '../services/github.js' +import {createGitHub, type TemplateInfo} from '../services/github.js' import { shutdown, track } from '../services/posthog.js' import { BaseCommand } from './base.js' @@ -29,6 +29,11 @@ export interface InitArgs { directory: string } +interface ExplicitInitFlags { + gitInit: boolean + installDeps: boolean +} + export default class InitCommand extends BaseCommand { static args = { directory: Args.directory({ @@ -78,95 +83,80 @@ private targetDir = '.' * @returns Promise that resolves when the command is complete. */ public async run(): Promise { - const {args, flags} = await this.parse(InitCommand) + const {args, flags, metadata} = await this.parse(InitCommand) const typedFlags = flags as InitFlags const typedArgs = args as InitArgs + const explicitFlags: ExplicitInitFlags = { + gitInit: !metadata.flags.gitInit?.setFromDefault, + installDeps: !metadata.flags.installDeps?.setFromDefault, + } // Set the target directory and create it if it doesn't exist this.targetDir = path.resolve(args.directory as string) - await this.runInteractive(typedFlags, typedArgs) + await this.runInteractive(typedFlags, typedArgs, explicitFlags) } - /** - * Interactive mode: prompts the user for each piece of info, with added template checks. - * @param flags - The flags passed to the command. - * @param args - The arguments passed to the command. - * @returns void - */ - private async runInteractive(flags: InitFlags, args: InitArgs): Promise { - - // Show animated intro - await animatedBunny('Let\'s create a new Directus project!') - intro(`${chalk.bgHex(DIRECTUS_PURPLE).white.bold('Directus Template CLI')} - Create Project`) - - // Check Docker availability before proceeding - const {createDocker} = await import('../services/docker.js') - const {DOCKER_CONFIG} = await import('../lib/init/config.js') - const dockerService = createDocker(DOCKER_CONFIG) - const dockerStatus = await dockerService.checkDocker() + private async confirmBooleanFlag(message: string): Promise { + const response = await confirm({ + initialValue: true, + message, + }) - if (!dockerStatus.installed || !dockerStatus.running) { - cancel(dockerStatus.message || 'Docker is required to initialize a Directus project.') - process.exit(1) + if (isCancel(response)) { + cancel('Project creation cancelled.') + process.exit(0) } - // Create GitHub service - const github = createGitHub() - - // If no dir is provided, ask for it - if (!args.directory || args.directory === '.') { - let dirResponse = await text({ - message: 'Enter the directory to create the project in:', - placeholder: './my-directus-project', - }) + return response as boolean + } + private async confirmOverwriteDirectory(flags: InitFlags): Promise { + if (!fs.existsSync(this.targetDir) || flags.overwriteDir) return Boolean(flags.overwriteDir) - if (isCancel(dirResponse)) { - cancel('Project creation cancelled.') - process.exit(0) - } - - // If there's no response, set a default - if (!dirResponse) { - clackLog.warn('No directory provided, using default: ./my-directus-project') - dirResponse = './my-directus-project' - } + const overwriteDirResponse = await confirm({ + initialValue: false, + message: 'Directory already exists. Would you like to overwrite it?', + }) - this.targetDir = dirResponse as string + if (isCancel(overwriteDirResponse) || overwriteDirResponse === false) { + cancel('Project creation cancelled.') + process.exit(0) } - if (fs.existsSync(this.targetDir) && !flags.overwriteDir) { - const overwriteDirResponse = await confirm({ - initialValue: false, - message: 'Directory already exists. Would you like to overwrite it?', - }) + return true + } - if (isCancel(overwriteDirResponse) || overwriteDirResponse === false) { - cancel('Project creation cancelled.') - process.exit(0) - } + private async promptForTargetDirectory(args: InitArgs): Promise { + if (args.directory && args.directory !== '.') return - if (overwriteDirResponse) { - flags.overwriteDir = true - } - } + let dirResponse = await text({ + message: 'Enter the directory to create the project in:', + placeholder: './my-directus-project', + }) - // 1. Fetch available templates (now returns Array<{id: string, name: string, description?: string}>) - const availableTemplates = await github.getTemplates() + if (isCancel(dirResponse)) { + cancel('Project creation cancelled.') + process.exit(0) + } - // 2. Prompt for template if not provided - let {template} = flags // This will store the chosen template ID - let chosenTemplateObject: undefined | { description?: string; id: string; name: string; }; + // If there's no response, set a default + if (!dirResponse) { + clackLog.warn('No directory provided, using default: ./my-directus-project') + dirResponse = './my-directus-project' + } + this.targetDir = dirResponse as string + } + private async promptForValidTemplate(template: string | undefined, availableTemplates: TemplateInfo[]): Promise { if (!template) { - const templateResponse = await select({ // Explicit types for clarity + const templateResponse = await select({ message: 'Which Directus backend template would you like to use?', options: availableTemplates.map(tmpl => ({ - hint: tmpl.description, // Show the description as a hint - label: tmpl.name, // Display the friendly name - value: tmpl.id, // The value submitted will be the ID (directory name) + hint: tmpl.description, + label: tmpl.name, + value: tmpl.id, })), }) @@ -178,16 +168,9 @@ private targetDir = '.' template = templateResponse } - // Find the chosen template object for potential future use (e.g., display name later) - chosenTemplateObject = availableTemplates.find(t => t.id === template); - - // 3. Validate that the template exists in the available list - const isDirectUrl = template?.startsWith('http') - - // Validate against the 'id' property of the template objects - while (!isDirectUrl && !availableTemplates.some(t => t.id === template)) { - // Keep the warning message simple or refer back to the list shown in the prompt + while (!template.startsWith('http') && !availableTemplates.some(t => t.id === template)) { clackLog.warn(`Template ID "${template}" is not valid. Please choose from the list provided or enter a direct GitHub URL.`) + // eslint-disable-next-line no-await-in-loop const templateNameResponse = await text({ message: 'Please enter a valid template ID, a direct GitHub URL, or Ctrl+C to cancel:', }) @@ -198,9 +181,47 @@ private targetDir = '.' } template = templateNameResponse as string - chosenTemplateObject = availableTemplates.find(t => t.id === template); // Update chosen object after re-entry } + return template + } + + /** + * Interactive mode: prompts the user for each piece of info, with added template checks. + * @param flags - The flags passed to the command. + * @param args - The arguments passed to the command. + * @returns void + */ + private async runInteractive(flags: InitFlags, args: InitArgs, explicitFlags: ExplicitInitFlags): Promise { + + // Show animated intro + await animatedBunny('Let\'s create a new Directus project!') + intro(`${chalk.bgHex(DIRECTUS_PURPLE).white.bold('Directus Template CLI')} - Create Project`) + + // Check Docker availability before proceeding + const {createDocker} = await import('../services/docker.js') + const {DOCKER_CONFIG} = await import('../lib/init/config.js') + const dockerService = createDocker(DOCKER_CONFIG) + const dockerStatus = await dockerService.checkDocker() + + if (!dockerStatus.installed || !dockerStatus.running) { + cancel(dockerStatus.message || 'Docker is required to initialize a Directus project.') + process.exit(1) + } + + // Create GitHub service + const github = createGitHub() + + // If no dir is provided, ask for it + await this.promptForTargetDirectory(args) + const overwriteDir = await this.confirmOverwriteDirectory(flags) + + // 1. Fetch available templates (now returns Array<{id: string, name: string, description?: string}>) + const availableTemplates = await github.getTemplates() + + // 2. Prompt for template if not provided, then validate it against known templates or a direct URL. + const template = await this.promptForValidTemplate(flags.template, availableTemplates) + flags.template = template // Ensure the flag stores the ID // Download the template to a temporary directory to read its configuration @@ -217,7 +238,7 @@ private targetDir = '.' const templateInfo = readTemplateConfig(tempDir) // 4. If template has frontends and user hasn't specified a valid one, ask from the list - if (templateInfo?.frontendOptions.length > 0 && (!chosenFrontend || !templateInfo.frontendOptions.find(f => f.id === chosenFrontend))) { + if (templateInfo?.frontendOptions.length > 0 && (!chosenFrontend || !templateInfo.frontendOptions.some(f => f.id === chosenFrontend))) { const frontendResponse = await select({ message: 'Which frontend framework do you want to use?', options: @@ -245,29 +266,13 @@ private targetDir = '.' } } - const installDepsResponse = await confirm({ - initialValue: true, - message: 'Would you like to install project dependencies automatically?', - }) - - if (isCancel(installDepsResponse)) { - cancel('Project creation cancelled.') - process.exit(0) - } - - const installDeps = installDepsResponse as boolean - - const initGitResponse = await confirm({ - initialValue: true, - message: 'Initialize a new Git repository?', - }) - - if (isCancel(initGitResponse)) { - cancel('Project creation cancelled.') - process.exit(0) - } + const installDeps = explicitFlags.installDeps + ? flags.installDeps ?? true + : await this.confirmBooleanFlag('Would you like to install project dependencies automatically?') - const initGit = initGitResponse as boolean + const initGit = explicitFlags.gitInit + ? flags.gitInit ?? true + : await this.confirmBooleanFlag('Initialize a new Git repository?') // Track the command start unless telemetry is disabled if (!flags.disableTelemetry) { @@ -293,7 +298,7 @@ private targetDir = '.' frontend: chosenFrontend, gitInit: initGit, installDeps, - overwriteDir: flags.overwriteDir, + overwriteDir, template, }, @@ -309,7 +314,7 @@ private targetDir = '.' frontend: chosenFrontend, gitInit: initGit, installDeps, - overwriteDir: flags.overwriteDir, + overwriteDir, template, }, lifecycle: 'complete',