canopycms-next 0.0.67-int.89 → 0.0.67-int.91

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/dist/adapter.d.ts CHANGED
@@ -1,23 +1,15 @@
1
1
  import { type CanopyHandlerOptions, type CanopyRequest } from 'canopycms/http';
2
2
  /**
3
- * Options for creating a Canopy Next.js handler.
4
3
  * Same as core CanopyHandlerOptions - re-exported for convenience.
5
4
  */
6
5
  export type CanopyNextOptions = CanopyHandlerOptions;
7
6
  /**
8
- * Wrap a standard Request (or NextRequest) to implement the CanopyRequest interface.
9
7
  * Only uses standard Request methods, so any Request subclass works.
10
8
  */
11
9
  export declare function wrapNextRequest(req: Request): CanopyRequest;
12
10
  /**
13
11
  * Catch-all Next.js handler for a single API route (e.g., /api/canopycms/[...canopycms]).
14
12
  *
15
- * This is a thin adapter that:
16
- * 1. Converts NextRequest to CanopyRequest
17
- * 2. Extracts path segments from Next.js params
18
- * 3. Delegates to the core handler
19
- * 4. Converts CanopyResponse to NextResponse
20
- *
21
13
  * @example
22
14
  * ```ts
23
15
  * // app/api/canopycms/[...canopycms]/route.ts
package/dist/adapter.js CHANGED
@@ -2,7 +2,6 @@ import { NextResponse } from 'next/server';
2
2
  import { createCanopyRequestHandler, isCanopyBinaryResponse, } from 'canopycms/http';
3
3
  import { getErrorMessage, redactCredentials, sanitizeErrorMessage } from 'canopycms/utils/error';
4
4
  /**
5
- * Wrap a standard Request (or NextRequest) to implement the CanopyRequest interface.
6
5
  * Only uses standard Request methods, so any Request subclass works.
7
6
  */
8
7
  export function wrapNextRequest(req) {
@@ -30,10 +29,6 @@ export function wrapNextRequest(req) {
30
29
  },
31
30
  };
32
31
  }
