import {execFileSync, spawnSync} from 'node:child_process'; import assert from 'node:assert/strict'; const image=process.argv[2]||'dsh-web:0.2.0-rc.2-6'; const docker=(args)=>execFileSync('docker',args,{encoding:'utf8',stdio:['ignore','pipe','pipe']}).trim(); const id=docker(['run','-d','--network','none','--read-only','--cap-drop','ALL','--security-opt','no-new-privileges','--tmpfs','/tmp:rw,exec,nosuid,nodev,size=256m','--tmpfs','/data/dsh-home:rw,uid=1000,gid=1000,size=256m','--tmpfs','/workspace:rw,uid=1000,gid=1000,size=64m',image]); try { let ready=false; for(let n=0;n<60;n++){ assert.equal(docker(['inspect','--format','{{.State.Running}}',id]),'true','runtime exited'); try {docker(['exec',id,'node','-e',"fetch('http://127.0.0.1:8080/').then(r=>process.exit(r.status<500?0:1)).catch(()=>process.exit(1))"]);ready=true;break;}catch{} await new Promise(r=>setTimeout(r,1000)); } assert.ok(ready,'offline startup readiness'); const startup = spawnSync('docker', ['logs', id], {encoding:'utf8'}); const token = (startup.stdout + startup.stderr).match(/[?&]token=([^\s\x1b]+)/)?.[1]; assert.ok(token, 'startup token available'); const rpc = (method, args = {}) => { const source = "const {token,method,args}=JSON.parse(require('node:fs').readFileSync(0,'utf8')); (async()=>{const auth=await fetch('http://127.0.0.1:3080/?token='+encodeURIComponent(token),{redirect:'manual'});const cookie=auth.headers.getSetCookie().map(v=>v.split(';')[0]).join('; ');const r=await fetch('http://127.0.0.1:3080/api/'+method,{method:'POST',headers:{'content-type':'application/json',cookie},body:JSON.stringify({type:'client-request',rpcId:'workspace-test',method,payload:{args}})});console.log(JSON.stringify((await r.json()).result));})().catch(()=>process.exit(1));"; const result = spawnSync('docker',['exec','-i',id,'node','-e',source],{encoding:'utf8',input:JSON.stringify({token,method,args})}); assert.equal(result.status,0,'authenticated RPC failed'); return JSON.parse(result.stdout); }; const initial = rpc('workspace/initializeDefault'); assert.equal(initial.ok, true, JSON.stringify(initial)); assert.equal(initial.value.workspace.path, '/workspace'); assert.deepEqual(initial.value.workspace.sessionIds, []); const again = rpc('workspace/initializeDefault'); assert.deepEqual(again, initial); const registered = rpc('workspace/create', {request:{path:'/workspace'}}); assert.equal(registered.ok,true); assert.equal(registered.value.created,false); assert.equal(registered.value.workspace.workspaceId, initial.value.workspace.workspaceId); console.log('PASS real offline fresh registry: canonical /workspace, no sessions, durable default reused by controller and create'); const manifest=JSON.parse(docker(['exec',id,'cat','/data/dsh-home/profiles/web/package.json'])); assert.ok(manifest.dsh.profile.bundles.includes('dsh-web-mobile')); assert.equal(manifest.dependencies['dsh-web-mobile'], 'file:/usr/local/lib/dsh-mgr/dsh-web-mobile-3.0.5.tgz'); const inventory = (home = '/data/dsh-home') => JSON.parse(docker(['exec', id, 'node', '--input-type=module', '-e', "import {readProfilePlugins} from '/usr/local/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/dsh-app-boot/lib/index.js'; console.log(JSON.stringify(readProfilePlugins({binName:'dsh',profileDir:" + JSON.stringify(home + '/profiles/web') + ",installAnchor:'/usr/local/lib/node_modules/@deepseek-ai/dsh/package.json'}).dependencies))"])); assert.deepEqual(inventory().sort((a,b)=>a.name.localeCompare(b.name)), [{name:'dsh-free-search',version:'0.8.5',bundle:true,enabled:true},{name:'dsh-mgr-workspace',version:'1.0.0',bundle:true,enabled:true},{name:'dsh-web-mobile',version:'3.0.5',bundle:true,enabled:true}]); const bundles = JSON.parse(docker(['exec',id,'node','--input-type=module','-e', "import {PluginManager} from '/usr/local/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/dsh-plugin-manager/lib/index.js'; const manager=Object.create(PluginManager.prototype); Object.defineProperties(manager,{profile:{value:{dir:'/data/dsh-home/profiles/web',installAnchor:'/usr/local/lib/node_modules/@deepseek-ai/dsh/package.json'}},ctx:{value:{loader:{entries:()=>[]},get:()=>undefined}},managementBundles:{value:new Set()},ownerEntryId:{value:'include:plugin-manager'}}); console.log(JSON.stringify((await manager.listBundles()).filter(b=>['dsh-web-mobile','dsh-free-search'].includes(b.name))));"])); assert.equal(bundles.length,2); for (const bundle of bundles) { assert.equal(bundle.installed,true); assert.equal(bundle.enabled,true); assert.equal(bundle.removable,false); assert.equal(bundle.error,undefined); assert.equal(bundle.readOnlyReason,undefined); } console.log('PASS upstream listBundles: mobile and free-search installed/enabled, no error/readOnlyReason; baked host bundle not removable'); docker(['exec',id,'node','--input-type=module','-e', "import {readdirSync,lstatSync,readlinkSync} from 'node:fs'; import {join,isAbsolute} from 'node:path'; import assert from 'node:assert/strict'; const root='/data/dsh-home/profiles/web/node_modules'; assert.deepEqual(readdirSync(root).filter(n=>!n.startsWith('.')),['@deepseek-ai','@standard-schema','dsh-free-search','dsh-mgr-workspace','dsh-web-mobile']); assert.deepEqual(readdirSync(join(root,'@deepseek-ai')).sort(),['cosmokit','schemastery']); assert.deepEqual(readdirSync(join(root,'@standard-schema')),['spec']); function scan(dir){for(const n of readdirSync(dir)){const p=join(dir,n);const s=lstatSync(p);if(s.isSymbolicLink())assert.equal(isAbsolute(readlinkSync(p)),false,p);else if(s.isDirectory())scan(p);}}scan(root);"]); const dump=docker(['exec',id,'dsh','--profile','web','--dump-config']); assert.match(dump,/name: ['"]?dsh-web-mobile/); const logResult=spawnSync('docker',['logs',id],{encoding:'utf8'}); assert.equal(logResult.status,0); assert.ok(!/failed to import|entry did not activate|entries did not activate/.test(logResult.stdout+logResult.stderr),'runtime activation failure (logs withheld to protect startup token)'); docker(['exec',id,'node','--input-type=module','-e', "const {apply}=await import('/usr/local/lib/node_modules/@deepseek-ai/dsh/node_modules/dsh-free-search/lib/index.js'); const {default:assert}=await import('node:assert/strict'); for(const selected of [undefined,'deepseek-official','custom']){const web={searchProviderId:selected,fetchProviderId:'custom-fetch',registerSearchProvider(){},registerFetchProvider(){}}; apply({web,logger:{info(){},warn(){}},inject(){},get(){}},{}); assert.equal(web.searchProviderId,selected==='custom'?'custom':'ddg'); assert.equal(web.fetchProviderId,'custom-fetch');}"]); const settings = JSON.parse(docker(['exec',id,'node','-e', "fetch('http://127.0.0.1:3080/api/dsh-free-search-settings/describe',{method:'POST'}).then(async r=>{if(r.status!==200)throw new Error('status '+r.status);console.log(JSON.stringify(await r.json()))})"])); assert.equal(settings.ok,true); assert.equal(settings.value.namespaces.find(n=>n.ns==='web-search-free').value.provider,'bing'); console.log('PASS live free-search activation, official provider takeover and settings describe route'); console.log('PASS offline nonroot startup, readProfilePlugins inventory, no duplicate peers or absolute symlinks'); // Restore only the portable profile manifest/patch/lockfile, without workspace or node_modules. docker(['exec',id,'bash','-ec', 'mkdir -p /tmp/mobile-restore/profiles/web; cp /data/dsh-home/profiles/web/package.json /data/dsh-home/profiles/web/pnpm-lock.yaml /tmp/mobile-restore/profiles/web/; if test -f /data/dsh-home/profiles/web/cordis.patch.yml; then cp /data/dsh-home/profiles/web/cordis.patch.yml /tmp/mobile-restore/profiles/web/; fi']); docker(['exec',id,'node','-e', "const fs=require('node:fs'); const p='/tmp/mobile-restore/profiles/web/package.json'; const m=JSON.parse(fs.readFileSync(p)); m.dsh.profile.bundles=m.dsh.profile.bundles.filter(n=>n!=='dsh-web-mobile');fs.writeFileSync(p,JSON.stringify(m));"]); docker(['exec', '-e', 'DSH_HOME=/tmp/mobile-restore', '-e', 'HOME=/tmp/mobile-restore', '-e', 'XDG_DATA_HOME=/tmp/mobile-restore/.local/share', '-e', 'npm_config_ignore_scripts=true', id, 'bash', '-ec', 'node /usr/local/lib/dsh-mgr/init-profile.mjs; dsh plugin --profile web install --ignore-scripts']); assert.deepEqual(inventory('/tmp/mobile-restore').sort((a,b)=>a.name.localeCompare(b.name)), [{name:'dsh-free-search',version:'0.8.5',bundle:true,enabled:true},{name:'dsh-mgr-workspace',version:'1.0.0',bundle:true,enabled:true},{name:'dsh-web-mobile',version:'3.0.5',bundle:true,enabled:false}]); const workspace = docker(['exec',id,'cat','/tmp/mobile-restore/profiles/web/pnpm-workspace.yaml']); assert.match(workspace,/autoInstallPeers: false/); assert.match(workspace,/nodeLinker: hoisted/); console.log('PASS offline backup restore without workspace/store/node_modules; disabled bundle preserved'); docker(['exec',id,'node','-e', "const fs=require('node:fs'); const dir='/tmp/mobile-legacy/profiles/web'; fs.mkdirSync(dir,{recursive:true}); fs.writeFileSync(dir+'/package.json',JSON.stringify({name:'dsh-profile-web',private:true,dependencies:{'dsh-web-mobile':'file:/usr/local/lib/dsh-mgr/dsh-web-mobile-3.0.5.tgz'},dsh:{profile:{bundles:['@deepseek-ai/dsh-base','@deepseek-ai/dsh-web-app','dsh-web-mobile']}}}));"]); docker(['exec','-e','DSH_HOME=/tmp/mobile-legacy','-e','HOME=/tmp/mobile-legacy','-e','XDG_DATA_HOME=/tmp/mobile-legacy/.local/share','-e','npm_config_ignore_scripts=true',id,'bash','-ec','node /usr/local/lib/dsh-mgr/init-profile.mjs; dsh plugin --profile web install --ignore-scripts']); assert.deepEqual(inventory('/tmp/mobile-legacy'),[{name:'dsh-web-mobile',version:'3.0.5',bundle:true,enabled:true}]); console.log('PASS recipe4 mobile-only manifest restore; no free-search injection'); docker(['exec', id, 'node', '-e', "require('node:fs').writeFileSync('/workspace/normal','preserved')"]); assert.equal(rpc('workspace/delete', {request:{workspaceId:initial.value.workspace.workspaceId}}).ok, true); assert.equal(rpc('workspace/initializeDefault').value, undefined); assert.equal(docker(['exec', id, 'cat', '/workspace/normal']), 'preserved'); assert.equal(rpc('workspace/create', {request:{path:'/workspace'}}).value.created, true); assert.equal(rpc('workspace/initializeDefault').value, undefined); console.log('PASS deleted default stays deleted; files preserved; explicit registration remains available'); } finally {docker(['rm','-f',id]);console.log('Removed dedicated smoke container (tmpfs data only)');}