@linxiraos/pi-utils 1.0.9 → 1.1.0
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/CHANGELOG.md +2 -477
- package/LICENSE +22 -0
- package/THIRD-PARTY-NOTICES.txt +22909 -0
- package/dist/types/ar/arj.d.ts +5 -0
- package/dist/types/ar/asar.d.ts +7 -0
- package/dist/types/ar/bytes.d.ts +20 -0
- package/dist/types/ar/cab.d.ts +5 -0
- package/dist/types/ar/checksums.d.ts +10 -0
- package/dist/types/ar/codecs/bzip2.d.ts +4 -0
- package/dist/types/ar/codecs/gzip.d.ts +6 -0
- package/dist/types/ar/codecs/lzma.d.ts +6 -0
- package/dist/types/ar/codecs/lzw.d.ts +4 -0
- package/dist/types/ar/codecs/lzx.d.ts +7 -0
- package/dist/types/ar/codecs/xz.d.ts +4 -0
- package/dist/types/ar/codecs/zstd.d.ts +6 -0
- package/dist/types/ar/cpio.d.ts +7 -0
- package/dist/types/ar/deb.d.ts +5 -0
- package/dist/types/ar/entries.d.ts +22 -0
- package/dist/types/ar/error.d.ts +8 -0
- package/dist/types/ar/index.d.ts +11 -0
- package/dist/types/ar/iso.d.ts +5 -0
- package/dist/types/ar/limits.d.ts +33 -0
- package/dist/types/ar/lzh.d.ts +7 -0
- package/dist/types/ar/open.d.ts +45 -0
- package/dist/types/ar/paths.d.ts +18 -0
- package/dist/types/ar/rar/rar4-decoder.d.ts +6 -0
- package/dist/types/ar/rar/rar5-decoder.d.ts +6 -0
- package/dist/types/ar/rar.d.ts +5 -0
- package/dist/types/ar/reader.d.ts +28 -0
- package/dist/types/ar/registry.d.ts +17 -0
- package/dist/types/ar/rpm.d.ts +5 -0
- package/dist/types/ar/sevenzip/decode.d.ts +33 -0
- package/dist/types/ar/sevenzip.d.ts +5 -0
- package/dist/types/ar/source.d.ts +54 -0
- package/dist/types/ar/tar.d.ts +9 -0
- package/dist/types/ar/types.d.ts +99 -0
- package/dist/types/ar/unix-ar.d.ts +7 -0
- package/dist/types/ar/write.d.ts +10 -0
- package/dist/types/ar/zip.d.ts +10 -0
- package/dist/types/binary.d.ts +6 -0
- package/dist/types/dirs.d.ts +10 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/postmortem.d.ts +33 -0
- package/dist/types/sqlite.d.ts +23 -0
- package/package.json +10 -4
- package/src/ar/arj.ts +314 -0
- package/src/ar/asar.ts +480 -0
- package/src/ar/bytes.ts +78 -0
- package/src/ar/cab.ts +377 -0
- package/src/ar/checksums.ts +62 -0
- package/src/ar/codecs/bzip2.ts +489 -0
- package/src/ar/codecs/gzip.ts +25 -0
- package/src/ar/codecs/lzma.ts +437 -0
- package/src/ar/codecs/lzw.ts +191 -0
- package/src/ar/codecs/lzx.ts +366 -0
- package/src/ar/codecs/xz.ts +522 -0
- package/src/ar/codecs/zstd.ts +25 -0
- package/src/ar/cpio.ts +389 -0
- package/src/ar/deb.ts +160 -0
- package/src/ar/entries.ts +97 -0
- package/src/ar/error.ts +11 -0
- package/src/ar/index.ts +16 -0
- package/src/ar/iso.ts +712 -0
- package/src/ar/limits.ts +80 -0
- package/src/ar/lzh.ts +659 -0
- package/src/ar/open.ts +225 -0
- package/src/ar/paths.ts +70 -0
- package/src/ar/rar/rar4-decoder.ts +459 -0
- package/src/ar/rar/rar5-decoder.ts +400 -0
- package/src/ar/rar.ts +735 -0
- package/src/ar/reader.ts +162 -0
- package/src/ar/registry.ts +208 -0
- package/src/ar/rpm.ts +320 -0
- package/src/ar/sevenzip/decode.ts +239 -0
- package/src/ar/sevenzip.ts +634 -0
- package/src/ar/source.ts +190 -0
- package/src/ar/tar.ts +771 -0
- package/src/ar/types.ts +131 -0
- package/src/ar/unix-ar.ts +312 -0
- package/src/ar/write.ts +55 -0
- package/src/ar/zip.ts +719 -0
- package/src/binary.ts +6 -2
- package/src/browsers.ts +2 -149
- package/src/dirs.ts +24 -4
- package/src/docx/converter.ts +9 -9
- package/src/env.ts +36 -5
- package/src/index.ts +1 -0
- package/src/logger.ts +19 -15
- package/src/postmortem.ts +74 -0
- package/src/sqlite.ts +33 -0
- package/src/stream.ts +11 -23
- package/src/vendor/mermaid-ascii/ascii/grid.ts +16 -1
- package/dist/types/docx/zip.d.ts +0 -6
- package/src/docx/zip.ts +0 -87
package/src/binary.ts
CHANGED
|
@@ -14,8 +14,12 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { peekFile, peekFileSync } from "./peek-file";
|
|
16
16
|
|
|
17
|
-
/**
|
|
18
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Header window sniffed for the binary heuristic; mirrors git's 8000-byte scan.
|
|
19
|
+
* Exported so callers that already hold the whole file in memory can sniff the
|
|
20
|
+
* identical prefix through {@link isProbablyBinaryHeader} instead of reopening.
|
|
21
|
+
*/
|
|
22
|
+
export const BINARY_SNIFF_BYTES = 8192;
|
|
19
23
|
|
|
20
24
|
/**
|
|
21
25
|
* Classify an in-memory byte header as binary (non-UTF-8-text).
|
package/src/browsers.ts
CHANGED
|
@@ -4,17 +4,10 @@ import type * as fs from "node:fs";
|
|
|
4
4
|
import * as fsp from "node:fs/promises";
|
|
5
5
|
import * as os from "node:os";
|
|
6
6
|
import * as path from "node:path";
|
|
7
|
-
import
|
|
7
|
+
import { extractArchive } from "./ar";
|
|
8
8
|
|
|
9
9
|
const CHROME_FOR_TESTING_BASE_URL = "https://storage.googleapis.com/chrome-for-testing-public";
|
|
10
10
|
const CHROME_METADATA_BASE_URL = "https://googlechromelabs.github.io/chrome-for-testing";
|
|
11
|
-
const ZIP_LOCAL_FILE_HEADER = 0x04034b50;
|
|
12
|
-
const ZIP_CENTRAL_DIRECTORY_HEADER = 0x02014b50;
|
|
13
|
-
const ZIP_END_OF_CENTRAL_DIRECTORY = 0x06054b50;
|
|
14
|
-
const ZIP_DIRECTORY_MODE = 0o040000;
|
|
15
|
-
const ZIP_REGULAR_FILE_MODE = 0o100000;
|
|
16
|
-
const ZIP_SYMLINK_MODE = 0o120000;
|
|
17
|
-
const ZIP_FILE_TYPE_MASK = 0o170000;
|
|
18
11
|
|
|
19
12
|
/** Supported browser products. */
|
|
20
13
|
export enum Browser {
|
|
@@ -104,16 +97,6 @@ interface PatchVersions {
|
|
|
104
97
|
builds: Record<string, { version: string }>;
|
|
105
98
|
}
|
|
106
99
|
|
|
107
|
-
interface ZipEntry {
|
|
108
|
-
name: string;
|
|
109
|
-
method: number;
|
|
110
|
-
crc: number;
|
|
111
|
-
compressedSize: number;
|
|
112
|
-
uncompressedSize: number;
|
|
113
|
-
externalAttributes: number;
|
|
114
|
-
localHeaderOffset: number;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
100
|
/** Detect the current host's Puppeteer browser platform. */
|
|
118
101
|
export function detectBrowserPlatform(): BrowserPlatform | undefined {
|
|
119
102
|
const platform = os.platform();
|
|
@@ -259,7 +242,7 @@ export async function install(options: InstallOptions): Promise<InstalledBrowser
|
|
|
259
242
|
archivePath,
|
|
260
243
|
options.downloadProgressCallback,
|
|
261
244
|
);
|
|
262
|
-
await
|
|
245
|
+
await extractArchive(archivePath, stagingPath);
|
|
263
246
|
await fsp.mkdir(path.dirname(installPath), { recursive: true });
|
|
264
247
|
await fsp.rm(installPath, { recursive: true, force: true });
|
|
265
248
|
await fsp.rename(stagingPath, installPath);
|
|
@@ -369,133 +352,3 @@ async function downloadArchive(
|
|
|
369
352
|
await file.close();
|
|
370
353
|
}
|
|
371
354
|
}
|
|
372
|
-
|
|
373
|
-
async function extractZipArchive(archivePath: string, destination: string): Promise<void> {
|
|
374
|
-
const archive = await fsp.readFile(archivePath);
|
|
375
|
-
const entries = readCentralDirectory(archive);
|
|
376
|
-
await fsp.mkdir(destination, { recursive: true });
|
|
377
|
-
const root = path.resolve(destination);
|
|
378
|
-
for (const entry of entries) {
|
|
379
|
-
const outputPath = safeArchivePath(root, entry.name);
|
|
380
|
-
const mode = entry.externalAttributes >>> 16;
|
|
381
|
-
const type = mode & ZIP_FILE_TYPE_MASK;
|
|
382
|
-
if (entry.name.endsWith("/") || type === ZIP_DIRECTORY_MODE) {
|
|
383
|
-
await fsp.mkdir(outputPath, { recursive: true });
|
|
384
|
-
if (mode & 0o777) await fsp.chmod(outputPath, mode & 0o777);
|
|
385
|
-
continue;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
const contents = readZipEntry(archive, entry);
|
|
389
|
-
await fsp.mkdir(path.dirname(outputPath), { recursive: true });
|
|
390
|
-
if (type === ZIP_SYMLINK_MODE) {
|
|
391
|
-
const target = contents.toString("utf8");
|
|
392
|
-
validateSymlinkTarget(root, outputPath, target);
|
|
393
|
-
await fsp.symlink(target, outputPath);
|
|
394
|
-
continue;
|
|
395
|
-
}
|
|
396
|
-
await fsp.writeFile(outputPath, contents, { mode: mode & 0o777 ? mode & 0o777 : 0o644 });
|
|
397
|
-
if ((mode & ZIP_FILE_TYPE_MASK) === ZIP_REGULAR_FILE_MODE && mode & 0o777)
|
|
398
|
-
await fsp.chmod(outputPath, mode & 0o777);
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
function readCentralDirectory(archive: Buffer): ZipEntry[] {
|
|
403
|
-
const minimumOffset = Math.max(0, archive.length - 65_557);
|
|
404
|
-
let endOffset = -1;
|
|
405
|
-
for (let offset = archive.length - 22; offset >= minimumOffset; offset--) {
|
|
406
|
-
if (archive.readUInt32LE(offset) === ZIP_END_OF_CENTRAL_DIRECTORY) {
|
|
407
|
-
endOffset = offset;
|
|
408
|
-
break;
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
if (endOffset < 0) throw new Error("Invalid ZIP archive: central directory was not found");
|
|
412
|
-
const disk = archive.readUInt16LE(endOffset + 4);
|
|
413
|
-
const centralDisk = archive.readUInt16LE(endOffset + 6);
|
|
414
|
-
const entryCount = archive.readUInt16LE(endOffset + 10);
|
|
415
|
-
const centralSize = archive.readUInt32LE(endOffset + 12);
|
|
416
|
-
const centralOffset = archive.readUInt32LE(endOffset + 16);
|
|
417
|
-
if (disk !== 0 || centralDisk !== 0) throw new Error("Multi-disk ZIP archives are not supported");
|
|
418
|
-
if (entryCount === 0xffff || centralSize === 0xffffffff || centralOffset === 0xffffffff) {
|
|
419
|
-
throw new Error("ZIP64 archives are not supported");
|
|
420
|
-
}
|
|
421
|
-
if (centralOffset + centralSize > endOffset)
|
|
422
|
-
throw new Error("Invalid ZIP archive: central directory is out of bounds");
|
|
423
|
-
|
|
424
|
-
const entries: ZipEntry[] = [];
|
|
425
|
-
let offset = centralOffset;
|
|
426
|
-
for (let index = 0; index < entryCount; index++) {
|
|
427
|
-
if (offset + 46 > archive.length || archive.readUInt32LE(offset) !== ZIP_CENTRAL_DIRECTORY_HEADER) {
|
|
428
|
-
throw new Error("Invalid ZIP archive: malformed central directory entry");
|
|
429
|
-
}
|
|
430
|
-
const flags = archive.readUInt16LE(offset + 8);
|
|
431
|
-
if (flags & 1) throw new Error("Encrypted ZIP entries are not supported");
|
|
432
|
-
const nameLength = archive.readUInt16LE(offset + 28);
|
|
433
|
-
const extraLength = archive.readUInt16LE(offset + 30);
|
|
434
|
-
const commentLength = archive.readUInt16LE(offset + 32);
|
|
435
|
-
const end = offset + 46 + nameLength + extraLength + commentLength;
|
|
436
|
-
if (end > archive.length) throw new Error("Invalid ZIP archive: truncated central directory entry");
|
|
437
|
-
entries.push({
|
|
438
|
-
name: archive.subarray(offset + 46, offset + 46 + nameLength).toString("utf8"),
|
|
439
|
-
method: archive.readUInt16LE(offset + 10),
|
|
440
|
-
crc: archive.readUInt32LE(offset + 16),
|
|
441
|
-
compressedSize: archive.readUInt32LE(offset + 20),
|
|
442
|
-
uncompressedSize: archive.readUInt32LE(offset + 24),
|
|
443
|
-
externalAttributes: archive.readUInt32LE(offset + 38),
|
|
444
|
-
localHeaderOffset: archive.readUInt32LE(offset + 42),
|
|
445
|
-
});
|
|
446
|
-
offset = end;
|
|
447
|
-
}
|
|
448
|
-
return entries;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
function readZipEntry(archive: Buffer, entry: ZipEntry): Buffer {
|
|
452
|
-
const offset = entry.localHeaderOffset;
|
|
453
|
-
if (offset + 30 > archive.length || archive.readUInt32LE(offset) !== ZIP_LOCAL_FILE_HEADER) {
|
|
454
|
-
throw new Error(`Invalid ZIP archive: malformed local header for ${entry.name}`);
|
|
455
|
-
}
|
|
456
|
-
const nameLength = archive.readUInt16LE(offset + 26);
|
|
457
|
-
const extraLength = archive.readUInt16LE(offset + 28);
|
|
458
|
-
const start = offset + 30 + nameLength + extraLength;
|
|
459
|
-
const end = start + entry.compressedSize;
|
|
460
|
-
if (end > archive.length) throw new Error(`Invalid ZIP archive: truncated data for ${entry.name}`);
|
|
461
|
-
const compressed = archive.subarray(start, end);
|
|
462
|
-
let contents: Buffer;
|
|
463
|
-
if (entry.method === 0) contents = Buffer.from(compressed);
|
|
464
|
-
else if (entry.method === 8) contents = zlib.inflateRawSync(compressed);
|
|
465
|
-
else throw new Error(`Unsupported ZIP compression method ${entry.method} for ${entry.name}`);
|
|
466
|
-
if (contents.length !== entry.uncompressedSize)
|
|
467
|
-
throw new Error(`Invalid uncompressed size for ZIP entry ${entry.name}`);
|
|
468
|
-
if (crc32(contents) !== entry.crc) throw new Error(`CRC mismatch for ZIP entry ${entry.name}`);
|
|
469
|
-
return contents;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
function safeArchivePath(root: string, name: string): string {
|
|
473
|
-
if (!name || name.includes("\0") || name.startsWith("/") || name.startsWith("\\") || /^[A-Za-z]:/.test(name)) {
|
|
474
|
-
throw new Error(`Unsafe path in ZIP archive: ${name}`);
|
|
475
|
-
}
|
|
476
|
-
const segments = name.replaceAll("\\", "/").split("/");
|
|
477
|
-
if (segments.some(segment => segment === "..")) throw new Error(`Unsafe path in ZIP archive: ${name}`);
|
|
478
|
-
const target = path.resolve(root, ...segments.filter(Boolean));
|
|
479
|
-
if (target !== root && !target.startsWith(`${root}${path.sep}`))
|
|
480
|
-
throw new Error(`Unsafe path in ZIP archive: ${name}`);
|
|
481
|
-
return target;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
function validateSymlinkTarget(root: string, linkPath: string, target: string): void {
|
|
485
|
-
if (!target || target.includes("\0") || path.isAbsolute(target) || /^[A-Za-z]:/.test(target)) {
|
|
486
|
-
throw new Error(`Unsafe symlink target in ZIP archive: ${target}`);
|
|
487
|
-
}
|
|
488
|
-
const resolved = path.resolve(path.dirname(linkPath), target);
|
|
489
|
-
if (resolved !== root && !resolved.startsWith(`${root}${path.sep}`)) {
|
|
490
|
-
throw new Error(`Unsafe symlink target in ZIP archive: ${target}`);
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
function crc32(contents: Uint8Array): number {
|
|
495
|
-
let crc = 0xffffffff;
|
|
496
|
-
for (const byte of contents) {
|
|
497
|
-
crc ^= byte;
|
|
498
|
-
for (let bit = 0; bit < 8; bit++) crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0);
|
|
499
|
-
}
|
|
500
|
-
return (crc ^ 0xffffffff) >>> 0;
|
|
501
|
-
}
|
package/src/dirs.ts
CHANGED
|
@@ -28,8 +28,8 @@ export const MAIN_CONFIG_FILENAMES = ["config.yml", "config.yaml"] as const;
|
|
|
28
28
|
/** Version (e.g. "1.0.0") */
|
|
29
29
|
export const VERSION: string = version;
|
|
30
30
|
|
|
31
|
-
/** Default User-Agent header string (e.g. "
|
|
32
|
-
export const USER_AGENT = `
|
|
31
|
+
/** Default User-Agent header string (e.g. "zeta/1.0.10") */
|
|
32
|
+
export const USER_AGENT = `zeta/${VERSION}`;
|
|
33
33
|
|
|
34
34
|
/** Minimum Bun version */
|
|
35
35
|
export const MIN_BUN_VERSION: string = engines.bun.replace(/[^0-9.]/g, "");
|
|
@@ -700,6 +700,11 @@ export function getGithubCacheDbPath(): string {
|
|
|
700
700
|
return dirs.rootSubdir(path.join("cache", "github-cache.db"), "cache");
|
|
701
701
|
}
|
|
702
702
|
|
|
703
|
+
/** Get the legacy Pi extension parse cache database path. */
|
|
704
|
+
export function getLegacyPiExtensionCacheDbPath(): string {
|
|
705
|
+
return dirs.rootSubdir(path.join("cache", "legacy-pi-extension-cache.db"), "cache");
|
|
706
|
+
}
|
|
707
|
+
|
|
703
708
|
/**
|
|
704
709
|
* Get the encrypted auth-broker snapshot cache path (~/.zeta/cache/auth-broker-snapshot.enc).
|
|
705
710
|
* Honors the `OMP_AUTH_BROKER_SNAPSHOT_CACHE` env var when set so tests and
|
|
@@ -712,6 +717,11 @@ export function getAuthBrokerSnapshotCachePath(): string {
|
|
|
712
717
|
}
|
|
713
718
|
|
|
714
719
|
/** Get the local FastEmbed model cache directory (~/.zeta/cache/fastembed). */
|
|
720
|
+
/** Get the commit-author avatar cache directory (~/.omp/cache/avatars). */
|
|
721
|
+
export function getAvatarCacheDir(): string {
|
|
722
|
+
return dirs.rootSubdir(path.join("cache", "avatars"), "cache");
|
|
723
|
+
}
|
|
724
|
+
/** Get the local FastEmbed model cache directory (~/.omp/cache/fastembed). */
|
|
715
725
|
export function getFastembedCacheDir(): string {
|
|
716
726
|
return dirs.rootSubdir(path.join("cache", "fastembed"), "cache");
|
|
717
727
|
}
|
|
@@ -876,10 +886,20 @@ export function getSecretPlaceholderKeyPath(): string {
|
|
|
876
886
|
return keyPath;
|
|
877
887
|
}
|
|
878
888
|
|
|
889
|
+
/** Root directory containing every per-project daemon runtime scope (~/.zeta/run/daemons; XDG default: $XDG_STATE_HOME/zeta/run/daemons). */
|
|
890
|
+
export function getDaemonRuntimeRoot(): string {
|
|
891
|
+
return dirs.rootSubdir(path.join("run", "daemons"), "state");
|
|
892
|
+
}
|
|
893
|
+
|
|
879
894
|
/** Get the daemon runtime directory for a project (~/.zeta/run/daemons/<hash>; XDG default: $XDG_STATE_HOME/zeta/run/daemons/<hash>). */
|
|
880
895
|
export function getDaemonRuntimeDir(projectDir: string): string {
|
|
881
896
|
const key = Bun.hash.wyhash(path.resolve(projectDir)).toString(16).padStart(16, "0");
|
|
882
|
-
return
|
|
897
|
+
return path.join(getDaemonRuntimeRoot(), key);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/** Root directory containing every machine-global daemon service scope. */
|
|
901
|
+
export function getGlobalDaemonRuntimeRoot(): string {
|
|
902
|
+
return path.join(getBaseConfigRoot(), "run", "daemons", "global");
|
|
883
903
|
}
|
|
884
904
|
|
|
885
905
|
/** Get a profile-independent runtime directory for a machine-global daemon service. */
|
|
@@ -887,7 +907,7 @@ export function getGlobalDaemonRuntimeDir(service: string): string {
|
|
|
887
907
|
if (!/^[a-z0-9][a-z0-9._-]*$/i.test(service)) {
|
|
888
908
|
throw new Error(`Invalid global daemon service name: ${JSON.stringify(service)}`);
|
|
889
909
|
}
|
|
890
|
-
return path.join(
|
|
910
|
+
return path.join(getGlobalDaemonRuntimeRoot(), service);
|
|
891
911
|
}
|
|
892
912
|
|
|
893
913
|
/** Get the provider in-flight root directory (~/.zeta/run/provider-inflight; XDG default: $XDG_STATE_HOME/zeta/run/provider-inflight). */
|
package/src/docx/converter.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
+
import { archiveEntryText, readArchiveEntries } from "../ar";
|
|
3
4
|
import { attribute, childElements, descendants, firstChild, localName, parseXml, type XmlElement } from "./xml";
|
|
4
|
-
import { readZip, readZipText, type ZipEntries } from "./zip";
|
|
5
5
|
|
|
6
6
|
/** A mammoth-compatible diagnostic emitted while converting a document. */
|
|
7
7
|
export interface DocxMessage {
|
|
@@ -119,7 +119,7 @@ interface NumberingLevel {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
interface ConversionContext {
|
|
122
|
-
readonly entries:
|
|
122
|
+
readonly entries: ReadonlyMap<string, Uint8Array>;
|
|
123
123
|
readonly relationships: ReadonlyMap<string, Relationship>;
|
|
124
124
|
readonly contentTypes: ReadonlyMap<string, string>;
|
|
125
125
|
readonly styles: ReadonlyMap<string, Style>;
|
|
@@ -655,21 +655,21 @@ function defaultImageConverter(): ImageConverter {
|
|
|
655
655
|
/** Convert a DOCX buffer or path to mammoth-compatible HTML. */
|
|
656
656
|
export async function convertToHtml(input: DocxInput, options: ConvertToHtmlOptions = {}): Promise<DocxResult> {
|
|
657
657
|
const bytes = "buffer" in input && input.buffer ? input.buffer : await fs.readFile(input.path);
|
|
658
|
-
const entries =
|
|
659
|
-
const documentXml =
|
|
658
|
+
const entries = await readArchiveEntries({ bytes, format: "zip" });
|
|
659
|
+
const documentXml = archiveEntryText(entries, "word/document.xml");
|
|
660
660
|
if (!documentXml) throw new Error("Invalid DOCX: missing word/document.xml");
|
|
661
661
|
const context: ConversionContext = {
|
|
662
662
|
entries,
|
|
663
|
-
relationships: parseRelationships(
|
|
664
|
-
contentTypes: parseContentTypes(
|
|
665
|
-
styles: parseStyles(
|
|
666
|
-
numbering: parseNumbering(
|
|
663
|
+
relationships: parseRelationships(archiveEntryText(entries, "word/_rels/document.xml.rels")),
|
|
664
|
+
contentTypes: parseContentTypes(archiveEntryText(entries, "[Content_Types].xml")),
|
|
665
|
+
styles: parseStyles(archiveEntryText(entries, "word/styles.xml")),
|
|
666
|
+
numbering: parseNumbering(archiveEntryText(entries, "word/numbering.xml")),
|
|
667
667
|
messages: [],
|
|
668
668
|
warnedStyles: new Set(),
|
|
669
669
|
customStyles: parseCustomStyles(options.styleMap),
|
|
670
670
|
includeDefaultStyleMap: options.includeDefaultStyleMap !== false,
|
|
671
671
|
convertImage: options.convertImage ?? defaultImageConverter(),
|
|
672
|
-
footnotes: parseFootnotes(
|
|
672
|
+
footnotes: parseFootnotes(archiveEntryText(entries, "word/footnotes.xml")),
|
|
673
673
|
usedFootnotes: [],
|
|
674
674
|
footnoteOrdinals: new Map(),
|
|
675
675
|
};
|
package/src/env.ts
CHANGED
|
@@ -102,16 +102,39 @@ export function filterChildShellEnv(
|
|
|
102
102
|
): Record<string, string> {
|
|
103
103
|
const result = filterProcessEnv(env);
|
|
104
104
|
const projectEnv = parseEnvFile(path.join(cwd, ".env"));
|
|
105
|
-
const
|
|
105
|
+
const launchNodeEnv = launchEnvValues ? launchEnvValues.get("NODE_ENV") : env.NODE_ENV;
|
|
106
|
+
const nodeEnvName = `.env.${launchNodeEnv || "development"}`;
|
|
106
107
|
const modeEnv = parseEnvFile(path.join(cwd, nodeEnvName));
|
|
107
108
|
const localEnv = parseEnvFile(path.join(cwd, ".env.local"));
|
|
108
|
-
const
|
|
109
|
+
const modeLocalEnv = parseEnvFile(path.join(cwd, `${nodeEnvName}.local`));
|
|
110
|
+
const launchEnv = { ...projectEnv, ...modeEnv, ...localEnv, ...modeLocalEnv };
|
|
109
111
|
const expandedLaunchEnv = {
|
|
110
112
|
...expandDotenvValues(projectEnv, result),
|
|
111
113
|
...expandDotenvValues(modeEnv, result),
|
|
112
114
|
...expandDotenvValues(localEnv, result),
|
|
115
|
+
...expandDotenvValues(modeLocalEnv, result),
|
|
113
116
|
};
|
|
114
|
-
|
|
117
|
+
let fallbackLaunchEnv: Record<string, string> | undefined;
|
|
118
|
+
let expandedFallbackLaunchEnv: Record<string, string> | undefined;
|
|
119
|
+
if (!launchEnvValues && nodeEnvName !== ".env.development") {
|
|
120
|
+
const fallbackModeEnv = parseEnvFile(path.join(cwd, ".env.development"));
|
|
121
|
+
const fallbackModeLocalEnv = parseEnvFile(path.join(cwd, ".env.development.local"));
|
|
122
|
+
const candidate = { ...projectEnv, ...fallbackModeEnv, ...localEnv, ...fallbackModeLocalEnv };
|
|
123
|
+
const expandedCandidate = {
|
|
124
|
+
...expandDotenvValues(projectEnv, result),
|
|
125
|
+
...expandDotenvValues(fallbackModeEnv, result),
|
|
126
|
+
...expandDotenvValues(localEnv, result),
|
|
127
|
+
...expandDotenvValues(fallbackModeLocalEnv, result),
|
|
128
|
+
};
|
|
129
|
+
if (candidate.NODE_ENV === env.NODE_ENV || expandedCandidate.NODE_ENV === env.NODE_ENV) {
|
|
130
|
+
// Without a launch snapshot, NODE_ENV may itself have come from dotenv.
|
|
131
|
+
// Bun chose the default mode before loading it, so retain both candidates.
|
|
132
|
+
fallbackLaunchEnv = candidate;
|
|
133
|
+
expandedFallbackLaunchEnv = expandedCandidate;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const allLaunchEnv = fallbackLaunchEnv ? { ...launchEnv, ...fallbackLaunchEnv } : launchEnv;
|
|
137
|
+
for (const key in allLaunchEnv) {
|
|
115
138
|
const launchValue = launchEnvValues?.get(key);
|
|
116
139
|
if (launchValue !== undefined) {
|
|
117
140
|
// Launcher-owned name: it keeps the launcher's own value. Bun overwrites
|
|
@@ -119,7 +142,10 @@ export function filterChildShellEnv(
|
|
|
119
142
|
// value whenever what survived is exactly what the dotenv file defines.
|
|
120
143
|
if (
|
|
121
144
|
result[key] !== launchValue &&
|
|
122
|
-
(result[key] === launchEnv[key] ||
|
|
145
|
+
(result[key] === launchEnv[key] ||
|
|
146
|
+
result[key] === expandedLaunchEnv[key] ||
|
|
147
|
+
result[key] === fallbackLaunchEnv?.[key] ||
|
|
148
|
+
result[key] === expandedFallbackLaunchEnv?.[key])
|
|
123
149
|
) {
|
|
124
150
|
result[key] = launchValue;
|
|
125
151
|
}
|
|
@@ -130,7 +156,12 @@ export function filterChildShellEnv(
|
|
|
130
156
|
// absent from it, or OMP itself injected the value — either way it came
|
|
131
157
|
// from a project dotenv file, not the parent shell.
|
|
132
158
|
delete result[key];
|
|
133
|
-
} else if (
|
|
159
|
+
} else if (
|
|
160
|
+
result[key] === launchEnv[key] ||
|
|
161
|
+
result[key] === expandedLaunchEnv[key] ||
|
|
162
|
+
result[key] === fallbackLaunchEnv?.[key] ||
|
|
163
|
+
result[key] === expandedFallbackLaunchEnv?.[key]
|
|
164
|
+
) {
|
|
134
165
|
// No launch-env snapshot (dotenv autoloaded without procfs): best-effort
|
|
135
166
|
// value match against the Bun-parsed dotenv.
|
|
136
167
|
delete result[key];
|
package/src/index.ts
CHANGED
|
@@ -28,6 +28,7 @@ export { AbortError, ChildProcess, Exception, NonZeroExitError } from "./ptree";
|
|
|
28
28
|
export * from "./runtime-install";
|
|
29
29
|
export * from "./sanitize-text";
|
|
30
30
|
export * from "./snowflake";
|
|
31
|
+
export * from "./sqlite";
|
|
31
32
|
export * from "./stderr-guard";
|
|
32
33
|
export * from "./stream";
|
|
33
34
|
export * from "./tab-spacing";
|
package/src/logger.ts
CHANGED
|
@@ -91,7 +91,7 @@ function pruneStaleProcessLogs(dir: string): void {
|
|
|
91
91
|
`${cutoff.getFullYear()}-${String(cutoff.getMonth() + 1).padStart(2, "0")}-` +
|
|
92
92
|
String(cutoff.getDate()).padStart(2, "0");
|
|
93
93
|
|
|
94
|
-
const staleLogsByProcessDay = new Map<string, Array<{ path: string;
|
|
94
|
+
const staleLogsByProcessDay = new Map<string, Array<{ path: string; rollover: number }>>();
|
|
95
95
|
for (const entry of entries) {
|
|
96
96
|
if (!entry.isFile()) continue;
|
|
97
97
|
const logMatch = PROCESS_LOG_PATTERN.exec(entry.name);
|
|
@@ -120,25 +120,29 @@ function pruneStaleProcessLogs(dir: string): void {
|
|
|
120
120
|
continue;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
});
|
|
131
|
-
staleLogsByProcessDay.set(key, staleLogs);
|
|
132
|
-
} catch {
|
|
133
|
-
// Another process may have pruned the same stale namespace.
|
|
134
|
-
}
|
|
123
|
+
const key = `${pidText}:${logMatch[1]}`;
|
|
124
|
+
const staleLogs = staleLogsByProcessDay.get(key) ?? [];
|
|
125
|
+
staleLogs.push({
|
|
126
|
+
path: entryPath,
|
|
127
|
+
rollover: Number(logMatch[3] ?? 0),
|
|
128
|
+
});
|
|
129
|
+
staleLogsByProcessDay.set(key, staleLogs);
|
|
135
130
|
}
|
|
136
131
|
|
|
137
132
|
for (const staleLogs of staleLogsByProcessDay.values()) {
|
|
138
|
-
staleLogs.
|
|
133
|
+
if (staleLogs.length <= RETAINED_STALE_LOGS_PER_PROCESS_DAY) continue;
|
|
134
|
+
const ranked: Array<{ path: string; mtimeMs: number; rollover: number }> = [];
|
|
135
|
+
for (const stale of staleLogs) {
|
|
136
|
+
try {
|
|
137
|
+
ranked.push({ ...stale, mtimeMs: fs.statSync(stale.path).mtimeMs });
|
|
138
|
+
} catch {
|
|
139
|
+
// Another process may have pruned the same stale namespace.
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
ranked.sort(
|
|
139
143
|
(a, b) => b.mtimeMs - a.mtimeMs || b.rollover - a.rollover || (a.path < b.path ? -1 : a.path > b.path ? 1 : 0),
|
|
140
144
|
);
|
|
141
|
-
for (const stale of
|
|
145
|
+
for (const stale of ranked.slice(RETAINED_STALE_LOGS_PER_PROCESS_DAY)) {
|
|
142
146
|
try {
|
|
143
147
|
fs.rmSync(stale.path, { force: true });
|
|
144
148
|
} catch {
|
package/src/postmortem.ts
CHANGED
|
@@ -146,6 +146,66 @@ export function isIpcSendEpipe(err: Error): boolean {
|
|
|
146
146
|
return classifyBrokenPipe(err) === "ipc-send";
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Detect Bun's advanced-serialization (structured-clone) IPC decode failure.
|
|
151
|
+
*
|
|
152
|
+
* When a worker subprocess spawned with `serialization: "advanced"` sends a
|
|
153
|
+
* malformed or truncated frame, Bun raises the decode failure as a
|
|
154
|
+
* process-level `uncaughtException` in the *parent* rather than routing it to
|
|
155
|
+
* the channel's `ipc()` callback (oven-sh/bun#37287). The error is a bare
|
|
156
|
+
* `TypeError: Unable to deserialize data.` whose only own property is `message`
|
|
157
|
+
* — it carries no `code`, no `syscall`, and no `stack`. Matching all four traits
|
|
158
|
+
* keeps unrelated application `TypeError`s (which always carry a populated
|
|
159
|
+
* multi-frame stack) on the fatal path, so a genuine bug is never silently
|
|
160
|
+
* swallowed.
|
|
161
|
+
*
|
|
162
|
+
* Every advanced-serialization channel in this process is an optional worker
|
|
163
|
+
* subsystem (TTS, STT, tiny-title, mnemopi embeddings, JS eval), so one
|
|
164
|
+
* worker's bad frame must fault only that worker — via its own `onExit`/error
|
|
165
|
+
* path — never tear down the whole session. Callers log-and-continue instead of
|
|
166
|
+
* taking the fatal path. Mirrors {@link classifyBrokenPipe} for the send side
|
|
167
|
+
* (#2997, #9158).
|
|
168
|
+
*/
|
|
169
|
+
export function isWorkerIpcDeserializeError(err: unknown): boolean {
|
|
170
|
+
return (
|
|
171
|
+
err instanceof TypeError &&
|
|
172
|
+
err.message === "Unable to deserialize data." &&
|
|
173
|
+
!err.stack &&
|
|
174
|
+
!("code" in err) &&
|
|
175
|
+
!("syscall" in err)
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Recycle callbacks for the active advanced-serialization worker IPC channels. */
|
|
180
|
+
const workerIpcFaultHandlers = new Set<(err: Error) => void>();
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Register a fault/recycle callback for an active advanced-serialization worker
|
|
184
|
+
* IPC channel.
|
|
185
|
+
*
|
|
186
|
+
* Bun surfaces a malformed frame as a process-global `uncaughtException`
|
|
187
|
+
* ({@link isWorkerIpcDeserializeError}) with no way to attribute it to a
|
|
188
|
+
* specific channel, so when one fires every registered handler is invoked to
|
|
189
|
+
* conservatively fault its worker — reject in-flight requests and recycle the
|
|
190
|
+
* subprocess — instead of leaving pending work to await forever. Returns an
|
|
191
|
+
* unregister function; callers MUST unregister when the worker exits.
|
|
192
|
+
*/
|
|
193
|
+
export function registerWorkerIpcFaultHandler(handler: (err: Error) => void): () => void {
|
|
194
|
+
workerIpcFaultHandlers.add(handler);
|
|
195
|
+
return () => workerIpcFaultHandlers.delete(handler);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Invoke every registered worker IPC fault handler, isolating handler throws. */
|
|
199
|
+
function faultWorkerIpcChannels(err: Error): void {
|
|
200
|
+
for (const handler of workerIpcFaultHandlers) {
|
|
201
|
+
try {
|
|
202
|
+
handler(err);
|
|
203
|
+
} catch (handlerErr) {
|
|
204
|
+
logger.warn("Worker IPC fault handler threw", { err: handlerErr });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
149
209
|
/**
|
|
150
210
|
* Treat unhandled stdout EPIPE rejections as a graceful peer disconnect.
|
|
151
211
|
*
|
|
@@ -283,6 +343,20 @@ if (isMainThread) {
|
|
|
283
343
|
logger.warn("Ignoring expected cleanup exception", { err });
|
|
284
344
|
return;
|
|
285
345
|
}
|
|
346
|
+
// A malformed advanced-serialization frame from a worker subprocess
|
|
347
|
+
// surfaces here as a process-level uncaughtException (oven-sh/bun#37287)
|
|
348
|
+
// rather than in the channel's ipc() callback, and Bun gives no way to
|
|
349
|
+
// tell which channel produced it. Contain it to the worker layer: keep
|
|
350
|
+
// the session alive and conservatively fault every active advanced-IPC
|
|
351
|
+
// worker so its owning client rejects in-flight requests and recycles
|
|
352
|
+
// the subprocess — a worker that sent a bad frame but stays alive would
|
|
353
|
+
// otherwise never fire onExit and leave callers awaiting forever.
|
|
354
|
+
// Mirrors the ipc-send EPIPE containment below (#9158, #2997).
|
|
355
|
+
if (isWorkerIpcDeserializeError(err)) {
|
|
356
|
+
logger.warn("Malformed worker IPC frame; faulting active worker subsystems", { err });
|
|
357
|
+
faultWorkerIpcChannels(err);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
286
360
|
await exitAfterFatal("Uncaught Exception", "Uncaught exception", err, Reason.UNCAUGHT_EXCEPTION);
|
|
287
361
|
})
|
|
288
362
|
.on("unhandledRejection", async reason => {
|
package/src/sqlite.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared classifiers for `bun:sqlite` error result codes.
|
|
3
|
+
*
|
|
4
|
+
* Every omp SQLite store (`agent.db` credential/usage store, `models.db` model
|
|
5
|
+
* cache, `history.db`) needs the same two distinctions: a transient BUSY that
|
|
6
|
+
* clears by retrying, and an unrecoverable corruption that never does. Keeping
|
|
7
|
+
* one implementation here prevents the classifiers from drifting between the
|
|
8
|
+
* credential store and the model cache.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* SQLite's busy result-code family — base `SQLITE_BUSY` plus the extended
|
|
13
|
+
* variants `SQLITE_BUSY_RECOVERY` (concurrent WAL recovery), `SQLITE_BUSY_SNAPSHOT`,
|
|
14
|
+
* and `SQLITE_BUSY_TIMEOUT`. All warrant the same backoff-and-retry treatment.
|
|
15
|
+
*/
|
|
16
|
+
export function isSqliteBusyError(err: unknown): boolean {
|
|
17
|
+
if (err === null || typeof err !== "object" || !("code" in err)) return false;
|
|
18
|
+
const code = err.code;
|
|
19
|
+
return typeof code === "string" && code.startsWith("SQLITE_BUSY");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* SQLite's unrecoverable-corruption result codes — the `SQLITE_CORRUPT` family
|
|
24
|
+
* (base plus extended variants like `SQLITE_CORRUPT_VTAB` / `SQLITE_CORRUPT_INDEX`)
|
|
25
|
+
* and `SQLITE_NOTADB` (the file header is not a database). Unlike
|
|
26
|
+
* {@link isSqliteBusyError}, these never clear by retrying: the store must be
|
|
27
|
+
* repaired or replaced, so callers latch, quarantine, or recreate the file.
|
|
28
|
+
*/
|
|
29
|
+
export function isSqliteCorruptionError(err: unknown): boolean {
|
|
30
|
+
if (err === null || typeof err !== "object" || !("code" in err)) return false;
|
|
31
|
+
const code = err.code;
|
|
32
|
+
return typeof code === "string" && (code.startsWith("SQLITE_CORRUPT") || code === "SQLITE_NOTADB");
|
|
33
|
+
}
|
package/src/stream.ts
CHANGED
|
@@ -4,25 +4,6 @@ import { abortableSource } from "./abortable";
|
|
|
4
4
|
import { parseStreamingJson } from "./json-parse";
|
|
5
5
|
|
|
6
6
|
const LF = 0x0a;
|
|
7
|
-
type JsonlChunkResult = {
|
|
8
|
-
values: unknown[];
|
|
9
|
-
error: unknown;
|
|
10
|
-
read: number;
|
|
11
|
-
done: boolean;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
function parseJsonlChunkCompat(input: Uint8Array, beg?: number, end?: number): JsonlChunkResult;
|
|
15
|
-
function parseJsonlChunkCompat(input: string): JsonlChunkResult;
|
|
16
|
-
function parseJsonlChunkCompat(input: Uint8Array | string, beg?: number, end?: number): JsonlChunkResult {
|
|
17
|
-
if (typeof input === "string") {
|
|
18
|
-
const { values, error, read, done } = Bun.JSONL.parseChunk(input);
|
|
19
|
-
return { values, error, read, done };
|
|
20
|
-
}
|
|
21
|
-
const start = beg ?? 0;
|
|
22
|
-
const stop = end ?? input.length;
|
|
23
|
-
const { values, error, read, done } = Bun.JSONL.parseChunk(input, start, stop);
|
|
24
|
-
return { values, error, read, done };
|
|
25
|
-
}
|
|
26
7
|
|
|
27
8
|
export async function* readLines(stream: ReadableStream<Uint8Array>, signal?: AbortSignal): AsyncGenerator<Uint8Array> {
|
|
28
9
|
const buffer = new ConcatSink();
|
|
@@ -58,7 +39,7 @@ export async function* readJsonl<T>(stream: ReadableStream<Uint8Array>, signal?:
|
|
|
58
39
|
const tail = buffer.flush();
|
|
59
40
|
if (tail) {
|
|
60
41
|
buffer.clear();
|
|
61
|
-
const { values, error, done } =
|
|
42
|
+
const { values, error, done } = Bun.JSONL.parseChunk(tail, 0, tail.length);
|
|
62
43
|
if (values.length > 0) {
|
|
63
44
|
yield* values as T[];
|
|
64
45
|
}
|
|
@@ -174,8 +155,15 @@ class ConcatSink {
|
|
|
174
155
|
return text;
|
|
175
156
|
}
|
|
176
157
|
*pullJSONL<T>(chunk: Uint8Array, beg: number, end: number) {
|
|
158
|
+
const newline = chunk.indexOf(LF, beg);
|
|
159
|
+
if (newline === -1 || newline >= end) {
|
|
160
|
+
if (this.isEmpty) this.reset(chunk.subarray(beg, end));
|
|
161
|
+
else this.append(chunk.subarray(beg, end));
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
177
165
|
if (this.isEmpty) {
|
|
178
|
-
const { values, error, read, done } =
|
|
166
|
+
const { values, error, read, done } = Bun.JSONL.parseChunk(chunk, beg, end);
|
|
179
167
|
if (values.length > 0) {
|
|
180
168
|
yield* values as T[];
|
|
181
169
|
}
|
|
@@ -192,7 +180,7 @@ class ConcatSink {
|
|
|
192
180
|
space.set(chunk.subarray(beg, end), offset);
|
|
193
181
|
this.#length = total;
|
|
194
182
|
|
|
195
|
-
const { values, error, read, done } =
|
|
183
|
+
const { values, error, read, done } = Bun.JSONL.parseChunk(space, 0, total);
|
|
196
184
|
if (values.length > 0) {
|
|
197
185
|
yield* values as T[];
|
|
198
186
|
}
|
|
@@ -456,7 +444,7 @@ export function parseJsonlLenient<T>(buffer: string, options: { onMalformedRecor
|
|
|
456
444
|
let entries: T[] | undefined;
|
|
457
445
|
|
|
458
446
|
while (buffer.length > 0) {
|
|
459
|
-
const { values, error, read, done } =
|
|
447
|
+
const { values, error, read, done } = Bun.JSONL.parseChunk(buffer);
|
|
460
448
|
if (values.length > 0) {
|
|
461
449
|
const ext = values as T[];
|
|
462
450
|
if (!entries) {
|
|
@@ -11,7 +11,14 @@ import type {
|
|
|
11
11
|
GridCoord, DrawingCoord, Direction, AsciiGraph, AsciiNode, AsciiSubgraph,
|
|
12
12
|
} from './types'
|
|
13
13
|
import { gridKey } from './types'
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
getCanvasSize,
|
|
16
|
+
increaseRoleCanvasSize,
|
|
17
|
+
increaseSize,
|
|
18
|
+
mkCanvas,
|
|
19
|
+
setCanvasSizeToGrid,
|
|
20
|
+
setRoleCanvasSizeToGrid,
|
|
21
|
+
} from './canvas'
|
|
15
22
|
import { determinePath, determineLabelLine } from './edge-routing'
|
|
16
23
|
import { analyzeEdgeBundles, processBundles } from './edge-bundling'
|
|
17
24
|
import { drawBox } from './draw'
|
|
@@ -374,6 +381,14 @@ export function offsetDrawingForSubgraphs(graph: AsciiGraph): void {
|
|
|
374
381
|
node.drawingCoord.y += offsetY
|
|
375
382
|
}
|
|
376
383
|
}
|
|
384
|
+
|
|
385
|
+
// The canvas was sized from the pre-shift grid extents, but every drawing
|
|
386
|
+
// coordinate — including edge path endpoints, which gridToDrawingCoord
|
|
387
|
+
// offsets on read — now sits `offset` cells further out. Grow it to match,
|
|
388
|
+
// or drawing the rightmost/bottom-most edges writes past the allocation.
|
|
389
|
+
const [maxX, maxY] = getCanvasSize(graph.canvas)
|
|
390
|
+
increaseSize(graph.canvas, maxX + offsetX, maxY + offsetY)
|
|
391
|
+
increaseRoleCanvasSize(graph.roleCanvas, maxX + offsetX, maxY + offsetY)
|
|
377
392
|
}
|
|
378
393
|
|
|
379
394
|
// ============================================================================
|
package/dist/types/docx/zip.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
/** The decompressed members of a DOCX ZIP package. */
|
|
2
|
-
export type ZipEntries = ReadonlyMap<string, Uint8Array>;
|
|
3
|
-
/** Decode a ZIP package by walking its central directory. */
|
|
4
|
-
export declare function readZip(bytes: Uint8Array): ZipEntries;
|
|
5
|
-
/** Decode a ZIP member as UTF-8 text. */
|
|
6
|
-
export declare function readZipText(entries: ZipEntries, name: string): string | undefined;
|