Skip to content

Commit 6efdbba

Browse files
authored
fix: use file URL for dynamic imports to support Windows (#156)
* fix: use file URL for dynamic imports to support Windows 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 * fix: comprehensive Windows compatibility fixes - 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 * chore: formatting
1 parent 725235f commit 6efdbba

25 files changed

Lines changed: 199 additions & 77 deletions

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,18 +52,18 @@
5252
"@hono/node-server": "^1",
5353
"@napi-rs/keyring": "^1.2.0",
5454
"@workos-inc/node": "^8.7.0",
55+
"@workos/migrations": "^2.0.0",
5556
"@workos/openapi-spec": "^0.1.0",
5657
"@workos/skills": "0.6.0",
5758
"chalk": "^5.6.2",
5859
"diff": "^8.0.3",
5960
"fast-glob": "^3.3.3",
6061
"hono": "^4",
6162
"ink": "^6.8.0",
62-
"opn": "^5.4.0",
63+
"open": "^11.0.0",
6364
"react": "^19.2.4",
6465
"semver": "^7.7.4",
6566
"uuid": "^13.0.0",
66-
"@workos/migrations": "^2.0.0",
6767
"xstate": "^5.28.0",
6868
"yaml": "^2.8.2",
6969
"yargs": "^18.0.0",

pnpm-lock.yaml

Lines changed: 91 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/bin.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
if (process.argv.includes('--local') || process.env.INSTALLER_DEV) {
55
const { config } = await import('dotenv');
66
// bin.ts compiles to dist/bin.js, so go up one level to find .env.local
7-
config({ path: new URL('../.env.local', import.meta.url).pathname });
7+
const { fileURLToPath } = await import('node:url');
8+
config({ path: fileURLToPath(new URL('../.env.local', import.meta.url)) });
89
}
910

1011
import { satisfies } from 'semver';

src/commands/claim.spec.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@ vi.mock('../utils/debug.js', () => ({
66
logError: vi.fn(),
77
}));
88

9-
// Mock opn (browser open)
109
const mockOpen = vi.fn().mockResolvedValue(undefined);
11-
vi.mock('opn', () => ({ default: mockOpen }));
10+
vi.mock('open', () => ({ default: mockOpen }));
1211

1312
// Mock clack
1413
const mockSpinner = {

src/commands/claim.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* until the environment is claimed.
77
*/
88

9-
import open from 'opn';
9+
import open from 'open';
1010
import clack from '../utils/clack.js';
1111
import { getActiveEnvironment, isUnclaimedEnvironment, markEnvironmentClaimed } from '../lib/config-store.js';
1212
import { createClaimNonce, UnclaimedEnvApiError } from '../lib/unclaimed-env-api.js';

src/commands/dev.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { readFileSync, existsSync } from 'node:fs';
55
import { resolve } from 'node:path';
66
import { parse as parseYaml } from 'yaml';
77
import chalk from 'chalk';
8+
import { IS_WINDOWS, SPAWN_OPTS } from '../utils/platform.js';
89

910
export interface DevArgs {
1011
port: number;
@@ -122,6 +123,7 @@ export async function runDev(argv: DevArgs): Promise<void> {
122123
...process.env,
123124
...buildDevEnv(emulator.url, emulator.apiKey),
124125
},
126+
...SPAWN_OPTS,
125127
});
126128
} catch {
127129
console.error(chalk.red(`Failed to start: ${devCmd.command} ${devCmd.args.join(' ')}`));
@@ -149,6 +151,9 @@ export async function runDev(argv: DevArgs): Promise<void> {
149151
};
150152
process.on('SIGINT', () => shutdown('SIGINT'));
151153
process.on('SIGTERM', () => shutdown('SIGTERM'));
154+
if (IS_WINDOWS) {
155+
process.on('SIGBREAK', () => shutdown('SIGINT'));
156+
}
152157

153158
// 6. If child exits, close emulator and exit with same code
154159
child.on('exit', (code) => {

src/commands/emulate.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { readFileSync, existsSync } from 'node:fs';
33
import { resolve } from 'node:path';
44
import { parse as parseYaml } from 'yaml';
55
import chalk from 'chalk';
6+
import { IS_WINDOWS } from '../utils/platform.js';
67

78
export interface EmulateArgs {
89
port: number;
@@ -75,4 +76,7 @@ export async function runEmulate(argv: EmulateArgs): Promise<void> {
7576
};
7677
process.once('SIGINT', shutdown);
7778
process.once('SIGTERM', shutdown);
79+
if (IS_WINDOWS) {
80+
process.once('SIGBREAK', shutdown);
81+
}
7882
}

src/commands/install-skill.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { existsSync } from 'fs';
44
import { mkdir, mkdtemp, cp, rename, rm, readdir, readFile, stat, access, writeFile } from 'fs/promises';
55
import chalk from 'chalk';
66
import { getSkillsDir as getSkillsPackageDir } from '@workos/skills';
7+
import { IS_WINDOWS } from '../utils/platform.js';
78

89
export const SKILL_VERSION_MARKER_FILENAME = '.workos-skill-version';
910

@@ -48,30 +49,31 @@ export interface AgentConfig {
4849
}
4950

5051
export function createAgents(home: string): Record<string, AgentConfig> {
52+
const appData = process.env.APPDATA ?? join(home, 'AppData', 'Roaming');
5153
return {
5254
'claude-code': {
5355
name: 'claude-code',
5456
displayName: 'Claude Code',
55-
globalSkillsDir: join(home, '.claude/skills'),
57+
globalSkillsDir: join(home, '.claude', 'skills'),
5658
detect: () => existsSync(join(home, '.claude')),
5759
},
5860
codex: {
5961
name: 'codex',
6062
displayName: 'Codex',
61-
globalSkillsDir: join(home, '.codex/skills'),
63+
globalSkillsDir: join(home, '.codex', 'skills'),
6264
detect: () => existsSync(join(home, '.codex')),
6365
},
6466
cursor: {
6567
name: 'cursor',
6668
displayName: 'Cursor',
67-
globalSkillsDir: join(home, '.cursor/skills'),
69+
globalSkillsDir: join(home, '.cursor', 'skills'),
6870
detect: () => existsSync(join(home, '.cursor')),
6971
},
7072
goose: {
7173
name: 'goose',
7274
displayName: 'Goose',
73-
globalSkillsDir: join(home, '.config/goose/skills'),
74-
detect: () => existsSync(join(home, '.config/goose')),
75+
globalSkillsDir: IS_WINDOWS ? join(appData, 'goose', 'skills') : join(home, '.config', 'goose', 'skills'),
76+
detect: () => (IS_WINDOWS ? existsSync(join(appData, 'goose')) : existsSync(join(home, '.config', 'goose'))),
7577
},
7678
};
7779
}

src/commands/login.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { join } from 'node:path';
44
import { tmpdir } from 'node:os';
55

66
const mockOpen = vi.fn();
7-
vi.mock('opn', () => ({ default: (...args: unknown[]) => mockOpen(...args) }));
7+
vi.mock('open', () => ({ default: (...args: unknown[]) => mockOpen(...args) }));
88

99
class MockDeviceAuthTimeoutError extends Error {}
1010
const mockRequestDeviceCode = vi.fn();

src/commands/login.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import open from 'opn';
1+
import open from 'open';
22
import chalk from 'chalk';
33
import clack from '../utils/clack.js';
44
import { saveCredentials, getCredentials, getAccessToken, isTokenExpired, updateTokens } from '../lib/credentials.js';

0 commit comments

Comments
 (0)