Skip to content

fix(build): preserve ESM import specifiers in published package#584

Closed
amartidandqdq wants to merge 1 commit into
NoeFabris:mainfrom
amartidandqdq:fix/esm-imports
Closed

fix(build): preserve ESM import specifiers in published package#584
amartidandqdq wants to merge 1 commit into
NoeFabris:mainfrom
amartidandqdq:fix/esm-imports

Conversation

@amartidandqdq

Copy link
Copy Markdown

Fixes #564, #568. Based on PR #565 by @pandaria75. Post-build script rewrites bare relative ESM specifiers to explicit .js or /index.js paths. Also fixes import of tool from @opencode-ai/plugin/tool.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e8bacb56-97ae-4947-992f-1039b5f948c4

📥 Commits

Reviewing files that changed from the base of the PR and between 740e315 and 4f75a8a.

📒 Files selected for processing (4)
  • package.json
  • script/fix-esm-imports.mjs
  • src/plugin.ts
  • src/plugin/quota-fallback.test.ts
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Greptile Review
🔇 Additional comments (6)
package.json (1)

37-37: LGTM!

script/fix-esm-imports.mjs (1)

1-107: LGTM!

src/plugin.ts (3)

2-2: LGTM!

Also applies to: 14-14, 43-45, 49-49, 758-758, 1456-1456


1057-1057: LGTM!

Also applies to: 2042-2065


2593-2597: LGTM!

Also applies to: 2717-2717, 2731-2731, 2735-2747, 2756-2756, 2836-2836, 2847-2847, 2854-2854, 2875-2875, 2913-2913, 2918-2918

src/plugin/quota-fallback.test.ts (1)

32-34: LGTM!


Walkthrough

