Skip to content

03.a ~ Migrating to v10

Jörn Berkefeld edited this page Oct 12, 2026 · 14 revisions

Migrating to v10

v10 updates project tooling and reorganizes locally retrieved Content Builder assets. Upgrade the configuration first, then migrate each branch's own files without changing their content or promoting pending work.

See Updating mcdev for general update instructions and earlier migration guides, or the v9 release notes for v9 behavior.

Need to know

Upgrade Node before upgrading tooling

The Node.js range is ^22.22.2 || >=24.15.0:

  • Node 22.22.2 and later Node 22 patches are supported.
  • Node 24.15.0 and newer stable versions are supported.
  • Node 20, 21, all Node 23 versions, earlier Node 22/24 patches and prereleases are unsupported.

Check node --version in the terminal, CI runner and containers. Both CLI usage and programmatic imports require a runtime that satisfies the package's engine range.

Comment: CI is configured to exercise the exact Node 22.22.2 and 24.15.0 floors alongside the current Node 25 patch.

Migrate assets on every branch

Run the migration in a quiet window between sprints, before you prepare the next promotion, not while a promotion is in flight.

Run mcdev upgrade once on prod, commit the configuration changes and downmerge them. Then run mcdev migrate separately on each branch, starting at prod and working down to dev. This keeps configuration decisions in one place while migrating each branch's own asset files.

  1. Pause work and promotions. Include all participating environment and existing release branches, plus active feature and hotfix branches that will merge back later.
  2. Save each branch's starting point. Start from a clean, committed snapshot and record its commit ID. Record baseline refs, exact comparison range strings, saved refs and merge bases used for promotions. Inventory pending edits, additions and deletions in every BU folder so you can check them afterwards. Do not promote work merely to make branches equal.
  3. Upgrade once on the production branch and downmerge. Check that the terminal, CI and containers use supported Node.js (^22.22.2 || >=24.15.0), install mcdev v10, then run mcdev upgrade on prod. Review required replacements and preserve custom settings. Upgrade refreshes .mcdevrc.json subtype selections to current defaults: reapply your intended custom scope and update custom selectors, scripts, CI and type/path filters. Commit these configuration/tooling changes and downmerge them through your branches before migrating assets. You only need to make the configuration decisions once. See Node requirements, configuration updates and backups.
  4. Migrate and commit each branch, from production down to development. Switch to your production branch and make sure you have no uncommitted changes. If you do, commit the changes you want to keep or discard those you no longer need. Run mcdev migrate cred/*, check the result, then commit the migration changes. Repeat on each environment and existing release branch until you have finished dev. Replace cred with your credential name and repeat for any other credentials, so all BU folders on each branch are covered. Resolve reported conflicts and investigate skipped assets before moving on. Keep content edits and formatting out of the migration commit. For example, with four branches (where release has already been merged into uat), start with a clean working tree on each branch and run:
   # 1. Production
   git switch prod
   mcdev migrate cred/*
   git add .
   git commit -nm "migrated assets to match mcdev v10"

   # 2. UAT
   git switch uat
   mcdev migrate cred/*
   git add .
   git commit -nm "migrated assets to match mcdev v10"

   # 3. Release (already merged into UAT)
   git switch release
   mcdev migrate cred/*
   git add .
   git commit -nm "migrated assets to match mcdev v10"

   # 4. Development
   git switch dev
   mcdev migrate cred/*
   git add .
   git commit -nm "migrated assets to match mcdev v10"

-n skips commit hooks so they do not reformat assets during the migration commit. If a branch has no migration changes, there is nothing to commit. See local command behavior for details.

  1. Rehearse merges and deployment packages before unfreezing. Test the expected environment and release merges without advancing the real branches. Check the resulting files even if Git reports no conflicts. Run the configured createDeltaPkg (CDP) comparisons and compare their output with the pending work recorded in step 2: moves alone must not become deployment work, and genuine changes must still be present.
  2. Resume normal work. Keep the team's existing merge and ancestry practices. Cut new release branches from migrated branches. If an older branch returns later, migrate its own BU trees and check its merge before integrating it.

Comment: Once a BU tree is fully migrated, running the migration again makes no further moves unless historical files have been added back. The command does not check Git state or create commits for you.

How createDeltaPkg treats the migration

createDeltaPkg (CDP) automatically skips assets that were only moved or renamed by the migration. If an asset was also changed in earlier commits included in your comparison, CDP still includes that real change. Commit the migration before running CDP.

If a migration-only asset does end up in the deployment package, remember that this migration is about assets only. Deploying an asset that already exists with the same content in the target environment does not change its content. The exception is an asset you do not want to exist in a higher environment at all: deploying it could create it there. Check the package and exclude any such assets before deploying.

For special cases and technical details, see detailed delta semantics and skipped migration caveats.

If a later merge reports conflicts

Migrating both branches does not guarantee a conflict-free merge. It preserves each branch's content and gives both branches the same layout, but Git may still need help connecting the old and new paths. A moved file on one branch and an edited file on another do not always conflict: Git can often apply the edit at the new path. But do not rely on that for every asset.

If a merge reports conflicts, do not blindly keep "ours" or "theirs" for all files. That can discard pending edits or bring back deleted assets. Check the recorded pre-migration versions and preserve the intended changes from both branches. If the conflict is only about the file's location, use the manual mapping and check both its folder and suffix, plus every companion file. If you cannot confidently separate a move from a real content change, stop and ask your team's Git maintainer for help before completing the merge.

Do not create a matching starting point by copying or retrieving another branch's content.

Comment: Git compares both branches with their shared starting point, called the merge base. That starting point can still contain old paths even after both branches are migrated. Git must recognize the renames; if it cannot, it may report conflicts such as two files added at the same new path. Even a clean merge needs checking for leftover old paths or duplicate assets.

For difficult cases, a Git maintainer can reconcile temporary copies of the merge base and both branch snapshots after applying the same layout mapping to all three. This lets them compare content at matching paths and use the checked result to complete a normal merge. It is not an automatic mcdev migrate feature, does not rewrite published history, and does not change the real merge base. Genuine conflicting content edits still need a decision.

Nice to know

Asset layout migration

Content Builder assets live in group directories below the retrieve tree. v10 renames one group and regroups several historical groups into a smaller set, and mcdev migrate performs those local moves for one BU. asset-message becomes asset-email: the directory changes from asset/message/ to asset/email/ and the exact metadata suffix from .asset-message-meta.* to .asset-email-meta.*. Salesforce API names and IDs do not change. The email members remain templatebasedemail, htmlemail, textonlyemail and base message (ID 5). The full mapping, including the groups that move into asset/webstudio/, is in the manual instructions.

Alternatives to the recommended command

Option B: manual moves and renames

Manual migration is equally supported. From a clean committed snapshot, inventory each owning JSON and all extracted companions. Use the exact assetType.name, not the old folder alone, to select a mapping. Apply it to the configured retrieve, deploy and template trees you actually use:

  • templatebasedemail, htmlemail, textonlyemail, message: asset/message/ → asset/email/; .asset-message-meta.* → .asset-email-meta.*.
  • jsonmessage: asset/message/, or the older asset/asset/, → asset/mobile/; matching .asset-message-meta.* or .asset-asset-meta.* → .asset-mobile-meta.*. jsonmessagetemplate stays in asset/template/.
  • webpage, webtemplate: asset/asset/ → asset/webstudio/; .asset-asset-meta.* → .asset-webstudio-meta.*.
  • cloudpages, landingpage, microsite, interactivecontent: asset/cloudpage/ → asset/webstudio/; .asset-cloudpage-meta.* → .asset-webstudio-meta.*.
  • jscoderesource, csscoderesource, jsoncoderesource, rsscoderesource, textcoderesource, xmlcoderesource: asset/coderesource/ → asset/webstudio/; .asset-coderesource-meta.* → .asset-webstudio-meta.*.

Move the owner plus all nested HTML, text, extracted code and binary companions. Change only subtype directory and exact metadata suffix; retain keys, API names/IDs, existing encoding, child-relative paths, bytes and modes. Do not change unrelated files or assume every old message is an email. Resolve duplicate destinations manually without overwriting either copy. Complete both directory and suffix changes before packaging, verify content is unchanged and commit mechanical moves separately from edits. Avoid formatting churn during migration.

Option C: re-retrieve (not recommended!)

mcdev retrieve cred/bu -m asset can obtain the new layout, but downloads current server-side content. It may introduce unexpected changes or replace pending local versions; it does not preserve Git snapshots and is not recommended for divergent promotion branches. Review obsolete leftovers and collisions rather than assuming retrieval reconciles every old path. If a BU was refreshed already, separate server-derived edits from mechanical changes and resolve duplicates first; do not call an arbitrary refresh migration-only.

Local migration command details

The checklist uses mcdev migrate cred/* from the project root. The argument selects the credential/Business Unit to migrate and mcdev reports X moved, Y conflicts, Z skipped. Only the resolved BU's asset/ tree under directories.retrieve is changed; deploy/template trees need manual migration.

Pass a single cred/bu to migrate one configured Business Unit, or cred/* to migrate every BU of one credential in a single run. With cred/*, mcdev reports the results per BU, and a conflict in one BU does not stop the remaining BUs from being migrated. Repeat for other credentials. cred/bu remains the safer choice when you want to review each BU separately. Omit the argument (mcdev migrate) for interactive credential/BU selection.

  1. Start from a clean committed snapshot: the command does not check Git state itself, so a baseline commit is what makes the migration diff reviewable. Review the resolved credential/BU and the reported move counts before continuing. Already-current assets are a no-op.
  2. The planner classifies each owning JSON by its assetType.name and resolves the current group from it, so a historical group that holds several asset types is split correctly. It does not add IDs or rewrite JSON: keys, nested relative paths, content bytes and file modes are preserved, and only the group directory and the exact .asset-<group>-meta. suffix change.
  3. Destination collisions block the whole asset: if any destination path already exists — even with identical content — the asset stays in place and is reported as a conflict instead of being moved partially. Resolve the reported conflicts and re-run.
  4. After success, review and create a new, separate migration commit before createDeltaPkg or switching branches. Keep the commit purely mechanical — identical bytes, no content edits — and separate from other work.

Comment: The command performs no authentication, server requests, retrieval, npm/configuration updates, Git fetch/pull, branch switches, staging, stashing, resetting or automatic commits. Applicability comes from the actual file layout, not a migration-completion flag.

While it runs, the command states up front that assets are moved to their new folders, that only the local retrieve folder is reorganized and nothing changes on the server, and that the run can take a while. Each historical group is processed with its own progress bar. Asset types that have no current mapping are no longer skipped silently: they stay where they are and are reported with a count, for example jsonmessagetemplate (12).

Detailed delta semantics

For normal two-dot comparisons (base..target) and bare-base comparisons (base, resolved to base..HEAD), a byte-preserving mcdev migrate move is not deployment work. Before applying path filters, the delta adapter pairs historical and current paths against the allowlisted historical-to-current group pairs; filtering then scopes the resulting changes. It compares the committed blobs at both endpoints of the range; when they are byte-identical, both the old and the new path are dropped, so a pure regrouping move produces neither an addition nor a deletion. Git's rename similarity does not decide this, and equivalent delete/add pairs across one or several commits are handled the same way.

That gives three practical outcomes:

  • A pure move adds nothing to the package, even when the migration commit sits inside the range — the temporary adapter, not the commit label, neutralizes it.
  • Genuine edits, additions and deletions still surface. A moved asset that was also edited stays actionable at its new path; for a non-rename pairing only the old path is dropped so the asset is not falsely reported as deleted.
  • Only migration-equivalent pairs are suppressed. Key changes, cross-BU moves and unrelated renames are not migration equivalents. Suppression is decided by content bytes, so a mode-only difference on an otherwise identical file is not surfaced separately. If an extracted companion is deleted but its owning asset survives at the current path, the owner is selected for an update rather than treating the companion as an asset deletion. A genuinely deleted asset remains a deletion.

Uncommitted moves are not completed comparison baselines. A separate migration commit remains inside ranges that span it, and old merge bases or saved refs can require compatibility even when both tips are current, so commit the migration before you build a package or switch branches.

Comment: Programmatic explicit lists: a supplied diffArr array without an explicit filter remains a caller-managed list of changes. It skips the Git comparison and the migration suppression described in this section, retaining ordinary template discovery instead. The caller is responsible for correct BU/type/key scoping, actions and current source files. Mapping lookup uses the original range string (which need not be a valid Git ref for an explicit list), with sourceTargetMapping as fallback. An empty array ([]) skips per-mapping builds and creates no package output. It is not a universal no-cleanup guarantee: with explicit purge enabled and multiple source-target mappings, deploy folders are purged up front before empty deltas are skipped. An explicit filter ignores diffArr as before and uses Git-backed copy mode.

These safeguards apply to Git-derived deltas and are available since v10 — do not rely on them in an older installed release.

Comment: Compatibility retirement checklist: verify all still-supported promotion ranges and old merge bases first. Remove only the delta adapter function, its call/import where applicable, dedicated tests and temporary delta-compatibility guidance; do not delete the entire shared module. Retain the migration command, its shared mappings/helpers and permanent manual instructions. Ordinary delta processing and local migration must still work independently. No retirement release is selected.

If the migration is skipped

Skipping mcdev migrate on a branch leaves that branch on the historical paths while a migrated branch (or a migrated release ref) uses the new ones. Raw Git then presents the regroup as a rename or as a delete/add pair per asset, so a plain branch or PR review looks like a large structural change rather than a move.

What the delta adapter does with that mismatch is directional, and which endpoint is migrated decides the outcome:

  • If the baseline endpoint is still unmigrated and the branch being packaged is migrated, the committed blobs match and the pair is suppressed — the neglect does not by itself create deploy instructions, and an asset that also changed still surfaces once, at its new path.
  • If the baseline is migrated and the branch you are packaging is not, the comparison runs the other way. The adapter only rewrites a historical group to its current group, so a move back toward the old layout is not recognized and is reported as a change. A package built from that range would reintroduce old-layout paths instead of remaining a no-op.

Migrate and check every BU tree on each participating branch before resuming routine promotions. Downmerge the upgrade configuration, then migrate each branch's own asset files as explained in the checklist. Packaging from an unmigrated source can carry old-layout paths onward.

Comment: Saved historical baselines may still need forward-only delta compatibility, even after every active branch has been migrated. That compatibility helps build deployment packages; it does not resolve Git merge conflicts.

Understand which BU is compared

Without --filter, configured templating selects the source BU from the chosen source market list and compares that same BU path at both Git endpoints. It does not compare the lower environment's BU directory against the higher environment's BU directory.

Mapping selection looks up the exact supplied range string in options.deployment.branchSourceTargetMapping and falls back to sourceTargetMapping. For example, on branch uat, run mcdev createDeltaPkg prod with the prod mapping selecting source cred/uat and target cred/prod. Bare prod resolves to prod..HEAD: compare prod:retrieve/cred/uat/** against uat:retrieve/cred/uat/**, then template the checked-out UAT source for PROD output. prod and prod..HEAD need not select the same configured mapping even when they resolve to the same endpoints. See Git-based deployments for configuration.

Review configuration updates and backups

Before running the v10 mcdev upgrade or initializing an existing project, save project-specific configuration and review the changes on a branch. Tooling updates include:

  • eslint.config.js
  • .prettierrc
  • .prettierignore
  • .vscode/settings.json
  • .vscode/extensions.json
  • mcdev-managed scripts, tooling dependencies, engines.node and type in package.json

The project becomes type: "module". An existing valid engines.node range is preserved only if it is a subset of mcdev's supported range; missing, invalid or broader ranges are replaced with mcdev's engine range. Review Node helpers and configuration for ESM compatibility.

Configuration files are processed and written sequentially, not as a coupled preflight. Missing files are created; identical files are reported as needing no update. Other differing files receive an optional Update <file>? prompt with Yes selected by default. Declining skips only that file and does not fail the upgrade. Historical version-gated forcedUpdates replacements bypass the prompt, as do ordinary replacements when --yes / --skipInteraction is supplied. Package scripts and dependency updates run after the configuration-file phase, without a separate approval prompt.

Replaced and retired files are renamed to .BAK; if that backup already exists, it is replaced with the latest pre-upgrade file. Repeated upgrades therefore continue and refresh the backup before each replacement or retirement. File failures prevent npm installation and completion, but earlier writes remain and other files may already have been processed. File inspection, read and rename errors are not silently ignored. Start from a clean committed state and review the diff: .BAK files are a convenience, not rollback protection. Completion/version persistence belongs only at the end, after the file and npm phases and the final save succeed.

Linting and formatting are separate

Generated projects use these commands:

  • npm run lint: diagnostics without automatic fixes.
  • npm run lint:fix: apply ESLint fixes.
  • npm run format: apply Prettier formatting.
  • npm run format:check: check formatting without writing.

Replace old CI invocations of eslint-check with lint, and run format:check separately. Only exact legacy boilerplate commands are retired: build: sfmc-build all, build-cp: sfmc-build cloudPages, build-email: sfmc-build emails and eslint-check: eslint. Custom or chained build commands are preserved. The managed lint, lint:fix, format and format:check scripts are set to the current defaults without a separate approval prompt, replacing any existing commands under those names.

For pre-v10 migrations and their retries, retired tooling dependencies are removed from both dependencies and devDependencies, including eslint-config-ssjs, generated eslint-plugin-prettier and prettier-plugin-sql. Already-current projects do not undergo perpetual dependency retirement. The generated tooling dependency list does not include sfmc-boilerplate; an existing declaration is not automatically removed. Review the installed package and lockfile changes. mcdev's own contributor formatting policy is separate from the generated-project policy.

The generated ESLint configuration uses canonical SFMC presets for standalone and embedded server-side code, with scoped Unicorn recommended rules and SFMC compatibility overrides. Generic core/JSDoc/browser layers must not override SSJS parsing, globals or ES5 script semantics. Browser ESM rules cover real JavaScript files under retrieve/ and deploy/, not extracted server blocks. Node ESM rules cover .mcdev-validations.js, recursive lib/**/*.js helpers and eslint.config.js.

