Skip to content

fix: use file URL for dynamic imports to support Windows#156

Merged
nicknisi merged 3 commits into
mainfrom
nicknisi/windows
May 20, 2026
Merged

fix: use file URL for dynamic imports to support Windows#156
nicknisi merged 3 commits into
mainfrom
nicknisi/windows

Conversation

@nicknisi
Copy link
Copy Markdown
Member

@nicknisi nicknisi commented May 20, 2026

Summary

  • Wraps the dynamic import() path in registry.ts with pathToFileURL().href so integration modules load on Windows
  • On Windows, bare absolute paths like C:\Users\...\index.js fail with ERR_UNSUPPORTED_ESM_URL_SCHEME because Node ESM interprets the drive letter as a URL scheme
  • The existing try/catch silently swallowed the error, leaving the registry empty and producing "Could not detect framework integration"

Fixes #155

Test plan

  • pnpm build passes
  • pnpm typecheck passes
  • pnpm test passes (all 1800 tests)
  • Test on Windows 11: npx workos in a Next.js project detects the framework

Summary by CodeRabbit

  • Bug Fixes
    • Improved compatibility for dynamic module loading and ESM imports.
    • More reliable browser launches for login/claim flows.
    • Better Windows support (signal handling, Node resolution, spawn behavior) and more robust child-process operations.
    • Correct handling of CRLF/LF line endings when parsing .env files.
  • Tests
    • Updated tests to mock the new browser-opening behavior.

Review Change Stack

Node's ESM dynamic `import()` rejects bare absolute paths on Windows
because the drive letter (e.g. `C:`) is interpreted as an unsupported
URL scheme. Wrap the path with `pathToFileURL()` so it works
cross-platform.

Fixes #155
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4ac75c14-378f-49e2-a313-41df284bfb4e

📥 Commits

Reviewing files that changed from the base of the PR and between e69ff94 and cd65844.

📒 Files selected for processing (2)
  • src/commands/install-skill.ts
  • src/utils/clack-utils.ts

📝 Walkthrough

Walkthrough

Adds platform detection and SPAWN_OPTS for cross-platform child-process handling, migrates browser-launch from opn to open, makes .env parsing CRLF-tolerant, updates registry dynamic imports to file:// URLs, and reorders one dependency in package.json.

Changes

Platform & runtime behavior updates

Layer / File(s) Summary
Platform exports and spawn option constant
src/utils/platform.ts
Adds IS_WINDOWS and SPAWN_OPTS exports for cross-platform spawn configuration.
Apply SPAWN_OPTS to spawn/exec callsites
src/utils/clack-utils.ts, src/utils/exec-file.ts, src/steps/run-prettier.ts, src/lib/validation/quick-checks.ts, src/lib/validation/build-validator.ts, src/steps/upload-environment-variables/providers/vercel.ts, src/commands/dev.ts
Applies SPAWN_OPTS to child process invocations and replaces exec-based flows with spawn where applicable (install flow, prettier, execFile, Vercel CLI).
Windows signal handling and bin resolution
src/commands/dev.ts, src/commands/emulate.ts, src/lib/dev-command.ts
Adds SIGBREAK handling on Windows and improves Node binary resolution by preferring .cmd wrappers on Windows.
Agent paths and Gradle wrapper detection
src/commands/install-skill.ts, src/lib/validation/build-validator.ts
Compute Windows APPDATA-based agent directories and prefer gradlew.bat on Windows when detecting Gradle wrapper.
Dotenv path resolution fix
src/bin.ts
Use fileURLToPath(new URL(...)) for .env.local path passed to dotenv.

Browser launcher migration

Layer / File(s) Summary
Switch imports from opn to open
src/lib/run-with-core.ts, src/commands/claim.ts, src/commands/login.ts
Replace opn imports with open and update dynamic import usage for device-auth/browser launching.
Test mocks updated to open
src/commands/claim.spec.ts, src/commands/login.spec.ts
Tests now mock open instead of opn.

CRLF-tolerant env parsing

Layer / File(s) Summary
CRLF-aware .env parsing & duplicate detection
src/utils/env-parser.ts, src/doctor/checks/auth-patterns.ts, src/doctor/checks/environment.ts, src/lib/credential-discovery.ts, src/lib/validation/validator.ts
Change env and .gitignore splitting to use /\r?\n/ so parsing and duplicate-env detection work with Windows CRLF line endings.

Registry import and metadata

