@ycodium-ai/plugin-office 0.2.3510 → 0.2.3566

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
@@ -1,155 +1,10 @@
1
1
  // src/index.ts
2
- import * as NodePath7 from "node:path";
2
+ import * as NodePath5 from "node:path";
3
3
  import { definePlugin } from "@ycodium-ai/plugin-api";
4
4
 
5
- // src/download.ts
6
- import * as NodeCrypto from "node:crypto";
7
- import * as NodeFS from "node:fs";
5
+ // src/paths.ts
8
6
  import * as NodeFSP from "node:fs/promises";
9
7
  import * as NodePath from "node:path";
10
- import * as NodeStream from "node:stream";
11
- import * as NodeStreamPromises from "node:stream/promises";
12
- var DEFAULT_SOURCES = {
13
- npmRegistry: "https://registry.npmjs.org",
14
- githubMirror: "",
15
- pypiMirror: "https://files.pythonhosted.org/packages",
16
- localDir: ""
17
- };
18
- var PYPI_PACKAGES = "https://files.pythonhosted.org/packages";
19
- var GITHUB_HOSTS = ["https://github.com/", "https://raw.githubusercontent.com/"];
20
- var trimSlash = (value) => value.replace(/\/+$/, "");
21
- var resolveDownloadUrl = (url, sources) => {
22
- if (url.startsWith(PYPI_PACKAGES)) {
23
- return `${trimSlash(sources.pypiMirror)}${url.slice(PYPI_PACKAGES.length)}`;
24
- }
25
- if (sources.githubMirror !== "" && GITHUB_HOSTS.some((host) => url.startsWith(host))) {
26
- return `${trimSlash(sources.githubMirror)}/${url}`;
27
- }
28
- return url;
29
- };
30
- var npmTarballUrl = (registry, name, version) => {
31
- const bare = name.includes("/") ? name.slice(name.indexOf("/") + 1) : name;
32
- return `${trimSlash(registry)}/${name}/-/${bare}-${version}.tgz`;
33
- };
34
- var parseSri = (sri) => {
35
- if (!sri.startsWith("sha512-")) throw new Error(`Unsupported integrity value: ${sri}`);
36
- return { algorithm: "sha512", base64: sri.slice("sha512-".length) };
37
- };
38
- var digestMatches = (hash, integrity) => integrity.algorithm === "sha256" ? hash.digest("hex") === integrity.hex.toLowerCase() : hash.digest("base64") === integrity.base64;
39
- var fileMatches = async (filePath, integrity) => {
40
- const hash = NodeCrypto.createHash(integrity.algorithm);
41
- try {
42
- await NodeStreamPromises.pipeline(NodeFS.createReadStream(filePath), hash);
43
- } catch {
44
- return false;
45
- }
46
- return digestMatches(hash, integrity);
47
- };
48
- var DownloadError = class extends Error {
49
- reason;
50
- constructor(reason, message) {
51
- super(message);
52
- this.reason = reason;
53
- }
54
- };
55
- var hostOf = (url) => {
56
- try {
57
- return new URL(url).host;
58
- } catch {
59
- return url;
60
- }
61
- };
62
- var describeNetworkError = (error) => {
63
- const message = error instanceof Error ? error.message : String(error);
64
- const cause = error?.cause;
65
- if (cause === void 0) return message;
66
- const code = typeof cause.code === "string" ? `${cause.code} ` : "";
67
- return `${message}: ${code}${typeof cause.message === "string" ? cause.message : ""}`.trim();
68
- };
69
- var downloadVerified = async (input) => {
70
- const { item, dir, sources } = input;
71
- const target = NodePath.join(dir, item.fileName);
72
- await NodeFSP.mkdir(dir, { recursive: true });
73
- if (await fileMatches(target, item.integrity)) return target;
74
- if (sources.localDir !== "") {
75
- const local = NodePath.join(sources.localDir, item.fileName);
76
- if (await fileMatches(local, item.integrity)) {
77
- await NodeFSP.copyFile(local, target);
78
- return target;
79
- }
80
- }
81
- const url = resolveDownloadUrl(item.url, sources);
82
- let response;
83
- try {
84
- response = await input.http(url, { signal: input.signal });
85
- } catch (error) {
86
- if (input.signal.aborted) throw error;
87
- throw new DownloadError(
88
- "network",
89
- `Could not reach ${hostOf(url)} to download ${item.fileName} (${describeNetworkError(error)}). Check this machine's internet access and proxy settings.`
90
- );
91
- }
92
- if (!response.ok || response.body === null) {
93
- throw new DownloadError(
94
- "http",
95
- `${hostOf(url)} answered HTTP ${String(response.status)} for ${item.fileName}.`
96
- );
97
- }
98
- const rawLength = Number(response.headers.get("content-length") ?? Number.NaN);
99
- const totalBytes = Number.isFinite(rawLength) && rawLength > 0 ? rawLength : null;
100
- const hash = NodeCrypto.createHash(input.item.integrity.algorithm);
101
- const partPath = `${target}.part`;
102
- let transferredBytes = 0;
103
- let lastReport = 0;
104
- const readable = NodeStream.Readable.fromWeb(
105
- response.body
106
- );
107
- readable.on("data", (chunk) => {
108
- transferredBytes += chunk.length;
109
- hash.update(chunk);
110
- const now = Date.now();
111
- if (now - lastReport >= 250) {
112
- lastReport = now;
113
- input.onProgress({ fileName: item.fileName, transferredBytes, totalBytes });
114
- }
115
- });
116
- try {
117
- await NodeStreamPromises.pipeline(readable, NodeFS.createWriteStream(partPath));
118
- } catch (error) {
119
- await NodeFSP.rm(partPath, { force: true });
120
- if (input.signal.aborted) throw error;
121
- const code = error.code;
122
- if (code === "ENOSPC") {
123
- throw new DownloadError(
124
- "disk",
125
- `This machine ran out of disk space while downloading ${item.fileName}.`
126
- );
127
- }
128
- throw new DownloadError(
129
- "network",
130
- `The download of ${item.fileName} stopped after ${String(transferredBytes)} bytes (${describeNetworkError(error)}).`
131
- );
132
- }
133
- input.onProgress({ fileName: item.fileName, transferredBytes, totalBytes });
134
- if (!digestMatches(hash, item.integrity)) {
135
- await NodeFSP.rm(partPath, { force: true });
136
- throw new DownloadError(
137
- "integrity",
138
- `${item.fileName} from ${hostOf(url)} did not match its pinned ${item.integrity.algorithm} hash and was discarded. If this machine uses a proxy or mirror, make sure it serves the original file.`
139
- );
140
- }
141
- await NodeFSP.rename(partPath, target);
142
- return target;
143
- };
144
-
145
- // src/engine.ts
146
- import * as NodeFSP4 from "node:fs/promises";
147
- import * as NodeProcess3 from "node:process";
148
-
149
- // src/install.ts
150
- import * as NodeFSP3 from "node:fs/promises";
151
- import * as NodePath4 from "node:path";
152
- import * as NodeProcess2 from "node:process";
153
8
 
154
9
  // src/lock.ts
