Skip to content
Draft
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
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@
"engines": {
"node": ">=20"
},
"imports": {
"#i18n/*.js": "./src/i18n/*.ts"
},
"dependencies": {
"@apify/actor-memory-expression": "^0.1.12",
"@apify/actor-templates": "^0.1.5",
Expand Down Expand Up @@ -101,6 +104,7 @@
"handlebars": "~4.7.9",
"ignore": "^7.0.5",
"indent-string": "^5.0.0",
"intl-messageformat": "^11.2.1",
"is-ci": "~4.1.0",
"istextorbinary": "~9.5.0",
"jju": "~1.4.0",
Expand Down
1,629 changes: 967 additions & 662 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

37 changes: 29 additions & 8 deletions src/commands/actor/calculate-memory.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { join, resolve } from 'node:path';
import process from 'node:process';

import { ActorCalculateMemoryCommandMessages } from '#i18n/commands/actor/calculate-memory.js';

import { calculateRunDynamicMemory } from '@apify/actor-memory-expression';

import { ApifyCommand } from '../../lib/command-framework/apify-command.js';
import { Flags } from '../../lib/command-framework/flags.js';
import { CommandExitCodes } from '../../lib/consts.js';
import { useActorConfig } from '../../lib/hooks/useActorConfig.js';
import { error, info, success } from '../../lib/outputs.js';
import { getJsonFileContent, getLocalKeyValueStorePath } from '../../lib/utils.js';

const DEFAULT_INPUT_PATH = join(getLocalKeyValueStorePath('default'), 'INPUT.json');
Expand Down Expand Up @@ -84,15 +85,13 @@ export class ActorCalculateMemoryCommand extends ApifyCommand<typeof ActorCalcul
const { input, memoryExpression, minMemory, maxMemory, runOptions } = await this.prepareMemoryArguments();

if (!memoryExpression) {
throw new Error(
`No memory-calculation expression found. Provide it via the --default-memory-mbytes flag or define defaultMemoryMbytes in actor.json.`,
);
throw new Error(this.t(ActorCalculateMemoryCommandMessages.missingMemoryExpression));
}

const inputPath = resolve(process.cwd(), input);
const inputJson = getJsonFileContent(inputPath) ?? {};

info({ message: `Evaluating memory expression: ${memoryExpression}` });
this.logger.stderr.info(this.t(ActorCalculateMemoryCommandMessages.evaluatingExpression, { memoryExpression }));

try {
const result = await calculateRunDynamicMemory(memoryExpression, {
Expand All @@ -101,9 +100,23 @@ export class ActorCalculateMemoryCommand extends ApifyCommand<typeof ActorCalcul
});
const clampedResult = Math.min(Math.max(result, minMemory), maxMemory);

success({ message: `Calculated memory: ${clampedResult} MB`, stdout: true });
this.logger.stdout.success(
this.t(ActorCalculateMemoryCommandMessages.calculatedMemory, {
result: String(clampedResult),
jsonParams: [
{
expression: memoryExpression,
resultMb: clampedResult,
},
],
}),
);
} catch (err) {
error({ message: `Memory calculation failed: ${(err as Error).message}` });
const { message } = err as Error;

this.logger.stderr.error(
this.t(ActorCalculateMemoryCommandMessages.calculationFailed, { message, jsonParams: [message] }),
);
}
}

Expand Down Expand Up @@ -147,7 +160,15 @@ export class ActorCalculateMemoryCommand extends ApifyCommand<typeof ActorCalcul
if (localConfigResult.isErr()) {
const { message, cause } = localConfigResult.unwrapErr();

error({ message: `${message}${cause ? `\n ${cause.message}` : ''}` });
this.logger.stderr.error(
cause
? this.t(ActorCalculateMemoryCommandMessages.configLoadErrorWithCause, {
message,
cause: cause.message,
jsonParams: [message, cause.message],
})
: this.t(ActorCalculateMemoryCommandMessages.configLoadError, { message, jsonParams: [message] }),
);
process.exitCode = CommandExitCodes.InvalidActorJson;
return {};
}
Expand Down
53 changes: 37 additions & 16 deletions src/commands/actor/charge.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { ActorChargeCommandMessages } from '#i18n/commands/actor/charge.js';

import { APIFY_ENV_VARS } from '@apify/consts';

import { getApifyTokenFromEnvOrAuthFile } from '../../lib/actor.js';
import { ApifyCommand } from '../../lib/command-framework/apify-command.js';
import { Args } from '../../lib/command-framework/args.js';
import { Flags } from '../../lib/command-framework/flags.js';
import { info } from '../../lib/outputs.js';
import { getLoggedClient } from '../../lib/utils.js';

