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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-code-server-app",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.37",
|
|
4
4
|
"description": "Integrate code-server (VS Code in the browser) into DSH Web and Desktop: right-sidebar tab (DSH >= 0.1.5-alpha.1) with a floating-ball fallback for older hosts, over the Connection /api channel (ctx.connection.fetch; no webServer dependency) with host-side process lifecycle.",
|
|
5
5
|
"homepage": "https://github.com/jinsiyu/dsh-code-server-app",
|
|
6
6
|
"repository": {
|
|
@@ -40,27 +40,75 @@
|
|
|
40
40
|
"files": [
|
|
41
41
|
"lib/index.js",
|
|
42
42
|
"lib/client.js",
|
|
43
|
+
"lib/vendor.js",
|
|
44
|
+
"lib/native.js",
|
|
43
45
|
"assets/favicon.ico",
|
|
44
46
|
"assets/favicon.svg",
|
|
45
47
|
"assets/extensions/dshcs-open-file",
|
|
46
|
-
"scripts/
|
|
48
|
+
"scripts/vendor-code-server.mjs",
|
|
49
|
+
"scripts/vendor-code-server-pkg.mjs",
|
|
50
|
+
"scripts/vendor-repacks.mjs",
|
|
51
|
+
"vendor/VENDOR.json",
|
|
47
52
|
"cordis.patch.yml",
|
|
48
53
|
"LICENSE",
|
|
49
54
|
"README.md",
|
|
50
55
|
"README.en.md"
|
|
51
56
|
],
|
|
52
|
-
"scripts": {
|
|
53
|
-
"setup:code-server": "node scripts/setup-code-server.mjs",
|
|
54
|
-
"build:client": "node scripts/build-client.mjs"
|
|
55
|
-
},
|
|
56
57
|
"devDependencies": {
|
|
57
58
|
"esbuild": "^0.25.0",
|
|
58
59
|
"motion": "^12.0.0"
|
|
59
60
|
},
|
|
60
61
|
"license": "MIT",
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"@microsoft/1ds-core-js": "3.2.13",
|
|
64
|
+
"@microsoft/1ds-post-js": "3.2.13",
|
|
65
|
+
"@vscode/iconv-lite-umd": "0.7.1",
|
|
66
|
+
"@vscode/ripgrep-universal": "1.18.0",
|
|
67
|
+
"@vscode/sandbox-runtime": "0.0.1",
|
|
68
|
+
"@vscode/tree-sitter-wasm": "0.3.1",
|
|
69
|
+
"@vscode/vscode-languagedetection": "1.0.23",
|
|
70
|
+
"@xterm/addon-clipboard": "0.3.0-beta.301",
|
|
71
|
+
"@xterm/addon-image": "0.10.0-beta.299",
|
|
72
|
+
"@xterm/addon-ligatures": "0.11.0-beta.299",
|
|
73
|
+
"@xterm/addon-progress": "0.3.0-beta.299",
|
|
74
|
+
"@xterm/addon-search": "0.17.0-beta.299",
|
|
75
|
+
"@xterm/addon-serialize": "0.15.0-beta.299",
|
|
76
|
+
"@xterm/addon-unicode11": "0.10.0-beta.299",
|
|
77
|
+
"@xterm/addon-webgl": "0.20.0-beta.298",
|
|
78
|
+
"@xterm/headless": "6.1.0-beta.301",
|
|
79
|
+
"@xterm/xterm": "6.1.0-beta.302",
|
|
80
|
+
"cookie": "0.7.2",
|
|
81
|
+
"detect-libc": "2.1.2",
|
|
82
|
+
"http-proxy-agent": "7.0.0",
|
|
83
|
+
"https-proxy-agent": "7.0.2",
|
|
84
|
+
"jschardet": "3.1.4",
|
|
85
|
+
"katex": "0.16.22",
|
|
86
|
+
"minimist": "1.2.8",
|
|
87
|
+
"node-addon-api": "6.1.0",
|
|
88
|
+
"tar": "7.5.22",
|
|
89
|
+
"tas-client": "0.4.3",
|
|
90
|
+
"typescript": "6.0.3",
|
|
91
|
+
"vscode-oniguruma": "1.7.0",
|
|
92
|
+
"vscode-regexpp": "3.1.0",
|
|
93
|
+
"vscode-textmate": "9.3.2",
|
|
94
|
+
"ws": "8.21.0",
|
|
95
|
+
"yauzl": "3.3.1",
|
|
96
|
+
"yazl": "2.4.3",
|
|
97
|
+
"zod": "4.4.3"
|
|
98
|
+
},
|
|
99
|
+
"optionalDependencies": {
|
|
100
|
+
"@jinsiyu/dsh-code-server-runtime-win32-arm64": "^0.1.37",
|
|
101
|
+
"@jinsiyu/dsh-code-server-runtime-win32-x64": "^0.1.37",
|
|
102
|
+
"@jinsiyu/dshcs-code-server-win32-arm64": "4.136.2",
|
|
103
|
+
"@jinsiyu/dshcs-code-server-win32-x64": "4.136.2"
|
|
104
|
+
},
|
|
105
|
+
"scripts": {
|
|
106
|
+
"vendor:code-server": "node scripts/vendor-code-server.mjs",
|
|
107
|
+
"vendor:latest": "node scripts/vendor-code-server.mjs --force",
|
|
108
|
+
"vendor:check": "node scripts/vendor-code-server.mjs --check",
|
|
109
|
+
"repack:build": "node scripts/vendor-repacks.mjs",
|
|
110
|
+
"repack:code-server": "node scripts/vendor-code-server-pkg.mjs",
|
|
111
|
+
"publish:repacks": "node scripts/publish-repacks.mjs",
|
|
112
|
+
"build:client": "node scripts/build-client.mjs"
|
|
65
113
|
}
|
|
66
|
-
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// scripts/vendor-code-server-pkg.mjs — 把内置 code-server 树打成**平台专属子包**。
|
|
2
|
+
//
|
|
3
|
+
// 产物(每个目标一个,os/cpu 限定):
|
|
4
|
+
// repack/build/code-server-<target>/
|
|
5
|
+
// package.json @jinsiyu/dshcs-code-server-<target>@<code-server 版本>
|
|
6
|
+
// code-server/** 完整 code-server 树(out/ + lib/vscode + 自带的 136 个依赖)
|
|
7
|
+
//
|
|
8
|
+
// 为什么要放在子目录 code-server/ 而不是包根:npm/pnpm 打包**永远排除包根目录的 node_modules**,
|
|
9
|
+
// 放在被 files 覆盖的子目录里才会随包发布(主包 0.1.36 用的就是同一招)。
|
|
10
|
+
//
|
|
11
|
+
// 为什么子包不写 dependencies:那 136 个依赖已经作为文件打包在 code-server/node_modules 里,
|
|
12
|
+
// 写成 dependencies 会让 pnpm 再去 registry 装一份。留着 `dependencies` 字段的是嵌套的
|
|
13
|
+
// code-server/package.json(它只是普通文件,不被解析)。
|
|
14
|
+
//
|
|
15
|
+
// 平台差异只有 argon2 的原生二进制(win32-arm64 上游无预编译 → 打包期编译;
|
|
16
|
+
// win32-x64 用 `node-gyp rebuild --arch=x64` 现编,并校验 PE machine)。
|
|
17
|
+
//
|
|
18
|
+
// 用法:
|
|
19
|
+
// node scripts/vendor-code-server-pkg.mjs [--target win32-arm64,win32-x64] [--pack]
|
|
20
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
21
|
+
import { dirname, join, relative, resolve } from 'node:path';
|
|
22
|
+
import { fileURLToPath } from 'node:url';
|
|
23
|
+
import { spawnSync } from 'node:child_process';
|
|
24
|
+
|
|
25
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const pkgRoot = resolve(here, '..');
|
|
27
|
+
const repackDir = join(pkgRoot, 'repack');
|
|
28
|
+
const buildDir = join(repackDir, 'build');
|
|
29
|
+
const tgzDir = join(repackDir, 'tgz');
|
|
30
|
+
const vendorTree = join(pkgRoot, 'vendor', 'code-server');
|
|
31
|
+
const npmCache = join(pkgRoot, '.npm-cache');
|
|
32
|
+
|
|
33
|
+
const argv = process.argv.slice(2);
|
|
34
|
+
const DO_PACK = argv.includes('--pack');
|
|
35
|
+
const TARGETS = (() => {
|
|
36
|
+
const i = argv.indexOf('--target');
|
|
37
|
+
if (i < 0 || argv[i + 1] === undefined) return [`${process.platform}-${process.arch}`];
|
|
38
|
+
return argv[i + 1].split(',').map((s) => s.trim()).filter(Boolean);
|
|
39
|
+
})();
|
|
40
|
+
|
|
41
|
+
const readJson = (p) => { try { return JSON.parse(readFileSync(p, 'utf8')); } catch { return null; } };
|
|
42
|
+
|
|
43
|
+
function npm(args, cwd) {
|
|
44
|
+
const cli = join(dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npm-cli.js');
|
|
45
|
+
const env = { ...process.env, npm_config_cache: npmCache, npm_config_update_notifier: 'false' };
|
|
46
|
+
const hasCli = existsSync(cli);
|
|
47
|
+
const cmd = hasCli ? process.execPath : (process.platform === 'win32' ? 'npm.cmd' : 'npm');
|
|
48
|
+
const full = hasCli ? [cli, ...args] : args;
|
|
49
|
+
const res = spawnSync(cmd, full, { cwd, shell: !hasCli && process.platform === 'win32', env, stdio: 'inherit' });
|
|
50
|
+
if ((res.status ?? 1) !== 0) throw new Error(`npm ${args.join(' ')} failed (${res.status})`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** PE machine(0x8664=x64 / 0xaa64=arm64),用于交叉编译后的静态校验。 */
|
|
54
|
+
function peMachine(file) {
|
|
55
|
+
try {
|
|
56
|
+
const b = readFileSync(file);
|
|
57
|
+
if (b[0] !== 0x4d || b[1] !== 0x5a) return null;
|
|
58
|
+
const off = b.readUInt32LE(0x3c);
|
|
59
|
+
if (b.toString('ascii', off, off + 4) !== 'PE\u0000\u0000') return null;
|
|
60
|
+
return `0x${b.readUInt16LE(off + 4).toString(16)}`;
|
|
61
|
+
} catch { return null; }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** 为某个目标编译 argon2 原生模块(就地覆盖包内 build/Release/argon2.node)。 */
|
|
65
|
+
function buildArgon2(tree, arch) {
|
|
66
|
+
const argon2 = join(tree, 'node_modules', 'argon2');
|
|
67
|
+
if (!existsSync(join(argon2, 'binding.gyp'))) throw new Error(`argon2 不在位: ${argon2}`);
|
|
68
|
+
console.log(`[cs-pkg] 编译 argon2 (--arch=${arch}) …`);
|
|
69
|
+
const ng = join(process.env.APPDATA ?? '', 'npm', 'node_modules', 'node-gyp', 'bin', 'node-gyp.js');
|
|
70
|
+
const cli = existsSync(ng) ? ng : null;
|
|
71
|
+
if (cli === null) throw new Error('找不到 node-gyp(需全局安装 node-gyp)');
|
|
72
|
+
const res = spawnSync(process.execPath, [cli, 'rebuild', `--arch=${arch}`], { cwd: argon2, stdio: 'inherit' });
|
|
73
|
+
if ((res.status ?? 1) !== 0) throw new Error(`argon2 编译失败 (${res.status})`);
|
|
74
|
+
const bin = join(argon2, 'build', 'Release', 'argon2.node');
|
|
75
|
+
if (!existsSync(bin)) throw new Error(`argon2 编译产物缺失: ${bin}`);
|
|
76
|
+
return bin;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function main() {
|
|
80
|
+
if (!existsSync(join(vendorTree, 'out', 'node', 'entry.js'))) {
|
|
81
|
+
throw new Error('缺少 vendor/code-server;先运行 `node scripts/vendor-code-server.mjs`');
|
|
82
|
+
}
|
|
83
|
+
const csVersion = readJson(join(vendorTree, 'package.json'))?.version;
|
|
84
|
+
if (typeof csVersion !== 'string') throw new Error('读不到 code-server 版本');
|
|
85
|
+
const pluginPkg = readJson(join(pkgRoot, 'package.json'));
|
|
86
|
+
const scope = '@jinsiyu';
|
|
87
|
+
const hostKey = `${process.platform}-${process.arch}`;
|
|
88
|
+
const plan = [];
|
|
89
|
+
|
|
90
|
+
for (const target of TARGETS) {
|
|
91
|
+
const [platform, arch] = target.split('-');
|
|
92
|
+
if (platform === undefined || arch === undefined) throw new Error(`--target 需要 <platform>-<arch>,收到 ${target}`);
|
|
93
|
+
const outName = `code-server-${target}`;
|
|
94
|
+
const dir = join(buildDir, outName);
|
|
95
|
+
rmSync(dir, { recursive: true, force: true });
|
|
96
|
+
mkdirSync(dir, { recursive: true });
|
|
97
|
+
console.log(`[cs-pkg] 复制 vendor/code-server → ${relative(pkgRoot, join(dir, 'code-server'))}`);
|
|
98
|
+
cpSync(vendorTree, join(dir, 'code-server'), { recursive: true, dereference: false, maxRetries: 6, retryDelay: 250 });
|
|
99
|
+
|
|
100
|
+
const tree = join(dir, 'code-server');
|
|
101
|
+
// 目标架构的 argon2 原生二进制
|
|
102
|
+
const wantMachine = arch === 'x64' ? '0x8664' : arch === 'arm64' ? '0xaa64' : null;
|
|
103
|
+
const have = join(tree, 'node_modules', 'argon2', 'build', 'Release', 'argon2.node');
|
|
104
|
+
if (target !== hostKey) {
|
|
105
|
+
const bin = buildArgon2(tree, arch);
|
|
106
|
+
const m = peMachine(bin);
|
|
107
|
+
if (wantMachine !== null && m !== wantMachine) throw new Error(`argon2 PE machine=${m},期望 ${wantMachine}`);
|
|
108
|
+
console.log(`[cs-pkg] argon2 → ${relative(pkgRoot, bin)} (${m})`);
|
|
109
|
+
} else {
|
|
110
|
+
console.log(`[cs-pkg] argon2 沿用本机产物 (${peMachine(have) ?? '?'})`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
writeFileSync(join(dir, 'package.json'), JSON.stringify({
|
|
114
|
+
name: `${scope}/dshcs-code-server-${target}`,
|
|
115
|
+
version: csVersion,
|
|
116
|
+
description: `Bundled code-server ${csVersion} tree for ${target} (out/ + lib/vscode + its ${readdirSync(join(tree, 'node_modules')).length} runtime deps), repacked so pnpm installs it without any build script.`,
|
|
117
|
+
os: [platform],
|
|
118
|
+
cpu: [arch],
|
|
119
|
+
files: ['code-server'],
|
|
120
|
+
license: 'MIT',
|
|
121
|
+
repository: pluginPkg?.repository ?? undefined,
|
|
122
|
+
}, null, 2) + '\n', 'utf8');
|
|
123
|
+
plan.push({ dir, file: `${`${scope}/dshcs-code-server-${target}`.replace(/^@/, '').replaceAll('/', '-')}-${csVersion}.tgz` });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (DO_PACK) {
|
|
127
|
+
mkdirSync(tgzDir, { recursive: true });
|
|
128
|
+
for (const item of plan) {
|
|
129
|
+
console.log(`[cs-pkg] npm pack ${item.file}`);
|
|
130
|
+
npm(['pack', '--pack-destination', tgzDir], item.dir);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
console.log(`[cs-pkg] 完成:${plan.length} 个平台子包 → repack/build/${DO_PACK ? ' + repack/tgz/' : ''}`);
|
|
134
|
+
console.log('[cs-pkg] 下一步:`node scripts/publish-repacks.mjs --only dshcs-code-server` 发布(或用 --dry-run 预览)');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
main();
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
// scripts/vendor-code-server.mjs — 打包期准备插件包内置的 code-server 本体。
|
|
2
|
+
//
|
|
3
|
+
// 产物:
|
|
4
|
+
// vendor/code-server/ code-server 本体 + 它自己的运行时依赖
|
|
5
|
+
// (npm install code-server@<v> --ignore-scripts;argon2 补齐原生二进制);
|
|
6
|
+
// **不含** VS Code 内部依赖(lib/vscode/node_modules,约 1GB),
|
|
7
|
+
// 那部分由包管理器按依赖安装(见 scripts/vendor-repacks.mjs);
|
|
8
|
+
// vendor/VENDOR.json 版本与来源元数据。
|
|
9
|
+
//
|
|
10
|
+
// 为什么 code-server 本体随包发布而不是写成依赖:pnpm 11 只接受宿主 profile 的构建许可,
|
|
11
|
+
// code-server / argon2 / unrs-resolver 带安装脚本 → 直接进 dependencies 会让
|
|
12
|
+
// `dsh plugin add` 报 [ERR_PNPM_IGNORED_BUILDS] exit 1。
|
|
13
|
+
//
|
|
14
|
+
// 用法:
|
|
15
|
+
// node scripts/vendor-code-server.mjs --check # 只报告内置版本 vs npm 最新版
|
|
16
|
+
// node scripts/vendor-code-server.mjs --force # 重建为 npm 最新版
|
|
17
|
+
// node scripts/vendor-code-server.mjs --version 4.136.2 # 指定版本
|
|
18
|
+
// node scripts/vendor-code-server.mjs --from <code-server 树> # 从已准备好的树快照(最快)
|
|
19
|
+
// DSHCS_ARGON2_BINARY=<argon2.node> # 编译不可用时复用已有原生二进制
|
|
20
|
+
//
|
|
21
|
+
// 换 code-server 版本后:还要重跑 `node scripts/vendor-repacks.mjs …` 更新预编译原生包,
|
|
22
|
+
// 再 `npm run publish:repacks` 发布,最后 bump 版本并 `pnpm pack`。
|
|
23
|
+
import { spawnSync } from 'node:child_process';
|
|
24
|
+
import { cpSync, copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync, statSync } from 'node:fs';
|
|
25
|
+
import { dirname, join, resolve } from 'node:path';
|
|
26
|
+
import { fileURLToPath } from 'node:url';
|
|
27
|
+
import { homedir } from 'node:os';
|
|
28
|
+
|
|
29
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
30
|
+
const pkgRoot = resolve(here, '..');
|
|
31
|
+
const vendorDir = join(pkgRoot, 'vendor');
|
|
32
|
+
const vendorTree = join(vendorDir, 'code-server');
|
|
33
|
+
// 临时目录与 npm 缓存都放在工作区内:本机沙箱下 ~\AppData\Local\npm-cache 不可写(EPERM)。
|
|
34
|
+
const workRoot = join(pkgRoot, '.vendor-tmp');
|
|
35
|
+
const npmCache = join(pkgRoot, '.npm-cache');
|
|
36
|
+
|
|
37
|
+
function argValues(name) {
|
|
38
|
+
const out = [];
|
|
39
|
+
for (let i = 0; i < process.argv.length - 1; i += 1) {
|
|
40
|
+
if (process.argv[i] !== name) continue;
|
|
41
|
+
for (const part of String(process.argv[i + 1]).split(',')) {
|
|
42
|
+
const v = part.trim();
|
|
43
|
+
if (v !== '') out.push(v);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
function argValue(name) {
|
|
49
|
+
const all = argValues(name);
|
|
50
|
+
return all.length > 0 ? all[all.length - 1] : null;
|
|
51
|
+
}
|
|
52
|
+
const FORCE = process.argv.includes('--force');
|
|
53
|
+
const FROM = argValue('--from');
|
|
54
|
+
const PINNED = argValue('--version') || process.env.DSHCS_CODE_SERVER_VERSION || null;
|
|
55
|
+
|
|
56
|
+
function run(cmd, args, cwd, extraEnv) {
|
|
57
|
+
// 只有 .cmd/.bat 需要 shell;node.exe 走 shell 会因路径带空格被 cmd 拆开(实测)。
|
|
58
|
+
const useShell = process.platform === 'win32' && /\.(cmd|bat)$/i.test(cmd);
|
|
59
|
+
console.log(`[vendor] $ ${cmd} ${args.join(' ')}${cwd ? ` (cwd=${cwd})` : ''}`);
|
|
60
|
+
const res = spawnSync(cmd, args, {
|
|
61
|
+
cwd,
|
|
62
|
+
stdio: 'inherit',
|
|
63
|
+
shell: useShell,
|
|
64
|
+
env: extraEnv ? { ...process.env, ...extraEnv } : process.env,
|
|
65
|
+
});
|
|
66
|
+
if (res.status !== 0) throw new Error(`${cmd} ${args.join(' ')} failed (${res.status})`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** npm 优先直调 npm-cli.js(避免 shell 引号问题);找不到再回退 npm.cmd。 */
|
|
70
|
+
function npm(args, cwd) {
|
|
71
|
+
const env = { npm_config_cache: npmCache, npm_config_update_notifier: 'false' };
|
|
72
|
+
const cli = join(dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npm-cli.js');
|
|
73
|
+
if (existsSync(cli)) run(process.execPath, [cli, ...args], cwd, env);
|
|
74
|
+
else run(process.platform === 'win32' ? 'npm.cmd' : 'npm', args, cwd, env);
|
|
75
|
+
}
|
|
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 readManifest(file) {
|
|
86
|
+
try { return JSON.parse(readFileSync(file, 'utf8')); } catch { return null; }
|
|
87
|
+
}
|
|
88
|
+
function readTreeVersion(tree) {
|
|
89
|
+
const m = readManifest(join(tree, 'package.json'));
|
|
90
|
+
return m !== null && typeof m.version === 'string' ? m.version : null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** 递归目录体积(MB),仅用于日志。 */
|
|
94
|
+
function sizeMB(dir) {
|
|
95
|
+
let total = 0;
|
|
96
|
+
const stack = [dir];
|
|
97
|
+
while (stack.length > 0) {
|
|
98
|
+
const cur = stack.pop();
|
|
99
|
+
let entries;
|
|
100
|
+
try { entries = readdirSync(cur, { withFileTypes: true }); } catch { continue; }
|
|
101
|
+
for (const ent of entries) {
|
|
102
|
+
if (ent.isDirectory()) stack.push(join(cur, ent.name));
|
|
103
|
+
else if (ent.isFile()) { try { total += statSync(join(cur, ent.name)).size; } catch { /* ignore */ } }
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return Math.round((total / 1024 / 1024) * 10) / 10;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** 补齐 code-server 自己的 argon2 native(win32-arm64 无预编译)。 */
|
|
110
|
+
function ensureArgon2(tree) {
|
|
111
|
+
const argon2 = join(tree, 'node_modules', 'argon2');
|
|
112
|
+
if (!existsSync(argon2)) return;
|
|
113
|
+
const probe = probeArgon2(argon2);
|
|
114
|
+
if (probe.ok) {
|
|
115
|
+
console.log(`[vendor] argon2 native 就绪(${probe.via})`);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
let built = false;
|
|
119
|
+
const ngb = findNgb(tree);
|
|
120
|
+
if (ngb !== null) {
|
|
121
|
+
try {
|
|
122
|
+
console.log(`[vendor] argon2 native 缺失(${probe.error}),执行 node-gyp-build…`);
|
|
123
|
+
run(process.execPath, [ngb, argon2], tree);
|
|
124
|
+
built = probeArgon2(argon2).ok;
|
|
125
|
+
} catch (e) {
|
|
126
|
+
console.warn(`[vendor] node-gyp-build 失败:${e.message}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (!built) {
|
|
130
|
+
const bin = findArgon2Binary();
|
|
131
|
+
if (bin === null) {
|
|
132
|
+
throw new Error('argon2 native 不可用:编译失败且未找到可复用二进制'
|
|
133
|
+
+ '(可在有工具链的机器上重跑,或用 DSHCS_ARGON2_BINARY 指定 argon2.node)');
|
|
134
|
+
}
|
|
135
|
+
const dst = join(argon2, 'build', 'Release');
|
|
136
|
+
rmSync(join(argon2, 'build'), { recursive: true, force: true });
|
|
137
|
+
mkdirSync(dst, { recursive: true });
|
|
138
|
+
copyFileSync(bin, join(dst, 'argon2.node'));
|
|
139
|
+
console.log(`[vendor] argon2 native ← 复用 ${bin}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** 可复用的 argon2.node:环境变量 → 本机 DSH profile 里已装好的安装 → 插件包内既有产物。 */
|
|
144
|
+
function findArgon2Binary() {
|
|
145
|
+
const cands = [];
|
|
146
|
+
if (typeof process.env.DSHCS_ARGON2_BINARY === 'string' && process.env.DSHCS_ARGON2_BINARY !== '') {
|
|
147
|
+
cands.push(process.env.DSHCS_ARGON2_BINARY);
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
const profiles = join(process.env.DSH_HOME || join(homedir(), '.dsh'), 'profiles');
|
|
151
|
+
if (existsSync(profiles)) {
|
|
152
|
+
for (const name of readdirSync(profiles)) {
|
|
153
|
+
// 0.1.35 及更早的安装位
|
|
154
|
+
cands.push(join(profiles, name, '.code-server-app', 'node_modules', 'code-server',
|
|
155
|
+
'node_modules', 'argon2', 'build', 'Release', 'argon2.node'));
|
|
156
|
+
// 0.1.36+ 就地安装位
|
|
157
|
+
cands.push(join(profiles, name, 'node_modules', 'dsh-code-server-app', 'vendor', 'code-server',
|
|
158
|
+
'node_modules', 'argon2', 'build', 'Release', 'argon2.node'));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
} catch { /* ignore */ }
|
|
162
|
+
cands.push(join(vendorTree, 'node_modules', 'argon2', 'build', 'Release', 'argon2.node'));
|
|
163
|
+
cands.push(join(pkgRoot, 'node_modules', 'code-server', 'node_modules', 'argon2', 'build', 'Release', 'argon2.node'));
|
|
164
|
+
for (const c of cands) if (existsSync(c)) return c;
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function findNgb(tree) {
|
|
169
|
+
const cands = [
|
|
170
|
+
join(tree, 'node_modules', 'node-gyp-build', 'bin.js'),
|
|
171
|
+
join(tree, 'node_modules', 'argon2', 'node_modules', 'node-gyp-build', 'bin.js'),
|
|
172
|
+
];
|
|
173
|
+
for (const c of cands) if (existsSync(c)) return c;
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function probeArgon2(dir) {
|
|
178
|
+
const via = [];
|
|
179
|
+
if (existsSync(join(dir, 'build', 'Release', 'argon2.node'))) via.push('build/Release');
|
|
180
|
+
const want = `${process.platform}-${process.arch}`;
|
|
181
|
+
if (existsSync(join(dir, 'prebuilds', want))) via.push(`prebuilds/${want}`);
|
|
182
|
+
if (via.length > 0) return { ok: true, via: via.join('+') };
|
|
183
|
+
// 不 require:argon2 的 index.js 会触发 node-gyp-build 现场编译(慢且可能失败)。
|
|
184
|
+
return { ok: false, error: `无 build/Release 也无 prebuilds/${want}` };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** 去掉 vendored 包里的安装脚本(pnpm/npm 不读它们,但避免后人误以为会执行)。 */
|
|
188
|
+
function stripInstallScripts(tree) {
|
|
189
|
+
const targets = [
|
|
190
|
+
join(tree, 'package.json'),
|
|
191
|
+
join(tree, 'node_modules', 'argon2', 'package.json'),
|
|
192
|
+
join(tree, 'node_modules', 'unrs-resolver', 'package.json'),
|
|
193
|
+
];
|
|
194
|
+
for (const file of targets) {
|
|
195
|
+
if (!existsSync(file)) continue;
|
|
196
|
+
try {
|
|
197
|
+
const manifest = readManifest(file);
|
|
198
|
+
if (manifest !== null && manifest.scripts
|
|
199
|
+
&& (manifest.scripts.preinstall || manifest.scripts.install || manifest.scripts.postinstall)) {
|
|
200
|
+
delete manifest.scripts.preinstall;
|
|
201
|
+
delete manifest.scripts.install;
|
|
202
|
+
delete manifest.scripts.postinstall;
|
|
203
|
+
writeFileSync(file, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
|
|
204
|
+
console.log(`[vendor] 已剥离安装脚本: ${manifest.name}`);
|
|
205
|
+
}
|
|
206
|
+
} catch (e) {
|
|
207
|
+
console.warn(`[vendor] 剥离脚本失败(${file}): ${e.message}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** 复制 code-server 本体到 vendor/code-server;跳过 VS Code 内部依赖目录(不进主包)。 */
|
|
213
|
+
function copyCodeServer(src, dest) {
|
|
214
|
+
rmSync(dest, { recursive: true, force: true });
|
|
215
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
216
|
+
const inner = [
|
|
217
|
+
join('lib', 'vscode', 'node_modules'),
|
|
218
|
+
join('lib', 'vscode', 'extensions', 'node_modules'),
|
|
219
|
+
].map((p) => p.replaceAll('\\', '/'));
|
|
220
|
+
cpSync(src, dest, {
|
|
221
|
+
recursive: true,
|
|
222
|
+
dereference: false,
|
|
223
|
+
maxRetries: 6,
|
|
224
|
+
retryDelay: 250,
|
|
225
|
+
filter: (source) => {
|
|
226
|
+
const rel = source.slice(src.length + 1).replaceAll('\\', '/');
|
|
227
|
+
return !inner.some((p) => rel === p || rel.startsWith(p + '/'));
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function main() {
|
|
233
|
+
const existing = readTreeVersion(vendorTree);
|
|
234
|
+
|
|
235
|
+
// --check:只报告内置版本 vs npm 最新版,不改任何东西。
|
|
236
|
+
if (process.argv.includes('--check')) {
|
|
237
|
+
const latest = await latestVersion();
|
|
238
|
+
console.log(`[vendor] 内置 code-server: ${existing ?? '(无)'} | npm latest: ${latest}`);
|
|
239
|
+
console.log(existing === latest
|
|
240
|
+
? '[vendor] 已是最新,无需重建'
|
|
241
|
+
: '[vendor] 有新版本:pnpm run vendor:latest → node scripts/vendor-repacks.mjs → pnpm run publish:repacks → 版本 +1 后 pnpm pack');
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const upToDate = existing !== null && !FORCE && FROM === null && PINNED === null;
|
|
246
|
+
let codeServerVersion = existing;
|
|
247
|
+
let sourceKind = 'registry';
|
|
248
|
+
|
|
249
|
+
if (upToDate) {
|
|
250
|
+
console.log(`[vendor] vendor/code-server 已存在(code-server@${existing});跳过(用 --force 重建)`);
|
|
251
|
+
} else if (FROM !== null) {
|
|
252
|
+
codeServerVersion = readTreeVersion(FROM);
|
|
253
|
+
if (codeServerVersion === null) throw new Error(`--from ${FROM} 不是一棵 code-server 树`);
|
|
254
|
+
console.log(`[vendor] 快照 ${FROM} → vendor/code-server (code-server@${codeServerVersion})`);
|
|
255
|
+
// 只在副本上补 argon2 / 剥脚本,不改动源树(--from 常常指向正在使用的安装)。
|
|
256
|
+
copyCodeServer(FROM, vendorTree);
|
|
257
|
+
ensureArgon2(vendorTree);
|
|
258
|
+
stripInstallScripts(vendorTree);
|
|
259
|
+
sourceKind = `snapshot:${FROM}`;
|
|
260
|
+
} else {
|
|
261
|
+
codeServerVersion = PINNED ?? await latestVersion();
|
|
262
|
+
console.log(`[vendor] 从 registry 准备 code-server@${codeServerVersion} …`);
|
|
263
|
+
const tmp = join(workRoot, `source-${process.pid}`);
|
|
264
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
265
|
+
mkdirSync(tmp, { recursive: true });
|
|
266
|
+
try {
|
|
267
|
+
npm(['install', `code-server@${codeServerVersion}`, '--ignore-scripts', '--omit=dev',
|
|
268
|
+
'--no-audit', '--no-fund', '--no-save', '--prefix', tmp], tmp);
|
|
269
|
+
const tree = join(tmp, 'node_modules', 'code-server');
|
|
270
|
+
if (!existsSync(join(tree, 'out', 'node', 'entry.js'))) throw new Error('code-server 安装不完整');
|
|
271
|
+
ensureArgon2(tree);
|
|
272
|
+
stripInstallScripts(tree);
|
|
273
|
+
copyCodeServer(tree, vendorTree);
|
|
274
|
+
} finally {
|
|
275
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const meta = {
|
|
280
|
+
codeServerVersion,
|
|
281
|
+
preparedAt: new Date().toISOString(),
|
|
282
|
+
source: sourceKind,
|
|
283
|
+
node: process.version,
|
|
284
|
+
platform: process.platform,
|
|
285
|
+
arch: process.arch,
|
|
286
|
+
sizeMB: sizeMB(vendorTree),
|
|
287
|
+
};
|
|
288
|
+
writeFileSync(join(vendorDir, 'VENDOR.json'), JSON.stringify(meta, null, 2) + '\n', 'utf8');
|
|
289
|
+
console.log(`[vendor] 完成:vendor/code-server (code-server@${codeServerVersion}, ${meta.sizeMB} MB) + vendor/VENDOR.json`);
|
|
290
|
+
console.log('[vendor] 提示:VS Code 内部依赖由包管理器安装;换版本后请重跑 scripts/vendor-repacks.mjs 更新预编译原生包');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
await main();
|