@commercebuild/extension 0.0.12 → 0.0.14

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.
@@ -5,8 +5,31 @@ import tailwindcss from "@tailwindcss/vite";
5
5
  // import dts from "vite-plugin-dts";
6
6
  // import { fileURLToPath } from "url";
7
7
  import path from "path";
8
- import { existsSync } from "fs";
8
+ import { existsSync, readFileSync } from "fs";
9
9
  import chalk from "chalk";
10
+
11
+ // The host contract version this extension targets, declared in the manifest.
12
+ // Injected into the bundle so the storefront host can check compatibility
13
+ // before mounting the extension. See cb-store's docs/extension-host-api.
14
+ function readCbApiVersion() {
15
+ try {
16
+ const manifestPath = path.resolve(process.cwd(), "commercebuild.json");
17
+ if (existsSync(manifestPath)) {
18
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
19
+ return manifest.cbApiVersion;
20
+ }
21
+ } catch {
22
+ // fall through — an extension with no declared version loads as legacy
23
+ }
24
+ return undefined;
25
+ }
26
+
27
+ const cbApiVersion = readCbApiVersion();
28
+ const cbMetaBanner = cbApiVersion
29
+ ? `;(function(){try{if(typeof window!=="undefined"){window.__CBExtensionMeta=Object.assign(window.__CBExtensionMeta||{},{cbApiVersion:${JSON.stringify(
30
+ cbApiVersion,
31
+ )}});}}catch(e){}})();`
32
+ : "";
10
33
  // const __filename = fileURLToPath(import.meta.url);
11
34
  // const __dirname = dirname(__filename);
12
35
 
