Skip to content

fix: handle with { type: "file" } import attributes in tsx dev mode#1009

Closed
betegon wants to merge 2 commits into
fix/require-shim-relative-pathsfrom
fix/with-file-import-attribute
Closed

fix: handle with { type: "file" } import attributes in tsx dev mode#1009
betegon wants to merge 2 commits into
fix/require-shim-relative-pathsfrom
fix/with-file-import-attribute

Conversation

@betegon
Copy link
Copy Markdown
Member

@betegon betegon commented May 22, 2026

Depends on #1008.

Problem

ink-ui.ts imports the Ink app sidecar using a with { type: "file" } import attribute:

import inkAppPath from "./ink-app.tsx" with { type: "file" };

This is a static module-level import, so Node.js evaluates it when ink-ui.ts is first loaded. Node.js only supports with { type: "json" } — everything else throws ERR_IMPORT_ATTRIBUTE_UNSUPPORTED. The factory catches the error and falls back to LoggingUI, but LoggingUI can't do interactive prompts, so the wizard fails with:

Error: The interactive UI failed to load. Run with --yes for non-interactive mode.

This affected anyone running pnpm cli init or pnpm tsx /path/to/bin.ts init in a real terminal — the interactive wizard UI was never reachable in dev mode.

Why it worked before: Bun supports with { type: "file" } natively. In esbuild builds, text-import-plugin transforms the attribute away before Node.js ever sees it.

Fix

Register a synchronous Node.js module hook in require-shim.mjs using registerHooks() (available from Node 22.15, our minimum). When a module is imported with with { type: "file" }, the hook returns a synthetic ESM module that exports the file's absolute path as a string — the same behaviour Bun provides natively.

registerHooks({
  load(url, context, nextLoad) {
    if (context.importAttributes?.type === "file") {
      return {
        format: "module",
        shortCircuit: true,
        source: `export default ${JSON.stringify(new URL(url).pathname)};`,
      };
    }
    return nextLoad(url, context);
  },
});

Test plan

# Before
pnpm tsx /path/to/bin.ts init
→ Error: The interactive UI failed to load. Run with --yes for non-interactive mode.

# After
pnpm tsx /path/to/bin.ts init
→ Launches the Ink wizard UI

Note: tsx v4.20.6 itself emits a [DEP0205] deprecation warning for module.register() — that's a tsx bug unrelated to this change.

BYK and others added 2 commits May 22, 2026 20:02
Align CI secret references with the actual secret names set in the
production environment:

- `secrets.CSC_LINK` → `secrets.APPLE_CERT_DATA`
- `secrets.CSC_KEY_PASSWORD` → `secrets.APPLE_CERT_PASSWORD`
- `vars.APPLE_TEAM_ID` → `secrets.APPLE_TEAM_ID`

The previous names were copied from Spotlight's workflow but the secrets
were set with different names in this repo.
In Bun, `with { type: "file" }` import attributes work natively.
In esbuild builds, text-import-plugin transforms them at bundle time.
In tsx dev mode, Node.js throws ERR_IMPORT_ATTRIBUTE_UNSUPPORTED because
it only supports `with { type: "json" }`. This caused ink-ui.ts to fail
to load entirely, falling back to LoggingUI and producing:

  Error: The interactive UI failed to load. Run with --yes for non-interactive mode.

Fix: register a synchronous Node.js module hook in require-shim.mjs via
registerHooks() (available from Node 22.15, our minimum) that intercepts
`with { type: "file" }` imports and returns the file's absolute path as
a default export — matching Bun's native behaviour.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

Codecov Results 📊

✅ Patch coverage is 100.00%. Project has 4235 uncovered lines.
✅ Project coverage is 81.87%. Comparing base (base) to head (head).

Coverage diff
@@            Coverage Diff             @@
##          main       #PR       +/-##
==========================================
+ Coverage    81.86%    81.87%    +0.01%
==========================================
  Files          328       328         —
  Lines        23351     23359        +8
  Branches     15114     15114         —
==========================================
+ Hits         19115     19124        +9
- Misses        4236      4235        -1
- Partials      1621      1621         —

Generated by Codecov Action

@BYK
Copy link
Copy Markdown
Member

BYK commented May 22, 2026

Changes incorporated into #1008 (file import hook + createRequire fixes)

@BYK BYK closed this May 22, 2026
BYK added a commit that referenced this pull request May 22, 2026
Documents the critical esbuild bundling rule that caused two
post-migration bugs (#1008, #1009):

**esbuild only statically resolves bare `require()` calls.** Any aliased
require (`_require`, `localRequire`, etc.) passes through as-is into the
bundle and breaks at runtime when used with relative paths.

Adds a reference table and 4 key rules to AGENTS.md so AI agents and
contributors avoid this pattern.

Filed #1010 for the companion testing improvements (deeper smoke tests
for binary + npm bundle).
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.

2 participants