enonic-content-migration
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseEnonic Content Migration
Enonic 内容迁移
Procedures
操作流程
Step 1: Identify the Enonic XP project and operation scope
- Inspect the workspace for Enonic XP project markers: with
build.gradledependencies,com.enonic.xpdirectory structure, orsrc/main/resources/withgradle.properties.xpVersion - Execute to scan for Enonic XP project markers and existing content operation files when a Node runtime is available.
node scripts/find-enonic-targets.mjs . - If no Enonic XP project is detected, stop and explain that this skill targets Enonic XP content operations only.
- Determine the target XP version from or
gradle.propertiesto select the correct API surface.build.gradle - Classify the requested operation:
- Bulk create: Importing content from external sources (JSON, CSV, APIs).
- Bulk update: Modifying existing content matching query criteria.
- Bulk delete: Removing content matching query criteria.
- Migration: Moving or transforming content between paths, sites, or environments.
- Query/Aggregation: Retrieving and analyzing content with NoQL and aggregations.
- Long-running task: Any operation processing more than ~100 items that needs progress reporting.
Step 2: Select the API layer and context
- Read for query DSL patterns, batch processing strategies, and branch handling rules.
references/migration-reference.md - Choose when the operation works with CMS content and needs publish/unpublish, content-type validation, and the content domain abstraction.
lib-content - Choose when the operation needs low-level node manipulation, custom repositories, or bypasses content-type validation for raw data migration.
lib-node - Determine the required context:
- Use to run operations in
lib-contextbranch for modifications anddraftbranch for reading published content.master - Use with
lib-contextprincipal when the operation requires elevated permissions.role:system.admin
- Use
- If the operation processes more than ~100 items, wrap it in a task controller using . Read
lib-tasksection on task controllers for the pattern.references/migration-reference.md
Step 3: Build the query
- Construct the NoQL query string or DSL expression to match the target content.
- For content-type filtering, use the parameter on
contentTypesor acontentLib.query()property comparison in node queries.type - For date-range queries, use or
instant()functions and thedateTime()query function.range() - For full-text search, use with the appropriate field paths and operator (
fulltext()/AND).OR - For path-scoped operations, use to match descendants. Note: the
_path LIKE '/content/site-path/*'property in NoQL queries includes the internal_pathprefix, but/content/in results returns the content-domain path without it. Always prependhit._pathwhen building queries from result paths./content/ - Add for efficient post-query narrowing using
filters,exists,notExists, orhasValuecombinations.boolean - Add when the operation needs grouped statistics (term counts, date histograms, numeric ranges, stats).
aggregations - Read for complete query and aggregation patterns.
references/examples.md
Step 4: Implement batch processing
- Use paginated retrieval with and
startparameters to avoid loading all results into memory.count - Set to a batch size between 50 and 200 depending on the complexity of per-item processing.
count - Loop until or
result.hits.length === 0, incrementingstart >= result.totalby the batch size each iteration.start - For in bulk, set
contentLib.create()to avoid per-item index refresh; call a manual refresh after the batch completes.refresh: false - For , use the editor callback pattern to safely transform each content item.
contentLib.modify() - For , batch keys into groups of 50–100 to avoid timeout on large publish sets.
contentLib.publish() - Track success and failure counts for reporting.
- Read for the reusable batch update controller template. Note: templates use TypeScript/ESM syntax (
assets/bulk-update.template.ts,import, arrow functions); adapt to CommonJS JavaScript (const,require(),var) for XP runtime deployment asfunction()files..js
Step 5: Handle branch operations and publishing
- Run content modifications in the branch context.
draft - After modifications complete, publish changed items to using
masterwithcontentLib.publish()andsourceBranch: 'draft'.targetBranch: 'master' - Set when publishing bulk-updated items to avoid unintended dependency publishing.
includeDependencies: false - For operations comparing draft and master state, use from
repo.diff()withlib-nodeandtarget: 'master'.includeChildren: true
Step 6: Wrap long-running operations in a task controller
- Use for inline task functions or
taskLib.executeFunction()for named task descriptors.taskLib.submitTask() - Report progress using at regular intervals during batch processing.
taskLib.progress({ info, current, total }) - Read for the reusable task controller template with progress reporting.
assets/task-migration.template.ts - Check for existing running instances with before starting a duplicate operation.
taskLib.isRunning() - Use for throttling between batches if the operation generates excessive load.
taskLib.sleep()
Step 7: Validate and report results
- After the operation completes, log a summary: items processed, items created/updated/deleted, items failed, total duration.
- For migration operations, verify target content exists by querying the destination path.
- For publish operations, verify published state by querying the branch.
master - If errors occurred, collect error details (content path, error message) into a structured report.
步骤1:识别Enonic XP项目和操作范围
- 检查工作区中的Enonic XP项目标识:包含依赖的
com.enonic.xp、build.gradle目录结构,或者包含src/main/resources/的xpVersion。gradle.properties - 当Node运行时可用时,执行扫描Enonic XP项目标识和已有的内容操作文件。
node scripts/find-enonic-targets.mjs . - 如果未检测到Enonic XP项目,终止操作并说明本技能仅适用于Enonic XP内容操作。
- 从或
gradle.properties中确定目标XP版本,以选择正确的API范围。build.gradle - 对请求的操作进行分类:
- 批量创建:从外部来源(JSON、CSV、API)导入内容。
- 批量更新:修改符合查询条件的现有内容。
- 批量删除:移除符合查询条件的内容。
- 迁移:在路径、站点或环境之间移动或转换内容。
- 查询/聚合:使用NoQL和聚合功能检索和分析内容。
- 长时间运行任务:任何处理约100项以上内容、需要进度上报的操作。
步骤2:选择API层和上下文
- 阅读了解查询DSL模式、批量处理策略和分支处理规则。
references/migration-reference.md - 当操作针对CMS内容、需要发布/取消发布、内容类型校验和内容领域抽象时,选择。
lib-content - 当操作需要低级节点操作、自定义存储库,或者需要绕过内容类型校验进行原始数据迁移时,选择。
lib-node - 确定所需的上下文:
- 使用在
lib-context分支中运行修改操作,在draft分支中读取已发布内容。master - 当操作需要提升权限时,结合主体使用
role:system.admin。lib-context
- 使用
- 如果操作处理约100项以上内容,使用将其封装在任务控制器中。阅读
lib-task中关于任务控制器的章节了解实现模式。references/migration-reference.md
步骤3:构建查询
- 构造NoQL查询字符串或DSL表达式来匹配目标内容。
- 如需内容类型过滤,使用的
contentLib.query()参数,或者在节点查询中使用contentTypes属性比较。type - 如需日期范围查询,使用或
instant()函数结合dateTime()查询函数。range() - 如需全文搜索,使用搭配合适的字段路径和运算符(
fulltext()/AND)。OR - 如需路径范围操作,使用匹配后代内容。注意:NoQL查询中的
_path LIKE '/content/site-path/*'属性包含内部的_path前缀,但结果中的/content/返回不带此前缀的内容领域路径。基于结果路径构建查询时,务必在前面添加hit._path。/content/ - 添加,通过
filters、exists、notExists或布尔组合实现高效的查询后筛选。hasValue - 当操作需要分组统计(词条计数、日期直方图、数值范围、统计数据)时添加。
aggregations - 阅读获取完整的查询和聚合模式。
references/examples.md
步骤4:实现批量处理
- 使用带和
start参数的分页检索,避免将所有结果加载到内存中。count - 根据单条内容处理的复杂度,将设置为50到200之间的批量大小。
count - 循环执行直到或者
result.hits.length === 0,每次迭代将start >= result.total增加批量大小的数值。start - 批量调用时,设置
contentLib.create()避免每条内容都刷新索引;批量完成后调用手动刷新。refresh: false - 调用时,使用编辑器回调模式安全地转换每个内容项。
contentLib.modify() - 调用时,将键批量分为50-100的组,避免大量发布集合导致超时。
contentLib.publish() - 统计成功和失败的数量用于上报。
- 阅读获取可复用的批量更新控制器模板。注意:模板使用TypeScript/ESM语法(
assets/bulk-update.template.ts、import、箭头函数);部署到XP运行时作为const文件时,需要适配为CommonJS JavaScript语法(.js、require()、var)。function()
步骤5:处理分支操作和发布
- 在分支上下文中运行内容修改操作。
draft - 修改完成后,使用将变更的条目发布到
contentLib.publish(),设置master和sourceBranch: 'draft'。targetBranch: 'master' - 发布批量更新的条目时设置,避免意外发布依赖项。
includeDependencies: false - 如需对比draft和master状态的操作,使用的
lib-node,设置repo.diff()和target: 'master'。includeChildren: true
步骤6:将长时间运行的操作封装到任务控制器
- 内联任务函数使用,命名任务描述符使用
taskLib.executeFunction()。taskLib.submitTask() - 在批量处理过程中,定期调用上报进度。
taskLib.progress({ info, current, total }) - 阅读获取带进度上报功能的可复用任务控制器模板。
assets/task-migration.template.ts - 启动操作前使用检查是否已有运行中的实例,避免重复操作。
taskLib.isRunning() - 如果操作产生过高负载,使用在批量处理之间进行节流。
taskLib.sleep()
步骤7:验证并上报结果
- 操作完成后,记录汇总信息:处理的条目数、创建/更新/删除的条目数、失败的条目数、总耗时。
- 对于迁移操作,通过查询目标路径验证目标内容是否存在。
- 对于发布操作,通过查询分支验证发布状态。
master - 如果发生错误,将错误详情(内容路径、错误信息)收集到结构化报告中。
Error Handling
错误处理
- If finds no Enonic XP project, explain that the workspace does not contain an Enonic XP application.
scripts/find-enonic-targets.mjs - If a content operation fails with , check whether the target exists and decide whether to update or skip. Read
contentAlreadyExistsfor duplicate handling patterns.references/troubleshooting.md - If a query returns zero results, verify the query syntax, branch context, and property paths. NoQL string-format mismatches (e.g. missing wrapper for date comparisons) are a common cause.
instant() - If a task fails or times out, check for state and progress details. Read
taskLib.get(taskId)for timeout and permission issues.references/troubleshooting.md - If an occurs on publish or modify, ensure the context includes
AccessDeniedExceptionprincipals viarole:system.admin.lib-context
- 如果未找到Enonic XP项目,说明工作区不包含Enonic XP应用。
scripts/find-enonic-targets.mjs - 如果内容操作返回错误,检查目标是否存在,决定是更新还是跳过。阅读
contentAlreadyExists了解重复项处理模式。references/troubleshooting.md - 如果查询返回零结果,验证查询语法、分支上下文和属性路径。NoQL字符串格式不匹配(例如日期比较缺少包装)是常见原因。
instant() - 如果任务失败或超时,使用查看状态和进度详情。阅读
taskLib.get(taskId)了解超时和权限问题的处理方法。references/troubleshooting.md - 如果发布或修改时出现错误,确保上下文通过
AccessDeniedException包含lib-context主体。role:system.admin