@ycodium-ai/plugin-browser-workbench 0.2.2106 → 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 CHANGED
@@ -71,7 +71,7 @@ var ProgressBus = class {
71
71
  };
72
72
 
73
73
  // src/runtime.ts
74
- import * as NodeFSP3 from "node:fs/promises";
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 NodeFSP3.rename(destPath, this.paths.cliPath);
1205
+ await NodeFSP5.rename(destPath, this.paths.cliPath);
1040
1206
  } catch (error) {
1041
- await NodeFSP3.unlink(destPath).catch(() => void 0);
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) => runtime.open({
1269
- acceptLicense: payload["acceptLicense"] === true
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.2106",
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.2106"
26
+ "@ycodium-ai/plugin-api": "0.2.2126"
26
27
  },
27
28
  "ycodium": {
28
29
  "plugin": "./dist/index.js",