Skip to content

Commit 97b3d9e

Browse files
[One Workflow] Update liquidjs to version 10.26.0 and add sha256 and hmac_sha256 filters (#269989)
## Summary Adopts Shopify-compatible `sha256` and `hmac_sha256` Liquid filters for Workflows by upgrading [liquidjs to 10.26.0](https://github.com/harttle/liquidjs/releases/tag/v10.26.0) ([harttle/liquidjs#889](harttle/liquidjs#889)). Filter behavior comes from liquidjs core—no custom server-side implementations. - **Execution (server):** Built-in filters work via `createWorkflowLiquidEngine()` with existing `renderSync` / `evalValueSync` (Node `crypto`). - **Editor autocomplete:** Added `sha256` and `hmac_sha256` entries with Shopify reference examples. - **Hover preview (browser):** Switched `evaluateExpression` to async `evalValue` so crypto filters resolve correctly in the browser bundle (Web Crypto is async-only). Hover only shows evaluated filter output when viewing an execution on the **Executions** tab and hovering on the filter segment inside `{{ }}`. ## Changes | Area | Change | |------|--------| | `package.json` / `yarn.lock` | `liquidjs` `10.25.7` → `10.26.0` | | `liquid_completions.ts` | Autocomplete for `sha256`, `hmac_sha256` | | `templating_engine.test.ts` | Server tests (Shopify reference vectors) | | `liquid_parse_cache.test.ts` | Parse/validation tests for built-in crypto filters | | `evaluate_expression.ts` | `evalValueSync` → `await evalValue` for browser crypto | | `unified_hover_provider.ts` | `await evaluateExpression(...)` | | `evaluate_expression.test.ts` | Async tests + crypto filter coverage | **Not changed:** `templating_engine.ts`, `liquid_parse_cache.ts` — no `registerFilter` stubs needed for built-in filters. ## Examples: ```yaml name: Crypto filters enabled: true triggers: - type: manual inputs: - name: message type: string default: "hello world" consts: secret: "secret" steps: - name: hmac_sha256 type: console with: message: "{{ inputs.message | hmac_sha256: consts.secret }}" - name: sha256 type: console with: message: "{{ inputs.message | sha256 }}" ``` --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> (cherry picked from commit 79909d5) # Conflicts: # package.json # src/platform/plugins/shared/workflows_execution_engine/server/templating_engine.test.ts # yarn.lock
1 parent 5527b15 commit 97b3d9e

9 files changed

Lines changed: 157 additions & 70 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1415,7 +1415,7 @@
14151415
"langchain": "1.2.30",
14161416
"langsmith": "0.7.1",
14171417
"launchdarkly-js-client-sdk": "3.9.0",
1418-
"liquidjs": "10.25.6",
1418+
"liquidjs": "10.26.0",
14191419
"lodash": "4.18.1",
14201420
"lru-cache": "11.3.5",
14211421
"lz-string": "1.5.0",

src/platform/plugins/shared/workflows_execution_engine/server/templating_engine.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,41 @@ describe('WorkflowTemplatingEngine', () => {
155155
});
156156
});
157157

