Skip to content

Commit 60cea04

Browse files
committed
wip
1 parent 73f6a3e commit 60cea04

4 files changed

Lines changed: 129 additions & 4 deletions

File tree

packages/cli-kit/src/public/node/system.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,47 @@ describe('readStdinString', () => {
113113
expect(got).toBe('hello world')
114114
})
115115
})
116+
117+
describe('convertWslPath', () => {
118+
test('converts Linux path to Windows path successfully', async () => {
119+
// Given
120+
const linuxPath = '/tmp/speedscope-123.html'
121+
const windowsPath = 'C:\\Users\\developer\\AppData\\Local\\Temp\\speedscope-123.html'
122+
vi.mocked(which.sync).mockReturnValueOnce('/usr/bin/wslpath')
123+
vi.mocked(execa).mockResolvedValueOnce({stdout: windowsPath + '\n'} as any)
124+
125+
// When
126+
const result = await system.convertWslPath(linuxPath)
127+
128+
// Then
129+
expect(result).toBe(windowsPath)
130+
expect(execa).toHaveBeenCalledWith('wslpath', ['-w', linuxPath], expect.any(Object))
131+
})
132+
133+
test('returns original path if wslpath command fails', async () => {
134+
// Given
135+
const linuxPath = '/tmp/speedscope-123.html'
136+
vi.mocked(which.sync).mockReturnValueOnce('/usr/bin/wslpath')
137+
vi.mocked(execa).mockRejectedValueOnce(new Error('wslpath not found'))
138+
139+
// When
140+
const result = await system.convertWslPath(linuxPath)
141+
142+
// Then
143+
expect(result).toBe(linuxPath)
144+
})
145+
146+
test('trims whitespace from wslpath output', async () => {
147+
// Given
148+
const linuxPath = '/tmp/file.html'
149+
const windowsPath = 'C:\\Temp\\file.html'
150+
vi.mocked(which.sync).mockReturnValueOnce('/usr/bin/wslpath')
151+
vi.mocked(execa).mockResolvedValueOnce({stdout: ` ${windowsPath} \n`} as any)
152+
153+
// When
154+
const result = await system.convertWslPath(linuxPath)
155+
156+
// Then
157+
expect(result).toBe(windowsPath)
158+
})
159+
})

packages/cli-kit/src/public/node/system.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,24 @@ export async function isWsl(): Promise<boolean> {
202202
return wsl.default
203203
}
204204

