enonic-content-migration

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Enonic Content Migration

Enonic 内容迁移

Procedures

操作流程

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

Error Handling

错误处理

  • If
    scripts/find-enonic-targets.mjs
    finds no Enonic XP project, explain that the workspace does not contain an Enonic XP application.
  • If a content operation fails with
    contentAlreadyExists
    , check whether the target exists and decide whether to update or skip. Read
    references/troubleshooting.md
    for duplicate handling patterns.
  • If a query returns zero results, verify the query syntax, branch context, and property paths. NoQL string-format mismatches (e.g. missing
    instant()
    wrapper for date comparisons) are a common cause.
  • If a task fails or times out, check
    taskLib.get(taskId)
    for state and progress details. Read
    references/troubleshooting.md
    for timeout and permission issues.
  • If an
    AccessDeniedException
    occurs on publish or modify, ensure the context includes
    role:system.admin
    principals via
    lib-context
    .
  • 如果
    scripts/find-enonic-targets.mjs
    未找到Enonic XP项目,说明工作区不包含Enonic XP应用。
  • 如果内容操作返回
    contentAlreadyExists
    错误,检查目标是否存在,决定是更新还是跳过。阅读
    references/troubleshooting.md
    了解重复项处理模式。
  • 如果查询返回零结果,验证查询语法、分支上下文和属性路径。NoQL字符串格式不匹配(例如日期比较缺少
    instant()
    包装)是常见原因。
  • 如果任务失败或超时,使用
    taskLib.get(taskId)
    查看状态和进度详情。阅读
    references/troubleshooting.md
    了解超时和权限问题的处理方法。
  • 如果发布或修改时出现
    AccessDeniedException
    错误,确保上下文通过
    lib-context
    包含
    role:system.admin
    主体。