dsh-code-server-app 0.3.69 → 0.3.71
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 +169 -69
- package/README.md +130 -63
- package/cordis.patch.yml +1 -2
- package/lib/client.js +103 -273
- package/lib/index.js +46 -136
- package/lib/vendor.js +7 -44
- package/lib/vendored.json +1 -1
- package/package.json +7 -5
- package/scripts/vendor-repacks.mjs +18 -1
- package/vendor/VENDOR.json +5 -5
package/lib/index.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* 路由经 ctx.connection.fetch.register 挂在 Connection 的共享 /api 通道上(**不依赖 webServer**):
|
|
10
10
|
* web profile 由 Connection 把 /api 挂到 webServer;desktop 由 apps/desktop-host 交给同一个
|
|
11
11
|
* createSharedFetchHandler('/api')(IPC 帧管道)→ 客户端只需同源 fetch('/api/code-server/<op>')。
|
|
12
|
-
* GET status | POST start{cwd?} | stop |
|
|
12
|
+
* GET status | POST start{cwd?} | stop | open-file{file,line?} | ui-mode{sidebar}
|
|
13
13
|
*
|
|
14
14
|
* 不变量:进程生命周期归本插件(启动写 pid.json,停止树级终止);host 重启后按 pid.json + /healthz
|
|
15
15
|
* adopt 存活实例(不重复启动、不误杀);auth 固定 none(0.2.0 起不再支持口令);插件销毁时回收进程与路由。
|
|
@@ -26,12 +26,11 @@ import { fileURLToPath } from 'node:url';
|
|
|
26
26
|
import { createRequire } from 'node:module';
|
|
27
27
|
import {
|
|
28
28
|
PACKAGE_ROOT,
|
|
29
|
-
codeServerPackageName,
|
|
30
|
-
legacyInstallRoot,
|
|
31
29
|
productPath,
|
|
32
30
|
readTreeVersion,
|
|
33
31
|
vendoredVersion,
|
|
34
32
|
vendorReady,
|
|
33
|
+
vscodeServerPackageName,
|
|
35
34
|
vsRoot,
|
|
36
35
|
vsServerEntry,
|
|
37
36
|
} from './vendor.js';
|
|
@@ -103,36 +102,25 @@ if (z === null || z === undefined) {
|
|
|
103
102
|
);
|
|
104
103
|
}
|
|
105
104
|
|
|
105
|
+
/** profile 条目 id(cordis.patch.yml 的 `- id: code-server`),同时也是**设置域里的条目 id**:
|
|
106
|
+
* `ctx.configForms.get(entryId)` 与宿主的 settings.describe 都按条目 id 取用(表单只展示
|
|
107
|
+
* "活动且可唯一定位"的条目)。客户端半部的 ENTRY_ID 必须与此一致。 */
|
|
106
108
|
export const name = 'code-server';
|
|
107
109
|
// connection:client↔host 通道(web 走 webServer 的 /api,desktop 走 IPC 帧管道);
|
|
108
|
-
// settings
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
// · 新线(alpha ≥ 0.1.7-alpha.1):`register/get/watch` 已删除,配置就是**本插件条目自己的 Config**
|
|
112
|
-
// (可编辑字段必须 `.volatile()`),读值走 `config.<field>.get()` 活引用,变更通知走
|
|
113
|
-
// `settings/document-updated`。
|
|
114
|
-
// 两条线的选择在 apply 里**按能力探测**(`typeof settings.register === 'function'`),不比较版本号。
|
|
110
|
+
// settings:**设置的数据域**(0.2.0 线只有这一种模型)—— 配置就是**本插件条目自己的 Config**
|
|
111
|
+
// (可编辑字段必须 `.volatile()`),读值走 `config.<field>.get()` 活引用,变更通知走
|
|
112
|
+
// `settings/document-updated`。两者都是硬依赖:没有 settings 就建不出配置、也声明不了配置页。
|
|
115
113
|
export const inject = ['connection', 'settings'];
|
|
116
114
|
|
|
117
|
-
/** profile 条目 id(cordis.patch.yml 的 `- id: code-server`)。
|
|
118
|
-
* 新线里它同时也是**设置命名空间**:`ctx.configForms.get(entryId)` 与宿主的 settings.describe
|
|
119
|
-
* 都按条目 id 取用(表单只展示"活动且可唯一定位"的条目)。客户端半部的 ENTRY_ID 必须与此一致。 */
|
|
120
|
-
export const SETTINGS_NS = 'code-server';
|
|
121
|
-
|
|
122
115
|
/**
|
|
123
|
-
* 可配置字段表(**唯一真源**)
|
|
116
|
+
* 可配置字段表(**唯一真源**):Config 逐字段据此生成。
|
|
124
117
|
*
|
|
125
|
-
*
|
|
126
|
-
* ·
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* 而**那里不能用带 volatile 的 schema**:schemastery 3.18.3 起 volatile 是**解析期**行为
|
|
132
|
-
* (`createVolatile(value)` 在 schema 里完成),旧的 settings 域不认识它,会把活引用直接交给线路
|
|
133
|
-
* (JSON 化后是空对象 `{}`)⇒ 卡片读到的全是空值。所以旧线注册 `SettingsSchema`(同字段、无 volatile)。
|
|
134
|
-
* · rc 线的 schemastery 是 3.18.2,**根本没有 `.volatile()`**(3.18.3 才加)⇒ 无条件调用会在模块
|
|
135
|
-
* 求值期抛 `TypeError`、整棵插件树加载失败。故一律经 `vol()` 探测能力后再包。
|
|
118
|
+
* 为什么字段必须 volatile(0.3.66 实测):
|
|
119
|
+
* · 宿主只把 **`.volatile()` 字段**当作该条目的可编辑表单(settings.describe 按 volatile 投影
|
|
120
|
+
* 并明确报错 `Config field "x" is not volatile`),且保存会**就地重解析**这个叶子(不重挂插件)
|
|
121
|
+
* ⇒ 字段必须 volatile,读值必须走 config.<field>.get()。
|
|
122
|
+
* · 老部署自带的 schemastery(< 3.18.3)没有 `.volatile()` ⇒ 无条件调用会在模块求值期抛
|
|
123
|
+
* `TypeError`、整棵插件树加载失败。故一律经 `vol()` 探测能力后再包。
|
|
136
124
|
*
|
|
137
125
|
* 旧设置文档里残留的 reserveComposer / windowedOpen / fileOpenScope 不再出现在 schema 里 ——
|
|
138
126
|
* schemastery 的 object 非 strict 会把未知键原样 merge 进解析结果,既不报错也不影响取值,故无需迁移。
|
|
@@ -180,25 +168,20 @@ const SETTING_FIELDS = {
|
|
|
180
168
|
fimDisableGlobs: () => z.string().default(''),
|
|
181
169
|
};
|
|
182
170
|
|
|
183
|
-
/** volatile 包装(**能力探测**,不看版本号)
|
|
171
|
+
/** volatile 包装(**能力探测**,不看版本号):部署自带的 schemastery 若没有这个方法则原样返回。 */
|
|
184
172
|
const vol = (schema) => (typeof schema.volatile === 'function' ? schema.volatile() : schema);
|
|
185
173
|
|
|
186
|
-
/**
|
|
174
|
+
/** 用同一份字段表建 schema;`wrap` 决定字段是否 volatile(唯一调用点是 Config 的 `vol()`)。 */
|
|
187
175
|
function settingShape(wrap) {
|
|
188
176
|
const shape = {};
|
|
189
177
|
for (const [key, make] of Object.entries(SETTING_FIELDS)) shape[key] = wrap(make());
|
|
190
178
|
return shape;
|
|
191
179
|
}
|
|
192
180
|
|
|
193
|
-
/** **本插件条目的 Config**(
|
|
181
|
+
/** **本插件条目的 Config**(0.2.0 线唯一的配置模型)。
|
|
194
182
|
* 字段全部 volatile:这是"宿主把该条目暴露成可编辑表单 + 保存就地重解析"的前提。 */
|
|
195
183
|
export const Config = z.object(settingShape(vol));
|
|
196
184
|
|
|
197
|
-
/** **旧线注册用 schema**(rc 0.1.5-rc.x 与 0.1.6-alpha.2 的 `settings.register`):
|
|
198
|
-
* 同字段、**不带 volatile** —— 原因见 SETTING_FIELDS 上方那段(schemastery 的 volatile 是解析期行为,
|
|
199
|
-
* 旧的 settings 域会把活引用交给线路,卡片会读到空对象)。 */
|
|
200
|
-
export const SettingsSchema = z.object(settingShape((schema) => schema));
|
|
201
|
-
|
|
202
185
|
const DEFAULT_CONFIG = {
|
|
203
186
|
bin: '',
|
|
204
187
|
host: '127.0.0.1',
|
|
@@ -705,20 +688,15 @@ let activeBridgeDispatch = null;
|
|
|
705
688
|
export async function apply(ctx, config) {
|
|
706
689
|
const cfg = { ...DEFAULT_CONFIG, ...(config ?? {}) };
|
|
707
690
|
|
|
708
|
-
// ----
|
|
709
|
-
// 判据是**能力**不是版本号:旧线的 settings 服务有 register(rc 0.1.5-rc.x,以及数据面还没换的
|
|
710
|
-
// 0.1.6-alpha.2);新线(alpha ≥ 0.1.7-alpha.1)删掉了它,配置就是本条目自己的 volatile Config。
|
|
711
|
-
const settingsSvc = ctx.get('settings');
|
|
712
|
-
const legacySettings = settingsSvc !== undefined && settingsSvc !== null
|
|
713
|
-
&& typeof settingsSvc.register === 'function';
|
|
691
|
+
// ---- 设置:本条目自己的 volatile Config(见文件头与 SETTING_FIELDS 的说明) ----
|
|
714
692
|
|
|
715
693
|
/**
|
|
716
694
|
* 读一个配置字段的**当前值**。
|
|
717
695
|
*
|
|
718
|
-
*
|
|
696
|
+
* 宿主编译期把 volatile 叶子换成**活引用**(`{ get() }`,见 cosmokit 的 createVolatile),
|
|
719
697
|
* 保存(表单 mutate → profile patch → loader 重解析)只就地更新这个叶子、**不重挂插件**,
|
|
720
|
-
* 所以每次都现读引用才是"实时值"
|
|
721
|
-
*
|
|
698
|
+
* 所以每次都现读引用才是"实时值"。部署自带的 schemastery 若没有 `.volatile()`,叶子就是普通值 ——
|
|
699
|
+
* 同一个读法两种形状都对。
|
|
722
700
|
*/
|
|
723
701
|
const cfgValue = (field) => {
|
|
724
702
|
const leaf = config === null || config === undefined ? undefined : config[field];
|
|
@@ -731,8 +709,8 @@ export async function apply(ctx, config) {
|
|
|
731
709
|
return live === undefined ? cfg[field] : live;
|
|
732
710
|
};
|
|
733
711
|
/**
|
|
734
|
-
*
|
|
735
|
-
*
|
|
712
|
+
* 从**当前配置**拼出一份快照,交给 commitSettings。
|
|
713
|
+
* 缺项就地取 DEFAULT_CONFIG / normalize 的兜底。
|
|
736
714
|
*/
|
|
737
715
|
const readSettingsSnapshot = () => ({
|
|
738
716
|
serve: readCfg('serve') === 'dsh' ? 'dsh' : 'loopback',
|
|
@@ -769,9 +747,9 @@ export async function apply(ctx, config) {
|
|
|
769
747
|
/**
|
|
770
748
|
* 把一份设置快照落到本地状态上,并在**值真的变了**时执行随动副作用。
|
|
771
749
|
*
|
|
772
|
-
*
|
|
773
|
-
*
|
|
774
|
-
*
|
|
750
|
+
* 触发点:装配时以 initial=true 落一遍初值(不触发副作用);之后每次
|
|
751
|
+
* `ctx.on('settings/document-updated', …)` → readSettingsSnapshot() → 这里
|
|
752
|
+
* (volatile 叶子的就地重解析不重挂插件,所以随动必须由事件驱动)。
|
|
775
753
|
* 副作用的清单(与 0.3.0 起的语义逐条一致):编辑器桥上下线、FIM 适配器注册/注销、
|
|
776
754
|
* FIM 三个子项即时生效、状态里的四元组(serve/keepResident/claimExtensions/fullscreenOnOpen)。
|
|
777
755
|
*/
|
|
@@ -853,26 +831,10 @@ export async function apply(ctx, config) {
|
|
|
853
831
|
}
|
|
854
832
|
};
|
|
855
833
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
const scope = settingsSvc.register(SETTINGS_NS, SettingsSchema);
|
|
861
|
-
const rawDoc = settingsSvc.get(SETTINGS_NS);
|
|
862
|
-
if (rawDoc !== undefined && rawDoc !== null) {
|
|
863
|
-
commitSettings(scope.get(), { initial: true });
|
|
864
|
-
}
|
|
865
|
-
scope.watch((next) => { commitSettings(next); });
|
|
866
|
-
} catch (error) {
|
|
867
|
-
console.error(`[code-server] settings unavailable; using defaults (claimExtensions=${DEFAULT_CLAIM_EXTENSIONS}, fullscreenOnOpen=true, keepResident=true): ${error.message}`);
|
|
868
|
-
}
|
|
869
|
-
} else {
|
|
870
|
-
// ---- 新线:本条目自己的 volatile Config(alpha ≥ 0.1.7-alpha.1) ----
|
|
871
|
-
// 先按活叶子装一遍初值(不触发副作用),再把后续变更挂到 settings 的文档事件上:
|
|
872
|
-
// volatile 叶子的就地重解析不重挂插件,所以随动必须由事件驱动。
|
|
873
|
-
commitSettings(readSettingsSnapshot(), { initial: true });
|
|
874
|
-
ctx.on('settings/document-updated', () => { commitSettings(readSettingsSnapshot()); });
|
|
875
|
-
}
|
|
834
|
+
// 先按活叶子装一遍初值(不触发副作用),再把后续变更挂到 settings 的文档事件上:
|
|
835
|
+
// volatile 叶子的就地重解析不重挂插件,所以随动必须由事件驱动。
|
|
836
|
+
commitSettings(readSettingsSnapshot(), { initial: true });
|
|
837
|
+
ctx.on('settings/document-updated', () => { commitSettings(readSettingsSnapshot()); });
|
|
876
838
|
|
|
877
839
|
// ---- 自带配置页的声明(0.3.66) ----
|
|
878
840
|
// 本插件的设置面是客户端半部自绘的表单(插件页的 `plugins.bundle.config`),不是按 schema 自动生成的页面。
|
|
@@ -884,7 +846,8 @@ export async function apply(ctx, config) {
|
|
|
884
846
|
try {
|
|
885
847
|
ctx.inject(['settings'], (child) => {
|
|
886
848
|
const svc = typeof child.get === 'function' ? child.get('settings') : child.settings;
|
|
887
|
-
|
|
849
|
+
// 没有 configure 就不注册(防御:Settings 服务是迟加载/被替换的极简实现);不影响配置读写。
|
|
850
|
+
if (svc === undefined || svc === null || typeof svc.configure !== 'function') return;
|
|
888
851
|
child.effect(() => {
|
|
889
852
|
try {
|
|
890
853
|
return svc.configure({ auto: false }, ctx.fiber);
|
|
@@ -941,7 +904,6 @@ export async function apply(ctx, config) {
|
|
|
941
904
|
/** 本实例是否由插件的自动预启动拉起(旧版 DSH 上报后据此回收,不动用户/被 adopt 的实例) */
|
|
942
905
|
prestarted: false,
|
|
943
906
|
env: envCheck(), // 环境检测(VS Code 树 / server 入口 / 内部依赖 / 预编译原生包)
|
|
944
|
-
setup: { running: false, done: true, ok: true, logTail: '0.1.36 起由包管理器安装依赖,无需「安装环境」步骤', startedAt: null, finishedAt: null }, // 兼容旧客户端
|
|
945
907
|
};
|
|
946
908
|
|
|
947
909
|
let child = null;
|
|
@@ -1522,6 +1484,15 @@ export async function apply(ctx, config) {
|
|
|
1522
1484
|
return `http://${cfg.host}:${state.port}/${token}`;
|
|
1523
1485
|
}
|
|
1524
1486
|
|
|
1487
|
+
/** `?v=` 缓存击穿标记 = 插件版本 + VS Code 树。子进程 env(DSHCS_HTML_TAG)与客户端 iframe URL 共用一条。
|
|
1488
|
+
*
|
|
1489
|
+
* 为什么 iframe URL 也要带它:只靠 `?s=<pid>` 不够 —— 升级插件/换树时 IDE 进程常常被 adopt(pid 不变),
|
|
1490
|
+
* 旧页面就会一直留在常驻面里。2026-10-01 的实例:面里跑着 `0.3.68-stable-59c988c7…`(插件 0.3.68 + 4.138),
|
|
1491
|
+
* 而 endpoint.json 已是 `stable-53c2f325…`(4.139.1)⇒ 用户看到的是升级前的页面、且那条 WebSocket 早已半死。 */
|
|
1492
|
+
function htmlTag() {
|
|
1493
|
+
return `${pluginVersion()}-${productPath() ?? 'dev'}`.replace(/[^A-Za-z0-9._-]/g, '');
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1525
1496
|
function snapshot() {
|
|
1526
1497
|
const running = state.status === 'running' && state.pid !== null;
|
|
1527
1498
|
const dshMode = state.serve === 'dsh';
|
|
@@ -1540,6 +1511,8 @@ export async function apply(ctx, config) {
|
|
|
1540
1511
|
url: running ? (dshMode ? '/code-server/' : loopbackUrl()) : null,
|
|
1541
1512
|
productPath: state.env?.productPath ?? null,
|
|
1542
1513
|
version: state.version,
|
|
1514
|
+
// 给客户端拼 `&v=<htmlTag>`:插件或树变了 ⇒ URL 变 ⇒ 常驻 iframe 自动换页(见 htmlTag() 的注释)
|
|
1515
|
+
htmlTag: htmlTag(),
|
|
1543
1516
|
error: state.error,
|
|
1544
1517
|
logTail: state.logTail.slice(-LOG_TAIL_MAX),
|
|
1545
1518
|
adopted: state.adopted,
|
|
@@ -1559,35 +1532,9 @@ export async function apply(ctx, config) {
|
|
|
1559
1532
|
file: path.join(bridgeMetaDir, 'bridge.json'),
|
|
1560
1533
|
},
|
|
1561
1534
|
env: state.env,
|
|
1562
|
-
setup: {
|
|
1563
|
-
running: state.setup.running,
|
|
1564
|
-
done: state.setup.done,
|
|
1565
|
-
ok: state.setup.ok,
|
|
1566
|
-
logTail: state.setup.logTail.slice(-LOG_TAIL_MAX),
|
|
1567
|
-
},
|
|
1568
|
-
lastSetupError: readLastSetupError(),
|
|
1569
1535
|
};
|
|
1570
1536
|
}
|
|
1571
1537
|
|
|
1572
|
-
/** 读旧版(≤0.1.35)环境安装失败标记文件;新版不再有安装步骤 → 通常为 null。
|
|
1573
|
-
* 标记在旧安装根:<profile>\.code-server-app\last-setup-error.json。 */
|
|
1574
|
-
function readLastSetupError() {
|
|
1575
|
-
try {
|
|
1576
|
-
const root = legacyInstallRoot();
|
|
1577
|
-
if (root === null) return null;
|
|
1578
|
-
const marker = path.join(root, 'last-setup-error.json');
|
|
1579
|
-
if (!fs.existsSync(marker)) return null;
|
|
1580
|
-
const raw = fs.readFileSync(marker, 'utf8');
|
|
1581
|
-
const j = JSON.parse(raw);
|
|
1582
|
-
return {
|
|
1583
|
-
at: j && typeof j.at === 'string' ? j.at : null,
|
|
1584
|
-
error: j && typeof j.error === 'string' ? j.error : '安装脚本失败(无详情)',
|
|
1585
|
-
};
|
|
1586
|
-
} catch {
|
|
1587
|
-
return null;
|
|
1588
|
-
}
|
|
1589
|
-
}
|
|
1590
|
-
|
|
1591
1538
|
function appendLog(chunk) {
|
|
1592
1539
|
try {
|
|
1593
1540
|
const text = typeof chunk === 'string' ? chunk : chunk.toString('utf8');
|
|
@@ -1865,7 +1812,7 @@ export async function apply(ctx, config) {
|
|
|
1865
1812
|
env.DSHCS_EXTENSIONS_DIR = extensionsDir;
|
|
1866
1813
|
// ?v= 缓存击穿标记:按"插件版本 + VS Code 树"生成 —— 只在真升级时让渲染器丢掉旧 bundle,
|
|
1867
1814
|
// 平时正常命中缓存(workbench.js 有 18MB,每次启动都重拉不划算)。
|
|
1868
|
-
env.DSHCS_HTML_TAG =
|
|
1815
|
+
env.DSHCS_HTML_TAG = htmlTag();
|
|
1869
1816
|
// 0.3.45 起不再需要给子进程加 NODE_PATH:重打包原生包按**真名**挂在插件依赖上,而它们要的
|
|
1870
1817
|
// **原始名字**(node-pty / @vscode/sqlite3 …)由 ensureRuntimeLayout() 在树里补 junction ——
|
|
1871
1818
|
// 目录链同时满足 ESM import 与 CJS require,NODE_PATH 只对 CJS 生效且属兜底。
|
|
@@ -1982,23 +1929,6 @@ export async function apply(ctx, config) {
|
|
|
1982
1929
|
return snapshot();
|
|
1983
1930
|
}
|
|
1984
1931
|
|
|
1985
|
-
/** 0.1.36 起没有「安装环境」步骤:code-server 随插件包发布,内部依赖与预编译原生模块
|
|
1986
|
-
* 都由包管理器在 `dsh plugin add` 时装好。这里只做一次重新自检(兼容旧客户端的按钮)。 */
|
|
1987
|
-
function refreshEnv() {
|
|
1988
|
-
state.env = envCheck();
|
|
1989
|
-
state.setup = {
|
|
1990
|
-
running: false,
|
|
1991
|
-
done: true,
|
|
1992
|
-
ok: state.env.ok,
|
|
1993
|
-
logTail: state.env.ok
|
|
1994
|
-
? '环境已就绪(依赖由包管理器安装,无需安装步骤)'
|
|
1995
|
-
: `环境未就绪:${describeEnvProblem(state.env)}`,
|
|
1996
|
-
startedAt: null,
|
|
1997
|
-
finishedAt: Date.now(),
|
|
1998
|
-
};
|
|
1999
|
-
return state.setup;
|
|
2000
|
-
}
|
|
2001
|
-
|
|
2002
1932
|
/** 环境问题的一句话描述(状态卡/日志共用)。 */
|
|
2003
1933
|
function describeEnvProblem(env) {
|
|
2004
1934
|
const parts = [];
|
|
@@ -2069,21 +1999,6 @@ export async function apply(ctx, config) {
|
|
|
2069
1999
|
}
|
|
2070
2000
|
}
|
|
2071
2001
|
|
|
2072
|
-
async function handleSetup() {
|
|
2073
|
-
try {
|
|
2074
|
-
// 兼容旧客户端:0.1.36 起没有安装步骤,这里只重新自检并返回结果。
|
|
2075
|
-
const setup = refreshEnv();
|
|
2076
|
-
return jsonResponse({
|
|
2077
|
-
...snapshot(),
|
|
2078
|
-
ok: true,
|
|
2079
|
-
message: setup.ok ? '环境已就绪,无需安装' : `环境未就绪:${setup.logTail}`,
|
|
2080
|
-
error: null,
|
|
2081
|
-
});
|
|
2082
|
-
} catch (err) {
|
|
2083
|
-
return failureResponse(err);
|
|
2084
|
-
}
|
|
2085
|
-
}
|
|
2086
|
-
|
|
2087
2002
|
async function handleOpenFile(request) {
|
|
2088
2003
|
try {
|
|
2089
2004
|
// 打开文件:host 写信号文件,内置扩展(dshcs-open-file)在 VS Code 中打开它。
|
|
@@ -2530,7 +2445,6 @@ export async function apply(ctx, config) {
|
|
|
2530
2445
|
const disposers = [ { path: `${API_BASE}/status`, methods: ['GET'], fetch: handleStatus },
|
|
2531
2446
|
{ path: `${API_BASE}/start`, methods: ['POST'], fetch: handleStart },
|
|
2532
2447
|
{ path: `${API_BASE}/stop`, methods: ['POST'], fetch: handleStop },
|
|
2533
|
-
{ path: `${API_BASE}/setup`, methods: ['POST'], fetch: handleSetup },
|
|
2534
2448
|
{ path: `${API_BASE}/open-file`, methods: ['POST'], fetch: handleOpenFile },
|
|
2535
2449
|
{ path: `${API_BASE}/ui-mode`, methods: ['POST'], fetch: handleUiMode },
|
|
2536
2450
|
// ---- 「问 DSH」对话框(0.3.24):DSH 页面里的 client 半部调这几条 ----
|
|
@@ -2604,7 +2518,7 @@ export async function apply(ctx, config) {
|
|
|
2604
2518
|
console.warn(`[code-server] 依赖链接创建失败: ${layout.failed.join('; ')}`);
|
|
2605
2519
|
}
|
|
2606
2520
|
if (!vendorReady()) {
|
|
2607
|
-
console.warn(`[code-server] 找不到 VS Code 树:平台无关包 ${
|
|
2521
|
+
console.warn(`[code-server] 找不到 VS Code 树:平台无关包 ${vscodeServerPackageName()} 未安装,`
|
|
2608
2522
|
+ '且包内 vendor/vscode 不存在(开发期请先 `node scripts/vendor-vscode-server.mjs --dev-links`)');
|
|
2609
2523
|
} else {
|
|
2610
2524
|
console.log(`[code-server] VS Code 树: ${vsRoot()}(productPath=${state.env.productPath ?? '?'})`);
|
|
@@ -2612,10 +2526,6 @@ export async function apply(ctx, config) {
|
|
|
2612
2526
|
if (!state.env.ok) {
|
|
2613
2527
|
console.warn(`[code-server] 环境未就绪: ${describeEnvProblem(state.env)}`);
|
|
2614
2528
|
}
|
|
2615
|
-
const legacyRoot = legacyInstallRoot();
|
|
2616
|
-
if (legacyRoot !== null && fs.existsSync(legacyRoot)) {
|
|
2617
|
-
console.log(`[code-server] 检测到旧版安装根 ${legacyRoot}(0.1.35 及更早遗留,已不再使用,可安全删除)`);
|
|
2618
|
-
}
|
|
2619
2529
|
|
|
2620
2530
|
// DSH host 重启后 adopt(仅 loopback 模式):pid.json 有效、进程存活、令牌文件在、endpoint 记录的端口
|
|
2621
2531
|
// 上 /healthz(带令牌)响应 → 接管。端口 0 时不能拿 cfg.port 去探(那是个占位值),必须用 endpoint 文件。
|
package/lib/vendor.js
CHANGED
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
// 模型(重构后,0.2.0 起):
|
|
4
4
|
// - VS Code 树 + 少量浏览器静态资源打成**平台无关子包**发布:
|
|
5
5
|
// @<scope>/dshcs-vscode-server@<code-server 版本>
|
|
6
|
-
// 子包内布局:<pkg>/vscode/{lib/vscode,out/browser,src/browser,…}
|
|
7
|
-
// 旧的全量树 @<scope>/dshcs-code-server@<版本> 仍然可作回退(0.1.43 及更早)。
|
|
6
|
+
// 子包内布局:<pkg>/vscode/{lib/vscode,out/browser,src/browser,…}。
|
|
8
7
|
// - code-server 的 Node 服务层(out/node/** + 136 个依赖)不再随包发布:
|
|
9
8
|
// 由插件自带的 lib/launcher.mjs 取代(见 docs/analysis-code-server-as-dsh-plugin.md)。
|
|
10
9
|
// - VS Code 内部依赖(约 1GB)与需编译的原生模块仍由包管理器安装:
|
|
@@ -13,7 +12,7 @@
|
|
|
13
12
|
// 并**直接挂在插件依赖上**(平台无关的 → dependencies;平台专属的 → optionalDependencies,
|
|
14
13
|
// 包自带 os/cpu 按架构自动选)。原始名字由 lib/native.js 按 lib/vendored.json 补 junction ——
|
|
15
14
|
// 0.3.45 起不再有平台聚合包,原因见 docs/desktop-first-install-root-cause.md;
|
|
16
|
-
// - 插件包内 `vendor/vscode/`
|
|
15
|
+
// - 插件包内 `vendor/vscode/` 只作为**开发期**回退(发布包不含 vendor)。
|
|
17
16
|
import { createRequire } from 'node:module';
|
|
18
17
|
import { existsSync, readFileSync } from 'node:fs';
|
|
19
18
|
import { dirname, join, resolve } from 'node:path';
|
|
@@ -22,26 +21,9 @@ import { fileURLToPath } from 'node:url';
|
|
|
22
21
|
const here = dirname(fileURLToPath(import.meta.url)); // <pkg>/lib
|
|
23
22
|
const require_ = createRequire(import.meta.url);
|
|
24
23
|
export const PACKAGE_ROOT = resolve(here, '..');
|
|
25
|
-
/** 包内 VS Code 树(
|
|
24
|
+
/** 包内 VS Code 树(新模型;发布包不含,仅开发期回退)。 */
|
|
26
25
|
export const VENDOR_TREE = join(PACKAGE_ROOT, 'vendor', 'vscode');
|
|
27
|
-
/** 包内旧全量 code-server 树(0.1.43 及更早;仅兼容回退)。 */
|
|
28
|
-
export const LEGACY_VENDOR_TREE = join(PACKAGE_ROOT, 'vendor', 'code-server');
|
|
29
26
|
const VENDOR_META = join(PACKAGE_ROOT, 'vendor', 'VENDOR.json');
|
|
30
|
-
/** 旧版安装根目录名(0.1.35 及更早);仅用于迁移提示。 */
|
|
31
|
-
export const APP_DIR_NAME = '.code-server-app';
|
|
32
|
-
|
|
33
|
-
/** <profile> 根;插件不在 profile 布局(开发期独立目录)时返回 null。 */
|
|
34
|
-
export function profileRootOf() {
|
|
35
|
-
const profileRoot = resolve(PACKAGE_ROOT, '..', '..');
|
|
36
|
-
const isProfile = existsSync(join(profileRoot, 'node_modules')) && existsSync(join(profileRoot, 'package.json'));
|
|
37
|
-
return isProfile ? profileRoot : null;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** 旧版安装根(<profile>\.code-server-app);存在时提示可删除。 */
|
|
41
|
-
export function legacyInstallRoot() {
|
|
42
|
-
const profileRoot = profileRootOf();
|
|
43
|
-
return profileRoot !== null ? join(profileRoot, APP_DIR_NAME) : null;
|
|
44
|
-
}
|
|
45
27
|
|
|
46
28
|
function declaredDependencyNames() {
|
|
47
29
|
try {
|
|
@@ -63,42 +45,23 @@ export function vscodeServerPackageName() {
|
|
|
63
45
|
return '@jinsiyu/dshcs-vscode-server';
|
|
64
46
|
}
|
|
65
47
|
|
|
66
|
-
/**
|
|
67
|
-
export function codeServerPackageName() {
|
|
68
|
-
const declared = declaredDependencyNames();
|
|
69
|
-
const hit = Object.keys(declared).find((name) => /^@[^/]+\/dshcs-code-server$/.test(name));
|
|
70
|
-
if (hit !== undefined) return hit;
|
|
71
|
-
return '@jinsiyu/dshcs-code-server';
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/** 旧版(0.1.37)平台专属 code-server 子包名,仅作兼容回退。 */
|
|
75
|
-
export function legacyCodeServerPackageName(platform = process.platform, arch = process.arch) {
|
|
76
|
-
return `@jinsiyu/dshcs-code-server-${platform}-${arch}`;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** 解析某个包目录下的 VS Code 树根。
|
|
80
|
-
* 新布局:<pkg>/vscode/lib/vscode/out/server-main.js;旧布局:<pkg>/code-server/out/node/entry.js。 */
|
|
48
|
+
/** 解析某个包目录下的 VS Code 树根:<pkg>/vscode/lib/vscode/out/server-main.js。 */
|
|
81
49
|
function rootOfPackage(name) {
|
|
82
50
|
try {
|
|
83
51
|
const manifest = require_.resolve(`${name}/package.json`);
|
|
84
52
|
const base = dirname(manifest);
|
|
85
53
|
const slim = join(base, 'vscode');
|
|
86
54
|
if (existsSync(join(slim, 'lib', 'vscode', 'out', 'server-main.js'))) return slim;
|
|
87
|
-
const legacy = join(base, 'code-server');
|
|
88
|
-
if (existsSync(join(legacy, 'out', 'node', 'entry.js'))) return legacy;
|
|
89
55
|
} catch { /* 未安装 */ }
|
|
90
56
|
return null;
|
|
91
57
|
}
|
|
92
58
|
|
|
93
|
-
/** VS Code 树根(
|
|
59
|
+
/** VS Code 树根(已安装的 dshcs-vscode-server 包 > 包内 vendor/vscode)。
|
|
94
60
|
* @returns {string|null} 绝对路径,如 <profile>\node_modules\@jinsiyu\dshcs-vscode-server\vscode */
|
|
95
61
|
export function vsRoot() {
|
|
96
|
-
const root = rootOfPackage(vscodeServerPackageName())
|
|
97
|
-
?? rootOfPackage(codeServerPackageName())
|
|
98
|
-
?? rootOfPackage(legacyCodeServerPackageName());
|
|
62
|
+
const root = rootOfPackage(vscodeServerPackageName());
|
|
99
63
|
if (root !== null) return root;
|
|
100
64
|
if (existsSync(join(VENDOR_TREE, 'lib', 'vscode', 'out', 'server-main.js'))) return VENDOR_TREE;
|
|
101
|
-
if (existsSync(join(LEGACY_VENDOR_TREE, 'out', 'node', 'entry.js'))) return LEGACY_VENDOR_TREE;
|
|
102
65
|
return null;
|
|
103
66
|
}
|
|
104
67
|
|
|
@@ -152,7 +115,7 @@ export function vendoredVersion() {
|
|
|
152
115
|
if (meta !== null && typeof meta.codeServerVersion === 'string' && meta.codeServerVersion !== '') {
|
|
153
116
|
return meta.codeServerVersion;
|
|
154
117
|
}
|
|
155
|
-
return readTreeVersion(VENDOR_TREE)
|
|
118
|
+
return readTreeVersion(VENDOR_TREE);
|
|
156
119
|
}
|
|
157
120
|
|
|
158
121
|
/** VS Code 树是否可用(包已安装或包内 vendor 在位)。 */
|
package/lib/vendored.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-code-server-app",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.71",
|
|
4
4
|
"description": "VS Code (from a code-server release) inside DSH: a right-sidebar tab driven by the plugin's own launcher over the in-process VS Code server (lib/launcher.mjs), plus an editor bridge (assets/extensions/dshcs-editor-bridge) that gives the agent what only the editor knows (unsaved buffers, language-server diagnostics, the active selection) and lets editor gestures drive the session. The bridge is completely read-only: all four of its routes (/health, /sync, /old, /event) are reads. Asking DSH from the editor opens a floating dialog inside the DSH page (draggable, resizable, bottom-right): the plugin's client half sends the question as user input (POST /api/code-server/ask/send) and renders that session's new content with DSH's own Markdown renderer - the same @deepseek-ai/dsh-client-ui-primitives instance the UI uses, required from the page's module table, so typography, highlighting and math match the interface and a renderer version mismatch is impossible. Thinking appears as collapsed rows and approval requests (writing outside the workspace / running commands) can be answered in place: whole-plugin write route POST /api/code-server/ask/approve, constrained to pending in-process requests and the outcomes allowed-once/rejected; dialog-first for 5 minutes, handed back to the official card the moment the dialog closes, never auto-approved. There is no build step anywhere: lib/client.js (client half including the dialog panel) and the extension are committed hand-written source, devDependencies holds a single test-only entry, and the published package carries no frontend bundle. Also: the tab claims DSH file addresses (dsh-resource://file/**) by file type (setting claimExtensions), so produced-file chips, delivered-file previews and inline prose mentions open in the workbench; the IDE is a resident surface moved with Element.moveBefore instead of being remounted, so switching sidebar tabs does not reload it; opening the tab switches the right sidebar to fullscreen by default (setting fullscreenOnOpen); a workspace switch re-navigates the workbench with the new ?folder= without restarting the IDE process. Requires a DSH with the right-sidebar services (sidebarRightTabs/sidebarRight, >= 0.1.5-alpha.1); older DSH versions only get an upgrade notice on the settings page. Two serving modes: loopback port (default) or same-origin mount on DSH's own webServer (/code-server, protected by ctx.connection.requestRejection). The diff the bridge opens always has the real pre-write text on the left, taken from result.value.before in tools/post-execute into a bounded snapshot cache and fetched by opaque key over the read-only GET /old, so files that are not open in the editor work too. No code-server Node layer, no argon2, no C++ toolchain.",
|
|
5
5
|
"homepage": "https://github.com/jinsiyu/dsh-code-server-app",
|
|
6
6
|
"repository": {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
]
|
|
31
31
|
},
|
|
32
32
|
"env": {
|
|
33
|
-
"minVersion": "0.
|
|
33
|
+
"minVersion": "0.2.0-rc.2"
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
36
|
"engines": {
|
|
@@ -84,8 +84,8 @@
|
|
|
84
84
|
"@jinsiyu/dshcs-parcel-watcher": "2.5.6",
|
|
85
85
|
"@jinsiyu/dshcs-ssh2": "1.17.0",
|
|
86
86
|
"@jinsiyu/dshcs-vscode-fs-copyfile": "2.0.0",
|
|
87
|
-
"@jinsiyu/dshcs-vscode-proxy-agent": "0.
|
|
88
|
-
"@jinsiyu/dshcs-vscode-server": "4.
|
|
87
|
+
"@jinsiyu/dshcs-vscode-proxy-agent": "0.45.0",
|
|
88
|
+
"@jinsiyu/dshcs-vscode-server": "4.140.0",
|
|
89
89
|
"@microsoft/1ds-core-js": "3.2.13",
|
|
90
90
|
"@microsoft/1ds-post-js": "3.2.13",
|
|
91
91
|
"@vscode/iconv-lite-umd": "0.7.1",
|
|
@@ -161,6 +161,7 @@
|
|
|
161
161
|
"vendor:vscode": "node scripts/vendor-vscode-server.mjs",
|
|
162
162
|
"vendor:latest": "node scripts/vendor-vscode-server.mjs --force",
|
|
163
163
|
"vendor:check": "node scripts/vendor-vscode-server.mjs --check",
|
|
164
|
+
"watch:upstream": "node scripts/watch-upstream.mjs",
|
|
164
165
|
"vendor:code-server": "node scripts/vendor-code-server.mjs",
|
|
165
166
|
"repack:build": "node scripts/vendor-repacks.mjs",
|
|
166
167
|
"publish:repacks": "node scripts/publish-repacks.mjs",
|
|
@@ -185,6 +186,7 @@
|
|
|
185
186
|
"test:edit-snapshot": "node scripts/test-edit-snapshot.mjs",
|
|
186
187
|
"test:bridge-extension": "node scripts/test-bridge-extension.mjs",
|
|
187
188
|
"test:ask-dialog": "node scripts/test-ask-dialog.mjs",
|
|
188
|
-
"test:vendored": "node scripts/test-vendored-table.mjs"
|
|
189
|
+
"test:vendored": "node scripts/test-vendored-table.mjs",
|
|
190
|
+
"test:upstream-watch": "node scripts/test-upstream-watch.mjs"
|
|
189
191
|
}
|
|
190
192
|
}
|
|
@@ -817,10 +817,26 @@ function main() {
|
|
|
817
817
|
}
|
|
818
818
|
byTarget.set(target, map);
|
|
819
819
|
}
|
|
820
|
-
|
|
820
|
+
// 宿主不在 --target 里时(例如 win32-arm64 机器上只建 x64 腿)退回**第一个被请求的目标**。
|
|
821
|
+
// 这里原来写的是 `targets[0]` —— 一个在这个作用域里从未定义的变量。因为 `??` 会短路,
|
|
822
|
+
// CI 上宿主(win32-x64)恰好总在 --target 里,右边永远不被求值,所以一直没暴露;
|
|
823
|
+
// 直到 2026-10-04 在 arm64 机器上跑 `--target win32-x64` 才炸出 ReferenceError: targets is not defined。
|
|
824
|
+
const hostMap = byTarget.get(hostKey) ?? byTarget.get(buildTargets[0]);
|
|
821
825
|
|
|
822
826
|
// 1) 全平台重打包包(从 host 树)
|
|
823
827
|
const buildDir = join(OUT, 'build');
|
|
828
|
+
// repository 必须改写成**本项目**:npm 在 GitHub Actions 里(工作流有 id-token: write)会**自动**附
|
|
829
|
+
// Sigstore provenance,而 provenance 会校验 package.json 的 repository.url 与"跑这次发布的工作流
|
|
830
|
+
// 所在仓库"一致。原样留着上游的地址(如 @vscode/proxy-agent 的
|
|
831
|
+
// git://github.com/microsoft/vscode-proxy-agent.git)会在 PUT 阶段被判 422:
|
|
832
|
+
// Error verifying sigstore provenance bundle: Failed to validate repository information
|
|
833
|
+
// 树包从来没这个问题 —— buildVscodeServerPackage 本来就把 repository 写成我们自己的(见上面那段)。
|
|
834
|
+
// 只在 CI 上暴露:本地 `npm publish` 不生成 provenance,所以历次首发都不是在这里踩到的。
|
|
835
|
+
const ourRepository = readJson(join(pkgRoot, 'package.json'))?.repository ?? undefined;
|
|
836
|
+
if (ourRepository === undefined) {
|
|
837
|
+
console.warn(' ⚠ 仓库 package.json 里没有 repository ⇒ 重打包包不会带仓库地址,'
|
|
838
|
+
+ 'CI 上带 provenance 的发布会以 422 失败(见本段注释)');
|
|
839
|
+
}
|
|
824
840
|
const writeRepack = (srcDir, pkg, outName) => {
|
|
825
841
|
const dir = join(buildDir, outName);
|
|
826
842
|
mkdirSync(dirname(dir), { recursive: true });
|
|
@@ -841,6 +857,7 @@ function main() {
|
|
|
841
857
|
}
|
|
842
858
|
m.name = pkg.pkgName;
|
|
843
859
|
m.version = pkg.version;
|
|
860
|
+
m.repository = ourRepository;
|
|
844
861
|
if (pkg.platformSpecific) {
|
|
845
862
|
const [platform, arch] = pkg.pkgName.match(/-(win32|darwin|linux)-(arm64|x64)$/).slice(1);
|
|
846
863
|
m.os = [platform];
|
package/vendor/VENDOR.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
-
"codeServerVersion": "4.
|
|
3
|
-
"vscodeVersion": "1.
|
|
4
|
-
"productPath": "stable-
|
|
2
|
+
"codeServerVersion": "4.140.0",
|
|
3
|
+
"vscodeVersion": "1.140.0",
|
|
4
|
+
"productPath": "stable-ccc19adc2e8992e18b14dd25eb1e646f5b9ef7cf",
|
|
5
5
|
"layout": "vscode-only",
|
|
6
|
-
"preparedAt": "2026-
|
|
6
|
+
"preparedAt": "2026-10-04T13:22:27.553Z",
|
|
7
7
|
"source": "registry",
|
|
8
8
|
"node": "v24.21.0",
|
|
9
9
|
"platform": "linux",
|
|
10
10
|
"arch": "x64",
|
|
11
|
-
"sizeMB":
|
|
11
|
+
"sizeMB": 165.1
|
|
12
12
|
}
|