diff --git a/README.md b/README.md index 5433497..cfe3d01 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,30 @@ specify it in the options like in the following: ] ``` +### Options + +| Name | Type | Default | Description | +|------|------|---------|-------------| +| `projectRoot` | `string` | `config.root` | Absolute path that will be reported to DevTools. Useful for monorepos or when the Vite root is not the desired folder. | +| `normalizeForWindowsContainer` | `boolean` | `true` | Convert Linux paths to UNC form so Chrome on Windows (WSL / Docker Desktop) can mount them (e.g. via WSL or Docker Desktop). Pass `false` to disable.
_Alias:_ `normalizeForChrome` (deprecated)_ | +| `uuid` | `string` | auto-generated | Fixed UUID if you prefer to control it yourself. | + +Example with all options: + +```js +import { defineConfig } from 'vite'; +import devtoolsJson from 'vite-plugin-devtools-json'; + +export default defineConfig({ + plugins: [ + devtoolsJson({ + projectRoot: '/absolute/path/to/project', + normalizeForWindowsContainer: true, + uuid: '6ec0bd7f-11c0-43da-975e-2a8ad9ebae0b' + }) + ] +}); +``` The `/.well-known/appspecific/com.chrome.devtools.json` endpoint will serve the project settings as JSON with the following structure diff --git a/src/index.ts b/src/index.ts index c1c1b4d..2c01477 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,31 @@ import path from 'path'; import {v4, validate} from 'uuid'; import {Plugin} from 'vite'; +// Plugin options +interface DevToolsJsonOptions { + /** + * Optional fixed UUID. If omitted the plugin will generate + * (and cache) one automatically, which is the previous default behaviour. + */ + uuid?: string; + + /** + * Absolute (or relative) path that should be reported as the project root + * in DevTools. When omitted, we fall back to Vite’s `config.root` logic. + */ + projectRoot?: string; + + /** + * @deprecated Use `normalizeForWindowsContainer` instead. Will be removed in a future major version. + */ + normalizeForChrome?: boolean; + /** + * Whether to rewrite Linux paths to UNC form so Chrome running on Windows + * (WSL or Docker Desktop) can mount them as a workspace. Enabled by default. + */ + normalizeForWindowsContainer?: boolean; +} + interface DevToolsJSON { workspace?: { root: string, @@ -12,7 +37,7 @@ interface DevToolsJSON { const ENDPOINT = '/.well-known/appspecific/com.chrome.devtools.json'; -const plugin = (options?: {uuid: string}): Plugin => ({ +const plugin = (options: DevToolsJsonOptions = {}): Plugin => ({ name: 'devtools-json', enforce: 'post', @@ -25,7 +50,7 @@ const plugin = (options?: {uuid: string}): Plugin => ({ } const getOrCreateUUID = () => { - if (options?.uuid) { + if (options.uuid) { return options.uuid; } // Per https://vite.dev/config/shared-options.html#cachedir @@ -57,23 +82,59 @@ const plugin = (options?: {uuid: string}): Plugin => ({ return uuid; }; - server.middlewares.use(ENDPOINT, async (req, res) => { - // Per https://vite.dev/config/shared-options.html#root the - // `config.root` can either be an absolute path, or a path - // relative to the current working directory. + // Determine effective normalisation flag once so we can reuse it. + const normalizePaths = + options.normalizeForWindowsContainer ?? + (options.normalizeForChrome ?? true); + + // Emit deprecation warning if old option is used in isolation. + if ( + Object.prototype.hasOwnProperty.call(options, 'normalizeForChrome') && + options.normalizeForWindowsContainer === undefined + ) { + logger.warn( + '[vite-plugin-devtools-json] "normalizeForChrome" is deprecated – please rename to "normalizeForWindowsContainer".' + ); + } + + server.middlewares.use(ENDPOINT, async (_req, res) => { + // Determine the project root that will be reported to DevTools. + const resolveProjectRoot = (): string => { + if (options.projectRoot) { + return path.resolve(options.projectRoot); + } + // Fall back to Vite's root handling (original behaviour) let {root} = config; if (!path.isAbsolute(root)) { root = path.resolve(process.cwd(), root); } + return root; + }; - // WSL case detection - if (process.env.WSL_DISTRO_NAME) { - // Convert Linux path to Windows path format for WSL - root = path - .join("\\\\wsl.localhost", process.env.WSL_DISTRO_NAME, root) - .replace(/\//g, "\\"); + const maybeNormalizePath = (absRoot: string): string => { + if (!normalizePaths) return absRoot; + + // WSL path rewrite + if (process.env.WSL_DISTRO_NAME) { + const distro = process.env.WSL_DISTRO_NAME; + const withoutLeadingSlash = absRoot.replace(/^\//, ''); + return path + .join('\\\\wsl.localhost', distro, withoutLeadingSlash) + .replace(/\//g, '\\'); + } + + // Docker Desktop on Windows path rewrite + if (process.env.DOCKER_DESKTOP && !absRoot.startsWith('\\\\')) { + const withoutLeadingSlash = absRoot.replace(/^\//, ''); + return path + .join('\\\\wsl.localhost', 'docker-desktop-data', withoutLeadingSlash) + .replace(/\//g, '\\'); } + return absRoot; + }; + + let root = maybeNormalizePath(resolveProjectRoot()); const uuid = getOrCreateUUID(); const devtoolsJson: DevToolsJSON = { workspace: { diff --git a/test/index.test.ts b/test/index.test.ts index 31574aa..de8100e 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,6 +1,7 @@ import request from 'supertest'; import {createServer} from 'vite'; import {describe, expect, it} from 'vitest'; +import path from 'path'; import VitePluginDevToolsJson from '../src'; @@ -82,5 +83,60 @@ describe('#VitePluginDevToolsJson', () => { await server.close(); }); + + it('should respect the `projectRoot` option', async () => { + delete process.env.WSL_DISTRO_NAME; + const customRoot = path.resolve('/tmp/custom-project-root'); + const server = await createServer({ + plugins: [VitePluginDevToolsJson({ projectRoot: customRoot })], + server: { port, host: true }, + }); + + await server.listen(); + + const response = await request(server.httpServer!) + .get('/.well-known/appspecific/com.chrome.devtools.json'); + const json = JSON.parse(response.text); + expect(json.workspace.root).to.equal(customRoot); + + await server.close(); + }); + + it('should skip path normalization when `normalizeForWindowsContainer` is false', async () => { + process.env.WSL_DISTRO_NAME = 'fake-distro'; + const server = await createServer({ + plugins: [VitePluginDevToolsJson({ normalizeForWindowsContainer: false })], + server: { port, host: true }, + }); + await server.listen(); + + const response = await request(server.httpServer!) + .get('/.well-known/appspecific/com.chrome.devtools.json'); + const json = JSON.parse(response.text); + expect(json.workspace.root).to.not.include('wsl.localhost'); + + await server.close(); + delete process.env.WSL_DISTRO_NAME; + }); + + it('should convert path when running inside Docker Desktop on Windows', async () => { + delete process.env.WSL_DISTRO_NAME; + process.env.DOCKER_DESKTOP = 'true'; + + const server = await createServer({ + plugins: [VitePluginDevToolsJson()], + server: { port, host: true }, + }); + + await server.listen(); + + const response = await request(server.httpServer!) + .get('/.well-known/appspecific/com.chrome.devtools.json'); + const json = JSON.parse(response.text); + expect(json.workspace.root).to.include('docker-desktop-data'); + + await server.close(); + delete process.env.DOCKER_DESKTOP; + }); }); });