@tryvitris/website-harness 0.1.0-beta.7 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -117
- package/agent/graph.yaml +12 -6
- package/agent/refs/admin.md +11 -0
- package/agent/refs/analytics.md +17 -0
- package/agent/refs/launch.md +15 -11
- package/agent/refs/performance.md +1 -1
- package/bin/api/agent/evaluate.js +5 -5
- package/bin/api/agent/grade.js +5 -5
- package/bin/api/cli/api.js +15 -13
- package/bin/api/cli.js +15 -15
- package/bin/api/engine.js +6 -6
- package/bin/api/hosting.js +9 -7
- package/bin/api/index.js +2 -2
- package/bin/api/render.js +1 -1
- package/bin/chunks/{chunk-ZIROO2U4.js → chunk-37F7XIMH.js} +4 -3
- package/bin/chunks/{chunk-TTAUU3AZ.js → chunk-3OKR5AZJ.js} +1 -1
- package/bin/chunks/{chunk-PGFFKPK6.js → chunk-A2XMAPVW.js} +2 -2
- package/bin/chunks/{chunk-O55LAQ2C.js → chunk-FGSQE3DU.js} +2 -2
- package/bin/chunks/{chunk-VTI32UGJ.js → chunk-H3BS2EBR.js} +2 -2
- package/bin/chunks/{chunk-DSS2U2OM.js → chunk-KVMZSQ3D.js} +8 -2
- package/bin/chunks/{chunk-4MFPATAB.js → chunk-L44ZTNDM.js} +12 -5
- package/bin/chunks/{chunk-7V3OKLKH.js → chunk-LZC37OJZ.js} +4 -4
- package/bin/chunks/{chunk-5YSPWKFF.js → chunk-PYCRUMCT.js} +1 -1
- package/bin/chunks/{chunk-QRWGFLUU.js → chunk-R2WRQDVE.js} +2 -2
- package/bin/chunks/{chunk-YZWEXHT4.js → chunk-SEMH6MCV.js} +129 -21
- package/bin/chunks/{chunk-H7Y72HXC.js → chunk-UFTVWTEZ.js} +26 -7
- package/bin/chunks/{chunk-P56ZDMX7.js → chunk-WMJVLMU2.js} +16 -4
- package/bin/chunks/{chunk-FRABKM65.js → chunk-XY73ZLHB.js} +12 -9
- package/bin/chunks/{chunk-ZWFG4GPG.js → chunk-YXJAQYDE.js} +69 -53
- package/bin/chunks/{design-context-store-DCZOAK56.js → design-context-store-W3VTZ4HA.js} +5 -5
- package/bin/chunks/{packets-QOLWPRNV.js → packets-LBMDIFPO.js} +5 -5
- package/bin/chunks/{preparation-T2IOD5SJ.js → preparation-AV4PDQY6.js} +5 -5
- package/bin/chunks/{template-gallery-ET76ERZQ.js → template-gallery-H2SVYNRD.js} +1 -1
- package/bin/cloudflare/backend.js +144 -62
- package/bin/types/src/admin/connection.d.ts +3 -0
- package/bin/types/src/cli/admin.d.ts +21 -0
- package/bin/types/src/cli/api.d.ts +1 -1
- package/bin/types/src/cli/deploy.d.ts +2 -0
- package/bin/types/src/cli/doctor.d.ts +1 -0
- package/bin/types/src/cli/site-create.d.ts +2 -0
- package/bin/types/src/compile/site.d.ts +1 -1
- package/bin/types/src/integrations/hosting.d.ts +2 -0
- package/bin/vitris-art.js +5 -5
- package/bin/vitris-build.js +3 -3
- package/bin/vitris-create-site.js +9 -7
- package/bin/vitris-cursor-hook.js +5 -5
- package/bin/vitris-eval.js +7 -7
- package/bin/vitris-go.js +7 -7
- package/bin/vitris-harvest.js +6 -6
- package/bin/vitris-knowledge.js +6 -6
- package/bin/vitris-launch-preflight.js +5 -5
- package/bin/vitris-motion-view.js +5 -5
- package/bin/vitris-references.js +3 -3
- package/bin/vitris-refresh.js +6 -6
- package/bin/vitris-stock.js +1 -1
- package/bin/vitris-templates.js +1 -1
- package/bin/vitris-themes.js +5 -5
- package/bin/vitris-upgrade.js +6 -6
- package/bin/vitris-verify-browser.js +5 -5
- package/bin/vitris-verify-performance.js +3 -3
- package/bin/vitris.js +15 -15
- package/docs/ADMIN.md +85 -0
- package/docs/CLI.md +14 -6
- package/docs/CLOUDFLARE.md +27 -2
- package/docs/DISTRIBUTION.md +5 -3
- package/docs/ENGINE.md +6 -0
- package/docs/PRESERVE.md +1 -1
- package/docs/SECURITY.md +2 -2
- package/package.json +28 -23
- package/templates/site/AGENTS.md +4 -2
- package/templates/site/README.md +8 -3
- package/templates/site/deployment/README.md +24 -0
- package/templates/site/{.github/workflows/deploy.yml → deployment/github-actions.yml.example} +5 -6
- package/templates/site/gitignore +3 -0
- package/templates/site/package.json +3 -1
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
planKitUpgrade,
|
|
7
7
|
recoverKitUpgrade,
|
|
8
8
|
rollbackKitUpgrade
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-XY73ZLHB.js";
|
|
10
10
|
import {
|
|
11
11
|
findHarnessRoot
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-H3BS2EBR.js";
|
|
13
13
|
import {
|
|
14
14
|
json,
|
|
15
15
|
save
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
sourceRevision,
|
|
8
8
|
verifyRefine,
|
|
9
9
|
withMutationLock
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-XY73ZLHB.js";
|
|
11
11
|
import {
|
|
12
12
|
assertRequestId,
|
|
13
13
|
parseChangePlan,
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
import {
|
|
18
18
|
buildVerified,
|
|
19
19
|
findHarnessRoot
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-H3BS2EBR.js";
|
|
21
21
|
import {
|
|
22
22
|
files,
|
|
23
23
|
fingerprint,
|
|
@@ -37,6 +37,12 @@ const $=id=>document.getElementById(id),allThemes=m=>[...m.themes,...m.archiveTh
|
|
|
37
37
|
let brandIndex=0,selected=null,selectedRelease=null,pending=null,opened=null,lastFocus=null,galleryY=0,detailFrame=null;
|
|
38
38
|
let example='story',viewport='desktop',compareExample='story',compareViewport='desktop',comparePair=[],compareTab=0;
|
|
39
39
|
const cards=new Map(),frames=new Set(),positions=new Map(),demoMemory=new Map();let compareFrames=[];
|
|
40
|
+
// Native lazy images can occupy every HTTP/1 connection while the server renders
|
|
41
|
+
// thumbnails. Reserve connections for the live preview document, fonts and images.
|
|
42
|
+
const thumbnailJobs=new Map();let thumbnailActive=0;
|
|
43
|
+
function pumpThumbnails(){for(const [img,job] of thumbnailJobs){if(!img.isConnected){thumbnailObserver.unobserve(img);job.cancel?.();thumbnailJobs.delete(img);continue;}if(thumbnailActive>=2||!job.visible||job.started)continue;job.started=true;thumbnailActive++;const finish=()=>{thumbnailObserver.unobserve(img);img.removeEventListener('load',finish);img.removeEventListener('error',finish);job.cancel=null;thumbnailActive--;thumbnailJobs.delete(img);pumpThumbnails();};job.cancel=finish;img.addEventListener('load',finish);img.addEventListener('error',finish);img.loading='eager';img.src=job.src;}}
|
|
44
|
+
const thumbnailObserver=new IntersectionObserver(entries=>{for(const entry of entries){const job=thumbnailJobs.get(entry.target);if(job)job.visible=entry.isIntersecting;}pumpThumbnails();},{rootMargin:'300px 0px'});
|
|
45
|
+
function thumbnailSource(img,src){if(img.dataset.thumbnailSource===src)return;const prior=thumbnailJobs.get(img);if(prior){thumbnailJobs.delete(img);prior.cancel?.();img.removeAttribute('src');}img.dataset.thumbnailSource=src;if(src.startsWith('data:')){thumbnailObserver.unobserve(img);img.src=src;return;}thumbnailJobs.set(img,{src,visible:false,started:false});thumbnailObserver.unobserve(img);thumbnailObserver.observe(img);}
|
|
40
46
|
const model=()=>models[brandIndex],draftKey=()=> 'vitris-theme-draft:'+model().brand.siteId+':'+model().brandHash,browseKey=()=> 'vitris-theme-browse:v3:'+model().themes[0].release+':'+model().brand.siteId;
|
|
41
47
|
const findTheme=(id,release)=>allThemes(model()).find(t=>t.id===id&&t.release===release),byKey=k=>allThemes(model()).find(t=>key(t)===k);
|
|
42
48
|
const defaults=()=>({collection:'All directions',families:[],identities:[],industries:[],media:[],tasks:[],query:'',group:'none',sort:'curated',archive:'current',shortlist:[],compare:[],palette:'brand',viewport:innerWidth<768?'mobile':'desktop'});
|
|
@@ -68,7 +74,7 @@ function makeCard(t,archive){
|
|
|
68
74
|
const c={element,theme:t,host,progress,choose:chooseButton,save,check,state:null,archive,role:pageKey(t.pages[0]),thumbs:[]};let active=false;
|
|
69
75
|
c.deactivate=(focus=true)=>{active=false;dispose(c.state);c.state=null;host.replaceChildren();host.hidden=true;top.hidden=true;if(focus){c.lastButton?.focus({preventScroll:true});if(Number.isFinite(c.galleryY))scrollTo({top:c.galleryY,behavior:'instant'});}};c.isActive=()=>active;
|
|
70
76
|
c.show=(role,focus)=>{for(const other of cards.values())if(other!==c&&other.state)other.deactivate(false);if(!active)c.galleryY=scrollY;c.role=role;c.lastButton=focus;active=true;host.hidden=false;top.hidden=false;dispose(c.state);c.state=mount(host,t,role,browse.viewport);c.syncThumbs();host.scrollIntoView({block:'center',behavior:'instant'});host.focus({preventScroll:true});};
|
|
71
|
-
c.syncThumbs=()=>{for(const item of c.thumbs){const r=resourceFor(t,item.role,browse.viewport);item.tile.dataset.visible=String(item.role===c.role);item.img.alt=t.name+' · '+item.label+' · '+(browse.palette==='brand'?model().brand.name:'Original palette');if(r.thumbnail){item.img
|
|
77
|
+
c.syncThumbs=()=>{for(const item of c.thumbs){const r=resourceFor(t,item.role,browse.viewport);item.tile.dataset.visible=String(item.role===c.role);item.img.alt=t.name+' · '+item.label+' · '+(browse.palette==='brand'?model().brand.name:'Original palette');if(r.thumbnail){thumbnailSource(item.img,r.thumbnail);item.img.hidden=false;item.fallback.hidden=true;}else{item.img.hidden=true;item.fallback.hidden=false;}}mobileSelect.value=c.role;c.full.href=resourceFor(t,pageKey(t.pages[0]),browse.viewport).full;c.full.textContent=t.pageCount>5?'Explore all '+t.pageCount+' pages':archive?'Open preserved preview ↗':'Open full site ↗';if(!session){c.full.onclick=e=>{e.preventDefault();openDetail(t,c.full,pageKey(t.pages[0]));};}};
|
|
72
78
|
for(const page of t.pages.filter(p=>p.featured!==false).slice(0,t.pageCount>5?5:99)){const tile=document.createElement('section');tile.className='page-tile';tile.dataset.role=pageKey(page);const imageButton=button('',()=>openDetail(t,imageButton));imageButton.className='thumbnail-button';imageButton.setAttribute('aria-label','Explore '+t.name+' '+page.label);imageButton.onclick=()=>{openDetail(t,imageButton,pageKey(page));};const img=document.createElement('img');img.loading='lazy';img.decoding='async';img.width=480;img.height=300;const fallback=document.createElement('span');fallback.className='thumb-fallback';fallback.textContent='Open the live page';img.onerror=()=>{img.hidden=true;fallback.hidden=false;fallback.textContent='Live page available';};imageButton.append(img,fallback);const caption=document.createElement('div');caption.className='tile-caption';const text=document.createElement('strong');text.textContent=page.label;const scroll=button('Scroll preview',()=>c.show(pageKey(page),scroll));scroll.setAttribute('aria-label','Scroll '+t.name+' '+page.label);caption.append(text,scroll);tile.append(imageButton,caption);tiles.append(tile);c.thumbs.push({tile,img,fallback,role:pageKey(page),label:page.label});const option=document.createElement('option');option.value=pageKey(page);option.textContent=page.label;mobileSelect.append(option);}
|
|
73
79
|
for(const page of t.pages){if([...mobileSelect.options].some(o=>o.value===pageKey(page)))continue;const option=document.createElement('option');option.value=pageKey(page);option.textContent=page.label;mobileSelect.append(option);}const full=document.createElement('a');full.className='full-site-tile';full.target='_blank';full.rel='noopener';full.textContent=archive?'Open preserved preview ↗':'Open full site ↗';c.full=full;tiles.append(full);mobileSelect.onchange=()=>{c.role=mobileSelect.value;if(c.state)c.show(c.role,mobileSelect);else c.syncThumbs();};overview.append(mobileSelect,tiles);element.append(meta,overview,top,host);c.syncThumbs();exit.onclick=()=>c.deactivate();
|
|
74
80
|
host.onkeydown=e=>{const map={ArrowDown:80,ArrowUp:-80,PageDown:Math.max(250,(c.state?.viewHeight||600)*.85),PageUp:-Math.max(250,(c.state?.viewHeight||600)*.85),Home:0,End:Number.MAX_SAFE_INTEGER};if(e.key==='Escape'){e.preventDefault();c.deactivate();}else if(e.key in map){e.preventDefault();scrollFrame(c.state,e.key==='Home'||e.key==='End'?map[e.key]:(c.state?.y||0)+map[e.key]);}};
|
|
@@ -82,7 +88,7 @@ function syncControls(){$('allentry').setAttribute('aria-pressed',String(browse.
|
|
|
82
88
|
document.addEventListener('wheel',e=>{if(e.defaultPrevented||e.ctrlKey||e.metaKey||Math.abs(e.deltaX)>Math.abs(e.deltaY))return;for(const s of frames){if(!s.host.isConnected||!s.ready||!s.host.getClientRects().length)continue;const r=s.host.getBoundingClientRect();if(e.clientX<r.left||e.clientX>=r.right||e.clientY<r.top||e.clientY>=r.bottom)continue;const delta=e.deltaY*(e.deltaMode===1?16:e.deltaMode===2?s.viewHeight:1);if((delta>0&&s.y<s.height-s.viewHeight-1)||(delta<0&&s.y>1)){e.preventDefault();scrollFrame(s,s.y+delta);}break;}},{capture:true,passive:false});
|
|
83
89
|
function applyFilter(){const result=browseResults(baseThemes(),browse);for(const c of cards.values())visibleObserver.unobserve(c.host);const fragment=document.createDocumentFragment();for(const g of result.groups){const section=document.createElement('section');section.className='group';if(g.name){const h=document.createElement('h2');h.textContent=g.name+' · '+g.themes.length;section.append(h);}const grid=document.createElement('div');grid.className='grid';grid.setAttribute('role','list');for(const t of g.themes)grid.append(cards.get(key(t)).element);section.append(grid);fragment.append(section);}$('grid').replaceChildren(fragment);const visible=new Set(result.visible.map(key));for(const [k,c] of cards)if(visible.has(k))visibleObserver.observe(c.host);else if(c.state){dispose(c.state);c.state=null;}$('count').textContent=result.visible.length+' of '+baseThemes().length+' directions'+(browse.query?' for “'+browse.query+'”':'');$('empty').hidden=!!result.visible.length;syncControls();syncFacetCounts();syncChips();syncShort();persist();}
|
|
84
90
|
function syncChips(){$('chips').replaceChildren();const chips=[];if(browse.collection!=='All directions')chips.push(['Collection: '+browse.collection,()=>{browse.collection='All directions';applyFilter();}]);for(const k of ['industries','families','identities','media','tasks'])for(const value of browse[k])chips.push([value,()=>{browse[k]=browse[k].filter(x=>x!==value);applyFilter();}]);if(browse.query)chips.push(['Search: '+browse.query,()=>{browse.query='';applyFilter();}]);for(const [name,remove] of chips){const b=button(name+' ×',remove);b.setAttribute('aria-label','Remove '+name);$('chips').append(b);}if(!chips.length)$('chips').hidden=true;else $('chips').hidden=false;$('empty').querySelector('p').textContent='Current filters: '+(chips.map(c=>c[0]).join(' · ')||'none')+'. Try a broader phrase or remove a filter.';}
|
|
85
|
-
function facetUI(){const specs=[['industries','Industry',vocabulary.industries||[]],['families','Family',Object.keys(vocabulary.families)],['identities','Identity',vocabulary.identities],['media','Images',['none','light','photo-led']],['tasks','Customer task',['call','quote','appointment','understand work','inspection','estimate']]];$('facets').replaceChildren();const advanced=document.createElement('details'),summary=document.createElement('summary');summary.textContent='More filters';advanced.append(summary);for(const [facet,name,values] of specs){const field=document.createElement('fieldset'),legend=document.createElement('legend');legend.textContent=name;field.append(legend);if(facet==='identities'){const explanation=document.createElement('p');explanation.className='family-description';explanation.textContent='How you want your business to feel.';field.append(explanation);}for(const value of values){const label=document.createElement('label'),input=document.createElement('input'),count=document.createElement('small');input.type='checkbox';input.value=value;input.dataset.facet=facet;input.onchange=()=>{browse[facet]=input.checked?[...browse[facet],value]:browse[facet].filter(x=>x!==value);applyFilter();};const display={none:'No photos needed',light:'A few images','photo-led':'Photography leads',call:'Phone enquiry',quote:'Request a quote',appointment:'Appointment / class','understand work':'Understand the work'};count.textContent=baseThemes().filter(t=>{if(facet==='families')return t.browse.family===value;if(facet==='media')return t.browse.media===value;if(facet==='industries')return (t.browse.industries||[t.browse.industry]).includes(value);return (t.browse[facet]||[]).includes(value);}).length;label.append(input,display[value]||value,count);field.append(label);if(facet==='families'){const d=document.createElement('p');d.className='family-description';d.textContent=vocabulary.families[value];field.append(d);if(browse.archive==='current'){const strip=document.createElement('div');strip.className='family-samples';strip.setAttribute('aria-label',value+' examples');for(const t of model().themes.filter(t=>t.browse.family===value&&vocabulary.samples[t.id]).slice(0,3)){const img=document.createElement('img');img
|
|
91
|
+
function facetUI(){const specs=[['industries','Industry',vocabulary.industries||[]],['families','Family',Object.keys(vocabulary.families)],['identities','Identity',vocabulary.identities],['media','Images',['none','light','photo-led']],['tasks','Customer task',['call','quote','appointment','understand work','inspection','estimate']]];$('facets').replaceChildren();const advanced=document.createElement('details'),summary=document.createElement('summary');summary.textContent='More filters';advanced.append(summary);for(const [facet,name,values] of specs){const field=document.createElement('fieldset'),legend=document.createElement('legend');legend.textContent=name;field.append(legend);if(facet==='identities'){const explanation=document.createElement('p');explanation.className='family-description';explanation.textContent='How you want your business to feel.';field.append(explanation);}for(const value of values){const label=document.createElement('label'),input=document.createElement('input'),count=document.createElement('small');input.type='checkbox';input.value=value;input.dataset.facet=facet;input.onchange=()=>{browse[facet]=input.checked?[...browse[facet],value]:browse[facet].filter(x=>x!==value);applyFilter();};const display={none:'No photos needed',light:'A few images','photo-led':'Photography leads',call:'Phone enquiry',quote:'Request a quote',appointment:'Appointment / class','understand work':'Understand the work'};count.textContent=baseThemes().filter(t=>{if(facet==='families')return t.browse.family===value;if(facet==='media')return t.browse.media===value;if(facet==='industries')return (t.browse.industries||[t.browse.industry]).includes(value);return (t.browse[facet]||[]).includes(value);}).length;label.append(input,display[value]||value,count);field.append(label);if(facet==='families'){const d=document.createElement('p');d.className='family-description';d.textContent=vocabulary.families[value];field.append(d);if(browse.archive==='current'){const strip=document.createElement('div');strip.className='family-samples';strip.setAttribute('aria-label',value+' examples');for(const t of model().themes.filter(t=>t.browse.family===value&&vocabulary.samples[t.id]).slice(0,3)){const img=document.createElement('img');thumbnailSource(img,vocabulary.samples[t.id]);img.alt=t.name+' original palette opening';img.title=t.name;strip.append(img);}field.append(strip);}}}if(['media','tasks'].includes(facet))advanced.append(field);else $('facets').append(field);}$('facets').append(advanced);}
|
|
86
92
|
function notice(text){$('browsenotice').textContent=text;setTimeout(()=>{$('browsenotice').textContent='';},5000);}
|
|
87
93
|
function syncFacetCounts(){for(const input of $('facets').querySelectorAll('input[data-facet]')){const facet=input.dataset.facet;const count=browseResults(baseThemes(),{...browse,[facet]:[input.value]}).visible.length;input.parentElement.querySelector('small').textContent=count;}}
|
|
88
94
|
function toggleShort(t){const k=key(t);if(browse.shortlist.includes(k)){browse.shortlist=browse.shortlist.filter(x=>x!==k);browse.compare=browse.compare.filter(x=>x!==k);}else if(browse.shortlist.length<6)browse.shortlist.push(k);else notice('Your shortlist holds six directions. Remove one to add another.');syncShort();persist();}
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
siteCss,
|
|
13
13
|
siteIslands,
|
|
14
14
|
sitemapXml
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-37F7XIMH.js";
|
|
16
16
|
import {
|
|
17
17
|
mergeTheme,
|
|
18
18
|
themeCss
|
|
@@ -258,6 +258,7 @@ async function buildSite(input) {
|
|
|
258
258
|
const js = siteIslands([page], input.catalog);
|
|
259
259
|
pageAssets[page.path] = { css: asset(siteCss([page], input.catalog), "css"), js: js ? asset(js, "js") : null };
|
|
260
260
|
}
|
|
261
|
+
if (backendOrigin) pageAssets[COOKIE_COMPLIANCE_PATH] = { css: asset(siteCss([], input.catalog), "css"), js: null };
|
|
261
262
|
const files = compileSite({
|
|
262
263
|
site,
|
|
263
264
|
pages: input.pages,
|
|
@@ -267,11 +268,14 @@ async function buildSite(input) {
|
|
|
267
268
|
publicOrigin: input.publicOrigin,
|
|
268
269
|
pageAssets,
|
|
269
270
|
webAnalyticsToken: input.webAnalyticsToken
|
|
270
|
-
});
|
|
271
|
+
}, compiled);
|
|
271
272
|
const written = [];
|
|
272
273
|
await mkdir(input.outDir, { recursive: true });
|
|
273
274
|
await mkdir(join(input.outDir, "assets/immutable"), { recursive: true });
|
|
274
|
-
for (const [path, body] of routeAssets)
|
|
275
|
+
for (const [path, body] of routeAssets) {
|
|
276
|
+
await writeFile(join(input.outDir, path.slice(1)), body, "utf8");
|
|
277
|
+
written.push(path.slice(1));
|
|
278
|
+
}
|
|
275
279
|
for (const file of files) {
|
|
276
280
|
const rel = file.path === "/" ? "index.html" : `${file.path.replace(/^\//, "").replace(/\/$/, "")}/index.html`;
|
|
277
281
|
const dest = join(input.outDir, rel);
|
|
@@ -281,7 +285,10 @@ async function buildSite(input) {
|
|
|
281
285
|
}
|
|
282
286
|
await writeFile(join(input.outDir, "site.css"), siteCss(compiled, input.catalog), "utf8");
|
|
283
287
|
await writeFile(join(input.outDir, "theme.css"), themeCss(theme), "utf8");
|
|
284
|
-
if (backendOrigin)
|
|
288
|
+
if (backendOrigin) {
|
|
289
|
+
await writeFile(join(input.outDir, "beacon.js"), beaconJs, "utf8");
|
|
290
|
+
written.push("beacon.js");
|
|
291
|
+
}
|
|
285
292
|
await writeFile(join(input.outDir, "robots.txt"), robotsTxt(input.publicOrigin), "utf8");
|
|
286
293
|
await writeFile(
|
|
287
294
|
join(input.outDir, "sitemap.xml"),
|
|
@@ -300,7 +307,7 @@ async function buildSite(input) {
|
|
|
300
307
|
written.push(motionAssetHref.slice(1));
|
|
301
308
|
}
|
|
302
309
|
if (islands) written.push("islands.js");
|
|
303
|
-
written.push("site.css", "theme.css", "
|
|
310
|
+
written.push("site.css", "theme.css", "robots.txt", "sitemap.xml");
|
|
304
311
|
return written;
|
|
305
312
|
}
|
|
306
313
|
async function fetchLiveContent(kernelOrigin, slug, token) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
capturePreservedSite,
|
|
3
3
|
verifyPreservedSite
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-R2WRQDVE.js";
|
|
5
5
|
import {
|
|
6
6
|
AGENT_RUNTIME_HISTORY_ENTRIES
|
|
7
7
|
} from "./chunk-34H54KEY.js";
|
|
@@ -68,7 +68,7 @@ import {
|
|
|
68
68
|
verifyAllowedWrites,
|
|
69
69
|
withMutationLock,
|
|
70
70
|
writeVisualProbe
|
|
71
|
-
} from "./chunk-
|
|
71
|
+
} from "./chunk-XY73ZLHB.js";
|
|
72
72
|
import {
|
|
73
73
|
QUESTION_VERSION,
|
|
74
74
|
questionSemanticsHash
|
|
@@ -99,7 +99,7 @@ import {
|
|
|
99
99
|
findHarnessRoot,
|
|
100
100
|
findKernelRoot,
|
|
101
101
|
isPreserveSite
|
|
102
|
-
} from "./chunk-
|
|
102
|
+
} from "./chunk-H3BS2EBR.js";
|
|
103
103
|
import {
|
|
104
104
|
STUDY_CAPTURE_HEIGHT,
|
|
105
105
|
STUDY_CAPTURE_VIEWPORTS,
|
|
@@ -253,7 +253,7 @@ function scaffoldNestedSite(opts) {
|
|
|
253
253
|
for (const [key, value] of Object.entries(pkg.scripts)) pkg.scripts[key] = value.replaceAll("__SLUG__", opts.slug);
|
|
254
254
|
writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
|
|
255
255
|
`);
|
|
256
|
-
for (const file2 of ["site.json", "README.md", "AGENTS.md"]) {
|
|
256
|
+
for (const file2 of ["site.json", "README.md", "AGENTS.md", "deployment/github-actions.yml.example"]) {
|
|
257
257
|
const p = join2(dest, file2);
|
|
258
258
|
if (!existsSync2(p)) continue;
|
|
259
259
|
if (file2 === "site.json") {
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-XL2DRAYI.js";
|
|
4
4
|
import {
|
|
5
5
|
sourceRevision
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-XY73ZLHB.js";
|
|
7
7
|
import {
|
|
8
8
|
previewServer
|
|
9
9
|
} from "./chunk-WEFDVXFX.js";
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
providerConsoleWarning,
|
|
23
23
|
readOnlyEmbedProvider,
|
|
24
24
|
unresolvedCaptureBlockers
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-H3BS2EBR.js";
|
|
26
26
|
import {
|
|
27
27
|
save,
|
|
28
28
|
within
|
|
@@ -11,48 +11,48 @@ import {
|
|
|
11
11
|
runDoctor,
|
|
12
12
|
shareSite,
|
|
13
13
|
synthesizeSite
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-YXJAQYDE.js";
|
|
15
15
|
import {
|
|
16
16
|
verifySite
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-WMJVLMU2.js";
|
|
18
18
|
import {
|
|
19
19
|
upgradeSite
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-A2XMAPVW.js";
|
|
21
21
|
import {
|
|
22
22
|
createSite
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-UFTVWTEZ.js";
|
|
24
24
|
import {
|
|
25
25
|
explainStage
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-3OKR5AZJ.js";
|
|
27
27
|
import {
|
|
28
28
|
runSite,
|
|
29
29
|
statusSite,
|
|
30
30
|
workflowArgvFromFlags
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-LZC37OJZ.js";
|
|
32
32
|
import {
|
|
33
33
|
EXIT_CODES
|
|
34
34
|
} from "./chunk-JCRGE6AL.js";
|
|
35
35
|
import {
|
|
36
36
|
siteInputsFromFlags
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-XY73ZLHB.js";
|
|
38
38
|
import {
|
|
39
39
|
buildVerified,
|
|
40
40
|
findHarnessRoot
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-H3BS2EBR.js";
|
|
42
42
|
import {
|
|
43
43
|
approvedOrigin,
|
|
44
44
|
inspectForms,
|
|
45
45
|
safeId,
|
|
46
46
|
validateFormConfig
|
|
47
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-L44ZTNDM.js";
|
|
48
48
|
import {
|
|
49
49
|
files
|
|
50
50
|
} from "./chunk-T6PE4KN4.js";
|
|
51
51
|
|
|
52
52
|
// src/cli/index.ts
|
|
53
|
-
import { existsSync as existsSync2, readFileSync as
|
|
54
|
-
import { resolve as
|
|
55
|
-
import { spawnSync } from "node:child_process";
|
|
53
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
|
|
54
|
+
import { resolve as resolve3 } from "node:path";
|
|
55
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
56
56
|
|
|
57
57
|
// src/cli/events.ts
|
|
58
58
|
var EventStream = class {
|
|
@@ -270,6 +270,106 @@ async function formsCommand(command, opts) {
|
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
+
// src/cli/admin.ts
|
|
274
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
275
|
+
import { resolve as resolve2 } from "node:path";
|
|
276
|
+
import { createRequire } from "node:module";
|
|
277
|
+
import { spawnSync } from "node:child_process";
|
|
278
|
+
import { parseManifest } from "@tryvitris/website-admin-contracts";
|
|
279
|
+
|
|
280
|
+
// src/admin/connection.ts
|
|
281
|
+
async function readConnection(manifest, token, resource, test = false, transport = fetch) {
|
|
282
|
+
if (!manifest.backend || !/^[a-zA-Z0-9_-]{32,200}$/.test(token)) throw Error("A dedicated VITRIS_ADMIN_API_TOKEN and backend connection are required");
|
|
283
|
+
const url = new URL(`/api/admin/v1/${encodeURIComponent(manifest.backend.tenantId)}/${resource}`, manifest.backend.origin);
|
|
284
|
+
if (test) url.searchParams.set("mode", "test");
|
|
285
|
+
const response = await transport(url, { redirect: "manual", signal: AbortSignal.timeout(12e3), headers: { authorization: `Bearer ${token}`, "x-vitris-installation": manifest.installationId, "x-vitris-site-origin": manifest.site.origin } });
|
|
286
|
+
if (!response.ok) throw Error(`Admin v1 ${resource} rejected (${response.status}); connection is not verified`);
|
|
287
|
+
const reader = response.body?.getReader();
|
|
288
|
+
if (!reader) throw Error("Empty backend response");
|
|
289
|
+
const chunks = [];
|
|
290
|
+
let size = 0;
|
|
291
|
+
for (; ; ) {
|
|
292
|
+
const { done, value } = await reader.read();
|
|
293
|
+
if (done) break;
|
|
294
|
+
size += value.length;
|
|
295
|
+
if (size > 4e6) {
|
|
296
|
+
await reader.cancel();
|
|
297
|
+
throw Error("Backend response too large");
|
|
298
|
+
}
|
|
299
|
+
chunks.push(value);
|
|
300
|
+
}
|
|
301
|
+
const bytes = new Uint8Array(size);
|
|
302
|
+
let offset = 0;
|
|
303
|
+
for (const chunk of chunks) {
|
|
304
|
+
bytes.set(chunk, offset);
|
|
305
|
+
offset += chunk.length;
|
|
306
|
+
}
|
|
307
|
+
const data = JSON.parse(new TextDecoder().decode(bytes));
|
|
308
|
+
if (data?.schemaVersion !== 1 || data.installationId !== manifest.installationId || data.tenantId !== manifest.backend.tenantId || data.siteOrigin !== manifest.site.origin) throw Error("Backend contract or installation identity mismatch");
|
|
309
|
+
if (resource === "status") {
|
|
310
|
+
if (!Array.isArray(data.scopes)) throw Error("Invalid backend scopes");
|
|
311
|
+
} else {
|
|
312
|
+
if (data.source !== "connected" || !Array.isArray(data.rows) || data.rows.length > 200) throw Error("Invalid backend records");
|
|
313
|
+
if (resource === "leads" && data.rows.some((r) => ["id", "name", "email", "message", "created_at", "status"].some((k) => typeof r?.[k] !== "string"))) throw Error("Invalid lead response");
|
|
314
|
+
if (resource === "analytics" && data.rows.some((r) => typeof r?.day !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(r.day) || ["views", "visitors", "enquiries"].some((k) => typeof r[k] !== "number" || !Number.isFinite(r[k]) || r[k] < 0))) throw Error("Invalid analytics response");
|
|
315
|
+
}
|
|
316
|
+
return data;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// src/cli/admin.ts
|
|
320
|
+
function mergeAdminGrant(existing, addition) {
|
|
321
|
+
const object = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
322
|
+
if (!object(existing) || !object(addition) || Object.keys(addition).length !== 1) throw Error("Expected complete grant map and one installation grant");
|
|
323
|
+
for (const [id, raw] of Object.entries({ ...existing, ...addition })) {
|
|
324
|
+
const g = raw;
|
|
325
|
+
if (!/^[a-z][a-z0-9-]{0,50}$/.test(id) || !object(g) || typeof g.tenantId !== "string" || !/^[a-zA-Z0-9_-]{1,100}$/.test(g.tenantId) || typeof g.tokenHash !== "string" || !/^[a-f0-9]{64}$/.test(g.tokenHash) || typeof g.siteOrigin !== "string" || new URL(g.siteOrigin).origin !== g.siteOrigin || !g.siteOrigin.startsWith("https://") || !Array.isArray(g.scopes) || g.scopes.some((s) => !["leads:read", "analytics:read", "leads:write"].includes(s)) || Object.keys(g).some((k) => !["tenantId", "siteOrigin", "tokenHash", "scopes"].includes(k))) throw Error("Invalid hashed grant; raw credentials are forbidden");
|
|
326
|
+
}
|
|
327
|
+
for (const id of Object.keys(addition)) if (Object.hasOwn(existing, id) && JSON.stringify(existing[id]) !== JSON.stringify(addition[id])) throw Error("Grant already exists with different authority; rotate explicitly");
|
|
328
|
+
return { ...existing, ...addition };
|
|
329
|
+
}
|
|
330
|
+
async function adminCommand(command, options) {
|
|
331
|
+
try {
|
|
332
|
+
const site = resolve2(options.site || ".");
|
|
333
|
+
if (command === "grant") {
|
|
334
|
+
if (!options.dir || !options.grants || !options.out || options.dryRun) throw Error("admin grant requires --dir, --grants COMPLETE_MAP_FILE and --out NEW_IGNORED_FILE");
|
|
335
|
+
const addition = JSON.parse(readFileSync2(resolve2(options.dir, ".vitris/backend-grant.json"), "utf8"));
|
|
336
|
+
const manifest2 = parseManifest(JSON.parse(readFileSync2(resolve2(options.dir, "admin.config.json"), "utf8")));
|
|
337
|
+
const grant = addition[manifest2.installationId];
|
|
338
|
+
if (!grant || grant.tenantId !== manifest2.backend?.tenantId || grant.siteOrigin !== manifest2.site.origin) throw Error("Grant and installation identity differ");
|
|
339
|
+
const merged = mergeAdminGrant(JSON.parse(readFileSync2(resolve2(options.grants), "utf8")), addition);
|
|
340
|
+
writeFileSync2(resolve2(options.out), JSON.stringify(merged, null, 2) + "\n", { flag: "wx", mode: 384 });
|
|
341
|
+
return { ok: true, exitCode: 0, summary: "Merged hashed grants into the new local file. No backend secret changed; review and install explicitly through secret stdin." };
|
|
342
|
+
}
|
|
343
|
+
if (command === "setup") {
|
|
344
|
+
if (!options.dir || !options.domain || !options.tenant || !!options.plan === !!options.apply) throw Error("admin setup requires --dir, --domain, --tenant and exactly one of --plan or --apply");
|
|
345
|
+
if (options.dryRun) throw Error("Use --plan for local setup planning; --dry-run does not write a setup plan");
|
|
346
|
+
const args = ["setup", "--site", site, "--dir", resolve2(options.dir), "--domain", options.domain, "--tenant", options.tenant, options.apply ? "--apply" : "--plan"];
|
|
347
|
+
if (options.website) args.push("--website", options.website);
|
|
348
|
+
if (options.backendOrigin) args.push("--backend-origin", options.backendOrigin);
|
|
349
|
+
if (options.leadWrite) args.push("--lead-write");
|
|
350
|
+
const entry = createRequire(import.meta.url).resolve("@tryvitris/website-admin-harness/cli");
|
|
351
|
+
const output = spawnSync(process.execPath, [entry, ...args], { encoding: "utf8", windowsHide: true, maxBuffer: 1024 * 1024 });
|
|
352
|
+
if (output.status !== 0) throw Error(output.error?.message || output.stderr.trim() || "Admin setup failed");
|
|
353
|
+
return { ok: true, exitCode: 0, summary: output.stdout.trim() };
|
|
354
|
+
}
|
|
355
|
+
if (!["status", "verify"].includes(command)) throw Error("Use vitris admin setup|status|verify|grant");
|
|
356
|
+
const launch = JSON.parse(readFileSync2(resolve2(site, "launch.json"), "utf8"));
|
|
357
|
+
if (command === "status") return { ok: true, exitCode: 0, summary: launch.admin ? "Local admin metadata exists. Backend, authentication and deployment are unverified; run admin verify with --dir." : "No admin connection. Read docs/ADMIN.md; scaffolding alone does not enable collection.", data: { connection: launch.admin || null, verified: false } };
|
|
358
|
+
if (!options.dir) throw Error("admin verify requires --dir ADMIN_DIRECTORY");
|
|
359
|
+
const manifest = parseManifest(JSON.parse(readFileSync2(resolve2(options.dir, "admin.config.json"), "utf8")));
|
|
360
|
+
if (!launch.admin || manifest.installationId !== launch.admin.installationId || manifest.site.id !== launch.admin.siteId || manifest.adminOrigin !== launch.admin.url || manifest.site.origin !== launch.admin.siteOrigin || manifest.backend?.tenantId !== launch.admin.tenantId) throw Error("Website and admin installation identities differ");
|
|
361
|
+
const token = process.env.VITRIS_ADMIN_API_TOKEN || "";
|
|
362
|
+
const status = await readConnection(manifest, token, "status");
|
|
363
|
+
if (!["leads:read", "analytics:read"].every((scope) => status.scopes.includes(scope))) throw Error("Default read scopes are missing");
|
|
364
|
+
const leads = await readConnection(manifest, token, "leads");
|
|
365
|
+
const tests = await readConnection(manifest, token, "leads", true);
|
|
366
|
+
const analytics = await readConnection(manifest, token, "analytics");
|
|
367
|
+
return { ok: true, exitCode: 0, summary: "Admin v1 data connection verified. Authentication, collection and deployment require separate acceptance checks.", data: { verified: true, productionLeads: leads.rows.length, testSubmissions: tests.rows.length, analyticsDays: analytics.rows.length, scopes: status.scopes } };
|
|
368
|
+
} catch (error) {
|
|
369
|
+
return { ok: false, exitCode: EXIT_CODES.PRECONDITION_FAILED, error: error instanceof Error ? error.message : String(error) };
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
273
373
|
// src/cli/index.ts
|
|
274
374
|
function parseArgs(args) {
|
|
275
375
|
const command = [];
|
|
@@ -309,9 +409,9 @@ function parseArgs(args) {
|
|
|
309
409
|
}
|
|
310
410
|
function resolvePrecedence(flags, siteRoot) {
|
|
311
411
|
const merged = {};
|
|
312
|
-
if (siteRoot && existsSync2(
|
|
412
|
+
if (siteRoot && existsSync2(resolve3(siteRoot, "launch.json"))) {
|
|
313
413
|
try {
|
|
314
|
-
const launch = JSON.parse(
|
|
414
|
+
const launch = JSON.parse(readFileSync3(resolve3(siteRoot, "launch.json"), "utf8"));
|
|
315
415
|
if (typeof launch.ownerEmail === "string") merged.owner = launch.ownerEmail;
|
|
316
416
|
if (typeof launch.slug === "string") merged.slug = launch.slug;
|
|
317
417
|
if (typeof launch.name === "string") merged.name = launch.name;
|
|
@@ -320,9 +420,9 @@ function resolvePrecedence(flags, siteRoot) {
|
|
|
320
420
|
}
|
|
321
421
|
if (flags.config && typeof flags.config === "string") {
|
|
322
422
|
try {
|
|
323
|
-
const configPath =
|
|
423
|
+
const configPath = resolve3(flags.config);
|
|
324
424
|
if (existsSync2(configPath)) {
|
|
325
|
-
const cfg = JSON.parse(
|
|
425
|
+
const cfg = JSON.parse(readFileSync3(configPath, "utf8"));
|
|
326
426
|
for (const [k, v] of Object.entries(cfg)) {
|
|
327
427
|
if (typeof v === "string" || typeof v === "boolean") merged[k] = v;
|
|
328
428
|
}
|
|
@@ -354,7 +454,7 @@ async function runCli(argv) {
|
|
|
354
454
|
}
|
|
355
455
|
const stream = new EventStream(outputMode);
|
|
356
456
|
if (flags.version || flags.v) {
|
|
357
|
-
stream.log(`@tryvitris/website-harness v${JSON.parse(
|
|
457
|
+
stream.log(`@tryvitris/website-harness v${JSON.parse(readFileSync3(resolve3(findHarnessRoot(), "package.json"), "utf8")).version}`);
|
|
358
458
|
return EXIT_CODES.SUCCESS;
|
|
359
459
|
}
|
|
360
460
|
if (flags.help || flags.h || command.length === 0) {
|
|
@@ -363,6 +463,7 @@ async function runCli(argv) {
|
|
|
363
463
|
Usage: vitris <command> [options]
|
|
364
464
|
|
|
365
465
|
Commands:
|
|
466
|
+
admin setup|status|verify Separate business admin; explicit cloud rollout
|
|
366
467
|
forms inspect|setup|status Inventory, provision, and check contact forms
|
|
367
468
|
doctor Check environment, dependencies, and site health
|
|
368
469
|
site create --dir DIR --slug SLUG Scaffold a new customer site repo (--from URL or --blank)
|
|
@@ -379,6 +480,7 @@ Commands:
|
|
|
379
480
|
verify Verify compilation and evaluate all quality gates
|
|
380
481
|
preview [--port PORT] Serve local verified dist preview
|
|
381
482
|
deploy [--provider cloudflare] Deploy site to Cloudflare Pages with smoke verification
|
|
483
|
+
--quick Build and deploy, skipping review and quality gates
|
|
382
484
|
cloudflare [doctor|whoami|login] Cloudflare credentials and permissions diagnostics
|
|
383
485
|
share [--run ID] Start local preview or design comparison review share
|
|
384
486
|
knowledge explain --stage STAGE Show the knowledge that stage would load. No model call.
|
|
@@ -390,6 +492,7 @@ Options:
|
|
|
390
492
|
--template-release RELEASE Optional template revision
|
|
391
493
|
--content TEXT Additional business content or instructions
|
|
392
494
|
--content-file FILE UTF-8 content file (instead of --content)
|
|
495
|
+
--admin New-site opt-in: prepare sibling admin next steps
|
|
393
496
|
--backend-origin URL Backend origin for a new site
|
|
394
497
|
--site DIR Site directory path (default: current directory)
|
|
395
498
|
--json Output single structured JSON result at completion
|
|
@@ -412,7 +515,10 @@ Options:
|
|
|
412
515
|
const secondary = command[1];
|
|
413
516
|
let result;
|
|
414
517
|
try {
|
|
415
|
-
if (primary === "
|
|
518
|
+
if (primary === "admin") {
|
|
519
|
+
const value = (key) => typeof opts[key] === "string" ? opts[key] : void 0;
|
|
520
|
+
result = await adminCommand(secondary || "status", { site: siteRoot, dir: value("dir"), domain: value("domain"), website: value("website"), tenant: value("tenant"), backendOrigin: value("backend-origin"), plan: !!opts.plan, apply: !!opts.apply, leadWrite: !!opts["lead-write"], grants: value("grants"), out: value("out"), dryRun });
|
|
521
|
+
} else if (primary === "forms") {
|
|
416
522
|
const list = (name) => typeof opts[name] === "string" ? opts[name].split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
417
523
|
result = await formsCommand(secondary || "status", { site: siteRoot, tenant: typeof opts.tenant === "string" ? opts.tenant : void 0, backendOrigin: typeof opts["backend-origin"] === "string" ? opts["backend-origin"] : void 0, origins: list("origins"), recipients: list("recipients"), mode: typeof opts.mode === "string" ? opts.mode : void 0, apply: !!opts.apply, testKeys: !!opts["test-keys"], cloudflare: !!opts.cloudflare, worker: typeof opts.worker === "string" ? opts.worker : void 0, config: typeof opts["forms-config"] === "string" ? opts["forms-config"] : void 0, dryRun });
|
|
418
524
|
} else if (primary === "doctor") {
|
|
@@ -424,6 +530,7 @@ Options:
|
|
|
424
530
|
} else if (primary === "site" && secondary === "create") {
|
|
425
531
|
result = await createSite({
|
|
426
532
|
...siteInputsFromFlags(opts),
|
|
533
|
+
admin: !!opts.admin,
|
|
427
534
|
dir: typeof opts.dir === "string" ? opts.dir : "",
|
|
428
535
|
slug: typeof opts.slug === "string" ? opts.slug : "",
|
|
429
536
|
name: typeof opts.name === "string" ? opts.name : void 0,
|
|
@@ -548,6 +655,7 @@ Options:
|
|
|
548
655
|
provider: "cloudflare",
|
|
549
656
|
environment: opts.environment === "production" ? "production" : "preview",
|
|
550
657
|
branch: typeof opts.branch === "string" ? opts.branch : void 0,
|
|
658
|
+
quick: flags.quick === true,
|
|
551
659
|
dryRun,
|
|
552
660
|
signal: abortController.signal,
|
|
553
661
|
onLog: (line) => stream.log(line)
|
|
@@ -557,14 +665,14 @@ Options:
|
|
|
557
665
|
if (sub === "doctor") {
|
|
558
666
|
result = await runDoctor({ provider: "cloudflare", operation: "pages" });
|
|
559
667
|
} else if (sub === "whoami") {
|
|
560
|
-
const probe =
|
|
668
|
+
const probe = spawnSync2(process.platform === "win32" ? "npx.cmd" : "npx", ["wrangler", "whoami"], { encoding: "utf8" });
|
|
561
669
|
result = {
|
|
562
670
|
ok: probe.status === 0,
|
|
563
671
|
exitCode: probe.status === 0 ? EXIT_CODES.SUCCESS : EXIT_CODES.CONFIG_OR_CLI_ERROR,
|
|
564
672
|
summary: probe.stdout || probe.stderr
|
|
565
673
|
};
|
|
566
674
|
} else if (sub === "login") {
|
|
567
|
-
const probe =
|
|
675
|
+
const probe = spawnSync2(process.platform === "win32" ? "npx.cmd" : "npx", ["wrangler", "login"], { stdio: "inherit" });
|
|
568
676
|
result = {
|
|
569
677
|
ok: probe.status === 0,
|
|
570
678
|
exitCode: probe.status === 0 ? EXIT_CODES.SUCCESS : EXIT_CODES.GENERIC_ERROR,
|
|
@@ -578,7 +686,7 @@ Options:
|
|
|
578
686
|
};
|
|
579
687
|
}
|
|
580
688
|
} else if (primary === "knowledge" && secondary === "explain") {
|
|
581
|
-
const explained = explainStage(
|
|
689
|
+
const explained = explainStage(resolve3(typeof opts.site === "string" ? opts.site : "."), typeof opts.stage === "string" ? opts.stage : "direction");
|
|
582
690
|
result = { ok: true, exitCode: EXIT_CODES.SUCCESS, summary: explained.text, data: explained };
|
|
583
691
|
} else if (primary === "share") {
|
|
584
692
|
result = await shareSite({
|
|
@@ -7,19 +7,19 @@ import {
|
|
|
7
7
|
import {
|
|
8
8
|
gatherBlankOnboard,
|
|
9
9
|
persistOnboard
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-PYCRUMCT.js";
|
|
11
11
|
import {
|
|
12
12
|
persistSiteInputs,
|
|
13
13
|
recordManagedKit,
|
|
14
14
|
resolveSiteInputsLive
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-XY73ZLHB.js";
|
|
16
16
|
import {
|
|
17
17
|
initialState,
|
|
18
18
|
parseGraphYaml
|
|
19
19
|
} from "./chunk-BBV7PMFC.js";
|
|
20
20
|
import {
|
|
21
21
|
findHarnessRoot
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-H3BS2EBR.js";
|
|
23
23
|
import {
|
|
24
24
|
save
|
|
25
25
|
} from "./chunk-T6PE4KN4.js";
|
|
@@ -103,6 +103,7 @@ async function createSite(opts) {
|
|
|
103
103
|
pkg.scripts.go = "node ./node_modules/@tryvitris/website-harness/bin/vitris.js run --site .";
|
|
104
104
|
pkg.scripts.eval = "node ./node_modules/@tryvitris/website-harness/bin/vitris.js verify --site .";
|
|
105
105
|
pkg.scripts.deploy = "node ./node_modules/@tryvitris/website-harness/bin/vitris.js deploy --site . --environment preview";
|
|
106
|
+
pkg.scripts["deploy:quick"] = "node ./node_modules/@tryvitris/website-harness/bin/vitris.js deploy --site . --environment preview --quick";
|
|
106
107
|
pkg.scripts["verify:browser"] = pkg.scripts.eval;
|
|
107
108
|
}
|
|
108
109
|
for (const [key, value] of Object.entries(pkg.scripts)) pkg.scripts[key] = value.replaceAll("__SLUG__", slug);
|
|
@@ -150,7 +151,9 @@ Preserved custom website from ${fromArg}.
|
|
|
150
151
|
2. npx playwright install chromium
|
|
151
152
|
3. npm run go
|
|
152
153
|
4. npm run preview
|
|
153
|
-
5. npm run deploy (temporary Cloudflare preview)
|
|
154
|
+
5. npm run deploy (build and verify locally, then upload a temporary Cloudflare preview)
|
|
155
|
+
|
|
156
|
+
Local builds and direct uploads are the default. GitHub stores source; no deployment workflow runs automatically. Keep dist/ and release bundles out of Git.
|
|
154
157
|
|
|
155
158
|
Custom source: preserved/. Verification: agent/reviews/preserve.json. See AGENTS.md and the installed harness docs/PRESERVE.md. Run vitris forms inspect and vitris forms setup to register and connect contact forms; backendOrigin alone does not enable submission or notifications. See docs/CONTACT-FORMS.md.
|
|
156
159
|
`);
|
|
@@ -160,10 +163,14 @@ Run npm run go to capture and verify the existing client-facing site. Keep its a
|
|
|
160
163
|
|
|
161
164
|
Repair source in preserved/ when agent/reviews/preserve.json reports a failure; run npm run go again. Never change reference screenshots or hashes to make a check pass. Do not overwrite source by recapturing. onboarding/preserve.json records the source and captured routes. Contact forms require explicit registration and public launch.json forms bindings. backendOrigin alone does not enable delivery. Default previews show a notice without submitting. See docs/CONTACT-FORMS.md.
|
|
162
165
|
|
|
163
|
-
Use npm run deploy
|
|
166
|
+
Use npm run deploy to build and verify locally and directly upload a temporary Cloudflare preview. Keep dist/ and release bundles out of Git. Hosted automation is opt-in; deployment/github-actions.yml.example is inactive by default. Production and customer domains are outside this mode. Scaffold a later redesign separately from the same source URL.
|
|
164
167
|
`);
|
|
165
168
|
}
|
|
166
|
-
|
|
169
|
+
if (mode === "preserve") {
|
|
170
|
+
const instructions = join(dest, "AGENTS.md");
|
|
171
|
+
writeFileSync(instructions, readFileSync(instructions, "utf8") + "\nFor a requested sibling business admin, read agent/refs/admin.md and the installed harness docs/ADMIN.md. Use vitris admin setup --plan then reviewed --apply. Preserve current forms and backend identity; cloud provisioning, authentication and deployment remain separate.\n");
|
|
172
|
+
}
|
|
173
|
+
const workflow = join(dest, "deployment", "github-actions.yml.example");
|
|
167
174
|
if (existsSync(workflow)) {
|
|
168
175
|
if (mode === "preserve") {
|
|
169
176
|
writeFileSync(workflow, `name: Preserve preview
|
|
@@ -197,6 +204,18 @@ jobs:
|
|
|
197
204
|
copyMarkdown: gathered.copyMarkdown
|
|
198
205
|
});
|
|
199
206
|
persistSiteInputs(dest, inputs);
|
|
207
|
+
if (opts.admin) {
|
|
208
|
+
writeFileSync(join(dest, "ADMIN-SETUP.md"), `# Optional business admin
|
|
209
|
+
|
|
210
|
+
Read node_modules/@tryvitris/website-harness/docs/ADMIN.md and agent/refs/admin.md.
|
|
211
|
+
|
|
212
|
+
Prepare a separate sibling installation after confirming the exact website origin, existing tenant ID and backend origin:
|
|
213
|
+
|
|
214
|
+
vitris admin setup --site . --dir ../${slug}-admin --domain admin.CUSTOMER_DOMAIN --website https://www.CUSTOMER_DOMAIN --tenant EXISTING_TENANT_ID --backend-origin https://BACKEND --plan
|
|
215
|
+
|
|
216
|
+
Review artifacts/admin/setup-plan.json, then repeat with --apply and a dedicated VITRIS_ADMIN_API_TOKEN in the operator environment. This opt-in has created instructions only. No backend, grant, account, collection, authentication, DNS or cloud resources have been changed.
|
|
217
|
+
`);
|
|
218
|
+
}
|
|
200
219
|
const graphPath = join(dest, "agent", "graph.yaml");
|
|
201
220
|
if (existsSync(graphPath)) {
|
|
202
221
|
const graph = parseGraphYaml(readFileSync(graphPath, "utf8"));
|
|
@@ -209,7 +228,7 @@ jobs:
|
|
|
209
228
|
return {
|
|
210
229
|
ok: true,
|
|
211
230
|
exitCode: EXIT_CODES.SUCCESS,
|
|
212
|
-
summary: `Created site ${slug} at ${dest}`,
|
|
231
|
+
summary: `Created site ${slug} at ${dest}${opts.admin ? "; optional admin next steps: ADMIN-SETUP.md" : ""}`,
|
|
213
232
|
data: { dest, slug, name }
|
|
214
233
|
};
|
|
215
234
|
}
|