@morlay/dsh-oxc-node 0.0.0-stage → 0.2.6

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 morlay
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,20 @@
1
- # Temporary Holding Version
1
+ # @morlay/dsh-oxc-node
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **本仓库的 TS 运行时入口**:注册 Node 的 module hooks,让 `node_modules` 下的 `.ts` 也能直接跑(ESM only)。
4
+
5
+ 为什么需要它:Node 内建的 type stripping 拒绝对 `node_modules` 里的文件动手,而 `@oxc-node/core` 也刻意
6
+ 放行那类文件(转译本身当然能做,只是那层策略不放)。这里自己注册 hooks、直接调 `oxc-transform`,
7
+ 于是"源码面"(`exports` 指向 `src/*.ts` 的包)在壳与宿主里都能直接加载。
8
+
9
+ ## 用法
10
+
11
+ ```
12
+ node --import <本包>/register.mjs <入口>
13
+ ```
14
+
15
+ - **壳**不直接依赖它:随包运行时(`<Resources>/runtime/loader`)里放着一份,壳按**路径**把它交给宿主
16
+ 与子进程(见 `packages/desktop/dsh-desktop-shell/src/index.ts` 的 `loader`);
17
+ - **工具**(`@local/dshdev`)依赖它,并在备 runtime 时把这一份连同它的依赖层一起拷进
18
+ `<buildCache>/runtime/loader`(见 `devpackages/dshdev/src/cli/prepare-runtime.ts` 的 `prepareLoader`)。
19
+
20
+ 依赖方向因此是"**devpackages → packages**":`@local/*` 只出现在工具链里,不落到任何发布包的运行时依赖上。
package/package.json CHANGED
@@ -1,6 +1,32 @@
1
1
  {
2
2
  "name": "@morlay/dsh-oxc-node",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.6",
4
+ "description": "本仓库的 TS 运行时入口:注册 hooks 后 Node 也能跑 node_modules 下的 .ts(ESM only)。壳与宿主按路径取用(工具把它装进随包运行时)。",
5
+ "keywords": [
6
+ "dsh",
7
+ "dsh-plugin",
8
+ "loader",
9
+ "oxc",
10
+ "typescript"
11
+ ],
12
+ "license": "MIT",
13
+ "author": "morlay",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/morlay/dsh-plugin.git"
17
+ },
18
+ "files": [
19
+ "register.mjs"
20
+ ],
21
+ "type": "module",
22
+ "exports": {
23
+ ".": "./register.mjs"
24
+ },
25
+ "dependencies": {
26
+ "@oxc-project/runtime": "^0.154.0",
27
+ "oxc-transform": "^0.154.0"
28
+ },
29
+ "engines": {
30
+ "node": ">=24.11.0"
31
+ }
6
32
  }
