@deepseek-ai/dsh-host-frontend-static 0.1.0-rc.8 → 0.1.1-rc.2

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.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/host/frontend-static/README.md
5
- README.md: e53a369384fb8d7fe020e99da0d2cacca59e8afd
6
- README.zh.md: 7018d80182d477f2bdff7b1d8510971dc98c71d5
5
+ README.md: e4f3765a0471566dfb2f6780b1072d3e79788dc5
6
+ README.zh.md: 7672478480f07b760634fef0c8640fcbdc0e6e7e
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  English | [中文](README.zh.md)
4
4
 
5
- SPA dist server for the Web shell: a function plugin (config `{distIndex}`) that claims the [webserver](../webserver/README.md)'s single fallback seat and serves the built frontend directory with the shell's locked semantics — traversal outside the dist root is 403, any miss falls back to `index.html` with HTTP 200 (SPA routing), unknown extensions ship as `application/octet-stream`, and non-GET/HEAD without a matching named route is 405. Every index response runs through the webserver's registered index taps (`applyIndexTaps`), which is how the boot manifest reaches the page. `distIndex` is an assembly fact of the composing application: [`dsh-web-app`](../../bundle/web-app/README.md) resolves it through the frontend package's exports and mounts this plugin; a deployment never hardcodes it.
5
+ SPA dist server for the Web shell: a function plugin (config `{distIndex}`) that claims the [webserver](../webserver/README.md)'s single fallback seat and serves the built frontend directory with explicit index entry points. While `distIndex` is readable, the dist root and configured index path render `index.html` with HTTP 200; other existing files are served directly. An absent or non-file target inside the dist root, including a missing configured index, returns an empty 404; traversal outside the dist root returns 403, unknown extensions ship as `application/octet-stream`, and non-GET/HEAD without a matching named route returns 405. Every successful index response is rendered through the webserver's `renderIndex` — structured injection rows first, then the raw index taps — which is how the boot manifest reaches the page. `distIndex` is an assembly fact of the composing application: [`dsh-web-app`](../../bundle/web-app/README.md) resolves it through the frontend package's exports and mounts this plugin; a deployment never hardcodes it.
6
6
 
7
7
  The fallback seat is single-owner (a second claim throws) and effect-scoped: disposing the plugin's fiber releases the seat, after which the unclaimed webserver answers 404.
8
8
 
@@ -17,3 +17,4 @@ None; this package neither assembles nor sends a provider request.
17
17
  ## Known Limitations and Deferred Work
18
18
 
19
19
  - **The starter MIME table is minimal** — it covers the Vite-emitted asset set plus the shipped PWA manifest; other extensions fall back to `application/octet-stream` until an asset class actually ships.
20
+ - **Pathname routing is explicit** — the current client enters through the root or configured index path and has no History API pathname routes. Adding one requires an explicit server rule and real-composition coverage rather than a broad fallback for every miss.
package/README.zh.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [English](README.md) | 中文
4
4
 
5
- Web 壳的 SPA dist 服务器:一个函数插件(配置为 `{distIndex}`),占据 [webserver](../webserver/README.md) 的唯一回退席位,并按壳层锁定的语义服务已构建的前端目录——越出 dist 根目录的遍历返回 403,任何未命中项都以 HTTP 200 回退到 `index.html`(SPA 路由),未知扩展名按 `application/octet-stream` 提供,GET/HEAD 之外的方法在没有匹配的具名路由时返回 405。每个 index 响应都会经过 webserver 已注册的 index 转换器(`applyIndexTaps`),启动 manifest(元数据清单)就是经这条路径送达页面的。`distIndex` 是组合应用的组装事实:[`dsh-web-app`](../../bundle/web-app/README.md) 通过前端包的 exports 解析它并挂载本插件;部署绝不硬编码它。
5
+ Web 壳的 SPA dist 服务器:一个函数插件(配置为 `{distIndex}`),占据 [webserver](../webserver/README.zh.md) 的唯一回退席位,并通过显式 index 入口服务已构建的前端目录。`distIndex` 可读时,dist 根目录和配置的 index 路径以 HTTP 200 渲染 `index.html`;其他现有文件直接提供。dist 根目录内缺失或不是文件的目标——包括缺失的配置 index——返回空的 404;越出 dist 根目录的遍历返回 403,未知扩展名按 `application/octet-stream` 提供,GET/HEAD 之外的方法在没有匹配的具名路由时返回 405。每个成功的 index 响应都经 webserver 的 `renderIndex` 渲染——先结构化注入行、后原始 index 转换器——启动 manifest(元数据清单)就是经这条路径送达页面的。`distIndex` 是组合应用的组装事实:[`dsh-web-app`](../../bundle/web-app/README.zh.md) 通过前端包的 exports 解析它并挂载本插件;部署绝不硬编码它。
6
6
 
