Skip to content

fix(types): drop dead require condition from @object-ui/types exports - #5315

Merged
os-support-ai merged 2 commits into
mainfrom
claude/issue-4896-types-exports-require-condition
Aug 19, 2026
Merged

fix(types): drop dead require condition from @object-ui/types exports#5315
os-support-ai merged 2 commits into
mainfrom
claude/issue-4896-types-exports-require-condition

Conversation

@os-support-ai

@os-support-aios-support-ai commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Fixes#4896

The defect

packages/types/package.json's exports["."] map declared a require condition pointing at dist/index.cjs — a file the package's "build": "tsc" script (bare tsc, no bundler, no dual-format step) structurally never emits. Verified on a clean rebuild (rm -rf dist tsconfig.tsbuildinfo && tsc inside packages/types): zero .cjs files under dist/, and require.resolve('.../dist/index.cjs') throws MODULE_NOT_FOUND.

Route taken — drop the condition, verified rather than assumed

The triage comment suggested dropping the require condition (ESM-only is the contract-honest shape, given "type": "module" and no bundler) rather than adding a second build format. Both premises behind that route were verified directly, not taken on faith:

  • "type": "module" is declaredpackages/types/package.json:5.

  • Nothing in-repo consumes this package via require()grep -rn "require(['\"]@object-ui/types" across the whole repo (excluding node_modules) returns zero hits. Counter-probed with a known-present neighbouring term (require('@object-ui/types broadly, and CJS package.jsons repo-wide) to make sure the zero was a real zero and not a grep miss: exactly two workspace packages default to CommonJS (packages/vscode-extension, apps/site); apps/site declares @object-ui/types as a dependency but has zero source references to it (phantom/unused); packages/vscode-extension is the one real case — it builds via tsup --format cjs with noExternal: ['@object-ui/types', '@object-ui/core'], forcing both to be bundled into its CJS output, and @object-ui/core does value-import runtime exports from @object-ui/types (ACTION_LOCATIONS, errorCodeIs, actionRendersAt, etc. — not just types). I built it both ways to settle whether esbuild's bundler actually needs the require condition for this path:

    # baseline (require condition present, pointing at nonexistent dist/index.cjs)
    CJS dist/extension.js 32.87 KB
    CJS ⚡️ Build success in 25ms
    # after removing the require condition
    CJS dist/extension.js 32.87 KB
    CJS ⚡️ Build success in 26ms
    

    Byte-identical output either way — esbuild resolves the import statements it's bundling via the import condition regardless of its own output format, so the require condition was dead weight even for the one real CJS-bundling consumer in this repo.

The changeset call

Judged non-breaking → patch, and here is why, measured rather than asserted. I ran a real require('@object-ui/types') through the package's actual workspace symlink (packages/core/node_modules/@object-ui/types, which resolves to ../../../types) in both states:

  • Before this fix: MODULE_NOT_FOUND: Cannot find module '.../dist/index.cjs' (the condition existed but pointed nowhere real).
  • After this fix: ERR_PACKAGE_PATH_NOT_EXPORTED: No "exports" main defined ... (no matching condition).

Both throw. No working require() call is turned into a failing one, because there was never a working one — the build has never emitted dist/index.cjs, in this repo or in any published version. Stays patch on @object-ui/types only — never major, well inside objectui's fixed-group minor/patch lane.

The pin — manifest-level, not artifact-level, and why

Added packages/types/src/__tests__/package-exports-manifest.test.ts: pins the . export to exactly {types, import}, pins "type": "module", pins "build": "tsc" (the premise that makes a require condition suspect at all — no bundler, no dual-emit), and asserts no export condition anywhere in the map targets a .cjs file.

This is deliberately a manifest-level pin, not a build-artifact-level gate, for a repo-specific reason: turbo.json's test task only dependsOn: ["^build"] (the dependency closure, never the package's own build), and ci.yml's per-PR Test job runs pnpm test with no build step ahead of it at all. A test that required a fresh dist/ to exist would be vacuously absent-or-red on a cold CI cache — exactly the trap scripts/check-package-self-import.mjs's header documents for a different gate, and the same reason this repo's own artifact-level gates (check:published-dist, check:node-esm-load's full/LOAD leg) are deliberately release-time/nightly workflows, never per-PR (published-dist-gate.yml, node-esm-load-gate.yml).

Reverse-verified, with the fix committed first so the revert is a real restore point:

  1. Reverted just packages/types/package.json to origin/main's content (test file kept).
  2. Predicted: the "root export exactly {types, import}" and "no .cjs target" tests go red; the "type: module" and "build: tsc" tests stay green.
  3. Observed: exactly thatTests 2 failed | 2 passed (4), both failures naming the reintroduced require: ./dist/index.cjs.
  4. Restored the fix from the branch's own committed state; all 4 green again.

The artifact-level claim (a clean tsc build never emits .cjs) is the one manual proof above, run once for this fix — it doesn't need a repeating gate, because what the pin protects going forward is the manifest staying internally consistent with the build config ("build": "tsc"), and any future change to either would need to touch this test.