Prettier with prettier-plugin-sfmc owns formatting. eslint-config-prettier runs last to disable conflicting lint rules; it does not execute Prettier. Review editor language associations and preserve custom settings when approving replacement: HTML stays HTML, standalone AMPscript uses ampscript, and SSJS uses ssjs.

Tooling details and integration consumers

Generated tooling versions are derived from the installed mcdev package's manifest for the names in its bundled tooling dependency list, not an independent version map. Refer to that manifest for the SFMC, JSDoc and Unicorn versions rather than relying on older pinned examples.

The Node/runtime and upgrade safeguards also apply to init/join. Tooling defaults come from mcdev's package manifest. Only equal/newer simple versions in devDependencies, optionally prefixed with ^ or ~, are preserved. Missing, older or non-simple specifications (such as tags, compound ranges or local paths) are installed at the default version. A declaration in dependencies alone does not satisfy this check. A legacy .beautyamp.json is retained as .beautyamp.json.BAK. The generated .prettierrc uses the SFMC plugin and base options with only the .mcdev-validations.js trailingComma override; separate SQL/SSJS overrides are unnecessary.

When npm fails, retain backups, repair installation and rerun upgrade: tooling changes have no automatic rollback. Retrying is not a guarantee of lockfile or installed-tree reconciliation. The dependency check reads declarations, not node_modules or the lockfile; if declarations already match and no retired dependencies remain, it can skip npm even when install artifacts are missing. A startup banner alone does not prove completion. Review the full generated ESLint configuration before reapplying custom overrides. Classic browser scripts need a narrowly scoped sourceType: 'script' override; the browser ESM default does not change how deployed pages load scripts. HTML-extracted server blocks must not inherit browser/Node presets; the HTML processor does not lint inline client scripts. Node helpers use Node ESM rather than CommonJS globals. Avoid retaining old imports, prettier/prettier rules or manual JSDoc type-normalization mappings.

