@forgecart/cli 1.1.0 → 2.0.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 (180) hide show
  1. package/dist/bin/forgecart.d.ts +2 -0
  2. package/dist/bin/forgecart.js +4 -0
  3. package/dist/bin/forgecart.js.map +1 -0
  4. package/dist/src/cli.d.ts +14 -1
  5. package/dist/src/cli.js +30 -15
  6. package/dist/src/cli.js.map +1 -1
  7. package/dist/src/commands/init.d.ts +20 -2
  8. package/dist/src/commands/init.js +88 -307
  9. package/dist/src/commands/init.js.map +1 -1
  10. package/dist/src/commands/placeholders.d.ts +19 -0
  11. package/dist/src/commands/placeholders.js +109 -0
  12. package/dist/src/commands/placeholders.js.map +1 -0
  13. package/dist/src/output.d.ts +11 -0
  14. package/dist/src/output.js +16 -0
  15. package/dist/src/output.js.map +1 -0
  16. package/package.json +21 -11
  17. package/templates/storefront/.env.example +12 -0
  18. package/templates/storefront/.forgecartignore +2 -0
  19. package/templates/storefront/Procfile +1 -0
  20. package/templates/storefront/README.md +87 -0
  21. package/templates/storefront/next.config.js +14 -0
  22. package/templates/storefront/package.json +25 -0
  23. package/templates/storefront/postcss.config.js +6 -0
  24. package/templates/storefront/src/app/cart/page.tsx +110 -0
  25. package/templates/storefront/src/app/globals.css +13 -0
  26. package/templates/storefront/src/app/layout.tsx +30 -0
  27. package/templates/storefront/src/app/page.tsx +49 -0
  28. package/templates/storefront/src/app/products/[slug]/not-found.tsx +20 -0
  29. package/templates/storefront/src/app/products/[slug]/page.tsx +96 -0
  30. package/templates/storefront/src/app/products/page.tsx +27 -0
  31. package/templates/storefront/src/components/AddToCartButton.tsx +30 -0
  32. package/templates/storefront/src/components/Header.tsx +36 -0
  33. package/templates/storefront/src/components/ProductCard.tsx +43 -0
  34. package/templates/storefront/src/lib/cart-context.tsx +116 -0
  35. package/templates/storefront/src/lib/forgecart.ts +202 -0
  36. package/{dist/templates/storefront-additions → templates/storefront}/tailwind.config.js +1 -1
  37. package/templates/storefront/tsconfig.json +23 -0
  38. package/LICENSE +0 -21
  39. package/README.md +0 -179
  40. package/bin/forgecart.js +0 -3
  41. package/dist/package.json +0 -50
  42. package/dist/src/cli.d.ts.map +0 -1
  43. package/dist/src/commands/delete.d.ts +0 -3
  44. package/dist/src/commands/delete.d.ts.map +0 -1
  45. package/dist/src/commands/delete.js +0 -60
  46. package/dist/src/commands/delete.js.map +0 -1
  47. package/dist/src/commands/deploy.d.ts +0 -3
  48. package/dist/src/commands/deploy.d.ts.map +0 -1
  49. package/dist/src/commands/deploy.js +0 -243
  50. package/dist/src/commands/deploy.js.map +0 -1
  51. package/dist/src/commands/env.d.ts +0 -3
  52. package/dist/src/commands/env.d.ts.map +0 -1
  53. package/dist/src/commands/env.js +0 -158
  54. package/dist/src/commands/env.js.map +0 -1
  55. package/dist/src/commands/init.d.ts.map +0 -1
  56. package/dist/src/commands/list.d.ts +0 -3
  57. package/dist/src/commands/list.d.ts.map +0 -1
  58. package/dist/src/commands/list.js +0 -65
  59. package/dist/src/commands/list.js.map +0 -1
  60. package/dist/src/commands/login.d.ts +0 -3
  61. package/dist/src/commands/login.d.ts.map +0 -1
  62. package/dist/src/commands/login.js +0 -120
  63. package/dist/src/commands/login.js.map +0 -1
  64. package/dist/src/commands/logs.d.ts +0 -3
  65. package/dist/src/commands/logs.d.ts.map +0 -1
  66. package/dist/src/commands/logs.js +0 -88
  67. package/dist/src/commands/logs.js.map +0 -1
  68. package/dist/src/commands/scale.d.ts +0 -3
  69. package/dist/src/commands/scale.d.ts.map +0 -1
  70. package/dist/src/commands/scale.js +0 -49
  71. package/dist/src/commands/scale.js.map +0 -1
  72. package/dist/src/commands/status.d.ts +0 -3
  73. package/dist/src/commands/status.d.ts.map +0 -1
  74. package/dist/src/commands/status.js +0 -90
  75. package/dist/src/commands/status.js.map +0 -1
  76. package/dist/src/commands/whoami.d.ts +0 -3
  77. package/dist/src/commands/whoami.d.ts.map +0 -1
  78. package/dist/src/commands/whoami.js +0 -59
  79. package/dist/src/commands/whoami.js.map +0 -1
  80. package/dist/src/index.d.ts +0 -3
  81. package/dist/src/index.d.ts.map +0 -1
  82. package/dist/src/index.js +0 -5
  83. package/dist/src/index.js.map +0 -1
  84. package/dist/src/lib/api-client.d.ts +0 -34
  85. package/dist/src/lib/api-client.d.ts.map +0 -1
  86. package/dist/src/lib/api-client.js +0 -141
  87. package/dist/src/lib/api-client.js.map +0 -1
  88. package/dist/src/lib/archive.d.ts +0 -9
  89. package/dist/src/lib/archive.d.ts.map +0 -1
  90. package/dist/src/lib/archive.js +0 -118
  91. package/dist/src/lib/archive.js.map +0 -1
  92. package/dist/src/lib/config-updater.d.ts +0 -10
  93. package/dist/src/lib/config-updater.d.ts.map +0 -1
  94. package/dist/src/lib/config-updater.js +0 -34
  95. package/dist/src/lib/config-updater.js.map +0 -1
  96. package/dist/src/lib/config.d.ts +0 -53
  97. package/dist/src/lib/config.d.ts.map +0 -1
  98. package/dist/src/lib/config.js +0 -162
  99. package/dist/src/lib/config.js.map +0 -1
  100. package/dist/src/lib/env-sync.d.ts +0 -44
  101. package/dist/src/lib/env-sync.d.ts.map +0 -1
  102. package/dist/src/lib/env-sync.js +0 -207
  103. package/dist/src/lib/env-sync.js.map +0 -1
  104. package/dist/src/lib/logger.d.ts +0 -15
  105. package/dist/src/lib/logger.d.ts.map +0 -1
  106. package/dist/src/lib/logger.js +0 -54
  107. package/dist/src/lib/logger.js.map +0 -1
  108. package/dist/src/lib/nx-generator.d.ts +0 -22
  109. package/dist/src/lib/nx-generator.d.ts.map +0 -1
  110. package/dist/src/lib/nx-generator.js +0 -345
  111. package/dist/src/lib/nx-generator.js.map +0 -1
  112. package/dist/src/lib/standalone.d.ts +0 -28
  113. package/dist/src/lib/standalone.d.ts.map +0 -1
  114. package/dist/src/lib/standalone.js +0 -107
  115. package/dist/src/lib/standalone.js.map +0 -1
  116. package/dist/src/types/index.d.ts +0 -80
  117. package/dist/src/types/index.d.ts.map +0 -1
  118. package/dist/src/types/index.js +0 -14
  119. package/dist/src/types/index.js.map +0 -1
  120. package/dist/src/utils/spinner.d.ts +0 -16
  121. package/dist/src/utils/spinner.d.ts.map +0 -1
  122. package/dist/src/utils/spinner.js +0 -88
  123. package/dist/src/utils/spinner.js.map +0 -1
  124. package/dist/src/utils/validation.d.ts +0 -12
  125. package/dist/src/utils/validation.d.ts.map +0 -1
  126. package/dist/src/utils/validation.js +0 -82
  127. package/dist/src/utils/validation.js.map +0 -1
  128. package/dist/templates/application-additions/.forgecartignore +0 -37
  129. package/dist/templates/application-additions/Procfile +0 -1
  130. package/dist/templates/application-additions/src/app/app.module.ts +0 -16
  131. package/dist/templates/application-additions/src/services/forgecart.service.ts +0 -62
  132. package/dist/templates/application-additions/src/services/index.ts +0 -2
  133. package/dist/templates/application-additions/src/services/product-event-listener.service.ts +0 -150
  134. package/dist/templates/application-e2e-additions/src/support/global-setup.ts +0 -16
  135. package/dist/templates/application-e2e-additions/src/support/test-setup.ts +0 -9
  136. package/dist/templates/storefront-additions/.forgecartignore +0 -64
  137. package/dist/templates/storefront-additions/Procfile +0 -1
  138. package/dist/templates/storefront-additions/package.json +0 -11
  139. package/dist/templates/storefront-additions/postcss.config.js +0 -5
  140. package/dist/templates/storefront-additions/src/app/cart/page.tsx +0 -77
  141. package/dist/templates/storefront-additions/src/app/global.css +0 -1
  142. package/dist/templates/storefront-additions/src/app/layout.tsx +0 -25
  143. package/dist/templates/storefront-additions/src/app/page.tsx +0 -43
  144. package/dist/templates/storefront-additions/src/components/AddToCartButton.tsx +0 -29
  145. package/dist/templates/storefront-additions/src/components/CartItem.tsx +0 -82
  146. package/dist/templates/storefront-additions/src/components/Header.tsx +0 -42
  147. package/dist/templates/storefront-additions/src/components/ProductCard.tsx +0 -57
  148. package/dist/templates/storefront-additions/src/components/QuantitySelector.tsx +0 -35
  149. package/dist/templates/storefront-additions/src/context/CartContext.tsx +0 -158
  150. package/dist/templates/storefront-additions/src/lib/forgecart.ts +0 -22
  151. package/dist/templates/storefront-e2e-additions/playwright.config.ts +0 -26
  152. package/dist/templates/storefront-e2e-additions/src/example.spec.ts +0 -11
  153. package/dist/templates/workspace/.nxignore +0 -2
  154. package/templates/application-additions/.forgecartignore +0 -37
  155. package/templates/application-additions/Procfile +0 -1
  156. package/templates/application-additions/src/app/app.module.ts +0 -16
  157. package/templates/application-additions/src/services/forgecart.service.ts +0 -62
  158. package/templates/application-additions/src/services/index.ts +0 -2
  159. package/templates/application-additions/src/services/product-event-listener.service.ts +0 -150
  160. package/templates/application-e2e-additions/src/support/global-setup.ts +0 -16
  161. package/templates/application-e2e-additions/src/support/test-setup.ts +0 -9
  162. package/templates/storefront-additions/.forgecartignore +0 -64
  163. package/templates/storefront-additions/Procfile +0 -1
  164. package/templates/storefront-additions/package.json +0 -11
  165. package/templates/storefront-additions/postcss.config.js +0 -5
  166. package/templates/storefront-additions/src/app/cart/page.tsx +0 -77
  167. package/templates/storefront-additions/src/app/global.css +0 -1
  168. package/templates/storefront-additions/src/app/layout.tsx +0 -25
  169. package/templates/storefront-additions/src/app/page.tsx +0 -43
  170. package/templates/storefront-additions/src/components/AddToCartButton.tsx +0 -29
  171. package/templates/storefront-additions/src/components/CartItem.tsx +0 -82
  172. package/templates/storefront-additions/src/components/Header.tsx +0 -42
  173. package/templates/storefront-additions/src/components/ProductCard.tsx +0 -57
  174. package/templates/storefront-additions/src/components/QuantitySelector.tsx +0 -35
  175. package/templates/storefront-additions/src/context/CartContext.tsx +0 -158
  176. package/templates/storefront-additions/src/lib/forgecart.ts +0 -22
  177. package/templates/storefront-additions/tailwind.config.js +0 -8
  178. package/templates/storefront-e2e-additions/playwright.config.ts +0 -26
  179. package/templates/storefront-e2e-additions/src/example.spec.ts +0 -11
  180. package/templates/workspace/.nxignore +0 -2
