Caterium v17.6.3 — fix developer gate and SaaS settings

Fix platform developer gate during workspace loading and prevent SaaS click events from reaching Developer Console as DOM buttons. Add v17.6.3 regression tests and PWA cache bump.
This commit is contained in:
pavlov346346-source 2026-09-07 19:31:00 +03:00 committed by GitHub
parent 5b2926c36b
commit ce6fa974aa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 122 additions and 16 deletions

View File

@ -1,10 +1,10 @@
{ {
"name": "caterium-app", "name": "caterium-app",
"private": true, "private": true,
"version": "17.6.2", "version": "17.6.3",
"type": "module", "type": "module",
"scripts": { "scripts": {
"check:syntax": "node --check public/app-runtime.js && node --check public/service-worker.js && node --check public/legacy/bootstrap.js && node --check public/core/sun-safe.js && node --check public/core/performance.js && node --check public/core/ops-ux-v1762.js && node --check public/core/pdf-engine.js", "check:syntax": "node --check public/app-runtime.js && node --check public/service-worker.js && node --check public/legacy/bootstrap.js && node --check public/core/sun-safe.js && node --check public/core/performance.js && node --check public/core/hotfix-v1763.js && node --check public/core/ops-ux-v1762.js && node --check public/core/pdf-engine.js",
"test:static": "node tests/static-security.mjs", "test:static": "node tests/static-security.mjs",
"check:release": "node tests/release-check.mjs", "check:release": "node tests/release-check.mjs",
"check:deploy": "npm run check:syntax && npm run test:static && npm run check:release", "check:deploy": "npm run check:syntax && npm run test:static && npm run check:release",

View File

@ -0,0 +1,71 @@
(()=>{
'use strict';
if(window.SunHotfixV1763)return;
const VERSION='17.6.3';
const $=id=>document.getElementById(id);
const esc=value=>window.SunSafe?.escapeHTML?window.SunSafe.escapeHTML(String(value??'')):String(value??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
let checkingGate=false;
function patchDeveloperOpen(){
const dev=window.SunDeveloperV22;
if(!dev||dev.__sunV1763SafeOpen||typeof dev.open!=='function')return false;
const original=dev.open.bind(dev);
dev.open=(source=null)=>original(source instanceof HTMLElement?source:null);
Object.defineProperty(dev,'__sunV1763SafeOpen',{value:true,configurable:true});
return true;
}
async function platformAdmin(){
const dev=window.SunDeveloperV22,cloud=window.SunCloudV2;
if(!dev||!cloud?.getSession?.()?.user)return false;
if(dev.isPlatformAdmin?.()===true)return true;
try{return await dev.checkPlatformAdmin?.(true)===true}catch(_){return false}
}
async function enhanceDeveloperGate(){
if(checkingGate)return false;
const cloud=window.SunCloudV2,dev=window.SunDeveloperV22;
if(!cloud||!dev)return false;
const session=cloud.getSession?.(),workspace=cloud.getWorkspace?.();
if(!session?.user||workspace)return false;
checkingGate=true;
try{
if(!await platformAdmin())return false;
const gate=$('sunCloudAuthGateV3'),card=gate?.querySelector('.sun-cloud-auth-card');
if(!gate||!card)return false;
if(card.dataset.dev1763==='1')return true;
gate.dataset.saasEnhanced='1';
card.dataset.devEnhanced='1';
card.dataset.dev1763='1';
card.innerHTML=`<div class="sun-cloud-auth-brand"><img src="caterium-login-logo.png" alt="Caterium"><div><h2>Аккаунт разработчика</h2><div class="hint">${esc(session.user.email||'')}</div></div></div><p class="hint">Этот аккаунт управляет платформой и не обязан иметь собственную рабочую компанию.</p><div class="sun-cloud-auth-actions"><button class="primary" type="button" data-open-dev-v1763>Открыть кабинет разработчика</button><button class="outline" type="button" data-signout-v1763>Выйти</button></div><div class="sun-cloud-auth-error" id="sunDevGateStatusV1763"></div>`;
card.querySelector('[data-open-dev-v1763]')?.addEventListener('click',()=>{patchDeveloperOpen();dev.open?.();});
card.querySelector('[data-signout-v1763]')?.addEventListener('click',()=>cloud.signOut?.());
return true;
}finally{checkingGate=false;}
}
function interceptSaasAdmin(event){
const button=event.target?.closest?.('[data-saas-admin]');
if(!button)return;
const dev=window.SunDeveloperV22;
if(!dev)return;
event.preventDefault();
event.stopImmediatePropagation();
patchDeveloperOpen();
dev.open?.();
}
function maintain(){
patchDeveloperOpen();
enhanceDeveloperGate().catch(()=>{});
}
document.addEventListener('click',interceptSaasAdmin,true);
window.addEventListener('sun:cloud-permissions-changed',()=>setTimeout(maintain,0));
window.addEventListener('sun:cloud-state-applied',()=>setTimeout(maintain,0));
setTimeout(maintain,0);
setInterval(()=>{if(!document.hidden)maintain();},1000);
window.SunHotfixV1763={VERSION,patchDeveloperOpen,enhanceDeveloperGate};
})();

View File

@ -53,7 +53,7 @@
const input=e.target;if(!(input instanceof HTMLInputElement)||input.id!=='sunChatFilesV29'||guardedInputs.has(input)||typeof DataTransfer==='undefined')return; const input=e.target;if(!(input instanceof HTMLInputElement)||input.id!=='sunChatFilesV29'||guardedInputs.has(input)||typeof DataTransfer==='undefined')return;
e.preventDefault();e.stopImmediatePropagation();guardChatFiles(input).catch(err=>{console.error('[Caterium photo compression]',err);guardedInputs.add(input);input.dispatchEvent(new Event('change',{bubbles:true}));guardedInputs.delete(input);}); e.preventDefault();e.stopImmediatePropagation();guardChatFiles(input).catch(err=>{console.error('[Caterium photo compression]',err);guardedInputs.add(input);input.dispatchEvent(new Event('change',{bubbles:true}));guardedInputs.delete(input);});
},true); },true);
window.SunAttachmentGuard={VERSION:'17.6.2',MAX_FILE,TARGET,MAX_SIDE,compressImage,prepareAttachment}; window.SunAttachmentGuard={VERSION:'17.6.3',MAX_FILE,TARGET,MAX_SIDE,compressImage,prepareAttachment};
let memoryData=null,memoryAt=0,memoryLoading=false; let memoryData=null,memoryAt=0,memoryLoading=false;
const esc=v=>window.SunSafe?.escapeHTML?window.SunSafe.escapeHTML(String(v??'')):String(v??''); const esc=v=>window.SunSafe?.escapeHTML?window.SunSafe.escapeHTML(String(v??'')):String(v??'');
@ -65,23 +65,27 @@
async function enhanceDeveloperMemory(){ async function enhanceDeveloperMemory(){
const view=document.getElementById('sun-developer-console-v22');if(!view?.classList.contains('on'))return; const view=document.getElementById('sun-developer-console-v22');if(!view?.classList.contains('on'))return;
const body=document.getElementById('sunDevBody');if(!body)return; const body=document.getElementById('sunDevBody');if(!body)return;
const version=document.getElementById('sunDevReleaseVersion');if(version)version.textContent='17.6.2'; const version=document.getElementById('sunDevReleaseVersion');if(version)version.textContent='17.6.3';
const d=await loadMemory();if(!d||!view.classList.contains('on'))return; const d=await loadMemory();if(!d||!view.classList.contains('on'))return;
let box=document.getElementById('sunDevMemoryV1761');if(!box){box=document.createElement('div');box.id='sunDevMemoryV1761';box.className='sun-dev-grid';box.style.marginBottom='12px';body.prepend(box);} let box=document.getElementById('sunDevMemoryV1761');if(!box){box=document.createElement('div');box.id='sunDevMemoryV1761';box.className='sun-dev-grid';box.style.marginBottom='12px';body.prepend(box);}
box.innerHTML=`<div class="sun-dev-kpi"><small>Память сервера</small><b>${esc(d.server_size||d.database_size||'—')}</b></div><div class="sun-dev-kpi"><small>База PostgreSQL</small><b>${esc(d.database_size||'—')}</b></div><div class="sun-dev-kpi"><small>Файлы Storage</small><b>${esc(d.storage_size||'—')}</b></div><div class="sun-dev-kpi"><small>Объектов Storage</small><b>${Number(d.storage_objects||0)}</b></div>`; box.innerHTML=`<div class="sun-dev-kpi"><small>Память сервера</small><b>${esc(d.server_size||d.database_size||'—')}</b></div><div class="sun-dev-kpi"><small>База PostgreSQL</small><b>${esc(d.database_size||'—')}</b></div><div class="sun-dev-kpi"><small>Файлы Storage</small><b>${esc(d.storage_size||'—')}</b></div><div class="sun-dev-kpi"><small>Объектов Storage</small><b>${Number(d.storage_objects||0)}</b></div>`;
} }
function loadHotfix(){
if(window.SunHotfixV1763||document.getElementById('sunHotfixV1763Script'))return;
const script=document.createElement('script');script.id='sunHotfixV1763Script';script.src='core/hotfix-v1763.js?v=20260907-v17-6-3-developer-hotfix';script.async=true;script.onerror=()=>console.error('[Caterium] Не загрузился модуль hotfix-v1763.js');document.head.appendChild(script);
}
function loadOpsUX(){ function loadOpsUX(){
if(window.SunOpsUXV1762||document.getElementById('sunOpsUXV1762Script'))return; if(window.SunOpsUXV1762||document.getElementById('sunOpsUXV1762Script'))return;
const script=document.createElement('script');script.id='sunOpsUXV1762Script';script.src='core/ops-ux-v1762.js?v=20260907-v17-6-2-ops-ux';script.async=true;script.onerror=()=>console.error('[Caterium] Не загрузился модуль ops-ux-v1762.js');document.head.appendChild(script); const script=document.createElement('script');script.id='sunOpsUXV1762Script';script.src='core/ops-ux-v1762.js?v=20260907-v17-6-3-developer-hotfix';script.async=true;script.onerror=()=>console.error('[Caterium] Не загрузился модуль ops-ux-v1762.js');document.head.appendChild(script);
} }
const start=()=>{ const start=()=>{
loadOpsUX();scan(document); loadHotfix();loadOpsUX();scan(document);
const mo=new MutationObserver(records=>{records.forEach(r=>r.addedNodes.forEach(n=>{if(n.nodeType===1)scan(n)}));enhanceDeveloperMemory();}); const mo=new MutationObserver(records=>{records.forEach(r=>r.addedNodes.forEach(n=>{if(n.nodeType===1)scan(n)}));enhanceDeveloperMemory();});
mo.observe(document.documentElement,{childList:true,subtree:true}); mo.observe(document.documentElement,{childList:true,subtree:true});
document.addEventListener('click',e=>{if(e.target.closest('#sunDeveloperNavV22,[data-dev-tab],#sunDevRefresh'))setTimeout(enhanceDeveloperMemory,100)},true); document.addEventListener('click',e=>{if(e.target.closest('#sunDeveloperNavV22,[data-dev-tab],#sunDevRefresh'))setTimeout(enhanceDeveloperMemory,100)},true);
window.addEventListener('sun:cloud-state-applied',()=>setTimeout(enhanceDeveloperMemory,150)); window.addEventListener('sun:cloud-state-applied',()=>setTimeout(enhanceDeveloperMemory,150));
setInterval(()=>{if(document.getElementById('sun-developer-console-v22')?.classList.contains('on'))enhanceDeveloperMemory();},5000); setInterval(()=>{if(document.getElementById('sun-developer-console-v22')?.classList.contains('on'))enhanceDeveloperMemory();},5000);
window.SunPerformance={scanImages:()=>scan(document),refreshDeveloperMemory:enhanceDeveloperMemory,loadOpsUX,disconnect:()=>mo.disconnect()}; window.SunPerformance={scanImages:()=>scan(document),refreshDeveloperMemory:enhanceDeveloperMemory,loadHotfix,loadOpsUX,disconnect:()=>mo.disconnect()};
}; };
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',start,{once:true});else start(); if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',start,{once:true});else start();
})(); })();

