
Strapi 版本升级指南深入解析 strapi/upgrade 升级工具的命令体系与 Codemod 机制【免费下载链接】strapi Strapi is the leading open-source headless CMS. It’s 100% JavaScript/TypeScript, fully customizable, and developer-first.项目地址: https://gitcode.com/GitHub_Trending/st/strapi本文以 Strapi 仓库中的packages/utils/upgrade/README.md为核心系统讲解官方升级 CLIstrapi/upgrade的全部命令与选项、json/code两类代码转换codemod的原理与编写方式并结合仓库源码说明 codemod 的目录规范、版本发现机制与升级流程约束帮助你安全完成 Strapi 主版本迁移、并能为项目贡献自己的 codemod。1. 升级工具的定位为什么不要手改 package.jsonStrapi Upgrade Tool 是一个专门用于在 Strapi 各版本之间迁移的 CLI 工具对应仓库中的 packages/utils/upgrade 包npm 包名为strapi/upgrade当前仓库版本为 5.52.2见 package.json。根据 README 的说明它负责三件事将项目package.json中的 Strapi 依赖更新到正确的版本运行包管理器安装器完成依赖安装针对主版本major中的破坏性变更breaking changes运行官方提供的代码转换脚本codemods。README 明确建议升级到任何 major、minor、patch 版本时都应使用该工具而不是手动修改package.json因为主版本升级往往伴随需要批量改动的 API 变更codemod 可以替你完成这些机械性替换。1.1 命令一览工具提供以下命令引自 READMElatest [options] Upgrade to the latest available version of Strapi major [options] Upgrade to the next available major version of Strapi minor [options] Upgrade to the latest minor and patch version of Strapi for the current major patch [options] Upgrade to latest patch version of Strapi for the current major and minor to version Upgrade to a specific version of Strapi codemods [options] Run a set of available codemods for the selected target version without updating the Strapi dependencies这四个按发布类型划分的命令latest/major/minor/patch并非简单重复——从源码 src/cli/commands/upgrade.ts 可以看到它们通过addReleaseUpgradeCommand统一注册只是把不同的releaseType作为target传给同一个upgrade动作函数。而to命令则接收一个具体版本号并对参数做 semver 合法性校验isValidSemVer非法输入会抛出InvalidArgumentError直接拒绝执行。1.2latest被注册策略挡住时怎么办README 特别提到一个实战场景当latest解析到的版本被 registry 策略例如min-release-age即新版本必须发布满若干小时后才可安装挡住时应改用to命令显式指定一个已发布的版本npx strapi/upgrade to 5.42.0对于预发布版本则用--codemods-target指定要运行哪一套 codemod默认取目标版本的major.minor.patch部分npx strapi/upgrade to 5.0.0-beta.951 --codemods-target 5.0.0从 src/cli/commands/upgrade.ts 的注册代码看--codemods-target简写-c是to命令独有的选项其argParser会用isLiteralSemVer强制要求number.number.number的完整字面量格式避免预发布号被误当作 codemod 目录名。在任务层src/tasks/upgrade/upgrade.ts该值通过upgrader.overrideCodemodsTarget(codemodsTarget)手动覆盖目标这正是装 5.0.0-beta.951、却跑 5.0.0 那套 codemod的实现来源。2. 通用选项--dry、--project-path 与确认提示latest/major/minor/patch/to命令共享同一组选项定义在 src/cli/options.ts选项简写作用默认值--project-path path-p指定 Strapi 应用或插件的根路径不传则使用当前工作目录process.cwd()--dry-n模拟升级不实际修改任何文件false--debug-d输出更多调试日志false--silent-s不输出任何日志false--yes-y对所有交互式提示自动回答yesfalse--dry是安全验证升级效果的首选方式dry: true会一路透传到 upgrader见 upgrade.ts 的.dry(options.dry ?? false)让整条流水线走完但跳过写盘。--yes则适合 CI 场景源码中confirm闭包在yes为真时直接返回true跳过prompts交互commands/upgrade.ts。codemods子命令额外支持--range range-r用于按 semver 范围筛选要执行的 codemod同样带有范围合法性校验。3. 使用方式npx、strapi upgrade 与 monorepo 开发README 给出的标准用法是在 Strapi 项目目录内执行npx strapi/upgrade --help npx strapi/upgrade to 5.42.0README 还说明在已安装 Strapi 的项目中也可以直接使用strapi upgrade触发同一工具在 Strapi 官方仓库内做 monorepo 开发、针对examples示例应用联调时可从示例应用目录直接运行../../packages/utils/upgrade/bin/upgrade对应 package.json 中的bin: ./bin/upgrade.js入口。值得注意的是 src/tasks/upgrade/upgrade.ts 中的一处硬性约束upgrade系列命令只能运行在Strapi 应用项目上对插件项目会抛出错误并提示改用codemods命令The target upgrade can only be run on a Strapi project; for plugins, please use codemods.也就是说latest/major/to这类会改写依赖的命令面向应用而codemods run则同时服务于应用与插件见 commands/codemods.ts 的描述在应用项目上默认只列与当前主版本匹配的 codemod在插件项目上则列出全部。3.1 升级流程在源码中如何走以to 5.42.0为例任务层src/tasks/upgrade/upgrade.ts的执行顺序是解析cwd构建project对象并校验其是否为 Strapi 应用通过npmPackageFactory从 NPM registry 拉取strapi/strapi的全部可用版本refresh()先调用prompts.pinVersions把范围式的strapi/*依赖固定为具体版本再解析升级目标创建 upgrader 实例链式设置dry、确认回调与 logger若显式提供了codemodsTarget则覆盖 codemod 目标版本运行前置提示latest会额外走prompts.latest的确认流程按目标类型挂载要求requirement后执行upgrader.upgrade()失败时抛出报告中的错误。其中 major 升级会强制两个要求upgrade.tsREQUIRE_AVAILABLE_NEXT_MAJOR必须存在可用的下一个主版本REQUIRE_LATEST_FOR_CURRENT_MAJOR必须先把当前主版本升到最新 patch再跨主版本。而通过to version给出的具体 semver 目标会有意跳过这些检查。此外所有升级都会挂载一个可选的REQUIRE_GIT要求——源码注释解释其目的是让 git 仓库处于干净状态便于升级失败时回滚。4. 什么是 Codemod两类 Transform 有什么区别README 对 codemod 的定义是以脚本化方式重构代码。当 Strapi 需要变更用户代码例如重命名一个包、替换一个导入时官方不写请手动全局替换的升级手册而是提供脚本由工具扫描你的项目并自动完成替换。工具提供两类 transformjson用于更新项目中的.json文件主要目标是package.jsoncode基于 jscodeshift 库的 codemod用于更新.js与.ts源码。仓库中真实存在的 codemod 位于 resources/codemods 目录例如 5.0.0 版本包含 11 个转换脚本如strapi-public-interface.code.ts、entity-service-document-service.code.ts5.1.0 包含 1 个dependency-better-sqlite3.json.ts。4.1 codemod 的命名与发现机制编写 codemod 的第一条规则引自 README新建文件upgrade/resources/codemods/{X.X.X}/{short-description-of-action}.{code|json}.ts其中X.X.X是该 codemod 服务的目标 Strapi 版本——例如 Strapi v5 首个正式版本的所有破坏性变更都放在upgrade/resources/codemods/5.0.0下。文件名中的连字符描述会被转换成展示给用户的空格分隔文本如sqlite3-to-better-sqlite3显示为 sqlite3 to better sqlite3。这个约定在源码中有严格的实现对应。CodemodRepository 的发现逻辑是refreshAvailableVersions读取 codemod 根目录只保留目录名是合法 semver的子目录并按版本升序排列refreshAvailableFilesForVersion遍历各版本目录只接受符合CODEMOD_FILE_REGEXP的文件parseCodemodKindFromFilenamerepository.ts从文件名倒数第二段.code.ts/.json.ts的code或json解析 codemod 类型并且断言该后缀必须在允许列表内——这就是为什么文件名必须严格遵循描述.{code|json}.ts格式否则仓库加载阶段就会报错。5. 编写jsontransformREADME 给出的完整示例针对根目录package.json把dependencies.strapi/strapi的版本改写为5.0.0import path from node:path; import type { JSONTransform } from ../../..; const transform: JSONTransform (file, params) { // Extract the json api and the cwd so we can target specific files const { cwd, json } params; // To target only a root level package.json file: const rootPackageJsonPath path.join(cwd, package.json); if (file.path ! rootPackageJsonPath) { // Return the json object unmodified to pass it to the next transform return file.json; } // Use json() to get useful helpers for performing your transform const j json(file.json); const strapiDepAddress dependencies.strapi/strapi; // if this file contains a value at dependencies.strapi/strapi if (j.has(strapiDepAddress)) { // we set the value to 5.0.0 j.set(strapiDepAddress, 5.0.0); } // at the end we must return the modified json object return j.root(); }; export default transform;关键契约json transform 会被调用于用户项目中的每一个 json 文件函数必须返回可能修改过的json 对象交给下一个 transform 接力处理不关心的文件要原样返回。README 引用的类型定义来自 src/modules/json/types.tsexport interface JSONTransformAPI { getT extends Utils.JSONValue(path: string): T | undefined; getT extends Utils.JSONValue(path: string, defaultValue: T): T; has(path: string): boolean; set(path: string, value: Utils.JSONValue): this; remove(path: string): this; merge(other: Utils.JSONObject): this; root(): Utils.JSONObject; }各方法语义README 原文 源码印证get(path, default)读取路径值不存在时返回默认值set(path, value)按点分路径如engines.node、author.name设置值has(path)判断路径是否存在merge(obj)合并两个 json 对象root()返回完整的 json 对象remove(path)删除路径对应的属性如dependencies.strapi。从源码 src/modules/json/transform-api.ts 可以看到这些方法全部是对 lodash/fp 的get、has、set、merge、omit的包装构造函数中先cloneDeep一份输入root()与get()返回的也都是深克隆——这意味着 transform 内部可以自由链式改写而不污染原始对象remove实际由omit实现天然支持路径式删除。真实仓库中的 dependency-better-sqlite3.json.ts 是一个很好的参考实现它同样只对根package.json生效并额外用semver.validsemver.lt做了只升级、不降级的保护——当现有依赖版本已是合法 semver 且低于12.8.0时才写入目标版本。编写 json codemod 时值得借鉴这一防御性写法。6. 编写codecodemodcode 类 transform 使用 jscodeshift 库该包在 package.json 中依赖 jscodeshift 17.3.0修改代码。file与api参数直接来自 jscodeshift 的同名入参。README 的官方示例是把项目里所有的console.log调用改名为console.infoimport type { Transform } from jscodeshift; const transform: Transform (file, api) { // Extract the jscodeshift API const { j } api; // Parse the file content const root j(file.source); root // Find console.log calls expressions .find(j.CallExpression, { callee: { object: { name: console }, property: { name: log } }, }) // For each call expression .forEach((path) { const { callee } path.node; if ( // Make sure the callee is a member expression (object/property) j.MemberExpression.check(callee) // Make sure the property is an actual identifier (contains a name property) j.Identifier.check(callee.property) ) { // Update the propertys identifier name callee.property.name info; } }); // Return the updated file content return root.toSource(); }; export default transform;写法要点用j(file.source)得到 AST 根节点用.find(节点类型, 过滤条件)定位目标语法修改前用j.MemberExpression.check(...)/j.Identifier.check(...)做类型守卫避免误改结构不匹配的节点最后必须return root.toSource()返回更新后的源码字符串。一个更具代表性的真实案例是 strapi-public-interface.code.ts它把旧版import strapi from strapi/strapi; strapi()的用法转换为新的公开接口——ESM 下改写为import { createStrapi } from strapi/strapi并调用createStrapi()CommonJS 下则改写为strapi.createStrapi()。文件头部用注释块完整记录了 Before/After 对照这正是官方 codemod 的良好文档习惯。7. codemods 子命令不升级依赖只跑转换codemods命令组注册于 src/cli/commands/codemods.ts包含两个子命令codemods run [uid]对当前项目执行一组 codemod。不带uid时交互式列出项目可用的全部 codemod 供多选源码使用autocompleteMultiselect提示默认全选提供uid时只运行对应的那一个。其默认 target 为majorDEFAULT_TARGET Version.RELEASE_TYPES.Major可用-r/--range覆盖为自定义 semver 范围codemods ls列出可用 codemod。两者在执行前都会打印备份警告Please make sure youve created a backup of your codebase and files before running the codemods。run的完整选项为--project-path、--dry、--debug、--silent、--range其中--dry让你在真正动手前预览将发生的代码变更。查询侧的实现CodemodRepository.find支持按 semver 范围range.test(version)与 uid 列表双重过滤且只返回至少含 1 个 codemod 的版本分组——这就是ls输出按版本组织、run [uid]能精确定位的底层机制。8. 数据迁移升级工具不做什么README 专门划清了边界数据迁移data migrations不由升级工具负责。对 Strapi v4不会允许数据迁移也没有计划支持极端特殊情况如与数据库结构相关的关键安全问题除外对 Strapi v5自动化的数据迁移可以加入本仓库develop分支的packages/core/database包中。因此用strapi/upgrade完成依赖与代码层面迁移后仍需自行关注数据库结构相关的兼容性问题仓库中的 tests/migration 目录含 CHECKPOINTS.md 与场景框架可作为 v5 数据迁移机制的测试参考。9. 实践清单与适用前提升级前备份CLI 在每次upgrade与codemods run时都会主动打印备份警告源码中还有可选的 git 干净状态要求来帮助回滚先用--dry模拟加-n参数跑完整流程但不落盘确认 codemod 影响面后再实跑latest被min-release-age类策略挡住时改用to version指定具体已发布版本预发布目标要配合-c, --codemods-target major.minor.patch指定 codemod 集合插件项目请使用codemods命令组而非latest/major/to运行环境该包声明node 20.0.0 26.x.x、npm 6.0.0见 package.json在 monorepo 内联调则从示例应用目录执行../../packages/utils/upgrade/bin/upgrade。综合来看packages/utils/upgrade这套工具把版本解析semver 模块— 依赖改写json transform lodash 封装— 代码重构jscodeshift— 流程约束requirement 校验 git 保护组织成了一条可 dry-run、可交互确认、可精确回放的升级流水线。理解 README 中的命令与 transform 编写规范再对照 src/modules 下的codemod-repository、json、runner等模块源码既能安全完成自身的版本升级也能按{X.X.X}/{描述}.{code|json}.ts的约定为 Strapi 社区贡献新的 codemod。【免费下载链接】strapi Strapi is the leading open-source headless CMS. It’s 100% JavaScript/TypeScript, fully customizable, and developer-first.项目地址: https://gitcode.com/GitHub_Trending/st/strapi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考