This PR fixes ESM module resolution failures in packaged environments (issue #564) by adding a post-build script that normalizes relative import/export specifiers to explicit .js/.mjs file extensions. It also modernizes the import pattern from @opencode-ai/plugin to @opencode-ai/plugin/tool. Additionally, the PR enhances rate-limit retry logic with a minimum backoff constant, exports the persistAccountPool function for test access, and extends the account/quota UI with usage tracking metrics and confirmation pauses in OAuth flows.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding a build step to preserve ESM import specifiers with file extensions in the published package.
Description check ✅ Passed The description is directly related to the changeset, referencing the fixed issues (#564, #568), the base PR (#565), and explaining the post-build script and import path change.
Linked Issues check ✅ Passed The code changes comprehensively address issue #564: the post-build script normalizes relative ESM specifiers to explicit .js/.../index.js paths, and the tool import is updated to use @opencode-ai/plugin/tool.
Out of Scope Changes check ✅ Passed All code changes are scoped to the ESM import specifier fix: package.json build script, the new fix-esm-imports.mjs script, plugin.ts import/rate-limit/export adjustments, and test mock updates align with the stated objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a post-build script (script/fix-esm-imports.mjs) that rewrites bare relative specifiers in compiled dist/ output to explicit .js/index.js paths, and wires it into the build npm script. It also corrects the @opencode-ai/plugin/tool import path and aligns the test mock accordingly.

  • script/fix-esm-imports.mjs walks dist/, applies a set of regex patterns to find bare relative import/export specifiers, and resolves them by probing the filesystem for .js, .mjs, or index.js variants — falling back to the original specifier if none are found.
  • src/plugin.ts bundles several unrelated feature changes: a new parseRateLimitReason/parseRateLimitResetMs from ./plugin/rate-limit-parser, getTotalUsage from ./plugin/usage-tracker, and promptContinue from ./plugin/cli — none of which exist in the repository, breaking the TypeScript build.

Confidence Score: 1/5

Not safe to merge — the TypeScript compiler will fail before the post-build ESM fixer ever runs.

Three imports added to src/plugin.ts reference modules and an export that do not exist anywhere in the codebase: ./plugin/rate-limit-parser, ./plugin/usage-tracker, and promptContinue from ./plugin/cli. Every one of these is a hard compile-time error, so tsc exits non-zero and the ESM-fixer script — the actual stated goal of the PR — never executes.

src/plugin.ts contains all three broken imports and needs the three missing source files (or exports) to be added before the build can succeed.

Important Files Changed

Filename Overview
src/plugin.ts Three missing dependencies introduced: ./plugin/rate-limit-parser (non-existent file), ./plugin/usage-tracker (non-existent file), and promptContinue from ./plugin/cli (not exported) — all three are compile-time errors that break the build.
script/fix-esm-imports.mjs New post-build script that rewrites bare relative ESM specifiers in dist/ to explicit .js/index.js paths; logic is sound for Linux/CI but contains redundant regex patterns (patterns 4–7 overlap with pattern 1).
package.json Build script extended to chain node script/fix-esm-imports.mjs after tsc; straightforward change with no issues.
src/plugin/quota-fallback.test.ts Mock target updated from @opencode-ai/plugin to @opencode-ai/plugin/tool to match the corrected import in plugin.ts; no issues.
Prompt To Fix All With AI
Fix the following 4 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 4
src/plugin.ts:42
**Missing module: `./plugin/rate-limit-parser`**

`parseRateLimitResetMs` and the new 2-argument `parseRateLimitReason(bodyText, status)` are imported from `./plugin/rate-limit-parser`, but `src/plugin/rate-limit-parser.ts` does not exist in the repository. The TypeScript build will fail with a "cannot find module" error the moment `tsc` is invoked, which means the post-build ESM-fixer script added in the same PR never even runs.

### Issue 2 of 4
src/plugin.ts:50
**Missing module: `./plugin/usage-tracker`**

`getTotalUsage` is imported from `./plugin/usage-tracker`, but `src/plugin/usage-tracker.ts` does not exist anywhere in the repository. This is a second compile-time "cannot find module" error that will break the build independently of the missing `rate-limit-parser`.

### Issue 3 of 4
src/plugin.ts:14
**Missing export: `promptContinue` from `./plugin/cli`**

`promptContinue` is added to the named import from `./plugin/cli`, but the function is not exported (nor defined) in `src/plugin/cli.ts`. The only exports there are `promptProjectId`, `promptAddAnotherAccount`, `promptLoginMode`, and re-exports from `./ui/auth-menu`. TypeScript will raise a compile error for this import, and the eleven call-sites added throughout the plugin will fail at runtime if this is worked around.

### Issue 4 of 4
script/fix-esm-imports.mjs:9-17
**Redundant regex patterns cause all `from "..."` specifiers to be visited twice**

Pattern 1 already matches every `from "./..."` statement — including all named imports, type imports, and re-exports that patterns 4–7 target. Because each pattern is applied to the mutated `output` in sequence, the affected specifiers will be matched and passed through `resolveSpecifier` a second time. The second pass is harmless only because `hasExtension` short-circuits once `.js` has already been appended, but removing patterns 4–7 (or restricting pattern 1 to non-`from` forms) would make the intent clearer and halve the work on large `dist` trees.

Reviews (1): Last reviewed commit: "fix(build): preserve ESM import specifie..." | Re-trigger Greptile

Comment thread src/plugin.ts
@@ -40,11 +40,13 @@ import { EmptyResponseError } from "./plugin/errors";
import { AntigravityTokenRefreshError, refreshAccessToken } from "./plugin/token";
import { startOAuthListener, type OAuthListener } from "./plugin/server";
import { clearAccounts, loadAccounts, saveAccounts, saveAccountsReplace } from "./plugin/storage";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 Missing module: ./plugin/rate-limit-parser

parseRateLimitResetMs and the new 2-argument parseRateLimitReason(bodyText, status) are imported from ./plugin/rate-limit-parser, but src/plugin/rate-limit-parser.ts does not exist in the repository. The TypeScript build will fail with a "cannot find module" error the moment tsc is invoked, which means the post-build ESM-fixer script added in the same PR never even runs.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/plugin.ts
Line: 42

Comment:
**Missing module: `./plugin/rate-limit-parser`**

`parseRateLimitResetMs` and the new 2-argument `parseRateLimitReason(bodyText, status)` are imported from `./plugin/rate-limit-parser`, but `src/plugin/rate-limit-parser.ts` does not exist in the repository. The TypeScript build will fail with a "cannot find module" error the moment `tsc` is invoked, which means the post-build ESM-fixer script added in the same PR never even runs.

How can I resolve this? If you propose a fix, please make it concise.

Comment thread src/plugin.ts
import { createSessionRecoveryHook, getRecoverySuccessToast } from "./plugin/recovery";
import { checkAccountsQuota } from "./plugin/quota";
import { getTotalUsage } from "./plugin/usage-tracker";
import { initDiskSignatureCache } from "./plugin/cache";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 Missing module: ./plugin/usage-tracker

getTotalUsage is imported from ./plugin/usage-tracker, but src/plugin/usage-tracker.ts does not exist anywhere in the repository. This is a second compile-time "cannot find module" error that will break the build independently of the missing rate-limit-parser.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/plugin.ts
Line: 50

Comment:
**Missing module: `./plugin/usage-tracker`**

`getTotalUsage` is imported from `./plugin/usage-tracker`, but `src/plugin/usage-tracker.ts` does not exist anywhere in the repository. This is a second compile-time "cannot find module" error that will break the build independently of the missing `rate-limit-parser`.

How can I resolve this? If you propose a fix, please make it concise.

Comment thread src/plugin.ts
import type { AntigravityTokenExchangeResult } from "./antigravity/oauth";
import { accessTokenExpired, isOAuthAuth, parseRefreshParts, formatRefreshParts } from "./plugin/auth";
import { promptAddAnotherAccount, promptLoginMode, promptProjectId } from "./plugin/cli";
import { promptAddAnotherAccount, promptLoginMode, promptProjectId, promptContinue } from "./plugin/cli";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 Missing export: promptContinue from ./plugin/cli

promptContinue is added to the named import from ./plugin/cli, but the function is not exported (nor defined) in src/plugin/cli.ts. The only exports there are promptProjectId, promptAddAnotherAccount, promptLoginMode, and re-exports from ./ui/auth-menu. TypeScript will raise a compile error for this import, and the eleven call-sites added throughout the plugin will fail at runtime if this is worked around.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/plugin.ts
Line: 14

Comment:
**Missing export: `promptContinue` from `./plugin/cli`**

`promptContinue` is added to the named import from `./plugin/cli`, but the function is not exported (nor defined) in `src/plugin/cli.ts`. The only exports there are `promptProjectId`, `promptAddAnotherAccount`, `promptLoginMode`, and re-exports from `./ui/auth-menu`. TypeScript will raise a compile error for this import, and the eleven call-sites added throughout the plugin will fail at runtime if this is worked around.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +9 to +17
const importPatterns = [
/(from\s*["'])(\.{1,2}\/[^"']+)(["'])/g,
/(import\s*\(\s*["'])(\.{1,2}\/[^"']+)(["'])/g,
/(\bimport\s*["'])(\.{1,2}\/[^"']+)(["'])/g,
/(export\s*\{[^}]*\}\s*from\s*["'])(\.{1,2}\/[^"']+)(["'])/g,
/(export\s*(?:type\s*)?\*\s*from\s*["'])(\.{1,2}\/[^"']+)(["'])/g,
/(import\s+type\s*\{[^}]*\}\s*from\s*["'])(\.{1,2}\/[^"']+)(["'])/g,
/(import\s*\{\s*type\s+[^}]*\}\s*from\s*["'])(\.{1,2}\/[^"']+)(["'])/g,
]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Redundant regex patterns cause all from "..." specifiers to be visited twice

