diff --git a/src/lambda/LambdaFunction.js b/src/lambda/LambdaFunction.js index 4d3158044..88f46192d 100644 --- a/src/lambda/LambdaFunction.js +++ b/src/lambda/LambdaFunction.js @@ -7,6 +7,7 @@ import { setTimeout } from "node:timers/promises" import { emptyDir, ensureDir, remove } from "fs-extra" import jszip from "jszip" import { log } from "../utils/log.js" +import renderIntrinsicFunction from "../utils/renderIntrinsicFunction.js" import HandlerRunner from "./handler-runner/index.js" import LambdaContext from "./LambdaContext.js" import { @@ -114,8 +115,8 @@ export default class LambdaFunction { entries(process.env).filter(([key]) => key.startsWith("AWS_")), )), ...this.#getAwsEnvVars(), - ...provider.environment, - ...functionDefinition.environment, + ...renderIntrinsicFunction(provider.environment), + ...renderIntrinsicFunction(functionDefinition.environment), IS_OFFLINE: "true", } diff --git a/src/utils/__tests__/renderIntrinsicFunction.test.js b/src/utils/__tests__/renderIntrinsicFunction.test.js new file mode 100644 index 000000000..e5a7f5964 --- /dev/null +++ b/src/utils/__tests__/renderIntrinsicFunction.test.js @@ -0,0 +1,80 @@ +import assert from "node:assert" +import renderIntrinsicFunction from "../renderIntrinsicFunction.js" + +describe("renderIntrinsicFunction", () => { + it("should process Fn::Join correctly", () => { + const input = { "Fn::Join": [":", ["a", "b", "c"]] } + const result = renderIntrinsicFunction(input) + assert.strictEqual(result, "a:b:c") + }) + + it("should process !Join correctly", () => { + const input = { "!Join": [":", ["a", "b", "c"]] } + const result = renderIntrinsicFunction(input) + assert.strictEqual(result, "a:b:c") + }) + + it("should process Fn::Sub correctly", () => { + const input = { + "Fn::Sub": ["The name is ${name}", { name: "CloudFormation" }], // eslint-disable-line no-template-curly-in-string + } + const result = renderIntrinsicFunction(input) + assert.strictEqual(result, "The name is CloudFormation") + }) + + it("should process !Sub correctly", () => { + const input = { "!Sub": ["Hello ${name}", { name: "World" }] } // eslint-disable-line no-template-curly-in-string + const result = renderIntrinsicFunction(input) + assert.strictEqual(result, "Hello World") + }) + + it("should process nested Join correctly", () => { + const input = { + "Fn::Join": ["-", [{ "!Join": [":", ["a", "b", "c"]] }, "d"]], + } + const result = renderIntrinsicFunction(input) + assert.strictEqual(result, "a:b:c-d") + }) + + it("should process plain strings without modification", () => { + const input = "This is a plain string" + const result = renderIntrinsicFunction(input) + assert.strictEqual(result, "This is a plain string") + }) + + it("should process numbers without modification", () => { + const input = 42 + const result = renderIntrinsicFunction(input) + assert.strictEqual(result, 42) + }) + + it("should process arrays without intrinsic functions", () => { + const input = ["a", "b", "c"] + const result = renderIntrinsicFunction(input) + assert.deepStrictEqual(result, ["a", "b", "c"]) + }) + + it("should process nested intrinsic functions in arrays", () => { + const input = ["a", { "Fn::Join": [":", ["b", "c"]] }, "d"] + const result = renderIntrinsicFunction(input) + assert.deepStrictEqual(result, ["a", "b:c", "d"]) + }) + + it("should throw an error for unsupported intrinsic functions", () => { + const input = { "Fn::UnsupportedFunction": "Value" } + const result = renderIntrinsicFunction(input) + assert.deepStrictEqual(result, { "Fn::UnsupportedFunction": "Value" }) + }) + + it("should throw an error for unsupported shorthand intrinsic functions", () => { + const input = { "!UnsupportedFunction": "Value" } + const result = renderIntrinsicFunction(input) + assert.deepStrictEqual(result, { "!UnsupportedFunction": "Value" }) + }) + + it("should process nested arrays with intrinsic functions", () => { + const input = ["a", ["b", { "!Join": [":", ["c", "d"]] }]] + const result = renderIntrinsicFunction(input) + assert.deepStrictEqual(result, ["a", ["b", "c:d"]]) + }) +}) diff --git a/src/utils/renderIntrinsicFunction.js b/src/utils/renderIntrinsicFunction.js new file mode 100644 index 000000000..d88b30f5b --- /dev/null +++ b/src/utils/renderIntrinsicFunction.js @@ -0,0 +1,34 @@ +export default function renderIntrinsicFunction(input) { + if (input === null || input === undefined) { + return input + } + + if (typeof input === "string") { + return input + } + + if (Array.isArray(input)) { + return input.map(renderIntrinsicFunction) + } + + if (typeof input === "object") { + const result = {} + for (const [key, value] of Object.entries(input)) { + if (key === "Fn::Join" || key === "!Join") { + const [delimiter, list] = value + return list.map(renderIntrinsicFunction).join(delimiter) + } + if (key === "Fn::Sub" || key === "!Sub") { + const [template, variables] = value + result[key] = template.replaceAll(/\${(.*?)}/g, (match, variable) => { + return variable in variables ? variables[variable] : match + }) + return result[key] + } + result[key] = renderIntrinsicFunction(value) + } + return result + } + + return input +}