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/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. profile 专用目录:profileRoot/.code-server-app/node_modules/code-server/...
126
- * (方案 D 默认安装位,独立项目、避开 profile 依赖树冲突);
127
- * 2. 包内:插件目录/node_modules/code-server/...(开发期 link:/独立 npm install 布局)
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 here = path.dirname(fileURLToPath(import.meta.url)); // .../dsh-code-server-app/lib 或 .../dsh-code-server/lib
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 entry of candidates) {
139
- if (fs.existsSync(entry)) return entry;
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
- /** 环境检测:code-server 入口 + 原生模块 + VS Code 内部依赖 是否就绪。
197
- * 返回 { ok, entry, native, vscodeInner, node, platform, arch, pathToSetup }。 */
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 nativeOk = csRoot !== null && (
202
- fs.existsSync(path.join(csRoot, 'node_modules', 'argon2', 'build', 'Release', 'argon2.node')) ||
203
- fs.existsSync(path.join(csRoot, 'node_modules', 'argon2', 'prebuilds'))
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 innerOk = csRoot !== null &&
206
- fs.existsSync(path.join(csRoot, 'lib', 'vscode', 'node_modules'));
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 && innerOk,
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: innerOk,
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
- `npm install -g code-server@latest(Windows 原生需配套最新版 node-gyp VS Spectre 缓解库),` +
249
- `或在 cordis.patch.yml 的 code-server config 中把 bin 指向已安装的 code-server 可执行文件 / out/node/entry.js。`,
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: false, ok: false, logTail: '', startedAt: null, finishedAt: null }, // 环境安装任务
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
- /** 读 setup 失败标记文件(按需安装失败时写入;成功则不存在)→ 用户可查看。
375
- * 标记在安装根:<profile>\.code-server-app\last-setup-error.json。 */
424
+ /** 读旧版(≤0.1.35)环境安装失败标记文件;新版不再有安装步骤 通常为 null。
425
+ * 标记在旧安装根:<profile>\.code-server-app\last-setup-error.json。 */
376
426
  function readLastSetupError() {
377
427
  try {
378
- const here = path.dirname(fileURLToPath(import.meta.url));
379
- // 插件目录 <profile>\node_modules\<name> 向上两级 = <profile> 根
380
- const profileRoot = path.join(here, '..', '..');
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
- /** 后台启动环境安装(setup 脚本):npm 自装 code-server + native + vscode 内部依赖。
638
- * 异步运行,进度经 status.setup 轮询返回;完成后刷新 envCheck。 */
639
- function startSetup() {
640
- if (state.setup.running) return;
641
- const script = envCheck().pathToSetup;
642
- state.setup = { running: true, done: false, ok: false, logTail: '', startedAt: Date.now(), finishedAt: null };
643
- let proc;
644
- try {
645
- proc = spawn(process.execPath, [script], {
646
- stdio: ['ignore', 'pipe', 'pipe'],
647
- windowsHide: true,
648
- });
649
- } catch (err) {
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
- proc.stdout?.on?.('data', onChunk);
660
- proc.stderr?.on?.('data', onChunk);
661
- proc.on('error', (err) => {
662
- state.setup = { ...state.setup, running: false, done: true, ok: false, logTail: state.setup.logTail + `\nspawn 错误: ${err.message}`, finishedAt: Date.now() };
663
- state.env = envCheck();
664
- });
665
- proc.on('exit', (code) => {
666
- state.setup = { ...state.setup, running: false, done: true, ok: code === 0, finishedAt: Date.now() };
667
- state.env = envCheck();
668
- console.log(`[code-server] setup finished code=${code} env.ok=${state.env.ok}`);
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
- // 环境安装:后台执行 setup 脚本(npm 自装 code-server + native + vscode 内部依赖)
728
- if (state.setup.running) {
729
- // 已在安装中:同样视为"已发起"(客户端会轮询 setup 状态直至结束)
730
- return jsonResponse({ ...snapshot(), ok: true, message: '环境安装已在进行中,请等待完成', error: null });
731
- }
732
- startSetup();
733
- // 安装任务已启动;服务态 error(如未安装)不掩盖安装态
734
- return jsonResponse({ ...snapshot(), ok: true });
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
- // DSH host 重启后 adopt:pid.json 有效且进程存活且 /healthz 响应 → 接管
851
+ // ---- 环境自检:code-server 就在插件目录里就地运行,依赖由包管理器装好,无任何安装步骤 ----
804
852
  const record = readPidFile(cfg);
805
- if (record && isAlive(record.pid)) {
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
+ }