Out of scope, filed separately if warranted

Per the card: the 22-package exports-vs-dist sweep the original finding (#4896) mentioned is explicitly out of scope for this PR. This fix doesn't establish that the wider pattern is a live problem elsewhere (the two other spot-checked packages in the finding, data-objectstack and fields, both have build configs that plausibly back their require conditions), so I have not filed a follow-up sweep issue — filing one on top of an unconfirmed pattern would be speculative, not evidence-based.

Verification

  • pnpm exec vitest run packages/types/39 files / 447 tests passed (includes the new pin, in both states above).
  • pnpm --filter '@object-ui/types' type-check → passed (tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json).
  • node scripts/check-changeset-no-major.mjs → passed.
  • node scripts/check-changeset-presence.mjs → passed (1 changeset for 1 released package touched).
  • node scripts/check-phantom-dependencies.mjs → passed (40 packages, 14925 specifiers scanned).
  • node scripts/check-node-esm-load.mjs --specifiers-only → passed (cheap/source leg; the build-dependent LOAD leg is the dedicated nightly workflow noted above, not a per-PR gate in this repo).
  • node scripts/check-control-bytes.mjs → passed.
  • All of the above re-run at the final commit, 0097d2752.

Generated by Claude Code

The root `.` export's `require` condition pointed at `dist/index.cjs`,
which the package's bare-`tsc` build (`"build": "tsc"`, no bundler)
structurally never emits. Verified on a clean rebuild: dist/ contains
zero .cjs files and require.resolve('.../dist/index.cjs') throws
MODULE_NOT_FOUND.
The package declares "type": "module" and is types-first, so ESM-only
is the contract-honest shape. Verified both premises for dropping
rather than adding an emit step: nothing in-repo require()s this
package (zero grep hits), and the one in-repo CJS-format bundle
consumer (packages/vscode-extension, tsup --format cjs with
noExternal: ['@object-ui/types', '@object-ui/core']) builds
byte-identically (32.87 KB) with the condition present or absent —
esbuild resolves the value imports it re-exports (ACTION_LOCATIONS,
errorCodeIs, actionRendersAt, etc.) via the "import" condition
regardless of its own output format.
Adds a manifest-level pin (packages/types/src/__tests__/
package-exports-manifest.test.ts) so the exports map cannot silently
regress to declaring a condition the build cannot back.
Judged non-breaking: measured require('@object-ui/types') through the
real workspace symlink both before and after the fix — it throws
either way (MODULE_NOT_FOUND before, ERR_PACKAGE_PATH_NOT_EXPORTED
after). No working require() call is turned into a failing one.
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)25.3 KB350 KB
Entry fileindex-h-5c0RAO.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)9.83KB3.70KB
app-shell (runtime-config.js)7.42KB2.32KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)8.92KB3.41KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)29.33KB7.05KB
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)40.21KB10.79KB
auth (createAuthenticatedFetch.js)6.34KB2.43KB
auth (index.js)2.71KB1.22KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.02KB0.88KB
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)506.26KB113.31KB
core (index.js)4.11KB1.62KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)159.80KB44.34KB
fields (index.js)237.07KB59.46KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.42KB1.39KB
i18n (pickLocalized.js)3.69KB1.73KB
i18n (provider.js)23.13KB7.63KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)29.43KB7.15KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)39.16KB10.97KB
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)9.35KB3.31KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.42KB1.42KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.81KB0.83KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.75KB18.37KB
plugin-chatbot (index.js)181.21KB43.14KB
plugin-dashboard (index.js)127.92KB32.80KB
plugin-designer (index.js)212.39KB42.83KB
plugin-detail (index.js)241.46KB60.56KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)124.19KB30.20KB
plugin-gantt (index.js)164.10KB39.87KB
plugin-grid (index.js)198.22KB53.27KB
plugin-kanban (index.js)52.93KB14.60KB
plugin-list (index.js)111.66KB27.13KB
plugin-map (index.js)20.08KB6.62KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.49KB11.93KB
plugin-timeline (index.js)26.68KB7.66KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.33KB20.61KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)36.10KB12.26KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.33KB0.69KB
react (schema-input.js)1.45KB0.83KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (index.js)4.77KB2.16KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)10.76KB3.17KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)6.92KB2.40KB
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 (dashboard-filter-alias.js)6.23KB2.74KB
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)3.08KB1.53KB
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

@os-support-ai
os-support-ai marked this pull request as ready for review August 19, 2026 13:21
@os-support-ai
os-support-ai added this pull request to the merge queueAug 19, 2026
Merged via the queue into main with commit 3cf4de0Aug 19, 2026
22 checks passed
@os-support-ai
os-support-ai deleted the claude/issue-4896-types-exports-require-condition branch August 19, 2026 13:22
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding: @object-ui/types exports["."].require points at dist/index.cjs, which its bare-tsc build never emits

2 participants

@os-support-ai@claude