@wix/agent-skills 1.18.2 → 1.19.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.
@@ -24,7 +24,8 @@ No installation, deployment, or AGENTS.md update is needed.
24
24
  The deployed implementation and configuration files do not need to be read; use the build and seed
25
25
  guides below for their contracts and usage. Continue with STEP 1.
26
26
 
27
- Follow STEPs 1–3 below exactly (run STEP 2 in parallel with STEP 1).
27
+ Follow STEPs 1–3 below exactly. Fire product image generations first, then build the client
28
+ (STEP 1), and seed (STEP 2) **after** the client is built — generation finishes while you build.
28
29
 
29
30
  ## STEP 1 — Build the client
30
31
 
@@ -74,13 +75,13 @@ Inline via exec_tool, `base44` is already declared — use it directly; do **not
74
75
  `@base44/sdk`, re-declare it, or call `createClient()` (that's for standalone `.js` files only;
75
76
  inline it throws *"Identifier 'base44' has already been declared."*).
76
77
 
77
- **Product images.** Generate with **Base44's built-in image generation**, then attach via the
78
- storefront seed module's image-attach step.
79
-
80
- **Seed images with the FINAL url, in one call.** Use the real `https://media.base44.com/...` url
81
- from the **completed** `generate_image` result and pass it straight into your single `setupStore`
82
- call (images included). `generate_image` runs in the background while you build the client, so the
83
- urls are ready by the time you seed.
78
+ **Product images.** Fire every `generate_image` **before you build the client**, and seed **after**
79
+ it — generation finishes while you build, so by the time you write the seed call your images are
80
+ ready: each finished result now reads `status: "completed"` with its final
81
+ `https://media.base44.com/...` url. Read every `imageUrl` from the results **as they read at that
82
+ moment** — not from what they said when you called the tool — and pass them in the single
83
+ `setupStore` call. A result without a final url by then (still running, or failed) → seed that
84
+ product **without** `imageUrl` and attach it afterwards with `attachProductImages`.
84
85
 
85
86
  ## STEP 3 — Wrap up
86
87
 
@@ -11,7 +11,8 @@ only), safe in the frontend; use it directly for the Wix client setup.
11
11
  **Banner enabled for this entry flow:** mount `<WixManageBanner/>` once in the Layout, above
12
12
  the header in the fixed top region, per the vertical instructions. It is preview-only.
13
13
 
14
- Follow STEPs 1–5 below exactly (run STEP 4 in parallel with STEP 3 — **except `forms`**, see STEP 3).
14
+ Follow STEPs 1–5 below exactly (run STEP 4 in parallel with STEP 3 — **except `forms`**, see
15
+ STEP 3, and **except a seed that attaches entity images**, which runs after the build; see STEP 4).
15
16
 
16
17
  ## STEP 1 — Install the Wix skills locally
17
18
 
@@ -98,8 +99,15 @@ rejects the write. Wire routes/imports in with `find_replace`, leave the rest as
98
99
 
99
100
  Seed by calling your vertical's ready-made seed module — read
100
101
  `.agents/skills/wix-vibe-headless/references/<vertical>/seed/SEED.md` and follow it (the loader
101
- snippet, admin connector token, entity images with the final `media.base44.com` url, and every field
102
- shape are there). Gaps or an unexpected shape → the documentation skill available in your environment.
102
+ snippet, admin connector token, and every field shape are there). Gaps or an unexpected shape → the
103
+ documentation skill available in your environment.
104
+
105
+ **Entity images:** fire every image generation **before you build the client**, and run an
106
+ image-carrying seed **after** the build — generation finishes while you build, and each finished
107
+ result then reads `status: "completed"` with its final `https://media.base44.com/...` url. Read
108
+ every image url from the results **as they read at that moment**, not from what they said when you
109
+ called the tool. No final url by then (still running, or failed) → seed without that image and
110
+ attach it afterwards per the vertical's `SEED.md`.
103
111
  Seeding is **admin-only** — not part of the client, which is built solely per the `wix-vibe-headless` skill.
104
112
 
105
113
  - **Additive only:** never delete or overwrite the user's content, even apparent sample data; ask first if a cleanup truly seems needed.
@@ -336,6 +336,10 @@ wire these exactly as shown below.
336
336
  When adding storefront routes and providers to `src/App.jsx`, preserve the existing platform
337
337
  authentication setup, including `AuthProvider`, `useAuth`, and their `@/lib/AuthContext` imports.
