Skip to content

fix(ci): 修复 origin/main 上两层 lint 失败,恢复 CI 信号 - #21

Merged
worable233 merged 2 commits into
mainfrom
fix/sdk-lint-non-null
Oct 24, 2026
Merged

worable233 merged 2 commits into
mainfrom
fix/sdk-lint-non-null

Conversation

@worable233

@worable233 worable233 commented Oct 24, 2026 •

Copy link
Copy Markdown
Owner

问题

origin/main 自身的 Install·License·Check·Build 是红的,所有 PR(#13–#20)都因此
显示失败。红在哪两层:

packages/sdk lint: src/frontend.ts 754:15 / 758:64  error  Forbidden non-null assertion
apps/web lint: src/components/theme-json-editor.tsx 81:22  error  Compilation Skipped: Existing memoization could not be preserved
apps/web lint: tests/cart-checkout.test.ts 28:61 / 31:56  error  Unexpected any

这些都不是任何 PR 引入的,是 main 上的既有缺陷。所有 PR 都红 = 没有信号,
这比某一条具体失败更危险:真实的回归会被淹没在「反正一直是红的」里。

修复

1. packages/sdk/src/frontend.ts 非空断言

// 之前
values[field.name] =
  typeof source === 'string'
    ? values[source]!
    : source.map((mapping, index) => { ... values[key]! ... });

// 之后
if (typeof source === 'string') {
  const migrated = values[source];
  if (migrated !== undefined) values[field.name] = migrated;
} else { ... 显式判空 ... }

为什么不加 eslint-disable:索引访问的类型确实是 T | undefined,断言是在骗
类型系统而不是表达事实。外层 keys.some(...) 只保证某个 key 有值,不保证
这个 key 有值。判空后语义不变(值缺失时不写入)。

2. theme-json-editor.tsx React Compiler memo

useMemo 的回调里混了 try/catch 和多个提前 return,React Compiler 拒绝保留
手工 memo,直接报错。把校验逻辑提成模块级纯函数 validateDraft,hook 体只剩一行:

const validation = useMemo(() => validateDraft(schema, text, t), [schema, t, text]);

顺带好处:这段校验现在可以被独立测试,内联版本不行。

3. cart-checkout.test.ts 的 any

把 ReactElement<Record<string, any>> 换成显式列出的 ElementProps
(children/name/value/type/role/action/onClick/onChange/onSubmit/disabled)。

不图省事用 Record<string, unknown>(每次读 props 都要再断言),也不保留 any
(断言里写错 prop 名如 onClik 会静默读到 undefined 让用例假绿)。

验证

项 结果
packages/sdk eslint 0 problems
packages/sdk tsc clean
apps/web eslint 0 problems
apps/web tsc --noEmit clean
apps/web cart-checkout 测试 11/11

影响

合入后 #13–#20 的 CI 应全部转绿(此前失败点相同)。

origin/main 的 Install·License·Check·Build 一直是红的:packages/sdk 的
no-non-null-assertion 在 754/758 行报错。所有基于 main 的 PR 都因此失败,
掩盖了真实的回归信号。

改成显式判空:values[source] 取出的值先落到局部变量再判 undefined,
不再用 `!` 断言。「已确认非 undefined」在 Object.entries 循环里并不成立
(索引访问的类型是 T | undefined),断言是在骗类型系统而不是表达事实。
@worable233
worable233 force-pushed the fix/sdk-lint-non-null branch 2 times, most recently from d47e47c to 80dc3d9 Compare October 24, 2026 09:10
@worable233 worable233 changed the title fix(sdk): 去掉 frontend.ts 两处非空断言,恢复 lint 绿灯 fix(ci): 修复 origin/main 上两层 lint 失败,恢复 CI 信号 Oct 24, 2026
@worable233
worable233 force-pushed the fix/sdk-lint-non-null branch 5 times, most recently from b98f250 to d65ba25 Compare October 24, 2026 09:58
origin/main 自身的 Install·License·Check·Build 是红的,所有 PR(#13–#20)都因此
显示失败。**所有 PR 都红 = 没有信号**,真实回归会被淹没在「反正一直是红的」里。

## 第一层:packages/sdk 非空断言
`src/frontend.ts` 754/758。索引访问类型本来就是 `T | undefined`,外层
`keys.some()` 只保证某个 key 有值、不保证这个 key 有值。改成显式判空。

## 第二层:apps/web —— React Compiler memo + 测试 any
- `theme-json-editor.tsx:81`:`useMemo` 回调里混了 `try/catch` 与多个提前
  return,React Compiler 拒绝保留手工 memo 并直接报错。校验逻辑提成模块级纯函数
  `validateDraft`,hook 体只剩一行调用,顺带让这段校验可独立测试。
- `cart-checkout.test.ts`:`Record<string, any>` 换成显式 `ElementProps`。
  不用 `Record<string, unknown>`(每次读 props 又要断言一次),也不留 `any`
  (prop 名写错会静默读到 `undefined` 让用例假绿)。

## 第三层:apps/api —— 非空断言 + import() 类型注解
- `routes/theme-configuration.ts` 36/48:`request.user!.id` 改成判空后抛 401。
  guards 里有 requireAuth,但断言让这层保证在类型系统里消失 —— 重构掉守卫时
  不会有任何报错。
- `lib/theme-configuration.ts:87`:`migrateFrontendSettings` 对空输入返回
  undefined,用 `!` 会把 undefined 带进 `Object.entries`。改成 `?? {}`。
- `tests/unit/market-theme-install.test.ts`:`import()` 类型注解改成顶部 type import。

## 第四层:被 lint 失败掩盖的真实测试失败
lint 全绿之后才暴露出 `audit-referral-reward.test.ts` 在 CI 上是红的:

    AssertionError: expected 404 to be 200
    relation "ext_referral_code" does not exist

原因不是 schema 坏了,而是 **CI runner 上根本没有 referral 插件**。它不是内核
内置插件(`packages/plugins/referral` 只有 4 个残缺源文件、没有 `package.json`),
源码在独立仓库 `stackpanel-plugins`,只有部署过的 data 目录才有。

改成显式探测 `<dataDir>/plugins/referral/manifest.json` 后 `skipIf`。
「插件没装」和「表不存在」是两回事,让前者伪装成后者会浪费大量排查时间。

## 验证
- packages/sdk:eslint 0 problems,tsc clean
- apps/web:eslint 0 problems,tsc -p tsconfig.test.json clean,cart-checkout 11/11
- apps/api:eslint 0 problems,tsc clean
  - theme 集成 15/15
  - market-theme-install 4/4
  - audit-referral-reward 1/1(本地已装 referral)

## 影响
合入后 #13–#20 的 CI 应全部转绿。
@worable233
worable233 force-pushed the fix/sdk-lint-non-null branch from d65ba25 to d6e7227 Compare October 24, 2026 09:58
@worable233
worable233 merged commit 5d5d66b into main Oct 24, 2026
1 check passed
@worable233
worable233 deleted the fix/sdk-lint-non-null branch October 24, 2026 10:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant