Skip to content

fix(create-plugin): 把脚手架 build 侧 devDependencies 锚到仓内工具链,并把整张清单钉进 parity 测试 - #3754

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-3742-scaffold-build-deps-parity
Aug 8, 2026
Merged

fix(create-plugin): 把脚手架 build 侧 devDependencies 锚到仓内工具链,并把整张清单钉进 parity 测试#3754
yinlianghui merged 1 commit into
mainfrom
claude/issue-3742-scaffold-build-deps-parity

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes#3742

先说结论:issue 标题的那个论断不成立,但卡片要做的事成立

标题说「@vitejs/plugin-react ^4.2.1 的 peer 结构性无法被 vite ^7.3.1 满足 …… pnpm install 必报 peer 冲突」。这一条经实测不成立,原因是把「区间」当成了「定版」来量:

  • issue 量的是 npm view @vitejs/plugin-react@4.2.1 peerDependencies{ vite: '^4.2.0 || ^5.0.0' },那是区间下界那一个版本的 peer。
  • ^4.2.1 实际装的是 4.x 的最高版 4.7.0,它的 peer 早已放宽:
$ npm view @vitejs/plugin-react@4.7.0 peerDependencies
{ vite: '^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0' }

vite ^7.3.1(解析到 7.3.6)落在里面。拿改动前的模板清单做一次真实解析,并且把 strict-peer-dependencies 开到比本仓更严:

$ pnpm install --lockfile-only # .npmrc: strict-peer-dependencies=true
Done in 5s using pnpm v10.33.0 # 退出码 0,零 peer 警告
# pnpm-lock.yaml:
'@vitejs/plugin-react':
specifier: ^4.2.1
version: 4.7.0(vite@7.3.6)

装得干干净净。所以没有 peer 冲突要修

但漂移是真的,而且卡片给的修法方向正确、与那个论断无关:五条 build 区间从未被锚定到任何东西,#3716 对生成产物的端到端验证只在仓内实际装的版本下跑过,声明区间从来不是被测的那一组。这个 PR 修的是漂移,不是 peer。changeset 和代码注释都按事实写,没有顺着标题写成「修了 peer 冲突」。

改了什么

1. 五条 build 区间改为从仓内锚点取值

沿用 PR #3733 给三条 test 区间定的做法(锚到仓根清单)。需要两个锚点,因为仓根并不声明全部:生成器写入 packages/plugin-插件名(相对 cwd),生成的包是 packages/plugin-*同级兄弟,所以仓根不声明的两个 build 工具就锚到这些 plugin 清单上。

依赖锚点
vite^7.3.1^8.2.0仓根
typescript^5.9.3^6.0.3仓根
vitest^4.0.18^4.1.10仓根
@vitejs/plugin-react^4.2.1^6.0.519/19 个 packages/plugin-*(仓根无)
vite-plugin-dts^4.5.4^5.0.319/19 个 packages/plugin-*(仓根无)

后两条无需在「选哪个 plugin」上做判断:全部 19 个仓内 plugin 对这两个区间完全一致,测试断言的就是这个一致性(不一致就红,并列出分歧的清单),而不是挑一个赢家。

2. parity 测试覆盖整张 devDependencies 清单

这是卡片的重点。原测试只钉三条 test 区间,旁边五条 build 区间就是因为没有任何东西钉它们才漂了 1–2 个 major。现在:

  • 完整性闸门 —— 生成的 devDependencies 的键集合必须与锚点表逐一对应;新增一个依赖却不声明锚点即红。这条是防止「再退回只覆盖子集」的关键。
  • 逐条 parity —— 每条区间必须逐字等于其锚点的区间。
  • 双锚一致性 —— 凡是仓根和 plugin 清单都声明的依赖,两处必须已经相同,于是「读哪个锚点」这件事不再承重、不会藏住漂移。

3. 生成的 vite.config.ts:__dirnameimport.meta.dirname

issue 的附带观察,已核实,而且比「有警告」更严重。用 vite 8.2.0 自己的 loadConfigFromFile 把生成产物的两个版本各按两种 loader 加载一遍(预测在前,四条全部命中):

########## 旧(__dirname) / bundle
(!) Your Vite config uses features that are unsupported by `configLoader: 'native'`,
which is planned to become the default in a future major version of Vite:
- `__dirname` (vite.config.old.ts:15:27). Use `import.meta.dirname` instead
RESULT: LOAD OK; build.lib.entry = .../src/index.tsx
########## 旧(__dirname) / native
RESULT: LOAD FAILED: __dirname is not defined in ES module scope
########## 新(import.meta.dirname) / bundle
RESULT: LOAD OK; build.lib.entry = .../src/index.tsx # 无警告
########## 新(import.meta.dirname) / native
RESULT: LOAD OK; build.lib.entry = .../src/index.tsx

即在未来默认的 native 下旧写法是直接加载失败,不只是告警;新写法在两种 loader 下都正常,且解析出同一个 entry。apps/console/vite.config.ts 已在 #3384 因同样理由转换过,本仓 engines.node: ">=22" 满足 import.meta.dirname

#3592(仓内 28 个 config 用 __dirname,标 finding)不重叠:那张单子的普查是 git grep -- '*vite.config.ts',扫不到本模板 —— 模板是 .ts 源码里的字符串字面量。这里修的是这个模式的产出源头,#3592 的存量替换不受影响。

验证

$ pnpm exec vitest run packages/create-plugin/ --maxWorkers=2
Test Files 1 passed (1)
Tests 12 passed (12)
$ pnpm --workspace-concurrency=2 --filter @object-ui/create-plugin type-check # 退出 0
$ pnpm --workspace-concurrency=2 --filter @object-ui/create-plugin lint # 退出 0
$ node scripts/check-control-bytes.mjs
✅ check-control-bytes: OK (scanned 3691 tracked text file(s); skipped 85 binary).
$ node scripts/check-changeset-no-major.mjs
✅ No changeset declares a `major` bump.

反向验证(先写预测再跑,四次全部按预测方向红):

扰动预测实际
vite^8.1.0parity 红,点名 viteAssertionError: vite range must match the repo root: expected '^8.1.0' to be '^8.2.0'
@vitejs/plugin-react^6.0.4parity 红,点名该依赖AssertionError: @vitejs/plugin-react range must match packages/plugin-*: expected '^6.0.4' to be '^6.0.5'
增一条无锚点依赖完整性测试红AssertionError: expected [ … 8 ] to deeply equal [ … 7 ] / + "unanchored-newcomer"
还原 __dirname新增的 config 测试红expected '…' to contain 'path.resolve(import.meta.dirname,'

全部扰动已还原,基线 12 passed。