7
7
  回退席位只有单一所有者(第二次占据会抛错),并受 effect 作用域约束:dispose(资源释放)插件的 fiber 会释放席位,此后无人占据的 webserver 回答 404。
8
8
 
@@ -17,3 +17,4 @@ Web 壳的 SPA dist 服务器:一个函数插件(配置为 `{distIndex}`)
17
17
  ## 已知限制与延期工作
18
18
 
19
19
  - **初始 MIME 表很精简**:它覆盖 Vite 输出的资产集合及实际交付的 PWA manifest;其他扩展名在相应资产类别实际发布前都会回退到 `application/octet-stream`。
20
+ - **pathname 路由是显式的**:当前客户端只从根路径或配置的 index 路径进入,没有 History API pathname 路由。添加这类路由时必须同时添加显式服务器规则和真实组合测试,不得为每个未命中项启用宽泛回退。
package/lib/index.js CHANGED
@@ -4,14 +4,14 @@ import z from "@deepseek-ai/schemastery";
4
4
  //#region lib/types/index.js
5
5
  /**
6
6
  * @deepseek-ai/dsh-host-frontend-static — SPA dist server over the webserver
7
- * fallback seat: serves the built frontend directory with the semantics the
8
- * Web shell locked at step1 — traversal outside the dist root is 403, any
9
- * miss falls back to index.html with HTTP 200 (SPA routing), unknown
10
- * extensions ship as octet-stream, non-GET/HEAD is 405. Every index response
11
- * runs through the webserver's registered index taps (boot-manifest
12
- * injection). The dist location is workspace knowledge of the composing
13
- * application, so `distIndex` is typically supplied through a `!!js`
14
- * expression, never hardcoded by a deployment.
7
+ * fallback seat: serves the built frontend directory with explicit index
8
+ * entry points. A readable index renders at the dist root and configured index
9
+ * path; missing paths return 404, traversal outside the dist root is 403,
10
+ * unknown extensions ship as octet-stream, and non-GET/HEAD is 405. Every
11
+ * index response runs through the webserver's index render (structured
12
+ * injection rows, then raw taps). The dist location is workspace knowledge of
13
+ * the composing application, so `distIndex` is typically supplied through a
14
+ * `!!js` expression, never hardcoded by a deployment.
15
15
  * @module @deepseek-ai/dsh-host-frontend-static
16
16
  */
17
17
  /** Stable Cordis plugin name. */
@@ -19,8 +19,9 @@ const name = "frontend-static";
19
19
  /** Service required before the fallback seat can be claimed. */
20
20
  const inject = ["webServer"];
21
21
  const Config = z.object({ distIndex: z.string().required() });
