@zhin.js/runtime 1.1.1 → 1.1.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.
@@ -3,6 +3,12 @@ import type { ModuleRuntime, ModuleWatchRoot } from './module-runtime.js';
3
3
  export interface NativeDevelopmentModuleRuntimeOptions {
4
4
  readonly projectRoot: string;
5
5
  readonly watch?: boolean;
6
+ /** Optional scoped loader used only when the active Node loader rejects TSX. */
7
+ readonly tsxLoader?: TsxModuleLoader;
8
+ }
9
+ export interface TsxModuleLoader {
10
+ load<T = unknown>(source: string, parentURL: string): Promise<T>;
11
+ close?(): Promise<void>;
6
12
  }
7
13
  /**
8
14
  * Uses Node's native ESM/TypeScript loader and adds only cache busting and watch.
@@ -18,6 +18,7 @@ const capabilityRoots = new Set([
18
18
  export class NativeDevelopmentModuleRuntime {
19
19
  #projectRoot;
20
20
  #watchEnabled;
21
+ #tsxLoader;
21
22
  #revisions = new Map();
22
23
  #watchers = new Set();
23
24
  #watchRoots;
@@ -25,19 +26,25 @@ export class NativeDevelopmentModuleRuntime {
25
26
  constructor(options) {
26
27
  this.#projectRoot = resolve(options.projectRoot);
27
28
  this.#watchEnabled = options.watch ?? true;
29
+ this.#tsxLoader = options.tsxLoader;
28
30
  this.#watchRoots = normalizeWatchRoots([this.#projectRoot]);
29
31
  }
30
32
  async load(source) {
31
33
  this.#assertOpen();
32
34
  const normalized = resolve(source);
33
- if (normalized.endsWith('.tsx')) {
34
- throw new Error(`Node native TypeScript does not support TSX: ${normalized}`);
35
- }
36
35
  if (normalized.endsWith('.ts'))
37
36
  assertNativeTypeScriptSupport();
38
37
  const url = pathToFileURL(normalized);
39
38
  url.searchParams.set('zhin-generation', String(this.#revisions.get(normalized) ?? 0));
40
- return import(url.href);
39
+ try {
40
+ return await import(url.href);
41
+ }
42
+ catch (error) {
43
+ if (!normalized.endsWith('.tsx') || !isUnsupportedTsxError(error) || !this.#tsxLoader) {
44
+ throw error;
45
+ }
46
+ return this.#tsxLoader.load(url.href, pathToFileURL(`${this.#projectRoot}${sep}`).href);
47
+ }
41
48
  }
42
49
  invalidate(source) {
43
50
  const normalized = resolve(source);
@@ -83,7 +90,7 @@ export class NativeDevelopmentModuleRuntime {
83
90
  // Support files inside capability directories (e.g. commands/_utils.ts)
84
91
  // are not discovery entries: reloading the entry URL only bumps that
85
92
  // entry's zhin-generation, so the importer closure keeps the old code.
86
- return ['.js', '.json', '.ts'].includes(extname(normalized));
93
+ return ['.js', '.json', '.ts', '.tsx'].includes(extname(normalized));
87
94
  }
88
95
  updateWatchRoots(roots) {
89
96
  this.#assertOpen();
@@ -115,6 +122,7 @@ export class NativeDevelopmentModuleRuntime {
115
122
  for (const watcher of this.#watchers)
116
123
  watcher.close();
117
124
  this.#watchers.clear();
125
+ await this.#tsxLoader?.close?.();
118
126
  }
119
127
  #assertOpen() {
120
128
  if (this.#closed)
@@ -134,7 +142,7 @@ function isNamedCapabilityDirectory(value, allowSnake) {
134
142
  function isDirectoryCapabilityEntry(parts) {
135
143
  return parts.length === 2
136
144
  && parts[1] === `index${extname(parts[1] ?? '')}`
137
- && ['.cjs', '.js', '.mjs', '.ts'].includes(extname(parts[1] ?? ''));
145
+ && ['.cjs', '.js', '.mjs', '.ts', '.tsx'].includes(extname(parts[1] ?? ''));
138
146
  }
139
147
  function isNestedDirectoryEntry(parts) {
140
148
  const index = parts.indexOf('tools');
@@ -157,6 +165,11 @@ export function assertNativeTypeScriptSupport() {
157
165
  'Use Node >=22.18.0 or start Node with --experimental-strip-types.',
158
166
  ].join(' '));
159
167
  }
168
+ function isUnsupportedTsxError(error) {
169
+ return error instanceof Error
170
+ && 'code' in error
171
+ && error.code === 'ERR_UNKNOWN_FILE_EXTENSION';
172
+ }
160
173
  class PortableSourceWatcher {
161
174
  listener;
162
175
  #watchers = new Set();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/runtime",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "Static Plugin graph, generation transaction and HMR Root runtime for Zhin.js",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -18,23 +18,24 @@
18
18
  "dependencies": {
19
19
  "ajv": "8.18.0",
20
20
  "semver": "7.8.5",
21
- "@zhin.js/feature-kit": "1.1.1",
22
- "@zhin.js/plugin-runtime": "1.1.10"
21
+ "@zhin.js/feature-kit": "1.1.2",
22
+ "@zhin.js/plugin-runtime": "1.1.11"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/node": "^26.1.2",
26
+ "tsx": "^4.23.1",
26
27
  "typescript": "^6.0.3",
27
- "@zhin.js/adapter": "1.1.14",
28
- "@zhin.js/agent-feature": "1.1.1",
29
- "@zhin.js/command": "1.1.1",
30
- "@zhin.js/component": "1.1.1",
31
- "@zhin.js/layout": "1.1.1",
32
- "@zhin.js/mcp-feature": "1.1.1",
33
- "@zhin.js/middleware": "1.1.1",
34
- "@zhin.js/page": "1.1.1",
35
- "@zhin.js/prompt-section": "1.1.1",
36
- "@zhin.js/skill": "1.1.1",
37
- "@zhin.js/tool": "1.1.1"
28
+ "@zhin.js/adapter": "1.1.15",
29
+ "@zhin.js/agent-feature": "1.1.2",
30
+ "@zhin.js/command": "1.1.2",
31
+ "@zhin.js/component": "1.1.2",
32
+ "@zhin.js/layout": "1.1.2",
33
+ "@zhin.js/mcp-feature": "1.1.2",
34
+ "@zhin.js/middleware": "1.1.2",
35
+ "@zhin.js/page": "1.1.2",
36
+ "@zhin.js/prompt-section": "1.1.2",
37
+ "@zhin.js/skill": "1.1.2",
38
+ "@zhin.js/tool": "1.1.2"
38
39
  },
39
40
  "repository": {
40
41
  "type": "git",