@ubean/app 0.5.1 → 0.6.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -401,10 +401,10 @@ interface UbeanAppOptions {
401
401
  dataCache?: boolean | DataCacheMiddlewareOptions;
402
402
  /**
403
403
  * HTTP / ISR cache store. Default is in-process memory (not shared
404
- * across instances). Pass `createFsCacheStore(dir)` for a Node fs backend.
404
+ * across instances). Pass `loadFsCacheStore(dir)`(Node fs 后端,动态加载)for a Node backend.
405
405
  */
406
406
  cacheStore?: CacheStore;
407
- /** Declarative cache backend. `store: 'fs'` uses `createFsCacheStore`. */
407
+ /** Declarative cache backend. `store: 'fs'` `loadFsCacheStore()`(Node-only,动态加载)。 */
408
408
  cache?: {
409
409
  store?: 'memory' | 'fs';
410
410
  dir?: string;
@@ -442,6 +442,13 @@ export declare class UbeanApp {
442
442
  readonly plugins: UbeanAppPlugin[];
443
443
  readonly options: UbeanAppOptions;
444
444
  private _ready;
445
+ /**
446
+ * Scalar 文档页(`/_scalar`)专用的 CSP:生效 CSP + `{@link SCALAR_SCRIPT_ORIGIN}`。
447
+ *
448
+ * 该页面从 CDN 取脚本,而 CSP 是全局的一份 —— 只在这一条响应上追加它需要的来源,不放宽应用
449
+ * 其余部分的策略。CSP 被关闭时为 `undefined`(不重加头)。构造期算好,`init()` 注册路由时用。
450
+ */
451
+ private _scalarCsp;
445
452
  constructor(options?: UbeanAppOptions);
446
453
  private _setupBaseMiddleware;
447
454
  init(): Promise<this>;
package/dist/index.js CHANGED
@@ -1,14 +1,12 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
- import { existsSync } from "node:fs";
3
2
  import { createI18nMiddleware, ensureLocaleMessages } from "@ubean/i18n";
4
3
  import { SERVER_COMPONENT_ENDPOINT, createServerComponentMiddleware } from "@ubean/islands/server";
5
- import { ACTIONS_ENDPOINT, bindActionContextStorage, buildActionContext, createActionsMiddleware, createRouteRulesMiddleware, registerOpenAPIRoutes, registerRoutes, setInternalFetcher } from "@ubean/routes";
6
- import { createCacheMiddleware, createFsCacheStore, createMemoryStore, resolveRouteCacheRules, useCacheStore } from "@ubean/server/cache";
4
+ import { ACTIONS_ENDPOINT, SCALAR_SCRIPT_ORIGIN, bindActionContextStorage, buildActionContext, createActionsMiddleware, createRouteRulesMiddleware, registerOpenAPIRoutes, registerRoutes, setInternalFetcher } from "@ubean/routes";
5
+ import { createCacheMiddleware, createLazyCacheStore, createMemoryStore, loadFsCacheStore, resolveRouteCacheRules, useCacheStore } from "@ubean/server/cache";
7
6
  import { createDataCacheMiddleware } from "@ubean/server/middleware";
8
7
  import { createWebSocketMiddleware } from "@ubean/server/realtime";
9
- import { createCsrfMiddleware, createSecurityHeadersMiddleware, mergeSecurityHeadersOptions } from "@ubean/server/security";
10
- import { serveStatic } from "@ubean/server/static";
11
- import { UbeanError, errorToResponse, isUbeanError } from "@ubean/shared";
8
+ import { createCsrfMiddleware, createSecurityHeadersMiddleware, extendCspScriptSrc, mergeSecurityHeadersOptions, serializeCsp } from "@ubean/server/security";
9
+ import { UbeanError, errorToResponse, isNodeRuntime, isUbeanError } from "@ubean/shared";
12
10
  import { Hono } from "hono";
13
11
  import { requestId } from "hono/request-id";
14
12
  import { createHooks } from "hookable";
@@ -269,6 +267,13 @@ var UbeanApp = class {
269
267
  plugins;
270
268
  options;
271
269
  _ready = false;
270
+ /**
271
+ * Scalar 文档页(`/_scalar`)专用的 CSP:生效 CSP + `{@link SCALAR_SCRIPT_ORIGIN}`。
272
+ *
273
+ * 该页面从 CDN 取脚本,而 CSP 是全局的一份 —— 只在这一条响应上追加它需要的来源,不放宽应用
274
+ * 其余部分的策略。CSP 被关闭时为 `undefined`(不重加头)。构造期算好,`init()` 注册路由时用。
275
+ */
276
+ _scalarCsp;
272
277
  constructor(options = {}) {
273
278
  this.options = options;
274
279
  this.hono = new Hono({ strict: false });
@@ -286,7 +291,12 @@ var UbeanApp = class {
286
291
  await actionContextAls.run(buildActionContext(c), () => next());
287
292
  });
288
293
  const securityHeaders = resolveToggle(this.options.securityHeaders, true);
289
- if (securityHeaders !== false) this.hono.use("*", createSecurityHeadersMiddleware(mergeSecurityHeadersOptions(DEFAULT_SECURITY_HEADERS, securityHeaders)));
294
+ if (securityHeaders !== false) {
295
+ const mergedSecurity = mergeSecurityHeadersOptions(DEFAULT_SECURITY_HEADERS, securityHeaders);
296
+ this.hono.use("*", createSecurityHeadersMiddleware(mergedSecurity));
297
+ const effectiveCsp = mergedSecurity.contentSecurityPolicy;
298
+ if (effectiveCsp && typeof effectiveCsp === "object") this._scalarCsp = serializeCsp(extendCspScriptSrc(effectiveCsp, [SCALAR_SCRIPT_ORIGIN]));
299
+ }
290
300
  const csrf = resolveToggle(this.options.csrf, true);
291
301
  if (csrf !== false) this.hono.use("*", createCsrfMiddleware({
292
302
  mode: "origin",
@@ -296,7 +306,10 @@ var UbeanApp = class {
296
306
  const dataCache = resolveToggle(this.options.dataCache, true);
297
307
  if (dataCache !== false) this.hono.use("*", createDataCacheMiddleware(dataCache));
298
308
  if (this.options.cacheStore) useCacheStore(this.options.cacheStore);
299
- else if (this.options.cache?.store === "fs") useCacheStore(createFsCacheStore(this.options.cache.dir || ".ubean/cache"));
309
+ else if (this.options.cache?.store === "fs") {
310
+ const fsDir = this.options.cache.dir || ".ubean/cache";
311
+ useCacheStore(createLazyCacheStore(() => loadFsCacheStore(fsDir)));
312
+ }
300
313
  const i18nCfg = this.options.i18nConfig;
301
314
  if (i18nCfg?.enabled !== false && (i18nCfg?.locales?.length ?? 0) > 0 && i18nCfg) {
302
315
  const locales = (i18nCfg.locales || []).map((l) => typeof l === "string" ? l : l.code);
@@ -345,8 +358,10 @@ var UbeanApp = class {
345
358
  await this.hooks.callHook("app:created", this.hono);
346
359
  for (const plugin of this.plugins) if (plugin.setup) await plugin.setup(this);
347
360
  await this.hooks.callHook("app:before:register", this.hono);
348
- if (this.options.publicDir) {
361
+ if (this.options.publicDir && isNodeRuntime()) {
362
+ const { existsSync } = await import("node:fs");
349
363
  const publicDir = isAbsolute(this.options.publicDir) ? this.options.publicDir : this.options.rootDir ? join(this.options.rootDir, this.options.publicDir) : this.options.publicDir;
364
+ const { serveStatic } = await import("@ubean/server/static");
350
365
  if (existsSync(publicDir)) this.hono.use("/*", serveStatic({ publicDir }));
351
366
  }
352
367
  const registerOpts = {
@@ -373,15 +388,18 @@ var UbeanApp = class {
373
388
  colorModeScript: this.options.colorModeScript,
374
389
  cacheStore: this.options.routeRules && Object.keys(this.options.routeRules).length > 0 ? useCacheStore() : void 0
375
390
  };
391
+ if (this.options.openAPI) {
392
+ const openAPIOpts = typeof this.options.openAPI === "object" ? this.options.openAPI : {};
393
+ registerOpenAPIRoutes(this.hono, {
394
+ ...openAPIOpts,
395
+ contentSecurityPolicy: this._scalarCsp
396
+ });
397
+ }
376
398
  await registerRoutes(this, registerOpts);
377
399
  if (this.options.ipxHandler) this.hono.get("/_ipx/*", this.options.ipxHandler);
378
400
  await this._registerSeoConventions();
379
401
  this.hono.on("POST", ACTIONS_ENDPOINT, createActionsMiddleware());
380
402
  this.hono.on("POST", SERVER_COMPONENT_ENDPOINT, createServerComponentMiddleware());
381
- if (this.options.openAPI) {
382
- const openAPIOpts = typeof this.options.openAPI === "object" ? this.options.openAPI : {};
383
- registerOpenAPIRoutes(this.hono, openAPIOpts);
384
- }
385
403
  await this.hooks.callHook("app:after:register", this.hono);
386
404
  setInternalFetcher((req) => applyHandleFetchHook(req, (r) => Promise.resolve(this.hono.fetch(r))));
387
405
  for (const plugin of this.plugins) if (plugin.ready) await plugin.ready(this);
@@ -390,6 +408,7 @@ var UbeanApp = class {
390
408
  }
391
409
  async _registerSeoConventions() {
392
410
  if (this.options.seoConventions === false) return;
411
+ if (!this.options.seoConventionModules && !isNodeRuntime()) return;
393
412
  const srcDir = (typeof this.options.seoConventions === "object" ? this.options.seoConventions : {}).srcDir ?? (this.options.rootDir ? join(this.options.rootDir, "src") : void 0);
394
413
  try {
395
414
  const mod = await import("@ubean/seo/conventions");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ubean/app",
3
- "version": "0.5.1",
3
+ "version": "0.6.0-beta.1",
4
4
  "description": "Hono app factory and server config for ubean (createUbeanApp, defineServer)",
5
5
  "files": [
6
6
  "dist"
@@ -16,26 +16,26 @@
16
16
  }
17
17
  },
18
18
  "dependencies": {
19
- "@ubean/i18n": "0.5.1",
20
- "@ubean/islands": "0.5.1",
21
- "@ubean/routes": "0.5.1",
22
- "@ubean/scan": "0.5.1",
23
- "@ubean/server": "0.5.1",
24
- "@ubean/shared": "0.5.1",
19
+ "@ubean/i18n": "0.6.0-beta.1",
20
+ "@ubean/islands": "0.6.0-beta.1",
21
+ "@ubean/routes": "0.6.0-beta.1",
22
+ "@ubean/scan": "0.6.0-beta.1",
23
+ "@ubean/server": "0.6.0-beta.1",
24
+ "@ubean/shared": "0.6.0-beta.1",
25
25
  "hono": "4.13.7",
26
26
  "hookable": "^6.1.1",
27
27
  "pathe": "^2.0.3"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@types/node": "^26.5.0",
31
- "@ubean/pages": "0.5.1",
32
- "@ubean/seo": "0.5.1",
31
+ "@ubean/pages": "0.6.0-beta.1",
32
+ "@ubean/seo": "0.6.0-beta.1",
33
33
  "typescript": "npm:typescript-native-bridge@latest",
34
34
  "vite-plus": "0.3.1"
35
35
  },
36
36
  "peerDependencies": {
37
- "@ubean/pages": "0.5.1",
38
- "@ubean/seo": "0.5.1"
37
+ "@ubean/pages": "0.6.0-beta.1",
38
+ "@ubean/seo": "0.6.0-beta.1"
39
39
  },
40
40
  "peerDependenciesMeta": {
41
41
  "@ubean/pages": {