Apr 8, 2026 2.0 KB typescript of v4
1import { exec } from "child_process";
2import { Platform } from "obsidian";
3
4const XATTR_PREFIX = "com.sublimated.";
5const XATTR_SUFFIX = "#PS";
6
7export interface FileMetadata {
8 version: string | null;
9 commit: string | null;
10 url: string | null;
11 contentHash: string | null;
12 id: string | null;
13 createdBy: string | null;
14 modifiedBy: string | null;
15 modifiedByType: string | null;
16 syncStatus: string | null;
17 space: string | null;
18 branch: string | null;
19 modifiedAt: string | null;
20 visibility: string | null;
21}
22
23const ATTRS: (keyof FileMetadata)[] = [
24 "version",
25 "commit",
26 "url",
27 "contentHash",
28 "id",
29 "createdBy",
30 "modifiedBy",
31 "modifiedByType",
32 "syncStatus",
33 "space",
34 "branch",
35 "modifiedAt",
36];
37
38function attrToXattrName(attr: string): string {
39 // camelCase → kebab-case: contentHash → content-hash
40 const kebab = attr.replace(/[A-Z]/g, (c) => "-" + c.toLowerCase());
41 return `${XATTR_PREFIX}${kebab}${XATTR_SUFFIX}`;
42}
43
44function readXattr(path: string, xattrName: string): Promise<string | null> {
45 return new Promise((resolve) => {
46 exec(
47 `xattr -p ${JSON.stringify(xattrName)} ${JSON.stringify(path)}`,
48 { timeout: 2000 },
49 (err, stdout) => {
50 if (err) {
51 resolve(null);
52 return;
53 }
54 resolve(stdout.trim() || null);
55 }
56 );
57 });
58}
59
60export async function readFileMetadata(
61 path: string
62): Promise<FileMetadata | null> {
63 if (!Platform.isMacOS) return null;
64
65 const results = await Promise.all(
66 ATTRS.map((attr) => readXattr(path, attrToXattrName(attr)))
67 );
68
69 // If no xattrs found at all, this isn't a sublimated file
70 if (results.every((r) => r === null)) return null;
71
72 const meta: FileMetadata = {
73 version: null, commit: null, url: null, contentHash: null,
74 id: null, createdBy: null, modifiedBy: null, modifiedByType: null,
75 syncStatus: null, space: null, branch: null, modifiedAt: null,
76 };
77 ATTRS.forEach((attr, i) => {
78 meta[attr] = results[i];
79 });
80 return meta;
81}
82
83export function isMacOS(): boolean {
84 return Platform.isMacOS;
85}