@uniformdev/next-app-router-upgrade 0.1.0 → 0.2.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/README.md +49 -8
- package/package.json +1 -1
- package/src/client-context.js +89 -0
- package/src/edge/akamai.js +62 -0
- package/src/edge/cloudflare.js +45 -0
- package/src/edge/common.js +400 -0
- package/src/edge/index.js +8 -4
- package/src/edge/vercel.js +20 -342
- package/src/migrate.js +39 -9
- package/src/report.js +1 -1
- package/src/rules.js +22 -2
- package/src/scan.js +86 -6
- package/src/session.js +14 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @uniformdev/next-app-router-upgrade
|
|
2
2
|
|
|
3
|
-
This CLI helps you move a Next.js App Router app that uses `@uniformdev/next-app-router` to the developer preview of the SDK (`20.81.1-alpha.
|
|
3
|
+
This CLI helps you move a Next.js App Router app that uses `@uniformdev/next-app-router` to the developer preview of the SDK (`20.81.1-alpha.41.sha-adcf82dedd`). The app can use a stable version (20.82 to 20.85) or an earlier developer preview. The upgrade has two phases:
|
|
4
4
|
|
|
5
5
|
- **Phase 1: upgrade the SDK.** The minimum changes. After phase 1, the app works in lite mode: the browser chooses the personalization and test variants after hydration.
|
|
6
6
|
- **Phase 2: turn on edge mode.** The edge chooses the variants of each visitor, so the first paint is correct. The steps depend on the host of the app.
|
|
@@ -38,9 +38,9 @@ The guide uses the same safety checks as `migrate`. One difference: when a file
|
|
|
38
38
|
| Host | Status |
|
|
39
39
|
|---|---|
|
|
40
40
|
| Vercel | Available: `--edge vercel` |
|
|
41
|
-
| Cloudflare |
|
|
41
|
+
| Cloudflare | Available: `--edge cloudflare`. The app must run on Cloudflare Workers with vinext. |
|
|
42
42
|
| Netlify | Coming soon. Follow the docs for other hosts. |
|
|
43
|
-
| Akamai |
|
|
43
|
+
| Akamai | Available: `--edge akamai`. The CLI changes the app. You make the EdgeWorker. |
|
|
44
44
|
|
|
45
45
|
## Usage
|
|
46
46
|
|
|
@@ -111,14 +111,16 @@ The migration changes only code where the result has the same behavior:
|
|
|
111
111
|
| Change | When |
|
|
112
112
|
|---|---|
|
|
113
113
|
| Set each `@uniformdev` package to the target version | The npm registry has the package at that version. `@uniformdev/toolbar-react` and `@uniformdev/context-edge` have their own version lines, so they do not change. |
|
|
114
|
-
| Add `@vercel/functions` `^3.7.2` to `dependencies` | The app imports `@vercel/functions`, and `package.json` does not list it.
|
|
114
|
+
| Add `@vercel/functions` `^3.7.2` to `dependencies` | The app imports `@vercel/functions`, and `package.json` does not list it. The stable SDK installs it for you. |
|
|
115
115
|
| Add an override for `@uniformdev/context` | The target is a prerelease, and the app uses `@uniformdev/toolbar-react`. npm gets `overrides`, and yarn gets `resolutions`. For pnpm, the migration gives a manual step. |
|
|
116
116
|
| Remove the `pathPatternsWithVariations` option | Always. The option has no effect in the developer preview. |
|
|
117
117
|
| Remove `dataClient: new DefaultDataClient()` and the `dataClient={new DefaultDataClient()}` prop | Only for the default data client. A custom data client stays, and the migration lists it as a manual change. |
|
|
118
118
|
| Remove `middlewareRuntimeCache` and `experimental.disableSwrMiddlewareCache` | In the server configuration: `uniform.server.config.*`, or an object of the type `UniformServerConfig`. |
|
|
119
|
+
| Remove the `compositionMetadata` argument of `useInitUniformContext` | The argument is the `compositionMetadata` prop. The SDK now sends the composition metadata of the page composition to the Context. |
|
|
120
|
+
| Remove the `compositionMetadata` prop of a custom client context component | No other code in the component uses it. |
|
|
119
121
|
| Remove the import of a removed API | Only when no code uses it after the changes above. |
|
|
120
122
|
|
|
121
|
-
All other findings of `check` are manual changes. Examples are a custom data client, `result.code`, `route.compositionApiResponse` without a type check, old page codes and the Next.js version. The migration shows each one with its file and line.
|
|
123
|
+
All other findings of `check` are manual changes. Examples are a custom data client, `result.code`, `route.compositionApiResponse` without a type check, old page codes, the visitor data options of 20.85 and the Next.js version. The migration shows each one with its file and line.
|
|
122
124
|
|
|
123
125
|
### Phase 2 on Vercel (`--edge vercel`)
|
|
124
126
|
|
|
@@ -147,7 +149,43 @@ These cases are manual changes:
|
|
|
147
149
|
- A matcher or options that are not literals.
|
|
148
150
|
- An own `onRevalidateTags` function. Call `expireVercelRuntimeCacheTags(tags)` from it.
|
|
149
151
|
|
|
150
|
-
The CLI also reminds you to check the Uniform webhook. It cannot see
|
|
152
|
+
The CLI also reminds you to check the Uniform webhook, and to turn on Protection Bypass for Automation if the deployment uses Vercel Deployment Protection. It cannot see these settings.
|
|
153
|
+
|
|
154
|
+
### Phase 2 on Cloudflare (`--edge cloudflare`)
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
npx @uniformdev/next-app-router-upgrade migrate --edge cloudflare --dry-run
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Edge mode on Cloudflare uses `cfVinextUniformEdgeMiddleware` from `@uniformdev/next-app-router/cf-vinext`. It needs vinext on Cloudflare Workers. If `package.json` does not list `vinext`, the CLI makes no changes, and tells you to move the app to vinext first.
|
|
161
|
+
|
|
162
|
+
| Change | When |
|
|
163
|
+
|---|---|
|
|
164
|
+
| Replace `uniformMiddleware(...)` with `cfVinextUniformEdgeMiddleware(...)`, and import it from `@uniformdev/next-app-router/cf-vinext` | Each reference to `uniformMiddleware` is a call. |
|
|
165
|
+
| Add `missing: [{ type: "header", key: "x-uniform-edge-origin" }]` to each matcher | The same as on Vercel. |
|
|
166
|
+
| Add a config with the default matcher of the docs | The middleware file has no config. |
|
|
167
|
+
| Add `edgeMode: [true, false]` to `createUniformStaticParams` | The options do not have `edgeMode`. |
|
|
168
|
+
|
|
169
|
+
The CLI does not add a runtime, and it accepts `proxy.ts`. The preview route needs no change: its `revalidateTag` calls also expire the vinext data cache. The CLI reminds you that the Worker needs `@cloudflare/vite-plugin` and a vinext data cache, for example KV.
|
|
170
|
+
|
|
171
|
+
### Phase 2 on Akamai (`--edge akamai`)
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
npx @uniformdev/next-app-router-upgrade migrate --edge akamai --dry-run
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
On Akamai, an EdgeWorker with `akamaiUniformEdgeWorker` from `@uniformdev/next-app-router/akamai` is in front of the app. The worker rewrites the routes and chooses the variants. The app keeps its middleware: `uniformMiddleware` and `handleUniformRoute` let the requests of the worker through, and handle draft mode.
|
|
178
|
+
|
|
179
|
+
| Change | When |
|
|
180
|
+
|---|---|
|
|
181
|
+
| Add `edgeMode: [true, false]` to `createUniformStaticParams` | The options do not have `edgeMode`. |
|
|
182
|
+
|
|
183
|
+
These are manual changes:
|
|
184
|
+
|
|
185
|
+
- Make the EdgeWorker. The step goes away when a file in the app folder imports `@uniformdev/next-app-router/akamai`.
|
|
186
|
+
- A middleware that does not use `uniformMiddleware` or `handleUniformRoute`. It must let requests with the `x-uniform-edge-origin` header through.
|
|
187
|
+
|
|
188
|
+
The CLI also reminds you of the settings that it cannot see: the property variable `PMUSER_BYPASS_EW_RP_EVENT`, the external modules of the EdgeWorker bundle, the Akamai cache, and the Uniform webhook.
|
|
151
189
|
|
|
152
190
|
### Safety
|
|
153
191
|
|
|
@@ -176,7 +214,10 @@ Each of these fails the TypeScript build, or fails at runtime, with the develope
|
|
|
176
214
|
| `result-code` | `.code` on the result of `resolveRouteFromCode` or `resolvePlaygroundRoute`. |
|
|
177
215
|
| `old-page-code` | Hard-coded `/uniform/2~…` page codes. |
|
|
178
216
|
| `next-version` | Next.js lower than 16.0.7. |
|
|
179
|
-
| `
|
|
217
|
+
| `context-composition-metadata` | The `compositionMetadata` prop of a custom client context component, and a second argument of `useInitUniformContext`. The docs and the starter for 20.82 use this pattern. |
|
|
218
|
+
| `context-storage-options` | `cookieMaxBytes`, `pruneUnknownTests`, `pruneUnknownDimensions` and `pruneZeroScores`: in the `context` server configuration, as props of a custom client context component, or as options of `createClientUniformContext`. 20.85.0 added them. The developer preview does not have them. |
|
|
219
|
+
| `uniform-context-props` | `UniformContext` with a `result` prop or with children. This is the API of `20.81.1-alpha.25`. Now, render the compositions, then `<UniformContext />`, and set `pageComposition` on the primary `UniformResolvedComposition`. |
|
|
220
|
+
| `vercel-functions-transitive` | Imports of `@vercel/functions` when `package.json` does not list it. The stable SDK installs it for you. |
|
|
180
221
|
|
|
181
222
|
### Behavior changes (warnings)
|
|
182
223
|
|
|
@@ -195,7 +236,7 @@ Each of these fails the TypeScript build, or fails at runtime, with the develope
|
|
|
195
236
|
|
|
196
237
|
### Notes (info)
|
|
197
238
|
|
|
198
|
-
`cached-resolver`, `static-params`, `context-edge-dependency` and `redirect-status` tell you about changes that need no code change.
|
|
239
|
+
`cached-resolver`, `static-params`, `context-edge-dependency`, `playground-path` and `redirect-status` tell you about changes that need no code change. `playground-path` finds a `processPlaygroundPath` in `createPreviewGETRouteHandler`: the middleware now accepts the same option.
|
|
199
240
|
|
|
200
241
|
## Limits
|
|
201
242
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniformdev/next-app-router-upgrade",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Check and upgrade a Next.js App Router app to the developer preview of the Uniform SDK (@uniformdev/next-app-router), with edge mode.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"uniform",
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Finds the code of a custom client context component that the developer preview does not accept.
|
|
2
|
+
// The scan reports it, and the migration removes the parts that have the same behavior without them.
|
|
3
|
+
|
|
4
|
+
import ts from "typescript";
|
|
5
|
+
|
|
6
|
+
const CONTEXT_HOOK = "useInitUniformContext";
|
|
7
|
+
const CONTEXT_FACTORIES = new Set(["createClientUniformContext", "createUniformContext"]);
|
|
8
|
+
export const COMPOSITION_METADATA = "compositionMetadata";
|
|
9
|
+
|
|
10
|
+
// 20.85.0 added these options. The developer preview does not have them.
|
|
11
|
+
export const STABLE_CONTEXT_OPTIONS = {
|
|
12
|
+
cookieMaxBytes: "Without it, the visitor data cookie has no size limit.",
|
|
13
|
+
pruneUnknownTests: "Without it, the stored assignments of removed A/B tests stay.",
|
|
14
|
+
pruneUnknownDimensions: "Without it, the stored scores of removed dimensions stay.",
|
|
15
|
+
pruneZeroScores: "Without it, the stored dimensions with a score of zero stay.",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Returns { hookArguments, props, options } for the source file `sf`.
|
|
20
|
+
* `sdkLocals` maps local names to { imported } for the imports from the SDK packages.
|
|
21
|
+
* - `hookArguments`: { call, argument, removable } for each second argument of useInitUniformContext.
|
|
22
|
+
* `removable` is true when the argument is the compositionMetadata prop.
|
|
23
|
+
* - `props`: { element, pattern, fn, name } for each destructured prop that ClientContextComponentProps
|
|
24
|
+
* no longer has.
|
|
25
|
+
* - `options`: { property, name } for each 20.85 option of createClientUniformContext.
|
|
26
|
+
*/
|
|
27
|
+
export function findClientContextIssues(sf, sdkLocals) {
|
|
28
|
+
const hookArguments = [];
|
|
29
|
+
const props = [];
|
|
30
|
+
const options = [];
|
|
31
|
+
if (!sdkLocals.size) return { hookArguments, props, options };
|
|
32
|
+
|
|
33
|
+
const isSdkCall = (node, names) =>
|
|
34
|
+
ts.isCallExpression(node) && ts.isIdentifier(node.expression) && names.has(sdkLocals.get(node.expression.text)?.imported);
|
|
35
|
+
const hookNames = new Set([CONTEXT_HOOK]);
|
|
36
|
+
|
|
37
|
+
const visit = (node) => {
|
|
38
|
+
if (isSdkCall(node, hookNames) && node.arguments.length > 1) {
|
|
39
|
+
const argument = node.arguments[1];
|
|
40
|
+
const removable =
|
|
41
|
+
node.arguments.length === 2 &&
|
|
42
|
+
((ts.isIdentifier(argument) && argument.text === COMPOSITION_METADATA) ||
|
|
43
|
+
(ts.isPropertyAccessExpression(argument) && argument.name.text === COMPOSITION_METADATA));
|
|
44
|
+
hookArguments.push({ call: node, argument, removable });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (isSdkCall(node, CONTEXT_FACTORIES)) {
|
|
48
|
+
for (const argument of node.arguments) {
|
|
49
|
+
if (!ts.isObjectLiteralExpression(argument)) continue;
|
|
50
|
+
for (const property of argument.properties) {
|
|
51
|
+
const name = property.name && (ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) ? property.name.text : undefined;
|
|
52
|
+
if (name && STABLE_CONTEXT_OPTIONS[name]) options.push({ property, name });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (ts.isFunctionLike(node) && isClientContextComponent(node, sf, (call) => isSdkCall(call, hookNames))) {
|
|
58
|
+
const pattern = node.parameters[0].name;
|
|
59
|
+
for (const element of pattern.elements) {
|
|
60
|
+
if (element.dotDotDotToken) continue;
|
|
61
|
+
const key = element.propertyName ?? element.name;
|
|
62
|
+
const name = ts.isIdentifier(key) || ts.isStringLiteral(key) ? key.text : undefined;
|
|
63
|
+
if (name === COMPOSITION_METADATA || STABLE_CONTEXT_OPTIONS[name]) props.push({ element, pattern, fn: node, name });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
ts.forEachChild(node, visit);
|
|
68
|
+
};
|
|
69
|
+
visit(sf);
|
|
70
|
+
return { hookArguments, props, options };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// A function with a destructured first parameter that is a ClientContextComponent: by its type, by
|
|
74
|
+
// the type of its props, or because it calls useInitUniformContext.
|
|
75
|
+
function isClientContextComponent(fn, sf, isHookCall) {
|
|
76
|
+
const parameter = fn.parameters?.[0];
|
|
77
|
+
if (!parameter || !ts.isObjectBindingPattern(parameter.name)) return false;
|
|
78
|
+
if (parameter.type && /\bClientContextComponentProps\b/.test(parameter.type.getText(sf))) return true;
|
|
79
|
+
const declaration = fn.parent;
|
|
80
|
+
if (ts.isVariableDeclaration(declaration) && declaration.type && /\bClientContextComponent\b/.test(declaration.type.getText(sf))) return true;
|
|
81
|
+
let calls = false;
|
|
82
|
+
const visit = (node) => {
|
|
83
|
+
if (calls) return;
|
|
84
|
+
if (isHookCall(node)) calls = true;
|
|
85
|
+
else ts.forEachChild(node, visit);
|
|
86
|
+
};
|
|
87
|
+
if (fn.body) visit(fn.body);
|
|
88
|
+
return calls;
|
|
89
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Phase 2 on Akamai: an EdgeWorker with akamaiUniformEdgeWorker in front of the app. The worker
|
|
2
|
+
// rewrites the routes and chooses the variants. The app keeps its middleware for draft mode and for
|
|
3
|
+
// the requests that the worker does not handle.
|
|
4
|
+
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
|
|
8
|
+
import { listSourceFiles } from "../scan.js";
|
|
9
|
+
import { EDGE_HEADER, findMiddlewareFile, migrateRouteFiles, parse, SDK, sdkImports, WEBHOOK_REMINDER } from "./common.js";
|
|
10
|
+
|
|
11
|
+
const AKAMAI = `${SDK}/akamai`;
|
|
12
|
+
// These functions let the requests of the worker through: they have the edge origin header.
|
|
13
|
+
const PASS_THROUGH = new Set(["uniformMiddleware", "handleUniformRoute", "vercelUniformEdgeMiddleware", "cfVinextUniformEdgeMiddleware", "uniformEdgeMiddleware", "handleUniformEdgeRoute"]);
|
|
14
|
+
|
|
15
|
+
const REMINDERS = [
|
|
16
|
+
"In the Akamai property, add the user-defined variable PMUSER_BYPASS_EW_RP_EVENT with the initial value false. Without it, responseProvider gets each request.",
|
|
17
|
+
"In the bundler of the EdgeWorker, mark the EdgeWorkers modules cookies, create-response, crypto, encoding, http-request, log, streams and url-search-params as external.",
|
|
18
|
+
"Akamai caches the pages. Make sure that the Akamai cache follows the cache headers of the app, or that publishing in Uniform purges it.",
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Returns { changes, contents, manual, reminders } for phase 2 on Akamai.
|
|
23
|
+
* `base` maps absolute paths to the text after phase 1, so that the two phases make one plan.
|
|
24
|
+
*/
|
|
25
|
+
export function planAkamaiEdge(root, { base = new Map() } = {}) {
|
|
26
|
+
const read = (file) => base.get(file) ?? fs.readFileSync(file, "utf8");
|
|
27
|
+
const changes = [];
|
|
28
|
+
const contents = new Map();
|
|
29
|
+
const manual = [];
|
|
30
|
+
|
|
31
|
+
// Step 1: the app's middleware stays, and lets the requests of the worker through.
|
|
32
|
+
const middleware = findMiddlewareFile(root);
|
|
33
|
+
if (!middleware) {
|
|
34
|
+
manual.push("No middleware.ts was found. Add middleware.ts with uniformMiddleware: it handles draft mode and the requests that the EdgeWorker does not rewrite.");
|
|
35
|
+
} else {
|
|
36
|
+
const locals = sdkImports(parse(middleware, read(middleware)));
|
|
37
|
+
const passes = [...locals.values()].some((info) => PASS_THROUGH.has(info.imported));
|
|
38
|
+
if (!passes) {
|
|
39
|
+
manual.push(
|
|
40
|
+
`${path.relative(root, middleware)}: the middleware does not use uniformMiddleware or handleUniformRoute. Let requests with the ${EDGE_HEADER} header through (NextResponse.next()), because the EdgeWorker already rewrote them.`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Step 2: static params for both edge mode values.
|
|
46
|
+
const routes = migrateRouteFiles(root, read, { skip: middleware });
|
|
47
|
+
for (const { file, text, changes: fileChanges } of routes.files) {
|
|
48
|
+
if (text !== read(file)) contents.set(file, text);
|
|
49
|
+
changes.push(...fileChanges);
|
|
50
|
+
}
|
|
51
|
+
manual.push(...routes.manual);
|
|
52
|
+
|
|
53
|
+
// Step 3: the EdgeWorker. It is a separate bundle, so the migration does not make it.
|
|
54
|
+
const hasWorker = listSourceFiles(root).some((file) => read(file).includes(AKAMAI));
|
|
55
|
+
if (!hasWorker) {
|
|
56
|
+
manual.push(
|
|
57
|
+
`Add the Akamai EdgeWorker. In its main.js, export the handlers of akamaiUniformEdgeWorker from ${AKAMAI}: export const { onClientRequest, responseProvider, onClientResponse } = akamaiUniformEdgeWorker({ config, manifest }). Give it the server config of the app (uniform.server.config), the Context manifest, and the rewrite options of the middleware. In the worker, rewriteRequestPath gets { request, path, query }. If the EdgeWorker is in a different folder, you can ignore this step.`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { changes, contents, manual, reminders: [...REMINDERS, WEBHOOK_REMINDER] };
|
|
62
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Phase 2 on Cloudflare: turn on edge mode with cfVinextUniformEdgeMiddleware. The app must run on
|
|
2
|
+
// Cloudflare Workers with vinext.
|
|
3
|
+
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
|
|
7
|
+
import { migrateEdgeMiddleware, planEdge, SDK } from "./common.js";
|
|
8
|
+
|
|
9
|
+
const HOST = {
|
|
10
|
+
factory: "cfVinextUniformEdgeMiddleware",
|
|
11
|
+
module: `${SDK}/cf-vinext`,
|
|
12
|
+
// vinext accepts middleware.ts and proxy.ts, and has no edge runtime option.
|
|
13
|
+
runtime: false,
|
|
14
|
+
// The preview route expires the vinext data cache with revalidateTag. No extra call is necessary.
|
|
15
|
+
onRevalidateTags: undefined,
|
|
16
|
+
packages: [],
|
|
17
|
+
reminders: [
|
|
18
|
+
"cfVinextUniformEdgeMiddleware needs a Worker that @cloudflare/vite-plugin builds. It keeps the manifest and the route records in the vinext data cache, for example a KV namespace with kvDataAdapter(). Make sure that the Worker has a data cache.",
|
|
19
|
+
],
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Returns { changes, contents, manual, reminders } for phase 2 on Cloudflare.
|
|
24
|
+
* `base` maps absolute paths to the text after phase 1, so that the two phases make one plan.
|
|
25
|
+
*/
|
|
26
|
+
export function planCloudflareEdge(root, { base = new Map() } = {}) {
|
|
27
|
+
const pkgFile = path.join(root, "package.json");
|
|
28
|
+
const pkg = JSON.parse(base.get(pkgFile) ?? fs.readFileSync(pkgFile, "utf8"));
|
|
29
|
+
if (!pkg.dependencies?.vinext && !pkg.devDependencies?.vinext) {
|
|
30
|
+
// cfVinextUniformEdgeMiddleware imports vinext and cloudflare:workers, so the build fails without vinext.
|
|
31
|
+
return {
|
|
32
|
+
changes: [],
|
|
33
|
+
contents: new Map(),
|
|
34
|
+
manual: [
|
|
35
|
+
"Edge mode on Cloudflare needs vinext: package.json does not list it. Move the app to vinext on Cloudflare Workers first, and then run the migration again. On other runtimes, use uniformEdgeMiddleware from @uniformdev/next-app-router/edge, as in the docs.",
|
|
36
|
+
],
|
|
37
|
+
reminders: [],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return planEdge(root, { base, host: HOST });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function migrateMiddleware(root, file, text) {
|
|
44
|
+
return migrateEdgeMiddleware(root, file, text, HOST);
|
|
45
|
+
}
|