338
338
  Do not remove or replace that authentication logic.
339
+ The shipped commerce flow needs no login: shoppers browse, cart, and check out on the Wix visitor
340
+ session, which is separate from platform user auth. Whether the storefront is public or members-only
341
+ is the brief's call — default to public, and gate routes behind platform auth only when the brief
342
+ asks for it. Neither choice affects the commerce flow.
339
343
  - Wrap the routed tree in `<CartProvider>` (from `@/context/CartContext`).
340
344
  - Put your **header + footer in a `Layout`** that renders `<Outlet/>` between them, and nest every
341
345
  route under one pathless `<Route element={<Layout/>}>`. Your brand chrome then wraps **every** page
@@ -19,9 +19,7 @@ const ctx = { token: accessToken };
19
19
  // ONE call: install (+ wait for V3) → create products → categories → attach images, ids kept
20
20
  // in memory (no hand-threading). Categories map name -> product NAMES. Pass an imageUrl per product
21
21
  // to attach its image; omit it to skip images.
22
- // imageUrl must be the FINAL https://media.base44.com/... url from the COMPLETED generate_image
23
- // result — not a still-generating /__generating__/<id>.png placeholder (Wix can't fetch that).
24
- // generate_image runs in the background while you build, so the urls are ready by seed time.
22
+ // imageUrl: a public, fetchable https:// url — Wix copies the image bytes at attach time.
25
23
  const result = await seed.setupStore(ctx, {
26
24
  currency: "EUR", // Pass only if the user asked for a currency or it's obvious for the store; else omit this line.
27
25
  products: [
@@ -38,6 +36,8 @@ const result = await seed.setupStore(ctx, {
38
36
  categories: { "Legends": ["The Glam Rocker"], "Rising Stars": [] }, // omit if the brief names none
39
37
  });
40
38
  // result: { products:[{id,slug,revision,name}], categories:[{id,name}], imagesAttached,
39
+ // imagesSkipped (product names whose imageUrl was not an absolute https:// url — attach those afterwards),
40
+ // productsWithoutImages (product names seeded with no imageUrl — attach afterwards once urls exist),
41
41
  // currency: { requested, actual, status, warnings } }
42
42
  ```
43
43
 
@@ -112,6 +112,8 @@ enrolls in — is **Pricing Plans**, not a Stores product, so it isn't seeded he
112
112
  download — uploaded and created with both the file and stock, which is what the cart requires
113
113
  (`quantity` is ignored). It's also the only way in: a file-less digital product is created
114
114
  successfully, reads back healthy, and is then rejected at add-to-cart as `ITEM_NOT_FOUND_IN_CATALOG`.
115
+ No real, fetchable file in hand → seed the product as **physical** with `inStock: true` and tell the
116
+ user; swap it to a digital download once a real file exists.
115
117
 
116
118
  Two things this module does **not** seed, so don't try:
117
119
 
@@ -138,7 +140,6 @@ const products = await seed.bulkCreateProducts(ctx, [ // → [{i
138
140
  ]);
139
141
  const cats = await seed.createCategories(ctx, ["Legends"]); // sequential → [{id,name}]
140
142
  await seed.addProductsToCategories(ctx, { [cats[0].id]: [products[0].id] });
141
- // images: use the FINAL https://media.base44.com/... url only (never a /__generating__/ placeholder)
142
143
  await seed.attachProductImages(ctx, products.map((p, i) => ({ id: p.id, url: imageUrls[i], altText: p.slug })));
143
144
  ```
144
145
 
@@ -257,7 +257,8 @@ async function uploadDigitalFile(ctx, url, fileName) {
257
257
  if (!mimeType) throw new Error(`digitalFileName needs one of these extensions (${Object.keys(FILE_MIME).join(", ")}): ${fileName}`);
258
258
  const { uploadUrl } = await req(ctx, "/site-media/v1/files/generate-upload-url", { body: { mimeType, fileName } });
259
259
  const src = await fetch(url);
260
- if (!src.ok) throw new Error(`digitalFileUrl ${url} -> ${src.status}`);
260
+ if (!src.ok) throw new Error(`digitalFileUrl ${url} -> ${src.status}. A digital product needs a real, ` +
261
+ `fetchable file — with none at hand, seed this product as physical with inStock: true and tell the user.`);
261
262
  const res = await fetch(uploadUrl, {
262
263
  method: "PUT", headers: { "Content-Type": mimeType }, body: Buffer.from(await src.arrayBuffer()),
263
264
  });
@@ -416,9 +417,17 @@ async function addProductsToCategories(ctx, mapping) {
416
417
  // caller never manages a revision token — attach any number of times, in any pass. Wix re-hosts the
417
418
  // image from the url server-side; the re-hosted media can take a little while to appear on read-back
418
419
  // (propagation) — that's normal, not a failure, so we don't block on it.
420
+ // Wix imports the image bytes server-side, so an attach needs an absolute, publicly fetchable url.
421
+ const isFetchableImageUrl = (url) => typeof url === "string" && /^https:\/\//.test(url);
422
+
419
423
  async function attachProductImages(ctx, items) {
420
424
  if (!items?.length) return;
421
425
  if (items.some((it) => !it.id)) throw new Error("Image attachment requires a product ID for every item; no image request was sent.");
426
+ const unfetchable = items.filter((it) => !isFetchableImageUrl(it.url));
427
+ if (unfetchable.length) throw new Error(
428
+ `Image url(s) for [${unfetchable.map((it) => it.altText || it.id).join(", ")}] are not absolute ` +
429
+ `https:// urls, and Wix copies the image bytes at attach time. Re-call attachProductImages once each ` +
430
+ `image has its final url. No image request was sent; products are unaffected.`);
422
431
  const ids = items.map((it) => it.id);
423
432
  const q = await req(ctx, "/stores/v3/products/query", { body: { query: { filter: { id: { $in: ids } }, paging: { limit: ids.length } } } });
424
433
  const revById = new Map((q.products ?? []).map((p) => [p.id, p.revision]));
@@ -475,7 +484,8 @@ async function configureCurrency(ctx, requested) {
475
484
  * digitalFileUrl?, digitalFileName? }],
476
485
  * categories?: { [categoryName]: string[] }, // map of category name -> product NAMES in it
477
486
  * }}
478
- * @returns { products: [{id,slug,revision,name}], categories: [{id,name}], imagesAttached: number, currency: {requested,actual,status,warnings} }
487
+ * @returns { products: [{id,slug,revision,name}], categories: [{id,name}], imagesAttached: number,
488
+ * imagesSkipped?: string[], note?: string, currency: {requested,actual,status,warnings} }
479
489
  */
480
490
  async function setupStore(ctx, { products = [], categories = {}, currency } = {}) {
481
491
  validateProducts(products);
@@ -498,11 +508,29 @@ async function setupStore(ctx, { products = [], categories = {}, currency } = {}
498
508
  }
499
509
 
500
510
  const imageItems = withNames
501
- .map((p, i) => ({ id: p.id, url: products[i]?.imageUrl, altText: products[i]?.altText ?? p.slug }))
511
+ .map((p, i) => ({ id: p.id, url: products[i]?.imageUrl, altText: products[i]?.altText ?? p.slug, name: p.name }))
502
512
  .filter((it) => it.url);
503
- if (imageItems.length) await attachProductImages(ctx, imageItems);
513
+ // A url Wix cannot fetch would fail the attach: seed the product imageless and report it,
514
+ // so the caller attaches once the final url exists.
515
+ const readyItems = imageItems.filter((it) => isFetchableImageUrl(it.url));
516
+ const skipped = imageItems.filter((it) => !isFetchableImageUrl(it.url)).map((it) => it.name);
517
+ if (readyItems.length) await attachProductImages(ctx, readyItems);
504
518
 
505
- return { products: withNames, categories: cats, imagesAttached: imageItems.length, currency: currencyResult };
519
+ const withoutImages = withNames.filter((p, i) => !products[i]?.imageUrl).map((p) => p.name);
520
+ return {
521
+ products: withNames, categories: cats, imagesAttached: readyItems.length,
522
+ ...(skipped.length && {
523
+ imagesSkipped: skipped,
524
+ note: "these products' image urls were not absolute https:// urls — attach them with " +
525
+ "attachProductImages once each image has its final url",
526
+ }),
527
+ ...(withoutImages.length && !skipped.length && {
528
+ productsWithoutImages: withoutImages,
529
+ note: "these products were seeded without an image — once their images have final urls, " +
530
+ "attach them with attachProductImages",
531
+ }),
532
+ currency: currencyResult,
533
+ };
506
534
  }
507
535
 
508
536
  module.exports = {