mcp-server-sfmc 1.9.1 → 2.0.0
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/README.md +50 -4
- package/dist/conversion-rules.d.ts +72 -0
- package/dist/conversion-rules.d.ts.map +1 -1
- package/dist/conversion-rules.js +373 -0
- package/dist/conversion-rules.js.map +1 -1
- package/dist/index.js +577 -10
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# mcp-server-sfmc
|
|
2
2
|
|
|
3
|
-
MCP server providing Salesforce Marketing Cloud language intelligence — AMPscript, SSJS, and
|
|
3
|
+
MCP server providing Salesforce Marketing Cloud language intelligence — AMPscript, SSJS, GTL, and **Marketing Cloud Next (MCN) Handlebars** — as Model Context Protocol tools, resources, and prompts for AI-assisted development and code review. Ships two searchable doc indexes: **MCE help** (Salesforce Help for Marketing Cloud Engagement administration and setup) and **MCN developer docs** (Marketing Cloud Next REST API and developer reference). Includes full **Marketing Cloud Next (MCN) platform awareness**: auto-detects whether a project targets MCE or MCN, restricts completions and validation to MCN-supported functions, flags SSJS as unsupported in MCN, surfaces MCN behavioral differences in hover and diagnostics, validates MCN Handlebars templates, and provides a **migration toolkit** to analyze and rewrite code for MCN — including AMPscript ↔ Handlebars and SSJS → Handlebars conversion.
|
|
4
4
|
|
|
5
5
|
Built on [sfmc-language-lsp](https://github.com/JoernBerkefeld/sfmc-language-lsp), the same engine that powers the [SFMC Language Service VS Code extension](https://marketplace.visualstudio.com/items?itemName=joernberkefeld.sfmc-language).
|
|
6
6
|
|
|
@@ -41,7 +41,7 @@ Ready-to-copy workflows (GitHub Actions, GitLab CI, Jenkins, Azure Pipelines, Bi
|
|
|
41
41
|
|
|
42
42
|
| Feature | Details |
|
|
43
43
|
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
44
|
-
| **Validation** | Syntax errors, unknown functions, arity mismatches, unsupported SSJS syntax; `target: 'next'` flags MCN-incompatible functions and all SSJS
|
|
44
|
+
| **Validation** | Syntax errors, unknown functions, arity mismatches, unsupported SSJS syntax; `target: 'next'` flags MCN-incompatible functions and all SSJS; `validate_handlebars` checks MCN Handlebars helper names, arity, block balance, and unsupported constructs |
|
|
45
45
|
| **Lookup** | Full function signatures, parameters, return types, MCN compatibility badge (API version), behavioral notes, and examples |
|
|
46
46
|
| **PR review** | Diff-aware review tool that surfaces issues in the exact lines that changed |
|
|
47
47
|
| **Fix suggestions** | Concrete, compilable replacement code; `target: 'next'` includes MCN platform issues |
|
|
@@ -50,8 +50,10 @@ Ready-to-copy workflows (GitHub Actions, GitLab CI, Jenkins, Azure Pipelines, Bi
|
|
|
50
50
|
| **MCN compatibility analysis** | `check_mcn_compatibility` analyzes files for MCN readiness — per-function classification (supported / needs review / not supported), SSJS block migration difficulty, and an executive summary |
|
|
51
51
|
| **MCN migration** | `rewrite_for_mcn` (tool + prompt) deterministically rewrites AMPscript and converts convertible SSJS to AMPscript for MCN, then applies AI reasoning to remaining manual-rewrite sections |
|
|
52
52
|
| **Code conversion** | `convertSsjsToAmpscript` and `convertAmpscriptToSsjs` (tool + prompt hybrid) — rule-based conversion with AI-enhanced handling of flagged sections |
|
|
53
|
-
| **
|
|
54
|
-
| **
|
|
53
|
+
| **MCN Handlebars** | `lookup_handlebars_helper`, `list_handlebars_helpers`, `get_handlebars_completions`, and `write_handlebars` (authoring) backed by the `handlebars-data` catalog |
|
|
54
|
+
| **Handlebars conversion** | `convertAmpscriptToHandlebars`, `convertHandlebarsToAmpscript`, and `convertSsjsToHandlebars` (tool + prompt hybrid) — data-driven from `ampscript-data` `handlebarsEquivalent` / `mcnHandlebarsGap` |
|
|
55
|
+
| **Prompts** | Guided prompts for writing AMPscript/SSJS/Handlebars (with MCN constraints), reviewing code, converting between languages, and rewriting for MCN |
|
|
56
|
+
| **Resources** | Full function catalogs, keyword list, unsupported ES6+ syntax list, MCN Handlebars helper and binding catalogs |
|
|
55
57
|
| **Help search** | `search_help` (unified, auto-detects MCE vs MCN from project root); `search_mce_help` (MCE help, 7 product scopes); `search_mcn_help` (MCN developer API reference) |
|
|
56
58
|
|
|
57
59
|
## Connecting AI clients
|
|
@@ -156,6 +158,14 @@ Then replace `"command": "npx", "args": ["-y", "mcp-server-sfmc@latest"]` with:
|
|
|
156
158
|
| `rewrite_for_mcn` | Deterministic MCN rewrite engine: fixes `.NET → Java` format strings, removes `StringToDate` wrappers, converts convertible SSJS to AMPscript, marks unsupported functions, annotates the rest as `MANUAL_REWRITE_REQUIRED`. **Prefer the `rewrite_for_mcn` prompt** for interactive use — it calls this tool first, then applies AI reasoning to the flagged sections. Use the tool directly only for structured JSON output in pipelines. |
|
|
157
159
|
| `convertSsjsToAmpscript` | Rule-based SSJS → AMPscript conversion engine: `Platform.Function.*` → AMPscript equivalents, variable/request access, control flow. Flags JS-native constructs as `MANUAL_REWRITE_REQUIRED`. **Prefer the `convertSsjsToAmpscript` prompt** for interactive use. |
|
|
158
160
|
| `convertAmpscriptToSsjs` | Rule-based AMPscript → SSJS conversion engine: variables, control flow, function calls. Flags AMPscript-only constructs as `MANUAL_REWRITE_REQUIRED`. **Prefer the `convertAmpscriptToSsjs` prompt** for interactive use. |
|
|
161
|
+
| `validate_handlebars` | Validate MCN Handlebars template code — helper names, arity, block balance, and unsupported constructs (partials, decorators, built-in helpers absent from the locked-down MCN engine). |
|
|
162
|
+
| `lookup_handlebars_helper` | Signature, parameters, description, origin, and MCN API version for a single MCN Handlebars helper (case-insensitive). |
|
|
163
|
+
| `list_handlebars_helpers` | List all MCN Handlebars helpers, optionally filtered by `category` and/or `origin` (`handlebars-builtin`, `mcn-helper`, `mcn-platform`). |
|
|
164
|
+
| `get_handlebars_completions` | List MCN Handlebars helper completions, optionally filtered by prefix. |
|
|
165
|
+
| `convertAmpscriptToHandlebars` | Rule-based AMPscript → MCN Handlebars conversion. Maps functions to helpers where an equivalent exists; flags unsupported functions and runtime gaps as `MANUAL_REWRITE_REQUIRED`. **Prefer the `convertAmpscriptToHandlebars` prompt** for interactive use. |
|
|
166
|
+
| `convertHandlebarsToAmpscript` | Rule-based MCN Handlebars → AMPscript conversion. Maps helpers and bindings back to AMPscript; flags block helpers as `MANUAL_REWRITE_REQUIRED`. **Prefer the `convertHandlebarsToAmpscript` prompt** for interactive use. |
|
|
167
|
+
| `convertSsjsToHandlebars` | Transitive SSJS → AMPscript → MCN Handlebars conversion. Conservatively flags imperative SSJS for manual rewrite. **Prefer the `convertSsjsToHandlebars` prompt** for interactive use. |
|
|
168
|
+
| `write_handlebars` | Validate an MCN Handlebars-in-HTML draft against the helper catalog. **Prefer the `writeHandlebars` prompt** for authoring. |
|
|
159
169
|
| `search_help` | **Unified help search** — auto-detects the platform from `projectRoot` and routes to the right doc index. MCN projects search both the developer API reference and MCN admin docs. Pass `target` to override detection. |
|
|
160
170
|
| `search_mce_help` | Search bundled Marketing Cloud Engagement setup/ops help; use `product_focus` to target Engagement vs Next |
|
|
161
171
|
| `search_mcn_help` | Search bundled Marketing Cloud Next developer API documentation (objects, flows, segments, REST/SOAP APIs, AMPscript behavior in MCN) |
|
|
@@ -168,6 +178,8 @@ Then replace `"command": "npx", "args": ["-y", "mcp-server-sfmc@latest"]` with:
|
|
|
168
178
|
| `sfmc://ssjs/functions` | Full SSJS function catalog |
|
|
169
179
|
| `sfmc://ampscript/keywords` | All AMPscript keywords |
|
|
170
180
|
| `sfmc://ssjs/unsupported-syntax` | ES6+ features not supported in SFMC SSJS |
|
|
181
|
+
| `sfmc://handlebars/helpers` | Full MCN Handlebars helper catalog with signatures, origin, and MCN API version |
|
|
182
|
+
| `sfmc://handlebars/bindings` | MCN Handlebars data bindings (context variables available in templates) |
|
|
171
183
|
| `sfmc://mce/product-context` | How **Marketing Cloud Engagement** differs from **Marketing Cloud Next** (when to use which) |
|
|
172
184
|
| `sfmc://mce/help-index` | List of bundled MCE help files and section counts per product scope |
|
|
173
185
|
| `sfmc://mcn/help-index` | List of bundled MCN developer API doc files and chunk counts |
|
|
@@ -184,6 +196,10 @@ Access via `/mcp.sfmc.writeAmpscript` etc. in VS Code, or via the prompts API:
|
|
|
184
196
|
| `rewrite_for_mcn` | **Primary interface for MCN migration.** Internally calls the `rewrite_for_mcn` tool for all deterministic rewrites, then applies AI reasoning to every `MANUAL_REWRITE_REQUIRED` section — producing a single fully migrated code block with a prose changelog. You always get both the rule-based pass and the AI pass in one step. |
|
|
185
197
|
| `convertSsjsToAmpscript` | **Primary interface for SSJS → AMPscript conversion.** Internally calls the `convertSsjsToAmpscript` tool for rule-based conversion, then AI-reasons over any sections the rules could not handle. |
|
|
186
198
|
| `convertAmpscriptToSsjs` | **Primary interface for AMPscript → SSJS conversion.** Internally calls the `convertAmpscriptToSsjs` tool for rule-based conversion, then applies AI reasoning to flagged AMPscript-only constructs. |
|
|
199
|
+
| `writeHandlebars` | Generate MCN Handlebars-in-HTML for a described task, constrained to the locked-down MCN helper catalog. Validates the draft against `handlebars-data` and reports unsupported constructs. |
|
|
200
|
+
| `convertAmpscriptToHandlebars` | **Primary interface for AMPscript → MCN Handlebars conversion.** Internally calls the `convertAmpscriptToHandlebars` tool for rule-based conversion, then AI-reasons over functions with no Handlebars equivalent (runtime gaps and unsupported functions). |
|
|
201
|
+
| `convertHandlebarsToAmpscript` | **Primary interface for MCN Handlebars → AMPscript conversion.** Internally calls the `convertHandlebarsToAmpscript` tool for rule-based conversion, then AI-reasons over block helpers and constructs the rules could not handle. |
|
|
202
|
+
| `convertSsjsToHandlebars` | **Primary interface for SSJS → MCN Handlebars conversion.** Transitive (SSJS → AMPscript → Handlebars); calls the `convertSsjsToHandlebars` tool, then applies AI reasoning to imperative SSJS flagged for manual rewrite. |
|
|
187
203
|
| `answerMceHowTo` | Guided prompt for admin/setup questions — searches bundled help and keeps Engagement vs Next explicit |
|
|
188
204
|
|
|
189
205
|
## Migrating code to Marketing Cloud Next
|
|
@@ -243,6 +259,34 @@ The same hybrid pattern applies to `convertSsjsToAmpscript` and `convertAmpscrip
|
|
|
243
259
|
|
|
244
260
|
Functions that depend on CloudPages context (`CloudPagesURL`, `RequestParameter`, `QueryParameter`, `Redirect`, `MicrositeURL`) have no equivalent in Marketing Cloud Next. `check_mcn_compatibility` and `rewrite_for_mcn` flag these as **Not migratable** immediately.
|
|
245
261
|
|
|
262
|
+
## MCN Handlebars
|
|
263
|
+
|
|
264
|
+
Marketing Cloud Next embeds a **locked-down Handlebars** templating layer (Handlebars.Net) alongside AMPscript. The server exposes the full helper catalog and conversion tooling, all data-driven from [`handlebars-data`](https://www.npmjs.com/package/handlebars-data) and the `handlebarsEquivalent` / `mcnHandlebarsGap` fields in [`ampscript-data`](https://www.npmjs.com/package/ampscript-data) — nothing is hand-maintained.
|
|
265
|
+
|
|
266
|
+
### Authoring & lookup
|
|
267
|
+
|
|
268
|
+
```
|
|
269
|
+
1. list_handlebars_helpers → browse the catalog (filter by category / origin)
|
|
270
|
+
2. lookup_handlebars_helper → signature, parameters, origin, MCN API version
|
|
271
|
+
3. get_handlebars_completions → prefix-filtered completions
|
|
272
|
+
4. writeHandlebars prompt → generate Handlebars-in-HTML, validated against the catalog
|
|
273
|
+
5. validate_handlebars → helper names, arity, block balance, unsupported constructs
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
`validate_handlebars` rejects constructs the MCN engine does not implement — partials (`{{> name}}`), decorators (`{{#* ...}}`), and built-in helpers absent from the locked-down runtime.
|
|
277
|
+
|
|
278
|
+
### Conversion categories
|
|
279
|
+
|
|
280
|
+
AMPscript → Handlebars conversion classifies every function from `ampscript-data`:
|
|
281
|
+
|
|
282
|
+
| Category | Condition (in `ampscript-data`) | Output |
|
|
283
|
+
| -------- | ----------------------------------------------------- | ----------------------------------------------------------------- |
|
|
284
|
+
| **A** | `handlebarsEquivalent` is a helper name | Mapped to the canonical MCN Handlebars helper |
|
|
285
|
+
| **B** | No Handlebars counterpart | `{{!-- MANUAL_REWRITE_REQUIRED: … no Handlebars equivalent --}}` |
|
|
286
|
+
| **C** | `mcnHandlebarsGap: true` (MCN-supported, no helper yet) | `{{!-- MANUAL_REWRITE_REQUIRED: … (runtime gap) --}}` (distinct note) |
|
|
287
|
+
|
|
288
|
+
The Category C note is intentionally distinct from Category B so consumers can tell a *runtime gap* (function works in MCN AMPscript but has no Handlebars helper) apart from a function with no MCN counterpart at all. `convertSsjsToHandlebars` is transitive (SSJS → AMPscript → Handlebars) and conservatively flags imperative SSJS for manual rewrite.
|
|
289
|
+
|
|
246
290
|
## Writing effective prompts
|
|
247
291
|
|
|
248
292
|
### Automatic tool use
|
|
@@ -262,6 +306,8 @@ Clients that honour the MCP `instructions` field (Cursor, Claude Desktop, GitHub
|
|
|
262
306
|
| Check if code is MCN-ready | Use `check_mcn_compatibility` with your file contents. |
|
|
263
307
|
| Migrate code to Marketing Cloud Next | Use the `rewrite_for_mcn` **prompt** — it calls the tool internally for deterministic rewrites, then applies AI reasoning to any remaining manual sections. Use the tool directly only when you need structured JSON output in a pipeline. |
|
|
264
308
|
| Convert SSJS ↔ AMPscript | Use the `convertSsjsToAmpscript` or `convertAmpscriptToSsjs` **prompt** — same hybrid pattern: tool runs first, AI handles what the rules couldn't. |
|
|
309
|
+
| Write or validate MCN Handlebars | Use the `writeHandlebars` prompt to author, or `validate_handlebars` to check existing template code. Look helpers up with `lookup_handlebars_helper` / `list_handlebars_helpers`. |
|
|
310
|
+
| Convert AMPscript ↔ Handlebars (or SSJS → Handlebars) | Use the `convertAmpscriptToHandlebars`, `convertHandlebarsToAmpscript`, or `convertSsjsToHandlebars` **prompt** — same hybrid pattern: tool runs first, AI handles what the rules couldn't. |
|
|
265
311
|
| Review a code diff | Use the `reviewSfmcCode` prompt or mention "review the following diff". |
|
|
266
312
|
|
|
267
313
|
### Copy-paste prompt templates
|
|
@@ -34,6 +34,31 @@ export declare const PLATFORM_FUNCTION_TO_AMP: Readonly<Record<string, string>>;
|
|
|
34
34
|
* PLATFORM_FUNCTION_TO_AMP at module load time.
|
|
35
35
|
*/
|
|
36
36
|
export declare const AMP_TO_PLATFORM_FUNCTION: Readonly<Record<string, string>>;
|
|
37
|
+
/**
|
|
38
|
+
* Maps an AMPscript function name (lowercase) to the canonical MCN Handlebars
|
|
39
|
+
* helper name it converts to. Built at module load time from ampscript-data's
|
|
40
|
+
* `handlebarsEquivalent` field — always in sync with the installed
|
|
41
|
+
* ampscript-data version, never hand-edited (`mcp-conversion-rules-sync.mdc`).
|
|
42
|
+
*
|
|
43
|
+
* Only Category A functions (non-null string `handlebarsEquivalent`) are
|
|
44
|
+
* included. The stored value is the bare helper name; the canonical casing is
|
|
45
|
+
* resolved through `handlebars-data` so emitted `{{helper}}` calls match the
|
|
46
|
+
* catalog exactly.
|
|
47
|
+
*/
|
|
48
|
+
export declare const AMP_TO_HANDLEBARS: Readonly<Record<string, string>>;
|
|
49
|
+
/**
|
|
50
|
+
* Maps a canonical MCN Handlebars helper name (lowercase) to its AMPscript
|
|
51
|
+
* function name. Inverted from AMP_TO_HANDLEBARS at module load time. Used by
|
|
52
|
+
* convertHandlebarsToAmpscript.
|
|
53
|
+
*/
|
|
54
|
+
export declare const HANDLEBARS_TO_AMP: Readonly<Record<string, string>>;
|
|
55
|
+
/**
|
|
56
|
+
* Set of AMPscript function names (lowercase) flagged `mcnHandlebarsGap: true`
|
|
57
|
+
* in ampscript-data (Category C). These are documented as MCN-supported but
|
|
58
|
+
* currently fail at runtime and have no Handlebars helper — converting them
|
|
59
|
+
* must emit a MANUAL_REWRITE marker distinct from Category B (no counterpart).
|
|
60
|
+
*/
|
|
61
|
+
export declare const AMP_MCN_HANDLEBARS_GAP: ReadonlySet<string>;
|
|
37
62
|
/**
|
|
38
63
|
* Maps an AMPscript function name (lowercase) to a native JavaScript rewrite
|
|
39
64
|
* hint comment. Checked BEFORE the TreatAsContent polyfill fallback in
|
|
@@ -109,6 +134,53 @@ export declare function ampscriptToSsjs(code: string): ConversionResult;
|
|
|
109
134
|
* @returns {string} Expression with `@` prefixes removed.
|
|
110
135
|
*/
|
|
111
136
|
export declare function stripAmpVars(expr: string): string;
|
|
137
|
+
/**
|
|
138
|
+
* Distinct MANUAL_REWRITE note for Category C functions (`mcnHandlebarsGap`).
|
|
139
|
+
* Must be visibly different from the Category B note so consumers can tell a
|
|
140
|
+
* documented-but-broken function apart from one with no counterpart at all.
|
|
141
|
+
*/
|
|
142
|
+
export declare const HBS_GAP_NOTE = "documented as supported in Marketing Cloud Next but currently fails at runtime \u2014 no Handlebars helper exists yet";
|
|
143
|
+
/**
|
|
144
|
+
* Convert AMPscript to MCN Handlebars using deterministic, data-driven rules.
|
|
145
|
+
*
|
|
146
|
+
* Inline expressions (`%%=Fn(args)=%%`, `%%=v(@x)=%%`, `%%var%%`) are mapped via
|
|
147
|
+
* the three conversion categories built from ampscript-data's
|
|
148
|
+
* `handlebarsEquivalent` / `mcnHandlebarsGap` fields:
|
|
149
|
+
* - **A** — mapped helper → `{{helper …}}`
|
|
150
|
+
* - **B** — no counterpart → `{{!-- MANUAL_REWRITE_REQUIRED … --}}`
|
|
151
|
+
* - **C** — `mcnHandlebarsGap` → `{{!-- MANUAL_REWRITE_REQUIRED … (distinct note) --}}`
|
|
152
|
+
*
|
|
153
|
+
* Procedural AMPscript blocks (`%%[ … ]%%`, `SET`/`VAR`/`IF`/`FOR`) have no
|
|
154
|
+
* Handlebars counterpart (Handlebars cannot assign variables or run imperative
|
|
155
|
+
* control flow) and are flagged MANUAL_REWRITE_REQUIRED.
|
|
156
|
+
* @param code - AMPscript source code (may include HTML context).
|
|
157
|
+
* @returns {ConversionResult} Converted Handlebars, change log, and flagged sections.
|
|
158
|
+
*/
|
|
159
|
+
export declare function ampscriptToHandlebars(code: string): ConversionResult;
|
|
160
|
+
/**
|
|
161
|
+
* Convert MCN Handlebars to AMPscript using deterministic, data-driven rules.
|
|
162
|
+
*
|
|
163
|
+
* Inline helper calls (`{{helper args}}`) whose helper maps back to an AMPscript
|
|
164
|
+
* function (via HANDLEBARS_TO_AMP) become `%%=Fn(args)=%%`; bare variables
|
|
165
|
+
* (`{{name}}`) become `%%=v(@name)=%%`. Block helpers (`{{#each}}`…), partials
|
|
166
|
+
* (`{{> …}}`), dotted binding paths, and unknown helpers are flagged
|
|
167
|
+
* MANUAL_REWRITE_REQUIRED.
|
|
168
|
+
* @param code - Handlebars source code (may include HTML context).
|
|
169
|
+
* @returns {ConversionResult} Converted AMPscript, change log, and flagged sections.
|
|
170
|
+
*/
|
|
171
|
+
export declare function handlebarsToAmpscript(code: string): ConversionResult;
|
|
172
|
+
/**
|
|
173
|
+
* Convert SSJS to MCN Handlebars deterministically via a two-step chain:
|
|
174
|
+
* SSJS → AMPscript (`ssjsToAmpscript`) → Handlebars (`ampscriptToHandlebars`).
|
|
175
|
+
*
|
|
176
|
+
* Because Handlebars is declarative, most imperative SSJS has no Handlebars
|
|
177
|
+
* counterpart and is conservatively flagged MANUAL_REWRITE_REQUIRED; inline
|
|
178
|
+
* `Platform.Function.X(…)` calls that map through AMPscript to a Handlebars
|
|
179
|
+
* helper are converted.
|
|
180
|
+
* @param code - SSJS source code (may include `<script runat="server">` tags).
|
|
181
|
+
* @returns {ConversionResult} Converted Handlebars, combined change log, and flagged sections.
|
|
182
|
+
*/
|
|
183
|
+
export declare function ssjsToHandlebars(code: string): ConversionResult;
|
|
112
184
|
export interface McnRewriteOptions {
|
|
113
185
|
/** Function to check if an AMPscript function is MCN-supported */
|
|
114
186
|
isMcnSupportedFn: (name: string) => boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversion-rules.d.ts","sourceRoot":"","sources":["../src/conversion-rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,eAAe,EAAE,cAAc,EAAE,CAAC;CACrC;AAQD;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMrE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAKrE,CAAC;
|
|
1
|
+
{"version":3,"file":"conversion-rules.d.ts","sourceRoot":"","sources":["../src/conversion-rules.ts"],"names":[],"mappings":"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"}
|
package/dist/conversion-rules.js
CHANGED
|
@@ -37,6 +37,48 @@ export const AMP_TO_PLATFORM_FUNCTION = Object.fromEntries(PLATFORM_FUNCTIONS.fi
|
|
|
37
37
|
*/
|
|
38
38
|
const SSJS_ONLY_FUNCTIONS = new Set(PLATFORM_FUNCTIONS.filter((f) => f.ampscriptEquivalent === null).map((f) => f.name.toLowerCase()));
|
|
39
39
|
// ---------------------------------------------------------------------------
|
|
40
|
+
// Dynamic AMPscript ↔ MCN Handlebars maps (built from ampscript-data at load time)
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
import { FUNCTIONS as AMPSCRIPT_FUNCTIONS } from 'ampscript-data';
|
|
43
|
+
import { getHelper as getHandlebarsHelper } from 'handlebars-data';
|
|
44
|
+
/**
|
|
45
|
+
* Maps an AMPscript function name (lowercase) to the canonical MCN Handlebars
|
|
46
|
+
* helper name it converts to. Built at module load time from ampscript-data's
|
|
47
|
+
* `handlebarsEquivalent` field — always in sync with the installed
|
|
48
|
+
* ampscript-data version, never hand-edited (`mcp-conversion-rules-sync.mdc`).
|
|
49
|
+
*
|
|
50
|
+
* Only Category A functions (non-null string `handlebarsEquivalent`) are
|
|
51
|
+
* included. The stored value is the bare helper name; the canonical casing is
|
|
52
|
+
* resolved through `handlebars-data` so emitted `{{helper}}` calls match the
|
|
53
|
+
* catalog exactly.
|
|
54
|
+
*/
|
|
55
|
+
export const AMP_TO_HANDLEBARS = Object.fromEntries(AMPSCRIPT_FUNCTIONS.filter((f) => typeof f.handlebarsEquivalent === 'string' && f.handlebarsEquivalent.length > 0).map((f) => {
|
|
56
|
+
const canonical = getHandlebarsHelper(f.handlebarsEquivalent);
|
|
57
|
+
return [
|
|
58
|
+
f.name.toLowerCase(),
|
|
59
|
+
canonical ? canonical.name : f.handlebarsEquivalent,
|
|
60
|
+
];
|
|
61
|
+
}));
|
|
62
|
+
/**
|
|
63
|
+
* Maps a canonical MCN Handlebars helper name (lowercase) to its AMPscript
|
|
64
|
+
* function name. Inverted from AMP_TO_HANDLEBARS at module load time. Used by
|
|
65
|
+
* convertHandlebarsToAmpscript.
|
|
66
|
+
*/
|
|
67
|
+
export const HANDLEBARS_TO_AMP = Object.fromEntries(AMPSCRIPT_FUNCTIONS.filter((f) => typeof f.handlebarsEquivalent === 'string' && f.handlebarsEquivalent.length > 0).map((f) => {
|
|
68
|
+
const canonical = getHandlebarsHelper(f.handlebarsEquivalent);
|
|
69
|
+
return [
|
|
70
|
+
(canonical ? canonical.name : f.handlebarsEquivalent).toLowerCase(),
|
|
71
|
+
f.name,
|
|
72
|
+
];
|
|
73
|
+
}));
|
|
74
|
+
/**
|
|
75
|
+
* Set of AMPscript function names (lowercase) flagged `mcnHandlebarsGap: true`
|
|
76
|
+
* in ampscript-data (Category C). These are documented as MCN-supported but
|
|
77
|
+
* currently fail at runtime and have no Handlebars helper — converting them
|
|
78
|
+
* must emit a MANUAL_REWRITE marker distinct from Category B (no counterpart).
|
|
79
|
+
*/
|
|
80
|
+
export const AMP_MCN_HANDLEBARS_GAP = new Set(AMPSCRIPT_FUNCTIONS.filter((f) => f.mcnHandlebarsGap === true).map((f) => f.name.toLowerCase()));
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
40
82
|
// AMP_NATIVE_JS_HINTS: AMPscript-only functions with clean native JS rewrites
|
|
41
83
|
// ---------------------------------------------------------------------------
|
|
42
84
|
/**
|
|
@@ -698,6 +740,337 @@ function ampCondToSsjs(cond) {
|
|
|
698
740
|
export function stripAmpVars(expr) {
|
|
699
741
|
return expr.replaceAll(/@(\w+)/g, '$1');
|
|
700
742
|
}
|
|
743
|
+
// ---------------------------------------------------------------------------
|
|
744
|
+
// AMPscript ↔ MCN Handlebars conversion
|
|
745
|
+
// ---------------------------------------------------------------------------
|
|
746
|
+
/**
|
|
747
|
+
* Distinct MANUAL_REWRITE note for Category C functions (`mcnHandlebarsGap`).
|
|
748
|
+
* Must be visibly different from the Category B note so consumers can tell a
|
|
749
|
+
* documented-but-broken function apart from one with no counterpart at all.
|
|
750
|
+
*/
|
|
751
|
+
export const HBS_GAP_NOTE = 'documented as supported in Marketing Cloud Next but currently fails at runtime — no Handlebars helper exists yet';
|
|
752
|
+
/**
|
|
753
|
+
* Split a function-argument string into top-level comma-separated arguments,
|
|
754
|
+
* respecting nested parens/brackets and quoted strings. Returns an empty array
|
|
755
|
+
* for an empty/whitespace-only string.
|
|
756
|
+
* @param argsStr - The argument string (contents between the outer parens).
|
|
757
|
+
* @returns {string[]} Trimmed top-level arguments.
|
|
758
|
+
*/
|
|
759
|
+
function splitArgs(argsStr) {
|
|
760
|
+
if (!argsStr.trim())
|
|
761
|
+
return [];
|
|
762
|
+
const args = [];
|
|
763
|
+
let depth = 0;
|
|
764
|
+
let current = '';
|
|
765
|
+
let quote = null;
|
|
766
|
+
for (const ch of argsStr) {
|
|
767
|
+
if (quote) {
|
|
768
|
+
current += ch;
|
|
769
|
+
if (ch === quote)
|
|
770
|
+
quote = null;
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
switch (ch) {
|
|
774
|
+
case '"':
|
|
775
|
+
case "'": {
|
|
776
|
+
quote = ch;
|
|
777
|
+
current += ch;
|
|
778
|
+
break;
|
|
779
|
+
}
|
|
780
|
+
case '(':
|
|
781
|
+
case '[': {
|
|
782
|
+
depth++;
|
|
783
|
+
current += ch;
|
|
784
|
+
break;
|
|
785
|
+
}
|
|
786
|
+
case ')':
|
|
787
|
+
case ']': {
|
|
788
|
+
depth--;
|
|
789
|
+
current += ch;
|
|
790
|
+
break;
|
|
791
|
+
}
|
|
792
|
+
default: {
|
|
793
|
+
if (ch === ',' && depth === 0) {
|
|
794
|
+
args.push(current.trim());
|
|
795
|
+
current = '';
|
|
796
|
+
}
|
|
797
|
+
else {
|
|
798
|
+
current += ch;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
if (current.trim())
|
|
804
|
+
args.push(current.trim());
|
|
805
|
+
return args;
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Convert a single AMPscript call argument to its Handlebars form: string and
|
|
809
|
+
* numeric literals pass through unchanged; `@var` references lose the `@`.
|
|
810
|
+
* @param arg - A single AMPscript argument.
|
|
811
|
+
* @returns {string} The Handlebars argument.
|
|
812
|
+
*/
|
|
813
|
+
function ampArgToHbs(arg) {
|
|
814
|
+
const t = arg.trim();
|
|
815
|
+
if (/^["']/.test(t))
|
|
816
|
+
return t;
|
|
817
|
+
return stripAmpVars(t);
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Convert a single Handlebars argument to its AMPscript form: string/number/
|
|
821
|
+
* boolean literals pass through; bare identifiers become `@var` references;
|
|
822
|
+
* dotted paths are returned unchanged (caller decides how to flag them).
|
|
823
|
+
* @param arg - A single Handlebars argument.
|
|
824
|
+
* @returns {string} The AMPscript argument.
|
|
825
|
+
*/
|
|
826
|
+
function hbsArgToAmp(arg) {
|
|
827
|
+
const t = arg.trim();
|
|
828
|
+
if (/^["']/.test(t))
|
|
829
|
+
return t;
|
|
830
|
+
if (/^-?\d/.test(t))
|
|
831
|
+
return t;
|
|
832
|
+
if (/^(true|false|null)$/i.test(t))
|
|
833
|
+
return t;
|
|
834
|
+
if (t.startsWith('@'))
|
|
835
|
+
return t;
|
|
836
|
+
if (/^[A-Za-z_]\w*$/.test(t))
|
|
837
|
+
return `@${t}`;
|
|
838
|
+
return t;
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Convert the inner text of a single AMPscript inline expression (`%%=…=%%`)
|
|
842
|
+
* to Handlebars, classifying function calls by the three conversion categories.
|
|
843
|
+
* @param inner - The expression between `%%=` and `=%%` (already trimmed).
|
|
844
|
+
* @param lineNum - Source line number for change/flag tracking.
|
|
845
|
+
* @param changes - Mutable change log.
|
|
846
|
+
* @param flaggedSections - Mutable flagged-section log.
|
|
847
|
+
* @returns {string} The Handlebars replacement string.
|
|
848
|
+
*/
|
|
849
|
+
function convertInlineAmpToHbs(inner, lineNum, changes, flaggedSections) {
|
|
850
|
+
// v(@x) / v(x) → {{x}}
|
|
851
|
+
const vMatch = /^v\s*\(\s*@?([A-Za-z_]\w*)\s*\)$/i.exec(inner);
|
|
852
|
+
if (vMatch) {
|
|
853
|
+
changes.push({ line: lineNum, description: `%%=v(@${vMatch[1]})=%% → {{${vMatch[1]}}}` });
|
|
854
|
+
return `{{${vMatch[1]}}}`;
|
|
855
|
+
}
|
|
856
|
+
// Bare @var / var → {{var}}
|
|
857
|
+
const varMatch = /^@?([A-Za-z_]\w*)$/.exec(inner);
|
|
858
|
+
if (varMatch) {
|
|
859
|
+
changes.push({ line: lineNum, description: `%%=${inner}=%% → {{${varMatch[1]}}}` });
|
|
860
|
+
return `{{${varMatch[1]}}}`;
|
|
861
|
+
}
|
|
862
|
+
// FunctionName(args)
|
|
863
|
+
const fnMatch = /^([A-Za-z_]\w*)\s*\(([\s\S]*)\)$/.exec(inner);
|
|
864
|
+
if (fnMatch) {
|
|
865
|
+
const fnName = fnMatch[1];
|
|
866
|
+
const key = fnName.toLowerCase();
|
|
867
|
+
const hbsArgs = splitArgs(fnMatch[2]).map((a) => ampArgToHbs(a));
|
|
868
|
+
// Category A — mapped helper.
|
|
869
|
+
const helper = AMP_TO_HANDLEBARS[key];
|
|
870
|
+
if (helper) {
|
|
871
|
+
changes.push({
|
|
872
|
+
line: lineNum,
|
|
873
|
+
description: `%%=${fnName}(…)=%% → {{${helper} …}}`,
|
|
874
|
+
});
|
|
875
|
+
return `{{${helper}${hbsArgs.length > 0 ? ' ' + hbsArgs.join(' ') : ''}}}`;
|
|
876
|
+
}
|
|
877
|
+
// Category C — mcnHandlebarsGap (distinct note from Category B).
|
|
878
|
+
if (AMP_MCN_HANDLEBARS_GAP.has(key)) {
|
|
879
|
+
flaggedSections.push({
|
|
880
|
+
line: lineNum,
|
|
881
|
+
code: `%%=${inner}=%%`,
|
|
882
|
+
reason: `${fnName} is ${HBS_GAP_NOTE}`,
|
|
883
|
+
});
|
|
884
|
+
return `{{!-- MANUAL_REWRITE_REQUIRED: ${fnName} is ${HBS_GAP_NOTE} --}}`;
|
|
885
|
+
}
|
|
886
|
+
// Category B — no Handlebars counterpart.
|
|
887
|
+
flaggedSections.push({
|
|
888
|
+
line: lineNum,
|
|
889
|
+
code: `%%=${inner}=%%`,
|
|
890
|
+
reason: `AMPscript function '${fnName}' has no Handlebars equivalent`,
|
|
891
|
+
});
|
|
892
|
+
return `{{!-- MANUAL_REWRITE_REQUIRED: AMPscript function '${fnName}' has no Handlebars equivalent --}}`;
|
|
893
|
+
}
|
|
894
|
+
// Anything else (complex expression).
|
|
895
|
+
flaggedSections.push({
|
|
896
|
+
line: lineNum,
|
|
897
|
+
code: `%%=${inner}=%%`,
|
|
898
|
+
reason: 'complex AMPscript expression has no direct Handlebars form',
|
|
899
|
+
});
|
|
900
|
+
return `{{!-- MANUAL_REWRITE_REQUIRED: ${inner} --}}`;
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Convert AMPscript to MCN Handlebars using deterministic, data-driven rules.
|
|
904
|
+
*
|
|
905
|
+
* Inline expressions (`%%=Fn(args)=%%`, `%%=v(@x)=%%`, `%%var%%`) are mapped via
|
|
906
|
+
* the three conversion categories built from ampscript-data's
|
|
907
|
+
* `handlebarsEquivalent` / `mcnHandlebarsGap` fields:
|
|
908
|
+
* - **A** — mapped helper → `{{helper …}}`
|
|
909
|
+
* - **B** — no counterpart → `{{!-- MANUAL_REWRITE_REQUIRED … --}}`
|
|
910
|
+
* - **C** — `mcnHandlebarsGap` → `{{!-- MANUAL_REWRITE_REQUIRED … (distinct note) --}}`
|
|
911
|
+
*
|
|
912
|
+
* Procedural AMPscript blocks (`%%[ … ]%%`, `SET`/`VAR`/`IF`/`FOR`) have no
|
|
913
|
+
* Handlebars counterpart (Handlebars cannot assign variables or run imperative
|
|
914
|
+
* control flow) and are flagged MANUAL_REWRITE_REQUIRED.
|
|
915
|
+
* @param code - AMPscript source code (may include HTML context).
|
|
916
|
+
* @returns {ConversionResult} Converted Handlebars, change log, and flagged sections.
|
|
917
|
+
*/
|
|
918
|
+
export function ampscriptToHandlebars(code) {
|
|
919
|
+
const changes = [];
|
|
920
|
+
const flaggedSections = [];
|
|
921
|
+
const lines = code.split('\n');
|
|
922
|
+
const outputLines = [];
|
|
923
|
+
let inBlock = false;
|
|
924
|
+
for (const [i, line] of lines.entries()) {
|
|
925
|
+
const lineNum = i + 1;
|
|
926
|
+
const trimmed = line.trim();
|
|
927
|
+
// Track multi-line %%[ … ]%% blocks — flag the whole block once.
|
|
928
|
+
if (inBlock) {
|
|
929
|
+
if (/\]%%/.test(line))
|
|
930
|
+
inBlock = false;
|
|
931
|
+
continue;
|
|
932
|
+
}
|
|
933
|
+
if (/%%\[/.test(line)) {
|
|
934
|
+
if (!/\]%%/.test(line))
|
|
935
|
+
inBlock = true;
|
|
936
|
+
outputLines.push('{{!-- MANUAL_REWRITE_REQUIRED: AMPscript block (SET/VAR/IF/FOR) has no Handlebars equivalent — Handlebars cannot assign variables or run imperative control flow --}}');
|
|
937
|
+
flaggedSections.push({
|
|
938
|
+
line: lineNum,
|
|
939
|
+
code: trimmed.slice(0, 80),
|
|
940
|
+
reason: 'AMPscript procedural block has no Handlebars counterpart',
|
|
941
|
+
});
|
|
942
|
+
continue;
|
|
943
|
+
}
|
|
944
|
+
// Convert inline expressions on this line.
|
|
945
|
+
const converted = line
|
|
946
|
+
.replaceAll(/%%=\s*([\s\S]*?)\s*=%%/g, (_full, raw) => convertInlineAmpToHbs(raw.trim(), lineNum, changes, flaggedSections))
|
|
947
|
+
.replaceAll(/%%([A-Za-z_]\w*)%%/g, (_full, v) => {
|
|
948
|
+
changes.push({ line: lineNum, description: `%%${v}%% → {{${v}}}` });
|
|
949
|
+
return `{{${v}}}`;
|
|
950
|
+
});
|
|
951
|
+
outputLines.push(converted);
|
|
952
|
+
}
|
|
953
|
+
return { convertedCode: outputLines.join('\n'), changes, flaggedSections };
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Convert MCN Handlebars to AMPscript using deterministic, data-driven rules.
|
|
957
|
+
*
|
|
958
|
+
* Inline helper calls (`{{helper args}}`) whose helper maps back to an AMPscript
|
|
959
|
+
* function (via HANDLEBARS_TO_AMP) become `%%=Fn(args)=%%`; bare variables
|
|
960
|
+
* (`{{name}}`) become `%%=v(@name)=%%`. Block helpers (`{{#each}}`…), partials
|
|
961
|
+
* (`{{> …}}`), dotted binding paths, and unknown helpers are flagged
|
|
962
|
+
* MANUAL_REWRITE_REQUIRED.
|
|
963
|
+
* @param code - Handlebars source code (may include HTML context).
|
|
964
|
+
* @returns {ConversionResult} Converted AMPscript, change log, and flagged sections.
|
|
965
|
+
*/
|
|
966
|
+
export function handlebarsToAmpscript(code) {
|
|
967
|
+
const changes = [];
|
|
968
|
+
const flaggedSections = [];
|
|
969
|
+
let lineCursor = 1;
|
|
970
|
+
let lastIndex = 0;
|
|
971
|
+
const convertedCode = code.replaceAll(/\{\{([\s\S]*?)\}\}/g, (full, raw, offset) => {
|
|
972
|
+
lineCursor += countNewlines(code.slice(lastIndex, offset));
|
|
973
|
+
lastIndex = offset;
|
|
974
|
+
const lineNum = lineCursor;
|
|
975
|
+
const inner = raw.trim();
|
|
976
|
+
// Comments — preserve as an AMPscript comment.
|
|
977
|
+
if (inner.startsWith('!')) {
|
|
978
|
+
return full;
|
|
979
|
+
}
|
|
980
|
+
// Block helpers / partials / closing tags — no deterministic AMPscript form.
|
|
981
|
+
if (/^[#/>]/.test(inner)) {
|
|
982
|
+
flaggedSections.push({
|
|
983
|
+
line: lineNum,
|
|
984
|
+
code: full,
|
|
985
|
+
reason: 'Handlebars block helper / partial has no direct AMPscript equivalent',
|
|
986
|
+
});
|
|
987
|
+
return `%%-- MANUAL_REWRITE_REQUIRED: ${full} --%%`;
|
|
988
|
+
}
|
|
989
|
+
// Strip a leading no-escape ampersand: {{& x}}.
|
|
990
|
+
const body = inner.replace(/^&\s*/, '');
|
|
991
|
+
// Helper with arguments: {{helper arg1 arg2 …}}
|
|
992
|
+
const fnMatch = /^([A-Za-z_]\w*)\s+(.+)$/.exec(body);
|
|
993
|
+
if (fnMatch) {
|
|
994
|
+
const helperName = fnMatch[1];
|
|
995
|
+
const key = helperName.toLowerCase();
|
|
996
|
+
const ampName = HANDLEBARS_TO_AMP[key];
|
|
997
|
+
const ampArgs = splitArgs(fnMatch[2]).map((a) => hbsArgToAmp(a));
|
|
998
|
+
if (ampName) {
|
|
999
|
+
changes.push({
|
|
1000
|
+
line: lineNum,
|
|
1001
|
+
description: `{{${helperName} …}} → %%=${ampName}(…)=%%`,
|
|
1002
|
+
});
|
|
1003
|
+
return `%%=${ampName}(${ampArgs.join(', ')})=%%`;
|
|
1004
|
+
}
|
|
1005
|
+
flaggedSections.push({
|
|
1006
|
+
line: lineNum,
|
|
1007
|
+
code: full,
|
|
1008
|
+
reason: `Handlebars helper '${helperName}' has no AMPscript equivalent`,
|
|
1009
|
+
});
|
|
1010
|
+
return `%%-- MANUAL_REWRITE_REQUIRED: Handlebars helper '${helperName}' has no AMPscript equivalent --%%`;
|
|
1011
|
+
}
|
|
1012
|
+
// Bare single identifier: {{name}} → %%=v(@name)=%%
|
|
1013
|
+
const bareMatch = /^([A-Za-z_]\w*)$/.exec(body);
|
|
1014
|
+
if (bareMatch) {
|
|
1015
|
+
changes.push({
|
|
1016
|
+
line: lineNum,
|
|
1017
|
+
description: `{{${bareMatch[1]}}} → %%=v(@${bareMatch[1]})=%%`,
|
|
1018
|
+
});
|
|
1019
|
+
return `%%=v(@${bareMatch[1]})=%%`;
|
|
1020
|
+
}
|
|
1021
|
+
// Dotted binding path or other expression — context-specific.
|
|
1022
|
+
flaggedSections.push({
|
|
1023
|
+
line: lineNum,
|
|
1024
|
+
code: full,
|
|
1025
|
+
reason: 'Handlebars binding path requires context-specific AMPscript mapping',
|
|
1026
|
+
});
|
|
1027
|
+
return `%%-- MANUAL_REWRITE_REQUIRED: ${full} --%%`;
|
|
1028
|
+
});
|
|
1029
|
+
return { convertedCode, changes, flaggedSections };
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Count newline characters in a string (helper for line tracking in
|
|
1033
|
+
* handlebarsToAmpscript).
|
|
1034
|
+
* @param s - Input string.
|
|
1035
|
+
* @returns {number} Number of `\n` characters.
|
|
1036
|
+
*/
|
|
1037
|
+
function countNewlines(s) {
|
|
1038
|
+
let n = 0;
|
|
1039
|
+
for (const ch of s) {
|
|
1040
|
+
if (ch === '\n')
|
|
1041
|
+
n++;
|
|
1042
|
+
}
|
|
1043
|
+
return n;
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Convert SSJS to MCN Handlebars deterministically via a two-step chain:
|
|
1047
|
+
* SSJS → AMPscript (`ssjsToAmpscript`) → Handlebars (`ampscriptToHandlebars`).
|
|
1048
|
+
*
|
|
1049
|
+
* Because Handlebars is declarative, most imperative SSJS has no Handlebars
|
|
1050
|
+
* counterpart and is conservatively flagged MANUAL_REWRITE_REQUIRED; inline
|
|
1051
|
+
* `Platform.Function.X(…)` calls that map through AMPscript to a Handlebars
|
|
1052
|
+
* helper are converted.
|
|
1053
|
+
* @param code - SSJS source code (may include `<script runat="server">` tags).
|
|
1054
|
+
* @returns {ConversionResult} Converted Handlebars, combined change log, and flagged sections.
|
|
1055
|
+
*/
|
|
1056
|
+
export function ssjsToHandlebars(code) {
|
|
1057
|
+
const toAmp = ssjsToAmpscript(code);
|
|
1058
|
+
const toHbs = ampscriptToHandlebars(toAmp.convertedCode);
|
|
1059
|
+
return {
|
|
1060
|
+
convertedCode: toHbs.convertedCode,
|
|
1061
|
+
changes: [
|
|
1062
|
+
...toAmp.changes.map((c) => ({
|
|
1063
|
+
line: c.line,
|
|
1064
|
+
description: `[SSJS→AMPscript] ${c.description}`,
|
|
1065
|
+
})),
|
|
1066
|
+
...toHbs.changes.map((c) => ({
|
|
1067
|
+
line: c.line,
|
|
1068
|
+
description: `[AMPscript→Handlebars] ${c.description}`,
|
|
1069
|
+
})),
|
|
1070
|
+
],
|
|
1071
|
+
flaggedSections: [...toAmp.flaggedSections, ...toHbs.flaggedSections],
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
701
1074
|
/**
|
|
702
1075
|
* Rewrite AMPscript code to be compatible with Marketing Cloud Next.
|
|
703
1076
|
*
|