From 123692827cd871a9c0a73560f2db71f09aa8dfe5 Mon Sep 17 00:00:00 2001 From: Carlos Valente Date: Fri, 11 Sep 2026 19:56:31 +0200 Subject: [PATCH] feat(automation): support URL-encoded template values --- .../automation/__tests__/automation.utils.test.ts | 8 ++++++++ apps/server/src/api-data/automation/automation.utils.ts | 9 ++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/apps/server/src/api-data/automation/__tests__/automation.utils.test.ts b/apps/server/src/api-data/automation/__tests__/automation.utils.test.ts index 06cb09487..a9b239171 100644 --- a/apps/server/src/api-data/automation/__tests__/automation.utils.test.ts +++ b/apps/server/src/api-data/automation/__tests__/automation.utils.test.ts @@ -44,6 +44,14 @@ describe('parseTemplateNested()', () => { const result = parseTemplateNested(testString, mockState); expect(result).toStrictEqual(expected); }); + + it('URL-encodes an explicitly marked variable', () => { + const result = parseTemplateNested('http://example.com/?title={{url:event.title}}', { + event: { title: 'Keynote & Roadmap #1' }, + }); + + expect(result).toBe('http://example.com/?title=Keynote%20%26%20Roadmap%20%231'); + }); }); describe('parseNestedTemplate() -> resolveAliasData()', () => { diff --git a/apps/server/src/api-data/automation/automation.utils.ts b/apps/server/src/api-data/automation/automation.utils.ts index 53546a044..f424d9ba5 100644 --- a/apps/server/src/api-data/automation/automation.utils.ts +++ b/apps/server/src/api-data/automation/automation.utils.ts @@ -72,6 +72,7 @@ const placeholderRegex = /{{(.*?)}}/g; /** * Parses a templated string to values in a nested object + * Prefix a variable with `url:` when its value must be safe inside a URL component. */ export function parseTemplateNested(template: string, state: object, humanReadable = quickAliases): string { let parsedTemplate = template; @@ -79,7 +80,9 @@ export function parseTemplateNested(template: string, state: object, humanReadab for (const match of matches) { const variableName = match[1]; - const variableParts = variableName.split('.'); + const shouldEncodeForUrl = variableName.startsWith('url:'); + const propertyName = shouldEncodeForUrl ? variableName.slice(4) : variableName; + const variableParts = propertyName.split('.'); let value: string | undefined = undefined; if (variableParts[0] === 'human') { @@ -93,10 +96,10 @@ export function parseTemplateNested(template: string, state: object, humanReadab } } else { // we cast to string since this will be used in a string context - value = getPropertyFromPath(variableName, state) as string; + value = getPropertyFromPath(propertyName, state) as string; } if (value !== undefined) { - parsedTemplate = parsedTemplate.replace(match[0], value); + parsedTemplate = parsedTemplate.replace(match[0], shouldEncodeForUrl ? encodeURIComponent(value) : value); } }