sproutkit-code 0.9.0 → 0.10.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 (2) hide show
  1. package/dist/index.js +75 -21
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -187,6 +187,10 @@ var require_db = __commonJS({
187
187
  db2.exec("ALTER TABLE leads ADD COLUMN dead_at TEXT");
188
188
  } catch {
189
189
  }
190
+ try {
191
+ db2.exec("ALTER TABLE leads ADD COLUMN site_uid TEXT");
192
+ } catch {
193
+ }
190
194
  }
191
195
  function getDb2() {
192
196
  return db2;
@@ -32728,6 +32732,12 @@ var require_cms_runtime = __commonJS({
32728
32732
  exports2.hashPassword = hashPassword;
32729
32733
  exports2.generateSessionSecret = generateSessionSecret;
32730
32734
  exports2.API_CONTENT_JS = `import { list, put } from "@vercel/blob";
32735
+
32736
+ // Per-site Blob namespace so many CMS sites share one store (one token per
32737
+ // agency) without overwriting each other's content. Empty when SITE_ID unset \u2192
32738
+ // legacy sites keep the flat content.json key, so nothing deployed breaks.
32739
+ const PFX = process.env.SITE_ID ? "sites/" + process.env.SITE_ID + "/" : "";
32740
+ const CONTENT_KEY = PFX + "content.json";
32731
32741
  import { createHmac, timingSafeEqual } from "node:crypto";
32732
32742
 
32733
32743
  // Runs on Node.js (not Edge) because @vercel/blob's undici dependency
@@ -32795,8 +32805,8 @@ function sanitise(raw) {
32795
32805
 
32796
32806
  async function readContent() {
32797
32807
  try {
32798
- const { blobs } = await list({ prefix: "content.json", limit: 1 });
32799
- const target = blobs.find((b) => b.pathname === "content.json");
32808
+ const { blobs } = await list({ prefix: CONTENT_KEY, limit: 1 });
32809
+ const target = blobs.find((b) => b.pathname === CONTENT_KEY);
32800
32810
  if (!target) return SAFE_DEFAULTS;
32801
32811
  const res = await fetch(target.url);
32802
32812
  if (!res.ok) return SAFE_DEFAULTS;
@@ -32864,7 +32874,7 @@ export default async function handler(req, res) {
32864
32874
  body = raw ? JSON.parse(raw) : null;
32865
32875
  }
32866
32876
  const clean = sanitise(body);
32867
- await put("content.json", JSON.stringify(clean), {
32877
+ await put(CONTENT_KEY, JSON.stringify(clean), {
32868
32878
  access: "public",
32869
32879
  addRandomSuffix: false,
32870
32880
  allowOverwrite: true,
@@ -32988,6 +32998,10 @@ export default function handler(req, res) {
32988
32998
 
32989
32999
  export const config = { runtime: "nodejs", api: { bodyParser: false } };
32990
33000
 
33001
+ // Per-site namespace so uploaded photos don't collide across sites sharing one
33002
+ // Blob store. Empty when SITE_ID unset (legacy sites keep flat photos/ path).
33003
+ const PFX = process.env.SITE_ID ? "sites/" + process.env.SITE_ID + "/" : "";
33004
+
32991
33005
  async function verifySession(req) {
32992
33006
  const cookie = req.headers.cookie || "";
32993
33007
  const match = cookie.match(/sk_admin=([^;]+)/);
@@ -33036,7 +33050,7 @@ export default async function handler(req, res) {
33036
33050
  res.status(413).json({ error: "too_large" });
33037
33051
  return;
33038
33052
  }
33039
- const name = "photos/" + Date.now() + "-" + Math.random().toString(36).slice(2, 8) + ".webp";
33053
+ const name = PFX + "photos/" + Date.now() + "-" + Math.random().toString(36).slice(2, 8) + ".webp";
33040
33054
  const blob = await put(name, buf, {
33041
33055
  access: "public",
33042
33056
  addRandomSuffix: false,
@@ -33804,6 +33818,13 @@ import { createHmac, timingSafeEqual } from "node:crypto";
33804
33818
 
33805
33819
  export const config = { runtime: "nodejs" };
33806
33820
 
33821
+ // Per-site Blob namespace: every site isolates its files under sites/<id>/ so
33822
+ // many booking sites can share ONE Blob store (one token per agency) without
33823
+ // overwriting each other. Empty when SITE_ID is unset \u2192 older sites keep
33824
+ // reading/writing the legacy flat keys, so nothing already deployed breaks.
33825
+ const PFX = process.env.SITE_ID ? "sites/" + process.env.SITE_ID + "/" : "";
33826
+ const CONFIG_KEY = PFX + "booking-config.json";
33827
+
33807
33828
  const SAFE_DEFAULTS = {
33808
33829
  archetype: "enquiry",
33809
33830
  services: [],
@@ -33856,8 +33877,8 @@ function sanitise(raw) {
33856
33877
 
33857
33878
  async function readConfig() {
33858
33879
  try {
33859
- const { blobs } = await list({ prefix: "booking-config.json", limit: 1 });
33860
- const target = blobs.find((b) => b.pathname === "booking-config.json");
33880
+ const { blobs } = await list({ prefix: CONFIG_KEY, limit: 1 });
33881
+ const target = blobs.find((b) => b.pathname === CONFIG_KEY);
33861
33882
  if (!target) return SAFE_DEFAULTS;
33862
33883
  const res = await fetch(target.url);
33863
33884
  if (!res.ok) return SAFE_DEFAULTS;
@@ -33912,7 +33933,7 @@ export default async function handler(req, res) {
33912
33933
  body = raw ? JSON.parse(raw) : null;
33913
33934
  }
33914
33935
  const clean = sanitise(body);
33915
- await put("booking-config.json", JSON.stringify(clean), {
33936
+ await put(CONFIG_KEY, JSON.stringify(clean), {
33916
33937
  access: "public", addRandomSuffix: false, allowOverwrite: true,
33917
33938
  contentType: "application/json",
33918
33939
  });
@@ -33930,6 +33951,10 @@ import { createHmac, timingSafeEqual } from "node:crypto";
33930
33951
 
33931
33952
  export const config = { runtime: "nodejs" };
33932
33953
 
33954
+ // Per-site namespace (see booking-config.js). Empty when SITE_ID unset.
33955
+ const PFX = process.env.SITE_ID ? "sites/" + process.env.SITE_ID + "/" : "";
33956
+ const BOOKINGS_KEY = PFX + "bookings.jsonl";
33957
+
33933
33958
  function verifySession(req) {
33934
33959
  const cookie = req.headers.cookie || "";
33935
33960
  const match = cookie.match(/sk_admin=([^;]+)/);
@@ -33959,8 +33984,8 @@ function readBody(req) {
33959
33984
 
33960
33985
  async function readAllBookings() {
33961
33986
  try {
33962
- const { blobs } = await list({ prefix: "bookings.jsonl", limit: 1 });
33963
- const target = blobs.find((b) => b.pathname === "bookings.jsonl");
33987
+ const { blobs } = await list({ prefix: BOOKINGS_KEY, limit: 1 });
33988
+ const target = blobs.find((b) => b.pathname === BOOKINGS_KEY);
33964
33989
  if (!target) return [];
33965
33990
  const res = await fetch(target.url);
33966
33991
  if (!res.ok) return [];
@@ -33975,7 +34000,7 @@ async function readAllBookings() {
33975
34000
 
33976
34001
  async function writeAllBookings(bookings) {
33977
34002
  const text = bookings.map((b) => JSON.stringify(b)).join("\\n");
33978
- await put("bookings.jsonl", text, {
34003
+ await put(BOOKINGS_KEY, text, {
33979
34004
  access: "public", addRandomSuffix: false, allowOverwrite: true,
33980
34005
  contentType: "application/x-ndjson",
33981
34006
  });
@@ -79485,7 +79510,9 @@ var require_builder = __commonJS({
79485
79510
  exports2.deployStandaloneBooking = deployStandaloneBooking2;
79486
79511
  var sdk_1 = __importDefault(require_sdk());
79487
79512
  var axios_1 = __importDefault(require_axios());
79513
+ var crypto_1 = require("crypto");
79488
79514
  var runtime_1 = require_runtime();
79515
+ var db_1 = require_db();
79489
79516
  var llm_1 = require_llm();
79490
79517
  var fs3 = __importStar(require("fs"));
79491
79518
  var path3 = __importStar(require("path"));
@@ -79493,6 +79520,15 @@ var require_builder = __commonJS({
79493
79520
  var cms_template_1 = require_cms_template();
79494
79521
  var cms_runtime_1 = require_cms_runtime();
79495
79522
  var booking_runtime_1 = require_booking_runtime();
79523
+ function siteUidFor(leadId) {
79524
+ const db2 = (0, db_1.getDb)();
79525
+ const row = db2.get("SELECT site_uid FROM leads WHERE id = ?", [leadId]);
79526
+ if (row?.site_uid)
79527
+ return row.site_uid;
79528
+ const uid = (0, crypto_1.randomUUID)();
79529
+ db2.run("UPDATE leads SET site_uid = ? WHERE id = ?", [uid, leadId]);
79530
+ return uid;
79531
+ }
79496
79532
  var STOCK_HOSTS = [
79497
79533
  "unsplash.com",
79498
79534
  "images.unsplash.com",
@@ -80058,6 +80094,7 @@ export default defineConfig({ plugins: [react()] });`;
80058
80094
  devDependencies: { "@vitejs/plugin-react": "^4.3.1", vite: "^5.4.2" }
80059
80095
  }) : packageJson;
80060
80096
  if (includeCms) {
80097
+ const cmsSiteUid = siteUidFor(lead.id);
80061
80098
  onProgress(reuseAdminPassword ? "Reusing existing credentials\u2026" : "Generating admin credentials\u2026");
80062
80099
  const adminPassword = reuseAdminPassword ?? (0, cms_runtime_1.generateFriendlyPassword)();
80063
80100
  const adminHash = await (0, cms_runtime_1.hashPassword)(adminPassword);
@@ -80092,7 +80129,7 @@ export default defineConfig({ plugins: [react()] });`;
80092
80129
  onProgress("Seeding CMS content\u2026");
80093
80130
  try {
80094
80131
  const { put } = await Promise.resolve().then(() => __importStar(require_dist5()));
80095
- await put("content.json", JSON.stringify(initialContent), {
80132
+ await put(`sites/${cmsSiteUid}/content.json`, JSON.stringify(initialContent), {
80096
80133
  access: "public",
80097
80134
  addRandomSuffix: false,
80098
80135
  allowOverwrite: true,
@@ -80107,6 +80144,9 @@ export default defineConfig({ plugins: [react()] });`;
80107
80144
  onProgress("Setting CMS env vars\u2026");
80108
80145
  await setProjectEnv(projectName, vercelToken, {
80109
80146
  BLOB_READ_WRITE_TOKEN: blobToken,
80147
+ // Namespaces this site's Blob files so it never collides with other
80148
+ // sites sharing the same store/token.
80149
+ SITE_ID: cmsSiteUid,
80110
80150
  ADMIN_PASSWORD_HASH: adminHash,
80111
80151
  SESSION_SECRET: sessionSecret,
80112
80152
  BUSINESS_NAME: lead.name
@@ -80224,12 +80264,16 @@ export default defineConfig({ plugins: [react()] });`;
80224
80264
  throw new Error("Couldn't fetch src/App.jsx from the existing deployment. Try Regenerate website first, then Redeploy CMS.");
80225
80265
  }
80226
80266
  onProgress("Refreshing CMS env vars\u2026");
80227
- await setProjectEnv(projectName, vercelToken, {
80267
+ const existingUid = (0, db_1.getDb)().get("SELECT site_uid FROM leads WHERE id = ?", [lead.id])?.site_uid;
80268
+ const cmsEnv = {
80228
80269
  BLOB_READ_WRITE_TOKEN: blobToken,
80229
80270
  ADMIN_PASSWORD_HASH: adminHash,
80230
80271
  SESSION_SECRET: sessionSecret,
80231
80272
  BUSINESS_NAME: lead.name
80232
- });
80273
+ };
80274
+ if (existingUid)
80275
+ cmsEnv.SITE_ID = existingUid;
80276
+ await setProjectEnv(projectName, vercelToken, cmsEnv);
80233
80277
  onProgress("Deploying\u2026");
80234
80278
  const res = await postVercelDeployment({
80235
80279
  name: projectName,
@@ -80293,10 +80337,12 @@ createRoot(document.getElementById('root')).render(<StrictMode><App /></StrictMo
80293
80337
  import react from '@vitejs/plugin-react';
80294
80338
  export default defineConfig({ plugins: [react()] });`;
80295
80339
  files.push({ file: "index.html", data: indexHtml, encoding: "utf-8" }, { file: "src/main.jsx", data: mainJsx, encoding: "utf-8" }, { file: "vite.config.js", data: viteConfig, encoding: "utf-8" });
80340
+ const bookingExistingUid = (0, db_1.getDb)().get("SELECT site_uid FROM leads WHERE id = ?", [lead.id])?.site_uid;
80341
+ const bookingKey = bookingExistingUid ? `sites/${bookingExistingUid}/booking-config.json` : "booking-config.json";
80296
80342
  onProgress("Seeding booking config into Blob\u2026");
80297
80343
  try {
80298
80344
  const { put } = await Promise.resolve().then(() => __importStar(require_dist5()));
80299
- await put("booking-config.json", JSON.stringify(bookingConfig), {
80345
+ await put(bookingKey, JSON.stringify(bookingConfig), {
80300
80346
  access: "public",
80301
80347
  addRandomSuffix: false,
80302
80348
  allowOverwrite: true,
@@ -80307,19 +80353,25 @@ export default defineConfig({ plugins: [react()] });`;
80307
80353
  throw new Error(`Failed to seed booking config: ${e.message}`);
80308
80354
  }
80309
80355
  onProgress("Setting booking env vars\u2026");
80310
- await setProjectEnv(projectName, vercelToken, {
80356
+ const bookingEnv = {
80311
80357
  RESEND_API_KEY: resendKey,
80312
80358
  OWNER_EMAIL: ownerEmail,
80313
80359
  BUSINESS_NAME: lead.name
80314
- });
80360
+ };
80361
+ if (bookingExistingUid)
80362
+ bookingEnv.SITE_ID = bookingExistingUid;
80363
+ await setProjectEnv(projectName, vercelToken, bookingEnv);
80315
80364
  if (lead.admin_password) {
80316
80365
  onProgress("Refreshing admin session env\u2026");
80317
80366
  const adminHash = await (0, cms_runtime_1.hashPassword)(lead.admin_password);
80318
- await setProjectEnv(projectName, vercelToken, {
80367
+ const adminEnv = {
80319
80368
  BLOB_READ_WRITE_TOKEN: blobToken,
80320
80369
  ADMIN_PASSWORD_HASH: adminHash,
80321
80370
  SESSION_SECRET: (0, cms_runtime_1.generateSessionSecret)()
80322
- });
80371
+ };
80372
+ if (bookingExistingUid)
80373
+ adminEnv.SITE_ID = bookingExistingUid;
80374
+ await setProjectEnv(projectName, vercelToken, adminEnv);
80323
80375
  }
80324
80376
  const packageJsonBooking = JSON.stringify({
80325
80377
  name: "sprout-site",
@@ -80378,9 +80430,10 @@ export default defineConfig({ plugins: [react()] });`;
80378
80430
  const slug = lead.name.toLowerCase().replace(/[^a-z0-9]/g, "-").replace(/-+/g, "-").slice(0, 26);
80379
80431
  const projectName = `sprout-book-${lead.id}-${slug}`;
80380
80432
  onProgress("Seeding booking config\u2026");
80433
+ const standaloneSiteUid = siteUidFor(lead.id);
80381
80434
  try {
80382
80435
  const { put } = await Promise.resolve().then(() => __importStar(require_dist5()));
80383
- await put("booking-config.json", JSON.stringify(bookingConfig), {
80436
+ await put(`sites/${standaloneSiteUid}/booking-config.json`, JSON.stringify(bookingConfig), {
80384
80437
  access: "public",
80385
80438
  addRandomSuffix: false,
80386
80439
  allowOverwrite: true,
@@ -80395,7 +80448,8 @@ export default defineConfig({ plugins: [react()] });`;
80395
80448
  RESEND_API_KEY: resendKey,
80396
80449
  OWNER_EMAIL: ownerEmail,
80397
80450
  BUSINESS_NAME: lead.name,
80398
- BLOB_READ_WRITE_TOKEN: blobToken
80451
+ BLOB_READ_WRITE_TOKEN: blobToken,
80452
+ SITE_ID: standaloneSiteUid
80399
80453
  });
80400
80454
  const files = [
80401
80455
  { file: "index.html", data: booking_runtime_1.BOOK_INDEX_HTML, encoding: "utf-8" },
@@ -82456,7 +82510,7 @@ var require_package = __commonJS({
82456
82510
  "package.json"(exports2, module2) {
82457
82511
  module2.exports = {
82458
82512
  name: "sproutkit-code",
82459
- version: "0.9.0",
82513
+ version: "0.10.0",
82460
82514
  description: "Sproutkit in your terminal \u2014 find leads, build sites, run outreach, all via chat.",
82461
82515
  bin: {
82462
82516
  sproutkit: "./dist/index.js"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sproutkit-code",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Sproutkit in your terminal — find leads, build sites, run outreach, all via chat.",
5
5
  "bin": {
6
6
  "sproutkit": "./dist/index.js"