22
+ const HTML_MIME = "text/html; charset=utf-8";
22
23
  const MIME = {
23
- ".html": "text/html; charset=utf-8",
24
+ ".html": HTML_MIME,
24
25
  ".js": "text/javascript; charset=utf-8",
25
26
  ".css": "text/css; charset=utf-8",
26
27
  ".svg": "image/svg+xml",
@@ -28,14 +29,19 @@ const MIME = {
28
29
  ".map": "application/json",
29
30
  ".webmanifest": "application/manifest+json"
30
31
  };
32
+ const STATIC_MISS_CODES = new Set([
33
+ "ENOENT",
34
+ "EISDIR",
35
+ "ENOTDIR"
36
+ ]);
31
37
  /**
32
38
  * Serve one GET/HEAD static request from the dist root.
33
39
  * @param pathname - decoded URL pathname of the request.
34
40
  * @param res - the node:http response to write.
35
41
  * @param distRoot - absolute dist root directory (resolved by the caller).
36
42
  * @param distIndex - absolute path of index.html inside distRoot.
37
- * @param renderIndex - produces the index.html body (index-tap injection) for
38
- * `/` and every SPA fallback.
43
+ * @param renderIndex - produces the index.html body (structured injection
44
+ * rendering) for the dist root and configured index path.
39
45
  */
40
46
  async function serveStatic(pathname, res, distRoot, distIndex, renderIndex) {
41
47
  const target = resolve(normalize(join(distRoot, pathname)));
@@ -44,22 +50,24 @@ async function serveStatic(pathname, res, distRoot, distIndex, renderIndex) {
44
50
  res.end();
45
51
  return;
46
52
  }
47
- const serveIndex = async () => {
48
- const body = await renderIndex();
49
- res.writeHead(200, { "content-type": MIME[".html"] });
50
- res.end(body);
51
- };
52
- if (target === distRoot || target === distIndex) {
53
- await serveIndex();
54
- return;
55
- }
53
+ let body;
54
+ let type;
56
55
  try {
57
- const body = await readFile(target);
58
- res.writeHead(200, { "content-type": MIME[extname(target)] ?? "application/octet-stream" });
59
- res.end(body);
60
- } catch {
61
- await serveIndex();
56
+ if (target === distRoot || target === distIndex) {
57
+ body = await renderIndex();
58
+ type = HTML_MIME;
59
+ } else {
60
+ body = await readFile(target);
61
+ type = MIME[extname(target)] ?? "application/octet-stream";
62
+ }
63
+ } catch (error) {
64
+ if (!STATIC_MISS_CODES.has(error.code)) throw error;
65
+ res.writeHead(404);
66
+ res.end();
67
+ return;
62
68
  }
69
+ res.writeHead(200, { "content-type": type });
70
+ res.end(body);
63
71
  }
64
72
  /**
65
73
  * Claim the webserver fallback seat and serve the dist.
@@ -69,7 +77,7 @@ async function serveStatic(pathname, res, distRoot, distIndex, renderIndex) {
69
77
  function apply(ctx, config) {
70
78
  const distIndex = config.distIndex;
71
79
  const distRoot = dirname(distIndex);
72
- const renderIndex = async () => ctx.webServer.applyIndexTaps(await readFile(distIndex, "utf8"));
80
+ const renderIndex = async () => ctx.webServer.renderIndex(await readFile(distIndex, "utf8"));
73
81
  ctx.effect(() => ctx.webServer.registerFallback(async (req, res) => {
74
82
  if (req.method !== "GET" && req.method !== "HEAD") {
75
83
  res.writeHead(405);
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * @deepseek-ai/dsh-host-frontend-static — SPA dist server over the webserver
3
- * fallback seat: serves the built frontend directory with the semantics the
4
- * Web shell locked at step1 — traversal outside the dist root is 403, any
5
- * miss falls back to index.html with HTTP 200 (SPA routing), unknown
6
- * extensions ship as octet-stream, non-GET/HEAD is 405. Every index response
7
- * runs through the webserver's registered index taps (boot-manifest
8
- * injection). The dist location is workspace knowledge of the composing
9
- * application, so `distIndex` is typically supplied through a `!!js`
10
- * expression, never hardcoded by a deployment.
3
+ * fallback seat: serves the built frontend directory with explicit index
4
+ * entry points. A readable index renders at the dist root and configured index
5
+ * path; missing paths return 404, traversal outside the dist root is 403,
6
+ * unknown extensions ship as octet-stream, and non-GET/HEAD is 405. Every
7
+ * index response runs through the webserver's index render (structured
8
+ * injection rows, then raw taps). The dist location is workspace knowledge of
9
+ * the composing application, so `distIndex` is typically supplied through a
10
+ * `!!js` expression, never hardcoded by a deployment.
11
11
  * @module @deepseek-ai/dsh-host-frontend-static
12
12
  */
13
13
  import type { ServerResponse } from 'node:http';
@@ -29,8 +29,8 @@ export declare const Config: z<Config>;
29
29
  * @param res - the node:http response to write.
30
30
  * @param distRoot - absolute dist root directory (resolved by the caller).
31
31
  * @param distIndex - absolute path of index.html inside distRoot.
32
- * @param renderIndex - produces the index.html body (index-tap injection) for
33
- * `/` and every SPA fallback.
32
+ * @param renderIndex - produces the index.html body (structured injection
33
+ * rendering) for the dist root and configured index path.
34
34
  */
35
35
  export declare function serveStatic(pathname: string, res: ServerResponse, distRoot: string, distIndex: string, renderIndex: () => Promise<string>): Promise<void>;
36
36
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-host-frontend-static",
3
- "description": "SPA dist server for the Web shell: owns the webserver fallback seat, serving the built frontend with index-tap injection, traversal rejection, and SPA index fallback",
4
- "version": "0.1.0-rc.8",
3
+ "description": "SPA dist server for the Web shell: owns the webserver fallback seat, serving explicit index entries and static assets with traversal rejection and 404 misses",
4
+ "version": "0.1.1-rc.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -32,17 +32,17 @@
32
32
  ],
33
33
  "license": "MIT",
34
34
  "peerDependencies": {
35
- "@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.8",
36
- "@deepseek-ai/dsh-invariants": "^0.1.0-rc.8",
37
- "@deepseek-ai/cordis": "^4.0.1"
35
+ "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
36
+ "@deepseek-ai/cordis": "^4.0.1",
37
+ "@deepseek-ai/dsh-host-webserver": "^0.1.1-rc.2"
38
38
  },
39
39
  "dependencies": {
40
40
  "@deepseek-ai/schemastery": "^3.18.1"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@deepseek-ai/cordis-plugin-loader": "^1.0.2",
44
- "@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.8",
45
- "@deepseek-ai/dsh-invariants": "^0.1.0-rc.8",
44
+ "@deepseek-ai/dsh-host-webserver": "^0.1.1-rc.2",
45
+ "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
46
46
  "@deepseek-ai/cordis": "^4.0.1"
47
47
  }
48
48
  }