33
- /**
34
- * Map a CanopyBinaryResponse's framework-agnostic header fields onto the
35
- * real HTTP header names, omitting any that weren't set.
36
- */
37
32
  function toBinaryHeaders(headers) {
38
33
  const result = {};
39
34
  if (headers.contentType)
@@ -46,17 +41,12 @@ function toBinaryHeaders(headers) {
46
41
  result['ETag'] = headers.etag;
47
42
  return result;
48
43
  }
49
- /**
50
- * Convert a CanopyResponse (or CanopyBinaryResponse) to a NextResponse.
51
- */
52
44
  function toNextResponse(response) {
53
45
  if (isCanopyBinaryResponse(response)) {
54
- // A Uint8Array can be backed by an arbitrary ArrayBufferLike (e.g. a
55
- // Node Buffer), which doesn't structurally satisfy the DOM lib's
56
- // BodyInit/ArrayBufferView (specifically ArrayBuffer-backed). Copying
57
- // through the typed-array constructor yields a plain ArrayBuffer-backed
58
- // view so this type-checks without an unsafe cast; ReadableStream
59
- // bodies pass through unchanged.
46
+ // A Uint8Array can be backed by an arbitrary ArrayBufferLike (e.g. a Node Buffer), which
47
+ // doesn't structurally satisfy the DOM lib's BodyInit/ArrayBufferView. Copying through the
48
+ // typed-array constructor yields a plain ArrayBuffer-backed view that type-checks without an
49
+ // unsafe cast; ReadableStream bodies pass through unchanged.
60
50
  const body = response.body instanceof Uint8Array ? new Uint8Array(response.body) : response.body;
61
51
  return new NextResponse(body, {
62
52
  status: response.status,
@@ -69,7 +59,6 @@ function toNextResponse(response) {
69
59
  });
70
60
  }
71
61
  /**
72
- * Extract path segments from Next.js catch-all route params.
73
62
  * Handles both Next.js 14 (direct object) and Next.js 15 (Promise) params.
74
63
  */
75
64
  async function extractPathSegments(ctx) {
@@ -81,12 +70,6 @@ async function extractPathSegments(ctx) {
81
70
  /**
82
71
  * Catch-all Next.js handler for a single API route (e.g., /api/canopycms/[...canopycms]).
83
72
  *
84
- * This is a thin adapter that:
85
- * 1. Converts NextRequest to CanopyRequest
86
- * 2. Extracts path segments from Next.js params
87
- * 3. Delegates to the core handler
88
- * 4. Converts CanopyResponse to NextResponse
89
- *
90
73
  * @example
91
74
  * ```ts
92
75
  * // app/api/canopycms/[...canopycms]/route.ts
@@ -116,17 +99,14 @@ export const createCanopyCatchAllHandler = (options) => {
116
99
  return toNextResponse(response);
117
100
  }
118
101
  catch (err) {
119
- // Defense-in-depth (API-C1): coreHandler already guards itself with a
120
- // top-level try/catch, but this adapter also wraps request conversion
121
- // (wrapNextRequest/extractPathSegments/toNextResponse) so a failure there
122
- // can never escape as Next's generic unhandled-error 500, which would
123
- // break the uniform { ok, status, error } envelope the editor expects.
102
+ // Defense-in-depth (API-C1): coreHandler already guards itself with a top-level try/catch,
103
+ // but this adapter also wraps request conversion, so a failure there can never escape as
104
+ // Next's generic unhandled-error 500, which would break the uniform { ok, status, error }
105
+ // envelope the editor expects.
124
106
  const message = getErrorMessage(err);
125
- // Redacted before logging, not just before responding. The HTTP body was
126
- // already sanitized, but this line went to the server log verbatim -- and
127
- // a git failure message can embed a token-bearing clone URL, which is the
128
- // one thing that must not reach a log aggregator. Every failure surface
129
- // in the worker already redacts; this was the outlier.
107
+ // Redacted before logging, not just before responding: the HTTP body is already sanitized,
108
+ // but this line reaches the server log verbatim, and a git failure message can embed a
109
+ // token-bearing clone URL — the one thing that must not reach a log aggregator.
130
110
  console.error('CanopyCMS: Unhandled error in Next.js catch-all handler:', redactCredentials(message));
131
111
  return toNextResponse({
132
112
  status: 500,
package/dist/client.d.ts CHANGED
@@ -3,13 +3,11 @@ import type { CanopyClientConfig, CustomFieldRenderers } from 'canopycms/client'
3
3
  * Next.js-specific wrapper for CanopyEditorPage that automatically reads
4
4
  * URL search params (branch, entry) using Next.js's useSearchParams hook.
5
5
  *
6
- * @param customRenderers Optional per-field-type render overrides, forwarded
7
- * to `CanopyEditorPage`. Forwarded rather than dropped for a specific reason:
8
- * Next is the primary target, so this wrapper is the entrypoint adopters
9
- * actually import (it is what the README's Quick Start scaffolds). Accepting
10
- * the argument only on the core `CanopyEditorPage` made the extension point
11
- * unreachable from the path every adopter uses, which is an extension point
12
- * in name only.
6
+ * @param customRenderers Optional per-field-type render overrides, forwarded to
7
+ * `CanopyEditorPage`. Forwarded (not dropped) because Next is the primary target: this wrapper
8
+ * is the entrypoint every adopter imports (the README's Quick Start scaffolds it), so accepting
9
+ * the argument only on core `CanopyEditorPage` would make the extension point unreachable from
10
+ * the path adopters actually use.
13
11
  *
14
12
  * @example
15
13
  * ```tsx
package/dist/client.js CHANGED
@@ -7,13 +7,11 @@ import { CanopyEditorPage } from 'canopycms/client';
7
7
  * Next.js-specific wrapper for CanopyEditorPage that automatically reads
8
8
  * URL search params (branch, entry) using Next.js's useSearchParams hook.
9
9
  *
10
- * @param customRenderers Optional per-field-type render overrides, forwarded
11
- * to `CanopyEditorPage`. Forwarded rather than dropped for a specific reason:
12
- * Next is the primary target, so this wrapper is the entrypoint adopters
13
- * actually import (it is what the README's Quick Start scaffolds). Accepting
14
- * the argument only on the core `CanopyEditorPage` made the extension point
15
- * unreachable from the path every adopter uses, which is an extension point
16
- * in name only.
10
+ * @param customRenderers Optional per-field-type render overrides, forwarded to
11
+ * `CanopyEditorPage`. Forwarded (not dropped) because Next is the primary target: this wrapper
12
+ * is the entrypoint every adopter imports (the README's Quick Start scaffolds it), so accepting
13
+ * the argument only on core `CanopyEditorPage` would make the extension point unreachable from
14
+ * the path adopters actually use.
17
15
  *
18
16
  * @example
19
17
  * ```tsx
@@ -31,10 +29,9 @@ import { CanopyEditorPage } from 'canopycms/client';
31
29
  */
32
30
  export const NextCanopyEditorPage = (config, customRenderers) => {
33
31
  const CorePage = CanopyEditorPage(config, customRenderers);
34
- // Reads URL search params. Next.js 15 requires useSearchParams() consumers to
35
- // sit under a <Suspense> boundary, otherwise static rendering errors with
36
- // "useSearchParams() should be wrapped in a suspense boundary". We provide the
37
- // boundary here so adopters don't have to wrap their /edit page themselves.
32
+ // Next.js 15 requires useSearchParams() consumers to sit under a <Suspense> boundary, or static
33
+ // rendering errors with "useSearchParams() should be wrapped in a suspense boundary" — provided
34
+ // here so adopters don't have to wrap their /edit page themselves.
38
35
  function EditorWithSearchParams() {
39
36
  const urlSearchParams = useSearchParams();
40
37
  const searchParams = {
package/dist/config.cjs CHANGED
@@ -36,7 +36,187 @@ module.exports = __toCommonJS(config_exports);
36
36
 
37
37
  // src/with-canopy.ts
38
38
  var import_node_module = require("node:module");
39
+ var import_node_path2 = __toESM(require("node:path"), 1);
40
+
41
+ // src/sharp-tracing.ts
42
+ var import_node_fs = require("node:fs");
39
43
  var import_node_path = __toESM(require("node:path"), 1);
44
+ var LOCKFILES = ["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lock", "bun.lockb"];
45
+ var LEGACY_LOCKFILES = ["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lockb"];
46
+ var NEXT_CONFIG_FILES = ["next.config.js", "next.config.mjs", "next.config.ts", "next.config.mts"];
47
+ var GLOB_METACHARACTERS = /[*?[\]{}(),!\\]/;
48
+ var LIBVIPS_PACKAGE = /^@img\/sharp-libvips-/;
49
+ var NATIVE_BINDING_PACKAGE = /^@img\/sharp-(?!libvips-)/;
50
+ function messageOf(err) {
51
+ if (err instanceof Error) return err.message;
52
+ if (typeof err === "string") return err;
53
+ return String(err);
54
+ }
55
+ function isFile(candidate) {
56
+ try {
57
+ return (0, import_node_fs.statSync)(candidate).isFile();
58
+ } catch {
59
+ return false;
60
+ }
61
+ }
62
+ function isDirectory(candidate) {
63
+ try {
64
+ return (0, import_node_fs.statSync)(candidate).isDirectory();
65
+ } catch {
66
+ return false;
67
+ }
68
+ }
69
+ function isOutside(root, target) {
70
+ const relative = import_node_path.default.relative(root, target);
71
+ return relative === ".." || relative.startsWith(`..${import_node_path.default.sep}`) || import_node_path.default.isAbsolute(relative);
72
+ }
73
+ function readManifest(packageDir) {
74
+ const parsed = JSON.parse((0, import_node_fs.readFileSync)(import_node_path.default.join(packageDir, "package.json"), "utf8"));
75
+ const manifest = { optionalDependencyNames: [] };
76
+ if (typeof parsed !== "object" || parsed === null) return manifest;
77
+ if ("name" in parsed && typeof parsed.name === "string") manifest.name = parsed.name;
78
+ if ("version" in parsed && typeof parsed.version === "string") manifest.version = parsed.version;
79
+ if ("optionalDependencies" in parsed && typeof parsed.optionalDependencies === "object" && parsed.optionalDependencies !== null) {
80
+ manifest.optionalDependencyNames = Object.keys(parsed.optionalDependencies);
81
+ }
82
+ return manifest;
83
+ }
84
+ function nodeModulesLookupDirs(fromDir) {
85
+ const dirs = [];
86
+ for (let dir = import_node_path.default.resolve(fromDir); ; dir = import_node_path.default.dirname(dir)) {
87
+ if (import_node_path.default.basename(dir) !== "node_modules") dirs.push(import_node_path.default.join(dir, "node_modules"));
88
+ if (import_node_path.default.dirname(dir) === dir) return dirs;
89
+ }
90
+ }
91
+ function findInstalledPackage(fromDir, name) {
92
+ for (const lookupDir of nodeModulesLookupDirs(fromDir)) {
93
+ const candidate = import_node_path.default.join(lookupDir, name);
94
+ if (isFile(import_node_path.default.join(candidate, "package.json"))) return (0, import_node_fs.realpathSync)(candidate);
95
+ }
96
+ return null;
97
+ }
98
+ function installedNextMajor(projectDir) {
99
+ try {
100
+ const nextDir = findInstalledPackage(projectDir, "next");
101
+ if (!nextDir) return null;
102
+ const major = Number.parseInt(readManifest(nextDir).version ?? "", 10);
103
+ return Number.isNaN(major) ? null : major;
104
+ } catch {
105
+ return null;
106
+ }
107
+ }
108
+ function hasNextConfig(dir) {
109
+ return NEXT_CONFIG_FILES.some((name) => isFile(import_node_path.default.join(dir, name)));
110
+ }
111
+ function findLockFileUpwards(startDir, names) {
112
+ for (let dir = startDir; ; dir = import_node_path.default.dirname(dir)) {
113
+ for (const name of names) {
114
+ const candidate = import_node_path.default.join(dir, name);
115
+ if (isFile(candidate)) return candidate;
116
+ }
117
+ if (import_node_path.default.dirname(dir) === dir) return null;
118
+ }
119
+ }
120
+ function resolveTracingRoot(input) {
121
+ const configured = input.outputFileTracingRoot || input.turbopackRoot;
122
+ if (configured) return import_node_path.default.resolve(input.projectDir, configured);
123
+ if (input.lockfileRoot === "closest") {
124
+ const closest = findLockFileUpwards(input.projectDir, LEGACY_LOCKFILES);
125
+ return closest ? import_node_path.default.dirname(closest) : input.projectDir;
126
+ }
127
+ let lockFile = findLockFileUpwards(input.projectDir, LOCKFILES);
128
+ if (!lockFile) return input.projectDir;
129
+ for (; ; ) {
130
+ const lockDir = import_node_path.default.dirname(lockFile);
131
+ const parentDir = import_node_path.default.dirname(lockDir);
132
+ if (parentDir === lockDir) break;
133
+ const outer = findLockFileUpwards(parentDir, LOCKFILES);
134
+ if (!outer) break;
135
+ lockFile = outer;
136
+ }
137
+ return import_node_path.default.dirname(lockFile);
138
+ }
139
+ function nativeLibraryDirsFor(sharpDir) {
140
+ const found = /* @__PURE__ */ new Set();
141
+ const collectLibvips = (fromDir, dependencyNames) => {
142
+ for (const name of dependencyNames.filter((n) => LIBVIPS_PACKAGE.test(n))) {
143
+ const dir = findInstalledPackage(fromDir, name);
144
+ if (dir) found.add(dir);
145
+ }
146
+ };
147
+ const sharpDependencies = readManifest(sharpDir).optionalDependencyNames;
148
+ collectLibvips(sharpDir, sharpDependencies);
149
+ for (const bindingName of sharpDependencies.filter((n) => NATIVE_BINDING_PACKAGE.test(n))) {
150
+ const bindingDir = findInstalledPackage(sharpDir, bindingName);
151
+ if (!bindingDir) continue;
152
+ const bindingDependencies = readManifest(bindingDir).optionalDependencyNames;
153
+ if (bindingDependencies.some((n) => LIBVIPS_PACKAGE.test(n))) {
154
+ collectLibvips(bindingDir, bindingDependencies);
155
+ } else {
156
+ found.add(bindingDir);
157
+ }
158
+ }
159
+ return [...found];
160
+ }
161
+ function sharpTracingIncludes(input) {
162
+ try {
163
+ const projectDir = (0, import_node_fs.realpathSync)(input.projectDir);
164
+ const root = (0, import_node_fs.realpathSync)(resolveTracingRoot(input));
165
+ if (isOutside(root, projectDir)) {
166
+ return {
167
+ includes: [],
168
+ problem: `the project directory ${projectDir} is outside the tracing root ${root}`
169
+ };
170
+ }
171
+ const sharpDirs = /* @__PURE__ */ new Set();
172
+ const canopycmsDir = findInstalledPackage(projectDir, "canopycms");
173
+ for (const candidate of [
174
+ canopycmsDir && findInstalledPackage(canopycmsDir, "sharp"),
175
+ findInstalledPackage(projectDir, "sharp")
176
+ ]) {
177
+ if (candidate && readManifest(candidate).name === "sharp") sharpDirs.add(candidate);
178
+ }
179
+ if (sharpDirs.size === 0) {
180
+ return {
181
+ includes: [],
182
+ problem: canopycmsDir ? `no sharp is installed for canopycms at ${canopycmsDir}` : `canopycms is not installed where ${projectDir} can find it`
183
+ };
184
+ }
185
+ const includes = /* @__PURE__ */ new Set();
186
+ const notes = [];
187
+ for (const sharpDir of sharpDirs) {
188
+ const libraryDirs = nativeLibraryDirsFor(sharpDir);
189
+ if (libraryDirs.length === 0) {
190
+ notes.push(
191
+ `no libvips package is installed for sharp at ${sharpDir}, and no installed native binding carries its own library`
192
+ );
193
+ }
194
+ for (const libraryDir of libraryDirs) {
195
+ const libDir = import_node_path.default.join(libraryDir, "lib");
196
+ if (!isDirectory(libDir)) {
197
+ notes.push(`${libDir} does not exist`);
198
+ continue;
199
+ }
200
+ if (isOutside(root, libDir)) {
201
+ notes.push(`${libDir} is outside the tracing root ${root}`);
202
+ continue;
203
+ }
204
+ const relative = import_node_path.default.relative(projectDir, libDir).split(import_node_path.default.sep).join("/");
205
+ if (GLOB_METACHARACTERS.test(relative)) {
206
+ notes.push(`${relative} contains a glob metacharacter`);
207
+ continue;
208
+ }
209
+ includes.add(`${relative}/**/*`);
210
+ }
211
+ }
212
+ if (includes.size === 0) return { includes: [], problem: notes.join("; ") };
213
+ return { includes: [...includes] };
214
+ } catch (err) {
215
+ return { includes: [], problem: messageOf(err) };
216
+ }
217
+ }
218
+
219
+ // src/with-canopy.ts
40
220
  var REQUIRED_PACKAGES = ["canopycms"];
41
221
  var OPTIONAL_PACKAGES = [
42
222
  "canopycms-next",
@@ -70,8 +250,8 @@ function withAssetsRewrite(existingRewrites) {
70
250
  function resolveReactAliases(resolve) {
71
251
  try {
72
252
  return {
73
- react: import_node_path.default.dirname(resolve("react")),
74
- "react-dom": import_node_path.default.dirname(resolve("react-dom"))
253
+ react: import_node_path2.default.dirname(resolve("react")),
254
+ "react-dom": import_node_path2.default.dirname(resolve("react-dom"))
75
255
  };
76
256
  } catch {
77
257
  return null;
@@ -100,8 +280,82 @@ function resolveStaticBuildId() {
100
280
  }
101
281
  return trimmed;
102
282
  }
283
+ var ALL_ROUTES = "/**";
284
+ function isTracingIncludes(value) {
285
+ return typeof value === "object" && value !== null && Object.values(value).every(
286
+ (globs) => Array.isArray(globs) && globs.every((g) => typeof g === "string")
287
+ );
288
+ }
289
+ function readProperty(value, key) {
290
+ return typeof value === "object" && value !== null ? Reflect.get(value, key) : void 0;
291
+ }
292
+ function stringOrUndefined(value) {
293
+ return typeof value === "string" ? value : void 0;
294
+ }
295
+ function isSet(value) {
296
+ return value !== void 0 && value !== null;
297
+ }
298
+ function mergeTracingIncludes(existing, includes) {
299
+ return {
300
+ ...existing,
301
+ [ALL_ROUTES]: [.../* @__PURE__ */ new Set([...existing?.[ALL_ROUTES] ?? [], ...includes])]
302
+ };
303
+ }
304
+ function sharpTracingWarning(problem, nextVersionUnknown) {
305
+ return "CanopyCMS: could not add sharp's libvips to this standalone build's file tracing" + (problem ? ` (${problem})` : "") + ". Next.js file tracing can miss that library for sharp 0.35 (https://github.com/vercel/next.js/issues/97973), and the built server then fails to load sharp with ERR_DLOPEN_FAILED. Add it in next.config yourself, with paths relative to the project directory. For pnpm: outputFileTracingIncludes: { '/**': ['node_modules/.pnpm/@img+sharp-libvips-*/node_modules/@img/*/lib/**/*'] }. For npm: outputFileTracingIncludes: { '/**': ['node_modules/@img/sharp-libvips-*/lib/**/*'] }." + (nextVersionUnknown ? " The installed Next.js version could not be read either: on Next 13 or 14, put outputFileTracingIncludes under experimental." : "");
306
+ }
307
+ function unknownNextVersionWarning(projectDir) {
308
+ return `CanopyCMS: could not read the version of the next package installed for ${projectDir}, so sharp's libvips was added to the top-level outputFileTracingIncludes key, which Next 15 and later read. Next 13 and 14 ignore that key: on those versions, move the entry under experimental.outputFileTracingIncludes.`;
309
+ }
310
+ var warnedAboutSharpTracing = false;
311
+ function sharpTracingConfig(nextConfig, options) {
312
+ if (options.staticBuild || nextConfig.output === "export") return {};
313
+ const projectDir = process.cwd();
314
+ const nextMajor = installedNextMajor(projectDir);
315
+ const experimental = nextConfig.experimental ?? {};
316
+ const experimentalOnly = nextMajor !== null && nextMajor < 15;
317
+ const includesUnderExperimental = experimentalOnly || isSet(readProperty(experimental, "outputFileTracingIncludes"));
318
+ const rootUnderExperimental = experimentalOnly || isSet(readProperty(experimental, "outputFileTracingRoot"));
319
+ const writtenIncludes = includesUnderExperimental ? readProperty(experimental, "outputFileTracingIncludes") : nextConfig.outputFileTracingIncludes;
320
+ const existing = isSet(writtenIncludes) ? writtenIncludes : void 0;
321
+ if (existing !== void 0 && !isTracingIncludes(existing)) return {};
322
+ const configuredRoot = stringOrUndefined(
323
+ rootUnderExperimental ? readProperty(experimental, "outputFileTracingRoot") : nextConfig.outputFileTracingRoot
324
+ );
325
+ const turbopack = nextConfig.turbopack;
326
+ const turbopackHasRootKey = typeof turbopack === "object" && turbopack !== null && "root" in turbopack;
327
+ const legacyTurbopackRoot = nextMajor === 15 && !turbopackHasRootKey ? stringOrUndefined(readProperty(readProperty(experimental, "turbo"), "root")) : void 0;
328
+ const turbopackRoot = experimentalOnly ? void 0 : stringOrUndefined(readProperty(turbopack, "root")) ?? legacyTurbopackRoot;
329
+ const { includes, problem } = hasNextConfig(projectDir) ? sharpTracingIncludes({
330
+ projectDir,
331
+ outputFileTracingRoot: configuredRoot,
332
+ turbopackRoot,
333
+ lockfileRoot: experimentalOnly ? "closest" : "outermost"
334
+ }) : {
335
+ includes: [],
336
+ problem: `the working directory ${projectDir} has no next.config file, so withCanopy cannot tell where the project is`
337
+ };
338
+ const nextVersionUnknown = nextMajor === null;
339
+ if (includes.length === 0) {
340
+ if (nextConfig.output === "standalone" && !warnedAboutSharpTracing) {
341
+ warnedAboutSharpTracing = true;
342
+ console.warn(sharpTracingWarning(problem, nextVersionUnknown));
343
+ }
344
+ return {};
345
+ }
346
+ const merged = mergeTracingIncludes(existing, includes);
347
+ if (!includesUnderExperimental) {
348
+ if (nextVersionUnknown && nextConfig.output === "standalone" && !warnedAboutSharpTracing) {
349
+ warnedAboutSharpTracing = true;
350
+ console.warn(unknownNextVersionWarning(projectDir));
351
+ }
352
+ return { outputFileTracingIncludes: merged };
353
+ }
354
+ const legacyExperimental = { ...experimental, outputFileTracingIncludes: merged };
355
+ return { experimental: legacyExperimental };
356
+ }
103
357
  function withCanopy(nextConfig = {}, options = {}) {
104
- const resolve = (0, import_node_module.createRequire)(import_node_path.default.join(process.cwd(), "noop.js")).resolve;
358
+ const resolve = (0, import_node_module.createRequire)(import_node_path2.default.join(process.cwd(), "noop.js")).resolve;
105
359
  const installedOptional = OPTIONAL_PACKAGES.filter((pkg) => {
106
360
  try {
107
361
  resolve(pkg);
@@ -136,6 +390,8 @@ function withCanopy(nextConfig = {}, options = {}) {
136
390
  }
137
391
  return config;
138
392
  } : existingWebpack;
393
+ const nextMajor = installedNextMajor(process.cwd());
394
+ const answerTurbopackGuard = webpack !== void 0 && !isSet(existingWebpack) && !isSet(nextConfig.turbopack) && nextMajor !== null && nextMajor >= 16;
139
395
  const pageExtensions = [
140
396
  .../* @__PURE__ */ new Set([
141
397
  ...nextConfig.pageExtensions ?? NEXTJS_DEFAULT_PAGE_EXTENSIONS,
@@ -149,9 +405,11 @@ function withCanopy(nextConfig = {}, options = {}) {
149
405
  pageExtensions,
150
406
  webpack,
151
407
  rewrites: withAssetsRewrite(nextConfig.rewrites),
408
+ ...sharpTracingConfig(nextConfig, options),
152
409
  // Spread conditionally: emitting `generateBuildId: undefined` would be a key Next has to
153
410
  // reason about, where absence is unambiguous.
154
- ...generateBuildId ? { generateBuildId } : {}
411
+ ...generateBuildId ? { generateBuildId } : {},
412
+ ...answerTurbopackGuard ? { turbopack: {} } : {}
155
413
  };
156
414
  }
157
415
  // Annotate the CommonJS export names for ESM import in node: