From b02557a42d3130b61b8853598ea941a95c5b2730 Mon Sep 17 00:00:00 2001 From: Ava Silver Date: Wed, 18 Mar 2026 13:37:20 +0100 Subject: [PATCH 1/3] [SVLS-5187] improve typing --- src/env.ts | 27 +++++----- src/forwarder.ts | 95 +++++++++++++++++++++++++----------- src/index.ts | 47 +++++++++--------- src/layer.ts | 17 ++++--- src/monitor-api-requests.ts | 16 +++--- src/monitors.ts | 28 +++++++---- src/span-link.ts | 2 +- src/step-functions-helper.ts | 21 ++++---- src/tracing.ts | 4 +- 9 files changed, 151 insertions(+), 106 deletions(-) diff --git a/src/env.ts b/src/env.ts index 2040450b..8c397864 100644 --- a/src/env.ts +++ b/src/env.ts @@ -84,7 +84,7 @@ export interface Configuration { // When set, this plugin will not try to redirect the handlers of these specified functions; exclude: string[]; // When set, this plugin will configure the specified monitors for the function - monitors?: { [id: string]: { [key: string]: any } }[]; + monitors?: Record>[]; // When set, this plugin will fail a deployment if monitors can't be created failOnError: boolean; @@ -217,7 +217,7 @@ export const defaultConfiguration: Configuration = { export function setEnvConfiguration(config: Configuration, handlers: FunctionInfo[]): void { handlers.forEach(({ handler, type }) => { handler.environment ??= {}; - const environment = handler.environment as any; + const environment = handler.environment as Record; const functionName = handler.name ?? ""; if ( process.env.DATADOG_API_KEY !== undefined && @@ -397,10 +397,7 @@ function throwEnvVariableError(variable: string, value: string, functionName: st } export function getConfig(service: Service): Configuration { - let custom = service.custom as any; - if (custom === undefined) { - custom = {}; - } + const custom = service.custom ?? {}; let datadog = custom.datadog as Partial | undefined; if (datadog === undefined) { @@ -425,7 +422,7 @@ export function getConfig(service: Service): Configuration { } export function forceExcludeDepsFromWebpack(service: Service): void { - const includeModules = getPropertyFromPath(service, ["custom", "webpack", "includeModules"]); + const includeModules = getPropertyFromPath(service as unknown as Record, ["custom", "webpack", "includeModules"]); if (includeModules === undefined) { return; } @@ -442,23 +439,25 @@ export function forceExcludeDepsFromWebpack(service: Service): void { } } -function getPropertyFromPath(obj: any, path: string[]): any { +function getPropertyFromPath(obj: Record, path: string[]): Record | undefined { + let current: Record = obj; for (const part of path) { - let prop = obj[part]; + let prop = current[part]; if (prop === undefined || prop === true) { prop = {}; - obj[part] = prop; + current[part] = prop; } if (prop === false) { return; } - obj = prop; + current = prop as Record; } - return obj; + return current; } export function hasWebpackPlugin(service: Service): boolean { - const plugins: string[] | undefined = (service as any).plugins; + const serviceWithPlugins = service as unknown as { plugins?: string[] | { modules?: string[] } }; + const plugins = serviceWithPlugins.plugins; if (plugins === undefined) { return false; } @@ -467,7 +466,7 @@ export function hasWebpackPlugin(service: Service): boolean { return plugins.find((plugin) => plugin === webpackPluginName) !== undefined; } // We have an enhanced plugins object - const modules: string[] | undefined = (service as any).plugins.modules; + const modules: string[] | undefined = plugins.modules; if (modules === undefined) { return false; } diff --git a/src/forwarder.ts b/src/forwarder.ts index 20226fb4..e8ac2874 100644 --- a/src/forwarder.ts +++ b/src/forwarder.ts @@ -63,14 +63,49 @@ const REST_EXECUTION_SUBSCRIPTION_KEY = "RestExecutionLogGroupSubscription"; const WEBSOCKETS_EXECUTION_LOG_GROUP_KEY = "WebsocketsExecutionLogGroup"; const WEBSOCKETS_EXECUTION_SUBSCRIPTION_KEY = "WebsocketsExecutionLogGroupSubscription"; +/** A single CloudFormation resource */ +interface CfnResource { + Type: string; + Properties?: Record; + [key: string]: unknown; +} + +/** The Resources block of a compiled CloudFormation template */ +export type CfnResourceMap = Record; + +/** A compiled AWS::StepFunctions::StateMachine CFN resource */ +export interface StateMachineCfnResource { + Properties?: { + Tags?: Array<{ Key: string; Value: string }>; + [key: string]: unknown; + }; + [key: string]: unknown; +} + +/** Logging config for a step function (from serverless-step-functions plugin) */ +interface StepFunctionLoggingConfig { + level: string; + includeExecutionData: boolean; + destinations: unknown[]; +} + +/** A step function entry from stepFunctions.stateMachines */ +export interface StepFunctionConfig { + name: string; + loggingConfig?: StepFunctionLoggingConfig; +} + +/** A CloudFormation intrinsic function value */ +type CfnIntrinsicValue = Record; + // When users define ARN with CloudFormation functions, the ARN takes this type instead of a string. export interface CloudFormationObjectArn { "Fn::Sub"?: string; "arn:aws"?: string; } -function isLogGroup(value: any): value is LogGroupResource { - return value.Type === logGroupKey; +function isLogGroup(value: unknown): value is LogGroupResource { + return (value as LogGroupResource).Type === logGroupKey; } /** @@ -91,7 +126,7 @@ export async function addExecutionLogGroupsAndSubscriptions( aws: Aws, functionArn: CloudFormationObjectArn | string, ): Promise { - const extendedProvider = (service.provider as any)?.logs; + const extendedProvider = (service.provider as unknown as { logs?: LogsConfig })?.logs; if (!isLogsConfig(extendedProvider)) { return; @@ -119,7 +154,7 @@ export async function addExecutionLogGroupsAndSubscriptions( } } -export async function addStepFunctionLogGroup(aws: Aws, resources: any, stepFunction: any): Promise { +export async function addStepFunctionLogGroup(aws: Aws, resources: CfnResourceMap, stepFunction: StepFunctionConfig): Promise { const stepFunctionName = stepFunction.name; const logGroupName = `/aws/vendedlogs/states/${stepFunctionName}-Logs-${aws.getStage()}`; const logGroupResourceName = `${normalizeResourceName(stepFunctionName)}LogGroup`; @@ -142,14 +177,14 @@ export async function addStepFunctionLogGroup(aws: Aws, resources: any, stepFunc }; } -export function addDdSlsPluginTag(stateMachineObj: any): void { +export function addDdSlsPluginTag(stateMachineObj: StateMachineCfnResource): void { stateMachineObj.Properties?.Tags?.push({ Key: "dd_sls_plugin", Value: `v${version}`, }); } -export function addDdTraceEnabledTag(stateMachineObj: any, enableStepFunctionsTracing: undefined | boolean): void { +export function addDdTraceEnabledTag(stateMachineObj: StateMachineCfnResource, enableStepFunctionsTracing: undefined | boolean): void { if (!enableStepFunctionsTracing) { return; } @@ -160,8 +195,8 @@ export function addDdTraceEnabledTag(stateMachineObj: any, enableStepFunctionsTr } export async function addStepFunctionLogGroupSubscription( - resources: any, - stepFunction: any, + resources: CfnResourceMap, + stepFunction: StepFunctionConfig, functionArn: CloudFormationObjectArn | string, ): Promise { const logGroupSubscriptionResourceName = `${normalizeResourceName(stepFunction.name)}LogGroupSubscription`; @@ -176,7 +211,7 @@ export async function addStepFunctionLogGroupSubscription( "Fn::Select": [ 6, { - "Fn::Split": [":", stepFunction.loggingConfig.destinations[0]], + "Fn::Split": [":", stepFunction.loggingConfig!.destinations[0]], }, ], }, @@ -266,21 +301,21 @@ export async function describeSubscriptionFilters(aws: Aws, logGroupName: string } // Helper functions to validate we have a particular log group and if we should subscribe to it -function validateRestApiSubscription(resource: any, subscribe: boolean, extendedProvider: any): boolean { +function validateRestApiSubscription(resource: LogGroupResource, subscribe: boolean, extendedProvider: LogsConfig): boolean { return ( restAccessLoggingIsEnabled(extendedProvider) && resource.Properties.LogGroupName.startsWith("/aws/api-gateway/") && subscribe ); } -function validateHttpApiSubscription(resource: any, subscribe: boolean, extendedProvider: any): boolean { +function validateHttpApiSubscription(resource: LogGroupResource, subscribe: boolean, extendedProvider: LogsConfig): boolean { return ( httpAccessLoggingIsEnabled(extendedProvider) && resource.Properties.LogGroupName.startsWith("/aws/http-api/") && subscribe ); } -function validateWebsocketSubscription(resource: any, subscribe: boolean, extendedProvider: any): boolean { +function validateWebsocketSubscription(resource: LogGroupResource, subscribe: boolean, extendedProvider: LogsConfig): boolean { return ( websocketAccessLoggingIsEnabled(extendedProvider) && resource.Properties.LogGroupName.startsWith("/aws/websocket/") && @@ -290,12 +325,12 @@ function validateWebsocketSubscription(resource: any, subscribe: boolean, extend function shouldSubscribe( resourceName: string, - resource: any, + resource: unknown, forwarderConfigs: ForwarderConfigs, handlers: FunctionInfo[], service: Service, ): boolean { - const extendedProvider = (service.provider as any)?.logs; + const extendedProvider = (service.provider as unknown as { logs?: LogsConfig })?.logs; if (!isLogGroup(resource)) { return false; } @@ -374,7 +409,7 @@ async function createWebsocketExecutionLogGroupName(aws: Aws) { }; } -function addExecutionLogGroup(logGroupName: any) { +function addExecutionLogGroup(logGroupName: string | CfnIntrinsicValue) { // Create the Execution log group for API Gateway REST logging manually const executionLogGroup = { Type: "AWS::Logs::LogGroup", @@ -397,45 +432,47 @@ function subscribeToExecutionLogGroup(functionArn: string | CloudFormationObject return executionSubscription; } -export function isLogsConfig(obj: any): obj is LogsConfig { - if (typeof obj !== "object") { +export function isLogsConfig(obj: unknown): obj is LogsConfig { + if (typeof obj !== "object" || obj === null) { return false; } + const record = obj as Record; - if (obj.hasOwnProperty("restApi")) { - if (!isSubLogsConfig(obj.restApi)) { + if (record.hasOwnProperty("restApi")) { + if (!isSubLogsConfig(record.restApi)) { return false; } } - if (obj.hasOwnProperty("httpApi")) { - if (!isSubLogsConfig(obj.httpApi)) { + if (record.hasOwnProperty("httpApi")) { + if (!isSubLogsConfig(record.httpApi)) { return false; } } - if (obj.hasOwnProperty("websocket")) { - if (!isSubLogsConfig(obj.websocket)) { + if (record.hasOwnProperty("websocket")) { + if (!isSubLogsConfig(record.websocket)) { return false; } } return true; } -function isSubLogsConfig(obj: any): obj is SubLogsConfig { +function isSubLogsConfig(obj: unknown): obj is SubLogsConfig { if (typeof obj === "boolean") { return true; } - if (typeof obj !== "object") { + if (typeof obj !== "object" || obj === null) { return false; } - if (obj.hasOwnProperty("accessLogging")) { - if (typeof obj.accessLogging !== "boolean" && typeof obj.accessLogging !== undefined) { + const record = obj as Record; + if (record.hasOwnProperty("accessLogging")) { + if (typeof record.accessLogging !== "boolean" && typeof record.accessLogging !== undefined) { return false; } } - if (obj.hasOwnProperty("executionLogging")) { - if (typeof obj.executionLogging !== "boolean" && typeof obj.executionLogging !== undefined) { + if (record.hasOwnProperty("executionLogging")) { + if (typeof record.executionLogging !== "boolean" && typeof record.executionLogging !== undefined) { return false; } } diff --git a/src/index.ts b/src/index.ts index 119ead6d..4b335f4d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,6 +31,8 @@ import { addExecutionLogGroupsAndSubscriptions, addStepFunctionLogGroup, addStepFunctionLogGroupSubscription, + StateMachineCfnResource, + StepFunctionConfig, } from "./forwarder"; import { newSimpleGit } from "./git"; import { @@ -53,8 +55,8 @@ import { inspectAndRecommendStepFunctionsInstrumentation } from "./step-function // Separate interface since DefinitelyTyped currently doesn't include tags or env export interface ExtendedFunctionDefinition extends FunctionDefinition { - tags?: { [key: string]: string }; - environment?: { [key: string]: string }; + tags?: Record; + environment?: Record; } enum TagKeys { @@ -101,7 +103,7 @@ module.exports = class ServerlessPlugin { private options: Serverless.Options, ) {} - private displayedMessages: { [msg: string]: true } = {}; + private displayedMessages: Record = {}; private logToCliOnce(message: string): void { if (this.displayedMessages[message] === undefined) { this.displayedMessages[message] = true; @@ -120,7 +122,7 @@ module.exports = class ServerlessPlugin { const config = getConfig(this.serverless.service); if (config.enabled === false) return; this.serverless.cli.log("Auto instrumenting functions with Datadog"); - configHasOldProperties(config); + configHasOldProperties(config as unknown as Record); if (config.monitorsApiKey !== undefined || config.monitorsAppKey !== undefined) { this.serverless.cli.log( "Warning: `monitorsApiKey` and `monitorsAppKey` have been deprecated. Please set DATADOG_API_KEY and DATADOG_APP_KEY in your environment instead.", @@ -305,18 +307,21 @@ This is expected if you only deploy part of the stack.`); if (config.enableStepFunctionsTracing || config.subscribeToStepFunctionLogs) { const resources = compiledCfnTemplate.Resources; - const stepFunctions = Object.values((this.serverless.service as any).stepFunctions.stateMachines); + const serviceWithSF = this.serverless.service as unknown as { + stepFunctions: { stateMachines: Record }; + }; + const stepFunctions = Object.values(serviceWithSF.stepFunctions.stateMachines); if (stepFunctions.length === 0) { this.serverless.cli.log("subscribeToStepFunctionLogs is set to true but no step functions were found."); } else { this.serverless.cli.log("Subscribing step function log groups to Datadog Forwarder"); - for (const stepFunction of stepFunctions as any[]) { + for (const stepFunction of stepFunctions) { if (!stepFunction.hasOwnProperty("loggingConfig")) { this.serverless.cli.log(`Creating log group for ${stepFunction.name} and logging to it with level ALL.`); await addStepFunctionLogGroup(aws, resources, stepFunction); } else { this.serverless.cli.log(`Found logging config for step function ${stepFunction.name}`); - const loggingConfig = stepFunction.loggingConfig; + const loggingConfig = stepFunction.loggingConfig!; if (loggingConfig.level !== "ALL") { loggingConfig.level = "ALL"; @@ -361,9 +366,9 @@ This is expected if you only deploy part of the stack.`); private async afterDeploy(): Promise { const config = getConfig(this.serverless.service); - const custom = (this.serverless.service.custom ?? {}) as any; - const service = custom.datadog?.service ?? this.serverless.service.getServiceName(); - const env = custom.datadog?.env ?? this.serverless.getProvider("aws").getStage(); + const custom = this.serverless.service.custom ?? {}; + const service = (custom as Record>).datadog?.service ?? this.serverless.service.getServiceName(); + const env = (custom as Record>).datadog?.env ?? this.serverless.getProvider("aws").getStage(); if (config.enabled === false) return; if ( @@ -420,16 +425,13 @@ This is expected if you only deploy part of the stack.`); const provider = this.serverless.service.provider as Provider; const service = this.serverless.service as Service; - let custom = service.custom as any; - if (custom === undefined) { - custom = {}; - } + const custom = service.custom ?? {}; handlers.forEach(({ handler }) => { handler.environment ??= {}; - const environment = handler.environment as any; + const environment = handler.environment as Record; provider.environment ??= {}; - const providerEnvironment = provider.environment as any; + const providerEnvironment = provider.environment as Record; if (custom?.datadog?.service) { environment[ddServiceEnvVar] ??= providerEnvironment[ddServiceEnvVar] ?? custom.datadog.service; @@ -460,14 +462,11 @@ This is expected if you only deploy part of the stack.`); private addDDTagsForLambda(handlers: FunctionInfo[]): void { const service = this.serverless.service as Service; - let custom = service.custom as any; - if (custom === undefined) { - custom = {}; - } + const custom = service.custom ?? {}; handlers.forEach(({ handler }) => { handler.tags ??= {}; - const tags = handler.tags as any; + const tags = handler.tags as Record; if (custom?.datadog?.service) { tags[TagKeys.Service] ??= custom.datadog.service; @@ -497,7 +496,7 @@ This is expected if you only deploy part of the stack.`); * Check for service, env, version, and additional tags at the custom level. * If these don't already exist on the state machine level, add them. */ - private addTagsForStateMachine(stateMachine: any): void { + private addTagsForStateMachine(stateMachine: StateMachineCfnResource): void { const service = this.serverless.service as Service; const datadog = service.custom?.datadog; @@ -506,7 +505,7 @@ This is expected if you only deploy part of the stack.`); } stateMachine.Properties ??= {}; - stateMachine.Properties.Tags ??= {}; + stateMachine.Properties.Tags ??= []; const tags = stateMachine.Properties.Tags; if (datadog.service && !tags.hasOwnProperty(TagKeys.Service)) { @@ -591,7 +590,7 @@ This is expected if you only deploy part of the stack.`); } }; -function configHasOldProperties(obj: any): void { +function configHasOldProperties(obj: Record): void { let hasOldProperties = false; let message = "The following configuration options have been removed:"; diff --git a/src/layer.ts b/src/layer.ts index 5c458886..120e380b 100644 --- a/src/layer.ts +++ b/src/layer.ts @@ -42,6 +42,7 @@ const US_GOV_REGION_PREFIX = "us-gov-"; // Separate interface since DefinitelyTyped currently doesn't include tags or env export interface ExtendedFunctionDefinition extends FunctionDefinition { architecture?: string; + layers?: string[]; } export interface LayerJSON { @@ -54,7 +55,7 @@ export interface LayerJSON { }; } -export const runtimeLookup: { [key: string]: RuntimeType } = { +export const runtimeLookup: Record = { "nodejs16.x": RuntimeType.NODE, "nodejs18.x": RuntimeType.NODE, "nodejs20.x": RuntimeType.NODE, @@ -87,7 +88,7 @@ export const runtimeLookup: { [key: string]: RuntimeType } = { }; // Map from x86 runtime keys in layers.json to the corresponding ARM runtime keys -export const ARM_RUNTIME_KEYS: { [key: string]: string } = { +export const ARM_RUNTIME_KEYS: Record = { "python3.8": "python3.8-arm", "python3.9": "python3.9-arm", "python3.10": "python3.10-arm", @@ -199,7 +200,7 @@ export function applyLambdaLibraryLayers( } const architecture = - handler.handler?.architecture ?? (service.provider as any).architecture ?? DEFAULT_ARCHITECTURE; + handler.handler?.architecture ?? (service.provider as unknown as { architecture?: string }).architecture ?? DEFAULT_ARCHITECTURE; const isArm64 = architecture === ARM64_ARCHITECTURE; // Use the ARM layer if customer's handler is using ARM @@ -249,7 +250,7 @@ export function applyExtensionLayer( continue; } const architecture = - (handler.handler as any).architecture ?? (service.provider as any).architecture ?? DEFAULT_ARCHITECTURE; + handler.handler.architecture ?? (service.provider as unknown as { architecture?: string }).architecture ?? DEFAULT_ARCHITECTURE; let extensionLayerKey: string = "extension"; if (architecture === ARM64_ARCHITECTURE) { @@ -283,7 +284,7 @@ export function pushLayerARN(layerARN: string, currentLayers: string[]): string[ } export function isFunctionDefinitionHandler(funcDef: FunctionDefinition): funcDef is FunctionDefinitionHandler { - return typeof (funcDef as any).handler === "string"; + return typeof (funcDef as unknown as Record).handler === "string"; } /** @@ -299,8 +300,8 @@ function addLayer(service: Service, handler: FunctionInfo, layerArn: string): vo } function getLayers(service: Service, handler: FunctionInfo): string[] { - const functionLayersList = ((handler.handler as any).layers as string[] | string[]) || []; - const serviceLayersList = ((service.provider as any).layers as string[] | string[]) || []; + const functionLayersList = handler.handler.layers ?? []; + const serviceLayersList = (service.provider as unknown as { layers?: string[] }).layers ?? []; // Function-level layers override service-level layers // Append to the function-level layers if other function-level layers are present // If service-level layers are present @@ -322,7 +323,7 @@ function removePreviousLayer(service: Service, handler: FunctionInfo, previousLa } function setLayers(handler: FunctionInfo, layers: string[]): void { - (handler.handler as any).layers = layers; + handler.handler.layers = layers; } function buildLocalLambdaLayerARN(layerARN: string | undefined, accountId: string, region: string): string | undefined { diff --git a/src/monitor-api-requests.ts b/src/monitor-api-requests.ts index 592bc6a7..4c7374b2 100644 --- a/src/monitor-api-requests.ts +++ b/src/monitor-api-requests.ts @@ -29,7 +29,7 @@ export interface RecommendedMonitorParams { description: string; type: string; options: { - thresholds: { [key: string]: any }; + thresholds: Record; }; name: string; template_variables?: TemplateVariable[]; @@ -117,7 +117,7 @@ export async function searchMonitors( throw new Error(`Can't fetch monitors. Status code: ${response.status}. Message: ${response.statusText}`); } - const json: any = await response.json(); // TODO: use proper type/parsing + const json = (await response.json()) as { monitors: QueriedMonitor[]; metadata: { page_count: number } }; monitors = monitors.concat(json.monitors); pageCount = json.metadata.page_count; page += 1; @@ -148,14 +148,14 @@ export async function getExistingMonitors( cloudFormationStackId: string, monitorsApiKey: string, monitorsAppKey: string, -): Promise<{ [key: string]: number }> { +): Promise> { const existingMonitors = await searchMonitors( site, `aws_cloudformation_stack-id:${cloudFormationStackId}`, monitorsApiKey, monitorsAppKey, ); - const serverlessMonitorIdByMonitorId: { [key: string]: number } = {}; + const serverlessMonitorIdByMonitorId: Record = {}; for (const existingMonitor of existingMonitors) { for (const tag of existingMonitor.tags) { if (tag.startsWith("serverless_monitor_id:") || tag.startsWith("serverless_id:")) { @@ -171,10 +171,8 @@ export async function getRecommendedMonitors( site: string, monitorsApiKey: string, monitorsAppKey: string, -): Promise<{ - [key: string]: ServerlessMonitor; -}> { - const recommendedMonitors: { [key: string]: ServerlessMonitor } = {}; +): Promise> { + const recommendedMonitors: Record = {}; // Setting a count of 50 in the hope that all can be fetched at once. The default is 10 per page. const endpoint = `https://api.${site}/api/v2/monitor/recommended?count=50&start=0&search=tag%3A%22product%3Aserverless%22%20AND%20tag%3A%22integration%3Aamazon-lambda%22`; const response = await fetch(endpoint, { @@ -189,7 +187,7 @@ export async function getRecommendedMonitors( throw new Error(`Can't fetch monitor params. Status code: ${response.status}. Message: ${response.statusText}`); } - const json: any = await response.json(); // TODO: use proper type/parsing + const json = (await response.json()) as { data: RecommendedMonitorParams[] }; const recommendedMonitorsData = json.data; recommendedMonitorsData.forEach((recommendedMonitorParam: RecommendedMonitorParams) => { const recommendedMonitorId = parseRecommendedMonitorServerlessId(recommendedMonitorParam); diff --git a/src/monitors.ts b/src/monitors.ts index 0bb83265..efe8064f 100644 --- a/src/monitors.ts +++ b/src/monitors.ts @@ -8,11 +8,21 @@ import { } from "./monitor-api-requests"; export interface MonitorParams { - [key: string]: any; -} -export interface Monitor { - [key: string]: MonitorParams; + name?: string; + type?: string; + query?: string; + message?: string; + tags?: string[]; + options?: { + thresholds?: { + critical?: number; + [key: string]: unknown; + }; + [key: string]: unknown; + }; + [key: string]: unknown; } +export type Monitor = Record; export interface ServerlessMonitor { name: string; @@ -23,9 +33,7 @@ export interface ServerlessMonitor { templateVariables?: TemplateVariable[]; } -export interface RecommendedMonitors { - [key: string]: ServerlessMonitor; -} +export type RecommendedMonitors = Record; /** * Adds the appropriate tags and required parameters that will be passed as part of the request body for creating and updating monitors * @param monitor - the Monitor object that is defined in the serverless.yml file @@ -41,7 +49,7 @@ export function buildMonitorParams( service: string, env: string, recommendedMonitors: RecommendedMonitors, -): { [x: string]: any } { +): MonitorParams { const serverlessMonitorId = Object.keys(monitor)[0]; if (!monitor[serverlessMonitorId]) { @@ -107,7 +115,7 @@ function isRecommendedMonitor(serverlessMonitorId: string, recommendedMonitors: * @param existingMonitors - Monitors that have already been created * @returns true if given monitor already exists */ -function doesMonitorExist(serverlessMonitorId: string, existingMonitors: { [key: string]: number }): boolean { +function doesMonitorExist(serverlessMonitorId: string, existingMonitors: Record): boolean { return Object.keys(existingMonitors).includes(serverlessMonitorId); } @@ -123,7 +131,7 @@ function doesMonitorExist(serverlessMonitorId: string, existingMonitors: { [key: async function deleteRemovedMonitors( site: string, pluginMonitors: Monitor[], - existingMonitors: { [key: string]: number }, + existingMonitors: Record, monitorsApiKey: string, monitorsAppKey: string, ): Promise { diff --git a/src/span-link.ts b/src/span-link.ts index 4ca994c4..46bc0a91 100644 --- a/src/span-link.ts +++ b/src/span-link.ts @@ -2,7 +2,7 @@ import { GeneralResource, updateDefinitionString } from "./step-functions-helper import * as Serverless from "serverless"; export function mergeStepFunctionAndLambdaTraces( - resources: { [key: string]: GeneralResource }, + resources: Record, serverless: Serverless, ): void { for (const [resourceName, resourceObj] of Object.entries(resources)) { diff --git a/src/step-functions-helper.ts b/src/step-functions-helper.ts index 5dafeb16..a4846dc5 100644 --- a/src/step-functions-helper.ts +++ b/src/step-functions-helper.ts @@ -6,22 +6,22 @@ export interface GeneralResource { DefinitionString?: | string | { - "Fn::Sub": any[]; + "Fn::Sub": (string | object)[]; }; }; } export interface StateMachineDefinition { - States: { [key: string]: StateMachineStep }; + States: Record; } export type PayloadObject = { - "Execution.$"?: any; - Execution?: any; - "State.$"?: any; - State?: any; - "StateMachine.$"?: any; - StateMachine?: any; + "Execution.$"?: string; + Execution?: unknown; + "State.$"?: string; + State?: unknown; + "StateMachine.$"?: string; + StateMachine?: unknown; }; export type StepFunctionInput = { @@ -323,7 +323,10 @@ manually merge these traces, check out https://docs.datadoghq.com/serverless/ste } export function inspectAndRecommendStepFunctionsInstrumentation(serverless: Serverless): void { - const stepFunctions = Object.values((serverless.service as any).stepFunctions?.stateMachines || {}); + const stepFunctions = Object.values( + (serverless.service as unknown as { stepFunctions?: { stateMachines?: Record } }).stepFunctions + ?.stateMachines ?? {}, + ); if (stepFunctions.length !== 0) { serverless.cli.log( `Uninstrumented Step Functions detected in your serverless.yml file. If you would like to see Step Functions traces, please see details of 'enableStepFunctionsTracing' and 'mergeStepFunctionAndLambdaTraces' variables in the README (https://github.com/DataDog/serverless-plugin-datadog/)`, diff --git a/src/tracing.ts b/src/tracing.ts index 95a77444..99a91f9a 100644 --- a/src/tracing.ts +++ b/src/tracing.ts @@ -25,14 +25,14 @@ export function enableTracing(service: Service, tracingMode: TracingMode, handle if (tracingMode === TracingMode.XRAY || tracingMode === TracingMode.HYBRID) { provider.tracing = { apiGateway: provider.apiGateway?.restApiId - ? (undefined as any) // Current type definition does not allow undefined however it is a valid option. + ? (undefined as unknown as boolean) // Current type definition does not allow undefined however it is a valid option. : true, lambda: true, }; } handlers.forEach(({ handler }) => { handler.environment ??= {}; - const environment = handler.environment as any; + const environment = handler.environment as Record; // if tracing is not enabled, merge x-ray cannot be enabled if (environment[ddTraceEnabledEnvVar] === false || environment[ddTraceEnabledEnvVar] === "false") { environment[ddMergeXrayTracesEnvVar] = false; From 5d5ef70ed7f8449d9ffc7288f62183ce72b68035 Mon Sep 17 00:00:00 2001 From: Ava Silver Date: Wed, 18 Mar 2026 14:38:48 +0100 Subject: [PATCH 2/3] [SVLS-5187] format --- src/env.ts | 6 +++++- src/forwarder.ts | 29 ++++++++++++++++++++++++----- src/index.ts | 6 ++++-- src/layer.ts | 8 ++++++-- 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/env.ts b/src/env.ts index 8c397864..da62133b 100644 --- a/src/env.ts +++ b/src/env.ts @@ -422,7 +422,11 @@ export function getConfig(service: Service): Configuration { } export function forceExcludeDepsFromWebpack(service: Service): void { - const includeModules = getPropertyFromPath(service as unknown as Record, ["custom", "webpack", "includeModules"]); + const includeModules = getPropertyFromPath(service as unknown as Record, [ + "custom", + "webpack", + "includeModules", + ]); if (includeModules === undefined) { return; } diff --git a/src/forwarder.ts b/src/forwarder.ts index e8ac2874..b9a6500c 100644 --- a/src/forwarder.ts +++ b/src/forwarder.ts @@ -154,7 +154,11 @@ export async function addExecutionLogGroupsAndSubscriptions( } } -export async function addStepFunctionLogGroup(aws: Aws, resources: CfnResourceMap, stepFunction: StepFunctionConfig): Promise { +export async function addStepFunctionLogGroup( + aws: Aws, + resources: CfnResourceMap, + stepFunction: StepFunctionConfig, +): Promise { const stepFunctionName = stepFunction.name; const logGroupName = `/aws/vendedlogs/states/${stepFunctionName}-Logs-${aws.getStage()}`; const logGroupResourceName = `${normalizeResourceName(stepFunctionName)}LogGroup`; @@ -184,7 +188,10 @@ export function addDdSlsPluginTag(stateMachineObj: StateMachineCfnResource): voi }); } -export function addDdTraceEnabledTag(stateMachineObj: StateMachineCfnResource, enableStepFunctionsTracing: undefined | boolean): void { +export function addDdTraceEnabledTag( + stateMachineObj: StateMachineCfnResource, + enableStepFunctionsTracing: undefined | boolean, +): void { if (!enableStepFunctionsTracing) { return; } @@ -301,21 +308,33 @@ export async function describeSubscriptionFilters(aws: Aws, logGroupName: string } // Helper functions to validate we have a particular log group and if we should subscribe to it -function validateRestApiSubscription(resource: LogGroupResource, subscribe: boolean, extendedProvider: LogsConfig): boolean { +function validateRestApiSubscription( + resource: LogGroupResource, + subscribe: boolean, + extendedProvider: LogsConfig, +): boolean { return ( restAccessLoggingIsEnabled(extendedProvider) && resource.Properties.LogGroupName.startsWith("/aws/api-gateway/") && subscribe ); } -function validateHttpApiSubscription(resource: LogGroupResource, subscribe: boolean, extendedProvider: LogsConfig): boolean { +function validateHttpApiSubscription( + resource: LogGroupResource, + subscribe: boolean, + extendedProvider: LogsConfig, +): boolean { return ( httpAccessLoggingIsEnabled(extendedProvider) && resource.Properties.LogGroupName.startsWith("/aws/http-api/") && subscribe ); } -function validateWebsocketSubscription(resource: LogGroupResource, subscribe: boolean, extendedProvider: LogsConfig): boolean { +function validateWebsocketSubscription( + resource: LogGroupResource, + subscribe: boolean, + extendedProvider: LogsConfig, +): boolean { return ( websocketAccessLoggingIsEnabled(extendedProvider) && resource.Properties.LogGroupName.startsWith("/aws/websocket/") && diff --git a/src/index.ts b/src/index.ts index 4b335f4d..5d2a067f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -367,8 +367,10 @@ This is expected if you only deploy part of the stack.`); private async afterDeploy(): Promise { const config = getConfig(this.serverless.service); const custom = this.serverless.service.custom ?? {}; - const service = (custom as Record>).datadog?.service ?? this.serverless.service.getServiceName(); - const env = (custom as Record>).datadog?.env ?? this.serverless.getProvider("aws").getStage(); + const service = + (custom as Record>).datadog?.service ?? this.serverless.service.getServiceName(); + const env = + (custom as Record>).datadog?.env ?? this.serverless.getProvider("aws").getStage(); if (config.enabled === false) return; if ( diff --git a/src/layer.ts b/src/layer.ts index 120e380b..99813c9e 100644 --- a/src/layer.ts +++ b/src/layer.ts @@ -200,7 +200,9 @@ export function applyLambdaLibraryLayers( } const architecture = - handler.handler?.architecture ?? (service.provider as unknown as { architecture?: string }).architecture ?? DEFAULT_ARCHITECTURE; + handler.handler?.architecture ?? + (service.provider as unknown as { architecture?: string }).architecture ?? + DEFAULT_ARCHITECTURE; const isArm64 = architecture === ARM64_ARCHITECTURE; // Use the ARM layer if customer's handler is using ARM @@ -250,7 +252,9 @@ export function applyExtensionLayer( continue; } const architecture = - handler.handler.architecture ?? (service.provider as unknown as { architecture?: string }).architecture ?? DEFAULT_ARCHITECTURE; + handler.handler.architecture ?? + (service.provider as unknown as { architecture?: string }).architecture ?? + DEFAULT_ARCHITECTURE; let extensionLayerKey: string = "extension"; if (architecture === ARM64_ARCHITECTURE) { From 10487201b78e830ad91a13a011694aee73c32b79 Mon Sep 17 00:00:00 2001 From: Ava Silver Date: Wed, 18 Mar 2026 15:58:57 +0100 Subject: [PATCH 3/3] [SVLS-5187] lint --- src/forwarder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/forwarder.ts b/src/forwarder.ts index b9a6500c..7c702677 100644 --- a/src/forwarder.ts +++ b/src/forwarder.ts @@ -76,7 +76,7 @@ export type CfnResourceMap = Record; /** A compiled AWS::StepFunctions::StateMachine CFN resource */ export interface StateMachineCfnResource { Properties?: { - Tags?: Array<{ Key: string; Value: string }>; + Tags?: { Key: string; Value: string }[]; [key: string]: unknown; }; [key: string]: unknown;