Pattern 1 already matches every from "./..." statement — including all named imports, type imports, and re-exports that patterns 4–7 target. Because each pattern is applied to the mutated output in sequence, the affected specifiers will be matched and passed through resolveSpecifier a second time. The second pass is harmless only because hasExtension short-circuits once .js has already been appended, but removing patterns 4–7 (or restricting pattern 1 to non-from forms) would make the intent clearer and halve the work on large dist trees.

Prompt To Fix With AI
This is a comment left during a code review.
Path: script/fix-esm-imports.mjs
Line: 9-17

Comment:
**Redundant regex patterns cause all `from "..."` specifiers to be visited twice**

Pattern 1 already matches every `from "./..."` statement — including all named imports, type imports, and re-exports that patterns 4–7 target. Because each pattern is applied to the mutated `output` in sequence, the affected specifiers will be matched and passed through `resolveSpecifier` a second time. The second pass is harmless only because `hasExtension` short-circuits once `.js` has already been appended, but removing patterns 4–7 (or restricting pattern 1 to non-`from` forms) would make the intent clearer and halve the work on large `dist` trees.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4f75a8a258

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/plugin.ts
import type { AntigravityTokenExchangeResult } from "./antigravity/oauth";
import { accessTokenExpired, isOAuthAuth, parseRefreshParts, formatRefreshParts } from "./plugin/auth";
import { promptAddAnotherAccount, promptLoginMode, promptProjectId } from "./plugin/cli";
import { promptAddAnotherAccount, promptLoginMode, promptProjectId, promptContinue } from "./plugin/cli";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Export promptContinue before importing it

