@bigcommerce/catalyst 1.2.0 → 1.4.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": "@bigcommerce/catalyst",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -15,7 +15,7 @@
15
15
  "templates"
16
16
  ],
17
17
  "engines": {
18
- "node": "^20.0.0 || ^22.0.0 || ^24.0.0"
18
+ "node": "^22.0.0 || ^24.0.0"
19
19
  },
20
20
  "dependencies": {
21
21
  "@commander-js/extra-typings": "^14.0.0",
@@ -35,6 +35,7 @@
35
35
  "semver": "^7.7.4",
36
36
  "std-env": "^3.9.0",
37
37
  "tar": "^7.5.7",
38
+ "tldts": "^7.4.12",
38
39
  "yocto-spinner": "^1.0.0",
39
40
  "zod": "^4.0.5"
40
41
  },
@@ -57,7 +58,12 @@
57
58
  "@bigcommerce/eslint-config-catalyst": "^1.0.0"
58
59
  },
59
60
  "peerDependencies": {
60
- "@opennextjs/cloudflare": "1.17.3"
61
+ "@opennextjs/cloudflare": "^1.17.3"
62
+ },
63
+ "peerDependenciesMeta": {
64
+ "@opennextjs/cloudflare": {
65
+ "optional": true
66
+ }
61
67
  },
62
68
  "scripts": {
63
69
  "dev": "tsup --watch",
@@ -1,17 +1,72 @@
1
1
  import { defineCloudflareConfig, type OpenNextConfig } from '@opennextjs/cloudflare';
2
- import { purgeCache } from '@opennextjs/cloudflare/overrides/cache-purge/index';
3
2
  import r2IncrementalCache from '@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache';
4
3
  import { withRegionalCache } from '@opennextjs/cloudflare/overrides/incremental-cache/regional-cache';
5
- import doQueue from '@opennextjs/cloudflare/overrides/queue/do-queue';
6
4
  import queueCache from '@opennextjs/cloudflare/overrides/queue/queue-cache';
7
5
  import doShardedTagCache from '@opennextjs/cloudflare/overrides/tag-cache/do-sharded-tag-cache';
8
6
 
7
+ // OpenNext's `doQueue` cannot be used on native hosting: its Durable Object
8
+ // requires a `WORKER_SELF_REFERENCE` service binding, which a dispatch-namespace
9
+ // script cannot have. See LTRAC-1457 for why the alternatives were rejected.
10
+ //
11
+ // No binding is required. The revalidation is a HEAD request to the page's own
12
+ // public URL carrying the build-time preview secret, which is exactly what the
13
+ // Durable Object issues once it holds the service handle. Sending it with a
14
+ // plain `fetch` leaves and re-enters through the dispatch router and arrives at
15
+ // the same Worker. `global_fetch_strictly_public` is set, so the subrequest is
16
+ // not short-circuited internally.
17
+ //
18
+ // Wrapped in `queueCache` below so concurrent stale hits for one path collapse
19
+ // into a single revalidation. What is lost relative to the Durable Object is its
20
+ // retry and max-concurrency handling; a failed revalidation is retried on the
21
+ // next stale hit rather than by the queue itself.
22
+
23
+ // Matches the Durable Object's default. Without a bound, a hanging revalidation
24
+ // would sit in `waitUntil` holding the invocation alive.
25
+ const REVALIDATION_TIMEOUT_MS = 10_000;
26
+
27
+ const selfFetchQueue = {
28
+ name: 'self-fetch-queue',
29
+ async send({ MessageBody: { host, url } }: { MessageBody: { host: string; url: string } }) {
30
+ const protocol = host.includes('localhost') ? 'http' : 'https';
31
+
32
+ const response = await fetch(`${protocol}://${host}${url}`, {
33
+ method: 'HEAD',
34
+ headers: {
35
+ // Inlined at build time; authorizes the revalidation. The name is
36
+ // Next's, so the leading underscores are not ours to rename.
37
+ // eslint-disable-next-line no-underscore-dangle
38
+ 'x-prerender-revalidate': process.env.__NEXT_PREVIEW_MODE_ID ?? '',
39
+ 'x-isr': '1',
40
+ },
41
+ signal: AbortSignal.timeout(REVALIDATION_TIMEOUT_MS),
42
+ });
43
+
44
+ // `fetch` resolves for 4xx/5xx, so a failed regeneration would otherwise be
45
+ // indistinguishable from success. Throwing surfaces it: `queueCache` logs
46
+ // the error and, with `waitForQueueAck`, skips caching the attempt — so the
47
+ // next stale hit retries rather than the failure being swallowed. Silent
48
+ // failure is the exact bug this queue exists to fix.
49
+ if (!response.ok) {
50
+ throw new Error(`Revalidation of ${url} failed with status ${response.status}`);
51
+ }
52
+ },
53
+ };
54
+
9
55
  const cloudflareConfig = defineCloudflareConfig({
56
+ // The absence of `cachePurge` and `bypassTagCacheOnCacheHit` is deliberate.
57
+ // Invalidating regional (Cache API) entries needs either a CDN purge that
58
+ // evicts them or a tag-cache check on every hit; this takes the second.
59
+ //
60
+ // Do not restore `cachePurge` without reading LTRAC-1458 first. OpenNext's
61
+ // `isPurgeCacheEnabled()` checks only whether it is *declared*, never whether
62
+ // it can authenticate, and declaring it turns off both
63
+ // `shouldLazilyUpdateOnCacheHit` and the tag check. A purge that cannot
64
+ // authenticate therefore leaves entries neither purged, nor refreshed from R2,
65
+ // nor tag-checked, and `revalidateTag` silently stops working.
10
66
  incrementalCache: withRegionalCache(r2IncrementalCache, {
11
67
  mode: 'long-lived',
12
- bypassTagCacheOnCacheHit: true,
13
68
  }),
14
- queue: queueCache(doQueue, {
69
+ queue: queueCache(selfFetchQueue, {
15
70
  regionalCacheTtlSec: 5,
16
71
  waitForQueueAck: true,
17
72
  }),
@@ -30,11 +85,17 @@ const cloudflareConfig = defineCloudflareConfig({
30
85
  },
31
86
  }),
32
87
  enableCacheInterception: false,
33
- cachePurge: purgeCache({ type: 'durableObject' }),
34
88
  });
35
89
 
36
90
  const config: OpenNextConfig = {
37
- buildCommand: 'node_modules/.bin/next build',
91
+ // Invoke Next through `node` on its published bin rather than the
92
+ // `node_modules/.bin/next` shim. OpenNext runs this string via
93
+ // `execSync`, which shells out to cmd.exe on native Windows — where the
94
+ // extensionless POSIX shim and forward-slash path both fail to resolve.
95
+ // Calling `node <bin>` works identically across sh and cmd.exe (node.exe
96
+ // accepts forward slashes) while still bypassing the project's `build`
97
+ // script so the `generate` step is skipped.
98
+ buildCommand: 'node ./node_modules/next/dist/bin/next build',
38
99
  ...cloudflareConfig,
39
100
  };
40
101