155
10
  var OFFICE_LOCK = {
@@ -492,24 +347,49 @@ var OFFICE_LOCK = {
492
347
  }
493
348
  },
494
349
  fonts: {
350
+ /** 字体取自 notofonts/noto-cjk 的 release 资产(raw 文件在受限网络里取不到);成员再各自按 sha256 核对。 */
351
+ archives: {
352
+ sans: {
353
+ url: "https://github.com/notofonts/noto-cjk/releases/download/Sans2.004/18_NotoSansSC.zip",
354
+ sha256: "4d107c09ada479d3e48b6e78c83835773cbd9214bf6e12cdb7b60f8e068292ec"
355
+ },
356
+ serif: {
357
+ url: "https://github.com/notofonts/noto-cjk/releases/download/Serif2.003/14_NotoSerifSC.zip",
358
+ sha256: "c58cd035ab2adb003510846db9ec80c35b1b97755d329486c3a1e88edfe6e98e"
359
+ }
360
+ },
361
+ license: {
362
+ archive: "sans",
363
+ member: "LICENSE",
364
+ sha256: "6a73f9541c2de74158c0e7cf6b0a58ef774f5a780bf191f2d7ec9cc53efe2bf2"
365
+ },
366
+ /** path 是上游仓库里的位置(出处);落盘名取其文件名,member 是它在压缩包里的路径。 */
495
367
  files: [
496
368
  {
497
369
  path: "Sans2.004/Sans/SubsetOTF/SC/NotoSansSC-Regular.otf",
370
+ archive: "sans",
371
+ member: "NotoSansSC-Regular.otf",
498
372
  size: 8331336,
499
373
  sha256: "faa6c9df652116dde789d351359f3d7e5d2285a2b2a1f04a2d7244df706d5ea9"
500
374
  },
501
375
  {
502
376
  path: "Sans2.004/Sans/SubsetOTF/SC/NotoSansSC-Bold.otf",
377
+ archive: "sans",
378
+ member: "NotoSansSC-Bold.otf",
503
379
  size: 8543168,
504
380
  sha256: "c6cb5a93abaa9edc8ee7463b7ebb7f42d618d40e6ed2f7a5371c97b0b64767c0"
505
381
  },
506
382
  {
507
383
  path: "Serif2.003/Serif/SubsetOTF/SC/NotoSerifSC-Regular.otf",
384
+ archive: "serif",
385
+ member: "SubsetOTF/SC/NotoSerifSC-Regular.otf",
508
386
  size: 11625800,
509
387
  sha256: "e8f396decc1f0963a016a989c3d8852e863d1350996f573860a80767c83a1cd3"
510
388
  },
511
389
  {
512
390
  path: "Serif2.003/Serif/SubsetOTF/SC/NotoSerifSC-Bold.otf",
391
+ archive: "serif",
392
+ member: "SubsetOTF/SC/NotoSerifSC-Bold.otf",
513
393
  size: 12094336,
514
394
  sha256: "24693d48bdb9152f0a06b02af625638a1097abd6de4010ebba027f6e82710527"
515
395
  }
@@ -518,109 +398,95 @@ var OFFICE_LOCK = {
518
398
  };
519
399
 
520
400
  // src/paths.ts
521
- import * as NodeCrypto2 from "node:crypto";
522
- import * as NodeFSP2 from "node:fs/promises";
523
- import * as NodePath2 from "node:path";
524
- var PYTHON_PROGRAM = {
525
- win32: "data:runtime/python/python.exe",
526
- other: "data:runtime/python/bin/python3"
527
- };
528
- var officePaths = (dataDir) => {
529
- const runtimeDir = NodePath2.join(dataDir, "runtime");
530
- const dirOf = (component) => NodePath2.join(runtimeDir, component);
401
+ var ENGINE_VERSION = OFFICE_LOCK.engine.version;
402
+ var PYTHON_VERSION = `${OFFICE_LOCK.python.version}+${OFFICE_LOCK.python.release}`;
403
+ var FONTS_VERSION = "Sans2.004+Serif2.003";
404
+ var officePaths = (pluginDir, dataDir, target, runtimePath) => {
405
+ const runtimeDir = runtimePath ?? NodePath.join(pluginDir, "runtime", target ?? "unsupported");
406
+ const pythonLibraries = NodePath.join(runtimeDir, "python", "site-packages");
531
407
  return {
532
408
  dataDir,
533
409
  runtimeDir,
534
- downloadsDir: NodePath2.join(dataDir, "downloads"),
535
- dirOf,
536
- recordOf: (component) => NodePath2.join(runtimeDir, `${component}.json`),
537
- engineCli: NodePath2.join(
538
- dirOf("engine"),
410
+ pythonLibraries,
411
+ pythonRunner: NodePath.join(pluginDir, "assets", "run_office.py"),
412
+ engineCli: NodePath.join(
413
+ runtimeDir,
539
414
  "node_modules",
540
415
  "@deepseek-ai",
541
416
  "libreoffice-kit",
542
417
  "lib",
543
418
  "cli.js"
544
419
  ),
545
- pythonExecutable: (platform5) => platform5 === "win32" ? NodePath2.join(dirOf("python"), "python.exe") : NodePath2.join(dirOf("python"), "bin", "python3")
420
+ dirOf: (component) => component === "python" ? pythonLibraries : component === "engine" ? NodePath.join(runtimeDir, "node_modules") : NodePath.join(runtimeDir, "fonts")
546
421
  };
547
422
  };
548
- var readInstallRecord = async (paths, component) => {
423
+ var bundledComponentReady = async (paths, component, target) => {
424
+ if (target === null) return false;
549
425
  try {
550
- const value = JSON.parse(await NodeFSP2.readFile(paths.recordOf(component), "utf8"));
551
- if (typeof value !== "object" || value === null) return null;
552
- const record = value;
553
- return typeof record["version"] === "string" && typeof record["target"] === "string" ? { version: record["version"], target: record["target"] } : null;
426
+ const manifest = JSON.parse(
427
+ await NodeFSP.readFile(NodePath.join(paths.runtimeDir, "manifest.json"), "utf8")
428
+ );
429
+ if (typeof manifest !== "object" || manifest === null) return false;
430
+ const value = manifest;
431
+ const versions = { engine: ENGINE_VERSION, python: PYTHON_VERSION, fonts: FONTS_VERSION };
432
+ if (value["target"] !== target || value[`${component}Version`] !== versions[component])
433
+ return false;
434
+ const files = component === "engine" ? [paths.engineCli] : component === "python" ? [
435
+ paths.pythonRunner,
436
+ ...["docx", "pptx", "openpyxl", "pandas", "PIL", "lxml", "xlsxwriter"].map(
437
+ (name) => NodePath.join(paths.pythonLibraries, name, "__init__.py")
438
+ )
439
+ ] : OFFICE_LOCK.fonts.files.map(
440
+ (font) => NodePath.join(paths.dirOf("fonts"), NodePath.basename(font.path))
441
+ );
442
+ return (await Promise.all(
443
+ files.map(async (file, index) => {
444
+ const stat2 = await NodeFSP.stat(file);
445
+ return stat2.isFile() && (component !== "fonts" || stat2.size === OFFICE_LOCK.fonts.files[index].size);
446
+ })
447
+ )).every(Boolean);
554
448
  } catch {
555
- return null;
556
- }
557
- };
558
- var writeInstallRecord = (paths, component, record) => writeAtomic(paths.recordOf(component), `${JSON.stringify(record)}
559
- `);
560
- var removePath = (target) => NodeFSP2.rm(target, { recursive: true, force: true });
561
- var writeAtomic = async (filePath, contents) => {
562
- await NodeFSP2.mkdir(NodePath2.dirname(filePath), { recursive: true });
563
- const tmp = `${filePath}.${NodeCrypto2.randomBytes(6).toString("hex")}.tmp`;
564
- try {
565
- await NodeFSP2.writeFile(tmp, contents);
566
- await NodeFSP2.rename(tmp, filePath);
567
- } catch (error) {
568
- await NodeFSP2.rm(tmp, { force: true });
569
- throw error;
570
- }
571
- };
572
- var swapIntoPlace = async (staging, target) => {
573
- const retired = `${target}.old-${NodeCrypto2.randomBytes(4).toString("hex")}`;
574
- const hadPrevious = await NodeFSP2.rename(target, retired).then(
575
- () => true,
576
- () => false
577
- );
578
- try {
579
- await NodeFSP2.rename(staging, target);
580
- } catch (error) {
581
- if (hadPrevious) await NodeFSP2.rename(retired, target).catch(() => void 0);
582
- throw error;
449
+ return false;
583
450
  }
584
- if (hadPrevious) await removePath(retired);
585
451
  };
586
452
 
587
453
  // src/platform.ts
588
- import * as NodeFS2 from "node:fs";
454
+ import * as NodeFS from "node:fs";
589
455
  import * as NodeOS from "node:os";
590
- import * as NodePath3 from "node:path";
456
+ import * as NodePath2 from "node:path";
591
457
  import * as NodeProcess from "node:process";
592
- var detectOfficeTarget = (platform5 = NodeProcess.platform, arch2 = NodeProcess.arch) => {
593
- const key = `${platform5}-${arch2}`;
458
+ var detectOfficeTarget = (platform2 = NodeProcess.platform, arch2 = NodeProcess.arch) => {
459
+ const key = `${platform2}-${arch2}`;
594
460
  return Object.hasOwn(OFFICE_LOCK.engine.targets, key) ? key : null;
595
461
  };
596
462
  var pythonTargetOf = (target) => OFFICE_LOCK.python.targets[target] ?? null;
597
- var systemFontDirectories = (platform5 = NodeProcess.platform, home = NodeOS.homedir(), env2 = NodeProcess.env) => {
598
- if (platform5 === "win32") {
463
+ var systemFontDirectories = (platform2 = NodeProcess.platform, home = NodeOS.homedir(), env2 = NodeProcess.env) => {
464
+ if (platform2 === "win32") {
599
465
  return [
600
- NodePath3.win32.join(env2["SystemRoot"] ?? "C:\\Windows", "Fonts"),
601
- NodePath3.win32.join(
602
- env2["LOCALAPPDATA"] ?? NodePath3.win32.join(home, "AppData", "Local"),
466
+ NodePath2.win32.join(env2["SystemRoot"] ?? "C:\\Windows", "Fonts"),
467
+ NodePath2.win32.join(
468
+ env2["LOCALAPPDATA"] ?? NodePath2.win32.join(home, "AppData", "Local"),
603
469
  "Microsoft",
604
470
  "Windows",
605
471
  "Fonts"
606
472
  )
607
473
  ];
608
474
  }
609
- if (platform5 === "darwin") {
475
+ if (platform2 === "darwin") {
610
476
  return [
611
477
  "/System/Library/Fonts",
612
478
  "/Library/Fonts",
613
- NodePath3.posix.join(home, "Library/Fonts"),
479
+ NodePath2.posix.join(home, "Library/Fonts"),
614
480
  ...mobileAssetFontDirectories("/System/Library/AssetsV2")
615
481
  ];
616
482
  }
617
483
  const shared = (env2["XDG_DATA_DIRS"] ?? "/usr/local/share:/usr/share").split(":").filter(Boolean);
618
484
  return [
619
485
  .../* @__PURE__ */ new Set([
620
- ...shared.map((dir) => NodePath3.posix.join(dir, "fonts")),
621
- NodePath3.posix.join(home, ".fonts"),
622
- NodePath3.posix.join(
623
- env2["XDG_DATA_HOME"] ?? NodePath3.posix.join(home, ".local/share"),
486
+ ...shared.map((dir) => NodePath2.posix.join(dir, "fonts")),
487
+ NodePath2.posix.join(home, ".fonts"),
488
+ NodePath2.posix.join(
489
+ env2["XDG_DATA_HOME"] ?? NodePath2.posix.join(home, ".local/share"),
624
490
  "fonts"
625
491
  )
626
492
  ])
@@ -629,265 +495,26 @@ var systemFontDirectories = (platform5 = NodeProcess.platform, home = NodeOS.hom
629
495
  var mobileAssetFontDirectories = (assets) => {
630
496
  let entries = [];
631
497
  try {
632
- entries = NodeFS2.readdirSync(assets);
498
+ entries = NodeFS.readdirSync(assets);
633
499
  } catch {
634
500
  return [];
635
501
  }
636
- return entries.filter((name) => /^com_apple_MobileAsset_Font\d*$/.test(name)).sort().map((name) => NodePath3.posix.join(assets, name));
502
+ return entries.filter((name) => /^com_apple_MobileAsset_Font\d*$/.test(name)).sort().map((name) => NodePath2.posix.join(assets, name));
637
503
  };
638
504
 
639
- // src/install.ts
640
- var InstallError = class extends Error {
641
- /** 插件 locales 里 `failure.<reason>` 的那一段。 */
642
- reason;
643
- constructor(reason, message) {
644
- super(message);
645
- this.reason = reason;
646
- }
647
- };
648
- var ENGINE_VERSION = OFFICE_LOCK.engine.version;
649
- var PYTHON_VERSION = `${OFFICE_LOCK.python.version}+${OFFICE_LOCK.python.release}`;
650
- var FONTS_VERSION = "Sans2.004+Serif2.003";
651
- var npmFileName = (name, version) => `${name.replace(/^@/, "").replace("/", "__")}-${version}.tgz`;
652
- var engineItems = (target, registry) => [...OFFICE_LOCK.engine.shared, ...OFFICE_LOCK.engine.targets[target]].map((pkg) => ({
653
- name: pkg.name,
654
- item: {
655
- url: npmTarballUrl(registry, pkg.name, pkg.version),
656
- fileName: npmFileName(pkg.name, pkg.version),
657
- integrity: parseSri(pkg.integrity)
658
- }
659
- }));
660
- var basenameOfUrl = (url) => decodeURIComponent(url.slice(url.lastIndexOf("/") + 1));
661
- var pythonItems = (target) => {
662
- const artifact = pythonTargetOf(target);
663
- if (artifact === null) return null;
664
- const fileName = `cpython-${OFFICE_LOCK.python.version}+${OFFICE_LOCK.python.release}-${artifact.pythonTarget}-install_only_stripped.tar.gz`;
665
- const archive = {
666
- url: `https://github.com/astral-sh/python-build-standalone/releases/download/${OFFICE_LOCK.python.release}/${encodeURIComponent(fileName)}`,
667
- fileName,
668
- integrity: { algorithm: "sha256", hex: artifact.sha256 }
669
- };
670
- const wheels = [...artifact.wheels, ...OFFICE_LOCK.python.wheels].map(
671
- (wheel) => ({
672
- url: wheel.url,
673
- fileName: basenameOfUrl(wheel.url),
674
- integrity: { algorithm: "sha256", hex: wheel.sha256 }
675
- })
676
- );
677
- return { archive, wheels };
678
- };
679
- var fontItems = () => OFFICE_LOCK.fonts.files.map((file) => ({
680
- url: `https://raw.githubusercontent.com/notofonts/noto-cjk/${file.path}`,
681
- fileName: basenameOfUrl(file.path),
682
- integrity: { algorithm: "sha256", hex: file.sha256 }
683
- }));
684
- var megabytes = (bytes) => (bytes / (1024 * 1024)).toFixed(1);
685
- var downloadAll = async (ctx, component, items) => {
686
- const dir = NodePath4.join(ctx.paths.downloadsDir, component);
687
- const files = [];
688
- for (const [index, item] of items.entries()) {
689
- const report = (progress) => ctx.emit({
690
- kind: "status",
691
- statusKey: progress.totalBytes === null ? "progress.downloadingUnknown" : "progress.downloading",
692
- params: {
693
- file: progress.fileName,
694
- index: String(index + 1),
695
- count: String(items.length),
696
- done: megabytes(progress.transferredBytes),
697
- total: progress.totalBytes === null ? "" : megabytes(progress.totalBytes)
698
- }
699
- });
700
- files.push(
701
- await downloadVerified({
702
- http: ctx.http,
703
- item,
704
- dir,
705
- sources: ctx.sources,
706
- signal: ctx.signal,
707
- onProgress: report
708
- })
709
- );
710
- }
711
- return files;
712
- };
713
- var relativeToData = (ctx, target) => NodePath4.relative(ctx.paths.dataDir, target).split(NodePath4.sep).join("/");
714
- var untar = async (ctx, archive, destination) => {
715
- await NodeFSP3.mkdir(destination, { recursive: true });
716
- const result = await ctx.process.run({
717
- program: "tar",
718
- args: [
719
- "-xzf",
720
- relativeToData(ctx, archive),
721
- "-C",
722
- relativeToData(ctx, destination),
723
- "--strip-components=1"
724
- ],
725
- cwd: ctx.paths.dataDir,
726
- timeoutMs: 6e5
727
- });
728
- if (result.exitCode !== 0) {
729
- throw new InstallError(
730
- "unpack-failed",
731
- `tar could not unpack ${NodePath4.basename(archive)}: ${(result.stderr || result.stdout).trim().slice(0, 400)}`
732
- );
733
- }
734
- };
735
- var steps = (ctx, states) => ctx.emit({
736
- kind: "steps",
737
- steps: ["download", "unpack", "check"].map((id) => ({
738
- id,
739
- labelKey: `progress.step.${id}`,
740
- state: states[id]
741
- }))
742
- });
743
- var stagingOf = (ctx, component) => `${ctx.paths.dirOf(component)}.staging`;
744
- var finish = async (ctx, component, version) => {
745
- await swapIntoPlace(stagingOf(ctx, component), ctx.paths.dirOf(component));
746
- await writeInstallRecord(ctx.paths, component, { version, target: ctx.target });
747
- await removePath(NodePath4.join(ctx.paths.downloadsDir, component));
748
- };
749
- var installEngine = async (ctx) => {
750
- const packages = engineItems(ctx.target, ctx.sources.npmRegistry);
751
- steps(ctx, { download: "running", unpack: "pending", check: "pending" });
752
- const files = await downloadAll(
753
- ctx,
754
- "engine",
755
- packages.map((pkg) => pkg.item)
756
- );
757
- steps(ctx, { download: "done", unpack: "running", check: "pending" });
758
- const staging = stagingOf(ctx, "engine");
759
- await removePath(staging);
760
- for (const [index, pkg] of packages.entries()) {
761
- await untar(ctx, files[index], NodePath4.join(staging, "node_modules", ...pkg.name.split("/")));
762
- }
763
- steps(ctx, { download: "done", unpack: "done", check: "running" });
764
- const cli = NodePath4.join(
765
- staging,
766
- "node_modules",
767
- "@deepseek-ai",
768
- "libreoffice-kit",
769
- "lib",
770
- "cli.js"
771
- );
772
- const backend = await engineBackend(ctx.process, cli, ctx.paths.dataDir);
773
- await finish(ctx, "engine", ENGINE_VERSION);
774
- steps(ctx, { download: "done", unpack: "done", check: "done" });
775
- return backend;
776
- };
777
- var engineBackend = async (processApi, cli, cwd) => {
778
- const result = await processApi.run({
779
- program: "host:node",
780
- args: [cli, "capabilities", "--json"],
781
- cwd,
782
- timeoutMs: 6e4
783
- });
784
- let backend;
785
- try {
786
- backend = JSON.parse(result.stdout).runtime?.backend;
787
- } catch {
788
- backend = void 0;
789
- }
790
- if (result.exitCode !== 0 || typeof backend !== "string") {
791
- throw new InstallError(
792
- NodeProcess2.platform === "win32" ? "engine-unavailable-windows" : "engine-unavailable",
793
- `The Office engine did not start: ${(result.stderr || result.stdout).trim().slice(0, 400)}`
794
- );
795
- }
796
- return backend;
797
- };
798
- var pythonProgram = (platform5 = NodeProcess2.platform) => platform5 === "win32" ? PYTHON_PROGRAM.win32 : PYTHON_PROGRAM.other;
799
- var runPython = async (ctx, args, what) => {
800
- const result = await ctx.process.run({
801
- program: pythonProgram(),
802
- args,
803
- cwd: ctx.paths.dataDir,
804
- timeoutMs: 6e5
805
- });
806
- if (result.exitCode !== 0) {
807
- throw new InstallError(
808
- "python-failed",
809
- `${what} failed: ${(result.stderr || result.stdout).trim().slice(-600)}`
810
- );
811
- }
812
- };
813
- var installPython = async (ctx) => {
814
- const items = pythonItems(ctx.target);
815
- if (items === null) {
816
- throw new InstallError("python-unsupported", `No Python build is pinned for ${ctx.target}.`);
817
- }
818
- steps(ctx, { download: "running", unpack: "pending", check: "pending" });
819
- const [archive, ...wheels] = await downloadAll(ctx, "python", [items.archive, ...items.wheels]);
820
- steps(ctx, { download: "done", unpack: "running", check: "pending" });
821
- const staging = stagingOf(ctx, "python");
822
- await removePath(staging);
823
- await untar(ctx, archive, staging);
824
- await removePath(ctx.paths.recordOf("python"));
825
- await swapIntoPlace(staging, ctx.paths.dirOf("python"));
826
- try {
827
- await runPython(
828
- ctx,
829
- [
830
- "-I",
831
- "-m",
832
- "pip",
833
- "install",
834
- "--no-index",
835
- "--no-deps",
836
- "--no-compile",
837
- "--disable-pip-version-check",
838
- ...wheels
839
- ],
840
- "Installing the document libraries"
841
- );
842
- steps(ctx, { download: "done", unpack: "done", check: "running" });
843
- await runPython(ctx, ["-I", "-m", "pip", "check"], "Checking the document libraries");
844
- await runPython(
845
- ctx,
846
- ["-I", "-c", "import docx, pptx, openpyxl, pandas, PIL, lxml"],
847
- "Importing the document libraries"
505
+ // src/engine.ts
506
+ var componentReady = bundledComponentReady;
507
+ var fontDirectoryArgs = async (paths, target) => {
508
+ if (!await componentReady(paths, "engine", target) || !await componentReady(paths, "fonts", target)) {
509
+ throw new Error(
510
+ "The bundled Office engine or fonts are missing, incomplete, or the wrong version. Restore the complete Ycodium installation."
848
511
  );
849
- } catch (error) {
850
- await removePath(ctx.paths.dirOf("python"));
851
- throw error;
852
- }
853
- await writeInstallRecord(ctx.paths, "python", { version: PYTHON_VERSION, target: ctx.target });
854
- await removePath(NodePath4.join(ctx.paths.downloadsDir, "python"));
855
- steps(ctx, { download: "done", unpack: "done", check: "done" });
856
- };
857
- var installFonts = async (ctx) => {
858
- steps(ctx, { download: "running", unpack: "pending", check: "pending" });
859
- const files = await downloadAll(ctx, "fonts", fontItems());
860
- steps(ctx, { download: "done", unpack: "running", check: "pending" });
861
- const staging = stagingOf(ctx, "fonts");
862
- await removePath(staging);
863
- await NodeFSP3.mkdir(staging, { recursive: true });
864
- for (const file of files) {
865
- await NodeFSP3.copyFile(file, NodePath4.join(staging, NodePath4.basename(file)));
866
512
  }
867
- await finish(ctx, "fonts", FONTS_VERSION);
868
- steps(ctx, { download: "done", unpack: "done", check: "done" });
513
+ return [...systemFontDirectories(), paths.dirOf("fonts")].flatMap((dir) => [
514
+ "--font-directory",
515
+ dir
516
+ ]);
869
517
  };
870
- var removeRuntime = async (paths) => {
871
- await removePath(paths.runtimeDir);
872
- await removePath(paths.downloadsDir);
873
- };
874
-
875
- // src/engine.ts
876
- var EXPECTED_VERSION = {
877
- engine: ENGINE_VERSION,
878
- python: PYTHON_VERSION,
879
- fonts: FONTS_VERSION
880
- };
881
- var componentReady = async (paths, component, target) => {
882
- if (target === null) return false;
883
- const record = await readInstallRecord(paths, component);
884
- if (record?.version !== EXPECTED_VERSION[component] || record.target !== target) return false;
885
- return NodeFSP4.stat(paths.dirOf(component)).then(
886
- (info) => info.isDirectory(),
887
- () => false
888
- );
889
- };
890
- var fontDirectoryArgs = async (paths, target) => await componentReady(paths, "fonts", target) ? [...systemFontDirectories(), paths.dirOf("fonts")].flatMap((dir) => ["--font-directory", dir]) : [];
891
518
  var EngineError = class extends Error {
892
519
  };
893
520
  var parseEngineOutput = (stdout, stderr, exitCode) => {
@@ -949,19 +576,39 @@ var runEngineCancellable = async (input) => {
949
576
  input.signal.removeEventListener("abort", onAbort);
950
577
  }
951
578
  };
952
- var machineHasCjkFont = async (processApi, paths, target, platform5 = NodeProcess3.platform) => {
953
- if (await componentReady(paths, "fonts", target)) return true;
954
- if (platform5 !== "linux") return true;
579
+ var machineHasCjkFont = async (_processApi, paths, target) => componentReady(paths, "fonts", target);
580
+
581
+ // src/runtime.ts
582
+ var compatiblePython = (runtime) => runtime !== null && runtime.version === PYTHON_VERSION;
583
+ var pythonArgs = (paths, script, args = []) => [
584
+ "-I",
585
+ "-S",
586
+ "-B",
587
+ paths.pythonRunner,
588
+ "--libraries",
589
+ paths.pythonLibraries,
590
+ "--",
591
+ script,
592
+ ...args
593
+ ];
594
+ var engineBackend = async (processApi, cli, cwd) => {
595
+ const result = await processApi.run({
596
+ program: "host:node",
597
+ args: [cli, "capabilities", "--json"],
598
+ cwd,
599
+ timeoutMs: 6e4
600
+ });
601
+ let backend;
955
602
  try {
956
- const result = await processApi.run({
957
- program: "fc-list",
958
- args: [":lang=zh", "family"],
959
- timeoutMs: 15e3
960
- });
961
- return result.exitCode === 0 && result.stdout.trim().length > 0;
603
+ backend = JSON.parse(result.stdout).runtime?.backend;
962
604
  } catch {
963
- return false;
605
+ backend = void 0;
964
606
  }
607
+ if (result.exitCode !== 0 || typeof backend !== "string")
608
+ throw new Error(
609
+ `The bundled Office engine did not start: ${(result.stderr || result.stdout).trim().slice(0, 400)}`
610
+ );
611
+ return backend;
965
612
  };
966
613
 
967
614
  // src/health.ts
@@ -975,7 +622,11 @@ var runHealthCheck = async (input) => {
975
622
  } else {
976
623
  try {
977
624
  const backend = await engineBackend(processApi, paths.engineCli, paths.dataDir);
978
- entries.push({ key: "health.engine.ok", params: { backend }, ok: true });
625
+ entries.push({
626
+ key: "health.engine.ok",
627
+ params: { backend, version: ENGINE_VERSION },
628
+ ok: true
629
+ });
979
630
  } catch (error) {
980
631
  entries.push({
981
632
  key: "health.engine.broken",
@@ -986,47 +637,59 @@ var runHealthCheck = async (input) => {
986
637
  }
987
638
  if (pythonTargetOf(target) === null) {
988
639
  entries.push({ key: "health.python.unsupported", ok: false });
989
- } else if (!await componentReady(paths, "python", target)) {
990
- entries.push({ key: "health.python.missing", ok: false });
991
640
  } else {
992
- const result = await processApi.run({
993
- program: pythonProgram(),
994
- args: ["-I", "-c", "import docx, pptx, openpyxl, pandas, PIL, lxml"],
995
- cwd: paths.dataDir,
996
- timeoutMs: 6e4
997
- });
998
- entries.push(
999
- result.exitCode === 0 ? { key: "health.python.ok", ok: true } : {
641
+ try {
642
+ const runtime = await input.resolvePython();
643
+ if (!compatiblePython(runtime) || !await componentReady(paths, "python", target)) {
644
+ entries.push({ key: "health.python.missing", ok: false });
645
+ } else {
646
+ const result = await processApi.run({
647
+ program: "host:python",
648
+ args: pythonArgs(paths, input.runtimeCheckScript),
649
+ env: runtime.env,
650
+ cwd: paths.dataDir,
651
+ timeoutMs: 6e4
652
+ });
653
+ entries.push(
654
+ result.exitCode === 0 ? { key: "health.python.ok", params: { version: runtime.version }, ok: true } : {
655
+ key: "health.python.broken",
656
+ params: { detail: (result.stderr || result.stdout).trim().slice(-300) },
657
+ ok: false
658
+ }
659
+ );
660
+ }
661
+ } catch (error) {
662
+ entries.push({
1000
663
  key: "health.python.broken",
1001
- params: { detail: (result.stderr || result.stdout).trim().slice(-300) },
664
+ params: { detail: firstLine(error) },
1002
665
  ok: false
1003
- }
1004
- );
666
+ });
667
+ }
1005
668
  }
1006
669
  entries.push(
1007
- await machineHasCjkFont(processApi, paths, target) ? { key: "health.fonts.ok", ok: true } : { key: "health.fonts.missing", ok: false }
670
+ await componentReady(paths, "fonts", target) ? { key: "health.fonts.ok", params: { version: FONTS_VERSION }, ok: true } : { key: "health.fonts.missing", ok: false }
1008
671
  );
1009
672
  return entries;
1010
673
  };
1011
674
 
1012
675
  // src/preview.ts
1013
- import * as NodeFSP5 from "node:fs/promises";
1014
- import * as NodePath5 from "node:path";
676
+ import * as NodeFSP2 from "node:fs/promises";
677
+ import * as NodePath3 from "node:path";
1015
678
  var PREVIEW_COMMAND = "convert-preview";
1016
679
  var isWithin = (root, candidate) => {
1017
- const relative4 = NodePath5.relative(root, candidate);
1018
- return relative4 !== "" && !relative4.startsWith("..") && !NodePath5.isAbsolute(relative4);
680
+ const relative3 = NodePath3.relative(root, candidate);
681
+ return relative3 !== "" && !relative3.startsWith("..") && !NodePath3.isAbsolute(relative3);
1019
682
  };
1020
683
  var isAllowedPreviewOutput = async (cacheDir, outputPath) => {
1021
- if (!NodePath5.isAbsolute(outputPath) || NodePath5.extname(outputPath).toLowerCase() !== ".pdf") {
684
+ if (!NodePath3.isAbsolute(outputPath) || NodePath3.extname(outputPath).toLowerCase() !== ".pdf") {
1022
685
  return false;
1023
686
  }
1024
687
  try {
1025
688
  const [root, parent] = await Promise.all([
1026
- NodeFSP5.realpath(cacheDir),
1027
- NodeFSP5.realpath(NodePath5.dirname(outputPath))
689
+ NodeFSP2.realpath(cacheDir),
690
+ NodeFSP2.realpath(NodePath3.dirname(outputPath))
1028
691
  ]);
1029
- return isWithin(root, NodePath5.join(parent, NodePath5.basename(outputPath)));
692
+ return isWithin(root, NodePath3.join(parent, NodePath3.basename(outputPath)));
1030
693
  } catch {
1031
694
  return false;
1032
695
  }
@@ -1068,8 +731,7 @@ var registerPreviewCommand = (ctx, deps) => ctx.stream({
1068
731
  });
1069
732
 
1070
733
  // src/tools.ts
1071
- import * as NodePath6 from "node:path";
1072
- import * as NodeProcess4 from "node:process";
734
+ import * as NodePath4 from "node:path";
1073
735
 
1074
736
  // src/ooxml.ts
1075
737
  import * as NodeZlib from "node:zlib";
@@ -1136,7 +798,7 @@ var ooxmlHasCjk = (input) => {
1136
798
  // src/tools.ts
1137
799
  var MAX_IMAGES = 4;
1138
800
  var MAX_IMAGE_BYTES = 3 * 1024 * 1024;
1139
- var SETUP_HINT = "Ask the user to set it up in Settings \u2192 Plugins \u2192 Office. Do not install LibreOffice, Python packages, or another renderer yourself.";
801
+ var SETUP_HINT = "Open Settings \u2192 Plugins \u2192 Office \u2192 Check for diagnostics. Restore the complete Ycodium installation from an offline installer if bundled files are missing or damaged. Do not install LibreOffice, Python packages, or another renderer yourself.";
1140
802
  var text = (value, isError = false) => isError ? { text: value, isError: true } : { text: value };
1141
803
  var stringArg = (input, key) => {
1142
804
  const value = input[key];
@@ -1144,10 +806,10 @@ var stringArg = (input, key) => {
1144
806
  };
1145
807
  var workingDirOf = (invocation) => invocation.workspace?.roots[0] ?? invocation.projectRoot;
1146
808
  var relativeTo = (cwd, target) => {
1147
- const relative4 = NodePath6.relative(cwd, target);
1148
- return relative4.startsWith("..") || NodePath6.isAbsolute(relative4) ? target : relative4;
809
+ const relative3 = NodePath4.relative(cwd, target);
810
+ return relative3.startsWith("..") || NodePath4.isAbsolute(relative3) ? target : relative3;
1149
811
  };
1150
- var requireEngine = async (deps) => await componentReady(deps.paths, "engine", deps.target) ? null : text(`The Office engine is not installed on this machine. ${SETUP_HINT}`, true);
812
+ var requireEngine = async (deps) => await componentReady(deps.paths, "engine", deps.target) && await componentReady(deps.paths, "fonts", deps.target) ? null : text(`The bundled Office engine or fonts are missing or damaged. ${SETUP_HINT}`, true);
1151
813
  var noWorkspace = text(
1152
814
  "This tool needs a conversation with a project folder; call it from a thread.",
1153
815
  true
@@ -1157,7 +819,7 @@ var engineFailure = (error) => text(
1157
819
  Do not retry with another renderer; report the failure.`,
1158
820
  true
1159
821
  );
1160
- var CJK_WARNING = "Warning: this machine has no Chinese/Japanese/Korean font, so CJK text in these images is drawn as boxes. Do not judge the layout from them. Fixes: install Chinese fonts in Settings \u2192 Plugins \u2192 Office, or install a system package such as fonts-noto-cjk.";
822
+ var CJK_WARNING = "Warning: bundled Chinese fonts are missing or damaged, so CJK text may be drawn as boxes. Do not judge the layout from these images. Restore the complete Ycodium installation from an offline installer.";
1161
823
  var missingFontsLine = (fonts) => fonts === void 0 || fonts.length === 0 ? "Missing fonts: none." : `Missing fonts (substituted): ${fonts.join(", ")}.`;
1162
824
  var readImages = async (invocation, images) => {
1163
825
  const workspace = invocation.workspace;
@@ -1183,7 +845,7 @@ var documentHasCjk = async (invocation, source) => {
1183
845
  };
1184
846
  var render = (deps) => ({
1185
847
  name: "render",
1186
- description: "Render pages, slides, or a worksheet range of an Office document (docx, pptx, xlsx, doc, ppt, xls, odt, odp, ods) or a PDF to PNG images with the bundled LibreOffice engine, for visual checks. Returns the page count, image paths, and missing fonts; up to four images are attached for models that read images. `outputDir` must be a new directory.",
848
+ description: "Render Word documents, PowerPoint slides, Excel worksheet ranges or OpenDocument files (docx, pptx, xlsx, doc, ppt, xls, odt, odp, ods), or PDF pages, to PNG images with the bundled LibreOffice engine for visual checks of layout, pagination, charts and fonts. Returns the page count, image paths, and missing fonts; up to four images are attached for models that read images. `outputDir` must be a new directory.",
1187
849
  inputSchema: {
1188
850
  type: "object",
1189
851
  properties: {
@@ -1239,7 +901,7 @@ var render = (deps) => ({
1239
901
  return engineFailure(error);
1240
902
  }
1241
903
  const images = result.images ?? [];
1242
- const absoluteSource = NodePath6.resolve(cwd, source);
904
+ const absoluteSource = NodePath4.resolve(cwd, source);
1243
905
  const warn = !await machineHasCjkFont(deps.process, deps.paths, deps.target) && await documentHasCjk(invocation, absoluteSource) === true;
1244
906
  const lines = [
1245
907
  ...warn ? [CJK_WARNING] : [],
@@ -1318,8 +980,12 @@ var check = (deps) => ({
1318
980
  required: ["path"]
1319
981
  },
1320
982
  handle: async (input, invocation) => {
1321
- if (!await componentReady(deps.paths, "python", deps.target)) {
1322
- return text(`Python for Office is not installed on this machine. ${SETUP_HINT}`, true);
983
+ const runtime = await deps.resolvePython();
984
+ if (!compatiblePython(runtime) || !await componentReady(deps.paths, "python", deps.target)) {
985
+ return text(
986
+ `The shared Python runtime or bundled Office document libraries are missing or damaged. ${SETUP_HINT}`,
987
+ true
988
+ );
1323
989
  }
1324
990
  const cwd = workingDirOf(invocation);
1325
991
  if (cwd === null) return noWorkspace;
@@ -1328,16 +994,14 @@ var check = (deps) => ({
1328
994
  const contains = Array.isArray(input["contains"]) ? input["contains"].filter((entry) => typeof entry === "string") : [];
1329
995
  const count = typeof input["count"] === "number" ? ["--count", String(input["count"])] : [];
1330
996
  const result = await deps.process.run({
1331
- program: pythonProgram(),
1332
- args: [
1333
- "-I",
1334
- deps.checkerScript,
997
+ program: "host:python",
998
+ args: pythonArgs(deps.paths, deps.checkerScript, [
1335
999
  source,
1336
1000
  ...contains.flatMap((value) => ["--contains", value]),
1337
1001
  ...count
1338
- ],
1002
+ ]),
1339
1003
  cwd,
1340
- env: { PYTHONIOENCODING: "utf-8" },
1004
+ env: runtime.env,
1341
1005
  timeoutMs: 12e4
1342
1006
  });
1343
1007
  const report = result.stdout.trim();
@@ -1352,24 +1016,38 @@ ${result.stderr.trim()}`.trim());
1352
1016
  });
1353
1017
  var python = (deps) => ({
1354
1018
  name: "python",
1355
- description: "Return the bundled Python interpreter for writing Office files, with python-docx, python-pptx, openpyxl, pandas, Pillow, lxml, and XlsxWriter preinstalled. Run your scripts with this executable instead of installing packages or looking for a system Python.",
1019
+ description: "Return the shared Ycodium Python executable and required arguments for creating or editing Word documents (docx), PowerPoint presentations (pptx) and Excel workbooks (xlsx), with python-docx, python-pptx, openpyxl, pandas, Pillow, lxml, and XlsxWriter preinstalled. Run your scripts with the returned executable AND args instead of installing packages or looking for a system Python.",
1356
1020
  inputSchema: { type: "object", properties: {} },
1357
1021
  handle: async () => {
1358
- if (!await componentReady(deps.paths, "python", deps.target)) {
1359
- return text(`Python for Office is not installed on this machine. ${SETUP_HINT}`, true);
1022
+ const runtime = await deps.resolvePython();
1023
+ if (!compatiblePython(runtime) || !await componentReady(deps.paths, "python", deps.target)) {
1024
+ return text(
1025
+ `The shared Python runtime or bundled Office document libraries are missing or damaged. ${SETUP_HINT}`,
1026
+ true
1027
+ );
1360
1028
  }
1361
1029
  return text(
1362
1030
  [
1363
1031
  JSON.stringify(
1364
1032
  {
1365
- python: deps.paths.pythonExecutable(NodeProcess4.platform),
1366
- version: OFFICE_LOCK.python.version,
1033
+ python: runtime.executablePath,
1034
+ args: [
1035
+ "-I",
1036
+ "-S",
1037
+ "-B",
1038
+ deps.paths.pythonRunner,
1039
+ "--libraries",
1040
+ deps.paths.pythonLibraries,
1041
+ "--"
1042
+ ],
1043
+ env: runtime.env,
1044
+ version: runtime.version,
1367
1045
  packages: OFFICE_LOCK.python.packages
1368
1046
  },
1369
1047
  null,
1370
1048
  2
1371
1049
  ),
1372
- "Quote the path when you run it; PowerShell also needs the `&` call operator. Keep scripts and outputs in the project folder."
1050
+ "Invoke `python` with every returned `args` element followed by your script path and script arguments; this loads the isolated Office libraries. Apply the returned env entries if any. Quote each argument; PowerShell also needs the `&` call operator. Keep scripts and outputs in the project folder."
1373
1051
  ].join("\n")
1374
1052
  );
1375
1053
  }
@@ -1383,110 +1061,39 @@ var officeTools = (deps) => [
1383
1061
  ];
1384
1062
 
1385
1063
  // src/index.ts
1386
- var sourcesOf = (config) => {
1387
- const pick = (value, fallback) => typeof value === "string" && value.trim() !== "" ? value.trim() : fallback;
1388
- return {
1389
- npmRegistry: pick(config.npmRegistry, DEFAULT_SOURCES.npmRegistry),
1390
- githubMirror: pick(config.githubMirror, DEFAULT_SOURCES.githubMirror),
1391
- pypiMirror: pick(config.pypiMirror, DEFAULT_SOURCES.pypiMirror),
1392
- localDir: pick(config.localDir, DEFAULT_SOURCES.localDir)
1393
- };
1394
- };
1395
- var failureOf = (error) => {
1396
- const detail = error instanceof Error ? error.message : String(error);
1397
- if (error instanceof InstallError)
1398
- return { kind: "failed", reasonKey: `failure.${error.reason}`, detail };
1399
- if (error instanceof DownloadError) {
1400
- return { kind: "failed", reasonKey: `failure.download-${error.reason}`, detail };
1401
- }
1402
- return { kind: "failed", reasonKey: "failure.unknown", detail };
1403
- };
1404
- var EMPTY_PAYLOAD = { type: "object", properties: {} };
1405
1064
  var index_default = definePlugin({
1406
1065
  id: "office",
1407
- apiVersion: 43,
1066
+ apiVersion: 46,
1408
1067
  activate: (ctx) => {
1409
- const paths = officePaths(ctx.paths.dataDir);
1410
1068
  const target = detectOfficeTarget();
1069
+ const paths = officePaths(ctx.paths.pluginDir, ctx.paths.dataDir, target, ctx.paths.runtimeDir);
1070
+ const resolvePython = async () => await ctx.runtimes?.resolve("python") ?? null;
1411
1071
  ctx.toolkit({
1412
1072
  name: "office",
1413
1073
  tools: officeTools({
1414
1074
  process: ctx.process,
1415
1075
  paths,
1416
1076
  target,
1417
- checkerScript: NodePath7.join(ctx.paths.pluginDir, "assets", "check_office.py")
1077
+ resolvePython,
1078
+ checkerScript: NodePath5.join(ctx.paths.pluginDir, "assets", "check_office.py")
1418
1079
  })
1419
1080
  });
1420
1081
  registerPreviewCommand(ctx, { paths, target });
1421
- let busy = false;
1422
- const action = (name, description, run) => ctx.stream({
1423
- name,
1424
- description,
1082
+ ctx.stream({
1083
+ name: "health",
1084
+ description: "Check bundled Office components and shared Python on this machine without downloading anything.",
1425
1085
  scope: "operate",
1426
- payloadSchema: EMPTY_PAYLOAD,
1427
- handle: async (_payload, emit, signal) => {
1428
- if (busy) {
1429
- emit({ kind: "failed", reasonKey: "failure.busy" });
1430
- return;
1431
- }
1432
- busy = true;
1433
- try {
1434
- emit(await run(emit, signal));
1435
- } catch (error) {
1436
- if (!signal.aborted) emit(failureOf(error));
1437
- } finally {
1438
- busy = false;
1439
- }
1440
- }
1441
- });
1442
- const installContext = (emit, signal) => {
1443
- if (target === null) {
1444
- throw new InstallError(
1445
- "unsupported-platform",
1446
- "This machine's platform has no Office engine build."
1447
- );
1448
- }
1449
- return {
1450
- http: ctx.http,
1451
- process: ctx.process,
1452
- paths,
1453
- target,
1454
- sources: sourcesOf(ctx.config),
1455
- signal,
1456
- emit
1457
- };
1458
- };
1459
- action("install-engine", "Download and install the Office engine.", async (emit, signal) => {
1460
- const backend = await installEngine(installContext(emit, signal));
1461
- return {
1462
- kind: "done",
1463
- fills: {},
1464
- messageKey: `progress.done.engine.${backend === "wasm" ? "wasm" : "native"}`
1465
- };
1466
- });
1467
- action(
1468
- "install-python",
1469
- "Download and install Python with the document libraries.",
1470
- async (emit, signal) => {
1471
- await installPython(installContext(emit, signal));
1472
- return { kind: "done", fills: {}, messageKey: "progress.done.python" };
1473
- }
1474
- );
1475
- action(
1476
- "install-fonts",
1477
- "Download Chinese fonts for the Office engine.",
1478
- async (emit, signal) => {
1479
- await installFonts(installContext(emit, signal));
1480
- return { kind: "done", fills: {}, messageKey: "progress.done.fonts" };
1481
- }
1482
- );
1483
- action(
1484
- "health",
1485
- "Check the Office engine, Python, and fonts on this machine.",
1486
- async (emit) => {
1086
+ payloadSchema: { type: "object", properties: {} },
1087
+ handle: async (_payload, emit) => {
1487
1088
  emit({ kind: "status", statusKey: "progress.checking" });
1488
- const entries = await runHealthCheck({ process: ctx.process, paths, target });
1489
- return {
1089
+ const entries = await runHealthCheck({
1090
+ process: ctx.process,
1091
+ paths,
1092
+ target,
1093
+ resolvePython,
1094
+ runtimeCheckScript: NodePath5.join(ctx.paths.pluginDir, "assets", "check_runtime.py")
1095
+ });
1096
+ emit({
1490
1097
  kind: "done",
1491
1098
  fills: {},
1492
1099
  messageKey: entries.every((entry) => entry.ok) ? "health.summary.ok" : "health.summary.issues",
@@ -1494,77 +1101,19 @@ var index_default = definePlugin({
1494
1101
  key: entry.key,
1495
1102
  ...entry.params === void 0 ? {} : { params: entry.params }
1496
1103
  }))
1497
- };
1498
- }
1499
- );
1500
- action(
1501
- "remove-runtime",
1502
- "Delete the Office engine, Python, and fonts from this machine.",
1503
- async (emit) => {
1504
- emit({ kind: "status", statusKey: "progress.removing" });
1505
- await removeRuntime(paths);
1506
- return { kind: "done", fills: {}, messageKey: "progress.done.removed" };
1104
+ });
1507
1105
  }
1508
- );
1106
+ });
1509
1107
  ctx.settingsCard({
1510
1108
  titleKey: "settings.card.title",
1511
1109
  descriptionKey: "settings.card.description",
1512
- fields: [
1513
- {
1514
- key: "npmRegistry",
1515
- kind: "text",
1516
- labelKey: "settings.npmRegistry.label",
1517
- descriptionKey: "settings.npmRegistry.description"
1518
- },
1519
- {
1520
- key: "githubMirror",
1521
- kind: "text",
1522
- labelKey: "settings.githubMirror.label",
1523
- descriptionKey: "settings.githubMirror.description"
1524
- },
1525
- {
1526
- key: "pypiMirror",
1527
- kind: "text",
1528
- labelKey: "settings.pypiMirror.label",
1529
- descriptionKey: "settings.pypiMirror.description"
1530
- },
1531
- {
1532
- key: "localDir",
1533
- kind: "text",
1534
- labelKey: "settings.localDir.label",
1535
- descriptionKey: "settings.localDir.description"
1536
- }
1537
- ],
1110
+ fields: [],
1538
1111
  actions: [
1539
- {
1540
- id: "install-engine",
1541
- labelKey: "actions.installEngine.label",
1542
- descriptionKey: "actions.installEngine.description",
1543
- command: "install-engine"
1544
- },
1545
- {
1546
- id: "install-python",
1547
- labelKey: "actions.installPython.label",
1548
- descriptionKey: "actions.installPython.description",
1549
- command: "install-python"
1550
- },
1551
- {
1552
- id: "install-fonts",
1553
- labelKey: "actions.installFonts.label",
1554
- descriptionKey: "actions.installFonts.description",
1555
- command: "install-fonts"
1556
- },
1557
1112
  {
1558
1113
  id: "health",
1559
1114
  labelKey: "actions.health.label",
1560
1115
  descriptionKey: "actions.health.description",
1561
1116
  command: "health"
1562
- },
1563
- {
1564
- id: "remove-runtime",
1565
- labelKey: "actions.remove.label",
1566
- descriptionKey: "actions.remove.description",
1567
- command: "remove-runtime"
1568
1117
  }
1569
1118
  ]
1570
1119
  });