@netlify/plugin-nextjs 5.13.5 → 5.14.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.
@@ -13,7 +13,7 @@ import {
13
13
  } from "../../esm-chunks/chunk-YUXQHOYO.js";
14
14
  import {
15
15
  require_semver
16
- } from "../../esm-chunks/chunk-TLQCAGE2.js";
16
+ } from "../../esm-chunks/chunk-TVEBGDAB.js";
17
17
  import {
18
18
  __toESM
19
19
  } from "../../esm-chunks/chunk-6BT4RYQJ.js";
@@ -13,7 +13,7 @@ import {
13
13
  } from "../../esm-chunks/chunk-YUXQHOYO.js";
14
14
  import {
15
15
  require_semver
16
- } from "../../esm-chunks/chunk-TLQCAGE2.js";
16
+ } from "../../esm-chunks/chunk-TVEBGDAB.js";
17
17
  import {
18
18
  __toESM
19
19
  } from "../../esm-chunks/chunk-6BT4RYQJ.js";
@@ -6,7 +6,7 @@
6
6
 
7
7
  import {
8
8
  require_semver
9
- } from "../esm-chunks/chunk-TLQCAGE2.js";
9
+ } from "../esm-chunks/chunk-TVEBGDAB.js";
10
10
  import {
11
11
  __toESM
12
12
  } from "../esm-chunks/chunk-6BT4RYQJ.js";