package/register.mjs ADDED
@@ -0,0 +1,84 @@
1
+ /**
2
+ * 让 Node 也能跑 `node_modules` 下的 `.ts`。
3
+ *
4
+ * Node 内建的 type stripping **拒绝对 `node_modules` 里的文件**做剥离
5
+ * (`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`),而 `@oxc-node/core/register` 也**刻意放行**那类文件
6
+ * (它把 Node 判成 `commonjs-typescript` 的 `.ts` 原样交给 Node,好让报错来自 Node 而不是它)。但**转译
7
+ * 本身**当然能做:这里自己注册 hooks(`registerHooks`,同步钩子),对 `.ts` 直接调 `oxc-transform`——
8
+ * 转译器还是 oxc,只是不再经过"给不给 node_modules 转"的那层策略。
9
+ *
10
+ * 为什么不用 `@oxc-node/core`:它的 `OxcTransformer` 没法配 target(`setTsconfigPath()` 要求"第一次
11
+ * transform/resolve 之前"调用,而加载它自己就已经过了那个时机;`cwd` 与环境变量都不决定 target),默认
12
+ * target 低到不支持顶层 await。独立的 `oxc-transform` 可以直接给 `target: "esnext"`。
13
+ *
14
+ * 也为什么不用内建的 `stripTypeScriptTypes`:它只有 `mode: "strip"`(`transform` 已移除),处理不了
15
+ * `enum` / `namespace` / 参数属性这些非可擦除语法。
16
+ *
17
+ * **为什么是 `.mjs` 而不是 `.ts`**:它自己就是"让 Node 能跑 TS"的那一步,写成 TS 就自举了。
18
+ *
19
+ * 用法:随进程启动挂上(壳的入口 `import` 它;宿主用 `--import`),本模块只做注册、没有导出。
20
+ */
21
+ import { readFileSync } from "node:fs";
22
+ import { registerHooks } from "node:module";
23
+ import { fileURLToPath } from "node:url";
24
+ import { transformSync } from "oxc-transform";
25
+
26
+ /** 排查用:`DSH_TS_DEBUG=1` 时把"挂上/转译了哪个"打到 stderr(平时安静)。 */
27
+ const debug = process.env.DSH_TS_DEBUG === "1";
28
+ if (debug) process.stderr.write(`[ts-register] 已挂载 pid=${String(process.pid)}\n`);
29
+
30
+ registerHooks({
31
+ load(url, context, nextLoad) {
32
+ if (!url.startsWith("file:") || !url.endsWith(".ts")) return nextLoad(url, context);
33
+ if (debug) process.stderr.write(`[ts-register] 转译 ${url.slice(7)}\n`);
34
+ const path = fileURLToPath(url);
35
+ // oxlint-disable-next-line node/no-sync -- `registerHooks` 的 load 钩子必须同步返回。
36
+ const original = readFileSync(path, "utf8");
37
+ // oxlint-disable-next-line node/no-sync -- 钩子契约要求同步。
38
+ const result = transformSync(path, original, {
39
+ sourcemap: false,
40
+ // `es2022`:装饰器要在这里降级(`esnext` 会把 `@dec` 原样留着,而 Node 不认它——报出来是一句没头没尾
41
+ // 的 `SyntaxError: Invalid or unexpected token`);顶层 await 在 es2022 就有,保得住。
42
+ target: "es2022",
43
+ // 装饰器:dsh 用的是 legacy 装饰器(`experimentalDecorators`),oxc 要显式开才会转;`@Remote` 这类
44
+ // 原样留着就是上面那句语法错。
45
+ decorator: { legacy: true },
46
+ // helper 用默认形态:输出 `@oxc-project/runtime/helpers/<name>` 这种子出口(那个包只导出
47
+ // `./helpers/*`)。显式写 `helpers: { mode: "Runtime" }` 反而会引到它的**根**导出处而解析失败。
48
+ });
49
+ if (result.errors.length > 0) {
50
+ const detail = result.errors.map((e) => e.message).join("; ");
51
+ // 上游把 import 失败吞成一句 "failed to import",所以这里直接打到 stderr——否则看不见真正的原因。
52
+ process.stderr.write(`[ts-register] 转译失败 ${path}: ${detail}\n`);
53
+ throw new Error(`ts-register: ${path}: ${detail}`);
54
+ }
55
+ // 一律按 ESM 交回 Node:这条链要跑的 `.ts`(上游经 `exports` 指到 `src` 的那些)都是 ESM 源码,
56
+ // 而它们的 `package.json` 未必带 `type: "module"`——按包类型去判会把 ESM 代码当 CJS 载入,
57
+ // 报出来是一句没头没尾的 `SyntaxError: Invalid or unexpected token`。
58
+ const format = "module";
59
+ // 装饰器 helper 的 specifier 是相对**被转译的文件**解析的,而那些文件(上游包)身边没有
60
+ // `@oxc-project/runtime`。这里把它解析成绝对 URL(按 loader 自己的解析面)——不能直接拼目录,
61
+ // 那个包的 helper 名字经 `exports` 映射到 `src/helpers/*.js`。
62
+ const code = result.code.replace(
63
+ /(["'])(@oxc-project\/runtime\/[^"']+)\1/gu,
64
+ (whole, quote, specifier) => {
65
+ try {
66
+ return `${quote}${import.meta.resolve(specifier)}${quote}`;
67
+ } catch {
68
+ return whole;
69
+ }
70
+ },
71
+ );
72
+ // `map` 可能是字符串,也可能是 sourcemap 对象——两种都要能带上(栈里才指向原文件)。
73
+ const map =
74
+ result.map === undefined || result.map === null ? undefined : JSON.stringify(result.map);
75
+ return {
76
+ format,
77
+ source:
78
+ map === undefined
79
+ ? code
80
+ : `${code}\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,${Buffer.from(map, "utf8").toString("base64")}`,
81
+ shortCircuit: true,
82
+ };
83
+ },
84
+ });