Skip to content

Releases: route-forge/php-thinkphp

v1.3.0

Choose a tag to compare

@xyj2156 xyj2156 released this 14 Sep 14:22
1.3.0
f1fd27e

v1.3.0

[1.3.0] - 2026-09-14

Added

  • route:forge:list --unnamed:全量未命名路由清单:一行命令列出所有没有路由名的路由,既含「被层级规则命中却无名」的(这类本该出现在端点/d.ts 里,却因无名凭空消失),也含「未命中任何层级」的。走本视图时只打印清单、不再打印正常表格与 warnings,避免同一事实双写;退出码恒 0,是给人排查用的视图,不参与 CI 门禁。
  • 未命名路由不再静默消失:以前只有「显式 ->tier() 却无名」这一种形态会出 warning,靠 config matchclassifier 归入层级的未命名路由完全静默,事后无从发现。现在扫描期对每条未命名路由做层级归属探测(永不抛异常,不新增失败模式),按命中来源给出对应说法并进入既有 warnings 通道(table 形态写 STDERR、--json 形态进 warnings 字段)。历史整句 Route (…) has tier [x] but no route name assigned; … 原样保留为连续子串(下游按整句 grep / substring 断言的脚本不受影响),只在句尾追加一列 HTTP 方法用于定位。未命名且未命中任何层级的路由不进 warnings——它压根与 forge 无关,否则「warnings 非空即配置有问题」的 CI 门禁会被无关路由长期打断,这类只在 --unnamed 视图出现。

Changed

  • 严格模式一次报全,HTTP 错误码聚合为 RF_BE_009strict_mode 下「命名路由未归级」原先逐条 fail-fast 抛 RF_BE_001(修一条刷一条)。现由依赖侧的全量违规预扫描在仓库取数入口接管,把「有层级归属却无路由名」(missing_name)与「命名路由未归级」(unassigned)两类一次性聚合并抛 RF_BE_009(HTTP 500),message 内含全量清单。层级名拼错、classifier 抛错等更精确的配置错误仍走各自错误码(RF_BE_002 / 004 / 006),fail-fast 不变。响应体仍只有 code / message / levelviolations 结构化清单未进 HTTP 契约(属前后端契约扩面,未动)。
  • 命令行严格模式「只报问题」route:forge:list 发现严格模式违规时逐行以 <error> 打印红色清单并返回退出码 1,本分支下不再打印正常表格;--json 的 stdout 仍是纯 JSON 产物(契约不变),红色清单改走 STDERR,保证 route:forge:list --json | jq 拿到的永远是合法 JSON。route:forge:types 同口径:有违规时把清单写 STDERR、退出码 1,且不再让未归级路由静默生成 d.ts
  • 依赖下限 route-forge/common ^1.1.2^1.2.0:本包接线用上了 1.2.0 新增的 TierResolver::probeRouteNameFilter::isUriExcluded / withUriPrefixesRouteAnalyzerviolations / unnamedStrictViolationScannerRF_BE_009ForgeService 把命令层(RouteAnalyzer)与仓库(RouteRepository)两处 RouteNameFilter 构造收进 makeNameFilter() 单点,并额外按 endpoint_prefix 追加 URI 排除——否则 forge 自身带 endpoint_middleware 的层级端点会被别的层级的 match.middleware 命中,包把自己报成配置错误。

对外影响与升级

  • 对断言严格模式 HTTP 错误码的调用方:strict_mode 下命名路由未归级的错误码由 RF_BE_001 变为 RF_BE_009。旧码从未出现在本包对外文档(README / llms.txt / 配置注释),实际破坏面小,按「破坏面趋近零走 minor」的口径发 1.3.0 而非 major。
  • 对 CI:开启 strict_mode 时,route:forge:list / types 在存在未命名或未归级路由时退出码由 0 变 1——这正是严格模式该给的信号。未开 strict_mode 的项目行为不变。
  • 回归测试:新增 tests/Feature/UnnamedAndStrictCommandTest.php 8 例(match/classifier 命中未命名进 warnings、未命中不进、--unnamed 视图不与表格双写、严格模式两类一次报全且红色、--json stdout 纯 JSON 且退出码 1、types 拒绝产出、自身端点不计入违规);TierAssignmentTest 的严格模式用例改断 RF_BE_009。本包 157 → 165 例全绿(对已发布的 route-forge/common 1.2.0 实装后实测)。
  • 文档口径:SPEC 的 §3.1.4(未命名可见性)/ §3.2(--unnamed)/ §6.1(RF_BE_009)变更由独立 docs 文档站统一承载,不随本包 .docs 分发。

