diff --git a/.github/workflows/test_and_release.yaml b/.github/workflows/test_and_release.yaml index 93af41fc5..368c36832 100644 --- a/.github/workflows/test_and_release.yaml +++ b/.github/workflows/test_and_release.yaml @@ -63,10 +63,24 @@ jobs: uses: apify/workflows/pnpm-install@main - run: pnpm lint + format-check: + name: Format check + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + - name: Use Node.js 24 + uses: actions/setup-node@v6 + with: + node-version: 24 + - name: Install pnpm and dependencies + uses: apify/workflows/pnpm-install@main + - run: pnpm format:check + publish: name: Publish to NPM if: github.ref == 'refs/heads/master' - needs: [ test, build, lint ] + needs: [ test, build, lint, format-check ] runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 000000000..c3bd7ce9c --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,16 @@ +{ + "tabWidth": 4, + "singleQuote": true, + "trailingComma": "all", + "printWidth": 120, + "ignorePatterns": [ + "**/*.md", + "**/*.json", + "**/*.jsonc", + "**/*.yaml", + "**/*.yml", + "**/node_modules", + "**/dist", + "coverage" + ] +} diff --git a/package.json b/package.json index 72e45d19b..c7385e2e8 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,8 @@ "release": "pnpm build && lerna version patch && lerna publish from-package --contents dist", "lint": "oxlint --type-aware", "lint:fix": "oxlint --type-aware --fix", + "format": "oxfmt", + "format:check": "oxfmt --check", "preinstall": "npx only-allow pnpm" }, "commitlint": { @@ -42,6 +44,7 @@ }, "lint-staged": { "*.ts": [ + "oxfmt --write --no-error-on-unmatched-pattern", "oxlint --type-aware --fix --no-error-on-unmatched-pattern" ] }, @@ -60,6 +63,7 @@ "lerna": "^9.0.6", "lint-staged": "^16.0.0", "nock": "^14.0.0", + "oxfmt": "0.46.0", "oxlint": "1.62.0", "oxlint-tsgolint": "0.22.0", "rimraf": "^6.0.1", diff --git a/packages/actor-memory-expression/src/memory_calculator.ts b/packages/actor-memory-expression/src/memory_calculator.ts index a7fab7ade..168409b83 100644 --- a/packages/actor-memory-expression/src/memory_calculator.ts +++ b/packages/actor-memory-expression/src/memory_calculator.ts @@ -70,14 +70,23 @@ const math = create({ const { compile } = math; // Disable potentially dangerous functions -math.import({ - // We disable evaluate to prevent users from calling it inside their expressions. - // For example: defaultMemoryMbytes = "evaluate('2 + 2')" - evaluate() { throw new Error('Function evaluate is disabled.'); }, - compile() { throw new Error('Function compile is disabled.'); }, - // We need to disable it, because compileDependencies imports parseDependencies. - parse() { throw new Error('Function parse is disabled.'); }, -}, { override: true }); +math.import( + { + // We disable evaluate to prevent users from calling it inside their expressions. + // For example: defaultMemoryMbytes = "evaluate('2 + 2')" + evaluate() { + throw new Error('Function evaluate is disabled.'); + }, + compile() { + throw new Error('Function compile is disabled.'); + }, + // We need to disable it, because compileDependencies imports parseDependencies. + parse() { + throw new Error('Function parse is disabled.'); + }, + }, + { override: true }, +); /** * Safely retrieves a nested property from an object using a dot-notation string path. @@ -89,9 +98,9 @@ math.import({ * @param path A dot-separated string representing the nested path (e.g., "input.payload.size"). * @param defaultVal The value to return if the path is not found or the value is `null` or `undefined`. * @returns The retrieved value, or `defaultVal` if the path is unreachable. -*/ + */ const customGetFunc = (obj: any, path: string, defaultVal?: number) => { - return (path.split('.').reduce((current, key) => current?.[key], obj)) ?? defaultVal; + return path.split('.').reduce((current, key) => current?.[key], obj) ?? defaultVal; }; /** @@ -99,7 +108,7 @@ const customGetFunc = (obj: any, path: string, defaultVal?: number) => { * The result is clamped to the allowed range (ACTOR_LIMITS.MIN_RUN_MEMORY_MBYTES - ACTOR_LIMITS.MAX_RUN_MEMORY_MBYTES). * @param num The number to round. * @returns The closest power of 2 within min/max range. -*/ + */ const roundToClosestPowerOf2 = (num: number): number => { if (typeof num !== 'number' || Number.isNaN(num) || !Number.isFinite(num)) { throw new Error(`Calculated memory value is not a valid number: ${num}.`); @@ -139,44 +148,46 @@ const roundToClosestPowerOf2 = (num: number): number => { const processTemplateVariables = (defaultMemoryMbytes: string): string => { const variableRegex = /{{\s*([a-zA-Z0-9_.]+)\s*}}/g; - const processedExpression = defaultMemoryMbytes.replace( - variableRegex, - (_, variableName: string) => { - // 1. Check if the variable is accessing input (e.g. {{input.someValue}}) - // We do not validate the specific property name because `input` is dynamic. - if (variableName.startsWith('input.')) { - return variableName; + const processedExpression = defaultMemoryMbytes.replace(variableRegex, (_, variableName: string) => { + // 1. Check if the variable is accessing input (e.g. {{input.someValue}}) + // We do not validate the specific property name because `input` is dynamic. + if (variableName.startsWith('input.')) { + return variableName; + } + + // 2. Check if the variable is accessing runOptions (e.g. {{runOptions.memoryMbytes}}) and validate the keys. + if (variableName.startsWith('runOptions.')) { + const key = variableName.slice('runOptions.'.length); + if (!ALLOWED_RUN_OPTION_KEYS.has(key as keyof ActorRunOptions)) { + throw new Error( + `Invalid variable '{{${variableName}}}' in expression. Only the following runOptions are allowed: ${Array.from( + ALLOWED_RUN_OPTION_KEYS, + ) + .map((k) => `runOptions.${k}`) + .join(', ')}.`, + ); } + return variableName; + } - // 2. Check if the variable is accessing runOptions (e.g. {{runOptions.memoryMbytes}}) and validate the keys. - if (variableName.startsWith('runOptions.')) { - const key = variableName.slice('runOptions.'.length); - if (!ALLOWED_RUN_OPTION_KEYS.has(key as keyof ActorRunOptions)) { - throw new Error( - `Invalid variable '{{${variableName}}}' in expression. Only the following runOptions are allowed: ${Array.from(ALLOWED_RUN_OPTION_KEYS).map((k) => `runOptions.${k}`).join(', ')}.`, - ); - } - return variableName; - } - - // 3. Throw error for unrecognized variables (e.g. {{someVariable}}) - throw new Error( - `Invalid variable '{{${variableName}}}' in expression.`, - ); - }, - ); + // 3. Throw error for unrecognized variables (e.g. {{someVariable}}) + throw new Error(`Invalid variable '{{${variableName}}}' in expression.`); + }); return processedExpression; }; /* -* Retrieves a compiled expression from the cache or compiles it if not present. -* -* @param expression The expression string to compile. -* @param cache An optional cache to store/retrieve compiled expressions. -* @returns The compiled CompilationResult. -*/ -const getCompiledExpression = async (expression: string, cache: CompilationCache | undefined): Promise => { + * Retrieves a compiled expression from the cache or compiles it if not present. + * + * @param expression The expression string to compile. + * @param cache An optional cache to store/retrieve compiled expressions. + * @returns The compiled CompilationResult. + */ +const getCompiledExpression = async ( + expression: string, + cache: CompilationCache | undefined, +): Promise => { if (!cache) { return compile(expression); } @@ -199,14 +210,16 @@ const getCompiledExpression = async (expression: string, cache: CompilationCache * @param context The `MemoryEvaluationContext` (containing `input` and `runOptions`) available to the expression. * @param options.cache Optional synchronous cache. Since compiled functions cannot be saved to a database/Redis, they are kept in local memory. * @returns The calculated memory value rounded to the closest power of 2 and clamped within allowed limits. -*/ + */ export const calculateRunDynamicMemory = async ( defaultMemoryMbytes: string, context: MemoryEvaluationContext, options: { cache: CompilationCache } | undefined = undefined, ) => { if (defaultMemoryMbytes.length > DEFAULT_MEMORY_MBYTES_EXPRESSION_MAX_LENGTH) { - throw new Error(`The defaultMemoryMbytes expression is too long. Max length is ${DEFAULT_MEMORY_MBYTES_EXPRESSION_MAX_LENGTH} characters.`); + throw new Error( + `The defaultMemoryMbytes expression is too long. Max length is ${DEFAULT_MEMORY_MBYTES_EXPRESSION_MAX_LENGTH} characters.`, + ); } // Replaces all occurrences of {{variable}} with variable diff --git a/packages/actor-memory-expression/src/types.ts b/packages/actor-memory-expression/src/types.ts index 1bcacda23..d2cd35efa 100644 --- a/packages/actor-memory-expression/src/types.ts +++ b/packages/actor-memory-expression/src/types.ts @@ -8,17 +8,17 @@ export type ActorRunOptions = { maxItems?: number; maxTotalChargeUsd?: number; restartOnError?: boolean; -} +}; export type MemoryEvaluationContext = { runOptions: ActorRunOptions; input: Record; -} +}; export type CompilationCache = { get: (expression: string) => Promise; set: (expression: string, compilationResult: EvalFunction) => Promise; size: () => Promise; -} +}; export type CompilationResult = EvalFunction; diff --git a/packages/consts/src/consts.ts b/packages/consts/src/consts.ts index a69ea340f..4c19e45e5 100644 --- a/packages/consts/src/consts.ts +++ b/packages/consts/src/consts.ts @@ -378,13 +378,9 @@ export const INTEGER_ENV_VARS = [ APIFY_ENV_VARS.SYSTEM_INFO_INTERVAL_MILLIS, ] as const; -export const COMMA_SEPARATED_LIST_ENV_VARS = [ - ACTOR_ENV_VARS.BUILD_TAGS, -] as const; +export const COMMA_SEPARATED_LIST_ENV_VARS = [ACTOR_ENV_VARS.BUILD_TAGS] as const; -export const JSON_ENCODED_ENV_VARS = [ - ACTOR_ENV_VARS.STORAGES_JSON, -] as const; +export const JSON_ENCODED_ENV_VARS = [ACTOR_ENV_VARS.STORAGES_JSON] as const; /** * Dictionary of names of build-time variables passed to the Actor's Docker build process. @@ -577,16 +573,10 @@ export const WEBHOOK_DEFAULT_PAYLOAD_TEMPLATE = `{ "eventData": {{eventData}}, "resource": {{resource}} }`; -export const WEBHOOK_ALLOWED_PAYLOAD_VARIABLES = new Set([ - 'userId', - 'createdAt', - 'eventType', - 'eventData', - 'resource', -]); +export const WEBHOOK_ALLOWED_PAYLOAD_VARIABLES = new Set(['userId', 'createdAt', 'eventType', 'eventData', 'resource']); // Max allowed size of files in multi-file editor -export const MAX_MULTIFILE_BYTES = 3 * (1024 ** 2); // 3MB +export const MAX_MULTIFILE_BYTES = 3 * 1024 ** 2; // 3MB // Formats for multi-file editor files export const SOURCE_FILE_FORMATS = { @@ -666,7 +656,7 @@ export const STORAGE_GENERAL_ACCESS = { ANYONE_WITH_NAME_CAN_READ: 'ANYONE_WITH_NAME_CAN_READ', } as const; -export type STORAGE_GENERAL_ACCESS = ValueOf +export type STORAGE_GENERAL_ACCESS = ValueOf; /** * Run setting determining how others can access the run. @@ -684,7 +674,7 @@ export const RUN_GENERAL_ACCESS = { ANYONE_WITH_ID_CAN_READ: 'ANYONE_WITH_ID_CAN_READ', } as const; -export type RUN_GENERAL_ACCESS = ValueOf +export type RUN_GENERAL_ACCESS = ValueOf; /** * Determines permissions that the Actor requires to run. diff --git a/packages/consts/src/regexs.ts b/packages/consts/src/regexs.ts index 2288f5af4..627e1ad35 100644 --- a/packages/consts/src/regexs.ts +++ b/packages/consts/src/regexs.ts @@ -1,6 +1,6 @@ // Parts for building an email regex (email will be constructed as `name@domain`) // name parts can be alnum + some special characters -const namePartSubRegexStr = '[a-zA-Z0-9!#$%&\'*+/=?^_`{|}~-]+'; +const namePartSubRegexStr = "[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+"; // name is 1+ name parts joined by periods (no leading or dangling period, no consecutive periods) const nameSubRegexStr = `${namePartSubRegexStr}(?:\\.${namePartSubRegexStr})*`; // domain parts can be alnum and dash characters (no leading and dangling dashes, max 63 chars long) @@ -79,7 +79,8 @@ export const GITHUB_REGEX = new RegExp(`^${GITHUB_REGEX_STR}$`, 'i'); * For matching linkedin URLs for both profiles and companies. * Used for validating urls in user settings. */ -export const LINKEDIN_PROFILE_REGEX = /^(https?:\/\/)?(www\.)?([a-z]{2}\.)?linkedin\.com\/(in|company)\/((?:[A-Za-z0-9_-]|%[0-9A-Fa-f]{2})+)\/?$/; +export const LINKEDIN_PROFILE_REGEX = + /^(https?:\/\/)?(www\.)?([a-z]{2}\.)?linkedin\.com\/(in|company)\/((?:[A-Za-z0-9_-]|%[0-9A-Fa-f]{2})+)\/?$/; /** * @deprecated Discontinue usage of this regexps, in favor of HTTP_URL_REGEX @@ -89,51 +90,55 @@ export const URL_REGEX = /^https?:\/\//i; // Inspired by https://gist.github.com/dperini/729294, but doesn't match FTP URLs export const HTTP_URL_REGEX = new RegExp( '^' + - // protocol identifier (optional) - // short syntax // still required - // NOTE: We removed "|ftp" - '(?:(?:(?:https?):)?\\/\\/)' + - // user:pass BasicAuth (optional) - '(?:\\S+(?::\\S*)?@)?' + - '(?:' + - // IP address exclusion - // private & local networks - '(?!(?:10|127)(?:\\.\\d{1,3}){3})' + - '(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})' + - '(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})' + - // IP address dotted notation octets - // excludes loopback network 0.0.0.0 - // excludes reserved space >= 224.0.0.0 - // excludes network & broadcast addresses - // (first & last IP address of each class) - '(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])' + - '(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}' + - '(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))' + - '|' + - // host & domain names, may end with dot - // can be replaced by a shortest alternative - // (?![-_])(?:[-\\w\\u00a1-\\uffff]{0,63}[^-_]\\.)+ - '(?:' + - '(?:' + - '[a-z0-9\\u00a1-\\uffff]' + - '[a-z0-9\\u00a1-\\uffff_-]{0,62}' + - ')?' + - '[a-z0-9\\u00a1-\\uffff]\\.' + - ')+' + - // TLD identifier name, may end with dot - // NOTE: "|xn--[a-z0-9]+" is our addition to support IDNs like "http://xn--80aaxitdbjk.xn--p1ai", - // they can be used in a browser, so we consider them valid - '(?:[a-z\\u00a1-\\uffff]{2,}\\.?|xn--[a-z0-9]+)' + - ')' + - // port number (optional) - '(?::\\d{2,5})?' + - // resource path (optional) - '(?:[/?#]\\S*)?' + - '$', 'i', + // protocol identifier (optional) + // short syntax // still required + // NOTE: We removed "|ftp" + '(?:(?:(?:https?):)?\\/\\/)' + + // user:pass BasicAuth (optional) + '(?:\\S+(?::\\S*)?@)?' + + '(?:' + + // IP address exclusion + // private & local networks + '(?!(?:10|127)(?:\\.\\d{1,3}){3})' + + '(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})' + + '(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})' + + // IP address dotted notation octets + // excludes loopback network 0.0.0.0 + // excludes reserved space >= 224.0.0.0 + // excludes network & broadcast addresses + // (first & last IP address of each class) + '(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])' + + '(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}' + + '(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))' + + '|' + + // host & domain names, may end with dot + // can be replaced by a shortest alternative + // (?![-_])(?:[-\\w\\u00a1-\\uffff]{0,63}[^-_]\\.)+ + '(?:' + + '(?:' + + '[a-z0-9\\u00a1-\\uffff]' + + '[a-z0-9\\u00a1-\\uffff_-]{0,62}' + + ')?' + + '[a-z0-9\\u00a1-\\uffff]\\.' + + ')+' + + // TLD identifier name, may end with dot + // NOTE: "|xn--[a-z0-9]+" is our addition to support IDNs like "http://xn--80aaxitdbjk.xn--p1ai", + // they can be used in a browser, so we consider them valid + '(?:[a-z\\u00a1-\\uffff]{2,}\\.?|xn--[a-z0-9]+)' + + ')' + + // port number (optional) + '(?::\\d{2,5})?' + + // resource path (optional) + '(?:[/?#]\\S*)?' + + '$', + 'i', ); // E.g. https://gist.github.com/jancurn/2dbe83fea77c439b1119fb3f118513e7 -export const GITHUB_GIST_URL_REGEX = new RegExp(`^https:\\/\\/gist\\.github\\.com\\/${GITHUB_REGEX_STR}\\/[0-9a-f]{32}$`, 'i'); +export const GITHUB_GIST_URL_REGEX = new RegExp( + `^https:\\/\\/gist\\.github\\.com\\/${GITHUB_REGEX_STR}\\/[0-9a-f]{32}$`, + 'i', +); /** * Split's path /aaa/bbb/ccc into an array ['aaa', 'bbb', 'ccc]. diff --git a/packages/datastructures/src/linked_list.ts b/packages/datastructures/src/linked_list.ts index 9c0dbadb6..1e686ca9f 100644 --- a/packages/datastructures/src/linked_list.ts +++ b/packages/datastructures/src/linked_list.ts @@ -1,4 +1,3 @@ - /*! * This module defines the LinkedList class, which represents a doubly-linked list data structure. * @@ -12,7 +11,7 @@ * The function attempts to do so using data1's function 'equal(data)' if there is one, * otherwise it uses '==' operator. */ -const dataEqual = (data1: T, data2: T): boolean => { +const dataEqual = (data1: T, data2: T): boolean => { if (data1 === null) return data2 === null; if ((data1 as any).equals) return (data1 as any).equals(data2); @@ -40,8 +39,8 @@ export class LinkedList { length = 0; /** - * Appends a new node with specific data to the end of the linked list. - */ + * Appends a new node with specific data to the end of the linked list. + */ add(data: T, toFirstPosition?: boolean): LinkedListNode { const node = new LinkedListNode(data); this.addNode(node, toFirstPosition); @@ -67,7 +66,8 @@ export class LinkedList { node.next = this.head; this.head!.prev = node; this.head = node; - } else { // last position + } else { + // last position node.prev = this.tail; this.tail!.next = node; this.tail = node; diff --git a/packages/git/src/git.ts b/packages/git/src/git.ts index c3262d26b..fa672f810 100644 --- a/packages/git/src/git.ts +++ b/packages/git/src/git.ts @@ -47,7 +47,11 @@ const parseApifyGitUrl = (gitRepoUrl: string): ParseGitUrl => { * also works if the user opted to use custom branch for deploying their actor to Apify. * @return {string} updated readme */ -export const convertRelativeImagePathsToAbsoluteInReadme = ({ readme, gitRepoUrl, gitBranchName }: ConvertOptions): string => { +export const convertRelativeImagePathsToAbsoluteInReadme = ({ + readme, + gitRepoUrl, + gitBranchName, +}: ConvertOptions): string => { const parsedRepoUrl = parseApifyGitUrl(gitRepoUrl); // Can't use parsedRepoUrl.full_name on it's own as Bitbucket adds irrelevant path suffix to the end of it @@ -77,8 +81,10 @@ export const convertRelativeImagePathsToAbsoluteInReadme = ({ readme, gitRepoUrl // (?:\.?\/) matches the starting ./ or / in a relative or root-relative URL, which can be ignored when converting to absolute link // (.*?) matches the actual relative URL // (".*?\/>) or ('.*?\/>) matches the end of the image code - const relativeImageHtmlRegexWithDoubleQuotes = /()/g; - const relativeImageHtmlRegexWithSingleQuotes = /()/g; + const relativeImageHtmlRegexWithDoubleQuotes = + /()/g; + const relativeImageHtmlRegexWithSingleQuotes = + /()/g; let urlPrefix = null; if (parsedRepoUrl.resource === 'github.com') { @@ -92,8 +98,8 @@ export const convertRelativeImagePathsToAbsoluteInReadme = ({ readme, gitRepoUrl return urlPrefix ? readme - .replace(relativeImageMarkdownRegex, `$1(${urlPrefix}/$2)`) - .replace(relativeImageHtmlRegexWithDoubleQuotes, `$1${urlPrefix}/$2$3`) - .replace(relativeImageHtmlRegexWithSingleQuotes, `$1${urlPrefix}/$2$3`) + .replace(relativeImageMarkdownRegex, `$1(${urlPrefix}/$2)`) + .replace(relativeImageHtmlRegexWithDoubleQuotes, `$1${urlPrefix}/$2$3`) + .replace(relativeImageHtmlRegexWithSingleQuotes, `$1${urlPrefix}/$2$3`) : readme; }; diff --git a/packages/input_schema/src/index.ts b/packages/input_schema/src/index.ts index 296136db5..cfa603e42 100644 --- a/packages/input_schema/src/index.ts +++ b/packages/input_schema/src/index.ts @@ -7,13 +7,10 @@ export { NumberFieldDefinition, ObjectFieldDefinition, ArrayFieldDefinition, - ResourceFieldDefinition, ResourceArrayFieldDefinition, - MixedFieldDefinition, FieldDefinition, - InputSchema, } from './types'; export * from './utilities'; diff --git a/packages/input_schema/src/input_schema.ts b/packages/input_schema/src/input_schema.ts index 0f44b8020..428fdabcb 100644 --- a/packages/input_schema/src/input_schema.ts +++ b/packages/input_schema/src/input_schema.ts @@ -23,9 +23,8 @@ const { definitions } = schema; // utility definitions, and component definitions, we need to filter them out and validate only against the appropriate ones. // We do this by checking the prefix of the definition title (Utils:, Component:, or Sub-schema:) -const [fieldDefinitions, subFieldDefinitions] = Object - .values(definitions) - .reduce<[any[], any[]]>((acc, definition) => { +const [fieldDefinitions, subFieldDefinitions] = Object.values(definitions).reduce<[any[], any[]]>( + (acc, definition) => { if (definition.title.startsWith('Utils:') || definition.title.startsWith('Component:')) { // skip utility and component definitions return acc; @@ -38,7 +37,9 @@ const [fieldDefinitions, subFieldDefinitions] = Object } return acc; - }, [[], []]); + }, + [[], []], +); /** * Retrieves a custom error message defined in the schema for a particular schema path. @@ -49,10 +50,7 @@ const [fieldDefinitions, subFieldDefinitions] = Object export function getCustomErrorMessage(rootSchema: Record, schemaPath: string): string | null { if (!schemaPath) return null; - const pathParts = schemaPath - .replace(/^#\//, '') - .split('/') - .filter(Boolean); + const pathParts = schemaPath.replace(/^#\//, '').split('/').filter(Boolean); // The last part is the keyword const keyword = pathParts.pop(); @@ -94,7 +92,7 @@ export function getCustomErrorMessage(rootSchema: Record, schemaPat export function parseAjvError( error: ErrorObject, rootName: string, - properties: Record = {}, + properties: Record = {}, input: Record = {}, ): { fieldKey: string; message: string } | null { // There are 3 possible errors comming from validation: @@ -167,7 +165,12 @@ function selectBestError(errors: ErrorObject[]): ErrorObject { /** * Validates a given object against the schema and throws a human-readable error. */ -const validateAgainstSchemaOrThrow = (validator: Ajv, obj: Record, inputSchema: Schema, rootName: string) => { +const validateAgainstSchemaOrThrow = ( + validator: Ajv, + obj: Record, + inputSchema: Schema, + rootName: string, +) => { if (validator.validate(inputSchema, obj)) return; let bestError = selectBestError(validator.errors!); @@ -222,14 +225,13 @@ function validateFieldAgainstSchemaDefinition( ): asserts fieldSchema is FieldDefinition { const relevantDefinitions = isSubField ? subFieldDefinitions : fieldDefinitions; - const matchingDefinitions = Object - .values(relevantDefinitions) // cast as any, as the code in first branch seems to be invalid + const matchingDefinitions = Object.values(relevantDefinitions) // cast as any, as the code in first branch seems to be invalid .filter((definition) => { return definition.properties.type.enum - // This is a normal case where fieldSchema.type can be only one possible value matching definition.properties.type.enum.0 - ? definition.properties.type.enum[0] === fieldSchema.type - // This is a type "Any" where fieldSchema.type is an array of possible values - : Array.isArray(fieldSchema.type); + ? // This is a normal case where fieldSchema.type can be only one possible value matching definition.properties.type.enum.0 + definition.properties.type.enum[0] === fieldSchema.type + : // This is a type "Any" where fieldSchema.type is an array of possible values + Array.isArray(fieldSchema.type); }); // There is not matching definition. @@ -250,7 +252,12 @@ function validateFieldAgainstSchemaDefinition( // If there is only one matching then we are done and simply compare it. if (matchingDefinitions.length === 1) { - validateAgainstSchemaOrThrow(validator, fieldSchema, enhanceDefinition(matchingDefinitions[0]), `schema.properties.${fieldKey}`); + validateAgainstSchemaOrThrow( + validator, + fieldSchema, + enhanceDefinition(matchingDefinitions[0]), + `schema.properties.${fieldKey}`, + ); return; } @@ -259,20 +266,38 @@ function validateFieldAgainstSchemaDefinition( if ((fieldSchema as StringFieldDefinition).enum) { const definition = matchingDefinitions.filter((item) => !!item.properties.enum).pop(); if (!definition) throw new Error('Input schema validation failed to find "enum property" definition'); - validateAgainstSchemaOrThrow(validator, fieldSchema, enhanceDefinition(definition), `schema.properties.${fieldKey}`); + validateAgainstSchemaOrThrow( + validator, + fieldSchema, + enhanceDefinition(definition), + `schema.properties.${fieldKey}`, + ); return; } // If the definition contains "resourceType" property then it's resource type. if ((fieldSchema as CommonResourceFieldDefinition).resourceType) { const definition = matchingDefinitions.filter((item) => !!item.properties.resourceType).pop(); if (!definition) throw new Error('Input schema validation failed to find "resource property" definition'); - validateAgainstSchemaOrThrow(validator, fieldSchema, enhanceDefinition(definition), `schema.properties.${fieldKey}`); + validateAgainstSchemaOrThrow( + validator, + fieldSchema, + enhanceDefinition(definition), + `schema.properties.${fieldKey}`, + ); return; } // Otherwise we use the other definition. - const definition = matchingDefinitions.filter((item) => !item.properties.enum && !item.properties.resourceType).pop(); - if (!definition) throw new Error('Input schema validation failed to find other than "enum" or "resource" definition'); - validateAgainstSchemaOrThrow(validator, fieldSchema, enhanceDefinition(definition), `schema.properties.${fieldKey}`); + const definition = matchingDefinitions + .filter((item) => !item.properties.enum && !item.properties.resourceType) + .pop(); + if (!definition) + throw new Error('Input schema validation failed to find other than "enum" or "resource" definition'); + validateAgainstSchemaOrThrow( + validator, + fieldSchema, + enhanceDefinition(definition), + `schema.properties.${fieldKey}`, + ); } /** @@ -282,7 +307,12 @@ function validateFieldAgainstSchemaDefinition( * @param fieldKey Key of the field in the input schema. * @param isSubField If true, the field is a sub-field of another field, so we need to skip some definitions. */ -function validateField(validator: Ajv, fieldSchema: Record, fieldKey: string, isSubField = false): asserts fieldSchema is FieldDefinition { +function validateField( + validator: Ajv, + fieldSchema: Record, + fieldKey: string, + isSubField = false, +): asserts fieldSchema is FieldDefinition { // Validate against schema definition first. validateFieldAgainstSchemaDefinition(validator, fieldSchema, fieldKey, isSubField); @@ -315,7 +345,11 @@ function validateSubFields(validator: Ajv, fieldSchema: InputSchemaBaseChecked, }); } -function validateArrayField(validator: Ajv, fieldSchema: { items?: { type: 'string', properties: Record }}, fieldKey: string) { +function validateArrayField( + validator: Ajv, + fieldSchema: { items?: { type: 'string'; properties: Record } }, + fieldKey: string, +) { const arraySchema = (fieldSchema as any).items; if (!arraySchema) return; @@ -376,7 +410,10 @@ export function validateExistenceOfRequiredFields(inputSchema: InputSchema) { * is using features from JSON Schema 2019 draft, so the AJV instance must support it. * @param inputSchema Input schema to validate. */ -export function validateInputSchema(validator: Ajv, inputSchema: Record): asserts inputSchema is InputSchema { +export function validateInputSchema( + validator: Ajv, + inputSchema: Record, +): asserts inputSchema is InputSchema { ensureAjvSupportsDraft2019(validator); // First validate just basic structure without fields. diff --git a/packages/input_schema/src/intl.ts b/packages/input_schema/src/intl.ts index b3e64ff5c..b01682f43 100644 --- a/packages/input_schema/src/intl.ts +++ b/packages/input_schema/src/intl.ts @@ -1,8 +1,6 @@ const intlStrings = { - 'inputSchema.validation.generic': - 'Field {rootName}.{fieldKey} {message}', - 'inputSchema.validation.required': - 'Field {rootName}.{fieldKey} is required', + 'inputSchema.validation.generic': 'Field {rootName}.{fieldKey} {message}', + 'inputSchema.validation.required': 'Field {rootName}.{fieldKey} is required', 'inputSchema.validation.proxyRequired': 'Field {rootName}.{fieldKey} is required. Please provide custom proxy URLs or use Apify Proxy.', 'inputSchema.validation.requestListSourcesInvalid': @@ -15,10 +13,8 @@ const intlStrings = { 'Keys [{invalidKeys}] in {rootName}.{fieldKey} must match regular expression "{pattern}', 'inputSchema.validation.objectValuesInvalid': 'Keys [{invalidKeys}] in {rootName}.{fieldKey} must have string value which matches regular expression "{pattern}"', - 'inputSchema.validation.additionalProperty': - 'Property {rootName}.{fieldKey} is not allowed.', - 'inputSchema.validation.proxyGroupsNotAvailable': - 'You currently do not have access to proxy groups: {groups}', + 'inputSchema.validation.additionalProperty': 'Property {rootName}.{fieldKey} is not allowed.', + 'inputSchema.validation.proxyGroupsNotAvailable': 'You currently do not have access to proxy groups: {groups}', 'inputSchema.validation.customProxyInvalid': 'Proxy URL "{invalidUrl}" has invalid format, it must be socks[4|4a|5|5h]|http[s]://[username[:password]]@hostname:port.', 'inputSchema.validation.apifyProxyCountryInvalid': @@ -28,7 +24,7 @@ const intlStrings = { 'inputSchema.validation.noAvailableAutoProxy': 'Currently you do not have access to any proxy group usable in automatic mode.', 'inputSchema.validation.noMatchingDefinition': - 'Field schema.properties.{fieldKey} is not matching any input schema type definition. Please make sure that it\'s type is valid.', + "Field schema.properties.{fieldKey} is not matching any input schema type definition. Please make sure that it's type is valid.", 'inputSchema.validation.missingRequiredField': 'Field schema.properties.{fieldKey} does not exist, but it is specified in schema.required. Either define the field or remove it from schema.required.', 'inputSchema.validation.proxyGroupMustBeArrayOfStrings': diff --git a/packages/input_schema/src/types.ts b/packages/input_schema/src/types.ts index c14a3e2f2..321d8167e 100644 --- a/packages/input_schema/src/types.ts +++ b/packages/input_schema/src/types.ts @@ -7,11 +7,20 @@ type CommonFieldDefinition = { nullable?: boolean; sectionCaption?: string; sectionDescription?: string; -} +}; export type StringFieldDefinition = CommonFieldDefinition & { - type: 'string' - editor: 'textfield' | 'textarea' | 'javascript' | 'python' | 'select' | 'datepicker' | 'hidden' | 'json' | 'fileupload'; + type: 'string'; + editor: + | 'textfield' + | 'textarea' + | 'javascript' + | 'python' + | 'select' + | 'datepicker' + | 'hidden' + | 'json' + | 'fileupload'; pattern?: string; minLength?: number; maxLength?: number; @@ -21,33 +30,33 @@ export type StringFieldDefinition = CommonFieldDefinition & { isSecret?: boolean; // Used for 'datepicker' editor, absolute is considered as default value dateType?: 'absolute' | 'relative' | 'absoluteOrRelative'; -} +}; export type BooleanFieldDefinition = CommonFieldDefinition & { - type: 'boolean' + type: 'boolean'; editor?: 'checkbox' | 'hidden'; groupCaption?: string; groupDescription?: string; -} +}; export type IntegerFieldDefinition = CommonFieldDefinition & { - type: 'integer' + type: 'integer'; editor?: 'number' | 'hidden'; maximum?: number; minimum?: number; unit?: string; -} +}; export type NumberFieldDefinition = CommonFieldDefinition & { - type: 'number' + type: 'number'; editor?: 'number' | 'hidden'; maximum?: number; minimum?: number; unit?: string; -} +}; export type ObjectFieldDefinition = CommonFieldDefinition & { - type: 'object' + type: 'object'; editor: 'json' | 'proxy' | 'schemaBased' | 'hidden'; patternKey?: string; patternValue?: string; @@ -56,11 +65,20 @@ export type ObjectFieldDefinition = CommonFieldDefinition & { properties?: Record; required?: string[]; additionalProperties?: boolean; -} +}; export type ArrayFieldDefinition = CommonFieldDefinition & { - type: 'array' - editor: 'json' | 'requestListSources' | 'pseudoUrls' | 'globs' | 'keyValue' | 'stringList' | 'select' | 'schemaBased' | 'hidden'; + type: 'array'; + editor: + | 'json' + | 'requestListSources' + | 'pseudoUrls' + | 'globs' + | 'keyValue' + | 'stringList' + | 'select' + | 'schemaBased' + | 'hidden'; placeholderKey?: string; placeholderValue?: string; patternKey?: string; @@ -69,17 +87,17 @@ export type ArrayFieldDefinition = CommonFieldDefinition & { minItems?: number; uniqueItems?: boolean; items?: unknown; -} +}; export type CommonResourceFieldDefinition = CommonFieldDefinition & { editor?: 'resourcePicker' | 'hidden'; resourceType: 'dataset' | 'keyValueStore' | 'requestQueue' | 'mcpConnector'; -} +}; type StorageResourceFieldDefinition = CommonResourceFieldDefinition & { resourceType: 'dataset' | 'keyValueStore' | 'requestQueue'; resourcePermissions?: ('READ' | 'WRITE')[]; -} +}; type McpServerTools = { required?: string[]; @@ -87,48 +105,48 @@ type McpServerTools = { destructive?: boolean; idempotent?: boolean; openWorld?: boolean; -} +}; type McpServer = { url: string; tools?: McpServerTools; -} +}; type McpConnectorResourceFieldDefinition = CommonResourceFieldDefinition & { resourceType: 'mcpConnector'; mcpServers: McpServer[]; -} +}; -type AnyResourceFieldDefinition = - | StorageResourceFieldDefinition - | McpConnectorResourceFieldDefinition +type AnyResourceFieldDefinition = StorageResourceFieldDefinition | McpConnectorResourceFieldDefinition; export type ResourceFieldDefinition = AnyResourceFieldDefinition & { type: 'string'; // Singular resource field also supports 'textfield' editor, unlike the array variant. editor?: CommonResourceFieldDefinition['editor'] | 'textfield'; -} +}; export type ResourceArrayFieldDefinition = AnyResourceFieldDefinition & { type: 'array'; maxItems?: number; minItems?: number; uniqueItems?: boolean; -} +}; -type AllTypes = StringFieldDefinition['type'] +type AllTypes = + | StringFieldDefinition['type'] | BooleanFieldDefinition['type'] | IntegerFieldDefinition['type'] | NumberFieldDefinition['type'] | ObjectFieldDefinition['type'] - | ArrayFieldDefinition['type'] + | ArrayFieldDefinition['type']; export type MixedFieldDefinition = CommonFieldDefinition & { type: readonly AllTypes[]; - editor: 'json' -} + editor: 'json'; +}; -export type FieldDefinition = StringFieldDefinition +export type FieldDefinition = + | StringFieldDefinition | BooleanFieldDefinition | IntegerFieldDefinition | NumberFieldDefinition @@ -136,14 +154,14 @@ export type FieldDefinition = StringFieldDefinition | ArrayFieldDefinition | MixedFieldDefinition | ResourceFieldDefinition - | ResourceArrayFieldDefinition + | ResourceArrayFieldDefinition; /** * Type with checked base, but not properties */ export type InputSchemaBaseChecked = Omit & { properties: Record>; -} +}; /** * Type with checked base & properties @@ -157,4 +175,4 @@ export type InputSchema = { required?: readonly string[]; $schema?: unknown; -} +}; diff --git a/packages/input_schema/src/utilities.ts b/packages/input_schema/src/utilities.ts index 640dc82b0..391a65c0b 100644 --- a/packages/input_schema/src/utilities.ts +++ b/packages/input_schema/src/utilities.ts @@ -23,7 +23,11 @@ function validateProxyField( fieldKey: Record, value: Record, isRequired = false, - options: { hasAutoProxyGroups?: boolean; availableProxyGroups?: string[]; disabledProxyGroups?: Record } | null = null, + options: { + hasAutoProxyGroups?: boolean; + availableProxyGroups?: string[]; + disabledProxyGroups?: Record; + } | null = null, ) { const fieldErrors: any[] = []; if (isRequired) { @@ -80,7 +84,7 @@ function validateProxyField( return fieldErrors; } - const selectedProxyGroups = (apifyProxyGroups || []); + const selectedProxyGroups = apifyProxyGroups || []; // Auto mode, check that user has access to alteast one proxy group usable in this mode if (!selectedProxyGroups.length && !options.hasAutoProxyGroups) { @@ -90,15 +94,19 @@ function validateProxyField( // Check if proxy groups selected by user are available to him const availableProxyGroupsById = {} as Record; - (options.availableProxyGroups || []).forEach((group) => { availableProxyGroupsById[group] = true; }); + (options.availableProxyGroups || []).forEach((group) => { + availableProxyGroupsById[group] = true; + }); const unavailableProxyGroups = selectedProxyGroups.filter((group: string) => !availableProxyGroupsById[group]); if (unavailableProxyGroups.length) { - fieldErrors.push(m('inputSchema.validation.proxyGroupsNotAvailable', { - rootName: 'input', - fieldKey, - groups: unavailableProxyGroups.join(', '), - })); + fieldErrors.push( + m('inputSchema.validation.proxyGroupsNotAvailable', { + rootName: 'input', + fieldKey, + groups: unavailableProxyGroups.join(', '), + }), + ); } // Check if any of the proxy groups are blocked and if yes then output the associated message @@ -131,11 +139,11 @@ export function validateInputUsingValidator( const { properties } = inputSchema; const required = inputSchema.required || []; - let errors: { fieldKey: string, message: string }[] = []; + let errors: { fieldKey: string; message: string }[] = []; // Process AJV validation errors if (!isValid) { - errors = validator.errors! - .filter((error) => { + errors = validator + .errors!.filter((error) => { // We are storing encrypted objects/arrays as strings, so AJV will throw type the error here. // So we need to skip these errors. if (error.keyword === 'type' && error.instancePath) { @@ -146,10 +154,10 @@ export function validateInputUsingValidator( // Check if the property is a secret and if the value is an encrypted value. // We do additional validation of the field schema in the later part of this function if ( - propSchema?.isSecret - && typeof value === 'string' - && (propSchema.type === 'object' || propSchema.type === 'array') - && isEncryptedValueForFieldType(value, propSchema.type) + propSchema?.isSecret && + typeof value === 'string' && + (propSchema.type === 'object' || propSchema.type === 'array') && + isEncryptedValueForFieldType(value, propSchema.type) ) { return false; } @@ -166,7 +174,12 @@ export function validateInputUsingValidator( const fieldErrors = []; // Check that proxy is required, if yes, valides that it's correctly setup if (type === 'object' && editor === 'proxy') { - const proxyValidationErrors = validateProxyField(property as any, value as Record, required.includes(property), options.proxy); + const proxyValidationErrors = validateProxyField( + property as any, + value as Record, + required.includes(property), + options.proxy, + ); proxyValidationErrors.forEach((error) => { fieldErrors.push(error); }); @@ -182,11 +195,13 @@ export function validateInputUsingValidator( else if (item.requestsFromUrl && !URL_REGEX.test(item.requestsFromUrl)) invalidIndexes.push(index); }); if (invalidIndexes.length) { - fieldErrors.push(m('inputSchema.validation.requestListSourcesInvalid', { - rootName: 'input', - fieldKey: property, - invalidIndexes: invalidIndexes.join(','), - })); + fieldErrors.push( + m('inputSchema.validation.requestListSourcesInvalid', { + rootName: 'input', + fieldKey: property, + invalidIndexes: invalidIndexes.join(','), + }), + ); } } // If patternKey is provided, then validate keys of objects in array @@ -198,12 +213,15 @@ export function validateInputUsingValidator( }); if (invalidIndexes.length) { const customError = getCustomErrorMessage(inputSchema, `properties/${property}/patternKey`); - fieldErrors.push(customError ?? m('inputSchema.validation.arrayKeysInvalid', { - rootName: 'input', - fieldKey: property, - invalidIndexes: invalidIndexes.join(','), - pattern: patternKey, - })); + fieldErrors.push( + customError ?? + m('inputSchema.validation.arrayKeysInvalid', { + rootName: 'input', + fieldKey: property, + invalidIndexes: invalidIndexes.join(','), + pattern: patternKey, + }), + ); } } // If patternValue is provided and editor is keyValue, then validate values of objecs in array @@ -215,14 +233,17 @@ export function validateInputUsingValidator( }); if (invalidIndexes.length) { const customError = getCustomErrorMessage(inputSchema, `properties/${property}/patternValue`); - fieldErrors.push(customError ?? m('inputSchema.validation.arrayValuesInvalid', { - rootName: 'input', - fieldKey: property, - invalidIndexes: invalidIndexes.join(','), - pattern: patternValue, - })); + fieldErrors.push( + customError ?? + m('inputSchema.validation.arrayValuesInvalid', { + rootName: 'input', + fieldKey: property, + invalidIndexes: invalidIndexes.join(','), + pattern: patternValue, + }), + ); } - // If patternValue is provided and editor is stringList, then validate each item in array + // If patternValue is provided and editor is stringList, then validate each item in array } else if (patternValue && editor === 'stringList') { const check = new RegExp(patternValue); const invalidIndexes: any[] = []; @@ -231,12 +252,15 @@ export function validateInputUsingValidator( }); if (invalidIndexes.length) { const customError = getCustomErrorMessage(inputSchema, `properties/${property}/patternValue`); - fieldErrors.push(customError ?? m('inputSchema.validation.arrayValuesInvalid', { - rootName: 'input', - fieldKey: property, - invalidIndexes: invalidIndexes.join(','), - pattern: patternValue, - })); + fieldErrors.push( + customError ?? + m('inputSchema.validation.arrayValuesInvalid', { + rootName: 'input', + fieldKey: property, + invalidIndexes: invalidIndexes.join(','), + pattern: patternValue, + }), + ); } } } @@ -250,12 +274,15 @@ export function validateInputUsingValidator( }); if (invalidKeys.length) { const customError = getCustomErrorMessage(inputSchema, `properties/${property}/patternKey`); - fieldErrors.push(customError ?? m('inputSchema.validation.objectKeysInvalid', { - rootName: 'input', - fieldKey: property, - invalidKeys: invalidKeys.join(','), - pattern: patternKey, - })); + fieldErrors.push( + customError ?? + m('inputSchema.validation.objectKeysInvalid', { + rootName: 'input', + fieldKey: property, + invalidKeys: invalidKeys.join(','), + pattern: patternKey, + }), + ); } } if (patternValue) { @@ -267,12 +294,15 @@ export function validateInputUsingValidator( }); if (invalidKeys.length) { const customError = getCustomErrorMessage(inputSchema, `properties/${property}/patternValue`); - fieldErrors.push(customError ?? m('inputSchema.validation.objectValuesInvalid', { - rootName: 'input', - fieldKey: property, - invalidKeys: invalidKeys.join(','), - pattern: patternValue, - })); + fieldErrors.push( + customError ?? + m('inputSchema.validation.objectValuesInvalid', { + rootName: 'input', + fieldKey: property, + invalidKeys: invalidKeys.join(','), + pattern: patternValue, + }), + ); } } } @@ -281,7 +311,10 @@ export function validateInputUsingValidator( if (isSecret && value && typeof value === 'string') { // If the value is a valid encrypted string for the field type, // we check if the field schema is likely to be still valid (is unchanged from the time of encryption). - if (isEncryptedValueForFieldType(value, type) && !isEncryptedValueForFieldSchema(value, properties[property])) { + if ( + isEncryptedValueForFieldType(value, type) && + !isEncryptedValueForFieldSchema(value, properties[property]) + ) { // If not, we add an error message to the field errors and user needs to update the value in the input editor. fieldErrors.push(m('inputSchema.validation.secretFieldSchemaChanged', { fieldKey: property })); } @@ -304,7 +337,12 @@ export function validateInputUsingValidator( * Then stringifies the code with given number of jsonSpacing spaces and finally prefixes whole * stringified JSON except the first line with globalSpacing spaces. */ -export function makeInputJsFieldsReadable(json: string, jsFields: string[], jsonSpacing = 4, globalSpacing = 0): string { +export function makeInputJsFieldsReadable( + json: string, + jsFields: string[], + jsonSpacing = 4, + globalSpacing = 0, +): string { const parsedJson = JSON.parse(json); const replacements: Record = {}; @@ -321,19 +359,20 @@ export function makeInputJsFieldsReadable(json: string, jsFields: string[], json } const isMultiline = maybeFunction.includes('\n'); - const isSingleFunction = ast - && ast.body.length === 1 - && ( - ast.body[0].type === 'FunctionDeclaration' - || (ast.body[0].type === 'ExpressionStatement' && ast.body[0].expression.type === 'ArrowFunctionExpression') - ); + const isSingleFunction = + ast && + ast.body.length === 1 && + (ast.body[0].type === 'FunctionDeclaration' || + (ast.body[0].type === 'ExpressionStatement' && + ast.body[0].expression.type === 'ArrowFunctionExpression')); // If it's not a function declaration or multiline JS code then we do nothing. if (!isSingleFunction && !isMultiline) return; const spaces = isSingleFunction ? ' '.repeat(jsonSpacing) : ''; maybeFunction = maybeFunction - .split('\n').join(`\n${spaces}`) // This prefixes each line with spaces. + .split('\n') + .join(`\n${spaces}`) // This prefixes each line with spaces. .trim(); // Trim whitespace on both sides const replacementValue = isSingleFunction @@ -350,7 +389,7 @@ export function makeInputJsFieldsReadable(json: string, jsFields: string[], json niceJson = niceJson.replace(`"${replacementToken}"`, replacementValue); }); - const globalSpaces = (new Array(globalSpacing)).fill(' ').join(''); + const globalSpaces = new Array(globalSpacing).fill(' ').join(''); niceJson = niceJson.split('\n').join(`\n${globalSpaces}`); return niceJson; diff --git a/packages/input_secrets/src/field_schema_utils.ts b/packages/input_secrets/src/field_schema_utils.ts index f0bdd982a..78ecac23b 100644 --- a/packages/input_secrets/src/field_schema_utils.ts +++ b/packages/input_secrets/src/field_schema_utils.ts @@ -4,7 +4,16 @@ import crypto from 'node:crypto'; * These keys are omitted from the field schema normalization process * because they are not relevant for validation of values against the schema. */ -const OMIT_KEYS = new Set(['title', 'description', 'sectionCaption', 'sectionDescription', 'nullable', 'example', 'prefill', 'editor']); +const OMIT_KEYS = new Set([ + 'title', + 'description', + 'sectionCaption', + 'sectionDescription', + 'nullable', + 'example', + 'prefill', + 'editor', +]); /** * Normalizes the field schema by removing irrelevant keys and sorting the remaining keys. diff --git a/packages/input_secrets/src/input_secrets.ts b/packages/input_secrets/src/input_secrets.ts index b471c0cac..a9763bdb8 100644 --- a/packages/input_secrets/src/input_secrets.ts +++ b/packages/input_secrets/src/input_secrets.ts @@ -25,23 +25,30 @@ const ENCRYPTED_STRING_VALUE_PREFIX = 'ENCRYPTED_VALUE'; const ENCRYPTED_JSON_VALUE_PREFIX = 'ENCRYPTED_JSON'; // All encrypted values must match this regular expression. -const ENCRYPTED_VALUE_REGEXP = new RegExp(`^(${ENCRYPTED_STRING_VALUE_PREFIX}|${ENCRYPTED_JSON_VALUE_PREFIX}):(?:(${BASE64_REGEXP.source}):)?(${BASE64_REGEXP.source}):(${BASE64_REGEXP.source})$`); +const ENCRYPTED_VALUE_REGEXP = new RegExp( + `^(${ENCRYPTED_STRING_VALUE_PREFIX}|${ENCRYPTED_JSON_VALUE_PREFIX}):(?:(${BASE64_REGEXP.source}):)?(${BASE64_REGEXP.source}):(${BASE64_REGEXP.source})$`, +); /** * Get keys of secret fields from input schema */ export function getInputSchemaSecretFieldKeys(inputSchema: any): string[] { - return Object.keys(inputSchema.properties) - .filter((key) => !!inputSchema.properties[key].isSecret); + return Object.keys(inputSchema.properties).filter((key) => !!inputSchema.properties[key].isSecret); } /** * Encrypts input secret value * Depending on the type of value, it returns either a string (for strings) or an object (for objects) with the `secret` key. */ -export function encryptInputSecretValue( - { value, publicKey, schema }: { value: T, publicKey: KeyObject, schema?: Record }, -): string { +export function encryptInputSecretValue({ + value, + publicKey, + schema, +}: { + value: T; + publicKey: KeyObject; + schema?: Record; +}): string { ow(value, ow.any(ow.string, ow.object)); ow(publicKey, ow.object.instanceOf(KeyObject)); ow(schema, ow.optional.object); @@ -117,9 +124,15 @@ export function isEncryptedValueForFieldSchema(value: string, fieldSchema: Recor /** * Encrypts actor input secrets */ -export function encryptInputSecrets>( - { input, inputSchema, publicKey }: { input: T, inputSchema: object, publicKey: KeyObject }, -): T { +export function encryptInputSecrets>({ + input, + inputSchema, + publicKey, +}: { + input: T; + inputSchema: object; + publicKey: KeyObject; +}): T { ow(input, ow.object); ow(inputSchema, ow.object); ow(publicKey, ow.object.instanceOf(KeyObject)); @@ -134,10 +147,16 @@ export function encryptInputSecrets>( // sending them using API. Or input was takes from task, run console or scheduler, where input is stored encrypted. if (value && !(ow.isValid(value, ow.string) && ENCRYPTED_VALUE_REGEXP.test(value))) { try { - encryptedInput[key] = encryptInputSecretValue({ value: input[key], publicKey, schema: (inputSchema as any).properties[key] }); + encryptedInput[key] = encryptInputSecretValue({ + value: input[key], + publicKey, + schema: (inputSchema as any).properties[key], + }); } catch (err) { - throw new Error(`The input field "${key}" could not be encrypted. Try updating the field's value in the input editor. ` - + `Encryption error: ${err}`); + throw new Error( + `The input field "${key}" could not be encrypted. Try updating the field's value in the input editor. ` + + `Encryption error: ${err}`, + ); } } } @@ -151,9 +170,7 @@ export function encryptInputSecrets>( * @param {KeyObject} privateKey * @returns Object */ -export function decryptInputSecrets( - { input, privateKey }: { input: T, privateKey: KeyObject }, -): T { +export function decryptInputSecrets({ input, privateKey }: { input: T; privateKey: KeyObject }): T { ow(input, ow.object); ow(privateKey, ow.object.instanceOf(KeyObject)); @@ -173,8 +190,10 @@ export function decryptInputSecrets( decryptedInput[key] = JSON.parse(decryptedValue); } } catch (err) { - throw new Error(`The input field "${key}" could not be decrypted. Try updating the field's value in the input editor. ` - + `Decryption error: ${err}`); + throw new Error( + `The input field "${key}" could not be decrypted. Try updating the field's value in the input editor. ` + + `Decryption error: ${err}`, + ); } } } diff --git a/packages/json_schemas/scripts/build-ide-schemas.ts b/packages/json_schemas/scripts/build-ide-schemas.ts index fdcee9cc2..ff8c20801 100644 --- a/packages/json_schemas/scripts/build-ide-schemas.ts +++ b/packages/json_schemas/scripts/build-ide-schemas.ts @@ -8,14 +8,7 @@ import { enchantJsonSchema, parseJsonContent } from '../tools/modificator/utils' const PACKAGE_DIR = path.resolve(__dirname, '..'); -const SCHEMA_NAMES = [ - 'actor', - 'dataset', - 'input', - 'key-value-store', - 'output', - 'draft-07-schema', -]; +const SCHEMA_NAMES = ['actor', 'dataset', 'input', 'key-value-store', 'output', 'draft-07-schema']; async function main() { const outputDir = path.join(PACKAGE_DIR, 'output'); diff --git a/packages/json_schemas/scripts/build-schemas.ts b/packages/json_schemas/scripts/build-schemas.ts index 0edc9d367..9146bedcc 100644 --- a/packages/json_schemas/scripts/build-schemas.ts +++ b/packages/json_schemas/scripts/build-schemas.ts @@ -5,9 +5,7 @@ import { writeFileSync } from 'node:fs'; import { actorSchema } from '../src/actor.schema'; -const schemasToBuild = [ - { schema: actorSchema, filename: 'schemas/actor.schema.json' }, -]; +const schemasToBuild = [{ schema: actorSchema, filename: 'schemas/actor.schema.json' }]; for (const { schema, filename } of schemasToBuild) { writeFileSync(filename, `${JSON.stringify(schema, null, 2)}\n`); diff --git a/packages/json_schemas/src/actor.schema.ts b/packages/json_schemas/src/actor.schema.ts index 6d3447f23..5792b1806 100644 --- a/packages/json_schemas/src/actor.schema.ts +++ b/packages/json_schemas/src/actor.schema.ts @@ -68,7 +68,11 @@ export const actorSchema = { defaultMemoryMbytes: { oneOf: [ { type: 'string' }, - { type: 'integer', minimum: ACTOR_LIMITS.MIN_RUN_MEMORY_MBYTES, maximum: ACTOR_LIMITS.MAX_RUN_MEMORY_MBYTES }, + { + type: 'integer', + minimum: ACTOR_LIMITS.MIN_RUN_MEMORY_MBYTES, + maximum: ACTOR_LIMITS.MAX_RUN_MEMORY_MBYTES, + }, ], }, input: { @@ -139,10 +143,7 @@ export const actorSchema = { minProperties: 1, maxProperties: 10, additionalProperties: { - oneOf: [ - { type: 'string' }, - { $ref: 'https://apify.com/schemas/v1/dataset.json' }, - ], + oneOf: [{ type: 'string' }, { $ref: 'https://apify.com/schemas/v1/dataset.json' }], }, propertyNames: { pattern: '^[A-Za-z][A-Za-z0-9_]{0,100}$', @@ -171,9 +172,5 @@ export const actorSchema = { type: 'string', }, }, - required: [ - 'actorSpecification', - 'version', - 'name', - ], + required: ['actorSpecification', 'version', 'name'], }; diff --git a/packages/json_schemas/src/schemas.ts b/packages/json_schemas/src/schemas.ts index b0d54c122..337ef3962 100644 --- a/packages/json_schemas/src/schemas.ts +++ b/packages/json_schemas/src/schemas.ts @@ -4,10 +4,4 @@ import inputSchema from '../schemas/input.schema.json'; import keyValueStoreSchema from '../schemas/key_value_store.schema.json'; import outputSchema from '../schemas/output.schema.json'; -export { - actorSchema, - datasetSchema, - inputSchema, - keyValueStoreSchema, - outputSchema, -}; +export { actorSchema, datasetSchema, inputSchema, keyValueStoreSchema, outputSchema }; diff --git a/packages/json_schemas/src/validations.ts b/packages/json_schemas/src/validations.ts index 2ab88bf8e..e531a5d90 100644 --- a/packages/json_schemas/src/validations.ts +++ b/packages/json_schemas/src/validations.ts @@ -3,13 +3,7 @@ import Ajv from 'ajv/dist/2019.js'; // TODO: it might be better to import this from ajv package import draft7MetaSchema from '../schemas/json-schema-draft-07.json'; -import { - actorSchema, - datasetSchema, - inputSchema, - keyValueStoreSchema, - outputSchema, -} from './schemas'; +import { actorSchema, datasetSchema, inputSchema, keyValueStoreSchema, outputSchema } from './schemas'; const ajv = new Ajv({ schemas: [ diff --git a/packages/json_schemas/tools/bundler/utils.ts b/packages/json_schemas/tools/bundler/utils.ts index f8f89d334..5f0fd4af2 100644 --- a/packages/json_schemas/tools/bundler/utils.ts +++ b/packages/json_schemas/tools/bundler/utils.ts @@ -6,14 +6,16 @@ import path from 'node:path'; import type { JsonSchemaObject, JsonSchemaValue } from './types'; function isPlainJsonObject(input: unknown): boolean { - return typeof input === 'object' - && input !== null - && !Array.isArray(input) - && Object.getPrototypeOf(input) === Object.prototype; + return ( + typeof input === 'object' && + input !== null && + !Array.isArray(input) && + Object.getPrototypeOf(input) === Object.prototype + ); } -interface ObjectPropertyInfo { - key: string, +interface ObjectPropertyInfo { + key: string; value: T; jsonPointer: string; parent?: ObjectPropertyInfo; @@ -27,16 +29,18 @@ function* iterateJsonProperties(input: JsonSchemaObject, parentJsonPath = ''): G for (const val of [value].flatMap((v) => v)) { const objectValue = val as JsonSchemaObject; - yield ({ + yield { key, value: objectValue, jsonPointer, - parent: parentJsonPath ? { - key: parentKey, - value: input, - jsonPointer: parentJsonPath, - } : undefined, - }); + parent: parentJsonPath + ? { + key: parentKey, + value: input, + jsonPointer: parentJsonPath, + } + : undefined, + }; for (const result of iterateJsonProperties(objectValue, jsonPointer)) { yield result; @@ -54,14 +58,13 @@ async function includeJsonByPath(absolutePath: string): Promise { +export async function bundleJsonSchema(filePath: string, jsonSchema?: JsonSchemaObject): Promise { jsonSchema ??= await includeJsonByPath(filePath); if (!jsonSchema) { @@ -89,8 +92,7 @@ export async function scopeJsonSchema( } for (const { value, jsonPointer, parent, key } of iterateJsonProperties(jsonSchema)) { - if (parent?.value && key === REF_ATTRIBUTE && value && typeof value === 'string' - ) { + if (parent?.value && key === REF_ATTRIBUTE && value && typeof value === 'string') { const [refRelativeFilePath, anchorPath] = value.trim().split('#'); if (!refRelativeFilePath && value.startsWith('#')) { @@ -139,7 +141,7 @@ export async function scopeJsonSchema( externalSchemaAbsolutePath, mainJsonSchema, externalSchema, - `/definitions/${defKey}${(anchorPath || '')}`, + `/definitions/${defKey}${anchorPath || ''}`, ); } else { console.error('Invalid reference: ', jsonPointer, value); diff --git a/packages/json_schemas/tools/modificator/rules/add-description-rule.ts b/packages/json_schemas/tools/modificator/rules/add-description-rule.ts index 0b29f6868..49dc4d2c8 100644 --- a/packages/json_schemas/tools/modificator/rules/add-description-rule.ts +++ b/packages/json_schemas/tools/modificator/rules/add-description-rule.ts @@ -68,12 +68,19 @@ function formatSimpleDescription(markdownContent: string): string | undefined { } }); - return descriptionElement.text() - .replace(/\s*\n\s*/g, ' ') // no new lines in plain Descriptions - .trim() || undefined; + return ( + descriptionElement + .text() + .replace(/\s*\n\s*/g, ' ') // no new lines in plain Descriptions + .trim() || undefined + ); } -function processAddDescriptionRule(objectPropertyInfo: ObjectPropertyInfo, json: JsonObject, rule: Omit) { +function processAddDescriptionRule( + objectPropertyInfo: ObjectPropertyInfo, + json: JsonObject, + rule: Omit, +) { const objectProperty = getJsonValue>(json, objectPropertyInfo.jsonPointer); if (objectProperty.value && isPlainJsonObject(objectProperty.value)) { @@ -88,7 +95,8 @@ function processAddDescriptionRule(objectPropertyInfo: ObjectPropertyInfo, json: // Check for an odd number of trailing "properties" attributes in a parent's jsonPointer path // ex. objectPropertyInfo.jsonPointer = /properties/properties/description not being an object is not a problem, // but objectPropertyInfo.jsonPointer = /properties/description not being an object deserves a warning - const hasOddNumberOfTrailingProperties = (parentJsonPointer.match(/(\/properties)(?=(\/properties)*$)/g)?.length ?? 0) % 2 === 1; + const hasOddNumberOfTrailingProperties = + (parentJsonPointer.match(/(\/properties)(?=(\/properties)*$)/g)?.length ?? 0) % 2 === 1; if (hasOddNumberOfTrailingProperties) { // eslint-disable-next-line no-console @@ -110,7 +118,8 @@ export function parseAddDescriptionRule($: CheerioAPI, ruleElement: Node): AddDe } as const; return { ...rule, - applyRule: (objectPropertyInfo: ObjectPropertyInfo, json: JsonObject) => processAddDescriptionRule(objectPropertyInfo, json, rule), + applyRule: (objectPropertyInfo: ObjectPropertyInfo, json: JsonObject) => + processAddDescriptionRule(objectPropertyInfo, json, rule), }; } // eslint-disable-next-line no-console diff --git a/packages/json_schemas/tools/modificator/rules/replace-value-rule.ts b/packages/json_schemas/tools/modificator/rules/replace-value-rule.ts index f7f64d1b6..f4df14e52 100644 --- a/packages/json_schemas/tools/modificator/rules/replace-value-rule.ts +++ b/packages/json_schemas/tools/modificator/rules/replace-value-rule.ts @@ -12,19 +12,31 @@ export interface ReplaceValueRule extends AbstractRule { content: string; } -function replaceByJsonValue(objectPropertyInfo: ObjectPropertyInfo, rule: Omit, json: JsonObject) { +function replaceByJsonValue( + objectPropertyInfo: ObjectPropertyInfo, + rule: Omit, + json: JsonObject, +) { const valueHolder = getJsonValue(json, objectPropertyInfo.jsonPointer); valueHolder.value = JSON.parse(rule.content); } -function replaceByJsValue(objectPropertyInfo: ObjectPropertyInfo, rule: Omit, json: JsonObject) { +function replaceByJsValue( + objectPropertyInfo: ObjectPropertyInfo, + rule: Omit, + json: JsonObject, +) { const valueHolder = getJsonValue(json, objectPropertyInfo.jsonPointer); // This should be safe as these XML rules are always bundled with the code in the repository. - valueHolder.value = vm.runInNewContext(rule.content, { - value: valueHolder.value, - }, { - timeout: 2000, - }); + valueHolder.value = vm.runInNewContext( + rule.content, + { + value: valueHolder.value, + }, + { + timeout: 2000, + }, + ); } export function parseReplaceValueRule($: CheerioAPI, ruleElement: Node): ReplaceValueRule | null { @@ -39,9 +51,11 @@ export function parseReplaceValueRule($: CheerioAPI, ruleElement: Node): Replace } as const; return { ...rule, - applyRule: (objectPropertyInfo: ObjectPropertyInfo, json: JsonObject) => replaceByJsonValue(objectPropertyInfo, rule, json), + applyRule: (objectPropertyInfo: ObjectPropertyInfo, json: JsonObject) => + replaceByJsonValue(objectPropertyInfo, rule, json), } satisfies ReplaceValueRule; - } if (type === 'js') { + } + if (type === 'js') { const rule = { ruleName: RULE_NAME, jsonPath: $(ruleElement).attr('json-path')!, @@ -50,7 +64,8 @@ export function parseReplaceValueRule($: CheerioAPI, ruleElement: Node): Replace } as const; return { ...rule, - applyRule: (objectPropertyInfo: ObjectPropertyInfo, json: JsonObject) => replaceByJsValue(objectPropertyInfo, rule, json), + applyRule: (objectPropertyInfo: ObjectPropertyInfo, json: JsonObject) => + replaceByJsValue(objectPropertyInfo, rule, json), } satisfies ReplaceValueRule; } // eslint-disable-next-line no-console diff --git a/packages/json_schemas/tools/modificator/types.ts b/packages/json_schemas/tools/modificator/types.ts index 3c4941ae8..b559a9725 100644 --- a/packages/json_schemas/tools/modificator/types.ts +++ b/packages/json_schemas/tools/modificator/types.ts @@ -11,7 +11,7 @@ export interface JsonObject { } export interface ObjectPropertyInfo { - key?: string, + key?: string; value: VALUE; jsonPointer: string; parent?: ObjectPropertyInfo; @@ -19,7 +19,7 @@ export interface ObjectPropertyInfo { export interface AbstractRule { ruleName: RULE_NAME; - applyRule: (objectPropertyInfo: ObjectPropertyInfo, json: JsonObject) => void + applyRule: (objectPropertyInfo: ObjectPropertyInfo, json: JsonObject) => void; jsonPath: string; } diff --git a/packages/json_schemas/tools/modificator/utils.ts b/packages/json_schemas/tools/modificator/utils.ts index f087a7ce1..4a3af8dd6 100644 --- a/packages/json_schemas/tools/modificator/utils.ts +++ b/packages/json_schemas/tools/modificator/utils.ts @@ -4,18 +4,23 @@ import type { JsonObject, JsonValue, ObjectPropertyInfo, Rule } from './types'; * Parses a JSON Pointer into its constituent parts, decoding escape sequences per RFC 6901. */ export function parseJsonPointer(jsonPointer: string): string[] { - return jsonPointer.replace(/^\//, '') - .split('/') - // Decode JSON Pointer escape sequences (RFC 6901): - // Must decode ~0 first, then ~1 to handle sequences like ~01 correctly - .map((part) => part.replace(/~0/g, '~').replace(/~1/g, '/')); + return ( + jsonPointer + .replace(/^\//, '') + .split('/') + // Decode JSON Pointer escape sequences (RFC 6901): + // Must decode ~0 first, then ~1 to handle sequences like ~01 correctly + .map((part) => part.replace(/~0/g, '~').replace(/~1/g, '/')) + ); } export function isPlainJsonObject(input: unknown): boolean { - return typeof input === 'object' - && input !== null - && !Array.isArray(input) - && Object.getPrototypeOf(input) === Object.prototype; + return ( + typeof input === 'object' && + input !== null && + !Array.isArray(input) && + Object.getPrototypeOf(input) === Object.prototype + ); } function isIterable(input: unknown): boolean { @@ -38,10 +43,10 @@ function* iterateJsonProperties(input: JsonValue, parentJsonPath = ''): Generato } if (parentJsonPath === '') { - yield ({ + yield { value: input, jsonPointer: '/', - }); + }; } if (isPlainJsonObject(input)) { @@ -52,22 +57,24 @@ function* iterateJsonProperties(input: JsonValue, parentJsonPath = ''): Generato const value = inputObject[key]; if (isIterable(value)) { - yield ({ + yield { key, value, jsonPointer, - parent: parentJsonPath ? { - key: parentKey, - value: inputObject, - jsonPointer: parentJsonPath, - } : undefined, - }); + parent: parentJsonPath + ? { + key: parentKey, + value: inputObject, + jsonPointer: parentJsonPath, + } + : undefined, + }; for (const result of iterateJsonProperties(value, jsonPointer)) { yield result; } } else { - yield ({ + yield { key, value, jsonPointer, @@ -76,7 +83,7 @@ function* iterateJsonProperties(input: JsonValue, parentJsonPath = ''): Generato value: inputObject, jsonPointer: parentJsonPath === '' ? '/' : parentJsonPath, }, - }); + }; } } } @@ -130,18 +137,14 @@ export function parseJsonContent(jsonContent: string): JsonObject { } function matchesJsonPointer(ruleJsonPointer: string, attributeJsonPointer: string): boolean { - return ruleJsonPointer === attributeJsonPointer + return ( + ruleJsonPointer === attributeJsonPointer || // Basic support for using wildcard symbols - || ( - ruleJsonPointer.startsWith('**') - && attributeJsonPointer.endsWith(ruleJsonPointer.replace(/^\*\*/, '')) - ); + (ruleJsonPointer.startsWith('**') && attributeJsonPointer.endsWith(ruleJsonPointer.replace(/^\*\*/, ''))) + ); } -export async function enchantJsonSchema( - jsonSchema: JsonObject, - enchantmentRules: Rule[], -): Promise { +export async function enchantJsonSchema(jsonSchema: JsonObject, enchantmentRules: Rule[]): Promise { if (!isIterable(jsonSchema)) { return jsonSchema; } @@ -151,8 +154,9 @@ export async function enchantJsonSchema( for (const jsonPropertyInfo of iterateJsonProperties(jsonSchema)) { const { jsonPointer } = jsonPropertyInfo; - const relatedRules = enchantmentRules - .filter((enchantmentRule) => matchesJsonPointer(enchantmentRule.jsonPath, jsonPointer)); + const relatedRules = enchantmentRules.filter((enchantmentRule) => + matchesJsonPointer(enchantmentRule.jsonPath, jsonPointer), + ); for (const relatedRule of relatedRules) { relatedRule.applyRule(jsonPropertyInfo, jsonSchema); diff --git a/packages/log/src/log.ts b/packages/log/src/log.ts index 265bd6653..d8d12a743 100644 --- a/packages/log/src/log.ts +++ b/packages/log/src/log.ts @@ -1,4 +1,11 @@ -import { LogFormat, LogLevel, PREFERRED_FIELDS, PREFIX_DELIMITER, TRUNCATION_FLAG_KEY, TRUNCATION_SUFFIX } from './log_consts'; +import { + LogFormat, + LogLevel, + PREFERRED_FIELDS, + PREFIX_DELIMITER, + TRUNCATION_FLAG_KEY, + TRUNCATION_SUFFIX, +} from './log_consts'; import { getFormatFromEnv, getLevelFromEnv, sanitizeData } from './log_helpers'; import type { Logger } from './logger'; import { LoggerJson } from './logger_json'; @@ -43,7 +50,7 @@ export interface LoggerOptions { */ logger?: Logger; /** Additional data to be added to each log line. */ - data?: Record, + data?: Record; } type AdditionalData = Record | null; @@ -157,15 +164,23 @@ export class Log { if (!LogLevel[this.options.level]) throw new Error('Options "level" must be one of log.LEVELS enum!'); if (typeof this.options.maxDepth !== 'number') throw new Error('Options "maxDepth" must be a number!'); - if (typeof this.options.gradualLimitFactor !== 'number') throw new Error('Options "gradualLimitFactor" must be a number!'); - if (typeof this.options.maxStringLength !== 'number') throw new Error('Options "maxStringLength" must be a number!'); - if (typeof this.options.maxArrayLength !== 'number') throw new Error('Options "maxArrayLength" must be a number!'); + if (typeof this.options.gradualLimitFactor !== 'number') + throw new Error('Options "gradualLimitFactor" must be a number!'); + if (typeof this.options.maxStringLength !== 'number') + throw new Error('Options "maxStringLength" must be a number!'); + if (typeof this.options.maxArrayLength !== 'number') + throw new Error('Options "maxArrayLength" must be a number!'); if (typeof this.options.maxFields !== 'number') throw new Error('Options "maxFields" must be a number!'); - if (!Array.isArray(this.options.preferredFields)) throw new Error('Options "preferredFields" must be an array!'); - if (this.options.prefix && typeof this.options.prefix !== 'string') throw new Error('Options "prefix" must be a string!'); - if (this.options.suffix && typeof this.options.suffix !== 'string') throw new Error('Options "suffix" must be a string!'); - if (typeof this.options.truncationSuffix !== 'string') throw new Error('Options "truncationSuffix" must be a string!'); - if (typeof this.options.truncationFlagKey !== 'string') throw new Error('Options "truncationFlagKey" must be a string!'); + if (!Array.isArray(this.options.preferredFields)) + throw new Error('Options "preferredFields" must be an array!'); + if (this.options.prefix && typeof this.options.prefix !== 'string') + throw new Error('Options "prefix" must be a string!'); + if (this.options.suffix && typeof this.options.suffix !== 'string') + throw new Error('Options "suffix" must be a string!'); + if (typeof this.options.truncationSuffix !== 'string') + throw new Error('Options "truncationSuffix" must be a string!'); + if (typeof this.options.truncationFlagKey !== 'string') + throw new Error('Options "truncationFlagKey" must be a string!'); if (typeof this.options.logger !== 'object') throw new Error('Options "logger" must be an object!'); if (typeof this.options.data !== 'object') throw new Error('Options "data" must be an object!'); @@ -175,19 +190,16 @@ export class Log { } private _sanitizeData(obj: any) { - return sanitizeData( - obj, - { - maxDepth: this.options.maxDepth, - gradualLimitFactor: this.options.gradualLimitFactor, - maxStringLength: this.options.maxStringLength, - maxArrayLength: this.options.maxArrayLength, - maxFields: this.options.maxFields, - preferredFieldsMap: this.preferredFieldsMap, - truncationSuffix: this.options.truncationSuffix, - truncationFlagKey: this.options.truncationFlagKey, - }, - ); + return sanitizeData(obj, { + maxDepth: this.options.maxDepth, + gradualLimitFactor: this.options.gradualLimitFactor, + maxStringLength: this.options.maxStringLength, + maxArrayLength: this.options.maxArrayLength, + maxFields: this.options.maxFields, + preferredFieldsMap: this.preferredFieldsMap, + truncationSuffix: this.options.truncationSuffix, + truncationFlagKey: this.options.truncationFlagKey, + }); } /** @@ -250,14 +262,10 @@ export class Log { let { prefix } = this.options; if (options.prefix) { - prefix = prefix - ? `${prefix}${PREFIX_DELIMITER}${options.prefix}` - : options.prefix; + prefix = prefix ? `${prefix}${PREFIX_DELIMITER}${options.prefix}` : options.prefix; } - const data = options.data - ? { ...this.options.data, ...options.data } - : this.options.data; + const data = options.data ? { ...this.options.data, ...options.data } : this.options.data; const newOptions = { ...this.options, diff --git a/packages/log/src/log_consts.ts b/packages/log/src/log_consts.ts index 0d39f7d53..4d2dd3289 100644 --- a/packages/log/src/log_consts.ts +++ b/packages/log/src/log_consts.ts @@ -50,29 +50,13 @@ export const PREFERRED_ID_FIELDS = [ ] as const; /** Standard JS Error fields */ -export const PREFERRED_ERROR_FIELDS = [ - 'name', - 'message', - 'stack', - 'cause', -] as const; +export const PREFERRED_ERROR_FIELDS = ['name', 'message', 'stack', 'cause'] as const; /** Standard HTTP / network-related fields */ -export const PREFERRED_HTTP_FIELDS = [ - 'url', - 'method', - 'code', - 'status', - 'statusCode', - 'statusText', -] as const; +export const PREFERRED_HTTP_FIELDS = ['url', 'method', 'code', 'status', 'statusCode', 'statusText'] as const; /** API error fields used in Apify system */ -export const PREFERRED_API_ERROR_FIELDS = [ - 'errorCode', - 'errorMessage', - 'errorResponse', -] as const; +export const PREFERRED_API_ERROR_FIELDS = ['errorCode', 'errorMessage', 'errorResponse'] as const; /** Potentially large or nested data fields */ export const PREFERRED_DATA_FIELDS = [ diff --git a/packages/log/src/log_helpers.ts b/packages/log/src/log_helpers.ts index afd64afb2..7ee3ec110 100644 --- a/packages/log/src/log_helpers.ts +++ b/packages/log/src/log_helpers.ts @@ -1,12 +1,6 @@ import { APIFY_ENV_VARS } from '@apify/consts'; -import { - IS_APIFY_LOGGER_EXCEPTION, - LogFormat, - LogLevel, - TRUNCATION_FLAG_KEY, - TRUNCATION_SUFFIX, -} from './log_consts'; +import { IS_APIFY_LOGGER_EXCEPTION, LogFormat, LogLevel, TRUNCATION_FLAG_KEY, TRUNCATION_SUFFIX } from './log_consts'; /** * Ensures a string is shorter than a specified number of character, and truncates it if not, appending a specific suffix to it. @@ -90,9 +84,7 @@ export function sanitizeData(data: unknown, options: SanitizeDataOptions): unkno // handle common cases quickly if (typeof data === 'string') { - return data.length > maxStringLength - ? truncate(data, maxStringLength, truncationSuffix) - : data; + return data.length > maxStringLength ? truncate(data, maxStringLength, truncationSuffix) : data; } if (['number', 'boolean', 'symbol', 'bigint'].includes(typeof data) || data == null || data instanceof Date) { @@ -106,9 +98,8 @@ export function sanitizeData(data: unknown, options: SanitizeDataOptions): unkno data = { name, message, stack, cause, ...rest, [IS_APIFY_LOGGER_EXCEPTION]: true }; } - const nextCall = (dat: unknown) => sanitizeData( - dat, - { + const nextCall = (dat: unknown) => + sanitizeData(dat, { ...options, maxDepth: maxDepth - 1, maxStringLength: Math.max( @@ -117,8 +108,7 @@ export function sanitizeData(data: unknown, options: SanitizeDataOptions): unkno ), maxArrayLength: Math.floor(maxArrayLength * gradualLimitFactor), maxFields: Math.floor(maxFields * gradualLimitFactor), - }, - ); + }); if (Array.isArray(data)) { if (maxDepth <= 0) return '[array]'; @@ -149,9 +139,9 @@ export function sanitizeData(data: unknown, options: SanitizeDataOptions): unkno // Sanitize only up to maxFields fields (keeping preferred ones first) const sanitized: Record = {}; - allKeys - .slice(0, maxFields) - .forEach((key) => { sanitized[key] = nextCall(data[key as keyof typeof data]); }); + allKeys.slice(0, maxFields).forEach((key) => { + sanitized[key] = nextCall(data[key as keyof typeof data]); + }); if (allKeys.length > maxFields) { sanitized[truncationFlagKey] = true; diff --git a/packages/log/src/logger_text.ts b/packages/log/src/logger_text.ts index 3bade2768..669ce0753 100644 --- a/packages/log/src/logger_text.ts +++ b/packages/log/src/logger_text.ts @@ -19,7 +19,9 @@ const LEVEL_TO_COLOR = { [LogLevel.PERF]: 'magenta', } as const; -const SHORTENED_LOG_LEVELS = LEVEL_TO_STRING.map((level) => SHORTEN_LEVELS[level as keyof typeof SHORTEN_LEVELS] || level); +const SHORTENED_LOG_LEVELS = LEVEL_TO_STRING.map( + (level) => SHORTEN_LEVELS[level as keyof typeof SHORTEN_LEVELS] || level, +); const MAX_LEVEL_LENGTH_SPACES = Math.max(...SHORTENED_LOG_LEVELS.map((l) => l.length)); const getLevelIndent = (level: string) => { @@ -44,7 +46,7 @@ export class LoggerText extends Logger { let maybeDate = ''; if (!this.options.skipTime) { - maybeDate = `${(new Date()).toISOString().replace('Z', '').replace('T', ' ')} `; + maybeDate = `${new Date().toISOString().replace('Z', '').replace('T', ' ')} `; } const errStack = exception ? this._parseException(exception) : ''; diff --git a/packages/log/src/node_internals.ts b/packages/log/src/node_internals.ts index 97a8ae514..a80217e2a 100644 --- a/packages/log/src/node_internals.ts +++ b/packages/log/src/node_internals.ts @@ -61,7 +61,7 @@ export function getStackFrames(err: Error, stack: string) { try { ({ cause } = err); } catch { - // If 'cause' is a getter that throws, ignore it. + // If 'cause' is a getter that throws, ignore it. } // Remove stack frames identical to frames in cause. diff --git a/packages/markdown/src/markdown_renderers.ts b/packages/markdown/src/markdown_renderers.ts index 4efa46b8d..65f96c806 100644 --- a/packages/markdown/src/markdown_renderers.ts +++ b/packages/markdown/src/markdown_renderers.ts @@ -5,7 +5,10 @@ import { isUrlRelative } from '@apify/utilities'; export function formatHeadingId(headingId: string) { // Replace non-word characters with dashes - headingId = headingId.toLowerCase().trim().replace(/[^\w]+/g, '-'); + headingId = headingId + .toLowerCase() + .trim() + .replace(/[^\w]+/g, '-'); // Replace multiple following dashes with one dash headingId = headingId.replace(/[-]+/g, '-'); // Remove dashes at the beginning and end @@ -46,7 +49,7 @@ export function extractHeadingIdAndText(text: string, raw: string): { headingTex * ### [](#custom-id) Heading text * becomes *

Heading text

-*/ + */ export function customHeadingRenderer({ depth, text, raw }: Tokens.Heading): string { const { headingId, headingText } = extractHeadingIdAndText(text, raw); return `\n${' '.repeat(12)}${headingText}`; @@ -123,7 +126,7 @@ export function generateGitRepoUrlPrefix(gitRepoUrl: string, gitBranchName: stri * 1) handle anchors, Apify links, and contact links (these don't point to a git repo and shouldn't have rel=nofollow). * 2) handle relative links for the Git repo and convert them to absolute * 3) handle absolute links -*/ + */ export function customLinkRenderer(href: string, text: string, gitRepoUrl: string, gitBranchName: string): string { // Handle anchor links, local Apify links, and mailto // Return Apify domain links without rel="nofollow" for SEO @@ -148,7 +151,7 @@ export function customLinkRenderer(href: string, text: string, gitRepoUrl: strin * Replaces relative links in images with absolute ones that point to the actor's git repo. * Mainly for use in actor READMES * Parses the actor's repo URL to extract the repo name and owner name. -*/ + */ export function customImageRenderer(href: string, text: string, gitRepoUrl: string, gitBranchName: string): string { // Only target relative URLs, which are used to refer to the git repo, and not anchors or absolute URLs const urlIsRelative = isUrlRelative(href); diff --git a/packages/markdown/src/marked.ts b/packages/markdown/src/marked.ts index 3efbd261f..269368c2e 100644 --- a/packages/markdown/src/marked.ts +++ b/packages/markdown/src/marked.ts @@ -41,11 +41,14 @@ const APIFY_CODE_TABS = 'apify-code-tabs'; const DEFAULT_MARKED_RENDERER = new Renderer(); interface Match { - groups: { header: string, lang: string, code: string }; + groups: { header: string; lang: string; code: string }; } -const codeTabObjectFromCodeTabMarkdown = (markdown: string): Record => { - const matchesIterator = matchAll(markdown, /(?.*?)<\/marked-tab>/sg); +const codeTabObjectFromCodeTabMarkdown = (markdown: string): Record => { + const matchesIterator = matchAll( + markdown, + /(?.*?)<\/marked-tab>/gs, + ); const matches: Match[] = []; let nextMatch = matchesIterator.nextRaw(); while (nextMatch) { @@ -53,7 +56,7 @@ const codeTabObjectFromCodeTabMarkdown = (markdown: string): Record = {}; + const tabs: Record = {}; for (const match of matches) { const { header, lang, code } = match.groups!; @@ -65,7 +68,7 @@ const codeTabObjectFromCodeTabMarkdown = (markdown: string): Record>; + codeTabsObjectPerIndex: Record>; } /** diff --git a/packages/pseudo_url/src/pseudo_url.ts b/packages/pseudo_url/src/pseudo_url.ts index 428da1110..ba0430c76 100644 --- a/packages/pseudo_url/src/pseudo_url.ts +++ b/packages/pseudo_url/src/pseudo_url.ts @@ -68,7 +68,9 @@ export class PseudoUrl { log.debug('PURL parsed', { purl, regex: this.regex }); } else { const type = Array.isArray(purl) ? 'array' : typeof purl; - throw new Error(`Invalid PseudoUrl format, 'string' or 'RegExp' required, got \`${inspect(purl)}\` of type '${type}' instead`); + throw new Error( + `Invalid PseudoUrl format, 'string' or 'RegExp' required, got \`${inspect(purl)}\` of type '${type}' instead`, + ); } } @@ -76,7 +78,7 @@ export class PseudoUrl { * Determines whether a URL matches this pseudo-URL pattern. */ matches(url: string): boolean { - return typeof url as unknown === 'string' && url.match(this.regex) !== null; + return (typeof url as unknown) === 'string' && url.match(this.regex) !== null; } } diff --git a/packages/timeout/src/index.ts b/packages/timeout/src/index.ts index d1f2e48e4..216e1c2ea 100644 --- a/packages/timeout/src/index.ts +++ b/packages/timeout/src/index.ts @@ -14,15 +14,13 @@ export const storage = new AsyncLocalStorage(); /** * Custom error class that will be used for timeout error. */ -export class TimeoutError extends Error { -} +export class TimeoutError extends Error {} /** * Custom error class to handle `tryCancel()` checks. * This should not be exposed to user land, as it will be caught in. */ -class InternalTimeoutError extends TimeoutError { -} +class InternalTimeoutError extends TimeoutError {} /** * Checks whether we are inside timeout handler created by this package, and cancels current @@ -62,7 +60,11 @@ export function tryCancel(): void { * ); * ``` */ -export async function addTimeoutToPromise(handler: () => Promise, timeoutMillis: number, errorMessage: string | Error): Promise { +export async function addTimeoutToPromise( + handler: () => Promise, + timeoutMillis: number, + errorMessage: string | Error, +): Promise { // respect existing context to support nesting const context = storage.getStore() ?? { cancelTask: new AbortController(), diff --git a/packages/utilities/src/crypto.ts b/packages/utilities/src/crypto.ts index 36bcd727d..09ed934cb 100644 --- a/packages/utilities/src/crypto.ts +++ b/packages/utilities/src/crypto.ts @@ -12,12 +12,12 @@ type DecryptOptions = { privateKey: KeyObject; encryptedPassword: string; encryptedValue: string; -} +}; type EncryptOptions = { publicKey: KeyObject; value: string; -} +}; /** * It encrypts the given value using AES cipher and the password for encryption using the public key. @@ -58,11 +58,7 @@ export function publicEncrypt({ publicKey, value }: EncryptOptions) { * @param encryptedValue {string} Content in Base64 encrypted using AES cipher * @returns {string} */ -export function privateDecrypt({ - privateKey, - encryptedPassword, - encryptedValue, -}: DecryptOptions): string { +export function privateDecrypt({ privateKey, encryptedPassword, encryptedValue }: DecryptOptions): string { const encryptedValueBuffer = Buffer.from(encryptedValue, 'base64'); const encryptedPasswordBuffer = Buffer.from(encryptedPassword, 'base64'); diff --git a/packages/utilities/src/exponential_backoff.ts b/packages/utilities/src/exponential_backoff.ts index 13f80fef2..ea600ea98 100644 --- a/packages/utilities/src/exponential_backoff.ts +++ b/packages/utilities/src/exponential_backoff.ts @@ -6,13 +6,17 @@ export class RetryableError extends Error { readonly error: Error; constructor(error: Error, ...args: unknown[]) { - super(...args as [string]); + super(...(args as [string])); this.error = error; } } export async function retryWithExpBackoff( - params: { func?: (...args: unknown[]) => T | Promise, expBackoffMillis?: number, expBackoffMaxRepeats?: number } = {}, + params: { + func?: (...args: unknown[]) => T | Promise; + expBackoffMillis?: number; + expBackoffMaxRepeats?: number; + } = {}, ): Promise { const { func, expBackoffMillis, expBackoffMaxRepeats } = params; @@ -45,7 +49,7 @@ export async function retryWithExpBackoff( throw error.error; } - const waitMillis = expBackoffMillis * (2 ** i); + const waitMillis = expBackoffMillis * 2 ** i; const rand = (from: number, to: number) => from + Math.floor(Math.random() * (to - from + 1)); const randomizedWaitMillis = rand(waitMillis, waitMillis * 2); diff --git a/packages/utilities/src/health_checker.ts b/packages/utilities/src/health_checker.ts index 9ec5f9089..66773561e 100644 --- a/packages/utilities/src/health_checker.ts +++ b/packages/utilities/src/health_checker.ts @@ -91,7 +91,8 @@ export class HealthChecker { _validateCheck(check: CheckType): void { if (!(check.type in CHECK_TYPES)) throw new Error(`Check type "${check.type}" is invalid`); - if (typeof check.client !== 'object') throw new Error(`Check client must be an object got "${typeof check.client}" instead`); + if (typeof check.client !== 'object') + throw new Error(`Check client must be an object got "${typeof check.client}" instead`); } async _performCheck(check: CheckType): Promise { diff --git a/packages/utilities/src/hmac.ts b/packages/utilities/src/hmac.ts index c82218fef..ddb6ac0b1 100644 --- a/packages/utilities/src/hmac.ts +++ b/packages/utilities/src/hmac.ts @@ -29,10 +29,7 @@ function encodeBase62(num: bigint) { * is available in both Node.js and browsers without the need for polyfills. */ export function createHmacSignature(secretKey: string, message: string): string { - const signature = crypto.createHmac('sha256', secretKey) - .update(message) - .digest('hex') - .substring(0, 30); + const signature = crypto.createHmac('sha256', secretKey).update(message).digest('hex').substring(0, 30); return encodeBase62(BigInt(`0x${signature}`)); } @@ -83,11 +80,7 @@ export async function createHmacSignatureAsync(secretKey: string, message: strin ['sign'], ); - const signatureBuffer = await subtleCrypto.sign( - 'HMAC', - key, - encoder.encode(message), - ); + const signatureBuffer = await subtleCrypto.sign('HMAC', key, encoder.encode(message)); const signatureArray = new Uint8Array(signatureBuffer); const signatureHex = Array.from(signatureArray) diff --git a/packages/utilities/src/utilities.client.ts b/packages/utilities/src/utilities.client.ts index ab1c9846c..02780ea8c 100644 --- a/packages/utilities/src/utilities.client.ts +++ b/packages/utilities/src/utilities.client.ts @@ -16,14 +16,21 @@ export function isNullOrUndefined(obj: unknown): boolean { } export function isBuffer(obj: any): boolean { - return obj != null && obj.constructor != null && typeof obj.constructor.isBuffer === 'function' && obj.constructor.isBuffer(obj); + return ( + obj != null && + obj.constructor != null && + typeof obj.constructor.isBuffer === 'function' && + obj.constructor.isBuffer(obj) + ); } /** * Converts Date object to ISO string. */ export function dateToString(date: Date, middleT: boolean): string { - if (!(date instanceof Date)) { return ''; } + if (!(date instanceof Date)) { + return ''; + } const year = date.getFullYear(); const month = date.getMonth() + 1; // January is 0, February is 1, and so on. const day = date.getDate(); @@ -35,7 +42,7 @@ export function dateToString(date: Date, middleT: boolean): string { const pad = (num: number) => (num < 10 ? `0${num}` : num); const datePart = `${year}-${pad(month)}-${pad(day)}`; // eslint-disable-next-line no-nested-ternary - const millisPart = millis < 10 ? `00${millis}` : (millis < 100 ? `0${millis}` : millis); + const millisPart = millis < 10 ? `00${millis}` : millis < 100 ? `0${millis}` : millis; const timePart = `${pad(hours)}:${pad(minutes)}:${pad(seconds)}.${millisPart}`; return `${datePart}${middleT ? 'T' : ' '}${timePart}`; @@ -89,8 +96,22 @@ export function parseUrl(str: string): Uri { if (typeof str !== 'string') return {}; const o = { strictMode: false, - key: ['source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', - 'relative', 'path', 'directory', 'file', 'query', 'fragment'], + key: [ + 'source', + 'protocol', + 'authority', + 'userInfo', + 'user', + 'password', + 'host', + 'port', + 'relative', + 'path', + 'directory', + 'file', + 'query', + 'fragment', + ], q: { name: 'queryKey', parser: /(?:^|&)([^&=]*)=?([^&]*)/g, @@ -167,12 +188,13 @@ export function markedSetNofollowLinks(href: string, title: string, text: string } catch { // Probably invalid url, go on } - const isApifyLink = (urlParsed! && /(\.|^)apify\.com$/i.test(urlParsed.hostname)); + const isApifyLink = urlParsed! && /(\.|^)apify\.com$/i.test(urlParsed.hostname); const isSameHostname = !referrerHostname || (urlParsed! && urlParsed.hostname === referrerHostname); if (isApifyLink && isSameHostname) { return `${title || text}`; - } if (isApifyLink) { + } + if (isApifyLink) { return `${title || text}`; } @@ -212,7 +234,9 @@ const ESCAPE_TO_STRING = '\uFF54\uFF4F\uFF33\uFF54\uFF52\uFF49\uFF4E\uFF47'; // const ESCAPE_BSON_TYPE = '\uFF3F\uFF42\uFF53\uFF4F\uFF4E\uFF54\uFF59\uFF50\uFF45'; // "_bsontype" const ESCAPE_NULL = ''; // "\0" (null chars are removed completely, they won't be recovered) -const REGEXP_IS_ESCAPED = new RegExp(`(${ESCAPE_DOT}|^${ESCAPE_DOLLAR}|^${ESCAPE_TO_BSON}$|^${ESCAPE_BSON_TYPE}|^${ESCAPE_TO_STRING}$)`); +const REGEXP_IS_ESCAPED = new RegExp( + `(${ESCAPE_DOT}|^${ESCAPE_DOLLAR}|^${ESCAPE_TO_BSON}$|^${ESCAPE_BSON_TYPE}|^${ESCAPE_TO_STRING}$)`, +); const REGEXP_DOT = new RegExp(ESCAPE_DOT, 'g'); const REGEXP_DOLLAR = new RegExp(`^${ESCAPE_DOLLAR}`); @@ -281,15 +305,20 @@ export function unescapePropertyName(name: string) { * @returns {*} * @private */ -export function traverseObject(obj: Record, clone: boolean, transformFunc: (key: string, value: unknown) => [string, unknown]) { +export function traverseObject( + obj: Record, + clone: boolean, + transformFunc: (key: string, value: unknown) => [string, unknown], +) { // Primitive types don't need to be cloned or further traversed. // Buffer needs to be skipped otherwise this will iterate over the whole buffer which kills the event loop. if ( - obj === null - || typeof obj !== 'object' - || Object.prototype.toString.call(obj) === '[object Date]' - || isBuffer(obj) - ) return obj; + obj === null || + typeof obj !== 'object' || + Object.prototype.toString.call(obj) === '[object Date]' || + isBuffer(obj) + ) + return obj; let result; @@ -306,7 +335,8 @@ export function traverseObject(obj: Record, clone: boolean, transfo // obj is an object, all keys need to be checked result = clone ? {} : obj; - for (const key in obj) { // eslint-disable-line no-restricted-syntax, guard-for-in + // eslint-disable-next-line no-restricted-syntax, guard-for-in + for (const key in obj) { const val = traverseObject(obj[key], clone, transformFunc); const [transformedKey, transformedVal] = transformFunc(key, val); if (key === transformedKey) { @@ -372,7 +402,7 @@ export function isBadForMongo(obj: Record): boolean { } export class JsonVariable { - constructor(readonly name: string) { } + constructor(readonly name: string) {} getToken() { return `{{${this.name}}}`; @@ -386,8 +416,13 @@ export class JsonVariable { * In addition to that supports instances of JsonVariable('my.token') that are replaced * with a {{my.token}}. */ -export function jsonStringifyExtended(value: Record, replacer?: ((k: string, val: unknown) => unknown) | null, space = 0): string { - if (replacer && !(replacer instanceof Function)) throw new Error('Parameter "replacer" of jsonStringifyExtended() must be a function!'); +export function jsonStringifyExtended( + value: Record, + replacer?: ((k: string, val: unknown) => unknown) | null, + space = 0, +): string { + if (replacer && !(replacer instanceof Function)) + throw new Error('Parameter "replacer" of jsonStringifyExtended() must be a function!'); const replacements: Record = {}; diff --git a/packages/utilities/src/utilities.ts b/packages/utilities/src/utilities.ts index 15f28c869..e7dcb63b6 100644 --- a/packages/utilities/src/utilities.ts +++ b/packages/utilities/src/utilities.ts @@ -22,7 +22,9 @@ export function cryptoRandomObjectId(length = 17): string { const bytes = crypto.randomBytes(length); let str = ''; // eslint-disable-next-line - for (let i = bytes.length - 1; i >= 0; i--) { str += chars[(bytes[i] | 0) % chars.length]; } + for (let i = bytes.length - 1; i >= 0; i--) { + str += chars[(bytes[i] | 0) % chars.length]; + } return str; } @@ -54,7 +56,9 @@ export function getRandomInt(maxExcluded: number) { * This function is useful to convert dates transfered via JSON which doesn't natively support dates. */ export function parseDateFromJson(date: string | Date) { - if (typeof date === 'string') { return new Date(Date.parse(date)); } + if (typeof date === 'string') { + return new Date(Date.parse(date)); + } return date; } @@ -63,13 +67,13 @@ export function parseDateFromJson(date: string | Date) { * @param {number} millis Time to wait. If the value is not larger than zero, the promise resolves immediately. */ export async function delayPromise(millis: number): Promise { - return new Promise(((resolve) => { + return new Promise((resolve) => { if (millis > 0) { setTimeout(() => resolve(), millis); } else { resolve(); } - })); + }); } /** @@ -105,7 +109,12 @@ export function http404Route(req: RequestLike, res: ResponseLike) { /** * Default error handler of Express API endpoints. */ -export function expressErrorHandler(err: Error, req: RequestLike, res: ResponseLike, next: (...a: unknown[]) => unknown) { +export function expressErrorHandler( + err: Error, + req: RequestLike, + res: ResponseLike, + next: (...a: unknown[]) => unknown, +) { log.warning('Client HTTP request failed', { url: req.url, errMsg: err.message }); if (res.headersSent) { next(err); @@ -126,7 +135,10 @@ export type BetterIntervalID = { _betterClearInterval: () => void }; * @param delay The number of milliseconds to wait to next invocation of the function after the current invocation finishes. * @returns Object that can be passed to betterClearInterval() */ -export function betterSetInterval(func: ((a: (...args: unknown[]) => unknown) => void) | ((...args: unknown[]) => unknown), delay: number): BetterIntervalID { +export function betterSetInterval( + func: ((a: (...args: unknown[]) => unknown) => void) | ((...args: unknown[]) => unknown), + delay: number, +): BetterIntervalID { let scheduleNextRun: () => void; let timeoutId: NodeJS.Timeout; let isRunning = true; @@ -203,75 +215,309 @@ export function weightedAverage(val1: number, weight1: number, val2: number, wei */ const FORBIDDEN_USERNAMES_REGEXPS = [ // App routes - 'page-not-found', 'docs', 'terms-of-use', 'about', 'pricing', 'privacy-policy', 'customers', - 'request-form', 'request-solution', 'release-notes', 'jobs', 'api-reference', 'video-tutorials', - 'acts', 'key-value-stores', 'schedules', 'account', 'sign-up', 'sign-in-discourse', 'admin', - 'documentation', 'change-password', 'enroll-account', 'forgot-password', 'reset-password', - 'sign-in', 'verify-email', 'live-status', 'browser-info', 'webhooks', 'health-check', 'api', - 'change-log', 'dashboard', 'community', 'crawlers', 'ext', 'schemas', + 'page-not-found', + 'docs', + 'terms-of-use', + 'about', + 'pricing', + 'privacy-policy', + 'customers', + 'request-form', + 'request-solution', + 'release-notes', + 'jobs', + 'api-reference', + 'video-tutorials', + 'acts', + 'key-value-stores', + 'schedules', + 'account', + 'sign-up', + 'sign-in-discourse', + 'admin', + 'documentation', + 'change-password', + 'enroll-account', + 'forgot-password', + 'reset-password', + 'sign-in', + 'verify-email', + 'live-status', + 'browser-info', + 'webhooks', + 'health-check', + 'api', + 'change-log', + 'dashboard', + 'community', + 'crawlers', + 'ext', + 'schemas', // Various strings - 'admin', 'administration', 'crawler', 'act', 'library', 'lib', 'apifier', 'team', - 'contact', 'doc', 'documentation', 'for-business', 'for-developers', 'developers', 'business', - 'integrations', 'job', 'setting', 'settings', 'privacy', 'policy', 'assets', 'help', - 'config', 'configuration', 'terms', 'hiring', 'hire', 'status', 'status-page', 'solutions', - 'support', 'market', 'marketplace', 'download', 'downloads', 'username', 'users', 'user', - 'login', 'logout', 'signin', 'sign', 'signup', 'sign-out', 'signout', 'plugins', 'plug-ins', - 'reset', 'password', 'passwords', 'square', 'profile-photos', 'profiles', 'true', 'false', - 'js', 'css', 'img', 'images', 'image', 'partials', 'fonts', 'font', 'dynamic_templates', - 'app', 'schedules', 'community', 'storage', 'storages', 'account', 'node_modules', 'bower_components', - 'video', 'knowledgebase', 'forum', 'customers', 'blog', 'health-check', 'health', 'anim', - 'forum_topics.json', 'forum_categories.json', 'me', 'you', 'him', 'she', 'it', 'external', - 'actor', 'crawler', 'scheduler', 'api', 'sdk', 'puppeteer', 'webdriver', - 'selenium', '(selenium.*webdriver)', 'undefined', 'page-analyzer', 'wp-login.php', - 'welcome.action', 'echo', 'proxy', 'super-proxy', 'gdpr', 'case-studies', 'use-cases', 'how-to', - 'kb', 'cookies', 'cookie-policy', 'cookies-policy', 'powered-by', 'run', 'runs', 'actor', 'actors', - 'act', 'acts', 'success-stories', 'roadmap', 'join-marketplace', 'presskit', 'press-kit', 'covid-19', - 'covid', 'covid19', 'matfyz', 'ideas', 'public-actors', 'resources', 'partners', 'affiliate', - 'industries', 'web-scraping', 'custom-solutions', 'solution-provider', 'alternatives', 'platform', - 'freelancers', 'freelancer', 'partner', 'preview', 'templates', 'data-for-generative-ai', - 'discord', 'praguecrawl', 'prague-crawl', 'bob', 'ai-agents', 'reel', 'video-reel', - 'mcp', 'mcpc', 'model-context-protocol', 'modelcontextprotocol', + 'admin', + 'administration', + 'crawler', + 'act', + 'library', + 'lib', + 'apifier', + 'team', + 'contact', + 'doc', + 'documentation', + 'for-business', + 'for-developers', + 'developers', + 'business', + 'integrations', + 'job', + 'setting', + 'settings', + 'privacy', + 'policy', + 'assets', + 'help', + 'config', + 'configuration', + 'terms', + 'hiring', + 'hire', + 'status', + 'status-page', + 'solutions', + 'support', + 'market', + 'marketplace', + 'download', + 'downloads', + 'username', + 'users', + 'user', + 'login', + 'logout', + 'signin', + 'sign', + 'signup', + 'sign-out', + 'signout', + 'plugins', + 'plug-ins', + 'reset', + 'password', + 'passwords', + 'square', + 'profile-photos', + 'profiles', + 'true', + 'false', + 'js', + 'css', + 'img', + 'images', + 'image', + 'partials', + 'fonts', + 'font', + 'dynamic_templates', + 'app', + 'schedules', + 'community', + 'storage', + 'storages', + 'account', + 'node_modules', + 'bower_components', + 'video', + 'knowledgebase', + 'forum', + 'customers', + 'blog', + 'health-check', + 'health', + 'anim', + 'forum_topics.json', + 'forum_categories.json', + 'me', + 'you', + 'him', + 'she', + 'it', + 'external', + 'actor', + 'crawler', + 'scheduler', + 'api', + 'sdk', + 'puppeteer', + 'webdriver', + 'selenium', + '(selenium.*webdriver)', + 'undefined', + 'page-analyzer', + 'wp-login.php', + 'welcome.action', + 'echo', + 'proxy', + 'super-proxy', + 'gdpr', + 'case-studies', + 'use-cases', + 'how-to', + 'kb', + 'cookies', + 'cookie-policy', + 'cookies-policy', + 'powered-by', + 'run', + 'runs', + 'actor', + 'actors', + 'act', + 'acts', + 'success-stories', + 'roadmap', + 'join-marketplace', + 'presskit', + 'press-kit', + 'covid-19', + 'covid', + 'covid19', + 'matfyz', + 'ideas', + 'public-actors', + 'resources', + 'partners', + 'affiliate', + 'industries', + 'web-scraping', + 'custom-solutions', + 'solution-provider', + 'alternatives', + 'platform', + 'freelancers', + 'freelancer', + 'partner', + 'preview', + 'templates', + 'data-for-generative-ai', + 'discord', + 'praguecrawl', + 'prague-crawl', + 'bob', + 'ai-agents', + 'reel', + 'video-reel', + 'mcp', + 'mcpc', + 'model-context-protocol', + 'modelcontextprotocol', // 'apify.com' intentionally unescaped so "." matches any character, also blocking variants like "apify_com" or "apifyxcom" - 'apify.com', 'design-kit', 'press-kit', - 'scrapers', 'professional-services', 'challenge', 'challange', '1m-challenge', '1m-usd-challenge', + 'apify.com', + 'design-kit', + 'press-kit', + 'scrapers', + 'professional-services', + 'challenge', + 'challange', + '1m-challenge', + '1m-usd-challenge', // Apify platform resources - 'key-value-store', 'request-queue', 'request-queues', 'builds', 'schedule', + 'key-value-store', + 'request-queue', + 'request-queues', + 'builds', + 'schedule', // AI / LLM related - 'llms', 'openai', 'anthropic', 'claude', 'copilot', 'mistral', - 'prompt', 'prompts', 'embeddings', 'vectors', + 'llms', + 'openai', + 'anthropic', + 'claude', + 'copilot', + 'mistral', + 'prompt', + 'prompts', + 'embeddings', + 'vectors', // Organizations / workspaces / permissions - 'orgs', 'workspaces', 'tenant', 'tenants', 'permission', 'permissions', + 'orgs', + 'workspaces', + 'tenant', + 'tenants', + 'permission', + 'permissions', // Auth / security - 'saml', 'scim', 'token', 'tokens', 'api-key', 'api-keys', + 'saml', + 'scim', + 'token', + 'tokens', + 'api-key', + 'api-keys', // Billing / commerce - 'coupon', 'coupons', 'discount', 'discounts', 'promos', - 'refund', 'refunds', 'credit', 'credits', 'billing-portal', + 'coupon', + 'coupons', + 'discount', + 'discounts', + 'promos', + 'refund', + 'refunds', + 'credit', + 'credits', + 'billing-portal', // Infrastructure / environments - 'production', 'canary', 'restricted', + 'production', + 'canary', + 'restricted', // Communication - 'conversations', 'reactions', 'mentions', + 'conversations', + 'reactions', + 'mentions', // Legal / trust / web standards - 'accessibility', 'imprint', 'impressum', 'trust', 'trust-center', 'security-center', + 'accessibility', + 'imprint', + 'impressum', + 'trust', + 'trust-center', + 'security-center', // Brand protection / impersonation prevention - 'brand', 'branding', 'verified', 'apify-support', 'apify-team', 'support-team', 'abuse', + 'brand', + 'branding', + 'verified', + 'apify-support', + 'apify-team', + 'support-team', + 'abuse', // Incidents / updates - 'outage', 'incident', 'incidents', 'what-is-new', + 'outage', + 'incident', + 'incidents', + 'what-is-new', // Special files - 'index', 'index\\.html', '(favicon\\.[a-z]+)', 'BingSiteAuth.xml', '(google.+\\.html)', 'robots\\.txt', - '(sitemap\\.[a-z]+)', '(apple-touch-icon.*)', 'security-whitepaper\\.pdf', 'security\\.txt', 'llms\\.txt', - 'llms-full\\.txt', 'AGENTS\\.md', 'CLAUDE\\.md', + 'index', + 'index\\.html', + '(favicon\\.[a-z]+)', + 'BingSiteAuth.xml', + '(google.+\\.html)', + 'robots\\.txt', + '(sitemap\\.[a-z]+)', + '(apple-touch-icon.*)', + 'security-whitepaper\\.pdf', + 'security\\.txt', + 'llms\\.txt', + 'llms-full\\.txt', + 'AGENTS\\.md', + 'CLAUDE\\.md', // All hidden files '(\\..*)', @@ -292,70 +538,609 @@ const FORBIDDEN_USERNAMES_REGEXPS = [ '(.*[_.\\-]{2}.*)', // Reserved usernames from https://github.com/shouldbee/reserved-usernames/blob/master/reserved-usernames.json - '0', 'about', 'access', 'account', 'accounts', 'activate', 'activities', 'activity', 'ad', 'add', - 'address', 'adm', 'admin', 'administration', 'administrator', 'ads', 'adult', 'advertising', - 'affiliate', 'affiliates', 'ajax', 'all', 'alpha', 'analysis', 'analytics', 'android', 'anon', - 'anonymous', 'api', 'app', 'apps', 'archive', 'archives', 'article', 'asct', 'asset', 'atom', - 'auth', 'authentication', 'avatar', 'backup', 'balancer-manager', 'banner', 'banners', 'beta', - 'billing', 'bin', 'blog', 'blogs', 'board', 'book', 'bookmark', 'bot', 'bots', 'bug', 'business', - 'cache', 'cadastro', 'calendar', 'call', 'campaign', 'cancel', 'captcha', 'career', 'careers', - 'cart', 'categories', 'category', 'cgi', 'cgi-bin', 'changelog', 'chat', 'check', 'checking', - 'checkout', 'client', 'cliente', 'clients', 'code', 'codereview', 'comercial', 'comment', - 'comments', 'communities', 'community', 'company', 'compare', 'compras', 'config', 'configuration', - 'connect', 'contact', 'contact-us', 'contact_us', 'contactus', 'contest', 'contribute', 'corp', - 'create', 'css', 'dashboard', 'data', 'db', 'default', 'delete', 'demo', 'design', 'designer', - 'destroy', 'dev', 'devel', 'developer', 'developers', 'diagram', 'diary', 'dict', 'dictionary', - 'die', 'dir', 'direct_messages', 'directory', 'dist', 'doc', 'docs', 'documentation', 'domain', - 'download', 'downloads', 'ecommerce', 'edit', 'editor', 'edu', 'education', 'email', 'employment', - 'empty', 'end', 'enterprise', 'entries', 'entry', 'error', 'errors', 'eval', 'event', 'events', 'exit', - 'explore', 'facebook', 'faq', 'favorite', 'favorites', 'feature', 'features', 'feed', 'feedback', - 'feeds', 'file', 'files', 'first', 'flash', 'fleet', 'fleets', 'flog', 'follow', 'followers', - 'following', 'forgot', 'form', 'forum', 'forums', 'founder', 'free', 'friend', 'friends', 'ftp', - 'gadget', 'gadgets', 'game', 'games', 'get', 'gift', 'gifts', 'gist', 'github', 'graph', 'group', - 'groups', 'guest', 'guests', 'help', 'home', 'homepage', 'host', 'hosting', 'hostmaster', - 'hostname', 'howto', 'hpg', 'html', 'http', 'httpd', 'https', 'i', 'iamges', 'icon', 'icons', - 'id', 'idea', 'ideas', 'image', 'images', 'imap', 'img', 'index', 'indice', 'info', 'information', - 'inquiry', 'instagram', 'intranet', 'invitations', 'invite', 'ipad', 'iphone', 'irc', 'is', - 'issue', 'issues', 'it', 'item', 'items', 'java', 'javascript', 'job', 'jobs', 'join', 'js', - 'json', 'jump', 'knowledgebase', 'language', 'languages', 'last', 'ldap-status', 'legal', 'license', - 'link', 'links', 'linux', 'list', 'lists', 'log', 'log-in', 'log-out', 'log_in', 'log_out', - 'login', 'logout', 'logs', 'm', 'mac', 'mail', 'mail1', 'mail2', 'mail3', 'mail4', 'mail5', - 'mailer', 'mailing', 'maintenance', 'manager', 'manual', 'map', 'maps', 'marketing', 'master', - 'me', 'media', 'member', 'members', 'message', 'messages', 'messenger', 'microblog', 'microblogs', - 'mine', 'mis', 'mob', 'mobile', 'movie', 'movies', 'mp3', 'msg', 'msn', 'music', 'musicas', 'mx', - 'my', 'mysql', 'name', 'named', 'nan', 'navi', 'navigation', 'net', 'network', 'new', 'news', - 'newsletter', 'nick', 'nickname', 'notes', 'noticias', 'notification', 'notifications', 'notify', - 'ns', 'ns1', 'ns10', 'ns2', 'ns3', 'ns4', 'ns5', 'ns6', 'ns7', 'ns8', 'ns9', 'null', 'oauth', - 'oauth_clients', 'offer', 'offers', 'official', 'old', 'online', 'openid', 'operator', 'order', - 'orders', 'organization', 'organizations', 'overview', 'owner', 'owners', 'page', 'pager', - 'pages', 'panel', 'password', 'payment', 'perl', 'phone', 'photo', 'photoalbum', 'photos', 'php', - 'phpmyadmin', 'phppgadmin', 'phpredisadmin', 'pic', 'pics', 'ping', 'plan', 'plans', 'plugin', - 'plugins', 'policy', 'pop', 'pop3', 'popular', 'portal', 'post', 'postfix', 'postmaster', 'posts', - 'pr', 'premium', 'press', 'price', 'pricing', 'privacy', 'privacy-policy', 'privacy_policy', - 'privacypolicy', 'private', 'product', 'products', 'profile', 'project', 'projects', 'promo', - 'pub', 'public', 'purpose', 'put', 'python', 'query', 'random', 'ranking', 'read', 'readme', - 'recent', 'recruit', 'recruitment', 'register', 'registration', 'release', 'remove', 'replies', - 'report', 'reports', 'repositories', 'repository', 'req', 'request', 'requests', 'reset', 'roc', - 'root', 'rss', 'ruby', 'rule', 'sag', 'sale', 'sales', 'sample', 'samples', 'save', 'school', - 'script', 'scripts', 'search', 'secure', 'security', 'self', 'send', 'server', 'server-info', - 'server-status', 'service', 'services', 'session', 'sessions', 'setting', 'settings', 'setup', - 'share', 'shop', 'show', 'sign-in', 'sign-up', 'sign_in', 'sign_up', 'signin', 'signout', 'signup', - 'site', 'sitemap', 'sites', 'smartphone', 'smtp', 'soporte', 'source', 'spec', 'special', 'sql', - 'src', 'ssh', 'ssl', 'ssladmin', 'ssladministrator', 'sslwebmaster', 'staff', 'stage', 'staging', - 'start', 'stat', 'state', 'static', 'stats', 'status', 'store', 'stores', 'stories', 'style', - 'styleguide', 'stylesheet', 'stylesheets', 'subdomain', 'subscribe', 'subscription', 'subscriptions', 'suporte', - 'support', 'svn', 'swf', 'sys', 'sysadmin', 'sysadministrator', 'system', 'tablet', 'tablets', - 'tag', 'talk', 'task', 'tasks', 'team', 'teams', 'tech', 'telnet', 'term', 'terms', - 'terms-of-service', 'terms_of_service', 'termsofservice', 'test', 'test1', 'test2', 'test3', - 'teste', 'testing', 'tests', 'theme', 'themes', 'thread', 'threads', 'tmp', 'todo', 'tool', - 'tools', 'top', 'topic', 'topics', 'tos', 'tour', 'translations', 'trends', 'tutorial', 'tux', - 'tv', 'twitter', 'undef', 'unfollow', 'unsubscribe', 'update', 'upload', 'uploads', 'url', - 'usage', 'user', 'username', 'users', 'usuario', 'vendas', 'ver', 'version', 'video', 'videos', - 'visitor', 'watch', 'weather', 'web', 'webhook', 'webhooks', 'webmail', 'webmaster', 'website', - 'websites', 'welcome', 'widget', 'widgets', 'wiki', 'win', 'windows', 'word', 'work', 'works', - 'workshop', 'ww', 'wws', 'www', 'www1', 'www2', 'www3', 'www4', 'www5', 'www6', 'www7', 'wwws', - 'wwww', 'xfn', 'xml', 'xmpp', 'xpg', 'xxx', 'yaml', 'year', 'yml', 'you', 'yourdomain', 'yourname', - 'yoursite', 'yourusername', + '0', + 'about', + 'access', + 'account', + 'accounts', + 'activate', + 'activities', + 'activity', + 'ad', + 'add', + 'address', + 'adm', + 'admin', + 'administration', + 'administrator', + 'ads', + 'adult', + 'advertising', + 'affiliate', + 'affiliates', + 'ajax', + 'all', + 'alpha', + 'analysis', + 'analytics', + 'android', + 'anon', + 'anonymous', + 'api', + 'app', + 'apps', + 'archive', + 'archives', + 'article', + 'asct', + 'asset', + 'atom', + 'auth', + 'authentication', + 'avatar', + 'backup', + 'balancer-manager', + 'banner', + 'banners', + 'beta', + 'billing', + 'bin', + 'blog', + 'blogs', + 'board', + 'book', + 'bookmark', + 'bot', + 'bots', + 'bug', + 'business', + 'cache', + 'cadastro', + 'calendar', + 'call', + 'campaign', + 'cancel', + 'captcha', + 'career', + 'careers', + 'cart', + 'categories', + 'category', + 'cgi', + 'cgi-bin', + 'changelog', + 'chat', + 'check', + 'checking', + 'checkout', + 'client', + 'cliente', + 'clients', + 'code', + 'codereview', + 'comercial', + 'comment', + 'comments', + 'communities', + 'community', + 'company', + 'compare', + 'compras', + 'config', + 'configuration', + 'connect', + 'contact', + 'contact-us', + 'contact_us', + 'contactus', + 'contest', + 'contribute', + 'corp', + 'create', + 'css', + 'dashboard', + 'data', + 'db', + 'default', + 'delete', + 'demo', + 'design', + 'designer', + 'destroy', + 'dev', + 'devel', + 'developer', + 'developers', + 'diagram', + 'diary', + 'dict', + 'dictionary', + 'die', + 'dir', + 'direct_messages', + 'directory', + 'dist', + 'doc', + 'docs', + 'documentation', + 'domain', + 'download', + 'downloads', + 'ecommerce', + 'edit', + 'editor', + 'edu', + 'education', + 'email', + 'employment', + 'empty', + 'end', + 'enterprise', + 'entries', + 'entry', + 'error', + 'errors', + 'eval', + 'event', + 'events', + 'exit', + 'explore', + 'facebook', + 'faq', + 'favorite', + 'favorites', + 'feature', + 'features', + 'feed', + 'feedback', + 'feeds', + 'file', + 'files', + 'first', + 'flash', + 'fleet', + 'fleets', + 'flog', + 'follow', + 'followers', + 'following', + 'forgot', + 'form', + 'forum', + 'forums', + 'founder', + 'free', + 'friend', + 'friends', + 'ftp', + 'gadget', + 'gadgets', + 'game', + 'games', + 'get', + 'gift', + 'gifts', + 'gist', + 'github', + 'graph', + 'group', + 'groups', + 'guest', + 'guests', + 'help', + 'home', + 'homepage', + 'host', + 'hosting', + 'hostmaster', + 'hostname', + 'howto', + 'hpg', + 'html', + 'http', + 'httpd', + 'https', + 'i', + 'iamges', + 'icon', + 'icons', + 'id', + 'idea', + 'ideas', + 'image', + 'images', + 'imap', + 'img', + 'index', + 'indice', + 'info', + 'information', + 'inquiry', + 'instagram', + 'intranet', + 'invitations', + 'invite', + 'ipad', + 'iphone', + 'irc', + 'is', + 'issue', + 'issues', + 'it', + 'item', + 'items', + 'java', + 'javascript', + 'job', + 'jobs', + 'join', + 'js', + 'json', + 'jump', + 'knowledgebase', + 'language', + 'languages', + 'last', + 'ldap-status', + 'legal', + 'license', + 'link', + 'links', + 'linux', + 'list', + 'lists', + 'log', + 'log-in', + 'log-out', + 'log_in', + 'log_out', + 'login', + 'logout', + 'logs', + 'm', + 'mac', + 'mail', + 'mail1', + 'mail2', + 'mail3', + 'mail4', + 'mail5', + 'mailer', + 'mailing', + 'maintenance', + 'manager', + 'manual', + 'map', + 'maps', + 'marketing', + 'master', + 'me', + 'media', + 'member', + 'members', + 'message', + 'messages', + 'messenger', + 'microblog', + 'microblogs', + 'mine', + 'mis', + 'mob', + 'mobile', + 'movie', + 'movies', + 'mp3', + 'msg', + 'msn', + 'music', + 'musicas', + 'mx', + 'my', + 'mysql', + 'name', + 'named', + 'nan', + 'navi', + 'navigation', + 'net', + 'network', + 'new', + 'news', + 'newsletter', + 'nick', + 'nickname', + 'notes', + 'noticias', + 'notification', + 'notifications', + 'notify', + 'ns', + 'ns1', + 'ns10', + 'ns2', + 'ns3', + 'ns4', + 'ns5', + 'ns6', + 'ns7', + 'ns8', + 'ns9', + 'null', + 'oauth', + 'oauth_clients', + 'offer', + 'offers', + 'official', + 'old', + 'online', + 'openid', + 'operator', + 'order', + 'orders', + 'organization', + 'organizations', + 'overview', + 'owner', + 'owners', + 'page', + 'pager', + 'pages', + 'panel', + 'password', + 'payment', + 'perl', + 'phone', + 'photo', + 'photoalbum', + 'photos', + 'php', + 'phpmyadmin', + 'phppgadmin', + 'phpredisadmin', + 'pic', + 'pics', + 'ping', + 'plan', + 'plans', + 'plugin', + 'plugins', + 'policy', + 'pop', + 'pop3', + 'popular', + 'portal', + 'post', + 'postfix', + 'postmaster', + 'posts', + 'pr', + 'premium', + 'press', + 'price', + 'pricing', + 'privacy', + 'privacy-policy', + 'privacy_policy', + 'privacypolicy', + 'private', + 'product', + 'products', + 'profile', + 'project', + 'projects', + 'promo', + 'pub', + 'public', + 'purpose', + 'put', + 'python', + 'query', + 'random', + 'ranking', + 'read', + 'readme', + 'recent', + 'recruit', + 'recruitment', + 'register', + 'registration', + 'release', + 'remove', + 'replies', + 'report', + 'reports', + 'repositories', + 'repository', + 'req', + 'request', + 'requests', + 'reset', + 'roc', + 'root', + 'rss', + 'ruby', + 'rule', + 'sag', + 'sale', + 'sales', + 'sample', + 'samples', + 'save', + 'school', + 'script', + 'scripts', + 'search', + 'secure', + 'security', + 'self', + 'send', + 'server', + 'server-info', + 'server-status', + 'service', + 'services', + 'session', + 'sessions', + 'setting', + 'settings', + 'setup', + 'share', + 'shop', + 'show', + 'sign-in', + 'sign-up', + 'sign_in', + 'sign_up', + 'signin', + 'signout', + 'signup', + 'site', + 'sitemap', + 'sites', + 'smartphone', + 'smtp', + 'soporte', + 'source', + 'spec', + 'special', + 'sql', + 'src', + 'ssh', + 'ssl', + 'ssladmin', + 'ssladministrator', + 'sslwebmaster', + 'staff', + 'stage', + 'staging', + 'start', + 'stat', + 'state', + 'static', + 'stats', + 'status', + 'store', + 'stores', + 'stories', + 'style', + 'styleguide', + 'stylesheet', + 'stylesheets', + 'subdomain', + 'subscribe', + 'subscription', + 'subscriptions', + 'suporte', + 'support', + 'svn', + 'swf', + 'sys', + 'sysadmin', + 'sysadministrator', + 'system', + 'tablet', + 'tablets', + 'tag', + 'talk', + 'task', + 'tasks', + 'team', + 'teams', + 'tech', + 'telnet', + 'term', + 'terms', + 'terms-of-service', + 'terms_of_service', + 'termsofservice', + 'test', + 'test1', + 'test2', + 'test3', + 'teste', + 'testing', + 'tests', + 'theme', + 'themes', + 'thread', + 'threads', + 'tmp', + 'todo', + 'tool', + 'tools', + 'top', + 'topic', + 'topics', + 'tos', + 'tour', + 'translations', + 'trends', + 'tutorial', + 'tux', + 'tv', + 'twitter', + 'undef', + 'unfollow', + 'unsubscribe', + 'update', + 'upload', + 'uploads', + 'url', + 'usage', + 'user', + 'username', + 'users', + 'usuario', + 'vendas', + 'ver', + 'version', + 'video', + 'videos', + 'visitor', + 'watch', + 'weather', + 'web', + 'webhook', + 'webhooks', + 'webmail', + 'webmaster', + 'website', + 'websites', + 'welcome', + 'widget', + 'widgets', + 'wiki', + 'win', + 'windows', + 'word', + 'work', + 'works', + 'workshop', + 'ww', + 'wws', + 'www', + 'www1', + 'www2', + 'www3', + 'www4', + 'www5', + 'www6', + 'www7', + 'wwws', + 'wwww', + 'xfn', + 'xml', + 'xmpp', + 'xpg', + 'xxx', + 'yaml', + 'year', + 'yml', + 'you', + 'yourdomain', + 'yourname', + 'yoursite', + 'yourusername', ]; // Regex matching forbidden usernames. @@ -387,12 +1172,19 @@ export async function sequentializePromises(promises: (Promise | (() => Pr /** * Helper function for validation if parameter is an instance of given prototype or multiple prototypes. */ -export function checkParamPrototypeOrThrow(paramVal: any, paramName: string, prototypes: any, prototypeName: string, isOptional = false) { +export function checkParamPrototypeOrThrow( + paramVal: any, + paramName: string, + prototypes: any, + prototypeName: string, + isOptional = false, +) { if (isOptional && (paramVal === undefined || paramVal === null)) return; - const hasCorrectPrototype = prototypes instanceof Array - ? prototypes.some((prototype) => paramVal instanceof prototype) - : paramVal instanceof prototypes; + const hasCorrectPrototype = + prototypes instanceof Array + ? prototypes.some((prototype) => paramVal instanceof prototype) + : paramVal instanceof prototypes; if (!hasCorrectPrototype) throw new Error(`Parameter "${paramName}" must be an instance of ${prototypeName}`); } @@ -447,7 +1239,11 @@ export function configureLogger(givenLog: Log, isProduction?: boolean) { /** * Wraps given promise with timeout. */ -export async function timeoutPromise(promise: Promise, timeoutMillis: number, errorMessage = 'Promise has timed-out') { +export async function timeoutPromise( + promise: Promise, + timeoutMillis: number, + errorMessage = 'Promise has timed-out', +) { return new Promise((resolve, reject) => { let timeout: number; let hasFulfilled = false; diff --git a/packages/utilities/src/webhook_payload_template.ts b/packages/utilities/src/webhook_payload_template.ts index c60314d26..dd5e2106f 100644 --- a/packages/utilities/src/webhook_payload_template.ts +++ b/packages/utilities/src/webhook_payload_template.ts @@ -1,4 +1,3 @@ - import { jsonStringifyExtended, JsonVariable } from './utilities.client'; class WebhookPayloadTemplateError extends Error { @@ -69,11 +68,13 @@ interface ParsePosition { export class WebhookPayloadTemplate { private payload: string; - readonly replacedVariables: { variableName: string, replacement: string }[] = []; + readonly replacedVariables: { variableName: string; replacement: string }[] = []; - constructor(private readonly template: string, - private readonly allowedVariables: Set | null = null, - private readonly context: Record = {}) { + constructor( + private readonly template: string, + private readonly allowedVariables: Set | null = null, + private readonly context: Record = {}, + ) { this.payload = template; } @@ -206,7 +207,7 @@ export class WebhookPayloadTemplate { private _findPositionOfNextVariable(startIndex = 0): ParsePosition | null { const openBraceIndex = this.payload.indexOf('{{', startIndex); const closeBraceIndex = this.payload.indexOf('}}', openBraceIndex) + 1; - const someVariableMaybeExists = (openBraceIndex > -1) && (closeBraceIndex > -1); + const someVariableMaybeExists = openBraceIndex > -1 && closeBraceIndex > -1; if (!someVariableMaybeExists) return null; const isInsideString = this._isVariableInsideString(openBraceIndex); return { isInsideString, openBraceIndex, closeBraceIndex }; @@ -235,7 +236,8 @@ export class WebhookPayloadTemplate { this._validateVariableName(variableName); const replacement = this._getVariableReplacement(variableName)!; this.replacedVariables.push({ variableName, replacement }); - this.payload = this.payload.substring(0, openBraceIndex) + replacement + this.payload.substring(closeBraceIndex + 1); + this.payload = + this.payload.substring(0, openBraceIndex) + replacement + this.payload.substring(closeBraceIndex + 1); } private _validateVariableName(variableName: string): void { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index de2ba0625..24678951f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,6 +50,9 @@ importers: nock: specifier: ^14.0.0 version: 14.0.12 + oxfmt: + specifier: 0.46.0 + version: 0.46.0 oxlint: specifier: 1.62.0 version: 1.62.0(oxlint-tsgolint@0.22.0) @@ -917,6 +920,128 @@ packages: '@oxc-project/types@0.124.0': resolution: {integrity: sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==} + '@oxfmt/binding-android-arm-eabi@0.46.0': + resolution: {integrity: sha512-b1doV4WRcJU+BESSlCvCjV+5CEr/T6h0frArAdV26Nir+gGNFNaylvDiiMPfF1pxeV0txZEs38ojzJaxBYg+ng==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxfmt/binding-android-arm64@0.46.0': + resolution: {integrity: sha512-v6+HhjsoV3GO0u2u9jLSAZrvWfTraDxKofUIQ7/ktS7tzS+epVsxdHmeM+XxuNcAY/nWxxU1Sg4JcGTNRXraBA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxfmt/binding-darwin-arm64@0.46.0': + resolution: {integrity: sha512-3eeooJGrqGIlI5MyryDZsAcKXSmKIgAD4yYtfRrRJzXZ0UTFZtiSveIur56YPrGMYZwT4XyVhHsMqrNwr1XeFA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxfmt/binding-darwin-x64@0.46.0': + resolution: {integrity: sha512-QG8BDM0CXWbu84k2SKmCqfEddPQPFiBicwtYnLqHRWZZl57HbtOLRMac/KTq2NO4AEc4ICCBpFxJIV9zcqYfkQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxfmt/binding-freebsd-x64@0.46.0': + resolution: {integrity: sha512-9DdCqS/n2ncu/Chazvt3cpgAjAmIGQDz7hFKSrNItMApyV/Ja9mz3hD4JakIE3nS8PW9smEbPWnb389QLBY4nw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxfmt/binding-linux-arm-gnueabihf@0.46.0': + resolution: {integrity: sha512-Dgs7VeE2jT0LHMhw6tPEt0xQYe54kBqHEovmWsv4FVQlegCOvlIJNx0S8n4vj8WUtpT+Z6BD2HhKJPLglLxvZg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm-musleabihf@0.46.0': + resolution: {integrity: sha512-Zxn3adhTH13JKnU4xXJj8FeEfF680XjXh3gSShKl57HCMBRde2tUJTgogV/1MSHA80PJEVrDa7r66TLVq3Ia7Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm64-gnu@0.46.0': + resolution: {integrity: sha512-+TWipjrgVM8D7aIdDD0tlr3teLTTvQTn7QTE5BpT10H1Fj82gfdn9X6nn2sDgx/MepuSCfSnzFNJq2paLL0OiA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-arm64-musl@0.46.0': + resolution: {integrity: sha512-aAUPBWJ1lGwwnxZUEDLJ94+Iy6MuwJwPxUgO4sCA5mEEyDk7b+cDQ+JpX1VR150Zoyd+D49gsrUzpUK5h587Eg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-ppc64-gnu@0.46.0': + resolution: {integrity: sha512-ufBCJukyFX/UDrokP/r6BGDoTInnsDs7bxyzKAgMiZlt2Qu8GPJSJ6Zm6whIiJzKk0naxA8ilwmbO1LMw6Htxw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-gnu@0.46.0': + resolution: {integrity: sha512-eqtlC2YmPqjun76R1gVfGLuKWx7NuEnLEAudZ7n6ipSKbCZTqIKSs1b5Y8K/JHZsRpLkeSmAAjig5HOIg8fQzQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-musl@0.46.0': + resolution: {integrity: sha512-yccVOO2nMXkQLGgy0He3EQEwKD7NF0zEk+/OWmroznkqXyJdN6bfK0LtNnr6/14Bh3FjpYq7bP33l/VloCnxpA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-s390x-gnu@0.46.0': + resolution: {integrity: sha512-aAf7fG23OQCey6VRPj9IeCraoYtpgtx0ZyJ1CXkPyT1wjzBE7c3xtuxHe/AdHaJfVVb/SXpSk8Gl1LzyQupSqw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-gnu@0.46.0': + resolution: {integrity: sha512-q0JPsTMyJNjYrBvYFDz4WbVsafNZaPCZv4RnFypRotLqpKROtBZcEaXQW4eb9YmvLU3NckVemLJnzkSZSdmOxw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-musl@0.46.0': + resolution: {integrity: sha512-7LsLY9Cw57GPkhSR+duI3mt9baRczK/DtHYSldQ4BEU92da9igBQNl4z7Vq5U9NNPsh1FmpKvv1q9WDtiUQR1A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-openharmony-arm64@0.46.0': + resolution: {integrity: sha512-lHiBOz8Duaku7JtRNLlps3j++eOaICPZSd8FCVmTDM4DFOPT71Bjn7g6iar1z7StXlKRweUKxWUs4sA+zWGDXg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxfmt/binding-win32-arm64-msvc@0.46.0': + resolution: {integrity: sha512-/5ktYUliP89RhgC37DBH1x20U5zPSZMy3cMEcO0j3793rbHP9MWsknBwQB6eozRzWmYrh0IFM/p20EbPvDlYlg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxfmt/binding-win32-ia32-msvc@0.46.0': + resolution: {integrity: sha512-3WTnoiuIr8XvV0DIY7SN+1uJSwKf4sPpcbHfobcRT9JutGcLaef/miyBB87jxd3aqH+mS0+G5lsgHuXLUwjjpQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxfmt/binding-win32-x64-msvc@0.46.0': + resolution: {integrity: sha512-IXxiQpkYnOwNfP23vzwSfhdpxJzyiPTY7eTn6dn3DsriKddESzM8i6kfq9R7CD/PUJwCvQT22NgtygBeug3KoA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@oxlint-tsgolint/darwin-arm64@0.22.0': resolution: {integrity: sha512-/exgXceakHbQrzaHTtKOe7MuDATaWMCCWpsCDQCZKeYhLGXzComipTrCYnHzAXrdnNBb5r5K+RRf5A6ormrhMA==} cpu: [arm64] @@ -3105,6 +3230,11 @@ packages: resolution: {integrity: sha512-dD4UpyBh/9m4X2NVjA+73/ZPBRF+uF4zIMFvvQsabMiEK8x41L3rQ8EENOi35kyyoaJwNxEeJcP6Fj1H4U409Q==} engines: {node: '>=12'} + oxfmt@0.46.0: + resolution: {integrity: sha512-CopwJOwPAjZ9p76fCvz+mSOJTw9/NY3cSksZK3VO/bUQ8UoEcketNgUuYS0UB3p+R9XnXe7wGGXUmyFxc7QxJA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + oxlint-tsgolint@0.22.0: resolution: {integrity: sha512-ku4MecLmCQIj1ScCtzNAqTuyl0BJQ02B36fJT+c5XQihHpYSFak+FC3GYO5fPyYk4oDwi0w0S7hTvrpNzuZhig==} hasBin: true @@ -3739,6 +3869,10 @@ packages: resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} engines: {node: '>=12.0.0'} + tinypool@2.1.0: + resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} + engines: {node: ^20.0.0 || >=22.0.0} + tinyrainbow@3.1.0: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} @@ -4799,6 +4933,63 @@ snapshots: '@oxc-project/types@0.124.0': {} + '@oxfmt/binding-android-arm-eabi@0.46.0': + optional: true + + '@oxfmt/binding-android-arm64@0.46.0': + optional: true + + '@oxfmt/binding-darwin-arm64@0.46.0': + optional: true + + '@oxfmt/binding-darwin-x64@0.46.0': + optional: true + + '@oxfmt/binding-freebsd-x64@0.46.0': + optional: true + + '@oxfmt/binding-linux-arm-gnueabihf@0.46.0': + optional: true + + '@oxfmt/binding-linux-arm-musleabihf@0.46.0': + optional: true + + '@oxfmt/binding-linux-arm64-gnu@0.46.0': + optional: true + + '@oxfmt/binding-linux-arm64-musl@0.46.0': + optional: true + + '@oxfmt/binding-linux-ppc64-gnu@0.46.0': + optional: true + + '@oxfmt/binding-linux-riscv64-gnu@0.46.0': + optional: true + + '@oxfmt/binding-linux-riscv64-musl@0.46.0': + optional: true + + '@oxfmt/binding-linux-s390x-gnu@0.46.0': + optional: true + + '@oxfmt/binding-linux-x64-gnu@0.46.0': + optional: true + + '@oxfmt/binding-linux-x64-musl@0.46.0': + optional: true + + '@oxfmt/binding-openharmony-arm64@0.46.0': + optional: true + + '@oxfmt/binding-win32-arm64-msvc@0.46.0': + optional: true + + '@oxfmt/binding-win32-ia32-msvc@0.46.0': + optional: true + + '@oxfmt/binding-win32-x64-msvc@0.46.0': + optional: true + '@oxlint-tsgolint/darwin-arm64@0.22.0': optional: true @@ -6962,6 +7153,30 @@ snapshots: lodash.isequal: 4.5.0 vali-date: 1.0.0 + oxfmt@0.46.0: + dependencies: + tinypool: 2.1.0 + optionalDependencies: + '@oxfmt/binding-android-arm-eabi': 0.46.0 + '@oxfmt/binding-android-arm64': 0.46.0 + '@oxfmt/binding-darwin-arm64': 0.46.0 + '@oxfmt/binding-darwin-x64': 0.46.0 + '@oxfmt/binding-freebsd-x64': 0.46.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.46.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.46.0 + '@oxfmt/binding-linux-arm64-gnu': 0.46.0 + '@oxfmt/binding-linux-arm64-musl': 0.46.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.46.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.46.0 + '@oxfmt/binding-linux-riscv64-musl': 0.46.0 + '@oxfmt/binding-linux-s390x-gnu': 0.46.0 + '@oxfmt/binding-linux-x64-gnu': 0.46.0 + '@oxfmt/binding-linux-x64-musl': 0.46.0 + '@oxfmt/binding-openharmony-arm64': 0.46.0 + '@oxfmt/binding-win32-arm64-msvc': 0.46.0 + '@oxfmt/binding-win32-ia32-msvc': 0.46.0 + '@oxfmt/binding-win32-x64-msvc': 0.46.0 + oxlint-tsgolint@0.22.0: optionalDependencies: '@oxlint-tsgolint/darwin-arm64': 0.22.0 @@ -7651,6 +7866,8 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinypool@2.1.0: {} + tinyrainbow@3.1.0: {} tmp@0.2.5: {} diff --git a/scripts/tsup.config.ts b/scripts/tsup.config.ts index 89491a490..bfc34b5c8 100644 --- a/scripts/tsup.config.ts +++ b/scripts/tsup.config.ts @@ -3,19 +3,21 @@ import { defineConfig, type Options } from 'tsup'; import { readFileSync, readdirSync } from 'node:fs'; const packages = readdirSync(resolveDir(__dirname, '../packages')); -const excludes = packages.map((name) => { - if (name.startsWith('.')) { - return null; - } +const excludes = packages + .map((name) => { + if (name.startsWith('.')) { + return null; + } - try { - const json = JSON.parse(readFileSync(resolveDir(__dirname, '../packages', name, 'package.json'), 'utf-8')); + try { + const json = JSON.parse(readFileSync(resolveDir(__dirname, '../packages', name, 'package.json'), 'utf-8')); - return json.name; - } catch { - return null; - } -}).filter(Boolean); + return json.name; + } catch { + return null; + } + }) + .filter(Boolean); const baseOptions: Options = { clean: true, @@ -62,24 +64,23 @@ export function createTsupConfig(options: EnhancedTsupOptions) { }), ...(options.iifeOptions?.enabled ? [ - defineConfig({ - ...baseOptions, - dts: false, - entry: ['src/index.ts'], - outDir: 'dist/iife', - format: 'iife', - ...options.iifeOptions, - esbuildOptions(esbuildOptions, context) { - if (options.iifeOptions?.esbuildOptions) { - options.iifeOptions.esbuildOptions(esbuildOptions, context); - } + defineConfig({ + ...baseOptions, + dts: false, + entry: ['src/index.ts'], + outDir: 'dist/iife', + format: 'iife', + ...options.iifeOptions, + esbuildOptions(esbuildOptions, context) { + if (options.iifeOptions?.esbuildOptions) { + options.iifeOptions.esbuildOptions(esbuildOptions, context); + } - esbuildOptions.target = options.iifeOptions?.target ?? baseOptions.target; - }, - }), - ] - : [] - ), + esbuildOptions.target = options.iifeOptions?.target ?? baseOptions.target; + }, + }), + ] + : []), ]; } diff --git a/test/consts.test.ts b/test/consts.test.ts index 90250d2fa..0d182494c 100644 --- a/test/consts.test.ts +++ b/test/consts.test.ts @@ -56,10 +56,7 @@ describe('consts', () => { describe('APIFY_ID_REGEX', () => { it('matches testing apify IDs', () => { const testingStrings = { - valid: [ - 'S64xo2hmHBFHbqZQq', - 'Z7rgePnfc04QHshc2', - ], + valid: ['S64xo2hmHBFHbqZQq', 'Z7rgePnfc04QHshc2'], invalid: [ // Invalid length 'Z7rgePnfc04QHshc', diff --git a/test/crypto.test.ts b/test/crypto.test.ts index 1eda32e74..6c51cc5cc 100644 --- a/test/crypto.test.ts +++ b/test/crypto.test.ts @@ -6,11 +6,17 @@ import * as utils from '@apify/utilities'; const publicKey = createPublicKey({ // eslint-disable-next-line max-len - key: Buffer.from('LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF0dis3NlNXbklhOFFKWC94RUQxRQpYdnBBQmE3ajBnQnVYenJNUU5adjhtTW1RU0t2VUF0TmpOL2xacUZpQ0haZUQxU2VDcGV1MnFHTm5XbGRxNkhUCnh5cXJpTVZEbFNKaFBNT09QSENISVNVdFI4Tk5lR1Y1MU0wYkxJcENabHcyTU9GUjdqdENWejVqZFRpZ1NvYTIKQWxrRUlRZWQ4UVlDKzk1aGJoOHk5bGcwQ0JxdEdWN1FvMFZQR2xKQ0hGaWNuaWxLVFFZay9MZzkwWVFnUElPbwozbUppeFl5bWFGNmlMZTVXNzg1M0VHWUVFVWdlWmNaZFNjaGVBMEdBMGpRSFVTdnYvMEZjay9adkZNZURJOTVsCmJVQ0JoQjFDbFg4OG4wZUhzUmdWZE5vK0NLMDI4T2IvZTZTK1JLK09VaHlFRVdPTi90alVMdGhJdTJkQWtGcmkKOFFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==', 'base64'), + key: Buffer.from( + 'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF0dis3NlNXbklhOFFKWC94RUQxRQpYdnBBQmE3ajBnQnVYenJNUU5adjhtTW1RU0t2VUF0TmpOL2xacUZpQ0haZUQxU2VDcGV1MnFHTm5XbGRxNkhUCnh5cXJpTVZEbFNKaFBNT09QSENISVNVdFI4Tk5lR1Y1MU0wYkxJcENabHcyTU9GUjdqdENWejVqZFRpZ1NvYTIKQWxrRUlRZWQ4UVlDKzk1aGJoOHk5bGcwQ0JxdEdWN1FvMFZQR2xKQ0hGaWNuaWxLVFFZay9MZzkwWVFnUElPbwozbUppeFl5bWFGNmlMZTVXNzg1M0VHWUVFVWdlWmNaZFNjaGVBMEdBMGpRSFVTdnYvMEZjay9adkZNZURJOTVsCmJVQ0JoQjFDbFg4OG4wZUhzUmdWZE5vK0NLMDI4T2IvZTZTK1JLK09VaHlFRVdPTi90alVMdGhJdTJkQWtGcmkKOFFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==', + 'base64', + ), }); const privateKey = createPrivateKey({ // eslint-disable-next-line max-len - key: Buffer.from('LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpQcm9jLVR5cGU6IDQsRU5DUllQVEVECkRFSy1JbmZvOiBERVMtRURFMy1DQkMsNTM1QURERjIzNUQ4QkFGOQoKMXFWUzl0S0FhdkVhVUVFMktESnpjM3plMk1lZkc1dmVEd2o1UVJ0ZkRaMXdWNS9VZmIvcU5sVThTSjlNaGhKaQp6RFdrWExueUUzSW0vcEtITVZkS0czYWZkcFRtcis2TmtidXptd0dVMk0vSWpzRjRJZlpad0lGbGJoY09jUnp4CmZmWVIvTlVyaHNrS1RpNGhGV0lBUDlLb3Z6VDhPSzNZY3h6eVZQWUxYNGVWbWt3UmZzeWkwUU5Xb0tGT3d0ZC8KNm9HYzFnd2piRjI5ZDNnUThZQjFGWmRLa1AyMTJGbkt1cTIrUWgvbE1zTUZrTHlTQTRLTGJ3ZG1RSXExbE1QUwpjbUNtZnppV3J1MlBtNEZoM0dmWlQyaE1JWHlIRFdEVzlDTkxKaERodExOZ2RRamFBUFpVT1E4V2hwSkE5MS9vCjJLZzZ3MDd5Z2RCcVd5dTZrc0pXcjNpZ1JpUEJ5QmVNWEpEZU5HY3NhaUZ3Q2c5eFlja1VORXR3NS90WlRsTjIKSEdZV0NpVU5Ed0F2WllMUHR1SHpIOFRFMGxsZm5HR0VuVC9QQlp1UHV4andlZlRleE1mdzFpbGJRU3lkcy9HMgpOOUlKKzkydms0N0ZXR2NOdGh1Q3lCbklva0NpZ0c1ZlBlV2IwQTdpdjk0UGtwRTRJZ3plc0hGQ0ZFQWoxWldLCnpQdFRBQlkwZlJrUzBNc3UwMHYxOXloTTUrdFUwYkVCZWo2eWpzWHRoYzlwS01hcUNIZWlQTC9TSHRkaWsxNVMKQmU4Sml4dVJxZitUeGlYWWVuNTg2aDlzTFpEYzA3cGpkUGp2NVNYRnBYQjhIMlVxQ0tZY2p4R3RvQWpTV0pjWApMNHc3RHNEby80bVg1N0htR09iamlCN1ZyOGhVWEJDdFh2V0dmQXlmcEFZNS9vOXowdm4zREcxaDc1NVVwdDluCkF2MFZrbm9qcmJVYjM1ZlJuU1lYTVltS01LSnpNRlMrdmFvRlpwV0ZjTG10cFRWSWNzc0JGUEYyZEo3V1c0WHMKK0d2Vkl2eFl3S2wyZzFPTE1TTXRZa09vekdlblBXTzdIdU0yMUVKVGIvbHNEZ25GaTkrYWRGZHBLY3R2cm0zdgpmbW1HeG5pRmhLU05GU0xtNms5YStHL2pjK3NVQVBhb2FZNEQ3NHVGajh0WGp0eThFUHdRRGxVUGRVZld3SE9PClF3bVgyMys1REh4V0VoQy91Tm8yNHNNY2ZkQzFGZUpBV281bUNuVU5vUVVmMStNRDVhMzNJdDhhMmlrNUkxUWoKeSs1WGpRaG0xd3RBMWhWTWE4aUxBR0toT09lcFRuK1VBZHpyS0hvNjVtYzNKbGgvSFJDUXJabnVxWkErK0F2WgpjeWU0dWZGWC8xdmRQSTdLb2Q0MEdDM2dlQnhweFFNYnp1OFNUcGpOcElJRkJvRVc5dFRhemUzeHZXWnV6dDc0CnFjZS8xWURuUHBLeW5lM0xGMk94VWoyYWVYUW5YQkpYcGhTZTBVTGJMcWJtUll4bjJKWkl1d09RNHV5dm94NjUKdG9TWGNac054dUs4QTErZXNXR3JSN3pVc0djdU9QQTFERE9Ja2JjcGtmRUxMNjk4RTJRckdqTU9JWnhrcWdxZQoySE5VNktWRmV2NzdZeEJDbm1VcVdXZEhYMjcyU2NPMUYzdWpUdFVnRVBNWGN0aEdBckYzTWxEaUw1Q0k0RkhqCnhHc3pVemxzalRQTmpiY2MzdUE2MjVZS3VVZEI2c1h1Rk5NUHk5UDgwTzBpRWJGTXl3MWxmN2VpdFhvaUUxWVoKc3NhMDVxTUx4M3pPUXZTLzFDdFpqaFp4cVJMRW5pQ3NWa2JVRlVYclpodEU4dG94bGpWSUtpQ25qbitORmtqdwo2bTZ1anpBSytZZHd2Nk5WMFB4S0gwUk5NYVhwb1lmQk1oUmZ3dGlaS3V3Y2hyRFB5UEhBQ2J3WXNZOXdtUE9rCnpwdDNxWi9JdDVYTmVqNDI0RzAzcGpMbk1sd1B1T1VzYmFQUWQ2VHU4TFhsckZReUVjTXJDNHdjUTA1SzFVN3kKM1NNN3RFaTlnbjV3RjY1YVI5eEFBR0grTUtMMk5WNnQrUmlTazJVaWs1clNmeDE4Mk9wYmpSQ2grdmQ4UXhJdwotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=', 'base64'), + key: Buffer.from( + 'LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpQcm9jLVR5cGU6IDQsRU5DUllQVEVECkRFSy1JbmZvOiBERVMtRURFMy1DQkMsNTM1QURERjIzNUQ4QkFGOQoKMXFWUzl0S0FhdkVhVUVFMktESnpjM3plMk1lZkc1dmVEd2o1UVJ0ZkRaMXdWNS9VZmIvcU5sVThTSjlNaGhKaQp6RFdrWExueUUzSW0vcEtITVZkS0czYWZkcFRtcis2TmtidXptd0dVMk0vSWpzRjRJZlpad0lGbGJoY09jUnp4CmZmWVIvTlVyaHNrS1RpNGhGV0lBUDlLb3Z6VDhPSzNZY3h6eVZQWUxYNGVWbWt3UmZzeWkwUU5Xb0tGT3d0ZC8KNm9HYzFnd2piRjI5ZDNnUThZQjFGWmRLa1AyMTJGbkt1cTIrUWgvbE1zTUZrTHlTQTRLTGJ3ZG1RSXExbE1QUwpjbUNtZnppV3J1MlBtNEZoM0dmWlQyaE1JWHlIRFdEVzlDTkxKaERodExOZ2RRamFBUFpVT1E4V2hwSkE5MS9vCjJLZzZ3MDd5Z2RCcVd5dTZrc0pXcjNpZ1JpUEJ5QmVNWEpEZU5HY3NhaUZ3Q2c5eFlja1VORXR3NS90WlRsTjIKSEdZV0NpVU5Ed0F2WllMUHR1SHpIOFRFMGxsZm5HR0VuVC9QQlp1UHV4andlZlRleE1mdzFpbGJRU3lkcy9HMgpOOUlKKzkydms0N0ZXR2NOdGh1Q3lCbklva0NpZ0c1ZlBlV2IwQTdpdjk0UGtwRTRJZ3plc0hGQ0ZFQWoxWldLCnpQdFRBQlkwZlJrUzBNc3UwMHYxOXloTTUrdFUwYkVCZWo2eWpzWHRoYzlwS01hcUNIZWlQTC9TSHRkaWsxNVMKQmU4Sml4dVJxZitUeGlYWWVuNTg2aDlzTFpEYzA3cGpkUGp2NVNYRnBYQjhIMlVxQ0tZY2p4R3RvQWpTV0pjWApMNHc3RHNEby80bVg1N0htR09iamlCN1ZyOGhVWEJDdFh2V0dmQXlmcEFZNS9vOXowdm4zREcxaDc1NVVwdDluCkF2MFZrbm9qcmJVYjM1ZlJuU1lYTVltS01LSnpNRlMrdmFvRlpwV0ZjTG10cFRWSWNzc0JGUEYyZEo3V1c0WHMKK0d2Vkl2eFl3S2wyZzFPTE1TTXRZa09vekdlblBXTzdIdU0yMUVKVGIvbHNEZ25GaTkrYWRGZHBLY3R2cm0zdgpmbW1HeG5pRmhLU05GU0xtNms5YStHL2pjK3NVQVBhb2FZNEQ3NHVGajh0WGp0eThFUHdRRGxVUGRVZld3SE9PClF3bVgyMys1REh4V0VoQy91Tm8yNHNNY2ZkQzFGZUpBV281bUNuVU5vUVVmMStNRDVhMzNJdDhhMmlrNUkxUWoKeSs1WGpRaG0xd3RBMWhWTWE4aUxBR0toT09lcFRuK1VBZHpyS0hvNjVtYzNKbGgvSFJDUXJabnVxWkErK0F2WgpjeWU0dWZGWC8xdmRQSTdLb2Q0MEdDM2dlQnhweFFNYnp1OFNUcGpOcElJRkJvRVc5dFRhemUzeHZXWnV6dDc0CnFjZS8xWURuUHBLeW5lM0xGMk94VWoyYWVYUW5YQkpYcGhTZTBVTGJMcWJtUll4bjJKWkl1d09RNHV5dm94NjUKdG9TWGNac054dUs4QTErZXNXR3JSN3pVc0djdU9QQTFERE9Ja2JjcGtmRUxMNjk4RTJRckdqTU9JWnhrcWdxZQoySE5VNktWRmV2NzdZeEJDbm1VcVdXZEhYMjcyU2NPMUYzdWpUdFVnRVBNWGN0aEdBckYzTWxEaUw1Q0k0RkhqCnhHc3pVemxzalRQTmpiY2MzdUE2MjVZS3VVZEI2c1h1Rk5NUHk5UDgwTzBpRWJGTXl3MWxmN2VpdFhvaUUxWVoKc3NhMDVxTUx4M3pPUXZTLzFDdFpqaFp4cVJMRW5pQ3NWa2JVRlVYclpodEU4dG94bGpWSUtpQ25qbitORmtqdwo2bTZ1anpBSytZZHd2Nk5WMFB4S0gwUk5NYVhwb1lmQk1oUmZ3dGlaS3V3Y2hyRFB5UEhBQ2J3WXNZOXdtUE9rCnpwdDNxWi9JdDVYTmVqNDI0RzAzcGpMbk1sd1B1T1VzYmFQUWQ2VHU4TFhsckZReUVjTXJDNHdjUTA1SzFVN3kKM1NNN3RFaTlnbjV3RjY1YVI5eEFBR0grTUtMMk5WNnQrUmlTazJVaWs1clNmeDE4Mk9wYmpSQ2grdmQ4UXhJdwotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=', + 'base64', + ), passphrase: 'pwd1234', }); @@ -34,7 +40,40 @@ describe('publicEncrypt() and privateDecrypt()', () => { it('should decrypt encrypted random strings with special characters', () => { const randomString = utils.cryptoRandomObjectId(10); // eslint-disable-next-line max-len - for (const char of ['👍', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '_', '=', '+', '[', ']', '{', '}', '|', ';', ':', '"', "'", ',', '.', '<', '>', '?', '/', '~', '`']) { + for (const char of [ + '👍', + '!', + '@', + '#', + '$', + '%', + '^', + '&', + '*', + '(', + ')', + '-', + '_', + '=', + '+', + '[', + ']', + '{', + '}', + '|', + ';', + ':', + '"', + "'", + ',', + '.', + '<', + '>', + '?', + '/', + '~', + '`', + ]) { const stringWithSpecialChar = `${char}${randomString}${char}${randomString}${char}`; const { encryptedPassword, encryptedValue } = utils.publicEncrypt({ publicKey, @@ -52,31 +91,39 @@ describe('publicEncrypt() and privateDecrypt()', () => { it('throws if encrypted password is not valid', () => { const randomString = utils.cryptoRandomObjectId(10); const { encryptedPassword, encryptedValue } = utils.publicEncrypt({ publicKey, value: randomString }); - expect(() => utils.privateDecrypt({ - privateKey, - encryptedPassword: encryptedPassword.slice(2), - encryptedValue, - })).toThrow(); - expect(() => utils.privateDecrypt({ - privateKey, - encryptedPassword: `bla${encryptedPassword}`, - encryptedValue, - })).toThrow(); + expect(() => + utils.privateDecrypt({ + privateKey, + encryptedPassword: encryptedPassword.slice(2), + encryptedValue, + }), + ).toThrow(); + expect(() => + utils.privateDecrypt({ + privateKey, + encryptedPassword: `bla${encryptedPassword}`, + encryptedValue, + }), + ).toThrow(); }); it('throws if encrypted value is not valid', () => { const randomString = utils.cryptoRandomObjectId(10); const { encryptedPassword, encryptedValue } = utils.publicEncrypt({ publicKey, value: randomString }); - expect(() => utils.privateDecrypt({ - privateKey, - encryptedPassword, - encryptedValue: encryptedValue.slice(2), - })).toThrow(); - expect(() => utils.privateDecrypt({ - privateKey, - encryptedPassword, - encryptedValue: `bla${encryptedValue}`, - })).toThrow(); + expect(() => + utils.privateDecrypt({ + privateKey, + encryptedPassword, + encryptedValue: encryptedValue.slice(2), + }), + ).toThrow(); + expect(() => + utils.privateDecrypt({ + privateKey, + encryptedPassword, + encryptedValue: `bla${encryptedValue}`, + }), + ).toThrow(); }); it('should return different cipher for the same string', () => { diff --git a/test/czk_payment_qr_codes.test.ts b/test/czk_payment_qr_codes.test.ts index 6e53ffc18..679057d56 100644 --- a/test/czk_payment_qr_codes.test.ts +++ b/test/czk_payment_qr_codes.test.ts @@ -1,27 +1,28 @@ import { describe, expect, it } from 'vitest'; -import { - encodeInputDataToRawQrCodeInputString, - generateCzkPaymentQrCodeDataUrl, -} from '@apify/payment_qr_codes'; +import { encodeInputDataToRawQrCodeInputString, generateCzkPaymentQrCodeDataUrl } from '@apify/payment_qr_codes'; describe('CZK payment QR code generation', () => { it('can encode payment data to QR code string', () => { - expect(encodeInputDataToRawQrCodeInputString({ - iban: 'CZ6508000000192000145399', // testing IBAN from https://www.cnb.cz/cs/platebni-styk/iban/iban-mezinarodni-format-cisla-uctu/ - amount: 123.45, - currencyCode: 'czk', - message: 'Test message', - beneficiaryName: 'John Doe', - })).toBe('SPD*1.0*RN:John Doe*ACC:CZ6508000000192000145399*AM:123.45*CC:CZK*MSG:Test message'); + expect( + encodeInputDataToRawQrCodeInputString({ + iban: 'CZ6508000000192000145399', // testing IBAN from https://www.cnb.cz/cs/platebni-styk/iban/iban-mezinarodni-format-cisla-uctu/ + amount: 123.45, + currencyCode: 'czk', + message: 'Test message', + beneficiaryName: 'John Doe', + }), + ).toBe('SPD*1.0*RN:John Doe*ACC:CZ6508000000192000145399*AM:123.45*CC:CZK*MSG:Test message'); - expect(encodeInputDataToRawQrCodeInputString({ - iban: 'CZ6508000000192000145399', // testing IBAN from https://www.cnb.cz/cs/platebni-styk/iban/iban-mezinarodni-format-cisla-uctu/ - amount: 123.45, - currencyCode: 'EUR', - message: 'Test message', - beneficiaryName: 'John Doe', - })).toBe('SPD*1.0*RN:John Doe*ACC:CZ6508000000192000145399*AM:123.45*CC:EUR*MSG:Test message'); + expect( + encodeInputDataToRawQrCodeInputString({ + iban: 'CZ6508000000192000145399', // testing IBAN from https://www.cnb.cz/cs/platebni-styk/iban/iban-mezinarodni-format-cisla-uctu/ + amount: 123.45, + currencyCode: 'EUR', + message: 'Test message', + beneficiaryName: 'John Doe', + }), + ).toBe('SPD*1.0*RN:John Doe*ACC:CZ6508000000192000145399*AM:123.45*CC:EUR*MSG:Test message'); }); it('test', async () => { diff --git a/test/git.test.ts b/test/git.test.ts index 867205c12..35507fd58 100644 --- a/test/git.test.ts +++ b/test/git.test.ts @@ -30,10 +30,12 @@ describe('convertRelativeImagePathsToAbsoluteInReadme()', () => { `; - expect(convertRelativeImagePathsToAbsoluteInReadme({ - readme: testMarkdown, - gitRepoUrl: 'git@github.com:apify/test-repo.git', - })).toEqual(expectedResult); + expect( + convertRelativeImagePathsToAbsoluteInReadme({ + readme: testMarkdown, + gitRepoUrl: 'git@github.com:apify/test-repo.git', + }), + ).toEqual(expectedResult); }); it('does not convert absolute paths', () => { @@ -47,10 +49,12 @@ describe('convertRelativeImagePathsToAbsoluteInReadme()', () => { `; - expect(convertRelativeImagePathsToAbsoluteInReadme({ - readme: testMarkdown, - gitRepoUrl: 'git@github.com:apify/test-repo.git', - })).toEqual(testMarkdown); + expect( + convertRelativeImagePathsToAbsoluteInReadme({ + readme: testMarkdown, + gitRepoUrl: 'git@github.com:apify/test-repo.git', + }), + ).toEqual(testMarkdown); }); it('does not convert tags with mismatched quotes', () => { @@ -60,10 +64,12 @@ describe('convertRelativeImagePathsToAbsoluteInReadme()', () => { `; - expect(convertRelativeImagePathsToAbsoluteInReadme({ - readme: testMarkdown, - gitRepoUrl: 'git@github.com:apify/test-repo.git', - })).toEqual(expectedResult); + expect( + convertRelativeImagePathsToAbsoluteInReadme({ + readme: testMarkdown, + gitRepoUrl: 'git@github.com:apify/test-repo.git', + }), + ).toEqual(expectedResult); }); it('works correctly for unknown repo', () => { @@ -230,9 +252,11 @@ describe('convertRelativeImagePathsToAbsoluteInReadme()', () => { ![img2](./relative-path-to-img.jpg) `; - expect(convertRelativeImagePathsToAbsoluteInReadme({ - readme: testMarkdown, - gitRepoUrl: 'git@some-unknown-git-site.com:apify/test-repo.git', - })).toEqual(testMarkdown); + expect( + convertRelativeImagePathsToAbsoluteInReadme({ + readme: testMarkdown, + gitRepoUrl: 'git@some-unknown-git-site.com:apify/test-repo.git', + }), + ).toEqual(testMarkdown); }); }); diff --git a/test/health_checker.test.ts b/test/health_checker.test.ts index c97b2f423..4a69ec237 100644 --- a/test/health_checker.test.ts +++ b/test/health_checker.test.ts @@ -80,9 +80,13 @@ describe('HealthChecker', () => { mongoRead.listCollections.mockReturnValueOnce({ toArray: async () => Promise.resolve([]) }); // Throws an error - writeTestCollection.findOne.mockImplementationOnce(() => { throw new Error('some problem'); }); + writeTestCollection.findOne.mockImplementationOnce(() => { + throw new Error('some problem'); + }); - await expect(healthChecker.ensureIsHealthy()).rejects.toThrow('Health check test "MONGODB_WRITE" failed with an error: some problem"'); + await expect(healthChecker.ensureIsHealthy()).rejects.toThrow( + 'Health check test "MONGODB_WRITE" failed with an error: some problem"', + ); expect(redis.set).toBeCalledTimes(1); expect(redis.get).toBeCalledTimes(1); @@ -93,11 +97,15 @@ describe('HealthChecker', () => { }); it('should fail when some of the checks timeouts', async () => { - redis.get.mockReturnValueOnce(new Promise((resolve) => { - setTimeout(resolve, 200); - })); // Timeouts. + redis.get.mockReturnValueOnce( + new Promise((resolve) => { + setTimeout(resolve, 200); + }), + ); // Timeouts. - await expect(healthChecker.ensureIsHealthy()).rejects.toThrow('Health check test "REDIS" failed with an error: Check has timed-out'); + await expect(healthChecker.ensureIsHealthy()).rejects.toThrow( + 'Health check test "REDIS" failed with an error: Check has timed-out', + ); expect(redis.set).toBeCalledTimes(1); }); @@ -120,9 +128,13 @@ describe('HealthChecker', () => { mongoRead.listCollections.mockReturnValueOnce({ toArray: async () => Promise.resolve([]) }); // Throws an error - writeTestCollection.findOne.mockImplementationOnce(() => { throw new Error('some problem'); }); + writeTestCollection.findOne.mockImplementationOnce(() => { + throw new Error('some problem'); + }); - await expect(healthChecker.ensureIsHealthy()).rejects.toThrow('Health check test "MONGODB_WRITE" failed with an error: some problem"'); + await expect(healthChecker.ensureIsHealthy()).rejects.toThrow( + 'Health check test "MONGODB_WRITE" failed with an error: some problem"', + ); expect(redis.set).toBeCalledTimes(1); expect(redis.get).toBeCalledTimes(1); @@ -135,9 +147,13 @@ describe('HealthChecker', () => { it('should fail when mongo cannot read', async () => { redis.get.mockReturnValueOnce('OK'); mongoRead.listCollections.mockReturnValueOnce({ toArray: async () => Promise.resolve([]) }); - writeTestCollection.findOne.mockImplementationOnce(() => { throw new Error('some-problem'); }); + writeTestCollection.findOne.mockImplementationOnce(() => { + throw new Error('some-problem'); + }); - await expect(healthChecker.ensureIsHealthy()).rejects.toThrow('Health check test "MONGODB_WRITE" failed with an error: some-problem"'); + await expect(healthChecker.ensureIsHealthy()).rejects.toThrow( + 'Health check test "MONGODB_WRITE" failed with an error: some-problem"', + ); expect(redis.set).toBeCalledTimes(1); expect(redis.get).toBeCalledTimes(1); @@ -151,7 +167,9 @@ describe('HealthChecker', () => { // @ts-expect-error expect(() => new HealthChecker({ checks: null })).toThrow('Parameter "check" must be an array'); // @ts-expect-error - expect(() => new HealthChecker({ checks: [{ type: 'xxx', client: {} }] })).toThrow('Check type "xxx" is invalid'); + expect(() => new HealthChecker({ checks: [{ type: 'xxx', client: {} }] })).toThrow( + 'Check type "xxx" is invalid', + ); // @ts-expect-error expect(() => new HealthChecker({ checks: [{ type: HealthChecker.CHECK_TYPES.REDIS, client: 123 }] })).toThrow( 'Check client must be an object got "number" instead', diff --git a/test/image_proxy_client.test.ts b/test/image_proxy_client.test.ts index 5e0f99ea4..73c331800 100644 --- a/test/image_proxy_client.test.ts +++ b/test/image_proxy_client.test.ts @@ -12,26 +12,32 @@ const imageProxyClient = new ImageProxyClient({ const TEST_IMAGE_URL = 'http://example.com/image.gif'; const TEST_IMAGE_URL_2 = 'HTTP://example.com/image.gif'; -const EXPECTED_IMAGE_URL = 'https://localhost:3000/14110f5a2b817884066da289ce004803b35cfefc/' - + '687474703a2f2f6578616d706c652e636f6d2f696d6167652e676966'; -const EXPECTED_IMAGE_URL_2 = 'https://localhost:3000/3bb9136ff6f149fce6770d65291f29554b372e11/' - + '485454503a2f2f6578616d706c652e636f6d2f696d6167652e676966'; +const EXPECTED_IMAGE_URL = + 'https://localhost:3000/14110f5a2b817884066da289ce004803b35cfefc/' + + '687474703a2f2f6578616d706c652e636f6d2f696d6167652e676966'; +const EXPECTED_IMAGE_URL_2 = + 'https://localhost:3000/3bb9136ff6f149fce6770d65291f29554b372e11/' + + '485454503a2f2f6578616d706c652e636f6d2f696d6167652e676966'; const getComplicatedHtml = (imageUrl: string) => { - return `test image
` - + `test image` - + `test image` - + `test image` - + `test image` - + `test image` - + 'test image'; + return ( + `test image
` + + `test image` + + `test image` + + `test image` + + `test image` + + `test image` + + 'test image' + ); }; describe('proxy image client', () => { it('generateUrlWithParam() works', () => { const testImageUrl = 'http://example.com/image.gif'; const proxyUrl = imageProxyClient.generateUrlWithParam(testImageUrl); - expect('https://localhost:3000/14110f5a2b817884066da289ce004803b35cfefc/?url=http%3A%2F%2Fexample.com%2Fimage.gif').toBe(proxyUrl); + expect( + 'https://localhost:3000/14110f5a2b817884066da289ce004803b35cfefc/?url=http%3A%2F%2Fexample.com%2Fimage.gif', + ).toBe(proxyUrl); }); it('generateUrl() works', () => { @@ -41,10 +47,12 @@ describe('proxy image client', () => { it('updateImagesInHtml() works', () => { const html = (imageUrl: string) => { - return '
' - + `test image` - + '' - + '
'; + return ( + '
' + + `test image` + + '' + + '
' + ); }; const testHtml = html(TEST_IMAGE_URL); const updatedHtml = imageProxyClient.updateImagesInHtml(testHtml); diff --git a/test/input_schema.test.ts b/test/input_schema.test.ts index 954258500..20ecd360c 100644 --- a/test/input_schema.test.ts +++ b/test/input_schema.test.ts @@ -111,7 +111,7 @@ describe('input_schema.json', () => { ); }); - it('should throw error on field that doesn\'t match any of types', () => { + it("should throw error on field that doesn't match any of types", () => { const schema = { title: 'Test input schema', type: 'object', @@ -127,8 +127,8 @@ describe('input_schema.json', () => { }; expect(() => validateInputSchema(validator, schema)).toThrow( - 'Input schema is not valid (Field schema.properties.myField is not matching any input schema type definition.' - + ' Please make sure that it\'s type is valid.', + 'Input schema is not valid (Field schema.properties.myField is not matching any input schema type definition.' + + " Please make sure that it's type is valid.", ); }); @@ -169,8 +169,8 @@ describe('input_schema.json', () => { }; expect(() => validateInputSchema(validator, schema)).toThrow( - 'Input schema is not valid (Field schema.properties.myField.editor must be equal to one of the allowed values: ' - + '"javascript", "python", "textfield", "textarea", "select", "fileupload", "hidden")', + 'Input schema is not valid (Field schema.properties.myField.editor must be equal to one of the allowed values: ' + + '"javascript", "python", "textfield", "textarea", "select", "fileupload", "hidden")', ); }); @@ -671,8 +671,8 @@ describe('input_schema.json', () => { }, }; expect(() => validateInputSchema(validator, schema)).toThrow( - 'Input schema is not valid (Field schema.properties.myField.resourceType must be equal to one of the allowed values: ' - + '"dataset", "keyValueStore", "requestQueue", "mcpConnector")', + 'Input schema is not valid (Field schema.properties.myField.resourceType must be equal to one of the allowed values: ' + + '"dataset", "keyValueStore", "requestQueue", "mcpConnector")', ); }); @@ -952,9 +952,7 @@ describe('input_schema.json', () => { description: 'My test field', type: 'array', resourceType: 'mcpConnector', - mcpServers: [ - { url: '*' }, - ], + mcpServers: [{ url: '*' }], }, }, }; @@ -972,9 +970,7 @@ describe('input_schema.json', () => { description: 'My test field', type: 'string', resourceType: 'dataset', - mcpServers: [ - { url: '*' }, - ], + mcpServers: [{ url: '*' }], }, }, }; @@ -994,9 +990,7 @@ describe('input_schema.json', () => { description: 'My test field', type: 'string', resourceType: 'mcpConnector', - mcpServers: [ - { url: 'https://mcp.example.com/*' }, - ], + mcpServers: [{ url: 'https://mcp.example.com/*' }], }, }, }; @@ -1089,9 +1083,7 @@ describe('input_schema.json', () => { description: 'My test field', type: 'string', resourceType: 'mcpConnector', - mcpServers: [ - { tools: { required: ['read_*'] } }, - ], + mcpServers: [{ tools: { required: ['read_*'] } }], }, }, }; diff --git a/test/input_schema_definition.test.ts b/test/input_schema_definition.test.ts index 2007eaab0..f2b21c9da 100644 --- a/test/input_schema_definition.test.ts +++ b/test/input_schema_definition.test.ts @@ -9,7 +9,7 @@ import { inputSchema } from '@apify/input_schema'; * * @returns {() => void} Function that will turn this behavior off. */ -const setThrowErrorOnConsoleWarn = (): () => void => { +const setThrowErrorOnConsoleWarn = (): (() => void) => { const consoleWarn = console.warn; console.warn = () => { throw new Error('Console.warn has been called!'); @@ -26,68 +26,80 @@ describe('input_schema.json', () => { describe('type any', () => { it('should allow to compile only a valid type any', () => { // Valid one. - if (!ajv.validate(inputSchema, { - title: 'Test input schema', - type: 'object', - schemaVersion: 1, - properties: { - myField: { - title: 'Field title', - type: ['object', 'array', 'string', 'integer', 'number', 'boolean'], - nullable: false, - description: 'Some description ...', - editor: 'json', + if ( + !ajv.validate(inputSchema, { + title: 'Test input schema', + type: 'object', + schemaVersion: 1, + properties: { + myField: { + title: 'Field title', + type: ['object', 'array', 'string', 'integer', 'number', 'boolean'], + nullable: false, + description: 'Some description ...', + editor: 'json', + }, }, - }, - })) throw new Error(ajv.errorsText()); + }) + ) + throw new Error(ajv.errorsText()); // Nonexisting type. - if (ajv.validate(inputSchema, { - title: 'Test input schema', - type: 'object', - schemaVersion: 1, - properties: { - myField: { - title: 'Field title', - type: ['array', 'nonexisting'], - nullable: false, - description: 'Some description ...', - editor: 'json', + if ( + ajv.validate(inputSchema, { + title: 'Test input schema', + type: 'object', + schemaVersion: 1, + properties: { + myField: { + title: 'Field title', + type: ['array', 'nonexisting'], + nullable: false, + description: 'Some description ...', + editor: 'json', + }, }, - }, - })) throw new Error('Input field definition with onexisting type should have failed!'); + }) + ) + throw new Error('Input field definition with onexisting type should have failed!'); // Missing type. - if (ajv.validate(inputSchema, { - title: 'Test input schema', - type: 'object', - schemaVersion: 1, - properties: { - myField: { - title: 'Field title', - type: [], - nullable: false, - description: 'Some description ...', - editor: 'json', + if ( + ajv.validate(inputSchema, { + title: 'Test input schema', + type: 'object', + schemaVersion: 1, + properties: { + myField: { + title: 'Field title', + type: [], + nullable: false, + description: 'Some description ...', + editor: 'json', + }, }, - }, - })) throw new Error('Input field definition that misses type should have failed!'); + }) + ) + throw new Error('Input field definition that misses type should have failed!'); // Duplicate types. - if (ajv.validate(inputSchema, { - title: 'Test input schema', - type: 'object', - schemaVersion: 1, - properties: { - myField: { - title: 'Field title', - type: ['string', 'integer', 'integer'], - nullable: false, - description: 'Some description ...', - editor: 'json', + if ( + ajv.validate(inputSchema, { + title: 'Test input schema', + type: 'object', + schemaVersion: 1, + properties: { + myField: { + title: 'Field title', + type: ['string', 'integer', 'integer'], + nullable: false, + description: 'Some description ...', + editor: 'json', + }, }, - }, - })) throw new Error('Input field definition that duplicate types should have failed!'); + }) + ) + throw new Error('Input field definition that duplicate types should have failed!'); }); it('should allow all the needed types', () => { @@ -294,54 +306,54 @@ describe('input_schema.json', () => { ].forEach((fields) => { expect(isSchemaValid(fields, true)).toBe(true); }); - [ - { type: 'boolean' }, - { type: 'integer' }, - { type: 'number' }, - ].forEach((fields) => { + [{ type: 'boolean' }, { type: 'integer' }, { type: 'number' }].forEach((fields) => { expect(isSchemaValid(fields, true)).toBe(false); }); }); it('should work without isSecret with all editors and properties', () => { - expect(ajv.validate(inputSchema, { - title: 'Test input schema', - type: 'object', - schemaVersion: 1, - properties: { - myField: { - title: 'Field title', - description: 'My test field', - type: 'string', - editor: 'textarea', - isSecret: false, - minLength: 2, - maxLength: 100, - default: 'blablablablabla', - prefill: 'blablablablablablablablablablabla', + expect( + ajv.validate(inputSchema, { + title: 'Test input schema', + type: 'object', + schemaVersion: 1, + properties: { + myField: { + title: 'Field title', + description: 'My test field', + type: 'string', + editor: 'textarea', + isSecret: false, + minLength: 2, + maxLength: 100, + default: 'blablablablabla', + prefill: 'blablablablablablablablablablabla', + }, }, - }, - })).toBe(true); + }), + ).toBe(true); - expect(ajv.validate(inputSchema, { - title: 'Test input schema', - type: 'object', - schemaVersion: 1, - properties: { - myField: { - title: 'Field title', - description: 'My test field', - type: 'string', - editor: 'textarea', - isSecret: false, - minLength: 2, - maxLength: 100, - default: 'blablablablabla', - prefill: 'blablablablablablablablablablabla', - bla: 'bla', // Validation failed because additional property + expect( + ajv.validate(inputSchema, { + title: 'Test input schema', + type: 'object', + schemaVersion: 1, + properties: { + myField: { + title: 'Field title', + description: 'My test field', + type: 'string', + editor: 'textarea', + isSecret: false, + minLength: 2, + maxLength: 100, + default: 'blablablablabla', + prefill: 'blablablablablablablablablablabla', + bla: 'bla', // Validation failed because additional property + }, }, - }, - })).toBe(false); + }), + ).toBe(false); }); }); @@ -382,62 +394,68 @@ describe('input_schema.json', () => { }); it('should work without isSecret with all editors and properties', () => { - expect(ajv.validate(inputSchema, { - title: 'Test input schema', - type: 'object', - schemaVersion: 1, - properties: { - myField: { - title: 'Field title', - description: 'My test field', - type: 'object', - editor: 'json', - isSecret: false, - minProperties: 2, - maxProperties: 100, - default: { key: 'value' }, - prefill: { key: 'value', key2: 'value2' }, + expect( + ajv.validate(inputSchema, { + title: 'Test input schema', + type: 'object', + schemaVersion: 1, + properties: { + myField: { + title: 'Field title', + description: 'My test field', + type: 'object', + editor: 'json', + isSecret: false, + minProperties: 2, + maxProperties: 100, + default: { key: 'value' }, + prefill: { key: 'value', key2: 'value2' }, + }, }, - }, - })).toBe(true); + }), + ).toBe(true); - expect(ajv.validate(inputSchema, { - title: 'Test input schema', - type: 'object', - schemaVersion: 1, - properties: { - myField: { - title: 'Field title', - description: 'My test field', - type: 'object', - editor: 'json', - isSecret: false, - minProperties: 2, - maxProperties: 100, - default: { key: 'value' }, - prefill: { key: 'value', key2: 'value2' }, - bla: 'bla', // Validation failed because additional property + expect( + ajv.validate(inputSchema, { + title: 'Test input schema', + type: 'object', + schemaVersion: 1, + properties: { + myField: { + title: 'Field title', + description: 'My test field', + type: 'object', + editor: 'json', + isSecret: false, + minProperties: 2, + maxProperties: 100, + default: { key: 'value' }, + prefill: { key: 'value', key2: 'value2' }, + bla: 'bla', // Validation failed because additional property + }, }, - }, - })).toBe(false); + }), + ).toBe(false); }); }); describe('special cases for datepicker editor type', () => { it('should accept dateType field omitted', () => { - expect(ajv.validate(inputSchema, { - title: 'Test input schema', - type: 'object', - schemaVersion: 1, - properties: { - myField: { - title: 'Field title', - description: 'My test field', - type: 'string', - editor: 'datepicker', + expect( + ajv.validate(inputSchema, { + title: 'Test input schema', + type: 'object', + schemaVersion: 1, + properties: { + myField: { + title: 'Field title', + description: 'My test field', + type: 'string', + editor: 'datepicker', + }, }, - }, - })).toBe(true); + }), + ).toBe(true); }); const isSchemaValid = (dateType: string) => { @@ -472,247 +490,269 @@ describe('input_schema.json', () => { describe('special cases for resourceProperty', () => { it('should accept resourceType with editor', () => { - expect(ajv.validate(inputSchema, { - title: 'Test input schema', - type: 'object', - schemaVersion: 1, - properties: { - myField: { - title: 'Field title', - description: 'My test field', - type: 'string', - resourceType: 'dataset', - editor: 'resourcePicker', + expect( + ajv.validate(inputSchema, { + title: 'Test input schema', + type: 'object', + schemaVersion: 1, + properties: { + myField: { + title: 'Field title', + description: 'My test field', + type: 'string', + resourceType: 'dataset', + editor: 'resourcePicker', + }, }, - }, - })).toBe(true); + }), + ).toBe(true); }); it('should accept resourceType without editor', () => { - expect(ajv.validate(inputSchema, { - title: 'Test input schema', - type: 'object', - schemaVersion: 1, - properties: { - myField: { - title: 'Field title', - description: 'My test field', - type: 'string', - resourceType: 'dataset', + expect( + ajv.validate(inputSchema, { + title: 'Test input schema', + type: 'object', + schemaVersion: 1, + properties: { + myField: { + title: 'Field title', + description: 'My test field', + type: 'string', + resourceType: 'dataset', + }, }, - }, - })).toBe(true); + }), + ).toBe(true); }); it('should accept array resourceType', () => { - expect(ajv.validate(inputSchema, { - title: 'Test input schema', - type: 'object', - schemaVersion: 1, - properties: { - myField: { - title: 'Field title', - description: 'My test field', - type: 'array', - resourceType: 'dataset', + expect( + ajv.validate(inputSchema, { + title: 'Test input schema', + type: 'object', + schemaVersion: 1, + properties: { + myField: { + title: 'Field title', + description: 'My test field', + type: 'array', + resourceType: 'dataset', + }, }, - }, - })).toBe(true); + }), + ).toBe(true); }); it('should reject mcpConnector resourceType without mcpServers', () => { - expect(ajv.validate(inputSchema, { - title: 'Test input schema', - type: 'object', - schemaVersion: 1, - properties: { - myField: { - title: 'Field title', - description: 'My test field', - type: 'string', - resourceType: 'mcpConnector', + expect( + ajv.validate(inputSchema, { + title: 'Test input schema', + type: 'object', + schemaVersion: 1, + properties: { + myField: { + title: 'Field title', + description: 'My test field', + type: 'string', + resourceType: 'mcpConnector', + }, }, - }, - })).toBe(false); + }), + ).toBe(false); }); it('should accept mcpConnector with mcpServers', () => { - expect(ajv.validate(inputSchema, { - title: 'Test input schema', - type: 'object', - schemaVersion: 1, - properties: { - myField: { - title: 'Field title', - description: 'My test field', - type: 'string', - resourceType: 'mcpConnector', - mcpServers: [ - { - url: 'https://example.com/*', - tools: { required: ['tool1'] }, - }, - ], + expect( + ajv.validate(inputSchema, { + title: 'Test input schema', + type: 'object', + schemaVersion: 1, + properties: { + myField: { + title: 'Field title', + description: 'My test field', + type: 'string', + resourceType: 'mcpConnector', + mcpServers: [ + { + url: 'https://example.com/*', + tools: { required: ['tool1'] }, + }, + ], + }, }, - }, - })).toBe(true); + }), + ).toBe(true); }); it('should accept mcpConnector array with mcpServers', () => { - expect(ajv.validate(inputSchema, { - title: 'Test input schema', - type: 'object', - schemaVersion: 1, - properties: { - myField: { - title: 'Field title', - description: 'My test field', - type: 'array', - resourceType: 'mcpConnector', - mcpServers: [ - { url: 'https://example.com/*' }, - ], + expect( + ajv.validate(inputSchema, { + title: 'Test input schema', + type: 'object', + schemaVersion: 1, + properties: { + myField: { + title: 'Field title', + description: 'My test field', + type: 'array', + resourceType: 'mcpConnector', + mcpServers: [{ url: 'https://example.com/*' }], + }, }, - }, - })).toBe(true); + }), + ).toBe(true); }); it('should reject mcpServers on storage resourceType', () => { - expect(ajv.validate(inputSchema, { - title: 'Test input schema', - type: 'object', - schemaVersion: 1, - properties: { - myField: { - title: 'Field title', - description: 'My test field', - type: 'string', - resourceType: 'dataset', - mcpServers: [ - { url: 'https://example.com/*' }, - ], + expect( + ajv.validate(inputSchema, { + title: 'Test input schema', + type: 'object', + schemaVersion: 1, + properties: { + myField: { + title: 'Field title', + description: 'My test field', + type: 'string', + resourceType: 'dataset', + mcpServers: [{ url: 'https://example.com/*' }], + }, }, - }, - })).toBe(false); + }), + ).toBe(false); }); it('should reject resourcePermissions on mcpConnector', () => { - expect(ajv.validate(inputSchema, { - title: 'Test input schema', - type: 'object', - schemaVersion: 1, - properties: { - myField: { - title: 'Field title', - description: 'My test field', - type: 'string', - resourceType: 'mcpConnector', - resourcePermissions: ['READ'], + expect( + ajv.validate(inputSchema, { + title: 'Test input schema', + type: 'object', + schemaVersion: 1, + properties: { + myField: { + title: 'Field title', + description: 'My test field', + type: 'string', + resourceType: 'mcpConnector', + resourcePermissions: ['READ'], + }, }, - }, - })).toBe(false); + }), + ).toBe(false); }); it('should accept storage resourceType without resourcePermissions', () => { - expect(ajv.validate(inputSchema, { - title: 'Test input schema', - type: 'object', - schemaVersion: 1, - properties: { - myField: { - title: 'Field title', - description: 'My test field', - type: 'string', - resourceType: 'dataset', + expect( + ajv.validate(inputSchema, { + title: 'Test input schema', + type: 'object', + schemaVersion: 1, + properties: { + myField: { + title: 'Field title', + description: 'My test field', + type: 'string', + resourceType: 'dataset', + }, }, - }, - })).toBe(true); + }), + ).toBe(true); }); it('should accept storage resourceType with resourcePermissions', () => { - expect(ajv.validate(inputSchema, { - title: 'Test input schema', - type: 'object', - schemaVersion: 1, - properties: { - myField: { - title: 'Field title', - description: 'My test field', - type: 'string', - resourceType: 'dataset', - resourcePermissions: ['READ'], + expect( + ajv.validate(inputSchema, { + title: 'Test input schema', + type: 'object', + schemaVersion: 1, + properties: { + myField: { + title: 'Field title', + description: 'My test field', + type: 'string', + resourceType: 'dataset', + resourcePermissions: ['READ'], + }, }, - }, - })).toBe(true); + }), + ).toBe(true); }); it('should accept mcpServers with tools containing only hint booleans', () => { - expect(ajv.validate(inputSchema, { - title: 'Test input schema', - type: 'object', - schemaVersion: 1, - properties: { - myField: { - title: 'Field title', - description: 'My test field', - type: 'string', - resourceType: 'mcpConnector', - mcpServers: [ - { - url: '*', - tools: { - readOnly: true, - idempotent: true, + expect( + ajv.validate(inputSchema, { + title: 'Test input schema', + type: 'object', + schemaVersion: 1, + properties: { + myField: { + title: 'Field title', + description: 'My test field', + type: 'string', + resourceType: 'mcpConnector', + mcpServers: [ + { + url: '*', + tools: { + readOnly: true, + idempotent: true, + }, }, - }, - ], + ], + }, }, - }, - })).toBe(true); + }), + ).toBe(true); }); it('should reject unknown properties in tools object', () => { - expect(ajv.validate(inputSchema, { - title: 'Test input schema', - type: 'object', - schemaVersion: 1, - properties: { - myField: { - title: 'Field title', - description: 'My test field', - type: 'string', - resourceType: 'mcpConnector', - mcpServers: [ - { - url: '*', - tools: { unknownHint: true }, - }, - ], + expect( + ajv.validate(inputSchema, { + title: 'Test input schema', + type: 'object', + schemaVersion: 1, + properties: { + myField: { + title: 'Field title', + description: 'My test field', + type: 'string', + resourceType: 'mcpConnector', + mcpServers: [ + { + url: '*', + tools: { unknownHint: true }, + }, + ], + }, }, - }, - })).toBe(false); + }), + ).toBe(false); }); it('should reject unknown properties in mcpServers entry', () => { - expect(ajv.validate(inputSchema, { - title: 'Test input schema', - type: 'object', - schemaVersion: 1, - properties: { - myField: { - title: 'Field title', - description: 'My test field', - type: 'string', - resourceType: 'mcpConnector', - mcpServers: [ - { - url: '*', - unknownProp: 'value', - }, - ], + expect( + ajv.validate(inputSchema, { + title: 'Test input schema', + type: 'object', + schemaVersion: 1, + properties: { + myField: { + title: 'Field title', + description: 'My test field', + type: 'string', + resourceType: 'mcpConnector', + mcpServers: [ + { + url: '*', + unknownProp: 'value', + }, + ], + }, }, - }, - })).toBe(false); + }), + ).toBe(false); }); }); }); diff --git a/test/input_secrets.test.ts b/test/input_secrets.test.ts index 8841a3616..4c13da5c6 100644 --- a/test/input_secrets.test.ts +++ b/test/input_secrets.test.ts @@ -6,11 +6,17 @@ import { decryptInputSecrets, encryptInputSecrets } from '@apify/input_secrets'; const publicKey = createPublicKey({ // eslint-disable-next-line max-len - key: Buffer.from('LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF0dis3NlNXbklhOFFKWC94RUQxRQpYdnBBQmE3ajBnQnVYenJNUU5adjhtTW1RU0t2VUF0TmpOL2xacUZpQ0haZUQxU2VDcGV1MnFHTm5XbGRxNkhUCnh5cXJpTVZEbFNKaFBNT09QSENISVNVdFI4Tk5lR1Y1MU0wYkxJcENabHcyTU9GUjdqdENWejVqZFRpZ1NvYTIKQWxrRUlRZWQ4UVlDKzk1aGJoOHk5bGcwQ0JxdEdWN1FvMFZQR2xKQ0hGaWNuaWxLVFFZay9MZzkwWVFnUElPbwozbUppeFl5bWFGNmlMZTVXNzg1M0VHWUVFVWdlWmNaZFNjaGVBMEdBMGpRSFVTdnYvMEZjay9adkZNZURJOTVsCmJVQ0JoQjFDbFg4OG4wZUhzUmdWZE5vK0NLMDI4T2IvZTZTK1JLK09VaHlFRVdPTi90alVMdGhJdTJkQWtGcmkKOFFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==', 'base64'), + key: Buffer.from( + 'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF0dis3NlNXbklhOFFKWC94RUQxRQpYdnBBQmE3ajBnQnVYenJNUU5adjhtTW1RU0t2VUF0TmpOL2xacUZpQ0haZUQxU2VDcGV1MnFHTm5XbGRxNkhUCnh5cXJpTVZEbFNKaFBNT09QSENISVNVdFI4Tk5lR1Y1MU0wYkxJcENabHcyTU9GUjdqdENWejVqZFRpZ1NvYTIKQWxrRUlRZWQ4UVlDKzk1aGJoOHk5bGcwQ0JxdEdWN1FvMFZQR2xKQ0hGaWNuaWxLVFFZay9MZzkwWVFnUElPbwozbUppeFl5bWFGNmlMZTVXNzg1M0VHWUVFVWdlWmNaZFNjaGVBMEdBMGpRSFVTdnYvMEZjay9adkZNZURJOTVsCmJVQ0JoQjFDbFg4OG4wZUhzUmdWZE5vK0NLMDI4T2IvZTZTK1JLK09VaHlFRVdPTi90alVMdGhJdTJkQWtGcmkKOFFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==', + 'base64', + ), }); const privateKey = createPrivateKey({ // eslint-disable-next-line max-len - key: Buffer.from('LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpQcm9jLVR5cGU6IDQsRU5DUllQVEVECkRFSy1JbmZvOiBERVMtRURFMy1DQkMsNTM1QURERjIzNUQ4QkFGOQoKMXFWUzl0S0FhdkVhVUVFMktESnpjM3plMk1lZkc1dmVEd2o1UVJ0ZkRaMXdWNS9VZmIvcU5sVThTSjlNaGhKaQp6RFdrWExueUUzSW0vcEtITVZkS0czYWZkcFRtcis2TmtidXptd0dVMk0vSWpzRjRJZlpad0lGbGJoY09jUnp4CmZmWVIvTlVyaHNrS1RpNGhGV0lBUDlLb3Z6VDhPSzNZY3h6eVZQWUxYNGVWbWt3UmZzeWkwUU5Xb0tGT3d0ZC8KNm9HYzFnd2piRjI5ZDNnUThZQjFGWmRLa1AyMTJGbkt1cTIrUWgvbE1zTUZrTHlTQTRLTGJ3ZG1RSXExbE1QUwpjbUNtZnppV3J1MlBtNEZoM0dmWlQyaE1JWHlIRFdEVzlDTkxKaERodExOZ2RRamFBUFpVT1E4V2hwSkE5MS9vCjJLZzZ3MDd5Z2RCcVd5dTZrc0pXcjNpZ1JpUEJ5QmVNWEpEZU5HY3NhaUZ3Q2c5eFlja1VORXR3NS90WlRsTjIKSEdZV0NpVU5Ed0F2WllMUHR1SHpIOFRFMGxsZm5HR0VuVC9QQlp1UHV4andlZlRleE1mdzFpbGJRU3lkcy9HMgpOOUlKKzkydms0N0ZXR2NOdGh1Q3lCbklva0NpZ0c1ZlBlV2IwQTdpdjk0UGtwRTRJZ3plc0hGQ0ZFQWoxWldLCnpQdFRBQlkwZlJrUzBNc3UwMHYxOXloTTUrdFUwYkVCZWo2eWpzWHRoYzlwS01hcUNIZWlQTC9TSHRkaWsxNVMKQmU4Sml4dVJxZitUeGlYWWVuNTg2aDlzTFpEYzA3cGpkUGp2NVNYRnBYQjhIMlVxQ0tZY2p4R3RvQWpTV0pjWApMNHc3RHNEby80bVg1N0htR09iamlCN1ZyOGhVWEJDdFh2V0dmQXlmcEFZNS9vOXowdm4zREcxaDc1NVVwdDluCkF2MFZrbm9qcmJVYjM1ZlJuU1lYTVltS01LSnpNRlMrdmFvRlpwV0ZjTG10cFRWSWNzc0JGUEYyZEo3V1c0WHMKK0d2Vkl2eFl3S2wyZzFPTE1TTXRZa09vekdlblBXTzdIdU0yMUVKVGIvbHNEZ25GaTkrYWRGZHBLY3R2cm0zdgpmbW1HeG5pRmhLU05GU0xtNms5YStHL2pjK3NVQVBhb2FZNEQ3NHVGajh0WGp0eThFUHdRRGxVUGRVZld3SE9PClF3bVgyMys1REh4V0VoQy91Tm8yNHNNY2ZkQzFGZUpBV281bUNuVU5vUVVmMStNRDVhMzNJdDhhMmlrNUkxUWoKeSs1WGpRaG0xd3RBMWhWTWE4aUxBR0toT09lcFRuK1VBZHpyS0hvNjVtYzNKbGgvSFJDUXJabnVxWkErK0F2WgpjeWU0dWZGWC8xdmRQSTdLb2Q0MEdDM2dlQnhweFFNYnp1OFNUcGpOcElJRkJvRVc5dFRhemUzeHZXWnV6dDc0CnFjZS8xWURuUHBLeW5lM0xGMk94VWoyYWVYUW5YQkpYcGhTZTBVTGJMcWJtUll4bjJKWkl1d09RNHV5dm94NjUKdG9TWGNac054dUs4QTErZXNXR3JSN3pVc0djdU9QQTFERE9Ja2JjcGtmRUxMNjk4RTJRckdqTU9JWnhrcWdxZQoySE5VNktWRmV2NzdZeEJDbm1VcVdXZEhYMjcyU2NPMUYzdWpUdFVnRVBNWGN0aEdBckYzTWxEaUw1Q0k0RkhqCnhHc3pVemxzalRQTmpiY2MzdUE2MjVZS3VVZEI2c1h1Rk5NUHk5UDgwTzBpRWJGTXl3MWxmN2VpdFhvaUUxWVoKc3NhMDVxTUx4M3pPUXZTLzFDdFpqaFp4cVJMRW5pQ3NWa2JVRlVYclpodEU4dG94bGpWSUtpQ25qbitORmtqdwo2bTZ1anpBSytZZHd2Nk5WMFB4S0gwUk5NYVhwb1lmQk1oUmZ3dGlaS3V3Y2hyRFB5UEhBQ2J3WXNZOXdtUE9rCnpwdDNxWi9JdDVYTmVqNDI0RzAzcGpMbk1sd1B1T1VzYmFQUWQ2VHU4TFhsckZReUVjTXJDNHdjUTA1SzFVN3kKM1NNN3RFaTlnbjV3RjY1YVI5eEFBR0grTUtMMk5WNnQrUmlTazJVaWs1clNmeDE4Mk9wYmpSQ2grdmQ4UXhJdwotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=', 'base64'), + key: Buffer.from( + 'LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpQcm9jLVR5cGU6IDQsRU5DUllQVEVECkRFSy1JbmZvOiBERVMtRURFMy1DQkMsNTM1QURERjIzNUQ4QkFGOQoKMXFWUzl0S0FhdkVhVUVFMktESnpjM3plMk1lZkc1dmVEd2o1UVJ0ZkRaMXdWNS9VZmIvcU5sVThTSjlNaGhKaQp6RFdrWExueUUzSW0vcEtITVZkS0czYWZkcFRtcis2TmtidXptd0dVMk0vSWpzRjRJZlpad0lGbGJoY09jUnp4CmZmWVIvTlVyaHNrS1RpNGhGV0lBUDlLb3Z6VDhPSzNZY3h6eVZQWUxYNGVWbWt3UmZzeWkwUU5Xb0tGT3d0ZC8KNm9HYzFnd2piRjI5ZDNnUThZQjFGWmRLa1AyMTJGbkt1cTIrUWgvbE1zTUZrTHlTQTRLTGJ3ZG1RSXExbE1QUwpjbUNtZnppV3J1MlBtNEZoM0dmWlQyaE1JWHlIRFdEVzlDTkxKaERodExOZ2RRamFBUFpVT1E4V2hwSkE5MS9vCjJLZzZ3MDd5Z2RCcVd5dTZrc0pXcjNpZ1JpUEJ5QmVNWEpEZU5HY3NhaUZ3Q2c5eFlja1VORXR3NS90WlRsTjIKSEdZV0NpVU5Ed0F2WllMUHR1SHpIOFRFMGxsZm5HR0VuVC9QQlp1UHV4andlZlRleE1mdzFpbGJRU3lkcy9HMgpOOUlKKzkydms0N0ZXR2NOdGh1Q3lCbklva0NpZ0c1ZlBlV2IwQTdpdjk0UGtwRTRJZ3plc0hGQ0ZFQWoxWldLCnpQdFRBQlkwZlJrUzBNc3UwMHYxOXloTTUrdFUwYkVCZWo2eWpzWHRoYzlwS01hcUNIZWlQTC9TSHRkaWsxNVMKQmU4Sml4dVJxZitUeGlYWWVuNTg2aDlzTFpEYzA3cGpkUGp2NVNYRnBYQjhIMlVxQ0tZY2p4R3RvQWpTV0pjWApMNHc3RHNEby80bVg1N0htR09iamlCN1ZyOGhVWEJDdFh2V0dmQXlmcEFZNS9vOXowdm4zREcxaDc1NVVwdDluCkF2MFZrbm9qcmJVYjM1ZlJuU1lYTVltS01LSnpNRlMrdmFvRlpwV0ZjTG10cFRWSWNzc0JGUEYyZEo3V1c0WHMKK0d2Vkl2eFl3S2wyZzFPTE1TTXRZa09vekdlblBXTzdIdU0yMUVKVGIvbHNEZ25GaTkrYWRGZHBLY3R2cm0zdgpmbW1HeG5pRmhLU05GU0xtNms5YStHL2pjK3NVQVBhb2FZNEQ3NHVGajh0WGp0eThFUHdRRGxVUGRVZld3SE9PClF3bVgyMys1REh4V0VoQy91Tm8yNHNNY2ZkQzFGZUpBV281bUNuVU5vUVVmMStNRDVhMzNJdDhhMmlrNUkxUWoKeSs1WGpRaG0xd3RBMWhWTWE4aUxBR0toT09lcFRuK1VBZHpyS0hvNjVtYzNKbGgvSFJDUXJabnVxWkErK0F2WgpjeWU0dWZGWC8xdmRQSTdLb2Q0MEdDM2dlQnhweFFNYnp1OFNUcGpOcElJRkJvRVc5dFRhemUzeHZXWnV6dDc0CnFjZS8xWURuUHBLeW5lM0xGMk94VWoyYWVYUW5YQkpYcGhTZTBVTGJMcWJtUll4bjJKWkl1d09RNHV5dm94NjUKdG9TWGNac054dUs4QTErZXNXR3JSN3pVc0djdU9QQTFERE9Ja2JjcGtmRUxMNjk4RTJRckdqTU9JWnhrcWdxZQoySE5VNktWRmV2NzdZeEJDbm1VcVdXZEhYMjcyU2NPMUYzdWpUdFVnRVBNWGN0aEdBckYzTWxEaUw1Q0k0RkhqCnhHc3pVemxzalRQTmpiY2MzdUE2MjVZS3VVZEI2c1h1Rk5NUHk5UDgwTzBpRWJGTXl3MWxmN2VpdFhvaUUxWVoKc3NhMDVxTUx4M3pPUXZTLzFDdFpqaFp4cVJMRW5pQ3NWa2JVRlVYclpodEU4dG94bGpWSUtpQ25qbitORmtqdwo2bTZ1anpBSytZZHd2Nk5WMFB4S0gwUk5NYVhwb1lmQk1oUmZ3dGlaS3V3Y2hyRFB5UEhBQ2J3WXNZOXdtUE9rCnpwdDNxWi9JdDVYTmVqNDI0RzAzcGpMbk1sd1B1T1VzYmFQUWQ2VHU4TFhsckZReUVjTXJDNHdjUTA1SzFVN3kKM1NNN3RFaTlnbjV3RjY1YVI5eEFBR0grTUtMMk5WNnQrUmlTazJVaWs1clNmeDE4Mk9wYmpSQ2grdmQ4UXhJdwotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=', + 'base64', + ), passphrase: 'pwd1234', }); @@ -91,25 +97,29 @@ describe('input secrets', () => { const encryptedInput = encryptInputSecrets({ input: testInput, inputSchema, publicKey }); expect(encryptedInput.secure).not.toEqual(testInput.secure); expect(encryptedInput.customString).toEqual(testInput.customString); - expect(() => decryptInputSecrets({ input: encryptedInput, privateKey: publicKey })) - .toThrow(`The input field "secure" could not be decrypted. Try updating the field's value in the input editor.`); + expect(() => decryptInputSecrets({ input: encryptedInput, privateKey: publicKey })).toThrow( + `The input field "secure" could not be decrypted. Try updating the field's value in the input editor.`, + ); }); it('should throw if secret object is not valid json', () => { // eslint-disable-next-line max-len - const secure = 'ENCRYPTED_VALUE:M8QcrS+opESY1KTi4bLvAx0Czxa+idIBq3XKD6gbzb7/CpK9soZrFhqgUIWsFKHMxbISUQu/Btex+WmakhDJFRA/vLLBp4Mit9JY+hwfnfQcBfwuI+ajqYyary6YqQth6gHKF5TZqhu2S1lc+O5t4oRRTCm+Qyk2dYY5nP0muCixatFT3Fu5UzpbFhElH8QiEbySy5jtjZLHZmFe9oPdk3Z8fV0nug9QlEuvYwR1eWK7e0A72zklgfBVNvjsA7OJ2rkaHHef6x6s36k4nI8uIvEHMOZJfuTBjail8xW00BrsKiecuTuRsREYinAMUszunqg0uJthhJFk+3GsrJEkIg==:LX2wyg1xhv94GQf7GRnR8ySbNrdlGrN0icw55a5H3kXhZ2SdOriLcjyPAU9GJob/NlFjzNkf'; + const secure = + 'ENCRYPTED_VALUE:M8QcrS+opESY1KTi4bLvAx0Czxa+idIBq3XKD6gbzb7/CpK9soZrFhqgUIWsFKHMxbISUQu/Btex+WmakhDJFRA/vLLBp4Mit9JY+hwfnfQcBfwuI+ajqYyary6YqQth6gHKF5TZqhu2S1lc+O5t4oRRTCm+Qyk2dYY5nP0muCixatFT3Fu5UzpbFhElH8QiEbySy5jtjZLHZmFe9oPdk3Z8fV0nug9QlEuvYwR1eWK7e0A72zklgfBVNvjsA7OJ2rkaHHef6x6s36k4nI8uIvEHMOZJfuTBjail8xW00BrsKiecuTuRsREYinAMUszunqg0uJthhJFk+3GsrJEkIg==:LX2wyg1xhv94GQf7GRnR8ySbNrdlGrN0icw55a5H3kXhZ2SdOriLcjyPAU9GJob/NlFjzNkf'; // This is an example of an encrypted object that is not valid JSON: // { "key1": "value1", "key2" } // This should never happen in practice, but we want to test that the decryption function handles it gracefully. // eslint-disable-next-line max-len - const secureObject = 'ENCRYPTED_JSON:kGUk2YdlMZGKdycmBUUZMSbZh/GMB+wvXkWDuI6G9cIzBnKQEqngpCb/lJSSdM4Gd1Xy6rwBVMxGm6ntnYaOyx6lgZqBs5hQqMe3Q0rK2ToW279ZNVNdMmeQDjPKKPpYEpz6p9yAmrRvWu7+1fW6UmazSYj1ErLI9WVJnG3MXb3CsSfQa3HHZ7Qtmgx5AXGT19z24cVSMqWsQOyJW2UwB83jcKcxqAS4w0YV9GsLgMX0K01BR1sXP303Om8c28h6EW6+Ad02pGWwANWjszwY/cWjCNXd44BqJxssLZ3rfk1EG8MkosdK0Zem9/8O4TCbxEAr7hQ2qVwNf43h4si05w==:ry21ohthwOdgBIR9TN0kxpSBe+h7rwhIxvSe4carBWYQWHSiYptLceQ55F8='; + const secureObject = + 'ENCRYPTED_JSON:kGUk2YdlMZGKdycmBUUZMSbZh/GMB+wvXkWDuI6G9cIzBnKQEqngpCb/lJSSdM4Gd1Xy6rwBVMxGm6ntnYaOyx6lgZqBs5hQqMe3Q0rK2ToW279ZNVNdMmeQDjPKKPpYEpz6p9yAmrRvWu7+1fW6UmazSYj1ErLI9WVJnG3MXb3CsSfQa3HHZ7Qtmgx5AXGT19z24cVSMqWsQOyJW2UwB83jcKcxqAS4w0YV9GsLgMX0K01BR1sXP303Om8c28h6EW6+Ad02pGWwANWjszwY/cWjCNXd44BqJxssLZ3rfk1EG8MkosdK0Zem9/8O4TCbxEAr7hQ2qVwNf43h4si05w==:ry21ohthwOdgBIR9TN0kxpSBe+h7rwhIxvSe4carBWYQWHSiYptLceQ55F8='; const encryptedInput = { secure, secureObject, customString: 'just string', }; - expect(() => decryptInputSecrets({ input: encryptedInput, privateKey })) - .toThrow(`The input field "secureObject" could not be decrypted.`); + expect(() => decryptInputSecrets({ input: encryptedInput, privateKey })).toThrow( + `The input field "secureObject" could not be decrypted.`, + ); }); }); diff --git a/test/linked_list.test.ts b/test/linked_list.test.ts index 4377dda0f..70f7f9476 100644 --- a/test/linked_list.test.ts +++ b/test/linked_list.test.ts @@ -74,21 +74,37 @@ describe('linked_list', () => { // check invalid params // @ts-expect-error - expect(() => { list.addNode(null); }).toThrow(); + expect(() => { + list.addNode(null); + }).toThrow(); // @ts-expect-error - expect(() => { list.addNode(undefined); }).toThrow(); + expect(() => { + list.addNode(undefined); + }).toThrow(); // @ts-expect-error - expect(() => { list.addNode('blabla'); }).toThrow(); + expect(() => { + list.addNode('blabla'); + }).toThrow(); // @ts-expect-error - expect(() => { list.addNode(123); }).toThrow(); + expect(() => { + list.addNode(123); + }).toThrow(); // @ts-expect-error - expect(() => { list.addNode(true); }).toThrow(); + expect(() => { + list.addNode(true); + }).toThrow(); // @ts-expect-error - expect(() => { list.addNode(false); }).toThrow(); + expect(() => { + list.addNode(false); + }).toThrow(); // @ts-expect-error - expect(() => { list.addNode({ prev: {} }); }).toThrow(); + expect(() => { + list.addNode({ prev: {} }); + }).toThrow(); // @ts-expect-error - expect(() => { list.addNode({ next: {} }); }).toThrow(); + expect(() => { + list.addNode({ next: {} }); + }).toThrow(); }); }); @@ -96,7 +112,11 @@ describe('linked_list', () => { it('just works', () => { const list = new LinkedList(); const obj = {}; - const objWithEquals = { equals(other: any) { return !!other && other.xxx; } }; + const objWithEquals = { + equals(other: any) { + return !!other && other.xxx; + }, + }; list.add(123); list.add('test'); list.add(0.123); @@ -137,7 +157,9 @@ describe('linked_list', () => { // remove selected items [33, 0, 99, 45, 15].forEach((val) => { list.removeNode(list.find(val)!); - array = array.filter((i) => { return i !== val; }); + array = array.filter((i) => { + return i !== val; + }); assertSame(list, array); }); @@ -152,15 +174,25 @@ describe('linked_list', () => { // check invalid params // @ts-expect-error - expect(() => { list.removeNode(null); }).toThrow(); + expect(() => { + list.removeNode(null); + }).toThrow(); // @ts-expect-error - expect(() => { list.removeNode(undefined); }).toThrow(); + expect(() => { + list.removeNode(undefined); + }).toThrow(); // @ts-expect-error - expect(() => { list.removeNode(true); }).toThrow(); + expect(() => { + list.removeNode(true); + }).toThrow(); // @ts-expect-error - expect(() => { list.removeNode(false); }).toThrow(); + expect(() => { + list.removeNode(false); + }).toThrow(); // @ts-expect-error - expect(() => { list.removeNode(''); }).toThrow(); + expect(() => { + list.removeNode(''); + }).toThrow(); }); }); }); diff --git a/test/list_dictionary.test.ts b/test/list_dictionary.test.ts index ccbf08931..e3053768e 100644 --- a/test/list_dictionary.test.ts +++ b/test/list_dictionary.test.ts @@ -28,17 +28,29 @@ describe('list_dictionary', () => { // check invalid params // @ts-expect-error - expect(() => { ld.add(null, 'val'); }).toThrow(); + expect(() => { + ld.add(null, 'val'); + }).toThrow(); // @ts-expect-error - expect(() => { ld.add(123, 'val'); }).toThrow(); + expect(() => { + ld.add(123, 'val'); + }).toThrow(); // @ts-expect-error - expect(() => { ld.add(true, 'val'); }).toThrow(); + expect(() => { + ld.add(true, 'val'); + }).toThrow(); // @ts-expect-error - expect(() => { ld.add(false, 'val'); }).toThrow(); + expect(() => { + ld.add(false, 'val'); + }).toThrow(); // @ts-expect-error - expect(() => { ld.add({}, 'val'); }).toThrow(); + expect(() => { + ld.add({}, 'val'); + }).toThrow(); // @ts-expect-error - expect(() => { ld.add(null, null); }).toThrow(); + expect(() => { + ld.add(null, null); + }).toThrow(); // add various new elements expect(ld.add('', 'empty')).toBe(true); @@ -99,15 +111,25 @@ describe('list_dictionary', () => { // check invalid params // @ts-expect-error - expect(() => { ld.get(null); }).toThrow(); + expect(() => { + ld.get(null); + }).toThrow(); // @ts-expect-error - expect(() => { ld.get(123); }).toThrow(); + expect(() => { + ld.get(123); + }).toThrow(); // @ts-expect-error - expect(() => { ld.get(true); }).toThrow(); + expect(() => { + ld.get(true); + }).toThrow(); // @ts-expect-error - expect(() => { ld.get(false); }).toThrow(); + expect(() => { + ld.get(false); + }).toThrow(); // @ts-expect-error - expect(() => { ld.get({}); }).toThrow(); + expect(() => { + ld.get({}); + }).toThrow(); expect(ld.add('', 'empty')).toBe(true); array.push({ key: '', value: 'empty' }); @@ -147,15 +169,25 @@ describe('list_dictionary', () => { // check invalid params // @ts-expect-error - expect(() => { ld.remove(null); }).toThrow(); + expect(() => { + ld.remove(null); + }).toThrow(); // @ts-expect-error - expect(() => { ld.remove(123); }).toThrow(); + expect(() => { + ld.remove(123); + }).toThrow(); // @ts-expect-error - expect(() => { ld.remove(true); }).toThrow(); + expect(() => { + ld.remove(true); + }).toThrow(); // @ts-expect-error - expect(() => { ld.remove(false); }).toThrow(); + expect(() => { + ld.remove(false); + }).toThrow(); // @ts-expect-error - expect(() => { ld.remove({}); }).toThrow(); + expect(() => { + ld.remove({}); + }).toThrow(); expect(ld.add('', 'empty')).toBe(true); array.push({ key: '', value: 'empty' }); @@ -174,11 +206,15 @@ describe('list_dictionary', () => { // try remove all items expect(ld.remove('')).toBe('empty'); - array = array.filter((elem) => { return elem.key !== ''; }); + array = array.filter((elem) => { + return elem.key !== ''; + }); assertSame(ld, array); expect(ld.remove('null')).toBe(null); - array = array.filter((elem) => { return elem.key !== 'null'; }); + array = array.filter((elem) => { + return elem.key !== 'null'; + }); assertSame(ld, array); // try remove non-existent items @@ -190,7 +226,9 @@ describe('list_dictionary', () => { const indexes = _.shuffle(_.range(50)); indexes.forEach((i) => { expect(ld.remove(`key${i}`)).toBe(`val${i}`); - array = array.filter((elem) => { return elem.key !== `key${i}`; }); + array = array.filter((elem) => { + return elem.key !== `key${i}`; + }); assertSame(ld, array); }); diff --git a/test/log.test.ts b/test/log.test.ts index 58277b1a2..7c81fe3ab 100644 --- a/test/log.test.ts +++ b/test/log.test.ts @@ -10,8 +10,8 @@ const CONSOLE_METHODS = ['log', 'warn', 'error', 'debug'] as const; describe('log', () => { let loggerSpy: MockInstance; - let loggedLines: { [key in typeof CONSOLE_METHODS[number]]?: string; }; - const originalConsoleMethods = {} as Record void>; + let loggedLines: { [key in (typeof CONSOLE_METHODS)[number]]?: string }; + const originalConsoleMethods = {} as Record<(typeof CONSOLE_METHODS)[number], (...args: any[]) => void>; beforeEach(() => { loggerSpy = vi.spyOn(Logger.prototype, 'log'); @@ -80,44 +80,74 @@ describe('log', () => { it('should support internal() method', () => { const log = new Log(); log.internal(LEVELS.ERROR, 'Something to be informed about happened', { foo: 'bar' }); - expect(loggerSpy).toBeCalledWith(LEVELS.ERROR, 'Something to be informed about happened', { foo: 'bar' }, undefined, { prefix: null, suffix: null }); + expect(loggerSpy).toBeCalledWith( + LEVELS.ERROR, + 'Something to be informed about happened', + { foo: 'bar' }, + undefined, + { prefix: null, suffix: null }, + ); }); it('should support error() method', () => { const log = new Log(); log.error('Error happened', { foo: 'bar' }); - expect(loggerSpy).toBeCalledWith(LEVELS.ERROR, 'Error happened', { foo: 'bar' }, undefined, { prefix: null, suffix: null }); + expect(loggerSpy).toBeCalledWith(LEVELS.ERROR, 'Error happened', { foo: 'bar' }, undefined, { + prefix: null, + suffix: null, + }); }); it('should support exception() method', () => { const log = new Log(); const err = new Error('some-error'); log.exception(err, 'Error happened', { foo: 'bar' }); - expect(loggerSpy).toBeCalledWith(LEVELS.ERROR, 'Error happened', { foo: 'bar' }, { - [IS_APIFY_LOGGER_EXCEPTION]: true, - message: 'some-error', - name: 'Error', - stack: expect.any(String), - cause: undefined, - }, { prefix: null, suffix: null }); + expect(loggerSpy).toBeCalledWith( + LEVELS.ERROR, + 'Error happened', + { foo: 'bar' }, + { + [IS_APIFY_LOGGER_EXCEPTION]: true, + message: 'some-error', + name: 'Error', + stack: expect.any(String), + cause: undefined, + }, + { prefix: null, suffix: null }, + ); }); it('should support softFail() method', () => { const log = new Log(); log.softFail('Soft fail happened', { foo: 'bar' }); - expect(loggerSpy).toBeCalledWith(LEVELS.SOFT_FAIL, 'Soft fail happened', { foo: 'bar' }, undefined, { prefix: null, suffix: null }); + expect(loggerSpy).toBeCalledWith(LEVELS.SOFT_FAIL, 'Soft fail happened', { foo: 'bar' }, undefined, { + prefix: null, + suffix: null, + }); }); it('should support warning() method', () => { const log = new Log(); log.warning('Something to be warn about happened', { foo: 'bar' }); - expect(loggerSpy).toBeCalledWith(LEVELS.WARNING, 'Something to be warn about happened', { foo: 'bar' }, undefined, { prefix: null, suffix: null }); + expect(loggerSpy).toBeCalledWith( + LEVELS.WARNING, + 'Something to be warn about happened', + { foo: 'bar' }, + undefined, + { prefix: null, suffix: null }, + ); }); it('should support info() method', () => { const log = new Log(); log.info('Something to be informed about happened', { foo: 'bar' }); - expect(loggerSpy).toBeCalledWith(LEVELS.INFO, 'Something to be informed about happened', { foo: 'bar' }, undefined, { prefix: null, suffix: null }); + expect(loggerSpy).toBeCalledWith( + LEVELS.INFO, + 'Something to be informed about happened', + { foo: 'bar' }, + undefined, + { prefix: null, suffix: null }, + ); }); it('should support debug() method', () => { @@ -125,7 +155,10 @@ describe('log', () => { const log = new Log(); log.debug('Something to be debugged happened', { foo: 'bar' }); - expect(loggerSpy).toBeCalledWith(LEVELS.DEBUG, 'Something to be debugged happened', { foo: 'bar' }, undefined, { prefix: null, suffix: null }); + expect(loggerSpy).toBeCalledWith(LEVELS.DEBUG, 'Something to be debugged happened', { foo: 'bar' }, undefined, { + prefix: null, + suffix: null, + }); delete process.env[APIFY_ENV_VARS.LOG_LEVEL]; }); @@ -135,7 +168,10 @@ describe('log', () => { const log = new Log(); log.perf('Some perf info', { foo: 'bar' }); - expect(loggerSpy).toBeCalledWith(LEVELS.PERF, 'Some perf info', { foo: 'bar' }, undefined, { prefix: null, suffix: null }); + expect(loggerSpy).toBeCalledWith(LEVELS.PERF, 'Some perf info', { foo: 'bar' }, undefined, { + prefix: null, + suffix: null, + }); delete process.env[APIFY_ENV_VARS.LOG_LEVEL]; }); @@ -153,9 +189,18 @@ describe('log', () => { log.deprecated('Message 2'); log.deprecated('Message 3'); - expect(loggerSpy).toBeCalledWith(LEVELS.WARNING, 'Message 1', undefined, undefined, { prefix: null, suffix: null }); - expect(loggerSpy).toBeCalledWith(LEVELS.WARNING, 'Message 2', undefined, undefined, { prefix: null, suffix: null }); - expect(loggerSpy).toBeCalledWith(LEVELS.WARNING, 'Message 3', undefined, undefined, { prefix: null, suffix: null }); + expect(loggerSpy).toBeCalledWith(LEVELS.WARNING, 'Message 1', undefined, undefined, { + prefix: null, + suffix: null, + }); + expect(loggerSpy).toBeCalledWith(LEVELS.WARNING, 'Message 2', undefined, undefined, { + prefix: null, + suffix: null, + }); + expect(loggerSpy).toBeCalledWith(LEVELS.WARNING, 'Message 3', undefined, undefined, { + prefix: null, + suffix: null, + }); }); it('should not pass empty objects in data', () => { @@ -165,15 +210,30 @@ describe('log', () => { log.warning('empty data object', {}); log.warning('non-empty data object', { foo: 123 }); - expect(loggerSpy).toBeCalledWith(LEVELS.WARNING, 'no data', undefined, undefined, { prefix: null, suffix: null }); - expect(loggerSpy).toBeCalledWith(LEVELS.WARNING, 'empty data object', undefined, undefined, { prefix: null, suffix: null }); - expect(loggerSpy).toBeCalledWith(LEVELS.WARNING, 'non-empty data object', { foo: 123 }, undefined, { prefix: null, suffix: null }); + expect(loggerSpy).toBeCalledWith(LEVELS.WARNING, 'no data', undefined, undefined, { + prefix: null, + suffix: null, + }); + expect(loggerSpy).toBeCalledWith(LEVELS.WARNING, 'empty data object', undefined, undefined, { + prefix: null, + suffix: null, + }); + expect(loggerSpy).toBeCalledWith(LEVELS.WARNING, 'non-empty data object', { foo: 123 }, undefined, { + prefix: null, + suffix: null, + }); }); it('should support data', () => { const log = new Log({ data: { foo: 'bar' } }); log.info('Something to be informed about happened', { hotel: 'restaurant' }); - expect(loggerSpy).toBeCalledWith(LEVELS.INFO, 'Something to be informed about happened', { foo: 'bar', hotel: 'restaurant' }, undefined, { prefix: null, suffix: null }); + expect(loggerSpy).toBeCalledWith( + LEVELS.INFO, + 'Something to be informed about happened', + { foo: 'bar', hotel: 'restaurant' }, + undefined, + { prefix: null, suffix: null }, + ); }); it('should log cause for errors', () => { diff --git a/test/log_helpers.test.ts b/test/log_helpers.test.ts index fcb2ca507..0ef1d0112 100644 --- a/test/log_helpers.test.ts +++ b/test/log_helpers.test.ts @@ -74,7 +74,14 @@ describe('sanitizeData()', () => { b: '[object]', }, arr: [1, 2, '[object]'], - err: { name: err.name, message: err.message, stack: err.stack, ...err as any, cause: undefined, [IS_APIFY_LOGGER_EXCEPTION]: true }, + err: { + name: err.name, + message: err.message, + stack: err.stack, + ...(err as any), + cause: undefined, + [IS_APIFY_LOGGER_EXCEPTION]: true, + }, }; expect(sanitizeData(object, { maxDepth: 2 })).toEqual(limited); diff --git a/test/logger_text.test.ts b/test/logger_text.test.ts index d9b063eb0..4e84a565d 100644 --- a/test/logger_text.test.ts +++ b/test/logger_text.test.ts @@ -7,7 +7,7 @@ const CONSOLE_METHODS = ['log', 'warn', 'error', 'debug'] as const; const DATE_REGEX = '\\d\\d\\d\\d-\\d\\d-\\d\\d \\d\\d:\\d\\d:\\d\\d\\.\\d\\d\\d'; describe('loggerText', () => { - let loggedLines: { [key in typeof CONSOLE_METHODS[number]]?: string; }; + let loggedLines: { [key in (typeof CONSOLE_METHODS)[number]]?: string }; const originalConsoleMethods: Record = {}; beforeEach(() => { @@ -45,7 +45,14 @@ describe('loggerText', () => { level = LogLevel.ERROR; message = 'Some error happened'; const err = new Error('some-error'); - const errObj = { name: err.name, message: err.message, stack: err.stack, ...(err as any), cause: undefined, [IS_APIFY_LOGGER_EXCEPTION]: true }; + const errObj = { + name: err.name, + message: err.message, + stack: err.stack, + ...(err as any), + cause: undefined, + [IS_APIFY_LOGGER_EXCEPTION]: true, + }; logger.log(level, message, data, errObj); line = loggedLines.error; @@ -92,9 +99,6 @@ describe('loggerText', () => { logger.log(LogLevel.INFO, 'Some info message'); logger.log(LogLevel.ERROR, 'Some error message'); - expect(emitted).toEqual([ - 'INFO Some info message', - 'ERROR Some error message', - ]); + expect(emitted).toEqual(['INFO Some info message', 'ERROR Some error message']); }); }); diff --git a/test/lru_cache.test.ts b/test/lru_cache.test.ts index 2291caea0..bde592da9 100644 --- a/test/lru_cache.test.ts +++ b/test/lru_cache.test.ts @@ -41,17 +41,29 @@ describe('lru_cache', () => { // check invalid params // @ts-expect-error - expect(() => { lru.add(null, 'val'); }).toThrow(); + expect(() => { + lru.add(null, 'val'); + }).toThrow(); // @ts-expect-error - expect(() => { lru.add(123, 'val'); }).toThrow(); + expect(() => { + lru.add(123, 'val'); + }).toThrow(); // @ts-expect-error - expect(() => { lru.add(true, 'val'); }).toThrow(); + expect(() => { + lru.add(true, 'val'); + }).toThrow(); // @ts-expect-error - expect(() => { lru.add(false, 'val'); }).toThrow(); + expect(() => { + lru.add(false, 'val'); + }).toThrow(); // @ts-expect-error - expect(() => { lru.add({}, 'val'); }).toThrow(); + expect(() => { + lru.add({}, 'val'); + }).toThrow(); // @ts-expect-error - expect(() => { lru.add(null, null); }).toThrow(); + expect(() => { + lru.add(null, null); + }).toThrow(); // add various new elements expect(lru.add('', 'empty')).toBe(true); @@ -117,15 +129,25 @@ describe('lru_cache', () => { // check invalid params // @ts-expect-error - expect(() => { lru.get(null); }).toThrow(); + expect(() => { + lru.get(null); + }).toThrow(); // @ts-expect-error - expect(() => { lru.get(123); }).toThrow(); + expect(() => { + lru.get(123); + }).toThrow(); // @ts-expect-error - expect(() => { lru.get(true); }).toThrow(); + expect(() => { + lru.get(true); + }).toThrow(); // @ts-expect-error - expect(() => { lru.get(false); }).toThrow(); + expect(() => { + lru.get(false); + }).toThrow(); // @ts-expect-error - expect(() => { lru.get({}); }).toThrow(); + expect(() => { + lru.get({}); + }).toThrow(); expect(lru.add('', 'empty')).toBe(true); array.push({ key: '', value: 'empty' }); @@ -179,15 +201,25 @@ describe('lru_cache', () => { // check invalid params // @ts-expect-error - expect(() => { lru.remove(null); }).toThrow(); + expect(() => { + lru.remove(null); + }).toThrow(); // @ts-expect-error - expect(() => { lru.remove(123); }).toThrow(); + expect(() => { + lru.remove(123); + }).toThrow(); // @ts-expect-error - expect(() => { lru.remove(true); }).toThrow(); + expect(() => { + lru.remove(true); + }).toThrow(); // @ts-expect-error - expect(() => { lru.remove(false); }).toThrow(); + expect(() => { + lru.remove(false); + }).toThrow(); // @ts-expect-error - expect(() => { lru.remove({}); }).toThrow(); + expect(() => { + lru.remove({}); + }).toThrow(); expect(lru.add('', 'empty')).toBe(true); array.push({ key: '', value: 'empty' }); diff --git a/test/markdown_renderers.test.ts b/test/markdown_renderers.test.ts index 3951fb260..cdab9d240 100644 --- a/test/markdown_renderers.test.ts +++ b/test/markdown_renderers.test.ts @@ -34,7 +34,9 @@ describe('apifyMarked custom renderers work', () => { const repoUrl = `https://github.com/${repoFullName}`; const renderedLink = customLinkRenderer(href, text, repoUrl, branchName); - expect(renderedLink).toEqual('link to bar'); + expect(renderedLink).toEqual( + 'link to bar', + ); }); it('replaces relative URLs in images from GitHub repos with absolute URLs pointing to raw files', () => { @@ -43,7 +45,9 @@ describe('apifyMarked custom renderers work', () => { const repoUrl = `https://gitlab.com/${repoFullName}`; const renderedLink = customImageRenderer(href, text, repoUrl, branchName); - expect(renderedLink).toEqual(`link to image`); + expect(renderedLink).toEqual( + `link to image`, + ); }); it('does not replace absolute URLs', () => { @@ -52,7 +56,9 @@ describe('apifyMarked custom renderers work', () => { const repoUrl = `https://github.com/${repoFullName}`; const renderedLink = customLinkRenderer(href, text, repoUrl, branchName); - expect(renderedLink).toEqual('absolute-link'); + expect(renderedLink).toEqual( + 'absolute-link', + ); }); it('customLinkRenderer works with SSH URLs', () => { @@ -61,7 +67,9 @@ describe('apifyMarked custom renderers work', () => { const repoUrl = `git@gitlab.com:${repoFullName}.git`; const renderedLink = customLinkRenderer(href, text, repoUrl, branchName); - expect(renderedLink).toEqual('SSH link'); + expect(renderedLink).toEqual( + 'SSH link', + ); }); it('customImageRenderer works with SSH URLs', () => { @@ -70,7 +78,9 @@ describe('apifyMarked custom renderers work', () => { const repoUrl = `git@gitlab.com:${repoFullName}.git`; const renderedLink = customImageRenderer(href, text, repoUrl, branchName); - expect(renderedLink).toEqual('SSH link'); + expect(renderedLink).toEqual( + 'SSH link', + ); }); describe('repo name parser works', () => { diff --git a/test/marked.test.ts b/test/marked.test.ts index f04a9e65c..f9f9bd7f3 100644 --- a/test/marked.test.ts +++ b/test/marked.test.ts @@ -51,44 +51,43 @@ echo "Some bash code 2" This is footer text. `; -const EXPECTED_HTML = '\n' + -'

Title

\n' + -'

Code block with tabs

[apify-code-tabs]0[/apify-code-tabs]\n' + -'

Code block without tabs

[apify-code-tabs]1[/apify-code-tabs]
console.log('Fenced block with no language')\n' +
-'
\n' + -'
console.log('Tab indented block')\n' +
-'
\n' + -'\n' + -'

Second block with tabs

[apify-code-tabs]2[/apify-code-tabs]\n' + -'

This is footer text.

\n'; +const EXPECTED_HTML = + '\n' + + '

Title

\n' + + '

Code block with tabs

[apify-code-tabs]0[/apify-code-tabs]\n' + + '

Code block without tabs

[apify-code-tabs]1[/apify-code-tabs]
console.log('Fenced block with no language')\n' +
+    '
\n' + + '
console.log('Tab indented block')\n' +
+    '
\n' + + '\n' + + '

Second block with tabs

[apify-code-tabs]2[/apify-code-tabs]\n' + + '

This is footer text.

\n'; describe('apifyMarked custom renderer works', () => { - it('correctly parses markdown containing both ordinary code block and code blocks with tabs', () => { - const {html, codeTabsObjectPerIndex} = apifyMarked(MARKDOWN_UNDER_TEST); + const { html, codeTabsObjectPerIndex } = apifyMarked(MARKDOWN_UNDER_TEST); expect(html).toEqual(EXPECTED_HTML); - expect(codeTabsObjectPerIndex).toEqual( - { - '0': { - 'JavaScript': { language: 'javascript', code: "console.log('Some JS code');" }, - Python: { - language: 'python', - code: "print('Some python code');\n" + + expect(codeTabsObjectPerIndex).toEqual({ + '0': { + JavaScript: { language: 'javascript', code: "console.log('Some JS code');" }, + Python: { + language: 'python', + code: + "print('Some python code');\n" + 'count = 1\n' + 'if count >= 1:\n' + " print('Some intended python code');\n" + - "print('Some python code on next line');" - }, - 'Bash': { language: 'bash', code: 'echo "Some bash code"' } - }, - '1': { - 'JavaScript': { language: 'javascript', code: "console.log('Your standard javascript code block')" }, + "print('Some python code on next line');", }, - '2': { - 'Custom title': { language: 'javascript', code: "console.log('Some JS code 2');" }, - Bash: { language: 'bash', code: 'echo "Some bash code 2"' } - } - } - ); + Bash: { language: 'bash', code: 'echo "Some bash code"' }, + }, + '1': { + JavaScript: { language: 'javascript', code: "console.log('Your standard javascript code block')" }, + }, + '2': { + 'Custom title': { language: 'javascript', code: "console.log('Some JS code 2');" }, + Bash: { language: 'bash', code: 'echo "Some bash code 2"' }, + }, + }); }); }); diff --git a/test/memory_calculator.test.ts b/test/memory_calculator.test.ts index 3acae07a7..69b93f8e4 100644 --- a/test/memory_calculator.test.ts +++ b/test/memory_calculator.test.ts @@ -57,7 +57,7 @@ describe('calculateDefaultMemoryFromExpression', () => { describe('operations supported', () => { const context = { - input: { }, + input: {}, runOptions: { timeoutSecs: 60, memoryMbytes: 512 }, }; @@ -77,53 +77,48 @@ describe('calculateDefaultMemoryFromExpression', () => { { expression: 'a = 128', result: 128, name: '=' }, ]; - it.each(cases)( - `supports operation '$name'`, - async ({ expression, result }) => { - // in case operation is not supported, mathjs will throw - // we round the result to the closest power of 2 and clamp within limits. - expect(await calculateRunDynamicMemory(expression, context)).toBe(result); - }, - ); + it.each(cases)(`supports operation '$name'`, async ({ expression, result }) => { + // in case operation is not supported, mathjs will throw + // we round the result to the closest power of 2 and clamp within limits. + expect(await calculateRunDynamicMemory(expression, context)).toBe(result); + }); }); describe('operations supported', () => { const context = { - input: { }, + input: {}, runOptions: { timeoutSecs: 60, memoryMbytes: 512 }, }; // Note: all results are rounded to the closest power of 2 and clamped within limits. const cases = [ - { expression: 'evaluate(\'5 + 1\')', name: 'evaluate', error: 'Function evaluate is disabled.' }, - { expression: 'compile(\'5 + 1\')', name: 'compile', error: 'Function compile is disabled.' }, + { expression: "evaluate('5 + 1')", name: 'evaluate', error: 'Function evaluate is disabled.' }, + { expression: "compile('5 + 1')", name: 'compile', error: 'Function compile is disabled.' }, { expression: "parse('3^2 + 4^2')", name: 'parse', error: 'Function parse is disabled.' }, - { expression: 'simplify(\'5 + 1\')', name: 'simplify', error: 'Undefined function simplify' }, - { expression: 'derivative(\'5 + 1\')', name: 'derivative', error: 'Undefined function derivative' }, - { expression: 'resolve(\'5 + 1\')', name: 'resolve', error: 'Undefined function resolve' }, + { expression: "simplify('5 + 1')", name: 'simplify', error: 'Undefined function simplify' }, + { expression: "derivative('5 + 1')", name: 'derivative', error: 'Undefined function derivative' }, + { expression: "resolve('5 + 1')", name: 'resolve', error: 'Undefined function resolve' }, { expression: 'import({ myvalue: 42 })', name: 'import', error: 'Undefined function import' }, - { expression: 'createUnit(\'foo\')', name: 'createUnit', error: 'Undefined function createUnit' }, + { expression: "createUnit('foo')", name: 'createUnit', error: 'Undefined function createUnit' }, { expression: 'reviver(\'{"mathjs":"Unit"}\')', name: 'reviver', error: 'Undefined function reviver' }, ]; - it.each(cases)( - `supports operation '$name'`, - async ({ expression, error }) => { - // in case operation is not supported, mathjs will throw - // we round the result to the closest power of 2 and clamp within limits. - await expect(calculateRunDynamicMemory(expression, context)).rejects.toThrow(error); - }, - ); + it.each(cases)(`supports operation '$name'`, async ({ expression, error }) => { + // in case operation is not supported, mathjs will throw + // we round the result to the closest power of 2 and clamp within limits. + await expect(calculateRunDynamicMemory(expression, context)).rejects.toThrow(error); + }); }); }); describe('Template {{variables}} support', () => { - it('should throw error if variable doesn\'t start with runOptions. or input.', async () => { + it("should throw error if variable doesn't start with runOptions. or input.", async () => { const context = { input: {}, runOptions: { memoryMbytes: 16 } }; const expr = '{{nonexistentVariable}} * 1024'; - await expect(calculateRunDynamicMemory(expr, context)) - .rejects.toThrow(`Invalid variable '{{nonexistentVariable}}' in expression.`); + await expect(calculateRunDynamicMemory(expr, context)).rejects.toThrow( + `Invalid variable '{{nonexistentVariable}}' in expression.`, + ); }); it('correctly evaluates valid runOptions property', async () => { @@ -134,17 +129,18 @@ describe('calculateDefaultMemoryFromExpression', () => { }); it('correctly evaluates input property', async () => { - const context = { input: { value: 16 }, runOptions: { } }; + const context = { input: { value: 16 }, runOptions: {} }; const expr = '{{input.value}} * 1024'; const result = await calculateRunDynamicMemory(expr, context); expect(result).toBe(16384); }); it('should throw error if runOptions property is not supported', async () => { - const context = { input: { value: 16 }, runOptions: { } }; + const context = { input: { value: 16 }, runOptions: {} }; const expr = '{{runOptions.customVariable}} * 1024'; - await expect(calculateRunDynamicMemory(expr, context)) - .rejects.toThrow(`Invalid variable '{{runOptions.customVariable}}' in expression. Only the following runOptions are allowed:`); + await expect(calculateRunDynamicMemory(expr, context)).rejects.toThrow( + `Invalid variable '{{runOptions.customVariable}}' in expression. Only the following runOptions are allowed:`, + ); }); }); @@ -175,39 +171,53 @@ describe('calculateDefaultMemoryFromExpression', () => { describe('Invalid/error handling', () => { it('should throw an error if expression length is greater than DEFAULT_MEMORY_MBYTES_MAX_CHARS', async () => { const expr = '1'.repeat(DEFAULT_MEMORY_MBYTES_EXPRESSION_MAX_LENGTH + 1); - await expect(calculateRunDynamicMemory(expr, emptyContext)) - .rejects.toThrow(`The defaultMemoryMbytes expression is too long. Max length is ${DEFAULT_MEMORY_MBYTES_EXPRESSION_MAX_LENGTH} characters.`); + await expect(calculateRunDynamicMemory(expr, emptyContext)).rejects.toThrow( + `The defaultMemoryMbytes expression is too long. Max length is ${DEFAULT_MEMORY_MBYTES_EXPRESSION_MAX_LENGTH} characters.`, + ); }); it('should throw an error for invalid syntax', async () => { const expr = '1 +* 2'; - await expect(calculateRunDynamicMemory(expr, emptyContext)) - .rejects.toThrow(); + await expect(calculateRunDynamicMemory(expr, emptyContext)).rejects.toThrow(); }); it('should throw error if result is 0', async () => { - await expect(calculateRunDynamicMemory('10 - 10', emptyContext)).rejects.toThrow(`Calculated memory value must be a positive number, greater than 0, got: 0.`); + await expect(calculateRunDynamicMemory('10 - 10', emptyContext)).rejects.toThrow( + `Calculated memory value must be a positive number, greater than 0, got: 0.`, + ); }); it('should throw error if result is negative', async () => { - await expect(calculateRunDynamicMemory('5 - 10', emptyContext)).rejects.toThrow(`Calculated memory value must be a positive number, greater than 0, got: -5.`); + await expect(calculateRunDynamicMemory('5 - 10', emptyContext)).rejects.toThrow( + `Calculated memory value must be a positive number, greater than 0, got: -5.`, + ); }); it('should throw error if result is NaN', async () => { - await expect(calculateRunDynamicMemory('0 / 0', emptyContext)).rejects.toThrow('Calculated memory value is not a valid number: NaN.'); + await expect(calculateRunDynamicMemory('0 / 0', emptyContext)).rejects.toThrow( + 'Calculated memory value is not a valid number: NaN.', + ); }); it('should throw error if result is Infinity', async () => { - await expect(calculateRunDynamicMemory('Infinity', emptyContext)).rejects.toThrow('Calculated memory value is not a valid number: Infinity.'); - await expect(calculateRunDynamicMemory('-Infinity', emptyContext)).rejects.toThrow('Calculated memory value is not a valid number: -Infinity.'); + await expect(calculateRunDynamicMemory('Infinity', emptyContext)).rejects.toThrow( + 'Calculated memory value is not a valid number: Infinity.', + ); + await expect(calculateRunDynamicMemory('-Infinity', emptyContext)).rejects.toThrow( + 'Calculated memory value is not a valid number: -Infinity.', + ); }); it('should throw error if result is a non-numeric (string)', async () => { - await expect(calculateRunDynamicMemory("'hello'", emptyContext)).rejects.toThrow('Calculated memory value is not a valid number: hello.'); + await expect(calculateRunDynamicMemory("'hello'", emptyContext)).rejects.toThrow( + 'Calculated memory value is not a valid number: hello.', + ); }); it('should throw error when disabled functionality of MathJS is used', async () => { - await expect(calculateRunDynamicMemory('evaluate(512)', emptyContext)).rejects.toThrow('Function evaluate is disabled.'); + await expect(calculateRunDynamicMemory('evaluate(512)', emptyContext)).rejects.toThrow( + 'Function evaluate is disabled.', + ); }); }); @@ -220,7 +230,9 @@ describe('calculateDefaultMemoryFromExpression', () => { const lruCache = new LruCache({ maxLength: 10 }); cache = { get: async (expression: string) => lruCache.get(expression), - set: async (expression: string, compilationResult: EvalFunction) => { lruCache.add(expression, compilationResult); }, + set: async (expression: string, compilationResult: EvalFunction) => { + lruCache.add(expression, compilationResult); + }, size: async () => lruCache.length(), }; }); diff --git a/test/parse_jsonl_stream.test.ts b/test/parse_jsonl_stream.test.ts index 76921c9de..aa8b6ddea 100644 --- a/test/parse_jsonl_stream.test.ts +++ b/test/parse_jsonl_stream.test.ts @@ -2,16 +2,7 @@ import { describe, expect, it } from 'vitest'; import { ParseJsonlStream } from '@apify/utilities'; -const OBJS = [ - { a: 123 }, - { bb: 234 }, - { ccc: 345 }, - { dddd: 456 }, - 5555, - 'string', - true, - false, -]; +const OBJS = [{ a: 123 }, { bb: 234 }, { ccc: 345 }, { dddd: 456 }, 5555, 'string', true, false]; describe('parse_jsonl_stream', () => { it('works on various objects', () => { @@ -46,11 +37,20 @@ describe('parse_jsonl_stream', () => { let count = 0; parseJsonl.on('object', (obj) => { switch (count++) { - case 0: expect({ aaa: 123 }).toEqual(obj); break; - case 1: expect(true).toEqual(obj); break; - case 2: expect(555666).toEqual(obj); break; - case 3: expect('string').toEqual(obj); break; - default: throw new Error(); + case 0: + expect({ aaa: 123 }).toEqual(obj); + break; + case 1: + expect(true).toEqual(obj); + break; + case 2: + expect(555666).toEqual(obj); + break; + case 3: + expect('string').toEqual(obj); + break; + default: + throw new Error(); } }); @@ -100,7 +100,9 @@ describe('parse_jsonl_stream', () => { // We need wait for next tick because of that. It would be great to rewrite the // stream to handle events properly, but given it's been like this since 2018, // I guess it would bring more trouble than benefit. - await new Promise((resolve) => { process.nextTick(resolve); }); + await new Promise((resolve) => { + process.nextTick(resolve); + }); }); it('fails on unfinished JSON', async () => { @@ -115,7 +117,9 @@ describe('parse_jsonl_stream', () => { parseJsonl.write('{ "aaa" :'); parseJsonl.end('"aaa'); - await new Promise((resolve) => { process.nextTick(resolve); }); + await new Promise((resolve) => { + process.nextTick(resolve); + }); expect(failed).toBe(true); }); }); diff --git a/test/pseudo_url.test.ts b/test/pseudo_url.test.ts index 87edff36c..3ec3788cf 100644 --- a/test/pseudo_url.test.ts +++ b/test/pseudo_url.test.ts @@ -13,13 +13,21 @@ describe('PseudoUrl', () => { test('should throw on invalid input', () => { // @ts-expect-error - expect(() => new PseudoUrl()).toThrow("Invalid PseudoUrl format, 'string' or 'RegExp' required, got `undefined` of type 'undefined' instead"); + expect(() => new PseudoUrl()).toThrow( + "Invalid PseudoUrl format, 'string' or 'RegExp' required, got `undefined` of type 'undefined' instead", + ); // @ts-expect-error - expect(() => new PseudoUrl(123)).toThrow("Invalid PseudoUrl format, 'string' or 'RegExp' required, got `123` of type 'number' instead"); + expect(() => new PseudoUrl(123)).toThrow( + "Invalid PseudoUrl format, 'string' or 'RegExp' required, got `123` of type 'number' instead", + ); // @ts-expect-error - expect(() => new PseudoUrl(['foo'])).toThrow("Invalid PseudoUrl format, 'string' or 'RegExp' required, got `[ 'foo' ]` of type 'array' instead"); + expect(() => new PseudoUrl(['foo'])).toThrow( + "Invalid PseudoUrl format, 'string' or 'RegExp' required, got `[ 'foo' ]` of type 'array' instead", + ); // @ts-expect-error - expect(() => new PseudoUrl({ foo: 'bar' })).toThrow("Invalid PseudoUrl format, 'string' or 'RegExp' required, got `{ foo: 'bar' }` of type 'object' instead"); + expect(() => new PseudoUrl({ foo: 'bar' })).toThrow( + "Invalid PseudoUrl format, 'string' or 'RegExp' required, got `{ foo: 'bar' }` of type 'object' instead", + ); }); test('should accept RegExp on input', () => { diff --git a/test/regexs.test.ts b/test/regexs.test.ts index 602485ce2..cd2f9416a 100644 --- a/test/regexs.test.ts +++ b/test/regexs.test.ts @@ -73,33 +73,12 @@ const tests = { }, APIFY_PROXY_VALUE_REGEX: { - valid: [ - '123_jkn_090', - '123_090', - 'klkn_kkk', - 'd', - '7', - '0.345245346', - 'fff~ggg', - ], - invalid: [ - '', - 'jjj-', - 's-s', - 'k#k', - '$', - ], + valid: ['123_jkn_090', '123_090', 'klkn_kkk', 'd', '7', '0.345245346', 'fff~ggg'], + invalid: ['', 'jjj-', 's-s', 'k#k', '$'], }, KEY_VALUE_STORE_KEY_REGEX: { - valid: [ - 'hello123', - '123hello', - 'this_is_1-key', - 'with(parens)', - ")(x_._-''", - '!!!', - ], + valid: ['hello123', '123hello', 'this_is_1-key', 'with(parens)', ")(x_._-''", '!!!'], invalid: [ '#', '"hello"', @@ -368,12 +347,6 @@ describe('regexps', () => { describe('SPLIT_PATH_REGEX', () => { it('works', () => { - expect( - '/aaa/bbb/ccc'.match(SPLIT_PATH_REGEX), - ).toEqual([ - 'aaa', - 'bbb', - 'ccc', - ]); + expect('/aaa/bbb/ccc'.match(SPLIT_PATH_REGEX)).toEqual(['aaa', 'bbb', 'ccc']); }); }); diff --git a/test/storages.test.ts b/test/storages.test.ts index 211b968e4..d5639c47e 100644 --- a/test/storages.test.ts +++ b/test/storages.test.ts @@ -1,6 +1,10 @@ import { describe, expect, it, vi } from 'vitest'; -import { createStorageContentSignature, createStorageContentSignatureAsync, cryptoRandomObjectId } from '@apify/utilities'; +import { + createStorageContentSignature, + createStorageContentSignatureAsync, + cryptoRandomObjectId, +} from '@apify/utilities'; describe('createStorageContentSignature()', () => { it('should set expiresAt to 0 for a non-expiring signature', () => { diff --git a/test/timeout.test.ts b/test/timeout.test.ts index f765a71a5..0e245f3c4 100644 --- a/test/timeout.test.ts +++ b/test/timeout.test.ts @@ -32,49 +32,29 @@ describe('timeout with abort controller', () => { }); it('passes without timeouting', async () => { - const res = await addTimeoutToPromise( - async () => handler(), - 200, - 'timed out', - ); + const res = await addTimeoutToPromise(async () => handler(), 200, 'timed out'); expect(res).toBe(123); expect(position).toBe(5); }); it('timeouts in the middle', async () => { - await expect(addTimeoutToPromise( - async () => handler(), - 100, - 'timed out', - )).rejects.toThrow(); + await expect(addTimeoutToPromise(async () => handler(), 100, 'timed out')).rejects.toThrow(); expect(position).toBe(3); }); it('timeouts with given error instance', async () => { const err = new Error('123'); - await expect(addTimeoutToPromise( - async () => handler(), - 100, - err, - )).rejects.toThrow(err); + await expect(addTimeoutToPromise(async () => handler(), 100, err)).rejects.toThrow(err); expect(position).toBe(3); }); it('timeouts with nesting', async () => { // this will timeout and cause failure, but it will happen sooner than in 200ms, so err 1 will be thrown async function handler2() { - await addTimeoutToPromise( - async () => handler(), - 100, - 'err 1', - ); + await addTimeoutToPromise(async () => handler(), 100, 'err 1'); } - await expect(addTimeoutToPromise( - async () => handler2(), - 200, - 'err 2', - )).rejects.toThrow('err 1'); + await expect(addTimeoutToPromise(async () => handler2(), 200, 'err 2')).rejects.toThrow('err 1'); expect(position).toBe(3); }); }); diff --git a/test/utilities.client.test.ts b/test/utilities.client.test.ts index 4c3679ed9..6a30768c3 100644 --- a/test/utilities.client.test.ts +++ b/test/utilities.client.test.ts @@ -92,8 +92,7 @@ const BAD_REVERSIBLE_OBJECTS = [ $: null, }, { - toBSON() { - }, + toBSON() {}, }, { toBSON: 'some text', @@ -150,7 +149,7 @@ const BAD_IRREVERSIBLE_OBJECTS = [ const BAD_OBJECTS = _.union(BAD_REVERSIBLE_OBJECTS, BAD_IRREVERSIBLE_OBJECTS); // this effectively tests _escapePropertyName() and _unescapePropertyName() -const KNOWN_ESCAPES: { irreversible?: boolean, src: any, trg: any }[] = [ +const KNOWN_ESCAPES: { irreversible?: boolean; src: any; trg: any }[] = [ { src: { $test: 1 }, trg: { '\uFF04test': 1 }, @@ -255,8 +254,8 @@ const testEscape = function (escapeFunc: any, src: any, trg: any) { // ensure srcClone didn't change if ( - (srcClone && isObject(srcClone) && !isDateOrBuffer(srcClone)) - || (trgClone && isObject(trgClone) && !isDateOrBuffer(trgClone)) + (srcClone && isObject(srcClone) && !isDateOrBuffer(srcClone)) || + (trgClone && isObject(trgClone) && !isDateOrBuffer(trgClone)) ) { expect(srcClone).not.toBe(trgClone); } @@ -496,10 +495,12 @@ describe('utilities.client', () => { expect(normalizeUrl('a https://example.com b')).toEqual(null); expect(normalizeUrl('https://example.com?q=foo bar')).toEqual('https://example.com?q=foo+bar'); expect(normalizeUrl('https://example.com?q=foo+bar')).toEqual('https://example.com?q=foo+bar'); - expect(normalizeUrl('https://google.com/maps/search/restaurant prague/@39.1029725,39.5483593,4z')) - .toEqual('https://google.com/maps/search/restaurant%20prague/@39.1029725,39.5483593,4z'); - expect(normalizeUrl('https://google.com/maps/search/restaurantprague/@39.1029725,39.5483593,4z')) - .toEqual('https://google.com/maps/search/restaurantprague/@39.1029725,39.5483593,4z'); + expect(normalizeUrl('https://google.com/maps/search/restaurant prague/@39.1029725,39.5483593,4z')).toEqual( + 'https://google.com/maps/search/restaurant%20prague/@39.1029725,39.5483593,4z', + ); + expect(normalizeUrl('https://google.com/maps/search/restaurantprague/@39.1029725,39.5483593,4z')).toEqual( + 'https://google.com/maps/search/restaurantprague/@39.1029725,39.5483593,4z', + ); }); it('should lowercase hostname and protocols', () => { @@ -523,7 +524,9 @@ describe('utilities.client', () => { it('should remove common tracking parameters', () => { expect(normalizeUrl('http://example.com/?utm_source=xyz')).toEqual('http://example.com'); - expect(normalizeUrl('http://example.com/?utm_campaign=xyz¶m=val')).toEqual('http://example.com?param=val'); + expect(normalizeUrl('http://example.com/?utm_campaign=xyz¶m=val')).toEqual( + 'http://example.com?param=val', + ); expect(normalizeUrl('http://example.com/?utm_campaign=xyz&utm_source=neco')).toEqual('http://example.com'); }); @@ -537,8 +540,12 @@ describe('utilities.client', () => { expect(normalizeUrl('http://example.com#', false)).toEqual('http://example.com'); expect(normalizeUrl('http://example.com#fragment', true)).toEqual('http://example.com#fragment'); expect(normalizeUrl('http://example.com#', true)).toEqual('http://example.com'); - expect(normalizeUrl('https://www.example.com#keyB=val1&keyA=val2', true)).toEqual('https://www.example.com#keyB=val1&keyA=val2'); - expect(normalizeUrl('https://www.example.com#keyB=val1&keyA=val2', false)).toEqual('https://www.example.com'); + expect(normalizeUrl('https://www.example.com#keyB=val1&keyA=val2', true)).toEqual( + 'https://www.example.com#keyB=val1&keyA=val2', + ); + expect(normalizeUrl('https://www.example.com#keyB=val1&keyA=val2', false)).toEqual( + 'https://www.example.com', + ); }); it('should not touch invalid or empty params', () => { @@ -546,19 +553,30 @@ describe('utilities.client', () => { }); it('should work with @ inside query', () => { - expect(normalizeUrl('https://www.google.com/maps/search/restaurant/@39.102972537998426,39.54835927707177,4z?foo=bar&aaa=bbb')) - .toEqual('https://www.google.com/maps/search/restaurant/@39.102972537998426,39.54835927707177,4z?aaa=bbb&foo=bar'); + expect( + normalizeUrl( + 'https://www.google.com/maps/search/restaurant/@39.102972537998426,39.54835927707177,4z?foo=bar&aaa=bbb', + ), + ).toEqual( + 'https://www.google.com/maps/search/restaurant/@39.102972537998426,39.54835927707177,4z?aaa=bbb&foo=bar', + ); }); it('should normalize real-world URLs', () => { - expect(normalizeUrl('https://www.czc.cz/dell-xps-15-9550-touch-stribrna_3/183874/produkt' - + '?utm_source=heureka.cz' - + '&utm_medium=cpc' - + '&utm_campaign=Notebooky' - + '&utm_term=Dell_XPS_15_9550_Touch_stribrna')).toEqual('https://www.czc.cz/dell-xps-15-9550-touch-stribrna_3/183874/produkt'); + expect( + normalizeUrl( + 'https://www.czc.cz/dell-xps-15-9550-touch-stribrna_3/183874/produkt' + + '?utm_source=heureka.cz' + + '&utm_medium=cpc' + + '&utm_campaign=Notebooky' + + '&utm_term=Dell_XPS_15_9550_Touch_stribrna', + ), + ).toEqual('https://www.czc.cz/dell-xps-15-9550-touch-stribrna_3/183874/produkt'); const expected = 'http://notebooky.heureka.cz/f:2111:25235;2278:9720,9539;p:579,580'; - expect(normalizeUrl('http://notebooky.heureka.cz/f:2111:25235;2278:9720,9539;p:579,580/')).toEqual(expected); + expect(normalizeUrl('http://notebooky.heureka.cz/f:2111:25235;2278:9720,9539;p:579,580/')).toEqual( + expected, + ); }); // this is no longer a valid URL and results in `null`, if we want to support it, @@ -717,12 +735,28 @@ describe('utilities.client', () => { { field: [] }, // Fails minItems check { field: [{ key: '$', value: '' }] }, // Fails patternKey check { field: [{ key: '', value: '$' }] }, // Fails patternValue check - { field: [{ key: 'aA0', value: 'aA0' }, { key: 'aA1', value: 'aA1' }, { key: 'aA2', value: 'aA2' }] }, // Fails maxItems check - { field: [{ key: 'aA0', value: 'aB0' }, { value: 'aB0', key: 'aA0' }] }, // Fails uniqueItems check + { + field: [ + { key: 'aA0', value: 'aA0' }, + { key: 'aA1', value: 'aA1' }, + { key: 'aA2', value: 'aA2' }, + ], + }, // Fails maxItems check + { + field: [ + { key: 'aA0', value: 'aB0' }, + { value: 'aB0', key: 'aA0' }, + ], + }, // Fails uniqueItems check // Valid { field: null }, { field: [{ key: 'aA0', value: 'aA0' }] }, - { field: [{ key: 'aA0', value: 'aA0' }, { key: 'aA1', value: 'aA1' }] }, + { + field: [ + { key: 'aA0', value: 'aA0' }, + { key: 'aA1', value: 'aA1' }, + ], + }, ]; const results = inputs .map((input) => validateInputUsingValidator(validator, inputSchema, input)) @@ -1162,7 +1196,10 @@ describe('utilities.client', () => { describe('special cases for isSecret properties', () => { const publicKey = createPublicKey({ // eslint-disable-next-line max-len - key: Buffer.from('LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF0dis3NlNXbklhOFFKWC94RUQxRQpYdnBBQmE3ajBnQnVYenJNUU5adjhtTW1RU0t2VUF0TmpOL2xacUZpQ0haZUQxU2VDcGV1MnFHTm5XbGRxNkhUCnh5cXJpTVZEbFNKaFBNT09QSENISVNVdFI4Tk5lR1Y1MU0wYkxJcENabHcyTU9GUjdqdENWejVqZFRpZ1NvYTIKQWxrRUlRZWQ4UVlDKzk1aGJoOHk5bGcwQ0JxdEdWN1FvMFZQR2xKQ0hGaWNuaWxLVFFZay9MZzkwWVFnUElPbwozbUppeFl5bWFGNmlMZTVXNzg1M0VHWUVFVWdlWmNaZFNjaGVBMEdBMGpRSFVTdnYvMEZjay9adkZNZURJOTVsCmJVQ0JoQjFDbFg4OG4wZUhzUmdWZE5vK0NLMDI4T2IvZTZTK1JLK09VaHlFRVdPTi90alVMdGhJdTJkQWtGcmkKOFFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==', 'base64'), + key: Buffer.from( + 'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF0dis3NlNXbklhOFFKWC94RUQxRQpYdnBBQmE3ajBnQnVYenJNUU5adjhtTW1RU0t2VUF0TmpOL2xacUZpQ0haZUQxU2VDcGV1MnFHTm5XbGRxNkhUCnh5cXJpTVZEbFNKaFBNT09QSENISVNVdFI4Tk5lR1Y1MU0wYkxJcENabHcyTU9GUjdqdENWejVqZFRpZ1NvYTIKQWxrRUlRZWQ4UVlDKzk1aGJoOHk5bGcwQ0JxdEdWN1FvMFZQR2xKQ0hGaWNuaWxLVFFZay9MZzkwWVFnUElPbwozbUppeFl5bWFGNmlMZTVXNzg1M0VHWUVFVWdlWmNaZFNjaGVBMEdBMGpRSFVTdnYvMEZjay9adkZNZURJOTVsCmJVQ0JoQjFDbFg4OG4wZUhzUmdWZE5vK0NLMDI4T2IvZTZTK1JLK09VaHlFRVdPTi90alVMdGhJdTJkQWtGcmkKOFFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==', + 'base64', + ), }); it('should allow encrypted/raw input for secret string', () => { @@ -1178,16 +1215,9 @@ describe('utilities.client', () => { }); const rawInput = { field: 'value' }; const encryptedInput = encryptInputSecrets({ input: rawInput, inputSchema, publicKey }); - const validInputs = [ - rawInput, - encryptedInput, - { field: null }, - ]; + const validInputs = [rawInput, encryptedInput, { field: null }]; - const invalidInputs = [ - { field: {} }, - { field: [] }, - ]; + const invalidInputs = [{ field: {} }, { field: [] }]; let errorResults = validInputs .map((input) => validateInputUsingValidator(validator, inputSchema, input)) @@ -1219,15 +1249,8 @@ describe('utilities.client', () => { }); const rawInput = { field: { key1: 'value1', key2: 'value2' } }; const encryptedInput = encryptInputSecrets({ input: rawInput, inputSchema, publicKey }); - const validInputs = [ - rawInput, - encryptedInput, - { field: null }, - ]; - const invalidInputs = [ - { field: 'DATASET_ID' }, - { field: [] }, - ]; + const validInputs = [rawInput, encryptedInput, { field: null }]; + const invalidInputs = [{ field: 'DATASET_ID' }, { field: [] }]; let errorResults = validInputs .map((input) => validateInputUsingValidator(validator, inputSchema, input)) @@ -1259,15 +1282,8 @@ describe('utilities.client', () => { }); const rawInput = { field: ['value1', 'value2'] }; const encryptedInput = encryptInputSecrets({ input: rawInput, inputSchema, publicKey }); - const validInputs = [ - rawInput, - encryptedInput, - { field: null }, - ]; - const invalidInputs = [ - { field: 'DATASET_ID' }, - { field: {} }, - ]; + const validInputs = [rawInput, encryptedInput, { field: null }]; + const invalidInputs = [{ field: 'DATASET_ID' }, { field: {} }]; let errorResults = validInputs .map((input) => validateInputUsingValidator(validator, inputSchema, input)) @@ -1316,7 +1332,9 @@ describe('utilities.client', () => { }); expect(validateInputUsingValidator(modifiedTitleValidator, modifiedTitleSchema, rawInput)).toEqual([]); - expect(validateInputUsingValidator(modifiedTitleValidator, modifiedTitleSchema, encryptedInput)).toEqual([]); + expect( + validateInputUsingValidator(modifiedTitleValidator, modifiedTitleSchema, encryptedInput), + ).toEqual([]); const { inputSchema: modifiedSchema, validator: modifiedValidator } = buildInputSchema({ field: { @@ -1335,7 +1353,9 @@ describe('utilities.client', () => { const errors = validateInputUsingValidator(modifiedValidator, modifiedSchema, encryptedInput); expect(errors).not.toEqual([]); // eslint-disable-next-line max-len - expect(errors[0].message).toEqual('The field schema.properties.field is a secret field, but its schema has changed. Please update the value in the input editor.'); + expect(errors[0].message).toEqual( + 'The field schema.properties.field is a secret field, but its schema has changed. Please update the value in the input editor.', + ); }); }); @@ -1365,10 +1385,7 @@ describe('utilities.client', () => { required: ['key1'], }, }); - const validInputs = [ - { field: { key1: 'value' } }, - { field: { key1: 'value', key2: 'value' } }, - ]; + const validInputs = [{ field: { key1: 'value' } }, { field: { key1: 'value', key2: 'value' } }]; const invalidInputs = [ { field: [] }, { field: {} }, @@ -1425,11 +1442,7 @@ describe('utilities.client', () => { { field: [{ key1: 'value' }, { key1: 'value' }] }, { field: [{ key1: 'value', key2: 'value' }, { key1: 'value' }] }, ]; - const invalidInputs = [ - { field: {} }, - { field: [{ key2: 'value' }] }, - { field: [{ key3: 'value' }] }, - ]; + const invalidInputs = [{ field: {} }, { field: [{ key2: 'value' }] }, { field: [{ key3: 'value' }] }]; let errorResults = validInputs .map((input) => validateInputUsingValidator(validator, inputSchema, input)) @@ -1464,10 +1477,7 @@ describe('utilities.client', () => { }, }); - const validInputs = [ - { field: ['12', '345', '6789'] }, - { field: [] }, - ]; + const validInputs = [{ field: ['12', '345', '6789'] }, { field: [] }]; const invalidInputs = [ { field: ['1', '23'] }, // '1' is too short @@ -1509,9 +1519,7 @@ describe('utilities.client', () => { required: ['key.with.dot'], }, }); - const validInputs = [ - { field: { 'key.with.dot': 'value' } }, - ]; + const validInputs = [{ field: { 'key.with.dot': 'value' } }]; const invalidInputs = [ { field: [] }, { field: {} }, @@ -1538,20 +1546,23 @@ describe('utilities.client', () => { describe('special cases for number and integer fields', () => { it('should allow float number only for number field', () => { - const { inputSchema, validator } = buildInputSchema({ - numberField: { - title: 'Field 1', - description: 'My test field 1', - type: 'number', - editor: 'number', - }, - intField: { - title: 'Field 2', - description: 'My test field 2', - type: 'integer', - editor: 'number', + const { inputSchema, validator } = buildInputSchema( + { + numberField: { + title: 'Field 1', + description: 'My test field 1', + type: 'number', + editor: 'number', + }, + intField: { + title: 'Field 2', + description: 'My test field 2', + type: 'integer', + editor: 'number', + }, }, - }, { required: [] }); + { required: [] }, + ); const validInputs = [ { numberField: 1 }, @@ -1600,24 +1611,27 @@ describe('utilities.client', () => { }); it('should respect minimum, maximum for number and integer fields', () => { - const { inputSchema, validator } = buildInputSchema({ - numberField: { - title: 'Field 1', - description: 'My test field 1', - type: 'number', - editor: 'number', - minimum: 1.5, - maximum: 5.5, - }, - intField: { - title: 'Field 2', - description: 'My test field 2', - type: 'integer', - editor: 'number', - exclusiveMinimum: 1, - exclusiveMaximum: 5, + const { inputSchema, validator } = buildInputSchema( + { + numberField: { + title: 'Field 1', + description: 'My test field 1', + type: 'number', + editor: 'number', + minimum: 1.5, + maximum: 5.5, + }, + intField: { + title: 'Field 2', + description: 'My test field 2', + type: 'integer', + editor: 'number', + exclusiveMinimum: 1, + exclusiveMaximum: 5, + }, }, - }, { required: [] }); + { required: [] }, + ); const validInputs = [ { numberField: 1.5 }, @@ -1628,12 +1642,7 @@ describe('utilities.client', () => { { intField: 4 }, ]; - const invalidInputs = [ - { numberField: 1.4 }, - { numberField: 5.6 }, - { intField: 1 }, - { intField: 5 }, - ]; + const invalidInputs = [{ numberField: 1.4 }, { numberField: 5.6 }, { intField: 1 }, { intField: 5 }]; let errorResults = validInputs .map((input) => validateInputUsingValidator(validator, inputSchema, input)) @@ -1817,15 +1826,23 @@ describe('utilities.client', () => { errors = validateInputUsingValidator(validator, inputSchema, { objectField: {} }); expect(errors?.[0].message).toBe('objectField must have at least 1 property'); - errors = validateInputUsingValidator(validator, inputSchema, { objectField: { a: 1, b: 2, c: 3, d: 4 } }); + errors = validateInputUsingValidator(validator, inputSchema, { + objectField: { a: 1, b: 2, c: 3, d: 4 }, + }); expect(errors?.[0].message).toBe('objectField must have at most 3 properties'); - errors = validateInputUsingValidator(validator, inputSchema, { 'subSchema.Field': { nestedField: 'ab' } }); + errors = validateInputUsingValidator(validator, inputSchema, { + 'subSchema.Field': { nestedField: 'ab' }, + }); expect(errors?.[0].message).toBe('nestedField must be at least 3 characters long'); - errors = validateInputUsingValidator(validator, inputSchema, { keyValue: [{ key: 'invalidKey', value: 'VALUE' }] }); + errors = validateInputUsingValidator(validator, inputSchema, { + keyValue: [{ key: 'invalidKey', value: 'VALUE' }], + }); expect(errors?.[0].message).toBe('All keys in keyValue must match the pattern ^key_[0-9]+$'); - errors = validateInputUsingValidator(validator, inputSchema, { keyValue: [{ key: 'key_1', value: 'invalidValue' }] }); + errors = validateInputUsingValidator(validator, inputSchema, { + keyValue: [{ key: 'key_1', value: 'invalidValue' }], + }); expect(errors?.[0].message).toBe('All values in keyValue must match the pattern ^[A-Z]+$'); errors = validateInputUsingValidator(validator, inputSchema, { stringList: ['invalidItem'] }); @@ -1863,11 +1880,7 @@ describe('utilities.client', () => { date: new Date('2019-05-06T13:08:15.590Z'), num: 1, boolean: true, - arr: [ - 1, - 'something', - arrFuncSimple, - ], + arr: [1, 'something', arrFuncSimple], obj: { foo: 'bar', arrFunc, @@ -1961,7 +1974,10 @@ describe('utilities.client', () => { expect(splitFullName(' John Newman ')).toEqual(['John', 'Newman']); expect(splitFullName(' John \t\n\r Newman ')).toEqual(['John', '\t\n\r Newman']); expect(splitFullName('John Paul New\nman')).toEqual(['John', 'Paul New\nman']); - expect(splitFullName('John Paul Newman Karl Ludvig III')).toEqual(['John', 'Paul Newman Karl Ludvig III']); + expect(splitFullName('John Paul Newman Karl Ludvig III')).toEqual([ + 'John', + 'Paul Newman Karl Ludvig III', + ]); expect(splitFullName('New-man')).toEqual([null, 'New-man']); expect(splitFullName(' New man ')).toEqual(['New', 'man']); expect(splitFullName('More Spaces Between')).toEqual(['More', 'Spaces Between']); @@ -1970,18 +1986,30 @@ describe('utilities.client', () => { describe('#markedSetNofollowLinks', () => { it('should return a link without rel or target attributes for Apify links on the same hostname', () => { - const result = markedSetNofollowLinks('https://console.apify.com', 'Apify console', 'Apify Link', 'console.apify.com'); + const result = markedSetNofollowLinks( + 'https://console.apify.com', + 'Apify console', + 'Apify Link', + 'console.apify.com', + ); expect(result).toBe('Apify console'); }); it('should return a link with rel="noopener noreferrer" and target="_blank" for Apify links on a different hostname', () => { - const result = markedSetNofollowLinks('https://www.apify.com', 'Apify', 'Apify Link', 'different-hostname.com'); + const result = markedSetNofollowLinks( + 'https://www.apify.com', + 'Apify', + 'Apify Link', + 'different-hostname.com', + ); expect(result).toBe('Apify'); }); it('should return a link with rel="noopener noreferrer nofollow" and target="_blank" for non-Apify links', () => { const result = markedSetNofollowLinks('https://www.example.com', 'Example', 'Example Link'); - expect(result).toBe('Example'); + expect(result).toBe( + 'Example', + ); }); it('should return a link with rel="noopener noreferrer nofollow" and target="_blank" for invalid URLs', () => { @@ -2006,7 +2034,9 @@ describe('utilities.client', () => { it('should apply rel="noopener noreferrer nofollow" for links with an undefined hostname and non-Apify URLs', () => { const result = markedSetNofollowLinks('https://example.com', 'Example', 'Example Link', undefined); - expect(result).toBe('Example'); + expect(result).toBe( + 'Example', + ); }); }); }); diff --git a/test/utilities.test.ts b/test/utilities.test.ts index b8ff46772..38aa68396 100644 --- a/test/utilities.test.ts +++ b/test/utilities.test.ts @@ -151,9 +151,7 @@ describe('utilities', () => { }); }); - return utils - .sequentializePromises(promises) - .then((data) => expect(data).toEqual(range)); + return utils.sequentializePromises(promises).then((data) => expect(data).toEqual(range)); }); it('delayPromise()', async () => { diff --git a/test/webhook_payload_template.test.ts b/test/webhook_payload_template.test.ts index 5cf2622e5..c03e45189 100644 --- a/test/webhook_payload_template.test.ts +++ b/test/webhook_payload_template.test.ts @@ -79,11 +79,15 @@ describe('WebhookPayloadTemplate', () => { userId: ['some-user-id'], eventType: null, createdAt: null, - eventData: [{ tmpl: { - status: 200, - body: 'hello-world', - messages: [1, 2, 3], - } }], + eventData: [ + { + tmpl: { + status: 200, + body: 'hello-world', + messages: [1, 2, 3], + }, + }, + ], resource: null, }); }); @@ -100,7 +104,11 @@ describe('WebhookPayloadTemplate', () => { someResource: 2, }, }; - const payload = WebhookPayloadTemplate.parse(WEBHOOK_DEFAULT_PAYLOAD_TEMPLATE, WEBHOOK_ALLOWED_PAYLOAD_VARIABLES, context); + const payload = WebhookPayloadTemplate.parse( + WEBHOOK_DEFAULT_PAYLOAD_TEMPLATE, + WEBHOOK_ALLOWED_PAYLOAD_VARIABLES, + context, + ); expect(payload).toEqual(context); }); @@ -125,7 +133,8 @@ describe('WebhookPayloadTemplate', () => { }, arrayField: [1, 2, 3], }; - const payload = WebhookPayloadTemplate.parse(` + const payload = WebhookPayloadTemplate.parse( + ` { "justVariable": "{{foo}}", "someOtherContent": "bar{{foo}}", @@ -137,7 +146,11 @@ describe('WebhookPayloadTemplate', () => { "resourceWrapped": "{{resource}}", "resourceDirect": {{resource}}, "resourceInString": "This is my object {{resource}}" - }`, null, context, { interpolateStrings: true }); + }`, + null, + context, + { interpolateStrings: true }, + ); expect(payload.justVariable).toBe('hello'); expect(payload.someOtherContent).toBe('barhello'); expect(payload.twoVariables).toBe('barhellobazhellobarworld'); @@ -211,10 +224,7 @@ describe('WebhookPayloadTemplate', () => { second: 'o', }, }, - array: [ - false, - [true], - ], + array: [false, [true]], }; const payload = WebhookPayloadTemplate.parse(template, null, context); expect(payload).toEqual({ diff --git a/vitest.config.mts b/vitest.config.mts index 1ad5db6f8..3bc68b9c7 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -3,9 +3,7 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ resolve: { - alias: [ - { find: /^@apify\/(.*)$/, replacement: path.resolve(__dirname, 'packages/$1/src') }, - ], + alias: [{ find: /^@apify\/(.*)$/, replacement: path.resolve(__dirname, 'packages/$1/src') }], }, test: { testTimeout: 30e3,