跳转到内容

Nx Migration 生成器

为 Nx 插件添加 Nx Migration。Migration 让 nx migrate 能够在你进行破坏性更改时自动更新由插件早期版本生成的项目——重命名的目标、更改的配置,或需要伴随代码更改的依赖升级。

该生成器会搭建 migration 脚手架,在插件的 migrations.json 中注册它,并将 nx-migrations 字段连接到插件的 package.json(如果它们尚不存在则创建)。

Terminal window
pnpm nx g @aws/nx-plugin:ts#nx-migration
您还可以执行试运行以查看哪些文件会被更改
Terminal window
pnpm nx g @aws/nx-plugin:ts#nx-migration --dry-run
参数类型默认值描述
project 必需string-要添加迁移的 Nx Plugin 项目。我们建议使用 ts#nx-plugin 生成器来创建此项目。
name 必需string-迁移名称,使用 kebab-case 格式(例如 rename-foo-target)
description string-迁移功能的描述,由 nx migrate 显示
kind deterministic | agentic | hybriddeterministic迁移的类型:deterministic(代码模组)、agentic(由 AI 应用的提示用于用户自有代码)或 hybrid(先执行机械部分的代码模组,然后交给代理)
preferInstallDependencies booleantrue是否在生成器运行后优先安装依赖项。设置为 false 可在批量运行多个生成器时延迟安装(如果后续生成器需要计算 Nx 项目图,仍会运行安装);在最后统一安装一次。

Migration 有三种形式,通过 migrations.json 条目携带的字段来区分。传递 --kind 来选择(默认为 deterministic):

  • Deterministicimplementation)— 具有精确前后状态的代码修改,也是首选方式。它无人值守运行,包括在 CI 和非交互式终端中,并对每个用户应用相同的修改。当更改具有可以在任何地方可靠匹配的形状时单独使用它,通过 nextSteps 报告任何跳过的内容。
  • Hybridimplementation + prompt)— 一个更改的两个部分。当更改是破坏性的且无法完全机械应用时使用它:代码修改尽可能安全地匹配并返回 agentContext 描述它更改了什么以及跳过了什么;Nx 将该上下文传递给配对的 prompt,后者指导 agent 处理其余部分。当剩余的编辑跨越太多形状而无法进行代码修改时,prompt 可以防止工作区处于损坏状态。
  • Agenticprompt)— 由用户的本地编码 agent 通过 Nx 的 agentic nx migrate 流程应用的 markdown 指令文件。当根本没有机械的前后状态时单独使用它,因为正确的编辑取决于用户构建的内容。当没有 agent 运行时(CI、未安装 agent、拒绝同意),Nx 将 prompt 作为手动说明呈现——因此将 prompt 编写为独立的、人类可操作的步骤。

生成器会在插件项目的源文件夹下创建以下文件,具体取决于选择的 kind

  • 文件夹src/migrations/latest/<name>/
    • migration.ts 代码修改实现(deterministic 和 hybrid)
    • migration.spec.ts migration 的测试(deterministic 和 hybrid)
    • prompt.md Agent/人类指令(agentic 和 hybrid)
  • migrations.json 创建或更新以注册你的 migration
  • package.json 创建或更新以添加 “nx-migrations” 条目

新的 migration 位于 latest/ 中。按发布它们的版本对 migration 进行分组,可以在集合增长时一目了然地看到它们的顺序——当你分配版本时,将 migration 移动到 v<x.y.z>/ 文件夹中(并在 migrations.json 中更新其路径)。

它在 migrations.json 中注册 migration,包含其类型的字段但没有 version——请参阅下面的版本控制。注册的键以 migration 所在的文件夹为前缀(latest-<name>),因此为后续更改重用名称不会静默覆盖已发布的 migration。

deterministic(或 hybrid)migration 是一个修改虚拟文件系统(Tree)的函数,返回 MigrationReturnObject。这是一个 hybrid 示例,它返回两者:

import { type MigrationReturnObject, type Tree } from '@nx/devkit';
import { formatFilesInSubtree } from '@aws/nx-plugin/sdk/utils/format';
export default async function migration(
tree: Tree,
): Promise<MigrationReturnObject> {
const nextSteps: string[] = [];
const agentContext: string[] = [];
// Read and update the files your generators produce here.
await formatFilesInSubtree(tree);
return { nextSteps, agentContext };
}
  • nextStepsnx migrate 运行后 Nx 为用户打印的后续操作。每个条目都应该是他们需要手动完成的事情:协调你跳过的文件(请参阅下面的防护措施),或完成 migration 无法触及的内容,例如安装以刷新锁文件。它们不是你更改内容的日志——当转换干净地应用时,不要添加任何内容,否则确实需要操作的条目会被掩埋。
  • agentContext(仅限 hybrid)— 代码修改更改内容的摘要,当在 Nx 的 agentic 流程下运行时传递给配对的 prompt,以便 agent 可以专注于用户拥有的部分。

