dsh-code-server-app 0.1.32 → 0.1.37
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.en.md +134 -90
- package/README.md +123 -88
- package/lib/client.js +3 -3
- package/lib/index.js +159 -90
- package/lib/native.js +86 -0
- package/lib/vendor.js +107 -0
- package/package.json +59 -11
- package/scripts/vendor-code-server-pkg.mjs +137 -0
- package/scripts/vendor-code-server.mjs +293 -0
- package/scripts/vendor-repacks.mjs +449 -0
- package/vendor/VENDOR.json +9 -0
- package/scripts/setup-code-server.mjs +0 -239
package/lib/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* - exports.name = 插件名(与 cordis.patch.yml 行 id 一致)
|
|
7
7
|
* - exports.inject = ['connection', 'settings'](connection 承载 client↔host 通道)
|
|
8
8
|
* - apply(ctx, config) 在共享 /api 通道注册 5 条 exact Fetch 路由:
|
|
9
|
-
* status / start / stop / setup / open-file
|
|
9
|
+
* status / start / stop / setup(兼容空操作) / open-file
|
|
10
10
|
*
|
|
11
11
|
* 通道选择(重要):**不依赖 webServer**。路由经 ctx.connection.fetch.register
|
|
12
12
|
* 挂在 Connection 服务的共享 /api 通道上:
|
|
@@ -38,6 +38,17 @@ import * as os from 'node:os';
|
|
|
38
38
|
import * as http from 'node:http';
|
|
39
39
|
import { fileURLToPath } from 'node:url';
|
|
40
40
|
import { createRequire } from 'node:module';
|
|
41
|
+
import {
|
|
42
|
+
PACKAGE_ROOT,
|
|
43
|
+
codeServerEntry,
|
|
44
|
+
codeServerPackageName,
|
|
45
|
+
codeServerRoot,
|
|
46
|
+
legacyInstallRoot,
|
|
47
|
+
readTreeVersion,
|
|
48
|
+
vendoredVersion,
|
|
49
|
+
vendorReady,
|
|
50
|
+
} from './vendor.js';
|
|
51
|
+
import { resolveRuntime, runtimePackageName, verifyNatives } from './native.js';
|
|
41
52
|
|
|
42
53
|
// schemastery 由 DSH 部署自带(官方核心依赖),仿 auto-open-web 的解析策略:
|
|
43
54
|
// 常规 import 优先,不可用时回退到全局 npm 布局的 DSH 部署副本。
|
|
@@ -76,9 +87,6 @@ export const Config = z.object({
|
|
|
76
87
|
/** windowedOpen=false(默认)点击悬浮球打开内部浮动窗口;
|
|
77
88
|
* true 时改为在浏览器新标签页打开 code-server(自动启动并跟随工作区)。 */
|
|
78
89
|
windowedOpen: z.boolean().default(false),
|
|
79
|
-
/** installGuideDismissed=false(默认)环境未就绪时弹出安装指引;
|
|
80
|
-
* true = 用户已永久关闭安装指引(不再弹出;设置卡可重新打开)。 */
|
|
81
|
-
installGuideDismissed: z.boolean().default(false),
|
|
82
90
|
});
|
|
83
91
|
|
|
84
92
|
const DEFAULT_CONFIG = {
|
|
@@ -121,22 +129,24 @@ function win32() {
|
|
|
121
129
|
return process.platform === 'win32';
|
|
122
130
|
}
|
|
123
131
|
|
|
124
|
-
/** 插件自带 code-server
|
|
125
|
-
* 1.
|
|
126
|
-
*
|
|
127
|
-
* 2.
|
|
132
|
+
/** 插件自带 code-server 入口探测(0.1.36 模型:就地运行,不再复制到 profile):
|
|
133
|
+
* 1. 平台专属子包 @<scope>/dshcs-code-server-<platform>-<arch>(0.1.37+ 正式布局,
|
|
134
|
+
* 由 pnpm 作为 optionalDependency 安装,内含 code-server/ 子树);
|
|
135
|
+
* 2. 包内 vendor/code-server(0.1.36 及更早的随包布局 / 开发期);
|
|
136
|
+
* 3. 旧版安装位 <profile>\.code-server-app/node_modules/code-server(兼容 0.1.35 及更早);
|
|
137
|
+
* 4. 插件目录/node_modules/code-server(开发期 link: 布局)。
|
|
128
138
|
* 不存在 → 回退配置/PATH。 */
|
|
129
139
|
function bundledRuntimeEntry() {
|
|
130
140
|
try {
|
|
131
|
-
const
|
|
141
|
+
const entry = codeServerEntry(); // 子包优先,其次包内 vendor
|
|
142
|
+
if (entry !== null) return entry;
|
|
143
|
+
const here = path.dirname(fileURLToPath(import.meta.url)); // .../dsh-code-server-app/lib
|
|
132
144
|
const candidates = [
|
|
133
|
-
// profile 专用目录:here=.../dsh-code-server/lib → ../..=node_modules → ../../..=profile根
|
|
134
145
|
path.join(here, '..', '..', '..', '.code-server-app', 'node_modules', 'code-server', 'out', 'node', 'entry.js'),
|
|
135
|
-
// 包内(dev link / 独立目录):here=.../lib → ../node_modules
|
|
136
146
|
path.join(here, '..', 'node_modules', 'code-server', 'out', 'node', 'entry.js'),
|
|
137
147
|
];
|
|
138
|
-
for (const
|
|
139
|
-
if (fs.existsSync(
|
|
148
|
+
for (const candidate of candidates) {
|
|
149
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
140
150
|
}
|
|
141
151
|
return null;
|
|
142
152
|
} catch {
|
|
@@ -193,26 +203,73 @@ function openFileSignalPath(userDataDir) {
|
|
|
193
203
|
return path.join(userDataDir, 'User', 'dshcs-open.json');
|
|
194
204
|
}
|
|
195
205
|
|
|
196
|
-
/**
|
|
197
|
-
|
|
206
|
+
/** 插件 package.json 里声明的内部依赖(纯 JS 直装集)。 */
|
|
207
|
+
function declaredInnerDeps() {
|
|
208
|
+
try {
|
|
209
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8'));
|
|
210
|
+
return Object.keys(manifest.dependencies ?? {});
|
|
211
|
+
} catch {
|
|
212
|
+
return [];
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** 从 code-server 运行位置解析内部依赖(与运行时同一套向上查找规则)。 */
|
|
217
|
+
function checkInnerDeps() {
|
|
218
|
+
const declared = declaredInnerDeps();
|
|
219
|
+
const root = codeServerRoot();
|
|
220
|
+
const from = createRequire(path.join(root ?? PACKAGE_ROOT, 'package.json'));
|
|
221
|
+
const missing = [];
|
|
222
|
+
let resolved = 0;
|
|
223
|
+
for (const name of declared) {
|
|
224
|
+
try {
|
|
225
|
+
from.resolve(name);
|
|
226
|
+
resolved += 1;
|
|
227
|
+
} catch {
|
|
228
|
+
missing.push(name);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return { declared: declared.length, resolved, missing };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** 环境检测:code-server 入口 + 原生模块(argon2) + 内部依赖 + 预编译原生包。
|
|
235
|
+
* 返回 { ok, entry, native, vscodeInner, innerDeps, codeServer, vendored, upToDate, nativeRuntime, node, platform, arch }。 */
|
|
198
236
|
function envCheck() {
|
|
199
237
|
const entry = bundledRuntimeEntry();
|
|
200
238
|
const csRoot = entry ? path.dirname(path.dirname(path.dirname(entry))) : null; // .../code-server
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
239
|
+
const argon2 = csRoot !== null ? path.join(csRoot, 'node_modules', 'argon2') : null;
|
|
240
|
+
// 必须按当前平台判定:argon2 的 prebuilds 目录存在不代表本平台有二进制(win32-arm64 就缺)。
|
|
241
|
+
const nativeOk = argon2 !== null && (
|
|
242
|
+
fs.existsSync(path.join(argon2, 'build', 'Release', 'argon2.node')) ||
|
|
243
|
+
fs.existsSync(path.join(argon2, 'prebuilds', `${process.platform}-${process.arch}`))
|
|
204
244
|
);
|
|
205
|
-
const
|
|
206
|
-
|
|
245
|
+
const inner = checkInnerDeps();
|
|
246
|
+
const runtime = resolveRuntime();
|
|
247
|
+
const natives = verifyNatives(runtime !== null ? runtime.modules : []);
|
|
248
|
+
const installed = csRoot !== null ? readTreeVersion(csRoot) : null;
|
|
249
|
+
const vendored = vendoredVersion();
|
|
207
250
|
return {
|
|
208
|
-
ok: entry !== null && nativeOk &&
|
|
251
|
+
ok: entry !== null && nativeOk && inner.missing.length === 0
|
|
252
|
+
&& runtime !== null && natives.missing.length === 0,
|
|
209
253
|
entry: entry !== null ? entry.replace(/\\/g, '/') : null,
|
|
210
254
|
native: nativeOk,
|
|
211
|
-
vscodeInner:
|
|
255
|
+
vscodeInner: inner.missing.length === 0,
|
|
256
|
+
innerDeps: { declared: inner.declared, resolved: inner.resolved, missing: inner.missing },
|
|
257
|
+
codeServer: installed,
|
|
258
|
+
vendored,
|
|
259
|
+
upToDate: installed !== null && vendored !== null && installed === vendored,
|
|
260
|
+
// 本平台预编译原生产物(平台聚合包 + 它带回原始名字的原生模块)
|
|
261
|
+
nativeRuntime: runtime !== null
|
|
262
|
+
? {
|
|
263
|
+
name: runtime.name,
|
|
264
|
+
source: 'package',
|
|
265
|
+
version: runtime.version,
|
|
266
|
+
packages: natives.resolved.length,
|
|
267
|
+
missing: natives.missing,
|
|
268
|
+
}
|
|
269
|
+
: { name: runtimePackageName(), source: null, version: null, packages: 0, missing: [] },
|
|
212
270
|
node: process.version,
|
|
213
271
|
platform: process.platform,
|
|
214
272
|
arch: process.arch,
|
|
215
|
-
pathToSetup: path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'scripts', 'setup-code-server.mjs').replace(/\\/g, '/'),
|
|
216
273
|
};
|
|
217
274
|
}
|
|
218
275
|
|
|
@@ -244,9 +301,9 @@ function resolveLaunch(config) {
|
|
|
244
301
|
return { kind: 'bin', command: out.split('\n')[0].trim() };
|
|
245
302
|
} catch {
|
|
246
303
|
throw new Error(
|
|
247
|
-
`code-server 未找到("${preferred}" 不在 PATH)
|
|
248
|
-
|
|
249
|
-
|
|
304
|
+
`code-server 未找到("${preferred}" 不在 PATH)。插件包内应自带 vendor/code-server/out/node/entry.js;` +
|
|
305
|
+
`若缺失请重新安装插件(dsh plugin --profile web add <tgz>),` +
|
|
306
|
+
`也可在 cordis.patch.yml 的 code-server config 中把 bin 指向已安装的 code-server 可执行文件 / out/node/entry.js。`,
|
|
250
307
|
);
|
|
251
308
|
}
|
|
252
309
|
}
|
|
@@ -294,7 +351,6 @@ export async function apply(ctx, config) {
|
|
|
294
351
|
const settingsSvc = ctx.get('settings');
|
|
295
352
|
let reserveComposer = true;
|
|
296
353
|
let windowedOpen = false;
|
|
297
|
-
let installGuideDismissed = false;
|
|
298
354
|
if (settingsSvc !== undefined && typeof settingsSvc.register === 'function') {
|
|
299
355
|
try {
|
|
300
356
|
const scope = settingsSvc.register(SETTINGS_NS, Config);
|
|
@@ -303,7 +359,6 @@ export async function apply(ctx, config) {
|
|
|
303
359
|
const resolved = scope.get();
|
|
304
360
|
reserveComposer = resolved && typeof resolved.reserveComposer === 'boolean' ? resolved.reserveComposer : true;
|
|
305
361
|
windowedOpen = resolved && typeof resolved.windowedOpen === 'boolean' ? resolved.windowedOpen : false;
|
|
306
|
-
installGuideDismissed = resolved && typeof resolved.installGuideDismissed === 'boolean' ? resolved.installGuideDismissed : false;
|
|
307
362
|
}
|
|
308
363
|
scope.watch((next) => {
|
|
309
364
|
if (next != null && typeof next.reserveComposer === 'boolean') {
|
|
@@ -314,10 +369,6 @@ export async function apply(ctx, config) {
|
|
|
314
369
|
windowedOpen = next.windowedOpen;
|
|
315
370
|
console.log(`[code-server] windowedOpen updated: ${windowedOpen}`);
|
|
316
371
|
}
|
|
317
|
-
if (next != null && typeof next.installGuideDismissed === 'boolean') {
|
|
318
|
-
installGuideDismissed = next.installGuideDismissed;
|
|
319
|
-
console.log(`[code-server] installGuideDismissed updated: ${installGuideDismissed}`);
|
|
320
|
-
}
|
|
321
372
|
});
|
|
322
373
|
} catch (error) {
|
|
323
374
|
console.error(`[code-server] settings unavailable; using defaults (reserveComposer=true, windowedOpen=false): ${error.message}`);
|
|
@@ -334,8 +385,8 @@ export async function apply(ctx, config) {
|
|
|
334
385
|
logTail: '',
|
|
335
386
|
startedAt: null,
|
|
336
387
|
adopted: false,
|
|
337
|
-
env: envCheck(), // 环境检测(code-server 入口/native
|
|
338
|
-
setup: { running: false, done:
|
|
388
|
+
env: envCheck(), // 环境检测(code-server 入口/native/内部依赖/预编译原生包)
|
|
389
|
+
setup: { running: false, done: true, ok: true, logTail: '0.1.36 起由包管理器安装依赖,无需「安装环境」步骤', startedAt: null, finishedAt: null }, // 兼容旧客户端
|
|
339
390
|
};
|
|
340
391
|
|
|
341
392
|
let child = null;
|
|
@@ -359,7 +410,6 @@ export async function apply(ctx, config) {
|
|
|
359
410
|
adopted: state.adopted,
|
|
360
411
|
reserveComposer,
|
|
361
412
|
windowedOpen,
|
|
362
|
-
installGuideDismissed,
|
|
363
413
|
env: state.env,
|
|
364
414
|
setup: {
|
|
365
415
|
running: state.setup.running,
|
|
@@ -371,14 +421,13 @@ export async function apply(ctx, config) {
|
|
|
371
421
|
};
|
|
372
422
|
}
|
|
373
423
|
|
|
374
|
-
/**
|
|
375
|
-
*
|
|
424
|
+
/** 读旧版(≤0.1.35)环境安装失败标记文件;新版不再有安装步骤 → 通常为 null。
|
|
425
|
+
* 标记在旧安装根:<profile>\.code-server-app\last-setup-error.json。 */
|
|
376
426
|
function readLastSetupError() {
|
|
377
427
|
try {
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
const
|
|
381
|
-
const marker = path.join(profileRoot, '.code-server-app', 'last-setup-error.json');
|
|
428
|
+
const root = legacyInstallRoot();
|
|
429
|
+
if (root === null) return null;
|
|
430
|
+
const marker = path.join(root, 'last-setup-error.json');
|
|
382
431
|
if (!fs.existsSync(marker)) return null;
|
|
383
432
|
const raw = fs.readFileSync(marker, 'utf8');
|
|
384
433
|
const j = JSON.parse(raw);
|
|
@@ -634,39 +683,38 @@ export async function apply(ctx, config) {
|
|
|
634
683
|
return snapshot();
|
|
635
684
|
}
|
|
636
685
|
|
|
637
|
-
/**
|
|
638
|
-
*
|
|
639
|
-
function
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
state.setup = { running: false, done: true, ok: false, logTail: `spawn 失败: ${err.message}`, startedAt: Date.now(), finishedAt: Date.now() };
|
|
651
|
-
return;
|
|
652
|
-
}
|
|
653
|
-
const onChunk = (chunk) => {
|
|
654
|
-
try {
|
|
655
|
-
const text = typeof chunk === 'string' ? chunk : chunk.toString('utf8');
|
|
656
|
-
state.setup.logTail = (state.setup.logTail + text).slice(-LOG_TAIL_MAX * 2);
|
|
657
|
-
} catch { /* ignore */ }
|
|
686
|
+
/** 0.1.36 起没有「安装环境」步骤:code-server 随插件包发布,内部依赖与预编译原生模块
|
|
687
|
+
* 都由包管理器在 `dsh plugin add` 时装好。这里只做一次重新自检(兼容旧客户端的按钮)。 */
|
|
688
|
+
function refreshEnv() {
|
|
689
|
+
state.env = envCheck();
|
|
690
|
+
state.setup = {
|
|
691
|
+
running: false,
|
|
692
|
+
done: true,
|
|
693
|
+
ok: state.env.ok,
|
|
694
|
+
logTail: state.env.ok
|
|
695
|
+
? '环境已就绪(依赖由包管理器安装,无需安装步骤)'
|
|
696
|
+
: `环境未就绪:${describeEnvProblem(state.env)}`,
|
|
697
|
+
startedAt: null,
|
|
698
|
+
finishedAt: Date.now(),
|
|
658
699
|
};
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
700
|
+
return state.setup;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/** 环境问题的一句话描述(状态卡/日志共用)。 */
|
|
704
|
+
function describeEnvProblem(env) {
|
|
705
|
+
const parts = [];
|
|
706
|
+
if (env.entry === null) parts.push('缺少包内 vendor/code-server/out/node/entry.js');
|
|
707
|
+
if (env.native !== true) parts.push('argon2 原生模块缺失(当前平台无预编译产物)');
|
|
708
|
+
if (env.vscodeInner !== true) {
|
|
709
|
+
const missing = Array.isArray(env.innerDeps?.missing) ? env.innerDeps.missing : [];
|
|
710
|
+
parts.push(`内部依赖未装全(缺 ${missing.length} 个:${missing.slice(0, 5).join(', ')}${missing.length > 5 ? ' …' : ''})`);
|
|
711
|
+
}
|
|
712
|
+
if (env.nativeRuntime !== null && env.nativeRuntime.source === null) {
|
|
713
|
+
parts.push(`平台预编译包未安装(${env.nativeRuntime.name})`);
|
|
714
|
+
} else if (Array.isArray(env.nativeRuntime?.missing) && env.nativeRuntime.missing.length > 0) {
|
|
715
|
+
parts.push(`原生模块解析失败:${env.nativeRuntime.missing.slice(0, 5).join(', ')}`);
|
|
716
|
+
}
|
|
717
|
+
return parts.length > 0 ? parts.join('; ') : '未知问题';
|
|
670
718
|
}
|
|
671
719
|
|
|
672
720
|
// ---- /api/code-server 路由(Connection 共享通道;web 与 desktop 通用) ----
|
|
@@ -724,14 +772,14 @@ export async function apply(ctx, config) {
|
|
|
724
772
|
|
|
725
773
|
async function handleSetup() {
|
|
726
774
|
try {
|
|
727
|
-
//
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
775
|
+
// 兼容旧客户端:0.1.36 起没有安装步骤,这里只重新自检并返回结果。
|
|
776
|
+
const setup = refreshEnv();
|
|
777
|
+
return jsonResponse({
|
|
778
|
+
...snapshot(),
|
|
779
|
+
ok: true,
|
|
780
|
+
message: setup.ok ? '环境已就绪,无需安装' : `环境未就绪:${setup.logTail}`,
|
|
781
|
+
error: null,
|
|
782
|
+
});
|
|
735
783
|
} catch (err) {
|
|
736
784
|
return failureResponse(err);
|
|
737
785
|
}
|
|
@@ -800,9 +848,36 @@ export async function apply(ctx, config) {
|
|
|
800
848
|
};
|
|
801
849
|
}, 'code-server: lifecycle');
|
|
802
850
|
|
|
803
|
-
//
|
|
851
|
+
// ---- 环境自检:code-server 就在插件目录里就地运行,依赖由包管理器装好,无任何安装步骤 ----
|
|
804
852
|
const record = readPidFile(cfg);
|
|
805
|
-
|
|
853
|
+
const liveInstance = record !== undefined && record !== null && isAlive(record.pid);
|
|
854
|
+
|
|
855
|
+
function maybePrestart() {
|
|
856
|
+
// 环境就绪且未运行 → 后台自动拉起(不等待),悬浮球打开时 iframe 立即加载。
|
|
857
|
+
if (state.env.ok && state.status !== 'running' && state.status !== 'starting') {
|
|
858
|
+
start().catch((err) => {
|
|
859
|
+
console.error('[code-server] prestart failed:', err && err.message ? err.message : String(err));
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
state.env = envCheck();
|
|
865
|
+
if (!vendorReady()) {
|
|
866
|
+
console.warn(`[code-server] 找不到 code-server 运行树:平台子包 ${codeServerPackageName()} 未安装,`
|
|
867
|
+
+ '且包内 vendor/code-server 不存在(开发期请先 `npm run vendor:code-server`)');
|
|
868
|
+
} else {
|
|
869
|
+
console.log(`[code-server] code-server 运行树: ${codeServerRoot()}`);
|
|
870
|
+
}
|
|
871
|
+
if (!state.env.ok) {
|
|
872
|
+
console.warn(`[code-server] 环境未就绪: ${describeEnvProblem(state.env)}`);
|
|
873
|
+
}
|
|
874
|
+
const legacyRoot = legacyInstallRoot();
|
|
875
|
+
if (legacyRoot !== null && fs.existsSync(legacyRoot)) {
|
|
876
|
+
console.log(`[code-server] 检测到旧版安装根 ${legacyRoot}(0.1.35 及更早遗留,已不再使用,可安全删除)`);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// DSH host 重启后 adopt:pid.json 有效且进程存活且 /healthz 响应 → 接管
|
|
880
|
+
if (liveInstance) {
|
|
806
881
|
const probe = await healthCheck(cfg.host, cfg.port, 800);
|
|
807
882
|
if (probe.ok) {
|
|
808
883
|
state.status = 'running';
|
|
@@ -816,13 +891,7 @@ export async function apply(ctx, config) {
|
|
|
816
891
|
}
|
|
817
892
|
}
|
|
818
893
|
|
|
819
|
-
|
|
820
|
-
// 悬浮球打开时若已 running,iframe 立即加载(消除冷启动等待)。
|
|
821
|
-
if (state.env.ok && state.status !== 'running' && state.status !== 'starting') {
|
|
822
|
-
start().catch((err) => {
|
|
823
|
-
console.error('[code-server] prestart failed:', err && err.message ? err.message : String(err));
|
|
824
|
-
});
|
|
825
|
-
}
|
|
894
|
+
maybePrestart();
|
|
826
895
|
|
|
827
896
|
console.log(`[code-server] static plugin loaded (host=${cfg.host} port=${cfg.port} auth=${cfg.auth})`);
|
|
828
897
|
}
|
package/lib/native.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// lib/native.js — 平台聚合包(预编译原生模块)的定位与校验。
|
|
2
|
+
//
|
|
3
|
+
// 模型(0.1.36 起):VS Code 内部依赖里那批「pnpm 拒绝安装」的原生包(node-pty /
|
|
4
|
+
// @vscode/sqlite3 / kerberos / koffi / ssh2 / cpu-features / @parcel/watcher /
|
|
5
|
+
// @vscode/windows-* …)在打包期由 scripts/vendor-repacks.mjs 重打包成
|
|
6
|
+
// @<scope>/dshcs-<名字>[-<平台>-<架构>],再由平台聚合包
|
|
7
|
+
// @<scope>/dsh-code-server-runtime-<平台>-<架构>
|
|
8
|
+
// 用 npm: 别名把它们装回**原始名字**。聚合包挂在插件 package.json 的
|
|
9
|
+
// optionalDependencies 上(os/cpu 限定)→ pnpm 一条命令按架构自动选中。
|
|
10
|
+
//
|
|
11
|
+
// 本模块只做两件事:
|
|
12
|
+
// 1. 找到本平台聚合包(以及它的依赖清单 = 期望可解析的原生模块名);
|
|
13
|
+
// 2. 从 code-server 运行位置出发校验这些模块是否真的能 require 到。
|
|
14
|
+
import { createRequire } from 'node:module';
|
|
15
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
16
|
+
import { dirname, join } from 'node:path';
|
|
17
|
+
import { PACKAGE_ROOT, codeServerRoot, profileRootOf } from './vendor.js';
|
|
18
|
+
|
|
19
|
+
const require_ = createRequire(import.meta.url);
|
|
20
|
+
|
|
21
|
+
/** 平台目录名,如 win32-arm64。 */
|
|
22
|
+
export function platformKey(platform = process.platform, arch = process.arch) {
|
|
23
|
+
return `${platform}-${arch}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** 本平台聚合包名(取插件 optionalDependencies 中匹配 <平台>-<架构> 的那个)。 */
|
|
27
|
+
export function runtimePackageName(platform = process.platform, arch = process.arch) {
|
|
28
|
+
const key = platformKey(platform, arch);
|
|
29
|
+
try {
|
|
30
|
+
const pkg = JSON.parse(readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf8'));
|
|
31
|
+
const declared = { ...(pkg.optionalDependencies ?? {}), ...(pkg.dependencies ?? {}) };
|
|
32
|
+
const hit = Object.keys(declared).find((name) => /^@[^/]+\/dsh-code-server-runtime-/.test(name) && name.endsWith(`-${key}`));
|
|
33
|
+
if (hit !== undefined) return hit;
|
|
34
|
+
} catch { /* 回退默认 scope */ }
|
|
35
|
+
return `@jinsiyu/dsh-code-server-runtime-${key}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** 定位已安装的平台聚合包。
|
|
39
|
+
* @returns {{name:string, version:string|null, root:string, modules:string[]}|null} */
|
|
40
|
+
export function resolveRuntime() {
|
|
41
|
+
const name = runtimePackageName();
|
|
42
|
+
const candidates = [];
|
|
43
|
+
try {
|
|
44
|
+
candidates.push(dirname(require_.resolve(`${name}/package.json`)));
|
|
45
|
+
} catch { /* 未安装 */ }
|
|
46
|
+
const profileRoot = profileRootOf();
|
|
47
|
+
for (const root of [join(PACKAGE_ROOT, 'node_modules'), profileRoot !== null ? join(profileRoot, 'node_modules') : null, PACKAGE_ROOT]) {
|
|
48
|
+
if (root !== null) candidates.push(join(root, name));
|
|
49
|
+
}
|
|
50
|
+
for (const root of candidates) {
|
|
51
|
+
const manifestPath = join(root, 'package.json');
|
|
52
|
+
if (!existsSync(manifestPath)) continue;
|
|
53
|
+
try {
|
|
54
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
55
|
+
const modules = Object.keys({ ...(manifest.dependencies ?? {}), ...(manifest.optionalDependencies ?? {}) });
|
|
56
|
+
return { name, version: typeof manifest.version === 'string' ? manifest.version : null, root, modules };
|
|
57
|
+
} catch { /* 尝试下一个 */ }
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** 从 code-server 运行位置校验原生模块能否解析(与运行时同一套向上查找规则)。
|
|
63
|
+
* 先试 `<name>/package.json`(ESM-only 包没有 require 入口,如 @microsoft/mxc-sdk),
|
|
64
|
+
* 再退回 `<name>` 本身。
|
|
65
|
+
* @param {string[]} modules 期望的原生模块名(通常来自聚合包依赖清单)
|
|
66
|
+
* @returns {{resolved:string[], missing:string[]}} */
|
|
67
|
+
export function verifyNatives(modules) {
|
|
68
|
+
// 锚点 = code-server 运行根(平台子包内的 code-server/ 或包内 vendor);
|
|
69
|
+
// 从它向上:自带 node_modules → 插件 node_modules → <profile>/node_modules。
|
|
70
|
+
const from = createRequire(join(codeServerRoot() ?? PACKAGE_ROOT, 'package.json'));
|
|
71
|
+
const resolved = [];
|
|
72
|
+
const missing = [];
|
|
73
|
+
for (const name of modules) {
|
|
74
|
+
let ok = false;
|
|
75
|
+
for (const spec of [`${name}/package.json`, name]) {
|
|
76
|
+
try {
|
|
77
|
+
from.resolve(spec);
|
|
78
|
+
ok = true;
|
|
79
|
+
break;
|
|
80
|
+
} catch { /* 试下一个 */ }
|
|
81
|
+
}
|
|
82
|
+
if (ok) resolved.push(name);
|
|
83
|
+
else missing.push(name);
|
|
84
|
+
}
|
|
85
|
+
return { resolved, missing };
|
|
86
|
+
}
|
package/lib/vendor.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// lib/vendor.js — 内置 code-server 产物的路径工具(host 与脚本共用)。
|
|
2
|
+
//
|
|
3
|
+
// 模型(0.1.37 起):
|
|
4
|
+
// - code-server 本体 + 它自己的运行时依赖(约 232MB 解包)打成**平台专属子包**发布:
|
|
5
|
+
// @<scope>/dshcs-code-server-<platform>-<arch>@<code-server 版本>
|
|
6
|
+
// (os/cpu 限定,挂在插件 optionalDependencies 上 → pnpm 按架构自动选中);
|
|
7
|
+
// 子包内布局:<pkg>/code-server/{out,lib/vscode,node_modules,…};
|
|
8
|
+
// - VS Code 内部依赖(约 1GB)与需编译的原生模块仍由包管理器安装:
|
|
9
|
+
// 纯 JS 部分写在插件 package.json 的 dependencies;
|
|
10
|
+
// pnpm 拒绝安装的原生包由 scripts/vendor-repacks.mjs 重打包成 @<scope>/dshcs-*,
|
|
11
|
+
// 再由平台聚合包 @<scope>/dsh-code-server-runtime-<platform>-<arch> 用 npm: 别名装回原名字;
|
|
12
|
+
// - 插件包内 `vendor/code-server/` 只作为**开发期/兼容**回退(0.1.37 起不再随包发布)。
|
|
13
|
+
import { createRequire } from 'node:module';
|
|
14
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
15
|
+
import { dirname, join, resolve } from 'node:path';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
|
|
18
|
+
const here = dirname(fileURLToPath(import.meta.url)); // <pkg>/lib
|
|
19
|
+
const require_ = createRequire(import.meta.url);
|
|
20
|
+
export const PACKAGE_ROOT = resolve(here, '..');
|
|
21
|
+
/** 包内 code-server 树(0.1.36 及更早随包发布;现在仅开发期/兼容回退)。 */
|
|
22
|
+
export const VENDOR_TREE = join(PACKAGE_ROOT, 'vendor', 'code-server');
|
|
23
|
+
const VENDOR_META = join(PACKAGE_ROOT, 'vendor', 'VENDOR.json');
|
|
24
|
+
/** 旧版安装根目录名(0.1.35 及更早);仅用于迁移提示。 */
|
|
25
|
+
export const APP_DIR_NAME = '.code-server-app';
|
|
26
|
+
|
|
27
|
+
/** <profile> 根;插件不在 profile 布局(开发期独立目录)时返回 null。 */
|
|
28
|
+
export function profileRootOf() {
|
|
29
|
+
const profileRoot = resolve(PACKAGE_ROOT, '..', '..');
|
|
30
|
+
const isProfile = existsSync(join(profileRoot, 'node_modules')) && existsSync(join(profileRoot, 'package.json'));
|
|
31
|
+
return isProfile ? profileRoot : null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** 旧版安装根(<profile>\.code-server-app);存在时提示可删除。 */
|
|
35
|
+
export function legacyInstallRoot() {
|
|
36
|
+
const profileRoot = profileRootOf();
|
|
37
|
+
return profileRoot !== null ? join(profileRoot, APP_DIR_NAME) : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** 本平台 code-server 子包名:优先取插件 optionalDependencies 里匹配 <平台>-<架构> 的声明。 */
|
|
41
|
+
export function codeServerPackageName(platform = process.platform, arch = process.arch) {
|
|
42
|
+
const key = `${platform}-${arch}`;
|
|
43
|
+
try {
|
|
44
|
+
const manifest = JSON.parse(readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf8'));
|
|
45
|
+
const declared = { ...(manifest.optionalDependencies ?? {}), ...(manifest.dependencies ?? {}) };
|
|
46
|
+
const hit = Object.keys(declared).find((name) => /dshcs-code-server-/.test(name) && name.endsWith(`-${key}`));
|
|
47
|
+
if (hit !== undefined) return hit;
|
|
48
|
+
} catch { /* 回退默认 scope */ }
|
|
49
|
+
return `@jinsiyu/dshcs-code-server-${key}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** code-server 运行根(优先依赖安装的平台子包,其次包内 vendor)。
|
|
53
|
+
* @returns {string|null} 绝对路径,如 <profile>\node_modules\@jinsiyu\dshcs-code-server-win32-arm64\code-server */
|
|
54
|
+
export function codeServerRoot() {
|
|
55
|
+
const name = codeServerPackageName();
|
|
56
|
+
try {
|
|
57
|
+
const manifest = require_.resolve(`${name}/package.json`);
|
|
58
|
+
const root = join(dirname(manifest), 'code-server');
|
|
59
|
+
if (existsSync(join(root, 'out', 'node', 'entry.js'))) return root;
|
|
60
|
+
} catch { /* 子包未安装 */ }
|
|
61
|
+
if (existsSync(join(VENDOR_TREE, 'out', 'node', 'entry.js'))) return VENDOR_TREE;
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** code-server 入口脚本(out/node/entry.js);不可用时返回 null。 */
|
|
66
|
+
export function codeServerEntry() {
|
|
67
|
+
const root = codeServerRoot();
|
|
68
|
+
return root !== null ? join(root, 'out', 'node', 'entry.js') : null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** code-server 运行根(旧名,脚本/调用方沿用)。 */
|
|
72
|
+
export function codeServerTarget() {
|
|
73
|
+
return codeServerRoot();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function readTreeVersion(dir) {
|
|
77
|
+
try {
|
|
78
|
+
const manifest = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8'));
|
|
79
|
+
return typeof manifest.version === 'string' ? manifest.version : null;
|
|
80
|
+
} catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** 内置产物元数据(vendor/VENDOR.json;打包期写入,随包发布)。 */
|
|
86
|
+
export function readVendorMeta() {
|
|
87
|
+
try {
|
|
88
|
+
const meta = JSON.parse(readFileSync(VENDOR_META, 'utf8'));
|
|
89
|
+
return meta !== null && typeof meta === 'object' ? meta : null;
|
|
90
|
+
} catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** 内置 code-server 版本(VENDOR.json 优先,回退树内 package.json)。 */
|
|
96
|
+
export function vendoredVersion() {
|
|
97
|
+
const meta = readVendorMeta();
|
|
98
|
+
if (meta !== null && typeof meta.codeServerVersion === 'string' && meta.codeServerVersion !== '') {
|
|
99
|
+
return meta.codeServerVersion;
|
|
100
|
+
}
|
|
101
|
+
return readTreeVersion(VENDOR_TREE);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** code-server 是否可用(平台子包已安装或包内 vendor 在位)。 */
|
|
105
|
+
export function vendorReady() {
|
|
106
|
+
return codeServerRoot() !== null;
|
|
107
|
+
}
|