obsidian-e2e 0.0.0-next.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.
@@ -0,0 +1,565 @@
1
+ import { access, mkdir, readFile, rm, writeFile } from "node:fs/promises";
2
+ import path, { posix } from "node:path";
3
+ import { spawn } from "node:child_process";
4
+ import { randomUUID } from "node:crypto";
5
+ //#region src/core/args.ts
6
+ function buildCommandArgv(vaultName, command, args = {}) {
7
+ const argv = [`vault=${vaultName}`, command];
8
+ for (const [key, value] of Object.entries(args)) {
9
+ if (value === false || value === null || value === void 0) continue;
10
+ if (value === true) {
11
+ argv.push(key);
12
+ continue;
13
+ }
14
+ argv.push(`${key}=${String(value)}`);
15
+ }
16
+ return argv;
17
+ }
18
+ //#endregion
19
+ //#region src/core/internals.ts
20
+ const clientInternals = /* @__PURE__ */ new WeakMap();
21
+ function attachClientInternals(client, internals) {
22
+ clientInternals.set(client, internals);
23
+ }
24
+ function getClientInternals(client) {
25
+ const internals = clientInternals.get(client);
26
+ if (!internals) throw new Error("Missing obsidian client internals.");
27
+ return internals;
28
+ }
29
+ function createRestoreManager(readFile) {
30
+ const snapshots = /* @__PURE__ */ new Map();
31
+ return {
32
+ async restoreAll() {
33
+ const entries = [...snapshots.entries()].reverse();
34
+ for (const [filePath, snapshot] of entries) await restoreSnapshot(filePath, snapshot);
35
+ snapshots.clear();
36
+ },
37
+ async restoreFile(filePath) {
38
+ const snapshot = snapshots.get(filePath);
39
+ if (!snapshot) return;
40
+ await restoreSnapshot(filePath, snapshot);
41
+ snapshots.delete(filePath);
42
+ },
43
+ async snapshotFileOnce(filePath) {
44
+ if (snapshots.has(filePath)) return;
45
+ try {
46
+ snapshots.set(filePath, {
47
+ exists: true,
48
+ value: await readFile(filePath)
49
+ });
50
+ } catch (error) {
51
+ if (isMissingFileError(error)) {
52
+ snapshots.set(filePath, {
53
+ exists: false,
54
+ value: ""
55
+ });
56
+ return;
57
+ }
58
+ throw error;
59
+ }
60
+ }
61
+ };
62
+ }
63
+ async function restoreSnapshot(filePath, snapshot) {
64
+ if (snapshot.exists) {
65
+ await writeFile(filePath, snapshot.value, "utf8");
66
+ return;
67
+ }
68
+ await rm(filePath, {
69
+ force: true,
70
+ recursive: true
71
+ });
72
+ }
73
+ function isMissingFileError(error) {
74
+ return Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
75
+ }
76
+ //#endregion
77
+ //#region src/vault/json-file.ts
78
+ function createJsonFile(filePath, beforeMutate) {
79
+ return {
80
+ async patch(updater) {
81
+ await beforeMutate?.();
82
+ const currentValue = await this.read();
83
+ const draft = structuredClone(currentValue);
84
+ const nextValue = await updater(draft) ?? draft;
85
+ await this.write(nextValue);
86
+ return nextValue;
87
+ },
88
+ async read() {
89
+ const value = await readFile(filePath, "utf8");
90
+ return JSON.parse(value);
91
+ },
92
+ async write(value) {
93
+ await beforeMutate?.();
94
+ await mkdir(path.dirname(filePath), { recursive: true });
95
+ await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
96
+ }
97
+ };
98
+ }
99
+ //#endregion
100
+ //#region src/plugin/plugin.ts
101
+ function createPluginHandle(client, id) {
102
+ async function resolveDataPath() {
103
+ const vaultPath = await client.vaultPath();
104
+ return path.join(vaultPath, ".obsidian", "plugins", id, "data.json");
105
+ }
106
+ return {
107
+ data() {
108
+ return {
109
+ async patch(updater) {
110
+ const dataPath = await resolveDataPath();
111
+ return createJsonFile(dataPath, () => getClientInternals(client).snapshotFileOnce(dataPath)).patch(updater);
112
+ },
113
+ async read() {
114
+ return createJsonFile(await resolveDataPath()).read();
115
+ },
116
+ async write(value) {
117
+ const dataPath = await resolveDataPath();
118
+ await createJsonFile(dataPath, () => getClientInternals(client).snapshotFileOnce(dataPath)).write(value);
119
+ }
120
+ };
121
+ },
122
+ async dataPath() {
123
+ return resolveDataPath();
124
+ },
125
+ async disable(options = {}) {
126
+ await client.exec("plugin:disable", {
127
+ filter: options.filter,
128
+ id
129
+ });
130
+ },
131
+ async enable(options = {}) {
132
+ await client.exec("plugin:enable", {
133
+ filter: options.filter,
134
+ id
135
+ });
136
+ },
137
+ id,
138
+ async isEnabled() {
139
+ const output = await client.execText("plugin", { id }, { allowNonZeroExit: true });
140
+ return /enabled\s+true/i.test(output);
141
+ },
142
+ async reload() {
143
+ await client.exec("plugin:reload", { id });
144
+ },
145
+ async restoreData() {
146
+ await getClientInternals(client).restoreFile(await resolveDataPath());
147
+ }
148
+ };
149
+ }
150
+ //#endregion
151
+ //#region src/core/errors.ts
152
+ var ObsidianCommandError = class extends Error {
153
+ result;
154
+ constructor(message, result) {
155
+ super(message);
156
+ this.name = "ObsidianCommandError";
157
+ this.result = result;
158
+ }
159
+ };
160
+ var WaitForTimeoutError = class extends Error {
161
+ causeError;
162
+ constructor(message, causeError) {
163
+ super(message);
164
+ this.name = "WaitForTimeoutError";
165
+ this.causeError = causeError;
166
+ }
167
+ };
168
+ //#endregion
169
+ //#region src/core/transport.ts
170
+ const DEFAULT_TIMEOUT_MS$1 = 3e4;
171
+ const executeCommand = async ({ allowNonZeroExit = false, argv, bin, cwd, env, timeoutMs = DEFAULT_TIMEOUT_MS$1 }) => {
172
+ const child = spawn(bin, argv, {
173
+ cwd,
174
+ env,
175
+ stdio: [
176
+ "ignore",
177
+ "pipe",
178
+ "pipe"
179
+ ]
180
+ });
181
+ const stdoutChunks = [];
182
+ const stderrChunks = [];
183
+ child.stdout.on("data", (chunk) => {
184
+ stdoutChunks.push(Buffer.from(chunk));
185
+ });
186
+ child.stderr.on("data", (chunk) => {
187
+ stderrChunks.push(Buffer.from(chunk));
188
+ });
189
+ const exitCode = await new Promise((resolve, reject) => {
190
+ const timer = setTimeout(() => {
191
+ child.kill("SIGTERM");
192
+ reject(/* @__PURE__ */ new Error(`Command timed out after ${timeoutMs}ms: ${bin} ${argv.join(" ")}`));
193
+ }, timeoutMs);
194
+ child.on("error", (error) => {
195
+ clearTimeout(timer);
196
+ reject(error);
197
+ });
198
+ child.on("close", (code) => {
199
+ clearTimeout(timer);
200
+ resolve(code ?? 0);
201
+ });
202
+ });
203
+ const result = {
204
+ argv,
205
+ command: bin,
206
+ exitCode,
207
+ stderr: Buffer.concat(stderrChunks).toString("utf8"),
208
+ stdout: Buffer.concat(stdoutChunks).toString("utf8")
209
+ };
210
+ if (exitCode !== 0 && !allowNonZeroExit) throw new ObsidianCommandError(`Obsidian command failed with exit code ${exitCode}: ${bin} ${argv.join(" ")}`, result);
211
+ return result;
212
+ };
213
+ //#endregion
214
+ //#region src/core/wait.ts
215
+ const DEFAULT_INTERVAL_MS = 100;
216
+ const DEFAULT_TIMEOUT_MS = 5e3;
217
+ async function waitForValue(fn, options = {}) {
218
+ const intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;
219
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
220
+ const startTime = Date.now();
221
+ let lastError;
222
+ while (Date.now() - startTime <= timeoutMs) {
223
+ try {
224
+ const result = await fn();
225
+ if (result !== false && result !== null && result !== void 0) return result;
226
+ } catch (error) {
227
+ lastError = error;
228
+ }
229
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
230
+ }
231
+ throw new WaitForTimeoutError(`Timed out waiting for ${options.message ?? "condition"} after ${timeoutMs}ms.`, lastError);
232
+ }
233
+ //#endregion
234
+ //#region src/core/client.ts
235
+ function createObsidianClient(options) {
236
+ const transport = options.transport ?? executeCommand;
237
+ const waitDefaults = {
238
+ intervalMs: options.intervalMs,
239
+ timeoutMs: options.timeoutMs
240
+ };
241
+ const restoreManager = createRestoreManager(async (filePath) => {
242
+ const { readFile } = await import("node:fs/promises");
243
+ return readFile(filePath, "utf8");
244
+ });
245
+ let cachedVaultPath;
246
+ const client = {};
247
+ const app = {
248
+ async reload(execOptions = {}) {
249
+ await client.exec("reload", {}, execOptions);
250
+ },
251
+ async restart({ readyOptions, waitUntilReady = true, ...execOptions } = {}) {
252
+ await client.exec("restart", {}, execOptions);
253
+ if (waitUntilReady) await app.waitUntilReady(readyOptions);
254
+ },
255
+ version(execOptions = {}) {
256
+ return client.execText("version", {}, execOptions);
257
+ },
258
+ async waitUntilReady(waitOptions) {
259
+ await client.waitFor(async () => {
260
+ try {
261
+ await client.vaultPath();
262
+ await client.commands();
263
+ return true;
264
+ } catch {
265
+ return false;
266
+ }
267
+ }, waitOptions);
268
+ }
269
+ };
270
+ Object.assign(client, {
271
+ app,
272
+ bin: options.bin ?? "obsidian",
273
+ dev: {
274
+ async dom(options, execOptions = {}) {
275
+ const output = await client.execText("dev:dom", {
276
+ all: options.all,
277
+ attr: options.attr,
278
+ css: options.css,
279
+ inner: options.inner,
280
+ selector: options.selector,
281
+ text: options.text,
282
+ total: options.total
283
+ }, execOptions);
284
+ if (options.total) return Number.parseInt(output, 10);
285
+ if (options.all) return output ? output.split(/\r?\n/u).filter(Boolean) : [];
286
+ return output;
287
+ },
288
+ async eval(code, execOptions = {}) {
289
+ return parseDevEvalOutput(await client.execText("eval", { code }, execOptions));
290
+ },
291
+ async screenshot(targetPath, execOptions = {}) {
292
+ await client.exec("dev:screenshot", { path: targetPath }, execOptions);
293
+ return targetPath;
294
+ }
295
+ },
296
+ command(id) {
297
+ return {
298
+ async exists(commandOptions = {}) {
299
+ return (await client.commands({
300
+ ...commandOptions,
301
+ filter: commandOptions.filter ?? id
302
+ })).includes(id);
303
+ },
304
+ id,
305
+ async run(execOptions = {}) {
306
+ await client.exec("command", { id }, execOptions);
307
+ }
308
+ };
309
+ },
310
+ async commands(commandOptions = {}, execOptions = {}) {
311
+ return parseCommandIds(await client.execText("commands", { filter: commandOptions.filter }, execOptions));
312
+ },
313
+ exec(command, args = {}, execOptions = {}) {
314
+ return transport({
315
+ ...execOptions,
316
+ argv: buildCommandArgv(options.vault, command, args),
317
+ bin: this.bin
318
+ });
319
+ },
320
+ async execJson(command, args = {}, execOptions = {}) {
321
+ const output = await this.execText(command, args, execOptions);
322
+ return JSON.parse(output);
323
+ },
324
+ async execText(command, args = {}, execOptions = {}) {
325
+ return (await this.exec(command, args, execOptions)).stdout.trimEnd();
326
+ },
327
+ async open(openOptions, execOptions = {}) {
328
+ await client.exec("open", {
329
+ file: openOptions.file,
330
+ newtab: openOptions.newTab,
331
+ path: openOptions.path
332
+ }, execOptions);
333
+ },
334
+ async openTab(tabOptions = {}, execOptions = {}) {
335
+ await client.exec("tab:open", {
336
+ file: tabOptions.file,
337
+ group: tabOptions.group,
338
+ view: tabOptions.view
339
+ }, execOptions);
340
+ },
341
+ plugin(id) {
342
+ return createPluginHandle(this, id);
343
+ },
344
+ async tabs(tabOptions = {}, execOptions = {}) {
345
+ return parseTabs(await client.execText("tabs", { ids: tabOptions.ids ?? true }, execOptions));
346
+ },
347
+ async vaultPath() {
348
+ if (!cachedVaultPath) cachedVaultPath = await this.execText("vault", { info: "path" });
349
+ return cachedVaultPath;
350
+ },
351
+ async verify() {
352
+ await transport({
353
+ argv: ["--help"],
354
+ bin: this.bin
355
+ });
356
+ await this.vaultPath();
357
+ },
358
+ vaultName: options.vault,
359
+ waitFor(fn, waitOptions) {
360
+ return waitForValue(fn, {
361
+ ...waitDefaults,
362
+ ...waitOptions
363
+ });
364
+ },
365
+ async workspace(workspaceOptions = {}, execOptions = {}) {
366
+ return parseWorkspace(await client.execText("workspace", { ids: workspaceOptions.ids ?? true }, execOptions));
367
+ }
368
+ });
369
+ attachClientInternals(client, restoreManager);
370
+ return client;
371
+ }
372
+ function parseCommandIds(output) {
373
+ return output.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean).map((line) => line.split(" ", 1)[0]?.trim() ?? "").filter(Boolean);
374
+ }
375
+ function parseDevEvalOutput(output) {
376
+ const normalized = output.startsWith("=> ") ? output.slice(3) : output;
377
+ try {
378
+ return JSON.parse(normalized);
379
+ } catch {
380
+ return normalized;
381
+ }
382
+ }
383
+ function parseTabs(output) {
384
+ return output.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean).map(parseTabLine);
385
+ }
386
+ function parseTabLine(line) {
387
+ const [descriptor, id] = line.split(" ");
388
+ const match = descriptor?.match(/^\[(.+?)\]\s+(.*)$/u);
389
+ if (!match) return {
390
+ id: id?.trim() || void 0,
391
+ title: descriptor?.trim() ?? "",
392
+ viewType: "unknown"
393
+ };
394
+ return {
395
+ id: id?.trim() || void 0,
396
+ title: match[2],
397
+ viewType: match[1]
398
+ };
399
+ }
400
+ function parseWorkspace(output) {
401
+ const roots = [];
402
+ const stack = [];
403
+ for (const rawLine of output.split(/\r?\n/u)) {
404
+ if (!rawLine.trim()) continue;
405
+ const depth = getWorkspaceDepth(rawLine);
406
+ const node = parseWorkspaceNode(rawLine);
407
+ while (stack.length > 0 && stack.at(-1).depth >= depth) stack.pop();
408
+ const parent = stack.at(-1)?.node;
409
+ if (parent) parent.children.push(node);
410
+ else roots.push(node);
411
+ stack.push({
412
+ depth,
413
+ node
414
+ });
415
+ }
416
+ return roots;
417
+ }
418
+ function getWorkspaceDepth(line) {
419
+ let depth = 0;
420
+ let remainder = line;
421
+ while (true) {
422
+ if (remainder.startsWith("│ ") || remainder.startsWith(" ") || remainder.startsWith("├── ") || remainder.startsWith("└── ")) {
423
+ depth += 1;
424
+ remainder = remainder.slice(4);
425
+ continue;
426
+ }
427
+ return depth;
428
+ }
429
+ }
430
+ function parseWorkspaceNode(line) {
431
+ let withoutTree = line;
432
+ while (true) {
433
+ if (withoutTree.startsWith("│ ") || withoutTree.startsWith(" ") || withoutTree.startsWith("├── ") || withoutTree.startsWith("└── ")) {
434
+ withoutTree = withoutTree.slice(4);
435
+ continue;
436
+ }
437
+ break;
438
+ }
439
+ withoutTree = withoutTree.trim();
440
+ const idMatch = withoutTree.match(/^(.*?)(?: \(([a-z0-9]+)\))?$/iu);
441
+ const content = idMatch?.[1]?.trim() ?? withoutTree;
442
+ const id = idMatch?.[2];
443
+ const leafMatch = content.match(/^\[(.+?)\]\s+(.*)$/u);
444
+ if (leafMatch) return {
445
+ children: [],
446
+ id,
447
+ label: leafMatch[2],
448
+ title: leafMatch[2],
449
+ viewType: leafMatch[1]
450
+ };
451
+ return {
452
+ children: [],
453
+ id,
454
+ label: content
455
+ };
456
+ }
457
+ //#endregion
458
+ //#region src/vault/vault.ts
459
+ function createVaultApi(options) {
460
+ const scopeRoot = normalizeScope(options.root);
461
+ return {
462
+ async delete(targetPath, deleteOptions = {}) {
463
+ await rm(await resolveFilesystemPath(options.obsidian, scopeRoot, targetPath), {
464
+ force: true,
465
+ recursive: true
466
+ });
467
+ if (deleteOptions.permanent === false) return;
468
+ },
469
+ async exists(targetPath) {
470
+ try {
471
+ await access(await resolveFilesystemPath(options.obsidian, scopeRoot, targetPath));
472
+ return true;
473
+ } catch {
474
+ return false;
475
+ }
476
+ },
477
+ json(targetPath) {
478
+ const jsonFile = {
479
+ async patch(updater) {
480
+ const currentValue = await jsonFile.read();
481
+ const draft = structuredClone(currentValue);
482
+ const nextValue = await updater(draft) ?? draft;
483
+ await jsonFile.write(nextValue);
484
+ return nextValue;
485
+ },
486
+ async read() {
487
+ const rawValue = await readFile(await resolveFilesystemPath(options.obsidian, scopeRoot, targetPath), "utf8");
488
+ return JSON.parse(rawValue);
489
+ },
490
+ async write(value) {
491
+ const resolvedPath = await resolveFilesystemPath(options.obsidian, scopeRoot, targetPath);
492
+ await mkdir(path.dirname(resolvedPath), { recursive: true });
493
+ await writeFile(resolvedPath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
494
+ }
495
+ };
496
+ return jsonFile;
497
+ },
498
+ async mkdir(targetPath) {
499
+ await mkdir(await resolveFilesystemPath(options.obsidian, scopeRoot, targetPath), { recursive: true });
500
+ },
501
+ async read(targetPath) {
502
+ return readFile(await resolveFilesystemPath(options.obsidian, scopeRoot, targetPath), "utf8");
503
+ },
504
+ async waitForExists(targetPath, waitOptions) {
505
+ await options.obsidian.waitFor(async () => await this.exists(targetPath) ? true : false, {
506
+ message: `vault path "${resolveVaultPath(scopeRoot, targetPath)}" to exist`,
507
+ ...waitOptions
508
+ });
509
+ },
510
+ async waitForMissing(targetPath, waitOptions) {
511
+ await options.obsidian.waitFor(async () => await this.exists(targetPath) ? false : true, {
512
+ message: `vault path "${resolveVaultPath(scopeRoot, targetPath)}" to be removed`,
513
+ ...waitOptions
514
+ });
515
+ },
516
+ async write(targetPath, content) {
517
+ const resolvedPath = await resolveFilesystemPath(options.obsidian, scopeRoot, targetPath);
518
+ await mkdir(path.dirname(resolvedPath), { recursive: true });
519
+ await writeFile(resolvedPath, content, "utf8");
520
+ }
521
+ };
522
+ }
523
+ function normalizeScope(scope) {
524
+ if (!scope || scope === ".") return "";
525
+ return scope.replace(/^\/+|\/+$/g, "");
526
+ }
527
+ function resolveVaultPath(scopeRoot, targetPath) {
528
+ if (!targetPath || targetPath === ".") return scopeRoot;
529
+ return scopeRoot ? posix.join(scopeRoot, targetPath) : posix.normalize(targetPath);
530
+ }
531
+ async function resolveFilesystemPath(obsidian, scopeRoot, targetPath) {
532
+ const vaultPath = await obsidian.vaultPath();
533
+ const relativePath = resolveVaultPath(scopeRoot, targetPath).split("/").filter(Boolean);
534
+ const resolvedPath = path.resolve(vaultPath, ...relativePath);
535
+ const normalizedVaultPath = path.resolve(vaultPath);
536
+ if (resolvedPath !== normalizedVaultPath && !resolvedPath.startsWith(`${normalizedVaultPath}${path.sep}`)) throw new Error(`Resolved path escapes the vault root: ${targetPath}`);
537
+ return resolvedPath;
538
+ }
539
+ //#endregion
540
+ //#region src/vault/sandbox.ts
541
+ async function createSandboxApi(options) {
542
+ const root = posix.join(options.sandboxRoot, `${sanitizeSegment(options.testName)}-${randomUUID().slice(0, 8)}`);
543
+ const vault = createVaultApi({
544
+ obsidian: options.obsidian,
545
+ root
546
+ });
547
+ await vault.mkdir(".");
548
+ return {
549
+ ...vault,
550
+ async cleanup() {
551
+ await vault.delete(".", { permanent: true });
552
+ },
553
+ path(...segments) {
554
+ return posix.join(root, ...segments);
555
+ },
556
+ root
557
+ };
558
+ }
559
+ function sanitizeSegment(value) {
560
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "test";
561
+ }
562
+ //#endregion
563
+ export { getClientInternals as i, createVaultApi as n, createObsidianClient as r, createSandboxApi as t };
564
+
565
+ //# sourceMappingURL=sandbox-BhesE1S4.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox-BhesE1S4.mjs","names":["DEFAULT_TIMEOUT_MS","pathPosix","pathPosix"],"sources":["../src/core/args.ts","../src/core/internals.ts","../src/vault/json-file.ts","../src/plugin/plugin.ts","../src/core/errors.ts","../src/core/transport.ts","../src/core/wait.ts","../src/core/client.ts","../src/vault/vault.ts","../src/vault/sandbox.ts"],"sourcesContent":["import type { ObsidianArg } from \"./types\";\n\nexport function buildCommandArgv(\n vaultName: string,\n command: string,\n args: Record<string, ObsidianArg> = {},\n): string[] {\n const argv = [`vault=${vaultName}`, command];\n\n for (const [key, value] of Object.entries(args)) {\n if (value === false || value === null || value === undefined) {\n continue;\n }\n\n if (value === true) {\n argv.push(key);\n continue;\n }\n\n argv.push(`${key}=${String(value)}`);\n }\n\n return argv;\n}\n","import { rm, writeFile } from \"node:fs/promises\";\n\nimport type { ObsidianClient } from \"./types\";\n\ninterface SnapshotEntry {\n exists: boolean;\n value: string;\n}\n\ninterface ClientInternals {\n restoreAll(): Promise<void>;\n restoreFile(filePath: string): Promise<void>;\n snapshotFileOnce(filePath: string): Promise<void>;\n}\n\nconst clientInternals = new WeakMap<ObsidianClient, ClientInternals>();\n\nexport function attachClientInternals(client: ObsidianClient, internals: ClientInternals): void {\n clientInternals.set(client, internals);\n}\n\nexport function getClientInternals(client: ObsidianClient): ClientInternals {\n const internals = clientInternals.get(client);\n\n if (!internals) {\n throw new Error(\"Missing obsidian client internals.\");\n }\n\n return internals;\n}\n\nexport function createRestoreManager(readFile: (filePath: string) => Promise<string>) {\n const snapshots = new Map<string, SnapshotEntry>();\n\n return {\n async restoreAll() {\n const entries = [...snapshots.entries()].reverse();\n\n for (const [filePath, snapshot] of entries) {\n await restoreSnapshot(filePath, snapshot);\n }\n\n snapshots.clear();\n },\n async restoreFile(filePath: string) {\n const snapshot = snapshots.get(filePath);\n\n if (!snapshot) {\n return;\n }\n\n await restoreSnapshot(filePath, snapshot);\n snapshots.delete(filePath);\n },\n async snapshotFileOnce(filePath: string) {\n if (snapshots.has(filePath)) {\n return;\n }\n\n try {\n snapshots.set(filePath, {\n exists: true,\n value: await readFile(filePath),\n });\n } catch (error) {\n if (isMissingFileError(error)) {\n snapshots.set(filePath, {\n exists: false,\n value: \"\",\n });\n return;\n }\n\n throw error;\n }\n },\n };\n}\n\nasync function restoreSnapshot(filePath: string, snapshot: SnapshotEntry): Promise<void> {\n if (snapshot.exists) {\n await writeFile(filePath, snapshot.value, \"utf8\");\n return;\n }\n\n await rm(filePath, { force: true, recursive: true });\n}\n\nfunction isMissingFileError(error: unknown): error is NodeJS.ErrnoException {\n return Boolean(error && typeof error === \"object\" && \"code\" in error && error.code === \"ENOENT\");\n}\n","import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport type { JsonFile, JsonFileUpdater } from \"../core/types\";\n\nexport function createJsonFile<T = unknown>(\n filePath: string,\n beforeMutate?: () => Promise<void>,\n): JsonFile<T> {\n return {\n async patch(updater: JsonFileUpdater<T>) {\n await beforeMutate?.();\n\n const currentValue = await this.read();\n const draft = structuredClone(currentValue);\n const result = await updater(draft);\n const nextValue = result ?? draft;\n\n await this.write(nextValue);\n\n return nextValue;\n },\n async read() {\n const value = await readFile(filePath, \"utf8\");\n return JSON.parse(value) as T;\n },\n async write(value: T) {\n await beforeMutate?.();\n await mkdir(path.dirname(filePath), { recursive: true });\n await writeFile(filePath, `${JSON.stringify(value, null, 2)}\\n`, \"utf8\");\n },\n };\n}\n","import path from \"node:path\";\n\nimport { getClientInternals } from \"../core/internals\";\nimport type { JsonFile, ObsidianClient, PluginHandle, PluginToggleOptions } from \"../core/types\";\nimport { createJsonFile } from \"../vault/json-file\";\n\nexport function createPluginHandle(client: ObsidianClient, id: string): PluginHandle {\n async function resolveDataPath() {\n const vaultPath = await client.vaultPath();\n return path.join(vaultPath, \".obsidian\", \"plugins\", id, \"data.json\");\n }\n\n return {\n data<T = unknown>(): JsonFile<T> {\n return {\n async patch(updater) {\n const dataPath = await resolveDataPath();\n return createJsonFile<T>(dataPath, () =>\n getClientInternals(client).snapshotFileOnce(dataPath),\n ).patch(updater);\n },\n async read() {\n const dataPath = await resolveDataPath();\n return createJsonFile<T>(dataPath).read();\n },\n async write(value) {\n const dataPath = await resolveDataPath();\n await createJsonFile<T>(dataPath, () =>\n getClientInternals(client).snapshotFileOnce(dataPath),\n ).write(value);\n },\n };\n },\n async dataPath() {\n return resolveDataPath();\n },\n async disable(options: PluginToggleOptions = {}) {\n await client.exec(\"plugin:disable\", {\n filter: options.filter,\n id,\n });\n },\n async enable(options: PluginToggleOptions = {}) {\n await client.exec(\"plugin:enable\", {\n filter: options.filter,\n id,\n });\n },\n id,\n async isEnabled() {\n const output = await client.execText(\"plugin\", { id }, { allowNonZeroExit: true });\n return /enabled\\s+true/i.test(output);\n },\n async reload() {\n await client.exec(\"plugin:reload\", { id });\n },\n async restoreData() {\n await getClientInternals(client).restoreFile(await resolveDataPath());\n },\n };\n}\n","import type { ExecResult } from \"./types\";\n\nexport class ObsidianCommandError extends Error {\n readonly result: ExecResult;\n\n constructor(message: string, result: ExecResult) {\n super(message);\n this.name = \"ObsidianCommandError\";\n this.result = result;\n }\n}\n\nexport class WaitForTimeoutError extends Error {\n readonly causeError?: unknown;\n\n constructor(message: string, causeError?: unknown) {\n super(message);\n this.name = \"WaitForTimeoutError\";\n this.causeError = causeError;\n }\n}\n","import { spawn } from \"node:child_process\";\n\nimport { ObsidianCommandError } from \"./errors\";\nimport type { CommandTransport, ExecuteRequest, ExecResult } from \"./types\";\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\nexport const executeCommand: CommandTransport = async ({\n allowNonZeroExit = false,\n argv,\n bin,\n cwd,\n env,\n timeoutMs = DEFAULT_TIMEOUT_MS,\n}: ExecuteRequest): Promise<ExecResult> => {\n const child = spawn(bin, argv, {\n cwd,\n env,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n\n child.stdout.on(\"data\", (chunk) => {\n stdoutChunks.push(Buffer.from(chunk));\n });\n\n child.stderr.on(\"data\", (chunk) => {\n stderrChunks.push(Buffer.from(chunk));\n });\n\n const exitCode = await new Promise<number>((resolve, reject) => {\n const timer = setTimeout(() => {\n child.kill(\"SIGTERM\");\n reject(new Error(`Command timed out after ${timeoutMs}ms: ${bin} ${argv.join(\" \")}`));\n }, timeoutMs);\n\n child.on(\"error\", (error) => {\n clearTimeout(timer);\n reject(error);\n });\n\n child.on(\"close\", (code) => {\n clearTimeout(timer);\n resolve(code ?? 0);\n });\n });\n\n const result: ExecResult = {\n argv,\n command: bin,\n exitCode,\n stderr: Buffer.concat(stderrChunks).toString(\"utf8\"),\n stdout: Buffer.concat(stdoutChunks).toString(\"utf8\"),\n };\n\n if (exitCode !== 0 && !allowNonZeroExit) {\n throw new ObsidianCommandError(\n `Obsidian command failed with exit code ${exitCode}: ${bin} ${argv.join(\" \")}`,\n result,\n );\n }\n\n return result;\n};\n","import { WaitForTimeoutError } from \"./errors\";\nimport type { WaitForOptions } from \"./types\";\n\nconst DEFAULT_INTERVAL_MS = 100;\nconst DEFAULT_TIMEOUT_MS = 5_000;\n\nexport async function waitForValue<T>(\n fn: () => Promise<T | false | null | undefined> | T | false | null | undefined,\n options: WaitForOptions = {},\n): Promise<T> {\n const intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const startTime = Date.now();\n\n let lastError: unknown;\n\n while (Date.now() - startTime <= timeoutMs) {\n try {\n const result = await fn();\n if (result !== false && result !== null && result !== undefined) {\n return result;\n }\n } catch (error) {\n lastError = error;\n }\n\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n\n const label = options.message ?? \"condition\";\n throw new WaitForTimeoutError(`Timed out waiting for ${label} after ${timeoutMs}ms.`, lastError);\n}\n","import { buildCommandArgv } from \"./args\";\nimport { attachClientInternals, createRestoreManager } from \"./internals\";\nimport { createPluginHandle } from \"../plugin/plugin\";\nimport { executeCommand } from \"./transport\";\nimport type {\n CommandListOptions,\n CreateObsidianClientOptions,\n DevDomQueryOptions,\n DevDomResult,\n ExecOptions,\n ObsidianArg,\n ObsidianAppHandle,\n ObsidianCommandHandle,\n ObsidianClient,\n ObsidianDevHandle,\n OpenFileOptions,\n OpenTabOptions,\n RestartAppOptions,\n TabsOptions,\n WaitForOptions,\n WorkspaceNode,\n WorkspaceOptions,\n WorkspaceTab,\n} from \"./types\";\nimport { waitForValue } from \"./wait\";\n\nexport function createObsidianClient(options: CreateObsidianClientOptions): ObsidianClient {\n const transport = options.transport ?? executeCommand;\n const waitDefaults = {\n intervalMs: options.intervalMs,\n timeoutMs: options.timeoutMs,\n };\n\n const restoreManager = createRestoreManager(async (filePath) => {\n const { readFile } = await import(\"node:fs/promises\");\n return readFile(filePath, \"utf8\");\n });\n\n let cachedVaultPath: string | undefined;\n\n const client = {} as ObsidianClient;\n\n const app: ObsidianAppHandle = {\n async reload(execOptions: ExecOptions = {}) {\n await client.exec(\"reload\", {}, execOptions);\n },\n async restart({\n readyOptions,\n waitUntilReady = true,\n ...execOptions\n }: RestartAppOptions & ExecOptions = {}) {\n await client.exec(\"restart\", {}, execOptions);\n\n if (waitUntilReady) {\n await app.waitUntilReady(readyOptions);\n }\n },\n version(execOptions: ExecOptions = {}) {\n return client.execText(\"version\", {}, execOptions);\n },\n async waitUntilReady(waitOptions?: WaitForOptions) {\n await client.waitFor(async () => {\n try {\n await client.vaultPath();\n await client.commands();\n return true;\n } catch {\n return false;\n }\n }, waitOptions);\n },\n };\n\n const dev: ObsidianDevHandle = {\n async dom(options: DevDomQueryOptions, execOptions: ExecOptions = {}): Promise<DevDomResult> {\n const output = await client.execText(\n \"dev:dom\",\n {\n all: options.all,\n attr: options.attr,\n css: options.css,\n inner: options.inner,\n selector: options.selector,\n text: options.text,\n total: options.total,\n },\n execOptions,\n );\n\n if (options.total) {\n return Number.parseInt(output, 10);\n }\n\n if (options.all) {\n return output ? output.split(/\\r?\\n/u).filter(Boolean) : [];\n }\n\n return output;\n },\n async eval<T = unknown>(code: string, execOptions: ExecOptions = {}) {\n const output = await client.execText(\n \"eval\",\n {\n code,\n },\n execOptions,\n );\n return parseDevEvalOutput<T>(output);\n },\n async screenshot(targetPath: string, execOptions: ExecOptions = {}) {\n await client.exec(\n \"dev:screenshot\",\n {\n path: targetPath,\n },\n execOptions,\n );\n\n return targetPath;\n },\n };\n\n Object.assign(client, {\n app,\n bin: options.bin ?? \"obsidian\",\n dev,\n command(id: string): ObsidianCommandHandle {\n return {\n async exists(commandOptions: CommandListOptions = {}) {\n const commands = await client.commands({\n ...commandOptions,\n filter: commandOptions.filter ?? id,\n });\n\n return commands.includes(id);\n },\n id,\n async run(execOptions: ExecOptions = {}) {\n await client.exec(\"command\", { id }, execOptions);\n },\n };\n },\n async commands(\n commandOptions: CommandListOptions = {},\n execOptions: ExecOptions = {},\n ): Promise<string[]> {\n const output = await client.execText(\n \"commands\",\n {\n filter: commandOptions.filter,\n },\n execOptions,\n );\n return parseCommandIds(output);\n },\n exec(command: string, args: Record<string, ObsidianArg> = {}, execOptions: ExecOptions = {}) {\n return transport({\n ...execOptions,\n argv: buildCommandArgv(options.vault, command, args),\n bin: this.bin,\n });\n },\n async execJson<T = unknown>(\n command: string,\n args: Record<string, ObsidianArg> = {},\n execOptions: ExecOptions = {},\n ) {\n const output = await this.execText(command, args, execOptions);\n return JSON.parse(output) as T;\n },\n async execText(\n command: string,\n args: Record<string, ObsidianArg> = {},\n execOptions: ExecOptions = {},\n ) {\n const result = await this.exec(command, args, execOptions);\n return result.stdout.trimEnd();\n },\n async open(openOptions: OpenFileOptions, execOptions: ExecOptions = {}) {\n await client.exec(\n \"open\",\n {\n file: openOptions.file,\n newtab: openOptions.newTab,\n path: openOptions.path,\n },\n execOptions,\n );\n },\n async openTab(tabOptions: OpenTabOptions = {}, execOptions: ExecOptions = {}) {\n await client.exec(\n \"tab:open\",\n {\n file: tabOptions.file,\n group: tabOptions.group,\n view: tabOptions.view,\n },\n execOptions,\n );\n },\n plugin(id: string) {\n return createPluginHandle(this, id);\n },\n async tabs(\n tabOptions: TabsOptions = {},\n execOptions: ExecOptions = {},\n ): Promise<WorkspaceTab[]> {\n const output = await client.execText(\n \"tabs\",\n {\n ids: tabOptions.ids ?? true,\n },\n execOptions,\n );\n return parseTabs(output);\n },\n async vaultPath() {\n if (!cachedVaultPath) {\n cachedVaultPath = await this.execText(\"vault\", { info: \"path\" });\n }\n\n return cachedVaultPath;\n },\n async verify() {\n await transport({\n argv: [\"--help\"],\n bin: this.bin,\n });\n\n await this.vaultPath();\n },\n vaultName: options.vault,\n waitFor<T>(\n fn: () => Promise<T | false | null | undefined> | T | false | null | undefined,\n waitOptions?: WaitForOptions,\n ) {\n return waitForValue(fn, {\n ...waitDefaults,\n ...waitOptions,\n });\n },\n async workspace(\n workspaceOptions: WorkspaceOptions = {},\n execOptions: ExecOptions = {},\n ): Promise<WorkspaceNode[]> {\n const output = await client.execText(\n \"workspace\",\n {\n ids: workspaceOptions.ids ?? true,\n },\n execOptions,\n );\n return parseWorkspace(output);\n },\n });\n\n attachClientInternals(client, restoreManager);\n\n return client;\n}\n\nfunction parseCommandIds(output: string): string[] {\n return output\n .split(/\\r?\\n/u)\n .map((line) => line.trim())\n .filter(Boolean)\n .map((line) => line.split(\"\\t\", 1)[0]?.trim() ?? \"\")\n .filter(Boolean);\n}\n\nfunction parseDevEvalOutput<T>(output: string): T {\n const normalized = output.startsWith(\"=> \") ? output.slice(3) : output;\n\n try {\n return JSON.parse(normalized) as T;\n } catch {\n return normalized as T;\n }\n}\n\nfunction parseTabs(output: string): WorkspaceTab[] {\n return output\n .split(/\\r?\\n/u)\n .map((line) => line.trim())\n .filter(Boolean)\n .map(parseTabLine);\n}\n\nfunction parseTabLine(line: string): WorkspaceTab {\n const [descriptor, id] = line.split(\"\\t\");\n const match = descriptor?.match(/^\\[(.+?)\\]\\s+(.*)$/u);\n\n if (!match) {\n return {\n id: id?.trim() || undefined,\n title: descriptor?.trim() ?? \"\",\n viewType: \"unknown\",\n };\n }\n\n return {\n id: id?.trim() || undefined,\n title: match[2]!,\n viewType: match[1]!,\n };\n}\n\nfunction parseWorkspace(output: string): WorkspaceNode[] {\n const roots: WorkspaceNode[] = [];\n const stack: Array<{ depth: number; node: WorkspaceNode }> = [];\n\n for (const rawLine of output.split(/\\r?\\n/u)) {\n if (!rawLine.trim()) {\n continue;\n }\n\n const depth = getWorkspaceDepth(rawLine);\n const node = parseWorkspaceNode(rawLine);\n\n while (stack.length > 0 && stack.at(-1)!.depth >= depth) {\n stack.pop();\n }\n\n const parent = stack.at(-1)?.node;\n\n if (parent) {\n parent.children.push(node);\n } else {\n roots.push(node);\n }\n\n stack.push({ depth, node });\n }\n\n return roots;\n}\n\nfunction getWorkspaceDepth(line: string): number {\n let depth = 0;\n let remainder = line;\n\n while (true) {\n if (\n remainder.startsWith(\"│ \") ||\n remainder.startsWith(\" \") ||\n remainder.startsWith(\"├── \") ||\n remainder.startsWith(\"└── \")\n ) {\n depth += 1;\n remainder = remainder.slice(4);\n continue;\n }\n\n return depth;\n }\n}\n\nfunction parseWorkspaceNode(line: string): WorkspaceNode {\n let withoutTree = line;\n\n while (true) {\n if (\n withoutTree.startsWith(\"│ \") ||\n withoutTree.startsWith(\" \") ||\n withoutTree.startsWith(\"├── \") ||\n withoutTree.startsWith(\"└── \")\n ) {\n withoutTree = withoutTree.slice(4);\n continue;\n }\n\n break;\n }\n\n withoutTree = withoutTree.trim();\n const idMatch = withoutTree.match(/^(.*?)(?: \\(([a-z0-9]+)\\))?$/iu);\n const content = idMatch?.[1]?.trim() ?? withoutTree;\n const id = idMatch?.[2];\n const leafMatch = content.match(/^\\[(.+?)\\]\\s+(.*)$/u);\n\n if (leafMatch) {\n return {\n children: [],\n id,\n label: leafMatch[2]!,\n title: leafMatch[2]!,\n viewType: leafMatch[1]!,\n };\n }\n\n return {\n children: [],\n id,\n label: content,\n };\n}\n","import { access, mkdir, readFile, rm, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { posix as pathPosix } from \"node:path\";\n\nimport type { DeleteOptions, JsonFile, ObsidianClient, VaultApi } from \"../core/types\";\n\ninterface CreateVaultApiOptions {\n obsidian: ObsidianClient;\n root?: string;\n}\n\nexport function createVaultApi(options: CreateVaultApiOptions): VaultApi {\n const scopeRoot = normalizeScope(options.root);\n\n return {\n async delete(targetPath, deleteOptions: DeleteOptions = {}) {\n const resolvedPath = await resolveFilesystemPath(options.obsidian, scopeRoot, targetPath);\n await rm(resolvedPath, {\n force: true,\n recursive: true,\n });\n\n if (deleteOptions.permanent === false) {\n return;\n }\n },\n async exists(targetPath) {\n try {\n const resolvedPath = await resolveFilesystemPath(options.obsidian, scopeRoot, targetPath);\n await access(resolvedPath);\n return true;\n } catch {\n return false;\n }\n },\n json<T = unknown>(targetPath: string) {\n const jsonFile: JsonFile<T> = {\n async patch(updater) {\n const currentValue = await jsonFile.read();\n const draft = structuredClone(currentValue);\n const result = await updater(draft);\n const nextValue = result ?? draft;\n\n await jsonFile.write(nextValue);\n\n return nextValue;\n },\n async read() {\n const resolvedPath = await resolveFilesystemPath(options.obsidian, scopeRoot, targetPath);\n const rawValue = await readFile(resolvedPath, \"utf8\");\n return JSON.parse(rawValue) as T;\n },\n async write(value) {\n const resolvedPath = await resolveFilesystemPath(options.obsidian, scopeRoot, targetPath);\n await mkdir(path.dirname(resolvedPath), { recursive: true });\n await writeFile(resolvedPath, `${JSON.stringify(value, null, 2)}\\n`, \"utf8\");\n },\n };\n\n return jsonFile;\n },\n async mkdir(targetPath) {\n const resolvedPath = await resolveFilesystemPath(options.obsidian, scopeRoot, targetPath);\n await mkdir(resolvedPath, { recursive: true });\n },\n async read(targetPath) {\n const resolvedPath = await resolveFilesystemPath(options.obsidian, scopeRoot, targetPath);\n return readFile(resolvedPath, \"utf8\");\n },\n async waitForExists(targetPath, waitOptions) {\n await options.obsidian.waitFor(async () => ((await this.exists(targetPath)) ? true : false), {\n message: `vault path \"${resolveVaultPath(scopeRoot, targetPath)}\" to exist`,\n ...waitOptions,\n });\n },\n async waitForMissing(targetPath, waitOptions) {\n await options.obsidian.waitFor(async () => ((await this.exists(targetPath)) ? false : true), {\n message: `vault path \"${resolveVaultPath(scopeRoot, targetPath)}\" to be removed`,\n ...waitOptions,\n });\n },\n async write(targetPath, content) {\n const resolvedPath = await resolveFilesystemPath(options.obsidian, scopeRoot, targetPath);\n await mkdir(path.dirname(resolvedPath), { recursive: true });\n await writeFile(resolvedPath, content, \"utf8\");\n },\n };\n}\n\nfunction normalizeScope(scope?: string): string {\n if (!scope || scope === \".\") {\n return \"\";\n }\n\n return scope.replace(/^\\/+|\\/+$/g, \"\");\n}\n\nfunction resolveVaultPath(scopeRoot: string, targetPath: string): string {\n if (!targetPath || targetPath === \".\") {\n return scopeRoot;\n }\n\n return scopeRoot ? pathPosix.join(scopeRoot, targetPath) : pathPosix.normalize(targetPath);\n}\n\nasync function resolveFilesystemPath(\n obsidian: ObsidianClient,\n scopeRoot: string,\n targetPath: string,\n): Promise<string> {\n const vaultPath = await obsidian.vaultPath();\n const scopedPath = resolveVaultPath(scopeRoot, targetPath);\n const relativePath = scopedPath.split(\"/\").filter(Boolean);\n const resolvedPath = path.resolve(vaultPath, ...relativePath);\n const normalizedVaultPath = path.resolve(vaultPath);\n\n if (\n resolvedPath !== normalizedVaultPath &&\n !resolvedPath.startsWith(`${normalizedVaultPath}${path.sep}`)\n ) {\n throw new Error(`Resolved path escapes the vault root: ${targetPath}`);\n }\n\n return resolvedPath;\n}\n","import { posix as pathPosix } from \"node:path\";\nimport { randomUUID } from \"node:crypto\";\n\nimport type { ObsidianClient, SandboxApi } from \"../core/types\";\nimport { createVaultApi } from \"./vault\";\n\ninterface CreateSandboxApiOptions {\n obsidian: ObsidianClient;\n sandboxRoot: string;\n testName: string;\n}\n\nexport async function createSandboxApi(options: CreateSandboxApiOptions): Promise<SandboxApi> {\n const root = pathPosix.join(\n options.sandboxRoot,\n `${sanitizeSegment(options.testName)}-${randomUUID().slice(0, 8)}`,\n );\n const vault = createVaultApi({\n obsidian: options.obsidian,\n root,\n });\n\n await vault.mkdir(\".\");\n\n return {\n ...vault,\n async cleanup() {\n await vault.delete(\".\", { permanent: true });\n },\n path(...segments: string[]) {\n return pathPosix.join(root, ...segments);\n },\n root,\n };\n}\n\nfunction sanitizeSegment(value: string): string {\n return (\n value\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 80) || \"test\"\n );\n}\n"],"mappings":"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"}