158+
describe('sha256 filter', () => {
159+
it('should return the SHA-256 hex digest (Shopify reference vector)', () => {
160+
const template = '{{ text | sha256 }}';
161+
const context = { text: 'Polyjuice' };
162+
const result = templatingEngine.render(template, context);
163+
expect(result).toBe('44ac1d7a2936e30a5de07082fd65d6fe9b1fb658a1a98bfe65bc5959beac5dd0');
164+
});
165+
166+
it('should coerce null and undefined to empty string', () => {
167+
const template = '{{ value | sha256 }}';
168+
expect(templatingEngine.render(template, { value: null })).toBe(
169+
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
170+
);
171+
expect(templatingEngine.render(template, { value: undefined })).toBe(
172+
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
173+
);
174+
});
175+
});
176+
177+
describe('hmac_sha256 filter', () => {
178+
it('should return the HMAC-SHA256 hex digest (Shopify reference vector)', () => {
179+
const template = '{{ text | hmac_sha256: key }}';
180+
const context = { text: 'Polyjuice', key: 'Polina' };
181+
const result = templatingEngine.render(template, context);
182+
expect(result).toBe('8e0d5d65cff1242a4af66c8f4a32854fd5fb80edcc8aabe9b302b29c7c71dc20');
183+
});
184+
185+
it('should stringify numeric secret keys', () => {
186+
const template = '{{ text | hmac_sha256: key }}';
187+
const context = { text: 'test', key: 12345 };
188+
const result = templatingEngine.render(template, context);
189+
expect(result).toMatch(/^[0-9a-f]{64}$/);
190+
});
191+
});
192+
158193
describe('base64_encode filter with Buffer support', () => {
159194
it('should base64 encode a plain string', () => {
160195
const template = '{{ text | base64_encode }}';

src/platform/plugins/shared/workflows_management/common/lib/liquid_parse_cache.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,19 @@ describe('getLiquidInstance', () => {
3232
expect(result).toBeDefined();
3333
});
3434

35+
it('parses templates using built-in sha256 and hmac_sha256 filters', () => {
36+
const engine = getLiquidInstance();
37+
const shaTemplate = engine.parse('{{ val | sha256 }}');
38+
expect(engine.renderSync(shaTemplate, { val: 'Polyjuice' })).toBe(
39+
'44ac1d7a2936e30a5de07082fd65d6fe9b1fb658a1a98bfe65bc5959beac5dd0'
40+
);
41+
42+
const hmacTemplate = engine.parse('{{ val | hmac_sha256: key }}');
43+
expect(engine.renderSync(hmacTemplate, { val: 'Polyjuice', key: 'Polina' })).toBe(
44+
'8e0d5d65cff1242a4af66c8f4a32854fd5fb80edcc8aabe9b302b29c7c71dc20'
45+
);
46+
});
47+
3548
describe('json_parse filter behavior', () => {
3649
it('parses valid JSON strings', () => {
3750
const engine = getLiquidInstance();

src/platform/plugins/shared/workflows_management/public/widgets/workflow_yaml_editor/lib/autocomplete/suggestions/liquid/liquid_completions.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ describe('liquid_completions', () => {
4040
expect(filterNames).toContain('minus');
4141
expect(filterNames).toContain('base64_encode');
4242
expect(filterNames).toContain('base64_decode');
43+
expect(filterNames).toContain('sha256');
44+
expect(filterNames).toContain('hmac_sha256');
4345
});
4446

4547
it('should have proper structure for each filter', () => {

src/platform/plugins/shared/workflows_management/public/widgets/workflow_yaml_editor/lib/autocomplete/suggestions/liquid/liquid_completions.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,13 @@ export const LIQUID_FILTERS = [
193193
example:
194194
'{{ products | has_exp: "item", "item.price > 100" }} => true if any product has price > 100',
195195
},
196+
{
197+
name: 'hmac_sha256',
198+
description: 'Returns the HMAC-SHA256 hex digest of a string using the given secret key',
199+
insertText: 'hmac_sha256: "${1:secret}"',
200+
example:
201+
'{{ "Polyjuice" | hmac_sha256: "Polina" }} => "8e0d5d65cff1242a4af66c8f4a32854fd5fb80edcc8aabe9b302b29c7c71dc20"',
202+
},
196203
{
197204
name: 'join',
198205
description: 'Combines the items in an array into a single string',
@@ -347,6 +354,13 @@ export const LIQUID_FILTERS = [
347354
insertText: 'rstrip',
348355
example: '{{ "hello " | rstrip }} => "hello"',
349356
},
357+
{
358+
name: 'sha256',
359+
description: 'Returns the SHA-256 hex digest of a string',
360+
insertText: 'sha256',
361+
example:
362+
'{{ "Polyjuice" | sha256 }} => "44ac1d7a2936e30a5de07082fd65d6fe9b1fb658a1a98bfe65bc5959beac5dd0"',
363+
},
350364
{
351365
name: 'shift',
352366
description: 'Removes the first item from an array',

src/platform/plugins/shared/workflows_management/public/widgets/workflow_yaml_editor/lib/monaco_providers/unified_hover_provider.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -555,13 +555,13 @@ export class UnifiedHoverProvider implements monaco.languages.HoverProvider {
555555

556556
if (templateInfo.filters.length > 0 && templateInfo.isOnFilter) {
557557
evaluatedPath = templateInfo.expression;
558-
value = evaluateExpression({
558+
value = await evaluateExpression({
559559
expression: templateInfo.expression,
560560
context: evalContext,
561561
});
562562
} else {
563563
evaluatedPath = templateInfo.pathUpToCursor.join('.');
564-
value = evaluateExpression({
564+
value = await evaluateExpression({
565565
expression: evaluatedPath,
566566
context: evalContext,
567567
});

0 commit comments

Comments
 (0)