Layer / File(s) Summary
Dynamic import path conversion to ESM format
src/lib/registry.ts
Import pathToFileURL and call pathToFileURL(resolvedIndexPath).href when dynamically importing integration index.js files.
package.json dependency reorder
package.json
Move @workos/migrations to a different position within the dependencies object without changing versions.

Possibly related PRs:

  • workos/cli#143: Also modified browser-launch/open behavior in shared device-auth and CLI flows.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.18% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: use file URL for dynamic imports to support Windows' clearly and concisely describes the main change: using file URLs for dynamic imports to fix Windows compatibility.
Linked Issues check ✅ Passed All code changes directly address issue #155: Windows dynamic import failures are fixed via pathToFileURL wrapping in registry.ts, Windows path handling improvements across multiple files, and spawn options for cross-platform process management.
Out of Scope Changes check ✅ Passed All changes are within scope: Windows file path handling, dynamic import fixes, spawn options, line-ending normalization, and package manager updates are all necessary to resolve the Windows installation failure.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch nicknisi/windows

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 20, 2026

Greptile Summary

This PR fixes Windows incompatibilities in the WorkOS CLI by addressing ESM dynamic import path handling, child-process spawning, signal handling, and .env line-ending parsing.

  • Core ESM fix (registry.ts): wraps import() paths with pathToFileURL().href so Windows absolute paths (C:\...) are converted to valid file:// URLs instead of triggering ERR_UNSUPPORTED_ESM_URL_SCHEME.
  • opnopen migration (login.ts, claim.ts, run-with-core.ts): replaces the abandoned opn v5 package with the actively maintained open v11, which has first-class Windows support.
  • Cross-platform spawn (platform.ts + 7 call sites): introduces SPAWN_OPTS = { shell: IS_WINDOWS } so .cmd/.bat shims resolve correctly on Windows without affecting POSIX behavior; also refactors several exec() string-concatenation calls to spawn() with array args, fixing filenames with spaces.
  • Windows path/signal fixes: fileURLToPath() replaces .pathname in bin.ts, .cmd shim lookup added in dev-command.ts, SIGBREAK handler registered in dev.ts/emulate.ts, and Gradle wrapper uses gradlew.bat on Windows.
  • CRLF handling: five .env-parsing sites updated to split on /\r?\n/ instead of \n.

Confidence Score: 5/5

Safe to merge — all changes are targeted Windows compatibility fixes with no regressions on existing POSIX behavior.

The changes are narrow and well-scoped: the ESM URL fix, the opn→open migration, the SPAWN_OPTS abstraction, and the CRLF normalisation all address confirmed Windows failure modes. Each fix is independently verifiable, the existing test suite passes (1800 tests), and the author validated the end-to-end flow on Windows 11. Non-Windows code paths are unaffected because SPAWN_OPTS expands to shell:false (the Node.js default) on POSIX, and all other changes are either additive guards or standard library swaps.

No files require special attention.

Important Files Changed