@@ -0,0 +1,109 @@
1
+ import { Command } from 'commander';
2
+ import { print } from '../output.js';
3
+ /**
4
+ * Commands whose behaviour ships with the publish pipeline. They are registered
5
+ * now so the command surface and `--help` text exist, but their actions only
6
+ * announce that the implementation is not yet wired in v2.
7
+ */
8
+ function notImplemented(command) {
9
+ print(`\`forgecart ${command}\` is not implemented yet in v2. ` +
10
+ 'It arrives with the publish pipeline.');
11
+ }
12
+ /** `init:application` — add a NestJS application to an existing project. */
13
+ export function createInitApplicationCommand() {
14
+ return new Command('init:application')
15
+ .description('Add a NestJS application to an existing ForgeCart project')
16
+ .action(() => {
17
+ notImplemented('init:application');
18
+ });
19
+ }
20
+ /** `deploy` — deploy the current project. */
21
+ export function createDeployCommand() {
22
+ return new Command('deploy')
23
+ .description('Deploy the current ForgeCart project')
24
+ .option('--skip-build', 'Skip the build step')
25
+ .action(() => {
26
+ notImplemented('deploy');
27
+ });
28
+ }
29
+ /** `list` — list deployments for the channel. */
30
+ export function createListCommand() {
31
+ return new Command('list')
32
+ .alias('ls')
33
+ .description('List all deployments for this channel')
34
+ .action(() => {
35
+ notImplemented('list');
36
+ });
37
+ }
38
+ /** `status [type]` — show deployment status. */
39
+ export function createStatusCommand() {
40
+ return new Command('status')
41
+ .description('Show detailed deployment status')
42
+ .argument('[type]', 'Deployment type (storefront or application)')
43
+ .action(() => {
44
+ notImplemented('status');
45
+ });
46
+ }
47
+ /** `logs [type]` — view deployment logs. */
48
+ export function createLogsCommand() {
49
+ return new Command('logs')
50
+ .description('View deployment logs')
51
+ .argument('[type]', 'Deployment type (storefront or application)', 'storefront')
52
+ .option('-f, --follow', 'Follow log output')
53
+ .option('-n, --lines <number>', 'Number of lines to show', '100')
54
+ .action(() => {
55
+ notImplemented('logs');
56
+ });
57
+ }
58
+ /** `delete <type>` — delete a deployment. */
59
+ export function createDeleteCommand() {
60
+ return new Command('delete')
61
+ .description('Delete a deployment')
62
+ .argument('<type>', 'Deployment type (storefront or application)')
63
+ .option('-f, --force', 'Skip confirmation prompt')
64
+ .action(() => {
65
+ notImplemented('delete');
66
+ });
67
+ }
68
+ /** `scale <type> <instances>` — scale a deployment. */
69
+ export function createScaleCommand() {
70
+ return new Command('scale')
71
+ .description('Scale a deployment')
72
+ .argument('<type>', 'Deployment type (storefront or application)')
73
+ .argument('<instances>', 'Number of instances (0-10)')
74
+ .action(() => {
75
+ notImplemented('scale');
76
+ });
77
+ }
78
+ /** `env` — manage environment variables (get/set/unset). */
79
+ export function createEnvCommand() {
80
+ const cmd = new Command('env').description('Manage deployment environment variables');
81
+ cmd
82
+ .command('get <type>')
83
+ .description('Get environment variables for a deployment')
84
+ .action(() => {
85
+ notImplemented('env get');
86
+ });
87
+ cmd
88
+ .command('set <type> <key> <value>')
89
+ .description('Set an environment variable')
90
+ .action(() => {
91
+ notImplemented('env set');
92
+ });
93
+ cmd
94
+ .command('unset <type> <key>')
95
+ .description('Remove an environment variable')
96
+ .action(() => {
97
+ notImplemented('env unset');
98
+ });
99
+ return cmd;
100
+ }
101
+ /** `whoami` — show current channel information. */
102
+ export function createWhoamiCommand() {
103
+ return new Command('whoami')
104
+ .description('Show current channel information')
105
+ .action(() => {
106
+ notImplemented('whoami');
107
+ });
108
+ }
109
+ //# sourceMappingURL=placeholders.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"placeholders.js","sourceRoot":"","sources":["../../../src/commands/placeholders.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Minimal stdout/stderr writers for the CLI.
3
+ *
4
+ * A publishable CLI legitimately prints to standard streams, but the repo lint
5
+ * rules forbid `console.*`. These helpers centralise process stream access so
6
+ * the rest of the CLI never touches `console`.
7
+ */
8
+ /** Write a line to standard output. */
9
+ export declare function print(message?: string): void;
10
+ /** Write a line to standard error. */
11
+ export declare function printError(message: string): void;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Minimal stdout/stderr writers for the CLI.
3
+ *
4
+ * A publishable CLI legitimately prints to standard streams, but the repo lint
5
+ * rules forbid `console.*`. These helpers centralise process stream access so
6
+ * the rest of the CLI never touches `console`.
7
+ */
8
+ /** Write a line to standard output. */
9
+ export function print(message = '') {
10
+ process.stdout.write(`${message}\n`);
11
+ }
12
+ /** Write a line to standard error. */
13
+ export function printError(message) {
14
+ process.stderr.write(`${message}\n`);
15
+ }
16
+ //# sourceMappingURL=output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/output.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,uCAAuC;AACvC,MAAM,UAAU,KAAK,CAAC,OAAO,GAAG,EAAE;IAChC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;AACvC,CAAC;AAED,sCAAsC;AACtC,MAAM,UAAU,UAAU,CAAC,OAAe;IACxC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;AACvC,CAAC"}
package/package.json CHANGED
@@ -1,30 +1,40 @@
1
1
  {
2
2
  "name": "@forgecart/cli",
3
- "version": "1.1.0",
3
+ "version": "2.0.0",
4
4
  "type": "module",
5
- "description": "CLI tool for deploying applications to ForgeCart",
6
- "main": "dist/index.js",
5
+ "description": "CLI for scaffolding and operating ForgeCart channel storefronts",
7
6
  "bin": {
8
- "forgecart": "./bin/forgecart.js"
7
+ "forgecart": "./dist/bin/forgecart.js"
9
8
  },
10
- "dependencies": {
11
- "@forgecart/sdk": "1.2.5"
9
+ "main": "./dist/src/cli.js",
10
+ "types": "./dist/src/cli.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/src/cli.d.ts",
14
+ "import": "./dist/src/cli.js",
15
+ "default": "./dist/src/cli.js"
16
+ }
12
17
  },
