@unieojs/unio-nextjs-adapter 0.1.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 +22 -0
- package/dist/assert-ssg.d.ts +5 -0
- package/dist/assert-ssg.js +30 -0
- package/dist/assert-ssg.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/modify-config.d.ts +8 -0
- package/dist/modify-config.js +12 -0
- package/dist/modify-config.js.map +1 -0
- package/dist/on-build-complete.d.ts +2 -0
- package/dist/on-build-complete.js +51 -0
- package/dist/on-build-complete.js.map +1 -0
- package/dist/types.d.ts +75 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/dist/uboa.d.ts +26 -0
- package/dist/uboa.js +259 -0
- package/dist/uboa.js.map +1 -0
- package/package.json +44 -0
- package/src/assert-ssg.ts +35 -0
- package/src/index.ts +36 -0
- package/src/modify-config.ts +19 -0
- package/src/on-build-complete.ts +62 -0
- package/src/types.ts +93 -0
- package/src/uboa.ts +356 -0
package/README.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# @unieojs/unio-nextjs-adapter
|
|
2
|
+
|
|
3
|
+
Next.js adapter for emitting Unio Build Output API (`uboa`) manifests.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i @unieojs/unio-nextjs-adapter --save
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
After a Next.js build completes, the adapter writes `.unio/output` with
|
|
12
|
+
`unio.json`, `routes.json`, `middleware.json`, `artifacts.json`, static assets,
|
|
13
|
+
and server function descriptors.
|
|
14
|
+
|
|
15
|
+
Current support:
|
|
16
|
+
|
|
17
|
+
- Static files and prerenders are copied into `.unio/output/static/`.
|
|
18
|
+
- Node.js routes become `serverFunction` descriptors under
|
|
19
|
+
`.unio/output/server-functions/`.
|
|
20
|
+
- Node.js middleware is represented in `middleware.json` and backed by a
|
|
21
|
+
`serverFunction`.
|
|
22
|
+
- Edge middleware and edge routes fail fast until Unio support lands.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate that a Next.js output set is pure static export.
|
|
3
|
+
*/
|
|
4
|
+
export function assertSsgOnly(outputs) {
|
|
5
|
+
const violations = [];
|
|
6
|
+
if (outputs.pages?.length) {
|
|
7
|
+
violations.push(`pages (${outputs.pages.length})`);
|
|
8
|
+
}
|
|
9
|
+
if (outputs.pagesApi?.length) {
|
|
10
|
+
violations.push(`pagesApi (${outputs.pagesApi.length})`);
|
|
11
|
+
}
|
|
12
|
+
if (outputs.appPages?.length) {
|
|
13
|
+
violations.push(`appPages (${outputs.appPages.length})`);
|
|
14
|
+
}
|
|
15
|
+
if (outputs.appRoutes?.length) {
|
|
16
|
+
violations.push(`appRoutes (${outputs.appRoutes.length})`);
|
|
17
|
+
}
|
|
18
|
+
if (outputs.prerenders?.length) {
|
|
19
|
+
violations.push(`prerenders (${outputs.prerenders.length})`);
|
|
20
|
+
}
|
|
21
|
+
if (outputs.middleware) {
|
|
22
|
+
violations.push(`middleware (${outputs.middleware.runtime})`);
|
|
23
|
+
}
|
|
24
|
+
if (violations.length === 0)
|
|
25
|
+
return;
|
|
26
|
+
throw new Error("[unio-nextjs-adapter] Only SSG is supported in this version. " +
|
|
27
|
+
`Encountered non-SSG outputs: ${violations.join(", ")}. ` +
|
|
28
|
+
"Set `output: 'export'` in next.config.ts, or wait for SSR support in a future release.");
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=assert-ssg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assert-ssg.js","sourceRoot":"","sources":["../src/assert-ssg.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,OAAgB;IAC5C,MAAM,UAAU,GAAa,EAAE,CAAC;IAEhC,IAAI,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC;QAC1B,UAAU,CAAC,IAAI,CAAC,UAAU,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IACrD,CAAC;IACD,IAAI,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;QAC7B,UAAU,CAAC,IAAI,CAAC,aAAa,OAAO,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;IAC3D,CAAC;IACD,IAAI,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;QAC7B,UAAU,CAAC,IAAI,CAAC,aAAa,OAAO,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;IAC3D,CAAC;IACD,IAAI,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC;QAC9B,UAAU,CAAC,IAAI,CAAC,cAAc,OAAO,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;IAC7D,CAAC;IACD,IAAI,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC;QAC/B,UAAU,CAAC,IAAI,CAAC,eAAe,OAAO,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC;IAC/D,CAAC;IACD,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;QACvB,UAAU,CAAC,IAAI,CAAC,eAAe,OAAO,CAAC,UAAU,CAAC,OAAO,GAAG,CAAC,CAAC;IAChE,CAAC;IAED,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAEpC,MAAM,IAAI,KAAK,CACb,+DAA+D;QAC7D,gCAAgC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;QACzD,wFAAwF,CAC3F,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { NextAdapter } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Adapter name. Exported at top level so Next.js can read it when loading
|
|
4
|
+
* via the CJS entry: its `interopDefault(mod) = mod.default` returns the
|
|
5
|
+
* whole CJS `exports` object, so `name` must live there, not only nested
|
|
6
|
+
* inside the default export.
|
|
7
|
+
*/
|
|
8
|
+
export declare const name = "unio-nextjs-adapter";
|
|
9
|
+
export declare const adapter: NextAdapter;
|
|
10
|
+
export default adapter;
|
|
11
|
+
export { modifyConfig } from "./modify-config.js";
|
|
12
|
+
export { onBuildComplete } from "./on-build-complete.js";
|
|
13
|
+
export { emitUboaOutput } from "./uboa.js";
|
|
14
|
+
export { assertSsgOnly } from "./assert-ssg.js";
|
|
15
|
+
export type { BuildContext, ModifyConfigContext, NextAdapter, NextConfigLike, NextPhase, Outputs, PrerenderOutput, RouteOutput, RoutingInfo, StaticFileOutput, } from "./types.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { modifyConfig } from "./modify-config.js";
|
|
2
|
+
import { onBuildComplete } from "./on-build-complete.js";
|
|
3
|
+
/**
|
|
4
|
+
* Adapter name. Exported at top level so Next.js can read it when loading
|
|
5
|
+
* via the CJS entry: its `interopDefault(mod) = mod.default` returns the
|
|
6
|
+
* whole CJS `exports` object, so `name` must live there, not only nested
|
|
7
|
+
* inside the default export.
|
|
8
|
+
*/
|
|
9
|
+
export const name = "unio-nextjs-adapter";
|
|
10
|
+
export const adapter = {
|
|
11
|
+
name,
|
|
12
|
+
modifyConfig,
|
|
13
|
+
onBuildComplete,
|
|
14
|
+
};
|
|
15
|
+
export default adapter;
|
|
16
|
+
export { modifyConfig } from "./modify-config.js";
|
|
17
|
+
export { onBuildComplete } from "./on-build-complete.js";
|
|
18
|
+
export { emitUboaOutput } from "./uboa.js";
|
|
19
|
+
export { assertSsgOnly } from "./assert-ssg.js";
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAGzD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,qBAAqB,CAAC;AAE1C,MAAM,CAAC,MAAM,OAAO,GAAgB;IAClC,IAAI;IACJ,YAAY;IACZ,eAAe;CAChB,CAAC;AAEF,eAAe,OAAO,CAAC;AAEvB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ModifyConfigContext, NextConfigLike } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Pass the config through unchanged.
|
|
4
|
+
*
|
|
5
|
+
* The adapter intentionally does not force `output: "standalone"`; it packages
|
|
6
|
+
* the outputs passed to `onBuildComplete`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function modifyConfig(config: NextConfigLike, ctx: ModifyConfigContext): NextConfigLike;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const LOG_PREFIX = "[unio-nextjs-adapter]";
|
|
2
|
+
/**
|
|
3
|
+
* Pass the config through unchanged.
|
|
4
|
+
*
|
|
5
|
+
* The adapter intentionally does not force `output: "standalone"`; it packages
|
|
6
|
+
* the outputs passed to `onBuildComplete`.
|
|
7
|
+
*/
|
|
8
|
+
export function modifyConfig(config, ctx) {
|
|
9
|
+
console.log(`${LOG_PREFIX} modifyConfig phase=${ctx.phase} nextVersion=${ctx.nextVersion}`);
|
|
10
|
+
return config;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=modify-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modify-config.js","sourceRoot":"","sources":["../src/modify-config.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,GAAG,uBAAuB,CAAC;AAE3C;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAC1B,MAAsB,EACtB,GAAwB;IAExB,OAAO,CAAC,GAAG,CACT,GAAG,UAAU,uBAAuB,GAAG,CAAC,KAAK,gBAAgB,GAAG,CAAC,WAAW,EAAE,CAC/E,CAAC;IACF,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { emitUboaOutput } from "./uboa.js";
|
|
4
|
+
const LOG_PREFIX = "[unio-nextjs-adapter]";
|
|
5
|
+
/**
|
|
6
|
+
* Delete an intermediate build dir (typically `out/`) that Next wrote
|
|
7
|
+
* build output into before we copied files into `.unio/output`.
|
|
8
|
+
*
|
|
9
|
+
* Guards:
|
|
10
|
+
* - only removes if the dir exists
|
|
11
|
+
* - refuses to remove the project root or the uboa output dir itself
|
|
12
|
+
* - refuses to remove a dir that contains the uboa output dir
|
|
13
|
+
*/
|
|
14
|
+
async function cleanIntermediate(intermediateDir, protectedDirs) {
|
|
15
|
+
const target = path.resolve(intermediateDir);
|
|
16
|
+
for (const p of protectedDirs) {
|
|
17
|
+
const protectedAbs = path.resolve(p);
|
|
18
|
+
if (target === protectedAbs)
|
|
19
|
+
return false;
|
|
20
|
+
const rel = path.relative(target, protectedAbs);
|
|
21
|
+
const isInside = rel !== "" && !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
22
|
+
if (isInside)
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
await fs.rm(target, { recursive: true, force: true });
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export async function onBuildComplete(ctx) {
|
|
34
|
+
const { projectDir, distDir } = ctx;
|
|
35
|
+
const uboa = await emitUboaOutput(ctx);
|
|
36
|
+
const uboaOutputDir = path.resolve(projectDir, ".unio", "output");
|
|
37
|
+
const intermediates = new Set();
|
|
38
|
+
intermediates.add(path.resolve(projectDir, "out"));
|
|
39
|
+
if (distDir)
|
|
40
|
+
intermediates.add(path.resolve(distDir));
|
|
41
|
+
for (const dir of intermediates) {
|
|
42
|
+
const cleaned = await cleanIntermediate(dir, [projectDir, uboaOutputDir]);
|
|
43
|
+
if (cleaned) {
|
|
44
|
+
console.log(`${LOG_PREFIX} cleaned intermediate ${path.relative(projectDir, dir) || dir}/`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
console.log(`${LOG_PREFIX} ✔ .unio/output ready: ` +
|
|
48
|
+
`${uboa.routes.length} routes + ${uboa.middleware.length} middleware + ` +
|
|
49
|
+
`${uboa.artifacts.length} artifacts`);
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=on-build-complete.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"on-build-complete.js","sourceRoot":"","sources":["../src/on-build-complete.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAG3C,MAAM,UAAU,GAAG,uBAAuB,CAAC;AAE3C;;;;;;;;GAQG;AACH,KAAK,UAAU,iBAAiB,CAC9B,eAAuB,EACvB,aAAuB;IAEvB,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IAE7C,KAAK,MAAM,CAAC,IAAI,aAAa,EAAE,CAAC;QAC9B,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACrC,IAAI,MAAM,KAAK,YAAY;YAAE,OAAO,KAAK,CAAC;QAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;QAChD,MAAM,QAAQ,GAAG,GAAG,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QAC9E,IAAI,QAAQ;YAAE,OAAO,KAAK,CAAC;IAC7B,CAAC;IAED,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAiB;IACrD,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,GAAG,CAAC;IAEpC,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,GAAG,CAAC,CAAC;IACvC,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IAElE,MAAM,aAAa,GAAG,IAAI,GAAG,EAAU,CAAC;IACxC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC;IACnD,IAAI,OAAO;QAAE,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;IAEtD,KAAK,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;QAChC,MAAM,OAAO,GAAG,MAAM,iBAAiB,CAAC,GAAG,EAAE,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC,CAAC;QAC1E,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,CAAC,GAAG,CAAC,GAAG,UAAU,2BAA2B,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC;QAChG,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,CACT,GAAG,UAAU,yBAAyB;QACpC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,aAAa,IAAI,CAAC,UAAU,CAAC,MAAM,gBAAgB;QACxE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,YAAY,CACvC,CAAC;AACJ,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next.js Adapter API surface — minimal subset used by this adapter.
|
|
3
|
+
*
|
|
4
|
+
* Not imported from `next` directly to keep the package free of a hard
|
|
5
|
+
* `next` dependency; the runtime contract is enforced by Next itself
|
|
6
|
+
* when it loads the adapter via `NEXT_ADAPTER_PATH` / `adapterPath`.
|
|
7
|
+
*
|
|
8
|
+
* Refs:
|
|
9
|
+
* https://nextjs.org/docs/app/api-reference/adapters/api-reference
|
|
10
|
+
* https://nextjs.org/docs/app/api-reference/adapters/output-types
|
|
11
|
+
*/
|
|
12
|
+
export type NextPhase = "phase-development-server" | "phase-production-server" | "phase-production-build" | "phase-export" | "phase-test";
|
|
13
|
+
export interface NextConfigLike {
|
|
14
|
+
output?: "export" | "standalone" | undefined;
|
|
15
|
+
distDir?: string;
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
export interface StaticFileOutput {
|
|
19
|
+
/** e.g. `/index.html`, `/_not-found.html`, `/_next/static/chunks/xxx.js` — carries extension, maps to filesystem */
|
|
20
|
+
id: string;
|
|
21
|
+
/** URL form (stripped `.html`) — NOT suitable as destination path */
|
|
22
|
+
pathname: string;
|
|
23
|
+
/** Absolute source path on disk */
|
|
24
|
+
filePath: string;
|
|
25
|
+
}
|
|
26
|
+
export interface PrerenderOutput {
|
|
27
|
+
id: string;
|
|
28
|
+
pathname: string;
|
|
29
|
+
filePath: string;
|
|
30
|
+
[key: string]: unknown;
|
|
31
|
+
}
|
|
32
|
+
export interface RouteOutput {
|
|
33
|
+
type: string;
|
|
34
|
+
id: string;
|
|
35
|
+
filePath: string;
|
|
36
|
+
pathname: string;
|
|
37
|
+
runtime: "nodejs" | "edge";
|
|
38
|
+
[key: string]: unknown;
|
|
39
|
+
}
|
|
40
|
+
export interface Outputs {
|
|
41
|
+
staticFiles: StaticFileOutput[];
|
|
42
|
+
prerenders: PrerenderOutput[];
|
|
43
|
+
pages: RouteOutput[];
|
|
44
|
+
pagesApi: RouteOutput[];
|
|
45
|
+
appPages: RouteOutput[];
|
|
46
|
+
appRoutes: RouteOutput[];
|
|
47
|
+
middleware?: RouteOutput;
|
|
48
|
+
}
|
|
49
|
+
export interface RoutingInfo {
|
|
50
|
+
[key: string]: unknown;
|
|
51
|
+
}
|
|
52
|
+
export interface BuildContext {
|
|
53
|
+
outputs: Outputs;
|
|
54
|
+
routing: RoutingInfo;
|
|
55
|
+
/** Absolute path Next.js wrote build artefacts to (e.g. `<project>/.next` or `<project>/out`) */
|
|
56
|
+
distDir: string;
|
|
57
|
+
/** Absolute project root */
|
|
58
|
+
projectDir: string;
|
|
59
|
+
config: NextConfigLike;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Second argument Next.js passes to `modifyConfig`. Verified against
|
|
63
|
+
* `next/dist/esm/server/config.js` in Next 16.2.4:
|
|
64
|
+
*
|
|
65
|
+
* adapterMod.modifyConfig(config, { phase, nextVersion })
|
|
66
|
+
*/
|
|
67
|
+
export interface ModifyConfigContext {
|
|
68
|
+
phase: NextPhase;
|
|
69
|
+
nextVersion: string;
|
|
70
|
+
}
|
|
71
|
+
export interface NextAdapter {
|
|
72
|
+
name: string;
|
|
73
|
+
modifyConfig?: (config: NextConfigLike, ctx: ModifyConfigContext) => NextConfigLike | Promise<NextConfigLike>;
|
|
74
|
+
onBuildComplete?: (ctx: BuildContext) => void | Promise<void>;
|
|
75
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next.js Adapter API surface — minimal subset used by this adapter.
|
|
3
|
+
*
|
|
4
|
+
* Not imported from `next` directly to keep the package free of a hard
|
|
5
|
+
* `next` dependency; the runtime contract is enforced by Next itself
|
|
6
|
+
* when it loads the adapter via `NEXT_ADAPTER_PATH` / `adapterPath`.
|
|
7
|
+
*
|
|
8
|
+
* Refs:
|
|
9
|
+
* https://nextjs.org/docs/app/api-reference/adapters/api-reference
|
|
10
|
+
* https://nextjs.org/docs/app/api-reference/adapters/output-types
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
|
package/dist/uboa.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { BuildContext } from "./types.js";
|
|
2
|
+
interface UboaRoute {
|
|
3
|
+
id: string;
|
|
4
|
+
match: string;
|
|
5
|
+
type: "static" | "serverFunction";
|
|
6
|
+
entry?: string;
|
|
7
|
+
serverFunction?: string;
|
|
8
|
+
}
|
|
9
|
+
interface UboaMiddleware {
|
|
10
|
+
name: string;
|
|
11
|
+
match: string[];
|
|
12
|
+
runtime: "serverless";
|
|
13
|
+
serverFunction?: string;
|
|
14
|
+
}
|
|
15
|
+
interface UboaArtifact {
|
|
16
|
+
id: string;
|
|
17
|
+
resourceKind: "assets" | "serverFunction";
|
|
18
|
+
path: string;
|
|
19
|
+
}
|
|
20
|
+
interface GeneratedUboa {
|
|
21
|
+
routes: UboaRoute[];
|
|
22
|
+
middleware: UboaMiddleware[];
|
|
23
|
+
artifacts: UboaArtifact[];
|
|
24
|
+
}
|
|
25
|
+
export declare function emitUboaOutput(ctx: BuildContext): Promise<GeneratedUboa>;
|
|
26
|
+
export {};
|
package/dist/uboa.js
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const ADAPTER_PACKAGE = "@unieojs/unio-nextjs-adapter";
|
|
4
|
+
const UBOA_VERSION = "1.0.0";
|
|
5
|
+
const ROUTE_GROUPS = [
|
|
6
|
+
["pages", "pages"],
|
|
7
|
+
["pages-api", "pagesApi"],
|
|
8
|
+
["app-pages", "appPages"],
|
|
9
|
+
["app-routes", "appRoutes"],
|
|
10
|
+
];
|
|
11
|
+
function toPosix(p) {
|
|
12
|
+
return p.split(path.sep).join("/");
|
|
13
|
+
}
|
|
14
|
+
function stripLeadingSlash(p) {
|
|
15
|
+
return p.startsWith("/") ? p.slice(1) : p;
|
|
16
|
+
}
|
|
17
|
+
function staticEntryFor(file) {
|
|
18
|
+
return `static/${toPosix(stripLeadingSlash(file.id))}`;
|
|
19
|
+
}
|
|
20
|
+
function isHtmlFile(file) {
|
|
21
|
+
return file.id.endsWith(".html");
|
|
22
|
+
}
|
|
23
|
+
function slugify(input, fallback) {
|
|
24
|
+
const source = input && input !== "/" ? input : fallback;
|
|
25
|
+
const slug = source
|
|
26
|
+
.replace(/\.html$/, "")
|
|
27
|
+
.replace(/^\/+/, "")
|
|
28
|
+
.replace(/\[\[\.\.\.([^\]]+)\]\]/g, "$1")
|
|
29
|
+
.replace(/\[\.\.\.([^\]]+)\]/g, "$1")
|
|
30
|
+
.replace(/\[([^\]]+)\]/g, "$1")
|
|
31
|
+
.replace(/[^A-Za-z0-9]+/g, "-")
|
|
32
|
+
.replace(/^-+|-+$/g, "")
|
|
33
|
+
.toLowerCase();
|
|
34
|
+
return slug || fallback;
|
|
35
|
+
}
|
|
36
|
+
function normalizeStaticMatch(file) {
|
|
37
|
+
if (file.pathname)
|
|
38
|
+
return file.pathname;
|
|
39
|
+
if (file.id === "/index.html")
|
|
40
|
+
return "/";
|
|
41
|
+
return file.id.replace(/\.html$/, "");
|
|
42
|
+
}
|
|
43
|
+
function nextPatternToUnio(pattern) {
|
|
44
|
+
if (!pattern)
|
|
45
|
+
return "/:path*";
|
|
46
|
+
if (pattern === "/")
|
|
47
|
+
return "/";
|
|
48
|
+
return pattern
|
|
49
|
+
.replace(/\[\[\.\.\.([^\]]+)\]\]/g, ":$1*")
|
|
50
|
+
.replace(/\[\.\.\.([^\]]+)\]/g, ":$1*")
|
|
51
|
+
.replace(/\[([^\]]+)\]/g, ":$1");
|
|
52
|
+
}
|
|
53
|
+
function routeMatch(route) {
|
|
54
|
+
return nextPatternToUnio(route.pathname || route.id);
|
|
55
|
+
}
|
|
56
|
+
function routeFunctionName(group, route) {
|
|
57
|
+
return `${group}-${slugify(route.pathname || route.id, "index")}`;
|
|
58
|
+
}
|
|
59
|
+
function isRecord(value) {
|
|
60
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
61
|
+
}
|
|
62
|
+
function routeConfig(route) {
|
|
63
|
+
return isRecord(route.config) ? route.config : {};
|
|
64
|
+
}
|
|
65
|
+
function routeMaxDuration(route) {
|
|
66
|
+
const maxDuration = routeConfig(route).maxDuration;
|
|
67
|
+
return typeof maxDuration === "number" ? maxDuration : 10;
|
|
68
|
+
}
|
|
69
|
+
function matcherSource(matcher) {
|
|
70
|
+
if (typeof matcher === "string")
|
|
71
|
+
return matcher;
|
|
72
|
+
if (!isRecord(matcher))
|
|
73
|
+
return undefined;
|
|
74
|
+
if (typeof matcher.originalSource === "string")
|
|
75
|
+
return matcher.originalSource;
|
|
76
|
+
if (typeof matcher.source === "string")
|
|
77
|
+
return matcher.source;
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
function middlewareMatchers(route) {
|
|
81
|
+
const matchers = routeConfig(route).matchers;
|
|
82
|
+
if (!Array.isArray(matchers) || matchers.length === 0)
|
|
83
|
+
return ["/:path*"];
|
|
84
|
+
const patterns = [];
|
|
85
|
+
for (const matcher of matchers) {
|
|
86
|
+
const source = matcherSource(matcher);
|
|
87
|
+
if (source)
|
|
88
|
+
patterns.push(nextPatternToUnio(source));
|
|
89
|
+
}
|
|
90
|
+
return patterns.length > 0 ? patterns : ["/:path*"];
|
|
91
|
+
}
|
|
92
|
+
async function writeJson(filePath, value) {
|
|
93
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
94
|
+
await fs.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
95
|
+
}
|
|
96
|
+
async function copyEntryFile(source, dest) {
|
|
97
|
+
await fs.mkdir(path.dirname(dest), { recursive: true });
|
|
98
|
+
await fs.copyFile(source, dest);
|
|
99
|
+
}
|
|
100
|
+
async function emitServerFunction(root, name, route) {
|
|
101
|
+
const functionDir = path.join(root, "server-functions", name);
|
|
102
|
+
await copyEntryFile(route.filePath, path.join(functionDir, "index.js"));
|
|
103
|
+
await writeJson(path.join(functionDir, "serverFunction.json"), {
|
|
104
|
+
name,
|
|
105
|
+
runtime: "nodejs",
|
|
106
|
+
entry: "index.js",
|
|
107
|
+
handler: "default",
|
|
108
|
+
memory: 512,
|
|
109
|
+
maxDuration: routeMaxDuration(route),
|
|
110
|
+
environment: {},
|
|
111
|
+
bindings: {},
|
|
112
|
+
source: {
|
|
113
|
+
type: route.type,
|
|
114
|
+
pathname: route.pathname,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function buildContentVersion() {
|
|
119
|
+
const parts = [
|
|
120
|
+
process.env.SPRINT_ID ? `SPRINT=${process.env.SPRINT_ID}` : undefined,
|
|
121
|
+
process.env.COMMIT_ID ? `COMMIT=${process.env.COMMIT_ID}` : undefined,
|
|
122
|
+
].filter(Boolean);
|
|
123
|
+
return parts.length > 0 ? parts.join(", ") : "local";
|
|
124
|
+
}
|
|
125
|
+
function buildCapabilities(ctx) {
|
|
126
|
+
const { outputs } = ctx;
|
|
127
|
+
const nodeRoutes = ROUTE_GROUPS.some(([, key]) => outputs[key].some((route) => route.runtime === "nodejs"));
|
|
128
|
+
const nodeMiddleware = outputs.middleware?.runtime === "nodejs";
|
|
129
|
+
return {
|
|
130
|
+
static: outputs.staticFiles.length > 0 || outputs.prerenders.length > 0 ?
|
|
131
|
+
"supported" : "unsupported",
|
|
132
|
+
routes: "supported",
|
|
133
|
+
serverlessFunction: nodeRoutes || nodeMiddleware ? "supported" : "unsupported",
|
|
134
|
+
edgeMiddleware: "unsupported",
|
|
135
|
+
isr: outputs.prerenders.length > 0 ? "degraded" : "unsupported",
|
|
136
|
+
imageOptimization: "unsupported",
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function assertSupportedOutputs(ctx) {
|
|
140
|
+
const unsupported = [];
|
|
141
|
+
if (ctx.outputs.middleware && ctx.outputs.middleware.runtime !== "nodejs") {
|
|
142
|
+
unsupported.push(`middleware (${ctx.outputs.middleware.runtime})`);
|
|
143
|
+
}
|
|
144
|
+
for (const [, key] of ROUTE_GROUPS) {
|
|
145
|
+
for (const route of ctx.outputs[key]) {
|
|
146
|
+
if (route.runtime !== "nodejs") {
|
|
147
|
+
unsupported.push(`${key}:${route.pathname || route.id} (${route.runtime})`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (unsupported.length === 0)
|
|
152
|
+
return;
|
|
153
|
+
throw new Error("[unio-nextjs-adapter] Unsupported Next.js outputs: " +
|
|
154
|
+
`${unsupported.join(", ")}. ` +
|
|
155
|
+
"This adapter currently supports static assets and nodejs serverless functions only.");
|
|
156
|
+
}
|
|
157
|
+
async function emitCoreManifest(root, ctx) {
|
|
158
|
+
await writeJson(path.join(root, "unio.json"), {
|
|
159
|
+
version: UBOA_VERSION,
|
|
160
|
+
framework: {
|
|
161
|
+
name: "next",
|
|
162
|
+
version: "unknown",
|
|
163
|
+
adapter: ADAPTER_PACKAGE,
|
|
164
|
+
},
|
|
165
|
+
build: {
|
|
166
|
+
contentVersion: buildContentVersion(),
|
|
167
|
+
buildId: process.env.BUILD_ID || process.env.TASK_ID || "local",
|
|
168
|
+
},
|
|
169
|
+
capabilities: buildCapabilities(ctx),
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
async function emitStaticFiles(root, files) {
|
|
173
|
+
const routes = [];
|
|
174
|
+
for (const file of files) {
|
|
175
|
+
const relative = stripLeadingSlash(file.id);
|
|
176
|
+
await copyEntryFile(file.filePath, path.join(root, "static", relative));
|
|
177
|
+
if (!isHtmlFile(file))
|
|
178
|
+
continue;
|
|
179
|
+
routes.push({
|
|
180
|
+
id: `static-${slugify(file.pathname || file.id, "index")}`,
|
|
181
|
+
match: normalizeStaticMatch(file),
|
|
182
|
+
type: "static",
|
|
183
|
+
entry: staticEntryFor(file),
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
return routes;
|
|
187
|
+
}
|
|
188
|
+
async function emitRouteOutputs(root, ctx) {
|
|
189
|
+
const routes = [];
|
|
190
|
+
const artifacts = [];
|
|
191
|
+
for (const [group, key] of ROUTE_GROUPS) {
|
|
192
|
+
for (const route of ctx.outputs[key]) {
|
|
193
|
+
const name = routeFunctionName(group, route);
|
|
194
|
+
await emitServerFunction(root, name, route);
|
|
195
|
+
routes.push({
|
|
196
|
+
id: `server-${name}`,
|
|
197
|
+
match: routeMatch(route),
|
|
198
|
+
type: "serverFunction",
|
|
199
|
+
serverFunction: name,
|
|
200
|
+
});
|
|
201
|
+
artifacts.push({
|
|
202
|
+
id: name,
|
|
203
|
+
resourceKind: "serverFunction",
|
|
204
|
+
path: `server-functions/${name}`,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return { routes, artifacts };
|
|
209
|
+
}
|
|
210
|
+
async function emitMiddleware(root, middleware) {
|
|
211
|
+
if (!middleware)
|
|
212
|
+
return { middleware: [], artifacts: [] };
|
|
213
|
+
const name = "middleware";
|
|
214
|
+
await emitServerFunction(root, name, middleware);
|
|
215
|
+
return {
|
|
216
|
+
middleware: [
|
|
217
|
+
{
|
|
218
|
+
name,
|
|
219
|
+
match: middlewareMatchers(middleware),
|
|
220
|
+
runtime: "serverless",
|
|
221
|
+
serverFunction: name,
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
artifacts: [
|
|
225
|
+
{
|
|
226
|
+
id: name,
|
|
227
|
+
resourceKind: "serverFunction",
|
|
228
|
+
path: `server-functions/${name}`,
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
export async function emitUboaOutput(ctx) {
|
|
234
|
+
assertSupportedOutputs(ctx);
|
|
235
|
+
const root = path.join(ctx.projectDir, ".unio", "output");
|
|
236
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
237
|
+
await fs.mkdir(root, { recursive: true });
|
|
238
|
+
const staticFiles = [...ctx.outputs.staticFiles, ...ctx.outputs.prerenders];
|
|
239
|
+
const staticRoutes = await emitStaticFiles(root, staticFiles);
|
|
240
|
+
const routeOutput = await emitRouteOutputs(root, ctx);
|
|
241
|
+
const middlewareOutput = await emitMiddleware(root, ctx.outputs.middleware);
|
|
242
|
+
const artifacts = [];
|
|
243
|
+
if (staticFiles.length > 0) {
|
|
244
|
+
artifacts.push({
|
|
245
|
+
id: "static",
|
|
246
|
+
resourceKind: "assets",
|
|
247
|
+
path: "static",
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
artifacts.push(...routeOutput.artifacts, ...middlewareOutput.artifacts);
|
|
251
|
+
const routes = [...staticRoutes, ...routeOutput.routes];
|
|
252
|
+
const middleware = middlewareOutput.middleware;
|
|
253
|
+
await emitCoreManifest(root, ctx);
|
|
254
|
+
await writeJson(path.join(root, "routes.json"), routes);
|
|
255
|
+
await writeJson(path.join(root, "middleware.json"), middleware);
|
|
256
|
+
await writeJson(path.join(root, "artifacts.json"), { artifacts });
|
|
257
|
+
return { routes, middleware, artifacts };
|
|
258
|
+
}
|
|
259
|
+
//# sourceMappingURL=uboa.js.map
|
package/dist/uboa.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uboa.js","sourceRoot":"","sources":["../src/uboa.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@unieojs/unio-nextjs-adapter",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Next.js adapter that emits Unio Build Output API manifests.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/unieojs/unio-adapters.git",
|
|
18
|
+
"directory": "packages/nextjs"
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public",
|
|
22
|
+
"registry": "https://registry.npmjs.org"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"src"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc -p tsconfig.json",
|
|
30
|
+
"test": "node --import tsx --test \"test/**/*.test.ts\"",
|
|
31
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^22.15.0",
|
|
35
|
+
"tsx": "^4.20.0",
|
|
36
|
+
"typescript": "^5.8.0"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18.19.0"
|
|
40
|
+
},
|
|
41
|
+
"author": "shizexian.szx",
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"yuyanId": "180020010101339979"
|
|
44
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Outputs } from "./types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Validate that a Next.js output set is pure static export.
|
|
5
|
+
*/
|
|
6
|
+
export function assertSsgOnly(outputs: Outputs): void {
|
|
7
|
+
const violations: string[] = [];
|
|
8
|
+
|
|
9
|
+
if (outputs.pages?.length) {
|
|
10
|
+
violations.push(`pages (${outputs.pages.length})`);
|
|
11
|
+
}
|
|
12
|
+
if (outputs.pagesApi?.length) {
|
|
13
|
+
violations.push(`pagesApi (${outputs.pagesApi.length})`);
|
|
14
|
+
}
|
|
15
|
+
if (outputs.appPages?.length) {
|
|
16
|
+
violations.push(`appPages (${outputs.appPages.length})`);
|
|
17
|
+
}
|
|
18
|
+
if (outputs.appRoutes?.length) {
|
|
19
|
+
violations.push(`appRoutes (${outputs.appRoutes.length})`);
|
|
20
|
+
}
|
|
21
|
+
if (outputs.prerenders?.length) {
|
|
22
|
+
violations.push(`prerenders (${outputs.prerenders.length})`);
|
|
23
|
+
}
|
|
24
|
+
if (outputs.middleware) {
|
|
25
|
+
violations.push(`middleware (${outputs.middleware.runtime})`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (violations.length === 0) return;
|
|
29
|
+
|
|
30
|
+
throw new Error(
|
|
31
|
+
"[unio-nextjs-adapter] Only SSG is supported in this version. " +
|
|
32
|
+
`Encountered non-SSG outputs: ${violations.join(", ")}. ` +
|
|
33
|
+
"Set `output: 'export'` in next.config.ts, or wait for SSR support in a future release.",
|
|
34
|
+
);
|
|
35
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { modifyConfig } from "./modify-config.js";
|
|
2
|
+
import { onBuildComplete } from "./on-build-complete.js";
|
|
3
|
+
import type { NextAdapter } from "./types.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Adapter name. Exported at top level so Next.js can read it when loading
|
|
7
|
+
* via the CJS entry: its `interopDefault(mod) = mod.default` returns the
|
|
8
|
+
* whole CJS `exports` object, so `name` must live there, not only nested
|
|
9
|
+
* inside the default export.
|
|
10
|
+
*/
|
|
11
|
+
export const name = "unio-nextjs-adapter";
|
|
12
|
+
|
|
13
|
+
export const adapter: NextAdapter = {
|
|
14
|
+
name,
|
|
15
|
+
modifyConfig,
|
|
16
|
+
onBuildComplete,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default adapter;
|
|
20
|
+
|
|
21
|
+
export { modifyConfig } from "./modify-config.js";
|
|
22
|
+
export { onBuildComplete } from "./on-build-complete.js";
|
|
23
|
+
export { emitUboaOutput } from "./uboa.js";
|
|
24
|
+
export { assertSsgOnly } from "./assert-ssg.js";
|
|
25
|
+
export type {
|
|
26
|
+
BuildContext,
|
|
27
|
+
ModifyConfigContext,
|
|
28
|
+
NextAdapter,
|
|
29
|
+
NextConfigLike,
|
|
30
|
+
NextPhase,
|
|
31
|
+
Outputs,
|
|
32
|
+
PrerenderOutput,
|
|
33
|
+
RouteOutput,
|
|
34
|
+
RoutingInfo,
|
|
35
|
+
StaticFileOutput,
|
|
36
|
+
} from "./types.js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ModifyConfigContext, NextConfigLike } from "./types.js";
|
|
2
|
+
|
|
3
|
+
const LOG_PREFIX = "[unio-nextjs-adapter]";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Pass the config through unchanged.
|
|
7
|
+
*
|
|
8
|
+
* The adapter intentionally does not force `output: "standalone"`; it packages
|
|
9
|
+
* the outputs passed to `onBuildComplete`.
|
|
10
|
+
*/
|
|
11
|
+
export function modifyConfig(
|
|
12
|
+
config: NextConfigLike,
|
|
13
|
+
ctx: ModifyConfigContext,
|
|
14
|
+
): NextConfigLike {
|
|
15
|
+
console.log(
|
|
16
|
+
`${LOG_PREFIX} modifyConfig phase=${ctx.phase} nextVersion=${ctx.nextVersion}`,
|
|
17
|
+
);
|
|
18
|
+
return config;
|
|
19
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { emitUboaOutput } from "./uboa.js";
|
|
5
|
+
import type { BuildContext } from "./types.js";
|
|
6
|
+
|
|
7
|
+
const LOG_PREFIX = "[unio-nextjs-adapter]";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Delete an intermediate build dir (typically `out/`) that Next wrote
|
|
11
|
+
* build output into before we copied files into `.unio/output`.
|
|
12
|
+
*
|
|
13
|
+
* Guards:
|
|
14
|
+
* - only removes if the dir exists
|
|
15
|
+
* - refuses to remove the project root or the uboa output dir itself
|
|
16
|
+
* - refuses to remove a dir that contains the uboa output dir
|
|
17
|
+
*/
|
|
18
|
+
async function cleanIntermediate(
|
|
19
|
+
intermediateDir: string,
|
|
20
|
+
protectedDirs: string[],
|
|
21
|
+
): Promise<boolean> {
|
|
22
|
+
const target = path.resolve(intermediateDir);
|
|
23
|
+
|
|
24
|
+
for (const p of protectedDirs) {
|
|
25
|
+
const protectedAbs = path.resolve(p);
|
|
26
|
+
if (target === protectedAbs) return false;
|
|
27
|
+
const rel = path.relative(target, protectedAbs);
|
|
28
|
+
const isInside = rel !== "" && !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
29
|
+
if (isInside) return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
await fs.rm(target, { recursive: true, force: true });
|
|
34
|
+
return true;
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function onBuildComplete(ctx: BuildContext): Promise<void> {
|
|
41
|
+
const { projectDir, distDir } = ctx;
|
|
42
|
+
|
|
43
|
+
const uboa = await emitUboaOutput(ctx);
|
|
44
|
+
const uboaOutputDir = path.resolve(projectDir, ".unio", "output");
|
|
45
|
+
|
|
46
|
+
const intermediates = new Set<string>();
|
|
47
|
+
intermediates.add(path.resolve(projectDir, "out"));
|
|
48
|
+
if (distDir) intermediates.add(path.resolve(distDir));
|
|
49
|
+
|
|
50
|
+
for (const dir of intermediates) {
|
|
51
|
+
const cleaned = await cleanIntermediate(dir, [projectDir, uboaOutputDir]);
|
|
52
|
+
if (cleaned) {
|
|
53
|
+
console.log(`${LOG_PREFIX} cleaned intermediate ${path.relative(projectDir, dir) || dir}/`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
console.log(
|
|
58
|
+
`${LOG_PREFIX} ✔ .unio/output ready: ` +
|
|
59
|
+
`${uboa.routes.length} routes + ${uboa.middleware.length} middleware + ` +
|
|
60
|
+
`${uboa.artifacts.length} artifacts`,
|
|
61
|
+
);
|
|
62
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next.js Adapter API surface — minimal subset used by this adapter.
|
|
3
|
+
*
|
|
4
|
+
* Not imported from `next` directly to keep the package free of a hard
|
|
5
|
+
* `next` dependency; the runtime contract is enforced by Next itself
|
|
6
|
+
* when it loads the adapter via `NEXT_ADAPTER_PATH` / `adapterPath`.
|
|
7
|
+
*
|
|
8
|
+
* Refs:
|
|
9
|
+
* https://nextjs.org/docs/app/api-reference/adapters/api-reference
|
|
10
|
+
* https://nextjs.org/docs/app/api-reference/adapters/output-types
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export type NextPhase =
|
|
14
|
+
| "phase-development-server"
|
|
15
|
+
| "phase-production-server"
|
|
16
|
+
| "phase-production-build"
|
|
17
|
+
| "phase-export"
|
|
18
|
+
| "phase-test";
|
|
19
|
+
|
|
20
|
+
export interface NextConfigLike {
|
|
21
|
+
output?: "export" | "standalone" | undefined;
|
|
22
|
+
distDir?: string;
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface StaticFileOutput {
|
|
27
|
+
/** e.g. `/index.html`, `/_not-found.html`, `/_next/static/chunks/xxx.js` — carries extension, maps to filesystem */
|
|
28
|
+
id: string;
|
|
29
|
+
/** URL form (stripped `.html`) — NOT suitable as destination path */
|
|
30
|
+
pathname: string;
|
|
31
|
+
/** Absolute source path on disk */
|
|
32
|
+
filePath: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface PrerenderOutput {
|
|
36
|
+
id: string;
|
|
37
|
+
pathname: string;
|
|
38
|
+
filePath: string;
|
|
39
|
+
[key: string]: unknown;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface RouteOutput {
|
|
43
|
+
type: string;
|
|
44
|
+
id: string;
|
|
45
|
+
filePath: string;
|
|
46
|
+
pathname: string;
|
|
47
|
+
runtime: "nodejs" | "edge";
|
|
48
|
+
[key: string]: unknown;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface Outputs {
|
|
52
|
+
staticFiles: StaticFileOutput[];
|
|
53
|
+
prerenders: PrerenderOutput[];
|
|
54
|
+
pages: RouteOutput[];
|
|
55
|
+
pagesApi: RouteOutput[];
|
|
56
|
+
appPages: RouteOutput[];
|
|
57
|
+
appRoutes: RouteOutput[];
|
|
58
|
+
middleware?: RouteOutput;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface RoutingInfo {
|
|
62
|
+
[key: string]: unknown;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface BuildContext {
|
|
66
|
+
outputs: Outputs;
|
|
67
|
+
routing: RoutingInfo;
|
|
68
|
+
/** Absolute path Next.js wrote build artefacts to (e.g. `<project>/.next` or `<project>/out`) */
|
|
69
|
+
distDir: string;
|
|
70
|
+
/** Absolute project root */
|
|
71
|
+
projectDir: string;
|
|
72
|
+
config: NextConfigLike;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Second argument Next.js passes to `modifyConfig`. Verified against
|
|
77
|
+
* `next/dist/esm/server/config.js` in Next 16.2.4:
|
|
78
|
+
*
|
|
79
|
+
* adapterMod.modifyConfig(config, { phase, nextVersion })
|
|
80
|
+
*/
|
|
81
|
+
export interface ModifyConfigContext {
|
|
82
|
+
phase: NextPhase;
|
|
83
|
+
nextVersion: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface NextAdapter {
|
|
87
|
+
name: string;
|
|
88
|
+
modifyConfig?: (
|
|
89
|
+
config: NextConfigLike,
|
|
90
|
+
ctx: ModifyConfigContext,
|
|
91
|
+
) => NextConfigLike | Promise<NextConfigLike>;
|
|
92
|
+
onBuildComplete?: (ctx: BuildContext) => void | Promise<void>;
|
|
93
|
+
}
|
package/src/uboa.ts
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
BuildContext,
|
|
6
|
+
PrerenderOutput,
|
|
7
|
+
RouteOutput,
|
|
8
|
+
StaticFileOutput,
|
|
9
|
+
} from "./types.js";
|
|
10
|
+
|
|
11
|
+
const ADAPTER_PACKAGE = "@unieojs/unio-nextjs-adapter";
|
|
12
|
+
const UBOA_VERSION = "1.0.0";
|
|
13
|
+
|
|
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
|
+
type StaticLikeOutput = StaticFileOutput | PrerenderOutput;
|
|
44
|
+
type RouteOutputKey = "pages" | "pagesApi" | "appPages" | "appRoutes";
|
|
45
|
+
|
|
46
|
+
const ROUTE_GROUPS: Array<[string, RouteOutputKey]> = [
|
|
47
|
+
["pages", "pages"],
|
|
48
|
+
["pages-api", "pagesApi"],
|
|
49
|
+
["app-pages", "appPages"],
|
|
50
|
+
["app-routes", "appRoutes"],
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
function toPosix(p: string): string {
|
|
54
|
+
return p.split(path.sep).join("/");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function stripLeadingSlash(p: string): string {
|
|
58
|
+
return p.startsWith("/") ? p.slice(1) : p;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function staticEntryFor(file: StaticLikeOutput): string {
|
|
62
|
+
return `static/${toPosix(stripLeadingSlash(file.id))}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isHtmlFile(file: StaticLikeOutput): boolean {
|
|
66
|
+
return file.id.endsWith(".html");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function slugify(input: string | undefined, fallback: string): string {
|
|
70
|
+
const source = input && input !== "/" ? input : fallback;
|
|
71
|
+
const slug = source
|
|
72
|
+
.replace(/\.html$/, "")
|
|
73
|
+
.replace(/^\/+/, "")
|
|
74
|
+
.replace(/\[\[\.\.\.([^\]]+)\]\]/g, "$1")
|
|
75
|
+
.replace(/\[\.\.\.([^\]]+)\]/g, "$1")
|
|
76
|
+
.replace(/\[([^\]]+)\]/g, "$1")
|
|
77
|
+
.replace(/[^A-Za-z0-9]+/g, "-")
|
|
78
|
+
.replace(/^-+|-+$/g, "")
|
|
79
|
+
.toLowerCase();
|
|
80
|
+
return slug || fallback;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function normalizeStaticMatch(file: StaticLikeOutput): string {
|
|
84
|
+
if (file.pathname) return file.pathname;
|
|
85
|
+
|
|
86
|
+
if (file.id === "/index.html") return "/";
|
|
87
|
+
return file.id.replace(/\.html$/, "");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function nextPatternToUnio(pattern: string | undefined): string {
|
|
91
|
+
if (!pattern) return "/:path*";
|
|
92
|
+
if (pattern === "/") return "/";
|
|
93
|
+
|
|
94
|
+
return pattern
|
|
95
|
+
.replace(/\[\[\.\.\.([^\]]+)\]\]/g, ":$1*")
|
|
96
|
+
.replace(/\[\.\.\.([^\]]+)\]/g, ":$1*")
|
|
97
|
+
.replace(/\[([^\]]+)\]/g, ":$1");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function routeMatch(route: RouteOutput): string {
|
|
101
|
+
return nextPatternToUnio(route.pathname || route.id);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function routeFunctionName(group: string, route: RouteOutput): string {
|
|
105
|
+
return `${group}-${slugify(route.pathname || route.id, "index")}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
109
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function routeConfig(route: RouteOutput): Record<string, unknown> {
|
|
113
|
+
return isRecord(route.config) ? route.config : {};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function routeMaxDuration(route: RouteOutput): number {
|
|
117
|
+
const maxDuration = routeConfig(route).maxDuration;
|
|
118
|
+
return typeof maxDuration === "number" ? maxDuration : 10;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function matcherSource(matcher: unknown): string | undefined {
|
|
122
|
+
if (typeof matcher === "string") return matcher;
|
|
123
|
+
if (!isRecord(matcher)) return undefined;
|
|
124
|
+
if (typeof matcher.originalSource === "string") return matcher.originalSource;
|
|
125
|
+
if (typeof matcher.source === "string") return matcher.source;
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function middlewareMatchers(route: RouteOutput): string[] {
|
|
130
|
+
const matchers = routeConfig(route).matchers;
|
|
131
|
+
if (!Array.isArray(matchers) || matchers.length === 0) return ["/:path*"];
|
|
132
|
+
|
|
133
|
+
const patterns: string[] = [];
|
|
134
|
+
for (const matcher of matchers) {
|
|
135
|
+
const source = matcherSource(matcher);
|
|
136
|
+
if (source) patterns.push(nextPatternToUnio(source));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return patterns.length > 0 ? patterns : ["/:path*"];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function writeJson(filePath: string, value: unknown): Promise<void> {
|
|
143
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
144
|
+
await fs.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function copyEntryFile(source: string, dest: string): Promise<void> {
|
|
148
|
+
await fs.mkdir(path.dirname(dest), { recursive: true });
|
|
149
|
+
await fs.copyFile(source, dest);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function emitServerFunction(
|
|
153
|
+
root: string,
|
|
154
|
+
name: string,
|
|
155
|
+
route: RouteOutput,
|
|
156
|
+
): Promise<void> {
|
|
157
|
+
const functionDir = path.join(root, "server-functions", name);
|
|
158
|
+
await copyEntryFile(route.filePath, path.join(functionDir, "index.js"));
|
|
159
|
+
await writeJson(path.join(functionDir, "serverFunction.json"), {
|
|
160
|
+
name,
|
|
161
|
+
runtime: "nodejs",
|
|
162
|
+
entry: "index.js",
|
|
163
|
+
handler: "default",
|
|
164
|
+
memory: 512,
|
|
165
|
+
maxDuration: routeMaxDuration(route),
|
|
166
|
+
environment: {},
|
|
167
|
+
bindings: {},
|
|
168
|
+
source: {
|
|
169
|
+
type: route.type,
|
|
170
|
+
pathname: route.pathname,
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function buildContentVersion(): string {
|
|
176
|
+
const parts = [
|
|
177
|
+
process.env.SPRINT_ID ? `SPRINT=${process.env.SPRINT_ID}` : undefined,
|
|
178
|
+
process.env.COMMIT_ID ? `COMMIT=${process.env.COMMIT_ID}` : undefined,
|
|
179
|
+
].filter(Boolean);
|
|
180
|
+
|
|
181
|
+
return parts.length > 0 ? parts.join(", ") : "local";
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function buildCapabilities(ctx: BuildContext): Record<string, CapabilityStatus> {
|
|
185
|
+
const { outputs } = ctx;
|
|
186
|
+
const nodeRoutes = ROUTE_GROUPS.some(([, key]) =>
|
|
187
|
+
outputs[key].some((route) => route.runtime === "nodejs"),
|
|
188
|
+
);
|
|
189
|
+
const nodeMiddleware = outputs.middleware?.runtime === "nodejs";
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
static: outputs.staticFiles.length > 0 || outputs.prerenders.length > 0 ?
|
|
193
|
+
"supported" : "unsupported",
|
|
194
|
+
routes: "supported",
|
|
195
|
+
serverlessFunction: nodeRoutes || nodeMiddleware ? "supported" : "unsupported",
|
|
196
|
+
edgeMiddleware: "unsupported",
|
|
197
|
+
isr: outputs.prerenders.length > 0 ? "degraded" : "unsupported",
|
|
198
|
+
imageOptimization: "unsupported",
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function assertSupportedOutputs(ctx: BuildContext): void {
|
|
203
|
+
const unsupported: string[] = [];
|
|
204
|
+
|
|
205
|
+
if (ctx.outputs.middleware && ctx.outputs.middleware.runtime !== "nodejs") {
|
|
206
|
+
unsupported.push(`middleware (${ctx.outputs.middleware.runtime})`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
for (const [, key] of ROUTE_GROUPS) {
|
|
210
|
+
for (const route of ctx.outputs[key]) {
|
|
211
|
+
if (route.runtime !== "nodejs") {
|
|
212
|
+
unsupported.push(`${key}:${route.pathname || route.id} (${route.runtime})`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (unsupported.length === 0) return;
|
|
218
|
+
|
|
219
|
+
throw new Error(
|
|
220
|
+
"[unio-nextjs-adapter] Unsupported Next.js outputs: " +
|
|
221
|
+
`${unsupported.join(", ")}. ` +
|
|
222
|
+
"This adapter currently supports static assets and nodejs serverless functions only.",
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function emitCoreManifest(
|
|
227
|
+
root: string,
|
|
228
|
+
ctx: BuildContext,
|
|
229
|
+
): Promise<void> {
|
|
230
|
+
await writeJson(path.join(root, "unio.json"), {
|
|
231
|
+
version: UBOA_VERSION,
|
|
232
|
+
framework: {
|
|
233
|
+
name: "next",
|
|
234
|
+
version: "unknown",
|
|
235
|
+
adapter: ADAPTER_PACKAGE,
|
|
236
|
+
},
|
|
237
|
+
build: {
|
|
238
|
+
contentVersion: buildContentVersion(),
|
|
239
|
+
buildId: process.env.BUILD_ID || process.env.TASK_ID || "local",
|
|
240
|
+
},
|
|
241
|
+
capabilities: buildCapabilities(ctx),
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function emitStaticFiles(
|
|
246
|
+
root: string,
|
|
247
|
+
files: StaticLikeOutput[],
|
|
248
|
+
): Promise<UboaRoute[]> {
|
|
249
|
+
const routes: UboaRoute[] = [];
|
|
250
|
+
|
|
251
|
+
for (const file of files) {
|
|
252
|
+
const relative = stripLeadingSlash(file.id);
|
|
253
|
+
await copyEntryFile(file.filePath, path.join(root, "static", relative));
|
|
254
|
+
|
|
255
|
+
if (!isHtmlFile(file)) continue;
|
|
256
|
+
|
|
257
|
+
routes.push({
|
|
258
|
+
id: `static-${slugify(file.pathname || file.id, "index")}`,
|
|
259
|
+
match: normalizeStaticMatch(file),
|
|
260
|
+
type: "static",
|
|
261
|
+
entry: staticEntryFor(file),
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return routes;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function emitRouteOutputs(
|
|
269
|
+
root: string,
|
|
270
|
+
ctx: BuildContext,
|
|
271
|
+
): Promise<Pick<GeneratedUboa, "routes" | "artifacts">> {
|
|
272
|
+
const routes: UboaRoute[] = [];
|
|
273
|
+
const artifacts: UboaArtifact[] = [];
|
|
274
|
+
|
|
275
|
+
for (const [group, key] of ROUTE_GROUPS) {
|
|
276
|
+
for (const route of ctx.outputs[key]) {
|
|
277
|
+
const name = routeFunctionName(group, route);
|
|
278
|
+
|
|
279
|
+
await emitServerFunction(root, name, route);
|
|
280
|
+
routes.push({
|
|
281
|
+
id: `server-${name}`,
|
|
282
|
+
match: routeMatch(route),
|
|
283
|
+
type: "serverFunction",
|
|
284
|
+
serverFunction: name,
|
|
285
|
+
});
|
|
286
|
+
artifacts.push({
|
|
287
|
+
id: name,
|
|
288
|
+
resourceKind: "serverFunction",
|
|
289
|
+
path: `server-functions/${name}`,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return { routes, artifacts };
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async function emitMiddleware(
|
|
298
|
+
root: string,
|
|
299
|
+
middleware: RouteOutput | undefined,
|
|
300
|
+
): Promise<Pick<GeneratedUboa, "middleware" | "artifacts">> {
|
|
301
|
+
if (!middleware) return { middleware: [], artifacts: [] };
|
|
302
|
+
|
|
303
|
+
const name = "middleware";
|
|
304
|
+
await emitServerFunction(root, name, middleware);
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
middleware: [
|
|
308
|
+
{
|
|
309
|
+
name,
|
|
310
|
+
match: middlewareMatchers(middleware),
|
|
311
|
+
runtime: "serverless",
|
|
312
|
+
serverFunction: name,
|
|
313
|
+
},
|
|
314
|
+
],
|
|
315
|
+
artifacts: [
|
|
316
|
+
{
|
|
317
|
+
id: name,
|
|
318
|
+
resourceKind: "serverFunction",
|
|
319
|
+
path: `server-functions/${name}`,
|
|
320
|
+
},
|
|
321
|
+
],
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export async function emitUboaOutput(ctx: BuildContext): Promise<GeneratedUboa> {
|
|
326
|
+
assertSupportedOutputs(ctx);
|
|
327
|
+
|
|
328
|
+
const root = path.join(ctx.projectDir, ".unio", "output");
|
|
329
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
330
|
+
await fs.mkdir(root, { recursive: true });
|
|
331
|
+
|
|
332
|
+
const staticFiles = [...ctx.outputs.staticFiles, ...ctx.outputs.prerenders];
|
|
333
|
+
const staticRoutes = await emitStaticFiles(root, staticFiles);
|
|
334
|
+
const routeOutput = await emitRouteOutputs(root, ctx);
|
|
335
|
+
const middlewareOutput = await emitMiddleware(root, ctx.outputs.middleware);
|
|
336
|
+
|
|
337
|
+
const artifacts: UboaArtifact[] = [];
|
|
338
|
+
if (staticFiles.length > 0) {
|
|
339
|
+
artifacts.push({
|
|
340
|
+
id: "static",
|
|
341
|
+
resourceKind: "assets",
|
|
342
|
+
path: "static",
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
artifacts.push(...routeOutput.artifacts, ...middlewareOutput.artifacts);
|
|
346
|
+
|
|
347
|
+
const routes = [...staticRoutes, ...routeOutput.routes];
|
|
348
|
+
const middleware = middlewareOutput.middleware;
|
|
349
|
+
|
|
350
|
+
await emitCoreManifest(root, ctx);
|
|
351
|
+
await writeJson(path.join(root, "routes.json"), routes);
|
|
352
|
+
await writeJson(path.join(root, "middleware.json"), middleware);
|
|
353
|
+
await writeJson(path.join(root, "artifacts.json"), { artifacts });
|
|
354
|
+
|
|
355
|
+
return { routes, middleware, artifacts };
|
|
356
|
+
}
|