dsh-code-server-app 0.1.42 → 0.2.0
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 +113 -106
- package/README.md +125 -92
- package/cordis.patch.yml +19 -15
- package/lib/client.js +1 -3
- package/lib/index.js +247 -144
- package/lib/launcher.mjs +313 -0
- package/lib/native.js +44 -12
- package/lib/serve-dsh.mjs +162 -0
- package/lib/vendor.js +94 -33
- package/package.json +11 -7
- package/scripts/vendor-repacks.mjs +72 -99
- package/scripts/vendor-vscode-server.mjs +278 -0
- package/vendor/VENDOR.json +5 -2
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
// scripts/vendor-vscode-server.mjs — 打包期准备「VS Code 树」(取代旧的 vendor-code-server.mjs 全量树)。
|
|
2
|
+
//
|
|
3
|
+
// 模型(重构后):
|
|
4
|
+
// code-server 的 Node 服务层(out/node/**, 136 个依赖)不再随包发布 —— 它由插件自带的
|
|
5
|
+
// lib/launcher.mjs 取代(见 docs/analysis-code-server-as-dsh-plugin.md)。本脚本只保留:
|
|
6
|
+
// lib/vscode/** 已由 code-server 打过补丁的 VS Code server + 内置扩展(≈197MB)
|
|
7
|
+
// out/browser/** serviceWorker.js
|
|
8
|
+
// src/browser/** favicon / PWA 图标 / robots.txt
|
|
9
|
+
// LICENSE, ThirdPartyNotices.txt
|
|
10
|
+
// 并生成树根 package.json(版本 = 上游 code-server 版本,便于版本比对)。
|
|
11
|
+
//
|
|
12
|
+
// 产物:
|
|
13
|
+
// vendor/vscode/ 本地开发树(--dev-links 时把 VS Code 内部依赖用 junction 补上,可直接跑)
|
|
14
|
+
// vendor/VENDOR.json 版本与来源元数据
|
|
15
|
+
//
|
|
16
|
+
// 用法:
|
|
17
|
+
// node scripts/vendor-vscode-server.mjs [--check] [--force]
|
|
18
|
+
// node scripts/vendor-vscode-server.mjs --from <code-server 树> # 从现有树复制(最快)
|
|
19
|
+
// node scripts/vendor-vscode-server.mjs --version 4.136.2 # 从 registry 安装
|
|
20
|
+
// node scripts/vendor-vscode-server.mjs --dev-links # 额外补 lib/vscode/node_modules
|
|
21
|
+
import { spawnSync } from 'node:child_process';
|
|
22
|
+
import {
|
|
23
|
+
cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync,
|
|
24
|
+
rmSync, statSync, symlinkSync, writeFileSync,
|
|
25
|
+
} from 'node:fs';
|
|
26
|
+
import { dirname, join, resolve } from 'node:path';
|
|
27
|
+
import { fileURLToPath } from 'node:url';
|
|
28
|
+
import { homedir } from 'node:os';
|
|
29
|
+
|
|
30
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
31
|
+
const pkgRoot = resolve(here, '..');
|
|
32
|
+
const vendorDir = join(pkgRoot, 'vendor');
|
|
33
|
+
const vendorTree = join(vendorDir, 'vscode');
|
|
34
|
+
const legacyTree = join(vendorDir, 'code-server'); // 旧全量树(回退用,不再生成)
|
|
35
|
+
const workRoot = join(pkgRoot, '.vendor-tmp');
|
|
36
|
+
const npmCache = join(pkgRoot, '.npm-cache');
|
|
37
|
+
|
|
38
|
+
function argValues(name) {
|
|
39
|
+
const out = [];
|
|
40
|
+
for (let i = 0; i < process.argv.length - 1; i += 1) {
|
|
41
|
+
if (process.argv[i] !== name) continue;
|
|
42
|
+
for (const part of String(process.argv[i + 1]).split(',')) {
|
|
43
|
+
const v = part.trim();
|
|
44
|
+
if (v !== '') out.push(v);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
function argValue(name) {
|
|
50
|
+
const all = argValues(name);
|
|
51
|
+
return all.length > 0 ? all[all.length - 1] : null;
|
|
52
|
+
}
|
|
53
|
+
const FORCE = process.argv.includes('--force');
|
|
54
|
+
const DEV_LINKS = process.argv.includes('--dev-links');
|
|
55
|
+
const FROM = argValue('--from');
|
|
56
|
+
const PINNED = argValue('--version') || process.env.DSHCS_CODE_SERVER_VERSION || null;
|
|
57
|
+
|
|
58
|
+
function run(cmd, args, cwd, extraEnv) {
|
|
59
|
+
const useShell = process.platform === 'win32' && /\.(cmd|bat)$/i.test(cmd);
|
|
60
|
+
console.log(`[vendor] $ ${cmd} ${args.join(' ')}${cwd ? ` (cwd=${cwd})` : ''}`);
|
|
61
|
+
const res = spawnSync(cmd, args, {
|
|
62
|
+
cwd, stdio: 'inherit', shell: useShell,
|
|
63
|
+
env: extraEnv ? { ...process.env, ...extraEnv } : process.env,
|
|
64
|
+
});
|
|
65
|
+
if (res.status !== 0) throw new Error(`${cmd} ${args.join(' ')} failed (${res.status})`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function npm(args, cwd) {
|
|
69
|
+
const env = { npm_config_cache: npmCache, npm_config_update_notifier: 'false' };
|
|
70
|
+
const cli = join(dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npm-cli.js');
|
|
71
|
+
if (existsSync(cli)) run(process.execPath, [cli, ...args], cwd, env);
|
|
72
|
+
else run(process.platform === 'win32' ? 'npm.cmd' : 'npm', args, cwd, env);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const readJson = (file) => { try { return JSON.parse(readFileSync(file, 'utf8')); } catch { return null; } };
|
|
76
|
+
|
|
77
|
+
async function latestVersion() {
|
|
78
|
+
const res = await fetch('https://registry.npmjs.org/code-server/latest');
|
|
79
|
+
if (!res.ok) throw new Error(`registry responded ${res.status}`);
|
|
80
|
+
const json = await res.json();
|
|
81
|
+
if (typeof json.version !== 'string') throw new Error('registry payload has no version');
|
|
82
|
+
return json.version;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function sizeMB(dir) {
|
|
86
|
+
let total = 0;
|
|
87
|
+
const stack = [dir];
|
|
88
|
+
while (stack.length > 0) {
|
|
89
|
+
const cur = stack.pop();
|
|
90
|
+
let entries;
|
|
91
|
+
try { entries = readdirSync(cur, { withFileTypes: true }); } catch { continue; }
|
|
92
|
+
for (const ent of entries) {
|
|
93
|
+
const full = join(cur, ent.name);
|
|
94
|
+
if (ent.isDirectory()) stack.push(full);
|
|
95
|
+
else if (ent.isFile()) { try { total += statSync(full).size; } catch { /* ignore */ } }
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return Math.round((total / 1024 / 1024) * 10) / 10;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** 默认源树:已安装 profile 里最新的 dshcs-code-server 树 → 旧 vendor 树。 */
|
|
102
|
+
function defaultSourceTree() {
|
|
103
|
+
const dshHome = process.env.DSH_HOME || join(process.env.USERPROFILE ?? homedir(), '.dsh');
|
|
104
|
+
const profiles = join(dshHome, 'profiles');
|
|
105
|
+
const cands = [];
|
|
106
|
+
try {
|
|
107
|
+
for (const name of readdirSync(profiles)) {
|
|
108
|
+
cands.push(join(profiles, name, 'node_modules', '@jinsiyu', 'dshcs-code-server', 'code-server'));
|
|
109
|
+
}
|
|
110
|
+
} catch { /* ignore */ }
|
|
111
|
+
cands.push(legacyTree);
|
|
112
|
+
for (const c of cands) if (existsSync(join(c, 'lib', 'vscode', 'out', 'server-main.js'))) return c;
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** 复制策略:只留服务 IDE 必需的部分。
|
|
117
|
+
* keep: lib/vscode/**、out/browser/**、src/browser/**(除 pages)、LICENSE、ThirdPartyNotices.txt、README.md
|
|
118
|
+
* 排除: node_modules/**、out/node/**、out/common/**、src/browser/pages/**、lib/vscode/(extensions/)node_modules/** */
|
|
119
|
+
function copySlim(src, dest) {
|
|
120
|
+
rmSync(dest, { recursive: true, force: true });
|
|
121
|
+
mkdirSync(dest, { recursive: true });
|
|
122
|
+
const dropDirs = [
|
|
123
|
+
'node_modules',
|
|
124
|
+
join('out', 'node'),
|
|
125
|
+
join('out', 'common'),
|
|
126
|
+
join('src', 'browser', 'pages'),
|
|
127
|
+
join('lib', 'vscode', 'node_modules'),
|
|
128
|
+
join('lib', 'vscode', 'extensions', 'node_modules'),
|
|
129
|
+
].map((p) => p.replaceAll('\\', '/'));
|
|
130
|
+
const keepFiles = new Set(['LICENSE', 'ThirdPartyNotices.txt', 'README.md']);
|
|
131
|
+
const keepRootDirs = new Set(['lib', 'out', 'src']);
|
|
132
|
+
cpSync(src, dest, {
|
|
133
|
+
recursive: true,
|
|
134
|
+
dereference: false,
|
|
135
|
+
maxRetries: 6,
|
|
136
|
+
retryDelay: 250,
|
|
137
|
+
filter: (source) => {
|
|
138
|
+
const rel = source.slice(src.length + 1).replaceAll('\\', '/');
|
|
139
|
+
if (rel === '') return true;
|
|
140
|
+
if (dropDirs.some((d) => rel === d || rel.startsWith(`${d}/`))) return false;
|
|
141
|
+
const top = rel.split('/')[0];
|
|
142
|
+
if (rel.includes('/')) return keepRootDirs.has(top);
|
|
143
|
+
return keepRootDirs.has(top) || keepFiles.has(top);
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** 把源树的 VS Code 内部依赖 junction 复刻到目标树(开发期直接可跑用)。
|
|
149
|
+
* 必须用 type:'junction'(Windows 下 symlink 需要权限,cpSync 复制链接会 EPERM)。 */
|
|
150
|
+
function linkInnerDeps(srcTree, destTree) {
|
|
151
|
+
const pairs = [
|
|
152
|
+
[join('lib', 'vscode', 'node_modules'), join('lib', 'vscode', 'package.json')],
|
|
153
|
+
[join('lib', 'vscode', 'extensions', 'node_modules'), join('lib', 'vscode', 'extensions', 'package.json')],
|
|
154
|
+
];
|
|
155
|
+
let linked = 0;
|
|
156
|
+
const failed = [];
|
|
157
|
+
for (const [relNm] of pairs) {
|
|
158
|
+
const srcNm = join(srcTree, relNm);
|
|
159
|
+
const dstNm = join(destTree, relNm);
|
|
160
|
+
if (!existsSync(srcNm)) continue;
|
|
161
|
+
mkdirSync(dstNm, { recursive: true });
|
|
162
|
+
const entries = [];
|
|
163
|
+
for (const ent of readdirSync(srcNm, { withFileTypes: true })) {
|
|
164
|
+
if (ent.name.startsWith('.')) continue;
|
|
165
|
+
if (ent.isDirectory() && ent.name.startsWith('@')) {
|
|
166
|
+
for (const sub of readdirSync(join(srcNm, ent.name), { withFileTypes: true })) {
|
|
167
|
+
if (sub.name.startsWith('.')) continue;
|
|
168
|
+
entries.push(`${ent.name}/${sub.name}`);
|
|
169
|
+
}
|
|
170
|
+
} else {
|
|
171
|
+
entries.push(ent.name);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
for (const name of entries) {
|
|
175
|
+
const from = join(srcNm, name);
|
|
176
|
+
const to = join(dstNm, name);
|
|
177
|
+
let target = from;
|
|
178
|
+
try { if (lstatSync(from).isSymbolicLink()) target = readlinkSync(from); } catch { /* use as-is */ }
|
|
179
|
+
try {
|
|
180
|
+
mkdirSync(dirname(to), { recursive: true });
|
|
181
|
+
rmSync(to, { recursive: true, force: true });
|
|
182
|
+
symlinkSync(target, to, 'junction');
|
|
183
|
+
linked += 1;
|
|
184
|
+
} catch (error) {
|
|
185
|
+
failed.push(`${name}: ${error.code ?? error.message}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return { linked, failed };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function main() {
|
|
193
|
+
const existing = readJson(join(vendorTree, 'package.json'))?.version ?? null;
|
|
194
|
+
|
|
195
|
+
if (process.argv.includes('--check')) {
|
|
196
|
+
const latest = await latestVersion();
|
|
197
|
+
console.log(`[vendor] 内置 VS Code 树: ${existing ?? '(无)'} | 上游 code-server latest: ${latest}`);
|
|
198
|
+
console.log(existing === latest ? '[vendor] 已是最新' : '[vendor] 有新版本:pnpm run vendor:latest → repack:build → publish:repacks → 版本 +1 → pnpm pack');
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const skip = existing !== null && !FORCE && FROM === null && PINNED === null;
|
|
203
|
+
let codeServerVersion = existing;
|
|
204
|
+
let sourceKind = 'registry';
|
|
205
|
+
let sourceTree = null;
|
|
206
|
+
|
|
207
|
+
if (skip) {
|
|
208
|
+
console.log(`[vendor] vendor/vscode 已存在(code-server@${existing});跳过(用 --force 重建)`);
|
|
209
|
+
} else if (FROM !== null || (sourceTree = defaultSourceTree()) !== null) {
|
|
210
|
+
const src = FROM ?? sourceTree;
|
|
211
|
+
codeServerVersion = readJson(join(src, 'package.json'))?.version ?? null;
|
|
212
|
+
if (codeServerVersion === null) throw new Error(`--from ${src} 不是一棵 code-server 树`);
|
|
213
|
+
console.log(`[vendor] 源树 ${src}(code-server@${codeServerVersion})→ vendor/vscode/`);
|
|
214
|
+
copySlim(src, vendorTree);
|
|
215
|
+
sourceKind = FROM !== null ? `snapshot:${src}` : `local:${src}`;
|
|
216
|
+
} else {
|
|
217
|
+
codeServerVersion = PINNED ?? await latestVersion();
|
|
218
|
+
console.log(`[vendor] 从 registry 准备 code-server@${codeServerVersion} …`);
|
|
219
|
+
const tmp = join(workRoot, `source-${process.pid}`);
|
|
220
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
221
|
+
mkdirSync(tmp, { recursive: true });
|
|
222
|
+
try {
|
|
223
|
+
npm(['install', `code-server@${codeServerVersion}`, '--ignore-scripts', '--omit=dev',
|
|
224
|
+
'--no-audit', '--no-fund', '--no-save', '--prefix', tmp], tmp);
|
|
225
|
+
const tree = join(tmp, 'node_modules', 'code-server');
|
|
226
|
+
if (!existsSync(join(tree, 'lib', 'vscode', 'out', 'server-main.js'))) throw new Error('code-server 安装不完整');
|
|
227
|
+
copySlim(tree, vendorTree);
|
|
228
|
+
} finally {
|
|
229
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const vscodePkg = readJson(join(vendorTree, 'lib', 'vscode', 'package.json')) ?? {};
|
|
234
|
+
const product = readJson(join(vendorTree, 'lib', 'vscode', 'product.json')) ?? {};
|
|
235
|
+
const productPath = `${product.quality ?? 'oss'}-${product.commit ?? 'dev'}`;
|
|
236
|
+
const layout = { name: 'dshcs-vscode-tree', private: true, version: codeServerVersion };
|
|
237
|
+
layout.vscodeVersion = vscodePkg.version ?? null;
|
|
238
|
+
layout.productPath = productPath;
|
|
239
|
+
writeFileSync(join(vendorTree, 'package.json'), JSON.stringify(layout, null, 2) + '\n', 'utf8');
|
|
240
|
+
writeFileSync(join(vendorTree, 'VENDOR-TREE.json'), JSON.stringify({
|
|
241
|
+
codeServerVersion,
|
|
242
|
+
vscodeVersion: vscodePkg.version ?? null,
|
|
243
|
+
productPath,
|
|
244
|
+
preparedAt: new Date().toISOString(),
|
|
245
|
+
source: sourceKind,
|
|
246
|
+
}, null, 2) + '\n', 'utf8');
|
|
247
|
+
|
|
248
|
+
let links = { linked: 0, failed: [] };
|
|
249
|
+
if (DEV_LINKS) {
|
|
250
|
+
const src = FROM ?? defaultSourceTree();
|
|
251
|
+
if (src === null) console.warn('[vendor] --dev-links: 找不到可复刻 junction 的源树,跳过');
|
|
252
|
+
else {
|
|
253
|
+
links = linkInnerDeps(src, vendorTree);
|
|
254
|
+
console.log(`[vendor] dev: 补 VS Code 内部依赖 junction ${links.linked} 个${links.failed.length > 0 ? `(失败 ${links.failed.length}: ${links.failed.slice(0, 5).join(', ')})` : ''}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const meta = {
|
|
259
|
+
codeServerVersion,
|
|
260
|
+
vscodeVersion: vscodePkg.version ?? null,
|
|
261
|
+
productPath,
|
|
262
|
+
layout: 'vscode-only',
|
|
263
|
+
preparedAt: new Date().toISOString(),
|
|
264
|
+
source: sourceKind,
|
|
265
|
+
node: process.version,
|
|
266
|
+
platform: process.platform,
|
|
267
|
+
arch: process.arch,
|
|
268
|
+
sizeMB: sizeMB(vendorTree),
|
|
269
|
+
};
|
|
270
|
+
writeFileSync(join(vendorDir, 'VENDOR.json'), JSON.stringify(meta, null, 2) + '\n', 'utf8');
|
|
271
|
+
console.log(`[vendor] 完成:vendor/vscode (code-server@${codeServerVersion}, VS Code ${meta.vscodeVersion}, ${meta.sizeMB} MB, productPath=${productPath})`);
|
|
272
|
+
if (!DEV_LINKS) {
|
|
273
|
+
console.log('[vendor] 提示:本树不含 VS Code 内部依赖;运行时由 lib/native.js 的 ensureRuntimeLayout() 补 junction'
|
|
274
|
+
+ '(开发期可直接 `--dev-links` 复刻)。');
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
await main();
|
package/vendor/VENDOR.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"codeServerVersion": "4.136.2",
|
|
3
|
-
"
|
|
3
|
+
"vscodeVersion": "1.136.1",
|
|
4
|
+
"productPath": "stable-8d5f383f301ca20681f5b6606b8207d9dc87bdd8",
|
|
5
|
+
"layout": "vscode-only",
|
|
6
|
+
"preparedAt": "2026-09-10T08:30:15.204Z",
|
|
4
7
|
"source": "registry",
|
|
5
8
|
"node": "v24.13.1",
|
|
6
9
|
"platform": "win32",
|
|
7
10
|
"arch": "arm64",
|
|
8
|
-
"sizeMB":
|
|
11
|
+
"sizeMB": 197.2
|
|
9
12
|
}
|