2026-08-14 13:44:36 +08:00
/** Assembled keyless snapshot for the default `dsh web` browser handoff. */
import { existsSync , mkdtempSync , rmSync , writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { execa } from 'execa'
import { afterEach , describe , expect , it } from 'vitest'
const repoRoot = fileURLToPath ( new URL ( '../../../' , import . meta . url ) )
const builtBin = join ( repoRoot , 'apps/cli/lib/bin.js' )
const frontendIndex = join ( repoRoot , 'apps/web/dist/index.html' )
const openerHook = new URL ( './fixtures/web-browser-open/register.mjs' , import . meta . url ) . href
const openingMessage = 'dsh web: opening the default browser; pass --no-open to disable'
const tempRoots : string [ ] = [ ]
const builtArtifactsExist = existsSync ( builtBin ) && existsSync ( frontendIndex )
if ( process . env . DSH_EXAMPLE_MODE === 'lib' && ! builtArtifactsExist ) {
throw new Error ( 'dsh web browser-open snapshot requires built CLI and Web artifacts in lib mode' )
}
afterEach ( ( ) = > {
for ( const root of tempRoots . splice ( 0 ) ) rmSync ( root , { recursive : true , force : true } )
} )
interface BrowserOpenRecord {
url : string
status : number
bootManifest : boolean
apiKeyPresent : boolean
dshHomePresent : boolean
}
function normalizeLocalUrl ( url : string ) : string {
2026-08-25 00:59:04 +08:00
return url
. replace ( / : \ d + / u , ' : { { p o r t } } ' )
. replace ( / t o k e n = [ ^ & ] + / u , ' t o k e n = { { t o k e n } } ' )
2026-08-14 13:44:36 +08:00
}
describe . skipIf ( ! builtArtifactsExist ) ( 'dsh web browser-open assembled snapshot' , ( ) = > {
it ( 'hands the reachable page to the default browser after the shipped tree settles' , async ( ) = > {
const root = mkdtempSync ( join ( tmpdir ( ) , 'dsh-web-browser-open-snapshot-' ) )
tempRoots . push ( root )
const result = await execa ( process . execPath , [
'--import' , openerHook ,
builtBin ,
'web' ,
'--port' , '0' ,
] , {
cwd : root ,
env : {
. . . process . env ,
DEEPSEEK_API_KEY : 'keyless-browser-open-no-call' ,
DSH_AGENTS_HOME : join ( root , '.agents' ) ,
DSH_HOME : join ( root , '.dsh' ) ,
DSH_TELEMETRY_DISABLED : '1' ,
NODE_NO_WARNINGS : '1' ,
SSH_CONNECTION : '' ,
SSH_TTY : '' ,
} ,
input : '' ,
timeout : 30_000 ,
killSignal : 'SIGKILL' ,
reject : false ,
} )
const readyUrl = / d s h w e b : ( h t t p : \ / \ / [ ^ \ s ] + ) / u . e x e c ( r e s u l t . s t d o u t ) ? . [ 1 ]
const openLine = result . stdout . split ( '\n' ) . find ( line = > line . startsWith ( 'dsh browser-open: ' ) )
const opening = result . stdout . includes ( openingMessage )
if ( readyUrl === undefined || openLine === undefined || ! opening ) {
throw new Error ( ` dsh web browser-open evidence missing \ nstdout: \ n ${ result . stdout } \ nstderr: \ n ${ result . stderr } ` )
}
const opened = JSON . parse ( openLine . slice ( 'dsh browser-open: ' . length ) ) as BrowserOpenRecord
expect ( {
exitCode : result.exitCode ,
opening ,
readyUrl : normalizeLocalUrl ( readyUrl ) ,
openedUrl : normalizeLocalUrl ( opened . url ) ,
status : opened.status ,
bootManifest : opened.bootManifest ,
apiKeyPresent : opened.apiKeyPresent ,
dshHomePresent : opened.dshHomePresent ,
stderr : result.stderr ,
} ) . toMatchInlineSnapshot ( `
{
"apiKeyPresent" : false ,
"bootManifest" : true ,
"dshHomePresent" : false ,
"exitCode" : 0 ,
2026-08-25 00:59:04 +08:00
"openedUrl" : "http://127.0.0.1:{{port}}/?token={{token}}" ,
2026-08-14 13:44:36 +08:00
"opening" : true ,
2026-08-25 00:59:04 +08:00
"readyUrl" : "http://127.0.0.1:{{port}}/?token={{token}}" ,
2026-08-14 13:44:36 +08:00
"status" : 200 ,
"stderr" : "" ,
}
` )
} )
it ( 'prints the launcher reason and manual URL after the Web app is ready' , async ( ) = > {
const root = mkdtempSync ( join ( tmpdir ( ) , 'dsh-web-browser-open-failure-snapshot-' ) )
tempRoots . push ( root )
const result = await execa ( process . execPath , [
'--import' , openerHook ,
builtBin ,
'web' ,
'--port' , '0' ,
] , {
cwd : root ,
env : {
. . . process . env ,
BROWSER_OPEN_TEST_FAILURE : 'fixture desktop unavailable' ,
DEEPSEEK_API_KEY : 'keyless-browser-open-no-call' ,
DSH_AGENTS_HOME : join ( root , '.agents' ) ,
DSH_BROWSER_OPEN_TEST_EXIT_ON_FAILURE : '1' ,
DSH_HOME : join ( root , '.dsh' ) ,
DSH_TELEMETRY_DISABLED : '1' ,
NODE_NO_WARNINGS : '1' ,
SSH_CONNECTION : '' ,
SSH_TTY : '' ,
} ,
input : '' ,
timeout : 30_000 ,
killSignal : 'SIGKILL' ,
reject : false ,
} )
const readyUrl = / d s h w e b : ( h t t p : \ / \ / [ ^ \ s ] + ) / u . e x e c ( r e s u l t . s t d o u t ) ? . [ 1 ]
const diagnostic = result . stderr . split ( / \ r ? \ n / u )
. find ( line = > line . startsWith ( 'web-app: could not open the default browser because ' ) )
expect ( {
diagnostic ,
exitCode : result.exitCode ,
opened : result.stdout.includes ( 'dsh browser-open: ' ) ,
opening : result.stdout.includes ( openingMessage ) ,
readyUrl : readyUrl === undefined ? undefined : normalizeLocalUrl ( readyUrl ) ,
} ) . toMatchInlineSnapshot ( `
{
2026-08-25 00:59:04 +08:00
"diagnostic" : "web-app: could not open the default browser because fixture desktop unavailable; use the dsh web URL printed at startup" ,
2026-08-14 13:44:36 +08:00
"exitCode" : 0 ,
"opened" : false ,
"opening" : true ,
2026-08-25 00:59:04 +08:00
"readyUrl" : "http://127.0.0.1:{{port}}/?token={{token}}" ,
2026-08-14 13:44:36 +08:00
}
` )
} )
it ( 'prints the host URL without launching a browser in a VS Code Remote SSH session' , async ( ) = > {
const root = mkdtempSync ( join ( tmpdir ( ) , 'dsh-web-browser-open-ssh-snapshot-' ) )
tempRoots . push ( root )
const result = await execa ( process . execPath , [
'--import' , openerHook ,
builtBin ,
'web' ,
'--port' , '0' ,
] , {
cwd : root ,
env : {
. . . process . env ,
DEEPSEEK_API_KEY : 'keyless-browser-open-no-call' ,
DSH_AGENTS_HOME : join ( root , '.agents' ) ,
DSH_BROWSER_OPEN_TEST_EXIT_ON_READY : '1' ,
DSH_HOME : join ( root , '.dsh' ) ,
DSH_TELEMETRY_DISABLED : '1' ,
NODE_NO_WARNINGS : '1' ,
SSH_CONNECTION : '10.0.0.2 55000 10.0.0.9 22' ,
SSH_TTY : '' ,
VSCODE_IPC_HOOK_CLI : '/tmp/vscode-ipc' ,
} ,
input : '' ,
timeout : 30_000 ,
killSignal : 'SIGKILL' ,
reject : false ,
} )
const readyUrl = / d s h w e b : ( h t t p : \ / \ / [ ^ \ s ] + ) / u . e x e c ( r e s u l t . s t d o u t ) ? . [ 1 ]
expect ( {
exitCode : result.exitCode ,
opening : result.stdout.includes ( openingMessage ) ,
readyUrl : readyUrl === undefined ? undefined : normalizeLocalUrl ( readyUrl ) ,
opened : result.stdout.includes ( 'dsh browser-open: ' ) ,
stderr : result.stderr ,
} ) . toMatchInlineSnapshot ( `
{
"exitCode" : 0 ,
"opened" : false ,
"opening" : false ,
2026-08-25 00:59:04 +08:00
"readyUrl" : "http://127.0.0.1:{{port}}/?token={{token}}" ,
2026-08-14 13:44:36 +08:00
"stderr" : "" ,
}
` )
} )
it ( 'rejects a project browser command before starting the Web app' , async ( ) = > {
const root = mkdtempSync ( join ( tmpdir ( ) , 'dsh-web-browser-open-env-snapshot-' ) )
tempRoots . push ( root )
writeFileSync ( join ( root , '.env' ) , 'BROWSER=./project-browser\n' )
const result = await execa ( process . execPath , [
'--import' , openerHook ,
builtBin ,
'web' ,
'--port' , '0' ,
] , {
cwd : root ,
env : {
. . . process . env ,
DEEPSEEK_API_KEY : 'keyless-browser-open-no-call' ,
DSH_AGENTS_HOME : join ( root , '.agents' ) ,
DSH_HOME : join ( root , '.dsh' ) ,
DSH_TELEMETRY_DISABLED : '1' ,
NODE_NO_WARNINGS : '1' ,
SSH_CONNECTION : '' ,
SSH_TTY : '' ,
} ,
input : '' ,
timeout : 30_000 ,
killSignal : 'SIGKILL' ,
reject : false ,
} )
const diagnostic = result . stderr . split ( / \ r ? \ n / u )
. find ( line = > line . startsWith ( 'Error: dsh: ' ) )
? . replace ( / ^ E r r o r : d s h : . * [ / \ \ ] \ . e n v / u , ' d s h : { { r o o t } } / . e n v ' )
expect ( {
diagnostic ,
exitCode : result.exitCode ,
opening : result.stdout.includes ( openingMessage ) ,
opened : result.stdout.includes ( 'dsh browser-open: ' ) ,
ready : result.stdout.includes ( 'dsh web: ' ) ,
} ) . toMatchInlineSnapshot ( `
{
"diagnostic" : "dsh: {{root}}/.env sets " BROWSER ", which only the launching environment may set (it decides how this process starts, where its code and instructions load from, or how it reaches the network); export BROWSER instead of putting it in a .env file" ,
"exitCode" : 1 ,
"opened" : false ,
"opening" : false ,
"ready" : false ,
}
` )
} )
} )