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