This import adds promptContinue, but src/plugin/cli.ts does not export that symbol (it only exports the existing prompt helpers and isTTY). Any build or runtime ESM load of the plugin will fail on this missing export before the new menu paths can run, so either add the helper to cli.ts or remove these calls.

Useful? React with 👍 / 👎.

Comment thread src/plugin.ts
import { clearAccounts, loadAccounts, saveAccounts, saveAccountsReplace } from "./plugin/storage";
import { AccountManager, type ModelFamily, parseRateLimitReason, calculateBackoffMs, computeSoftQuotaCacheTtlMs } from "./plugin/accounts";
import { AccountManager, type ModelFamily, calculateBackoffMs, computeSoftQuotaCacheTtlMs } from "./plugin/accounts";
import { parseRateLimitResetMs, parseRateLimitReason } from "./plugin/rate-limit-parser";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add the missing rate-limit parser module

This changes the parser import to ./plugin/rate-limit-parser, but that file is not present in the tree (I checked the repo file list with find/rg). As a result the package cannot compile or load once src/plugin.ts is included; either commit the new parser module or keep importing the existing parser from ./plugin/accounts.

Useful? React with 👍 / 👎.

Comment thread src/plugin.ts
import { loadConfig, initRuntimeConfig, type AntigravityConfig } from "./plugin/config";
import { createSessionRecoveryHook, getRecoverySuccessToast } from "./plugin/recovery";
import { checkAccountsQuota } from "./plugin/quota";
import { getTotalUsage } from "./plugin/usage-tracker";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add the missing usage tracker module

The new Usage menu branch imports ./plugin/usage-tracker, but no matching source file is committed in src/plugin or elsewhere in the repo. This unresolved module breaks the build/load path for the plugin even if the menu action is never selected, so the usage tracker implementation needs to be added or the import and branch removed.

Useful? React with 👍 / 👎.

Comment thread src/plugin.ts
@@ -1,5 +1,5 @@
import { exec } from "node:child_process";
import { tool } from "@opencode-ai/plugin";
import { tool } from "@opencode-ai/plugin/tool";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Import tool from the published plugin entrypoint

OpenCode’s plugin/custom-tool API exposes tool from @opencode-ai/plugin, while this change switches to a @opencode-ai/plugin/tool subpath without updating the dependency or showing that this subpath is exported by the installed package. In installations that only have the declared @opencode-ai/plugin package entrypoint, this unresolved import prevents the plugin module from evaluating before any hooks run, so keep the root import unless the dependency/export map is updated accordingly.

Useful? React with 👍 / 👎.

const entryStat = await stat(entryPath)
if (entryStat.isDirectory()) {
files.push(...await listJsFiles(entryPath))
} else if (entry.endsWith(".js") || entry.endsWith(".mjs")) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Rewrite declaration imports as well

The post-build fixer only walks .js and .mjs outputs, but the package publishes dist/index.d.ts via the types field and those declarations keep extensionless relative exports like ./src/plugin. Consumers using NodeNext/Node16 TypeScript resolution can still fail on the published types even though the runtime .js files were fixed, so the same rewrite needs to cover emitted .d.ts files too.

Useful? React with 👍 / 👎.

Comment thread src/plugin.ts
continue;
}

if (menuResult.mode === "usage") {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Wire the usage action into the auth menu

This new usage branch is never reachable: promptLoginMode/showAuthMenu only return add, fresh, manage, check, verify, verify-all, or cancel actions, and there is no menu item or fallback input that produces mode: "usage". After the missing module is added, users still cannot open the advertised session token usage view from opencode auth login unless the menu result types and prompts are updated to return this mode.

Useful? React with 👍 / 👎.

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.

Published package fails to load in OpenCode due to broken ESM import specifiers

1 participant