Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
76 changes: 55 additions & 21 deletions src/commands/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -329,29 +334,58 @@ export class CreateCommand extends ApifyCommand<typeof CreateCommand> {
}
}

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.' });
}
}
}
48 changes: 36 additions & 12 deletions src/lib/create-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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.
*/
Expand All @@ -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;
}
Expand Down Expand Up @@ -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,
Expand All @@ -145,20 +174,15 @@ async function promptTemplateDefinition(manifest: Manifest, programmingLanguage:
return templateDefinition;
}

async function promptTemplateInstallation(templateDefinition: Template) {
async function promptTemplateInstallation() {
const choices: ChoicesType<boolean> = [
{ 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<boolean>({
message,
message: 'Almost done! Last step is to install dependencies.',
default: choices[0],
choices,
loop: false,
Expand Down
7 changes: 5 additions & 2 deletions src/lib/outputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
],
});
}

Expand Down
Loading