@zorgo/next 1.3.0 → 2.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.
Files changed (66) hide show
  1. package/dist/cli.d.mts +9 -0
  2. package/dist/cli.d.ts +9 -0
  3. package/dist/cli.js +44 -66
  4. package/dist/cli.js.map +1 -1
  5. package/dist/cli.mjs +43 -65
  6. package/dist/cli.mjs.map +1 -1
  7. package/dist/components.d.mts +735 -5
  8. package/dist/components.d.ts +735 -5
  9. package/dist/components.js +3692 -525
  10. package/dist/components.js.map +1 -1
  11. package/dist/components.mjs +3663 -540
  12. package/dist/components.mjs.map +1 -1
  13. package/dist/images.d.mts +25 -13
  14. package/dist/images.d.ts +25 -13
  15. package/dist/images.js +18 -8
  16. package/dist/images.js.map +1 -1
  17. package/dist/images.mjs +15 -8
  18. package/dist/images.mjs.map +1 -1
  19. package/dist/index.d.mts +315 -9
  20. package/dist/index.d.ts +315 -9
  21. package/dist/index.js +58 -52
  22. package/dist/index.js.map +1 -1
  23. package/dist/index.mjs +58 -52
  24. package/dist/index.mjs.map +1 -1
  25. package/dist/item-page.d.mts +6 -0
  26. package/dist/item-page.d.ts +6 -0
  27. package/dist/item-page.js +4 -2
  28. package/dist/item-page.js.map +1 -1
  29. package/dist/item-page.mjs +4 -2
  30. package/dist/item-page.mjs.map +1 -1
  31. package/dist/legal.d.mts +31 -0
  32. package/dist/legal.d.ts +31 -0
  33. package/dist/legal.js +322 -0
  34. package/dist/legal.js.map +1 -0
  35. package/dist/legal.mjs +293 -0
  36. package/dist/legal.mjs.map +1 -0
  37. package/dist/seo.d.mts +56 -1
  38. package/dist/seo.d.ts +56 -1
  39. package/dist/seo.js +111 -2
  40. package/dist/seo.js.map +1 -1
  41. package/dist/seo.mjs +106 -1
  42. package/dist/seo.mjs.map +1 -1
  43. package/dist/server.d.mts +27 -1
  44. package/dist/server.d.ts +27 -1
  45. package/dist/server.js +322 -102
  46. package/dist/server.js.map +1 -1
  47. package/dist/server.mjs +322 -104
  48. package/dist/server.mjs.map +1 -1
  49. package/dist/slugs.d.mts +2 -1
  50. package/dist/slugs.d.ts +2 -1
  51. package/dist/slugs.js +2 -0
  52. package/dist/slugs.js.map +1 -1
  53. package/dist/slugs.mjs +1 -0
  54. package/dist/tree/README.md +2 -2
  55. package/dist/tree/app/error.tsx +12 -3
  56. package/dist/tree/app/global-error.tsx +21 -4
  57. package/dist/tree/app/globals.css +41 -0
  58. package/dist/tree/app/not-found.tsx +17 -3
  59. package/dist/tree/app/privacy/page.tsx.gen.ts +28 -9
  60. package/dist/tree/app/terms/page.tsx.gen.ts +26 -10
  61. package/dist/tree/gitignore +6 -1
  62. package/dist/tree/next.config.ts +6 -5
  63. package/dist/tree/open-next.config.ts +7 -0
  64. package/dist/tree/package.json.gen.ts +8 -2
  65. package/package.json +7 -2
  66. package/dist/tree/app/globals.css.gen.ts +0 -124
package/dist/server.js CHANGED
@@ -31,6 +31,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var server_exports = {};
32
32
  __export(server_exports, {
33
33
  SiteTokenError: () => SiteTokenError,
34
+ buildBrandingCss: () => buildBrandingCss,
35
+ buildWranglerToml: () => buildWranglerToml,
34
36
  fetchSiteToken: () => fetchSiteToken,
35
37
  runPrebuild: () => runPrebuild
36
38
  });
@@ -40,7 +42,87 @@ var import_path2 = __toESM(require("path"));
40
42
  var import_env = require("@next/env");
41
43
  var import_zod = require("zod");
42
44
 
45
+ // ../../node_modules/date-fns-tz/dist/esm/_lib/tzTokenizeDate/index.js
46
+ var testDateFormatted = new Intl.DateTimeFormat("en-US", {
47
+ hourCycle: "h23",
48
+ timeZone: "America/New_York",
49
+ year: "numeric",
50
+ month: "2-digit",
51
+ day: "2-digit",
52
+ hour: "2-digit",
53
+ minute: "2-digit",
54
+ second: "2-digit"
55
+ }).format(/* @__PURE__ */ new Date("2014-06-25T04:00:00.123Z"));
56
+
57
+ // ../../node_modules/date-fns-tz/dist/esm/format/formatters/index.js
58
+ var MILLISECONDS_IN_MINUTE = 60 * 1e3;
59
+
60
+ // ../zorgo/universal/utils/locationOrderingAvailability.ts
61
+ function wallTime(value) {
62
+ return value.replace(/[+-].*$/, "").trim().slice(0, 5);
63
+ }
64
+
43
65
  // src/seo.ts
