From a95145233d1f9840d491d95e85b92884eef51726 Mon Sep 17 00:00:00 2001 From: zhoujundi Date: Sat, 10 Oct 2026 12:20:41 +0800 Subject: [PATCH] fix(web): resolve preview and probe script paths from the repository root The apps migration in #32 moved workdsh-web to apps/web and the shared packages to the repository root, but left two classes of path arithmetic behind, so the source run path documented in apps/web/README.zh-CN.md fails on a clean checkout. shared-profile-features.mjs resolved the plan's feature.directory entries (repository-root relative, e.g. packages/plugins/skills) against apps/web, so preview:install stopped at ENOENT looking for apps/web/packages/plugins/skills. install-preview.mjs, start-preview.mjs, install-project-release.mjs and seven probe scripts prefixed ../../profiles/ to paths already resolved against DSH_HOME, where profiles live at /profiles/. Verified on macOS: preview:install now completes and preview serves the UI (conversation, skills, projects and library views render). The probe and release-installer scripts received the same mechanical correction but were not executed, as they need a browser or a temporary profile installation. None of these scripts are covered by corepack yarn check, which is why main stayed green while the documented source path was broken. Co-Authored-By: Claude Code --- apps/web/scripts/install-preview.mjs | 22 ++++++++++--------- apps/web/scripts/install-project-release.mjs | 12 +++++----- apps/web/scripts/probe-activity.mjs | 4 ++-- .../probe-connectors-personal-browser.mjs | 2 +- apps/web/scripts/probe-native-team-web.mjs | 2 +- apps/web/scripts/probe-office-live.mjs | 2 +- .../probe-personal-default-install.mjs | 4 ++-- .../probe-personal-default-objects.mjs | 4 ++-- .../probe-personal-project-lifecycle.mjs | 4 ++-- apps/web/scripts/shared-profile-features.mjs | 6 +++-- apps/web/scripts/start-preview.mjs | 2 +- 11 files changed, 34 insertions(+), 30 deletions(-) diff --git a/apps/web/scripts/install-preview.mjs b/apps/web/scripts/install-preview.mjs index d9ebabb5c2..0e740a9c18 100644 --- a/apps/web/scripts/install-preview.mjs +++ b/apps/web/scripts/install-preview.mjs @@ -35,7 +35,9 @@ await mkdir(home, { recursive: true }); await mkdir(artifacts, { recursive: true const tarballs = []; const packages = []; const shared = await sharedProfileFeatures(); -for (const directory of ['../../packages/providers/identity-local', '../../packages/providers/browser-session', '../../packages/plugins/audit', '../../packages/plugins/access', ...shared.features.map(feature => feature.directory), '../../packages/plugins/activity', '../../packages/bundle']) { +// The shared plan addresses features from the repository root; every other entry +// here is relative to this app, so qualify the plan entries the same way. +for (const directory of ['../../packages/providers/identity-local', '../../packages/providers/browser-session', '../../packages/plugins/audit', '../../packages/plugins/access', ...shared.features.map(feature => `../../${feature.directory}`), '../../packages/plugins/activity', '../../packages/bundle']) { const manifest = JSON.parse(await readFile(join(root, directory, 'package.json'), 'utf8')); await access(join(root, directory, manifest.exports['.'].default)); await run('pnpm/bin/pnpm.cjs', ['--filter', manifest.name, 'pack', '--pack-destination', artifacts]); @@ -52,13 +54,13 @@ for (const directory of ['../../packages/providers/identity-local', '../../packa packages.push({ directory, manifest }); } let initialized = false; -try { await access(join(home, '../../profiles/preview/package.json')); initialized = true; } +try { await access(join(home, 'profiles/preview/package.json')); initialized = true; } catch (error) { if (error.code !== 'ENOENT') throw error; } if (!initialized) await run('@deepseek-ai/dsh/lib/bin.js', ['--profile', 'preview', '--from-default-profile', 'web', '--dump-config']); // Experts already contributes the official Team service, tools and Web action. // The standalone Team profile adds the same loader ids again and can be toggled // on from the upstream plugin gallery. Keep this preview's bundle list singular. -const previewManifestPath = join(home, '../../profiles/preview/package.json'); +const previewManifestPath = join(home, 'profiles/preview/package.json'); const previewManifest = JSON.parse(await readFile(previewManifestPath, 'utf8')); const normalizedManifest = withoutRedundantAgentTeamProfile(previewManifest); if (normalizedManifest) { @@ -83,15 +85,15 @@ if (!layersMatch) { // Profiles disable automatic peer installation. The platform account adapter // also needs its declared native account peer in this standalone runtime. const installedVersion = async (name) => { - try { return JSON.parse(await readFile(join(home, '../../profiles/preview/node_modules/@deepseek-ai', name, 'package.json'), 'utf8')).version; } + try { return JSON.parse(await readFile(join(home, 'profiles/preview/node_modules/@deepseek-ai', name, 'package.json'), 'utf8')).version; } catch (error) { if (error.code === 'ENOENT') return undefined; throw error; } }; if (await installedVersion('dsh') !== cliVersion || await installedVersion('dsh-deepseek-account') !== cliVersion || await installedVersion('cordis-plugin-group') !== '1.0.4') { - await run('pnpm/bin/pnpm.cjs', ['--dir', join(home, '../../profiles/preview'), 'add', '--save-exact', `@deepseek-ai/dsh@${cliVersion}`, `@deepseek-ai/dsh-deepseek-account@${cliVersion}`, '@deepseek-ai/cordis-plugin-group@1.0.4']); + await run('pnpm/bin/pnpm.cjs', ['--dir', join(home, 'profiles/preview'), 'add', '--save-exact', `@deepseek-ai/dsh@${cliVersion}`, `@deepseek-ai/dsh-deepseek-account@${cliVersion}`, '@deepseek-ai/cordis-plugin-group@1.0.4']); } // DSH Profiles disable automatic peer installation. A fresh preview must // install and upgrade the same official runtime peer closure as the release installer. -const profile = join(home, '../../profiles/preview'); +const profile = join(home, 'profiles/preview'); const officialScope = join(profile, 'node_modules/@deepseek-ai'); for (let pass = 0; pass < 9; pass++) { const missing = new Map(); @@ -139,16 +141,16 @@ if ((await officialResolutionDrift()).length) { const drift = await officialResolutionDrift(); if (drift.length) throw new Error(`Official preview dependency resolution mismatch: ${drift.join('; ')}`); } -const installedBase = JSON.parse(await readFile(join(home, '../../profiles/preview/node_modules/@deepseek-ai/dsh-base/package.json'), 'utf8')); +const installedBase = JSON.parse(await readFile(join(home, 'profiles/preview/node_modules/@deepseek-ai/dsh-base/package.json'), 'utf8')); if (installedBase.version !== baseVersion) throw new Error(`Installed @deepseek-ai/dsh-base ${installedBase.version} does not match pinned ${baseVersion}.`); -const installedWebApp = JSON.parse(await readFile(join(home, '../../profiles/preview/node_modules/@deepseek-ai/dsh-web-app/package.json'), 'utf8')); +const installedWebApp = JSON.parse(await readFile(join(home, 'profiles/preview/node_modules/@deepseek-ai/dsh-web-app/package.json'), 'utf8')); if (installedWebApp.version !== webAppVersion) throw new Error(`Installed @deepseek-ai/dsh-web-app ${installedWebApp.version} does not match pinned ${webAppVersion}.`); // DSH scopes are module-instance local. Installing only the Web bundle // beside a CLI-provided Base bundle can load two physical dsh-scope copies: the // Agent consumers must resolve one shared scope module; otherwise new // session fails as an "unscoped context". Resolve both consumers from the // Profile and fail installation unless they share the exact same module file. -const profileRequire = createRequire(join(home, '../../profiles/preview/package.json')); +const profileRequire = createRequire(join(home, 'profiles/preview/package.json')); const resolveProfileDependency = async (consumer, dependency) => { const consumerManifest = profileRequire.resolve(`${consumer}/package.json`); const consumerRequire = createRequire(consumerManifest); @@ -165,7 +167,7 @@ for (const { directory, manifest } of packages) { const entry = manifest.exports[face]?.default; if (!entry) continue; const expected = await readFile(join(root, directory, entry)); - const installed = await readFile(join(home, '../../profiles/preview/node_modules', manifest.name, entry)); + const installed = await readFile(join(home, 'profiles/preview/node_modules', manifest.name, entry)); if (!expected.equals(installed)) throw new Error(`Installed ${manifest.name} ${face} differs from the current build; refusing to report a successful preview update.`); } } diff --git a/apps/web/scripts/install-project-release.mjs b/apps/web/scripts/install-project-release.mjs index 315e58efb8..e5a42cfbbb 100644 --- a/apps/web/scripts/install-project-release.mjs +++ b/apps/web/scripts/install-project-release.mjs @@ -86,7 +86,7 @@ if (actualHarness !== expectedHarness) { throw new Error(`WorkDSH ${manifest.version} requires dsh ${expectedHarness}; found ${actualHarness || 'unknown'}. Pass --dsh /absolute/path/to/a-compatible-dsh.`); } -const profileManifest = join(dshHome, '../../profiles', profile, 'package.json'); +const profileManifest = join(dshHome, 'profiles', profile, 'package.json'); if (existsSync(profileManifest)) { console.log(`Existing profile ${profile} detected; preserving its configuration and stored data.`); execute(['--profile', profile, '--dump-config']); @@ -101,7 +101,7 @@ if (!dryRun && existsSync(profileManifest) && /^pnpm@\d+\.\d+\.\d+$/.test(manife writeFileSync(profileManifest, JSON.stringify(profilePackage, null, 2) + '\n'); } -const workspaceFile = join(dshHome, '../../profiles', profile, 'pnpm-workspace.yaml'); +const workspaceFile = join(dshHome, 'profiles', profile, 'pnpm-workspace.yaml'); if (!dryRun && existsSync(workspaceFile)) { let current = readFileSync(workspaceFile, 'utf8'); // Native helpers are required by the official runtime; the other two scripts @@ -157,7 +157,7 @@ for (const name of installOrder) { } // Keep CLI and ConfigEditor in the same Profile dependency graph (0.1.7). -const runtimeArgs = ['pnpm', '--dir', join(dshHome, '../../profiles', profile), 'add', '--save-exact', `@deepseek-ai/dsh@${expectedHarness}`, `@deepseek-ai/dsh-deepseek-account@${expectedHarness}`, '@deepseek-ai/cordis-plugin-group@1.0.4']; +const runtimeArgs = ['pnpm', '--dir', join(dshHome, 'profiles', profile), 'add', '--save-exact', `@deepseek-ai/dsh@${expectedHarness}`, `@deepseek-ai/dsh-deepseek-account@${expectedHarness}`, '@deepseek-ai/cordis-plugin-group@1.0.4']; console.log(`> ${corepack} ${runtimeArgs.join(' ')}`); if (!dryRun) { const result = spawnSync(corepack, runtimeArgs, { stdio: 'inherit' }); @@ -168,7 +168,7 @@ if (!dryRun) { // are bundled and not published to npm. Install the official runtime peer closure // explicitly so a clean Profile can boot without relying on the developer repo. if (!dryRun) { - const scope = join(dshHome, '../../profiles', profile, 'node_modules', '@deepseek-ai'); + const scope = join(dshHome, 'profiles', profile, 'node_modules', '@deepseek-ai'); for (let pass = 0; existsSync(scope); pass++) { const missing = new Map(); for (const entry of readdirSync(scope)) { @@ -184,11 +184,11 @@ if (!dryRun) { } if (!missing.size) break; if (pass >= 8) throw new Error('Official runtime peer dependencies did not converge.'); - const result = spawnSync(corepack, ['pnpm', '--dir', join(dshHome, '../../profiles', profile), 'add', '--save-exact', ...missing.values()], { stdio: 'inherit' }); + const result = spawnSync(corepack, ['pnpm', '--dir', join(dshHome, 'profiles', profile), 'add', '--save-exact', ...missing.values()], { stdio: 'inherit' }); if (result.error) throw result.error; if (result.status !== 0) process.exit(result.status ?? 1); } } -const profileCli = join(dshHome, '../../profiles', profile, 'node_modules', '@deepseek-ai', 'dsh', 'lib', 'bin.js'); +const profileCli = join(dshHome, 'profiles', profile, 'node_modules', '@deepseek-ai', 'dsh', 'lib', 'bin.js'); console.log(`\nWorkDSH ${manifest.version} ${dryRun ? 'installation plan verified' : 'is installed'} in profile ${profile}.`); console.log(`Start it with: node ${JSON.stringify(profileCli)} --profile ${JSON.stringify(profile)}`); diff --git a/apps/web/scripts/probe-activity.mjs b/apps/web/scripts/probe-activity.mjs index 81ed83e5a9..75d53f3630 100644 --- a/apps/web/scripts/probe-activity.mjs +++ b/apps/web/scripts/probe-activity.mjs @@ -11,7 +11,7 @@ await mkdir(cwd,{recursive:true});await mkdir(join(home,'storages'),{recursive:t await writeFile(join(home,'storages/workspace.json'),JSON.stringify({unit:{name:'workspace',version:2},global:{initialized:true,workspaceIds:[workspaceId],archivedSessionIds:[]},tables:{workspaces:{[workspaceId]:{path:cwd,title:'Activity fixture',sessionIds:[id],createdAt:new Date().toISOString(),updatedAt:new Date().toISOString()}}}})); const dir=join(home,'sessions','--'+cwd.replaceAll('/','-').replace(/^-+/, '')+'--',id);await mkdir(dir,{recursive:true}); const disabled=process.argv.includes('--disabled'); -await writeFile(join(home,'../../profiles/preview/cordis.patch.yml'),disabled ? '- id: workdsh-activity\n disabled: true\n' : '[]\n'); +await writeFile(join(home,'profiles/preview/cordis.patch.yml'),disabled ? '- id: workdsh-activity\n disabled: true\n' : '[]\n'); const now=Date.now();const rows=[{type:'session',version:3,id,createdAt:now,cwd,isSeeded:false,delegationDepth:0,agentPreset:'dsh-base'}, {type:'turn/start',seq:0,time:now,data:{turn:1}}, {type:'user/message',seq:1,time:now,data:{role:'user',id:'fixture-user',source:{kind:'user'},content:[{type:'text',text:'Activity plugin verification'}]},surfaceOp:'append'}, @@ -44,4 +44,4 @@ assert.ok((await page.locator('body').innerText()).includes('Native conversation await mkdir(join(root,'.artifacts/activity'),{recursive:true});await page.screenshot({path:join(root,'.artifacts/activity/browser.png')}); console.log('PASS: official Profile/Client load; one 46px bar; native body retained; animation off persists; system reduced-motion; Escape; no browser errors.'); } -}finally{await browser?.close();if(server.exitCode===null){const closed=new Promise(r=>server.once('close',r));server.kill('SIGTERM');const timer=setTimeout(()=>server.kill('SIGKILL'),5000);await closed;clearTimeout(timer);}await writeFile(join(home,'../../profiles/preview/cordis.patch.yml'),'[]\n');} +}finally{await browser?.close();if(server.exitCode===null){const closed=new Promise(r=>server.once('close',r));server.kill('SIGTERM');const timer=setTimeout(()=>server.kill('SIGKILL'),5000);await closed;clearTimeout(timer);}await writeFile(join(home,'profiles/preview/cordis.patch.yml'),'[]\n');} diff --git a/apps/web/scripts/probe-connectors-personal-browser.mjs b/apps/web/scripts/probe-connectors-personal-browser.mjs index f0241ecf3d..3567db212f 100644 --- a/apps/web/scripts/probe-connectors-personal-browser.mjs +++ b/apps/web/scripts/probe-connectors-personal-browser.mjs @@ -9,7 +9,7 @@ import { chromium, expect } from '@playwright/test'; const root = new URL('../', import.meta.url).pathname; const home = await realpath(process.argv[2]); assert.match(home, /^\/private\/tmp\/workdsh-experts-package-[\w-]+$/); -const profile = join(home, '../../profiles/experts'); +const profile = join(home, 'profiles/experts'); const manifest = JSON.parse(await readFile(join(profile, 'package.json'), 'utf8')); assert.ok(!JSON.stringify(manifest).includes('enterprise')); const artifacts = join(root, '.artifacts/connectors-personal-browser'); diff --git a/apps/web/scripts/probe-native-team-web.mjs b/apps/web/scripts/probe-native-team-web.mjs index 77c41316a5..4c65127a82 100644 --- a/apps/web/scripts/probe-native-team-web.mjs +++ b/apps/web/scripts/probe-native-team-web.mjs @@ -92,7 +92,7 @@ try { await command(pnpm, ['pack', '--pack-destination', artifacts], fixture); tarballs.push(join(artifacts, 'workdsh-native-team-probe-0.0.0.tgz')); await command(dsh, ['--profile', 'native-team', '--from-default-profile', 'web', '--dump-config']); await command(dsh, ['plugin', '--profile', 'native-team', 'add', `@deepseek-ai/dsh-base@${dshVersion}`, `@deepseek-ai/dsh-web-app@${dshVersion}`, ...tarballs, '--prefer-offline']); - const profilePatch = join(home, '../../profiles/native-team/cordis.patch.yml'); + const profilePatch = join(home, 'profiles/native-team/cordis.patch.yml'); const browserOverride = '- id: browser-use-playwright-mcp\n disabled: true\n'; await writeFile(profilePatch, browserOverride); pass('seven-independent-packages-installed-via-official-cli'); diff --git a/apps/web/scripts/probe-office-live.mjs b/apps/web/scripts/probe-office-live.mjs index 4cd3b88f5d..b138f0b1f3 100644 --- a/apps/web/scripts/probe-office-live.mjs +++ b/apps/web/scripts/probe-office-live.mjs @@ -313,7 +313,7 @@ export function apply(ctx){ctx.effect(()=>ctx.connection.fetch.register({path:'/ "web", "--dump-config", ); - const profileManifest=join(home,'../../profiles/office/package.json'); + const profileManifest=join(home,'profiles/office/package.json'); const profileJson=JSON.parse(await readFile(profileManifest,'utf8'));profileJson.packageManager='pnpm@10.34.5';await writeFile(profileManifest,JSON.stringify(profileJson,null,2)+'\n'); for(let i=0;iname.startsWith('@deepseek-ai/'))),packages,installation:{defaultPackages:packages.map(p=>p.name)}})); const wrapper=join(home,'dsh');await writeFile(wrapper,`#!/bin/sh\nexec '${process.execPath}' '${cli}' "$@"\n`,{mode:0o700}); await run(process.execPath,[join(root,'scripts/install-project-release.mjs'),'--directory',assets,'--profile','personal','--dsh',wrapper]); - const profile=JSON.parse(await readFile(join(home,'../../profiles/personal/package.json'),'utf8')); + const profile=JSON.parse(await readFile(join(home,'profiles/personal/package.json'),'utf8')); for(const name of packages.map(p=>p.name))assert.ok(profile.dependencies[name],name); for(const name of ['workdsh-plugin-office','workdsh-plugin-projects','workdsh-plugin-activity','workdsh-provider-identity-enterprise','workdsh-plugin-enterprise-collaboration'])assert.ok(!profile.dependencies[name],name); pass('Actual source installer creates a fresh personal Profile with only the default owned package set'); - server=spawn(process.execPath,[join(home,'../../profiles/personal/node_modules/@deepseek-ai/dsh/lib/bin.js'),'--profile','personal','--host','127.0.0.1','--port','0','--no-open'],{cwd:home,env,stdio:['ignore','pipe','pipe']}); + server=spawn(process.execPath,[join(home,'profiles/personal/node_modules/@deepseek-ai/dsh/lib/bin.js'),'--profile','personal','--host','127.0.0.1','--port','0','--no-open'],{cwd:home,env,stdio:['ignore','pipe','pipe']}); server.stdout.on('data',b=>{log+=b;});server.stderr.on('data',b=>{log+=b;}); let host; for(let until=Date.now()+45000;Date.now()server.once('close',r));server.kill('SIGTERM');const timer=setTimeout(()=>server.kill('SIGKILL'),3000);await closed;clearTimeout(timer);} -async function start(){let log='';server=spawn(process.execPath,[join(home,'../../profiles/personal/node_modules/@deepseek-ai/dsh/lib/bin.js'),'--profile','personal','--host','127.0.0.1','--port','0','--no-open'],{cwd:home,env,stdio:['ignore','pipe','pipe']});server.stdout.on('data',b=>{log+=b;});server.stderr.on('data',b=>{log+=b;}); +async function start(){let log='';server=spawn(process.execPath,[join(home,'profiles/personal/node_modules/@deepseek-ai/dsh/lib/bin.js'),'--profile','personal','--host','127.0.0.1','--port','0','--no-open'],{cwd:home,env,stdio:['ignore','pipe','pipe']});server.stdout.on('data',b=>{log+=b;});server.stderr.on('data',b=>{log+=b;}); for(let until=Date.now()+45000;Date.now()x.split(';')[0]).join('; ');if(cookie){host={address:new URL(match[0]).origin,cookie};return;}}catch{}if(server.exitCode!==null)break;await new Promise(r=>setTimeout(r,100));}throw new Error('Owned personal Host startup failed');} async function api(domain,endpoint,payload={}){const response=await fetch(host.address+'/api/workdsh-'+domain,{method:'POST',headers:{cookie:host.cookie,'content-type':'application/json'},body:JSON.stringify({endpoint,payload}),signal:AbortSignal.timeout(15000)});assert.equal(response.status,200);const result=await response.json();assert.equal(result.ok,true,JSON.stringify(result.error));return result.value;} const pass=text=>{checks.push(text);console.log('PASS: '+text);}; diff --git a/apps/web/scripts/probe-personal-project-lifecycle.mjs b/apps/web/scripts/probe-personal-project-lifecycle.mjs index 50316d6296..9ab2ced50f 100644 --- a/apps/web/scripts/probe-personal-project-lifecycle.mjs +++ b/apps/web/scripts/probe-personal-project-lifecycle.mjs @@ -9,10 +9,10 @@ const root=new URL('../',import.meta.url).pathname,evidence=join(root,'.artifact const baseline=JSON.parse(await readFile(join(evidence,'personal-default-runtime.json'),'utf8')); const home=await realpath(baseline.home); assert.match(home,/\/T\/workdsh-personal-default-[\w-]+$/); -const manifestPath=join(home,'../../profiles/personal/package.json'); +const manifestPath=join(home,'profiles/personal/package.json'); const before=JSON.parse(await readFile(manifestPath,'utf8'));assert.ok(!before.dependencies['workdsh-plugin-projects']); const env={PATH:`${root}/node_modules/.bin:${dirname(process.execPath)}:/usr/bin:/bin`,HOME:process.env.HOME,DSH_HOME:home,DSH_AGENTS_HOME:join(home,'agents'),npm_config_offline:'true'}; -const cli=join(home,'../../profiles/personal/node_modules/@deepseek-ai/dsh/lib/bin.js'),exec=promisify(execFile); +const cli=join(home,'profiles/personal/node_modules/@deepseek-ai/dsh/lib/bin.js'),exec=promisify(execFile); const run=async(args)=>exec(process.execPath,[cli,...args],{cwd:home,env,timeout:120000,maxBuffer:8*1024*1024}); const checks=[];let server,browser,log=''; async function stop(){await browser?.close();browser=undefined;if(!server||server.exitCode!==null||server.signalCode!==null)return;const done=new Promise(r=>server.once('close',r));server.kill('SIGTERM');const timer=setTimeout(()=>server.kill('SIGKILL'),3000);await done;clearTimeout(timer);} diff --git a/apps/web/scripts/shared-profile-features.mjs b/apps/web/scripts/shared-profile-features.mjs index 0287ca8f08..856e59d0d5 100644 --- a/apps/web/scripts/shared-profile-features.mjs +++ b/apps/web/scripts/shared-profile-features.mjs @@ -1,7 +1,9 @@ import {readFile} from 'node:fs/promises'; -const root=new URL('../',import.meta.url); +// The plan and every feature directory are addressed from the repository root, +// not from the Web app: (apps/web/scripts/ -> repository root). +const root=new URL('../../../',import.meta.url); export async function sharedProfileFeatures(){ - const plan=JSON.parse(await readFile(new URL('../../profiles/shared/workdsh-features.json',root),'utf8')); + const plan=JSON.parse(await readFile(new URL('profiles/shared/workdsh-features.json',root),'utf8')); const names=new Set(); for(const feature of plan.features){ if(names.has(feature.name))throw Error('Duplicate shared feature '+feature.name); diff --git a/apps/web/scripts/start-preview.mjs b/apps/web/scripts/start-preview.mjs index a0d5657874..753be38907 100644 --- a/apps/web/scripts/start-preview.mjs +++ b/apps/web/scripts/start-preview.mjs @@ -12,7 +12,7 @@ const port = process.env.WORKDSH_PREVIEW_PORT ?? '18989'; // Preview currently needs a larger startup heap; this does not fix the underlying growth. const heapMb = process.env.WORKDSH_PREVIEW_HEAP_MB ?? '8192'; if (!/^\d+$/.test(heapMb) || Number(heapMb) < 512) throw new Error('WORKDSH_PREVIEW_HEAP_MB must be an integer >= 512'); -const profile = resolve(previewHome, '../../profiles/preview'); +const profile = resolve(previewHome, 'profiles/preview'); const profileManifestPath = resolve(profile, 'package.json'); if (existsSync(profileManifestPath)) { const profileManifest = JSON.parse(readFileSync(profileManifestPath, 'utf8'));