@ycodium-ai/plugin-browser-workbench 0.2.2098 → 0.2.2126
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/dist/index.js +180 -7
- package/locales/zh-CN.json +30 -0
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -71,7 +71,7 @@ var ProgressBus = class {
|
|
|
71
71
|
};
|
|
72
72
|
|
|
73
73
|
// src/runtime.ts
|
|
74
|
-
import * as
|
|
74
|
+
import * as NodeFSP5 from "node:fs/promises";
|
|
75
75
|
|
|
76
76
|
// src/attach.ts
|
|
77
77
|
import * as NodeNet from "node:net";
|
|
@@ -745,6 +745,122 @@ var stopManagedProcess = async (managed, paths) => {
|
|
|
745
745
|
await removeFile(paths.instancePath);
|
|
746
746
|
};
|
|
747
747
|
|
|
748
|
+
// src/productPatch.ts
|
|
749
|
+
import * as NodeFSP3 from "node:fs/promises";
|
|
750
|
+
import * as NodePath2 from "node:path";
|
|
751
|
+
var NLS_CORE_BASE_URL_KEY = "nlsCoreBaseUrl";
|
|
752
|
+
var stripNlsCoreBaseUrl = (raw) => {
|
|
753
|
+
let parsed;
|
|
754
|
+
try {
|
|
755
|
+
parsed = JSON.parse(raw);
|
|
756
|
+
} catch {
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
|
|
760
|
+
const entries = Object.entries(parsed);
|
|
761
|
+
if (!entries.some(([key]) => key === NLS_CORE_BASE_URL_KEY)) return null;
|
|
762
|
+
const kept = entries.filter(([key]) => key !== NLS_CORE_BASE_URL_KEY);
|
|
763
|
+
return JSON.stringify(Object.fromEntries(kept), null, " ");
|
|
764
|
+
};
|
|
765
|
+
var patchServeWebProducts = async (cliDataDir) => {
|
|
766
|
+
const serveWebDir = NodePath2.join(cliDataDir, "serve-web");
|
|
767
|
+
let entries;
|
|
768
|
+
try {
|
|
769
|
+
entries = await NodeFSP3.readdir(serveWebDir, { withFileTypes: true });
|
|
770
|
+
} catch {
|
|
771
|
+
return false;
|
|
772
|
+
}
|
|
773
|
+
let patched = false;
|
|
774
|
+
for (const entry of entries) {
|
|
775
|
+
if (!entry.isDirectory()) continue;
|
|
776
|
+
const productPath = NodePath2.join(serveWebDir, entry.name, "product.json");
|
|
777
|
+
let raw;
|
|
778
|
+
try {
|
|
779
|
+
raw = await NodeFSP3.readFile(productPath, "utf8");
|
|
780
|
+
} catch {
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
783
|
+
const next = stripNlsCoreBaseUrl(raw);
|
|
784
|
+
if (next === null) continue;
|
|
785
|
+
await writeAtomic(productPath, next);
|
|
786
|
+
patched = true;
|
|
787
|
+
}
|
|
788
|
+
return patched;
|
|
789
|
+
};
|
|
790
|
+
|
|
791
|
+
// src/webConfigPatch.ts
|
|
792
|
+
import * as NodeFSP4 from "node:fs/promises";
|
|
793
|
+
import * as NodePath3 from "node:path";
|
|
794
|
+
var WORKSPACE_TRUST_DISABLED = { "security.workspace.trust.enabled": false };
|
|
795
|
+
var webConfigDefaults = (windowTitle) => ({
|
|
796
|
+
...WORKSPACE_TRUST_DISABLED,
|
|
797
|
+
...windowTitle === null ? {} : { "window.title": windowTitle }
|
|
798
|
+
});
|
|
799
|
+
var MARKER = "ycodium:web-config";
|
|
800
|
+
var MARKED_BLOCK = /\t\t<!-- ycodium:[a-z-]+ -->\n(?:.*\n)*?\t\t<\/script>\n/g;
|
|
801
|
+
var ANCHOR = ' <meta id="vscode-workbench-web-base-url"';
|
|
802
|
+
var jsLiteral = (value) => JSON.stringify(value).replaceAll("<", "\\u003c").replaceAll(">", "\\u003e").replaceAll("&", "\\u0026");
|
|
803
|
+
var overridesScript = (defaults) => [
|
|
804
|
+
` <!-- ${MARKER} -->`,
|
|
805
|
+
` <script nonce="{{WORKBENCH_SCRIPT_NONCE}}">`,
|
|
806
|
+
` (() => {`,
|
|
807
|
+
` try {`,
|
|
808
|
+
` const meta = document.getElementById('vscode-workbench-web-configuration');`,
|
|
809
|
+
` const settings = JSON.parse(meta.getAttribute('data-settings'));`,
|
|
810
|
+
` settings.configurationDefaults = Object.assign({}, settings.configurationDefaults, ${jsLiteral(defaults)});`,
|
|
811
|
+
` meta.setAttribute('data-settings', JSON.stringify(settings));`,
|
|
812
|
+
` } catch {}`,
|
|
813
|
+
` })();`,
|
|
814
|
+
` </script>`,
|
|
815
|
+
""
|
|
816
|
+
].join("\n");
|
|
817
|
+
var applyWebConfigToHtml = (html, defaults) => {
|
|
818
|
+
const stripped = html.replace(MARKED_BLOCK, "");
|
|
819
|
+
if (!stripped.includes(ANCHOR)) return null;
|
|
820
|
+
const next = stripped.replace(ANCHOR, `${overridesScript(defaults)}${ANCHOR}`);
|
|
821
|
+
return next === html ? null : next;
|
|
822
|
+
};
|
|
823
|
+
var workbenchHtmlPath = (serveWebDir, commit) => NodePath3.join(serveWebDir, commit, "out", "vs", "code", "browser", "workbench", "workbench.html");
|
|
824
|
+
var patchServeWebConfig = async (cliDataDir, defaults) => {
|
|
825
|
+
const serveWebDir = NodePath3.join(cliDataDir, "serve-web");
|
|
826
|
+
let entries;
|
|
827
|
+
try {
|
|
828
|
+
entries = await NodeFSP4.readdir(serveWebDir, { withFileTypes: true });
|
|
829
|
+
} catch {
|
|
830
|
+
return "unchanged";
|
|
831
|
+
}
|
|
832
|
+
let patched = false;
|
|
833
|
+
let anchorMissing = false;
|
|
834
|
+
for (const entry of entries) {
|
|
835
|
+
if (!entry.isDirectory()) continue;
|
|
836
|
+
const htmlPath = workbenchHtmlPath(serveWebDir, entry.name);
|
|
837
|
+
let html;
|
|
838
|
+
try {
|
|
839
|
+
html = await NodeFSP4.readFile(htmlPath, "utf8");
|
|
840
|
+
} catch {
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
const next = applyWebConfigToHtml(html, defaults);
|
|
844
|
+
if (next === null) {
|
|
845
|
+
if (!html.includes(ANCHOR)) anchorMissing = true;
|
|
846
|
+
continue;
|
|
847
|
+
}
|
|
848
|
+
await writeAtomic(htmlPath, next);
|
|
849
|
+
patched = true;
|
|
850
|
+
}
|
|
851
|
+
if (patched) return "patched";
|
|
852
|
+
return anchorMissing ? "no-anchor" : "unchanged";
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
// src/windowTitle.ts
|
|
856
|
+
var TITLE_TAIL = "${separator}${rootName}";
|
|
857
|
+
var escapeTitleLiteral = (value) => value.replaceAll("$", "$}");
|
|
858
|
+
var windowTitleTemplate = (machineLabel) => {
|
|
859
|
+
const label = machineLabel?.trim() ?? "";
|
|
860
|
+
if (label.length === 0) return null;
|
|
861
|
+
return `${escapeTitleLiteral(label)}${TITLE_TAIL}`;
|
|
862
|
+
};
|
|
863
|
+
|
|
748
864
|
// src/runtime.ts
|
|
749
865
|
var MANAGED_LABEL = "VS Code";
|
|
750
866
|
var EXTERNAL_LABEL = "code-server";
|
|
@@ -897,12 +1013,62 @@ var WorkbenchRuntime = class {
|
|
|
897
1013
|
}
|
|
898
1014
|
await this.stepLicense(payload.acceptLicense === true);
|
|
899
1015
|
const cliVersion = await this.stepCheckAndDownloadCli();
|
|
1016
|
+
await this.prepareServerData(payload.machineLabel);
|
|
900
1017
|
await this.stepStartServer(cliVersion);
|
|
901
1018
|
const token = await ensureServeWebToken(this.paths);
|
|
902
1019
|
await this.stepDownloadServer(token);
|
|
1020
|
+
await this.restartIfProductJustPatched(cliVersion, token);
|
|
1021
|
+
await this.patchWebConfig(payload.machineLabel);
|
|
903
1022
|
const portId = await this.stepRegisterManaged();
|
|
904
1023
|
return { portId, token };
|
|
905
1024
|
}
|
|
1025
|
+
/**
|
|
1026
|
+
* 起服前把服务端产物调好:摘掉 CDN 翻译表的入口。服务端早就下载过时是空操作,所以每轮
|
|
1027
|
+
* open 都跑。补丁失败不拦 open。
|
|
1028
|
+
*/
|
|
1029
|
+
async prepareServerData(machineLabel) {
|
|
1030
|
+
try {
|
|
1031
|
+
await patchServeWebProducts(this.paths.cliDataDir);
|
|
1032
|
+
} catch (error) {
|
|
1033
|
+
this.ctx.log.warn("could not prepare the workbench server data", {
|
|
1034
|
+
detail: messageOf(error)
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
await this.patchWebConfig(machineLabel);
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* 网页工作台那几条设置:标签页标题改成「机器名 - 项目」、关掉工作区信任。写歪了不拦
|
|
1041
|
+
* open——设置退回 VS Code 的默认,别的照走。
|
|
1042
|
+
*/
|
|
1043
|
+
async patchWebConfig(machineLabel) {
|
|
1044
|
+
const defaults = webConfigDefaults(windowTitleTemplate(machineLabel));
|
|
1045
|
+
try {
|
|
1046
|
+
if (await patchServeWebConfig(this.paths.cliDataDir, defaults) === "no-anchor") {
|
|
1047
|
+
this.ctx.log.warn("workbench.html has no known anchor; left the web config alone");
|
|
1048
|
+
}
|
|
1049
|
+
} catch (error) {
|
|
1050
|
+
this.ctx.log.warn("could not patch the workbench web config", { detail: messageOf(error) });
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
/**
|
|
1054
|
+
* 首次下载那一轮补丁赶不上服务端启动:CLI 起来一秒就把服务端子进程拉起来了,而那边
|
|
1055
|
+
* 在 boot 时读 product.json。这一轮真的改了盘就重启一次,让摘键生效。
|
|
1056
|
+
*/
|
|
1057
|
+
async restartIfProductJustPatched(cliVersion, token) {
|
|
1058
|
+
let patched = false;
|
|
1059
|
+
try {
|
|
1060
|
+
patched = await patchServeWebProducts(this.paths.cliDataDir);
|
|
1061
|
+
} catch (error) {
|
|
1062
|
+
this.ctx.log.warn("could not patch the downloaded product.json", {
|
|
1063
|
+
detail: messageOf(error)
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
if (!patched) return;
|
|
1067
|
+
this.ctx.log.info("restarting the workbench server to apply the patched product.json");
|
|
1068
|
+
await this.stopManaged();
|
|
1069
|
+
await this.stepStartServer(cliVersion);
|
|
1070
|
+
await this.stepDownloadServer(token);
|
|
1071
|
+
}
|
|
906
1072
|
async stepLicense(acceptLicense) {
|
|
907
1073
|
this.stage("license", "started");
|
|
908
1074
|
if (await fileExists(this.paths.licensePath)) {
|
|
@@ -1036,9 +1202,9 @@ var WorkbenchRuntime = class {
|
|
|
1036
1202
|
destPath,
|
|
1037
1203
|
onTransfer: (transfer) => this.stage("download-cli", "progress", { transfer })
|
|
1038
1204
|
});
|
|
1039
|
-
await
|
|
1205
|
+
await NodeFSP5.rename(destPath, this.paths.cliPath);
|
|
1040
1206
|
} catch (error) {
|
|
1041
|
-
await
|
|
1207
|
+
await NodeFSP5.unlink(destPath).catch(() => void 0);
|
|
1042
1208
|
this.rethrowAs(error, "download-cli");
|
|
1043
1209
|
}
|
|
1044
1210
|
this.stage("download-cli", "succeeded");
|
|
@@ -1119,7 +1285,10 @@ var emptyPayloadSchema = emptyObject;
|
|
|
1119
1285
|
var openPayloadSchema = {
|
|
1120
1286
|
type: "object",
|
|
1121
1287
|
properties: {
|
|
1122
|
-
acceptLicense: { type: "boolean" }
|
|
1288
|
+
acceptLicense: { type: "boolean" },
|
|
1289
|
+
// 机器名只存在客户端,由它在 open 时捎过来当标签页标题。不设长度下限:空串与
|
|
1290
|
+
// 超长名字都由插件自己判,长一点的名字不该让整个 open 被 schema 挡下来。
|
|
1291
|
+
machineLabel: { type: "string" }
|
|
1123
1292
|
},
|
|
1124
1293
|
additionalProperties: false
|
|
1125
1294
|
};
|
|
@@ -1265,9 +1434,13 @@ var index_default = definePlugin({
|
|
|
1265
1434
|
scope: "operate",
|
|
1266
1435
|
payloadSchema: openPayloadSchema,
|
|
1267
1436
|
resultSchema: openResultSchema,
|
|
1268
|
-
handle: (payload) =>
|
|
1269
|
-
|
|
1270
|
-
|
|
1437
|
+
handle: (payload) => {
|
|
1438
|
+
const machineLabel = payload["machineLabel"];
|
|
1439
|
+
return runtime.open({
|
|
1440
|
+
acceptLicense: payload["acceptLicense"] === true,
|
|
1441
|
+
...typeof machineLabel === "string" ? { machineLabel } : {}
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1271
1444
|
});
|
|
1272
1445
|
ctx.command({
|
|
1273
1446
|
name: "stop",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"settings": {
|
|
3
|
+
"card": {
|
|
4
|
+
"title": "外部工作台",
|
|
5
|
+
"description": "将此机器连接到你已运行的 code-server。Ycodium 仅转发端口,不会启动或停止该进程。"
|
|
6
|
+
},
|
|
7
|
+
"attachPort": {
|
|
8
|
+
"label": "code-server 端口",
|
|
9
|
+
"description": "此机器上已在监听的 code-server 回环端口。留空则改用官方 VS Code CLI。"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"failure": {
|
|
13
|
+
"license-required": "请确认 VS Code Server 许可条款后继续。",
|
|
14
|
+
"download-failed": "无法下载 VS Code CLI。",
|
|
15
|
+
"disk-full": "此机器没有足够的磁盘空间来安装 VS Code CLI。",
|
|
16
|
+
"launch-failed": "VS Code CLI 已启动,但未能及时提供本地地址。",
|
|
17
|
+
"port-in-use": "工作台的本地端口已被占用。",
|
|
18
|
+
"server-download-failed": "VS Code server 未能及时就绪。",
|
|
19
|
+
"grant-expired": "桌面连接授权已过期。",
|
|
20
|
+
"host-key-mismatch": "此机器的密钥与固定的 Host 密钥不匹配。",
|
|
21
|
+
"channel-failed": "桌面端无法打开与工作台的通道。",
|
|
22
|
+
"unknown": "无法打开工作台。"
|
|
23
|
+
},
|
|
24
|
+
"attach": {
|
|
25
|
+
"noListener": "127.0.0.1:{{port}} 没有程序在监听。"
|
|
26
|
+
},
|
|
27
|
+
"running": {
|
|
28
|
+
"stopFirst": "请先停止工作台,再更新 CLI。"
|
|
29
|
+
}
|
|
30
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ycodium-ai/plugin-browser-workbench",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2126",
|
|
4
4
|
"description": "Installs the official VS Code CLI on this machine and opens it in a system browser.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"browser-workbench",
|
|
@@ -21,8 +21,9 @@
|
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@effect/vitest": "4.0.0-beta.103",
|
|
23
23
|
"esbuild": "0.28.1",
|
|
24
|
+
"typescript": "~6.0.3",
|
|
24
25
|
"vite-plus": "0.2.2",
|
|
25
|
-
"@ycodium-ai/plugin-api": "0.2.
|
|
26
|
+
"@ycodium-ai/plugin-api": "0.2.2126"
|
|
26
27
|
},
|
|
27
28
|
"ycodium": {
|
|
28
29
|
"plugin": "./dist/index.js",
|