Caterium v17.7.1 - data layer adoption
This commit is contained in:
parent
8391f826fe
commit
b6ec42b2e2
31
.github/workflows/release-v17-7-1.yml
vendored
31
.github/workflows/release-v17-7-1.yml
vendored
@ -1,31 +0,0 @@
|
||||
name: Caterium v17.7.1 data layer adoption
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- '.github/workflows/release-v17-7-1.yml'
|
||||
permissions:
|
||||
contents: write
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
- name: Apply patch
|
||||
run: python ops/release_v1771.py
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
- name: Validate release
|
||||
run: npm run check:deploy
|
||||
- name: Commit release
|
||||
run: |
|
||||
rm -f .github/workflows/release-v17-7-1.yml ops/release_v1771.py
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git add -A
|
||||
git commit -m "Caterium v17.7.1 - data layer adoption"
|
||||
git push
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"app": "Caterium",
|
||||
"version": "v17.7.0",
|
||||
"version": "v17.7.1",
|
||||
"channel": "production",
|
||||
"schema": 17,
|
||||
"legacyStateKept": true,
|
||||
@ -11,7 +11,7 @@
|
||||
"serverReady": true,
|
||||
"workspaceAutoDiscovery": true,
|
||||
"invitesTemporarilyDisabled": false,
|
||||
"pwaCache": "v75-20260908-v17-7-0-architecture-foundation",
|
||||
"pwaCache": "v76-20260909-v17-7-1-data-layer-adoption",
|
||||
"fullOfferDescriptions": true,
|
||||
"dynamicOfferRows": true,
|
||||
"pdfOfferDescriptionFix": true,
|
||||
@ -108,7 +108,7 @@
|
||||
"catalogCompositionTildaEndpoint": "getproduct",
|
||||
"catalogCompositionPremiumForceRefresh": true,
|
||||
"catalogCompositionCacheRequiresPremium": true,
|
||||
"notes": "Architecture foundation: explicit data layer, server-side order automation with legacy/cloud compatibility, telemetry hygiene, current stability version, regression checks.",
|
||||
"notes": "Data layer adoption: base order save/status/delete and catalog save/delete paths now write through Caterium Data Layer with legacy local persistence retained as a fallback.",
|
||||
"catalogPhotoSources": 113,
|
||||
"catalogPhotosStoredInCatalog": true,
|
||||
"catalogLegacyPhotosInCatalog": 60,
|
||||
@ -246,7 +246,7 @@
|
||||
"signupTrial": "14-day Full",
|
||||
"developerMfaInputSelectorFixed": true,
|
||||
"developerMfaRepeatedValidationToastsFixed": true,
|
||||
"release": "20260908-v17-7-0-architecture-foundation",
|
||||
"release": "20260909-v17-7-1-data-layer-adoption",
|
||||
"registrationFlow": "email-password-confirm-company-auto-login",
|
||||
"emailConfirmationRequired": false,
|
||||
"employeeInviteLinks": false,
|
||||
@ -327,7 +327,7 @@
|
||||
"cloudConflictMaxRetries": 4,
|
||||
"errorLogDedupMinutes": 5,
|
||||
"networkFailureBackoff": true,
|
||||
"stabilityLoggerVersion": "17.7.0",
|
||||
"stabilityLoggerVersion": "17.7.1",
|
||||
"developerAdminCheckTtlMs": 30000,
|
||||
"supportRefreshLightweight": true,
|
||||
"supportRefreshIntervalMs": 20000,
|
||||
@ -360,12 +360,15 @@
|
||||
"offerTwoCustomGalleryPhotos": true,
|
||||
"offerWorkspaceModule": "core/offer-workspace-v1769.js",
|
||||
"architectureFoundation": true,
|
||||
"dataLayer": "core/data-layer-v1770.js",
|
||||
"dataLayer": "core/data-layer-v1771.js",
|
||||
"serverOrderAutomation": true,
|
||||
"serverOrderAutomationCron": "every minute",
|
||||
"serverOrderTimezoneColumn": true,
|
||||
"browserOrderAutomationCloudDisabled": true,
|
||||
"errorTelemetryRejectsLocalFile": true,
|
||||
"errorTelemetryServerDedupMinutes": 5,
|
||||
"errorTelemetryRetentionDays": 30
|
||||
"errorTelemetryRetentionDays": 30,
|
||||
"dataLayerPhase": 2,
|
||||
"dataLayerLegacyOrderWrites": true,
|
||||
"dataLayerLegacyCatalogWrites": true
|
||||
}
|
||||
|
||||
10
docs/releases/V17.7.1-CHANGES.txt
Normal file
10
docs/releases/V17.7.1-CHANGES.txt
Normal file
@ -0,0 +1,10 @@
|
||||
Caterium v17.7.1 — Data Layer Adoption
|
||||
Date: 2026-09-09
|
||||
|
||||
1. Base order create/update, status and delete writes now go through Caterium Data Layer.
|
||||
2. Base catalog save/delete writes now go through the same data layer.
|
||||
3. Added catalog repository list/get/replace/upsert/remove operations.
|
||||
4. Legacy persist() remains as a compatibility fallback.
|
||||
5. CateriumDataV1770 remains as a compatibility alias; CateriumDataV1771 is current.
|
||||
6. Server order automation remains compatible through CateriumData.
|
||||
7. PWA cache/versioning and stability telemetry advance to v17.7.1.
|
||||
@ -1,232 +0,0 @@
|
||||
from pathlib import Path
|
||||
import json
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
OLD_RELEASE = '20260908-v17-7-0-architecture-foundation'
|
||||
NEW_RELEASE = '20260909-v17-7-1-data-layer-adoption'
|
||||
OLD_CACHE = 'v75-20260908-v17-7-0-architecture-foundation'
|
||||
NEW_CACHE = 'v76-20260909-v17-7-1-data-layer-adoption'
|
||||
|
||||
|
||||
def read(path):
|
||||
return (ROOT / path).read_text(encoding='utf-8')
|
||||
|
||||
|
||||
def write(path, text):
|
||||
p = ROOT / path
|
||||
p.parent.mkdir(parents=True, exist_ok=True)
|
||||
p.write_text(text, encoding='utf-8')
|
||||
|
||||
|
||||
def replace_once(path, old, new):
|
||||
text = read(path)
|
||||
if old not in text:
|
||||
raise SystemExit(f'marker not found in {path}: {old[:100]}')
|
||||
write(path, text.replace(old, new, 1))
|
||||
|
||||
|
||||
if json.loads(read('package.json')).get('version') == '17.7.1':
|
||||
print('v17.7.1 already applied')
|
||||
raise SystemExit(0)
|
||||
|
||||
DATA_LAYER = r'''(()=>{
|
||||
'use strict';
|
||||
if(window.CateriumDataV1771)return;
|
||||
|
||||
const VERSION='17.7.1';
|
||||
const RELEASE='20260909-v17-7-1-data-layer-adoption';
|
||||
const listeners=new Map();
|
||||
const clone=value=>{try{return structuredClone(value)}catch(_){return JSON.parse(JSON.stringify(value))}};
|
||||
const safeJson=(raw,fallback)=>{try{const value=JSON.parse(raw);return value==null?fallback:value}catch(_){return fallback}};
|
||||
const emit=(topic,payload)=>{for(const fn of listeners.get(topic)||[]){try{fn(payload)}catch(error){console.error('[Caterium Data]',error)}}};
|
||||
const subscribe=(topic,fn)=>{if(typeof fn!=='function')return()=>{};if(!listeners.has(topic))listeners.set(topic,new Set());listeners.get(topic).add(fn);return()=>listeners.get(topic)?.delete(fn)};
|
||||
|
||||
function runtimeOrders(){try{if(typeof orders!=='undefined'&&Array.isArray(orders))return orders}catch(_){}return null}
|
||||
function runtimeCatalog(){try{if(typeof boxes!=='undefined'&&Array.isArray(boxes))return boxes}catch(_){}return null}
|
||||
function runtimePersist(){try{return typeof persist==='function'?persist:null}catch(_){return null}}
|
||||
function cloud(){return window.SunCloudV2||null}
|
||||
function session(){try{return cloud()?.getSession?.()||null}catch(_){return null}}
|
||||
function workspace(){try{return cloud()?.getWorkspace?.()||null}catch(_){return null}}
|
||||
function supportMode(){try{return cloud()?.getSupportMode?.()||null}catch(_){return null}}
|
||||
function isSignedIn(){return Boolean(session()?.user)}
|
||||
function isSupportReadOnly(){return Boolean(supportMode()||cloud()?.isSupportMode?.())}
|
||||
function canWrite(permission='app.write'){
|
||||
if(isSupportReadOnly())return false;
|
||||
if(!isSignedIn())return true;
|
||||
try{const c=cloud();if(typeof c?.hasPermission==='function')return c.hasPermission(permission)||c.hasPermission('app.write')}catch(_){}
|
||||
return true;
|
||||
}
|
||||
|
||||
const storage={
|
||||
read(key,fallback=null){const raw=localStorage.getItem(String(key));if(raw==null)return fallback;return safeJson(raw,fallback)},
|
||||
write(key,value,{silent=false}={}){localStorage.setItem(String(key),JSON.stringify(value));if(!silent)emit(`storage:${key}`,clone(value));return value},
|
||||
remove(key){localStorage.removeItem(String(key));emit(`storage:${key}`,null)},
|
||||
raw(key){return localStorage.getItem(String(key))}
|
||||
};
|
||||
|
||||
function persistDomain(key,list,persistLocal){
|
||||
if(!persistLocal){storage.write(key,list,{silent:true});return}
|
||||
const p=runtimePersist();
|
||||
if(p){try{p();return}catch(error){console.warn('[Caterium Data] persist fallback',error)}}
|
||||
storage.write(key,list,{silent:true});
|
||||
}
|
||||
|
||||
function getOrders(){const live=runtimeOrders();return clone(live||storage.read('sunOrders',[])||[])}
|
||||
function replaceOrders(next,{persistLocal=true,reason='replace'}={}){
|
||||
const list=Array.isArray(next)?clone(next):[],live=runtimeOrders();
|
||||
if(live){live.length=0;live.push(...clone(list))}
|
||||
persistDomain('sunOrders',list,persistLocal);emit('orders',{reason,orders:clone(list)});return list;
|
||||
}
|
||||
function getOrder(id){return getOrders().find(o=>String(o?.id)===String(id))||null}
|
||||
function updateOrder(id,updater,{persistLocal=true,reason='update'}={}){
|
||||
const list=getOrders(),index=list.findIndex(o=>String(o?.id)===String(id));if(index<0)return null;
|
||||
const current=clone(list[index]),next=typeof updater==='function'?updater(current):{...current,...clone(updater||{})};if(!next)return null;
|
||||
list[index]=next;replaceOrders(list,{persistLocal,reason});return clone(next);
|
||||
}
|
||||
function applyServerOrders(changed,{reason='server'}={}){
|
||||
const patches=Array.isArray(changed)?changed:[];if(!patches.length)return {changed:0,ids:[]};
|
||||
const list=getOrders(),map=new Map(list.map((o,i)=>[String(o?.id),i])),ids=[];
|
||||
for(const patch of patches){const id=String(patch?.id??'');if(!id)continue;const index=map.get(id);if(index==null){list.push(clone(patch));map.set(id,list.length-1)}else list[index]=clone(patch);ids.push(id)}
|
||||
replaceOrders(list,{persistLocal:false,reason});
|
||||
try{window.render?.();window.renderOrders?.();window.renderStats?.();window.renderClients?.()}catch(_){}
|
||||
try{window.dispatchEvent(new CustomEvent('caterium:data-orders-applied',{detail:{ids,reason}}))}catch(_){}
|
||||
return {changed:ids.length,ids};
|
||||
}
|
||||
|
||||
function getCatalog(){const live=runtimeCatalog();return clone(live||storage.read('sunBoxes',[])||[])}
|
||||
function replaceCatalog(next,{persistLocal=true,reason='replace'}={}){
|
||||
const list=Array.isArray(next)?clone(next):[],live=runtimeCatalog();
|
||||
if(live){live.length=0;live.push(...clone(list))}
|
||||
persistDomain('sunBoxes',list,persistLocal);emit('catalog',{reason,items:clone(list)});return list;
|
||||
}
|
||||
function getCatalogItem(id){return getCatalog().find(x=>String(x?.id)===String(id))||null}
|
||||
function upsertCatalogItem(item,{persistLocal=true,reason='upsert'}={}){
|
||||
if(!item?.id)return null;const list=getCatalog(),index=list.findIndex(x=>String(x?.id)===String(item.id)),next=clone(item);
|
||||
if(index<0)list.push(next);else list[index]=next;replaceCatalog(list,{persistLocal,reason});return clone(next);
|
||||
}
|
||||
function removeCatalogItem(id,{persistLocal=true,reason='remove'}={}){
|
||||
const list=getCatalog(),next=list.filter(x=>String(x?.id)!==String(id));if(next.length===list.length)return false;
|
||||
replaceCatalog(next,{persistLocal,reason});return true;
|
||||
}
|
||||
|
||||
const ordersRepo={list:getOrders,get:getOrder,replace:replaceOrders,update:updateOrder,applyServerChanges:applyServerOrders};
|
||||
const catalogRepo={list:getCatalog,get:getCatalogItem,replace:replaceCatalog,upsert:upsertCatalogItem,remove:removeCatalogItem};
|
||||
const diagnostics={
|
||||
snapshot(){return {version:VERSION,workspaceId:workspace()?.id||null,signedIn:isSignedIn(),supportReadOnly:isSupportReadOnly(),orderCount:getOrders().length,catalogCount:getCatalog().length,listenerTopics:listeners.size}},
|
||||
measure(name,fn){const started=performance.now();return Promise.resolve().then(fn).finally(()=>emit('metric',{name,durationMs:Math.round((performance.now()-started)*10)/10,at:new Date().toISOString()}))}
|
||||
};
|
||||
|
||||
const api={VERSION,RELEASE,storage,orders:ordersRepo,catalog:catalogRepo,subscribe,emit,cloud,session,workspace,isSignedIn,isSupportReadOnly,canWrite,diagnostics};
|
||||
window.CateriumDataV1771=api;
|
||||
window.CateriumDataV1770=api;
|
||||
window.CateriumData=api;
|
||||
try{window.dispatchEvent(new CustomEvent('caterium:data-ready',{detail:{version:VERSION,phase:2}}))}catch(_){}
|
||||
})();
|
||||
'''
|
||||
write('public/core/data-layer-v1771.js', DATA_LAYER)
|
||||
|
||||
legacy = read('public/legacy/bootstrap.js')
|
||||
old = "function persist(){localStorage.sunBoxes=JSON.stringify(boxes);localStorage.sunOrders=JSON.stringify(orders)}function show(id,b)"
|
||||
new = "function persist(){localStorage.sunBoxes=JSON.stringify(boxes);localStorage.sunOrders=JSON.stringify(orders)}function dataLayer(){return window.CateriumDataV1771||window.CateriumData||window.CateriumDataV1770||null}function persistOrders(reason){let d=dataLayer();if(d?.orders?.replace){d.orders.replace(orders,{persistLocal:true,reason:reason||'legacy.orders'});return}persist()}function persistCatalog(reason){let d=dataLayer();if(d?.catalog?.replace){d.catalog.replace(boxes,{persistLocal:true,reason:reason||'legacy.catalog'});return}persist()}function show(id,b)"
|
||||
if old not in legacy:
|
||||
raise SystemExit('legacy persist marker missing')
|
||||
legacy = legacy.replace(old, new, 1)
|
||||
for a, b in [
|
||||
("persist();deleteOrderBtn.style.display='inline-block';renderOrders();updateOrderSummary();alert('Заказ сохранён.');", "persistOrders('order.save');deleteOrderBtn.style.display='inline-block';renderOrders();updateOrderSummary();alert('Заказ сохранён.');"),
|
||||
("function status(id,s){orders.find(x=>x.id==id).status=s;persist()}", "function status(id,s){orders.find(x=>x.id==id).status=s;persistOrders('order.status')}"),
|
||||
("orders=orders.filter(x=>x.id!=id);persist();renderOrders();", "orders=orders.filter(x=>x.id!=id);persistOrders('order.delete');renderOrders();"),
|
||||
("boxes[boxes.findIndex(x=>x.id==edited.id)]=edited;persist();closeModal('editor');", "boxes[boxes.findIndex(x=>x.id==edited.id)]=edited;persistCatalog('catalog.save');closeModal('editor');"),
|
||||
("boxes=boxes.filter(x=>x.id!=edited.id);persist();closeModal('editor');", "boxes=boxes.filter(x=>x.id!=edited.id);persistCatalog('catalog.delete');closeModal('editor');")
|
||||
]:
|
||||
if a not in legacy:
|
||||
raise SystemExit('legacy CRUD marker missing: ' + a[:80])
|
||||
legacy = legacy.replace(a, b, 1)
|
||||
write('public/legacy/bootstrap.js', legacy)
|
||||
|
||||
perf = read('public/core/performance.js')
|
||||
perf = perf.replace("const VERSION='17.7.0';\n const RELEASE='20260908-v17-7-0-architecture-foundation';", "const VERSION='17.7.1';\n const RELEASE='20260909-v17-7-1-data-layer-adoption';", 1)
|
||||
for a, b in [
|
||||
("window.CateriumDataV1770||document.getElementById('cateriumDataV1770Script')", "window.CateriumDataV1771||document.getElementById('cateriumDataV1771Script')"),
|
||||
("script.id='cateriumDataV1770Script'", "script.id='cateriumDataV1771Script'"),
|
||||
("core/data-layer-v1770.js?v=${RELEASE}", "core/data-layer-v1771.js?v=${RELEASE}"),
|
||||
("Не загрузился data-layer-v1770.js", "Не загрузился data-layer-v1771.js")
|
||||
]:
|
||||
if a not in perf:
|
||||
raise SystemExit('performance marker missing: ' + a)
|
||||
perf = perf.replace(a, b, 1)
|
||||
write('public/core/performance.js', perf)
|
||||
|
||||
replace_once('public/app-runtime.js', "const VERSION = '17.7.0';\n const RELEASE = 'v17.7.0 Architecture Foundation';", "const VERSION = '17.7.1';\n const RELEASE = 'v17.7.1 Data Layer Adoption';")
|
||||
|
||||
sw = read('public/service-worker.js').replace(OLD_CACHE, NEW_CACHE).replace(OLD_RELEASE, NEW_RELEASE).replace('data-layer-v1770.js', 'data-layer-v1771.js')
|
||||
write('public/service-worker.js', sw)
|
||||
write('public/index.html', read('public/index.html').replace(OLD_RELEASE, NEW_RELEASE))
|
||||
|
||||
pkg = json.loads(read('package.json'))
|
||||
pkg['version'] = '17.7.1'
|
||||
pkg['scripts']['check:syntax'] = pkg['scripts']['check:syntax'].replace('core/data-layer-v1770.js', 'core/data-layer-v1771.js')
|
||||
write('package.json', json.dumps(pkg, ensure_ascii=False, indent=2) + '\n')
|
||||
lock = json.loads(read('package-lock.json'))
|
||||
lock['version'] = '17.7.1'
|
||||
lock['packages']['']['version'] = '17.7.1'
|
||||
write('package-lock.json', json.dumps(lock, ensure_ascii=False, indent=2) + '\n')
|
||||
|
||||
manifest = json.loads(read('docs/release-manifest.json'))
|
||||
manifest.update({
|
||||
'version': 'v17.7.1', 'pwaCache': NEW_CACHE, 'release': NEW_RELEASE,
|
||||
'notes': 'Data layer adoption: base order save/status/delete and catalog save/delete paths now write through Caterium Data Layer with legacy local persistence retained as a fallback.',
|
||||
'stabilityLoggerVersion': '17.7.1', 'dataLayer': 'core/data-layer-v1771.js', 'dataLayerPhase': 2,
|
||||
'dataLayerLegacyOrderWrites': True, 'dataLayerLegacyCatalogWrites': True, 'legacyStateKept': True
|
||||
})
|
||||
write('docs/release-manifest.json', json.dumps(manifest, ensure_ascii=False, indent=2) + '\n')
|
||||
write('docs/releases/V17.7.1-CHANGES.txt', '''Caterium v17.7.1 — Data Layer Adoption\nDate: 2026-09-09\n\n1. Base order create/update, status and delete writes now go through Caterium Data Layer.\n2. Base catalog save/delete writes now go through the same data layer.\n3. Added catalog repository list/get/replace/upsert/remove operations.\n4. Legacy persist() remains as a compatibility fallback.\n5. CateriumDataV1770 remains as a compatibility alias; CateriumDataV1771 is current.\n6. Server order automation remains compatible through CateriumData.\n7. PWA cache/versioning and stability telemetry advance to v17.7.1.\n''')
|
||||
|
||||
static = read('tests/static-security.mjs')
|
||||
static = static.replace("dataLayer=readPub('core/data-layer-v1770.js')", "dataLayer=readPub('core/data-layer-v1771.js')")
|
||||
static = static.replace("v17-7-0-architecture-foundation')||!sw.includes('data-layer-v1770.js')", "v17-7-1-data-layer-adoption')||!sw.includes('data-layer-v1771.js')")
|
||||
static = static.replace("20260908-v17-7-0-architecture-foundation')", "20260909-v17-7-1-data-layer-adoption')", 1)
|
||||
static = static.replace("if(!performance.includes('loadDataLayer')||!performance.includes('loadServerAutomation'))fail('v17.7.0 foundation loaders missing');else ok('v17.7.0 foundation loaders present');", "if(!performance.includes('loadDataLayer')||!performance.includes('data-layer-v1771.js')||!performance.includes('loadServerAutomation'))fail('v17.7.1 data layer loaders missing');else ok('v17.7.1 data layer loaders present');")
|
||||
static = static.replace("if(!dataLayer.includes(\"const VERSION='17.7.0'\")||!dataLayer.includes('ordersRepo')||!dataLayer.includes('applyServerOrders'))fail('v17.7.0 data layer missing');else ok('v17.7.0 data layer present');", "if(!dataLayer.includes(\"const VERSION='17.7.1'\")||!dataLayer.includes('ordersRepo')||!dataLayer.includes('catalogRepo')||!dataLayer.includes('applyServerOrders')||!dataLayer.includes('CateriumDataV1770=api'))fail('v17.7.1 data layer missing');else ok('v17.7.1 data layer present');")
|
||||
static = static.replace("\"const VERSION = '17.7.0'\"", "\"const VERSION = '17.7.1'\"", 1)
|
||||
anchor = "if(!legacy.includes('sunEsc(')||!legacy.includes('sunAttr('))fail('legacy bootstrap is not using shared escaping');else ok('legacy bootstrap escapes text and attributes');"
|
||||
static = static.replace(anchor, anchor + "\nif(!legacy.includes(\"persistOrders('order.save')\")||!legacy.includes(\"persistOrders('order.status')\")||!legacy.includes(\"persistOrders('order.delete')\")||!legacy.includes(\"persistCatalog('catalog.save')\")||!legacy.includes(\"persistCatalog('catalog.delete')\"))fail('legacy CRUD is not routed through data layer');else ok('legacy CRUD routes through data layer');")
|
||||
write('tests/static-security.mjs', static)
|
||||
|
||||
release = read('tests/release-check.mjs')
|
||||
release = release.replace("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'),ux=read('core/ux-fixes-v1764.js'),classic=read('core/classic-offer-pdf-v1767.js'),developerUX=read('core/developer-console-v1768.js'),offerWorkspace=read('core/offer-workspace-v1769.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'),dataLayer=read('core/data-layer-v1771.js'),legacy=read('legacy/bootstrap.js'),ops=read('core/ops-ux-v1762.js'),hotfix=read('core/hotfix-v1763.js'),ux=read('core/ux-fixes-v1764.js'),classic=read('core/classic-offer-pdf-v1767.js'),developerUX=read('core/developer-console-v1768.js'),offerWorkspace=read('core/offer-workspace-v1769.js');")
|
||||
release = release.replace("v75-20260908-v17-7-0-architecture-foundation", "v76-20260909-v17-7-1-data-layer-adoption")
|
||||
release = release.replace("data-layer-v1770.js", "data-layer-v1771.js")
|
||||
release = release.replace("20260908-v17-7-0-architecture-foundation", "20260909-v17-7-1-data-layer-adoption")
|
||||
release = release.replace("['17.6.2','17.6.3','17.6.4','17.6.5','17.6.6','17.6.7','17.6.8','17.6.9','17.7.0']", "['17.6.2','17.6.3','17.6.4','17.6.5','17.6.6','17.6.7','17.6.8','17.6.9','17.7.0','17.7.1']")
|
||||
release = release.replace("release notes exist through v17.7.0", "release notes exist through v17.7.1")
|
||||
release = release.replace("const VERSION = '17.7.0'", "const VERSION = '17.7.1'")
|
||||
release = release.replace("pkg.version==='17.7.0'", "pkg.version==='17.7.1'")
|
||||
release = release.replace("package version is v17.7.0", "package version is v17.7.1")
|
||||
release = release.replace("v17.7.0 Architecture Foundation", "v17.7.1 Data Layer Adoption")
|
||||
release = release.replace("stability logger reports v17.7.0", "stability logger reports v17.7.1")
|
||||
extra = "\ncheck(dataLayer.includes(\"const VERSION='17.7.1'\")&&dataLayer.includes('catalogRepo')&&dataLayer.includes('CateriumDataV1770=api'),'v17.7.1 data layer exposes order/catalog repositories with compatibility alias');\ncheck(legacy.includes(\"persistOrders('order.save')\")&&legacy.includes(\"persistOrders('order.status')\")&&legacy.includes(\"persistOrders('order.delete')\")&&legacy.includes(\"persistCatalog('catalog.save')\")&&legacy.includes(\"persistCatalog('catalog.delete')\"),'base legacy CRUD routes through data layer');\ncheck(releaseManifest.dataLayerPhase===2&&releaseManifest.dataLayerLegacyOrderWrites===true&&releaseManifest.dataLayerLegacyCatalogWrites===true,'release manifest records data layer phase 2');\n"
|
||||
release = release.replace("if(bad)process.exit(1);", extra + "if(bad)process.exit(1);")
|
||||
write('tests/release-check.mjs', release)
|
||||
|
||||
app = read('tests/app.spec.mjs').replace("expect(result.version).toBe('17.7.0');", "expect(result.version).toBe('17.7.1');", 1).replace("const VERSION = '17.7.0'", "const VERSION = '17.7.1'", 1)
|
||||
smoke = r'''
|
||||
|
||||
test('v17.7.1 data layer serves orders and catalog with v17.7.0 compatibility alias', async ({ page }) => {
|
||||
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
|
||||
await page.waitForFunction(()=>Boolean(window.CateriumDataV1771),null,{timeout:5000});
|
||||
const result=await page.evaluate(()=>{
|
||||
const d=window.CateriumDataV1771,beforeOrders=d.orders.list(),beforeCatalog=d.catalog.list();
|
||||
d.orders.replace([...beforeOrders,{id:'qa-v1771',event:'QA',lines:[]}],{persistLocal:false,reason:'qa-order'});
|
||||
d.catalog.replace([...beforeCatalog,{id:'qa-v1771',name:'QA item',price:0,ingredients:[]}],{persistLocal:false,reason:'qa-catalog'});
|
||||
const hasOrder=Boolean(d.orders.get('qa-v1771')),hasCatalog=Boolean(d.catalog.get('qa-v1771'));
|
||||
d.orders.replace(beforeOrders,{persistLocal:false,reason:'qa-restore'});d.catalog.replace(beforeCatalog,{persistLocal:false,reason:'qa-restore'});
|
||||
return {version:d.VERSION,alias:window.CateriumDataV1770===d,hasOrder,hasCatalog};
|
||||
});
|
||||
expect(result).toEqual({version:'17.7.1',alias:true,hasOrder:true,hasCatalog:true});
|
||||
});
|
||||
'''
|
||||
if "v17.7.1 data layer serves orders and catalog" not in app:
|
||||
app += smoke
|
||||
write('tests/app.spec.mjs', app)
|
||||
|
||||
print('Applied v17.7.1 data layer adoption patch')
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "caterium-app",
|
||||
"version": "17.7.0",
|
||||
"version": "17.7.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "caterium-app",
|
||||
"version": "17.7.0",
|
||||
"version": "17.7.1",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.51.0",
|
||||
"http-server": "^14.1.1",
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "caterium-app",
|
||||
"private": true,
|
||||
"version": "17.7.0",
|
||||
"version": "17.7.1",
|
||||
"type": "module",
|
||||
"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/data-layer-v1770.js && node --check public/core/server-automation-v1770.js && node --check public/core/hotfix-v1763.js && node --check public/core/ops-ux-v1762.js && node --check public/core/ux-fixes-v1764.js && node --check public/core/pdf-engine.js && node --check public/core/classic-offer-pdf-v1767.js && node --check public/core/developer-console-v1768.js && node --check public/core/offer-workspace-v1769.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/data-layer-v1771.js && node --check public/core/server-automation-v1770.js && node --check public/core/hotfix-v1763.js && node --check public/core/ops-ux-v1762.js && node --check public/core/ux-fixes-v1764.js && node --check public/core/pdf-engine.js && node --check public/core/classic-offer-pdf-v1767.js && node --check public/core/developer-console-v1768.js && node --check public/core/offer-workspace-v1769.js",
|
||||
"test:static": "node tests/static-security.mjs",
|
||||
"check:release": "node tests/release-check.mjs",
|
||||
"check:deploy": "npm run check:syntax && npm run test:static && npm run check:release",
|
||||
|
||||
@ -4221,8 +4221,8 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс
|
||||
'use strict';
|
||||
if (window.SunStabilityV17) return;
|
||||
|
||||
const VERSION = '17.7.0';
|
||||
const RELEASE = 'v17.7.0 Architecture Foundation';
|
||||
const VERSION = '17.7.1';
|
||||
const RELEASE = 'v17.7.1 Data Layer Adoption';
|
||||
const DB_NAME = 'SunStabilityV17';
|
||||
const DB_VERSION = 1;
|
||||
const DAILY_KEY = 'sunV17DailyBackup';
|
||||
|
||||
93
public/core/data-layer-v1771.js
Normal file
93
public/core/data-layer-v1771.js
Normal file
@ -0,0 +1,93 @@
|
||||
(()=>{
|
||||
'use strict';
|
||||
if(window.CateriumDataV1771)return;
|
||||
|
||||
const VERSION='17.7.1';
|
||||
const RELEASE='20260909-v17-7-1-data-layer-adoption';
|
||||
const listeners=new Map();
|
||||
const clone=value=>{try{return structuredClone(value)}catch(_){return JSON.parse(JSON.stringify(value))}};
|
||||
const safeJson=(raw,fallback)=>{try{const value=JSON.parse(raw);return value==null?fallback:value}catch(_){return fallback}};
|
||||
const emit=(topic,payload)=>{for(const fn of listeners.get(topic)||[]){try{fn(payload)}catch(error){console.error('[Caterium Data]',error)}}};
|
||||
const subscribe=(topic,fn)=>{if(typeof fn!=='function')return()=>{};if(!listeners.has(topic))listeners.set(topic,new Set());listeners.get(topic).add(fn);return()=>listeners.get(topic)?.delete(fn)};
|
||||
|
||||
function runtimeOrders(){try{if(typeof orders!=='undefined'&&Array.isArray(orders))return orders}catch(_){}return null}
|
||||
function runtimeCatalog(){try{if(typeof boxes!=='undefined'&&Array.isArray(boxes))return boxes}catch(_){}return null}
|
||||
function runtimePersist(){try{return typeof persist==='function'?persist:null}catch(_){return null}}
|
||||
function cloud(){return window.SunCloudV2||null}
|
||||
function session(){try{return cloud()?.getSession?.()||null}catch(_){return null}}
|
||||
function workspace(){try{return cloud()?.getWorkspace?.()||null}catch(_){return null}}
|
||||
function supportMode(){try{return cloud()?.getSupportMode?.()||null}catch(_){return null}}
|
||||
function isSignedIn(){return Boolean(session()?.user)}
|
||||
function isSupportReadOnly(){return Boolean(supportMode()||cloud()?.isSupportMode?.())}
|
||||
function canWrite(permission='app.write'){
|
||||
if(isSupportReadOnly())return false;
|
||||
if(!isSignedIn())return true;
|
||||
try{const c=cloud();if(typeof c?.hasPermission==='function')return c.hasPermission(permission)||c.hasPermission('app.write')}catch(_){}
|
||||
return true;
|
||||
}
|
||||
|
||||
const storage={
|
||||
read(key,fallback=null){const raw=localStorage.getItem(String(key));if(raw==null)return fallback;return safeJson(raw,fallback)},
|
||||
write(key,value,{silent=false}={}){localStorage.setItem(String(key),JSON.stringify(value));if(!silent)emit(`storage:${key}`,clone(value));return value},
|
||||
remove(key){localStorage.removeItem(String(key));emit(`storage:${key}`,null)},
|
||||
raw(key){return localStorage.getItem(String(key))}
|
||||
};
|
||||
|
||||
function persistDomain(key,list,persistLocal){
|
||||
if(!persistLocal){storage.write(key,list,{silent:true});return}
|
||||
const p=runtimePersist();
|
||||
if(p){try{p();return}catch(error){console.warn('[Caterium Data] persist fallback',error)}}
|
||||
storage.write(key,list,{silent:true});
|
||||
}
|
||||
|
||||
function getOrders(){const live=runtimeOrders();return clone(live||storage.read('sunOrders',[])||[])}
|
||||
function replaceOrders(next,{persistLocal=true,reason='replace'}={}){
|
||||
const list=Array.isArray(next)?clone(next):[],live=runtimeOrders();
|
||||
if(live){live.length=0;live.push(...clone(list))}
|
||||
persistDomain('sunOrders',list,persistLocal);emit('orders',{reason,orders:clone(list)});return list;
|
||||
}
|
||||
function getOrder(id){return getOrders().find(o=>String(o?.id)===String(id))||null}
|
||||
function updateOrder(id,updater,{persistLocal=true,reason='update'}={}){
|
||||
const list=getOrders(),index=list.findIndex(o=>String(o?.id)===String(id));if(index<0)return null;
|
||||
const current=clone(list[index]),next=typeof updater==='function'?updater(current):{...current,...clone(updater||{})};if(!next)return null;
|
||||
list[index]=next;replaceOrders(list,{persistLocal,reason});return clone(next);
|
||||
}
|
||||
function applyServerOrders(changed,{reason='server'}={}){
|
||||
const patches=Array.isArray(changed)?changed:[];if(!patches.length)return {changed:0,ids:[]};
|
||||
const list=getOrders(),map=new Map(list.map((o,i)=>[String(o?.id),i])),ids=[];
|
||||
for(const patch of patches){const id=String(patch?.id??'');if(!id)continue;const index=map.get(id);if(index==null){list.push(clone(patch));map.set(id,list.length-1)}else list[index]=clone(patch);ids.push(id)}
|
||||
replaceOrders(list,{persistLocal:false,reason});
|
||||
try{window.render?.();window.renderOrders?.();window.renderStats?.();window.renderClients?.()}catch(_){}
|
||||
try{window.dispatchEvent(new CustomEvent('caterium:data-orders-applied',{detail:{ids,reason}}))}catch(_){}
|
||||
return {changed:ids.length,ids};
|
||||
}
|
||||
|
||||
function getCatalog(){const live=runtimeCatalog();return clone(live||storage.read('sunBoxes',[])||[])}
|
||||
function replaceCatalog(next,{persistLocal=true,reason='replace'}={}){
|
||||
const list=Array.isArray(next)?clone(next):[],live=runtimeCatalog();
|
||||
if(live){live.length=0;live.push(...clone(list))}
|
||||
persistDomain('sunBoxes',list,persistLocal);emit('catalog',{reason,items:clone(list)});return list;
|
||||
}
|
||||
function getCatalogItem(id){return getCatalog().find(x=>String(x?.id)===String(id))||null}
|
||||
function upsertCatalogItem(item,{persistLocal=true,reason='upsert'}={}){
|
||||
if(!item?.id)return null;const list=getCatalog(),index=list.findIndex(x=>String(x?.id)===String(item.id)),next=clone(item);
|
||||
if(index<0)list.push(next);else list[index]=next;replaceCatalog(list,{persistLocal,reason});return clone(next);
|
||||
}
|
||||
function removeCatalogItem(id,{persistLocal=true,reason='remove'}={}){
|
||||
const list=getCatalog(),next=list.filter(x=>String(x?.id)!==String(id));if(next.length===list.length)return false;
|
||||
replaceCatalog(next,{persistLocal,reason});return true;
|
||||
}
|
||||
|
||||
const ordersRepo={list:getOrders,get:getOrder,replace:replaceOrders,update:updateOrder,applyServerChanges:applyServerOrders};
|
||||
const catalogRepo={list:getCatalog,get:getCatalogItem,replace:replaceCatalog,upsert:upsertCatalogItem,remove:removeCatalogItem};
|
||||
const diagnostics={
|
||||
snapshot(){return {version:VERSION,workspaceId:workspace()?.id||null,signedIn:isSignedIn(),supportReadOnly:isSupportReadOnly(),orderCount:getOrders().length,catalogCount:getCatalog().length,listenerTopics:listeners.size}},
|
||||
measure(name,fn){const started=performance.now();return Promise.resolve().then(fn).finally(()=>emit('metric',{name,durationMs:Math.round((performance.now()-started)*10)/10,at:new Date().toISOString()}))}
|
||||
};
|
||||
|
||||
const api={VERSION,RELEASE,storage,orders:ordersRepo,catalog:catalogRepo,subscribe,emit,cloud,session,workspace,isSignedIn,isSupportReadOnly,canWrite,diagnostics};
|
||||
window.CateriumDataV1771=api;
|
||||
window.CateriumDataV1770=api;
|
||||
window.CateriumData=api;
|
||||
try{window.dispatchEvent(new CustomEvent('caterium:data-ready',{detail:{version:VERSION,phase:2}}))}catch(_){}
|
||||
})();
|
||||
@ -1,7 +1,7 @@
|
||||
(()=>{
|
||||
'use strict';
|
||||
const VERSION='17.7.0';
|
||||
const RELEASE='20260908-v17-7-0-architecture-foundation';
|
||||
const VERSION='17.7.1';
|
||||
const RELEASE='20260909-v17-7-1-data-layer-adoption';
|
||||
const critical=img=>img.closest('header,.brand,#sunCloudAuthGate,.sun-auth-gate')||img.id==='sunLoginLogo'||img.classList.contains('sun-live-catalog-logo');
|
||||
const tune=img=>{
|
||||
if(!(img instanceof HTMLImageElement)||critical(img))return;
|
||||
@ -92,8 +92,8 @@
|
||||
function startMemoryTimer(){if(memoryTimer)return;memoryTimer=setInterval(()=>{if(!document.hidden&&developerVisible())enhanceDeveloperMemory({force:true}).catch(()=>{})},MEMORY_REFRESH_MS)}
|
||||
|
||||
function loadDataLayer(){
|
||||
if(window.CateriumDataV1770||document.getElementById('cateriumDataV1770Script'))return;
|
||||
const script=document.createElement('script');script.id='cateriumDataV1770Script';script.src=`core/data-layer-v1770.js?v=${RELEASE}`;script.async=false;script.onerror=()=>console.error('[Caterium] Не загрузился data-layer-v1770.js');document.head.appendChild(script);
|
||||
if(window.CateriumDataV1771||document.getElementById('cateriumDataV1771Script'))return;
|
||||
const script=document.createElement('script');script.id='cateriumDataV1771Script';script.src=`core/data-layer-v1771.js?v=${RELEASE}`;script.async=false;script.onerror=()=>console.error('[Caterium] Не загрузился data-layer-v1771.js');document.head.appendChild(script);
|
||||
}
|
||||
function loadServerAutomation(){
|
||||
if(window.CateriumServerAutomationV1770||document.getElementById('cateriumServerAutomationV1770Script'))return;
|
||||
|
||||
File diff suppressed because one or more lines are too long
2
public/legacy/bootstrap.js
vendored
2
public/legacy/bootstrap.js
vendored
File diff suppressed because one or more lines are too long
@ -1,8 +1,8 @@
|
||||
const CACHE='sun-catering-pwa-v75-20260908-v17-7-0-architecture-foundation';
|
||||
const VERSION='20260908-v17-7-0-architecture-foundation';
|
||||
const CACHE='sun-catering-pwa-v76-20260909-v17-7-1-data-layer-adoption';
|
||||
const VERSION='20260909-v17-7-1-data-layer-adoption';
|
||||
const CORE=[
|
||||
'./','./index.html',
|
||||
`./core/sun-safe.js?v=${VERSION}`,`./core/performance.js?v=${VERSION}`,`./core/data-layer-v1770.js?v=${VERSION}`,`./core/server-automation-v1770.js?v=${VERSION}`,`./core/hotfix-v1763.js?v=${VERSION}`,`./core/ops-ux-v1762.js?v=${VERSION}`,`./core/ux-fixes-v1764.js?v=${VERSION}`,`./core/pdf-engine.js?v=${VERSION}`,`./core/classic-offer-pdf-v1767.js?v=${VERSION}`,`./core/developer-console-v1768.js?v=${VERSION}`,`./core/offer-workspace-v1769.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/data-layer-v1771.js?v=${VERSION}`,`./core/server-automation-v1770.js?v=${VERSION}`,`./core/hotfix-v1763.js?v=${VERSION}`,`./core/ops-ux-v1762.js?v=${VERSION}`,`./core/ux-fixes-v1764.js?v=${VERSION}`,`./core/pdf-engine.js?v=${VERSION}`,`./core/classic-offer-pdf-v1767.js?v=${VERSION}`,`./core/developer-console-v1768.js?v=${VERSION}`,`./core/offer-workspace-v1769.js?v=${VERSION}`,`./legacy/bootstrap.js?v=${VERSION}`,`./app-runtime.js?v=${VERSION}`,
|
||||
'./offer-gallery/001.jpg','./offer-gallery/002.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',
|
||||
|
||||
@ -156,7 +156,7 @@ test('Developer Console memory refresh is bounded and does not react to its own
|
||||
const result=await page.evaluate(()=>({count:window.__rpcCount,box:document.querySelectorAll('#sunDevMemoryV1761').length,version:document.getElementById('sunDevReleaseVersion')?.textContent}));
|
||||
expect(result.count).toBeLessThanOrEqual(2);
|
||||
expect(result.box).toBe(1);
|
||||
expect(result.version).toBe('17.7.0');
|
||||
expect(result.version).toBe('17.7.1');
|
||||
});
|
||||
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);
|
||||
@ -172,7 +172,7 @@ test('v17.6.5 stays free of timer page errors during idle', async ({ page }, tes
|
||||
await page.waitForTimeout(5500);
|
||||
expect(errors).toEqual([]);
|
||||
const runtimeSource=fs.readFileSync(path.join(process.cwd(),'public','app-runtime.js'),'utf8');
|
||||
expect(runtimeSource).toContain("const VERSION = '17.7.0'");
|
||||
expect(runtimeSource).toContain("const VERSION = '17.7.1'");
|
||||
});
|
||||
|
||||
test('v17.6.5 support refresh uses lightweight cloud API', async ({ page }) => {
|
||||
@ -273,3 +273,18 @@ test('v17.7.0 server automation applies returned normalized orders through data
|
||||
await page.waitForFunction(()=>Boolean(window.CateriumServerAutomationV1770));await page.evaluate(async()=>{window.__signed=true;await window.CateriumServerAutomationV1770.run({force:true})});
|
||||
expect(await page.evaluate(()=>window.__applied)).toEqual([{id:8,status:'Отдан заказчику'}]);
|
||||
});
|
||||
|
||||
|
||||
test('v17.7.1 data layer serves orders and catalog with v17.7.0 compatibility alias', async ({ page }) => {
|
||||
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
|
||||
await page.waitForFunction(()=>Boolean(window.CateriumDataV1771),null,{timeout:5000});
|
||||
const result=await page.evaluate(()=>{
|
||||
const d=window.CateriumDataV1771,beforeOrders=d.orders.list(),beforeCatalog=d.catalog.list();
|
||||
d.orders.replace([...beforeOrders,{id:'qa-v1771',event:'QA',lines:[]}],{persistLocal:false,reason:'qa-order'});
|
||||
d.catalog.replace([...beforeCatalog,{id:'qa-v1771',name:'QA item',price:0,ingredients:[]}],{persistLocal:false,reason:'qa-catalog'});
|
||||
const hasOrder=Boolean(d.orders.get('qa-v1771')),hasCatalog=Boolean(d.catalog.get('qa-v1771'));
|
||||
d.orders.replace(beforeOrders,{persistLocal:false,reason:'qa-restore'});d.catalog.replace(beforeCatalog,{persistLocal:false,reason:'qa-restore'});
|
||||
return {version:d.VERSION,alias:window.CateriumDataV1770===d,hasOrder,hasCatalog};
|
||||
});
|
||||
expect(result).toEqual({version:'17.7.1',alias:true,hasOrder:true,hasCatalog:true});
|
||||
});
|
||||
|
||||
@ -4,7 +4,7 @@ const root=process.cwd(), pub=path.join(root,'public');
|
||||
const read=p=>fs.readFileSync(path.join(pub,p),'utf8');
|
||||
const readRoot=p=>fs.readFileSync(path.join(root,p),'utf8');
|
||||
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'),hotfix=read('core/hotfix-v1763.js'),ux=read('core/ux-fixes-v1764.js'),classic=read('core/classic-offer-pdf-v1767.js'),developerUX=read('core/developer-console-v1768.js'),offerWorkspace=read('core/offer-workspace-v1769.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'),dataLayer=read('core/data-layer-v1771.js'),legacy=read('legacy/bootstrap.js'),ops=read('core/ops-ux-v1762.js'),hotfix=read('core/hotfix-v1763.js'),ux=read('core/ux-fixes-v1764.js'),classic=read('core/classic-offer-pdf-v1767.js'),developerUX=read('core/developer-console-v1768.js'),offerWorkspace=read('core/offer-workspace-v1769.js');
|
||||
const pkg=JSON.parse(readRoot('package.json'));
|
||||
const lock=JSON.parse(readRoot('package-lock.json'));
|
||||
const releaseManifest=JSON.parse(readRoot('docs/release-manifest.json'));
|
||||
@ -14,8 +14,8 @@ 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(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(sw.includes('v75-20260908-v17-7-0-architecture-foundation')&&sw.includes('data-layer-v1770.js')&&sw.includes('server-automation-v1770.js')&&sw.includes('offer-workspace-v1769.js'),'service worker cache is v17.7.0');
|
||||
check(index.includes('20260908-v17-7-0-architecture-foundation')&&index.includes('classic-offer-pdf-v1767.js')&&!index.includes('20260907-v17-6-0-stability-security'),'index cache-busting points to v17.7.0');
|
||||
check(sw.includes('v76-20260909-v17-7-1-data-layer-adoption')&&sw.includes('data-layer-v1771.js')&&sw.includes('server-automation-v1770.js')&&sw.includes('offer-workspace-v1769.js'),'service worker cache is v17.7.0');
|
||||
check(index.includes('20260909-v17-7-1-data-layer-adoption')&&index.includes('classic-offer-pdf-v1767.js')&&!index.includes('20260907-v17-6-0-stability-security'),'index cache-busting points to v17.7.0');
|
||||
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('MEMORY_REFRESH_MS=30000')&&performance.includes('MEMORY_TIMEOUT_MS=8000')&&performance.includes('memoryPromise'),'Developer Console memory refresh is bounded');
|
||||
@ -39,17 +39,17 @@ check(!/sb_secret_[A-Za-z0-9_-]{20,}|service_role\s*[:=]\s*["'][A-Za-z0-9._-]{30
|
||||
check(lock.version===pkg.version&&lock.packages?.['']?.version===pkg.version,'package.json and package-lock.json versions match');
|
||||
check(releaseManifest.version===`v${pkg.version}`,'release manifest version matches package.json');
|
||||
check(releaseManifest.channel==='production','release manifest channel is production');
|
||||
check(String(releaseManifest.pwaCache||'').includes('v75-20260908-v17-7-0-architecture-foundation'),'release manifest points to current PWA cache');
|
||||
check(['17.6.2','17.6.3','17.6.4','17.6.5','17.6.6','17.6.7','17.6.8','17.6.9','17.7.0'].every(v=>fs.existsSync(path.join(root,`docs/releases/V${v}-CHANGES.txt`))),'release notes exist through v17.7.0');
|
||||
check(String(releaseManifest.pwaCache||'').includes('v76-20260909-v17-7-1-data-layer-adoption'),'release manifest points to current PWA cache');
|
||||
check(['17.6.2','17.6.3','17.6.4','17.6.5','17.6.6','17.6.7','17.6.8','17.6.9','17.7.0','17.7.1'].every(v=>fs.existsSync(path.join(root,`docs/releases/V${v}-CHANGES.txt`))),'release notes exist through v17.7.1');
|
||||
check(runtime.includes('CLOUD_RPC_TIMEOUT_MS=12000')&&runtime.includes('CLOUD_CONFLICT_MAX_RETRIES=4')&&runtime.includes('retryCount'),'cloud sync has timeout and capped exponential conflict retries');
|
||||
check(runtime.includes("const VERSION = '17.7.0'")&&runtime.includes('ERROR_DEDUPE_MS=5*60*1000')&&runtime.includes('mirrorBusy=false')&&runtime.includes('backupBusy=false'),'stability logger uses current version, dedupe and single-flight guards');
|
||||
check(runtime.includes("const VERSION = '17.7.1'")&&runtime.includes('ERROR_DEDUPE_MS=5*60*1000')&&runtime.includes('mirrorBusy=false')&&runtime.includes('backupBusy=false'),'stability logger uses current version, dedupe and single-flight guards');
|
||||
check(runtime.includes('refreshSupportWorkspace')&&runtime.includes('sun_dev_support_snapshot'),'cloud exposes lightweight read-only support refresh');
|
||||
check(runtime.includes('DEV_ADMIN_TTL_MS=30000')&&hotfix.includes('checkPlatformAdmin?.(false)')&&hotfix.includes('},10000);'),'developer access checks are throttled');
|
||||
check(performance.includes('pendingImageRoots')&&performance.includes('queueImageScan')&&ux.includes('},5000);'),'background DOM maintenance is batched/throttled');
|
||||
check(runtime.includes('explicitTemplate')&&runtime.includes("OFFER_TEMPLATE_IDS.has(explicitTemplate)"),'per-order proposal template survives render and PDF');
|
||||
check(runtime.includes("else if(id==='midnight-glass')")&&runtime.includes("else if(id==='emerald-gold')")&&runtime.includes("if(id==='editorial-grid')"),'existing proposal layout sequences remain available');
|
||||
check(runtime.includes('data-template-mini')&&ux.includes('data-template-mini'),'global and per-offer selectors show structural PDF previews');
|
||||
check(pkg.version==='17.7.0','package version is v17.7.0');
|
||||
check(pkg.version==='17.7.1','package version is v17.7.1');
|
||||
check(classic.includes("const VERSION='17.6.7'")&&classic.includes('CLASSIC_IDS')&&classic.includes('ARCHIVE_IDS'),'classic proposal PDF module is v17.6.7');
|
||||
check(runtime.includes("'warm-sun'")&&runtime.includes("'bento-cards'")&&runtime.includes("'event-story'")&&runtime.includes("'personal-letter'")&&runtime.includes("'event-ticket'")&&runtime.includes("'solar-experience'"),'10 classic proposal designs are available');
|
||||
check(runtime.includes("'midnight-compact'")&&runtime.includes("'black-gold'")&&runtime.includes("'neon-emerald'"),'hidden archive proposal templates are restored');
|
||||
@ -64,11 +64,15 @@ check(offerWorkspace.includes("const VERSION='17.6.9'")&&offerWorkspace.includes
|
||||
check(offerWorkspace.includes('PDF и предпросмотр')&&offerWorkspace.includes('Оформление PDF')&&offerWorkspace.includes('data-offer-gallery-slot'),'offer workspace separates preview/editor/templates and exposes two gallery uploads');
|
||||
check(offerWorkspace.includes('SunClassicOfferPDFV1767')&&offerWorkspace.includes('finalGallery=galleryFor'),'custom gallery is injected into PDF renderer');
|
||||
check(releaseManifest.offerWorkspaceTabs===true&&releaseManifest.offerTemplatesSeparateTab===true&&releaseManifest.offerTwoCustomGalleryPhotos===true,'release manifest records offer workspace changes');
|
||||
check(pkg.version==='17.7.0','package version is v17.7.0');
|
||||
check(index.includes('20260908-v17-7-0-architecture-foundation'),'index cache bust is v17.7.0');
|
||||
check(sw.includes('v75-20260908-v17-7-0-architecture-foundation')&&sw.includes('data-layer-v1770.js')&&sw.includes('server-automation-v1770.js'),'PWA caches architecture foundation modules');
|
||||
check(fs.existsSync(path.join(root,'public/core/data-layer-v1770.js'))&&fs.existsSync(path.join(root,'public/core/server-automation-v1770.js')),'data layer and server automation modules exist');
|
||||
check(pkg.version==='17.7.1','package version is v17.7.1');
|
||||
check(index.includes('20260909-v17-7-1-data-layer-adoption'),'index cache bust is v17.7.0');
|
||||
check(sw.includes('v76-20260909-v17-7-1-data-layer-adoption')&&sw.includes('data-layer-v1771.js')&&sw.includes('server-automation-v1770.js'),'PWA caches architecture foundation modules');
|
||||
check(fs.existsSync(path.join(root,'public/core/data-layer-v1771.js'))&&fs.existsSync(path.join(root,'public/core/server-automation-v1770.js')),'data layer and server automation modules exist');
|
||||
check(ux.includes('CateriumServerAutomationV1770?.enabled'),'cloud browser auto completion is disabled when server automation is active');
|
||||
check(runtime.includes("const VERSION = '17.7.0'")&&runtime.includes("v17.7.0 Architecture Foundation"),'stability logger reports v17.7.0');
|
||||
check(runtime.includes("const VERSION = '17.7.1'")&&runtime.includes("v17.7.1 Data Layer Adoption"),'stability logger reports v17.7.1');
|
||||
check(fs.existsSync(path.join(root,'ops/sql/SUPABASE-V17.7.0-SERVER-ORDER-AUTOMATION.sql'))&&fs.existsSync(path.join(root,'ops/sql/SUPABASE-V17.7.0-ERROR-TELEMETRY-HYGIENE.sql')),'v17.7.0 server migrations are versioned');
|
||||
|
||||
check(dataLayer.includes("const VERSION='17.7.1'")&&dataLayer.includes('catalogRepo')&&dataLayer.includes('CateriumDataV1770=api'),'v17.7.1 data layer exposes order/catalog repositories with compatibility alias');
|
||||
check(legacy.includes("persistOrders('order.save')")&&legacy.includes("persistOrders('order.status')")&&legacy.includes("persistOrders('order.delete')")&&legacy.includes("persistCatalog('catalog.save')")&&legacy.includes("persistCatalog('catalog.delete')"),'base legacy CRUD routes through data layer');
|
||||
check(releaseManifest.dataLayerPhase===2&&releaseManifest.dataLayerLegacyOrderWrites===true&&releaseManifest.dataLayerLegacyCatalogWrites===true,'release manifest records data layer phase 2');
|
||||
if(bad)process.exit(1);
|
||||
|
||||
@ -6,11 +6,12 @@ const readPub=p=>fs.readFileSync(path.join(pub,p),'utf8');
|
||||
const readRoot=p=>fs.readFileSync(path.join(root,p),'utf8');
|
||||
const fail=m=>{console.error('FAIL:',m);process.exitCode=1};
|
||||
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'),dataLayer=readPub('core/data-layer-v1770.js'),serverAutomation=readPub('core/server-automation-v1770.js'),ops=readPub('core/ops-ux-v1762.js'),hotfix=readPub('core/hotfix-v1763.js'),ux=readPub('core/ux-fixes-v1764.js'),classic=readPub('core/classic-offer-pdf-v1767.js'),developerUX=readPub('core/developer-console-v1768.js'),offerWorkspace=readPub('core/offer-workspace-v1769.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'),dataLayer=readPub('core/data-layer-v1771.js'),serverAutomation=readPub('core/server-automation-v1770.js'),ops=readPub('core/ops-ux-v1762.js'),hotfix=readPub('core/hotfix-v1763.js'),ux=readPub('core/ux-fixes-v1764.js'),classic=readPub('core/classic-offer-pdf-v1767.js'),developerUX=readPub('core/developer-console-v1768.js'),offerWorkspace=readPub('core/offer-workspace-v1769.js');
|
||||
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');
|
||||
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}`);
|
||||
if(!legacy.includes('sunEsc(')||!legacy.includes('sunAttr('))fail('legacy bootstrap is not using shared escaping');else ok('legacy bootstrap escapes text and attributes');
|
||||
if(!legacy.includes("persistOrders('order.save')")||!legacy.includes("persistOrders('order.status')")||!legacy.includes("persistOrders('order.delete')")||!legacy.includes("persistCatalog('catalog.save')")||!legacy.includes("persistCatalog('catalog.delete')"))fail('legacy CRUD is not routed through data layer');else ok('legacy CRUD routes through data layer');
|
||||
const duplicateEsc=[html,runtime].join('\n').split('\n').filter(line=>/\b(?:const|let)\s+esc\b/.test(line)&&line.includes('.replace('));
|
||||
if(duplicateEsc.length)fail(`duplicate esc implementations remain: ${duplicateEsc.length}`);else ok('local esc aliases delegate to SunSafe');
|
||||
if(runtime.includes('/Type /Catalog'))fail('manual PDF writer remains in app-runtime.js');else ok('PDF packing removed from runtime');
|
||||
@ -27,15 +28,15 @@ 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');
|
||||
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(!sw.includes('v17-7-0-architecture-foundation')||!sw.includes('data-layer-v1770.js')||!sw.includes('server-automation-v1770.js')||!sw.includes('offer-workspace-v1769.js')||sw.includes('offer-gallery-data.js'))fail('service worker cache is stale');else ok('PWA cache updated to v17.7.0');
|
||||
if(html.includes('20260907-v17-6-0-stability-security')||!html.includes('20260908-v17-7-0-architecture-foundation')||!html.includes('classic-offer-pdf-v1767.js'))fail('index still serves stale core asset version');else ok('index cache-busting is current');
|
||||
if(!sw.includes('v17-7-1-data-layer-adoption')||!sw.includes('data-layer-v1771.js')||!sw.includes('server-automation-v1770.js')||!sw.includes('offer-workspace-v1769.js')||sw.includes('offer-gallery-data.js'))fail('service worker cache is stale');else ok('PWA cache updated to v17.7.0');
|
||||
if(html.includes('20260907-v17-6-0-stability-security')||!html.includes('20260909-v17-7-1-data-layer-adoption')||!html.includes('classic-offer-pdf-v1767.js'))fail('index still serves stale core asset version');else ok('index cache-busting is current');
|
||||
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('records.forEach(r=>r.addedNodes.forEach(n=>{if(n.nodeType===1)scan(n)}));enhanceDeveloperMemory()'))fail('Developer Console memory refresh is still coupled to MutationObserver');else ok('Developer Console memory refresh loop removed');
|
||||
if(!performance.includes('MEMORY_REFRESH_MS=30000')||!performance.includes('MEMORY_TIMEOUT_MS=8000')||!performance.includes('memoryPromise'))fail('Developer Console bounded refresh controls missing');else ok('Developer Console bounded refresh controls present');
|
||||
if(!performance.includes('pendingImageRoots')||!performance.includes('queueImageScan'))fail('batched image mutation scanning missing');else ok('image mutation scanning is batched');
|
||||
if(!performance.includes('loadDataLayer')||!performance.includes('loadServerAutomation'))fail('v17.7.0 foundation loaders missing');else ok('v17.7.0 foundation loaders present');
|
||||
if(!dataLayer.includes("const VERSION='17.7.0'")||!dataLayer.includes('ordersRepo')||!dataLayer.includes('applyServerOrders'))fail('v17.7.0 data layer missing');else ok('v17.7.0 data layer present');
|
||||
if(!performance.includes('loadDataLayer')||!performance.includes('data-layer-v1771.js')||!performance.includes('loadServerAutomation'))fail('v17.7.1 data layer loaders missing');else ok('v17.7.1 data layer loaders present');
|
||||
if(!dataLayer.includes("const VERSION='17.7.1'")||!dataLayer.includes('ordersRepo')||!dataLayer.includes('catalogRepo')||!dataLayer.includes('applyServerOrders')||!dataLayer.includes('CateriumDataV1770=api'))fail('v17.7.1 data layer missing');else ok('v17.7.1 data layer present');
|
||||
if(!serverAutomation.includes("const VERSION='17.7.0'")||!serverAutomation.includes("rpc('sun_run_order_automation'")||!serverAutomation.includes('applyServerChanges'))fail('v17.7.0 server automation client missing');else ok('v17.7.0 server automation client present');
|
||||
if(!performance.includes('ux-fixes-v1764.js')||!performance.includes('SunUXFixV1764'))fail('UX fix loader missing');else ok('UX fix loader 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');
|
||||
@ -43,7 +44,7 @@ if(!performance.includes('ops-ux-v1762.js')||!performance.includes('SunOpsUXV176
|
||||
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=20000','supportReadPermission','refreshSupportWorkspace','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 ["AUTO_DELAY_MS=60*1000","order.prepayment=total","order.status='Отдан заказчику'",'sunAutoCompletedAt','classificationDate','persistOfferTemplate','clientOfferTemplateId','offerTemplateId','sun-v1764-menu-icon','CateriumServerAutomationV1770?.enabled']) if(!ux.includes(marker))fail(`UX compatibility marker missing: ${marker}`);else ok(`UX compatibility marker: ${marker}`);
|
||||
for(const marker of ['CLOUD_RPC_TIMEOUT_MS=12000','CLOUD_CONFLICT_MAX_RETRIES=4','refreshSupportWorkspace',"const VERSION = '17.7.0'",'ERROR_DEDUPE_MS=5*60*1000','DEV_ADMIN_TTL_MS=30000']) if(!runtime.includes(marker))fail(`stability marker missing: ${marker}`);else ok(`stability marker: ${marker}`);
|
||||
for(const marker of ['CLOUD_RPC_TIMEOUT_MS=12000','CLOUD_CONFLICT_MAX_RETRIES=4','refreshSupportWorkspace',"const VERSION = '17.7.1'",'ERROR_DEDUPE_MS=5*60*1000','DEV_ADMIN_TTL_MS=30000']) if(!runtime.includes(marker))fail(`stability marker missing: ${marker}`);else ok(`stability marker: ${marker}`);
|
||||
if(!hotfix.includes('checkPlatformAdmin?.(false)')||!hotfix.includes('},10000);'))fail('Developer fallback polling is still aggressive');else ok('Developer fallback polling is throttled');
|
||||
if(!ux.includes('sunMenuIconV1766')||!ux.includes('sun-offer-template-mini-editorial-grid')||!runtime.includes('explicitTemplate'))fail('v17.6.6 proposal/menu markers missing');else ok('v17.6.6 proposal/menu markers present');
|
||||
if(!classic.includes("const VERSION='17.6.7'")||!classic.includes('CLASSIC_IDS')||!classic.includes('ARCHIVE_IDS')||!classic.includes('renderPages'))fail('v17.6.7 classic PDF module missing');else ok('v17.6.7 classic PDF module present');
|
||||
|
||||
Loading…
Reference in New Issue
Block a user