完整变更记录见 CHANGELOG.md

v1.2.2

Choose a tag to compare

@xyj2156 xyj2156 released this 13 Sep 15:07
7d06138

Added

  • route:forge:list 表格中未分配层级的路由整行品红(与 Laravel 版 fg=magenta 同色):unassigned 是「该配 ->tier() 却没配」最常见的信号,原先这些行与其他行完全同色,只能靠上方 Tier counts: 的计数提醒,几十行表格里定位具体是哪几条得逐行看 Level 列。现在整行染色后一眼可辨。
    • 着色优先级为 unassigned 品红 > 别名黄 > 默认,指向未分级路由的别名行也被品红覆盖——此时别名身份仍由 Alias Of 列的文字表达,不依赖颜色(与 Laravel 版取舍一致)。
    • think console 只有 info / error / comment / question / highlight / warning 六个具名样式,没有品红,故用 Formatter 支持的内联标签 <fg=magenta>(渲染为 ESC[35m … ESC[39m)。表格宽度计算本就按去标签后的可见文本,列对齐不受影响。
    • 零契约变更:只作用于 table 形态,--jsonroute:forge:types 产物仍是纯文本、字节不变;命令选项与错误码集合无变化。
    • 回归测试:新增 CommandTest::testListTableColorizesUnassignedRowsMagenta(未分级行品红、别名黄通道未被抢走、--json 不含任何标签),本包 156 → 157 例全绿。Buffer 驱动不过 Formatter,故包内断言的是原始标签文本;真实 ANSI 由示例项目 --ansi 管道下核验(未分级行 4 段 ESC[35m,默认管道与 --json --ansi 恒为 0)。
    • 顺带纠正一处两端偏差:SPEC §3.2 的表格说明里「落在 unassigned 特殊层级的路由整行以品红显示」早已规定(与统计行的 warn 提示互为冗余),Laravel 版一直是 <fg=magenta>,think 侧漏做了。本次是补齐合规,不是新增契约。
    • 文档同步于 README 的「终端着色」小节与 llms.txt 的命令一节。零破坏性变更,故为补丁版本。

完整记录见 CHANGELOG.md

v1.2.1

Choose a tag to compare

@xyj2156 xyj2156 released this 13 Sep 07:45
c7d25ab

Added

  • Windows 下的命令着色自愈(route:forge:* 观感与 Laravel 版对齐):包内五命令一直在用 think console 的 <info> / <comment> / <error> 标签(层级统计、别名黄行、撞车红行、失败提示),但在 Windows 上全部退化为纯文本。根因在框架而非本包:think\console\output\driver\Console::hasColorSupport() 的 Windows 分支要求系统版本号精确等于 10.0.10586(Win10 1511 的首发版号,symfony 2.x 时代抄来的写法),且只认 TERM 严格等于 xterm,于是 Win11(如 10.0.26200)与 Git Bash(TERM=xterm-256color)恒判「不支持颜色」,标签被 Formatter 剥掉却不写 ANSI 码;Linux/macOS 走 posix_isatty 分支所以正常。Laravel 侧之所以有颜色,是 symfony/console 早已把这条判据换成 >= + WT_SESSION + 主动启用 VT 模式。
    • 新增 src/Support/ConsoleColorDetector.php:Windows 下改为「版本号 ≥ 10.0.10586 PHP 成功开启控制台 VT 模式」,并识别 Windows Terminal(WT_SESSION)、mintty / Git Bash(MSYSCON)、ConEmu(ConEmuANSI=ON)、ansicon/cmder 与带后缀的 TERM。VT 启用成功还兼作「背后是真控制台」的第二证据(管道/重定向下该调用必然失败,不会因此误染色)。判定核心 decide() 是纯函数(环境、控制台、os 家族、版本号、VT 状态全部注入),故 Windows 分支在任意平台上都可单测。
    • 新增 src/Console/Concerns/EnsuresAnsiOutput.php 并接入五命令的 execute():时序上 Console::run()configureIO() 先跑、之后才到 execute(),所以覆盖 setDecorated() 安全且天然早于任何一次 writeln()。只调框架公开 API,不重绑、不反射任何框架对象,零侵入约定不变。
    • 取向偏保守stdout 不是终端(管道、重定向、CI)时一律不上色,route:forge:list --jsonroute:forge:types 的产物里不会混入 ANSI 转义码;遵守 NO_COLORTERM=dumb;个别终端下 PHP 认不出控制台时宁可不上色,也不把 ←[32m 这类乱码写进终端。
    • 显式表态优先:命令带 --ansi / --no-ansi 时本包完全不介入(框架的 configureIO 已处理),--ansi 同时是自动判定过保守时的逃生舱。think 的 Buffer / Nothing 输出驱动压根没有 setDecorated,此处静默降级,不牵动包内测试与非 console 输出场景。
    • 验证:示例项目(vendor/route-forge/thinkphp 为符号链接,改动即时生效)在管道下实测 ANSI 转义序列计数——list / types 默认 0--ansi 26--no-ansi 0--json(连 --json --ansi 也是)恒 0json_decode 正常、clear / publish --ansi2;包内 145 → 156 例全绿(检测器 8 例 + trait 契约 3 例)。「终端真出颜色」这层包内 Buffer 驱动验不到(它压根不过 Formatter),由维护者在**主终端 PowerShell 7.6.5(Windows Terminal 宿主)**与 Git Bash、git-cmd、cmd 四类终端确认真实交互下 stream_isatty(STDOUT)sapi_windows_vt100_support(STDOUT, true) 均为 true——即 conhost 路径与第三方终端路径各自独立成立,自愈必然放行(着色取决于控制台宿主是否支持 VT,与 PowerShell 版本无直接关系)。
    • 命令选项、端点与摘要结构、错误码集合均无变化,JSON/TS 产物字节不变,故为补丁版本。文档同步于 README「终端着色」与「与 Laravel 版的差异」、llms.txt

完整记录见 CHANGELOG.md

v1.2.0

Choose a tag to compare

@xyj2156 xyj2156 released this 13 Sep 05:19
ea2b436

Fixed

  • 命令场景收集不到 route/*.php 注册的路由(route:forge:list / route:forge:types 在真实项目里不可用):三命令原先只 event->trigger(RouteLoaded::class),但 ThinkPHP 8 里 include 路由文件的是 Http::loadRoutes()(只在 HTTP 派发路径上执行),RouteLoaded 只是「加载完毕」的通知、监听者仅来自服务包的 Service::loadRoutesFrom()。于是 console 里只收得到 forge 自身的端点与管理器路由,应用业务路由一条都不进收集结果——配了 aliases 时直接抛 [RF_BE_008] 悬空别名退出。框架自带 route:list 之所以正常,是因为它自己 scanRoute() include 完才 trigger 事件。
    • 修复:list / types / gen 统一先经新增的 RouteFileLoader 真实加载路由文件。加载姿势参照官方 route:list(目录取 Http::getRoutePath()、include 之后才 trigger 事件),但刻意不跟它两处破坏框架状态的动作(Route::clear()lazy(false)):对规则树只增不减、不重绑任何框架对象,加载结果只存在于当前进程。同一次扫描会被迭代多遍,故加载器由 ForgeService 绑成容器单例做进程内幂等(二次 include 会注册出新的 RuleItem 对象,对象去重挡不住)。
    • 连带修好:route:forge:gen 的幂等基准读的是实时名称表 Route::getName(null),路由文件没进规则树时 route/app.php 里已有的名字查不到,存在重复生成风险——现在这条承诺才真正成立。
    • 与 HTTP 严格同口径的两处取舍:route/ 子目录里的路由文件(框架按 route_auto_group 递归它们是 route:list 的展示福利)与 app.with_route=false 时连顶层文件都不加载,两者都只给 warning、不悄悄多报,守住「forge 看到的 == 运行时真在服务的」。warning 复用既有通道(STDERR 与 list --jsonwarnings 字段同现),只是数组元素新增,不改任何键与结构。
    • 回归测试:新增 16 例,其中 6 例走「业务路由只来自真实路由文件」这条此前完全空白的路径(含别名目标写在路由文件里能解析、同进程连跑两条命令不产生重复条目)。本包 129 例全绿却漏掉该缺陷的根源,正是测试里的路由全部由测试代码直接 Route::get() 注册,已在 AGENTS.md 记为铁律。另在真实骨架应用 route-forge-thinkphp-example 做 A/B:修复后 list / typespublic 5 / client 4 / manage 2 / unassigned 1 + 2 条别名、manage/logs(有 tier 无 name)warning 仍在,HTTP 端点与管理器 API 数字一致;还原成 1.1.0 后旧症状复现。
  • route:forge:gen 的产物不是合法 PHPHEADER 写在双引号串里多了一层转义,落盘成 use think\\facade\\Route;(两个连续反斜杠),生成的 route/forge.auto.php 直接 ParseError。而 route/*.php 在 HTTP 与 console 都会被 include,等于跑一次 gen 就把整个应用打挂;旧用例只对条目文本做字符串断言、从没加载过产物,所以缺陷长期隐身。现改用单引号串,并补「产物能被 RouteFileLoader 真实加载」的回归。
    • 升级提示:1.1.0 上已经跑过 gen 的项目,磁盘上那个坏文件不会被自动修正(本命令只增不删)。gen 现在会自动检出坏头部,并早于加载停下报错、给出可照抄的正确写法——按提示手工改那一行,或删掉该文件后重跑(命令幂等,条目会重新生成)。
  • route/ 下名为 *.php 的目录会让命令崩glob('*.php') 连目录一起匹配,include 目录只抛 E_WARNING,而 think 的 Error 初始化器把 warning 转成 ErrorException(例如 gen 写失败留下的同名占位目录)。加载器按官方 route:list 的判据(DirectoryIteratorgetType() === 'file')跳过非文件命中;不可读文件仍不静默跳过。

Added

  • src/Support/RouteFileLoader.php(内部支撑类):console 侧唯一的路由文件加载入口,附 warnings() 输出「按运行时口径压根不会被加载」的结构性提示。公共契约未扩面——命令选项、端点与摘要结构、错误码集合均无变化。

完整记录见 CHANGELOG.md

v1.1.0

Choose a tag to compare

@xyj2156 xyj2156 released this 13 Sep 05:19
5a4ad66

Added

  • 管理器 /_forge/manager(仅 app_debug=true:参考 laravel 版实现的可视化面板——层级总览、路由搜索与详情、levels 与全局设置的编辑落盘。
    • 两层访问控制:非 debug 环境根本不注册管理器路由(判定走 App::isDebug()——think 只认 APP_DEBUG=0/1,读原始 env 会把生产当开发),再叠加 manager_allowed_ips 来源 IP 白名单。白名单读取处 (array) 归一,并区分「键缺失」(默认仅回环)与「显式 null」(不限制)——think 的 Config::get 点号路径按 isset() 判定,显式 null 会被误读成缺失。
    • 只读 APIGET api/routes(含别名条目、剔 HEAD)、GET api/config(展示值与守卫生效值同源)。路由名统一带 forge.manager. 前缀,由 common 的排除规则兜住,不进任何元信息输出。
    • 页面零依赖:包内自包含模板 + ManagerPageRenderer 直出,不依赖 topthink/think-viewthink\View 只是 Manager 壳)。注入数据带 JSON_HEX_TAG,层级 description 里的 </script> 无法截断脚本块;占位符未替换完即抛错,不交付半个页面。
    • 写盘比 laravel 版更严三处:复用 common 生成器后由 ThinkConfigFileStyler 只重排外壳为 think 风格,写前做值不变性校验(分别回读适配前后产物,严格相等才落盘);覆盖前按本包纪律备份 .bak-{Ymd-His};成功后清 runtime/config.php 与路由元信息缓存两级,改完下一个请求即生效。classifier 是闭包无法序列化,配置了它时拒存 422 而非静默抹平。
    • 生成物的值是字面量,不再包 Env::get:页面上改了就该立即生效,包回去会被 .env 旧值遮蔽成「改了没生效」;该取舍写进生成文件头部注释,需要 .env 驱动的手工改回。
    • 新增配置项 manager_allowed_ips(默认 ['127.0.0.1', '::1'])。此前已发布过配置的项目没有该键,落到仅本机默认,不会因升级而放开访问。

Fixed

  • 层级 endpoint_middleware 传单值字符串时被静默忽略(端点裸奔)ForgeService 注册层级元信息端点时用裸值 is_array() 守卫,配置写 'endpoint_middleware' => 'auth'(与 think 的 ->middleware() 同形的合法写法)会判 false 直接跳过注册——不报错、不崩溃,该层级元信息端点连中间件都没挂,开发者却以为它受保护,属危险方向的静默失效。现与摘要端点侧、laravel 版同口径在入口 (array) 归一(null[],保持「不限制」语义)。该配置项不经 route-forge/common 任何读取路径(common 1.1.1 的归一化只覆盖 match.prefix / match.middleware / middleware_match),故归一只能落在适配层。
    • 回归测试:层级 / 摘要端点各补一条「单值字符串写法仍被拦截」用例;反向验证过还原旧写法时层级端点返回 200 且正常吐出数据。
    • 文档:README 配置表与 config/forge.php 注释把 endpoint_middleware 写明为「数组或单个字符串都接受」。
  • 依赖下限 route-forge/common ^1.1.1^1.1.2:管理器的配置保存路径需要 1.1.2 的生成侧单值归一。页面里把 match.prefix / match.middleware 写成单值字符串(与 think 的 ->middleware() 同形的直觉写法)时,1.1.1 会在 exportInlineArray(array) 的类型声明上 TypeError,表现为保存返回 500「配置写入失败」,真因只出现在应用日志里。已补端到端回归用例锁住该写法能保存成功并落盘为单元素数组。

完整记录见 CHANGELOG.md

v1.0.0

Choose a tag to compare

@xyj2156 xyj2156 released this 11 Sep 15:21
2d1c6a8

首个正式版本。0.0.x 属脚手架期;自本版起承诺公共 API、/_forge/routes 端点契约与命令输出形态稳定,破坏性变更一律走 major。

新增

  • route:forge:gen 命令 + AutoRouteScanner:把「当前可被 ThinkPHP 自动路由触达的端点」反向物化成显式命名路由,让习惯自动路由的项目低成本接入。单应用写 route/forge.auto.php,多应用写 app/{模块}/route/forge.auto.php
    • 只新增、绝不删除;幂等可反复运行;悬空条目只提醒不清理;生成条目不写 tier,落 unassigned 并留 // ->tier('…') 待填
    • 动作段按 ThinkPHP 自身的可达规则反推:think 用「URL 段 + action_suffix」命中方法,故 listViewaction_suffix='View' 下的可达 URL 是 user/list,生成短形式;不以该后缀结尾的方法本无可达 URL,只登记不生成(生成即凭空新增端点)。
    • 防误用:单应用禁 --module、多应用必须显式 --module(或 *);app/controller 与模块控制器目录并存的混合布局判为有歧义并停下要求 --mode=single|multi。另提供 --path / --namespace / --dry-run
  • README 门面补徽章行;composer.jsonsupport(issues / source / docs)。

修复

  • 命令失败不再向用户倒框架堆栈route:forge:list / types 此前只捕 ForgeExceptionContract,适配层自己的 fail-fast(url_lazy_route=true、非 RuleItem 规则)逃到 console 异常处理器。且数据产物形态的失败信息改走 STDERR——此前连 [RF_BE_008] 错误文本都会混进 d.ts 产物。
  • route:forge:gen 落盘可见:目标不可写 / 被目录占位时显式报错并说明本轮已写入哪些文件,不再静默留下半个文件却返回成功(本命令幂等,修好后重跑即补齐)。
  • route:forge:types --out 的写盘校验此前在生产运行时永不生效:ThinkPHP 的错误初始化器把 E_WARNING 抛成 ErrorException=== false 分支轮不到执行(仅在测试里可达)。
  • AutoRouteScanner::detectMode() 死分支:混合布局过去静默回落 single,导致 route:forge:gen 扫错根目录并无故拒绝 --module

依赖

  • route-forge/common 下限 ^1.0^1.1.1^1.0 会放行 1.0.0,而它上面 'prefix' => 'manage' 这类最自然的单值写法直接 TypeError 崩溃,中文 level description 在摘要内嵌里还会被转成 \uXXXX

文档

  • 去掉 ThinkPHP 8 根本不存在的 url_convert 边界描述,改写为真实边界(action_suffix / camelCase / url_case_sensitive)。
  • 元数据与包简介中文化,明确不做英文版(ThinkPHP 使用者基本在国内)。

验证:本地 82 用例 / 264 断言全绿;GitHub Actions PHP 8.2–8.5 矩阵 success。

Full Changelog: 0.0.2...1.0.0

v0.0.2

Choose a tag to compare

@xyj2156 xyj2156 released this 09 Sep 13:54
6e83149

0.0.1 之后的增量:配置发布命令、易用性审查修复与 IDE 提示桩。框架无关逻辑复用 route-forge/common 1.0.0

新增

  • route:forge:publish 命令:把包内默认 config/forge.php 复制到应用 config/(ThinkPHP 无 vendor:publish)。目标已存在默认跳过;--force 覆盖前先备份。
  • 缺配置守卫route:forge:list / types / clearconfig/forge.php 未发布时给提示——交互终端询问是否立即复制;数据产物形态(--json / types 的 d.ts)只走 STDERR,绝不污染管道输出。CI / 非交互不挂起。
  • dev-only IDE 提示桩 _ide_helper.php:对 think\route\Rule@method tier()/forgeAlias(),补全 __call 魔术链式方法(不进 autoload,仅 IDE 索引)。

修复(易用性审查)

  • route:forge:types --out:目录不可建 / 写入失败时返回退出码 1 并报错,不再假报成功;成功回显绝对路径。
  • forge_summary():未注册 ForgeService 时抛可操作提示,不再冒容器「无法解析参数」堆栈。
  • route:forge:list / types:新增 ->tier()/->forgeAlias() 拼写告警->tiere() 这类被 __call 静默吞掉的写法会被点名并提示正确用法)。
  • --force 备份名同秒冲突时追加序号,不再覆盖上一个备份。
  • 缺配置守卫话术按命令产物区分。

文档

  • 完整文档入口统一改指 route-forge 文档站 https://route-forge.github.io/docs/ ;安装说明改以 route:forge:publish 为主;配置表补齐 url_prefix / scheme_version

Full Changelog: 0.0.1...0.0.2

v0.0.1

Choose a tag to compare

@xyj2156 xyj2156 released this 09 Sep 11:46
1c4047c

Route Forge for ThinkPHP —— route-forge 项目的 ThinkPHP 8 后端适配包(route-forge/thinkphp,PHP ^8.2,topthink/framework ^8.0,MIT)。把 ThinkPHP 命名路由经按层级懒加载的 HTTP 元信息端点暴露给前端,并从真实路由规则树生成 TypeScript 类型。框架无关业务逻辑全部复用 route-forge/common

新增能力

  • 层级分配三通道:路由链式 ->tier()、分组链式 ->tier() 透传(嵌套内层覆盖外层)、config/forge.php 按前缀/中间件(any/all/DNF)批量匹配
  • 五级优先级:显式 ->tier() > 分组透传 > classifier 回调 > 配置匹配 > unassigned 兜底;strict_mode 严格模式
  • 元信息端点 GET /_forge/routes/{level} + 摘要端点 GET /_forge/routes,各层级独立 endpoint_middleware 保护,URI 归一(<id>{id})、methods 归一
  • 路由别名->forgeAlias() 与 config aliases 双通道,宏优先、悬空别名 fail-fast
  • 统一缓存:think Cache 驱动桥接(0=永久映射),app_debug 自动旁路
  • think console 命令route:forge:list(table/JSON、--level/--unassigned/--aliases、别名色标)、route:forge:types(d.ts/--json/--out)、route:forge:clear
  • 内嵌摘要 helper forge_summary()(模板 {:forge_summary()},等价 Laravel @forgeSummary
  • 零侵入适配:不继承、不重绑 think 路由,->tier()/->forgeAlias()Rule::__call 落 option

已知限制(如实说明)

  • 层级合法性在扫描期校验(非定义期 fail-fast);未显式 ->name() 的路由视为未命名,不入元信息
  • 不支持 url_lazy_route=true(延迟解析下规则树不完整,端点/命令 fail-fast 抛清晰异常)
  • v1 不含可视化管理器页面(规划二期)

完整功能规格(框架无关)见 route-forge 文档站。

Full Changelog: 0c66d1e...0.0.1