13
18
  "files": [
14
19
  "dist",
15
- "bin",
16
20
  "templates"
17
21
  ],
22
+ "dependencies": {
23
+ "commander": "^12.1.0"
24
+ },
18
25
  "engines": {
19
26
  "node": ">=18.0.0"
20
27
  },
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
21
31
  "keywords": [
22
32
  "forgecart",
23
33
  "cli",
24
- "deploy",
25
- "paas",
26
- "nodejs"
34
+ "storefront",
35
+ "scaffold",
36
+ "nextjs"
27
37
  ],
28
38
  "author": "ForgeCart",
29
39
  "license": "MIT"
30
- }
40
+ }
@@ -0,0 +1,12 @@
1
+ # ForgeCart channel credentials.
2
+ #
3
+ # `forgecart init` writes the real values to `.env.local` for you. This file
4
+ # documents the variables the storefront reads. Both are required and used
5
+ # server-side only (never exposed to the browser).
6
+
7
+ # The channel token identifies which ForgeCart channel this storefront serves.
8
+ # It is sent as the `forgecart-token` header on every shop API request.
9
+ FORGECART_CHANNEL_TOKEN=
10
+
11
+ # Base URL of the ForgeCart shop GraphQL API (the shop-api endpoint).
12
+ FORGECART_SHOP_API_URL=https://api.forgecart.com/shop-api
@@ -0,0 +1,2 @@
1
+ node_modules
2
+ .next
@@ -0,0 +1 @@
1
+ web: npm run start
@@ -0,0 +1,87 @@
1
+ # Storefront
2
+
3
+ A minimal [Next.js 15](https://nextjs.org) (App Router) storefront for a
4
+ ForgeCart channel. It talks to the ForgeCart shop GraphQL API through
5
+ `@forgecart/sdk` and renders product data in Server Components.
6
+
7
+ This directory is the scaffold copied verbatim into your project by
8
+ `forgecart init`. Nothing here is compiled by the ForgeCart monorepo; it ships
9
+ as-is in the published `@forgecart/cli` package.
10
+
11
+ ## How the channel token + API URL wire in
12
+
13
+ The storefront reads two environment variables (server-side only):
14
+
15
+ | Variable | Purpose |
16
+ | ------------------------- | ------------------------------------------------------------- |
17
+ | `FORGECART_CHANNEL_TOKEN` | Identifies the channel; sent as the `forgecart-token` header. |
18
+ | `FORGECART_SHOP_API_URL` | Base URL of the channel's shop GraphQL API (`.../shop-api`). |
19
+
20
+ `forgecart init` writes both to `.env.local` for you. To configure manually,
21
+ copy `.env.example` to `.env.local` and fill them in.
22
+
23
+ These values are consumed in [`src/lib/forgecart.ts`](src/lib/forgecart.ts),
24
+ which constructs and memoizes a singleton `ForgeCartShopClient`:
25
+
26
+ ```ts
27
+ new ForgeCartShopClient({
28
+ endpoint: process.env.FORGECART_SHOP_API_URL,
29
+ channelToken: process.env.FORGECART_CHANNEL_TOKEN,
30
+ });
31
+ ```
32
+
33
+ Because the client is only imported from Server Components, the channel token
34
+ never reaches the browser.
35
+
36
+ ## Running locally
37
+
38
+ ```bash
39
+ npm install
40
+ npm run dev # next dev -> http://localhost:3000
41
+ ```
42
+
43
+ A ForgeCart channel workspace serves this app as its live site by running
44
+ `npm run dev` (development) or `npm run build && npm run start` (production; see
45
+ the `Procfile`).
46
+
47
+ ## Project layout
48
+
49
+ ```
50
+ src/
51
+ lib/
52
+ forgecart.ts # shop client singleton + typed product helpers
53
+ cart-context.tsx # local (localStorage) cart state for the MVP
54
+ components/
55
+ Header.tsx # top nav: Home / Products / Cart
56
+ ProductCard.tsx # product tile
57
+ AddToCartButton.tsx
58
+ app/
59
+ layout.tsx # html shell + header/footer + CartProvider
60
+ page.tsx # home: greeting + featured products
61
+ products/
62
+ page.tsx # product grid
63
+ [slug]/page.tsx # product detail
64
+ cart/page.tsx # local cart
65
+ ```
66
+
67
+ ## Data access
68
+
69
+ `src/lib/forgecart.ts` exposes typed helpers over the shop client:
70
+
71
+ - `getProducts({ take, skip })` -> `{ items, totalItems }`
72
+ - `getFeaturedProducts(count)` -> `StorefrontProduct[]`
73
+ - `getProductBySlug(slug)` / `getProductById(id)` -> `StorefrontProduct | null`
74
+ - `getStartingPrice(product)` and `formatPrice(minorUnits, currency)`
75
+
76
+ The generated typed product operations (`shopClient.product.shopProducts` /
77
+ `.shopProduct`) only select `id` + `optionGroups`, so these helpers use the
78
+ SDK's supported raw-query escape hatch (`shopClient.query<T>(document, vars)`)
79
+ to also fetch `name`, `slug`, `description`, `featuredAsset`, and `variants`
80
+ (price in minor units). Every selected field exists on the shop schema's
81
+ `Product` type — extend the documents in `forgecart.ts` to fetch more.
82
+
83
+ ## Cart
84
+
85
+ The starter cart (`src/lib/cart-context.tsx` + `app/cart/page.tsx`) keeps state
86
+ in the browser via `localStorage`. To take real orders, wire it to the
87
+ ForgeCart `cart` / `checkout` shop operations exposed by the SDK.
@@ -0,0 +1,14 @@
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ // Produce a self-contained build so a channel workspace can run the
4
+ // storefront with `next start` without a full node_modules tree.
5
+ output: 'standalone',
6
+ reactStrictMode: true,
7
+ images: {
8
+ // The shop API serves product/asset previews from arbitrary hosts, so
9
+ // allow any remote image. Tighten this to your asset host in production.
10
+ remotePatterns: [{ protocol: 'https', hostname: '**' }],
11
+ },
12
+ };
13
+
14
+ module.exports = nextConfig;
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "storefront",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start"
9
+ },
10
+ "dependencies": {
11
+ "@forgecart/sdk": "^1.2.6",
12
+ "next": "^15.1.0",
13
+ "react": "^19.0.0",
14
+ "react-dom": "^19.0.0"
15
+ },
16
+ "devDependencies": {
17
+ "@types/node": "^22.10.0",
18
+ "@types/react": "^19.0.0",
19
+ "@types/react-dom": "^19.0.0",
20
+ "autoprefixer": "^10.4.20",
21
+ "postcss": "^8.4.49",
22
+ "tailwindcss": "^3.4.17",
23
+ "typescript": "^5.7.0"
24
+ }
25
+ }
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ };
@@ -0,0 +1,110 @@
1
+ 'use client';
2
+
3
+ import Link from 'next/link';
4
+
5
+ import { formatPrice } from '../../lib/forgecart';
6
+ import { useCart } from '../../lib/cart-context';
7
+
8
+ export default function CartPage() {
9
+ const { lines, subtotal, itemCount, setQuantity, removeLine, clear } = useCart();
10
+
11
+ if (itemCount === 0) {
12
+ return (
13
+ <div className="space-y-4">
14
+ <h1 className="text-2xl font-bold tracking-tight text-gray-900">Your cart</h1>
15
+ <p className="text-gray-500">Your cart is empty.</p>
16
+ <Link
17
+ href="/products"
18
+ className="inline-block rounded-md bg-gray-900 px-5 py-2.5 text-sm font-medium text-white transition hover:bg-gray-700"
19
+ >
20
+ Browse products
21
+ </Link>
22
+ </div>
23
+ );
24
+ }
25
+
26
+ return (
27
+ <div className="space-y-6">
28
+ <div className="flex items-center justify-between">
29
+ <h1 className="text-2xl font-bold tracking-tight text-gray-900">Your cart</h1>
30
+ <button
31
+ type="button"
32
+ onClick={clear}
33
+ className="text-sm text-gray-500 hover:text-gray-900"
34
+ >
35
+ Clear cart
36
+ </button>
37
+ </div>
38
+
39
+ <ul className="divide-y divide-gray-200 rounded-lg border border-gray-200 bg-white">
40
+ {lines.map((line) => (
41
+ <li key={line.variantId} className="flex items-center gap-4 p-4">
42
+ <div className="h-16 w-16 shrink-0 overflow-hidden rounded bg-gray-100">
43
+ {line.imageUrl ? (
44
+ // eslint-disable-next-line @next/next/no-img-element
45
+ <img
46
+ src={line.imageUrl}
47
+ alt={line.name}
48
+ className="h-full w-full object-cover"
49
+ />
50
+ ) : null}
51
+ </div>
52
+
53
+ <div className="min-w-0 flex-1">
54
+ <Link
55
+ href={`/products/${line.productSlug}`}
56
+ className="truncate font-medium text-gray-900 hover:underline"
57
+ >
58
+ {line.name}
59
+ </Link>
60
+ <p className="text-sm text-gray-500">{formatPrice(line.unitPrice)} each</p>
61
+ </div>
62
+
63
+ <div className="flex items-center gap-2">
64
+ <label className="sr-only" htmlFor={`qty-${line.variantId}`}>
65
+ Quantity
66
+ </label>
67
+ <input
68
+ id={`qty-${line.variantId}`}
69
+ type="number"
70
+ min={1}
71
+ value={line.quantity}
72
+ onChange={(e) =>
73
+ setQuantity(line.variantId, Number.parseInt(e.target.value, 10) || 1)
74
+ }
75
+ className="w-16 rounded border border-gray-300 px-2 py-1 text-sm"
76
+ />
77
+ </div>
78
+
79
+ <div className="w-24 text-right font-medium text-gray-900">
80
+ {formatPrice(line.unitPrice * line.quantity)}
81
+ </div>
82
+
83
+ <button
84
+ type="button"
85
+ onClick={() => removeLine(line.variantId)}
86
+ className="text-sm text-gray-400 hover:text-red-600"
87
+ aria-label={`Remove ${line.name}`}
88
+ >
89
+ Remove
90
+ </button>
91
+ </li>
92
+ ))}
93
+ </ul>
94
+
95
+ <div className="flex items-center justify-between rounded-lg border border-gray-200 bg-white p-4">
96
+ <span className="text-sm text-gray-600">Subtotal ({itemCount} items)</span>
97
+ <span className="text-lg font-semibold text-gray-900">
98
+ {formatPrice(subtotal)}
99
+ </span>
100
+ </div>
101
+
102
+ <p className="text-sm text-gray-500">
103
+ This is a starter cart with local state. Wire it to the ForgeCart{' '}
104
+ <code className="rounded bg-gray-100 px-1">cart</code> /{' '}
105
+ <code className="rounded bg-gray-100 px-1">checkout</code> APIs to take real
106
+ orders.
107
+ </p>
108
+ </div>
109
+ );
110
+ }
@@ -0,0 +1,13 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ :root {
6
+ --background: #ffffff;
7
+ --foreground: #111827;
8
+ }
9
+
10
+ body {
11
+ color: var(--foreground);
12
+ background: var(--background);
13
+ }
@@ -0,0 +1,30 @@
1
+ import type { Metadata } from 'next';
2
+ import type { ReactNode } from 'react';
3
+
4
+ import { Header } from '../components/Header';
5
+ import { CartProvider } from '../lib/cart-context';
6
+
7
+ import './globals.css';
8
+
9
+ export const metadata: Metadata = {
10
+ title: 'Storefront',
11
+ description: 'A ForgeCart channel storefront.',
12
+ };
13
+
14
+ export default function RootLayout({ children }: { children: ReactNode }) {
15
+ return (
16
+ <html lang="en">
17
+ <body className="min-h-screen bg-gray-50 text-gray-900 antialiased">
18
+ <CartProvider>
19
+ <Header />
20
+ <main className="mx-auto max-w-6xl px-4 py-8">{children}</main>
21
+ <footer className="border-t border-gray-200 bg-white">
22
+ <div className="mx-auto max-w-6xl px-4 py-6 text-sm text-gray-500">
23
+ Powered by ForgeCart
24
+ </div>
25
+ </footer>
26
+ </CartProvider>
27
+ </body>
28
+ </html>
29
+ );
30
+ }
@@ -0,0 +1,49 @@
1
+ import Link from 'next/link';
2
+
3
+ import { ProductCard } from '../components/ProductCard';
4
+ import { getFeaturedProducts } from '../lib/forgecart';
5
+
6
+ // Product data changes in the dashboard, so render on each request.
7
+ export const dynamic = 'force-dynamic';
8
+
9
+ export default async function HomePage() {
10
+ const products = await getFeaturedProducts(4);
11
+
12
+ return (
13
+ <div className="space-y-12">
14
+ <section className="rounded-2xl bg-white p-10 shadow-sm">
15
+ <h1 className="text-3xl font-bold tracking-tight text-gray-900">
16
+ Welcome to the shop
17
+ </h1>
18
+ <p className="mt-3 max-w-2xl text-gray-600">
19
+ This storefront is served by a ForgeCart channel. Browse the catalog
20
+ and add products to your cart.
21
+ </p>
22
+ <Link
23
+ href="/products"
24
+ className="mt-6 inline-block rounded-md bg-gray-900 px-5 py-2.5 text-sm font-medium text-white transition hover:bg-gray-700"
25
+ >
26
+ Shop all products
27
+ </Link>
28
+ </section>
29
+
30
+ <section>
31
+ <div className="mb-4 flex items-center justify-between">
32
+ <h2 className="text-xl font-semibold text-gray-900">Featured</h2>
33
+ <Link href="/products" className="text-sm text-gray-600 hover:text-gray-900">
34
+ View all
35
+ </Link>
36
+ </div>
37
+ {products.length === 0 ? (
38
+ <p className="text-gray-500">No products yet. Add some in your dashboard.</p>
39
+ ) : (
40
+ <div className="grid grid-cols-2 gap-6 sm:grid-cols-3 lg:grid-cols-4">
41
+ {products.map((product) => (
42
+ <ProductCard key={product.id} product={product} />
43
+ ))}
44
+ </div>
45
+ )}
46
+ </section>
47
+ </div>
48
+ );
49
+ }
@@ -0,0 +1,20 @@
1
+ import Link from 'next/link';
2
+
3
+ export default function ProductNotFound() {
4
+ return (
5
+ <div className="space-y-4">
6
+ <h1 className="text-2xl font-bold tracking-tight text-gray-900">
7
+ Product not found
8
+ </h1>
9
+ <p className="text-gray-500">
10
+ We couldn&apos;t find that product. It may have been removed.
11
+ </p>
12
+ <Link
13
+ href="/products"
14
+ className="inline-block rounded-md bg-gray-900 px-5 py-2.5 text-sm font-medium text-white transition hover:bg-gray-700"
15
+ >
16
+ Back to products
17
+ </Link>
18
+ </div>
19
+ );
20
+ }