@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/README.md +84 -75
- package/assets/check_runtime.py +8 -0
- package/assets/run_office.py +34 -0
- package/dist/index.js +218 -669
- package/locales/en.json +16 -81
- package/locales/zh-CN.json +19 -84
- package/package.json +4 -6
- package/skills/office-docx/SKILL.md +3 -3
- package/skills/office-pptx/SKILL.md +3 -3
- package/skills/office-xlsx/SKILL.md +2 -2
- package/ui/ui.js +1 -1
package/dist/index.js
CHANGED
|
@@ -1,155 +1,10 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import * as
|
|
2
|
+
import * as NodePath5 from "node:path";
|
|
3
3
|
import { definePlugin } from "@ycodium-ai/plugin-api";
|
|
4
4
|
|
|
5
|
-
// src/
|
|
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
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
var
|
|
525
|
-
|
|
526
|
-
|
|
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
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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
|
-
|
|
420
|
+
dirOf: (component) => component === "python" ? pythonLibraries : component === "engine" ? NodePath.join(runtimeDir, "node_modules") : NodePath.join(runtimeDir, "fonts")
|
|
546
421
|
};
|
|
547
422
|
};
|
|
548
|
-
var
|
|
423
|
+
var bundledComponentReady = async (paths, component, target) => {
|
|
424
|
+
if (target === null) return false;
|
|
549
425
|
try {
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
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
|
|
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
|
|
454
|
+
import * as NodeFS from "node:fs";
|
|
589
455
|
import * as NodeOS from "node:os";
|
|
590
|
-
import * as
|
|
456
|
+
import * as NodePath2 from "node:path";
|
|
591
457
|
import * as NodeProcess from "node:process";
|
|
592
|
-
var detectOfficeTarget = (
|
|
593
|
-
const key = `${
|
|
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 = (
|
|
598
|
-
if (
|
|
463
|
+
var systemFontDirectories = (platform2 = NodeProcess.platform, home = NodeOS.homedir(), env2 = NodeProcess.env) => {
|
|
464
|
+
if (platform2 === "win32") {
|
|
599
465
|
return [
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
env2["LOCALAPPDATA"] ??
|
|
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 (
|
|
475
|
+
if (platform2 === "darwin") {
|
|
610
476
|
return [
|
|
611
477
|
"/System/Library/Fonts",
|
|
612
478
|
"/Library/Fonts",
|
|
613
|
-
|
|
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) =>
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
env2["XDG_DATA_HOME"] ??
|
|
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 =
|
|
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) =>
|
|
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/
|
|
640
|
-
var
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
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
|
-
|
|
868
|
-
|
|
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 (
|
|
953
|
-
|
|
954
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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({
|
|
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
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
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: (
|
|
664
|
+
params: { detail: firstLine(error) },
|
|
1002
665
|
ok: false
|
|
1003
|
-
}
|
|
1004
|
-
|
|
666
|
+
});
|
|
667
|
+
}
|
|
1005
668
|
}
|
|
1006
669
|
entries.push(
|
|
1007
|
-
await
|
|
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
|
|
1014
|
-
import * as
|
|
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
|
|
1018
|
-
return
|
|
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 (!
|
|
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
|
-
|
|
1027
|
-
|
|
689
|
+
NodeFSP2.realpath(cacheDir),
|
|
690
|
+
NodeFSP2.realpath(NodePath3.dirname(outputPath))
|
|
1028
691
|
]);
|
|
1029
|
-
return isWithin(root,
|
|
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
|
|
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 = "
|
|
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
|
|
1148
|
-
return
|
|
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
|
|
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:
|
|
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
|
|
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 =
|
|
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
|
-
|
|
1322
|
-
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
|
|
1359
|
-
|
|
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:
|
|
1366
|
-
|
|
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
|
-
"
|
|
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:
|
|
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
|
-
|
|
1077
|
+
resolvePython,
|
|
1078
|
+
checkerScript: NodePath5.join(ctx.paths.pluginDir, "assets", "check_office.py")
|
|
1418
1079
|
})
|
|
1419
1080
|
});
|
|
1420
1081
|
registerPreviewCommand(ctx, { paths, target });
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
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:
|
|
1427
|
-
handle: async (_payload, emit
|
|
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({
|
|
1489
|
-
|
|
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
|
});
|