/**
Expand Down Expand Up @@ -67,45 +68,65 @@ export class ActorChargeCommand extends ApifyCommand<typeof ActorChargeCommand>
async run() {
const { eventName } = this.args;
const { count, testPayPerEvent, idempotencyKey } = this.flags;
const idempotencyKeyDisplay = idempotencyKey ?? 'not-provided';

const isAtHome = Boolean(process.env.APIFY_IS_AT_HOME);

if (!isAtHome) {
info({
message: `No platform detected: would charge ${count} events of type "${eventName}" with idempotency key "${idempotencyKey ?? 'not-provided'}".`,
stdout: true,
});
this.logger.stdout.info(
this.t(ActorChargeCommandMessages.noPlatformDetected, {
count,
eventName,
idempotencyKey: idempotencyKeyDisplay,
jsonParams: [{ count, eventName, idempotencyKey: idempotencyKeyDisplay }],
}),
);
return;
}

if (testPayPerEvent) {
info({
message: `PPE test mode: would charge ${count} events of type "${eventName}" with idempotency key "${idempotencyKey ?? 'not-provided'}".`,
stdout: true,
});
this.logger.stdout.info(
this.t(ActorChargeCommandMessages.ppeTestMode, {
count,
eventName,
idempotencyKey: idempotencyKeyDisplay,
jsonParams: [{ count, eventName, idempotencyKey: idempotencyKeyDisplay }],
}),
);
return;
}

const apifyToken = await getApifyTokenFromEnvOrAuthFile();
const apifyClient = await getLoggedClient(apifyToken);
if (!apifyClient) {
throw new Error('Apify token is not set. Please set it using the environment variable APIFY_TOKEN.');
throw new Error(this.t(ActorChargeCommandMessages.missingApifyToken));
}

const runId = process.env[APIFY_ENV_VARS.ACTOR_RUN_ID];

if (!runId) {
throw new Error('Charge command must be executed in a running Actor. Run ID not found.');
throw new Error(this.t(ActorChargeCommandMessages.notInRunningActor));
}

const run = await apifyClient.run(runId).get();

if (run?.pricingInfo?.pricingModel !== 'PAY_PER_EVENT') {
throw new Error('Charge command can only be used with pay-per-event pricing model.');
throw new Error(
this.t(ActorChargeCommandMessages.invalidPricingModel, {
jsonParams: [run?.pricingInfo?.pricingModel ?? 'N/A'],
}),
);
}

info({
message: `Charging ${count} events of type "${eventName}" with idempotency key "${idempotencyKey ?? 'not-provided'}" (runId: ${runId}).`,
stdout: true,
});
this.logger.stdout.info(
this.t(ActorChargeCommandMessages.charging, {
count,
eventName,
idempotencyKey: idempotencyKeyDisplay,
runId,
jsonParams: [{ count, eventName, idempotencyKey: idempotencyKeyDisplay, runId }],
}),
);
await apifyClient.run(runId).charge({
eventName,
count,
Expand Down
78 changes: 55 additions & 23 deletions src/commands/actor/generate-schema-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { mkdir, stat, writeFile } from 'node:fs/promises';
import path from 'node:path';
import process from 'node:process';

import { ActorGenerateSchemaTypesCommandMessages } from '#i18n/commands/actor/generate-schema-types.js';
import type { JSONSchema4 } from 'json-schema';
import { compile, type Options } from 'json-schema-to-typescript';

Expand All @@ -15,7 +16,6 @@ import {
readOutputSchema,
readStorageSchema,
} from '../../lib/input_schema.js';
import { error, info, success, warning } from '../../lib/outputs.js';
import {
clearAllRequired,
makePropertiesRequired,
Expand Down Expand Up @@ -130,8 +130,10 @@ just as if the command were run from that directory with no argument.`;
cwd: effectiveCwd,
getMessage: (schemaPath) =>
schemaPath
? `Generating types from input schema at ${schemaPath}`
: `Generating types from input schema embedded in '${LOCAL_CONFIG_PATH}'`,
? this.t(ActorGenerateSchemaTypesCommandMessages.generatingFromInputSchemaAt, { schemaPath })
: this.t(ActorGenerateSchemaTypesCommandMessages.generatingFromInputSchemaEmbedded, {
configPath: LOCAL_CONFIG_PATH,
}),
});

const name = 'input';
Expand All @@ -157,7 +159,9 @@ just as if the command were run from that directory with no argument.`;
const outputFile = path.join(outputDir, `${name}.ts`);
await writeFile(outputFile, result, 'utf-8');

success({ message: `Generated types written to ${outputFile}` });
this.logger.stderr.success(
this.t(ActorGenerateSchemaTypesCommandMessages.generatedTypesWritten, { outputFile }),
);

// When no specific file path is provided, also generate types from additional schemas
// (this includes both "no argument" and "directory argument" modes)
Expand All @@ -174,9 +178,15 @@ just as if the command were run from that directory with no argument.`;
for (const [i, schemaResult] of schemaResults.entries()) {
if (schemaResult.status === 'rejected') {
anyFailed = true;
error({
message: `Failed to generate types for ${schemaLabels[i]} schema: ${schemaResult.reason instanceof Error ? schemaResult.reason.message : String(schemaResult.reason)}`,
});
this.logger.stderr.error(
this.t(ActorGenerateSchemaTypesCommandMessages.schemaGenerationFailed, {
label: schemaLabels[i],
message:
schemaResult.reason instanceof Error
? schemaResult.reason.message
: String(schemaResult.reason),
}),
);
}
}

Expand Down Expand Up @@ -204,15 +214,23 @@ just as if the command were run from that directory with no argument.`;
const { datasetSchema, datasetSchemaPath } = datasetResult;

if (datasetSchemaPath) {
info({ message: `[experimental] Generating types from Dataset schema at ${datasetSchemaPath}` });
this.logger.stderr.info(
this.t(ActorGenerateSchemaTypesCommandMessages.experimentalDatasetAt, {
schemaPath: datasetSchemaPath,
}),
);
} else {
info({ message: `[experimental] Generating types from Dataset schema embedded in '${LOCAL_CONFIG_PATH}'` });
this.logger.stderr.info(
this.t(ActorGenerateSchemaTypesCommandMessages.experimentalDatasetEmbedded, {
configPath: LOCAL_CONFIG_PATH,
}),
);
}

const prepared = prepareFieldsSchemaForCompilation(datasetSchema);

if (!prepared) {
warning({ message: 'Dataset schema has no fields defined, skipping type generation.' });
this.logger.stderr.warning(this.t(ActorGenerateSchemaTypesCommandMessages.datasetHasNoFields));
return;
}

Expand All @@ -225,7 +243,9 @@ just as if the command were run from that directory with no argument.`;
const outputFile = path.join(outputDir, `${datasetName}.ts`);
await writeFile(outputFile, result, 'utf-8');

success({ message: `Generated types written to ${outputFile}` });
this.logger.stderr.success(
this.t(ActorGenerateSchemaTypesCommandMessages.generatedTypesWritten, { outputFile }),
);
}

private async generateOutputTypes({
Expand All @@ -246,15 +266,21 @@ just as if the command were run from that directory with no argument.`;
const { outputSchema, outputSchemaPath } = outputResult;

if (outputSchemaPath) {
info({ message: `[experimental] Generating types from Output schema at ${outputSchemaPath}` });
this.logger.stderr.info(
this.t(ActorGenerateSchemaTypesCommandMessages.experimentalOutputAt, { schemaPath: outputSchemaPath }),
);
} else {
info({ message: `[experimental] Generating types from Output schema embedded in '${LOCAL_CONFIG_PATH}'` });
this.logger.stderr.info(
this.t(ActorGenerateSchemaTypesCommandMessages.experimentalOutputEmbedded, {
configPath: LOCAL_CONFIG_PATH,
}),
);
}

const prepared = prepareOutputSchemaForCompilation(outputSchema);

if (!prepared) {
warning({ message: 'Output schema has no properties defined, skipping type generation.' });
this.logger.stderr.warning(this.t(ActorGenerateSchemaTypesCommandMessages.outputHasNoProperties));
return;
}

Expand All @@ -267,7 +293,9 @@ just as if the command were run from that directory with no argument.`;
const outputFile = path.join(outputDir, `${outputName}.ts`);
await writeFile(outputFile, result, 'utf-8');

success({ message: `Generated types written to ${outputFile}` });
this.logger.stderr.success(
this.t(ActorGenerateSchemaTypesCommandMessages.generatedTypesWritten, { outputFile }),
);
}

private async generateKvsTypes({
Expand All @@ -288,19 +316,21 @@ just as if the command were run from that directory with no argument.`;
const { schema: kvsSchema, schemaPath: kvsSchemaPath } = kvsResult;

if (kvsSchemaPath) {
info({ message: `[experimental] Generating types from Key-Value Store schema at ${kvsSchemaPath}` });
this.logger.stderr.info(
this.t(ActorGenerateSchemaTypesCommandMessages.experimentalKvsAt, { schemaPath: kvsSchemaPath }),
);
} else {
info({
message: `[experimental] Generating types from Key-Value Store schema embedded in '${LOCAL_CONFIG_PATH}'`,
});
this.logger.stderr.info(
this.t(ActorGenerateSchemaTypesCommandMessages.experimentalKvsEmbedded, {
configPath: LOCAL_CONFIG_PATH,
}),
);
}

const collections = prepareKvsCollectionsForCompilation(kvsSchema);

if (collections.length === 0) {
warning({
message: 'Key-Value Store schema has no collections with JSON schemas, skipping type generation.',
});
this.logger.stderr.warning(this.t(ActorGenerateSchemaTypesCommandMessages.kvsHasNoCollections));
return;
}

Expand All @@ -321,6 +351,8 @@ just as if the command were run from that directory with no argument.`;
const outputFile = path.join(outputDir, 'key-value-store.ts');
await writeFile(outputFile, parts.join('\n'), 'utf-8');

success({ message: `Generated types written to ${outputFile}` });
this.logger.stderr.success(
this.t(ActorGenerateSchemaTypesCommandMessages.generatedTypesWritten, { outputFile }),
);
}
}
Loading
Loading