From f51f761c2d7f4813ee80ef36c21623f75e708cea Mon Sep 17 00:00:00 2001 From: Corie Watson Date: Thu, 24 Jul 2025 12:02:00 +0100 Subject: [PATCH 1/4] fix(deploy/functions): parsing list param --- src/deploy/functions/params.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/deploy/functions/params.ts b/src/deploy/functions/params.ts index 106617fa91c..4946b63592f 100644 --- a/src/deploy/functions/params.ts +++ b/src/deploy/functions/params.ts @@ -291,6 +291,14 @@ export class ParamValue { } asList(): string[] { + // Handle something like "['a', 'b', 'c']" + if (this.rawValue.includes("[")) { + // Convert quotes to apostrophes + const unquoted = this.rawValue.replace(/'/g, '"'); + return JSON.parse(unquoted); + } + + // Continue to handle something like "a,b,c" return this.rawValue.split(this.delimiter); } From 87ae2c919365ed3fda12d9dc5e2d26b8a9a6ba90 Mon Sep 17 00:00:00 2001 From: Corie Watson Date: Thu, 24 Jul 2025 21:51:34 +0100 Subject: [PATCH 2/4] fix(deploy/functions): improve list param parsing robustness --- src/deploy/functions/params.spec.ts | 135 ++++++++++++++++++++++++++++ src/deploy/functions/params.ts | 20 +++-- 2 files changed, 149 insertions(+), 6 deletions(-) diff --git a/src/deploy/functions/params.spec.ts b/src/deploy/functions/params.spec.ts index ca7ee49b792..21d20acfca6 100644 --- a/src/deploy/functions/params.spec.ts +++ b/src/deploy/functions/params.spec.ts @@ -299,3 +299,138 @@ describe("resolveParams", () => { await expect(params.resolveParams(paramsToResolve, fakeConfig, {})).to.eventually.be.rejected; }); }); + +describe("ParamValue.asList()", () => { + describe("basic usage", () => { + it("should parse JSON-style array with double quotes", () => { + const paramValue = new params.ParamValue('["1", "2", "3"]', false, { list: true }); + expect(paramValue.asList()).to.deep.equal(["1", "2", "3"]); + }); + + it("should split comma-separated values", () => { + const paramValue = new params.ParamValue("a,b,c", false, { list: true }); + expect(paramValue.asList()).to.deep.equal(["a", "b", "c"]); + }); + + it("should handle empty array", () => { + const paramValue = new params.ParamValue("[]", false, { list: true }); + expect(paramValue.asList()).to.deep.equal([]); + }); + + it("should handle a single value", () => { + const paramValue = new params.ParamValue("foo", false, { list: true }); + expect(paramValue.asList()).to.deep.equal(["foo"]); + }); + + it("should handle whitespace around values", () => { + const paramValue = new params.ParamValue(" a , b , c ", false, { list: true }); + expect(paramValue.asList()).to.deep.equal([" a ", " b ", " c "]); + }); + }); + + describe("delimiter handling", () => { + it("should use custom delimiter", () => { + const paramValue = new params.ParamValue("a;b;c", false, { list: true }); + paramValue.setDelimiter(";"); + expect(paramValue.asList()).to.deep.equal(["a", "b", "c"]); + }); + }); + + describe("robustness", () => { + it("should handle values with commas inside JSON array", () => { + const paramValue = new params.ParamValue('["hello, world", "foo, bar"]', false, { list: true }); + expect(paramValue.asList()).to.deep.equal(["hello, world", "foo, bar"]); + }); + + it("should fall back to delimiter splitting for malformed JSON", () => { + const paramValue = new params.ParamValue("[a, b, c]", false, { list: true }); + expect(paramValue.asList()).to.deep.equal(["a", " b", " c"]); + }); + + it("should handle special characters", () => { + const paramValue = new params.ParamValue('["@#$%", "^&*()"]', false, { list: true }); + expect(paramValue.asList()).to.deep.equal(["@#$%", "^&*()"]); + }); + + it("should work with fromList static method", () => { + const originalList = ["apple", "banana", "cherry"]; + const paramValue = params.ParamValue.fromList(originalList); + expect(paramValue.asList()).to.deep.equal(originalList); + }); + + it("should handle square brackets in the middle of the string", () => { + const paramValue = new params.ParamValue('["]", "[", "]"]', false, { list: true }); + expect(paramValue.asList()).to.deep.equal(["]", "[", "]"]); + }); + + it("should handle quotes, apostrophes, and backticks in the middle of the string", () => { + const paramValue = new params.ParamValue('["a\\"b", "c`d", "e\'f"]', false, { list: true }); + expect(paramValue.asList()).to.deep.equal(["a\"b", "c`d", "e'f"]); + }); + + it("should handle empty string", () => { + const paramValue = new params.ParamValue("", false, { list: true }); + expect(paramValue.asList()).to.deep.equal([""]); + }); + + it("should handle whitespace-only string", () => { + const paramValue = new params.ParamValue(" ", false, { list: true }); + expect(paramValue.asList()).to.deep.equal([" "]); + }); + + it("should handle JSON array with numbers", () => { + const paramValue = new params.ParamValue("[1, 2, 3]", false, { list: true }); + expect(paramValue.asList()).to.deep.equal([1, 2, 3]); + }); + + it("should handle JSON array with booleans", () => { + const paramValue = new params.ParamValue("[true, false, true]", false, { list: true }); + expect(paramValue.asList()).to.deep.equal([true, false, true]); + }); + + it("should handle JSON array with nulls", () => { + const paramValue = new params.ParamValue('["a", null, "b"]', false, { list: true }); + expect(paramValue.asList()).to.deep.equal(["a", null, "b"]); + }); + + it("should handle trailing delimiter", () => { + const paramValue = new params.ParamValue("a,b,c,", false, { list: true }); + expect(paramValue.asList()).to.deep.equal(["a", "b", "c", ""]); + }); + + it("should handle leading delimiter", () => { + const paramValue = new params.ParamValue(",a,b,c", false, { list: true }); + expect(paramValue.asList()).to.deep.equal(["", "a", "b", "c"]); + }); + + it("should handle consecutive delimiters", () => { + const paramValue = new params.ParamValue("a,,b", false, { list: true }); + expect(paramValue.asList()).to.deep.equal(["a", "", "b"]); + }); + + it("should handle only delimiters", () => { + const paramValue = new params.ParamValue(",,,", false, { list: true }); + expect(paramValue.asList()).to.deep.equal(["", "", "", ""]); + }); + + it("should handle JSON array with empty strings", () => { + const paramValue = new params.ParamValue('["", "a", ""]', false, { list: true }); + expect(paramValue.asList()).to.deep.equal(["", "a", ""]); + }); + + it("should handle JSON array with whitespace strings", () => { + const paramValue = new params.ParamValue('[" ", "a", " "]', false, { list: true }); + expect(paramValue.asList()).to.deep.equal([" ", "a", " "]); + }); + + it("should handle JSON array with nested arrays", () => { + const paramValue = new params.ParamValue('[["a", "b"], ["c"]]', false, { list: true }); + expect(paramValue.asList()).to.deep.equal([["a", "b"], ["c"]]); + }); + + it("should handle JSON array with objects", () => { + const paramValue = new params.ParamValue('[{"foo":1}, {"bar":2}]', false, { list: true }); + expect(paramValue.asList()).to.deep.equal([{ foo: 1 }, { bar: 2 }]); + }); + }); +}); diff --git a/src/deploy/functions/params.ts b/src/deploy/functions/params.ts index 4946b63592f..239e6ad1ecd 100644 --- a/src/deploy/functions/params.ts +++ b/src/deploy/functions/params.ts @@ -291,15 +291,23 @@ export class ParamValue { } asList(): string[] { - // Handle something like "['a', 'b', 'c']" - if (this.rawValue.includes("[")) { - // Convert quotes to apostrophes - const unquoted = this.rawValue.replace(/'/g, '"'); - return JSON.parse(unquoted); + let modifiedValue = this.rawValue; + + // Handle something like "["a", "b", "c"]" + if (modifiedValue.startsWith("[") && modifiedValue.endsWith("]")) { + try { + return JSON.parse(modifiedValue); + } catch (e) { + // Remove the brackets + // Handles scenarios like "[a, b, c]" + modifiedValue = modifiedValue.replace(/[\[\]]/g, ""); + + // Not a valid JSON array, fall through to splitting by delimiter. + } } // Continue to handle something like "a,b,c" - return this.rawValue.split(this.delimiter); + return modifiedValue.split(this.delimiter); } asNumber(): number { From 91e6fb952d09f2ff5290216f806ef8fb436b0578 Mon Sep 17 00:00:00 2001 From: Corie Watson Date: Thu, 24 Jul 2025 22:46:58 +0100 Subject: [PATCH 3/4] fix(deploy/functions): make more robust --- src/deploy/functions/params.spec.ts | 10 +++++----- src/deploy/functions/params.ts | 26 ++++++++++++++++++++------ 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/deploy/functions/params.spec.ts b/src/deploy/functions/params.spec.ts index 21d20acfca6..6e3f734dda2 100644 --- a/src/deploy/functions/params.spec.ts +++ b/src/deploy/functions/params.spec.ts @@ -380,17 +380,17 @@ describe("ParamValue.asList()", () => { it("should handle JSON array with numbers", () => { const paramValue = new params.ParamValue("[1, 2, 3]", false, { list: true }); - expect(paramValue.asList()).to.deep.equal([1, 2, 3]); + expect(paramValue.asList()).to.deep.equal(["1", "2", "3"]); }); it("should handle JSON array with booleans", () => { const paramValue = new params.ParamValue("[true, false, true]", false, { list: true }); - expect(paramValue.asList()).to.deep.equal([true, false, true]); + expect(paramValue.asList()).to.deep.equal(["true", "false", "true"]); }); it("should handle JSON array with nulls", () => { const paramValue = new params.ParamValue('["a", null, "b"]', false, { list: true }); - expect(paramValue.asList()).to.deep.equal(["a", null, "b"]); + expect(paramValue.asList()).to.deep.equal(["a", "null", "b"]); }); it("should handle trailing delimiter", () => { @@ -425,12 +425,12 @@ describe("ParamValue.asList()", () => { it("should handle JSON array with nested arrays", () => { const paramValue = new params.ParamValue('[["a", "b"], ["c"]]', false, { list: true }); - expect(paramValue.asList()).to.deep.equal([["a", "b"], ["c"]]); + expect(paramValue.asList()).to.deep.equal(['["a","b"]', '["c"]']); }); it("should handle JSON array with objects", () => { const paramValue = new params.ParamValue('[{"foo":1}, {"bar":2}]', false, { list: true }); - expect(paramValue.asList()).to.deep.equal([{ foo: 1 }, { bar: 2 }]); + expect(paramValue.asList()).to.deep.equal(['{"foo":1}', '{"bar":2}']); }); }); }); diff --git a/src/deploy/functions/params.ts b/src/deploy/functions/params.ts index 239e6ad1ecd..c332a7376fd 100644 --- a/src/deploy/functions/params.ts +++ b/src/deploy/functions/params.ts @@ -296,13 +296,27 @@ export class ParamValue { // Handle something like "["a", "b", "c"]" if (modifiedValue.startsWith("[") && modifiedValue.endsWith("]")) { try { - return JSON.parse(modifiedValue); + const parsed = JSON.parse(modifiedValue); + if (Array.isArray(parsed)) { + // The return type is string[], so we must convert all elements to strings. + return parsed.map((elem: any) => { + if (elem === null) { + // String(null) is "null", which is what we want. + return "null"; + } + if (typeof elem === "object") { + // String(obj) is "[object Object]", JSON.stringify is more useful. + // Avoid inserting spaces after commas for objects/arrays + return JSON.stringify(elem); + } + return String(elem); + }); + } + // It's valid JSON but not an array (e.g. a string literal "foo,bar"). + // Fall through to splitting by delimiter. } catch (e) { - // Remove the brackets - // Handles scenarios like "[a, b, c]" - modifiedValue = modifiedValue.replace(/[\[\]]/g, ""); - - // Not a valid JSON array, fall through to splitting by delimiter. + // Malformed JSON, e.g. "[a, b, c]". Remove brackets and fall through. + modifiedValue = modifiedValue.slice(1, -1); } } From 22ff3f9e5bf5da381c27419bc88b1b1b6500822a Mon Sep 17 00:00:00 2001 From: Corie Watson Date: Wed, 6 Aug 2025 15:45:41 +0100 Subject: [PATCH 4/4] chore: format --- src/deploy/functions/params.spec.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/deploy/functions/params.spec.ts b/src/deploy/functions/params.spec.ts index 6e3f734dda2..ee8ea52749e 100644 --- a/src/deploy/functions/params.spec.ts +++ b/src/deploy/functions/params.spec.ts @@ -338,7 +338,9 @@ describe("ParamValue.asList()", () => { describe("robustness", () => { it("should handle values with commas inside JSON array", () => { - const paramValue = new params.ParamValue('["hello, world", "foo, bar"]', false, { list: true }); + const paramValue = new params.ParamValue('["hello, world", "foo, bar"]', false, { + list: true, + }); expect(paramValue.asList()).to.deep.equal(["hello, world", "foo, bar"]); }); @@ -365,7 +367,7 @@ describe("ParamValue.asList()", () => { it("should handle quotes, apostrophes, and backticks in the middle of the string", () => { const paramValue = new params.ParamValue('["a\\"b", "c`d", "e\'f"]', false, { list: true }); - expect(paramValue.asList()).to.deep.equal(["a\"b", "c`d", "e'f"]); + expect(paramValue.asList()).to.deep.equal(['a"b', "c`d", "e'f"]); }); it("should handle empty string", () => {