restaurant-kit 0.1.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/LICENSE +21 -0
- package/README.md +488 -0
- package/dist/RestaurantSiteRoutes.js +23 -0
- package/dist/admin/AdminActionMenu.js +29 -0
- package/dist/admin/AdminAuthPanel.js +53 -0
- package/dist/admin/AdminBackupsDialog.js +54 -0
- package/dist/admin/AdminDraftProvider.js +10 -0
- package/dist/admin/AdminGroupsPanel.js +68 -0
- package/dist/admin/AdminItemDrawer.js +264 -0
- package/dist/admin/AdminMenuCard.js +63 -0
- package/dist/admin/AdminMenuEditor.module.css.js +209 -0
- package/dist/admin/AdminMenuGroupTabs.js +43 -0
- package/dist/admin/AdminMenuIconPicker.js +39 -0
- package/dist/admin/AdminRoutes.js +13 -0
- package/dist/admin/AdminSectionEditor.js +104 -0
- package/dist/admin/AdminTestimonialsPanel.js +214 -0
- package/dist/admin/AdminTestimonialsPanel.module.css.js +29 -0
- package/dist/admin/adminDraftContext.js +5 -0
- package/dist/admin/adminEditorUtils.js +47 -0
- package/dist/admin/adminMenuCommands.js +83 -0
- package/dist/admin/useAdminDraft.js +14 -0
- package/dist/admin/useAdminMenuDraft.js +226 -0
- package/dist/cart/CartDrawer.js +87 -0
- package/dist/cart/CartDrawer.module.css.js +47 -0
- package/dist/cart/CartProvider.js +31 -0
- package/dist/cart/cartContext.js +5 -0
- package/dist/cart/cartOrder.js +34 -0
- package/dist/cart/cartReducer.js +57 -0
- package/dist/cart/useCart.js +10 -0
- package/dist/components/Brand.js +16 -0
- package/dist/components/Brand.module.css.js +15 -0
- package/dist/components/Footer.js +19 -0
- package/dist/components/Footer.module.css.js +11 -0
- package/dist/components/Header.js +34 -0
- package/dist/components/Header.module.css.js +19 -0
- package/dist/components/Hero.js +23 -0
- package/dist/components/Hero.module.css.js +15 -0
- package/dist/components/InstagramIcon.js +27 -0
- package/dist/components/LocationsSection.js +34 -0
- package/dist/components/LocationsSection.module.css.js +17 -0
- package/dist/components/Marquee.js +10 -0
- package/dist/components/Marquee.module.css.js +9 -0
- package/dist/components/OrderIcon.js +11 -0
- package/dist/components/RatingSection.js +32 -0
- package/dist/components/RatingSection.module.css.js +19 -0
- package/dist/components/TestimonialsSection.js +73 -0
- package/dist/components/TestimonialsSection.module.css.js +37 -0
- package/dist/components/WhatsAppIcon.js +13 -0
- package/dist/config/RestaurantKitProvider.js +26 -0
- package/dist/config/siteConfigContext.js +23 -0
- package/dist/config/useOrdering.js +12 -0
- package/dist/index.js +92 -0
- package/dist/menu/localMenuApi.js +43 -0
- package/dist/menu/menuApi.js +121 -0
- package/dist/menu/menuDraftStorage.js +35 -0
- package/dist/menu/menuIcons.js +24 -0
- package/dist/menu/menuMutations.js +99 -0
- package/dist/menu/menuOrdering.js +94 -0
- package/dist/menu/menuSchema.js +4 -0
- package/dist/menu/priceFormat.js +39 -0
- package/dist/menu/useMenuData.js +38 -0
- package/dist/menu/validateMenu.js +63 -0
- package/dist/menu-browser/MenuBrowser.js +54 -0
- package/dist/menu-browser/MenuBrowser.module.css.js +153 -0
- package/dist/menu-browser/MenuCard.js +106 -0
- package/dist/menu-browser/MenuCartDrawer.js +18 -0
- package/dist/menu-browser/MenuCategoryBlock.js +45 -0
- package/dist/menu-browser/MenuItemSheet.js +132 -0
- package/dist/menu-browser/MenuLightbox.js +33 -0
- package/dist/menu-browser/MenuRow.js +43 -0
- package/dist/menu-browser/MenuSection.js +10 -0
- package/dist/menu-browser/menuItemDisplay.js +19 -0
- package/dist/menu-browser/useMediaQuery.js +19 -0
- package/dist/menu-browser/useMenuBrowserState.js +82 -0
- package/dist/menu-browser/useParkedSheet.js +32 -0
- package/dist/pages/AdminMenuEditor.js +291 -0
- package/dist/pages/AdminPreview.js +43 -0
- package/dist/pages/AdminPreview.module.css.js +9 -0
- package/dist/pages/MenuSite.js +40 -0
- package/dist/restaurant-kit.css +1 -0
- package/dist/testimonials/downscaleImage.js +14 -0
- package/dist/testimonials/testimonialMutations.js +52 -0
- package/dist/types/RestaurantSiteRoutes.d.ts +11 -0
- package/dist/types/admin/AdminActionMenu.d.ts +20 -0
- package/dist/types/admin/AdminAuthPanel.d.ts +13 -0
- package/dist/types/admin/AdminBackupsDialog.d.ts +9 -0
- package/dist/types/admin/AdminDraftProvider.d.ts +12 -0
- package/dist/types/admin/AdminGroupsPanel.d.ts +14 -0
- package/dist/types/admin/AdminItemDrawer.d.ts +16 -0
- package/dist/types/admin/AdminMenuCard.d.ts +10 -0
- package/dist/types/admin/AdminMenuGroupTabs.d.ts +11 -0
- package/dist/types/admin/AdminMenuIconPicker.d.ts +8 -0
- package/dist/types/admin/AdminRoutes.d.ts +5 -0
- package/dist/types/admin/AdminSectionEditor.d.ts +20 -0
- package/dist/types/admin/AdminTestimonialsPanel.d.ts +7 -0
- package/dist/types/admin/adminDraftContext.d.ts +2 -0
- package/dist/types/admin/adminEditorUtils.d.ts +22 -0
- package/dist/types/admin/adminMenuCommands.d.ts +73 -0
- package/dist/types/admin/useAdminDraft.d.ts +5 -0
- package/dist/types/admin/useAdminMenuDraft.d.ts +41 -0
- package/dist/types/cart/CartDrawer.d.ts +7 -0
- package/dist/types/cart/CartProvider.d.ts +4 -0
- package/dist/types/cart/cartContext.d.ts +2 -0
- package/dist/types/cart/cartOrder.d.ts +9 -0
- package/dist/types/cart/cartReducer.d.ts +36 -0
- package/dist/types/cart/cartTypes.d.ts +37 -0
- package/dist/types/cart/useCart.d.ts +1 -0
- package/dist/types/components/Brand.d.ts +5 -0
- package/dist/types/components/Footer.d.ts +1 -0
- package/dist/types/components/Header.d.ts +1 -0
- package/dist/types/components/Hero.d.ts +1 -0
- package/dist/types/components/InstagramIcon.d.ts +6 -0
- package/dist/types/components/LocationsSection.d.ts +1 -0
- package/dist/types/components/Marquee.d.ts +1 -0
- package/dist/types/components/OrderIcon.d.ts +4 -0
- package/dist/types/components/RatingSection.d.ts +1 -0
- package/dist/types/components/TestimonialsSection.d.ts +2 -0
- package/dist/types/components/WhatsAppIcon.d.ts +5 -0
- package/dist/types/config/RestaurantKitProvider.d.ts +24 -0
- package/dist/types/config/siteConfigContext.d.ts +28 -0
- package/dist/types/config/siteTypes.d.ts +50 -0
- package/dist/types/config/useOrdering.d.ts +16 -0
- package/dist/types/index.d.ts +38 -0
- package/dist/types/menu/localMenuApi.d.ts +9 -0
- package/dist/types/menu/menuApi.d.ts +68 -0
- package/dist/types/menu/menuDraftStorage.d.ts +5 -0
- package/dist/types/menu/menuIcons.d.ts +7 -0
- package/dist/types/menu/menuMutations.d.ts +13 -0
- package/dist/types/menu/menuOrdering.d.ts +38 -0
- package/dist/types/menu/menuSchema.d.ts +68 -0
- package/dist/types/menu/priceFormat.d.ts +8 -0
- package/dist/types/menu/useMenuData.d.ts +12 -0
- package/dist/types/menu/validateMenu.d.ts +16 -0
- package/dist/types/menu-browser/MenuBrowser.d.ts +6 -0
- package/dist/types/menu-browser/MenuCard.d.ts +7 -0
- package/dist/types/menu-browser/MenuCartDrawer.d.ts +5 -0
- package/dist/types/menu-browser/MenuCategoryBlock.d.ts +11 -0
- package/dist/types/menu-browser/MenuItemSheet.d.ts +10 -0
- package/dist/types/menu-browser/MenuLightbox.d.ts +9 -0
- package/dist/types/menu-browser/MenuRow.d.ts +7 -0
- package/dist/types/menu-browser/MenuSection.d.ts +1 -0
- package/dist/types/menu-browser/menuItemDisplay.d.ts +5 -0
- package/dist/types/menu-browser/useMediaQuery.d.ts +1 -0
- package/dist/types/menu-browser/useMenuBrowserState.d.ts +17 -0
- package/dist/types/menu-browser/useParkedSheet.d.ts +13 -0
- package/dist/types/pages/AdminMenuEditor.d.ts +1 -0
- package/dist/types/pages/AdminPreview.d.ts +1 -0
- package/dist/types/pages/MenuSite.d.ts +6 -0
- package/dist/types/testimonials/downscaleImage.d.ts +6 -0
- package/dist/types/testimonials/testimonialMutations.d.ts +9 -0
- package/dist/types/utils.d.ts +1 -0
- package/dist/types/worker/auth.d.ts +97 -0
- package/dist/types/worker/http.d.ts +17 -0
- package/dist/types/worker/index.d.ts +16 -0
- package/dist/types/worker/menuStorage.d.ts +94 -0
- package/dist/utils.js +6 -0
- package/dist/worker/auth.js +183 -0
- package/dist/worker/http.js +65 -0
- package/dist/worker/menuStorage.js +95 -0
- package/dist/worker.js +131 -0
- package/package.json +105 -0
- package/scripts/bootstrap-site.mjs +125 -0
- package/scripts/hash-admin-password.mjs +11 -0
- package/scripts/lib/password.mjs +72 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// One-shot provisioning for a restaurant-kit site (or one of its wrangler envs).
|
|
3
|
+
//
|
|
4
|
+
// npx restaurant-kit-bootstrap --bucket demo-menu-staging --env staging --seed-from demo-menu
|
|
5
|
+
// npx restaurant-kit-bootstrap --bucket demo-menu --restaurant demo
|
|
6
|
+
// npx restaurant-kit-bootstrap --dev-vars # writes .dev.vars for `wrangler dev`
|
|
7
|
+
//
|
|
8
|
+
// Steps (each skippable with --skip-<step>): bucket, seed, secrets, dry-run. Run from the site
|
|
9
|
+
// directory (where wrangler.toml lives). Needs `wrangler login` done first.
|
|
10
|
+
import { spawnSync } from "node:child_process";
|
|
11
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { hashPassword, randomSessionSecret, readPassword } from "./lib/password.mjs";
|
|
15
|
+
|
|
16
|
+
const args = parseArgs(process.argv.slice(2));
|
|
17
|
+
if (args.help) {
|
|
18
|
+
console.log(readFileSync(new URL(import.meta.url)).toString().split("\n").slice(1, 9).map((line) => line.replace(/^\/\/ ?/, "")).join("\n"));
|
|
19
|
+
process.exit(0);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const envFlag = args.env ? ["--env", args.env] : [];
|
|
23
|
+
const restaurantId = args.restaurant ?? readRestaurantIdFromToml(args.env);
|
|
24
|
+
const label = args.env ? `env "${args.env}"` : "production";
|
|
25
|
+
|
|
26
|
+
function wrangler(cmdArgs, options = {}) {
|
|
27
|
+
const result = spawnSync("npx", ["wrangler", ...cmdArgs], {
|
|
28
|
+
stdio: options.input === undefined ? "inherit" : ["pipe", "inherit", "inherit"],
|
|
29
|
+
input: options.input,
|
|
30
|
+
encoding: "utf8",
|
|
31
|
+
});
|
|
32
|
+
if (result.status !== 0 && !options.allowFailure) {
|
|
33
|
+
console.error(`\nwrangler ${cmdArgs.join(" ")} failed (exit ${result.status}).`);
|
|
34
|
+
process.exit(result.status ?? 1);
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function step(title) {
|
|
40
|
+
console.log(`\n== ${title}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (args["dev-vars"]) {
|
|
44
|
+
step("Local .dev.vars for `wrangler dev`");
|
|
45
|
+
if (existsSync(".dev.vars") && !args.force) {
|
|
46
|
+
console.log(".dev.vars already exists; pass --force to overwrite.");
|
|
47
|
+
} else {
|
|
48
|
+
const password = await readPassword(args.password);
|
|
49
|
+
writeFileSync(".dev.vars", `ADMIN_PASSWORD_HASH="${hashPassword(password)}"\nADMIN_SESSION_SECRET="${randomSessionSecret()}"\n`, { mode: 0o600 });
|
|
50
|
+
console.log("Wrote .dev.vars (gitignored). Start the stack with:\n npx wrangler dev # API + built site on :8787\n npm run dev:worker # vite on :5173 talking to :8787");
|
|
51
|
+
}
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!args.bucket) {
|
|
56
|
+
console.error("Missing --bucket <name>. See --help.");
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!args["skip-bucket"]) {
|
|
61
|
+
step(`R2 bucket ${args.bucket}`);
|
|
62
|
+
const created = wrangler(["r2", "bucket", "create", args.bucket], { allowFailure: true });
|
|
63
|
+
if (created.status !== 0) console.log("(bucket create failed — it probably already exists; continuing)");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (args["seed-from"] && !args["skip-seed"]) {
|
|
67
|
+
step(`Seed ${args.bucket} from ${args["seed-from"]} (published → published + draft)`);
|
|
68
|
+
const dir = mkdtempSync(join(tmpdir(), "rk-seed-"));
|
|
69
|
+
const file = join(dir, "menu.json");
|
|
70
|
+
wrangler(["r2", "object", "get", `${args["seed-from"]}/restaurants/${restaurantId}/published/menu.json`, "--file", file, "--remote"]);
|
|
71
|
+
for (const target of ["published", "draft"]) {
|
|
72
|
+
wrangler(["r2", "object", "put", `${args.bucket}/restaurants/${restaurantId}/${target}/menu.json`, "--file", file, "--content-type", "application/json", "--remote"]);
|
|
73
|
+
}
|
|
74
|
+
rmSync(dir, { recursive: true, force: true });
|
|
75
|
+
} else if (!args["skip-seed"]) {
|
|
76
|
+
console.log(`\n(no --seed-from: the ${label} draft/published menu starts empty; the site shows its bundled seed until you publish)`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!args["skip-secrets"]) {
|
|
80
|
+
step(`Admin secrets for ${label}`);
|
|
81
|
+
const password = await readPassword(args.password);
|
|
82
|
+
wrangler(["secret", "put", "ADMIN_PASSWORD_HASH", ...envFlag], { input: hashPassword(password) });
|
|
83
|
+
wrangler(["secret", "put", "ADMIN_SESSION_SECRET", ...envFlag], { input: randomSessionSecret() });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!args["skip-dry-run"]) {
|
|
87
|
+
step(`Deploy dry run (${label})`);
|
|
88
|
+
// `--env=` targets the top-level (production) config explicitly; wrangler warns when envs exist and none is named.
|
|
89
|
+
wrangler(["deploy", "--dry-run", ...(args.env ? envFlag : ["--env="])], { allowFailure: true });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
step("Remaining manual steps");
|
|
93
|
+
console.log(
|
|
94
|
+
[
|
|
95
|
+
"1. Zone: the domain must be on this Cloudflare account (registrar nameservers → Cloudflare).",
|
|
96
|
+
"2. DNS: a hostname you list as `custom_domain = true` must have NO existing A/AAAA/CNAME record,",
|
|
97
|
+
" or the deploy fails with 409. Delete the record, or use a zone route (`pattern = \"host/*\"`).",
|
|
98
|
+
"3. Old Pages project on the same hostname? Remove its custom domain first.",
|
|
99
|
+
`4. Deploy: npx wrangler deploy --env${args.env ? ` ${args.env}` : '=""'}`,
|
|
100
|
+
'5. Check: curl -s https://<host>/api/admin/session → {"strategy":"password",...}',
|
|
101
|
+
].join("\n"),
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
function parseArgs(argv) {
|
|
105
|
+
const out = {};
|
|
106
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
107
|
+
const arg = argv[index];
|
|
108
|
+
if (!arg.startsWith("--")) continue;
|
|
109
|
+
const key = arg.slice(2);
|
|
110
|
+
const next = argv[index + 1];
|
|
111
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
112
|
+
out[key] = next;
|
|
113
|
+
index += 1;
|
|
114
|
+
} else out[key] = true;
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function readRestaurantIdFromToml(env) {
|
|
120
|
+
if (!existsSync("wrangler.toml")) return "restaurant";
|
|
121
|
+
const toml = readFileSync("wrangler.toml", "utf8");
|
|
122
|
+
const section = env ? toml.split(`[env.${env}.vars]`)[1] : undefined;
|
|
123
|
+
const match = section?.match(/RESTAURANT_ID\s*=\s*"([^"]+)"/) ?? toml.match(/RESTAURANT_ID\s*=\s*"([^"]+)"/);
|
|
124
|
+
return match?.[1] ?? "restaurant";
|
|
125
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Prints the ADMIN_PASSWORD_HASH value for the restaurant-kit Worker.
|
|
3
|
+
//
|
|
4
|
+
// npx restaurant-kit-hash-password # prompts (input hidden)
|
|
5
|
+
// echo -n 'pass' | npx restaurant-kit-hash-password
|
|
6
|
+
// npx restaurant-kit-hash-password 'pass' # lands in shell history; avoid
|
|
7
|
+
import { hashPassword, readPassword } from "./lib/password.mjs";
|
|
8
|
+
|
|
9
|
+
const password = await readPassword(process.argv[2]);
|
|
10
|
+
console.log(hashPassword(password));
|
|
11
|
+
console.error("\nStore it on the Worker (never in wrangler.toml):\n wrangler secret put ADMIN_PASSWORD_HASH\n wrangler secret put ADMIN_SESSION_SECRET # e.g. openssl rand -base64 32");
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { pbkdf2Sync, randomBytes } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
// Workers Free plan caps PBKDF2 at 100k iterations.
|
|
4
|
+
export const defaultIterations = Number(process.env.PBKDF2_ITERATIONS ?? 100_000);
|
|
5
|
+
|
|
6
|
+
const CTRL_C = "\u0003";
|
|
7
|
+
const BACKSPACE = "\u007f";
|
|
8
|
+
|
|
9
|
+
/** Same format the Worker verifies: pbkdf2-sha256$<iterations>$<salt>$<hash>. */
|
|
10
|
+
export function hashPassword(password, iterations = defaultIterations) {
|
|
11
|
+
const salt = randomBytes(16);
|
|
12
|
+
const hash = pbkdf2Sync(password, salt, iterations, 32, "sha256");
|
|
13
|
+
return ["pbkdf2-sha256", String(iterations), salt.toString("base64url"), hash.toString("base64url")].join("$");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function randomSessionSecret() {
|
|
17
|
+
return randomBytes(32).toString("base64");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function readHidden(prompt) {
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
const { stdin, stderr } = process;
|
|
23
|
+
let value = "";
|
|
24
|
+
stderr.write(prompt);
|
|
25
|
+
stdin.setRawMode(true);
|
|
26
|
+
stdin.resume();
|
|
27
|
+
stdin.setEncoding("utf8");
|
|
28
|
+
const onData = (chunk) => {
|
|
29
|
+
for (const char of chunk) {
|
|
30
|
+
if (char === CTRL_C) process.exit(130);
|
|
31
|
+
if (char === "\r" || char === "\n") {
|
|
32
|
+
stdin.setRawMode(false);
|
|
33
|
+
stdin.pause();
|
|
34
|
+
stdin.off("data", onData);
|
|
35
|
+
stderr.write("\n");
|
|
36
|
+
resolve(value);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (char === BACKSPACE || char === "\b") value = value.slice(0, -1);
|
|
40
|
+
else value += char;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
stdin.on("data", onData);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function readStdin() {
|
|
48
|
+
let data = "";
|
|
49
|
+
for await (const chunk of process.stdin) data += chunk;
|
|
50
|
+
return data.replace(/\r?\n$/, "");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** argv value, else piped stdin, else a hidden double prompt on a TTY. Exits on mismatch/empty. */
|
|
54
|
+
export async function readPassword(argValue) {
|
|
55
|
+
let password;
|
|
56
|
+
if (argValue) password = argValue;
|
|
57
|
+
else if (!process.stdin.isTTY) password = await readStdin();
|
|
58
|
+
else {
|
|
59
|
+
const first = await readHidden("Admin password: ");
|
|
60
|
+
const second = await readHidden("Repeat password: ");
|
|
61
|
+
if (first !== second) {
|
|
62
|
+
console.error("Passwords do not match.");
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
password = first;
|
|
66
|
+
}
|
|
67
|
+
if (!password) {
|
|
68
|
+
console.error("Password must not be empty.");
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
return password;
|
|
72
|
+
}
|