@deneb-ui/cli 2.0.98 → 2.0.99

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 CHANGED
@@ -60,11 +60,20 @@ npm install -D @deneb-ui/cli
60
60
  | `deneb zip` | Create upload-ready `fivora-template.zip` |
61
61
  | `deneb doctor` | Environment, dependency, and export diagnostics |
62
62
  | `deneb add <component>` | Copy DENEB UI components into your project |
63
- | `deneb lab` | Launch local visual editing preview lab |
63
+ | `deneb lab` | Launch the local visual editor, preview changes, and save them to the declared `siteDataFile` |
64
64
  | `deneb update` | Update `@deneb-ui/*` packages and synced components |
65
65
 
66
66
  Full reference: [deneb.fivora.site/docs/cli](https://deneb.fivora.site/docs/cli)
67
67
 
68
+ ### Local preview before upload
69
+
70
+ Run `deneb lab .` in a template project, then paste the printed controller URL
71
+ and token into **Developer Portal → Local Test Lab**. Content and style edits are
72
+ applied to the preview immediately. Use **Save local** to persist the current
73
+ editor state to the `siteDataFile` declared in `fivora-template.json`; the write
74
+ stays on your machine and no template files are uploaded. Run **Test** after
75
+ saving to perform the same local preflight checks used before packaging.
76
+
68
77
  ---
69
78
 
70
79
  ## What `deneb init` does (ARC)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deneb-ui/cli",
3
- "version": "2.0.98",
3
+ "version": "2.0.99",
4
4
  "description": "Official DENEB CLI — scaffold, convert, validate, and package Fivora-ready storefront templates.",
5
5
  "bin": {
6
6
  "deneb": "bin/index.js",
@@ -49,7 +49,7 @@
49
49
  "license": "MIT",
50
50
  "dependencies": {
51
51
  "@babel/parser": "^7.28.0",
52
- "@deneb-ui/core": "^2.0.98",
52
+ "@deneb-ui/core": "^2.0.99",
53
53
  "@octokit/rest": "^22.0.1",
54
54
  "adm-zip": "^0.6.0",
55
55
  "dotenv": "^17.4.2",
@@ -12,7 +12,13 @@ const { scanProject, buildDependencyGraph } = require('../scanner.cjs');
12
12
  const { analyzeFile } = require('../semantic.cjs');
13
13
  const { planTransformations } = require('../planner.cjs');
14
14
  const { applyFilePlan } = require('../transformer.cjs');
15
- const { buildFieldPath, inferFieldName, isListActionCtaKey } = require('../field-paths.cjs');
15
+ const {
16
+ buildFieldPath,
17
+ classifyFieldType,
18
+ inferFieldName,
19
+ isListActionCtaKey,
20
+ } = require('../field-paths.cjs');
21
+ const { normalizeFieldType } = require('../fivora-schema-authority.cjs');
16
22
  const { enrichSchemasFromContent } = require('../manifest.cjs');
17
23
  const { runDenebArc } = require('../index.cjs');
18
24
  const { parseSource } = require('../ast.cjs');
@@ -67,6 +73,16 @@ test('field-paths recognizes list action CTA keys', () => {
67
73
  assert.equal(isListActionCtaKey('features'), false);
68
74
  });
69
75
 
76
+ test('price fields use numeric data while currency and formatted prices stay text', () => {
77
+ assert.equal(normalizeFieldType('price', 'price'), 'number');
78
+ assert.equal(normalizeFieldType('currency', 'currency'), 'text');
79
+ assert.equal(classifyFieldType('text', 1000, 'price'), 'number');
80
+ assert.equal(classifyFieldType('text', '1000', 'price'), 'number');
81
+ assert.equal(classifyFieldType('text', '1000.50', 'amount'), 'number');
82
+ assert.equal(classifyFieldType('text', 'Rs. 1,000', 'price'), 'text');
83
+ assert.equal(classifyFieldType('text', 'LKR 1000', 'price'), 'text');
84
+ });
85
+
70
86
  test('manifest enrichSchemasFromContent registers list CTA and paired directions fields', () => {
71
87
  const sections = [
72
88
  {
@@ -447,10 +463,30 @@ test('static-array collections become list contracts without changing render log
447
463
  const products = home.fields.find((f) => f.key === 'products');
448
464
  assert.equal(products.type, 'list');
449
465
  assert.deepEqual(products.fields.map((f) => f.key).sort(), ['description', 'image', 'price', 'title']);
466
+ assert.equal(products.fields.find((field) => field.key === 'price').type, 'text');
450
467
  assert.equal(siteData.content.home.products.length, 3);
451
468
  assert.equal(siteData.content.home.products[0].title, 'Minimalist Smart Watch');
452
469
  });
453
470
 
471
+ test('numeric catalog prices stay numbers in generated site data and editor schema', () => {
472
+ const dir = copyOf(STOREFRONT_FIXTURE);
473
+ const gridPath = path.join(dir, 'src', 'components', 'ProductGrid.tsx');
474
+ const grid = fs
475
+ .readFileSync(gridPath, 'utf8')
476
+ .replace(/price: '\$(\d+)\.00'/g, 'price: $1');
477
+ fs.writeFileSync(gridPath, grid, 'utf8');
478
+
479
+ silence(() => runDenebArc(dir, 'numeric-price-store', { telemetry: 'off' }));
480
+ const { manifest, siteData } = auditFivora(dir);
481
+ const home = manifest.editorSchema.sections.find((section) => section.id === 'home');
482
+ const products = home.fields.find((field) => field.key === 'products');
483
+ const price = products.fields.find((field) => field.key === 'price');
484
+
485
+ assert.equal(price.type, 'number');
486
+ assert.equal(siteData.content.home.products[0].price, 249);
487
+ assert.equal(typeof siteData.content.home.products[0].price, 'number');
488
+ });
489
+
454
490
  test('collections holding component references still bind primitive item fields', () => {
455
491
  const dir = copyOf(STOREFRONT_FIXTURE);
456
492
  silence(() => runDenebArc(dir, 'acme-store', { telemetry: 'off' }));
@@ -3923,4 +3959,3 @@ test('ARC v3 Phase 18: formatBlockedExplanationTerminal renders actionable remed
3923
3959
 
3924
3960
 
3925
3961
 
3926
-
@@ -181,7 +181,14 @@ function classifyFieldType(kind, value, fieldName = '') {
181
181
  /price|amount|rating|count|score|stars|qty|quantity|discount|fee|cost/i.test(name)
182
182
  ) {
183
183
  if (typeof value === 'string' && /\$|lkr|usd|rs\.?\s*\d/i.test(value)) return 'text';
184
- return typeof value === 'number' ? 'number' : 'text';
184
+ if (typeof value === 'number') return 'number';
185
+ if (
186
+ typeof value === 'string' &&
187
+ /^[+-]?(?:\d+\.?\d*|\.\d+)$/.test(value.trim())
188
+ ) {
189
+ return 'number';
190
+ }
191
+ return 'text';
185
192
  }
186
193
  if (typeof value === 'boolean' || /enabled|visible|show|active/i.test(name)) return 'boolean';
187
194
  if (kind === 'textarea' || (typeof value === 'string' && (value.length > 70 || value.includes('\n')))) return 'textarea';
@@ -25,8 +25,8 @@ const PLATFORM_ALLOWED_TYPES = new Set([
25
25
  ]);
26
26
 
27
27
  const TYPE_COERCION_MAP = {
28
- currency: 'text', // In Fivora storefronts, prices with symbols (e.g. '$249' or 'LKR 5,000') must be text or number
29
- price: 'text',
28
+ currency: 'text',
29
+ price: 'number',
30
30
  richtext: 'textarea',
31
31
  phone: 'tel',
32
32
  link: 'url',
@@ -370,7 +370,12 @@ const PLATFORM_CONTROLLED_PATHS = new Set(
370
370
  );
371
371
 
372
372
  function slimListItems(items, itemFields) {
373
- const keys = (itemFields || []).map((field) => field.key).filter(Boolean);
373
+ const fieldsByKey = new Map(
374
+ (itemFields || [])
375
+ .filter((field) => field?.key)
376
+ .map((field) => [field.key, field]),
377
+ );
378
+ const keys = [...fieldsByKey.keys()];
374
379
  if (!keys.length || !Array.isArray(items)) return items || [];
375
380
  return items.map((item) => {
376
381
  if (!item || typeof item !== 'object' || Array.isArray(item)) return item;
@@ -378,7 +383,14 @@ function slimListItems(items, itemFields) {
378
383
  for (const key of keys) {
379
384
  if (!(key in item)) continue;
380
385
  if (Array.isArray(item[key])) continue;
381
- out[key] = item[key];
386
+ const field = fieldsByKey.get(key);
387
+ const value = item[key];
388
+ out[key] =
389
+ field?.type === 'number' &&
390
+ typeof value === 'string' &&
391
+ /^[+-]?(?:\d+\.?\d*|\.\d+)$/.test(value.trim())
392
+ ? Number(value)
393
+ : value;
382
394
  }
383
395
  return Object.keys(out).length ? out : item;
384
396
  });
@@ -33,6 +33,7 @@ const { resolveImportSpecifier } = require('./scanner.cjs');
33
33
  const { resolvePublicAssetUrl } = require('./ir-builder.cjs');
34
34
  const { analyzeElementFragments } = require('./text-fragment-analyzer.cjs');
35
35
  const { BROAD_CONTENT_CONTAINERS } = require('./fivora-contract.cjs');
36
+ const { classifyFieldType } = require('./field-paths.cjs');
36
37
 
37
38
  const TECHNICAL_TEXT_RE = /^(true|false|null|undefined|px|rem|em|auto|hidden|flex|grid|sr-only)$/i;
38
39
  const ARIA_ONLY_RE = /^(aria-|data-state|data-slot|data-orientation)/;
@@ -1105,6 +1106,21 @@ function analyzeFile(optionsOrFile, codeArg, profileArg) {
1105
1106
  const stringItems = arr.items.every((item) => item.type === 'string');
1106
1107
  const rootTagName = getJsxName(mapInfo.rootElement);
1107
1108
  const isChildCustomComponent = /^[A-Z]/.test(rootTagName);
1109
+ const sampleItem = objectItems
1110
+ ? arr.items.find((item) => item?.value && typeof item.value === 'object')?.value
1111
+ : null;
1112
+
1113
+ // JSX text positions alone look like generic text, but commerce values
1114
+ // such as a bare numeric `price` must remain numeric for catalog range
1115
+ // filters, sorting, totals, and database persistence. Formatted values
1116
+ // such as "$249" intentionally remain text for legacy templates.
1117
+ if (sampleItem) {
1118
+ for (const [key, role] of itemUsage) {
1119
+ if (role !== 'text') continue;
1120
+ const inferredRole = classifyFieldType('text', sampleItem[key], key);
1121
+ if (inferredRole !== 'text') itemUsage.set(key, inferredRole);
1122
+ }
1123
+ }
1108
1124
 
1109
1125
  // If the map returns a custom child component (e.g. <ProductCard product={item} />),
1110
1126
  // derive item fields from the sample object items if direct JSX member usage was empty
@@ -1112,13 +1128,7 @@ function analyzeFile(optionsOrFile, codeArg, profileArg) {
1112
1128
  const sampleObj = arr.items[0].value;
1113
1129
  for (const key of Object.keys(sampleObj)) {
1114
1130
  if (/^(id|_id|key|slug)$/i.test(key)) continue;
1115
- const role = /image|photo|avatar|icon|thumb|img/i.test(key)
1116
- ? 'image'
1117
- : /price|cost|amount|count|rating|stars/i.test(key)
1118
- ? 'number'
1119
- : /url|link|href/i.test(key)
1120
- ? 'url'
1121
- : 'text';
1131
+ const role = classifyFieldType('text', sampleObj[key], key);
1122
1132
  itemUsage.set(key, role);
1123
1133
  }
1124
1134
  }
@@ -1207,4 +1217,3 @@ module.exports = {
1207
1217
  isStaticSkipText,
1208
1218
  resolveChildText,
1209
1219
  };
1210
-
@@ -1,17 +1,18 @@
1
1
  #!/usr/bin/env node
2
- const Z=require("node:crypto"),_=require("node:fs"),T=require("node:http"),f=require("node:path"),{spawn:D}=require("node:child_process"),q=4174,ee=4173,U=96*1024,v="127.0.0.1",W="/__fivora_local_preview",te=`(()=>{const m=["market","place"].join(""),O="__"+m.toUpperCase()+"_LOCAL_VISUAL_BRIDGE__",Y=m.toUpperCase()+"_PREVIEW_",v=t=>Y+t;if(window.__FIVORA_LOCAL_VISUAL_BRIDGE__||window[O])return;window.__FIVORA_LOCAL_VISUAL_BRIDGE__=!0,window[O]=!0;const F="FIVORA_PREVIEW_EDIT_MODE",K=v("EDIT_MODE"),N="FIVORA_PREVIEW_ELEMENT_CLICKED",W=v("ELEMENT_CLICKED"),k="FIVORA_PREVIEW_READY",j=v("READY"),R="data-preview-field-path",V="data-preview-list-path",x="data-preview-item-path",C="data-fivora-local-edit-target",_="data-fivora-local-selected-target",P="data-"+m+"-local-edit-target",h="data-"+m+"-local-selected-target",H="[data-preview-field-path], [data-preview-list-path], [data-preview-item-path]";let l=window.parent!==window,f=new Map,s=null,o=null,g="*";const T=document.createElement("style");T.setAttribute("data-fivora-local-visual-bridge",""),T.setAttribute("data-"+m+"-local-visual-bridge",""),T.textContent=["["+C+"], ["+P+"] {"," outline: 2px solid #06b6d4 !important;"," outline-offset: 3px !important;"," cursor: pointer !important;"," box-shadow: 0 0 0 5px rgba(6, 182, 212, 0.16) !important;","}","["+_+"], ["+h+"] {"," outline: 2px dashed #06b6d4 !important;"," outline-offset: 3px !important;"," box-shadow: 0 0 0 5px rgba(6, 182, 212, 0.18) !important;","}",'html[data-fivora-local-edit-mode="true"] [data-preview-field-path],','html[data-fivora-local-edit-mode="true"] [data-preview-list-path],','html[data-fivora-local-edit-mode="true"] [data-preview-item-path],','html[data-fivora-local-edit-mode="true"] [data-preview-field-path],','html[data-fivora-local-edit-mode="true"] [data-preview-list-path],','html[data-fivora-local-edit-mode="true"] [data-preview-item-path] {'," cursor: pointer !important;","}"].join(\`
3
- \`),document.head.appendChild(T),document.documentElement.setAttribute("data-fivora-local-edit-mode",String(l)),document.documentElement.setAttribute("data-fivora-local-edit-mode",String(l));function $(t){return t.source!==window.parent?!1:(g==="*"&&t.origin&&t.origin!=="null"&&(g=t.origin),g==="*"||t.origin===g)}function I(t){window.parent.postMessage(t,g)}function L(){s&&(s.removeAttribute(C),s.removeAttribute(P)),s=null}function X(t){o&&o!==t&&(o.removeAttribute(_),o.removeAttribute(h)),o=t,o.setAttribute(_,"true"),o.setAttribute(h,"true")}function q(){o&&(o.removeAttribute(_),o.removeAttribute(h)),o=null}function G(t){return t instanceof Element?t.closest(H):null}function z(t,e){const d=f.get(e);return d&&["string","number","boolean"].includes(typeof d.value)?d.value:t instanceof HTMLImageElement?t.currentSrc||t.src||"":(t.textContent||"").trim()}function J(t){if(!l)return;const e=G(t.target);!e||e===s||(L(),s=e,s.setAttribute(C,"true"))}function Q(t){if(!l||!s)return;const e=t.relatedTarget;e instanceof Node&&s.contains(e)||L()}function Z(t,e,d){const A=[],p=new Set(e?[e]:[]),n=d||e&&e.replace(/(\\[\\d+\\])?\\.\\w+$/,"").replace(/\\[\\d+\\]$/,"");if(n)for(const[i,a]of f)i!==e&&!p.has(i)&&(i.startsWith(n+".")||i.startsWith(n+"["))&&a?.kind!=="collection"&&(p.add(i),A.push({path:i,label:a.label||i.split(".").pop()||"Content",type:a.type||"text"}));let r=t instanceof Element?t:null,c=0;for(;r&&r!==document.body&&c<3;){const i=r.getAttribute(R);if(i&&i!==e&&!p.has(i)){const a=f.get(i);a?.kind!=="collection"&&(p.add(i),A.push({path:i,label:a?.label||i.split(".").pop()||"Content",type:a?.type||"text"}))}r=r.parentElement,c+=1}return A}function tt(t){if(!l)return;const e=G(t.target);if(!e)return;t.preventDefault(),t.stopPropagation(),t.stopImmediatePropagation(),X(e);const d=e.closest("["+R+"]"),A=e.closest("["+V+"]"),p=e.closest("["+x+"]"),n=d?.getAttribute(R)||null,r=A?.getAttribute(V)||null,c=p?.getAttribute(x)||null,i=n?f.get(n):null,a=e.getBoundingClientRect(),U=c?.match(/\\[(\\d+)\\](?!.*\\[\\d+\\])/),y=[],E=(M,w,b)=>{!w||y.some(S=>S.key===w)||y.push({kind:M,key:w,label:b})};E("element",n,"This element"),E("card",c,"This card"),E("grid",r,"Entire grid");const D=e.closest("[data-preview-page-key]")?.getAttribute("data-preview-page-key")||"",u=e.closest("section,[data-design-section],[data-section-id]");if(u){const M=u.getAttribute("data-design-section")||u.getAttribute("data-section-id")||u.id,b=(u.parentElement?Array.from(u.parentElement.children).filter(et=>et.tagName==="SECTION"):[]).indexOf(u)+1,S=M||(b>0?"nth-"+b:"");E("section",S?"section:"+(D||"all")+":"+S:null,"This section")}E("page",D?"page:"+D:null,"Current page"),E("site","site:all","Whole website");const B={type:N,fieldPath:n,fieldValue:n?z(d||e,n):"",elementTag:e.tagName.toLowerCase(),isImage:e instanceof HTMLImageElement||i?.type==="image",boundingRect:{top:a.top,left:a.left,width:a.width,height:a.height},collectionPath:r,listPath:r,itemPath:c,itemIndex:U?Number(U[1]):null,descriptorKind:n?"field":r?"collection":null,relatedFields:Z(e,n,c),styleScopes:y};I(B),I({...B,type:W})}window.addEventListener("message",t=>{!$(t)||!t.data||typeof t.data!="object"||t.data.type!==F&&t.data.type!==K||(l=t.data.editMode===!0,f=new Map(Array.isArray(t.data.fields)?t.data.fields.filter(e=>e&&typeof e.path=="string").map(e=>[e.path,e]):[]),document.documentElement.setAttribute("data-fivora-local-edit-mode",String(l)),document.documentElement.setAttribute("data-fivora-local-edit-mode",String(l)),l||(L(),q()))}),document.addEventListener("mouseover",J,!0),document.addEventListener("mouseout",Q,!0),document.addEventListener("click",tt,!0),I({type:k,pathname:window.location.pathname}),I({type:j,pathname:window.location.pathname})})();`;let w=te;try{const e=require("./template-preview-focus-bridge.cjs");typeof e=="string"&&e.trim()&&(w=e)}catch{}const ae=`var __name = typeof __name === "function" ? __name : ((target, value) => (typeof Object.defineProperty === "function" ? Object.defineProperty(target, "name", { value, configurable: true }) : target));
4
- `;w.includes("__name")&&!w.includes("var __name")&&(w=`${ae}${w}`);function p(e){process.stderr.write(`Local Template Lab: ${e}
5
- `),process.exit(1)}try{new Function(w)}catch(e){p(`Visual editor bridge is invalid: ${e.message}`)}function N(e,t,a){if(e===void 0)return a;const n=Number(e);return(!Number.isInteger(n)||n<1024||n>65535)&&p(`${t} must be a port between 1024 and 65535.`),n}function ne(e){const t=[...e];let a="",n,r,s=!1;for(let m=0;m<t.length;m+=1){const d=t[m];d==="--api-port"?(n=t[m+1],m+=1):d.startsWith("--api-port=")?n=d.slice(11):d==="--preview-port"?(r=t[m+1],m+=1):d.startsWith("--preview-port=")?r=d.slice(15):d==="--skip-install"?s=!0:d==="--help"||d==="-h"?(process.stdout.write(["Fivora Local Template Lab","","Usage:"," npm run lab -- <template-directory> [options]","","Options:"," --api-port <port> Loopback controller port (default: 4174)"," --preview-port <port> Template dev-server port (default: 4173)"," --skip-install Do not install missing local dependencies",""].join(`
6
- `)),process.exit(0)):d.startsWith("-")?p(`Unknown option: ${d}`):a?p(`Unexpected argument: ${d}`):a=d}return a||p("A template directory is required. Run with --help for usage."),{templatePath:f.resolve(a),apiPort:N(n,"--api-port",q),previewPort:N(r,"--preview-port",ee),skipInstall:s}}function $(e,t){try{return JSON.parse(_.readFileSync(e,"utf8"))}catch(a){p(`${t} is missing or invalid at ${e}: ${a instanceof Error?a.message:"unknown error"}`)}}const i=ne(process.argv.slice(2));_.existsSync(i.templatePath)||p(`Template directory does not exist: ${i.templatePath}`),_.statSync(i.templatePath).isDirectory()||p(`Template path must be a directory: ${i.templatePath}`);const re=["fivora-template.json","fivora-template.json"].find(e=>_.existsSync(f.join(i.templatePath,e)))||"fivora-template.json",B=f.join(i.templatePath,re),ie=f.join(i.templatePath,"package.json"),c=$(B,"Template manifest"),I=$(ie,"package.json");c.framework!=="nextjs-static-export"&&p('Template manifest framework must be "nextjs-static-export".'),(!I.scripts||typeof I.scripts.dev!="string")&&p("Template package.json must define a dev script for live preview."),(typeof c.siteDataFile!="string"||!c.siteDataFile.trim())&&p("Template manifest siteDataFile is required.");const j=f.resolve(i.templatePath,c.siteDataFile.trim()),H=f.relative(i.templatePath,j);(H.startsWith("..")||f.isAbsolute(H))&&p("Template manifest siteDataFile must stay inside the template directory.");const G=Z.randomBytes(24).toString("base64url"),y=`http://${v}:${i.apiPort}`,k=`http://${v}:${i.previewPort}`,x=`${y}${W}`,oe=f.join(__dirname,"deneb-template-validator.cjs");let P=!1,u=null,g=null,b=null,A=null,R=0;const X=5;let V="",C="";const o={protocolVersion:2,connected:!0,templateName:typeof c.name=="string"&&c.name.trim()?c.name.trim():typeof I.name=="string"?I.name:f.basename(i.templatePath),templatePath:i.templatePath,previewUrl:x,apiUrl:y,devStatus:"starting",devError:null,startedAt:new Date().toISOString(),validation:{status:"idle",startedAt:null,completedAt:null,exitCode:null}};function l(e,t){const a=t.toString();e==="dev"?V=`${V}${a}`.slice(-U):C=`${C}${a}`.slice(-U),process.stdout.write(a)}function J(){return{...o,devLog:V,validationLog:C}}function se(e,t){const a=e.headers.origin;a&&/^(https?:\/\/|null$)/.test(a)&&(t.setHeader("Access-Control-Allow-Origin",a),t.setHeader("Vary","Origin")),t.setHeader("Access-Control-Allow-Headers","Authorization, Content-Type"),t.setHeader("Access-Control-Allow-Methods","GET, POST, OPTIONS"),t.setHeader("Access-Control-Allow-Private-Network","true"),t.setHeader("Cache-Control","no-store"),t.setHeader("X-Content-Type-Options","nosniff")}function h(e,t,a){const n=JSON.stringify(a);e.statusCode=t,e.setHeader("Content-Type","application/json; charset=utf-8"),e.setHeader("Content-Length",Buffer.byteLength(n)),e.end(n)}function le(e){return e.headers.authorization===`Bearer ${G}`}function de(){try{return JSON.parse(_.readFileSync(j,"utf8"))}catch(e){throw new Error(`Unable to read ${c.siteDataFile}: ${e instanceof Error?e.message:"unknown error"}`)}}function pe(){return g?!1:(C="",o.validation={status:"running",startedAt:new Date().toISOString(),completedAt:null,exitCode:null},g=D(process.execPath,[oe,"validate",i.templatePath],{cwd:__dirname,env:process.env,stdio:["ignore","pipe","pipe"]}),g.stdout.on("data",e=>l("validation",e)),g.stderr.on("data",e=>l("validation",e)),g.on("error",e=>{l("validation",`
2
+ const q=require("node:crypto"),m=require("node:fs"),T=require("node:http"),h=require("node:path"),{spawn:$}=require("node:child_process"),ee=4174,te=4173,W=96*1024,ae=10*1024*1024,g="127.0.0.1",B="/__fivora_local_preview",ne=`(()=>{const m=["market","place"].join(""),O="__"+m.toUpperCase()+"_LOCAL_VISUAL_BRIDGE__",Y=m.toUpperCase()+"_PREVIEW_",v=t=>Y+t;if(window.__FIVORA_LOCAL_VISUAL_BRIDGE__||window[O])return;window.__FIVORA_LOCAL_VISUAL_BRIDGE__=!0,window[O]=!0;const F="FIVORA_PREVIEW_EDIT_MODE",K=v("EDIT_MODE"),N="FIVORA_PREVIEW_ELEMENT_CLICKED",W=v("ELEMENT_CLICKED"),k="FIVORA_PREVIEW_READY",j=v("READY"),R="data-preview-field-path",V="data-preview-list-path",x="data-preview-item-path",C="data-fivora-local-edit-target",_="data-fivora-local-selected-target",P="data-"+m+"-local-edit-target",h="data-"+m+"-local-selected-target",H="[data-preview-field-path], [data-preview-list-path], [data-preview-item-path]";let l=window.parent!==window,f=new Map,s=null,o=null,g="*";const T=document.createElement("style");T.setAttribute("data-fivora-local-visual-bridge",""),T.setAttribute("data-"+m+"-local-visual-bridge",""),T.textContent=["["+C+"], ["+P+"] {"," outline: 2px solid #06b6d4 !important;"," outline-offset: 3px !important;"," cursor: pointer !important;"," box-shadow: 0 0 0 5px rgba(6, 182, 212, 0.16) !important;","}","["+_+"], ["+h+"] {"," outline: 2px dashed #06b6d4 !important;"," outline-offset: 3px !important;"," box-shadow: 0 0 0 5px rgba(6, 182, 212, 0.18) !important;","}",'html[data-fivora-local-edit-mode="true"] [data-preview-field-path],','html[data-fivora-local-edit-mode="true"] [data-preview-list-path],','html[data-fivora-local-edit-mode="true"] [data-preview-item-path],','html[data-fivora-local-edit-mode="true"] [data-preview-field-path],','html[data-fivora-local-edit-mode="true"] [data-preview-list-path],','html[data-fivora-local-edit-mode="true"] [data-preview-item-path] {'," cursor: pointer !important;","}"].join(\`
3
+ \`),document.head.appendChild(T),document.documentElement.setAttribute("data-fivora-local-edit-mode",String(l)),document.documentElement.setAttribute("data-fivora-local-edit-mode",String(l));function $(t){return t.source!==window.parent?!1:(g==="*"&&t.origin&&t.origin!=="null"&&(g=t.origin),g==="*"||t.origin===g)}function I(t){window.parent.postMessage(t,g)}function L(){s&&(s.removeAttribute(C),s.removeAttribute(P)),s=null}function X(t){o&&o!==t&&(o.removeAttribute(_),o.removeAttribute(h)),o=t,o.setAttribute(_,"true"),o.setAttribute(h,"true")}function q(){o&&(o.removeAttribute(_),o.removeAttribute(h)),o=null}function G(t){return t instanceof Element?t.closest(H):null}function z(t,e){const d=f.get(e);return d&&["string","number","boolean"].includes(typeof d.value)?d.value:t instanceof HTMLImageElement?t.currentSrc||t.src||"":(t.textContent||"").trim()}function J(t){if(!l)return;const e=G(t.target);!e||e===s||(L(),s=e,s.setAttribute(C,"true"))}function Q(t){if(!l||!s)return;const e=t.relatedTarget;e instanceof Node&&s.contains(e)||L()}function Z(t,e,d){const A=[],p=new Set(e?[e]:[]),n=d||e&&e.replace(/(\\[\\d+\\])?\\.\\w+$/,"").replace(/\\[\\d+\\]$/,"");if(n)for(const[i,a]of f)i!==e&&!p.has(i)&&(i.startsWith(n+".")||i.startsWith(n+"["))&&a?.kind!=="collection"&&(p.add(i),A.push({path:i,label:a.label||i.split(".").pop()||"Content",type:a.type||"text"}));let r=t instanceof Element?t:null,c=0;for(;r&&r!==document.body&&c<3;){const i=r.getAttribute(R);if(i&&i!==e&&!p.has(i)){const a=f.get(i);a?.kind!=="collection"&&(p.add(i),A.push({path:i,label:a?.label||i.split(".").pop()||"Content",type:a?.type||"text"}))}r=r.parentElement,c+=1}return A}function tt(t){if(!l)return;const e=G(t.target);if(!e)return;t.preventDefault(),t.stopPropagation(),t.stopImmediatePropagation(),X(e);const d=e.closest("["+R+"]"),A=e.closest("["+V+"]"),p=e.closest("["+x+"]"),n=d?.getAttribute(R)||null,r=A?.getAttribute(V)||null,c=p?.getAttribute(x)||null,i=n?f.get(n):null,a=e.getBoundingClientRect(),U=c?.match(/\\[(\\d+)\\](?!.*\\[\\d+\\])/),y=[],E=(M,w,b)=>{!w||y.some(S=>S.key===w)||y.push({kind:M,key:w,label:b})};E("element",n,"This element"),E("card",c,"This card"),E("grid",r,"Entire grid");const D=e.closest("[data-preview-page-key]")?.getAttribute("data-preview-page-key")||"",u=e.closest("section,[data-design-section],[data-section-id]");if(u){const M=u.getAttribute("data-design-section")||u.getAttribute("data-section-id")||u.id,b=(u.parentElement?Array.from(u.parentElement.children).filter(et=>et.tagName==="SECTION"):[]).indexOf(u)+1,S=M||(b>0?"nth-"+b:"");E("section",S?"section:"+(D||"all")+":"+S:null,"This section")}E("page",D?"page:"+D:null,"Current page"),E("site","site:all","Whole website");const B={type:N,fieldPath:n,fieldValue:n?z(d||e,n):"",elementTag:e.tagName.toLowerCase(),isImage:e instanceof HTMLImageElement||i?.type==="image",boundingRect:{top:a.top,left:a.left,width:a.width,height:a.height},collectionPath:r,listPath:r,itemPath:c,itemIndex:U?Number(U[1]):null,descriptorKind:n?"field":r?"collection":null,relatedFields:Z(e,n,c),styleScopes:y};I(B),I({...B,type:W})}window.addEventListener("message",t=>{!$(t)||!t.data||typeof t.data!="object"||t.data.type!==F&&t.data.type!==K||(l=t.data.editMode===!0,f=new Map(Array.isArray(t.data.fields)?t.data.fields.filter(e=>e&&typeof e.path=="string").map(e=>[e.path,e]):[]),document.documentElement.setAttribute("data-fivora-local-edit-mode",String(l)),document.documentElement.setAttribute("data-fivora-local-edit-mode",String(l)),l||(L(),q()))}),document.addEventListener("mouseover",J,!0),document.addEventListener("mouseout",Q,!0),document.addEventListener("click",tt,!0),I({type:k,pathname:window.location.pathname}),I({type:j,pathname:window.location.pathname})})();`;let E=ne;try{const e=require("./template-preview-focus-bridge.cjs");typeof e=="string"&&e.trim()&&(E=e)}catch{}const re=`var __name = typeof __name === "function" ? __name : ((target, value) => (typeof Object.defineProperty === "function" ? Object.defineProperty(target, "name", { value, configurable: true }) : target));
4
+ `;E.includes("__name")&&!E.includes("var __name")&&(E=`${re}${E}`);function u(e){process.stderr.write(`Local Template Lab: ${e}
5
+ `),process.exit(1)}try{new Function(E)}catch(e){u(`Visual editor bridge is invalid: ${e.message}`)}function j(e,t,n){if(e===void 0)return n;const a=Number(e);return(!Number.isInteger(a)||a<1024||a>65535)&&u(`${t} must be a port between 1024 and 65535.`),a}function ie(e){const t=[...e];let n="",a,r,s=!1;for(let c=0;c<t.length;c+=1){const o=t[c];o==="--api-port"?(a=t[c+1],c+=1):o.startsWith("--api-port=")?a=o.slice(11):o==="--preview-port"?(r=t[c+1],c+=1):o.startsWith("--preview-port=")?r=o.slice(15):o==="--skip-install"?s=!0:o==="--help"||o==="-h"?(process.stdout.write(["Fivora Local Template Lab","","Usage:"," npm run lab -- <template-directory> [options]","","Options:"," --api-port <port> Loopback controller port (default: 4174)"," --preview-port <port> Template dev-server port (default: 4173)"," --skip-install Do not install missing local dependencies",""].join(`
6
+ `)),process.exit(0)):o.startsWith("-")?u(`Unknown option: ${o}`):n?u(`Unexpected argument: ${o}`):n=o}return n||u("A template directory is required. Run with --help for usage."),{templatePath:h.resolve(n),apiPort:j(a,"--api-port",ee),previewPort:j(r,"--preview-port",te),skipInstall:s}}function x(e,t){try{return JSON.parse(m.readFileSync(e,"utf8"))}catch(n){u(`${t} is missing or invalid at ${e}: ${n instanceof Error?n.message:"unknown error"}`)}}const i=ie(process.argv.slice(2));m.existsSync(i.templatePath)||u(`Template directory does not exist: ${i.templatePath}`),m.statSync(i.templatePath).isDirectory()||u(`Template path must be a directory: ${i.templatePath}`);const oe=["fivora-template.json","fivora-template.json"].find(e=>m.existsSync(h.join(i.templatePath,e)))||"fivora-template.json",H=h.join(i.templatePath,oe),se=h.join(i.templatePath,"package.json"),p=x(H,"Template manifest"),I=x(se,"package.json");p.framework!=="nextjs-static-export"&&u('Template manifest framework must be "nextjs-static-export".'),(!I.scripts||typeof I.scripts.dev!="string")&&u("Template package.json must define a dev script for live preview."),(typeof p.siteDataFile!="string"||!p.siteDataFile.trim())&&u("Template manifest siteDataFile is required.");const _=h.resolve(i.templatePath,p.siteDataFile.trim()),k=h.relative(i.templatePath,_);(k.startsWith("..")||h.isAbsolute(k))&&u("Template manifest siteDataFile must stay inside the template directory.");const G=q.randomBytes(24).toString("base64url"),P=`http://${g}:${i.apiPort}`,J=`http://${g}:${i.previewPort}`,V=`${P}${B}`,le=h.join(__dirname,"deneb-template-validator.cjs");let A=!1,f=null,w=null,S=null,b=null,R=0;const X=5;let M="",O="";const l={protocolVersion:2,connected:!0,templateName:typeof p.name=="string"&&p.name.trim()?p.name.trim():typeof I.name=="string"?I.name:h.basename(i.templatePath),templatePath:i.templatePath,previewUrl:V,apiUrl:P,devStatus:"starting",devError:null,startedAt:new Date().toISOString(),validation:{status:"idle",startedAt:null,completedAt:null,exitCode:null}};function d(e,t){const n=t.toString();e==="dev"?M=`${M}${n}`.slice(-W):O=`${O}${n}`.slice(-W),process.stdout.write(n)}function Y(){return{...l,devLog:M,validationLog:O}}function de(e,t){const n=e.headers.origin;n&&/^(https?:\/\/|null$)/.test(n)&&(t.setHeader("Access-Control-Allow-Origin",n),t.setHeader("Vary","Origin")),t.setHeader("Access-Control-Allow-Headers","Authorization, Content-Type"),t.setHeader("Access-Control-Allow-Methods","GET, POST, OPTIONS"),t.setHeader("Access-Control-Allow-Private-Network","true"),t.setHeader("Cache-Control","no-store"),t.setHeader("X-Content-Type-Options","nosniff")}function v(e,t,n){const a=JSON.stringify(n);e.statusCode=t,e.setHeader("Content-Type","application/json; charset=utf-8"),e.setHeader("Content-Length",Buffer.byteLength(a)),e.end(a)}function ce(e){return e.headers.authorization===`Bearer ${G}`}function pe(){try{return JSON.parse(m.readFileSync(_,"utf8"))}catch(e){throw new Error(`Unable to read ${p.siteDataFile}: ${e instanceof Error?e.message:"unknown error"}`)}}function ue(e,t=ae){return new Promise((n,a)=>{const r=[];let s=0,c=!1;e.on("data",o=>{if(!c){if(s+=o.length,s>t){c=!0,a(new Error(`Request body is too large. Local site data must be ${Math.floor(t/1024/1024)} MB or less.`));return}r.push(o)}}),e.on("end",()=>{if(!c)try{const o=Buffer.concat(r).toString("utf8");n(JSON.parse(o))}catch{a(new Error("Request body must be valid JSON."))}}),e.on("error",o=>{c||(c=!0,a(o))})})}function F(e){return!!e&&typeof e=="object"&&!Array.isArray(e)}function me(e){const t=`${_}.${process.pid}.${Date.now()}.tmp`,n=m.existsSync(_)?m.statSync(_).mode:420;try{m.writeFileSync(t,`${JSON.stringify(e,null,2)}
7
+ `,{encoding:"utf8",flag:"wx",mode:n}),m.renameSync(t,_)}catch(a){if(m.existsSync(t))try{m.unlinkSync(t)}catch{}throw a}}function fe(){return w?!1:(O="",l.validation={status:"running",startedAt:new Date().toISOString(),completedAt:null,exitCode:null},w=$(process.execPath,[le,"validate",i.templatePath],{cwd:__dirname,env:process.env,stdio:["ignore","pipe","pipe"]}),w.stdout.on("data",e=>d("validation",e)),w.stderr.on("data",e=>d("validation",e)),w.on("error",e=>{d("validation",`
7
8
  Unable to start validation: ${e.message}
8
- `)}),g.on("close",e=>{o.validation={...o.validation,status:e===0?"passed":"failed",completedAt:new Date().toISOString(),exitCode:e},g=null}),!0)}function M(){if(P||o.devStatus==="ready")return;const e=T.get(k,t=>{if(t.resume(),t.statusCode&&t.statusCode<500){o.devStatus="ready",o.devError=null,R=0,process.stdout.write(`
9
- Live preview ready: ${x}
10
- `);return}b=setTimeout(M,600)});e.setTimeout(900,()=>e.destroy()),e.on("error",()=>{b=setTimeout(M,600)})}function ce(e){if(!(P||A)){if(R>=X){o.devStatus="failed",o.devError=e;return}R+=1,o.devStatus="starting",o.devError=null,l("dev",`
9
+ `)}),w.on("close",e=>{l.validation={...l.validation,status:e===0?"passed":"failed",completedAt:new Date().toISOString(),exitCode:e},w=null}),!0)}function U(){if(A||l.devStatus==="ready")return;const e=T.get(J,t=>{if(t.resume(),t.statusCode&&t.statusCode<500){l.devStatus="ready",l.devError=null,R=0,process.stdout.write(`
10
+ Live preview ready: ${V}
11
+ `);return}S=setTimeout(U,600)});e.setTimeout(900,()=>e.destroy()),e.on("error",()=>{S=setTimeout(U,600)})}function ve(e){if(!(A||b)){if(R>=X){l.devStatus="failed",l.devError=e;return}R+=1,l.devStatus="starting",l.devError=null,d("dev",`
11
12
  Preview server stopped during navigation (${e}). Restarting (${R}/${X})...
12
- `),A=setTimeout(()=>{A=null,F()},900)}}function ue(){const e=f.join(i.templatePath,"node_modules");if(!i.skipInstall&&!_.existsSync(e)){o.devStatus="installing";const t=typeof c.installCommand=="string"&&c.installCommand.trim()?c.installCommand.trim():"npm install";l("dev",`Installing local dependencies with: ${t}
13
- `);const a=D(t,{cwd:i.templatePath,env:process.env,shell:!0,stdio:["ignore","pipe","pipe"]});u=a,a.stdout.on("data",n=>l("dev",n)),a.stderr.on("data",n=>l("dev",n)),a.on("error",n=>{o.devStatus="failed",o.devError=n.message,u=null}),a.on("close",n=>{if(u=null,n!==0){o.devStatus="failed",o.devError=`Dependency installation exited with code ${n}.`;return}F()});return}F()}function F(){o.devStatus="starting",o.devError=null,l("dev",`Starting template source server on ${k}. Source edits will hot reload.
14
- `);const e=process.platform==="win32"?"npm.cmd":"npm";u=D(e,["run","dev","--","--hostname",v,"--port",String(i.previewPort)],{cwd:i.templatePath,env:{...process.env,NEXT_PUBLIC_SITE_BASE_PATH:""},shell:process.platform==="win32",stdio:["ignore","pipe","pipe"]}),u.stdout.on("data",t=>l("dev",t)),u.stderr.on("data",t=>l("dev",t)),u.on("error",t=>{o.devStatus="failed",o.devError=t.message,u=null}),u.on("close",(t,a)=>{if(u=null,!P){const n=a?`signal ${a}`:`exit code ${t}`;ce(n)}}),M()}function me(){return`<!doctype html>
13
+ `),b=setTimeout(()=>{b=null,N()},900)}}function he(){const e=h.join(i.templatePath,"node_modules");if(!i.skipInstall&&!m.existsSync(e)){l.devStatus="installing";const t=typeof p.installCommand=="string"&&p.installCommand.trim()?p.installCommand.trim():"npm install";d("dev",`Installing local dependencies with: ${t}
14
+ `);const n=$(t,{cwd:i.templatePath,env:process.env,shell:!0,stdio:["ignore","pipe","pipe"]});f=n,n.stdout.on("data",a=>d("dev",a)),n.stderr.on("data",a=>d("dev",a)),n.on("error",a=>{l.devStatus="failed",l.devError=a.message,f=null}),n.on("close",a=>{if(f=null,a!==0){l.devStatus="failed",l.devError=`Dependency installation exited with code ${a}.`;return}N()});return}N()}function N(){l.devStatus="starting",l.devError=null,d("dev",`Starting template source server on ${J}. Source edits will hot reload.
15
+ `);const e=process.platform==="win32"?"npm.cmd":"npm";f=$(e,["run","dev","--","--hostname",g,"--port",String(i.previewPort)],{cwd:i.templatePath,env:{...process.env,NEXT_PUBLIC_SITE_BASE_PATH:""},shell:process.platform==="win32",stdio:["ignore","pipe","pipe"]}),f.stdout.on("data",t=>d("dev",t)),f.stderr.on("data",t=>d("dev",t)),f.on("error",t=>{l.devStatus="failed",l.devError=t.message,f=null}),f.on("close",(t,n)=>{if(f=null,!A){const a=n?`signal ${n}`:`exit code ${t}`;ve(a)}}),U()}function ge(){return`<!doctype html>
15
16
  <html lang="en">
16
17
  <head>
17
18
  <meta charset="utf-8">
@@ -29,7 +30,7 @@ Preview server stopped during navigation (${e}). Restarting (${R}/${X})...
29
30
  <iframe id="template-preview" title="Local template preview"></iframe>
30
31
  <script>
31
32
  (() => {
32
- const BRIDGE_SOURCE = ${JSON.stringify(w)};
33
+ const BRIDGE_SOURCE = ${JSON.stringify(E)};
33
34
  const preview = document.getElementById('template-preview');
34
35
  const errorBox = document.getElementById('bridge-error');
35
36
  const PREVIOUS_PREVIEW_PREFIX = ['MARKET', 'PLACE'].join('') + '_PREVIEW_';
@@ -120,23 +121,23 @@ Preview server stopped during navigation (${e}). Restarting (${R}/${X})...
120
121
  })();
121
122
  <\/script>
122
123
  </body>
123
- </html>`}function E(e){if(!e||typeof e!="object")return!1;const t="code"in e?String(e.code):"";return t==="ECONNRESET"||t==="ECONNABORTED"||t==="EPIPE"||t==="ERR_STREAM_DESTROYED"}function fe(e,t){const a={...e.headers};a.host=`${v}:${i.previewPort}`,delete a["accept-encoding"],delete a.authorization;const n=T.request({hostname:v,port:i.previewPort,method:e.method,path:e.url,headers:a},r=>{t.writeHead(r.statusCode||502,r.statusMessage,r.headers),r.on("error",s=>{E(s)||l("dev",`
124
+ </html>`}function y(e){if(!e||typeof e!="object")return!1;const t="code"in e?String(e.code):"";return t==="ECONNRESET"||t==="ECONNABORTED"||t==="EPIPE"||t==="ERR_STREAM_DESTROYED"}function we(e,t){const n={...e.headers};n.host=`${g}:${i.previewPort}`,delete n["accept-encoding"],delete n.authorization;const a=T.request({hostname:g,port:i.previewPort,method:e.method,path:e.url,headers:n},r=>{t.writeHead(r.statusCode||502,r.statusMessage,r.headers),r.on("error",s=>{y(s)||d("dev",`
124
125
  Preview proxy upstream error: ${s.message}
125
- `),t.writableEnded||t.destroy()}),t.on("error",s=>{E(s)||l("dev",`
126
+ `),t.writableEnded||t.destroy()}),t.on("error",s=>{y(s)||d("dev",`
126
127
  Preview proxy response error: ${s.message}
127
- `),r.destroy()}),r.pipe(t)});n.on("error",r=>{E(r)||l("dev",`
128
+ `),r.destroy()}),r.pipe(t)});a.on("error",r=>{y(r)||d("dev",`
128
129
  Preview proxy request error: ${r.message}
129
- `),t.headersSent?t.writableEnded||t.destroy():h(t,502,{message:`Local preview is not ready: ${r.message}`})}),e.on("error",r=>{E(r)||l("dev",`
130
+ `),t.headersSent?t.writableEnded||t.destroy():v(t,502,{message:`Local preview is not ready: ${r.message}`})}),e.on("error",r=>{y(r)||d("dev",`
130
131
  Preview proxy client error: ${r.message}
131
- `),n.destroy()}),e.pipe(n)}const S=T.createServer((e,t)=>{const a=new URL(e.url||"/",y);if(e.method==="GET"&&a.pathname===W){const n=me();t.writeHead(200,{"Cache-Control":"no-store","Content-Length":Buffer.byteLength(n),"Content-Type":"text/html; charset=utf-8"}),t.end(n);return}if(!a.pathname.startsWith("/api/")){fe(e,t);return}if(se(e,t),e.method==="OPTIONS"){t.statusCode=204,t.end();return}if(!le(e)){h(t,401,{message:"Invalid Local Template Lab token."});return}if(e.method==="GET"&&a.pathname==="/api/status"){h(t,200,J());return}if(e.method==="GET"&&a.pathname==="/api/site-data"){try{let n=c;try{n=$(B,"Template manifest")}catch{}h(t,200,{manifest:n,siteData:de(),siteDataFile:n.siteDataFile||c.siteDataFile})}catch(n){h(t,500,{message:n instanceof Error?n.message:"Unable to read site data."})}return}if(e.method==="POST"&&a.pathname==="/api/validate"){if(!pe()){h(t,409,{message:"Validation is already running."});return}h(t,202,J());return}h(t,404,{message:"Local Template Lab endpoint not found."})});function ve(e,t){const a=()=>{e.destroyed||e.destroy(),t.destroyed||t.destroy()},n=r=>{E(r)||l("dev",`
132
+ `),a.destroy()}),e.pipe(a)}const C=T.createServer(async(e,t)=>{const n=new URL(e.url||"/",P);if(e.method==="GET"&&n.pathname===B){const a=ge();t.writeHead(200,{"Cache-Control":"no-store","Content-Length":Buffer.byteLength(a),"Content-Type":"text/html; charset=utf-8"}),t.end(a);return}if(!n.pathname.startsWith("/api/")){we(e,t);return}if(de(e,t),e.method==="OPTIONS"){t.statusCode=204,t.end();return}if(!ce(e)){v(t,401,{message:"Invalid Local Template Lab token."});return}if(e.method==="GET"&&n.pathname==="/api/status"){v(t,200,Y());return}if(e.method==="GET"&&n.pathname==="/api/site-data"){try{let a=p;try{a=x(H,"Template manifest")}catch{}v(t,200,{manifest:a,siteData:pe(),siteDataFile:a.siteDataFile||p.siteDataFile})}catch(a){v(t,500,{message:a instanceof Error?a.message:"Unable to read site data."})}return}if(e.method==="POST"&&n.pathname==="/api/site-data"){try{const a=await ue(e),r=F(a)?a.siteData:null;if(!F(r)||!F(r.content)){v(t,400,{message:"siteData must be a JSON object with a content object."});return}me(r),v(t,200,{saved:!0,siteData:r,siteDataFile:p.siteDataFile})}catch(a){v(t,400,{message:a instanceof Error?a.message:"Unable to save local site data."})}return}if(e.method==="POST"&&n.pathname==="/api/validate"){if(!fe()){v(t,409,{message:"Validation is already running."});return}v(t,202,Y());return}v(t,404,{message:"Local Template Lab endpoint not found."})});function Ee(e,t){const n=()=>{e.destroyed||e.destroy(),t.destroyed||t.destroy()},a=r=>{y(r)||d("dev",`
132
133
  Preview proxy socket error: ${r.message}
133
- `),a()};e.on("error",n),t.on("error",n),e.on("close",()=>{t.destroyed||t.end()}),t.on("close",()=>{e.destroyed||e.end()}),t.pipe(e),e.pipe(t)}S.on("upgrade",(e,t,a)=>{if(e.url?.startsWith("/api/")){t.destroy();return}t.on("error",s=>{E(s)||l("dev",`
134
+ `),n()};e.on("error",a),t.on("error",a),e.on("close",()=>{t.destroyed||t.end()}),t.on("close",()=>{e.destroyed||e.end()}),t.pipe(e),e.pipe(t)}C.on("upgrade",(e,t,n)=>{if(e.url?.startsWith("/api/")){t.destroy();return}t.on("error",s=>{y(s)||d("dev",`
134
135
  Preview proxy upgrade client error: ${s.message}
135
- `)});const n={...e.headers};n.host=`${v}:${i.previewPort}`;const r=T.request({hostname:v,port:i.previewPort,method:e.method,path:e.url,headers:n});r.on("upgrade",(s,m,d)=>{const z=Object.entries(s.headers).flatMap(([Q,O])=>(Array.isArray(O)?O:[O]).filter(L=>L!==void 0).map(L=>`${Q}: ${L}`)).join(`\r
136
+ `)});const a={...e.headers};a.host=`${g}:${i.previewPort}`;const r=T.request({hostname:g,port:i.previewPort,method:e.method,path:e.url,headers:a});r.on("upgrade",(s,c,o)=>{const Q=Object.entries(s.headers).flatMap(([Z,L])=>(Array.isArray(L)?L:[L]).filter(D=>D!==void 0).map(D=>`${Z}: ${D}`)).join(`\r
136
137
  `);t.write(`HTTP/1.1 ${s.statusCode||101} ${s.statusMessage||"Switching Protocols"}\r
137
- ${z}\r
138
+ ${Q}\r
138
139
  \r
139
- `),a.length&&m.write(a),d.length&&t.write(d),ve(t,m)}),r.on("error",s=>{E(s)||l("dev",`
140
+ `),n.length&&c.write(n),o.length&&t.write(o),Ee(t,c)}),r.on("error",s=>{y(s)||d("dev",`
140
141
  Preview proxy upgrade upstream error: ${s.message}
141
- `),t.destroyed||t.destroy()}),r.end()});function Y(e){!e||e.killed||e.kill("SIGTERM")}function K(){P||(P=!0,b&&clearTimeout(b),A&&clearTimeout(A),Y(g),Y(u),S.close(()=>process.exit(0)),setTimeout(()=>process.exit(0),1500).unref())}S.on("error",e=>{p(`Unable to start loopback controller at ${y}: ${e instanceof Error?e.message:"unknown error"}`)}),S.listen(i.apiPort,v,()=>{process.stdout.write(["","Fivora Local Template Lab is running.",`Template: ${o.templateName}`,`Controller URL: ${y}`,`Connection token: ${G}`,`Preview URL: ${x}`,"","Paste the controller URL and connection token into Developer Portal > Local Test Lab.","Press Ctrl+C to stop. No template files are uploaded by this process.",""].join(`
142
- `)),ue()}),process.on("SIGINT",K),process.on("SIGTERM",K);
142
+ `),t.destroyed||t.destroy()}),r.end()});function z(e){!e||e.killed||e.kill("SIGTERM")}function K(){A||(A=!0,S&&clearTimeout(S),b&&clearTimeout(b),z(w),z(f),C.close(()=>process.exit(0)),setTimeout(()=>process.exit(0),1500).unref())}C.on("error",e=>{u(`Unable to start loopback controller at ${P}: ${e instanceof Error?e.message:"unknown error"}`)}),C.listen(i.apiPort,g,()=>{process.stdout.write(["","Fivora Local Template Lab is running.",`Template: ${l.templateName}`,`Controller URL: ${P}`,`Connection token: ${G}`,`Preview URL: ${V}`,"","Paste the controller URL and connection token into Developer Portal > Local Test Lab.","Use Save local in the portal to write edits to the template site-data file.","Press Ctrl+C to stop. No template files are uploaded by this process.",""].join(`
143
+ `)),he()}),process.on("SIGINT",K),process.on("SIGTERM",K);