View File

@ -1,8 +1,8 @@
const CACHE='sun-catering-pwa-v67-20260907-v17-6-2-ops-ux'; const CACHE='sun-catering-pwa-v68-20260907-v17-6-3-developer-hotfix';
const VERSION='20260907-v17-6-2-ops-ux'; const VERSION='20260907-v17-6-3-developer-hotfix';
const CORE=[ const CORE=[
'./','./index.html', './','./index.html',
`./core/sun-safe.js?v=${VERSION}`,`./core/performance.js?v=${VERSION}`,`./core/ops-ux-v1762.js?v=${VERSION}`,`./core/pdf-engine.js?v=${VERSION}`,`./legacy/bootstrap.js?v=${VERSION}`,`./app-runtime.js?v=${VERSION}`, `./core/sun-safe.js?v=${VERSION}`,`./core/performance.js?v=${VERSION}`,`./core/hotfix-v1763.js?v=${VERSION}`,`./core/ops-ux-v1762.js?v=${VERSION}`,`./core/pdf-engine.js?v=${VERSION}`,`./legacy/bootstrap.js?v=${VERSION}`,`./app-runtime.js?v=${VERSION}`,
'./offer-gallery/001.jpg','./offer-gallery/002.jpg', './offer-gallery/001.jpg','./offer-gallery/002.jpg',
'./catalog/001.jpg','./catalog/002.jpg','./catalog/003.jpg', './catalog/001.jpg','./catalog/002.jpg','./catalog/003.jpg',
'./sun-logo.png','./caterium-login-logo.png','./pwa-icon-192.png','./pwa-icon-512.png','./manifest.webmanifest', './sun-logo.png','./caterium-login-logo.png','./pwa-icon-192.png','./pwa-icon-512.png','./manifest.webmanifest',

View File

@ -65,6 +65,32 @@ test('v17.6.2 operations UX boots with menu and route features', async ({ page }
expect(checks.calendarMore).toBeTruthy(); expect(checks.calendarMore).toBeTruthy();
expect(await page.locator('header nav button', {hasText:'Меню'}).count()).toBeGreaterThan(0); expect(await page.locator('header nav button', {hasText:'Меню'}).count()).toBeGreaterThan(0);
}); });
test('v17.6.3 developer gate bypasses workspace loading and SaaS click is safe', async ({ page }) => {
await page.goto('/index.html', { waitUntil:'domcontentloaded' });
await page.evaluate(()=>{
document.body.innerHTML='<div id="sunCloudAuthGateV3"><div class="sun-cloud-auth-card"><h2>Загружаю рабочую базу</h2></div></div><button type="button" data-saas-admin>Управление SaaS</button>';
window.__devOpenArgs=[];
window.SunCloudV2={
getSession:()=>({user:{id:'dev',email:'developer@example.com'}}),
getWorkspace:()=>null,
status:()=>({membershipsLoading:true,membershipsLoaded:false}),
signOut:()=>{}
};
window.SunDeveloperV22={
open:arg=>{window.__devOpenArgs.push(arg===null?'null':arg?.constructor?.name||typeof arg)},
isPlatformAdmin:()=>true,
checkPlatformAdmin:async()=>true
};
});
const content=fs.readFileSync(path.join(process.cwd(),'public','core','hotfix-v1763.js'),'utf8');
await page.addScriptTag({content});
await page.waitForFunction(()=>document.querySelector('[data-open-dev-v1763]')&&Boolean(window.SunHotfixV1763),null,{timeout:5000});
expect(await page.locator('#sunCloudAuthGateV3 h2').textContent()).toBe('Аккаунт разработчика');
await page.locator('[data-saas-admin]').click();
await page.locator('[data-open-dev-v1763]').click();
const args=await page.evaluate(()=>window.__devOpenArgs);
expect(args).toEqual(['null','null']);
});
test('mobile body does not overflow viewport', async ({ page }, testInfo) => { test('mobile body does not overflow viewport', async ({ page }, testInfo) => {
test.skip(testInfo.project.name!=='mobile-390'); await page.goto('/index.html', { waitUntil:'domcontentloaded' }); await page.waitForTimeout(500); test.skip(testInfo.project.name!=='mobile-390'); await page.goto('/index.html', { waitUntil:'domcontentloaded' }); await page.waitForTimeout(500);
const dims=await page.evaluate(()=>({innerWidth,scrollWidth:document.documentElement.scrollWidth,bodyWidth:document.body.scrollWidth})); const dims=await page.evaluate(()=>({innerWidth,scrollWidth:document.documentElement.scrollWidth,bodyWidth:document.body.scrollWidth}));

View File

@ -3,16 +3,19 @@ import path from 'node:path';
const root=process.cwd(), pub=path.join(root,'public'); const root=process.cwd(), pub=path.join(root,'public');
const read=p=>fs.readFileSync(path.join(pub,p),'utf8'); const read=p=>fs.readFileSync(path.join(pub,p),'utf8');
let bad=0;const check=(v,m)=>{console.log(`${v?'OK':'FAIL'}: ${m}`);if(!v)bad++}; let bad=0;const check=(v,m)=>{console.log(`${v?'OK':'FAIL'}: ${m}`);if(!v)bad++};
const index=read('index.html'),runtime=read('app-runtime.js'),sw=read('service-worker.js'),css=read('core/stability-v1760.css'),performance=read('core/performance.js'),ops=read('core/ops-ux-v1762.js'); const index=read('index.html'),runtime=read('app-runtime.js'),sw=read('service-worker.js'),css=read('core/stability-v1760.css'),performance=read('core/performance.js'),ops=read('core/ops-ux-v1762.js'),hotfix=read('core/hotfix-v1763.js');
check(index.includes('core/stability-v1760.css'),'mobile stability stylesheet loaded'); check(index.includes('core/stability-v1760.css'),'mobile stability stylesheet loaded');
check(css.includes('overflow-x:hidden')&&css.includes('.cats'),'mobile overflow guard present'); check(css.includes('overflow-x:hidden')&&css.includes('.cats'),'mobile overflow guard present');
check(!index.includes('offer-gallery-data.js'),'blocking Base64 gallery absent'); check(!index.includes('offer-gallery-data.js'),'blocking Base64 gallery absent');
check((runtime.match(/\/Type \/Catalog/g)||[]).length===0,'runtime contains no PDF binary writer'); check((runtime.match(/\/Type \/Catalog/g)||[]).length===0,'runtime contains no PDF binary writer');
check(read('core/pdf-engine.js').includes('595.28')&&read('core/pdf-engine.js').includes('841.89'),'PDF engine uses A4 MediaBox'); check(read('core/pdf-engine.js').includes('595.28')&&read('core/pdf-engine.js').includes('841.89'),'PDF engine uses A4 MediaBox');
check([...index.matchAll(/@page\{([^}]*)\}/g)].every(m=>/size:A4/i.test(m[1])),'compact @page rules use A4'); check([...index.matchAll(/@page\{([^}]*)\}/g)].every(m=>/size:A4/i.test(m[1])),'compact @page rules use A4');
check(sw.includes('v17-6-2-ops-ux')&&sw.includes('ops-ux-v1762.js'),'service worker cache is v17.6.2'); check(sw.includes('v17-6-3-developer-hotfix')&&sw.includes('hotfix-v1763.js'),'service worker cache is v17.6.3');
check(performance.includes('SunAttachmentGuard')&&performance.includes('TARGET=2*1024*1024'),'chat photo auto-compression is versioned'); check(performance.includes('SunAttachmentGuard')&&performance.includes('TARGET=2*1024*1024'),'chat photo auto-compression is versioned');
check(performance.includes("rpc('sun_dev_dashboard')")&&performance.includes('server_size')&&performance.includes('storage_size'),'Developer Console server/storage counters are versioned'); check(performance.includes("rpc('sun_dev_dashboard')")&&performance.includes('server_size')&&performance.includes('storage_size'),'Developer Console server/storage counters are versioned');
check(performance.includes('hotfix-v1763.js')&&performance.includes('SunHotfixV1763'),'developer/SaaS hotfix is loaded');
check(hotfix.includes('patchDeveloperOpen')&&hotfix.includes('enhanceDeveloperGate')&&hotfix.includes('data-saas-admin'),'developer gate and SaaS click hotfix is versioned');
check(hotfix.includes('source instanceof HTMLElement')&&hotfix.includes('stopImmediatePropagation'),'SaaS event object cannot reach Developer Console as a nav button');
check(performance.includes('ops-ux-v1762.js'),'operations UX module is loaded by performance core'); check(performance.includes('ops-ux-v1762.js'),'operations UX module is loaded by performance core');
check(ops.includes('supportReadPermission')&&ops.includes('SUPPORT_POLL_MS=12000'),'developer support read-only live refresh is versioned'); check(ops.includes('supportReadPermission')&&ops.includes('SUPPORT_POLL_MS=12000'),'developer support read-only live refresh is versioned');
check(ops.includes('sun-menu-editor-v1762')&&ops.includes('Премиум боксы'),'full-page menu editor is versioned'); check(ops.includes('sun-menu-editor-v1762')&&ops.includes('Премиум боксы'),'full-page menu editor is versioned');
@ -20,5 +23,5 @@ check(ops.includes('showCalendarDay')&&ops.includes('.cal-more'),'calendar overf
check(ops.includes("ROUTE_BASE_KEY='sunRouteBaseV1'")&&ops.includes('saveRouteStart'),'configurable route origin is versioned'); check(ops.includes("ROUTE_BASE_KEY='sunRouteBaseV1'")&&ops.includes('saveRouteStart'),'configurable route origin is versioned');
check(ops.includes('showRouteOrder')&&ops.includes('data-route-open'),'route order modal is versioned'); check(ops.includes('showRouteOrder')&&ops.includes('data-route-open'),'route order modal is versioned');
check(fs.existsSync(path.join(root,'supabase/functions/caterium-create-employee/index.ts')),'employee Edge Function source is versioned'); check(fs.existsSync(path.join(root,'supabase/functions/caterium-create-employee/index.ts')),'employee Edge Function source is versioned');
check(!/sb_secret_[A-Za-z0-9_-]{20,}|service_role\s*[:=]\s*["'][A-Za-z0-9._-]{30,}/i.test(index+runtime+performance+ops),'no client secret-like token'); check(!/sb_secret_[A-Za-z0-9_-]{20,}|service_role\s*[:=]\s*["'][A-Za-z0-9._-]{30,}/i.test(index+runtime+performance+ops+hotfix),'no client secret-like token');
if(bad)process.exit(1); if(bad)process.exit(1);

View File

@ -6,7 +6,7 @@ const readPub=p=>fs.readFileSync(path.join(pub,p),'utf8');
const readRoot=p=>fs.readFileSync(path.join(root,p),'utf8'); const readRoot=p=>fs.readFileSync(path.join(root,p),'utf8');
const fail=m=>{console.error('FAIL:',m);process.exitCode=1}; const fail=m=>{console.error('FAIL:',m);process.exitCode=1};
const ok=m=>console.log('OK:',m); const ok=m=>console.log('OK:',m);
const html=readPub('index.html'),legacy=readPub('legacy/bootstrap.js'),runtime=readPub('app-runtime.js'),safe=readPub('core/sun-safe.js'),sw=readPub('service-worker.js'),performance=readPub('core/performance.js'),ops=readPub('core/ops-ux-v1762.js'); const html=readPub('index.html'),legacy=readPub('legacy/bootstrap.js'),runtime=readPub('app-runtime.js'),safe=readPub('core/sun-safe.js'),sw=readPub('service-worker.js'),performance=readPub('core/performance.js'),ops=readPub('core/ops-ux-v1762.js'),hotfix=readPub('core/hotfix-v1763.js');
if(!html.includes('core/sun-safe.js'))fail('SunSafe must load before legacy modules');else ok('shared SunSafe loaded'); if(!html.includes('core/sun-safe.js'))fail('SunSafe must load before legacy modules');else ok('shared SunSafe loaded');
if(html.includes('offer-gallery-data.js')||fs.existsSync(path.join(pub,'offer-gallery-data.js')))fail('blocking offer-gallery-data.js still present');else ok('base64 gallery removed'); if(html.includes('offer-gallery-data.js')||fs.existsSync(path.join(pub,'offer-gallery-data.js')))fail('blocking offer-gallery-data.js still present');else ok('base64 gallery removed');
for(const raw of ['<span>${b.name}</span>','<span>${o.event}</span>','<span>${o.address||','value="${x[0]}"','value="${x[2]}"']) if(legacy.includes(raw)) fail(`legacy bootstrap contains raw HTML interpolation ${raw}`); for(const raw of ['<span>${b.name}</span>','<span>${o.event}</span>','<span>${o.address||','value="${x[0]}"','value="${x[2]}"']) if(legacy.includes(raw)) fail(`legacy bootstrap contains raw HTML interpolation ${raw}`);
@ -27,10 +27,12 @@ if(current!==113)fail(`current catalog photo count ${current}, expected 113`);el
if(legacyCount!==60)fail(`legacy catalog photo count ${legacyCount}, expected 60`);else ok('60 legacy catalog photos'); if(legacyCount!==60)fail(`legacy catalog photo count ${legacyCount}, expected 60`);else ok('60 legacy catalog photos');
const gallery=fs.readdirSync(path.join(pub,'offer-gallery')).filter(x=>/\.jpg$/i.test(x)); const gallery=fs.readdirSync(path.join(pub,'offer-gallery')).filter(x=>/\.jpg$/i.test(x));
if(gallery.length!==2)fail(`offer gallery contains ${gallery.length} jpg files, expected 2`);else ok('offer gallery trimmed'); if(gallery.length!==2)fail(`offer gallery contains ${gallery.length} jpg files, expected 2`);else ok('offer gallery trimmed');
if(!sw.includes('v17-6-2-ops-ux')||!sw.includes('ops-ux-v1762.js')||sw.includes('offer-gallery-data.js'))fail('service worker cache is stale');else ok('PWA cache updated'); if(!sw.includes('v17-6-3-developer-hotfix')||!sw.includes('hotfix-v1763.js')||sw.includes('offer-gallery-data.js'))fail('service worker cache is stale');else ok('PWA cache updated');
if(!performance.includes('SunAttachmentGuard')||!performance.includes('MAX_SIDE=2048'))fail('chat photo compression guard missing');else ok('chat photo compression guard present'); if(!performance.includes('SunAttachmentGuard')||!performance.includes('MAX_SIDE=2048'))fail('chat photo compression guard missing');else ok('chat photo compression guard present');
if(!performance.includes("rpc('sun_dev_dashboard')")||!performance.includes('storage_size')||!performance.includes('server_size'))fail('Developer Console memory counters missing');else ok('Developer Console memory counters present'); if(!performance.includes("rpc('sun_dev_dashboard')")||!performance.includes('storage_size')||!performance.includes('server_size'))fail('Developer Console memory counters missing');else ok('Developer Console memory counters present');
if(!performance.includes('hotfix-v1763.js')||!performance.includes('SunHotfixV1763'))fail('v17.6.3 hotfix loader missing');else ok('v17.6.3 hotfix loader present');
if(!performance.includes('ops-ux-v1762.js')||!performance.includes('SunOpsUXV1762'))fail('ops UX loader missing');else ok('ops UX loader present'); if(!performance.includes('ops-ux-v1762.js')||!performance.includes('SunOpsUXV1762'))fail('ops UX loader missing');else ok('ops UX loader present');
for(const marker of ['patchDeveloperOpen','enhanceDeveloperGate','data-saas-admin','stopImmediatePropagation','instanceof HTMLElement']) if(!hotfix.includes(marker))fail(`developer/SaaS hotfix marker missing: ${marker}`);else ok(`developer/SaaS hotfix marker: ${marker}`);
for(const marker of ['SUPPORT_POLL_MS=12000','supportReadPermission','sun-menu-editor-v1762','showCalendarDay',"ROUTE_BASE_KEY='sunRouteBaseV1'",'showRouteOrder','routeOpenYandex']) if(!ops.includes(marker)) fail(`ops UX marker missing: ${marker}`); else ok(`ops UX marker: ${marker}`); for(const marker of ['SUPPORT_POLL_MS=12000','supportReadPermission','sun-menu-editor-v1762','showCalendarDay',"ROUTE_BASE_KEY='sunRouteBaseV1'",'showRouteOrder','routeOpenYandex']) if(!ops.includes(marker)) fail(`ops UX marker missing: ${marker}`); else ok(`ops UX marker: ${marker}`);
if(/service_role\s*[:=]\s*['"][A-Za-z0-9._-]{20,}/i.test(ops)||/eyJ[a-zA-Z0-9_-]{30,}/.test(ops))fail('possible secret in ops UX module');else ok('ops UX module has no hard-coded secret'); if(/service_role\s*[:=]\s*['"][A-Za-z0-9._-]{20,}/i.test(ops+hotfix)||/eyJ[a-zA-Z0-9_-]{30,}/.test(ops+hotfix))fail('possible secret in ops/hotfix module');else ok('ops/hotfix modules have no hard-coded secret');
if(process.exitCode)process.exit(process.exitCode); if(process.exitCode)process.exit(process.exitCode);