@homurajs/app-kit 0.0.0-stage → 26.10.13
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 +29 -2
- package/dist/adapter-BZXaYf3G.d.ts +20 -0
- package/dist/index.d.ts +94 -0
- package/dist/index.js +176 -0
- package/dist/qw-pages.d.ts +13 -0
- package/dist/qw-pages.js +34 -0
- package/dist/serve.d.ts +4 -0
- package/dist/serve.js +29 -0
- package/package.json +48 -3
package/README.md
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @homurajs/app-kit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
组装入口:`createApp` 渐进式装配(零注入也能跑)+ 双形态 RuntimeAdapter(qw-pages 全部未知约定的单点收口)。
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { createApp } from '@homurajs/app-kit'
|
|
7
|
+
import { Hono } from 'hono'
|
|
8
|
+
|
|
9
|
+
// 最小 App:零能力注入
|
|
10
|
+
const { app, runtime } = createApp({ routes: (deps) => myRoutes(deps) })
|
|
11
|
+
|
|
12
|
+
// 渐进解锁:注入即启用(实例由 App 构造,app-kit 不硬依赖能力包)
|
|
13
|
+
const composed = createApp({
|
|
14
|
+
auth: { sessions, sessionMiddleware, authRoutes }, // 登录链
|
|
15
|
+
license: { client: license, guard: licenseGuard }, // 激活守卫
|
|
16
|
+
thirdParty: { platform }, // deps.platform 暴露给路由
|
|
17
|
+
healthz: { checks: [{ name: 'db', check: () => db.select().from(...).limit(1).then(() => true) }] },
|
|
18
|
+
routes: myRoutes,
|
|
19
|
+
})
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
固定中间件链:`basePath → request-id/日志 → /healthz(豁免守卫)→ licenseGuard → sessionMiddleware → authRoutes → App routes → 静态资源(dist/client) → SPA fallback`
|
|
23
|
+
|
|
24
|
+
双形态启动:
|
|
25
|
+
```ts
|
|
26
|
+
import { createNodeAdapter } from '@homurajs/app-kit/serve' // 独立:监听端口
|
|
27
|
+
import { createQwPagesAdapter, exportHandlers } from '@homurajs/app-kit/qw-pages' // qw-pages:三导出对冲
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
使用示范见模板 `src/server/index.ts` 与 `src/server/qw-pages.ts`。
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { RuntimeTarget } from "@homurajs/core";
|
|
3
|
+
//#region src/runtime/adapter.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* 双形态运行时适配器——qw-pages 全部未知约定的单点收口。
|
|
6
|
+
* 跨形态差异(trust proxy、静态服务、端口)全走 adapter,业务代码零 if-else。
|
|
7
|
+
*/
|
|
8
|
+
interface RuntimeAdapter {
|
|
9
|
+
target: RuntimeTarget;
|
|
10
|
+
start(app: Hono, opts: {
|
|
11
|
+
port?: number;
|
|
12
|
+
signal?: AbortSignal;
|
|
13
|
+
}): Promise<void>;
|
|
14
|
+
/** qw-pages 在网关后 → 信任 X-Forwarded-*(影响 cookie secure/redirect URL) */
|
|
15
|
+
trustProxy(): boolean;
|
|
16
|
+
/** 默认 dist/client,可被 HOMURA_ASSETS_DIR 覆盖 */
|
|
17
|
+
assetsDir(env: NodeJS.ProcessEnv): string;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { RuntimeAdapter as t };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { t as RuntimeAdapter } from "./adapter-BZXaYf3G.js";
|
|
2
|
+
import { Context, Hono, MiddlewareHandler } from "hono";
|
|
3
|
+
import { Logger, RuntimeInfo, RuntimeTarget, detectRuntime } from "@homurajs/core";
|
|
4
|
+
//#region src/create-app.d.ts
|
|
5
|
+
/** 能力实例的结构化契约(鸭子类型,避免包依赖;运行时由 App 传入) */
|
|
6
|
+
interface AppDeps {
|
|
7
|
+
db?: Record<string, unknown>;
|
|
8
|
+
storage?: StorageLike;
|
|
9
|
+
sessions?: SessionManagerLike;
|
|
10
|
+
platform?: PlatformLike;
|
|
11
|
+
license?: LicenseClientLike;
|
|
12
|
+
}
|
|
13
|
+
/** 各能力的最小结构化契约(App 传真实例,app-kit 只透传/调用这些方法) */
|
|
14
|
+
interface StorageLike {
|
|
15
|
+
uploadFile(...args: never[]): Promise<unknown>;
|
|
16
|
+
}
|
|
17
|
+
interface SessionManagerLike {
|
|
18
|
+
verifyAccessToken(t: string): Promise<unknown>;
|
|
19
|
+
}
|
|
20
|
+
interface PlatformLike {
|
|
21
|
+
name: string;
|
|
22
|
+
capabilities: readonly string[];
|
|
23
|
+
}
|
|
24
|
+
interface LicenseClientLike {
|
|
25
|
+
getStatus(): string;
|
|
26
|
+
}
|
|
27
|
+
/** auth 注入包(由 @homurajs/auth/hono 构造后传入) */
|
|
28
|
+
interface AuthInject {
|
|
29
|
+
sessions: SessionManagerLike;
|
|
30
|
+
sessionMiddleware: MiddlewareHandler;
|
|
31
|
+
authRoutes: Hono;
|
|
32
|
+
}
|
|
33
|
+
/** license 注入包(由 @homurajs/license/hono 构造后传入) */
|
|
34
|
+
interface LicenseInject {
|
|
35
|
+
client: LicenseClientLike;
|
|
36
|
+
guard: MiddlewareHandler;
|
|
37
|
+
}
|
|
38
|
+
interface HealthCheck {
|
|
39
|
+
name: string;
|
|
40
|
+
check: () => Promise<boolean> | boolean;
|
|
41
|
+
}
|
|
42
|
+
interface CreateAppOptions {
|
|
43
|
+
runtime?: RuntimeTarget | 'auto';
|
|
44
|
+
/** qw-pages 子路径挂载时,全中间件感知(默认 '/') */
|
|
45
|
+
basePath?: string;
|
|
46
|
+
routes?: Hono | ((deps: AppDeps) => Hono);
|
|
47
|
+
/** 注入即启用认证链(sessionMiddleware + authRoutes) */
|
|
48
|
+
auth?: AuthInject | false;
|
|
49
|
+
/** 3p 注入(deps.platform 暴露给 routes,App 内 supports() 协商用能力) */
|
|
50
|
+
thirdParty?: {
|
|
51
|
+
platform: PlatformLike;
|
|
52
|
+
};
|
|
53
|
+
/** 注入即挂停服守卫 */
|
|
54
|
+
license?: LicenseInject | false;
|
|
55
|
+
/** 注入 db/storage 汇总进 deps(供 routes 回调用) */
|
|
56
|
+
db?: Record<string, unknown>;
|
|
57
|
+
storage?: StorageLike;
|
|
58
|
+
healthz?: {
|
|
59
|
+
checks?: HealthCheck[];
|
|
60
|
+
};
|
|
61
|
+
/** 静态资源目录(adapter.assetsDir() 算出);提供则挂 static + SPA fallback */
|
|
62
|
+
staticDir?: string;
|
|
63
|
+
logger?: Logger;
|
|
64
|
+
onError?: (err: Error, c: {
|
|
65
|
+
path: string;
|
|
66
|
+
}) => Response | Promise<Response>;
|
|
67
|
+
}
|
|
68
|
+
interface HomuraApp {
|
|
69
|
+
app: Hono;
|
|
70
|
+
runtime: RuntimeInfo;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 固定中间件链(模板文档逐环解释):
|
|
74
|
+
* basePath → requestId+访问日志 → /healthz(注册于 guard 前,天然豁免)→ licenseGuard
|
|
75
|
+
* → sessionMiddleware → authRoutes → App routes → 静态资源 → SPA fallback → onError
|
|
76
|
+
*/
|
|
77
|
+
export declare function createApp(options?: CreateAppOptions): HomuraApp;
|
|
78
|
+
declare module 'hono' {
|
|
79
|
+
interface ContextVariableMap {
|
|
80
|
+
requestId: string;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region src/static.d.ts
|
|
85
|
+
/** 静态文件中间件:命中则回文件,未命中 next()(交给 SPA fallback) */
|
|
86
|
+
export declare function createStaticMiddleware(root: string): MiddlewareHandler;
|
|
87
|
+
/**
|
|
88
|
+
* SPA fallback(notFound 处理器):
|
|
89
|
+
* - API/鉴权/文件路径 → 404 JSON
|
|
90
|
+
* - 其余 GET(页面请求)→ index.html(前端路由接管)
|
|
91
|
+
*/
|
|
92
|
+
export declare function createSpaFallback(root: string): (c: Context) => Response | Promise<Response>;
|
|
93
|
+
//#endregion
|
|
94
|
+
export { type AppDeps, type AuthInject, type CreateAppOptions, type HealthCheck, type HomuraApp, type LicenseClientLike, type LicenseInject, type PlatformLike, type RuntimeAdapter, type SessionManagerLike, type StorageLike, detectRuntime };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { detectRuntime, detectRuntime as detectRuntime$1 } from "@homurajs/core";
|
|
4
|
+
import { createReadStream, existsSync, statSync } from "node:fs";
|
|
5
|
+
import { extname, join, normalize, resolve, sep } from "node:path";
|
|
6
|
+
import { Readable } from "node:stream";
|
|
7
|
+
//#region src/static.ts
|
|
8
|
+
/**
|
|
9
|
+
* fs 静态资源处理 + SPA fallback(node / qw-pages 共享,均跑在 Node 上)。
|
|
10
|
+
* 只做「按 path 找文件」,不监听端口——端口/导出差异归 adapter。
|
|
11
|
+
*/
|
|
12
|
+
const MIME = {
|
|
13
|
+
".html": "text/html; charset=utf-8",
|
|
14
|
+
".js": "text/javascript; charset=utf-8",
|
|
15
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
16
|
+
".css": "text/css; charset=utf-8",
|
|
17
|
+
".json": "application/json; charset=utf-8",
|
|
18
|
+
".svg": "image/svg+xml",
|
|
19
|
+
".png": "image/png",
|
|
20
|
+
".jpg": "image/jpeg",
|
|
21
|
+
".jpeg": "image/jpeg",
|
|
22
|
+
".gif": "image/gif",
|
|
23
|
+
".webp": "image/webp",
|
|
24
|
+
".ico": "image/x-icon",
|
|
25
|
+
".woff": "font/woff",
|
|
26
|
+
".woff2": "font/woff2",
|
|
27
|
+
".txt": "text/plain; charset=utf-8",
|
|
28
|
+
".map": "application/json; charset=utf-8"
|
|
29
|
+
};
|
|
30
|
+
function mimeOf(file) {
|
|
31
|
+
return MIME[extname(file).toLowerCase()] ?? "application/octet-stream";
|
|
32
|
+
}
|
|
33
|
+
/** 路径穿越防护:解析后必须仍在 root 内 */
|
|
34
|
+
function safeResolve(root, reqPath) {
|
|
35
|
+
const rel = decodeURIComponent(reqPath).replace(/^\/+/, "");
|
|
36
|
+
const abs = resolve(root, normalize(rel));
|
|
37
|
+
const rootWithSep = root.endsWith(sep) ? root : root + sep;
|
|
38
|
+
if (abs !== root && !abs.startsWith(rootWithSep)) return null;
|
|
39
|
+
return abs;
|
|
40
|
+
}
|
|
41
|
+
/** 静态文件中间件:命中则回文件,未命中 next()(交给 SPA fallback) */
|
|
42
|
+
function createStaticMiddleware(root) {
|
|
43
|
+
return async (c, next) => {
|
|
44
|
+
if (c.req.method !== "GET" && c.req.method !== "HEAD") {
|
|
45
|
+
await next();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const urlPath = new URL(c.req.url).pathname;
|
|
49
|
+
const abs = safeResolve(root, urlPath);
|
|
50
|
+
if (!abs || !existsSync(abs) || !statSync(abs).isFile()) {
|
|
51
|
+
await next();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const isAsset = urlPath.startsWith("/assets/") || urlPath.includes(".");
|
|
55
|
+
c.header("content-type", mimeOf(abs));
|
|
56
|
+
c.header("cache-control", isAsset ? "public, max-age=31536000, immutable" : "no-cache");
|
|
57
|
+
if (c.req.method === "HEAD") return c.body(null);
|
|
58
|
+
const stream = Readable.toWeb(createReadStream(abs));
|
|
59
|
+
return c.body(stream);
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* SPA fallback(notFound 处理器):
|
|
64
|
+
* - API/鉴权/文件路径 → 404 JSON
|
|
65
|
+
* - 其余 GET(页面请求)→ index.html(前端路由接管)
|
|
66
|
+
*/
|
|
67
|
+
function createSpaFallback(root) {
|
|
68
|
+
return async (c) => {
|
|
69
|
+
const path = new URL(c.req.url).pathname;
|
|
70
|
+
if (path.startsWith("/api") || path.startsWith("/auth") || path.startsWith("/files") || path.startsWith("/healthz") || c.req.method !== "GET") return c.json({
|
|
71
|
+
error: "not_found",
|
|
72
|
+
path
|
|
73
|
+
}, 404);
|
|
74
|
+
const index = join(root, "index.html");
|
|
75
|
+
if (!existsSync(index)) return c.text("not found", 404);
|
|
76
|
+
c.header("content-type", "text/html; charset=utf-8");
|
|
77
|
+
c.header("cache-control", "no-cache");
|
|
78
|
+
const stream = Readable.toWeb(createReadStream(index));
|
|
79
|
+
return c.body(stream);
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
//#region src/create-app.ts
|
|
84
|
+
/**
|
|
85
|
+
* createApp:渐进式组装入口。零注入也能跑(纯 routes),
|
|
86
|
+
* 每注入一个模块解锁一层能力(auth/3p/license/db/storage 实例由 App 构造后传入)。
|
|
87
|
+
* app-kit 不硬依赖这些能力包——类型采用结构化契约(鸭子类型),见 AppDeps。
|
|
88
|
+
*/
|
|
89
|
+
/**
|
|
90
|
+
* 固定中间件链(模板文档逐环解释):
|
|
91
|
+
* basePath → requestId+访问日志 → /healthz(注册于 guard 前,天然豁免)→ licenseGuard
|
|
92
|
+
* → sessionMiddleware → authRoutes → App routes → 静态资源 → SPA fallback → onError
|
|
93
|
+
*/
|
|
94
|
+
function createApp(options = {}) {
|
|
95
|
+
const runtime = options.runtime && options.runtime !== "auto" ? {
|
|
96
|
+
target: options.runtime,
|
|
97
|
+
explicit: true,
|
|
98
|
+
reasons: [`createApp runtime=${options.runtime}`]
|
|
99
|
+
} : detectRuntime$1(process.env);
|
|
100
|
+
const basePath = options.basePath && options.basePath !== "/" ? options.basePath : void 0;
|
|
101
|
+
const app = basePath ? new Hono().basePath(basePath) : new Hono();
|
|
102
|
+
const deps = {};
|
|
103
|
+
if (options.db) deps.db = options.db;
|
|
104
|
+
if (options.storage) deps.storage = options.storage;
|
|
105
|
+
if (options.auth) deps.sessions = options.auth.sessions;
|
|
106
|
+
if (options.thirdParty) deps.platform = options.thirdParty.platform;
|
|
107
|
+
if (options.license) deps.license = options.license.client;
|
|
108
|
+
app.use("*", async (c, next) => {
|
|
109
|
+
const reqId = c.req.header("x-request-id") ?? randomUUID();
|
|
110
|
+
c.set("requestId", reqId);
|
|
111
|
+
c.header("X-Request-Id", reqId);
|
|
112
|
+
const start = Date.now();
|
|
113
|
+
await next();
|
|
114
|
+
options.logger?.info("request", {
|
|
115
|
+
requestId: reqId,
|
|
116
|
+
method: c.req.method,
|
|
117
|
+
path: new URL(c.req.url).pathname,
|
|
118
|
+
status: c.res.status,
|
|
119
|
+
durationMs: Date.now() - start
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
const checks = options.healthz?.checks ?? [];
|
|
123
|
+
app.get("/healthz", async (c) => {
|
|
124
|
+
if (checks.length === 0) return c.json({
|
|
125
|
+
status: "ok",
|
|
126
|
+
runtime: runtime.target
|
|
127
|
+
});
|
|
128
|
+
const results = {};
|
|
129
|
+
let allOk = true;
|
|
130
|
+
for (const { name, check } of checks) try {
|
|
131
|
+
const ok = await check();
|
|
132
|
+
results[name] = ok ? "ok" : "fail";
|
|
133
|
+
if (!ok) allOk = false;
|
|
134
|
+
} catch {
|
|
135
|
+
results[name] = "fail";
|
|
136
|
+
allOk = false;
|
|
137
|
+
}
|
|
138
|
+
return c.json({
|
|
139
|
+
status: allOk ? "ok" : "degraded",
|
|
140
|
+
runtime: runtime.target,
|
|
141
|
+
checks: results
|
|
142
|
+
}, allOk ? 200 : 503);
|
|
143
|
+
});
|
|
144
|
+
if (options.license) app.use("*", options.license.guard);
|
|
145
|
+
if (options.auth) app.use("*", options.auth.sessionMiddleware);
|
|
146
|
+
if (options.auth) app.route("/", options.auth.authRoutes);
|
|
147
|
+
if (options.routes) {
|
|
148
|
+
const businessRoutes = typeof options.routes === "function" ? options.routes(deps) : options.routes;
|
|
149
|
+
app.route("/", businessRoutes);
|
|
150
|
+
}
|
|
151
|
+
if (options.staticDir) {
|
|
152
|
+
app.use("*", createStaticMiddleware(options.staticDir));
|
|
153
|
+
app.notFound(createSpaFallback(options.staticDir));
|
|
154
|
+
} else app.notFound((c) => c.json({
|
|
155
|
+
error: "not_found",
|
|
156
|
+
path: new URL(c.req.url).pathname
|
|
157
|
+
}, 404));
|
|
158
|
+
app.onError((err, c) => {
|
|
159
|
+
options.logger?.error("unhandled", {
|
|
160
|
+
err: err.message,
|
|
161
|
+
path: c.req.path,
|
|
162
|
+
requestId: c.get("requestId")
|
|
163
|
+
});
|
|
164
|
+
if (options.onError) return Promise.resolve(options.onError(err, { path: c.req.path }));
|
|
165
|
+
return c.json({
|
|
166
|
+
error: "internal_error",
|
|
167
|
+
message: err.message
|
|
168
|
+
}, 500);
|
|
169
|
+
});
|
|
170
|
+
return {
|
|
171
|
+
app,
|
|
172
|
+
runtime
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
//#endregion
|
|
176
|
+
export { createApp, createSpaFallback, createStaticMiddleware, detectRuntime };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { t as RuntimeAdapter } from "./adapter-BZXaYf3G.js";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
//#region src/runtime/qw-pages.d.ts
|
|
4
|
+
export interface ExportHandlers {
|
|
5
|
+
/** qw-pages 入口约定未知的三重对冲:default / fetch / app 同时导出 */
|
|
6
|
+
default: (req: Request) => Promise<Response>;
|
|
7
|
+
fetch: (req: Request) => Promise<Response>;
|
|
8
|
+
app: Hono;
|
|
9
|
+
}
|
|
10
|
+
export declare function createQwPagesAdapter(): RuntimeAdapter;
|
|
11
|
+
/** 三重导出对冲:default / fetch / app 同时给,覆盖 qw-pages 未知入口约定 */
|
|
12
|
+
export declare function exportHandlers(app: Hono): ExportHandlers;
|
|
13
|
+
//#endregion
|
package/dist/qw-pages.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { dirname, resolve } from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
//#region src/runtime/qw-pages.ts
|
|
4
|
+
/**
|
|
5
|
+
* qw-pages 适配器:不监听端口;exportHandlers 同时给 default/fetch/app
|
|
6
|
+
* 三种导出形态(对冲 qw-pages 入口约定未知)。
|
|
7
|
+
* 静态资源相对 import.meta.url 定位——自包含 bundle 下 server 产物与 client assets 同级,
|
|
8
|
+
* 故从当前模块 URL 回溯到 dist/ 再进 client/(可被 HOMURA_ASSETS_DIR 覆盖)。
|
|
9
|
+
*/
|
|
10
|
+
function createQwPagesAdapter() {
|
|
11
|
+
return {
|
|
12
|
+
target: "qw-pages",
|
|
13
|
+
async start() {
|
|
14
|
+
throw new Error("qw-pages 形态不监听端口:请用 exportHandlers(app) 导出 default/fetch/app,由平台调用");
|
|
15
|
+
},
|
|
16
|
+
trustProxy: () => true,
|
|
17
|
+
assetsDir: (env) => {
|
|
18
|
+
if (env.HOMURA_ASSETS_DIR) return resolve(env.HOMURA_ASSETS_DIR);
|
|
19
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
return resolve(here, "../client");
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** 三重导出对冲:default / fetch / app 同时给,覆盖 qw-pages 未知入口约定 */
|
|
25
|
+
function exportHandlers(app) {
|
|
26
|
+
const handler = (req) => Promise.resolve(app.fetch(req));
|
|
27
|
+
return {
|
|
28
|
+
default: handler,
|
|
29
|
+
fetch: handler,
|
|
30
|
+
app
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
export { createQwPagesAdapter, exportHandlers };
|
package/dist/serve.d.ts
ADDED
package/dist/serve.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
//#region src/runtime/node.ts
|
|
3
|
+
/**
|
|
4
|
+
* standalone 适配器:@hono/node-server 端口监听。
|
|
5
|
+
* 可选 peer:@hono/node-server(动态 import,不装也能 typecheck 其余部分)。
|
|
6
|
+
* 静态资源由 createApp 的 staticDir 挂载(见 static.ts),adapter 只负责监听与 trust proxy。
|
|
7
|
+
*/
|
|
8
|
+
function createNodeAdapter() {
|
|
9
|
+
return {
|
|
10
|
+
target: "standalone",
|
|
11
|
+
async start(app, opts) {
|
|
12
|
+
const { serve } = await import("@hono/node-server");
|
|
13
|
+
const port = opts.port ?? Number(process.env.PORT ?? 3e3);
|
|
14
|
+
return new Promise((resolveStart) => {
|
|
15
|
+
const server = serve({
|
|
16
|
+
fetch: app.fetch,
|
|
17
|
+
port
|
|
18
|
+
}, () => {
|
|
19
|
+
resolveStart();
|
|
20
|
+
});
|
|
21
|
+
opts.signal?.addEventListener("abort", () => server.close());
|
|
22
|
+
});
|
|
23
|
+
},
|
|
24
|
+
trustProxy: () => process.env.TRUST_PROXY === "1",
|
|
25
|
+
assetsDir: (env) => resolve(env.HOMURA_ASSETS_DIR ?? "dist/client")
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { createNodeAdapter };
|
package/package.json
CHANGED
|
@@ -1,6 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@homurajs/app-kit",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
3
|
+
"version": "26.10.13",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "homura 组装入口:createApp 中间件链装配 + 双形态 RuntimeAdapter(standalone / qw-pages)",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=22.5"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./serve": {
|
|
18
|
+
"types": "./dist/serve.d.ts",
|
|
19
|
+
"import": "./dist/serve.js"
|
|
20
|
+
},
|
|
21
|
+
"./qw-pages": {
|
|
22
|
+
"types": "./dist/qw-pages.d.ts",
|
|
23
|
+
"import": "./dist/qw-pages.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public",
|
|
28
|
+
"registry": "https://registry.npmjs.org/"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@homurajs/core": "26.10.13",
|
|
32
|
+
"hono": "^4.10.0"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@hono/node-server": "^1.19.0"
|
|
36
|
+
},
|
|
37
|
+
"peerDependenciesMeta": {
|
|
38
|
+
"@hono/node-server": {
|
|
39
|
+
"optional": true
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@hono/node-server": "^1.19.0",
|
|
44
|
+
"tsdown": "^0.23.1"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsdown",
|
|
48
|
+
"typecheck": "tsc --noEmit",
|
|
49
|
+
"test": "vitest run"
|
|
50
|
+
}
|
|
6
51
|
}
|