@unieojs/unio-nextjs-adapter 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 +46 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +8 -6
- package/dist/index.js.map +1 -1
- package/dist/on-build-complete.d.ts +2 -2
- package/dist/on-build-complete.js +8 -3
- package/dist/on-build-complete.js.map +1 -1
- package/dist/types.d.ts +131 -0
- package/dist/uboa.d.ts +2 -26
- package/dist/uboa.js +219 -30
- package/dist/uboa.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +36 -7
- package/src/on-build-complete.ts +17 -4
- package/src/types.ts +182 -0
- package/src/uboa.ts +287 -56
- package/dist/assert-ssg.d.ts +0 -5
- package/dist/assert-ssg.js +0 -30
- package/dist/assert-ssg.js.map +0 -1
- package/src/assert-ssg.ts +0 -35
package/src/types.ts
CHANGED
|
@@ -69,7 +69,11 @@ export interface BuildContext {
|
|
|
69
69
|
distDir: string;
|
|
70
70
|
/** Absolute project root */
|
|
71
71
|
projectDir: string;
|
|
72
|
+
/** Absolute repository root, when Next.js provides it */
|
|
73
|
+
repoRoot?: string;
|
|
72
74
|
config: NextConfigLike;
|
|
75
|
+
nextVersion?: string;
|
|
76
|
+
buildId?: string;
|
|
73
77
|
}
|
|
74
78
|
|
|
75
79
|
/**
|
|
@@ -91,3 +95,181 @@ export interface NextAdapter {
|
|
|
91
95
|
) => NextConfigLike | Promise<NextConfigLike>;
|
|
92
96
|
onBuildComplete?: (ctx: BuildContext) => void | Promise<void>;
|
|
93
97
|
}
|
|
98
|
+
|
|
99
|
+
export type CapabilityStatus =
|
|
100
|
+
| "supported"
|
|
101
|
+
| "partial"
|
|
102
|
+
| "degraded"
|
|
103
|
+
| "unsupported";
|
|
104
|
+
|
|
105
|
+
export interface UboaCapabilities {
|
|
106
|
+
static: CapabilityStatus;
|
|
107
|
+
routes: CapabilityStatus;
|
|
108
|
+
serverlessFunction: CapabilityStatus;
|
|
109
|
+
serverFunctionIntrospection: CapabilityStatus;
|
|
110
|
+
restRouteIntrospection: CapabilityStatus;
|
|
111
|
+
edgeMiddleware: CapabilityStatus;
|
|
112
|
+
isr: CapabilityStatus;
|
|
113
|
+
imageOptimization: CapabilityStatus;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
interface UboaRouteBase {
|
|
117
|
+
id: string;
|
|
118
|
+
match: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface UboaAssetsRoute extends UboaRouteBase {
|
|
122
|
+
type: "assets";
|
|
123
|
+
entry: string;
|
|
124
|
+
serverFunction?: never;
|
|
125
|
+
edgeFunction?: never;
|
|
126
|
+
methods?: never;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface UboaServerFunctionRoute extends UboaRouteBase {
|
|
130
|
+
type: "serverFunction";
|
|
131
|
+
serverFunction: string;
|
|
132
|
+
methods?: string[];
|
|
133
|
+
entry?: never;
|
|
134
|
+
edgeFunction?: never;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface UboaEdgeFunctionRoute extends UboaRouteBase {
|
|
138
|
+
type: "edgeFunction";
|
|
139
|
+
edgeFunction: string;
|
|
140
|
+
entry?: never;
|
|
141
|
+
serverFunction?: never;
|
|
142
|
+
methods?: never;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export type UboaRoute =
|
|
146
|
+
| UboaAssetsRoute
|
|
147
|
+
| UboaServerFunctionRoute
|
|
148
|
+
| UboaEdgeFunctionRoute;
|
|
149
|
+
|
|
150
|
+
interface UboaMiddlewareBase {
|
|
151
|
+
name: string;
|
|
152
|
+
match: string[];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface UboaEdgeMiddleware extends UboaMiddlewareBase {
|
|
156
|
+
runtime: "edge";
|
|
157
|
+
entry: string;
|
|
158
|
+
serverFunction?: never;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface UboaNodejsMiddleware extends UboaMiddlewareBase {
|
|
162
|
+
runtime: "nodejs";
|
|
163
|
+
serverFunction: string;
|
|
164
|
+
entry?: never;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export type UboaMiddleware =
|
|
168
|
+
| UboaEdgeMiddleware
|
|
169
|
+
| UboaNodejsMiddleware;
|
|
170
|
+
|
|
171
|
+
export type UboaArtifactResourceKind =
|
|
172
|
+
| "assets"
|
|
173
|
+
| "serverFunction"
|
|
174
|
+
| "edgeBundle"
|
|
175
|
+
| "apiSchema";
|
|
176
|
+
|
|
177
|
+
export interface UboaAssetsArtifact {
|
|
178
|
+
id: string;
|
|
179
|
+
resourceKind: "assets";
|
|
180
|
+
path: string;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface UboaServerFunctionArtifact {
|
|
184
|
+
id: string;
|
|
185
|
+
resourceKind: "serverFunction";
|
|
186
|
+
path: string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface UboaEdgeBundleArtifact {
|
|
190
|
+
id: string;
|
|
191
|
+
resourceKind: "edgeBundle";
|
|
192
|
+
path: string;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export interface UboaArtifactReference {
|
|
196
|
+
id: string;
|
|
197
|
+
resourceKind: UboaArtifactResourceKind;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export type UboaApiSchemaFormat = "oneapi";
|
|
201
|
+
|
|
202
|
+
export interface UboaApiSchemaArtifact {
|
|
203
|
+
id: string;
|
|
204
|
+
resourceKind: "apiSchema";
|
|
205
|
+
path: string;
|
|
206
|
+
schemaFormat: UboaApiSchemaFormat;
|
|
207
|
+
describes?: UboaArtifactReference;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export type UboaArtifact =
|
|
211
|
+
| UboaAssetsArtifact
|
|
212
|
+
| UboaServerFunctionArtifact
|
|
213
|
+
| UboaEdgeBundleArtifact
|
|
214
|
+
| UboaApiSchemaArtifact;
|
|
215
|
+
|
|
216
|
+
export interface UboaServerFunctionDescriptor {
|
|
217
|
+
name: string;
|
|
218
|
+
runtime: string;
|
|
219
|
+
entry: string;
|
|
220
|
+
handler: string;
|
|
221
|
+
memory: number;
|
|
222
|
+
maxDuration: number;
|
|
223
|
+
environment: Record<string, unknown>;
|
|
224
|
+
bindings: Record<string, unknown>;
|
|
225
|
+
framework: Record<string, unknown>;
|
|
226
|
+
source: Record<string, unknown>;
|
|
227
|
+
[key: string]: unknown;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export type NextRouteOutputGroup =
|
|
231
|
+
| "pages"
|
|
232
|
+
| "pagesApi"
|
|
233
|
+
| "appPages"
|
|
234
|
+
| "appRoutes"
|
|
235
|
+
| "middleware";
|
|
236
|
+
|
|
237
|
+
export interface GeneratedUboa {
|
|
238
|
+
outputDir: string;
|
|
239
|
+
routes: UboaRoute[];
|
|
240
|
+
middleware: UboaMiddleware[];
|
|
241
|
+
artifacts: UboaArtifact[];
|
|
242
|
+
capabilities: UboaCapabilities;
|
|
243
|
+
warnings: string[];
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export interface ExtendServerFunctionDescriptorContext {
|
|
247
|
+
descriptor: UboaServerFunctionDescriptor;
|
|
248
|
+
name: string;
|
|
249
|
+
route: RouteOutput;
|
|
250
|
+
routeGroup: NextRouteOutputGroup;
|
|
251
|
+
ctx: BuildContext;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export interface ExtendArtifactsContext {
|
|
255
|
+
artifacts: UboaArtifact[];
|
|
256
|
+
routes: UboaRoute[];
|
|
257
|
+
middleware: UboaMiddleware[];
|
|
258
|
+
ctx: BuildContext;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export interface AfterEmitContext {
|
|
262
|
+
outputDir: string;
|
|
263
|
+
generated: GeneratedUboa;
|
|
264
|
+
ctx: BuildContext;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export interface UnioNextjsAdapterOptions {
|
|
268
|
+
extendServerFunctionDescriptor?: (
|
|
269
|
+
context: ExtendServerFunctionDescriptorContext,
|
|
270
|
+
) => Record<string, unknown> | Promise<Record<string, unknown>>;
|
|
271
|
+
extendArtifacts?: (
|
|
272
|
+
context: ExtendArtifactsContext,
|
|
273
|
+
) => UboaArtifact[] | Promise<UboaArtifact[]>;
|
|
274
|
+
afterEmit?: (context: AfterEmitContext) => void | Promise<void>;
|
|
275
|
+
}
|
package/src/uboa.ts
CHANGED
|
@@ -3,45 +3,25 @@ import path from "node:path";
|
|
|
3
3
|
|
|
4
4
|
import type {
|
|
5
5
|
BuildContext,
|
|
6
|
+
GeneratedUboa,
|
|
7
|
+
NextRouteOutputGroup,
|
|
6
8
|
PrerenderOutput,
|
|
7
9
|
RouteOutput,
|
|
8
10
|
StaticFileOutput,
|
|
11
|
+
UboaArtifact,
|
|
12
|
+
UboaCapabilities,
|
|
13
|
+
UboaMiddleware,
|
|
14
|
+
UboaRoute,
|
|
15
|
+
UboaServerFunctionDescriptor,
|
|
16
|
+
UnioNextjsAdapterOptions,
|
|
9
17
|
} from "./types.js";
|
|
10
18
|
|
|
11
19
|
const ADAPTER_PACKAGE = "@unieojs/unio-nextjs-adapter";
|
|
12
20
|
const UBOA_VERSION = "1.0.0";
|
|
13
21
|
|
|
14
|
-
type CapabilityStatus = "supported" | "partial" | "degraded" | "unsupported";
|
|
15
|
-
|
|
16
|
-
interface UboaRoute {
|
|
17
|
-
id: string;
|
|
18
|
-
match: string;
|
|
19
|
-
type: "static" | "serverFunction";
|
|
20
|
-
entry?: string;
|
|
21
|
-
serverFunction?: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
interface UboaMiddleware {
|
|
25
|
-
name: string;
|
|
26
|
-
match: string[];
|
|
27
|
-
runtime: "serverless";
|
|
28
|
-
serverFunction?: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
interface UboaArtifact {
|
|
32
|
-
id: string;
|
|
33
|
-
resourceKind: "assets" | "serverFunction";
|
|
34
|
-
path: string;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
interface GeneratedUboa {
|
|
38
|
-
routes: UboaRoute[];
|
|
39
|
-
middleware: UboaMiddleware[];
|
|
40
|
-
artifacts: UboaArtifact[];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
22
|
type StaticLikeOutput = StaticFileOutput | PrerenderOutput;
|
|
44
23
|
type RouteOutputKey = "pages" | "pagesApi" | "appPages" | "appRoutes";
|
|
24
|
+
type DynamicServerOutput = Pick<GeneratedUboa, "routes" | "middleware" | "artifacts">;
|
|
45
25
|
|
|
46
26
|
const ROUTE_GROUPS: Array<[string, RouteOutputKey]> = [
|
|
47
27
|
["pages", "pages"],
|
|
@@ -109,6 +89,21 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
109
89
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
110
90
|
}
|
|
111
91
|
|
|
92
|
+
function isStringRecord(value: unknown): value is Record<string, string> {
|
|
93
|
+
return isRecord(value) &&
|
|
94
|
+
Object.values(value).every((entry) => typeof entry === "string");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function isSafeRelativePath(value: string): boolean {
|
|
98
|
+
if (!value) return false;
|
|
99
|
+
if (path.isAbsolute(value) || path.win32.isAbsolute(value)) return false;
|
|
100
|
+
return !value.split(/[\\/]+/).includes("..");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function buildId(ctx: BuildContext): string {
|
|
104
|
+
return ctx.buildId || process.env.BUILD_ID || process.env.TASK_ID || "local";
|
|
105
|
+
}
|
|
106
|
+
|
|
112
107
|
function routeConfig(route: RouteOutput): Record<string, unknown> {
|
|
113
108
|
return isRecord(route.config) ? route.config : {};
|
|
114
109
|
}
|
|
@@ -118,6 +113,14 @@ function routeMaxDuration(route: RouteOutput): number {
|
|
|
118
113
|
return typeof maxDuration === "number" ? maxDuration : 10;
|
|
119
114
|
}
|
|
120
115
|
|
|
116
|
+
function edgeRuntimeModulePath(route: RouteOutput): string {
|
|
117
|
+
const edgeRuntime = route.edgeRuntime;
|
|
118
|
+
if (isRecord(edgeRuntime) && typeof edgeRuntime.modulePath === "string") {
|
|
119
|
+
return edgeRuntime.modulePath;
|
|
120
|
+
}
|
|
121
|
+
return route.filePath;
|
|
122
|
+
}
|
|
123
|
+
|
|
121
124
|
function matcherSource(matcher: unknown): string | undefined {
|
|
122
125
|
if (typeof matcher === "string") return matcher;
|
|
123
126
|
if (!isRecord(matcher)) return undefined;
|
|
@@ -149,27 +152,99 @@ async function copyEntryFile(source: string, dest: string): Promise<void> {
|
|
|
149
152
|
await fs.copyFile(source, dest);
|
|
150
153
|
}
|
|
151
154
|
|
|
155
|
+
function routeSourceRoot(ctx: BuildContext): string {
|
|
156
|
+
return ctx.repoRoot ?? ctx.projectDir;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function relativeRouteFile(ctx: BuildContext, filePath: string): string {
|
|
160
|
+
const relativePath = path.relative(routeSourceRoot(ctx), filePath);
|
|
161
|
+
|
|
162
|
+
if (!relativePath || relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
|
|
163
|
+
return path.basename(filePath);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return toPosix(relativePath);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function assetTargetPath(
|
|
170
|
+
ctx: BuildContext,
|
|
171
|
+
assetKey: string,
|
|
172
|
+
assetPath: string,
|
|
173
|
+
): string {
|
|
174
|
+
return isSafeRelativePath(assetKey)
|
|
175
|
+
? toPosix(assetKey)
|
|
176
|
+
: relativeRouteFile(ctx, assetPath);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function copyRoutePayload(
|
|
180
|
+
functionDir: string,
|
|
181
|
+
ctx: BuildContext,
|
|
182
|
+
route: RouteOutput,
|
|
183
|
+
): Promise<string> {
|
|
184
|
+
const entry = relativeRouteFile(ctx, route.filePath);
|
|
185
|
+
await copyEntryFile(route.filePath, path.join(functionDir, entry));
|
|
186
|
+
|
|
187
|
+
for (const assetMap of [route.assets, route.wasmAssets]) {
|
|
188
|
+
if (!isStringRecord(assetMap)) continue;
|
|
189
|
+
|
|
190
|
+
for (const [assetKey, assetPath] of Object.entries(assetMap)) {
|
|
191
|
+
await copyEntryFile(
|
|
192
|
+
assetPath,
|
|
193
|
+
path.join(functionDir, assetTargetPath(ctx, assetKey, assetPath)),
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return entry;
|
|
199
|
+
}
|
|
200
|
+
|
|
152
201
|
async function emitServerFunction(
|
|
153
202
|
root: string,
|
|
203
|
+
ctx: BuildContext,
|
|
154
204
|
name: string,
|
|
155
205
|
route: RouteOutput,
|
|
206
|
+
routeGroup: NextRouteOutputGroup,
|
|
207
|
+
options: UnioNextjsAdapterOptions,
|
|
156
208
|
): Promise<void> {
|
|
157
209
|
const functionDir = path.join(root, "server-functions", name);
|
|
158
|
-
await
|
|
159
|
-
|
|
210
|
+
const entry = await copyRoutePayload(functionDir, ctx, route);
|
|
211
|
+
const descriptor: UboaServerFunctionDescriptor = {
|
|
160
212
|
name,
|
|
161
213
|
runtime: "nodejs",
|
|
162
|
-
entry
|
|
163
|
-
handler: "
|
|
214
|
+
entry,
|
|
215
|
+
handler: "handler",
|
|
164
216
|
memory: 512,
|
|
165
217
|
maxDuration: routeMaxDuration(route),
|
|
166
218
|
environment: {},
|
|
167
219
|
bindings: {},
|
|
220
|
+
framework: {
|
|
221
|
+
name: "next",
|
|
222
|
+
version: ctx.nextVersion ?? "unknown",
|
|
223
|
+
routeGroup,
|
|
224
|
+
outputType: route.type,
|
|
225
|
+
},
|
|
168
226
|
source: {
|
|
227
|
+
id: route.id,
|
|
169
228
|
type: route.type,
|
|
170
229
|
pathname: route.pathname,
|
|
230
|
+
filePath: relativeRouteFile(ctx, route.filePath),
|
|
171
231
|
},
|
|
172
|
-
}
|
|
232
|
+
};
|
|
233
|
+
const extendedDescriptor = options.extendServerFunctionDescriptor
|
|
234
|
+
? await options.extendServerFunctionDescriptor({
|
|
235
|
+
descriptor,
|
|
236
|
+
name,
|
|
237
|
+
route,
|
|
238
|
+
routeGroup,
|
|
239
|
+
ctx,
|
|
240
|
+
})
|
|
241
|
+
: descriptor;
|
|
242
|
+
|
|
243
|
+
if (!isRecord(extendedDescriptor)) {
|
|
244
|
+
throw new Error("extendServerFunctionDescriptor must return an object.");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
await writeJson(path.join(functionDir, "serverFunction.json"), extendedDescriptor);
|
|
173
248
|
}
|
|
174
249
|
|
|
175
250
|
function buildContentVersion(): string {
|
|
@@ -181,34 +256,48 @@ function buildContentVersion(): string {
|
|
|
181
256
|
return parts.length > 0 ? parts.join(", ") : "local";
|
|
182
257
|
}
|
|
183
258
|
|
|
184
|
-
function buildCapabilities(ctx: BuildContext):
|
|
259
|
+
function buildCapabilities(ctx: BuildContext): UboaCapabilities {
|
|
185
260
|
const { outputs } = ctx;
|
|
186
261
|
const nodeRoutes = ROUTE_GROUPS.some(([, key]) =>
|
|
187
262
|
outputs[key].some((route) => route.runtime === "nodejs"),
|
|
188
263
|
);
|
|
189
264
|
const nodeMiddleware = outputs.middleware?.runtime === "nodejs";
|
|
265
|
+
const edgeMiddleware = outputs.middleware?.runtime === "edge";
|
|
266
|
+
const hasServerFunction = nodeRoutes || nodeMiddleware;
|
|
190
267
|
|
|
191
268
|
return {
|
|
192
269
|
static: outputs.staticFiles.length > 0 || outputs.prerenders.length > 0 ?
|
|
193
270
|
"supported" : "unsupported",
|
|
194
271
|
routes: "supported",
|
|
195
|
-
serverlessFunction:
|
|
196
|
-
|
|
272
|
+
serverlessFunction: hasServerFunction ? "supported" : "unsupported",
|
|
273
|
+
serverFunctionIntrospection: hasServerFunction ? "supported" : "unsupported",
|
|
274
|
+
restRouteIntrospection: nodeRoutes ? "supported" : "unsupported",
|
|
275
|
+
edgeMiddleware: edgeMiddleware ? "supported" : "unsupported",
|
|
197
276
|
isr: outputs.prerenders.length > 0 ? "degraded" : "unsupported",
|
|
198
277
|
imageOptimization: "unsupported",
|
|
199
278
|
};
|
|
200
279
|
}
|
|
201
280
|
|
|
281
|
+
function buildWarnings(ctx: BuildContext): string[] {
|
|
282
|
+
return ctx.outputs.prerenders.length > 0
|
|
283
|
+
? ["Next.js prerenders are emitted as static assets; ISR metadata is degraded."]
|
|
284
|
+
: [];
|
|
285
|
+
}
|
|
286
|
+
|
|
202
287
|
function assertSupportedOutputs(ctx: BuildContext): void {
|
|
203
288
|
const unsupported: string[] = [];
|
|
204
289
|
|
|
205
|
-
if (
|
|
290
|
+
if (
|
|
291
|
+
ctx.outputs.middleware &&
|
|
292
|
+
ctx.outputs.middleware.runtime !== "nodejs" &&
|
|
293
|
+
ctx.outputs.middleware.runtime !== "edge"
|
|
294
|
+
) {
|
|
206
295
|
unsupported.push(`middleware (${ctx.outputs.middleware.runtime})`);
|
|
207
296
|
}
|
|
208
297
|
|
|
209
298
|
for (const [, key] of ROUTE_GROUPS) {
|
|
210
299
|
for (const route of ctx.outputs[key]) {
|
|
211
|
-
if (route.runtime !== "nodejs") {
|
|
300
|
+
if (route.runtime !== "nodejs" && route.runtime !== "edge") {
|
|
212
301
|
unsupported.push(`${key}:${route.pathname || route.id} (${route.runtime})`);
|
|
213
302
|
}
|
|
214
303
|
}
|
|
@@ -219,26 +308,41 @@ function assertSupportedOutputs(ctx: BuildContext): void {
|
|
|
219
308
|
throw new Error(
|
|
220
309
|
"[unio-nextjs-adapter] Unsupported Next.js outputs: " +
|
|
221
310
|
`${unsupported.join(", ")}. ` +
|
|
222
|
-
"This adapter currently supports static assets
|
|
311
|
+
"This adapter currently supports static assets, nodejs serverless functions, and edge functions only.",
|
|
223
312
|
);
|
|
224
313
|
}
|
|
225
314
|
|
|
226
315
|
async function emitCoreManifest(
|
|
227
316
|
root: string,
|
|
228
317
|
ctx: BuildContext,
|
|
318
|
+
capabilities: UboaCapabilities,
|
|
319
|
+
warnings: string[],
|
|
229
320
|
): Promise<void> {
|
|
230
321
|
await writeJson(path.join(root, "unio.json"), {
|
|
231
322
|
version: UBOA_VERSION,
|
|
232
323
|
framework: {
|
|
233
324
|
name: "next",
|
|
234
|
-
version: "unknown",
|
|
325
|
+
version: ctx.nextVersion ?? "unknown",
|
|
235
326
|
adapter: ADAPTER_PACKAGE,
|
|
236
327
|
},
|
|
237
328
|
build: {
|
|
238
329
|
contentVersion: buildContentVersion(),
|
|
239
|
-
buildId:
|
|
330
|
+
buildId: buildId(ctx),
|
|
240
331
|
},
|
|
241
|
-
capabilities
|
|
332
|
+
capabilities,
|
|
333
|
+
warnings,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async function emitFeaturesManifest(root: string): Promise<void> {
|
|
338
|
+
await writeJson(path.join(root, "features.json"), { features: [] });
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async function emitObservabilityManifest(root: string, ctx: BuildContext): Promise<void> {
|
|
342
|
+
await writeJson(path.join(root, "observability.json"), {
|
|
343
|
+
adapter: ADAPTER_PACKAGE,
|
|
344
|
+
framework: "next",
|
|
345
|
+
buildId: buildId(ctx),
|
|
242
346
|
});
|
|
243
347
|
}
|
|
244
348
|
|
|
@@ -257,7 +361,7 @@ async function emitStaticFiles(
|
|
|
257
361
|
routes.push({
|
|
258
362
|
id: `static-${slugify(file.pathname || file.id, "index")}`,
|
|
259
363
|
match: normalizeStaticMatch(file),
|
|
260
|
-
type: "
|
|
364
|
+
type: "assets",
|
|
261
365
|
entry: staticEntryFor(file),
|
|
262
366
|
});
|
|
263
367
|
}
|
|
@@ -265,9 +369,19 @@ async function emitStaticFiles(
|
|
|
265
369
|
return routes;
|
|
266
370
|
}
|
|
267
371
|
|
|
372
|
+
function sourceForRoute(ctx: BuildContext, route: RouteOutput): Record<string, unknown> {
|
|
373
|
+
return {
|
|
374
|
+
id: route.id,
|
|
375
|
+
type: route.type,
|
|
376
|
+
pathname: route.pathname,
|
|
377
|
+
filePath: relativeRouteFile(ctx, route.filePath),
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
268
381
|
async function emitRouteOutputs(
|
|
269
382
|
root: string,
|
|
270
383
|
ctx: BuildContext,
|
|
384
|
+
options: UnioNextjsAdapterOptions,
|
|
271
385
|
): Promise<Pick<GeneratedUboa, "routes" | "artifacts">> {
|
|
272
386
|
const routes: UboaRoute[] = [];
|
|
273
387
|
const artifacts: UboaArtifact[] = [];
|
|
@@ -276,7 +390,23 @@ async function emitRouteOutputs(
|
|
|
276
390
|
for (const route of ctx.outputs[key]) {
|
|
277
391
|
const name = routeFunctionName(group, route);
|
|
278
392
|
|
|
279
|
-
|
|
393
|
+
if (route.runtime === "edge") {
|
|
394
|
+
await emitEdgeBundle(root, ctx, name, route, key);
|
|
395
|
+
routes.push({
|
|
396
|
+
id: `edge-${name}`,
|
|
397
|
+
match: routeMatch(route),
|
|
398
|
+
type: "edgeFunction",
|
|
399
|
+
edgeFunction: name,
|
|
400
|
+
});
|
|
401
|
+
artifacts.push({
|
|
402
|
+
id: name,
|
|
403
|
+
resourceKind: "edgeBundle",
|
|
404
|
+
path: `edge/${name}`,
|
|
405
|
+
});
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
await emitServerFunction(root, ctx, name, route, key, options);
|
|
280
410
|
routes.push({
|
|
281
411
|
id: `server-${name}`,
|
|
282
412
|
match: routeMatch(route),
|
|
@@ -294,21 +424,71 @@ async function emitRouteOutputs(
|
|
|
294
424
|
return { routes, artifacts };
|
|
295
425
|
}
|
|
296
426
|
|
|
427
|
+
async function emitEdgeBundle(
|
|
428
|
+
root: string,
|
|
429
|
+
ctx: BuildContext,
|
|
430
|
+
name: string,
|
|
431
|
+
route: RouteOutput,
|
|
432
|
+
routeGroup: NextRouteOutputGroup,
|
|
433
|
+
): Promise<void> {
|
|
434
|
+
const entry = `edge/${name}/index.js`;
|
|
435
|
+
await copyEntryFile(edgeRuntimeModulePath(route), path.join(root, entry));
|
|
436
|
+
await writeJson(path.join(root, "edge", name, "edge.json"), {
|
|
437
|
+
name,
|
|
438
|
+
runtime: "edge",
|
|
439
|
+
entry: "index.js",
|
|
440
|
+
handlerProtocol: "fetch",
|
|
441
|
+
framework: {
|
|
442
|
+
name: "next",
|
|
443
|
+
version: ctx.nextVersion ?? "unknown",
|
|
444
|
+
routeGroup,
|
|
445
|
+
outputType: route.type,
|
|
446
|
+
},
|
|
447
|
+
source: sourceForRoute(ctx, route),
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
297
451
|
async function emitMiddleware(
|
|
298
452
|
root: string,
|
|
453
|
+
ctx: BuildContext,
|
|
299
454
|
middleware: RouteOutput | undefined,
|
|
455
|
+
options: UnioNextjsAdapterOptions,
|
|
300
456
|
): Promise<Pick<GeneratedUboa, "middleware" | "artifacts">> {
|
|
301
457
|
if (!middleware) return { middleware: [], artifacts: [] };
|
|
302
458
|
|
|
303
459
|
const name = "middleware";
|
|
304
|
-
|
|
460
|
+
|
|
461
|
+
if (middleware.runtime === "edge") {
|
|
462
|
+
const entry = `edge/${name}/index.js`;
|
|
463
|
+
await emitEdgeBundle(root, ctx, name, middleware, "middleware");
|
|
464
|
+
|
|
465
|
+
return {
|
|
466
|
+
middleware: [
|
|
467
|
+
{
|
|
468
|
+
name,
|
|
469
|
+
match: middlewareMatchers(middleware),
|
|
470
|
+
runtime: "edge",
|
|
471
|
+
entry,
|
|
472
|
+
},
|
|
473
|
+
],
|
|
474
|
+
artifacts: [
|
|
475
|
+
{
|
|
476
|
+
id: name,
|
|
477
|
+
resourceKind: "edgeBundle",
|
|
478
|
+
path: `edge/${name}`,
|
|
479
|
+
},
|
|
480
|
+
],
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
await emitServerFunction(root, ctx, name, middleware, "middleware", options);
|
|
305
485
|
|
|
306
486
|
return {
|
|
307
487
|
middleware: [
|
|
308
488
|
{
|
|
309
489
|
name,
|
|
310
490
|
match: middlewareMatchers(middleware),
|
|
311
|
-
runtime: "
|
|
491
|
+
runtime: "nodejs",
|
|
312
492
|
serverFunction: name,
|
|
313
493
|
},
|
|
314
494
|
],
|
|
@@ -322,7 +502,25 @@ async function emitMiddleware(
|
|
|
322
502
|
};
|
|
323
503
|
}
|
|
324
504
|
|
|
325
|
-
|
|
505
|
+
async function emitDynamicServerOutput(
|
|
506
|
+
root: string,
|
|
507
|
+
ctx: BuildContext,
|
|
508
|
+
options: UnioNextjsAdapterOptions,
|
|
509
|
+
): Promise<DynamicServerOutput> {
|
|
510
|
+
const routeOutput = await emitRouteOutputs(root, ctx, options);
|
|
511
|
+
const middlewareOutput = await emitMiddleware(root, ctx, ctx.outputs.middleware, options);
|
|
512
|
+
|
|
513
|
+
return {
|
|
514
|
+
routes: routeOutput.routes,
|
|
515
|
+
middleware: middlewareOutput.middleware,
|
|
516
|
+
artifacts: [...routeOutput.artifacts, ...middlewareOutput.artifacts],
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
export async function emitUboaOutput(
|
|
521
|
+
ctx: BuildContext,
|
|
522
|
+
options: UnioNextjsAdapterOptions = {},
|
|
523
|
+
): Promise<GeneratedUboa> {
|
|
326
524
|
assertSupportedOutputs(ctx);
|
|
327
525
|
|
|
328
526
|
const root = path.join(ctx.projectDir, ".unio", "output");
|
|
@@ -331,10 +529,9 @@ export async function emitUboaOutput(ctx: BuildContext): Promise<GeneratedUboa>
|
|
|
331
529
|
|
|
332
530
|
const staticFiles = [...ctx.outputs.staticFiles, ...ctx.outputs.prerenders];
|
|
333
531
|
const staticRoutes = await emitStaticFiles(root, staticFiles);
|
|
334
|
-
const
|
|
335
|
-
const middlewareOutput = await emitMiddleware(root, ctx.outputs.middleware);
|
|
532
|
+
const dynamicOutput = await emitDynamicServerOutput(root, ctx, options);
|
|
336
533
|
|
|
337
|
-
|
|
534
|
+
let artifacts: UboaArtifact[] = [];
|
|
338
535
|
if (staticFiles.length > 0) {
|
|
339
536
|
artifacts.push({
|
|
340
537
|
id: "static",
|
|
@@ -342,15 +539,49 @@ export async function emitUboaOutput(ctx: BuildContext): Promise<GeneratedUboa>
|
|
|
342
539
|
path: "static",
|
|
343
540
|
});
|
|
344
541
|
}
|
|
345
|
-
artifacts.push(...
|
|
542
|
+
artifacts.push(...dynamicOutput.artifacts);
|
|
543
|
+
|
|
544
|
+
const routes = [...staticRoutes, ...dynamicOutput.routes];
|
|
545
|
+
const middleware = dynamicOutput.middleware;
|
|
546
|
+
const capabilities = buildCapabilities(ctx);
|
|
547
|
+
const warnings = buildWarnings(ctx);
|
|
548
|
+
|
|
549
|
+
if (options.extendArtifacts) {
|
|
550
|
+
const extendedArtifacts = await options.extendArtifacts({
|
|
551
|
+
artifacts,
|
|
552
|
+
routes,
|
|
553
|
+
middleware,
|
|
554
|
+
ctx,
|
|
555
|
+
});
|
|
346
556
|
|
|
347
|
-
|
|
348
|
-
|
|
557
|
+
if (!Array.isArray(extendedArtifacts)) {
|
|
558
|
+
throw new Error("extendArtifacts must return an array.");
|
|
559
|
+
}
|
|
349
560
|
|
|
350
|
-
|
|
561
|
+
artifacts = extendedArtifacts;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const generated: GeneratedUboa = {
|
|
565
|
+
outputDir: root,
|
|
566
|
+
routes,
|
|
567
|
+
middleware,
|
|
568
|
+
artifacts,
|
|
569
|
+
capabilities,
|
|
570
|
+
warnings,
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
await emitCoreManifest(root, ctx, capabilities, warnings);
|
|
351
574
|
await writeJson(path.join(root, "routes.json"), routes);
|
|
352
575
|
await writeJson(path.join(root, "middleware.json"), middleware);
|
|
353
576
|
await writeJson(path.join(root, "artifacts.json"), { artifacts });
|
|
577
|
+
await emitFeaturesManifest(root);
|
|
578
|
+
await emitObservabilityManifest(root, ctx);
|
|
579
|
+
|
|
580
|
+
await options.afterEmit?.({
|
|
581
|
+
outputDir: root,
|
|
582
|
+
generated,
|
|
583
|
+
ctx,
|
|
584
|
+
});
|
|
354
585
|
|
|
355
|
-
return
|
|
586
|
+
return generated;
|
|
356
587
|
}
|