66
+ var DAYS = [
67
+ "Monday",
68
+ "Tuesday",
69
+ "Wednesday",
70
+ "Thursday",
71
+ "Friday",
72
+ "Saturday",
73
+ "Sunday"
74
+ ];
75
+ function restaurantId(info) {
76
+ return `${info.url ?? ""}#restaurant`;
77
+ }
78
+ function branchId(info, location) {
79
+ return `${info.url ?? ""}#location-${location.location_id}`;
80
+ }
81
+ function openingHoursSpecification(location) {
82
+ const byWindow = /* @__PURE__ */ new Map();
83
+ for (const day of DAYS) {
84
+ const key = day.toLowerCase();
85
+ const open = location[`${key}_open`];
86
+ const close = location[`${key}_close`];
87
+ if (!open || !close) continue;
88
+ const window = `${wallTime(open)}|${wallTime(close)}`;
89
+ byWindow.set(window, [...byWindow.get(window) ?? [], `https://schema.org/${day}`]);
90
+ }
91
+ return [...byWindow].map(([window, dayOfWeek]) => {
92
+ const [opens, closes] = window.split("|");
93
+ return { "@type": "OpeningHoursSpecification", dayOfWeek, opens, closes };
94
+ });
95
+ }
96
+ var US_ADDRESS = /^(.+),\s*([^,]+),\s*([A-Z]{2})\s+(\d{5}(?:-\d{4})?)(?:,\s*USA?)?$/;
97
+ function postalAddress(address) {
98
+ const match = US_ADDRESS.exec(address.trim());
99
+ if (!match) return address;
100
+ const [, streetAddress, addressLocality, addressRegion, postalCode] = match;
101
+ if (!streetAddress || !addressLocality || !addressRegion || !postalCode) return address;
102
+ return {
103
+ "@type": "PostalAddress",
104
+ streetAddress: streetAddress.trim(),
105
+ addressLocality: addressLocality.trim(),
106
+ addressRegion,
107
+ postalCode,
108
+ addressCountry: "US"
109
+ };
110
+ }
111
+ function locationFacts(location) {
112
+ const facts = {};
113
+ if (location.address) facts.address = postalAddress(location.address);
114
+ if (location.phone) facts.telephone = location.phone;
115
+ if (location.lat != null && location.lng != null) {
116
+ facts.geo = {
117
+ "@type": "GeoCoordinates",
118
+ latitude: location.lat,
119
+ longitude: location.lng
120
+ };
121
+ }
122
+ const hours = openingHoursSpecification(location);
123
+ if (hours.length) facts.openingHoursSpecification = hours;
124
+ return facts;
125
+ }
44
126
  function priceString(cents) {
45
127
  return (cents / 100).toFixed(2);
46
128
  }