205+
/**
206+
* Convert a WSL Linux path to a Windows path using wslpath.
207+
* This is useful when opening files in Windows browsers from WSL.
208+
*
209+
* @param linuxPath - The Linux path to convert (e.g., /tmp/file.html).
210+
* @returns A promise that resolves with the Windows path.
211+
*/
212+
export async function convertWslPath(linuxPath: string): Promise<string> {
213+
try {
214+
const windowsPath = await captureOutput('wslpath', ['-w', linuxPath])
215+
return windowsPath.trim()
216+
// eslint-disable-next-line no-catch-all/no-catch-all
217+
} catch (error) {
218+
outputDebug(`Failed to convert WSL path using wslpath: ${error}. Falling back to original Linux path: ${linuxPath}`)
219+
return linuxPath
220+
}
221+
}
222+
205223
/**
206224
* Check if stdin has piped data available.
207225
* This distinguishes between actual piped input (e.g., `echo "query" | cmd`)

packages/theme/src/cli/services/profile.test.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,15 @@ import {AbortError} from '@shopify/cli-kit/node/error'
88
import {readFile} from 'fs/promises'
99

1010
vi.mock('@shopify/cli-kit/node/session')
11-
vi.mock('@shopify/cli-kit/node/system')
11+
vi.mock('@shopify/cli-kit/node/system', async () => {
12+
const actual = await vi.importActual('@shopify/cli-kit/node/system')
13+
return {
14+
...actual,
15+
openURL: vi.fn().mockResolvedValue(true),
16+
isWsl: vi.fn().mockResolvedValue(false),
17+
convertWslPath: vi.fn((path: string) => Promise.resolve(path)),
18+
}
19+
})
1220
vi.mock('@shopify/cli-kit/node/output')
1321
vi.mock('../utilities/theme-environment/storefront-password-prompt.js')
1422
vi.mock('../utilities/theme-environment/storefront-session.js')
@@ -82,6 +90,42 @@ describe('profile', () => {
8290
expect(htmlContent).toContain('speedscope')
8391
})
8492

93+
test('converts all WSL paths before opening browser', async () => {
94+
// Given
95+
const {isWsl, convertWslPath} = await import('@shopify/cli-kit/node/system')
96+
vi.mocked(isWsl).mockResolvedValue(true)
97+
vi.mocked(convertWslPath).mockImplementation((linuxPath: string) => {
98+
if (linuxPath.endsWith('.html')) {
99+
return Promise.resolve('C:\\Users\\dev\\AppData\\Local\\Temp\\speedscope-12345-999.html')
100+
}
101+
if (linuxPath.endsWith('.js')) {
102+
return Promise.resolve('C:\\Users\\dev\\AppData\\Local\\Temp\\speedscope-12345-999.js')
103+
}
104+
// speedscope index.html asset path
105+
return Promise.resolve('C:\\Users\\dev\\node_modules\\speedscope\\dist\\release\\index.html')
106+
})
107+
108+
// When
109+
await profile(mockAdminSession, themeId, urlPath, false, undefined, undefined)
110+
111+
// Then
112+
expect(isWsl).toHaveBeenCalled()
113+
// All 3 paths should be converted
114+
expect(convertWslPath).toHaveBeenCalledTimes(3)
115+
expect(convertWslPath).toHaveBeenCalledWith(expect.stringContaining('index.html'))
116+
expect(convertWslPath).toHaveBeenCalledWith(expect.stringMatching(/\.js$/))
117+
expect(convertWslPath).toHaveBeenCalledWith(expect.stringMatching(/\.html$/))
118+
expect(openURL).toHaveBeenCalledWith('file://C:\\Users\\dev\\AppData\\Local\\Temp\\speedscope-12345-999.html')
119+
120+
// Verify the redirect HTML contains Windows paths (not Linux paths)
121+
const openUrlCalls = vi.mocked(openURL).mock.calls
122+
const firstCall = openUrlCalls[0]
123+
if (!firstCall) throw new Error('Expected at least one openURL call')
124+
const convertWslPathCalls = vi.mocked(convertWslPath).mock.calls
125+
expect(convertWslPathCalls.some((call) => String(call[0]).endsWith('.js'))).toBe(true)
126+
expect(convertWslPathCalls.some((call) => String(call[0]).includes('index.html'))).toBe(true)
127+
})
128+
85129
test('throws error when fetch fails', async () => {
86130
// Given
87131
vi.mocked(render).mockRejectedValue(new Error('Network error'))

packages/theme/src/cli/services/profile.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {ensureValidPassword} from '../utilities/theme-environment/storefront-pas
33
import {fetchDevServerSession} from '../utilities/theme-environment/dev-server-session.js'
44
import {render} from '../utilities/theme-environment/storefront-renderer.js'
55
import {resolveAssetPath} from '../utilities/asset-path.js'
6-
import {openURL} from '@shopify/cli-kit/node/system'
6+
import {openURL, isWsl, convertWslPath} from '@shopify/cli-kit/node/system'
77
import {joinPath} from '@shopify/cli-kit/node/path'
88
import {AdminSession} from '@shopify/cli-kit/node/session'
99
import {writeFile, tempDirectory} from '@shopify/cli-kit/node/fs'
@@ -69,7 +69,20 @@ async function openProfile(profileJson: string) {
6969
outputDebug(`[Theme Profile] writing JS file to: ${jsPath}`)
7070
await writeFile(jsPath, jsSource)
7171
outputDebug(`[Theme Profile] JS file created successfully: ${jsPath}`)
72-
urlToOpen += `#localProfilePath=${jsPath}`
72+
73+
// In WSL, we need to convert Linux paths to Windows paths for the browser to access them.
74+
const wsl = await isWsl()
75+
if (wsl) {
76+
const windowsSpeedscopePath = await convertWslPath(urlToOpen)
77+
outputDebug(`[Theme Profile] Converted WSL speedscope path: ${urlToOpen} -> ${windowsSpeedscopePath}`)
78+
urlToOpen = windowsSpeedscopePath
79+
80+
const windowsJsPath = await convertWslPath(jsPath)
81+
outputDebug(`[Theme Profile] Converted WSL JS path: ${jsPath} -> ${windowsJsPath}`)
82+
urlToOpen += `#localProfilePath=${windowsJsPath}`
83+
} else {
84+
urlToOpen += `#localProfilePath=${jsPath}`
85+
}
7386

7487
// For some silly reason, the OS X open command ignores any query parameters or hash parameters
7588
// passed as part of the URL. To get around this weird issue, we'll create a local HTML file
@@ -79,7 +92,13 @@ async function openProfile(profileJson: string) {
7992
await writeFile(htmlPath, `<script>window.location=${JSON.stringify(urlToOpen)}</script>`)
8093
outputDebug(`[Theme Profile] HTML file created successfully: ${htmlPath}`)
8194

82-
urlToOpen = `file://${htmlPath}`
95+
let pathToOpen = htmlPath
96+
if (wsl) {
97+
pathToOpen = await convertWslPath(htmlPath)
98+
outputDebug(`[Theme Profile] Converted WSL HTML path: ${htmlPath} -> ${pathToOpen}`)
99+
}
100+
101+
urlToOpen = `file://${pathToOpen}`
83102
outputDebug(`[Theme Profile] Opening URL: ${urlToOpen}`)
84103
const opened = await openURL(urlToOpen)
85104
outputDebug(`[Theme Profile] URL opened successfully: ${opened}`)

0 commit comments

Comments
 (0)