产物生成 smoke(#3716 的离线做法):pnpm --filter @object-ui/create-plugin build 后把构建出的生成器跑进临时目录,产出的 package.json devDependencies 与 vite.config.ts 均为新值(上面第 3 节的 config 加载就是直接拿这份产物跑的)。未做产物的完整 vite build

改后清单的两两 peer 一致性(非 optional peer 冲突 0 条):

@vitejs/plugin-react@^6.0.5 resolves to 6.0.5
OK peer vite@^8.0.0 vs declared ^8.2.0 (resolves 8.2.1)
vite-plugin-dts@^5.0.3 resolves to 5.0.3
OK peer vite@>=3 vs declared ^8.2.0 (resolves 8.2.1)
vitest@^4.1.10 resolves to 4.1.10
OK peer vite@^6.0.0 || ^7.0.0 || ^8.0.0 vs declared ^8.2.0 (resolves 8.2.1)
OK peer jsdom@* vs declared ^30.0.1 (resolves 30.0.1)
non-optional peer conflicts: 0

再用真实解析复核(同样开 strict-peer-dependencies=true),生成产物的清单装得干净:

 vite: specifier: ^8.2.0 version: 8.2.1
typescript: specifier: ^6.0.3 version: 6.0.3
vite-plugin-dts: specifier: ^5.0.3 version: 5.0.3(rolldown@1.2.3)(typescript@6.0.3)(vite@8.2.1)
vitest: specifier: ^4.1.10 version: 4.1.10(jsdom@30.0.1)(vite@8.2.1)

另外 packages/plugin-grid/node_modules 里实际装着 vite 8.2.0 + @vitejs/plugin-react 6.0.5 + vite-plugin-dts 5.0.3,即这一组本来就在本仓共存。

范围

只动 packages/create-plugin/(templates.tstemplates.test.ts)加一个 changeset。未动 content/docs/utilities/create-plugin.mdx(属 #3715),未动 content/docs/releases/

越界发现另行记录,未在本 PR 修:生成的 dependencieslucide-react: '^0.563.0',而仓内 23 处声明全部是 ^1.28.0;且没有任何生成的源文件 import 它。0.x 的 caret 不跨 minor 浮动(^0.563.0>=0.563.0 <0.564.0),所以脚手架是被钉死在 0.563.x 上的。它位于 dependencies 而非 devDependencies,不在本卡范围内。


Generated by Claude Code

…in the whole manifest
The five build-side ranges in `DEV_DEPENDENCIES` were never sourced from
anything: the scaffold declared vite ^7.3.1, @vitejs/plugin-react ^4.2.1,
vite-plugin-dts ^4.5.4, typescript ^5.9.3 and vitest ^4.0.18 while this repo
builds and tests every in-tree plugin on ^8.2.0 / ^6.0.5 / ^5.0.3 / ^6.0.3 /
^4.1.10. objectui#3716's end-to-end run of the generated artifact only ever
exercised the in-repo versions, so the declared ranges were not under test.
All five now quote an in-repo anchor. Two anchors are needed: the generator
writes into `<cwd>/packages/plugin-<name>`, so a generated plugin is a sibling
of `packages/plugin-*`, and those manifests anchor the two build-only tools the
root manifest omits (@vitejs/plugin-react, vite-plugin-dts) while the root
anchors the rest. All 19 in-repo plugins already agree on both ranges.
The parity test now covers every entry of the generated devDependencies instead
of the three testing ones, plus a completeness check that fails on a dependency
added without an anchor, and a cross-check that the two anchors agree wherever
both declare a dependency.
Also switches the generated vite.config.ts from `__dirname` to
`import.meta.dirname`. vite 8 still defines `__dirname` under its default
`bundle` config loader but warns on it, and under `configLoader: 'native'` --
planned to become the default -- the generated config failed to load outright
("__dirname is not defined in ES module scope"). Same conversion
apps/console/vite.config.ts got in objectui#3384.
Not a peer-dependency fix: @vitejs/plugin-react ^4.2.1 resolved to 4.7.0, whose
vite peer had widened to accept vite 7, so the old manifest installed cleanly
under strict-peer-dependencies. The cost was a scaffold one to two majors
behind its own monorepo.
Fixes#3742
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
@vercel

vercelBot commented Aug 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectuiIgnoredIgnoredAug 8, 2026 12:00pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)28.1 KB350 KB
Entry fileindex-CtzM40k0.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)8.66KB3.13KB
app-shell (runtime-config.js)7.42KB2.32KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)7.57KB2.97KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)22.10KB4.37KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.13KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.64KB2.21KB
auth (SocialSignInButtons.js)9.60KB3.89KB
auth (UserMenu.js)3.40KB1.22KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)35.76KB9.11KB
auth (createAuthenticatedFetch.js)4.37KB1.69KB
auth (index.js)2.35KB1.07KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)4.91KB0.87KB
auth (useIsWorkspaceAdmin.js)1.61KB0.85KB
collaboration (CommentThread.js)26.07KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.65KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)480.72KB105.64KB
core (index.js)2.96KB1.13KB
create-plugin (index.js)9.85KB3.18KB
data-objectstack (index.js)137.51KB35.11KB
fields (index.js)230.90KB56.84KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.32KB1.77KB
i18n (index.js)2.65KB1.06KB
i18n (pickLocalized.js)1.70KB0.83KB
i18n (provider.js)9.48KB3.27KB
i18n (useObjectLabel.js)26.14KB6.07KB
i18n (useSafeTranslation.js)4.52KB1.96KB
layout (index.js)38.53KB10.71KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.74KB
mobile (index.js)1.50KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.71KB0.42KB
mobile (useResponsiveConfig.js)1.36KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)8.75KB3.06KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)3.67KB1.12KB
permissions (evaluator.js)4.41KB1.44KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.55KB0.71KB
plugin-ai (index.js)15.71KB3.79KB
plugin-calendar (index.js)44.98KB12.37KB
plugin-charts (index.js)61.04KB17.31KB
plugin-chatbot (index.js)180.09KB42.72KB
plugin-dashboard (index.js)115.50KB29.96KB
plugin-designer (index.js)210.51KB42.51KB
plugin-detail (index.js)232.79KB57.42KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)112.10KB27.10KB
plugin-gantt (index.js)162.55KB39.57KB
plugin-grid (index.js)186.61KB49.34KB
plugin-kanban (index.js)48.30KB13.28KB
plugin-list (index.js)105.12KB25.48KB
plugin-map (index.js)16.81KB5.24KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)40.58KB10.58KB
plugin-timeline (index.js)25.76KB7.33KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.03KB20.55KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.71KB3.53KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.67KB2.37KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)19.28KB6.38KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.02KB0.55KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)4.09KB1.74KB
sdui-parser (index.js)4.47KB2.03KB
sdui-parser (parse.js)10.04KB2.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)4.69KB1.48KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-retry.js)4.32KB2.02KB
types (index.js)2.71KB1.34KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@yinlianghui
yinlianghui marked this pull request as ready for review August 8, 2026 12:10
@yinlianghui
yinlianghui added this pull request to the merge queueAug 8, 2026
Merged via the queue into main with commit e473b6cAug 8, 2026
19 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-3742-scaffold-build-deps-parity branch August 8, 2026 12:10
yinlianghui pushed a commit that referenced this pull request Aug 8, 2026
…nding (#3715)
PR #3754 (#3742) landed while this branch was open and turned templates.test.ts
from "the three testing ranges are sourced from the repo" into "every generated
devDependency range is anchored, none unpinned". The page said "the testing ones",
which was accurate at the fetch point and understated after that merge — exactly
the drift this shrink exists to avoid, so the sentence now names the anchoring
without naming a subset or a count.
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Aug 10, 2026
…bjectstack-ai#3715) (objectstack-ai#3760)
* docs(create-plugin): shrink the page to what does not drift, point at buildPluginFiles() (objectstack-ai#3715)
The page described a scaffolder that does not exist: it claimed the CLI installs
dependencies and inits a git repo (it does neither), listed six prompts (there are
two after the name), put the output in the current directory (it writes
packages/plugin-NAME), told readers to run `npm run dev` against a build.lib config
that has no dev server, and documented a whole `.create-plugin.config.js`
configuration surface the generator never reads.
Every one of those distortions came from transcribing generator output into prose,
so per the triage ruling (route B) the transcriptions are deleted rather than
rewritten: the generated tree, the four generated-file samples and the CLI's own
dependency list are replaced with a pointer at buildPluginFiles() in
packages/create-plugin/src/templates.ts, which is the single source of truth for
what a scaffolded plugin contains. What survives is the invocation surface
(install, name rules, where it writes, how to iterate, how to publish), plus the
structural facts a reader needs and that cannot go stale on their own.
No version literal is restated anywhere on the page, so doc-version-claims'
KNOWN_CLAIMS needs no row deleted (it had none for this file).
* docs(create-plugin): widen the parity-gate sentence to match objectstack-ai#3742 landing (objectstack-ai#3715)
PR objectstack-ai#3754 (objectstack-ai#3742) landed while this branch was open and turned templates.test.ts
from "the three testing ranges are sourced from the repo" into "every generated
devDependency range is anchored, none unpinned". The page said "the testing ones",
which was accurate at the fetch point and understated after that merge — exactly
the drift this shrink exists to avoid, so the sentence now names the anchoring
without naming a subset or a count.
---------
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@yinlianghui@claude