diff --git a/src/commands/create.ts b/src/commands/create.ts index f9f41cf1..f9ba617c 100644 --- a/src/commands/create.ts +++ b/src/commands/create.ts @@ -16,7 +16,12 @@ import { PYTHON_VENV_PATH, SUPPORTED_NODEJS_VERSION, } from '../lib/consts.js'; -import { enhanceReadmeWithLocalSuffix, ensureValidActorName, getTemplateDefinition } from '../lib/create-utils.js'; +import { + enhanceReadmeWithLocalSuffix, + ensureValidActorName, + formatCreateSuccessMessage, + getTemplateDefinition, +} from '../lib/create-utils.js'; import { execWithLog } from '../lib/exec.js'; import { updateLocalJson } from '../lib/files.js'; import { usePythonRuntime } from '../lib/hooks/runtimes/python.js'; @@ -329,29 +334,58 @@ export class CreateCommand extends ApifyCommand { } } - if (dependenciesInstalled) { - success({ message: `Actor '${actorName}' was created. To run it, run "cd ${actorName}" and "apify run".` }); - info({ message: 'To run your code in the cloud, run "apify push" and deploy your code to Apify Console.' }); - if (messages?.postCreate) { - info({ message: messages?.postCreate }); - } - } else { - success({ - message: `Actor '${actorName}' was created. Please install its dependencies to be able to run it using "apify run".`, + // Suggest install command if dependencies were not installed + let installCommandSuggestion: string | null = null; + if (!dependenciesInstalled) { + const projectInfo = await useCwdProject({ cwd: actFolderDir }); + await projectInfo.inspectAsync(async (project) => { + if (project.type === ProjectLanguage.JavaScript) { + const hasYarnLock = await stat(join(actFolderDir, 'yarn.lock')) + .then(() => true) + .catch(() => false); + const hasPnpmLock = await stat(join(actFolderDir, 'pnpm-lock.yaml')) + .then(() => true) + .catch(() => false); + const hasBunLock = await stat(join(actFolderDir, 'bun.lockb')) + .then(() => true) + .catch(() => false); + if (hasYarnLock) { + installCommandSuggestion = 'yarn install'; + } else if (hasPnpmLock) { + installCommandSuggestion = 'pnpm install'; + } else if (hasBunLock) { + installCommandSuggestion = 'bun install'; + } else if (project.runtime?.pmName === 'bun') { + installCommandSuggestion = 'bun install'; + } else if (project.runtime?.pmName === 'deno') { + installCommandSuggestion = 'deno install --node-modules-dir'; + } else { + installCommandSuggestion = 'npm install'; + } + } else if (project.type === ProjectLanguage.Python || project.type === ProjectLanguage.Scrapy) { + installCommandSuggestion = 'python -m pip install -r requirements.txt'; + } }); } - // Report git initialization result after actor creation success - if (!skipGitInit && !cwdHasGit) { - if (gitInitResult.success) { - info({ - message: `Git repository initialized in '${actorName}'. You can now commit and push your Actor to Git.`, - }); - } else { - // Git init is not critical, so we just warn if it fails - warning({ message: `Failed to initialize git repository: ${gitInitResult.error!.message}` }); - warning({ message: 'You can manually run "git init" in the Actor directory if needed.' }); - } + success( + { + message: formatCreateSuccessMessage({ + actorName, + dependenciesInstalled, + postCreate: messages?.postCreate ?? null, + gitRepositoryInitialized: !skipGitInit && !cwdHasGit && gitInitResult.success, + installCommandSuggestion, + }), + }, + true, + ); + + // Report git initialization result only if it failed (success already included in success message) + if (!skipGitInit && !cwdHasGit && !gitInitResult.success) { + // Git init is not critical, so we just warn if it fails + warning({ message: `Failed to initialize git repository: ${gitInitResult.error!.message}` }); + warning({ message: 'You can manually run "git init" in the Actor directory if needed.' }); } } } diff --git a/src/lib/create-utils.ts b/src/lib/create-utils.ts index b5b45560..a5a9fe53 100644 --- a/src/lib/create-utils.ts +++ b/src/lib/create-utils.ts @@ -2,7 +2,6 @@ import { createWriteStream } from 'node:fs'; import { pipeline } from 'node:stream/promises'; import { Separator } from '@inquirer/core'; -import chalk from 'chalk'; import type { Manifest, Template } from '@apify/actor-templates'; @@ -66,6 +65,37 @@ export async function enhanceReadmeWithLocalSuffix(readmePath: string, manifestP } } +export function formatCreateSuccessMessage(params: { + actorName: string; + dependenciesInstalled: boolean; + postCreate?: string | null; + gitRepositoryInitialized?: boolean; + installCommandSuggestion?: string | null; +}) { + const { actorName, dependenciesInstalled, postCreate, gitRepositoryInitialized, installCommandSuggestion } = params; + + let message = `āœ… Actor '${actorName}' created successfully!`; + + if (dependenciesInstalled) { + message += `\n\nNext steps:\n\ncd '${actorName}'\napify run`; + } else { + const installLine = installCommandSuggestion || 'install dependencies'; + message += `\n\nNext steps:\n\ncd '${actorName}'\n${installLine}\napify run`; + } + + message += `\n\nšŸ’” Tip: Use 'apify push' to deploy your Actor to the Apify platform\nšŸ“– Docs: https://docs.apify.com/platform/actors/development`; + + if (gitRepositoryInitialized) { + message += `\n🌱 Git repository initialized in '${actorName}'. You can now commit and push your Actor to Git.`; + } + + if (postCreate) { + message += `\n\n${postCreate}`; + } + + return message; +} + /** * Inquirer does not have a native way to "go back" between prompts. */ @@ -75,7 +105,7 @@ async function executePrompts(manifest: Manifest) { while (true) { const templateDefinition = await promptTemplateDefinition(manifest, programmingLanguage); if (templateDefinition) { - const shouldInstall = await promptTemplateInstallation(templateDefinition); + const shouldInstall = await promptTemplateInstallation(); if (shouldInstall) { return templateDefinition; } @@ -134,8 +164,7 @@ async function promptTemplateDefinition(manifest: Manifest, programmingLanguage: ]; const templateDefinition = await useSelectFromList({ - message: - 'Choose a template for your new Actor. Detailed information about the template will be shown in the next step.', + message: 'Choose a template for your new Actor. You can check more information at https://apify.com/templates.', default: choices[0], choices, loop: false, @@ -145,20 +174,15 @@ async function promptTemplateDefinition(manifest: Manifest, programmingLanguage: return templateDefinition; } -async function promptTemplateInstallation(templateDefinition: Template) { +async function promptTemplateInstallation() { const choices: ChoicesType = [ - { name: `Install template`, value: true }, + { name: `Install dependencies`, value: true }, new Separator(), { name: 'Go back', value: false }, ]; - const label = chalk.underline(templateDefinition.label); - const description = chalk.dim(templateDefinition.description); - const suffix = `\n ${label}:\n ${description}`; - const message = `Do you want to install the following template?${suffix}`; - const answer = await useSelectFromList({ - message, + message: 'Almost done! Last step is to install dependencies.', default: choices[0], choices, loop: false, diff --git a/src/lib/outputs.ts b/src/lib/outputs.ts index 33e58cbb..e364fe34 100644 --- a/src/lib/outputs.ts +++ b/src/lib/outputs.ts @@ -38,9 +38,12 @@ export function warning(options: SimpleLogOptions) { }); } -export function success(options: SimpleLogOptions) { +export function success(options: SimpleLogOptions, newLine = false) { internalLog({ - [options.stdout ? 'stdoutOutput' : 'stderrOutput']: [chalk.green('Success:'), options.message], + [options.stdout ? 'stdoutOutput' : 'stderrOutput']: [ + chalk.green(`${newLine ? '\n' : ''}Success:`), + options.message, + ], }); }