@@ -150,6 +150,10 @@ var PluginContext = class {
150
150
  get edgeHandlerDir() {
151
151
  return join(this.edgeFunctionsDir, EDGE_HANDLER_NAME);
152
152
  }
153
+ /** Absolute path to the skew protection config */
154
+ get skewProtectionConfigPath() {
155
+ return this.resolveFromPackagePath(".netlify/v1/skew-protection.json");
156
+ }
153
157
  constructor(options) {
154
158
  this.constants = options.constants;
155
159
  this.featureFlags = options.featureFlags;
@@ -0,0 +1,106 @@
1
+
2
+ var require = await (async () => {
3
+ var { createRequire } = await import("node:module");
4
+ return createRequire(import.meta.url);
5
+ })();
6
+
7
+ import "../esm-chunks/chunk-6BT4RYQJ.js";
8
+
9
+ // src/build/skew-protection.ts
10
+ import { mkdir, writeFile } from "node:fs/promises";
11
+ import { dirname } from "node:path";
12
+ var EnabledOrDisabledReason = /* @__PURE__ */ ((EnabledOrDisabledReason2) => {
13
+ EnabledOrDisabledReason2["OPT_OUT_DEFAULT"] = "off-default";
14
+ EnabledOrDisabledReason2["OPT_OUT_NO_VALID_DEPLOY_ID"] = "off-no-valid-deploy-id";
15
+ EnabledOrDisabledReason2["OPT_OUT_NO_VALID_DEPLOY_ID_ENV_VAR"] = "off-no-valid-deploy-id-env-var";
16
+ EnabledOrDisabledReason2["OPT_IN_FF"] = "on-ff";
17
+ EnabledOrDisabledReason2["OPT_IN_ENV_VAR"] = "on-env-var";
18
+ EnabledOrDisabledReason2["OPT_OUT_ENV_VAR"] = "off-env-var";
19
+ return EnabledOrDisabledReason2;
20
+ })(EnabledOrDisabledReason || {});
21
+ var optInOptions = /* @__PURE__ */ new Set([
22
+ "on-ff" /* OPT_IN_FF */,
23
+ "on-env-var" /* OPT_IN_ENV_VAR */
24
+ ]);
25
+ var skewProtectionConfig = {
26
+ patterns: [".*"],
27
+ sources: [
28
+ {
29
+ type: "cookie",
30
+ name: "__vdpl"
31
+ },
32
+ {
33
+ type: "header",
34
+ name: "X-Deployment-Id"
35
+ },
36
+ {
37
+ type: "query",
38
+ name: "dpl"
39
+ }
40
+ ]
41
+ };
42
+ function shouldEnableSkewProtection(ctx) {
43
+ let enabledOrDisabledReason = "off-default" /* OPT_OUT_DEFAULT */;
44
+ if (process.env.NETLIFY_NEXT_SKEW_PROTECTION === "true" || process.env.NETLIFY_NEXT_SKEW_PROTECTION === "1") {
45
+ enabledOrDisabledReason = "on-env-var" /* OPT_IN_ENV_VAR */;
46
+ } else if (process.env.NETLIFY_NEXT_SKEW_PROTECTION === "false" || process.env.NETLIFY_NEXT_SKEW_PROTECTION === "0") {
47
+ return {
48
+ enabled: false,
49
+ enabledOrDisabledReason: "off-env-var" /* OPT_OUT_ENV_VAR */
50
+ };
51
+ } else if (ctx.featureFlags?.["next-runtime-skew-protection"]) {
52
+ enabledOrDisabledReason = "on-ff" /* OPT_IN_FF */;
53
+ } else {
54
+ return {
55
+ enabled: false,
56
+ enabledOrDisabledReason: "off-default" /* OPT_OUT_DEFAULT */
57
+ };
58
+ }
59
+ if ((!process.env.DEPLOY_ID || process.env.DEPLOY_ID === "0") && optInOptions.has(enabledOrDisabledReason)) {
60
+ return {
61
+ enabled: false,
62
+ enabledOrDisabledReason: enabledOrDisabledReason === "on-env-var" /* OPT_IN_ENV_VAR */ && ctx.constants.IS_LOCAL ? (
63
+ // this case is singled out to provide visible feedback to users that env var has no effect
64
+ "off-no-valid-deploy-id-env-var" /* OPT_OUT_NO_VALID_DEPLOY_ID_ENV_VAR */
65
+ ) : (
66
+ // this is silent disablement to avoid spam logs for users opted in via feature flag
67
+ // that don't explicitly opt in via env var
68
+ "off-no-valid-deploy-id" /* OPT_OUT_NO_VALID_DEPLOY_ID */
69
+ )
70
+ };
71
+ }
72
+ return {
73
+ enabled: optInOptions.has(enabledOrDisabledReason),
74
+ enabledOrDisabledReason
75
+ };
76
+ }
77
+ var setSkewProtection = async (ctx, span) => {
78
+ const { enabled, enabledOrDisabledReason } = shouldEnableSkewProtection(ctx);
79
+ span.setAttribute("skewProtection", enabledOrDisabledReason);
80
+ if (!enabled) {
81
+ if (enabledOrDisabledReason === "off-no-valid-deploy-id-env-var" /* OPT_OUT_NO_VALID_DEPLOY_ID_ENV_VAR */) {
82
+ console.warn(
83
+ `NETLIFY_NEXT_SKEW_PROTECTION environment variable is set to ${process.env.NETLIFY_NEXT_SKEW_PROTECTION}, but skew protection is currently unavailable for CLI deploys. Skew protection will not be enabled.`
84
+ );
85
+ }
86
+ return;
87
+ }
88
+ if (enabledOrDisabledReason === "on-env-var" /* OPT_IN_ENV_VAR */) {
89
+ console.log(
90
+ `Setting up Next.js Skew Protection due to NETLIFY_NEXT_SKEW_PROTECTION=${process.env.NETLIFY_NEXT_SKEW_PROTECTION} environment variable.`
91
+ );
92
+ } else {
93
+ console.log("Setting up Next.js Skew Protection.");
94
+ }
95
+ process.env.NEXT_DEPLOYMENT_ID = process.env.DEPLOY_ID;
96
+ await mkdir(dirname(ctx.skewProtectionConfigPath), {
97
+ recursive: true
98
+ });
99
+ await writeFile(ctx.skewProtectionConfigPath, JSON.stringify(skewProtectionConfig));
100
+ };
101
+ export {
102
+ EnabledOrDisabledReason,
103
+ setSkewProtection,
104
+ shouldEnableSkewProtection,
105
+ skewProtectionConfig
106
+ };
@@ -9,7 +9,7 @@ import {
9
9
  } from "../esm-chunks/chunk-YUXQHOYO.js";
10
10
  import {
11
11
  require_semver
12
- } from "../esm-chunks/chunk-TLQCAGE2.js";
12
+ } from "../esm-chunks/chunk-TVEBGDAB.js";
13
13
  import {
14
14
  __toESM
15
15
  } from "../esm-chunks/chunk-6BT4RYQJ.js";
@@ -163,6 +163,9 @@ var require_identifiers = __commonJS({
163
163
  "use strict";
164
164
  var numeric = /^[0-9]+$/;
165
165
  var compareIdentifiers = (a, b) => {
166
+ if (typeof a === "number" && typeof b === "number") {
167
+ return a === b ? 0 : a < b ? -1 : 1;
168
+ }
166
169
  const anum = numeric.test(a);
167
170
  const bnum = numeric.test(b);
168
171
  if (anum && bnum) {
@@ -269,7 +272,25 @@ var require_semver = __commonJS({
269
272
  if (!(other instanceof _SemVer)) {
270
273
  other = new _SemVer(other, this.options);
271
274
  }
272
- return compareIdentifiers(this.major, other.major) || compareIdentifiers(this.minor, other.minor) || compareIdentifiers(this.patch, other.patch);
275
+ if (this.major < other.major) {
276
+ return -1;
277
+ }
278
+ if (this.major > other.major) {
279
+ return 1;
280
+ }
281
+ if (this.minor < other.minor) {
282
+ return -1;
283
+ }
284
+ if (this.minor > other.minor) {
285
+ return 1;
286
+ }
287
+ if (this.patch < other.patch) {
288
+ return -1;
289
+ }
290
+ if (this.patch > other.patch) {
291
+ return 1;
292
+ }
293
+ return 0;
273
294
  }
274
295
  comparePre(other) {
275
296
  if (!(other instanceof _SemVer)) {
@@ -1030,6 +1051,7 @@ var require_range = __commonJS({
1030
1051
  return result;
1031
1052
  };
1032
1053
  var parseComparator = (comp, options) => {
1054
+ comp = comp.replace(re[t.BUILD], "");
1033
1055
  debug("comp", comp, options);
1034
1056
  comp = replaceCarets(comp, options);
1035
1057
  debug("caret", comp);
package/dist/index.js CHANGED
@@ -26,6 +26,7 @@ import { clearStaleEdgeHandlers, createEdgeHandlers } from "./build/functions/ed
26
26
  import { clearStaleServerHandlers, createServerHandler } from "./build/functions/server.js";
27
27
  import { setImageConfig } from "./build/image-cdn.js";
28
28
  import { PluginContext } from "./build/plugin-context.js";
29
+ import { setSkewProtection } from "./build/skew-protection.js";
29
30
  import {
30
31
  verifyAdvancedAPIRoutes,
31
32
  verifyNetlifyFormsWorkaround,
@@ -49,7 +50,7 @@ var onPreBuild = async (options) => {
49
50
  console.warn(skipText);
50
51
  return;
51
52
  }
52
- await tracer.withActiveSpan("onPreBuild", async () => {
53
+ await tracer.withActiveSpan("onPreBuild", async (span) => {
53
54
  process.env.NEXT_PRIVATE_STANDALONE = "true";
54
55
  const ctx = new PluginContext(options);
55
56
  if (options.constants.IS_LOCAL) {
@@ -58,6 +59,7 @@ var onPreBuild = async (options) => {
58
59
  } else {
59
60
  await restoreBuildCache(ctx);
60
61
  }
62
+ await setSkewProtection(ctx, span);
61
63
  });
62
64
  };
63
65
  var onBuild = async (options) => {
@@ -204,16 +204,20 @@ var NetlifyCacheHandler = class {
204
204
  );
205
205
  return null;
206
206
  }
207
- const staleByTags = await this.checkCacheEntryStaleByTags(
207
+ const { stale: staleByTags, expired: expiredByTags } = await this.checkCacheEntryStaleByTags(
208
208
  blob,
209
209
  context.tags,
210
210
  context.softTags
211
211
  );
212
- if (staleByTags) {
213
- span.addEvent("Stale", { staleByTags, key, ttl });
212
+ if (expiredByTags) {
213
+ span.addEvent("Expired", { expiredByTags, key, ttl });
214
214
  return null;
215
215
  }
216
216
  this.captureResponseCacheLastModified(blob, key, span);
217
+ if (staleByTags) {
218
+ span.addEvent("Stale", { staleByTags, key, ttl });
219
+ blob.lastModified = -1;
220
+ }
217
221
  const isDataRequest = Boolean(context.fetchUrl);
218
222
  if (!isDataRequest) {
219
223
  this.captureCacheTags(blob.value, key);
@@ -350,8 +354,8 @@ var NetlifyCacheHandler = class {
350
354
  }
351
355
  });
352
356
  }
353
- async revalidateTag(tagOrTags) {
354
- return (0, import_tags_handler.markTagsAsStaleAndPurgeEdgeCache)(tagOrTags);
357
+ async revalidateTag(tagOrTags, durations) {
358
+ return (0, import_tags_handler.markTagsAsStaleAndPurgeEdgeCache)(tagOrTags, durations);
355
359
  }
356
360
  resetRequestCache() {
357
361
  }
@@ -365,16 +369,22 @@ var NetlifyCacheHandler = class {
365
369
  } else if (cacheEntry.value?.kind === "PAGE" || cacheEntry.value?.kind === "PAGES" || cacheEntry.value?.kind === "APP_PAGE" || cacheEntry.value?.kind === "ROUTE" || cacheEntry.value?.kind === "APP_ROUTE") {
366
370
  cacheTags = cacheEntry.value.headers?.[import_constants.NEXT_CACHE_TAGS_HEADER]?.split(/,|%2c/gi) || [];
367
371
  } else {
368
- return false;
372
+ return {
373
+ stale: false,
374
+ expired: false
375
+ };
369
376
  }
370
377
  if (this.revalidatedTags && this.revalidatedTags.length !== 0) {
371
378
  for (const tag of this.revalidatedTags) {
372
379
  if (cacheTags.includes(tag)) {
373
- return true;
380
+ return {
381
+ stale: true,
382
+ expired: true
383
+ };
374
384
  }
375
385
  }
376
386
  }
377
- return (0, import_tags_handler.isAnyTagStale)(cacheTags, cacheEntry.lastModified);
387
+ return (0, import_tags_handler.isAnyTagStaleOrExpired)(cacheTags, cacheEntry.lastModified);
378
388
  }
379
389
  };
380
390
  var cache_default = NetlifyCacheHandler;
@@ -20,8 +20,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/run/handlers/tags-handler.cts
21
21
  var tags_handler_exports = {};
22
22
  __export(tags_handler_exports, {
23
- getMostRecentTagRevalidationTimestamp: () => getMostRecentTagRevalidationTimestamp,
24
- isAnyTagStale: () => isAnyTagStale,
23
+ getMostRecentTagExpirationTimestamp: () => getMostRecentTagExpirationTimestamp,
24
+ isAnyTagStaleOrExpired: () => isAnyTagStaleOrExpired,
25
25
  markTagsAsStaleAndPurgeEdgeCache: () => markTagsAsStaleAndPurgeEdgeCache,
26
26
  purgeEdgeCache: () => purgeEdgeCache
27
27
  });
@@ -86,58 +86,86 @@ var pipeline = (0, import_util.promisify)(import_stream.pipeline);
86
86
 
87
87
  // package.json
88
88
  var name = "@netlify/plugin-nextjs";
89
- var version = "5.13.5";
89
+ var version = "5.14.0";
90
90
 
91
91
  // src/run/handlers/tags-handler.cts
92
92
  var import_storage = require("../storage/storage.cjs");
93
93
  var import_request_context = require("./request-context.cjs");
94
94
  var purgeCacheUserAgent = `${name}@${version}`;
95
- async function getTagRevalidatedAt(tag, cacheStore) {
95
+ async function getTagManifest(tag, cacheStore) {
96
96
  const tagManifest = await cacheStore.get(tag, "tagManifest.get");
97
97
  if (!tagManifest) {
98
98
  return null;
99
99
  }
100
- return tagManifest.revalidatedAt;
100
+ return tagManifest;
101
101
  }
102
- async function getMostRecentTagRevalidationTimestamp(tags) {
102
+ async function getMostRecentTagExpirationTimestamp(tags) {
103
103
  if (tags.length === 0) {
104
104
  return 0;
105
105
  }
106
106
  const cacheStore = (0, import_storage.getMemoizedKeyValueStoreBackedByRegionalBlobStore)({ consistency: "strong" });
107
- const timestampsOrNulls = await Promise.all(
108
- tags.map((tag) => getTagRevalidatedAt(tag, cacheStore))
109
- );
110
- const timestamps = timestampsOrNulls.filter((timestamp) => timestamp !== null);
111
- if (timestamps.length === 0) {
107
+ const manifestsOrNulls = await Promise.all(tags.map((tag) => getTagManifest(tag, cacheStore)));
108
+ const expirationTimestamps = manifestsOrNulls.filter((manifest) => manifest !== null).map((manifest) => manifest.expireAt);
109
+ if (expirationTimestamps.length === 0) {
112
110
  return 0;
113
111
  }
114
- return Math.max(...timestamps);
112
+ return Math.max(...expirationTimestamps);
115
113
  }
116
- function isAnyTagStale(tags, timestamp) {
114
+ function isAnyTagStaleOrExpired(tags, timestamp) {
117
115
  if (tags.length === 0 || !timestamp) {
118
- return Promise.resolve(false);
116
+ return Promise.resolve({ stale: false, expired: false });
119
117
  }
120
118
  const cacheStore = (0, import_storage.getMemoizedKeyValueStoreBackedByRegionalBlobStore)({ consistency: "strong" });
121
119
  return new Promise((resolve, reject) => {
122
120
  const tagManifestPromises = [];
123
121
  for (const tag of tags) {
124
- const lastRevalidationTimestampPromise = getTagRevalidatedAt(tag, cacheStore);
122
+ const tagManifestPromise = getTagManifest(tag, cacheStore);
125
123
  tagManifestPromises.push(
126
- lastRevalidationTimestampPromise.then((lastRevalidationTimestamp) => {
127
- if (!lastRevalidationTimestamp) {
128
- return false;
124
+ tagManifestPromise.then((tagManifest) => {
125
+ if (!tagManifest) {
126
+ return { stale: false, expired: false };
127
+ }
128
+ const stale = tagManifest.staleAt >= timestamp;
129
+ const expired = tagManifest.expireAt >= timestamp && tagManifest.expireAt <= Date.now();
130
+ if (expired && stale) {
131
+ const expiredResult = {
132
+ stale,
133
+ expired
134
+ };
135
+ resolve(expiredResult);
136
+ return expiredResult;
129
137
  }
130
- const isStale = lastRevalidationTimestamp >= timestamp;
131
- if (isStale) {
132
- resolve(true);
133
- return true;
138
+ if (stale) {
139
+ const staleResult = {
140
+ stale,
141
+ expired,
142
+ expireAt: tagManifest.expireAt
143
+ };
144
+ return staleResult;
134
145
  }
135
- return false;
146
+ return { stale: false, expired: false };
136
147
  })
137
148
  );
138
149
  }
139
- Promise.all(tagManifestPromises).then((tagManifestAreStale) => {
140
- resolve(tagManifestAreStale.some((tagIsStale) => tagIsStale));
150
+ Promise.all(tagManifestPromises).then((tagManifestsAreStaleOrExpired) => {
151
+ let result = { stale: false, expired: false };
152
+ for (const tagResult of tagManifestsAreStaleOrExpired) {
153
+ if (tagResult.expired) {
154
+ result = tagResult;
155
+ break;
156
+ }
157
+ if (tagResult.stale) {
158
+ result = {
159
+ stale: true,
160
+ expired: false,
161
+ expireAt: (
162
+ // make sure to use expireAt that is lowest of all tags
163
+ result.stale && !result.expired && typeof result.expireAt === "number" ? Math.min(result.expireAt, tagResult.expireAt) : tagResult.expireAt
164
+ )
165
+ };
166
+ }
167
+ }
168
+ resolve(result);
141
169
  }).catch(reject);
142
170
  });
143
171
  }
@@ -154,13 +182,15 @@ function purgeEdgeCache(tagOrTags) {
154
182
  (0, import_request_context.getLogger)().withError(error).error(`[NextRuntime] Purging the cache for tags [${tags.join(",")}] failed`);
155
183
  });
156
184
  }
157
- async function doRevalidateTagAndPurgeEdgeCache(tags) {
158
- (0, import_request_context.getLogger)().withFields({ tags }).debug("doRevalidateTagAndPurgeEdgeCache");
185
+ async function doRevalidateTagAndPurgeEdgeCache(tags, durations) {
186
+ (0, import_request_context.getLogger)().withFields({ tags, durations }).debug("doRevalidateTagAndPurgeEdgeCache");
159
187
  if (tags.length === 0) {
160
188
  return;
161
189
  }
190
+ const now = Date.now();
162
191
  const tagManifest = {
163
- revalidatedAt: Date.now()
192
+ staleAt: now,
193
+ expireAt: now + (durations?.expire ? durations.expire * 1e3 : 0)
164
194
  };
165
195
  const cacheStore = (0, import_storage.getMemoizedKeyValueStoreBackedByRegionalBlobStore)({ consistency: "strong" });
166
196
  await Promise.all(
@@ -174,9 +204,9 @@ async function doRevalidateTagAndPurgeEdgeCache(tags) {
174
204
  );
175
205
  await purgeEdgeCache(tags);
176
206
  }
177
- function markTagsAsStaleAndPurgeEdgeCache(tagOrTags) {
207
+ function markTagsAsStaleAndPurgeEdgeCache(tagOrTags, durations) {
178
208
  const tags = getCacheTagsFromTagOrTags(tagOrTags);
179
- const revalidateTagPromise = doRevalidateTagAndPurgeEdgeCache(tags);
209
+ const revalidateTagPromise = doRevalidateTagAndPurgeEdgeCache(tags, durations);
180
210
  const requestContext = (0, import_request_context.getRequestContext)();
181
211
  if (requestContext) {
182
212
  requestContext.trackBackgroundWork(revalidateTagPromise);
@@ -185,8 +215,8 @@ function markTagsAsStaleAndPurgeEdgeCache(tagOrTags) {
185
215
  }
186
216
  // Annotate the CommonJS export names for ESM import in node:
187
217
  0 && (module.exports = {
188
- getMostRecentTagRevalidationTimestamp,
189
- isAnyTagStale,
218
+ getMostRecentTagExpirationTimestamp,
219
+ isAnyTagStaleOrExpired,
190
220
  markTagsAsStaleAndPurgeEdgeCache,
191
221
  purgeEdgeCache
192
222
  });
@@ -1381,8 +1381,8 @@ var LRUCache = class _LRUCache {
1381
1381
  // src/run/handlers/use-cache-handler.ts
1382
1382
  import { getLogger } from "./request-context.cjs";
1383
1383
  import {
1384
- getMostRecentTagRevalidationTimestamp,
1385
- isAnyTagStale,
1384
+ getMostRecentTagExpirationTimestamp,
1385
+ isAnyTagStaleOrExpired,
1386
1386
  markTagsAsStaleAndPurgeEdgeCache
1387
1387
  } from "./tags-handler.cjs";
1388
1388
  import { getTracer } from "./tracer.cjs";
@@ -1444,7 +1444,8 @@ var NetlifyDefaultUseCacheHandler = {
1444
1444
  });
1445
1445
  return void 0;
1446
1446
  }
1447
- if (await isAnyTagStale(entry.tags, entry.timestamp)) {
1447
+ const { stale } = await isAnyTagStaleOrExpired(entry.tags, entry.timestamp);
1448
+ if (stale) {
1448
1449
  getLogger().withFields({ cacheKey, ttl, status: "STALE BY TAG" }).debug(`[NetlifyDefaultUseCacheHandler] get result`);
1449
1450
  span.setAttributes({
1450
1451
  cacheStatus: "stale tag, discarded",
@@ -1520,7 +1521,7 @@ var NetlifyDefaultUseCacheHandler = {
1520
1521
  span.setAttributes({
1521
1522
  tags
1522
1523
  });
1523
- const expiration = await getMostRecentTagRevalidationTimestamp(tags);
1524
+ const expiration = await getMostRecentTagExpirationTimestamp(tags);
1524
1525
  getLogger().withFields({ tags, expiration }).debug(`[NetlifyDefaultUseCacheHandler] getExpiration`);
1525
1526
  span.setAttributes({
1526
1527
  expiration
@@ -25,7 +25,7 @@ __export(blob_types_exports, {
25
25
  });
26
26
  module.exports = __toCommonJS(blob_types_exports);
27
27
  var isTagManifest = (value) => {
28
- return typeof value === "object" && value !== null && "revalidatedAt" in value && typeof value.revalidatedAt === "number" && Object.keys(value).length === 1;
28
+ return typeof value === "object" && value !== null && "staleAt" in value && typeof value.staleAt === "number" && "expiredAt" in value && typeof value.expiredAt === "number" && Object.keys(value).length === 2;
29
29
  };
30
30
  var isHtmlBlob = (value) => {
31
31
  return typeof value === "object" && value !== null && "html" in value && "isFullyStaticPage" in value && typeof value.html === "string" && typeof value.isFullyStaticPage === "boolean" && Object.keys(value).length === 2;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/plugin-nextjs",
3
- "version": "5.13.5",
3
+ "version": "5.14.0",
4
4
  "description": "Run Next.js seamlessly on Netlify",
5
5
  "main": "./dist/index.js",
6
6
  "type": "module",