@@ -81,6 +163,17 @@ function buildRestaurantJsonLd(menu2, info) {
81
163
  if (info.image) restaurant.image = info.image;
82
164
  if (info.description) restaurant.description = info.description;
83
165
  if (info.servesCuisine?.length) restaurant.servesCuisine = info.servesCuisine;
166
+ const locations2 = info.locations ?? [];
167
+ if (locations2.length === 1) {
168
+ Object.assign(restaurant, locationFacts(locations2[0]));
169
+ } else if (locations2.length > 1) {
170
+ restaurant["@id"] = restaurantId(info);
171
+ restaurant.subOrganization = locations2.map((location) => ({
172
+ "@type": "Restaurant",
173
+ "@id": branchId(info, location),
174
+ name: location.name
175
+ }));
176
+ }
84
177
  restaurant.hasMenu = {
85
178
  "@type": "Menu",
86
179
  name: "Menu",
@@ -89,16 +182,19 @@ function buildRestaurantJsonLd(menu2, info) {
89
182
  return restaurant;
90
183
  }
91
184
 
92
- // src/internal/itemImages.ts
185
+ // src/internal/images.ts
93
186
  var import_fs = require("fs");
187
+ var import_promises = require("fs/promises");
94
188
  var import_module = require("module");
95
189
  var import_path = __toESM(require("path"));
96
190
 
97
191
  // ../zorgo/universal/constants.ts
98
- var SUPABASE_URL = "https://dhnpoxorllfdobwftkyb.supabase.co";
99
- var API_BASE_URL = process.env.NODE_ENV === "production" ? "https://api.zorgotech.com" : "http://localhost:3000";
192
+ var SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.EXPO_PUBLIC_SUPABASE_URL || "https://dhnpoxorllfdobwftkyb.supabase.co";
193
+ var SUPABASE_ANON_KEY = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImRobnBveG9ybGxmZG9id2Z0a3liIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzkwNTQxMDgsImV4cCI6MjA1NDYzMDEwOH0.zI8PwbgKwijHZ1QmxJqmaPrFvg02w1vOY2Qcs7SddzI";
194
+ var API_BASE_URL = process.env.NODE_ENV === "production" ? "https://api.zorgotech.com" /* Production */ : "http://localhost:3000" /* Development */;
100
195
 
101
196
  // ../zorgo/universal/utils/menuUtils.ts
197
+ var UNSORTED = Number.MAX_SAFE_INTEGER;
102
198
  function menuImageUrl(image, absolute = false) {
103
199
  if (!image) return null;
104
200
  if (image.local_key && !absolute) return `/images/items/${image.local_key}`;
@@ -108,11 +204,13 @@ function menuImageUrl(image, absolute = false) {
108
204
  }
109
205
 
110
206
  // src/images.ts
111
- var ITEM_IMAGE_WIDTHS = [384, 640, 1080];
207
+ var IMAGE_WIDTHS = [384, 640, 1080, 1920];
112
208
  var ITEM_IMAGE_PREFIX = "/images/items/";
113
209
 
114
- // src/internal/itemImages.ts
210
+ // src/internal/images.ts
115
211
  var AVIF_QUALITY = 60;
212
+ var SITE_IMAGE_SOURCE_DIR = "assets/images";
213
+ var SOURCE_EXTENSIONS = /\.(jpe?g|png|webp|avif|tiff?|gif)$/i;
116
214
  function loadSharp(root) {
117
215
  for (const from of [root, process.cwd()]) {
118
216
  try {
@@ -125,33 +223,31 @@ function loadSharp(root) {
125
223
  `sharp is not installed under ${root}. It is pinned in every scaffolded site's package.json \u2014 run an install there.`
126
224
  );
127
225
  }
226
+ function variantPath(outBase, width) {
227
+ return `${outBase}-${width}.avif`;
228
+ }
229
+ async function encodeLadder(source, outBase, sharp) {
230
+ (0, import_fs.mkdirSync)(import_path.default.dirname(outBase), { recursive: true });
231
+ for (const width of IMAGE_WIDTHS) {
232
+ await sharp(source).resize({ width, withoutEnlargement: true }).avif({ quality: AVIF_QUALITY }).toFile(variantPath(outBase, width));
233
+ }
234
+ }
128
235
  function itemImageLocalKey(image) {
129
236
  const storagePath = image.processed_path ?? image.raw_path;
130
237
  if (!storagePath) return null;
131
238
  return storagePath.replace(/\.[^./]+$/, "").replace(/[^a-zA-Z0-9_]+/g, "-").replace(/^-|-$/g, "");
132
239
  }
133
- function itemImageVariantPath(root, key, width) {
134
- return import_path.default.join(root, "public", ITEM_IMAGE_PREFIX, `${key}-${width}.avif`);
135
- }
136
240
  async function materializeItemImage(image, root) {
137
241
  const key = itemImageLocalKey(image);
138
242
  if (!key) return null;
139
- const targets = ITEM_IMAGE_WIDTHS.map((width) => ({
140
- width,
141
- file: itemImageVariantPath(root, key, width)
142
- }));
143
- if (targets.every(({ file }) => (0, import_fs.existsSync)(file))) return key;
243
+ const outBase = import_path.default.join(root, "public", ITEM_IMAGE_PREFIX, key);
244
+ if (IMAGE_WIDTHS.every((width) => (0, import_fs.existsSync)(variantPath(outBase, width)))) return key;
144
245
  const url = menuImageUrl(image, true);
145
246
  if (!url) return null;
146
247
  try {
147
248
  const res = await fetch(url);
148
249
  if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
149
- const source = Buffer.from(await res.arrayBuffer());
150
- const sharp = loadSharp(root);
151
- (0, import_fs.mkdirSync)(import_path.default.dirname(targets[0].file), { recursive: true });
152
- for (const { width, file } of targets) {
153
- await sharp(source).resize({ width, withoutEnlargement: true }).avif({ quality: AVIF_QUALITY }).toFile(file);
154
- }
250
+ await encodeLadder(Buffer.from(await res.arrayBuffer()), outBase, loadSharp(root));
155
251
  return key;
156
252
  } catch (err) {
157
253
  console.error(`prebuild: image ${image.image_id} (${url}) failed:`, err);
@@ -168,6 +264,57 @@ async function stampItemImages(menu2, root) {
168
264
  console.log(`prebuild: ${stamped}/${images.length} item images localized`);
169
265
  }
170
266
  }
267
+ function collectSources(dir, prefix = "") {
268
+ return (0, import_fs.readdirSync)(dir, { withFileTypes: true }).flatMap((entry) => {
269
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
270
+ if (entry.isDirectory()) return collectSources(import_path.default.join(dir, entry.name), rel);
271
+ return SOURCE_EXTENSIONS.test(entry.name) ? [rel] : [];
272
+ });
273
+ }
274
+ function siteImageKey(relativePath) {
275
+ return relativePath.replace(/\.[^./]+$/, "").split("/").map((segment) => segment.replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-|-$/g, "")).join("/");
276
+ }
277
+ function laddersAreFresh(sourcePath, outBase) {
278
+ const sourceMtime = (0, import_fs.statSync)(sourcePath).mtimeMs;
279
+ return IMAGE_WIDTHS.every((width) => {
280
+ const file = variantPath(outBase, width);
281
+ return (0, import_fs.existsSync)(file) && (0, import_fs.statSync)(file).mtimeMs >= sourceMtime;
282
+ });
283
+ }
284
+ async function optimizeSiteImages(root) {
285
+ const sourceDir = import_path.default.join(root, SITE_IMAGE_SOURCE_DIR);
286
+ if (!(0, import_fs.existsSync)(sourceDir)) return;
287
+ const sources = collectSources(sourceDir);
288
+ if (sources.length === 0) return;
289
+ const sharp = loadSharp(root);
290
+ const claimed = /* @__PURE__ */ new Map();
291
+ let encoded = 0;
292
+ let failed = 0;
293
+ for (const relativePath of sources) {
294
+ const key = siteImageKey(relativePath);
295
+ const clash = claimed.get(key);
296
+ if (clash) {
297
+ console.warn(
298
+ `prebuild: ${relativePath} and ${clash} both map to /images/${key} \u2014 skipping ${relativePath}`
299
+ );
300
+ continue;
301
+ }
302
+ claimed.set(key, relativePath);
303
+ const sourcePath = import_path.default.join(sourceDir, relativePath);
304
+ const outBase = import_path.default.join(root, "public", "images", key);
305
+ if (laddersAreFresh(sourcePath, outBase)) continue;
306
+ try {
307
+ await encodeLadder(await (0, import_promises.readFile)(sourcePath), outBase, sharp);
308
+ encoded++;
309
+ } catch (err) {
310
+ failed++;
311
+ console.error(`prebuild: site image ${relativePath} failed:`, err);
312
+ }
313
+ }
314
+ console.log(
315
+ `prebuild: ${sources.length} site images (${encoded} encoded, ${sources.length - encoded - failed} cached, ${failed} failed)`
316
+ );
317
+ }
171
318
 
172
319
  // src/server.ts
173
320
  function apiBaseUrl() {
@@ -230,7 +377,25 @@ var FranchiseSchema = import_zod.z.object({
230
377
  name: import_zod.z.string().nullish().transform((v) => v ?? process.env.ZORGO_PROJECT_NAME ?? "Restaurant"),
231
378
  site_url: import_zod.z.string().nullish().transform((v) => v ?? void 0)
232
379
  }).prefault({});
233
- var HoursSchema = import_zod.z.array(import_zod.z.unknown()).prefault([]);
380
+ var LocationsSchema = import_zod.z.array(import_zod.z.unknown()).prefault([]);
381
+ var FeatureConfigSchema = import_zod.z.object({
382
+ catering: import_zod.z.object({
383
+ does_cater: import_zod.z.boolean().nullish(),
384
+ minimum_notice_hours: import_zod.z.number().nullish(),
385
+ minimum_people_required: import_zod.z.number().nullish(),
386
+ minimum_price: import_zod.z.number().nullish(),
387
+ preferred_time: import_zod.z.string().nullish()
388
+ }).nullish().transform((v) => v ?? null),
389
+ catering_bulk_discount: import_zod.z.object({
390
+ is_active: import_zod.z.boolean().nullish(),
391
+ full_discount_at: import_zod.z.number().nullish(),
392
+ maximum_percent_off: import_zod.z.number().nullish()
393
+ }).nullish().transform((v) => v ?? null),
394
+ // Passed through unvalidated: the server already parsed it against
395
+ // FoodTruckConfig, and re-declaring eight money fields here just creates a
396
+ // second place to forget one. The site imports it as FoodTruckConfigValue.
397
+ food_truck: import_zod.z.unknown().transform((v) => v ?? null)
398
+ }).prefault({ food_truck: null });
234
399
  var MenuSchema = import_zod.z.object({
235
400
  items: import_zod.z.array(import_zod.z.unknown()).default([]),
236
401
  categories: import_zod.z.array(import_zod.z.unknown()).default([]),
@@ -252,8 +417,9 @@ async function fetchData(endpoint, token, schema) {
252
417
  }
253
418
  var FONT_ON_LIGHT = "#0a0a0a";
254
419
  var FONT_ON_DARK = "#f5f5f5";
255
- var BACKGROUND_SURFACE_DELTA = 15;
256
- var BACKGROUND_TEMPERATURE_BIAS = 8;
420
+ var NEUTRAL_GRAY = { r: 128, g: 128, b: 128 };
421
+ var SURFACE_SHIFT_SUBTLE = 0.1;
422
+ var SURFACE_SHIFT_STRONG = 0.18;
257
423
  function parseHexRgb(background) {
258
424
  const hex = background.trim().replace(/^#/, "");
259
425
  const full = hex.length === 3 ? hex.split("").map((c) => c + c).join("") : hex;
@@ -272,42 +438,39 @@ function toHex({ r, g, b }) {
272
438
  function relativeLuminance({ r, g, b }) {
273
439
  return (0.299 * r + 0.587 * g + 0.114 * b) / 255;
274
440
  }
275
- function temperatureBiasFor({ r, g, b }) {
276
- return Math.round(BACKGROUND_TEMPERATURE_BIAS * relativeLuminance({ r, g, b }));
277
- }
278
441
  function deriveFontColor(background) {
279
442
  const rgb = parseHexRgb(background);
280
443
  if (!rgb) return FONT_ON_LIGHT;
281
444
  return relativeLuminance(rgb) > 0.5 ? FONT_ON_LIGHT : FONT_ON_DARK;
282
445
  }
446
+ function lerpToward(rgb, target, t) {
447
+ return toHex({
448
+ r: Math.round(rgb.r + (target.r - rgb.r) * t),
449
+ g: Math.round(rgb.g + (target.g - rgb.g) * t),
450
+ b: Math.round(rgb.b + (target.b - rgb.b) * t)
451
+ });
452
+ }
283
453
  function deriveElevatedBackground(background) {
284
454
  const rgb = parseHexRgb(background);
285
455
  if (!rgb) return background;
286
- const bias = temperatureBiasFor(rgb);
287
- const warm = Math.round(bias / 4);
288
- return toHex({
289
- r: Math.min(255, rgb.r + BACKGROUND_SURFACE_DELTA + warm),
290
- g: Math.min(255, rgb.g + BACKGROUND_SURFACE_DELTA),
291
- b: Math.min(255, rgb.b + BACKGROUND_SURFACE_DELTA - bias)
292
- });
456
+ const t = relativeLuminance(rgb) > 0.5 ? SURFACE_SHIFT_SUBTLE : SURFACE_SHIFT_STRONG;
457
+ return lerpToward(rgb, NEUTRAL_GRAY, t);
293
458
  }
294
459
  function deriveRecessedBackground(background) {
295
460
  const rgb = parseHexRgb(background);
296
461
  if (!rgb) return background;
297
- const bias = temperatureBiasFor(rgb);
298
- const cool = Math.round(bias / 4);
299
- return toHex({
300
- r: Math.max(0, rgb.r - BACKGROUND_SURFACE_DELTA - cool),
301
- g: Math.max(0, rgb.g - BACKGROUND_SURFACE_DELTA),
302
- b: Math.max(0, rgb.b - BACKGROUND_SURFACE_DELTA + bias)
303
- });
304
- }
305
- function writeWranglerToml() {
306
- const name = process.env.ZORGO_PROJECT_NAME ?? "zorgo-site";
307
- const siteUrl = process.env.NEXT_PUBLIC_SITE_URL ?? "";
308
- const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL ?? "";
309
- const baseUrl = process.env.NEXT_PUBLIC_BASE_URL ?? siteUrl;
310
- const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL ?? "";
462
+ const t = relativeLuminance(rgb) > 0.5 ? SURFACE_SHIFT_STRONG : SURFACE_SHIFT_SUBTLE;
463
+ return lerpToward(rgb, NEUTRAL_GRAY, t);
464
+ }
465
+ function varsBlock(vars) {
466
+ return Object.entries(vars).filter(([, value]) => value !== "").map(([key, value]) => `${key} = "${value}"`).join("\n");
467
+ }
468
+ function buildWranglerToml(env) {
469
+ const name = env.ZORGO_PROJECT_NAME ?? "zorgo-site";
470
+ const siteUrl = env.NEXT_PUBLIC_SITE_URL ?? "";
471
+ const apiUrl = env.NEXT_PUBLIC_API_BASE_URL ?? "";
472
+ const baseUrl = env.NEXT_PUBLIC_BASE_URL ?? siteUrl;
473
+ const supabaseUrl = env.NEXT_PUBLIC_SUPABASE_URL ?? "";
311
474
  let host;
312
475
  try {
313
476
  host = new URL(siteUrl).hostname;
@@ -323,32 +486,23 @@ custom_domain = true
323
486
  pattern = "www.${host}"
324
487
  custom_domain = true
325
488
  ` : "";
326
- const vars = {
327
- NEXT_PUBLIC_SUPABASE_URL: supabaseUrl,
328
- NEXT_PUBLIC_API_BASE_URL: apiUrl,
329
- NEXT_PUBLIC_SITE_URL: siteUrl,
330
- NEXT_PUBLIC_BASE_URL: baseUrl
331
- };
332
- const varsBlock = Object.entries(vars).filter(([, value]) => value !== "").map(([key, value]) => `${key} = "${value}"`).join("\n");
333
489
  const toml = `# AUTO-GENERATED by scripts/prebuild.ts from .env \u2014 do not edit.
334
490
  name = "${name}"
335
491
  main = ".open-next/worker.js"
336
492
  compatibility_date = "2024-09-23"
337
- compatibility_flags = ["nodejs_compat"]
338
-
339
- # Bundles the Cloudflare worker as part of \`wrangler deploy\` (and \`wrangler dev\`),
340
- # so the deploy command stays a plain \`wrangler deploy\`. prebuild (which writes
341
- # this file + lib/generated) must still run first \u2014 on Cloudflare Workers Builds
342
- # that is the project's Build command; locally it is the npm scripts below.
343
- [build]
344
- command = "npx opennextjs-cloudflare build"
493
+ compatibility_flags = ["nodejs_compat_v2"]
345
494
 
346
495
  [assets]
347
496
  directory = ".open-next/assets"
348
497
  binding = "ASSETS"
349
498
 
350
499
  [vars]
351
- ${varsBlock}
500
+ ${varsBlock({
501
+ NEXT_PUBLIC_SUPABASE_URL: supabaseUrl,
502
+ NEXT_PUBLIC_API_BASE_URL: apiUrl,
503
+ NEXT_PUBLIC_SITE_URL: siteUrl,
504
+ NEXT_PUBLIC_BASE_URL: baseUrl
505
+ })}
352
506
 
353
507
  [observability]
354
508
  enabled = true
@@ -356,8 +510,27 @@ enabled = true
356
510
  [observability.logs]
357
511
  enabled = true
358
512
  invocation_logs = true
359
- ${routes}`;
360
- (0, import_fs2.writeFileSync)(import_path2.default.join(process.cwd(), "wrangler.toml"), toml);
513
+ ${routes}${buildStagingBlock(env, name, supabaseUrl, apiUrl)}`;
514
+ return toml;
515
+ }
516
+ function buildStagingBlock(env, name, supabaseUrl, apiUrl) {
517
+ const stagingUrl = env.NEXT_PUBLIC_STAGING_BASE_URL ?? "";
518
+ return `
519
+ [env.staging]
520
+ name = "${name}-staging"
521
+ routes = []
522
+
523
+ [env.staging.vars]
524
+ ${varsBlock({
525
+ NEXT_PUBLIC_SUPABASE_URL: supabaseUrl,
526
+ NEXT_PUBLIC_API_BASE_URL: apiUrl,
527
+ NEXT_PUBLIC_SITE_URL: stagingUrl,
528
+ NEXT_PUBLIC_BASE_URL: stagingUrl
529
+ })}
530
+ `;
531
+ }
532
+ function writeWranglerToml() {
533
+ (0, import_fs2.writeFileSync)(import_path2.default.join(process.cwd(), "wrangler.toml"), buildWranglerToml(process.env));
361
534
  }
362
535
  async function logo({ franchiseId, token }) {
363
536
  const logoResponse = await fetch(
@@ -370,7 +543,6 @@ async function logo({ franchiseId, token }) {
370
543
  const outDir = import_path2.default.join(process.cwd(), `public/images/`);
371
544
  (0, import_fs2.mkdirSync)(outDir, { recursive: true });
372
545
  (0, import_fs2.writeFileSync)(import_path2.default.join(outDir, "logo.png"), buffer);
373
- (0, import_fs2.writeFileSync)(import_path2.default.join(outDir, "logo.jpg"), buffer);
374
546
  }
375
547
  var dropHeavyColumns = (key, value) => key.endsWith("_vector") || key.endsWith("_embedding") ? void 0 : value;
376
548
  function writeMenuModule(menu2) {
@@ -406,7 +578,7 @@ async function menu({ franchiseId, token }) {
406
578
  writeMenuModule(parsed);
407
579
  return parsed;
408
580
  }
409
- async function jsonLd({ franchiseId, token }, menuData) {
581
+ async function jsonLd({ franchiseId, token }, menuData, locationRows) {
410
582
  const franchise = await fetchData(
411
583
  `/franchise-info/franchise/${franchiseId}`,
412
584
  token,
@@ -416,7 +588,8 @@ async function jsonLd({ franchiseId, token }, menuData) {
416
588
  const data = buildRestaurantJsonLd(menuData, {
417
589
  name: franchise.name,
418
590
  url,
419
- image: url ? `${url}/images/logo.png` : void 0
591
+ image: url ? `${url}/images/logo.png` : void 0,
592
+ locations: locationRows
420
593
  });
421
594
  const outDir = import_path2.default.join(process.cwd(), `lib/generated/`);
422
595
  (0, import_fs2.mkdirSync)(outDir, { recursive: true });
@@ -427,8 +600,61 @@ export const restaurantJsonLd = ${JSON.stringify(data, null, 2)};
427
600
  `
428
601
  );
429
602
  }
430
- async function hours({ franchiseId, token }) {
431
- await fetchData(`/franchise-info/hours/${franchiseId}`, token, HoursSchema);
603
+ async function locations({ franchiseId, token }) {
604
+ const rows = await fetchData(
605
+ `/franchise-info/locations/${franchiseId}`,
606
+ token,
607
+ LocationsSchema
608
+ );
609
+ const outDir = import_path2.default.join(process.cwd(), `lib/generated/`);
610
+ (0, import_fs2.mkdirSync)(outDir, { recursive: true });
611
+ (0, import_fs2.writeFileSync)(
612
+ import_path2.default.join(outDir, "locations.ts"),
613
+ `// AUTO-GENERATED by scripts/prebuild.ts \u2014 do not edit.
614
+ import type { Location } from "@zorgo/next";
615
+
616
+ export const locations = ${JSON.stringify(rows, null, 2)} as unknown as Location[];
617
+ `
618
+ );
619
+ return rows;
620
+ }
621
+ async function featureConfig({ franchiseId, token }) {
622
+ const config = await fetchData(
623
+ `/franchise-info/feature-config/${franchiseId}`,
624
+ token,
625
+ FeatureConfigSchema
626
+ );
627
+ const outDir = import_path2.default.join(process.cwd(), `lib/generated/`);
628
+ const outFile = import_path2.default.join(outDir, "featureConfig.ts");
629
+ const empty = !config.catering && !config.catering_bulk_discount && !config.food_truck;
630
+ if (empty && (0, import_fs2.existsSync)(outFile)) {
631
+ console.warn(
632
+ "prebuild: feature-config came back empty \u2014 keeping the existing lib/generated/featureConfig.ts."
633
+ );
634
+ return;
635
+ }
636
+ (0, import_fs2.mkdirSync)(outDir, { recursive: true });
637
+ (0, import_fs2.writeFileSync)(
638
+ outFile,
639
+ `// AUTO-GENERATED by scripts/prebuild.ts \u2014 do not edit.
640
+ import type {
641
+ CateringSiteConfig,
642
+ CateringBulkDiscount,
643
+ FoodTruckConfigValue,
644
+ } from "@zorgo/next";
645
+
646
+ export const cateringConfig: CateringSiteConfig | null = ${JSON.stringify(config.catering, null, 2)};
647
+
648
+ export const cateringBulkDiscount: CateringBulkDiscount | null = ${JSON.stringify(config.catering_bulk_discount, null, 2)};
649
+
650
+ export const foodTruck = ${JSON.stringify(config.food_truck ?? null, null, 2)} as FoodTruckConfigValue | null;
651
+ `
652
+ );
653
+ }
654
+ var FONT_FILE_RE = /\.(woff2?|ttf|otf)$/i;
655
+ function hasSelfHostedFonts(cwd) {
656
+ const fontsDir = import_path2.default.join(cwd, "public/fonts");
657
+ return (0, import_fs2.existsSync)(fontsDir) && (0, import_fs2.readdirSync)(fontsDir).some((f) => FONT_FILE_RE.test(f));
432
658
  }
433
659
  async function styles({ franchiseId, token }) {
434
660
  const brand = await fetchData(
@@ -450,26 +676,23 @@ async function styles({ franchiseId, token }) {
450
676
  import_path2.default.join(stylesDir, "styles.json"),
451
677
  JSON.stringify(branding, null, 2)
452
678
  );
679
+ const generatedDir = import_path2.default.join(process.cwd(), "app", "generated");
680
+ (0, import_fs2.mkdirSync)(generatedDir, { recursive: true });
453
681
  (0, import_fs2.writeFileSync)(
454
- import_path2.default.join(process.cwd(), "app", "globals.css"),
455
- buildGlobalsCss(brand)
682
+ import_path2.default.join(generatedDir, "branding.css"),
683
+ buildBrandingCss(brand, { selfHostedFont: hasSelfHostedFonts(process.cwd()) })
456
684
  );
457
685
  }
458
- function buildGlobalsCss(branding) {
459
- const fontFamilyParam = branding.fontFamily.replace(/ /g, "+");
686
+ function buildBrandingCss(branding, opts) {
460
687
  const foreground = deriveFontColor(branding.backgroundBase);
461
688
  const primaryForeground = deriveFontColor(branding.primary);
462
689
  const accentForeground = deriveFontColor(branding.accent);
463
690
  const backgroundElevated = deriveElevatedBackground(branding.backgroundBase);
464
691
  const backgroundRecessed = deriveRecessedBackground(branding.backgroundBase);
465
- return `
466
- @import url("https://fonts.googleapis.com/css2?family=${fontFamilyParam}:wght@400;700&display=swap");
467
- @import "tailwindcss";
468
- /* Tailwind v4 skips node_modules; @zorgo/next ships un-obfuscated components so
469
- their class names are scannable here. */
470
- @source "../node_modules/@zorgo/next/dist/components.mjs";
471
-
472
- :root {
692
+ const fontFamilyEscaped = branding.fontFamily.replace(/"/g, '\\"');
693
+ const fontImport = opts.selfHostedFont ? "" : `@import url("https://fonts.googleapis.com/css2?family=${branding.fontFamily.replace(/ /g, "+")}:wght@400;700&display=swap");
694
+ `;
695
+ return `${fontImport}:root {
473
696
  --background: ${branding.backgroundBase};
474
697
  --background-elevated: ${backgroundElevated};
475
698
  --background-recessed: ${backgroundRecessed};
@@ -478,7 +701,7 @@ function buildGlobalsCss(branding) {
478
701
  --primary-foreground: ${primaryForeground};
479
702
  --accent: ${branding.accent};
480
703
  --accent-foreground: ${accentForeground};
481
- --font-sans: "${branding.fontFamily}", sans-serif;
704
+ --font-sans: "${fontFamilyEscaped}", sans-serif;
482
705
  }
483
706
 
484
707
  @theme inline {
@@ -492,22 +715,6 @@ function buildGlobalsCss(branding) {
492
715
  --color-accent-foreground: var(--accent-foreground);
493
716
  --font-sans: var(--font-sans);
494
717
  }
495
-
496
- @layer base {
497
- button,
498
- [type="button"],
499
- [type="submit"],
500
- [type="reset"] {
501
- cursor: pointer;
502
- }
503
-
504
- button:disabled,
505
- [type="button"]:disabled,
506
- [type="submit"]:disabled,
507
- [type="reset"]:disabled {
508
- cursor: not-allowed;
509
- }
510
- }
511
718
  `;
512
719
  }
513
720
  async function runPrebuild() {
@@ -521,13 +728,24 @@ async function runPrebuild() {
521
728
  }
522
729
  const payload = JSON.parse(Buffer.from(encodedPayload, "base64url").toString());
523
730
  const franchiseId = payload.franchise_ids[0];
524
- const [menuData] = await Promise.all([
731
+ const [menuData, locationRows] = await Promise.all([
525
732
  menu({ franchiseId, token }),
733
+ locations({ franchiseId, token }).catch((err) => {
734
+ console.error("locations failed:", err);
735
+ return [];
736
+ }),
526
737
  logo({ franchiseId, token }).catch((err) => console.error("logo failed:", err)),
527
- hours({ franchiseId, token }).catch((err) => console.error("hours failed:", err)),
528
- styles({ franchiseId, token }).catch((err) => console.error("styles failed:", err))
738
+ styles({ franchiseId, token }).catch((err) => console.error("styles failed:", err)),
739
+ featureConfig({ franchiseId, token }).catch(
740
+ (err) => console.error("featureConfig failed:", err)
741
+ ),
742
+ // Purely local (assets/images -> public/images), so it needs no token and
743
+ // rides along with the network work instead of adding to the wall clock.
744
+ optimizeSiteImages(process.cwd()).catch(
745
+ (err) => console.error("site images failed:", err)
746
+ )
529
747
  ]);
530
- await jsonLd({ franchiseId, token }, menuData).catch(
748
+ await jsonLd({ franchiseId, token }, menuData, locationRows).catch(
531
749
  (err) => console.error("jsonLd failed:", err)
532
750
  );
533
751
  console.log("prebuild script completed");
@@ -535,6 +753,8 @@ async function runPrebuild() {
535
753
  // Annotate the CommonJS export names for ESM import in node:
536
754
  0 && (module.exports = {
537
755
  SiteTokenError,
756
+ buildBrandingCss,
757
+ buildWranglerToml,
538
758
  fetchSiteToken,
539
759
  runPrebuild
540
760
  });