@teleporthq/teleport-plugin-next-workflows 0.43.21 → 0.43.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/custom-js-loop-scope.test.ts +61 -0
- package/__tests__/email-body-context-resolution.test.ts +123 -0
- package/dist/cjs/executor-generator.d.ts.map +1 -1
- package/dist/cjs/executor-generator.js +100 -3
- package/dist/cjs/executor-generator.js.map +1 -1
- package/dist/cjs/nodes/general/general-custom-js.d.ts.map +1 -1
- package/dist/cjs/nodes/general/general-custom-js.js +14 -0
- package/dist/cjs/nodes/general/general-custom-js.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/executor-generator.d.ts.map +1 -1
- package/dist/esm/executor-generator.js +100 -3
- package/dist/esm/executor-generator.js.map +1 -1
- package/dist/esm/nodes/general/general-custom-js.d.ts.map +1 -1
- package/dist/esm/nodes/general/general-custom-js.js +14 -0
- package/dist/esm/nodes/general/general-custom-js.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/src/executor-generator.ts +100 -3
- package/src/nodes/general/general-custom-js.ts +14 -0
|
@@ -125,6 +125,102 @@ function isSecretRef(value) {
|
|
|
125
125
|
);
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
// Resolve the inline workflow-context placeholders the rich-text email-body
|
|
129
|
+
// editor embeds for every dynamic value:
|
|
130
|
+
// <span class="context-value-inline" data-ctx-node-id="<id>"
|
|
131
|
+
// data-ctx-path='["<id>","result"]'>Label</span>
|
|
132
|
+
// resolveConfig leaves plain strings untouched, so without this the literal
|
|
133
|
+
// placeholder label (e.g. "Reset URL") would be delivered in the email instead
|
|
134
|
+
// of the resolved value. Handles both the raw single-quoted attribute form and
|
|
135
|
+
// the Quill-serialized "-escaped form, plus the nested inner <span> Quill
|
|
136
|
+
// wraps the label in.
|
|
137
|
+
function resolveRichTextContext(html, context) {
|
|
138
|
+
if (typeof html !== 'string' || html.indexOf('context-value-inline') === -1) {
|
|
139
|
+
return html;
|
|
140
|
+
}
|
|
141
|
+
var out = '';
|
|
142
|
+
var i = 0;
|
|
143
|
+
while (i < html.length) {
|
|
144
|
+
var spanStart = html.indexOf('<span', i);
|
|
145
|
+
if (spanStart === -1) { out += html.slice(i); break; }
|
|
146
|
+
var openEnd = html.indexOf('>', spanStart);
|
|
147
|
+
if (openEnd === -1) { out += html.slice(i); break; }
|
|
148
|
+
var openTag = html.slice(spanStart, openEnd + 1);
|
|
149
|
+
if (openTag.indexOf('context-value-inline') === -1) {
|
|
150
|
+
// Unrelated <span> — copy it through verbatim and keep scanning.
|
|
151
|
+
out += html.slice(i, openEnd + 1);
|
|
152
|
+
i = openEnd + 1;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
// Copy everything before the embed, then replace the whole embed span
|
|
156
|
+
// (including any nested inner spans) with the resolved value.
|
|
157
|
+
out += html.slice(i, spanStart);
|
|
158
|
+
var depth = 1;
|
|
159
|
+
var j = openEnd + 1;
|
|
160
|
+
while (j < html.length && depth > 0) {
|
|
161
|
+
var nextOpen = html.indexOf('<span', j);
|
|
162
|
+
var nextClose = html.indexOf('</span>', j);
|
|
163
|
+
if (nextClose === -1) { j = html.length; break; }
|
|
164
|
+
if (nextOpen !== -1 && nextOpen < nextClose) {
|
|
165
|
+
var innerEnd = html.indexOf('>', nextOpen);
|
|
166
|
+
j = innerEnd === -1 ? html.length : innerEnd + 1;
|
|
167
|
+
depth++;
|
|
168
|
+
} else {
|
|
169
|
+
j = nextClose + 7; // '</span>'.length
|
|
170
|
+
depth--;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
out += resolveRichTextContextSpan(openTag, context);
|
|
174
|
+
i = j;
|
|
175
|
+
}
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function resolveRichTextContextSpan(openTag, context) {
|
|
180
|
+
var nodeId = extractHtmlAttr(openTag, 'data-ctx-node-id');
|
|
181
|
+
var pathRaw = extractHtmlAttr(openTag, 'data-ctx-path');
|
|
182
|
+
var path = null;
|
|
183
|
+
if (pathRaw) {
|
|
184
|
+
var decoded = pathRaw.replace(/"/g, '"').replace(/"/g, '"').replace(/&/g, '&');
|
|
185
|
+
try { path = JSON.parse(decoded); } catch (e) { path = null; }
|
|
186
|
+
}
|
|
187
|
+
if (!Array.isArray(path) || path.length === 0) {
|
|
188
|
+
path = nodeId ? [nodeId] : [];
|
|
189
|
+
}
|
|
190
|
+
if (!nodeId && path.length > 0) { nodeId = path[0]; }
|
|
191
|
+
if (!nodeId) { return ''; }
|
|
192
|
+
var value = resolveContextRef({ type: 'workflowContext', nodeId: nodeId, path: path }, context);
|
|
193
|
+
if (value === undefined || value === null) { return ''; }
|
|
194
|
+
return escapeHtmlText(String(value));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Read a quoted HTML attribute value (double- or single-quoted) without a regex
|
|
198
|
+
// so the surrounding template literal needs no backslash escaping.
|
|
199
|
+
function extractHtmlAttr(tag, name) {
|
|
200
|
+
var key = name + '=';
|
|
201
|
+
var at = tag.indexOf(key);
|
|
202
|
+
if (at === -1) { return ''; }
|
|
203
|
+
var quote = tag.charAt(at + key.length);
|
|
204
|
+
if (quote !== '"' && quote !== "'") { return ''; }
|
|
205
|
+
var start = at + key.length + 1;
|
|
206
|
+
var end = tag.indexOf(quote, start);
|
|
207
|
+
if (end === -1) { return ''; }
|
|
208
|
+
return tag.slice(start, end);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function escapeHtmlText(value) {
|
|
212
|
+
return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Scalar config values are normally just secret-resolved. Rich-text strings
|
|
216
|
+
// (email bodies) additionally need their inline context placeholders resolved.
|
|
217
|
+
function resolveScalarValue(value, context) {
|
|
218
|
+
if (typeof value === 'string' && value.indexOf('context-value-inline') !== -1) {
|
|
219
|
+
return resolveRichTextContext(value, context);
|
|
220
|
+
}
|
|
221
|
+
return resolveSecret(value, context);
|
|
222
|
+
}
|
|
223
|
+
|
|
128
224
|
function resolveConfig(config, context) {
|
|
129
225
|
if (!config || typeof config !== 'object') return config;
|
|
130
226
|
const resolved = {};
|
|
@@ -154,7 +250,7 @@ function resolveConfig(config, context) {
|
|
|
154
250
|
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
|
155
251
|
return resolveConfig(item, context);
|
|
156
252
|
}
|
|
157
|
-
return
|
|
253
|
+
return resolveScalarValue(item, context);
|
|
158
254
|
});
|
|
159
255
|
} else {
|
|
160
256
|
resolved[key] = val.map(function(item) {
|
|
@@ -164,7 +260,7 @@ function resolveConfig(config, context) {
|
|
|
164
260
|
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
|
165
261
|
return resolveConfig(item, context);
|
|
166
262
|
}
|
|
167
|
-
return
|
|
263
|
+
return resolveScalarValue(item, context);
|
|
168
264
|
});
|
|
169
265
|
}
|
|
170
266
|
} else if (val && typeof val === 'object' && val.type === 'workflowContext') {
|
|
@@ -176,7 +272,7 @@ function resolveConfig(config, context) {
|
|
|
176
272
|
} else if (val && typeof val === 'object' && !Array.isArray(val)) {
|
|
177
273
|
resolved[key] = resolveConfig(val, context);
|
|
178
274
|
} else {
|
|
179
|
-
resolved[key] =
|
|
275
|
+
resolved[key] = resolveScalarValue(val, context);
|
|
180
276
|
}
|
|
181
277
|
}
|
|
182
278
|
return resolved;
|
|
@@ -741,6 +837,7 @@ module.exports = {
|
|
|
741
837
|
resolveSecret,
|
|
742
838
|
resolveConfig,
|
|
743
839
|
resolveContextRef,
|
|
840
|
+
resolveRichTextContext,
|
|
744
841
|
unwrapWorkflowCollection,
|
|
745
842
|
evaluateCondition,
|
|
746
843
|
evaluateSingleComparison,
|
|
@@ -244,6 +244,20 @@ async function general_custom_js(config: any, context: Record<string, unknown>)
|
|
|
244
244
|
const argValues: Record<string, unknown> = {
|
|
245
245
|
previousContext,
|
|
246
246
|
params,
|
|
247
|
+
// The workflow editor + AI generator advertise the top-level regular
|
|
248
|
+
// signature as `customHandler(params, inputs, workflowContext)` (see
|
|
249
|
+
// teleport-services-worker custom-js-contract.ts) where `workflowContext`
|
|
250
|
+
// is documented as "prior node results by node index" and `inputs` is a
|
|
251
|
+
// reserved positional. At top level that array IS `params` (built above in
|
|
252
|
+
// execution order: params[0] = trigger, params[1] = first node after the
|
|
253
|
+
// trigger). Without these aliases a handler that follows the advertised
|
|
254
|
+
// contract and reads `workflowContext[1]` gets `undefined[1]` → TypeError
|
|
255
|
+
// at runtime (the 2026-06-19 Sugarpost homepage "Something went wrong"
|
|
256
|
+
// page-load crash). Aliasing both to `params` honours the documented
|
|
257
|
+
// contract; the legacy `(params)` form is unaffected since it never names
|
|
258
|
+
// them, and inside a loop the still-distinct `innerParams*` win below.
|
|
259
|
+
inputs: params,
|
|
260
|
+
workflowContext: params,
|
|
247
261
|
}
|
|
248
262
|
for (let i = 0; i < innerParamsList.length; i++) {
|
|
249
263
|
const name = i === 0 ? 'innerParams' : 'innerParams' + (i + 1)
|