@forgecart/cli 2.1.1 → 2.202606151453.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgecart/cli",
3
- "version": "2.1.1",
3
+ "version": "2.202606151453.0",
4
4
  "type": "module",
5
5
  "description": "CLI for scaffolding and operating ForgeCart channel storefronts",
6
6
  "bin": {
@@ -26,7 +26,8 @@
26
26
  "node": ">=18.0.0"
27
27
  },
28
28
  "publishConfig": {
29
- "access": "public"
29
+ "access": "public",
30
+ "registry": "https://registry.npmjs.org"
30
31
  },
31
32
  "keywords": [
32
33
  "forgecart",
@@ -37,7 +37,7 @@ never reaches the browser.
37
37
 
38
38
  ```bash
39
39
  npm install
40
- npm run dev # next dev -> http://localhost:3000
40
+ npm run dev # next dev --turbopack -> http://localhost:3000
41
41
  ```
42
42
 
43
43
  A ForgeCart channel workspace serves this app as its live site by running
@@ -4,6 +4,25 @@ const nextConfig = {
4
4
  // storefront with `next start` without a full node_modules tree.
5
5
  output: 'standalone',
6
6
  reactStrictMode: true,
7
+ // Dev-only `data-fc-source` stamping for the ForgeCart visual editor. The
8
+ // loader marks host JSX elements with their source file:line:col so the
9
+ // dashboard can map a sprayed region back to code. It runs under Turbopack
10
+ // before SWC — with no `as` field, Turbopack chains loader -> built-in SWC,
11
+ // so the stamped TSX is still compiled normally.
12
+ //
13
+ // GLOB GOTCHA (Next 15.5): Turbopack matches a rule key by FILENAME unless it
14
+ // contains `/` (then by full project-relative path), and it does NOT expand
15
+ // braces. The earlier `src/**/*.{tsx,jsx}` therefore matched nothing and the
16
+ // loader silently never ran (`data-fc-source` count: 0). Per-extension
17
+ // filename globs are what Turbopack honors. The loader self-gates on
18
+ // NODE_ENV === 'development', so `next build` (webpack, production) is
19
+ // untouched.
20
+ turbopack: {
21
+ rules: {
22
+ '*.tsx': { loaders: ['@forgecart/designer-runtime/turbo-loader'] },
23
+ '*.jsx': { loaders: ['@forgecart/designer-runtime/turbo-loader'] },
24
+ },
25
+ },
7
26
  images: {
8
27
  // The shop API serves product/asset previews from arbitrary hosts, so
9
28
  // allow any remote image. Tighten this to your asset host in production.
@@ -3,11 +3,12 @@
3
3
  "version": "0.1.0",
4
4
  "private": true,
5
5
  "scripts": {
6
- "dev": "next dev",
6
+ "dev": "next dev --turbopack",
7
7
  "build": "next build",
8
8
  "start": "next start"
9
9
  },
10
10
  "dependencies": {
11
+ "@forgecart/designer-runtime": "^1.0.0",
11
12
  "@forgecart/sdk": "^1.2.6",
12
13
  "next": "^15.1.0",
13
14
  "react": "^19.0.0",
@@ -1,6 +1,7 @@
1
1
  import type { Metadata } from 'next';
2
2
  import type { ReactNode } from 'react';
3
3
 
4
+ import { ForgecartDesigner } from '../components/ForgecartDesigner';
4
5
  import { Header } from '../components/Header';
5
6
  import { getCart } from '../lib/cart-actions';
6
7
  import { CartProvider } from '../lib/cart-context';
@@ -26,6 +27,7 @@ export default async function RootLayout({ children }: { children: ReactNode })
26
27
  </div>
27
28
  </footer>
28
29
  </CartProvider>
30
+ <ForgecartDesigner />
29
31
  </body>
30
32
  </html>
31
33
  );
@@ -0,0 +1,43 @@
1
+ 'use client';
2
+
3
+ import { useEffect } from 'react';
4
+
5
+ /**
6
+ * Mounts the ForgeCart visual-editor guest runtime. Renders nothing.
7
+ *
8
+ * The runtime stays dormant until activated: it registers a single `message`
9
+ * listener and posts a READY beacon, then waits for the dashboard host to
10
+ * drive a nonce-gated handshake over `postMessage` (the activation nonce
11
+ * arrives via the `#fcd=` URL fragment — there is no env switch). On a page
12
+ * that is not embedded in an iframe it never even loads.
13
+ *
14
+ * Production builds ship zero bytes of this: the literal `NODE_ENV` check is
15
+ * inlined by the bundler, so the effect body — including the dynamic import
16
+ * and its chunk — is dead-code-eliminated from `next build` output.
17
+ */
18
+ export function ForgecartDesigner() {
19
+ useEffect(() => {
20
+ if (process.env.NODE_ENV !== 'development') {
21
+ return;
22
+ }
23
+ // Top-level pages have no designer host; skip loading the chunk entirely.
24
+ if (window.parent === window) {
25
+ return;
26
+ }
27
+ let cancelled = false;
28
+ let dispose: (() => void) | undefined;
29
+ const install = async () => {
30
+ const mod = await import('@forgecart/designer-runtime');
31
+ if (cancelled) {
32
+ return;
33
+ }
34
+ dispose = mod.installDesignerRuntime();
35
+ };
36
+ void install();
37
+ return () => {
38
+ cancelled = true;
39
+ dispose?.();
40
+ };
41
+ }, []);
42
+ return null;
43
+ }
@@ -176,12 +176,23 @@ export function getStartingPrice(product: Product): number | null {
176
176
  }
177
177
 
178
178
  /**
179
- * Format a minor-unit price (cents) as a currency string. Defaults to USD;
180
- * pass a different ISO currency code as needed.
179
+ * Format a minor-unit price as a currency string. Defaults to USD; pass a
180
+ * different ISO currency code as needed.
181
+ *
182
+ * ForgeCart prices are integers in the currency's smallest denomination, so
183
+ * the divisor depends on the currency's ISO-4217 exponent: 2500 is $25.00
184
+ * (two-decimal cents) but ¥2,500 (zero-decimal yen) and BHD 2.500
185
+ * (three-decimal fils). The exponent is derived from the same
186
+ * `Intl.NumberFormat` instance that renders the price — its
187
+ * `resolvedOptions().maximumFractionDigits` carries the CLDR fraction digits
188
+ * for the currency — so the divisor always agrees with the digits shown,
189
+ * with a defensive fallback of 2 should the formatter resolve none.
181
190
  */
182
191
  export function formatPrice(minorUnits: number, currency = 'USD'): string {
183
- return new Intl.NumberFormat('en-US', {
192
+ const formatter = new Intl.NumberFormat('en-US', {
184
193
  style: 'currency',
185
194
  currency,
186
- }).format(minorUnits / 100);
195
+ });
196
+ const exponent = formatter.resolvedOptions().maximumFractionDigits ?? 2;
197
+ return formatter.format(minorUnits / 10 ** exponent);
187
198
  }