@twinsearth/dsh-2xray 0.1.0 → 0.1.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/CHANGELOG.md +6 -0
- package/index.js +2 -2
- package/lib/process.js +44 -1
- package/package.json +1 -1
- package/scripts/release-check.mjs +1 -1
- package/test/process.test.js +49 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.1 — 2026-10-29
|
|
4
|
+
|
|
5
|
+
### 修复
|
|
6
|
+
|
|
7
|
+
- 插件启用时报 `cannot get property "subprocess" without inject`:Cordis 会拦截未声明 `inject` 的服务属性读取。改为 `ctx.get('subprocess')` 反射查询,并把 launcher 改为首次 spawn 时惰性解析;服务晚挂载或缺失时分别使用服务 / `child_process` 兜底,不再阻断启用。
|
|
8
|
+
|
|
3
9
|
## 0.1.0 — 2026-10-29
|
|
4
10
|
|
|
5
11
|
首个发布版本(npm:`@twinsearth/dsh-2xray`)。
|
package/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { normalizeConfig } from './lib/config.js'
|
|
2
|
-
import {
|
|
2
|
+
import { createLazyLauncher } from './lib/process.js'
|
|
3
3
|
import { RouteManager } from './lib/route.js'
|
|
4
4
|
import { Supervisor } from './lib/supervisor.js'
|
|
5
5
|
import { createLogger } from './lib/log.js'
|
|
@@ -49,7 +49,7 @@ export function apply(ctx, rawConfig) {
|
|
|
49
49
|
logger.info('使用旧字段 publicKey 配置 REALITY;xray 会同时读取 password,两者都写可兼容新旧版本')
|
|
50
50
|
} }
|
|
51
51
|
|
|
52
|
-
const launcher =
|
|
52
|
+
const launcher = createLazyLauncher(ctx, { logger })
|
|
53
53
|
const route = new RouteManager({ logger })
|
|
54
54
|
const control =
|
|
55
55
|
config.control.enabled
|
package/lib/process.js
CHANGED
|
@@ -3,6 +3,28 @@ import { setTimeout as delay } from 'node:timers/promises'
|
|
|
3
3
|
|
|
4
4
|
const COLLECT_BYTES = 64 * 1024
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Resolve the subprocess service without tripping Cordis' inject guard.
|
|
8
|
+
*
|
|
9
|
+
* Reading `ctx.subprocess` directly throws
|
|
10
|
+
* `cannot get property "subprocess" without inject` on a fiber that did not
|
|
11
|
+
* declare it; `ctx.get(name)` is the reflective lookup and returns undefined
|
|
12
|
+
* instead. The service stays optional because a profile without
|
|
13
|
+
* `dsh-subprocess-local` still gets the `child_process` fallback.
|
|
14
|
+
*/
|
|
15
|
+
function resolveSubprocess(ctx) {
|
|
16
|
+
if (!ctx) return undefined
|
|
17
|
+
try {
|
|
18
|
+
if (typeof ctx.get === 'function') {
|
|
19
|
+
const service = ctx.get('subprocess')
|
|
20
|
+
if (service) return service
|
|
21
|
+
}
|
|
22
|
+
} catch {
|
|
23
|
+
// inactive or guarded context: fall through to the fallback launcher
|
|
24
|
+
}
|
|
25
|
+
return undefined
|
|
26
|
+
}
|
|
27
|
+
|
|
6
28
|
/**
|
|
7
29
|
* A launcher hides whether the child runs through `ctx.subprocess` (Windows Job
|
|
8
30
|
* Object, process-tree containment, DSH's environment scrub) or through a bare
|
|
@@ -16,7 +38,7 @@ const COLLECT_BYTES = 64 * 1024
|
|
|
16
38
|
* pid?: number
|
|
17
39
|
*/
|
|
18
40
|
export function createLauncher(ctx, { logger } = {}) {
|
|
19
|
-
const service = ctx
|
|
41
|
+
const service = resolveSubprocess(ctx)
|
|
20
42
|
if (service && typeof service.spawn === 'function' && typeof service.resolveExecutable === 'function') {
|
|
21
43
|
return createServiceLauncher(service)
|
|
22
44
|
}
|
|
@@ -24,6 +46,27 @@ export function createLauncher(ctx, { logger } = {}) {
|
|
|
24
46
|
return createNodeLauncher()
|
|
25
47
|
}
|
|
26
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Resolve the launcher lazily at first spawn instead of at activation: the
|
|
51
|
+
* subprocess provider may mount after this plugin's fiber, and a strict
|
|
52
|
+
* `inject` dependency would stop the plugin from activating at all.
|
|
53
|
+
*/
|
|
54
|
+
export function createLazyLauncher(ctx, options) {
|
|
55
|
+
let inner
|
|
56
|
+
const resolve = () => (inner ??= createLauncher(ctx, options))
|
|
57
|
+
return {
|
|
58
|
+
get kind() {
|
|
59
|
+
return resolve().kind
|
|
60
|
+
},
|
|
61
|
+
resolveExecutable(command, env, signal) {
|
|
62
|
+
return resolve().resolveExecutable(command, env, signal)
|
|
63
|
+
},
|
|
64
|
+
spawn(spec) {
|
|
65
|
+
return resolve().spawn(spec)
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
27
70
|
function createServiceLauncher(service) {
|
|
28
71
|
return {
|
|
29
72
|
kind: 'ctx.subprocess',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twinsearth/dsh-2xray",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "DSH-2Xray: supervises xray-core and routes DSH process-wide HTTP(S) traffic through its local inbound, with optional multi-node gRPC control plane",
|
|
6
6
|
"type": "module",
|
|
@@ -18,7 +18,7 @@ const check = (name, ok, detail = '') => checks.push({ name, ok, detail })
|
|
|
18
18
|
const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8'))
|
|
19
19
|
|
|
20
20
|
check('name is @twinsearth/dsh-2xray', pkg.name === '@twinsearth/dsh-2xray', pkg.name)
|
|
21
|
-
check('version is
|
|
21
|
+
check('version is semver', /^\d+\.\d+\.\d+$/.test(pkg.version), pkg.version)
|
|
22
22
|
check('publishable', pkg.private !== true, `private=${pkg.private}`)
|
|
23
23
|
check('license MIT', pkg.license === 'MIT', String(pkg.license))
|
|
24
24
|
check('type module', pkg.type === 'module', String(pkg.type))
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import test from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { createLauncher, createLazyLauncher } from '../lib/process.js'
|
|
4
|
+
|
|
5
|
+
const logger = { warn() {} }
|
|
6
|
+
|
|
7
|
+
test('a context that guards service properties never throws at activation', () => {
|
|
8
|
+
const guarded = { get: () => undefined }
|
|
9
|
+
Object.defineProperty(guarded, 'subprocess', {
|
|
10
|
+
get() {
|
|
11
|
+
throw new Error('cannot get property "subprocess" without inject')
|
|
12
|
+
},
|
|
13
|
+
})
|
|
14
|
+
assert.doesNotThrow(() => createLauncher(guarded, { logger }))
|
|
15
|
+
assert.equal(createLauncher(guarded, { logger }).kind, 'child_process')
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('ctx.get is used when it returns the service', () => {
|
|
19
|
+
const service = { spawn() {}, resolveExecutable() {} }
|
|
20
|
+
const ctx = { get: (name) => (name === 'subprocess' ? service : undefined) }
|
|
21
|
+
assert.equal(createLauncher(ctx, { logger }).kind, 'ctx.subprocess')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('a context without ctx.get falls back to child_process', () => {
|
|
25
|
+
assert.equal(createLauncher({}, { logger }).kind, 'child_process')
|
|
26
|
+
assert.equal(createLauncher(undefined, { logger }).kind, 'child_process')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('the lazy launcher resolves at first use, so a later-mounted service is seen', () => {
|
|
30
|
+
let service
|
|
31
|
+
const ctx = { get: () => service }
|
|
32
|
+
const lazy = createLazyLauncher(ctx, { logger })
|
|
33
|
+
service = {
|
|
34
|
+
spawn() {
|
|
35
|
+
return {
|
|
36
|
+
pid: 7,
|
|
37
|
+
done: Promise.resolve({ exitCode: 0, signal: null }),
|
|
38
|
+
terminate: async () => {},
|
|
39
|
+
waitForExit: async () => {},
|
|
40
|
+
collected: {},
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
resolveExecutable() {},
|
|
44
|
+
}
|
|
45
|
+
assert.equal(lazy.kind, 'ctx.subprocess')
|
|
46
|
+
const handle = lazy.spawn({ argv: ['x'], cwd: '.', graceMs: 1, env: {} })
|
|
47
|
+
assert.equal(handle.kind, 'ctx.subprocess')
|
|
48
|
+
assert.equal(handle.pid, 7)
|
|
49
|
+
})
|