有关可以在 migration 中执行的一些示例操作(生成文件、使用 GritQL 修改现有文件、读取和更新 JSON),请参阅 Nx Generator 指南

Migration 针对你无法控制的工作区运行,因此建议遵守以下规则(脚手架骨架已内置这些规则):

  • 使用 GritQL 转换源代码。 使用 GritQL 辅助函数applyGritQLmatchGritQL)来编辑源代码,而不是正则表达式或字符串替换。GritQL 匹配 AST,因此一个模式可以跨越用户副本可能偏离的格式、空格和引号,而文本匹配会悄悄地错过等效代码或损坏文件。对 JSON 使用 updateJson,对其他结构化配置使用匹配的解析器。
  • 写入前进行模式匹配。 如果目标文件已偏离生成器生成的形状,请跳过它并通过 nextSteps 报告,或考虑 hybrid migration,而不是覆盖用户的更改。matchGritQL 是进行该检查的便捷方式。
  • 仅报告剩余要做的事情。 nextSteps 用于用户必须接手的工作,而不是你所做编辑的记录——这些已经在他们的 git diff 中了。
  • 幂等性。 重新运行 migration 必须是无操作的。使用 where { ... <: not contains ... } 子句保护注入代码的 GritQL 重写,这样第二次运行不会追加重复项。
  • 格式化你写入的内容。formatFilesInSubtree(tree) 结束,以便 migration 写入的文件格式正确。

脚手架的 migration.spec.ts 使用 createTreeUsingTsSolutionSetup() 构建与我们的预设生成的形状匹配的虚拟工作区。测试 migration 将工作区从”之前”状态带到目标状态,跳过(并报告)它无法识别的代码,并且是幂等的:

import type { Tree } from '@nx/devkit';
import { createTreeUsingTsSolutionSetup } from '@aws/nx-plugin/sdk/utils/test';
import migration from './migration.js';
describe('my migration', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeUsingTsSolutionSetup();
});
it('should update the generated shape', async () => {
// Set up a tree with the "before" state
tree.write('project.json', JSON.stringify({ targets: { foo: {} } }));
// Run the migration
await migration(tree);
// Check that the tree matches the target state
const projectJson = JSON.parse(tree.read('project.json', 'utf-8'));
expect(projectJson.targets).toHaveProperty('bar');
expect(projectJson.targets).not.toHaveProperty('foo');
});
});

单元测试只能证明 migration 执行了你编写的内容。还要针对由插件已发布版本生成的工作区测试它——用户升级的状态——并确认结果与你的生成器今天生成的内容匹配:检查差异,将相同的项目生成到新工作区中并进行比较,检查工作区是否仍然构建,然后重新运行 migration 以确认它没有更改任何内容。使用你首先自定义的工作区重复此操作,以检查 migration 是否保留你的编辑并报告它们。

当用户安装的插件版本低于 migration 的 version 时,Nx 会运行 migration。此生成器写入 version 字段——在你发布包含 migration 的版本时分配它(或从发布流程中标记它),以便它为从早期版本升级的每个人运行。

一旦分配版本,注册每种类型的一个 migration 的 migrations.json 如下所示:

{
"$schema": "http://json-schema.org/schema",
"name": "my-plugin",
"generators": {
"v2.0.0-rename-foo-target": {
"version": "2.0.0",
"description": "Rename the foo target to bar",
"implementation": "./src/migrations/v2.0.0/rename-foo-target/migration"
},
"v3.0.0-migrate-custom-handlers": {
"version": "3.0.0",
"description": "Update custom handlers for the new API",
"prompt": "./src/migrations/v3.0.0/migrate-custom-handlers/prompt.md"
},
"v4.0.0-upgrade-framework": {
"version": "4.0.0",
"description": "Upgrade the framework and reconcile call sites",
"implementation": "./src/migrations/v4.0.0/upgrade-framework/migration",
"prompt": "./src/migrations/v4.0.0/upgrade-framework/prompt.md"
}
}
}

用户使用两步 nx migrate 流程应用插件的 migration——生成待处理的 migration,安装,然后运行它们:

Terminal window
pnpm nx migrate my-plugin@latest
pnpm nx migrate --run-migrations

当有可用的本地编码 agent 时,agentic 和 hybrid migration 会在 nx migrate --run-migrations 期间由用户的本地编码 agent 应用。

有关从升级用户角度的完整流程——包括 deterministic 和 agentic migration 的示例输出——请参阅升级你的工作区