@@ -27,9 +50,10 @@ const baseBuildConfig = {
27
50
  rollupOptions: {
28
51
  external: ["react", "react-dom"],
29
52
  output: {
53
+ banner: cbMetaBanner,
30
54
  globals: {
31
- react: "cb.React",
32
- "react-dom": "cb.ReactDom",
55
+ react: "cb.lib.React",
56
+ "react-dom": "cb.lib.ReactDom",
33
57
  },
34
58
  assetFileNames: (assetInfo) => {
35
59
  if (assetInfo.name.endsWith(".css")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commercebuild/extension",
3
- "version": "0.0.12",
3
+ "version": "0.0.14",
4
4
  "types": "./types/index.d.ts",
5
5
  "exports": {
6
6
  ".": {
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "dependencies": {
13
13
  "@commercebuild/platform-api": "^0.*",
14
- "@commercebuild/ui": "^0.0.5",
14
+ "@commercebuild/ui": "^0.*",
15
15
  "@headlessui/react": "^2.2.4",
16
16
  "@tailwindcss/postcss": "^4.1.10",
17
17
  "@tailwindcss/vite": "^4.1.11",
@@ -34,4 +34,4 @@
34
34
  "bin": {
35
35
  "commercebuild-extension": "./scripts/cli.js"
36
36
  }
37
- }
37
+ }
package/types/global.d.ts CHANGED
@@ -1,15 +1,359 @@
1
+ // AUTO-GENERATED — DO NOT EDIT.
2
+ // Generated by scripts/generate-extension-host-types.mjs from
3
+ // apps/storefront/src/lib/extension/cb-host-api.types.ts (the CbHostApi
4
+ // contract). Regenerate with `yarn generate-extension-host-types`.
5
+
6
+ import * as React from "react";
7
+ import React__default, { CSSProperties } from "react";
8
+ import * as ReactDOM from "react-dom";
9
+ import { cart_v2, catalog_v1, Platform } from "@commercebuild/platform-api";
10
+ import * as icons from "lucide-react";
11
+ import * as uis from "@commercebuild/ui";
1
12
  import {
2
13
  Disclosure,
3
14
  DisclosureButton,
4
15
  DisclosurePanel,
5
16
  } from "@headlessui/react";
6
- import * as uis from "@commercebuild/ui";
7
- import * as icons from "lucide-react";
8
- import * as React from "react";
9
- import { Platform } from "@commercebuild/platform-api";
10
- import type { UrlObject } from "url";
17
+ import { UrlObject } from "url";
18
+ import { ProductTypeInterface } from "@commercebuild/platform-api/type/v1";
19
+
20
+ declare function useLanagueTranslater(): {
21
+ _t: (k: string, obj?: any) => string | undefined;
22
+ t: (k: string) => string;
23
+ tt: (k: string, obj?: any) => (kk: string) => string;
24
+ tmsg: (k: string) => any;
25
+ tlink: (uri: string) => string;
26
+ talink: (uri: string) => string;
27
+ trlink: (uri: string) => string;
28
+ lang: "en-US" | "fr-FR" | "zh";
29
+ locale: "en-US" | "fr-FR" | "zh";
30
+ };
31
+
32
+ declare const useTranslater: typeof useLanagueTranslater;
33
+
34
+ type Category = catalog_v1.VerifiedCategoryMapper;
35
+ type CartRequestItem = Omit<
36
+ cart_v2.CartUserCartItemInterface,
37
+ "configuratorCode" | "groupCode"
38
+ > & {
39
+ imageUrl?: string;
40
+ description?: string;
41
+ unitPrice?: number;
42
+ detailPageUrl?: string;
43
+ };
44
+ type PostItemsSearchItem = catalog_v1.PostItemsSearchItem;
45
+ type PostItemsSearchResponse = catalog_v1.PostItemsSearchResponse;
46
+ type GetItemDetailResponse = catalog_v1.GetItemDetailResponse;
47
+
48
+ interface AddToCartProps {
49
+ items: CartRequestItem[];
50
+ disabled?: boolean;
51
+ onSuccessCallback?: () => void;
52
+ className?: string;
53
+ /** Inline style forwarded to the button (e.g. Product Card button-scheme vars). */
54
+ style?: CSSProperties;
55
+ /**
56
+ * Width control (opt-in). Omit for the default responsive width; `true` = full
57
+ * width at all breakpoints; `false` = content width (auto).
58
+ */
59
+ fullWidth?: boolean;
60
+ }
61
+ declare function AddToCart({
62
+ items,
63
+ disabled,
64
+ onSuccessCallback,
65
+ className,
66
+ style,
67
+ fullWidth,
68
+ }: AddToCartProps): React.JSX.Element;
69
+
70
+ declare function CategoryProduct({
71
+ product,
72
+ imagePriority,
73
+ }: {
74
+ product: PostItemsSearchItem;
75
+ imagePriority?: boolean;
76
+ }): React.JSX.Element;
77
+
78
+ declare function CategoryBreadcrumb({
79
+ category,
80
+ }: {
81
+ category: Category;
82
+ }): React.JSX.Element;
83
+
84
+ declare function CategoryTitle({
85
+ category,
86
+ }: {
87
+ category: Category;
88
+ }): React.JSX.Element;
89
+
90
+ declare function CategorySidebar({
91
+ category,
92
+ url,
93
+ aggregation,
94
+ currentFilters,
95
+ isDrawer,
96
+ }: {
97
+ category: Category;
98
+ url: string;
99
+ aggregation?: PostItemsSearchResponse["aggregation"];
100
+ currentFilters: Record<string, string[]>;
101
+ isDrawer?: boolean;
102
+ }): React.JSX.Element | null;
103
+
104
+ declare function CategorySort({
105
+ url,
106
+ options,
107
+ sort,
108
+ }: {
109
+ url: string;
110
+ options: {
111
+ name: string;
112
+ }[];
113
+ sort: number;
114
+ }): React.JSX.Element;
115
+
116
+ declare function CategoryProducts({
117
+ hasSiderbar,
118
+ items,
119
+ }: {
120
+ hasSiderbar?: boolean;
121
+ items: PostItemsSearchItem[];
122
+ }): React.JSX.Element;
123
+
124
+ declare function CategoryProductsPageSize({
125
+ url,
126
+ options,
127
+ size,
128
+ }: {
129
+ url: string;
130
+ options: {
131
+ name: string;
132
+ value: number;
133
+ }[];
134
+ size: number;
135
+ }): React.JSX.Element;
136
+
137
+ declare function ProductTitle({
138
+ product,
139
+ }: {
140
+ product: GetItemDetailResponse;
141
+ }): React.JSX.Element;
142
+
143
+ type ThumbnailPosition = "top" | "right" | "bottom" | "left";
144
+ declare const ProductImages: ({
145
+ product,
146
+ thumbnailPosition,
147
+ }: {
148
+ product: GetItemDetailResponse;
149
+ thumbnailPosition?: ThumbnailPosition;
150
+ }) => React.JSX.Element;
151
+
152
+ declare function ProductPrice({
153
+ product,
154
+ items,
155
+ }: {
156
+ product: GetItemDetailResponse | PostItemsSearchItem;
157
+ items?: {
158
+ price?: number;
159
+ }[];
160
+ }): React.JSX.Element | null;
161
+
162
+ declare const ProductStockStatus: ({
163
+ product,
164
+ displayStatusTitle,
165
+ }: {
166
+ product: GetItemDetailResponse | PostItemsSearchItem;
167
+ displayStatusTitle?: boolean;
168
+ }) => React.JSX.Element | null;
169
+
170
+ declare const ProductStockQuantity: ({
171
+ product,
172
+ }: {
173
+ product: GetItemDetailResponse;
174
+ }) => React.JSX.Element | null;
175
+
176
+ declare function ProductRating({
177
+ product,
178
+ }: {
179
+ product: GetItemDetailResponse;
180
+ }): React.JSX.Element;
181
+
182
+ declare function ProductDescription({
183
+ product,
184
+ }: {
185
+ product: GetItemDetailResponse;
186
+ }): React.JSX.Element;
187
+
188
+ interface ProductQuantityInputProps {
189
+ qty: number;
190
+ setQty: (qty: number) => void;
191
+ min?: number;
192
+ max?: number;
193
+ disabled?: boolean;
194
+ }
195
+ declare const ProductQuantityInput: React__default.FC<ProductQuantityInputProps>;
11
196
 
12
- // === Link and Image from nextjs =============================================
197
+ declare function StandardProductAddToCart({
198
+ product,
199
+ quantityRequested,
200
+ }: {
201
+ product: GetItemDetailResponse;
202
+ quantityRequested: number;
203
+ }): React.JSX.Element;
204
+
205
+ declare function ProductAddToFavorite({
206
+ product,
207
+ }: {
208
+ product: GetItemDetailResponse;
209
+ }): React.JSX.Element;
210
+
211
+ declare function ProductMakeOffer({
212
+ product,
213
+ qty,
214
+ }: {
215
+ product: GetItemDetailResponse;
216
+ qty: number;
217
+ }): React.JSX.Element | null;
218
+
219
+ declare function ProductRelatedProducts({
220
+ relatedProducts,
221
+ }: {
222
+ relatedProducts: PostItemsSearchItem[];
223
+ }): React.JSX.Element | null;
224
+
225
+ declare function VariantProductAddToCart({
226
+ items,
227
+ }: {
228
+ items: CartRequestItem[];
229
+ }): React.JSX.Element;
230
+
231
+ declare function ProductBreadcrumb({
232
+ product,
233
+ }: {
234
+ product: GetItemDetailResponse;
235
+ }): React.JSX.Element;
236
+
237
+ /**
238
+ * @deprecated Use convertProductToCartRequestItem instead
239
+ */
240
+
241
+ type ProductProps = {
242
+ type: ProductTypeInterface;
243
+ itemCode: string;
244
+ variantCode?: string;
245
+ defaultUnitOfMeasure: {
246
+ unitCode: string;
247
+ conversion: number;
248
+ default: boolean;
249
+ } | null;
250
+ description?: string;
251
+ detailPageUrl?: string;
252
+ images: {
253
+ main: {
254
+ thumbnailSmall: string;
255
+ } | null;
256
+ } | null;
257
+ quantityRequested?: number;
258
+ };
259
+ declare const convertProductItemToCartRequestItem: (
260
+ product: ProductProps,
261
+ ) => CartRequestItem;
262
+
263
+ interface Options extends Intl.NumberFormatOptions {
264
+ locale?: string;
265
+ forceDecimals?: number;
266
+ }
267
+ declare const displayMoney: (
268
+ amount: string | number,
269
+ options?: Options,
270
+ ) => string;
271
+
272
+ declare function revalidateTag(
273
+ tag: string,
274
+ profile:
275
+ | string
276
+ | {
277
+ expire?: number;
278
+ },
279
+ ): Promise<void>;
280
+ declare function revalidatePath(
281
+ path: string,
282
+ type?: "layout" | "page",
283
+ ): Promise<void>;
284
+ declare function revalidate(): Promise<void>;
285
+
286
+ /**
287
+ * The extension host API contract (`window.cb`).
288
+ *
289
+ * This file is the SINGLE SOURCE OF TRUTH for the `cb` surface. The runtime
290
+ * factory `buildCbHostApi()` (./cb-host-api.ts) is typed to return `CbHostApi`,
291
+ * so `tsc` fails if the runtime values and this contract ever diverge.
292
+ *
293
+ * The author-facing type declaration (`global.d.ts`) is GENERATED from this
294
+ * file by `scripts/generate-extension-host-types.mjs` (`yarn
295
+ * generate-extension-host-types`). Never hand-edit the generated file.
296
+ *
297
+ * Boundary typing is hybrid: most members derive their type straight from the
298
+ * runtime symbol (`typeof ProductTitle`, `typeof displayMoney`); a member is
299
+ * only curated by hand when deriving would emit an import an extension author's
300
+ * project cannot resolve (e.g. `next/link`) or would leak internals.
301
+ */
302
+
303
+ type TranslationHelpers = ReturnType<typeof useTranslater>;
304
+ /**
305
+ * The Next.js App Router surface exposed to extensions. Curated (not derived
306
+ * from `next`) so the generated declaration does not import a `next/*` path an
307
+ * extension author's project cannot resolve.
308
+ */
309
+ interface CbRouter {
310
+ push(
311
+ href: string,
312
+ options?: {
313
+ scroll?: boolean;
314
+ },
315
+ ): void;
316
+ replace(
317
+ href: string,
318
+ options?: {
319
+ scroll?: boolean;
320
+ },
321
+ ): void;
322
+ back(): void;
323
+ forward(): void;
324
+ refresh(): void;
325
+ prefetch(href: string): void;
326
+ }
327
+ /**
328
+ * Dynamic values the host supplies to the API at build time (per-request React
329
+ * context values that cannot be static imports).
330
+ */
331
+ interface CbHostDeps {
332
+ platformApi: Platform;
333
+ router: CbRouter;
334
+ tmsg: TranslationHelpers["tmsg"];
335
+ tlink: TranslationHelpers["tlink"];
336
+ }
337
+ type CbClassValue =
338
+ | string
339
+ | number
340
+ | bigint
341
+ | boolean
342
+ | null
343
+ | undefined
344
+ | Record<string, unknown>
345
+ | CbClassValue[];
346
+ type CbClassNameFn = (...inputs: CbClassValue[]) => string;
347
+ type CbContainerProps = React.PropsWithChildren<
348
+ React.HTMLAttributes<HTMLDivElement> & {
349
+ maxWidth?: "sm" | "md" | "lg" | "xl" | "2xl" | "full";
350
+ padding?: "default";
351
+ additionalMaxWidth?: "default";
352
+ }
353
+ >;
354
+ type CbContainer = React.ForwardRefExoticComponent<
355
+ CbContainerProps & React.RefAttributes<HTMLDivElement>
356
+ >;
13
357
  type Url = string | UrlObject;
14
358
  type OnNavigateEventHandler = (event: { preventDefault: () => void }) => void;
15
359
  type InternalLinkProps = {
@@ -28,15 +372,13 @@ type InternalLinkProps = {
28
372
  children?: React.ReactNode | undefined;
29
373
  onNavigate?: OnNavigateEventHandler;
30
374
  } & React.RefAttributes<HTMLAnchorElement>;
31
-
32
- declare const NextLink: React.ForwardRefExoticComponent<
375
+ type CbLink = React.ForwardRefExoticComponent<
33
376
  Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, keyof InternalLinkProps> &
34
377
  InternalLinkProps & {
35
378
  children?: React.ReactNode | undefined;
36
379
  } & React.RefAttributes<HTMLAnchorElement>
37
380
  >;
38
-
39
- export interface StaticImageData {
381
+ interface StaticImageData {
40
382
  src: string;
41
383
  height: number;
42
384
  width: number;
@@ -44,19 +386,19 @@ export interface StaticImageData {
44
386
  blurWidth?: number;
45
387
  blurHeight?: number;
46
388
  }
47
- export interface StaticRequire {
389
+ interface StaticRequire {
48
390
  default: StaticImageData;
49
391
  }
50
- export type StaticImport = StaticRequire | StaticImageData;
51
- export type ImageLoaderProps = {
392
+ type StaticImport = StaticRequire | StaticImageData;
393
+ type ImageLoaderProps = {
52
394
  src: string;
53
395
  width: number;
54
396
  quality?: number;
55
397
  };
56
- export type ImageLoader = (p: ImageLoaderProps) => string;
57
- export type PlaceholderValue = "blur" | "empty" | `data:image/${string}`;
58
- export type OnLoadingComplete = (img: HTMLImageElement) => void;
59
- declare const NextImage: React.ForwardRefExoticComponent<
398
+ type ImageLoader = (p: ImageLoaderProps) => string;
399
+ type PlaceholderValue = "blur" | "empty" | `data:image/${string}`;
400
+ type OnLoadingComplete = (img: HTMLImageElement) => void;
401
+ type CbImage = React.ForwardRefExoticComponent<
60
402
  Omit<
61
403
  React.DetailedHTMLProps<
62
404
  React.ImgHTMLAttributes<HTMLImageElement>,
@@ -85,70 +427,98 @@ declare const NextImage: React.ForwardRefExoticComponent<
85
427
  lazyRoot?: string;
86
428
  } & React.RefAttributes<HTMLImageElement | null>
87
429
  >;
88
-
89
- // ============================================================================
90
-
91
- declare global {
92
- const cb: {
93
- version: string;
430
+ interface CbHostApi {
431
+ /** Host-contract semver — see docs/extension-host-api/versioning.md. */
432
+ version: string;
433
+ /**
434
+ * Shared libraries the host provides as build-time externals, so an extension
435
+ * shares the host's single instance instead of bundling its own (critical for
436
+ * React — two instances break hooks). The extension build maps
437
+ * `import ... from "react"` → `cb.lib.React` and `"react-dom"` →
438
+ * `cb.lib.ReactDom`.
439
+ */
440
+ lib: {
94
441
  React: typeof React;
95
- platformApi: Platform;
96
- utils: {
97
- convertProductItemToCartRequestItem;
98
- tlink;
99
- tmsg;
100
- router;
101
- displayMoney;
102
- cn;
103
- revalidateTag;
104
- revalidatePath;
442
+ ReactDom: typeof ReactDOM;
443
+ };
444
+ /** The platform (commerce backend) API SDK client. */
445
+ platform: Platform;
446
+ /**
447
+ * Feature-detect a member by dotted path, e.g.
448
+ * `cb.has("com.Product.ProductMakeOffer")`. Lets an extension degrade
449
+ * gracefully across host versions instead of hard-crashing.
450
+ */
451
+ has: (path: string) => boolean;
452
+ utils: {
453
+ convertProductItemToCartRequestItem: typeof convertProductItemToCartRequestItem;
454
+ tlink: TranslationHelpers["tlink"];
455
+ tmsg: TranslationHelpers["tmsg"];
456
+ router: CbRouter;
457
+ displayMoney: typeof displayMoney;
458
+ cn: CbClassNameFn;
459
+ revalidate: typeof revalidate;
460
+ revalidateTag: typeof revalidateTag;
461
+ revalidatePath: typeof revalidatePath;
462
+ };
463
+ com: {
464
+ Cart: {
465
+ AddToCart: typeof AddToCart;
105
466
  };
106
- com: {
107
- Cart: {
108
- AddToCart;
109
- };
110
- Category: {
111
- CategoryProducts;
112
- CategoryProduct;
113
- CategoryBreadcrumb;
114
- CategoryTitle;
115
- CategorySidebar;
116
- CategorySort;
117
- CategoryProductsPageSize;
118
- };
119
- Product: {
120
- ProductTitle;
121
- ProductImages;
122
- ProductPrice;
123
- ProductStockStatus;
124
- ProductStockQuantity;
125
- ProductRating;
126
- ProductDescription;
127
- ProductTabs;
128
- ProductQuantityInput;
129
- StandardProductAddToCart;
130
- VariantProductAddToCart;
131
- ProductAddToFavorite;
132
- ProductMakeOffer;
133
- ProductRelatedProducts;
134
- };
467
+ Category: {
468
+ CategoryProducts: typeof CategoryProducts;
469
+ CategoryProduct: typeof CategoryProduct;
470
+ CategoryBreadcrumb: typeof CategoryBreadcrumb;
471
+ CategoryTitle: typeof CategoryTitle;
472
+ CategorySidebar: typeof CategorySidebar;
473
+ CategorySort: typeof CategorySort;
474
+ CategoryProductsPageSize: typeof CategoryProductsPageSize;
475
+ };
476
+ Product: {
477
+ ProductTitle: typeof ProductTitle;
478
+ ProductImages: typeof ProductImages;
479
+ ProductPrice: typeof ProductPrice;
480
+ ProductStockStatus: typeof ProductStockStatus;
481
+ ProductStockQuantity: typeof ProductStockQuantity;
482
+ ProductRating: typeof ProductRating;
483
+ ProductDescription: typeof ProductDescription;
484
+ ProductQuantityInput: typeof ProductQuantityInput;
485
+ StandardProductAddToCart: typeof StandardProductAddToCart;
486
+ VariantProductAddToCart: typeof VariantProductAddToCart;
487
+ ProductAddToFavorite: typeof ProductAddToFavorite;
488
+ ProductMakeOffer: typeof ProductMakeOffer;
489
+ ProductRelatedProducts: typeof ProductRelatedProducts;
490
+ ProductBreadcrumb: typeof ProductBreadcrumb;
135
491
  };
136
- ui: {
137
- icons: { [K in keyof typeof icons]: (typeof icons)[K] };
138
- Link: typeof NextLink;
139
- Image: typeof NextImage;
140
- Container: React.FC<
141
- React.PropsWithChildren<React.HTMLAttributes<HTMLDivElement>>
142
- >;
143
- Disclosure: typeof Disclosure;
144
- DisclosureButton: typeof DisclosureButton;
145
- DisclosurePanel: typeof DisclosurePanel;
146
- } & { [K in keyof typeof uis]: (typeof uis)[K] };
147
492
  };
148
-
493
+ ui: {
494
+ icons: typeof icons;
495
+ Link: CbLink;
496
+ Image: CbImage;
497
+ Container: CbContainer;
498
+ Disclosure: typeof Disclosure;
499
+ DisclosureButton: typeof DisclosureButton;
500
+ DisclosurePanel: typeof DisclosurePanel;
501
+ } & typeof uis;
502
+ }
503
+ declare global {
504
+ const cb: CbHostApi;
149
505
  interface Window {
150
- cb: typeof cb;
506
+ cb: CbHostApi;
507
+ }
508
+ /**
509
+ * Storefront domain types made ambient for extension authors, so
510
+ * component/page code (and editor scaffolds) can reference them bare
511
+ * without importing from the SDK.
512
+ */
513
+ type Category = catalog_v1.VerifiedCategoryMapper;
514
+ interface Product extends catalog_v1.CatalogProductSearchProduct {
515
+ image: Required<
516
+ Pick<catalog_v1.CatalogProductSearchProduct["details"]["images"], "main">
517
+ >["main"];
518
+ originalPrice?: catalog_v1.CatalogPriceItemDto;
519
+ formattedPrice: string;
520
+ href: string;
151
521
  }
152
522
  }
153
523
 
154
- export {};
524
+ export type { CbHostApi, CbHostDeps, CbRouter };