From 99f0a495eb83c4595dc07f22712ed6b48db6eb6b Mon Sep 17 00:00:00 2001 From: 0xTaneja Date: Fri, 4 Jul 2025 17:39:16 +0000 Subject: [PATCH 1/3] Worked on Path-Mapping and Devtools-Plugin --- .../templates/react-app/vite.config.ts | 2 + .../templates/react-app/vite/devToolsJson.ts | 86 +++++++++++++++++++ .../WebAppGenerator/Vite/VitePlugin.hs | 4 +- 3 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 waspc/data/Generator/templates/react-app/vite/devToolsJson.ts diff --git a/waspc/data/Generator/templates/react-app/vite.config.ts b/waspc/data/Generator/templates/react-app/vite.config.ts index cc1b524ac2..b0cb533e59 100644 --- a/waspc/data/Generator/templates/react-app/vite.config.ts +++ b/waspc/data/Generator/templates/react-app/vite.config.ts @@ -6,6 +6,7 @@ import { defaultExclude } from "vitest/config" import { detectServerImports } from "./vite/detectServerImports" import { validateEnv } from "./vite/validateEnv.js"; import path from "node:path" +import devToolsJsonPlugin from "./vite/devToolsJson"; {=# customViteConfig.isDefined =} // Ignoring the TS error because we are importing a file outside of TS root dir. @@ -23,6 +24,7 @@ const defaultViteConfig = { validateEnv(), react(), detectServerImports(), + devToolsJsonPlugin(), ], optimizeDeps: { exclude: ['wasp'] diff --git a/waspc/data/Generator/templates/react-app/vite/devToolsJson.ts b/waspc/data/Generator/templates/react-app/vite/devToolsJson.ts new file mode 100644 index 0000000000..dd52c71abe --- /dev/null +++ b/waspc/data/Generator/templates/react-app/vite/devToolsJson.ts @@ -0,0 +1,86 @@ +import fs from 'fs'; +import path from 'path'; +import {v4,validate} from 'uuid'; +import {Plugin} from 'vite'; + +interface DevToolsJSON { + workspace?: {root:string,uuid:string}; +} + +const ENDPOINT = '/.well-known/appspecific/com.chrome.devtools.json'; + +export default function devToolsJsonPlugin():Plugin {8 + return { + name: 'devtools-json', + enforce: 'post', + configureServer(server){ + const {config,middlewares} = server; + const {logger} = config; + if(!config.env.DEV) return; + + const getOrCreateUUID = ():string => { + let {cacheDir} = config; + if (!path.isAbsolute(cacheDir)) { + let {root} = config; + if (!path.isAbsolute(root)) { + root = path.resolve(process.cwd(), root); + } + cacheDir = path.resolve(root, cacheDir); + } + const uuidPath = path.resolve(cacheDir,'uuid.json'); + if(fs.existsSync(uuidPath)){ + const uuid = fs.readFileSync(uuidPath,{encoding:'utf-8'}); + if(validate(uuid)){ + return uuid; + } + } + if(!fs.existsSync(cacheDir)){ + fs.mkdirSync(cacheDir,{recursive:true}); + } + + const uuid = v4(); + fs.writeFileSync(uuidPath,uuid,{encoding:'utf-8'}); + logger.info(`Generated UUID '${uuid}' for DevTools project settings.`); + return uuid; + + + } + const normalizeForChrome = (root:string):string => { + if(path.isAbsolute(root) && root[1] === ':') + return root; + if (process.env.WSL_DISTRO_NAME) { + const distroName = process.env.WSL_DISTRO_NAME; + const withoutLeadingSlash = root.replace(/^\//, ''); + root = path + .join('\\\\wsl.localhost', distroName, withoutLeadingSlash) + .replace(/\//g, '\\'); + logger.info('[devtools-json] Path rewritten for WSL'); + } + // if we use cgroup , it will work for linux docker containers, but not for the main host which is running docker + //need to think about it + if(process.env.DOCKER_DESKTOP && !root.startsWith('\\\\')){ + const withoutLeadingSlash = root.replace(/^\//, ''); + root = path.join('\\\\wsl.localhost','docker-desktop-data',withoutLeadingSlash) + .replace(/\//g, '\\'); + logger.info('[devtools-json] Path rewritten for Docker Desktop'); + } + return root; + } + middlewares.use(ENDPOINT,(_req,res)=>{ + let {root} = config; + if(!path.isAbsolute(root)){ + root = path.resolve(process.cwd(),root); + + } + + root = normalizeForChrome(root); + + + const uuid = getOrCreateUUID(); + const devtoolsJson: DevToolsJSON = {workspace:{root,uuid}}; + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify(devtoolsJson, null, 2)); + }) + } + } +} \ No newline at end of file diff --git a/waspc/src/Wasp/Generator/WebAppGenerator/Vite/VitePlugin.hs b/waspc/src/Wasp/Generator/WebAppGenerator/Vite/VitePlugin.hs index 62a0fc0b52..a2f7eff59e 100644 --- a/waspc/src/Wasp/Generator/WebAppGenerator/Vite/VitePlugin.hs +++ b/waspc/src/Wasp/Generator/WebAppGenerator/Vite/VitePlugin.hs @@ -14,7 +14,7 @@ import Wasp.Generator.WebAppGenerator.Common (WebAppTemplatesDir) import qualified Wasp.Generator.WebAppGenerator.Common as C import Wasp.Project.Common (WaspProjectDir, srcDirInWaspProjectDir, waspProjectDirFromAppComponentDir) -data VitePluginName = DetectServerImports | ValidateEnv +data VitePluginName = DetectServerImports | ValidateEnv | DevToolsJson deriving (Enum, Bounded) type TmplFilePath = Path' (Rel WebAppTemplatesDir) File' @@ -44,10 +44,12 @@ getTmplFilePathForVitePlugin pluginName = C.asTmplFile $ vitePluginsDirInWebAppD where pluginFilePathInPluginsDir DetectServerImports = [relfile|detectServerImports.ts|] pluginFilePathInPluginsDir ValidateEnv = [relfile|validateEnv.ts|] + pluginFilePathInPluginsDir DevToolsJson = [relfile|devToolsJson.ts|] genVitePlugin :: VitePlugin -> Generator FileDraft genVitePlugin (DetectServerImports, tmplFile) = genDetectServerImportsPlugin tmplFile genVitePlugin (ValidateEnv, tmplFile) = genValidateEnvPlugin tmplFile +genVitePlugin (DevToolsJson,tmplFile) = return $ C.mkTmplFd tmplFile genDetectServerImportsPlugin :: Path' (Rel WebAppTemplatesDir) File' -> Generator FileDraft genDetectServerImportsPlugin tmplFile = return $ C.mkTmplFdWithData tmplFile tmplData From 33ee85248df9b316961681bc0484660f7c8c891a Mon Sep 17 00:00:00 2001 From: 0xTaneja Date: Mon, 7 Jul 2025 22:55:25 +0000 Subject: [PATCH 2/3] feat(devtools): worked on .wasp configuration --- waspc/ChangeLog.md | 10 +- .../templates/react-app/vite/devToolsJson.ts | 128 +++++++++++------ .../waspBuild-golden/expected-files.manifest | 1 + .../.wasp/build/web-app/tsconfig.vite.json | 1 + .../.wasp/build/web-app/vite.config.ts | 2 + .../.wasp/build/web-app/vite/devToolsJson.ts | 132 ++++++++++++++++++ .../expected-files.manifest | 1 + .../.wasp/out/web-app/tsconfig.vite.json | 1 + .../.wasp/out/web-app/vite.config.ts | 2 + .../.wasp/out/web-app/vite/devToolsJson.ts | 132 ++++++++++++++++++ .../expected-files.manifest | 1 + .../.wasp/out/web-app/tsconfig.vite.json | 1 + .../.wasp/out/web-app/vite.config.ts | 2 + .../.wasp/out/web-app/vite/devToolsJson.ts | 132 ++++++++++++++++++ .../waspJob-golden/expected-files.manifest | 1 + .../.wasp/out/web-app/tsconfig.vite.json | 1 + .../waspJob/.wasp/out/web-app/vite.config.ts | 2 + .../.wasp/out/web-app/vite/devToolsJson.ts | 132 ++++++++++++++++++ .../expected-files.manifest | 1 + .../.wasp/out/web-app/tsconfig.vite.json | 1 + .../.wasp/out/web-app/vite.config.ts | 2 + .../.wasp/out/web-app/vite/devToolsJson.ts | 132 ++++++++++++++++++ .../Wasp/Generator/WebAppGenerator/Start.hs | 6 +- 23 files changed, 777 insertions(+), 47 deletions(-) create mode 100644 waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/web-app/vite/devToolsJson.ts create mode 100644 waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/vite/devToolsJson.ts create mode 100644 waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/vite/devToolsJson.ts create mode 100644 waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/vite/devToolsJson.ts create mode 100644 waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/vite/devToolsJson.ts diff --git a/waspc/ChangeLog.md b/waspc/ChangeLog.md index d4e0dac0e6..60b38237aa 100644 --- a/waspc/ChangeLog.md +++ b/waspc/ChangeLog.md @@ -126,9 +126,9 @@ Follow the [the official migration guide](https://wasp.sh/docs/migration-guides/ #### Env variables validation with Zod -Wasp now uses Zod to validate environment variables, allowing it to fail faster if something is misconfigured. This means you’ll get more relevant error messages when running your app with incorrect env variables. +Wasp now uses Zod to validate environment variables, allowing it to fail faster if something is misconfigured. This means you'll get more relevant error messages when running your app with incorrect env variables. -You can also use Zod to validate your own environment variables. Here’s an example: +You can also use Zod to validate your own environment variables. Here's an example: ```ts // src/env.ts @@ -155,7 +155,7 @@ app myApp { ### Deployment docs upgrade -Based on feedback from our Discord community, we’ve revamped our deployment docs to make it simpler to deploy your app to production. We focused on explaining key deployment concepts, regardless of the deployment method you choose. We’ve added guides on hosting Wasp apps on your own servers, for example, how to use Coolify and Caprover for self-hosting. The Env Variables section now includes a comprehensive list of all available Wasp env variables and provides clearer instructions on how to set them up in a deployed app. +Based on feedback from our Discord community, we've revamped our deployment docs to make it simpler to deploy your app to production. We focused on explaining key deployment concepts, regardless of the deployment method you choose. We've added guides on hosting Wasp apps on your own servers, for example, how to use Coolify and Caprover for self-hosting. The Env Variables section now includes a comprehensive list of all available Wasp env variables and provides clearer instructions on how to set them up in a deployed app. Check the updated deployment docs here: https://wasp.sh/docs/deployment/intro @@ -956,7 +956,7 @@ Check below for details on each of them! ### ⚠️ Breaking changes -- Wasp's **signup action** `import signup from '@wasp/auth/signup` now accepts only the user entity fields relevant to the auth process (e.g. `username` and `password`). +- Wasp's **signup action** `import signup from '@wasp/auth/signup' now accepts only the user entity fields relevant to the auth process (e.g. `username` and `password`). This ensures no unexpected data can be inserted into the database during signup, but it also means you can't any more set any user entity fields via signup action (e.g. `age` or `address`). Instead, those should be set in the separate step after signup, or via a custom signup action of your own. - Wasp now uses **React 18**! Check the following upgrade guide for details: https://react.dev/blog/2022/03/08/react-18-upgrade-guide . @@ -1921,3 +1921,5 @@ For exact details about new syntax, check https://wasp.sh/docs/language/syntax . - and more! ## Unreleased changes + +- Wasp apps now expose Chrome DevTools Automatic Workspace Folders metadata in dev mode, so the browser automatically maps edited files to your real project directory. The Vite dev-server serves /.well-known/appspecific/com.chrome.devtools.json with per-project UUID caching and Windows/WSL/Docker path handling. diff --git a/waspc/data/Generator/templates/react-app/vite/devToolsJson.ts b/waspc/data/Generator/templates/react-app/vite/devToolsJson.ts index dd52c71abe..7e4f53d367 100644 --- a/waspc/data/Generator/templates/react-app/vite/devToolsJson.ts +++ b/waspc/data/Generator/templates/react-app/vite/devToolsJson.ts @@ -1,83 +1,129 @@ import fs from 'fs'; import path from 'path'; -import {v4,validate} from 'uuid'; -import {Plugin} from 'vite'; +import { Plugin } from 'vite'; +import crypto from 'crypto'; interface DevToolsJSON { - workspace?: {root:string,uuid:string}; + workspace?: { root: string, uuid: string }; } const ENDPOINT = '/.well-known/appspecific/com.chrome.devtools.json'; -export default function devToolsJsonPlugin():Plugin {8 +export default function devToolsJsonPlugin(): Plugin { return { name: 'devtools-json', enforce: 'post', - configureServer(server){ - const {config,middlewares} = server; - const {logger} = config; - if(!config.env.DEV) return; + configureServer(server) { + const { config, middlewares } = server; + const { logger } = config; + if (!config.env.DEV) return; + + const getOrCreateUUID = (): string => { + let { cacheDir } = config; + let { root } = config; + if (!path.isAbsolute(root)) + root = path.resolve(process.cwd(), root); - const getOrCreateUUID = ():string => { - let {cacheDir} = config; if (!path.isAbsolute(cacheDir)) { - let {root} = config; - if (!path.isAbsolute(root)) { - root = path.resolve(process.cwd(), root); - } cacheDir = path.resolve(root, cacheDir); + } - const uuidPath = path.resolve(cacheDir,'uuid.json'); - if(fs.existsSync(uuidPath)){ - const uuid = fs.readFileSync(uuidPath,{encoding:'utf-8'}); - if(validate(uuid)){ - return uuid; + const projectHash = getProjectHash(root); + const uuidDir = path.resolve(cacheDir, projectHash); + const uuidPath = path.resolve(uuidDir, 'uuid.json'); + const validate = (u: string) => + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(u); + + try { + if (fs.existsSync(uuidPath)) { + const cached = fs.readFileSync(uuidPath, { encoding: "utf-8" }); + if (validate(cached)) { + return cached; + } + logger.warn( + "[devtools-json] Cached UUID was invalid – regenerating" + ); } - } - if(!fs.existsSync(cacheDir)){ - fs.mkdirSync(cacheDir,{recursive:true}); + } catch (err) { + logger.error( + `[devtools-json] Failed to read cached UUID: ${String(err)}` + ); } - const uuid = v4(); - fs.writeFileSync(uuidPath,uuid,{encoding:'utf-8'}); - logger.info(`Generated UUID '${uuid}' for DevTools project settings.`); + if (!fs.existsSync(uuidDir)) { + try { + fs.mkdirSync(uuidDir, { recursive: true }); + } catch (err) { + logger.error( + `[devtools-json] Failed creating cache dir: ${String(err)}` + ); + } + } + const uuid = crypto.randomUUID(); + try { + fs.writeFileSync(uuidPath, uuid, { encoding: "utf-8" }); + logger.info( + `[devtools-json] Generated UUID '${uuid}' for DevTools project settings.` + ); + } catch (err) { + logger.error( + `[devtools-json] Unable to persist UUID cache: ${String(err)}` + ); + } return uuid; - - } - const normalizeForChrome = (root:string):string => { - if(path.isAbsolute(root) && root[1] === ':') + const getProjectHash = (root: string): string => { + return crypto.createHash('sha1').update(root).digest('hex').slice(0, 8); + } + const normalizeForChrome = (root: string): string => { + if (path.isAbsolute(root) && root[1] === ':') return root; if (process.env.WSL_DISTRO_NAME) { const distroName = process.env.WSL_DISTRO_NAME; const withoutLeadingSlash = root.replace(/^\//, ''); root = path - .join('\\\\wsl.localhost', distroName, withoutLeadingSlash) - .replace(/\//g, '\\'); + .join('\\\\wsl.localhost', distroName, withoutLeadingSlash) + .replace(/\//g, '\\'); logger.info('[devtools-json] Path rewritten for WSL'); } // if we use cgroup , it will work for linux docker containers, but not for the main host which is running docker //need to think about it - if(process.env.DOCKER_DESKTOP && !root.startsWith('\\\\')){ + if (process.env.DOCKER_DESKTOP && !root.startsWith('\\\\')) { const withoutLeadingSlash = root.replace(/^\//, ''); - root = path.join('\\\\wsl.localhost','docker-desktop-data',withoutLeadingSlash) - .replace(/\//g, '\\'); + root = path.join('\\\\wsl.localhost', 'docker-desktop-data', withoutLeadingSlash) + .replace(/\//g, '\\'); logger.info('[devtools-json] Path rewritten for Docker Desktop'); } return root; } - middlewares.use(ENDPOINT,(_req,res)=>{ - let {root} = config; - if(!path.isAbsolute(root)){ - root = path.resolve(process.cwd(),root); + const findProjectRoot = (): string => { + // Prefer explicit env var if provided. + const envRoot = process.env.WASP_PROJECT_ROOT; + if (envRoot && envRoot.length > 0) { + + if (!envRoot.includes(`${path.sep}.wasp${path.sep}out`)) { + return path.resolve(envRoot); + } + } + + let dir = path.resolve(config.root); + const fsRoot = path.parse(dir).root; + while (dir !== fsRoot) { + if (fs.existsSync(path.join(dir, '.wasp'))) { + return dir; // directory that owns the .wasp folder + } + dir = path.dirname(dir); } - + // Could not find `.wasp`, use vite root. + return path.resolve(config.root); + }; + middlewares.use(ENDPOINT, (_req, res) => { + let root = findProjectRoot(); root = normalizeForChrome(root); - const uuid = getOrCreateUUID(); - const devtoolsJson: DevToolsJSON = {workspace:{root,uuid}}; + const devtoolsJson: DevToolsJSON = { workspace: { root, uuid } }; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify(devtoolsJson, null, 2)); }) diff --git a/waspc/e2e-test/test-outputs/waspBuild-golden/expected-files.manifest b/waspc/e2e-test/test-outputs/waspBuild-golden/expected-files.manifest index 547523028a..86a3a9d388 100644 --- a/waspc/e2e-test/test-outputs/waspBuild-golden/expected-files.manifest +++ b/waspc/e2e-test/test-outputs/waspBuild-golden/expected-files.manifest @@ -326,6 +326,7 @@ waspBuild/.wasp/build/web-app/tsconfig.json waspBuild/.wasp/build/web-app/tsconfig.vite.json waspBuild/.wasp/build/web-app/vite.config.ts waspBuild/.wasp/build/web-app/vite/detectServerImports.ts +waspBuild/.wasp/build/web-app/vite/devToolsJson.ts waspBuild/.wasp/build/web-app/vite/validateEnv.ts waspBuild/.wasp/out/sdk/wasp/api/events.ts waspBuild/.wasp/out/sdk/wasp/api/index.ts diff --git a/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/web-app/tsconfig.vite.json b/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/web-app/tsconfig.vite.json index 6d51817939..a19628d7f5 100644 --- a/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/web-app/tsconfig.vite.json +++ b/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/web-app/tsconfig.vite.json @@ -11,5 +11,6 @@ "vite.config.ts", "vite/detectServerImports.ts", "vite/validateEnv.ts", + "vite/devToolsJson.ts", ] } diff --git a/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/web-app/vite.config.ts b/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/web-app/vite.config.ts index d5169f7d34..a7c758d9a3 100644 --- a/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/web-app/vite.config.ts +++ b/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/web-app/vite.config.ts @@ -5,6 +5,7 @@ import { defaultExclude } from "vitest/config" import { detectServerImports } from "./vite/detectServerImports" import { validateEnv } from "./vite/validateEnv.js"; import path from "node:path" +import devToolsJsonPlugin from "./vite/devToolsJson"; // Ignoring the TS error because we are importing a file outside of TS root dir. // @ts-ignore @@ -17,6 +18,7 @@ const defaultViteConfig = { validateEnv(), react(), detectServerImports(), + devToolsJsonPlugin(), ], optimizeDeps: { exclude: ['wasp'] diff --git a/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/web-app/vite/devToolsJson.ts b/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/web-app/vite/devToolsJson.ts new file mode 100644 index 0000000000..7e4f53d367 --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/web-app/vite/devToolsJson.ts @@ -0,0 +1,132 @@ +import fs from 'fs'; +import path from 'path'; +import { Plugin } from 'vite'; +import crypto from 'crypto'; + +interface DevToolsJSON { + workspace?: { root: string, uuid: string }; +} + +const ENDPOINT = '/.well-known/appspecific/com.chrome.devtools.json'; + +export default function devToolsJsonPlugin(): Plugin { + return { + name: 'devtools-json', + enforce: 'post', + configureServer(server) { + const { config, middlewares } = server; + const { logger } = config; + if (!config.env.DEV) return; + + const getOrCreateUUID = (): string => { + let { cacheDir } = config; + let { root } = config; + if (!path.isAbsolute(root)) + root = path.resolve(process.cwd(), root); + + if (!path.isAbsolute(cacheDir)) { + cacheDir = path.resolve(root, cacheDir); + + } + const projectHash = getProjectHash(root); + const uuidDir = path.resolve(cacheDir, projectHash); + const uuidPath = path.resolve(uuidDir, 'uuid.json'); + const validate = (u: string) => + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(u); + + try { + if (fs.existsSync(uuidPath)) { + const cached = fs.readFileSync(uuidPath, { encoding: "utf-8" }); + if (validate(cached)) { + return cached; + } + logger.warn( + "[devtools-json] Cached UUID was invalid – regenerating" + ); + } + } catch (err) { + logger.error( + `[devtools-json] Failed to read cached UUID: ${String(err)}` + ); + } + + if (!fs.existsSync(uuidDir)) { + try { + fs.mkdirSync(uuidDir, { recursive: true }); + } catch (err) { + logger.error( + `[devtools-json] Failed creating cache dir: ${String(err)}` + ); + } + } + const uuid = crypto.randomUUID(); + try { + fs.writeFileSync(uuidPath, uuid, { encoding: "utf-8" }); + logger.info( + `[devtools-json] Generated UUID '${uuid}' for DevTools project settings.` + ); + } catch (err) { + logger.error( + `[devtools-json] Unable to persist UUID cache: ${String(err)}` + ); + } + return uuid; + } + const getProjectHash = (root: string): string => { + return crypto.createHash('sha1').update(root).digest('hex').slice(0, 8); + } + const normalizeForChrome = (root: string): string => { + if (path.isAbsolute(root) && root[1] === ':') + return root; + if (process.env.WSL_DISTRO_NAME) { + const distroName = process.env.WSL_DISTRO_NAME; + const withoutLeadingSlash = root.replace(/^\//, ''); + root = path + .join('\\\\wsl.localhost', distroName, withoutLeadingSlash) + .replace(/\//g, '\\'); + logger.info('[devtools-json] Path rewritten for WSL'); + } + // if we use cgroup , it will work for linux docker containers, but not for the main host which is running docker + //need to think about it + if (process.env.DOCKER_DESKTOP && !root.startsWith('\\\\')) { + const withoutLeadingSlash = root.replace(/^\//, ''); + root = path.join('\\\\wsl.localhost', 'docker-desktop-data', withoutLeadingSlash) + .replace(/\//g, '\\'); + logger.info('[devtools-json] Path rewritten for Docker Desktop'); + } + return root; + } + const findProjectRoot = (): string => { + // Prefer explicit env var if provided. + const envRoot = process.env.WASP_PROJECT_ROOT; + if (envRoot && envRoot.length > 0) { + + if (!envRoot.includes(`${path.sep}.wasp${path.sep}out`)) { + return path.resolve(envRoot); + } + } + + + let dir = path.resolve(config.root); + const fsRoot = path.parse(dir).root; + while (dir !== fsRoot) { + if (fs.existsSync(path.join(dir, '.wasp'))) { + return dir; // directory that owns the .wasp folder + } + dir = path.dirname(dir); + } + // Could not find `.wasp`, use vite root. + return path.resolve(config.root); + }; + middlewares.use(ENDPOINT, (_req, res) => { + let root = findProjectRoot(); + root = normalizeForChrome(root); + + const uuid = getOrCreateUUID(); + const devtoolsJson: DevToolsJSON = { workspace: { root, uuid } }; + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify(devtoolsJson, null, 2)); + }) + } + } +} \ No newline at end of file diff --git a/waspc/e2e-test/test-outputs/waspCompile-golden/expected-files.manifest b/waspc/e2e-test/test-outputs/waspCompile-golden/expected-files.manifest index da6c4da73e..ae3d36d880 100644 --- a/waspc/e2e-test/test-outputs/waspCompile-golden/expected-files.manifest +++ b/waspc/e2e-test/test-outputs/waspCompile-golden/expected-files.manifest @@ -321,6 +321,7 @@ waspCompile/.wasp/out/web-app/tsconfig.json waspCompile/.wasp/out/web-app/tsconfig.vite.json waspCompile/.wasp/out/web-app/vite.config.ts waspCompile/.wasp/out/web-app/vite/detectServerImports.ts +waspCompile/.wasp/out/web-app/vite/devToolsJson.ts waspCompile/.wasp/out/web-app/vite/validateEnv.ts waspCompile/.waspignore waspCompile/.wasproot diff --git a/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/tsconfig.vite.json b/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/tsconfig.vite.json index 6d51817939..a19628d7f5 100644 --- a/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/tsconfig.vite.json +++ b/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/tsconfig.vite.json @@ -11,5 +11,6 @@ "vite.config.ts", "vite/detectServerImports.ts", "vite/validateEnv.ts", + "vite/devToolsJson.ts", ] } diff --git a/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/vite.config.ts b/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/vite.config.ts index d5169f7d34..a7c758d9a3 100644 --- a/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/vite.config.ts +++ b/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/vite.config.ts @@ -5,6 +5,7 @@ import { defaultExclude } from "vitest/config" import { detectServerImports } from "./vite/detectServerImports" import { validateEnv } from "./vite/validateEnv.js"; import path from "node:path" +import devToolsJsonPlugin from "./vite/devToolsJson"; // Ignoring the TS error because we are importing a file outside of TS root dir. // @ts-ignore @@ -17,6 +18,7 @@ const defaultViteConfig = { validateEnv(), react(), detectServerImports(), + devToolsJsonPlugin(), ], optimizeDeps: { exclude: ['wasp'] diff --git a/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/vite/devToolsJson.ts b/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/vite/devToolsJson.ts new file mode 100644 index 0000000000..7e4f53d367 --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/vite/devToolsJson.ts @@ -0,0 +1,132 @@ +import fs from 'fs'; +import path from 'path'; +import { Plugin } from 'vite'; +import crypto from 'crypto'; + +interface DevToolsJSON { + workspace?: { root: string, uuid: string }; +} + +const ENDPOINT = '/.well-known/appspecific/com.chrome.devtools.json'; + +export default function devToolsJsonPlugin(): Plugin { + return { + name: 'devtools-json', + enforce: 'post', + configureServer(server) { + const { config, middlewares } = server; + const { logger } = config; + if (!config.env.DEV) return; + + const getOrCreateUUID = (): string => { + let { cacheDir } = config; + let { root } = config; + if (!path.isAbsolute(root)) + root = path.resolve(process.cwd(), root); + + if (!path.isAbsolute(cacheDir)) { + cacheDir = path.resolve(root, cacheDir); + + } + const projectHash = getProjectHash(root); + const uuidDir = path.resolve(cacheDir, projectHash); + const uuidPath = path.resolve(uuidDir, 'uuid.json'); + const validate = (u: string) => + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(u); + + try { + if (fs.existsSync(uuidPath)) { + const cached = fs.readFileSync(uuidPath, { encoding: "utf-8" }); + if (validate(cached)) { + return cached; + } + logger.warn( + "[devtools-json] Cached UUID was invalid – regenerating" + ); + } + } catch (err) { + logger.error( + `[devtools-json] Failed to read cached UUID: ${String(err)}` + ); + } + + if (!fs.existsSync(uuidDir)) { + try { + fs.mkdirSync(uuidDir, { recursive: true }); + } catch (err) { + logger.error( + `[devtools-json] Failed creating cache dir: ${String(err)}` + ); + } + } + const uuid = crypto.randomUUID(); + try { + fs.writeFileSync(uuidPath, uuid, { encoding: "utf-8" }); + logger.info( + `[devtools-json] Generated UUID '${uuid}' for DevTools project settings.` + ); + } catch (err) { + logger.error( + `[devtools-json] Unable to persist UUID cache: ${String(err)}` + ); + } + return uuid; + } + const getProjectHash = (root: string): string => { + return crypto.createHash('sha1').update(root).digest('hex').slice(0, 8); + } + const normalizeForChrome = (root: string): string => { + if (path.isAbsolute(root) && root[1] === ':') + return root; + if (process.env.WSL_DISTRO_NAME) { + const distroName = process.env.WSL_DISTRO_NAME; + const withoutLeadingSlash = root.replace(/^\//, ''); + root = path + .join('\\\\wsl.localhost', distroName, withoutLeadingSlash) + .replace(/\//g, '\\'); + logger.info('[devtools-json] Path rewritten for WSL'); + } + // if we use cgroup , it will work for linux docker containers, but not for the main host which is running docker + //need to think about it + if (process.env.DOCKER_DESKTOP && !root.startsWith('\\\\')) { + const withoutLeadingSlash = root.replace(/^\//, ''); + root = path.join('\\\\wsl.localhost', 'docker-desktop-data', withoutLeadingSlash) + .replace(/\//g, '\\'); + logger.info('[devtools-json] Path rewritten for Docker Desktop'); + } + return root; + } + const findProjectRoot = (): string => { + // Prefer explicit env var if provided. + const envRoot = process.env.WASP_PROJECT_ROOT; + if (envRoot && envRoot.length > 0) { + + if (!envRoot.includes(`${path.sep}.wasp${path.sep}out`)) { + return path.resolve(envRoot); + } + } + + + let dir = path.resolve(config.root); + const fsRoot = path.parse(dir).root; + while (dir !== fsRoot) { + if (fs.existsSync(path.join(dir, '.wasp'))) { + return dir; // directory that owns the .wasp folder + } + dir = path.dirname(dir); + } + // Could not find `.wasp`, use vite root. + return path.resolve(config.root); + }; + middlewares.use(ENDPOINT, (_req, res) => { + let root = findProjectRoot(); + root = normalizeForChrome(root); + + const uuid = getOrCreateUUID(); + const devtoolsJson: DevToolsJSON = { workspace: { root, uuid } }; + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify(devtoolsJson, null, 2)); + }) + } + } +} \ No newline at end of file diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/expected-files.manifest b/waspc/e2e-test/test-outputs/waspComplexTest-golden/expected-files.manifest index 184d3d67a0..7eff22b9e2 100644 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/expected-files.manifest +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/expected-files.manifest @@ -684,6 +684,7 @@ waspComplexTest/.wasp/out/web-app/tsconfig.json waspComplexTest/.wasp/out/web-app/tsconfig.vite.json waspComplexTest/.wasp/out/web-app/vite.config.ts waspComplexTest/.wasp/out/web-app/vite/detectServerImports.ts +waspComplexTest/.wasp/out/web-app/vite/devToolsJson.ts waspComplexTest/.wasp/out/web-app/vite/validateEnv.ts waspComplexTest/.waspignore waspComplexTest/.wasproot diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/tsconfig.vite.json b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/tsconfig.vite.json index 6d51817939..a19628d7f5 100644 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/tsconfig.vite.json +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/tsconfig.vite.json @@ -11,5 +11,6 @@ "vite.config.ts", "vite/detectServerImports.ts", "vite/validateEnv.ts", + "vite/devToolsJson.ts", ] } diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/vite.config.ts b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/vite.config.ts index d5169f7d34..a7c758d9a3 100644 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/vite.config.ts +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/vite.config.ts @@ -5,6 +5,7 @@ import { defaultExclude } from "vitest/config" import { detectServerImports } from "./vite/detectServerImports" import { validateEnv } from "./vite/validateEnv.js"; import path from "node:path" +import devToolsJsonPlugin from "./vite/devToolsJson"; // Ignoring the TS error because we are importing a file outside of TS root dir. // @ts-ignore @@ -17,6 +18,7 @@ const defaultViteConfig = { validateEnv(), react(), detectServerImports(), + devToolsJsonPlugin(), ], optimizeDeps: { exclude: ['wasp'] diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/vite/devToolsJson.ts b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/vite/devToolsJson.ts new file mode 100644 index 0000000000..7e4f53d367 --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/vite/devToolsJson.ts @@ -0,0 +1,132 @@ +import fs from 'fs'; +import path from 'path'; +import { Plugin } from 'vite'; +import crypto from 'crypto'; + +interface DevToolsJSON { + workspace?: { root: string, uuid: string }; +} + +const ENDPOINT = '/.well-known/appspecific/com.chrome.devtools.json'; + +export default function devToolsJsonPlugin(): Plugin { + return { + name: 'devtools-json', + enforce: 'post', + configureServer(server) { + const { config, middlewares } = server; + const { logger } = config; + if (!config.env.DEV) return; + + const getOrCreateUUID = (): string => { + let { cacheDir } = config; + let { root } = config; + if (!path.isAbsolute(root)) + root = path.resolve(process.cwd(), root); + + if (!path.isAbsolute(cacheDir)) { + cacheDir = path.resolve(root, cacheDir); + + } + const projectHash = getProjectHash(root); + const uuidDir = path.resolve(cacheDir, projectHash); + const uuidPath = path.resolve(uuidDir, 'uuid.json'); + const validate = (u: string) => + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(u); + + try { + if (fs.existsSync(uuidPath)) { + const cached = fs.readFileSync(uuidPath, { encoding: "utf-8" }); + if (validate(cached)) { + return cached; + } + logger.warn( + "[devtools-json] Cached UUID was invalid – regenerating" + ); + } + } catch (err) { + logger.error( + `[devtools-json] Failed to read cached UUID: ${String(err)}` + ); + } + + if (!fs.existsSync(uuidDir)) { + try { + fs.mkdirSync(uuidDir, { recursive: true }); + } catch (err) { + logger.error( + `[devtools-json] Failed creating cache dir: ${String(err)}` + ); + } + } + const uuid = crypto.randomUUID(); + try { + fs.writeFileSync(uuidPath, uuid, { encoding: "utf-8" }); + logger.info( + `[devtools-json] Generated UUID '${uuid}' for DevTools project settings.` + ); + } catch (err) { + logger.error( + `[devtools-json] Unable to persist UUID cache: ${String(err)}` + ); + } + return uuid; + } + const getProjectHash = (root: string): string => { + return crypto.createHash('sha1').update(root).digest('hex').slice(0, 8); + } + const normalizeForChrome = (root: string): string => { + if (path.isAbsolute(root) && root[1] === ':') + return root; + if (process.env.WSL_DISTRO_NAME) { + const distroName = process.env.WSL_DISTRO_NAME; + const withoutLeadingSlash = root.replace(/^\//, ''); + root = path + .join('\\\\wsl.localhost', distroName, withoutLeadingSlash) + .replace(/\//g, '\\'); + logger.info('[devtools-json] Path rewritten for WSL'); + } + // if we use cgroup , it will work for linux docker containers, but not for the main host which is running docker + //need to think about it + if (process.env.DOCKER_DESKTOP && !root.startsWith('\\\\')) { + const withoutLeadingSlash = root.replace(/^\//, ''); + root = path.join('\\\\wsl.localhost', 'docker-desktop-data', withoutLeadingSlash) + .replace(/\//g, '\\'); + logger.info('[devtools-json] Path rewritten for Docker Desktop'); + } + return root; + } + const findProjectRoot = (): string => { + // Prefer explicit env var if provided. + const envRoot = process.env.WASP_PROJECT_ROOT; + if (envRoot && envRoot.length > 0) { + + if (!envRoot.includes(`${path.sep}.wasp${path.sep}out`)) { + return path.resolve(envRoot); + } + } + + + let dir = path.resolve(config.root); + const fsRoot = path.parse(dir).root; + while (dir !== fsRoot) { + if (fs.existsSync(path.join(dir, '.wasp'))) { + return dir; // directory that owns the .wasp folder + } + dir = path.dirname(dir); + } + // Could not find `.wasp`, use vite root. + return path.resolve(config.root); + }; + middlewares.use(ENDPOINT, (_req, res) => { + let root = findProjectRoot(); + root = normalizeForChrome(root); + + const uuid = getOrCreateUUID(); + const devtoolsJson: DevToolsJSON = { workspace: { root, uuid } }; + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify(devtoolsJson, null, 2)); + }) + } + } +} \ No newline at end of file diff --git a/waspc/e2e-test/test-outputs/waspJob-golden/expected-files.manifest b/waspc/e2e-test/test-outputs/waspJob-golden/expected-files.manifest index e9733c05a2..6a1a15d59c 100644 --- a/waspc/e2e-test/test-outputs/waspJob-golden/expected-files.manifest +++ b/waspc/e2e-test/test-outputs/waspJob-golden/expected-files.manifest @@ -376,6 +376,7 @@ waspJob/.wasp/out/web-app/tsconfig.json waspJob/.wasp/out/web-app/tsconfig.vite.json waspJob/.wasp/out/web-app/vite.config.ts waspJob/.wasp/out/web-app/vite/detectServerImports.ts +waspJob/.wasp/out/web-app/vite/devToolsJson.ts waspJob/.wasp/out/web-app/vite/validateEnv.ts waspJob/.waspignore waspJob/.wasproot diff --git a/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/tsconfig.vite.json b/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/tsconfig.vite.json index 6d51817939..a19628d7f5 100644 --- a/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/tsconfig.vite.json +++ b/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/tsconfig.vite.json @@ -11,5 +11,6 @@ "vite.config.ts", "vite/detectServerImports.ts", "vite/validateEnv.ts", + "vite/devToolsJson.ts", ] } diff --git a/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/vite.config.ts b/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/vite.config.ts index d5169f7d34..a7c758d9a3 100644 --- a/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/vite.config.ts +++ b/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/vite.config.ts @@ -5,6 +5,7 @@ import { defaultExclude } from "vitest/config" import { detectServerImports } from "./vite/detectServerImports" import { validateEnv } from "./vite/validateEnv.js"; import path from "node:path" +import devToolsJsonPlugin from "./vite/devToolsJson"; // Ignoring the TS error because we are importing a file outside of TS root dir. // @ts-ignore @@ -17,6 +18,7 @@ const defaultViteConfig = { validateEnv(), react(), detectServerImports(), + devToolsJsonPlugin(), ], optimizeDeps: { exclude: ['wasp'] diff --git a/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/vite/devToolsJson.ts b/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/vite/devToolsJson.ts new file mode 100644 index 0000000000..7e4f53d367 --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/vite/devToolsJson.ts @@ -0,0 +1,132 @@ +import fs from 'fs'; +import path from 'path'; +import { Plugin } from 'vite'; +import crypto from 'crypto'; + +interface DevToolsJSON { + workspace?: { root: string, uuid: string }; +} + +const ENDPOINT = '/.well-known/appspecific/com.chrome.devtools.json'; + +export default function devToolsJsonPlugin(): Plugin { + return { + name: 'devtools-json', + enforce: 'post', + configureServer(server) { + const { config, middlewares } = server; + const { logger } = config; + if (!config.env.DEV) return; + + const getOrCreateUUID = (): string => { + let { cacheDir } = config; + let { root } = config; + if (!path.isAbsolute(root)) + root = path.resolve(process.cwd(), root); + + if (!path.isAbsolute(cacheDir)) { + cacheDir = path.resolve(root, cacheDir); + + } + const projectHash = getProjectHash(root); + const uuidDir = path.resolve(cacheDir, projectHash); + const uuidPath = path.resolve(uuidDir, 'uuid.json'); + const validate = (u: string) => + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(u); + + try { + if (fs.existsSync(uuidPath)) { + const cached = fs.readFileSync(uuidPath, { encoding: "utf-8" }); + if (validate(cached)) { + return cached; + } + logger.warn( + "[devtools-json] Cached UUID was invalid – regenerating" + ); + } + } catch (err) { + logger.error( + `[devtools-json] Failed to read cached UUID: ${String(err)}` + ); + } + + if (!fs.existsSync(uuidDir)) { + try { + fs.mkdirSync(uuidDir, { recursive: true }); + } catch (err) { + logger.error( + `[devtools-json] Failed creating cache dir: ${String(err)}` + ); + } + } + const uuid = crypto.randomUUID(); + try { + fs.writeFileSync(uuidPath, uuid, { encoding: "utf-8" }); + logger.info( + `[devtools-json] Generated UUID '${uuid}' for DevTools project settings.` + ); + } catch (err) { + logger.error( + `[devtools-json] Unable to persist UUID cache: ${String(err)}` + ); + } + return uuid; + } + const getProjectHash = (root: string): string => { + return crypto.createHash('sha1').update(root).digest('hex').slice(0, 8); + } + const normalizeForChrome = (root: string): string => { + if (path.isAbsolute(root) && root[1] === ':') + return root; + if (process.env.WSL_DISTRO_NAME) { + const distroName = process.env.WSL_DISTRO_NAME; + const withoutLeadingSlash = root.replace(/^\//, ''); + root = path + .join('\\\\wsl.localhost', distroName, withoutLeadingSlash) + .replace(/\//g, '\\'); + logger.info('[devtools-json] Path rewritten for WSL'); + } + // if we use cgroup , it will work for linux docker containers, but not for the main host which is running docker + //need to think about it + if (process.env.DOCKER_DESKTOP && !root.startsWith('\\\\')) { + const withoutLeadingSlash = root.replace(/^\//, ''); + root = path.join('\\\\wsl.localhost', 'docker-desktop-data', withoutLeadingSlash) + .replace(/\//g, '\\'); + logger.info('[devtools-json] Path rewritten for Docker Desktop'); + } + return root; + } + const findProjectRoot = (): string => { + // Prefer explicit env var if provided. + const envRoot = process.env.WASP_PROJECT_ROOT; + if (envRoot && envRoot.length > 0) { + + if (!envRoot.includes(`${path.sep}.wasp${path.sep}out`)) { + return path.resolve(envRoot); + } + } + + + let dir = path.resolve(config.root); + const fsRoot = path.parse(dir).root; + while (dir !== fsRoot) { + if (fs.existsSync(path.join(dir, '.wasp'))) { + return dir; // directory that owns the .wasp folder + } + dir = path.dirname(dir); + } + // Could not find `.wasp`, use vite root. + return path.resolve(config.root); + }; + middlewares.use(ENDPOINT, (_req, res) => { + let root = findProjectRoot(); + root = normalizeForChrome(root); + + const uuid = getOrCreateUUID(); + const devtoolsJson: DevToolsJSON = { workspace: { root, uuid } }; + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify(devtoolsJson, null, 2)); + }) + } + } +} \ No newline at end of file diff --git a/waspc/e2e-test/test-outputs/waspMigrate-golden/expected-files.manifest b/waspc/e2e-test/test-outputs/waspMigrate-golden/expected-files.manifest index 6460e10697..8e01eadbe9 100644 --- a/waspc/e2e-test/test-outputs/waspMigrate-golden/expected-files.manifest +++ b/waspc/e2e-test/test-outputs/waspMigrate-golden/expected-files.manifest @@ -332,6 +332,7 @@ waspMigrate/.wasp/out/web-app/tsconfig.json waspMigrate/.wasp/out/web-app/tsconfig.vite.json waspMigrate/.wasp/out/web-app/vite.config.ts waspMigrate/.wasp/out/web-app/vite/detectServerImports.ts +waspMigrate/.wasp/out/web-app/vite/devToolsJson.ts waspMigrate/.wasp/out/web-app/vite/validateEnv.ts waspMigrate/.waspignore waspMigrate/.wasproot diff --git a/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/tsconfig.vite.json b/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/tsconfig.vite.json index 6d51817939..a19628d7f5 100644 --- a/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/tsconfig.vite.json +++ b/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/tsconfig.vite.json @@ -11,5 +11,6 @@ "vite.config.ts", "vite/detectServerImports.ts", "vite/validateEnv.ts", + "vite/devToolsJson.ts", ] } diff --git a/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/vite.config.ts b/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/vite.config.ts index d5169f7d34..a7c758d9a3 100644 --- a/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/vite.config.ts +++ b/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/vite.config.ts @@ -5,6 +5,7 @@ import { defaultExclude } from "vitest/config" import { detectServerImports } from "./vite/detectServerImports" import { validateEnv } from "./vite/validateEnv.js"; import path from "node:path" +import devToolsJsonPlugin from "./vite/devToolsJson"; // Ignoring the TS error because we are importing a file outside of TS root dir. // @ts-ignore @@ -17,6 +18,7 @@ const defaultViteConfig = { validateEnv(), react(), detectServerImports(), + devToolsJsonPlugin(), ], optimizeDeps: { exclude: ['wasp'] diff --git a/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/vite/devToolsJson.ts b/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/vite/devToolsJson.ts new file mode 100644 index 0000000000..7e4f53d367 --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/vite/devToolsJson.ts @@ -0,0 +1,132 @@ +import fs from 'fs'; +import path from 'path'; +import { Plugin } from 'vite'; +import crypto from 'crypto'; + +interface DevToolsJSON { + workspace?: { root: string, uuid: string }; +} + +const ENDPOINT = '/.well-known/appspecific/com.chrome.devtools.json'; + +export default function devToolsJsonPlugin(): Plugin { + return { + name: 'devtools-json', + enforce: 'post', + configureServer(server) { + const { config, middlewares } = server; + const { logger } = config; + if (!config.env.DEV) return; + + const getOrCreateUUID = (): string => { + let { cacheDir } = config; + let { root } = config; + if (!path.isAbsolute(root)) + root = path.resolve(process.cwd(), root); + + if (!path.isAbsolute(cacheDir)) { + cacheDir = path.resolve(root, cacheDir); + + } + const projectHash = getProjectHash(root); + const uuidDir = path.resolve(cacheDir, projectHash); + const uuidPath = path.resolve(uuidDir, 'uuid.json'); + const validate = (u: string) => + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(u); + + try { + if (fs.existsSync(uuidPath)) { + const cached = fs.readFileSync(uuidPath, { encoding: "utf-8" }); + if (validate(cached)) { + return cached; + } + logger.warn( + "[devtools-json] Cached UUID was invalid – regenerating" + ); + } + } catch (err) { + logger.error( + `[devtools-json] Failed to read cached UUID: ${String(err)}` + ); + } + + if (!fs.existsSync(uuidDir)) { + try { + fs.mkdirSync(uuidDir, { recursive: true }); + } catch (err) { + logger.error( + `[devtools-json] Failed creating cache dir: ${String(err)}` + ); + } + } + const uuid = crypto.randomUUID(); + try { + fs.writeFileSync(uuidPath, uuid, { encoding: "utf-8" }); + logger.info( + `[devtools-json] Generated UUID '${uuid}' for DevTools project settings.` + ); + } catch (err) { + logger.error( + `[devtools-json] Unable to persist UUID cache: ${String(err)}` + ); + } + return uuid; + } + const getProjectHash = (root: string): string => { + return crypto.createHash('sha1').update(root).digest('hex').slice(0, 8); + } + const normalizeForChrome = (root: string): string => { + if (path.isAbsolute(root) && root[1] === ':') + return root; + if (process.env.WSL_DISTRO_NAME) { + const distroName = process.env.WSL_DISTRO_NAME; + const withoutLeadingSlash = root.replace(/^\//, ''); + root = path + .join('\\\\wsl.localhost', distroName, withoutLeadingSlash) + .replace(/\//g, '\\'); + logger.info('[devtools-json] Path rewritten for WSL'); + } + // if we use cgroup , it will work for linux docker containers, but not for the main host which is running docker + //need to think about it + if (process.env.DOCKER_DESKTOP && !root.startsWith('\\\\')) { + const withoutLeadingSlash = root.replace(/^\//, ''); + root = path.join('\\\\wsl.localhost', 'docker-desktop-data', withoutLeadingSlash) + .replace(/\//g, '\\'); + logger.info('[devtools-json] Path rewritten for Docker Desktop'); + } + return root; + } + const findProjectRoot = (): string => { + // Prefer explicit env var if provided. + const envRoot = process.env.WASP_PROJECT_ROOT; + if (envRoot && envRoot.length > 0) { + + if (!envRoot.includes(`${path.sep}.wasp${path.sep}out`)) { + return path.resolve(envRoot); + } + } + + + let dir = path.resolve(config.root); + const fsRoot = path.parse(dir).root; + while (dir !== fsRoot) { + if (fs.existsSync(path.join(dir, '.wasp'))) { + return dir; // directory that owns the .wasp folder + } + dir = path.dirname(dir); + } + // Could not find `.wasp`, use vite root. + return path.resolve(config.root); + }; + middlewares.use(ENDPOINT, (_req, res) => { + let root = findProjectRoot(); + root = normalizeForChrome(root); + + const uuid = getOrCreateUUID(); + const devtoolsJson: DevToolsJSON = { workspace: { root, uuid } }; + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify(devtoolsJson, null, 2)); + }) + } + } +} \ No newline at end of file diff --git a/waspc/src/Wasp/Generator/WebAppGenerator/Start.hs b/waspc/src/Wasp/Generator/WebAppGenerator/Start.hs index 6bd126f6b3..8facc5725b 100644 --- a/waspc/src/Wasp/Generator/WebAppGenerator/Start.hs +++ b/waspc/src/Wasp/Generator/WebAppGenerator/Start.hs @@ -7,9 +7,11 @@ import StrongPath (Abs, Dir, Path', ()) import Wasp.Generator.Common (ProjectRootDir) import qualified Wasp.Generator.WebAppGenerator.Common as Common import qualified Wasp.Job as J -import Wasp.Job.Process (runNodeCommandAsJob) +import qualified StrongPath as SP +import Wasp.Job.Process (runNodeCommandAsJobWithExtraEnv) startWebApp :: Path' Abs (Dir ProjectRootDir) -> J.Job startWebApp projectDir = do let webAppDir = projectDir Common.webAppRootDirInProjectRootDir - runNodeCommandAsJob webAppDir "npm" ["start"] J.WebApp + envVars = [("WASP_PROJECT_ROOT", SP.fromAbsDir projectDir)] + runNodeCommandAsJobWithExtraEnv envVars webAppDir "npm" ["start"] J.WebApp From ec1f3be64904a5a29ea1a9c53a841762a7318029 Mon Sep 17 00:00:00 2001 From: 0xTaneja Date: Thu, 17 Jul 2025 21:14:28 +0000 Subject: [PATCH 3/3] test: refresh goldens after devtools-json upgrade --- waspc/ChangeLog.md | 4 +- .../templates/react-app/vite.config.ts | 10 +- .../templates/react-app/vite/devToolsJson.ts | 132 ------------------ .../waspBuild-golden/expected-files.manifest | 1 - .../waspBuild/.wasp/build/.waspchecksums | 4 +- .../.wasp/build/installedNpmDepsLog.json | 2 +- .../.wasp/build/web-app/package.json | 3 +- .../.wasp/build/web-app/tsconfig.vite.json | 1 - .../.wasp/build/web-app/vite.config.ts | 10 +- .../.wasp/build/web-app/vite/devToolsJson.ts | 132 ------------------ .../expected-files.manifest | 1 - .../waspCompile/.wasp/out/.waspchecksums | 4 +- .../.wasp/out/installedNpmDepsLog.json | 2 +- .../.wasp/out/web-app/package.json | 3 +- .../.wasp/out/web-app/tsconfig.vite.json | 1 - .../.wasp/out/web-app/vite.config.ts | 10 +- .../.wasp/out/web-app/vite/devToolsJson.ts | 132 ------------------ .../expected-files.manifest | 1 - .../waspComplexTest/.wasp/out/.waspchecksums | 4 +- .../.wasp/out/installedNpmDepsLog.json | 2 +- .../.wasp/out/web-app/package.json | 3 +- .../.wasp/out/web-app/tsconfig.vite.json | 1 - .../.wasp/out/web-app/vite.config.ts | 10 +- .../.wasp/out/web-app/vite/devToolsJson.ts | 132 ------------------ .../waspJob-golden/expected-files.manifest | 1 - .../waspJob/.wasp/out/.waspchecksums | 4 +- .../.wasp/out/installedNpmDepsLog.json | 2 +- .../waspJob/.wasp/out/web-app/package.json | 3 +- .../.wasp/out/web-app/tsconfig.vite.json | 1 - .../waspJob/.wasp/out/web-app/vite.config.ts | 10 +- .../.wasp/out/web-app/vite/devToolsJson.ts | 132 ------------------ .../expected-files.manifest | 1 - .../waspMigrate/.wasp/out/.waspchecksums | 4 +- .../.wasp/out/installedNpmDepsLog.json | 2 +- .../.wasp/out/web-app/package.json | 3 +- .../.wasp/out/web-app/tsconfig.vite.json | 1 - .../.wasp/out/web-app/vite.config.ts | 10 +- .../.wasp/out/web-app/vite/devToolsJson.ts | 132 ------------------ waspc/src/Wasp/Generator/WebAppGenerator.hs | 2 + .../Wasp/Generator/WebAppGenerator/Start.hs | 12 +- .../WebAppGenerator/Vite/VitePlugin.hs | 8 +- 41 files changed, 89 insertions(+), 844 deletions(-) delete mode 100644 waspc/data/Generator/templates/react-app/vite/devToolsJson.ts delete mode 100644 waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/web-app/vite/devToolsJson.ts delete mode 100644 waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/vite/devToolsJson.ts delete mode 100644 waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/vite/devToolsJson.ts delete mode 100644 waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/vite/devToolsJson.ts delete mode 100644 waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/vite/devToolsJson.ts diff --git a/waspc/ChangeLog.md b/waspc/ChangeLog.md index 60b38237aa..df0b925bd5 100644 --- a/waspc/ChangeLog.md +++ b/waspc/ChangeLog.md @@ -956,8 +956,8 @@ Check below for details on each of them! ### ⚠️ Breaking changes -- Wasp's **signup action** `import signup from '@wasp/auth/signup' now accepts only the user entity fields relevant to the auth process (e.g. `username` and `password`). - This ensures no unexpected data can be inserted into the database during signup, but it also means you can't any more set any user entity fields via signup action (e.g. `age` or `address`). +- Wasp's **signup action** `import signup from '@wasp/auth/signup' now accepts only the user entity fields relevant to the auth process (e.g. `username`and`password`). +This ensures no unexpected data can be inserted into the database during signup, but it also means you can't any more set any user entity fields via signup action (e.g. `age`or`address`). Instead, those should be set in the separate step after signup, or via a custom signup action of your own. - Wasp now uses **React 18**! Check the following upgrade guide for details: https://react.dev/blog/2022/03/08/react-18-upgrade-guide . The most obvious difference you might notice is that your `useEffect` hooks run twice on component mount. diff --git a/waspc/data/Generator/templates/react-app/vite.config.ts b/waspc/data/Generator/templates/react-app/vite.config.ts index b0cb533e59..d904843989 100644 --- a/waspc/data/Generator/templates/react-app/vite.config.ts +++ b/waspc/data/Generator/templates/react-app/vite.config.ts @@ -6,7 +6,7 @@ import { defaultExclude } from "vitest/config" import { detectServerImports } from "./vite/detectServerImports" import { validateEnv } from "./vite/validateEnv.js"; import path from "node:path" -import devToolsJsonPlugin from "./vite/devToolsJson"; +import devtoolsJson from "vite-plugin-devtools-json"; {=# customViteConfig.isDefined =} // Ignoring the TS error because we are importing a file outside of TS root dir. @@ -24,7 +24,13 @@ const defaultViteConfig = { validateEnv(), react(), detectServerImports(), - devToolsJsonPlugin(), + devtoolsJson({ + // Relative path that resolves to the Wasp project root at runtime + // ("../../../" when evaluated from .wasp/out/web-app). + projectRoot: "{= projectDir =}", + // Keep UNC-path rewriting enabled (default) for WSL / Docker on Windows + normalizeForWindowsContainer: true, + }), ], optimizeDeps: { exclude: ['wasp'] diff --git a/waspc/data/Generator/templates/react-app/vite/devToolsJson.ts b/waspc/data/Generator/templates/react-app/vite/devToolsJson.ts deleted file mode 100644 index 7e4f53d367..0000000000 --- a/waspc/data/Generator/templates/react-app/vite/devToolsJson.ts +++ /dev/null @@ -1,132 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import { Plugin } from 'vite'; -import crypto from 'crypto'; - -interface DevToolsJSON { - workspace?: { root: string, uuid: string }; -} - -const ENDPOINT = '/.well-known/appspecific/com.chrome.devtools.json'; - -export default function devToolsJsonPlugin(): Plugin { - return { - name: 'devtools-json', - enforce: 'post', - configureServer(server) { - const { config, middlewares } = server; - const { logger } = config; - if (!config.env.DEV) return; - - const getOrCreateUUID = (): string => { - let { cacheDir } = config; - let { root } = config; - if (!path.isAbsolute(root)) - root = path.resolve(process.cwd(), root); - - if (!path.isAbsolute(cacheDir)) { - cacheDir = path.resolve(root, cacheDir); - - } - const projectHash = getProjectHash(root); - const uuidDir = path.resolve(cacheDir, projectHash); - const uuidPath = path.resolve(uuidDir, 'uuid.json'); - const validate = (u: string) => - /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(u); - - try { - if (fs.existsSync(uuidPath)) { - const cached = fs.readFileSync(uuidPath, { encoding: "utf-8" }); - if (validate(cached)) { - return cached; - } - logger.warn( - "[devtools-json] Cached UUID was invalid – regenerating" - ); - } - } catch (err) { - logger.error( - `[devtools-json] Failed to read cached UUID: ${String(err)}` - ); - } - - if (!fs.existsSync(uuidDir)) { - try { - fs.mkdirSync(uuidDir, { recursive: true }); - } catch (err) { - logger.error( - `[devtools-json] Failed creating cache dir: ${String(err)}` - ); - } - } - const uuid = crypto.randomUUID(); - try { - fs.writeFileSync(uuidPath, uuid, { encoding: "utf-8" }); - logger.info( - `[devtools-json] Generated UUID '${uuid}' for DevTools project settings.` - ); - } catch (err) { - logger.error( - `[devtools-json] Unable to persist UUID cache: ${String(err)}` - ); - } - return uuid; - } - const getProjectHash = (root: string): string => { - return crypto.createHash('sha1').update(root).digest('hex').slice(0, 8); - } - const normalizeForChrome = (root: string): string => { - if (path.isAbsolute(root) && root[1] === ':') - return root; - if (process.env.WSL_DISTRO_NAME) { - const distroName = process.env.WSL_DISTRO_NAME; - const withoutLeadingSlash = root.replace(/^\//, ''); - root = path - .join('\\\\wsl.localhost', distroName, withoutLeadingSlash) - .replace(/\//g, '\\'); - logger.info('[devtools-json] Path rewritten for WSL'); - } - // if we use cgroup , it will work for linux docker containers, but not for the main host which is running docker - //need to think about it - if (process.env.DOCKER_DESKTOP && !root.startsWith('\\\\')) { - const withoutLeadingSlash = root.replace(/^\//, ''); - root = path.join('\\\\wsl.localhost', 'docker-desktop-data', withoutLeadingSlash) - .replace(/\//g, '\\'); - logger.info('[devtools-json] Path rewritten for Docker Desktop'); - } - return root; - } - const findProjectRoot = (): string => { - // Prefer explicit env var if provided. - const envRoot = process.env.WASP_PROJECT_ROOT; - if (envRoot && envRoot.length > 0) { - - if (!envRoot.includes(`${path.sep}.wasp${path.sep}out`)) { - return path.resolve(envRoot); - } - } - - - let dir = path.resolve(config.root); - const fsRoot = path.parse(dir).root; - while (dir !== fsRoot) { - if (fs.existsSync(path.join(dir, '.wasp'))) { - return dir; // directory that owns the .wasp folder - } - dir = path.dirname(dir); - } - // Could not find `.wasp`, use vite root. - return path.resolve(config.root); - }; - middlewares.use(ENDPOINT, (_req, res) => { - let root = findProjectRoot(); - root = normalizeForChrome(root); - - const uuid = getOrCreateUUID(); - const devtoolsJson: DevToolsJSON = { workspace: { root, uuid } }; - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify(devtoolsJson, null, 2)); - }) - } - } -} \ No newline at end of file diff --git a/waspc/e2e-test/test-outputs/waspBuild-golden/expected-files.manifest b/waspc/e2e-test/test-outputs/waspBuild-golden/expected-files.manifest index 86a3a9d388..547523028a 100644 --- a/waspc/e2e-test/test-outputs/waspBuild-golden/expected-files.manifest +++ b/waspc/e2e-test/test-outputs/waspBuild-golden/expected-files.manifest @@ -326,7 +326,6 @@ waspBuild/.wasp/build/web-app/tsconfig.json waspBuild/.wasp/build/web-app/tsconfig.vite.json waspBuild/.wasp/build/web-app/vite.config.ts waspBuild/.wasp/build/web-app/vite/detectServerImports.ts -waspBuild/.wasp/build/web-app/vite/devToolsJson.ts waspBuild/.wasp/build/web-app/vite/validateEnv.ts waspBuild/.wasp/out/sdk/wasp/api/events.ts waspBuild/.wasp/out/sdk/wasp/api/index.ts diff --git a/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/.waspchecksums b/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/.waspchecksums index b46f502f2a..ceec8a7ff3 100644 --- a/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/.waspchecksums +++ b/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/.waspchecksums @@ -578,7 +578,7 @@ "file", "web-app/package.json" ], - "a4013f0a45e86a308859279ba4e5881818f90a2a7b09f3c06cb2a0c2b266bc39" + "3323d778f366504215bba989868bb0e0b62bbbe622ba416c0f6eedb77597c1d9" ], [ [ @@ -704,7 +704,7 @@ "file", "web-app/vite.config.ts" ], - "4978521ed6bc33f15d8ce08ec77f5f0e812bb5bbeb430e37bdc6d41625c72de1" + "3ed291bbae685706c48c92d6946d870e65a7cce86545a6327f7cafd9a03da580" ], [ [ diff --git a/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/installedNpmDepsLog.json b/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/installedNpmDepsLog.json index d77d7458f3..fa9aa38ad7 100644 --- a/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/installedNpmDepsLog.json +++ b/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/installedNpmDepsLog.json @@ -1 +1 @@ -{"_waspSdkNpmDeps":{"dependencies":[{"name":"@prisma/client","version":"5.19.1"},{"name":"prisma","version":"5.19.1"},{"name":"@tanstack/react-query","version":"4.36.1"},{"name":"axios","version":"^1.4.0"},{"name":"express","version":"~5.1.0"},{"name":"mitt","version":"3.0.0"},{"name":"react","version":"^18.2.0"},{"name":"react-router-dom","version":"^6.26.2"},{"name":"react-hook-form","version":"^7.45.4"},{"name":"superjson","version":"^2.2.1"},{"name":"vitest","version":"^1.2.1"},{"name":"@vitest/ui","version":"^1.2.1"},{"name":"jsdom","version":"^21.1.1"},{"name":"@testing-library/react","version":"^14.1.2"},{"name":"@testing-library/jest-dom","version":"^6.3.0"},{"name":"msw","version":"^1.1.0"},{"name":"zod","version":"^3.23.8"}],"devDependencies":[{"name":"@types/express","version":"^5.0.0"},{"name":"@types/express-serve-static-core","version":"^5.0.0"}]},"_userNpmDeps":{"userDependencies":[{"name":"react","version":"^18.2.0"},{"name":"react-dom","version":"^18.2.0"},{"name":"react-router-dom","version":"^6.26.2"},{"name":"wasp","version":"file:.wasp/out/sdk/wasp"}],"userDevDependencies":[{"name":"@types/react","version":"^18.0.37"},{"name":"prisma","version":"5.19.1"},{"name":"typescript","version":"5.8.2"},{"name":"vite","version":"^4.3.9"}]},"_waspFrameworkNpmDeps":{"npmDepsForWebApp":{"dependencies":[{"name":"@tanstack/react-query","version":"4.36.1"},{"name":"axios","version":"^1.4.0"}],"devDependencies":[{"name":"@tsconfig/vite-react","version":"^2.0.0"},{"name":"@types/react-dom","version":"^18.0.11"},{"name":"@vitejs/plugin-react","version":"4.5.1"}]},"npmDepsForServer":{"dependencies":[{"name":"cookie-parser","version":"~1.4.6"},{"name":"cors","version":"^2.8.5"},{"name":"dotenv","version":"^16.0.2"},{"name":"express","version":"~5.1.0"},{"name":"helmet","version":"^6.0.0"},{"name":"morgan","version":"~1.10.0"},{"name":"superjson","version":"^2.2.1"}],"devDependencies":[{"name":"@rollup/plugin-node-resolve","version":"^16.0.0"},{"name":"@tsconfig/node20","version":"latest"},{"name":"@types/cors","version":"^2.8.5"},{"name":"@types/express","version":"^5.0.0"},{"name":"@types/express-serve-static-core","version":"^5.0.0"},{"name":"@types/node","version":"^20.0.0"},{"name":"nodemon","version":"^2.0.19"},{"name":"rollup","version":"^4.9.6"},{"name":"rollup-plugin-esbuild","version":"^6.1.1"}]}}} \ No newline at end of file +{"_waspSdkNpmDeps":{"dependencies":[{"name":"@prisma/client","version":"5.19.1"},{"name":"prisma","version":"5.19.1"},{"name":"@tanstack/react-query","version":"4.36.1"},{"name":"axios","version":"^1.4.0"},{"name":"express","version":"~5.1.0"},{"name":"mitt","version":"3.0.0"},{"name":"react","version":"^18.2.0"},{"name":"react-router-dom","version":"^6.26.2"},{"name":"react-hook-form","version":"^7.45.4"},{"name":"superjson","version":"^2.2.1"},{"name":"vitest","version":"^1.2.1"},{"name":"@vitest/ui","version":"^1.2.1"},{"name":"jsdom","version":"^21.1.1"},{"name":"@testing-library/react","version":"^14.1.2"},{"name":"@testing-library/jest-dom","version":"^6.3.0"},{"name":"msw","version":"^1.1.0"},{"name":"zod","version":"^3.23.8"}],"devDependencies":[{"name":"@types/express","version":"^5.0.0"},{"name":"@types/express-serve-static-core","version":"^5.0.0"}]},"_userNpmDeps":{"userDependencies":[{"name":"react","version":"^18.2.0"},{"name":"react-dom","version":"^18.2.0"},{"name":"react-router-dom","version":"^6.26.2"},{"name":"wasp","version":"file:.wasp/out/sdk/wasp"}],"userDevDependencies":[{"name":"@types/react","version":"^18.0.37"},{"name":"prisma","version":"5.19.1"},{"name":"typescript","version":"5.8.2"},{"name":"vite","version":"^4.3.9"}]},"_waspFrameworkNpmDeps":{"npmDepsForWebApp":{"dependencies":[{"name":"@tanstack/react-query","version":"4.36.1"},{"name":"axios","version":"^1.4.0"}],"devDependencies":[{"name":"@tsconfig/vite-react","version":"^2.0.0"},{"name":"@types/react-dom","version":"^18.0.11"},{"name":"@vitejs/plugin-react","version":"4.5.1"},{"name":"vite-plugin-devtools-json","version":"^0.3.0"}]},"npmDepsForServer":{"dependencies":[{"name":"cookie-parser","version":"~1.4.6"},{"name":"cors","version":"^2.8.5"},{"name":"dotenv","version":"^16.0.2"},{"name":"express","version":"~5.1.0"},{"name":"helmet","version":"^6.0.0"},{"name":"morgan","version":"~1.10.0"},{"name":"superjson","version":"^2.2.1"}],"devDependencies":[{"name":"@rollup/plugin-node-resolve","version":"^16.0.0"},{"name":"@tsconfig/node20","version":"latest"},{"name":"@types/cors","version":"^2.8.5"},{"name":"@types/express","version":"^5.0.0"},{"name":"@types/express-serve-static-core","version":"^5.0.0"},{"name":"@types/node","version":"^20.0.0"},{"name":"nodemon","version":"^2.0.19"},{"name":"rollup","version":"^4.9.6"},{"name":"rollup-plugin-esbuild","version":"^6.1.1"}]}}} \ No newline at end of file diff --git a/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/web-app/package.json b/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/web-app/package.json index 0360f3cbb3..3050cd4047 100644 --- a/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/web-app/package.json +++ b/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/web-app/package.json @@ -18,7 +18,8 @@ "devDependencies": { "@tsconfig/vite-react": "^2.0.0", "@types/react-dom": "^18.0.11", - "@vitejs/plugin-react": "4.5.1" + "@vitejs/plugin-react": "4.5.1", + "vite-plugin-devtools-json": "^0.3.0" }, "engineStrict": true, "engines": { diff --git a/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/web-app/tsconfig.vite.json b/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/web-app/tsconfig.vite.json index a19628d7f5..6d51817939 100644 --- a/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/web-app/tsconfig.vite.json +++ b/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/web-app/tsconfig.vite.json @@ -11,6 +11,5 @@ "vite.config.ts", "vite/detectServerImports.ts", "vite/validateEnv.ts", - "vite/devToolsJson.ts", ] } diff --git a/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/web-app/vite.config.ts b/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/web-app/vite.config.ts index a7c758d9a3..31c340e76f 100644 --- a/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/web-app/vite.config.ts +++ b/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/web-app/vite.config.ts @@ -5,7 +5,7 @@ import { defaultExclude } from "vitest/config" import { detectServerImports } from "./vite/detectServerImports" import { validateEnv } from "./vite/validateEnv.js"; import path from "node:path" -import devToolsJsonPlugin from "./vite/devToolsJson"; +import devtoolsJson from "vite-plugin-devtools-json"; // Ignoring the TS error because we are importing a file outside of TS root dir. // @ts-ignore @@ -18,7 +18,13 @@ const defaultViteConfig = { validateEnv(), react(), detectServerImports(), - devToolsJsonPlugin(), + devtoolsJson({ + // Relative path that resolves to the Wasp project root at runtime + // ("../../../" when evaluated from .wasp/out/web-app). + projectRoot: "../../../", + // Keep UNC-path rewriting enabled (default) for WSL / Docker on Windows + normalizeForWindowsContainer: true, + }), ], optimizeDeps: { exclude: ['wasp'] diff --git a/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/web-app/vite/devToolsJson.ts b/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/web-app/vite/devToolsJson.ts deleted file mode 100644 index 7e4f53d367..0000000000 --- a/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/web-app/vite/devToolsJson.ts +++ /dev/null @@ -1,132 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import { Plugin } from 'vite'; -import crypto from 'crypto'; - -interface DevToolsJSON { - workspace?: { root: string, uuid: string }; -} - -const ENDPOINT = '/.well-known/appspecific/com.chrome.devtools.json'; - -export default function devToolsJsonPlugin(): Plugin { - return { - name: 'devtools-json', - enforce: 'post', - configureServer(server) { - const { config, middlewares } = server; - const { logger } = config; - if (!config.env.DEV) return; - - const getOrCreateUUID = (): string => { - let { cacheDir } = config; - let { root } = config; - if (!path.isAbsolute(root)) - root = path.resolve(process.cwd(), root); - - if (!path.isAbsolute(cacheDir)) { - cacheDir = path.resolve(root, cacheDir); - - } - const projectHash = getProjectHash(root); - const uuidDir = path.resolve(cacheDir, projectHash); - const uuidPath = path.resolve(uuidDir, 'uuid.json'); - const validate = (u: string) => - /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(u); - - try { - if (fs.existsSync(uuidPath)) { - const cached = fs.readFileSync(uuidPath, { encoding: "utf-8" }); - if (validate(cached)) { - return cached; - } - logger.warn( - "[devtools-json] Cached UUID was invalid – regenerating" - ); - } - } catch (err) { - logger.error( - `[devtools-json] Failed to read cached UUID: ${String(err)}` - ); - } - - if (!fs.existsSync(uuidDir)) { - try { - fs.mkdirSync(uuidDir, { recursive: true }); - } catch (err) { - logger.error( - `[devtools-json] Failed creating cache dir: ${String(err)}` - ); - } - } - const uuid = crypto.randomUUID(); - try { - fs.writeFileSync(uuidPath, uuid, { encoding: "utf-8" }); - logger.info( - `[devtools-json] Generated UUID '${uuid}' for DevTools project settings.` - ); - } catch (err) { - logger.error( - `[devtools-json] Unable to persist UUID cache: ${String(err)}` - ); - } - return uuid; - } - const getProjectHash = (root: string): string => { - return crypto.createHash('sha1').update(root).digest('hex').slice(0, 8); - } - const normalizeForChrome = (root: string): string => { - if (path.isAbsolute(root) && root[1] === ':') - return root; - if (process.env.WSL_DISTRO_NAME) { - const distroName = process.env.WSL_DISTRO_NAME; - const withoutLeadingSlash = root.replace(/^\//, ''); - root = path - .join('\\\\wsl.localhost', distroName, withoutLeadingSlash) - .replace(/\//g, '\\'); - logger.info('[devtools-json] Path rewritten for WSL'); - } - // if we use cgroup , it will work for linux docker containers, but not for the main host which is running docker - //need to think about it - if (process.env.DOCKER_DESKTOP && !root.startsWith('\\\\')) { - const withoutLeadingSlash = root.replace(/^\//, ''); - root = path.join('\\\\wsl.localhost', 'docker-desktop-data', withoutLeadingSlash) - .replace(/\//g, '\\'); - logger.info('[devtools-json] Path rewritten for Docker Desktop'); - } - return root; - } - const findProjectRoot = (): string => { - // Prefer explicit env var if provided. - const envRoot = process.env.WASP_PROJECT_ROOT; - if (envRoot && envRoot.length > 0) { - - if (!envRoot.includes(`${path.sep}.wasp${path.sep}out`)) { - return path.resolve(envRoot); - } - } - - - let dir = path.resolve(config.root); - const fsRoot = path.parse(dir).root; - while (dir !== fsRoot) { - if (fs.existsSync(path.join(dir, '.wasp'))) { - return dir; // directory that owns the .wasp folder - } - dir = path.dirname(dir); - } - // Could not find `.wasp`, use vite root. - return path.resolve(config.root); - }; - middlewares.use(ENDPOINT, (_req, res) => { - let root = findProjectRoot(); - root = normalizeForChrome(root); - - const uuid = getOrCreateUUID(); - const devtoolsJson: DevToolsJSON = { workspace: { root, uuid } }; - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify(devtoolsJson, null, 2)); - }) - } - } -} \ No newline at end of file diff --git a/waspc/e2e-test/test-outputs/waspCompile-golden/expected-files.manifest b/waspc/e2e-test/test-outputs/waspCompile-golden/expected-files.manifest index ae3d36d880..da6c4da73e 100644 --- a/waspc/e2e-test/test-outputs/waspCompile-golden/expected-files.manifest +++ b/waspc/e2e-test/test-outputs/waspCompile-golden/expected-files.manifest @@ -321,7 +321,6 @@ waspCompile/.wasp/out/web-app/tsconfig.json waspCompile/.wasp/out/web-app/tsconfig.vite.json waspCompile/.wasp/out/web-app/vite.config.ts waspCompile/.wasp/out/web-app/vite/detectServerImports.ts -waspCompile/.wasp/out/web-app/vite/devToolsJson.ts waspCompile/.wasp/out/web-app/vite/validateEnv.ts waspCompile/.waspignore waspCompile/.wasproot diff --git a/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/.waspchecksums b/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/.waspchecksums index d5441f53c6..b02fcfaf0f 100644 --- a/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/.waspchecksums +++ b/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/.waspchecksums @@ -592,7 +592,7 @@ "file", "web-app/package.json" ], - "f276211a2e31df7afcefc31b7e9e439c95e11b8d02c32fcb031dae1e921841fb" + "07bb69c5d91b1340fd04c81f9bed4d541560fdcad6145c39ba6c02cf63c345f2" ], [ [ @@ -718,7 +718,7 @@ "file", "web-app/vite.config.ts" ], - "4978521ed6bc33f15d8ce08ec77f5f0e812bb5bbeb430e37bdc6d41625c72de1" + "3ed291bbae685706c48c92d6946d870e65a7cce86545a6327f7cafd9a03da580" ], [ [ diff --git a/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/installedNpmDepsLog.json b/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/installedNpmDepsLog.json index d77d7458f3..fa9aa38ad7 100644 --- a/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/installedNpmDepsLog.json +++ b/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/installedNpmDepsLog.json @@ -1 +1 @@ -{"_waspSdkNpmDeps":{"dependencies":[{"name":"@prisma/client","version":"5.19.1"},{"name":"prisma","version":"5.19.1"},{"name":"@tanstack/react-query","version":"4.36.1"},{"name":"axios","version":"^1.4.0"},{"name":"express","version":"~5.1.0"},{"name":"mitt","version":"3.0.0"},{"name":"react","version":"^18.2.0"},{"name":"react-router-dom","version":"^6.26.2"},{"name":"react-hook-form","version":"^7.45.4"},{"name":"superjson","version":"^2.2.1"},{"name":"vitest","version":"^1.2.1"},{"name":"@vitest/ui","version":"^1.2.1"},{"name":"jsdom","version":"^21.1.1"},{"name":"@testing-library/react","version":"^14.1.2"},{"name":"@testing-library/jest-dom","version":"^6.3.0"},{"name":"msw","version":"^1.1.0"},{"name":"zod","version":"^3.23.8"}],"devDependencies":[{"name":"@types/express","version":"^5.0.0"},{"name":"@types/express-serve-static-core","version":"^5.0.0"}]},"_userNpmDeps":{"userDependencies":[{"name":"react","version":"^18.2.0"},{"name":"react-dom","version":"^18.2.0"},{"name":"react-router-dom","version":"^6.26.2"},{"name":"wasp","version":"file:.wasp/out/sdk/wasp"}],"userDevDependencies":[{"name":"@types/react","version":"^18.0.37"},{"name":"prisma","version":"5.19.1"},{"name":"typescript","version":"5.8.2"},{"name":"vite","version":"^4.3.9"}]},"_waspFrameworkNpmDeps":{"npmDepsForWebApp":{"dependencies":[{"name":"@tanstack/react-query","version":"4.36.1"},{"name":"axios","version":"^1.4.0"}],"devDependencies":[{"name":"@tsconfig/vite-react","version":"^2.0.0"},{"name":"@types/react-dom","version":"^18.0.11"},{"name":"@vitejs/plugin-react","version":"4.5.1"}]},"npmDepsForServer":{"dependencies":[{"name":"cookie-parser","version":"~1.4.6"},{"name":"cors","version":"^2.8.5"},{"name":"dotenv","version":"^16.0.2"},{"name":"express","version":"~5.1.0"},{"name":"helmet","version":"^6.0.0"},{"name":"morgan","version":"~1.10.0"},{"name":"superjson","version":"^2.2.1"}],"devDependencies":[{"name":"@rollup/plugin-node-resolve","version":"^16.0.0"},{"name":"@tsconfig/node20","version":"latest"},{"name":"@types/cors","version":"^2.8.5"},{"name":"@types/express","version":"^5.0.0"},{"name":"@types/express-serve-static-core","version":"^5.0.0"},{"name":"@types/node","version":"^20.0.0"},{"name":"nodemon","version":"^2.0.19"},{"name":"rollup","version":"^4.9.6"},{"name":"rollup-plugin-esbuild","version":"^6.1.1"}]}}} \ No newline at end of file +{"_waspSdkNpmDeps":{"dependencies":[{"name":"@prisma/client","version":"5.19.1"},{"name":"prisma","version":"5.19.1"},{"name":"@tanstack/react-query","version":"4.36.1"},{"name":"axios","version":"^1.4.0"},{"name":"express","version":"~5.1.0"},{"name":"mitt","version":"3.0.0"},{"name":"react","version":"^18.2.0"},{"name":"react-router-dom","version":"^6.26.2"},{"name":"react-hook-form","version":"^7.45.4"},{"name":"superjson","version":"^2.2.1"},{"name":"vitest","version":"^1.2.1"},{"name":"@vitest/ui","version":"^1.2.1"},{"name":"jsdom","version":"^21.1.1"},{"name":"@testing-library/react","version":"^14.1.2"},{"name":"@testing-library/jest-dom","version":"^6.3.0"},{"name":"msw","version":"^1.1.0"},{"name":"zod","version":"^3.23.8"}],"devDependencies":[{"name":"@types/express","version":"^5.0.0"},{"name":"@types/express-serve-static-core","version":"^5.0.0"}]},"_userNpmDeps":{"userDependencies":[{"name":"react","version":"^18.2.0"},{"name":"react-dom","version":"^18.2.0"},{"name":"react-router-dom","version":"^6.26.2"},{"name":"wasp","version":"file:.wasp/out/sdk/wasp"}],"userDevDependencies":[{"name":"@types/react","version":"^18.0.37"},{"name":"prisma","version":"5.19.1"},{"name":"typescript","version":"5.8.2"},{"name":"vite","version":"^4.3.9"}]},"_waspFrameworkNpmDeps":{"npmDepsForWebApp":{"dependencies":[{"name":"@tanstack/react-query","version":"4.36.1"},{"name":"axios","version":"^1.4.0"}],"devDependencies":[{"name":"@tsconfig/vite-react","version":"^2.0.0"},{"name":"@types/react-dom","version":"^18.0.11"},{"name":"@vitejs/plugin-react","version":"4.5.1"},{"name":"vite-plugin-devtools-json","version":"^0.3.0"}]},"npmDepsForServer":{"dependencies":[{"name":"cookie-parser","version":"~1.4.6"},{"name":"cors","version":"^2.8.5"},{"name":"dotenv","version":"^16.0.2"},{"name":"express","version":"~5.1.0"},{"name":"helmet","version":"^6.0.0"},{"name":"morgan","version":"~1.10.0"},{"name":"superjson","version":"^2.2.1"}],"devDependencies":[{"name":"@rollup/plugin-node-resolve","version":"^16.0.0"},{"name":"@tsconfig/node20","version":"latest"},{"name":"@types/cors","version":"^2.8.5"},{"name":"@types/express","version":"^5.0.0"},{"name":"@types/express-serve-static-core","version":"^5.0.0"},{"name":"@types/node","version":"^20.0.0"},{"name":"nodemon","version":"^2.0.19"},{"name":"rollup","version":"^4.9.6"},{"name":"rollup-plugin-esbuild","version":"^6.1.1"}]}}} \ No newline at end of file diff --git a/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/package.json b/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/package.json index 799e66d385..ee72db2d5c 100644 --- a/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/package.json +++ b/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/package.json @@ -18,7 +18,8 @@ "devDependencies": { "@tsconfig/vite-react": "^2.0.0", "@types/react-dom": "^18.0.11", - "@vitejs/plugin-react": "4.5.1" + "@vitejs/plugin-react": "4.5.1", + "vite-plugin-devtools-json": "^0.3.0" }, "engineStrict": true, "engines": { diff --git a/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/tsconfig.vite.json b/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/tsconfig.vite.json index a19628d7f5..6d51817939 100644 --- a/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/tsconfig.vite.json +++ b/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/tsconfig.vite.json @@ -11,6 +11,5 @@ "vite.config.ts", "vite/detectServerImports.ts", "vite/validateEnv.ts", - "vite/devToolsJson.ts", ] } diff --git a/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/vite.config.ts b/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/vite.config.ts index a7c758d9a3..31c340e76f 100644 --- a/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/vite.config.ts +++ b/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/vite.config.ts @@ -5,7 +5,7 @@ import { defaultExclude } from "vitest/config" import { detectServerImports } from "./vite/detectServerImports" import { validateEnv } from "./vite/validateEnv.js"; import path from "node:path" -import devToolsJsonPlugin from "./vite/devToolsJson"; +import devtoolsJson from "vite-plugin-devtools-json"; // Ignoring the TS error because we are importing a file outside of TS root dir. // @ts-ignore @@ -18,7 +18,13 @@ const defaultViteConfig = { validateEnv(), react(), detectServerImports(), - devToolsJsonPlugin(), + devtoolsJson({ + // Relative path that resolves to the Wasp project root at runtime + // ("../../../" when evaluated from .wasp/out/web-app). + projectRoot: "../../../", + // Keep UNC-path rewriting enabled (default) for WSL / Docker on Windows + normalizeForWindowsContainer: true, + }), ], optimizeDeps: { exclude: ['wasp'] diff --git a/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/vite/devToolsJson.ts b/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/vite/devToolsJson.ts deleted file mode 100644 index 7e4f53d367..0000000000 --- a/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/web-app/vite/devToolsJson.ts +++ /dev/null @@ -1,132 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import { Plugin } from 'vite'; -import crypto from 'crypto'; - -interface DevToolsJSON { - workspace?: { root: string, uuid: string }; -} - -const ENDPOINT = '/.well-known/appspecific/com.chrome.devtools.json'; - -export default function devToolsJsonPlugin(): Plugin { - return { - name: 'devtools-json', - enforce: 'post', - configureServer(server) { - const { config, middlewares } = server; - const { logger } = config; - if (!config.env.DEV) return; - - const getOrCreateUUID = (): string => { - let { cacheDir } = config; - let { root } = config; - if (!path.isAbsolute(root)) - root = path.resolve(process.cwd(), root); - - if (!path.isAbsolute(cacheDir)) { - cacheDir = path.resolve(root, cacheDir); - - } - const projectHash = getProjectHash(root); - const uuidDir = path.resolve(cacheDir, projectHash); - const uuidPath = path.resolve(uuidDir, 'uuid.json'); - const validate = (u: string) => - /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(u); - - try { - if (fs.existsSync(uuidPath)) { - const cached = fs.readFileSync(uuidPath, { encoding: "utf-8" }); - if (validate(cached)) { - return cached; - } - logger.warn( - "[devtools-json] Cached UUID was invalid – regenerating" - ); - } - } catch (err) { - logger.error( - `[devtools-json] Failed to read cached UUID: ${String(err)}` - ); - } - - if (!fs.existsSync(uuidDir)) { - try { - fs.mkdirSync(uuidDir, { recursive: true }); - } catch (err) { - logger.error( - `[devtools-json] Failed creating cache dir: ${String(err)}` - ); - } - } - const uuid = crypto.randomUUID(); - try { - fs.writeFileSync(uuidPath, uuid, { encoding: "utf-8" }); - logger.info( - `[devtools-json] Generated UUID '${uuid}' for DevTools project settings.` - ); - } catch (err) { - logger.error( - `[devtools-json] Unable to persist UUID cache: ${String(err)}` - ); - } - return uuid; - } - const getProjectHash = (root: string): string => { - return crypto.createHash('sha1').update(root).digest('hex').slice(0, 8); - } - const normalizeForChrome = (root: string): string => { - if (path.isAbsolute(root) && root[1] === ':') - return root; - if (process.env.WSL_DISTRO_NAME) { - const distroName = process.env.WSL_DISTRO_NAME; - const withoutLeadingSlash = root.replace(/^\//, ''); - root = path - .join('\\\\wsl.localhost', distroName, withoutLeadingSlash) - .replace(/\//g, '\\'); - logger.info('[devtools-json] Path rewritten for WSL'); - } - // if we use cgroup , it will work for linux docker containers, but not for the main host which is running docker - //need to think about it - if (process.env.DOCKER_DESKTOP && !root.startsWith('\\\\')) { - const withoutLeadingSlash = root.replace(/^\//, ''); - root = path.join('\\\\wsl.localhost', 'docker-desktop-data', withoutLeadingSlash) - .replace(/\//g, '\\'); - logger.info('[devtools-json] Path rewritten for Docker Desktop'); - } - return root; - } - const findProjectRoot = (): string => { - // Prefer explicit env var if provided. - const envRoot = process.env.WASP_PROJECT_ROOT; - if (envRoot && envRoot.length > 0) { - - if (!envRoot.includes(`${path.sep}.wasp${path.sep}out`)) { - return path.resolve(envRoot); - } - } - - - let dir = path.resolve(config.root); - const fsRoot = path.parse(dir).root; - while (dir !== fsRoot) { - if (fs.existsSync(path.join(dir, '.wasp'))) { - return dir; // directory that owns the .wasp folder - } - dir = path.dirname(dir); - } - // Could not find `.wasp`, use vite root. - return path.resolve(config.root); - }; - middlewares.use(ENDPOINT, (_req, res) => { - let root = findProjectRoot(); - root = normalizeForChrome(root); - - const uuid = getOrCreateUUID(); - const devtoolsJson: DevToolsJSON = { workspace: { root, uuid } }; - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify(devtoolsJson, null, 2)); - }) - } - } -} \ No newline at end of file diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/expected-files.manifest b/waspc/e2e-test/test-outputs/waspComplexTest-golden/expected-files.manifest index 7eff22b9e2..184d3d67a0 100644 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/expected-files.manifest +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/expected-files.manifest @@ -684,7 +684,6 @@ waspComplexTest/.wasp/out/web-app/tsconfig.json waspComplexTest/.wasp/out/web-app/tsconfig.vite.json waspComplexTest/.wasp/out/web-app/vite.config.ts waspComplexTest/.wasp/out/web-app/vite/detectServerImports.ts -waspComplexTest/.wasp/out/web-app/vite/devToolsJson.ts waspComplexTest/.wasp/out/web-app/vite/validateEnv.ts waspComplexTest/.waspignore waspComplexTest/.wasproot diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/.waspchecksums b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/.waspchecksums index f0cdcdb97e..7894a07103 100644 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/.waspchecksums +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/.waspchecksums @@ -1229,7 +1229,7 @@ "file", "web-app/package.json" ], - "16d5d7a468e045233ff065edf6660f856009df2dab17485f39ee0c7b7c881df0" + "31df33dc9aa5d47e293fcf1edb63c776b14ae8d7e08b36d79abd166f316e224b" ], [ [ @@ -1369,7 +1369,7 @@ "file", "web-app/vite.config.ts" ], - "4978521ed6bc33f15d8ce08ec77f5f0e812bb5bbeb430e37bdc6d41625c72de1" + "3ed291bbae685706c48c92d6946d870e65a7cce86545a6327f7cafd9a03da580" ], [ [ diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/installedNpmDepsLog.json b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/installedNpmDepsLog.json index 0fc6ace20b..2ea731727d 100644 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/installedNpmDepsLog.json +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/installedNpmDepsLog.json @@ -1 +1 @@ -{"_waspSdkNpmDeps":{"dependencies":[{"name":"@prisma/client","version":"5.19.1"},{"name":"prisma","version":"5.19.1"},{"name":"@tanstack/react-query","version":"4.36.1"},{"name":"axios","version":"^1.4.0"},{"name":"express","version":"~5.1.0"},{"name":"mitt","version":"3.0.0"},{"name":"react","version":"^18.2.0"},{"name":"react-router-dom","version":"^6.26.2"},{"name":"react-hook-form","version":"^7.45.4"},{"name":"superjson","version":"^2.2.1"},{"name":"@stitches/react","version":"^1.2.8"},{"name":"@node-rs/argon2","version":"^1.8.3"},{"name":"arctic","version":"^1.2.1"},{"name":"lucia","version":"^3.0.1"},{"name":"oslo","version":"^1.1.2"},{"name":"@lucia-auth/adapter-prisma","version":"^4.0.0"},{"name":"@sendgrid/mail","version":"^7.7.0"},{"name":"vitest","version":"^1.2.1"},{"name":"@vitest/ui","version":"^1.2.1"},{"name":"jsdom","version":"^21.1.1"},{"name":"@testing-library/react","version":"^14.1.2"},{"name":"@testing-library/jest-dom","version":"^6.3.0"},{"name":"msw","version":"^1.1.0"},{"name":"pg-boss","version":"^8.4.2"},{"name":"zod","version":"^3.23.8"}],"devDependencies":[{"name":"@types/express","version":"^5.0.0"},{"name":"@types/express-serve-static-core","version":"^5.0.0"}]},"_userNpmDeps":{"userDependencies":[{"name":"react","version":"^18.2.0"},{"name":"react-dom","version":"^18.2.0"},{"name":"react-router-dom","version":"^6.26.2"},{"name":"wasp","version":"file:.wasp/out/sdk/wasp"}],"userDevDependencies":[{"name":"@types/react","version":"^18.0.37"},{"name":"prisma","version":"5.19.1"},{"name":"typescript","version":"5.8.2"},{"name":"vite","version":"^4.3.9"}]},"_waspFrameworkNpmDeps":{"npmDepsForWebApp":{"dependencies":[{"name":"@tanstack/react-query","version":"4.36.1"},{"name":"axios","version":"^1.4.0"}],"devDependencies":[{"name":"@tsconfig/vite-react","version":"^2.0.0"},{"name":"@types/react-dom","version":"^18.0.11"},{"name":"@vitejs/plugin-react","version":"4.5.1"}]},"npmDepsForServer":{"dependencies":[{"name":"cookie-parser","version":"~1.4.6"},{"name":"cors","version":"^2.8.5"},{"name":"dotenv","version":"^16.0.2"},{"name":"express","version":"~5.1.0"},{"name":"helmet","version":"^6.0.0"},{"name":"morgan","version":"~1.10.0"},{"name":"superjson","version":"^2.2.1"}],"devDependencies":[{"name":"@rollup/plugin-node-resolve","version":"^16.0.0"},{"name":"@tsconfig/node20","version":"latest"},{"name":"@types/cors","version":"^2.8.5"},{"name":"@types/express","version":"^5.0.0"},{"name":"@types/express-serve-static-core","version":"^5.0.0"},{"name":"@types/node","version":"^20.0.0"},{"name":"nodemon","version":"^2.0.19"},{"name":"rollup","version":"^4.9.6"},{"name":"rollup-plugin-esbuild","version":"^6.1.1"}]}}} \ No newline at end of file +{"_waspSdkNpmDeps":{"dependencies":[{"name":"@prisma/client","version":"5.19.1"},{"name":"prisma","version":"5.19.1"},{"name":"@tanstack/react-query","version":"4.36.1"},{"name":"axios","version":"^1.4.0"},{"name":"express","version":"~5.1.0"},{"name":"mitt","version":"3.0.0"},{"name":"react","version":"^18.2.0"},{"name":"react-router-dom","version":"^6.26.2"},{"name":"react-hook-form","version":"^7.45.4"},{"name":"superjson","version":"^2.2.1"},{"name":"@stitches/react","version":"^1.2.8"},{"name":"@node-rs/argon2","version":"^1.8.3"},{"name":"arctic","version":"^1.2.1"},{"name":"lucia","version":"^3.0.1"},{"name":"oslo","version":"^1.1.2"},{"name":"@lucia-auth/adapter-prisma","version":"^4.0.0"},{"name":"@sendgrid/mail","version":"^7.7.0"},{"name":"vitest","version":"^1.2.1"},{"name":"@vitest/ui","version":"^1.2.1"},{"name":"jsdom","version":"^21.1.1"},{"name":"@testing-library/react","version":"^14.1.2"},{"name":"@testing-library/jest-dom","version":"^6.3.0"},{"name":"msw","version":"^1.1.0"},{"name":"pg-boss","version":"^8.4.2"},{"name":"zod","version":"^3.23.8"}],"devDependencies":[{"name":"@types/express","version":"^5.0.0"},{"name":"@types/express-serve-static-core","version":"^5.0.0"}]},"_userNpmDeps":{"userDependencies":[{"name":"react","version":"^18.2.0"},{"name":"react-dom","version":"^18.2.0"},{"name":"react-router-dom","version":"^6.26.2"},{"name":"wasp","version":"file:.wasp/out/sdk/wasp"}],"userDevDependencies":[{"name":"@types/react","version":"^18.0.37"},{"name":"prisma","version":"5.19.1"},{"name":"typescript","version":"5.8.2"},{"name":"vite","version":"^4.3.9"}]},"_waspFrameworkNpmDeps":{"npmDepsForWebApp":{"dependencies":[{"name":"@tanstack/react-query","version":"4.36.1"},{"name":"axios","version":"^1.4.0"}],"devDependencies":[{"name":"@tsconfig/vite-react","version":"^2.0.0"},{"name":"@types/react-dom","version":"^18.0.11"},{"name":"@vitejs/plugin-react","version":"4.5.1"},{"name":"vite-plugin-devtools-json","version":"^0.3.0"}]},"npmDepsForServer":{"dependencies":[{"name":"cookie-parser","version":"~1.4.6"},{"name":"cors","version":"^2.8.5"},{"name":"dotenv","version":"^16.0.2"},{"name":"express","version":"~5.1.0"},{"name":"helmet","version":"^6.0.0"},{"name":"morgan","version":"~1.10.0"},{"name":"superjson","version":"^2.2.1"}],"devDependencies":[{"name":"@rollup/plugin-node-resolve","version":"^16.0.0"},{"name":"@tsconfig/node20","version":"latest"},{"name":"@types/cors","version":"^2.8.5"},{"name":"@types/express","version":"^5.0.0"},{"name":"@types/express-serve-static-core","version":"^5.0.0"},{"name":"@types/node","version":"^20.0.0"},{"name":"nodemon","version":"^2.0.19"},{"name":"rollup","version":"^4.9.6"},{"name":"rollup-plugin-esbuild","version":"^6.1.1"}]}}} \ No newline at end of file diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/package.json b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/package.json index 4ad5de71bf..1777e0665a 100644 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/package.json +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/package.json @@ -18,7 +18,8 @@ "devDependencies": { "@tsconfig/vite-react": "^2.0.0", "@types/react-dom": "^18.0.11", - "@vitejs/plugin-react": "4.5.1" + "@vitejs/plugin-react": "4.5.1", + "vite-plugin-devtools-json": "^0.3.0" }, "engineStrict": true, "engines": { diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/tsconfig.vite.json b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/tsconfig.vite.json index a19628d7f5..6d51817939 100644 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/tsconfig.vite.json +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/tsconfig.vite.json @@ -11,6 +11,5 @@ "vite.config.ts", "vite/detectServerImports.ts", "vite/validateEnv.ts", - "vite/devToolsJson.ts", ] } diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/vite.config.ts b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/vite.config.ts index a7c758d9a3..31c340e76f 100644 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/vite.config.ts +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/vite.config.ts @@ -5,7 +5,7 @@ import { defaultExclude } from "vitest/config" import { detectServerImports } from "./vite/detectServerImports" import { validateEnv } from "./vite/validateEnv.js"; import path from "node:path" -import devToolsJsonPlugin from "./vite/devToolsJson"; +import devtoolsJson from "vite-plugin-devtools-json"; // Ignoring the TS error because we are importing a file outside of TS root dir. // @ts-ignore @@ -18,7 +18,13 @@ const defaultViteConfig = { validateEnv(), react(), detectServerImports(), - devToolsJsonPlugin(), + devtoolsJson({ + // Relative path that resolves to the Wasp project root at runtime + // ("../../../" when evaluated from .wasp/out/web-app). + projectRoot: "../../../", + // Keep UNC-path rewriting enabled (default) for WSL / Docker on Windows + normalizeForWindowsContainer: true, + }), ], optimizeDeps: { exclude: ['wasp'] diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/vite/devToolsJson.ts b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/vite/devToolsJson.ts deleted file mode 100644 index 7e4f53d367..0000000000 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/web-app/vite/devToolsJson.ts +++ /dev/null @@ -1,132 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import { Plugin } from 'vite'; -import crypto from 'crypto'; - -interface DevToolsJSON { - workspace?: { root: string, uuid: string }; -} - -const ENDPOINT = '/.well-known/appspecific/com.chrome.devtools.json'; - -export default function devToolsJsonPlugin(): Plugin { - return { - name: 'devtools-json', - enforce: 'post', - configureServer(server) { - const { config, middlewares } = server; - const { logger } = config; - if (!config.env.DEV) return; - - const getOrCreateUUID = (): string => { - let { cacheDir } = config; - let { root } = config; - if (!path.isAbsolute(root)) - root = path.resolve(process.cwd(), root); - - if (!path.isAbsolute(cacheDir)) { - cacheDir = path.resolve(root, cacheDir); - - } - const projectHash = getProjectHash(root); - const uuidDir = path.resolve(cacheDir, projectHash); - const uuidPath = path.resolve(uuidDir, 'uuid.json'); - const validate = (u: string) => - /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(u); - - try { - if (fs.existsSync(uuidPath)) { - const cached = fs.readFileSync(uuidPath, { encoding: "utf-8" }); - if (validate(cached)) { - return cached; - } - logger.warn( - "[devtools-json] Cached UUID was invalid – regenerating" - ); - } - } catch (err) { - logger.error( - `[devtools-json] Failed to read cached UUID: ${String(err)}` - ); - } - - if (!fs.existsSync(uuidDir)) { - try { - fs.mkdirSync(uuidDir, { recursive: true }); - } catch (err) { - logger.error( - `[devtools-json] Failed creating cache dir: ${String(err)}` - ); - } - } - const uuid = crypto.randomUUID(); - try { - fs.writeFileSync(uuidPath, uuid, { encoding: "utf-8" }); - logger.info( - `[devtools-json] Generated UUID '${uuid}' for DevTools project settings.` - ); - } catch (err) { - logger.error( - `[devtools-json] Unable to persist UUID cache: ${String(err)}` - ); - } - return uuid; - } - const getProjectHash = (root: string): string => { - return crypto.createHash('sha1').update(root).digest('hex').slice(0, 8); - } - const normalizeForChrome = (root: string): string => { - if (path.isAbsolute(root) && root[1] === ':') - return root; - if (process.env.WSL_DISTRO_NAME) { - const distroName = process.env.WSL_DISTRO_NAME; - const withoutLeadingSlash = root.replace(/^\//, ''); - root = path - .join('\\\\wsl.localhost', distroName, withoutLeadingSlash) - .replace(/\//g, '\\'); - logger.info('[devtools-json] Path rewritten for WSL'); - } - // if we use cgroup , it will work for linux docker containers, but not for the main host which is running docker - //need to think about it - if (process.env.DOCKER_DESKTOP && !root.startsWith('\\\\')) { - const withoutLeadingSlash = root.replace(/^\//, ''); - root = path.join('\\\\wsl.localhost', 'docker-desktop-data', withoutLeadingSlash) - .replace(/\//g, '\\'); - logger.info('[devtools-json] Path rewritten for Docker Desktop'); - } - return root; - } - const findProjectRoot = (): string => { - // Prefer explicit env var if provided. - const envRoot = process.env.WASP_PROJECT_ROOT; - if (envRoot && envRoot.length > 0) { - - if (!envRoot.includes(`${path.sep}.wasp${path.sep}out`)) { - return path.resolve(envRoot); - } - } - - - let dir = path.resolve(config.root); - const fsRoot = path.parse(dir).root; - while (dir !== fsRoot) { - if (fs.existsSync(path.join(dir, '.wasp'))) { - return dir; // directory that owns the .wasp folder - } - dir = path.dirname(dir); - } - // Could not find `.wasp`, use vite root. - return path.resolve(config.root); - }; - middlewares.use(ENDPOINT, (_req, res) => { - let root = findProjectRoot(); - root = normalizeForChrome(root); - - const uuid = getOrCreateUUID(); - const devtoolsJson: DevToolsJSON = { workspace: { root, uuid } }; - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify(devtoolsJson, null, 2)); - }) - } - } -} \ No newline at end of file diff --git a/waspc/e2e-test/test-outputs/waspJob-golden/expected-files.manifest b/waspc/e2e-test/test-outputs/waspJob-golden/expected-files.manifest index 6a1a15d59c..e9733c05a2 100644 --- a/waspc/e2e-test/test-outputs/waspJob-golden/expected-files.manifest +++ b/waspc/e2e-test/test-outputs/waspJob-golden/expected-files.manifest @@ -376,7 +376,6 @@ waspJob/.wasp/out/web-app/tsconfig.json waspJob/.wasp/out/web-app/tsconfig.vite.json waspJob/.wasp/out/web-app/vite.config.ts waspJob/.wasp/out/web-app/vite/detectServerImports.ts -waspJob/.wasp/out/web-app/vite/devToolsJson.ts waspJob/.wasp/out/web-app/vite/validateEnv.ts waspJob/.waspignore waspJob/.wasproot diff --git a/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/.waspchecksums b/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/.waspchecksums index 0cc82ce936..b178cfd234 100644 --- a/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/.waspchecksums +++ b/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/.waspchecksums @@ -690,7 +690,7 @@ "file", "web-app/package.json" ], - "5f82c47c99523900b638bde2706d1ce09fb9351ae9269f50761a753c1f43a31f" + "975508bb2b2bf49fc44d5e91de626563468891e3c01d123274d2aff53f099f83" ], [ [ @@ -816,7 +816,7 @@ "file", "web-app/vite.config.ts" ], - "4978521ed6bc33f15d8ce08ec77f5f0e812bb5bbeb430e37bdc6d41625c72de1" + "3ed291bbae685706c48c92d6946d870e65a7cce86545a6327f7cafd9a03da580" ], [ [ diff --git a/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/installedNpmDepsLog.json b/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/installedNpmDepsLog.json index d18c72ca1c..90dd5e61c6 100644 --- a/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/installedNpmDepsLog.json +++ b/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/installedNpmDepsLog.json @@ -1 +1 @@ -{"_waspSdkNpmDeps":{"dependencies":[{"name":"@prisma/client","version":"5.19.1"},{"name":"prisma","version":"5.19.1"},{"name":"@tanstack/react-query","version":"4.36.1"},{"name":"axios","version":"^1.4.0"},{"name":"express","version":"~5.1.0"},{"name":"mitt","version":"3.0.0"},{"name":"react","version":"^18.2.0"},{"name":"react-router-dom","version":"^6.26.2"},{"name":"react-hook-form","version":"^7.45.4"},{"name":"superjson","version":"^2.2.1"},{"name":"vitest","version":"^1.2.1"},{"name":"@vitest/ui","version":"^1.2.1"},{"name":"jsdom","version":"^21.1.1"},{"name":"@testing-library/react","version":"^14.1.2"},{"name":"@testing-library/jest-dom","version":"^6.3.0"},{"name":"msw","version":"^1.1.0"},{"name":"pg-boss","version":"^8.4.2"},{"name":"zod","version":"^3.23.8"}],"devDependencies":[{"name":"@types/express","version":"^5.0.0"},{"name":"@types/express-serve-static-core","version":"^5.0.0"}]},"_userNpmDeps":{"userDependencies":[{"name":"react","version":"^18.2.0"},{"name":"react-dom","version":"^18.2.0"},{"name":"react-router-dom","version":"^6.26.2"},{"name":"wasp","version":"file:.wasp/out/sdk/wasp"}],"userDevDependencies":[{"name":"@types/react","version":"^18.0.37"},{"name":"prisma","version":"5.19.1"},{"name":"typescript","version":"5.8.2"},{"name":"vite","version":"^4.3.9"}]},"_waspFrameworkNpmDeps":{"npmDepsForWebApp":{"dependencies":[{"name":"@tanstack/react-query","version":"4.36.1"},{"name":"axios","version":"^1.4.0"}],"devDependencies":[{"name":"@tsconfig/vite-react","version":"^2.0.0"},{"name":"@types/react-dom","version":"^18.0.11"},{"name":"@vitejs/plugin-react","version":"4.5.1"}]},"npmDepsForServer":{"dependencies":[{"name":"cookie-parser","version":"~1.4.6"},{"name":"cors","version":"^2.8.5"},{"name":"dotenv","version":"^16.0.2"},{"name":"express","version":"~5.1.0"},{"name":"helmet","version":"^6.0.0"},{"name":"morgan","version":"~1.10.0"},{"name":"superjson","version":"^2.2.1"}],"devDependencies":[{"name":"@rollup/plugin-node-resolve","version":"^16.0.0"},{"name":"@tsconfig/node20","version":"latest"},{"name":"@types/cors","version":"^2.8.5"},{"name":"@types/express","version":"^5.0.0"},{"name":"@types/express-serve-static-core","version":"^5.0.0"},{"name":"@types/node","version":"^20.0.0"},{"name":"nodemon","version":"^2.0.19"},{"name":"rollup","version":"^4.9.6"},{"name":"rollup-plugin-esbuild","version":"^6.1.1"}]}}} \ No newline at end of file +{"_waspSdkNpmDeps":{"dependencies":[{"name":"@prisma/client","version":"5.19.1"},{"name":"prisma","version":"5.19.1"},{"name":"@tanstack/react-query","version":"4.36.1"},{"name":"axios","version":"^1.4.0"},{"name":"express","version":"~5.1.0"},{"name":"mitt","version":"3.0.0"},{"name":"react","version":"^18.2.0"},{"name":"react-router-dom","version":"^6.26.2"},{"name":"react-hook-form","version":"^7.45.4"},{"name":"superjson","version":"^2.2.1"},{"name":"vitest","version":"^1.2.1"},{"name":"@vitest/ui","version":"^1.2.1"},{"name":"jsdom","version":"^21.1.1"},{"name":"@testing-library/react","version":"^14.1.2"},{"name":"@testing-library/jest-dom","version":"^6.3.0"},{"name":"msw","version":"^1.1.0"},{"name":"pg-boss","version":"^8.4.2"},{"name":"zod","version":"^3.23.8"}],"devDependencies":[{"name":"@types/express","version":"^5.0.0"},{"name":"@types/express-serve-static-core","version":"^5.0.0"}]},"_userNpmDeps":{"userDependencies":[{"name":"react","version":"^18.2.0"},{"name":"react-dom","version":"^18.2.0"},{"name":"react-router-dom","version":"^6.26.2"},{"name":"wasp","version":"file:.wasp/out/sdk/wasp"}],"userDevDependencies":[{"name":"@types/react","version":"^18.0.37"},{"name":"prisma","version":"5.19.1"},{"name":"typescript","version":"5.8.2"},{"name":"vite","version":"^4.3.9"}]},"_waspFrameworkNpmDeps":{"npmDepsForWebApp":{"dependencies":[{"name":"@tanstack/react-query","version":"4.36.1"},{"name":"axios","version":"^1.4.0"}],"devDependencies":[{"name":"@tsconfig/vite-react","version":"^2.0.0"},{"name":"@types/react-dom","version":"^18.0.11"},{"name":"@vitejs/plugin-react","version":"4.5.1"},{"name":"vite-plugin-devtools-json","version":"^0.3.0"}]},"npmDepsForServer":{"dependencies":[{"name":"cookie-parser","version":"~1.4.6"},{"name":"cors","version":"^2.8.5"},{"name":"dotenv","version":"^16.0.2"},{"name":"express","version":"~5.1.0"},{"name":"helmet","version":"^6.0.0"},{"name":"morgan","version":"~1.10.0"},{"name":"superjson","version":"^2.2.1"}],"devDependencies":[{"name":"@rollup/plugin-node-resolve","version":"^16.0.0"},{"name":"@tsconfig/node20","version":"latest"},{"name":"@types/cors","version":"^2.8.5"},{"name":"@types/express","version":"^5.0.0"},{"name":"@types/express-serve-static-core","version":"^5.0.0"},{"name":"@types/node","version":"^20.0.0"},{"name":"nodemon","version":"^2.0.19"},{"name":"rollup","version":"^4.9.6"},{"name":"rollup-plugin-esbuild","version":"^6.1.1"}]}}} \ No newline at end of file diff --git a/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/package.json b/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/package.json index ac7aa6c360..59cecbc004 100644 --- a/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/package.json +++ b/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/package.json @@ -18,7 +18,8 @@ "devDependencies": { "@tsconfig/vite-react": "^2.0.0", "@types/react-dom": "^18.0.11", - "@vitejs/plugin-react": "4.5.1" + "@vitejs/plugin-react": "4.5.1", + "vite-plugin-devtools-json": "^0.3.0" }, "engineStrict": true, "engines": { diff --git a/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/tsconfig.vite.json b/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/tsconfig.vite.json index a19628d7f5..6d51817939 100644 --- a/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/tsconfig.vite.json +++ b/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/tsconfig.vite.json @@ -11,6 +11,5 @@ "vite.config.ts", "vite/detectServerImports.ts", "vite/validateEnv.ts", - "vite/devToolsJson.ts", ] } diff --git a/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/vite.config.ts b/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/vite.config.ts index a7c758d9a3..31c340e76f 100644 --- a/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/vite.config.ts +++ b/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/vite.config.ts @@ -5,7 +5,7 @@ import { defaultExclude } from "vitest/config" import { detectServerImports } from "./vite/detectServerImports" import { validateEnv } from "./vite/validateEnv.js"; import path from "node:path" -import devToolsJsonPlugin from "./vite/devToolsJson"; +import devtoolsJson from "vite-plugin-devtools-json"; // Ignoring the TS error because we are importing a file outside of TS root dir. // @ts-ignore @@ -18,7 +18,13 @@ const defaultViteConfig = { validateEnv(), react(), detectServerImports(), - devToolsJsonPlugin(), + devtoolsJson({ + // Relative path that resolves to the Wasp project root at runtime + // ("../../../" when evaluated from .wasp/out/web-app). + projectRoot: "../../../", + // Keep UNC-path rewriting enabled (default) for WSL / Docker on Windows + normalizeForWindowsContainer: true, + }), ], optimizeDeps: { exclude: ['wasp'] diff --git a/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/vite/devToolsJson.ts b/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/vite/devToolsJson.ts deleted file mode 100644 index 7e4f53d367..0000000000 --- a/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/web-app/vite/devToolsJson.ts +++ /dev/null @@ -1,132 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import { Plugin } from 'vite'; -import crypto from 'crypto'; - -interface DevToolsJSON { - workspace?: { root: string, uuid: string }; -} - -const ENDPOINT = '/.well-known/appspecific/com.chrome.devtools.json'; - -export default function devToolsJsonPlugin(): Plugin { - return { - name: 'devtools-json', - enforce: 'post', - configureServer(server) { - const { config, middlewares } = server; - const { logger } = config; - if (!config.env.DEV) return; - - const getOrCreateUUID = (): string => { - let { cacheDir } = config; - let { root } = config; - if (!path.isAbsolute(root)) - root = path.resolve(process.cwd(), root); - - if (!path.isAbsolute(cacheDir)) { - cacheDir = path.resolve(root, cacheDir); - - } - const projectHash = getProjectHash(root); - const uuidDir = path.resolve(cacheDir, projectHash); - const uuidPath = path.resolve(uuidDir, 'uuid.json'); - const validate = (u: string) => - /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(u); - - try { - if (fs.existsSync(uuidPath)) { - const cached = fs.readFileSync(uuidPath, { encoding: "utf-8" }); - if (validate(cached)) { - return cached; - } - logger.warn( - "[devtools-json] Cached UUID was invalid – regenerating" - ); - } - } catch (err) { - logger.error( - `[devtools-json] Failed to read cached UUID: ${String(err)}` - ); - } - - if (!fs.existsSync(uuidDir)) { - try { - fs.mkdirSync(uuidDir, { recursive: true }); - } catch (err) { - logger.error( - `[devtools-json] Failed creating cache dir: ${String(err)}` - ); - } - } - const uuid = crypto.randomUUID(); - try { - fs.writeFileSync(uuidPath, uuid, { encoding: "utf-8" }); - logger.info( - `[devtools-json] Generated UUID '${uuid}' for DevTools project settings.` - ); - } catch (err) { - logger.error( - `[devtools-json] Unable to persist UUID cache: ${String(err)}` - ); - } - return uuid; - } - const getProjectHash = (root: string): string => { - return crypto.createHash('sha1').update(root).digest('hex').slice(0, 8); - } - const normalizeForChrome = (root: string): string => { - if (path.isAbsolute(root) && root[1] === ':') - return root; - if (process.env.WSL_DISTRO_NAME) { - const distroName = process.env.WSL_DISTRO_NAME; - const withoutLeadingSlash = root.replace(/^\//, ''); - root = path - .join('\\\\wsl.localhost', distroName, withoutLeadingSlash) - .replace(/\//g, '\\'); - logger.info('[devtools-json] Path rewritten for WSL'); - } - // if we use cgroup , it will work for linux docker containers, but not for the main host which is running docker - //need to think about it - if (process.env.DOCKER_DESKTOP && !root.startsWith('\\\\')) { - const withoutLeadingSlash = root.replace(/^\//, ''); - root = path.join('\\\\wsl.localhost', 'docker-desktop-data', withoutLeadingSlash) - .replace(/\//g, '\\'); - logger.info('[devtools-json] Path rewritten for Docker Desktop'); - } - return root; - } - const findProjectRoot = (): string => { - // Prefer explicit env var if provided. - const envRoot = process.env.WASP_PROJECT_ROOT; - if (envRoot && envRoot.length > 0) { - - if (!envRoot.includes(`${path.sep}.wasp${path.sep}out`)) { - return path.resolve(envRoot); - } - } - - - let dir = path.resolve(config.root); - const fsRoot = path.parse(dir).root; - while (dir !== fsRoot) { - if (fs.existsSync(path.join(dir, '.wasp'))) { - return dir; // directory that owns the .wasp folder - } - dir = path.dirname(dir); - } - // Could not find `.wasp`, use vite root. - return path.resolve(config.root); - }; - middlewares.use(ENDPOINT, (_req, res) => { - let root = findProjectRoot(); - root = normalizeForChrome(root); - - const uuid = getOrCreateUUID(); - const devtoolsJson: DevToolsJSON = { workspace: { root, uuid } }; - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify(devtoolsJson, null, 2)); - }) - } - } -} \ No newline at end of file diff --git a/waspc/e2e-test/test-outputs/waspMigrate-golden/expected-files.manifest b/waspc/e2e-test/test-outputs/waspMigrate-golden/expected-files.manifest index 8e01eadbe9..6460e10697 100644 --- a/waspc/e2e-test/test-outputs/waspMigrate-golden/expected-files.manifest +++ b/waspc/e2e-test/test-outputs/waspMigrate-golden/expected-files.manifest @@ -332,7 +332,6 @@ waspMigrate/.wasp/out/web-app/tsconfig.json waspMigrate/.wasp/out/web-app/tsconfig.vite.json waspMigrate/.wasp/out/web-app/vite.config.ts waspMigrate/.wasp/out/web-app/vite/detectServerImports.ts -waspMigrate/.wasp/out/web-app/vite/devToolsJson.ts waspMigrate/.wasp/out/web-app/vite/validateEnv.ts waspMigrate/.waspignore waspMigrate/.wasproot diff --git a/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/.waspchecksums b/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/.waspchecksums index db69fc37e3..d89e75d5e8 100644 --- a/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/.waspchecksums +++ b/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/.waspchecksums @@ -599,7 +599,7 @@ "file", "web-app/package.json" ], - "9d0609be5a87344c50c95d5e2f9b49b13285e281042734f071a498896486f549" + "0ebe2f5bb7be0a87e7879002e6ed46357caaaaff8e35d3e7935559e297833e37" ], [ [ @@ -725,7 +725,7 @@ "file", "web-app/vite.config.ts" ], - "4978521ed6bc33f15d8ce08ec77f5f0e812bb5bbeb430e37bdc6d41625c72de1" + "3ed291bbae685706c48c92d6946d870e65a7cce86545a6327f7cafd9a03da580" ], [ [ diff --git a/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/installedNpmDepsLog.json b/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/installedNpmDepsLog.json index d77d7458f3..fa9aa38ad7 100644 --- a/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/installedNpmDepsLog.json +++ b/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/installedNpmDepsLog.json @@ -1 +1 @@ -{"_waspSdkNpmDeps":{"dependencies":[{"name":"@prisma/client","version":"5.19.1"},{"name":"prisma","version":"5.19.1"},{"name":"@tanstack/react-query","version":"4.36.1"},{"name":"axios","version":"^1.4.0"},{"name":"express","version":"~5.1.0"},{"name":"mitt","version":"3.0.0"},{"name":"react","version":"^18.2.0"},{"name":"react-router-dom","version":"^6.26.2"},{"name":"react-hook-form","version":"^7.45.4"},{"name":"superjson","version":"^2.2.1"},{"name":"vitest","version":"^1.2.1"},{"name":"@vitest/ui","version":"^1.2.1"},{"name":"jsdom","version":"^21.1.1"},{"name":"@testing-library/react","version":"^14.1.2"},{"name":"@testing-library/jest-dom","version":"^6.3.0"},{"name":"msw","version":"^1.1.0"},{"name":"zod","version":"^3.23.8"}],"devDependencies":[{"name":"@types/express","version":"^5.0.0"},{"name":"@types/express-serve-static-core","version":"^5.0.0"}]},"_userNpmDeps":{"userDependencies":[{"name":"react","version":"^18.2.0"},{"name":"react-dom","version":"^18.2.0"},{"name":"react-router-dom","version":"^6.26.2"},{"name":"wasp","version":"file:.wasp/out/sdk/wasp"}],"userDevDependencies":[{"name":"@types/react","version":"^18.0.37"},{"name":"prisma","version":"5.19.1"},{"name":"typescript","version":"5.8.2"},{"name":"vite","version":"^4.3.9"}]},"_waspFrameworkNpmDeps":{"npmDepsForWebApp":{"dependencies":[{"name":"@tanstack/react-query","version":"4.36.1"},{"name":"axios","version":"^1.4.0"}],"devDependencies":[{"name":"@tsconfig/vite-react","version":"^2.0.0"},{"name":"@types/react-dom","version":"^18.0.11"},{"name":"@vitejs/plugin-react","version":"4.5.1"}]},"npmDepsForServer":{"dependencies":[{"name":"cookie-parser","version":"~1.4.6"},{"name":"cors","version":"^2.8.5"},{"name":"dotenv","version":"^16.0.2"},{"name":"express","version":"~5.1.0"},{"name":"helmet","version":"^6.0.0"},{"name":"morgan","version":"~1.10.0"},{"name":"superjson","version":"^2.2.1"}],"devDependencies":[{"name":"@rollup/plugin-node-resolve","version":"^16.0.0"},{"name":"@tsconfig/node20","version":"latest"},{"name":"@types/cors","version":"^2.8.5"},{"name":"@types/express","version":"^5.0.0"},{"name":"@types/express-serve-static-core","version":"^5.0.0"},{"name":"@types/node","version":"^20.0.0"},{"name":"nodemon","version":"^2.0.19"},{"name":"rollup","version":"^4.9.6"},{"name":"rollup-plugin-esbuild","version":"^6.1.1"}]}}} \ No newline at end of file +{"_waspSdkNpmDeps":{"dependencies":[{"name":"@prisma/client","version":"5.19.1"},{"name":"prisma","version":"5.19.1"},{"name":"@tanstack/react-query","version":"4.36.1"},{"name":"axios","version":"^1.4.0"},{"name":"express","version":"~5.1.0"},{"name":"mitt","version":"3.0.0"},{"name":"react","version":"^18.2.0"},{"name":"react-router-dom","version":"^6.26.2"},{"name":"react-hook-form","version":"^7.45.4"},{"name":"superjson","version":"^2.2.1"},{"name":"vitest","version":"^1.2.1"},{"name":"@vitest/ui","version":"^1.2.1"},{"name":"jsdom","version":"^21.1.1"},{"name":"@testing-library/react","version":"^14.1.2"},{"name":"@testing-library/jest-dom","version":"^6.3.0"},{"name":"msw","version":"^1.1.0"},{"name":"zod","version":"^3.23.8"}],"devDependencies":[{"name":"@types/express","version":"^5.0.0"},{"name":"@types/express-serve-static-core","version":"^5.0.0"}]},"_userNpmDeps":{"userDependencies":[{"name":"react","version":"^18.2.0"},{"name":"react-dom","version":"^18.2.0"},{"name":"react-router-dom","version":"^6.26.2"},{"name":"wasp","version":"file:.wasp/out/sdk/wasp"}],"userDevDependencies":[{"name":"@types/react","version":"^18.0.37"},{"name":"prisma","version":"5.19.1"},{"name":"typescript","version":"5.8.2"},{"name":"vite","version":"^4.3.9"}]},"_waspFrameworkNpmDeps":{"npmDepsForWebApp":{"dependencies":[{"name":"@tanstack/react-query","version":"4.36.1"},{"name":"axios","version":"^1.4.0"}],"devDependencies":[{"name":"@tsconfig/vite-react","version":"^2.0.0"},{"name":"@types/react-dom","version":"^18.0.11"},{"name":"@vitejs/plugin-react","version":"4.5.1"},{"name":"vite-plugin-devtools-json","version":"^0.3.0"}]},"npmDepsForServer":{"dependencies":[{"name":"cookie-parser","version":"~1.4.6"},{"name":"cors","version":"^2.8.5"},{"name":"dotenv","version":"^16.0.2"},{"name":"express","version":"~5.1.0"},{"name":"helmet","version":"^6.0.0"},{"name":"morgan","version":"~1.10.0"},{"name":"superjson","version":"^2.2.1"}],"devDependencies":[{"name":"@rollup/plugin-node-resolve","version":"^16.0.0"},{"name":"@tsconfig/node20","version":"latest"},{"name":"@types/cors","version":"^2.8.5"},{"name":"@types/express","version":"^5.0.0"},{"name":"@types/express-serve-static-core","version":"^5.0.0"},{"name":"@types/node","version":"^20.0.0"},{"name":"nodemon","version":"^2.0.19"},{"name":"rollup","version":"^4.9.6"},{"name":"rollup-plugin-esbuild","version":"^6.1.1"}]}}} \ No newline at end of file diff --git a/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/package.json b/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/package.json index 40d7c58b1e..29872ddb22 100644 --- a/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/package.json +++ b/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/package.json @@ -18,7 +18,8 @@ "devDependencies": { "@tsconfig/vite-react": "^2.0.0", "@types/react-dom": "^18.0.11", - "@vitejs/plugin-react": "4.5.1" + "@vitejs/plugin-react": "4.5.1", + "vite-plugin-devtools-json": "^0.3.0" }, "engineStrict": true, "engines": { diff --git a/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/tsconfig.vite.json b/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/tsconfig.vite.json index a19628d7f5..6d51817939 100644 --- a/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/tsconfig.vite.json +++ b/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/tsconfig.vite.json @@ -11,6 +11,5 @@ "vite.config.ts", "vite/detectServerImports.ts", "vite/validateEnv.ts", - "vite/devToolsJson.ts", ] } diff --git a/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/vite.config.ts b/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/vite.config.ts index a7c758d9a3..31c340e76f 100644 --- a/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/vite.config.ts +++ b/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/vite.config.ts @@ -5,7 +5,7 @@ import { defaultExclude } from "vitest/config" import { detectServerImports } from "./vite/detectServerImports" import { validateEnv } from "./vite/validateEnv.js"; import path from "node:path" -import devToolsJsonPlugin from "./vite/devToolsJson"; +import devtoolsJson from "vite-plugin-devtools-json"; // Ignoring the TS error because we are importing a file outside of TS root dir. // @ts-ignore @@ -18,7 +18,13 @@ const defaultViteConfig = { validateEnv(), react(), detectServerImports(), - devToolsJsonPlugin(), + devtoolsJson({ + // Relative path that resolves to the Wasp project root at runtime + // ("../../../" when evaluated from .wasp/out/web-app). + projectRoot: "../../../", + // Keep UNC-path rewriting enabled (default) for WSL / Docker on Windows + normalizeForWindowsContainer: true, + }), ], optimizeDeps: { exclude: ['wasp'] diff --git a/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/vite/devToolsJson.ts b/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/vite/devToolsJson.ts deleted file mode 100644 index 7e4f53d367..0000000000 --- a/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/web-app/vite/devToolsJson.ts +++ /dev/null @@ -1,132 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import { Plugin } from 'vite'; -import crypto from 'crypto'; - -interface DevToolsJSON { - workspace?: { root: string, uuid: string }; -} - -const ENDPOINT = '/.well-known/appspecific/com.chrome.devtools.json'; - -export default function devToolsJsonPlugin(): Plugin { - return { - name: 'devtools-json', - enforce: 'post', - configureServer(server) { - const { config, middlewares } = server; - const { logger } = config; - if (!config.env.DEV) return; - - const getOrCreateUUID = (): string => { - let { cacheDir } = config; - let { root } = config; - if (!path.isAbsolute(root)) - root = path.resolve(process.cwd(), root); - - if (!path.isAbsolute(cacheDir)) { - cacheDir = path.resolve(root, cacheDir); - - } - const projectHash = getProjectHash(root); - const uuidDir = path.resolve(cacheDir, projectHash); - const uuidPath = path.resolve(uuidDir, 'uuid.json'); - const validate = (u: string) => - /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(u); - - try { - if (fs.existsSync(uuidPath)) { - const cached = fs.readFileSync(uuidPath, { encoding: "utf-8" }); - if (validate(cached)) { - return cached; - } - logger.warn( - "[devtools-json] Cached UUID was invalid – regenerating" - ); - } - } catch (err) { - logger.error( - `[devtools-json] Failed to read cached UUID: ${String(err)}` - ); - } - - if (!fs.existsSync(uuidDir)) { - try { - fs.mkdirSync(uuidDir, { recursive: true }); - } catch (err) { - logger.error( - `[devtools-json] Failed creating cache dir: ${String(err)}` - ); - } - } - const uuid = crypto.randomUUID(); - try { - fs.writeFileSync(uuidPath, uuid, { encoding: "utf-8" }); - logger.info( - `[devtools-json] Generated UUID '${uuid}' for DevTools project settings.` - ); - } catch (err) { - logger.error( - `[devtools-json] Unable to persist UUID cache: ${String(err)}` - ); - } - return uuid; - } - const getProjectHash = (root: string): string => { - return crypto.createHash('sha1').update(root).digest('hex').slice(0, 8); - } - const normalizeForChrome = (root: string): string => { - if (path.isAbsolute(root) && root[1] === ':') - return root; - if (process.env.WSL_DISTRO_NAME) { - const distroName = process.env.WSL_DISTRO_NAME; - const withoutLeadingSlash = root.replace(/^\//, ''); - root = path - .join('\\\\wsl.localhost', distroName, withoutLeadingSlash) - .replace(/\//g, '\\'); - logger.info('[devtools-json] Path rewritten for WSL'); - } - // if we use cgroup , it will work for linux docker containers, but not for the main host which is running docker - //need to think about it - if (process.env.DOCKER_DESKTOP && !root.startsWith('\\\\')) { - const withoutLeadingSlash = root.replace(/^\//, ''); - root = path.join('\\\\wsl.localhost', 'docker-desktop-data', withoutLeadingSlash) - .replace(/\//g, '\\'); - logger.info('[devtools-json] Path rewritten for Docker Desktop'); - } - return root; - } - const findProjectRoot = (): string => { - // Prefer explicit env var if provided. - const envRoot = process.env.WASP_PROJECT_ROOT; - if (envRoot && envRoot.length > 0) { - - if (!envRoot.includes(`${path.sep}.wasp${path.sep}out`)) { - return path.resolve(envRoot); - } - } - - - let dir = path.resolve(config.root); - const fsRoot = path.parse(dir).root; - while (dir !== fsRoot) { - if (fs.existsSync(path.join(dir, '.wasp'))) { - return dir; // directory that owns the .wasp folder - } - dir = path.dirname(dir); - } - // Could not find `.wasp`, use vite root. - return path.resolve(config.root); - }; - middlewares.use(ENDPOINT, (_req, res) => { - let root = findProjectRoot(); - root = normalizeForChrome(root); - - const uuid = getOrCreateUUID(); - const devtoolsJson: DevToolsJSON = { workspace: { root, uuid } }; - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify(devtoolsJson, null, 2)); - }) - } - } -} \ No newline at end of file diff --git a/waspc/src/Wasp/Generator/WebAppGenerator.hs b/waspc/src/Wasp/Generator/WebAppGenerator.hs index 9ed9140db0..dd9e52b9ec 100644 --- a/waspc/src/Wasp/Generator/WebAppGenerator.hs +++ b/waspc/src/Wasp/Generator/WebAppGenerator.hs @@ -158,6 +158,8 @@ npmDepsForWasp _spec = -- Core issue: https://github.com/wasp-lang/wasp/issues/2726 -- Long term fix: https://github.com/wasp-lang/wasp/issues/1838 ("@vitejs/plugin-react", "4.5.1"), + -- Provides Chrome DevTools workspace mapping via official plugin. + ("vite-plugin-devtools-json", "^0.3.0"), -- NOTE: Make sure to bump the version of the tsconfig -- when updating Vite or React versions ("@tsconfig/vite-react", "^2.0.0") diff --git a/waspc/src/Wasp/Generator/WebAppGenerator/Start.hs b/waspc/src/Wasp/Generator/WebAppGenerator/Start.hs index 8facc5725b..0c1cdf0555 100644 --- a/waspc/src/Wasp/Generator/WebAppGenerator/Start.hs +++ b/waspc/src/Wasp/Generator/WebAppGenerator/Start.hs @@ -7,11 +7,15 @@ import StrongPath (Abs, Dir, Path', ()) import Wasp.Generator.Common (ProjectRootDir) import qualified Wasp.Generator.WebAppGenerator.Common as Common import qualified Wasp.Job as J -import qualified StrongPath as SP -import Wasp.Job.Process (runNodeCommandAsJobWithExtraEnv) +import Wasp.Job.Process (runNodeCommandAsJob) startWebApp :: Path' Abs (Dir ProjectRootDir) -> J.Job startWebApp projectDir = do let webAppDir = projectDir Common.webAppRootDirInProjectRootDir - envVars = [("WASP_PROJECT_ROOT", SP.fromAbsDir projectDir)] - runNodeCommandAsJobWithExtraEnv envVars webAppDir "npm" ["start"] J.WebApp + -- We previously passed the absolute project root via the WASP_PROJECT_ROOT + -- environment variable so the vendored DevToolsJson Vite plugin could + -- resolve the correct path at runtime. That plugin has now been replaced + -- by the upstream `vite-plugin-devtools-json`, and the project root is + -- injected directly into `vite.config.ts` during code-generation, so the + -- extra environment variable is no longer required. + runNodeCommandAsJob webAppDir "npm" ["start"] J.WebApp diff --git a/waspc/src/Wasp/Generator/WebAppGenerator/Vite/VitePlugin.hs b/waspc/src/Wasp/Generator/WebAppGenerator/Vite/VitePlugin.hs index a2f7eff59e..46894cc43b 100644 --- a/waspc/src/Wasp/Generator/WebAppGenerator/Vite/VitePlugin.hs +++ b/waspc/src/Wasp/Generator/WebAppGenerator/Vite/VitePlugin.hs @@ -14,7 +14,7 @@ import Wasp.Generator.WebAppGenerator.Common (WebAppTemplatesDir) import qualified Wasp.Generator.WebAppGenerator.Common as C import Wasp.Project.Common (WaspProjectDir, srcDirInWaspProjectDir, waspProjectDirFromAppComponentDir) -data VitePluginName = DetectServerImports | ValidateEnv | DevToolsJson +data VitePluginName = DetectServerImports | ValidateEnv deriving (Enum, Bounded) type TmplFilePath = Path' (Rel WebAppTemplatesDir) File' @@ -32,7 +32,9 @@ vitePlugins = (\name -> (name, getTmplFilePathForVitePlugin name)) vitePluginNames where - vitePluginNames = [minBound .. maxBound] + -- We intentionally exclude the legacy `DevToolsJson` plugin now that + -- Wasp relies on the upstream `vite-plugin-devtools-json` npm package. + vitePluginNames = [DetectServerImports, ValidateEnv] data WebAppVitePluginsDir @@ -44,12 +46,10 @@ getTmplFilePathForVitePlugin pluginName = C.asTmplFile $ vitePluginsDirInWebAppD where pluginFilePathInPluginsDir DetectServerImports = [relfile|detectServerImports.ts|] pluginFilePathInPluginsDir ValidateEnv = [relfile|validateEnv.ts|] - pluginFilePathInPluginsDir DevToolsJson = [relfile|devToolsJson.ts|] genVitePlugin :: VitePlugin -> Generator FileDraft genVitePlugin (DetectServerImports, tmplFile) = genDetectServerImportsPlugin tmplFile genVitePlugin (ValidateEnv, tmplFile) = genValidateEnvPlugin tmplFile -genVitePlugin (DevToolsJson,tmplFile) = return $ C.mkTmplFd tmplFile genDetectServerImportsPlugin :: Path' (Rel WebAppTemplatesDir) File' -> Generator FileDraft genDetectServerImportsPlugin tmplFile = return $ C.mkTmplFdWithData tmplFile tmplData