6663a80 antonApr 8, 2026 api

create xattr.ts via macOS

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