@jayyuen66/dsh-plugin-shared 0.1.7 → 0.1.8

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 JayYuen
3
+ Copyright (c) 2026 JayYuen666
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -0,0 +1 @@
1
+ import{randomUUID as e}from"node:crypto";import{realpathSync as t}from"node:fs";import{mkdir as n,readFile as r,rename as i,rm as a,writeFile as o}from"node:fs/promises";import{setTimeout as s}from"node:timers/promises";import c from"node:path";function l(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function u(e){return l(e)?e:{}}async function d(e){let t=c.join(e,`package.json`);try{return JSON.parse(await r(t,`utf8`))}catch(e){throw Error(`读 ${t} 失败:${String(e)}`,{cause:e})}}function f(e){let t=e.indexOf(`/`);if(!e.startsWith(`@`))return t===-1?e:e.slice(0,t);let n=t===-1?-1:e.indexOf(`/`,t+1);return n===-1?e:e.slice(0,n)}function p(e){return e?.type===`chunk`&&e.isEntry===!0&&typeof e.code==`string`&&e.code.trim()!==``}function m(e,t){if(e.length!==1){let n=e.map(e=>`${e.type}:${e.fileName}`).join(`, `);throw Error(`rolldown 为 ${t} 产出了 ${e.length} 项输出(期望恰好一个 entry chunk):${n}`)}let n=e.at(0);if(!p(n))throw Error(`rolldown 为 ${t} 产出的不是单个带正文的 entry chunk`);return n}const h=/* @__PURE__ */ new Set([`EPERM`,`EBUSY`,`EACCES`]),g=[50,100,200];async function _(e,t,n=0){try{await i(e,t)}catch(r){let i=typeof r==`object`&&r&&`code`in r?r.code:void 0;if(typeof i!=`string`||!h.has(i))throw r;let a=g.at(n);if(a===void 0)throw r;await s(a),await _(e,t,n+1)}}async function v(t,r){let i=c.join(c.dirname(t),`.tmp`);await n(i,{recursive:!0});let s=c.join(i,`${c.basename(t)}.${process.pid}.${e()}.tmp`);try{await o(s,r,`utf8`),await _(s,t)}catch(e){try{await a(s,{force:!0})}catch{}throw Error(`写产物失败:${t}(草稿 ${s}):${String(e)}`,{cause:e})}}async function y(e){try{await e.close()}catch(e){console.error(`释放 rolldown bundle 失败(不影响上面那条构建错因):`,e)}}function b(e){let n=process.argv.at(1);if(n===void 0||n.length===0||c.basename(n)!==c.basename(e))return!1;try{return t(n)===t(e)}catch(e){throw Error(`无法解析本脚本自身的路径(argv[1]=${n}):构建未发生,不要当成成功。`,{cause:e})}}export{u as asRecord,y as closeQuietly,b as invokedDirectly,l as isRecord,f as packageNameOf,d as readManifest,m as singleEntryChunk,v as writeArtifact};
@@ -0,0 +1,2 @@
1
+ import{asRecord as e,closeQuietly as t,packageNameOf as n,readManifest as r,singleEntryChunk as i}from"./build-artifacts.js";import{t as a}from"./canonicalize-region-paths-BxladpX3.js";import{readFile as o}from"node:fs/promises";import s from"node:path";const c=[`react`,`react/jsx-runtime`,`react-dom`,`react-dom/client`];function l(e){let t=e.replaceAll(/^\/\/#region .*$/gmu,e=>e.replaceAll(`\\`,`/`).replace(/^\/\/#region (?:(?:\.\.?\/)+)?node_modules\/\.pnpm\/[^/]+\/node_modules\//u,`//#region node_modules/`)),n=/node_modules\/\.pnpm\//u.exec(t);if(n!==null){let e=Math.max(0,n.index-40);throw Error(`产物里仍有 pnpm store 路径未折叠(出现在 …${JSON.stringify(t.slice(e,e+100))}…)。折叠判据与 rolldown 写下的形式不再匹配,client 产物的字节会随安装布局变化。`)}return t}async function u(e){return o(e,`utf8`)}function d(e){let t=e.name;if(typeof t!=`string`||t===``)throw Error(`package.json 的 name 缺失或非字符串。client 产物的 id 必须等于 dsh client 模块表的键(bundle 包名)。`);if(!/^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/u.test(t))throw Error(`package.json 的 name ${JSON.stringify(t)} 不符合 npm 包名字符集。`);return t}function f(e){let t=new Set(e);return e=>{if(t.has(e))return!0;let r=n(e);if(r===`react`||r===`react-dom`)throw Error(`${e} 是 react 家族说明符但不在外置清单里。外置会得到宿主模块表里没有的键(加载期报错),内联会得到第二份 React(hooks 静默失效);真要引入子路径,先把它加进外置清单并确认宿主模块表里有同名项。`);return!1}}async function p(n){let o=d(e(await r(n.root))),p=n.entry??s.join(`src`,`client-entry.ts`),m=n.minify??!1,h=n.comments??!m,g={format:`cjs`,...m?{minify:!0}:{},...h?{}:{comments:!1}},_=await n.rolldown({input:s.join(n.root,p),external:f(n.externals??c),platform:`browser`,cwd:n.root,resolve:{extensions:[`.tsx`,`.ts`,`.jsx`,`.js`,`.mjs`,`.json`]}}),v;try{let{output:e}=await _.generate(g),t=i(e,p);v=l(a(t.code.trimEnd(),n.root,process.cwd()))}catch(e){throw await t(_),e}return await _.close(),await n.verifyName?.({pkgName:o,body:v,readSource:e=>u(s.join(n.root,e))}),[n.header,`window.__ModuleLoader__.load({`,` id: ${JSON.stringify(o)},`,` factory: (require) => {`,` 'use strict';`,` const module = { exports: {} };`,` let exports = module.exports;`,v,` return module.exports;`,` },`,`});`,``].join(`
2
+ `)}export{c as REACT_RUNTIME_MODULES,p as buildClientBundle,f as clientExternalPredicate,l as collapseStorePaths};
@@ -0,0 +1 @@
1
+ import{asRecord as e,closeQuietly as t,isRecord as n,packageNameOf as r,readManifest as i,singleEntryChunk as a}from"./build-artifacts.js";import{t as o}from"./canonicalize-region-paths-BxladpX3.js";import s from"node:path";function c(e,t){let r=e[t];if(r===void 0)return[];if(!n(r))throw TypeError(`package.json 的 ${t} 不是对象(实为 ${Array.isArray(r)?`array`:typeof r})。折成空集会让外部化名单失效、依赖全部内联进产物,所以拒绝继续。`);return Object.keys(r).map(e=>{if(typeof r[e]!=`string`)throw TypeError(`package.json 的 ${t}.${e} 不是字符串范围(收到 ${typeof r[e]})。静默跳过会让这个包被当成内部依赖整包内联,而内联恰好复制它的模块级状态。`);return e})}function l(e,t){let n=[...c(e,`dependencies`),...c(e,`peerDependencies`)];return t&&n.push(...c(e,`optionalDependencies`)),new Set(n)}function u(e){return Array.isArray(e)&&e.length>0&&e.every(e=>typeof e==`string`&&e.length>0)}async function d(n){let c=l(e(await i(n.root)),n.externalizeOptional??!0);if(c.size===0)throw Error(`package.json 的 dependencies / peerDependencies 都取不到字符串条目,外部化集合为空——依赖会被整包内联。`);let d=e=>c.has(r(e)),f=n.resolveExtensions??[`.ts`,`.mjs`,`.js`],p=n.minify??!1,m=n.comments??!p,h={format:`esm`,...p?{minify:!0}:{},...m?{}:{comments:!1}},g=async e=>{let r=await n.rolldown({input:s.join(n.root,e.source),external:d,platform:`node`,cwd:n.root,resolve:{extensions:[...f]},...n.plugins===void 0?{}:{plugins:n.plugins}}),i;try{let{output:t}=await r.generate(h),c=a(t,e.source);if(!u(c.moduleIds))throw TypeError(`rolldown 为 ${e.source} 产出的 entry chunk 的 moduleIds 不是"非空字符串数组"`);let l=o(c.code.trimEnd(),n.root,process.cwd());i={source:e.source,output:e.output,file:s.join(n.root,e.output),code:`${e.header}${l}\n`,inlinedModules:c.moduleIds}}catch(e){throw await t(r),e}return await r.close(),i};return Promise.all(n.entries.map(e=>g(e)))}export{d as buildHostEntries};
@@ -0,0 +1,2 @@
1
+ import{readFile as e,readdir as t,rename as n,rm as r,writeFile as i}from"node:fs/promises";import a from"node:path";import{execFileSync as o}from"node:child_process";const s=/(?<=\bfrom\s*)["']\.{1,2}\/[^"'\n]*?(?<!\.d)\.(?:mts|cts|ts)["']/gu,c=/(?<=\bimport\(\s*)["']\.{1,2}\/[^"'\n]*?(?<!\.d)\.(?:mts|cts|ts)["']/gu,l=/(?<=\bdeclare\s+module\s*)["']\.{1,2}\/[^"'\n]*?(?<!\.d)\.(?:mts|cts|ts)["']/gu,u={cts:`cjs`,mts:`mjs`,ts:`js`},d=/\.(?<extension>mts|cts|ts)(?=["']$)/u;function f(e){return e.replace(d,(e,t)=>`.${u[t]}`)}function p(e){return e.replaceAll(s,f).replaceAll(c,f).replaceAll(l,f)}function m(e){return typeof e==`string`?e:Buffer.isBuffer(e)?e.toString(`utf8`):``}function h(e,t,n){try{o(e,t,{cwd:n,stdio:[`ignore`,`pipe`,`pipe`],encoding:`utf8`})}catch(e){let t=(e instanceof Error&&`stdout`in e&&`stderr`in e?[e.stdout,e.stderr]:[]).map(e=>m(e)).join(`
2
+ `).trim();throw Error(t===``?String(e):t,{cause:e})}}async function g(e,n){let r=await t(e,{withFileTypes:!0}),i=r.filter(e=>!e.isDirectory()&&e.name.endsWith(`.d.ts`)).map(e=>`${n}${e.name}`),o=await Promise.all(r.filter(e=>e.isDirectory()).map(t=>g(a.join(e,t.name),`${n}${t.name}/`)));return[...i,...o.flat()]}async function _(t){let o=t.scratch??a.join(t.root,`.tmp`,`types`),s=t.outDir??a.join(t.root,`types`),c=t.tsconfig??`tsconfig.types.json`;await r(o,{recursive:!0,force:!0});try{h(process.execPath,[t.tscEntry,`-p`,c,`--outDir`,o],t.root);let l=await g(o,``);return await Promise.all(l.map(async t=>{let n=a.join(o,t),r=await e(n,`utf8`);await i(n,p(r),`utf8`)})),await r(s,{recursive:!0,force:!0}),await n(o,s),l}catch(e){throw await r(o,{recursive:!0,force:!0}),e}}export{_ as buildDeclarations,p as canonicalizeDeclarationSpecifiers,g as listDeclarations,h as runTool,m as textOf};
@@ -0,0 +1 @@
1
+ const e=/^\/\/#region .*$/gmu,t=/^[^\s'"`]+$/u,n=/^(?:\.\.?\/)+/u,r=/[/\\]+/u;function i(e){let t=e.replace(/[/\\]+$/u,``),n=Math.max(t.lastIndexOf(`/`),t.lastIndexOf(`\\`));return n===-1?t:t.slice(n+1)}function a(e){let t=e.replace(/[/\\]+$/u,``),n=Math.max(t.lastIndexOf(`/`),t.lastIndexOf(`\\`));return n===-1?t:t.slice(0,n)}function o(e){let t=i(e);return[i(a(e)),t].filter(e=>e.length>0)}function s(e,t){let n=e;for(let e of t)n.startsWith(`${e}/`)&&(n=n.slice(e.length+1));return n}function c(e){return e.split(r)}function l(e){let t=[...e];for(;t.length>1&&t.at(-1)===``;)t.pop();return t}function u(e){return e.startsWith(`/`)||e.startsWith(`\\`)||/^[A-Za-z]:[\\/]/u.test(e)}function d(e,t){let n=u(e);if(n!==u(t))return null;let r=l(c(e)),i=l(c(t));if(n&&r[0]!==i[0])return null;let a=0;for(;a<r.length&&a<i.length&&r[a]===i[a];)a+=1;return[...Array.from({length:r.length-a},()=>`..`),...i.slice(a)]}function f(e){if(e===null||e.length===0)return``;let t=0;for(;t<e.length&&e[t]===`..`;)t+=1;return t===e.length?``:e.slice(t).join(`/`)}function p(e,t){return{own:f(d(e,t)),parent:f(d(e,a(t)))}}function m(e,t){return t.own.length>0&&e.startsWith(`${t.own}/`)?e.slice(t.own.length+1):t.parent.length>0&&e.startsWith(`${t.parent}/`)?e.slice(t.parent.length+1):e}function h(e,r,i){let a=e.slice(10);if(!t.test(a))return e;let o=a.replace(n,``);return`//#region ${i===null?s(o,r):m(o,i)}`}function g(t,n,r){let i=o(n);if(r===void 0)return i.length===0?t:t.replaceAll(e,e=>h(e,i,null));let a=p(r,n);return t.replaceAll(e,e=>h(e,i,a))}export{g as t};
@@ -0,0 +1,116 @@
1
+ import type { InputOptions } from "rolldown";
2
+ /**
3
+ * 对象守卫:数组与非对象都不算 Record。
4
+ * @param raw - JSON.parse 之后的任意值
5
+ * @returns 是否为普通对象
6
+ */
7
+ export declare function isRecord(raw: unknown): raw is Record<string, unknown>;
8
+ /**
9
+ * 从 JSON.parse 的 unknown 投影为 Record(非对象 → 空对象兜底),避免 any 索引。
10
+ * @param raw - JSON.parse 之后的任意值
11
+ * @returns 普通对象;输入不是对象时为空对象
12
+ */
13
+ export declare function asRecord(raw: unknown): Record<string, unknown>;
14
+ /**
15
+ * 读包根的 package.json,带上下文的失败信息。
16
+ *
17
+ * 惰性(调用时才读)而不是在消费方模块顶层读:顶层一次 JSON 语法错误会以未捕获的顶层
18
+ * await 错误 + 裸 SyntaxError 收场,读者看不出是哪个文件坏了;而测试 import 构建模块时
19
+ * 只为拿构建函数,不该连带付一次磁盘 I/O。
20
+ * @param root - 包根绝对路径
21
+ * @returns 解析后的 manifest
22
+ */
23
+ export declare function readManifest(root: string): Promise<unknown>;
24
+ /**
25
+ * 说明符的包名段:`@scope/pkg/sub` → `@scope/pkg`,`pkg/sub` → `pkg`。
26
+ *
27
+ * external 的字符串项是精确匹配,所以按「包名段」判定而不是枚举子路径。
28
+ * @param id - 说明符
29
+ * @returns 包名段
30
+ */
31
+ export declare function packageNameOf(id: string): string;
32
+ /**
33
+ * rolldown 输出项的最小投影:单文件产物只关心这几项。
34
+ * `moduleIds` 之类不进投影——本类型只判形状,多一层声明就多一处与 rolldown 版本耦合的面,
35
+ * 而调用方拿到的仍是原始输出项,不缺字段。
36
+ */
37
+ export interface OutputItem {
38
+ readonly type: string;
39
+ readonly fileName: string;
40
+ readonly code?: string | undefined;
41
+ readonly isEntry?: boolean | undefined;
42
+ readonly moduleIds?: readonly string[] | undefined;
43
+ }
44
+ /**
45
+ * 单文件产物只允许一个 entry chunk:多余 chunk / asset 不落盘,产物要到运行时才因缺模块而炸。
46
+ * 这是「静默不渲染」同一类难查故障,所以在构建期就失败。
47
+ * @param output - rolldown 的 generate 输出清单
48
+ * @param source - 入口源码名(错误信息点名用)
49
+ * @returns 唯一的那个 entry chunk
50
+ */
51
+ export declare function singleEntryChunk(output: readonly OutputItem[], source: string): OutputItem & {
52
+ code: string;
53
+ };
54
+ /**
55
+ * 原子落盘:目标目录下 `.tmp/` 草稿 + rename;失败时清掉草稿再抛。
56
+ *
57
+ * 草稿落在**目标文件所在目录**下的 `.tmp/`,不是 `os.tmpdir()`,也不是产物旁边的裸名:
58
+ * - 不用 `os.tmpdir()`:rename 只在同一文件系统内原子,跨设备会以 EXDEV 失败,而系统临时
59
+ * 目录常被挂到另一个分区/卷;
60
+ * - 跟随目标目录而不是写死包根:本函数住在公共库里,产物落点由调用方决定;
61
+ * - 不用「产物名 + 后缀」直接落在产物旁边:那个名字通常不在 `.gitignore` 里,构建被中断
62
+ * 时半份产物会以未跟踪文件出现在 `git status`,被误当成源码改动提交。`.tmp/` 是各包
63
+ * 统一忽略的目录,不需要为构建残留单独扩忽略规则。
64
+ *
65
+ * 失败信息带上两个路径并保留 cause:裸 `ENOENT … open '…/<uuid>.tmp'` 把注意力引到草稿名上,
66
+ * 读者还得自己剥出真正的产物路径。
67
+ * @param target - 目标文件绝对路径
68
+ * @param text - 产物正文
69
+ */
70
+ export declare function writeArtifact(target: string, text: string): Promise<void>;
71
+ /** 异常路径上释放 rolldown bundle 的最小形状。 */
72
+ export interface CloseableBundle {
73
+ readonly close: () => Promise<void>;
74
+ }
75
+ /** generate 交给调用方的最小输出形状(属性式签名,真实 bundle 结构满足)。 */
76
+ export interface RolldownBundle extends CloseableBundle {
77
+ readonly generate: (options: {
78
+ format: "esm" | "cjs";
79
+ minify?: boolean;
80
+ comments?: boolean;
81
+ }) => Promise<{
82
+ output: readonly OutputItem[];
83
+ }>;
84
+ }
85
+ /**
86
+ * 调用方注入的 rolldown 构造器。
87
+ *
88
+ * 注入而不是由本包声明依赖:rolldown 是各包构建期自己的 devDependency,本包作为库
89
+ * 不该为构建工具声明运行期依赖面。参数类型借 rolldown 自己的 InputOptions(仅类型
90
+ * 导入,产物里没有它)。
91
+ */
92
+ export type RolldownFactory = (options: InputOptions) => Promise<RolldownBundle>;
93
+ /**
94
+ * 异常路径上释放 rolldown bundle:close 失败只记日志,不覆盖调用方正在抛的原始错因。
95
+ *
96
+ * bundle 持有原生资源,generate 或输出守卫抛错时同样要释放;但那时唯一有价值的日志是
97
+ * 「构建为什么失败」,让一条关于资源释放的噪音顶掉它,排障就从「读一行」变成「猜」。
98
+ * 成功路径不要用本函数:那里 close 失败仍应判红,资源没释放是构建器的问题。
99
+ * @param bundle - 待释放的 bundle
100
+ */
101
+ export declare function closeQuietly(bundle: CloseableBundle): Promise<void>;
102
+ /**
103
+ * 直跑守卫:本脚本是否被当作入口直接执行(而不是被测试 import)。
104
+ *
105
+ * argv[1] 可能缺席或是软链路径(pnpm 场景、`/tmp` → `/private/tmp`),两边折成 realpath
106
+ * 再比;裸字符串比较会让直跑分支被静默跳过,脚本挂在 prepare / prepack 上时等于发布
107
+ * 过期产物而不报错。
108
+ *
109
+ * 两种失败必须分开:argv[1] 不是本文件(被 import、或被别的脚本调用)→ 安静返回 false;
110
+ * 基名就是本文件却解不出 realpath(软链断了、父目录 EACCES、argv[1] 已被删除)→ 直接抛。
111
+ * 后者若也返回 false,脚本会既不构建也不报错、以 0 退出,发布链路认为构建成功而磁盘
112
+ * 产物仍是旧的。
113
+ * @param moduleFile - 调用方的 `import.meta.filename`
114
+ * @returns 是否直接运行
115
+ */
116
+ export declare function invokedDirectly(moduleFile: string): boolean;
@@ -0,0 +1,79 @@
1
+ import type { RolldownFactory } from "./build-artifacts.js";
2
+ /**
3
+ * 宿主 seed 表里 react 家族的全部说明符(客户端产物只外置宿主真给得出的模块)。
4
+ * 与宿主客户端自己的模块表同源:那张表里 react 家族只有这四条。外置面更大的包
5
+ * (宿主外壳基座扩容后)由调用方在 `externals` 里给全量清单。
6
+ */
7
+ export declare const REACT_RUNTIME_MODULES: readonly string[];
8
+ /**
9
+ * 把 `//#region` 行里的 pnpm 内容寻址段(store 目录里带「名@版_哈希」的那一层)折成
10
+ * 普通的 `node_modules/` 一层。
11
+ *
12
+ * 为什么 client 侧需要而 host 侧不需要:host 把依赖全外部化,产物里没有依赖的模块标记;
13
+ * client 必须把共享库的模块**内联**(浏览器解析不了裸说明符),于是 rolldown 写下的标记里
14
+ * 烙着依赖版本与本机 store 哈希。带着它发布,同一份源码在不同机器上产出不同字节(跨机器
15
+ * 不可复现),还把安装布局公开进公共 tarball。
16
+ *
17
+ * 折叠必须覆盖整段而不是只换中段:store 段之上还可能带着 `../` 前缀(依赖落在工作区根
18
+ * 一侧时就是这样),只换中段会留下 `../../node_modules/<包名>/…`,产物字节仍然随检出
19
+ * 深度变,等于没折。所以判据锚在 `//#region ` 之后、允许任意个相对段。
20
+ *
21
+ * 反斜杠先归一:Windows 上 rolldown 可能写 `\`,而这行是注释、整行只有路径,归一不会
22
+ * 误伤代码。折叠完再验一遍「整份产物里不剩 `.pnpm` 段」——静默未命中是这条折法最坏的
23
+ * 失效形态:判据锚在 region 行的形状上,而 rolldown 一旦改写成别的行形态,折叠会一处都
24
+ * 不命中,兜底若也跟着只用同一套形状假设,就会一起静默失明。`node_modules/.pnpm/`
25
+ * 这个子串与行形态无关。
26
+ * @param code - chunk 正文
27
+ * @returns 折叠后的正文
28
+ */
29
+ export declare function collapseStorePaths(code: string): string;
30
+ /**
31
+ * 包名同源校验的上下文:校验逻辑在各包(它知道自己哪几个源文件带着包名字面量),
32
+ * 判据材料在这里。
33
+ */
34
+ export interface ClientNameContext {
35
+ /** package.json 的 name(外壳 id 的真源)。 */
36
+ readonly pkgName: string;
37
+ /** 折叠后的 chunk 正文(未包外壳)。 */
38
+ readonly body: string;
39
+ /** 读包根下一份源码文件。 */
40
+ readonly readSource: (relative: string) => Promise<string>;
41
+ }
42
+ /** client 侧构建的完整配置。 */
43
+ export interface ClientBuildOptions {
44
+ /** 包根绝对路径(调用方的 `import.meta.dirname`)。 */
45
+ readonly root: string;
46
+ /** 调用方注入的 rolldown。 */
47
+ readonly rolldown: RolldownFactory;
48
+ /** 客户端入口(相对包根)。缺省 `src/client-entry.ts`。 */
49
+ readonly entry?: string;
50
+ /** 产物首行的 GENERATED 头(不含换行)。它是产物字节的一部分,逐字由调用方给定。 */
51
+ readonly header: string;
52
+ /** 外置说明符全量清单(精确匹配)。缺省 {@link REACT_RUNTIME_MODULES}。 */
53
+ readonly externals?: readonly string[];
54
+ /** 压缩产物。缺省不压。 */
55
+ readonly minify?: boolean;
56
+ /** 保留注释。缺省跟随 minify(压则剥,不压则留)。 */
57
+ readonly comments?: boolean;
58
+ /** 包名同源校验。槽位 key、`$mount` 的 package 位之类各包自带的字面量声明在这里核对;
59
+ * 缺省不校验。抛错即构建失败。 */
60
+ readonly verifyName?: (context: ClientNameContext) => Promise<void> | void;
61
+ }
62
+ /**
63
+ * 客户端外置判据:登记过的精确说明符外置,react 家族里**没登记**的说明符当场失败。
64
+ *
65
+ * 为什么既不静默内联也不前缀放行:外壳注入的 require 只在宿主模块表里查键。前缀放行会
66
+ * 得到一条表里没有的 require(加载期 not in the module table,整张卡片静默不渲染);而
67
+ * 静默内联会把 node_modules 里那份 React 打进产物、与宿主那份并存,页面里两个 React
68
+ * 实例, hooks 直接报错。两种失败都不在构建期报错,所以这里没有可观察的中间态。
69
+ * 导出给各包的构建脚本复用与测试直接驱动,与构建走同一份判据。
70
+ * @param externals - 外置说明符全量清单(精确匹配)
71
+ * @returns rolldown 的 external 判据
72
+ */
73
+ export declare function clientExternalPredicate(externals: readonly string[]): (id: string) => boolean;
74
+ /**
75
+ * 构建客户端产物的完整文本(含 ModuleLoader 外壳)。
76
+ * @param options - 见 {@link ClientBuildOptions}
77
+ * @returns 产物正文
78
+ */
79
+ export declare function buildClientBundle(options: ClientBuildOptions): Promise<string>;
@@ -0,0 +1,54 @@
1
+ import type { RolldownPluginOption } from "rolldown";
2
+ import type { RolldownFactory } from "./build-artifacts.js";
3
+ /** 一个 host 入口:源码、产物与生成头。多个入口各自单独构建,互不共享 chunk。 */
4
+ export interface HostEntry {
5
+ /** 入口源码(相对包根,如 `host.ts`)。 */
6
+ readonly source: string;
7
+ /** 产物文件名(相对包根,如 `host.js`)。 */
8
+ readonly output: string;
9
+ /** 产物首行的 GENERATED 头(含行尾换行)。它是产物字节的一部分,逐字由调用方给定。 */
10
+ readonly header: string;
11
+ }
12
+ /** host 侧构建的完整配置。 */
13
+ export interface HostBuildOptions {
14
+ /** 包根绝对路径(调用方的 `import.meta.dirname`)。 */
15
+ readonly root: string;
16
+ /** 调用方注入的 rolldown。 */
17
+ readonly rolldown: RolldownFactory;
18
+ /** 入口表。每项单独一次构建:多输入会把公共依赖提成共享 chunk,这里要的是自包含单文件。 */
19
+ readonly entries: readonly HostEntry[];
20
+ /** 压缩产物。缺省不压;装载器只 `import()` 文本,注释与空白是真实装载成本。 */
21
+ readonly minify?: boolean;
22
+ /** 保留注释。缺省跟随 minify(压则剥,不压则留)。 */
23
+ readonly comments?: boolean;
24
+ /** rolldown 插件(如装饰器预转换)。缺省无。类型借 rolldown 自己的插件形状(仅类型导入)。 */
25
+ readonly plugins?: RolldownPluginOption;
26
+ /** 解析扩展名表,**替换** rolldown 默认值而非追加。缺省 `[".ts", ".mjs", ".js"]`;
27
+ * 入口会导入 `.json` 数据的包要显式带上 `.json`。 */
28
+ readonly resolveExtensions?: readonly string[];
29
+ /** 是否外部化 optionalDependencies。缺省外部化:「装上就用、没装也能跑」的语义由
30
+ * 运行期 require 决定,内联会把可选实现烤进产物、抹掉「可选」这层语义。
31
+ * 个别包刻意反向(可选依赖内联成「缺包时功能照旧」),传 false。 */
32
+ readonly externalizeOptional?: boolean;
33
+ }
34
+ /** 一个 host 产物的构建结果。`inlinedModules` 是结构性外部化门禁的判据面。 */
35
+ export interface HostArtifact {
36
+ readonly source: string;
37
+ readonly output: string;
38
+ /** 产物绝对路径。 */
39
+ readonly file: string;
40
+ /** 产物完整正文(含生成头)。 */
41
+ readonly code: string;
42
+ /** 被打进 chunk 的模块 id 清单。外部化的依赖不出现在这里——依赖表里的包名一旦
43
+ * 出现,就是有人把它从外部化名单里挪走了(内联)。 */
44
+ readonly inlinedModules: readonly string[];
45
+ }
46
+ /**
47
+ * 构建全部 host 入口。
48
+ *
49
+ * 各入口单独一次 rolldown 并发跑(它们互不依赖,串行只是在等第二次构建);产物正文
50
+ * 先按 `process.cwd()` 无关的 canonical 形态正典化,再交给调用方落盘。
51
+ * @param options - 见 {@link HostBuildOptions}
52
+ * @returns 与入口表同序的产物清单
53
+ */
54
+ export declare function buildHostEntries(options: HostBuildOptions): Promise<HostArtifact[]>;
@@ -0,0 +1,61 @@
1
+ /** 声明构建的配置。 */
2
+ export interface TypesBuildOptions {
3
+ /** 包根绝对路径(调用方的 `import.meta.dirname`)。 */
4
+ readonly root: string;
5
+ /** tsc 入口脚本的绝对路径(用 `process.execPath` 跑它,跨平台且不经 PATH / npx)。 */
6
+ readonly tscEntry: string;
7
+ /** tsconfig 项目名(相对包根)。缺省 `tsconfig.types.json`。 */
8
+ readonly tsconfig?: string;
9
+ /** 草稿目录绝对路径。缺省 `<root>/.tmp/types`(该目录在各包统一被 git 忽略,
10
+ * 且与 types/ 同一文件系统,最后一步 rename 原子且不跨设备)。 */
11
+ readonly scratch?: string;
12
+ /** 产物目录绝对路径。缺省 `<root>/types`。 */
13
+ readonly outDir?: string;
14
+ }
15
+ /**
16
+ * 把一段声明正文里的相对源码后缀说明符改写成对侧产物后缀。
17
+ *
18
+ * 三处 replace 各自收一种句法位置,互不依赖;同一个文件里三种都出现时不漏也不重。
19
+ * @param text - 声明文件正文
20
+ * @returns 改写后的正文
21
+ */
22
+ export declare function canonicalizeDeclarationSpecifiers(text: string): string;
23
+ /**
24
+ * 把子进程的一路输出读成文本。
25
+ *
26
+ * `execFileSync` 不给 `encoding` 时两路都是 Buffer;按形态显式分支:文本原样用、Buffer
27
+ * 按 UTF-8 解、其余形态返回空串。写成 `String(stream)` 会把 Buffer 送进默认字符串化,
28
+ * 类型检查也会因为「可能是任意对象」而判红。
29
+ * @param stream - 子进程的一路输出
30
+ * @returns 文本
31
+ */
32
+ export declare function textOf(stream: unknown): string;
33
+ /**
34
+ * 跑一条命令;失败时抛出的信息取子进程自己的诊断文本,而不是包装层的壳。
35
+ *
36
+ * 用同步版:这是构建步骤,阻塞等 tsc 跑完完全够用,异步包装在这里只会为了一个用不上
37
+ * 的并发度引入回调与 Promise 两种形态。两路都得收:tsc 把编译诊断写 **stdout**,工具
38
+ * 自身崩溃与 node 的用法错误写在 stderr,任取一路都会丢一半。
39
+ * @param file - 可执行文件(用 `process.execPath` 跑入口 JS,不经 npx:Windows 上 `npx`
40
+ * 实际是 `npx.cmd`,`execFileSync` 不带 `shell` 不按 PATHEXT 解析,一律 ENOENT)
41
+ * @param args - 参数
42
+ * @param cwd - 工作目录
43
+ */
44
+ export declare function runTool(file: string, args: readonly string[], cwd: string): void;
45
+ /**
46
+ * 递归列出目录下的声明文件(`/` 分隔,路径相对 `dir`)。
47
+ *
48
+ * 文件与目录分成两支再汇总:混在一个 map 里时,只有文件的那一支会变成一个没有 `await`
49
+ * 的 `async` 回调;拆开后两支各自理由完整。导出而非私藏:调用方的声明门禁要按同一口径
50
+ * 列 types/,在调用方重写一份遍历逻辑只会让两边的目录形状悄悄分叉。
51
+ * @param dir - 当前目录绝对路径
52
+ * @param prefix - 已走过的相对前缀(顶层调用传空串)
53
+ * @returns 命中的文件清单
54
+ */
55
+ export declare function listDeclarations(dir: string, prefix: string): Promise<string[]>;
56
+ /**
57
+ * 产出 types/:跑 tsc 出声明,再逐个改写相对说明符,成功后整体换上。
58
+ * @param options - 见 {@link TypesBuildOptions}
59
+ * @returns 改写过的声明文件清单(相对 outDir、`/` 分隔)
60
+ */
61
+ export declare function buildDeclarations(options: TypesBuildOptions): Promise<string[]>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jayyuen66/dsh-plugin-shared",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "dsh 自研插件的共享工具库:tool 事件流记账扫描 + 卡片 apply/差异骨架 + 官方作业结局映射 + 项目键派生 + 代理对安全截断。作为库包被 import,不由用户启用",
5
5
  "keywords": [
6
6
  "cordis",
@@ -81,6 +81,22 @@
81
81
  "types": "./dist/types/canonicalize-region-paths.d.ts",
82
82
  "default": "./dist/canonicalize-region-paths.js"
83
83
  },
84
+ "./lib/build-artifacts": {
85
+ "types": "./dist/types/build-artifacts.d.ts",
86
+ "default": "./dist/build/build-artifacts.js"
87
+ },
88
+ "./lib/build-host": {
89
+ "types": "./dist/types/build-host.d.ts",
90
+ "default": "./dist/build/build-host.js"
91
+ },
92
+ "./lib/build-client": {
93
+ "types": "./dist/types/build-client.d.ts",
94
+ "default": "./dist/build/build-client.js"
95
+ },
96
+ "./lib/build-types": {
97
+ "types": "./dist/types/build-types.d.ts",
98
+ "default": "./dist/build/build-types.js"
99
+ },
84
100
  "./config/oxlint": {
85
101
  "types": "./dist/types/config/oxlint.base.d.ts",
86
102
  "default": "./dist/config/oxlint.base.js"
@@ -99,15 +115,15 @@
99
115
  },
100
116
  "devDependencies": {
101
117
  "@deepseek-ai/cordis": "4.0.5",
102
- "@deepseek-ai/dsh-brand": "0.2.1-rc.2",
103
- "@deepseek-ai/dsh-client-locale": "0.2.1-rc.2",
104
- "@deepseek-ai/dsh-jobs": "0.2.1-rc.2",
105
- "@deepseek-ai/dsh-llm": "0.2.1-rc.2",
106
- "@deepseek-ai/dsh-output-retention": "0.2.1-rc.2",
107
- "@deepseek-ai/dsh-scope": "0.2.1-rc.2",
108
- "@deepseek-ai/dsh-session": "0.2.1-rc.2",
109
- "@deepseek-ai/dsh-shell": "0.2.1-rc.2",
110
- "@deepseek-ai/dsh-tools": "0.2.1-rc.2",
118
+ "@deepseek-ai/dsh-brand": "0.2.1-rc.3",
119
+ "@deepseek-ai/dsh-client-locale": "0.2.1-rc.3",
120
+ "@deepseek-ai/dsh-jobs": "0.2.1-rc.3",
121
+ "@deepseek-ai/dsh-llm": "0.2.1-rc.3",
122
+ "@deepseek-ai/dsh-output-retention": "0.2.1-rc.3",
123
+ "@deepseek-ai/dsh-scope": "0.2.1-rc.3",
124
+ "@deepseek-ai/dsh-session": "0.2.1-rc.3",
125
+ "@deepseek-ai/dsh-shell": "0.2.1-rc.3",
126
+ "@deepseek-ai/dsh-tools": "0.2.1-rc.3",
111
127
  "@types/node": "26.6.5",
112
128
  "@vitest/coverage-v8": "5.0.5",
113
129
  "eslint-plugin-sonarjs": "4.2.2",
@@ -121,13 +137,13 @@
121
137
  },
122
138
  "peerDependencies": {
123
139
  "@deepseek-ai/cordis": "4.0.5",
124
- "@deepseek-ai/dsh": "^0.2.1-rc.2",
125
- "@deepseek-ai/dsh-client-locale": "0.2.1-rc.2",
126
- "@deepseek-ai/dsh-jobs": "0.2.1-rc.2",
127
- "@deepseek-ai/dsh-output-retention": "0.2.1-rc.2",
128
- "@deepseek-ai/dsh-session": "0.2.1-rc.2",
129
- "@deepseek-ai/dsh-shell": "0.2.1-rc.2",
130
- "@deepseek-ai/dsh-tools": "0.2.1-rc.2"
140
+ "@deepseek-ai/dsh": "^0.2.1-rc.3",
141
+ "@deepseek-ai/dsh-client-locale": "0.2.1-rc.3",
142
+ "@deepseek-ai/dsh-jobs": "0.2.1-rc.3",
143
+ "@deepseek-ai/dsh-output-retention": "0.2.1-rc.3",
144
+ "@deepseek-ai/dsh-session": "0.2.1-rc.3",
145
+ "@deepseek-ai/dsh-shell": "0.2.1-rc.3",
146
+ "@deepseek-ai/dsh-tools": "0.2.1-rc.3"
131
147
  },
132
148
  "peerDependenciesMeta": {
133
149
  "@deepseek-ai/cordis": {
@@ -157,7 +173,7 @@
157
173
  },
158
174
  "icon": "icon.svg",
159
175
  "engines": {
160
- "dsh": "^0.2.1-rc.2",
176
+ "dsh": "^0.2.1-rc.3",
161
177
  "node": ">=22.12.0"
162
178
  },
163
179
  "scripts": {