Filename Overview
src/lib/registry.ts Core fix: wraps dynamic import path with pathToFileURL().href so Windows absolute paths are converted to valid file:// URLs, preventing ERR_UNSUPPORTED_ESM_URL_SCHEME.
src/utils/platform.ts New file centralizing IS_WINDOWS flag and SPAWN_OPTS ({shell: IS_WINDOWS}); cleanly isolates platform detection used across many spawn call sites.
src/bin.ts Replaces URL.pathname with fileURLToPath() for .env.local resolution; pathname returns /C:/... on Windows whereas fileURLToPath correctly strips the leading slash.
src/lib/dev-command.ts Windows binary resolution now checks for .cmd shim first; correctly falls back to bare command name when neither shim nor POSIX symlink exists.
src/utils/clack-utils.ts installPackage refactored from exec() (string concatenation) to spawn() with array args + SPAWN_OPTS; eliminates whitespace-splitting issues and adds Windows shell resolution.
src/steps/run-prettier.ts Prettier invocation switched from exec() with space-joined filename string to spawn() with individual array args, correctly handling filenames that contain spaces.
src/utils/exec-file.ts Replaces explicit shell: false with ...SPAWN_OPTS; shell remains false on non-Windows and becomes true on Windows to support .cmd shim resolution.
src/utils/env-parser.ts CRLF fix: split(/\r?\n/) handles Windows line endings; same fix applied consistently across auth-patterns.ts, environment.ts, credential-discovery.ts, and validator.ts.
src/lib/run-with-core.ts Both static and dynamic imports migrated from opn to open v11; {wait: false} option is valid in open v11 (and is also its default).
src/commands/dev.ts Adds SPAWN_OPTS to child process spawn and registers SIGBREAK handler on Windows (Ctrl+Break equivalent that SIGINT doesn't cover on Windows).
src/commands/install-skill.ts Goose paths now use Windows AppData when on Windows; join() calls use separate segment args for correct cross-platform separator handling.

Reviews (3): Last reviewed commit: "chore: formatting" | Re-trigger Greptile

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 2 additional findings.

Open in Devin Review

- Add cross-platform spawn helper (shell: true on Windows for .cmd shims)
- Fix URL.pathname in bin.ts (use fileURLToPath for Windows drive letters)
- Fix node_modules/.bin resolution to prefer .cmd on Windows
- Fix all env file parsers to handle CRLF line endings
- Replace opn with open for better Windows URL handling
- Add SIGBREAK handler for Windows signal handling
- Fix Prettier step to use spawn with array args (safe path handling)
- Fix package install to use spawn with array args (safe special chars)
- Fix Gradle wrapper resolution for Windows (gradlew.bat)
- Fix Goose agent config dir to use %APPDATA% on Windows
- Fix vercel spawn calls with shell option

Ref #155
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0a9d8d0f-beba-4d8c-9411-9943bdb85fd9

📥 Commits

Reviewing files that changed from the base of the PR and between b9c9f76 and e69ff94.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (23)
  • package.json
  • src/bin.ts
  • src/commands/claim.spec.ts
  • src/commands/claim.ts
  • src/commands/dev.ts
  • src/commands/emulate.ts
  • src/commands/install-skill.ts
  • src/commands/login.spec.ts
  • src/commands/login.ts
  • src/doctor/checks/auth-patterns.ts
  • src/doctor/checks/environment.ts
  • src/lib/credential-discovery.ts
  • src/lib/dev-command.ts
  • src/lib/run-with-core.ts
  • src/lib/validation/build-validator.ts
  • src/lib/validation/quick-checks.ts
  • src/lib/validation/validator.ts
  • src/steps/run-prettier.ts
  • src/steps/upload-environment-variables/providers/vercel.ts
  • src/utils/clack-utils.ts
  • src/utils/env-parser.ts
  • src/utils/exec-file.ts
  • src/utils/platform.ts
✅ Files skipped from review due to trivial changes (3)
  • package.json
  • src/lib/credential-discovery.ts
  • src/lib/validation/validator.ts

Comment thread src/commands/install-skill.ts Outdated
Comment thread src/utils/clack-utils.ts
Comment thread src/utils/clack-utils.ts
Comment on lines +337 to +345
fs.writeFileSync(
join(process.cwd(), `workos-installation-error-${Date.now()}.log`),
JSON.stringify({
stdout: redactSensitiveInfo(stdout),
stderr: redactSensitiveInfo(stderr),
}),
{ encoding: 'utf8' },
);
reject(new Error(`${cmd} exited with code ${code}\n${stderr}`));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't let error-log persistence crash the failure path.

If fs.writeFileSync(...) throws here, the 'close' handler will throw asynchronously and the install promise won't reject with the original package-manager error. Please make the log write best-effort and non-blocking before rejecting.

Proposed fix
-        proc.on('close', (code) => {
+        proc.on('close', async (code) => {
           if (code !== 0) {
-            fs.writeFileSync(
-              join(process.cwd(), `workos-installation-error-${Date.now()}.log`),
-              JSON.stringify({
-                stdout: redactSensitiveInfo(stdout),
-                stderr: redactSensitiveInfo(stderr),
-              }),
-              { encoding: 'utf8' },
-            );
+            try {
+              await fs.promises.writeFile(
+                join(process.cwd(), `workos-installation-error-${Date.now()}.log`),
+                JSON.stringify({
+                  stdout: redactSensitiveInfo(stdout),
+                  stderr: redactSensitiveInfo(stderr),
+                }),
+                { encoding: 'utf8' },
+              );
+            } catch (error) {
+              debug('Failed to write installation error log:', error);
+            }
             reject(new Error(`${cmd} exited with code ${code}\n${stderr}`));
           } else {
             resolve();
           }
         });
As per coding guidelines, `src/**/*.{ts,tsx,js}`: Avoid Node-specific sync APIs (crypto, fs sync) unless necessary

@nicknisi nicknisi merged commit 6efdbba into main May 20, 2026
8 checks passed
@nicknisi nicknisi deleted the nicknisi/windows branch May 20, 2026 17:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Windows 11 failed installation

1 participant