The updated .vscode/extensions.json recommends joernberkefeld.sfmc-language and marks xnerd.ampscript-language and FiB.beautyAmp as conflicting. Disable those extensions in the workspace if already installed; this file does not uninstall them.

Programmatic integrations must await and check the boolean result of File.writeJSONToFile(directory, filename, content) and File.saveConfigFile(properties, version): true means the write succeeded; false means the write failed and was logged. saveConfigFile restores the previous in-memory properties.version on failure. Do not interpret a fulfilled promise alone as a successful save.

File.beautify_beautyAmp() is removed without an alias; programmatic consumers should use mcdev's normal formatting/write path backed by prettier-plugin-sfmc. Existing retrieve/deploy formatting controls remain separate from project-wide Prettier: options.formatOnSave, command --format / --no-format, and formatErrorLog.

Review the VS Code extension's bundled CLI/optional peer range and version guard, the MCP server's mcdev dependency and published wiki bundle, and eslint-plugin-mcdev's peer range and metadata-validation imports.

Validate your migrated project

Run npm run lint and npm run format:check first. The generated defaults cover both retrieve and deploy trees, but exclude docs/, logs/, node_modules/, template/ and .BAK files/directories; Prettier also excludes package-lock.json. These checks do not validate excluded backups or templates. Inspect diagnostics before choosing fixes; avoid a blanket autofix of retrieved assets. Review any deliberate formatting changes, especially mixed-language assets and comments. Repeat checks after resolving issues, and run your project's own build/deploy validation separately.

Clone this wiki locally