关于 contentstack-migration-companion
引导项目从 Contentful 迁移到 Contentstack 的工作流技能,先迁移内容,再处理网站代码。它在步骤间传递命令输出和迁移产物,并在账户操作或修改代码前征求确认。
主要功能
- 01指导迁移 Contentful 内容
- 02后续迁移网站代码
- 03按顺序传递迁移产物
- 04隔离每次迁移的工作区
- 05敏感步骤前请求确认
适用场景
- 01将内容类型和条目迁至 Contentstack
- 02内容迁移后处理网站的 CMS 读取代码
- 03确保各步骤沿用一致的迁移输出
- 04登录账户或编辑代码前暂停确认
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ contentstack/ contentstack-agent-skills --skill contentstack-migration-companion安装后在 Claude Code 中输入/contentstack-migration-companion确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/CONTENTFUL_TO_CONTENTSTACK_MIGRATION_CONTEXT.mdscripts/_lib.sh01_contentful_residue.sh02_field_access.sh03_sdk_init.sh04_query_builder.sh05_references.sh06_richtext.sh07_assets.sh08_locales.sh09_graphql.sh10_livepreview.sh11_build_typecheck.sh12_secrets.sh13_todos_report.shcheck_prereqs.pylog.shparse_import_summary.pyREADME.mdrun-all.sh详细介绍
contentstack-migration-companion
引导用户将项目从 Contentful 迁移到 Contentstack —— 先通过 Contentstack CLI migrate 插件迁移内容(内容类型、条目、资源、本地化内容),然后迁移从 CMS 读取数据的网站代码。
这是一个顺序工作流,每一步的输出都会被下一步使用(create 命令会生成一个已填充内容的堆栈,以及包含凭据的 bundle,供代码迁移使用)。请将每条命令打印的产物路径和数量视为必须捕获并传递到后续步骤的状态。
操作原则
请始终遵循以下原则——它们比任何单条命令都重要:
- 在唯一的会话工作区中工作。 在第 1 步开始时,运行以下命令创建一个会话专属目录:
记录打印出的SESSION_ID=$(date +%Y%m%d-%H%M%S) && SESSION_DIR="/tmp/migrate-to-cs/$SESSION_ID" && mkdir -p "$SESSION_DIR" && echo "SESSION_DIR=$SESSION_DIR"SESSION_DIR值(例如/tmp/migrate-to-cs/20260608-143022),并在本次迁移的每条 shell 命令中将其作为具体字面值使用——不要重新生成。这样可以隔离每次迁移,避免并发会话和重复运行时发生冲突。如果用户指定了其他工作区,请改用他们提供的路径。 - 随此技能附带的脚本和参考资料位于技能文件旁边——通过
{SKILL_DIR}定位。 此技能随附辅助脚本(scripts/文件夹)和参考文档(references/文件夹),它们与SKILL.md文件位于同一目录中。凡是这些说明中出现{SKILL_DIR},都应替换为加载此SKILL.md所在目录的绝对路径,也就是技能自身的安装目录。不要假设路径固定。 路径会因 AI 助手、操作系统以及技能是按项目安装还是按用户安装而异——例如,可能是<project>/.claude/skills/contentstack-migration-companion、~/.claude/skills/contentstack-migration-companion,也可能是%USERPROFILE%\.claude\skills\contentstack-migration-companion这样的 Windows 路径。确定真实路径一次(即你读取此SKILL.md的文件夹;如果不确定,请在工作区和主目录中搜索*/contentstack-migration-companion/SKILL.md),并在 shell 命令开头将其设为变量(SKILL_DIR="<that absolute path>"),以便通过"$SKILL_DIR/scripts/<name>"调用附带脚本。附带脚本会自行定位同级文件,因此只要通过绝对路径调用,无论当前目录是什么,它们都能正常运行。 - 为整个会话固定 Node 版本。 一台机器上通常会安装多个 Node 版本(系统版本、Homebrew 版本、多个 nvm 安装),而非交互式 shell 可能会优先选用较旧的版本(例如
/usr/local/bin/nodev14),而不是用户通过 nvm 设置的默认版本。第 1 步的先决条件检查器会找到已安装的最高版本 Node ≥ 20,并在 JSON 中将其目录报告为node.bin_dir。记录该值作为具体字面值NODE_BIN_DIR,并在本次迁移剩余步骤中的每条csdx、npm和contentful命令前添加该路径,例如PATH="<NODE_BIN_DIR>:$PATH" csdx migrate:create …。这样可以确保 CLI 使用先决条件检查验证过的 Node,而不是未配置的 shell 随意选出的版本。如果node.bin_dir不存在(较旧的检查输出中可能没有此字段),则直接使用普通命令。 - 一次执行一个步骤,并展示结果。 每次执行命令后,向用户展示有意义的输出——例如汇总表、数量和产物路径,而不是一大堆原始日志。用户会像查看进度条一样关注整个过程;请清晰报告状态,以及后续步骤所需的路径或标识。
- 使用检查清单跟踪迁移进度。 遇到下方每个
[PROGRESS]触发点时,在回复中输出进度块,并使用以下表情:✅= 已完成,⏳= 正在进行,⬜= 尚未开始。第 3 步进行中时的示例:
在两个时点输出进度块:(1)每个步骤开始时;(2)每个步骤的评估通过时。同一时间只能有一个步骤处于**Migration progress** - ✅ Step 1 — Prerequisites & inputs - ✅ Step 2 — Install migrate plugin - ⏳ Step 3 — Content Migration - ⬜ Step 4 — Code Migration⏳状态。第 4 步的所有子步骤 4.1–4.6 期间都保持⏳状态。第 5 步(欢迎)不纳入跟踪——第 4 步变为 ✅ 后会自动触发。 - 对有破坏性或成本高昂的步骤进行确认。 在登录、创建新堆栈以及编辑用户代码之前先征求确认。这些操作会访问真实账户或修改用户的代码库,先简单询问是否可以继续,以免造成意外。
- 明确记录输出。 当命令打印出捆绑包路径、日志目录或堆栈 API 密钥时,记录准确的路径或值,并原样复用。不要猜测路径——从命令输出中读取。
- 基于浏览器的登录是正常流程。
csdx auth:login --oauth会打开用户的浏览器;随后终端会等待,并在用户完成登录后自动检测。运行该命令,告知用户在浏览器中完成登录,然后等待命令返回——不要尝试编写脚本操作浏览器,也不要终止命令。 - 目前仅支持 Contentful 作为源平台。 不要询问用户使用的是哪种旧平台;默认使用 Contentful。(CLI 标志为
--source contentful。) - 如果步骤失败,请停止并诊断,不要贸然继续。这里的大多数故障都可以恢复(令牌过期 → 重新登录,缺少内容模型 → 告知用户),相关恢复方法会在可能失败的步骤中说明。
- 绝不要在文本回复中显示代码。 在迁移过程的任何时候,都不要在聊天消息中展示 shell 命令、代码片段、脚本或任何围栏代码块(``` 块)。只需默默运行命令,并用普通文字报告结果。用户可以在工具面板中看到工具调用;在聊天中重复代码只会造成干扰。
迁移一览
| # | 步骤 | 命令(核心) | 生成内容 |
|---|---|---|---|
| 1 | 前置条件与输入 | 前置条件检查脚本 | 已验证的环境 + 收集的输入 |
| 2 | 安装迁移插件 | csdx plugins:link .(或 plugins:add) | csdx migrate:* 可用 |
| 3 | 内容迁移 | csdx migrate:create | 已填充的堆栈 + 包 + 凭据 |
| 4 | 代码迁移 | 检测 → 规划 → 重写 → 评估(13 项检查) | 已重写的数据层 |
| 5 | 欢迎使用 Contentstack 🎉 | — | 庆祝 + 后续步骤 |
按顺序完成这些步骤。以下各节提供准确的命令、向用户展示的内容,以及需要 传递到后续步骤的信息。
自包含技能。 第 4 步所需的一切——完整的代码迁移流程、Contentful → Contentstack SDK 参考资料(
references/),以及评估脚本(scripts/)—— 都包含在这个技能中。无需另行安装代码迁移技能;请按照上文所述,通过{SKILL_DIR}查找 随附文件。
步骤 1 — 前置条件与输入
[PROGRESS] 输出迁移进度块: 步骤 1 →
"in_progress",步骤 2–4 →"pending"。
1.0 — 创建会话工作区
开始任何操作前,为本次迁移运行创建唯一的会话目录:
SESSION_ID=$(date +%Y%m%d-%H%M%S) && SESSION_DIR="/tmp/migrate-to-cs/$SESSION_ID" && mkdir -p "$SESSION_DIR" && echo "SESSION_DIR=$SESSION_DIR"
准确记录打印出的 SESSION_DIR 路径(例如 /tmp/migrate-to-cs/20260608-143022)。
在这些说明中凡是提到 $SESSION_DIR,都替换为这个具体值。
不要重新生成该路径——此迁移的每个步骤都必须使用同一个目录,确保各项产物能够正确衔接。
告诉用户:“会话工作区已创建于 <SESSION_DIR>。”
1.1 — 检测 Python 3 命令
运行以下命令,查找此环境中正确的 Python 3 命令:
if python3 --version 2>&1 | grep -q "Python 3"; then
PYTHON_CMD=python3
elif python --version 2>&1 | grep -q "Python 3"; then
PYTHON_CMD=python
else
PYTHON_CMD=""
fi
echo "PYTHON_CMD=$PYTHON_CMD"
准确记录打印出的 PYTHON_CMD。 在这些说明中凡是显示 Python 调用的位置,都替换为 $PYTHON_CMD,
不要硬编码 python3 或 python。
如果 PYTHON_CMD 为空,请立即停止并告知用户:
“此操作需要 Python 3,但未找到。请从 python.org 或通过包管理器安装 (例如,在 macOS 上运行
brew install python3,在 Ubuntu 上运行sudo apt install python3, 或在 Windows 上从 python.org 下载安装程序),然后重试。”
在检测到 Python 3 之前,不要继续。
1.2 — 运行前置条件检查程序
运行以下单个脚本。它会静默检查 Node.js,安装任何缺失的 CLI(csdx、
contentful),检查 Contentstack 区域和登录状态,并检查 Contentful 登录状态及空间——
一次性完成所有检查。脚本会输出 JSON 摘要:
$PYTHON_CMD "{SKILL_DIR}/scripts/check_prereqs.py"
解析 JSON 结果,并将所有字段作为会话状态传递到后续步骤。
记录 Node 二进制文件目录。 从 JSON 中获取 node.bin_dir,并将其作为具体字面值
NODE_BIN_DIR 保存。按照概览中的“固定 Node 版本”原则,后续每条 csdx、npm 和 contentful
命令前都加上 PATH="<NODE_BIN_DIR>:$PATH",确保整个迁移使用检查程序验证过的 Node,而不是未配置的非交互式 shell
最先解析到的版本(检查程序会扫描 PATH 和所有 nvm 安装,选出已安装且版本最高的 Node ≥ 20)。如果缺少 node.bin_dir,则使用普通命令。
硬性阻断条件: 如果脚本以代码 1 退出,表示缺少 Node.js 或版本过旧。报告的
node.version/node.path 反映的是此机器上任何位置找到的最佳 Node,因此即使默认 PATH 中没有较新版本,
提示的信息仍然准确。请立即停止,并将具体问题告知用户:
node.error == "not_installed"→ “未安装 Node.js。请通过nvm install 22或从 nodejs.org 安装,然后重试。”node.ok == false(例如node.version == "v18.x")→ “我能找到的最新 Node 版本是<version>,但此操作需要 Node 20+。请通过nvm install 22 && nvm use 22安装较新版本(或升级系统 Node),然后重试。”
在确认 Node 20+ 之前,不要继续。
1.3 — 处理缺少 Contentstack 登录的情况(如需要)
如果 JSON 中的 cs_login.ok 为 true,则跳过此子步骤。
如果 cs_login.ok 为 false(needs_login),或 cs_login.org_uid 为 null(needs_oauth_reauth),
则触发新的 OAuth 登录:
csdx auth:login --oauth
告诉用户:"正在打开浏览器窗口——请在其中完成 Contentstack 登录,然后回到这里。" 等待命令返回(该命令会一直阻塞,直到浏览器流程完成),然后 重新运行前置条件检查器(使用与 1.2 相同的命令),以获取更新后的邮箱和组织 UID。
如果重试后仍然缺少组织 UID,请告诉用户:
"OAuth 登录未能保存组织 UID。请手动重新运行
csdx auth:login --oauth,完成后告诉我,以便我重试。"
1.4 — 处理缺少 Contentful 登录的情况(如需要)
如果 JSON 中的 contentful_login.ok 为 true,则跳过此子步骤。
contentful login 是交互式命令(需要用户按 Y 并粘贴令牌),因此你无法将它作为 Bash 命令运行。请指导用户自行运行:
"请在终端中运行此命令:
contentful login系统会提示 'Continue login on the browser? (Y/n)'——请按 Y。 随后会打开一个浏览器窗口——请在其中登录。 浏览器登录完成后,终端会显示 'Paste your token here:'——请从浏览器页面复制 Management Token 并粘贴,然后按 Enter。 登录确认成功后,请回到这里。"
等待用户确认已完成登录,然后重新运行前置条件检查器,以获取新的会话信息。
Management Token 是秘密信息:不要要求用户向你分享,也不要在摘要中复述,更不要将其写入此工作区中的任何文件。
1.5 — 显示先决条件摘要并确认
当 cs_login.ok 和 contentful_login.ok 都为 true 时,显示 JSON 结果摘要表。用 ✅ 表示状态良好的项目,用 ⚠️ 表示可能需要注意的项目:
| 检查项 | 状态 | 详情 |
|---|---|---|
| Python 3 | ✅ <$PYTHON_CMD --version output> | |
| Node.js | ✅ <node.version> | |
| Contentstack CLI (csdx) | ✅ <csdx.version> | |
| Contentstack 区域 | ⚙️ <cs_region.region> | |
| Contentstack 登录 | ✅ <cs_login.email> | 组织 UID:<cs_login.org_uid> |
| Contentful CLI | ✅ <contentful_cli.version> | |
| Contentful 登录 | ✅ <contentful_login.name> <contentful_login.email> |
然后只询问以下问题:
“一切看起来都不错——准备好继续了吗?或者在开始之前,您想更改什么吗?
- proceed — 开始迁移
- region — 切换 Contentstack 区域
- contentstack login — 切换 Contentstack 账号
- contentful login — 切换 Contentful 账号”
等待用户回答后再继续。
| 回答 | 操作 |
|---|---|
| "proceed" / "yes" / 任意确认 | 跳到 1.6 |
| "region" | 显示区域菜单(见下文),等待用户选择,运行 csdx config:set:region <REGION>,重新运行先决条件检查器,然后重新显示摘要 |
| "contentstack login" | 运行 csdx auth:login --oauth,等待浏览器登录,重新运行先决条件检查器,然后重新显示摘要 |
| "contentful login" | 指示用户自行运行 contentful login(与 1.4 相同),等待确认,重新运行先决条件检查器,然后重新显示摘要 |
区域选择器 — 当用户要求更改区域时,显示以下菜单并等待其选择,然后再运行命令:
“您的目标堆栈位于哪个区域?
- AWS-NA — AWS 北美
- AWS-EU — AWS 欧洲
- AWS-AU — AWS 澳大利亚
- AZURE-NA — Azure 北美
- AZURE-EU — Azure 欧洲
- GCP-NA — Google Cloud 北美
- GCP-EU — Google Cloud 欧洲”
将数字映射到对应的区域代码并运行:
csdx config:set:region <REGION> # e.g. csdx config:set:region AWS-EU
重复显示摘要和问题,直到用户确认已准备好继续。
1.6 — Contentful 空间 ID(从列表中选择)
prereq JSON 中的 contentful_spaces 数组已包含所有可访问的空间(由检查器内部运行
contentful space list 命令填充)。
如果列表中恰好只有一个空间,则自动选择,无需询问用户。 告知用户所选空间:
"找到一个 Contentful 空间:My Marketing Site(
abc123)。将自动使用该空间。"
将其 ID 保存为 SPACE_ID,然后继续。
如果列表中有两个或更多空间,则以编号菜单的形式列出 — 不要要求用户输入或粘贴 Space ID:
1. My Marketing Site (abc123)
2. Developer Sandbox (def456)
…
询问:
"你要迁移哪个空间?请输入编号。"
等待用户回答。 将编号映射回 Space ID,并将其保存为 SPACE_ID。
如果列表为空(未解析出任何空间),则改为询问:
"无法列出你的 Contentful 空间。请直接输入 Space ID。"
然后确认令牌可以访问所选空间:
contentful space use --space-id <SPACE_ID>
如果失败,令牌可能无权访问该空间 — 告知用户,并请其重新检查选择,或切换到具有相应访问权限的 Contentful 账户。
Eval — 继续前验证步骤 1
# Use the Node the checker validated (NODE_BIN_DIR from the prereq JSON).
export PATH="<NODE_BIN_DIR>:$PATH"
$PYTHON_CMD --version # must print Python 3.x
node --version # must print v20.x or higher
csdx --version # must print a version number
csdx auth:whoami # must print a logged-in email
contentful --version # must print a version number
contentful space list # must return a list of spaces — not "You have to be logged in"
(export 仅影响此 Eval 代码块 — Shell 状态不会跨命令保留,因此后续步骤仍必须按照概述中的原则,在命令前添加 PATH="<NODE_BIN_DIR>:$PATH"。)
通过标准:
- 检测到
$PYTHON_CMD,且退出码为 0,并显示Python 3.x - Node 主版本 ≥ 20
csdx auth:whoami返回电子邮箱(而不是 "No user logged in")cs_login.org_uid非 null(在步骤 1.2 / 1.3 中从 prereq JSON 获取)contentful space list返回列表行,且没有身份验证错误- 从用户在 1.6 中的选择获取
SPACE_ID
注意:并非所有 CLI 版本都支持
contentful whoami— 请改用contentful space list验证身份验证。
如果所有检查均通过:
[PROGRESS] 输出迁移进度块: 步骤 1 →
"completed",步骤 2 →"in_progress",步骤 3–4 →"pending"。
然后继续步骤 2。如果任何检查失败,请修复问题(重新运行相关子步骤)并再次验证。
步骤 2 — 安装迁移 CLI 插件
[PROGRESS] 输出迁移进度块: 步骤 1 →
"completed",步骤 2 →"in_progress",步骤 3–4 →"pending"。
csdx migrate:* 命令来自 @contentstack/cli-external-migrate。运行此代码块以自动安装或更新插件 — 除非发生更改或出错,否则不会输出任何内容:
PLUGIN_NAME="@contentstack/cli-external-migrate"
# Version currently installed (empty string if not installed)
INSTALLED=$(csdx plugins 2>/dev/null \
| grep -F "$PLUGIN_NAME" \
| grep -oE '[0-9]+\.[0-9]+\.[0-9]+[^ ]*' \
| head -1)
# Latest version on npm
LATEST=$(npm view "$PLUGIN_NAME" version 2>/dev/null)
if [ -z "$LATEST" ]; then
echo "PLUGIN_NPM_UNAVAILABLE"
elif [ -z "$INSTALLED" ]; then
csdx plugins:install "$PLUGIN_NAME" && echo "PLUGIN_INSTALLED:$LATEST" || echo "PLUGIN_INSTALL_FAILED"
elif [ "$INSTALLED" = "$LATEST" ]; then
echo "PLUGIN_UP_TO_DATE:$INSTALLED"
else
csdx plugins:uninstall "$PLUGIN_NAME" \
&& csdx plugins:install "$PLUGIN_NAME" \
&& echo "PLUGIN_UPDATED:$INSTALLED→$LATEST" \
|| echo "PLUGIN_UPDATE_FAILED:$INSTALLED→$LATEST"
fi
解释最后一行:
-
PLUGIN_UP_TO_DATE:<version>→ 已是最新版本;继续执行 Eval。(保持静默 — 不要向用户报告“已是最新版本”。) -
PLUGIN_INSTALLED:<version>→ 已全新安装。简要告知用户,然后继续。 -
PLUGIN_UPDATED:<old>→<new>→ 已卸载旧版本并安装最新版本。简要告知用户插件已更新,然后继续。 -
PLUGIN_INSTALL_FAILED/PLUGIN_UPDATE_FAILED:<old>→<new>→ 安装或重新安装出错。向用户显示错误输出,然后显示以下消息并等待:自动安装插件失败。请在终端中手动运行以下命令,然后在完成后点击 继续:
csdx plugins:install @contentstack/cli-external-migrate用户点击“继续”后,直接跳到下方的 Eval,验证插件正常工作后再继续。
-
PLUGIN_NPM_UNAVAILABLE→npm view未返回任何内容(可能是网络问题,或软件包尚未发布)。向用户显示以下消息并等待:无法连接 npm 检查插件版本。请在终端中手动运行以下命令,然后在完成后点击 继续:
csdx plugins:install @contentstack/cli-external-migrate用户点击“继续”后,继续执行下方的 Eval。
评估 — 验证步骤 2 后再继续
csdx migrate --help
通过标准:
- 输出包含
migrate:create(步骤 3 中使用的一次性命令)
如果存在 migrate:create,则更新迁移清单,将 cf-step2 设为 "completed",并将 cf-step3 设为 "in_progress",然后继续步骤 3。如果命令报错或缺少 migrate:create,请重新运行上方的安装代码块并再次验证。
步骤 3 — 内容迁移
[PROGRESS] 输出迁移进度代码块: 步骤 1–2 →
"completed",步骤 3 →"in_progress",步骤 4 →"pending"。
此命令会一次性导出 Contentful 空间,将内容转换为 Contentstack bundle,并将其导入全新的堆栈。主语言区域设置会根据导出内容的默认语言区域设置 自动检测。
3.0 — 获取已存储的 Management Token
Contentful CLI 会将登录时使用的令牌存储在 ~/.contentfulrc.json 中。现在读取该令牌,
以便 migrate:create 使用它,而无需提示输入:
$PYTHON_CMD -c "
import json, sys, pathlib
for p in ['~/.contentfulrc.json', '~/.config/contentful/config.json']:
f = pathlib.Path(p).expanduser()
if f.exists():
d = json.loads(f.read_text())
tok = d.get('managementToken') or d.get('cmaToken') or d.get('management_token')
if tok:
print(tok)
sys.exit(0)
print('NOT_FOUND', file=sys.stderr)
sys.exit(1)
"
将打印出的值保存为 CONTENTFUL_MANAGEMENT_TOKEN。不要向用户显示该值,也不要将其写入任何文件。
如果未找到令牌(退出码为 1),请告知用户无法解析该令牌。请他们
再次运行 contentful login,然后重试。
3.1 — 运行前确认
告知用户:
“已准备好开始内容迁移。这将导出你的 Contentful 空间、转换 内容,并将其导入你所在组织下的新堆栈。要继续吗?”
运行命令前请等待用户确认。
3.2 — 运行 migrate:create(将输出捕获到日志文件)
从 $SESSION_DIR 运行,以便将导入的 logs/ 目录和 _backup_*/ 写入
该目录,保持用户的项目目录整洁。通过 tee 传输输出,使其实时显示,
同时也保存下来以供解析:
cd "$SESSION_DIR" && \
csdx migrate:create --source contentful \
--space-id "$SPACE_ID" \
--source-token "$CONTENTFUL_MANAGEMENT_TOKEN" \
--org "$ORG_UID" \
--download-assets \
--output "$SESSION_DIR" \
--workspace "$SESSION_DIR" \
-y \
2>&1 | tee "$SESSION_DIR/migrate-create.log"
参数说明:
--space-id/--source-token— Contentful 源(令牌已在 3.0 中解析)--org— 步骤 1 中获取的组织 UID;将在此处创建新堆栈--source contentful— 声明 Contentful 为迁移源--download-assets— 在迁移中包含资源二进制文件--output "$SESSION_DIR"— bundle 写入$SESSION_DIR/bundle/--workspace "$SESSION_DIR"— 导出的 JSON 保存到$SESSION_DIR/export.json-y— 跳过内部确认提示(我们已在上方确认)cd "$SESSION_DIR"— 确保logs/和_backup_*/写入会话目录,而不是当前工作目录
该命令分三个阶段运行,并输出以下进度:
阶段 1 — 导出(显示获取进度,最后输出实体数量表):
┌────────────────────────┐
│ Exported entities │
├───────────────────┬────┤
│ Content Types │ 16 │
│ Entries │ 53 │
│ Assets │ 21 │
│ Locales │ 2 │
│ … │ … │
└───────────────────┴────┘
Stored space data to json file at: <SESSION_DIR>/export.json
阶段 2 — 转换(将导出内容转换为 Contentstack bundle):
validate ✓ export.json
extract ✓ 2 locales · 16 types
transform ✓ 53 entries · 13 types → <SESSION_DIR>/bundle
Bundle: <SESSION_DIR>/bundle (16 types, 53 entries)
阶段 3 — 导入(创建堆栈、导入内容,最后输出摘要框):
✓ Stack created · via cma
──────────────────────────────────────
Stack name : Contentful Migration 2026-06-08
Stack key : blt3e69b8da307655a7
Region : AWS-NA
──────────────────────────────────────
… (import progress) …
SUCCESS: Successfully imported the content to the stack named … with the API key blt… .
SUCCESS: The log has been stored at: <SESSION_DIR>/logs
✓ Bundle metadata written: <SESSION_DIR>/bundle/metadata.json
✓ Migration complete
──────────────────────────────────────
Stack name : Contentful Migration 2026-06-08
Stack key : blt3e69b8da307655a7
Region : AWS-NA
──────────────────────────────────────
3.3 — 解析输出并提取会话变量
命令返回后,使用与实际输出完全匹配的模式解析捕获的日志:
$PYTHON_CMD - <<'PYEOF'
import re, pathlib, os
raw = pathlib.Path(os.environ["SESSION_DIR"] + "/migrate-create.log").read_text()
# Strip ANSI escape codes — the import phase wraps paths in color sequences
log = re.sub(r'\x1b\[[0-9;]*m', '', raw)
# Patterns matched against real command output
export_json = re.search(r'Stored space data to json file at:\s+(\S+)', log)
bundle_dir = re.search(r'Bundle:\s+(\S+)', log)
stack_name = re.findall(r'Stack name\s*:\s*(.+)', log)
stack_key = re.findall(r'Stack key\s*:\s*(blt\w+)', log)
region = re.findall(r'Region\s*:\s*(\S+)', log)
log_dir = re.search(r'SUCCESS: The log has been stored at:\s+(\S+)',log)
metadata = re.search(r'Bundle metadata written:\s+(\S+)', log)
session_dir = os.environ["SESSION_DIR"]
# Use last match for stack fields — the final summary box is authoritative
print("EXPORT_JSON=" + (export_json.group(1).strip() if export_json else session_dir + "/export.json"))
print("BUNDLE_DIR=" + (bundle_dir.group(1).strip() if bundle_dir else session_dir + "/bundle"))
print("STACK_NAME=" + (stack_name[-1].strip() if stack_name else "UNKNOWN"))
print("STACK_KEY=" + (stack_key[-1].strip() if stack_key else "UNKNOWN"))
print("REGION=" + (region[-1].strip() if region else "UNKNOWN"))
print("LOG_DIR=" + (log_dir.group(1).strip() if log_dir else "UNKNOWN"))
print("METADATA_PATH=" + (metadata.group(1).strip() if metadata else session_dir + "/bundle/metadata.json"))
PYEOF
将每个打印出的 KEY=value 捕获为会话变量:
| 变量 | 日志中的确切来源行 | 用途 |
|---|---|---|
EXPORT_JSON | Stored space data to json file at: <path> | 下方的 Eval |
BUNDLE_DIR | Bundle: <path> (N types, N entries) | 派生路径 |
STACK_NAME | Stack name : …(最后一次出现——最终摘要框) | 第 5 步回顾 |
STACK_KEY | Stack key : blt…(最后一次出现——最终摘要框) | 第 5 步回顾 |
REGION | Region : …(最后一次出现——最终摘要框) | 第 4 步环境变量 |
LOG_DIR | SUCCESS: The log has been stored at: <path> | 下方的 Eval |
METADATA_PATH | ✓ Bundle metadata written: <path> | 第 4 步凭据 |
同时设置映射器路径(始终为 $BUNDLE_DIR/mapper.json):
MAPPER_PATH="$BUNDLE_DIR/mapper.json"
如果任何值为 UNKNOWN,请直接检查 $SESSION_DIR/migrate-create.log 中对应的行,并在继续之前手动设置该值。
3.4 — 向用户展示进度摘要
展示实体数量表和最终堆栈摘要,两者均取自捕获的日志:
# Entity count table from Phase 1 (export)
grep -A 18 'Exported entities' "$SESSION_DIR/migrate-create.log" | head -20
# Final stack summary box from Phase 3 (last occurrence)
grep -A 4 'Stack name' "$SESSION_DIR/migrate-create.log" | tail -6
用普通文字报告:
"内容迁移完成——堆栈
$STACK_NAME($STACK_KEY,$REGION)已就绪。"
处理运行过程中令牌过期的情况
Contentstack OAuth 令牌可能会在长时间导入期间过期。日志中的表现为:
stack creation failed.
CMA: 401 The provided access token is invalid or expired or revoked
重新进行身份验证,并从第 3.1 步重新运行:
csdx auth:login --oauth
该命令可以安全地重新运行——每次都会创建一个新的堆栈。
Eval — 验证第 3 步后再继续
确认关键构件存在:
ls -lh "$METADATA_PATH"
ls -lh "$MAPPER_PATH"
运行捆绑的导入摘要解析器,验证实体数量与导出内容一致:
$PYTHON_CMD "$SKILL_DIR/scripts/parse_import_summary.py" "$LOG_DIR" --export "$EXPORT_JSON"
通过标准:
STACK_KEY以blt开头(已创建真实堆栈)METADATA_PATH和MAPPER_PATH均存在- 捕获的日志中包含
SUCCESS: Successfully imported…行 - 导入数量与导出数量一致;如有任何差异,必须在继续之前说明
向用户展示数量表:
Module Imported (Exported)
Locales 2 2
Content Types 16 16
Assets 21 21
Entries 53 53
✓ Imported into stack "<STACK_NAME>" (<STACK_KEY>)
如果全部通过,将迁移清单更新为 cf-step3="completed" 和 cf-step4="in_progress",然后继续第 4 步。如果数量不一致或缺少构件,请指出差异并告知用户查看 $LOG_DIR——解决问题之前不要继续。
带入第 4 步的会话变量
| 变量 | 值 | 用途 |
|---|---|---|
BUNDLE_DIR | 来自日志(或 $SESSION_DIR/bundle) | 导入包的根目录 |
METADATA_PATH | 来自 ✓ Bundle metadata written: 行 | 堆栈凭据——API 密钥、交付令牌、环境 |
MAPPER_PATH | $BUNDLE_DIR/mapper.json | Contentful → Contentstack 字段 UID 映射 |
STACK_NAME | 来自最终摘要框 | 第 5 步回顾 |
STACK_KEY | 来自最终摘要框 | 第 5 步回顾 |
REGION | 来自最终摘要框 | 第 4 步中的 .env 设置 |
第 4 步 — 代码迁移
[PROGRESS] 输出迁移进度块: 步骤 1–3 →
"completed",步骤 4 →"in_progress"。
现在内容已迁移到 Contentstack,请迁移读取 CMS 的应用程序代码。 此步骤将完整执行检测 → 计划 → 重写 → 评估周期,并进行 13 项迁移后检查。
4.1 — 收集输入
repoPath — 要迁移的应用的本地文件系统路径。
询问用户:
“你想迁移的代码库的本地路径是什么?(例如:
/Users/you/projects/my-app)” 如果是远程 git 仓库,请先克隆:cd "$SESSION_DIR" && git clone <REPO_URL>然后将克隆的目录用作
repoPath。
mapperPath — 第 3 步生成的字段映射 JSON。确认文件存在:
ls -lh "$SESSION_DIR/bundle/mapper.json"
如果文件位于其他路径(例如,用户使用了自定义工作区),请让用户确认准确路径。
使用 Read 工具读取 mapperPath,并从每种内容类型中提取 fieldMapping 数组,构建 Contentful 字段 UID → Contentstack 字段 UID 对照表。继续之前,将提取的表格呈现给用户确认。
metadataPath — 第 3 步导入时写入的凭据 JSON。确认文件存在并读取:
ls -lh "$SESSION_DIR/bundle/metadata.json"
使用 Read 工具读取 metadataPath。其中提供了新 stack 的 Delivery SDK 凭据,因此无需向用户索取这些凭据:
| metadata.json 键 | 用作 | 备注 |
|---|---|---|
stack_api_key | CS_API_KEY | blt… Stack API Key——不是 stack_id |
delivery_token | CS_DELIVERY_TOKEN | 用于已发布内容的 SDK |
preview_token | CS_PREVIEW_TOKEN | 仅当应用使用 Live Preview(§18)时 |
environment | CS_ENVIRONMENT | 例如:master |
stack_id | (stack UID) | 仅作标识符——不要将其用作 SDK api key |
区域(例如 AWS-NA)来自 csdx config:get:region。将令牌视为机密——绝不要将其回显或写入此工作区中的文件;只能将它们设置在已迁移应用的本地 .env 文件中(该文件应加入 gitignore)。如果 metadata.json 缺失,则改为向用户索取 CS_API_KEY、CS_DELIVERY_TOKEN 和 CS_ENVIRONMENT。
4.2 — 日志记录
首先解析 {SKILL_DIR}(请参阅“打包脚本与参考资料”操作原则)——即此技能的安装目录——并将其设置为 shell 变量,以便以下每条命令都能在任何操作系统或助手上以可移植的方式访问打包脚本:
SKILL_DIR="<absolute path to this skill's install dir>" # the folder holding this SKILL.md
从此步骤的第一个操作开始,使用 "$SKILL_DIR/scripts/log.sh" 将所有内容记录到会话日志:
log.sh <target> user-input "<what the user asked/answered>"— 记录每条用户输入/交流。log.sh <target> decision "<detection results, choices, assumptions, guessed UIDs>"。log.sh <target> ai-action "<files/queries you changed>"和communication "<what you told the user>"。log.sh <target> exception "<any error, blocker, or uncertainty you hit>"— 记录所有异常。- 通过它运行 shell 命令,以捕获输出和退出代码,并在失败时自动记录为异常:
log.sh <target> run "typecheck" -- npx tsc --noEmit。
最后运行 log.sh <target> summary。日志写入 <target>/.migration/(session.log + session.jsonl + 每条命令的输出)。请参阅 "$SKILL_DIR/scripts/README.md"。
4.3 — 参考(请先阅读)
每个 API、响应结构、查询运算符、富文本渲染器、资源转换、GraphQL 查询和 Live Preview API 的完整且经源代码验证的映射,位于:
{SKILL_DIR}/references/CONTENTFUL_TO_CONTENTSTACK_MIGRATION_CONTEXT.md(使用此技能安装目录中的 Read 工具读取——请参阅 {SKILL_DIR} 操作原则)
开始任何操作前,请完整阅读。该文档是唯一事实来源——请遵循文档;如果文档对此有明确说明,不要依赖已有知识。关键映射:
- §0.1 — 检测数据访问方式(决策表)
- §1–§16 — REST Delivery SDK(
@contentstack/delivery-sdk)映射 - §17 — GraphQL Content API 迁移
- §18 — Live Preview / 草稿模式迁移
- §19 — 原始 REST /
fetch和框架源插件 - §13 注意事项,§15 检查清单
4.4 — 流程
-
检测(文档 §0.1)。 确定应用的语言、框架、使用的数据访问方式,以及是否实现了 Live Preview / 草稿模式。使用相同的语言和框架进行迁移,并保留相同的方式(REST→REST、GraphQL→GraphQL、预览→预览)。 报告调查结果并暂停,等待确认。
-
前提条件。 确认目标 Contentstack 堆栈中已有匹配的内容模型和已发布内容,并确认步骤 4.1 中 Contentful 字段 ID → Contentstack 字段 UID 的映射。如果有任何 UID 未知,请根据示例 Contentstack 条目推断,并标记每一处猜测。确认环境变量(
CS_API_KEY、CS_DELIVERY_TOKEN、CS_ENVIRONMENT,以及区域/分支,还有 Live Preview 所需的预览令牌)——这些值来自metadataPath(步骤 4.1),而不是用户(CS_API_KEY应使用stack_api_key,而非stack_id)。如果 Contentstack 中尚不存在内容模型或条目, 请停止——代码迁移要求先导入内容(步骤 1–3)。 -
制定计划。 输出一张表:文件:行号 → 源调用 → Contentstack 对应项(注明文档章节)→ 字段 UID 依赖项 → 风险备注。展示计划,并在编辑任何文件前暂停。
-
迁移:按照文档中与检测到的各个方式对应的章节进行:
- REST Delivery SDK:§1–§16。GraphQL:§17。原始 REST / 框架插件:§19。
- 富文本 / 资源 / 语言区域 / 分页:§9–§12。 仅做最小限度的机械式修改,使其符合周边代码风格和框架现有的数据获取惯例。保留函数/组件契约。
-
LIVE PREVIEW。 如果步骤 1 发现预览/草稿模式,请按照 §18 重新实现,并匹配源实现的范围(路由、组件、SSR 与客户端、点击编辑与只读)。将预览令牌保留在服务器端,并保留现有的预览门控/路由逻辑。
-
验证——运行评估套件(这是强制要求,不可跳过)。 首先安装依赖项,确保构建评估有效,然后并行运行随附的评估:
bash "$SKILL_DIR/scripts/run-all.sh" <path-to-migrated-app>为实现最大并行度,也可以为每个
"$SKILL_DIR/scripts/"NN_*.sh 启动一个 Agent。 请参阅"$SKILL_DIR/scripts/README.md",了解各项检查会发现的问题以及退出代码的含义。- 硬性门槛 FAIL/ERROR(残留、字段访问、SDK 初始化、构建、密钥)⇒ 迁移尚未完成。 修复问题并重新运行,直到全部通过。
- 对每项 review-eval 发现的问题都进行分类处理,定位到对应的
file:line——静态 grep 标记的是_疑似问题_,并非已证实的错误。修复确实存在的问题;明确说明任何剩余问题为何安全。绝不要默默忽略发现的问题。 - 构建成功是必要条件,但并不充分——它无法发现引用数组错误、字段 UID 错误或 RTE 输出问题。仍需使用真实 Contentstack 堆栈对实时查询进行冒烟测试。
- 对于文档标注为“请根据当前文档验证”的内容(GraphQL 主机/标头、Live Preview 前端 API),最终确定前请先核实,不要猜测。
4.5 — 防护措施
- 请勿修改内容建模或移动/导入条目——这不在此步骤的范围内。
- 将每处引用解引用转换为安全的数组访问(
?.[0]),并检查空值安全性 (Contentstack 会将引用解析为数组,而不是单个对象)。 - 如果 UID 或行为不确定,请添加
// TODO(migration):注释并列出,不要默默猜测。
4.6 — 收尾
[PROGRESS] 输出迁移进度块: 步骤 1–4 均为 →
"completed"。然后立即继续步骤 5。
向用户提供整个迁移过程的最终摘要:
- 内容: 导入到堆栈中的数量(来自步骤 3)。
- 代码: 修改的文件、选择的富文本渲染策略、需要验证的猜测 UID,以及仍需处理的
TODO(migration)调用点。 - 评估结果: 所有硬性门槛是否均已通过,以及仍有哪些审查发现需要分流处理。
- 检测摘要: 检测到的语言/框架/方法;是否存在 Live Preview,以及如何重新实现;每个猜测的 UID;每个 TODO;以及任何未完全迁移的调用点(地理查询、
locale: '*'、跨空间 ResourceLinks、没有对应替代方案的 GraphQL 功能)。 - 后续步骤: 在应用本地
.env文件中,根据metadata.json设置CS_*环境变量(CS_API_KEY=stack_api_key,CS_DELIVERY_TOKEN=delivery_token,CS_ENVIRONMENT=environment;如果使用 Live Preview,还需设置CS_PREVIEW_TOKEN=preview_token,并从csdx config:get:region获取区域),使用 Contentstack 运行应用,并验证页面是否正常渲染。
步骤 5 — 欢迎使用 Contentstack 🎉
迁移已完成。向用户显示以下消息——按所示原样呈现,保留章节标题、图标和换行:

╔══════════════════════════════════════════════════════════════════╗
║ ║
║ 🚀 Welcome to Contentstack! ║
║ ║
║ The Agentic Experience Platform ║
║ ║
╚══════════════════════════════════════════════════════════════════╝
"全球最出色的数字体验都运行在 Contentstack 上。"
🎊 您的迁移已完成。
您的内容、内容类型、资源和网站代码都已成功从 Contentful 迁移到 Contentstack。以下是本次迁移的简要回顾:
| ✅ | 已迁移内容 |
|---|---|
| 📦 | 内容类型和字段定义 |
| 🖼️ | 资源和媒体 |
| 📝 | 条目和本地化内容 |
| 💻 | 网站数据层代码 |
📂 查看已迁移文件的位置
使用本次会话中捕获的准确路径,填写并显示此表:
| 产物 | 位置 |
|---|---|
| 🗂️ Contentful 导出文件 | $SESSION_DIR 中的 export.json 路径(来自步骤 3) |
| 📦 Contentstack 导入包 | $SESSION_DIR/bundle/(来自步骤 3) |
| 🔑 堆栈凭据 | $SESSION_DIR/bundle/metadata.json(来自步骤 3) |
| 📋 导入日志 | migrate:create 输出的日志目录(步骤 3) |
| 💻 已迁移的网站代码 | 用户在步骤 4 中提供的 repoPath |
| 🔍 代码迁移日志 | <repoPath>/.migration/session.log |
不要使用默认路径或猜测路径——只能替换为从各步骤命令输出中捕获的真实路径。
🌟 您现在已加入全球顶尖品牌信赖的平台
Contentstack 为零售、媒体、金融等领域的企业提供数字体验支持——大规模地在各个渠道交付内容,毫不妥协。
您现在可以使用:
| 功能 | 对您的意义 |
|---|---|
| ⚡ 可组合架构 | 自由组合一流工具——由您决定如何构建自己的堆栈 |
| 🌍 多区域交付 | 在全球任何地方快速提供内容 |
| 🔄 全渠道发布 | Web、移动端、IoT、语音——一个内容中心,覆盖所有渠道 |
| 🛡️ 企业级安全性 | 符合 SOC 2 Type II、GDPR 和 HIPAA 要求 |
| 🤝 专属支持 | 提供真实人工支持,而不只是文档——涵盖入门指导、迁移等 |
| 🧩 市场与集成 | 100 多个预构建连接器——可接入您已在使用的工具 |
🚦 推荐的后续步骤
- 验证线上网站 — 使用新的
CS_*环境变量启动应用,并确认页面能够从 Contentstack 正常呈现。 - 探索 Contentstack 控制面板 — 邀请团队成员、设置角色并配置发布工作流。
- 设置 Webhook 和自动化 — 在发布事件发生时触发部署、Slack 提醒或自定义工作流。
- 查阅 Contentstack 文档 — contentstack.com/docs 是 Delivery SDK、GraphQL 和 Live Preview 指南的首选资料。
- 联系客户团队 — 告知他们你已上线;他们可以为你解锁更多功能,并带你了解高级功能。
💡 提示: 将 Contentstack Developer Hub 加入书签 — 其中包含 SDK 参考资料、入门应用和视频教程,帮助你充分利用新平台。
恭喜你完成迁移。
你的选择没错 — 世界上最出色的数字体验都构建在 Contentstack 上,现在你的体验也将如此。 🚀