@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.
- package/dist/cli.d.mts +9 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +44 -66
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +43 -65
- package/dist/cli.mjs.map +1 -1
- package/dist/components.d.mts +735 -5
- package/dist/components.d.ts +735 -5
- package/dist/components.js +3692 -525
- package/dist/components.js.map +1 -1
- package/dist/components.mjs +3663 -540
- package/dist/components.mjs.map +1 -1
- package/dist/images.d.mts +25 -13
- package/dist/images.d.ts +25 -13
- package/dist/images.js +18 -8
- package/dist/images.js.map +1 -1
- package/dist/images.mjs +15 -8
- package/dist/images.mjs.map +1 -1
- package/dist/index.d.mts +315 -9
- package/dist/index.d.ts +315 -9
- package/dist/index.js +58 -52
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +58 -52
- package/dist/index.mjs.map +1 -1
- package/dist/item-page.d.mts +6 -0
- package/dist/item-page.d.ts +6 -0
- package/dist/item-page.js +4 -2
- package/dist/item-page.js.map +1 -1
- package/dist/item-page.mjs +4 -2
- package/dist/item-page.mjs.map +1 -1
- package/dist/legal.d.mts +31 -0
- package/dist/legal.d.ts +31 -0
- package/dist/legal.js +322 -0
- package/dist/legal.js.map +1 -0
- package/dist/legal.mjs +293 -0
- package/dist/legal.mjs.map +1 -0
- package/dist/seo.d.mts +56 -1
- package/dist/seo.d.ts +56 -1
- package/dist/seo.js +111 -2
- package/dist/seo.js.map +1 -1
- package/dist/seo.mjs +106 -1
- package/dist/seo.mjs.map +1 -1
- package/dist/server.d.mts +27 -1
- package/dist/server.d.ts +27 -1
- package/dist/server.js +322 -102
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +322 -104
- package/dist/server.mjs.map +1 -1
- package/dist/slugs.d.mts +2 -1
- package/dist/slugs.d.ts +2 -1
- package/dist/slugs.js +2 -0
- package/dist/slugs.js.map +1 -1
- package/dist/slugs.mjs +1 -0
- package/dist/tree/README.md +2 -2
- package/dist/tree/app/error.tsx +12 -3
- package/dist/tree/app/global-error.tsx +21 -4
- package/dist/tree/app/globals.css +41 -0
- package/dist/tree/app/not-found.tsx +17 -3
- package/dist/tree/app/privacy/page.tsx.gen.ts +28 -9
- package/dist/tree/app/terms/page.tsx.gen.ts +26 -10
- package/dist/tree/gitignore +6 -1
- package/dist/tree/next.config.ts +6 -5
- package/dist/tree/open-next.config.ts +7 -0
- package/dist/tree/package.json.gen.ts +8 -2
- package/package.json +7 -2
- 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/
|
|
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
|
|
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
|
|
169
|
+
var IMAGE_WIDTHS = [384, 640, 1080, 1920];
|
|
76
170
|
var ITEM_IMAGE_PREFIX = "/images/items/";
|
|
77
171
|
|
|
78
|
-
// src/internal/
|
|
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
|
|
104
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
220
|
-
var
|
|
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
|
|
251
|
-
|
|
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
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const
|
|
271
|
-
const
|
|
272
|
-
const
|
|
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 = ["
|
|
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
|
-
|
|
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
|
|
395
|
-
await fetchData(
|
|
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(
|
|
419
|
-
|
|
644
|
+
path2.join(generatedDir, "branding.css"),
|
|
645
|
+
buildBrandingCss(brand, { selfHostedFont: hasSelfHostedFonts(process.cwd()) })
|
|
420
646
|
);
|
|
421
647
|
}
|
|
422
|
-
function
|
|
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
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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: "${
|
|
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
|
-
|
|
492
|
-
|
|
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
|
};
|