From ca005ba56bbfccdc73f209b221f1cdf5f39bbaef Mon Sep 17 00:00:00 2001 From: Daniel Tschinder <231804+danez@users.noreply.github.com> Date: Mon, 18 Jul 2022 17:27:25 +0200 Subject: [PATCH] fix: annotate ISC errors and split into to separate errors (#1146) * fix: annotate ISC errors and split into to separate errors * chore: refactor all other places * chore: remove unused function and fix types Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- src/config.ts | 2 +- src/main.ts | 2 +- src/runtimes/go/builder.ts | 8 +-- src/runtimes/node/bundlers/esbuild/bundler.ts | 13 +--- src/runtimes/node/bundlers/esbuild/index.ts | 2 +- .../bundlers/esbuild/plugin_native_modules.ts | 2 +- .../node/bundlers/esbuild/src_files.ts | 2 +- src/runtimes/node/bundlers/index.ts | 72 +------------------ src/runtimes/node/bundlers/nft/index.ts | 2 +- src/runtimes/node/bundlers/nft/transpile.ts | 13 +--- src/runtimes/node/bundlers/types.ts | 70 ++++++++++++++++++ src/runtimes/node/bundlers/zisi/index.ts | 2 +- .../node/bundlers/zisi/list_imports.ts | 13 +--- src/runtimes/node/bundlers/zisi/src_files.ts | 2 +- src/runtimes/node/in_source_config/index.ts | 33 ++++++--- src/runtimes/node/index.ts | 2 +- src/runtimes/runtime.ts | 2 +- src/runtimes/rust/builder.ts | 12 +--- src/utils/error.ts | 33 +++++++++ .../cron_esm_schedule_multi_import.js | 5 ++ .../cron_cjs_schedule_not_called.js | 0 .../cron_esm_schedule_not_called.js | 0 tests/main.js | 34 +++++++-- 23 files changed, 185 insertions(+), 141 deletions(-) create mode 100644 src/runtimes/node/bundlers/types.ts create mode 100644 src/utils/error.ts create mode 100644 tests/fixtures/in-source-config/functions/cron_esm_schedule_multi_import.js rename tests/fixtures/in-source-config/{functions_missing_cron_expression => functions_missing_schedule_usage}/cron_cjs_schedule_not_called.js (100%) rename tests/fixtures/in-source-config/{functions_missing_cron_expression => functions_missing_schedule_usage}/cron_esm_schedule_not_called.js (100%) diff --git a/src/config.ts b/src/config.ts index 263175c2e..19d763db6 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,7 +1,7 @@ import mergeOptions from 'merge-options' import { FunctionSource } from './function.js' -import type { NodeBundlerName } from './runtimes/node/bundlers/index.js' +import type { NodeBundlerName } from './runtimes/node/bundlers/types.js' import type { NodeVersionString } from './runtimes/node/index.js' import { minimatch } from './utils/matching.js' diff --git a/src/main.ts b/src/main.ts index 7a3285e8c..689369277 100644 --- a/src/main.ts +++ b/src/main.ts @@ -39,7 +39,7 @@ const augmentWithISC = async (func: FunctionSource): Promise { const functionName = basename(srcDir) @@ -17,13 +17,9 @@ export const build = async ({ destPath, mainFile, srcDir }: { destPath: string; }, }) } catch (error) { - const runtime: RuntimeName = 'go' - - error.customErrorInfo = { type: 'functionsBundling', location: { functionName, runtime } } - console.error(`Could not compile Go function ${functionName}:\n`) - throw error + throw new FunctionBundlingUserError(error, { functionName, runtime: 'go' }) } const stat = await fs.lstat(destPath) diff --git a/src/runtimes/node/bundlers/esbuild/bundler.ts b/src/runtimes/node/bundlers/esbuild/bundler.ts index 1ddd5eb7a..ffac5d415 100644 --- a/src/runtimes/node/bundlers/esbuild/bundler.ts +++ b/src/runtimes/node/bundlers/esbuild/bundler.ts @@ -5,9 +5,8 @@ import { tmpName } from 'tmp-promise' import type { FunctionConfig } from '../../../../config.js' import { FeatureFlags } from '../../../../feature_flags.js' +import { FunctionBundlingUserError } from '../../../../utils/error.js' import { getPathWithExtension, safeUnlink } from '../../../../utils/fs.js' -import type { RuntimeName } from '../../../runtime.js' -import type { NodeBundlerName } from '../index.js' import { getBundlerTarget, getModuleFormat } from './bundler_target.js' import { getDynamicImportsPlugin } from './plugin_dynamic_imports.js' @@ -134,15 +133,7 @@ export const bundleJsFile = async function ({ warnings, } } catch (error) { - const bundler: NodeBundlerName = 'esbuild' - const runtime: RuntimeName = 'js' - - error.customErrorInfo = { - type: 'functionsBundling', - location: { bundler, functionName: name, runtime }, - } - - throw error + throw new FunctionBundlingUserError(error, { functionName: name, runtime: 'js', bundler: 'esbuild' }) } } diff --git a/src/runtimes/node/bundlers/esbuild/index.ts b/src/runtimes/node/bundlers/esbuild/index.ts index b222f5a71..5f658ccad 100644 --- a/src/runtimes/node/bundlers/esbuild/index.ts +++ b/src/runtimes/node/bundlers/esbuild/index.ts @@ -4,7 +4,7 @@ import type { FunctionConfig } from '../../../../config.js' import { getPathWithExtension } from '../../../../utils/fs.js' import { nonNullable } from '../../../../utils/non_nullable.js' import { getBasePath } from '../../utils/base_path.js' -import type { BundleFunction } from '../index.js' +import type { BundleFunction } from '../types.js' import { bundleJsFile } from './bundler.js' import { getExternalAndIgnoredModulesFromSpecialCases } from './special_cases.js' diff --git a/src/runtimes/node/bundlers/esbuild/plugin_native_modules.ts b/src/runtimes/node/bundlers/esbuild/plugin_native_modules.ts index 9a8eb118b..ffe54e918 100644 --- a/src/runtimes/node/bundlers/esbuild/plugin_native_modules.ts +++ b/src/runtimes/node/bundlers/esbuild/plugin_native_modules.ts @@ -5,7 +5,7 @@ import readPackageJson from 'read-package-json-fast' import { isNativeModule } from '../../utils/detect_native_module.js' import { PackageJson } from '../../utils/package_json.js' -import type { NativeNodeModules } from '../index.js' +import type { NativeNodeModules } from '../types.js' type NativeModuleCacheEntry = [boolean | undefined, PackageJson] type NativeModuleCache = Record> diff --git a/src/runtimes/node/bundlers/esbuild/src_files.ts b/src/runtimes/node/bundlers/esbuild/src_files.ts index aae90c2aa..ee9f68c63 100644 --- a/src/runtimes/node/bundlers/esbuild/src_files.ts +++ b/src/runtimes/node/bundlers/esbuild/src_files.ts @@ -1,7 +1,7 @@ import { filterExcludedPaths, getPathsOfIncludedFiles } from '../../utils/included_files.js' import { getPackageJson, PackageJson } from '../../utils/package_json.js' import { getNewCache, TraversalCache } from '../../utils/traversal_cache.js' -import type { GetSrcFilesFunction } from '../index.js' +import type { GetSrcFilesFunction } from '../types.js' import { getDependencyPathsForDependency } from '../zisi/traverse.js' export const getSrcFiles: GetSrcFilesFunction = async ({ config, mainFile, pluginsModulesPath, srcDir }) => { diff --git a/src/runtimes/node/bundlers/index.ts b/src/runtimes/node/bundlers/index.ts index cf20d545d..3b23cacc6 100644 --- a/src/runtimes/node/bundlers/index.ts +++ b/src/runtimes/node/bundlers/index.ts @@ -1,79 +1,11 @@ -import type { Message } from '@netlify/esbuild' - -import { FunctionConfig } from '../../../config.js' -import { FeatureFlag, FeatureFlags } from '../../../feature_flags.js' -import { FunctionSource } from '../../../function.js' +import { FeatureFlags } from '../../../feature_flags.js' import { detectEsModule } from '../utils/detect_es_module.js' -import { ModuleFormat } from '../utils/module_format.js' import esbuildBundler from './esbuild/index.js' import nftBundler from './nft/index.js' +import type { NodeBundler, NodeBundlerName } from './types.js' import zisiBundler from './zisi/index.js' -export type NodeBundlerName = 'esbuild' | 'esbuild_zisi' | 'nft' | 'zisi' - -// TODO: Create a generic warning type -type BundlerWarning = Message - -type CleanupFunction = () => Promise - -export type NativeNodeModules = Record> - -export type BundleFunction = ( - args: { - basePath?: string - config: FunctionConfig - featureFlags: Record - pluginsModulesPath?: string - repositoryRoot?: string - } & FunctionSource, -) => Promise<{ - // Aliases are used to change the path that a file should take inside the - // generated archive. For example: - // - // "/my-transpiled-function.js" => "/my-function.js" - // - // When "/my-transpiled-function.js" is found in the list of files, it will - // be added to the archive with the "/my-function.js" path. - aliases?: Map - - // Rewrites are used to change the source file associated with a given path. - // For example: - // - // "/my-function.js" => "console.log(`Hello!`)" - // - // When "/my-function.js" is found in the list of files, it will be added to - // the archive with "console.log(`Hello!`)" as its source, replacing whatever - // the file at "/my-function.js" contains. - rewrites?: Map - - basePath: string - bundlerWarnings?: BundlerWarning[] - cleanupFunction?: CleanupFunction - includedFiles: string[] - inputs: string[] - mainFile: string - moduleFormat: ModuleFormat - nativeNodeModules?: NativeNodeModules - nodeModulesWithDynamicImports?: string[] - srcFiles: string[] -}> - -export type GetSrcFilesFunction = ( - args: { - basePath?: string - config: FunctionConfig - featureFlags: FeatureFlags - pluginsModulesPath?: string - repositoryRoot?: string - } & FunctionSource, -) => Promise<{ srcFiles: string[]; includedFiles: string[] }> - -interface NodeBundler { - bundle: BundleFunction - getSrcFiles: GetSrcFilesFunction -} - export const getBundler = (name: NodeBundlerName): NodeBundler => { switch (name) { case 'esbuild': diff --git a/src/runtimes/node/bundlers/nft/index.ts b/src/runtimes/node/bundlers/nft/index.ts index 1d85715bf..ab73aab9a 100644 --- a/src/runtimes/node/bundlers/nft/index.ts +++ b/src/runtimes/node/bundlers/nft/index.ts @@ -9,7 +9,7 @@ import { cachedReadFile, FsCache } from '../../../../utils/fs.js' import { minimatch } from '../../../../utils/matching.js' import { getBasePath } from '../../utils/base_path.js' import { filterExcludedPaths, getPathsOfIncludedFiles } from '../../utils/included_files.js' -import type { GetSrcFilesFunction, BundleFunction } from '../index.js' +import type { GetSrcFilesFunction, BundleFunction } from '../types.js' import { processESM } from './es_modules.js' diff --git a/src/runtimes/node/bundlers/nft/transpile.ts b/src/runtimes/node/bundlers/nft/transpile.ts index 20cd8ac04..65bc3e151 100644 --- a/src/runtimes/node/bundlers/nft/transpile.ts +++ b/src/runtimes/node/bundlers/nft/transpile.ts @@ -1,9 +1,8 @@ import { build } from '@netlify/esbuild' import type { FunctionConfig } from '../../../../config.js' -import type { RuntimeName } from '../../../runtime.js' +import { FunctionBundlingUserError } from '../../../../utils/error.js' import { getBundlerTarget } from '../esbuild/bundler_target.js' -import type { NodeBundlerName } from '../index.js' export const transpile = async (path: string, config: FunctionConfig, functionName: string) => { // The version of ECMAScript to use as the build target. This will determine @@ -24,14 +23,6 @@ export const transpile = async (path: string, config: FunctionConfig, functionNa return transpiled.outputFiles[0].text } catch (error) { - const bundler: NodeBundlerName = 'nft' - const runtime: RuntimeName = 'js' - - error.customErrorInfo = { - type: 'functionsBundling', - location: { bundler, functionName, runtime }, - } - - throw error + throw new FunctionBundlingUserError(error, { functionName, runtime: 'js', bundler: 'nft' }) } } diff --git a/src/runtimes/node/bundlers/types.ts b/src/runtimes/node/bundlers/types.ts new file mode 100644 index 000000000..776ef9fd4 --- /dev/null +++ b/src/runtimes/node/bundlers/types.ts @@ -0,0 +1,70 @@ +import type { Message } from '@netlify/esbuild' + +import type { FunctionConfig } from '../../../config.js' +import type { FeatureFlag, FeatureFlags } from '../../../feature_flags.js' +import type { FunctionSource } from '../../../function.js' +import type { ModuleFormat } from '../utils/module_format.js' + +export type NodeBundlerName = 'esbuild' | 'esbuild_zisi' | 'nft' | 'zisi' + +// TODO: Create a generic warning type +type BundlerWarning = Message + +type CleanupFunction = () => Promise + +export type NativeNodeModules = Record> + +export type BundleFunction = ( + args: { + basePath?: string + config: FunctionConfig + featureFlags: Record + pluginsModulesPath?: string + repositoryRoot?: string + } & FunctionSource, +) => Promise<{ + // Aliases are used to change the path that a file should take inside the + // generated archive. For example: + // + // "/my-transpiled-function.js" => "/my-function.js" + // + // When "/my-transpiled-function.js" is found in the list of files, it will + // be added to the archive with the "/my-function.js" path. + aliases?: Map + + // Rewrites are used to change the source file associated with a given path. + // For example: + // + // "/my-function.js" => "console.log(`Hello!`)" + // + // When "/my-function.js" is found in the list of files, it will be added to + // the archive with "console.log(`Hello!`)" as its source, replacing whatever + // the file at "/my-function.js" contains. + rewrites?: Map + + basePath: string + bundlerWarnings?: BundlerWarning[] + cleanupFunction?: CleanupFunction + includedFiles: string[] + inputs: string[] + mainFile: string + moduleFormat: ModuleFormat + nativeNodeModules?: NativeNodeModules + nodeModulesWithDynamicImports?: string[] + srcFiles: string[] +}> + +export type GetSrcFilesFunction = ( + args: { + basePath?: string + config: FunctionConfig + featureFlags: FeatureFlags + pluginsModulesPath?: string + repositoryRoot?: string + } & FunctionSource, +) => Promise<{ srcFiles: string[]; includedFiles: string[] }> + +export interface NodeBundler { + bundle: BundleFunction + getSrcFiles: GetSrcFilesFunction +} diff --git a/src/runtimes/node/bundlers/zisi/index.ts b/src/runtimes/node/bundlers/zisi/index.ts index ee2999561..98ec78545 100644 --- a/src/runtimes/node/bundlers/zisi/index.ts +++ b/src/runtimes/node/bundlers/zisi/index.ts @@ -1,7 +1,7 @@ import { dirname, normalize } from 'path' import { getBasePath } from '../../utils/base_path.js' -import type { BundleFunction } from '../index.js' +import type { BundleFunction } from '../types.js' import { getSrcFiles } from './src_files.js' diff --git a/src/runtimes/node/bundlers/zisi/list_imports.ts b/src/runtimes/node/bundlers/zisi/list_imports.ts index 62305642b..049abb49b 100644 --- a/src/runtimes/node/bundlers/zisi/list_imports.ts +++ b/src/runtimes/node/bundlers/zisi/list_imports.ts @@ -2,9 +2,8 @@ import * as esbuild from '@netlify/esbuild' import isBuiltinModule from 'is-builtin-module' import { tmpName } from 'tmp-promise' +import { FunctionBundlingUserError } from '../../../../utils/error.js' import { safeUnlink } from '../../../../utils/fs.js' -import type { RuntimeName } from '../../../runtime.js' -import type { NodeBundlerName } from '../index.js' // Maximum number of log messages that an esbuild instance will produce. This // limit is important to avoid out-of-memory errors due to too much data being @@ -58,15 +57,7 @@ export const listImports = async ({ target: 'esnext', }) } catch (error) { - const bundler: NodeBundlerName = 'zisi' - const runtime: RuntimeName = 'js' - - error.customErrorInfo = { - type: 'functionsBundling', - location: { bundler, functionName, runtime }, - } - - throw error + throw new FunctionBundlingUserError(error, { functionName, runtime: 'js', bundler: 'zisi' }) } finally { await safeUnlink(targetPath) } diff --git a/src/runtimes/node/bundlers/zisi/src_files.ts b/src/runtimes/node/bundlers/zisi/src_files.ts index 1e2e79e34..551a8453c 100644 --- a/src/runtimes/node/bundlers/zisi/src_files.ts +++ b/src/runtimes/node/bundlers/zisi/src_files.ts @@ -9,7 +9,7 @@ import { nonNullable } from '../../../../utils/non_nullable.js' import { filterExcludedPaths, getPathsOfIncludedFiles } from '../../utils/included_files.js' import { getPackageJson, PackageJson } from '../../utils/package_json.js' import { getNewCache, TraversalCache } from '../../utils/traversal_cache.js' -import type { GetSrcFilesFunction } from '../index.js' +import type { GetSrcFilesFunction } from '../types.js' import { listImports } from './list_imports.js' import { resolvePathPreserveSymlinks } from './resolve.js' diff --git a/src/runtimes/node/in_source_config/index.ts b/src/runtimes/node/in_source_config/index.ts index 4cd361c31..4b5ae900b 100644 --- a/src/runtimes/node/in_source_config/index.ts +++ b/src/runtimes/node/in_source_config/index.ts @@ -1,5 +1,6 @@ import { ArgumentPlaceholder, Expression, SpreadElement, JSXNamespacedName } from '@babel/types' +import { FunctionBundlingUserError } from '../../../utils/error.js' import { nonNullable } from '../../../utils/non_nullable.js' import { createBindingsMethod } from '../parser/bindings.js' import { getMainExport } from '../parser/exports.js' @@ -12,10 +13,26 @@ export const IN_SOURCE_CONFIG_MODULE = '@netlify/functions' export type ISCValues = Partial> +const validateScheduleFunction = (functionFound: boolean, scheduleFound: boolean, functionName: string): void => { + if (!functionFound) { + throw new FunctionBundlingUserError( + "The `schedule` helper was imported but we couldn't find any usages. If you meant to schedule a function, please check that `schedule` is invoked and `handler` correctly exported.", + { functionName, runtime: 'js' }, + ) + } + + if (!scheduleFound) { + throw new FunctionBundlingUserError( + 'Unable to find cron expression for scheduled function. The cron expression (first argument) for the `schedule` helper needs to be accessible inside the file and cannot be imported.', + { functionName, runtime: 'js' }, + ) + } +} + // Parses a JS/TS file and looks for in-source config declarations. It returns // an array of all declarations found, with `property` indicating the name of // the property and `data` its value. -export const findISCDeclarationsInPath = async (sourcePath: string): Promise => { +export const findISCDeclarationsInPath = async (sourcePath: string, functionName: string): Promise => { const ast = await safelyParseFile(sourcePath) if (ast === null) { @@ -24,8 +41,9 @@ export const findISCDeclarationsInPath = async (sourcePath: string): Promise getImports(node, IN_SOURCE_CONFIG_MODULE)) - const scheduledFuncsExpected = imports.filter(({ imported }) => imported === 'schedule').length - let scheduledFuncsFound = 0 + const scheduledFunctionExpected = imports.some(({ imported }) => imported === 'schedule') + let scheduledFunctionFound = false + let scheduleFound = false const getAllBindings = createBindingsMethod(ast.body) const mainExports = getMainExport(ast.body, getAllBindings) @@ -41,8 +59,9 @@ export const findISCDeclarationsInPath = async (sourcePath: string): Promise ({ ...acc, ...obj }), {}) diff --git a/src/runtimes/node/index.ts b/src/runtimes/node/index.ts index 93eb59f54..b2ad02999 100644 --- a/src/runtimes/node/index.ts +++ b/src/runtimes/node/index.ts @@ -88,7 +88,7 @@ const zipFunction: ZipFunction = async function ({ stat, }) - const inSourceConfig = await findISCDeclarationsInPath(mainFile) + const inSourceConfig = await findISCDeclarationsInPath(mainFile, name) createPluginsModulesPathAliases(srcFiles, pluginsModulesPath, aliases, finalBasePath) diff --git a/src/runtimes/runtime.ts b/src/runtimes/runtime.ts index 48d0608cc..3109b296a 100644 --- a/src/runtimes/runtime.ts +++ b/src/runtimes/runtime.ts @@ -4,7 +4,7 @@ import { FeatureFlags } from '../feature_flags.js' import { FunctionSource, SourceFile } from '../function.js' import { FsCache } from '../utils/fs.js' -import type { NodeBundlerName } from './node/bundlers/index.js' +import type { NodeBundlerName } from './node/bundlers/types.js' import type { ISCValues } from './node/in_source_config/index.js' export type RuntimeName = 'go' | 'js' | 'rs' diff --git a/src/runtimes/rust/builder.ts b/src/runtimes/rust/builder.ts index f7c3e5fe6..74c74b1b6 100644 --- a/src/runtimes/rust/builder.ts +++ b/src/runtimes/rust/builder.ts @@ -5,23 +5,19 @@ import tmp from 'tmp-promise' import toml from 'toml' import { FunctionConfig } from '../../config.js' +import { FunctionBundlingUserError } from '../../utils/error.js' import { shellUtils } from '../../utils/shell.js' -import type { RuntimeName } from '../runtime.js' import { CargoManifest } from './cargo_manifest.js' import { BUILD_TARGET, MANIFEST_NAME } from './constants.js' -const runtimeName: RuntimeName = 'rs' - export const build = async ({ config, name, srcDir }: { config: FunctionConfig; name: string; srcDir: string }) => { const functionName = basename(srcDir) try { await installToolchainOnce() } catch (error) { - error.customErrorInfo = { type: 'functionsBundling', location: { functionName, runtime: runtimeName } } - - throw error + throw new FunctionBundlingUserError(error, { functionName, runtime: 'rs' }) } const targetDirectory = await getTargetDirectory({ config, name }) @@ -71,9 +67,7 @@ const cargoBuild = async ({ 'There is no Rust toolchain installed. Visit https://ntl.fyi/missing-rust-toolchain for more information.' } - error.customErrorInfo = { type: 'functionsBundling', location: { functionName, runtime: runtimeName } } - - throw error + throw new FunctionBundlingUserError(error, { functionName, runtime: 'rs' }) } } diff --git a/src/utils/error.ts b/src/utils/error.ts new file mode 100644 index 000000000..a7e6111ca --- /dev/null +++ b/src/utils/error.ts @@ -0,0 +1,33 @@ +import type { NodeBundlerName } from '../runtimes/node/bundlers/types.js' +import type { RuntimeName } from '../runtimes/runtime' + +interface CustomErrorLocation { + functionName: string + runtime: RuntimeName + bundler?: NodeBundlerName +} + +interface CustomErrorInfo { + type: 'functionsBundling' + location: CustomErrorLocation +} + +export class FunctionBundlingUserError extends Error { + customErrorInfo: CustomErrorInfo + + constructor(messageOrError: string | Error, customErrorInfo: CustomErrorLocation) { + const isError = messageOrError instanceof Error + + super(isError ? messageOrError.message : messageOrError) + + Object.setPrototypeOf(this, new.target.prototype) + this.name = 'FunctionBundlingUserError' + if (isError) { + this.stack = messageOrError.stack + } else { + Error.captureStackTrace(this, FunctionBundlingUserError) + } + + this.customErrorInfo = { type: 'functionsBundling', location: customErrorInfo } + } +} diff --git a/tests/fixtures/in-source-config/functions/cron_esm_schedule_multi_import.js b/tests/fixtures/in-source-config/functions/cron_esm_schedule_multi_import.js new file mode 100644 index 000000000..3e766e107 --- /dev/null +++ b/tests/fixtures/in-source-config/functions/cron_esm_schedule_multi_import.js @@ -0,0 +1,5 @@ +// eslint-disable-next-line n/no-unsupported-features/es-syntax +import { schedule } from '@netlify/functions' +import { schedule as schedule2 } from '@netlify/functions' + +export const handler = schedule2('@daily', () => {}) diff --git a/tests/fixtures/in-source-config/functions_missing_cron_expression/cron_cjs_schedule_not_called.js b/tests/fixtures/in-source-config/functions_missing_schedule_usage/cron_cjs_schedule_not_called.js similarity index 100% rename from tests/fixtures/in-source-config/functions_missing_cron_expression/cron_cjs_schedule_not_called.js rename to tests/fixtures/in-source-config/functions_missing_schedule_usage/cron_cjs_schedule_not_called.js diff --git a/tests/fixtures/in-source-config/functions_missing_cron_expression/cron_esm_schedule_not_called.js b/tests/fixtures/in-source-config/functions_missing_schedule_usage/cron_esm_schedule_not_called.js similarity index 100% rename from tests/fixtures/in-source-config/functions_missing_cron_expression/cron_esm_schedule_not_called.js rename to tests/fixtures/in-source-config/functions_missing_schedule_usage/cron_esm_schedule_not_called.js diff --git a/tests/main.js b/tests/main.js index ff7dab0f8..b7277e5c9 100644 --- a/tests/main.js +++ b/tests/main.js @@ -2633,7 +2633,7 @@ testMany( 'Finds in-source config declarations using the `schedule` helper', ['bundler_default', 'bundler_esbuild', 'bundler_nft'], async (options, t) => { - const FUNCTIONS_COUNT = 12 + const FUNCTIONS_COUNT = 13 const { files } = await zipFixture(t, join('in-source-config', 'functions'), { opts: options, length: FUNCTIONS_COUNT, @@ -2648,14 +2648,18 @@ testMany( ) testMany( - 'Throws error when `schedule` helper is imported but cron expression not found', + 'Throws error when `schedule` helper is used but cron expression not found', ['bundler_default', 'bundler_esbuild', 'bundler_nft'], async (options, t) => { const rejected = (error) => { - t.true(error.message.startsWith('Warning: unable to find cron expression for scheduled function.')) + t.true(error.message.startsWith('Unable to find cron expression for scheduled function.')) + t.is(error.customErrorInfo.type, 'functionsBundling') + t.is(error.customErrorInfo.location.bundler, undefined) + t.is(typeof error.customErrorInfo.location.functionName, 'string') + t.is(error.customErrorInfo.location.runtime, 'js') } - const FUNCTIONS_COUNT = 3 + const FUNCTIONS_COUNT = 1 await zipFixture(t, join('in-source-config', 'functions_missing_cron_expression'), { opts: options, length: FUNCTIONS_COUNT, @@ -2663,6 +2667,26 @@ testMany( }, ) +testMany( + 'Throws error when `schedule` helper is imported but not used', + ['bundler_default', 'bundler_esbuild', 'bundler_nft'], + async (options, t) => { + const rejected = (error) => { + t.true(error.message.startsWith("The `schedule` helper was imported but we couldn't find any usages.")) + t.is(error.customErrorInfo.type, 'functionsBundling') + t.is(error.customErrorInfo.location.bundler, undefined) + t.is(typeof error.customErrorInfo.location.functionName, 'string') + t.is(error.customErrorInfo.location.runtime, 'js') + } + + const FUNCTIONS_COUNT = 2 + await zipFixture(t, join('in-source-config', 'functions_missing_schedule_usage'), { + opts: options, + length: FUNCTIONS_COUNT, + }).catch(rejected) + }, +) + test('listFunctions surfaces schedule config property', async (t) => { const functions = await listFunctions(join(FIXTURES_DIR, 'many-functions'), { config: { @@ -2679,7 +2703,7 @@ test('listFunctions includes in-source config declarations', async (t) => { const functions = await listFunctions(join(FIXTURES_DIR, 'in-source-config', 'functions'), { parseISC: true, }) - const FUNCTIONS_COUNT = 12 + const FUNCTIONS_COUNT = 13 t.is(functions.length, FUNCTIONS_COUNT) functions.forEach((func) => { t.is(func.schedule, '@daily')