@cueloop/integration-obsidian 0.1.0-alpha.3 → 0.1.0-alpha.30

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/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@cueloop/integration-obsidian",
3
- "version": "0.1.0-alpha.3",
3
+ "version": "0.1.0-alpha.30",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": "./src/index.ts"
8
8
  },
9
9
  "dependencies": {
10
- "@cueloop/extension-api": "0.1.0-alpha.3",
11
- "@cueloop/schema": "0.1.0-alpha.3"
10
+ "@cueloop/extension-api": "0.1.0-alpha.30",
11
+ "@cueloop/schema": "0.1.0-alpha.30"
12
12
  },
13
13
  "publishConfig": {
14
14
  "access": "public"
@@ -24,5 +24,10 @@
24
24
  "type": "git",
25
25
  "url": "git+https://github.com/mmurakaru/cueloop.git",
26
26
  "directory": "packages/integrations/obsidian"
27
- }
28
- }
27
+ },
28
+ "homepage": "https://github.com/mmurakaru/cueloop#readme",
29
+ "bugs": {
30
+ "url": "https://github.com/mmurakaru/cueloop/issues"
31
+ },
32
+ "description": "Export reviewed cueloop plans into an Obsidian vault"
33
+ }
@@ -15,6 +15,7 @@ afterEach(() => {
15
15
 
16
16
  describe("detectVaults", () => {
17
17
  test("returns registered vault paths that exist on disk", () => {
18
+ // Arrange
18
19
  const alive = join(dir, "notes");
19
20
  mkdirSync(alive);
20
21
  const gone = join(dir, "deleted-vault");
@@ -28,6 +29,8 @@ describe("detectVaults", () => {
28
29
  },
29
30
  }),
30
31
  );
32
+
33
+ // Assert
31
34
  expect(detectVaults(configPath)).toEqual([alive]);
32
35
  });
33
36
 
package/src/detect.ts CHANGED
@@ -26,8 +26,8 @@ export function detectVaults(configPath: string = obsidianConfigPath()): string[
26
26
  vaults?: Record<string, { path?: string }>;
27
27
  };
28
28
  return Object.values(raw.vaults ?? {})
29
- .map((v) => v.path)
30
- .filter((p): p is string => typeof p === "string" && existsSync(p));
29
+ .map((vault) => vault.path)
30
+ .filter((path): path is string => typeof path === "string" && existsSync(path));
31
31
  } catch {
32
32
  // no Obsidian install, or an unreadable registry: no vaults
33
33
  return [];
@@ -44,8 +44,13 @@ function config(overrides: Partial<ObsidianConfig> = {}): ObsidianConfig {
44
44
 
45
45
  describe("exportSession", () => {
46
46
  test("writes the final plan with frontmatter into vault/folder", () => {
47
+ // Arrange
47
48
  const session = resolvedSession();
49
+
50
+ // Act
48
51
  const result = exportSession(session, config(), NOW);
52
+
53
+ // Assert
49
54
  expect(result.success).toBe(true);
50
55
  expect(result.path).toBe(join(vault, "cueloop", "2026-08-07 - Migration Plan.md"));
51
56
  const written = readFileSync(result.path!, "utf8");
@@ -57,40 +62,63 @@ describe("exportSession", () => {
57
62
  });
58
63
 
59
64
  test("the working copy wins over the submitted content", () => {
65
+ // Arrange
60
66
  const session = resolvedSession({ workingCopy: "# Migration Plan\n\nMove the store carefully.\n" });
67
+
68
+ // Act
61
69
  const result = exportSession(session, config(), NOW);
70
+
71
+ // Assert
62
72
  expect(readFileSync(result.path!, "utf8")).toContain("Move the store carefully.");
63
73
  });
64
74
 
65
75
  test("never overwrites: collisions get counting suffixes", () => {
76
+ // Arrange
66
77
  const session = resolvedSession();
78
+
79
+ // Act
67
80
  const first = exportSession(session, config(), NOW);
68
81
  const second = exportSession(session, config(), NOW);
69
82
  const third = exportSession(session, config(), NOW);
83
+
84
+ // Assert
70
85
  expect(second.path).toBe(join(vault, "cueloop", "2026-08-07 - Migration Plan 2.md"));
71
86
  expect(third.path).toBe(join(vault, "cueloop", "2026-08-07 - Migration Plan 3.md"));
72
87
  expect(existsSync(first.path!)).toBe(true);
73
88
  });
74
89
 
75
90
  test("no vault configured and none detected fails without writing", () => {
91
+ // Arrange
76
92
  const emptyConfigPath = join(vault, "obsidian.json");
93
+
94
+ // Act
77
95
  const result = exportSession(resolvedSession(), config({ vault: undefined, obsidianConfigPath: emptyConfigPath }), NOW);
96
+
97
+ // Assert
78
98
  expect(result.success).toBe(false);
79
99
  expect(result.error).toContain("no Obsidian vault");
80
100
  });
81
101
 
82
102
  test("a configured vault path that does not exist fails", () => {
103
+ // Act
83
104
  const result = exportSession(resolvedSession(), config({ vault: join(vault, "missing") }), NOW);
105
+
106
+ // Assert
84
107
  expect(result.success).toBe(false);
85
108
  expect(result.error).toContain("vault not found");
86
109
  });
87
110
 
88
111
  test("falls back to the first auto-detected vault", () => {
112
+ // Arrange
89
113
  const detected = join(vault, "detected-vault");
90
114
  mkdirSync(detected);
91
115
  const obsidianJson = join(vault, "obsidian.json");
92
116
  writeFileSync(obsidianJson, JSON.stringify({ vaults: { v1: { path: detected } } }));
117
+
118
+ // Act
93
119
  const result = exportSession(resolvedSession(), config({ vault: undefined, obsidianConfigPath: obsidianJson }), NOW);
120
+
121
+ // Assert
94
122
  expect(result.success).toBe(true);
95
123
  expect(result.path).toBe(join(detected, "cueloop", "2026-08-07 - Migration Plan.md"));
96
124
  });
@@ -98,12 +126,21 @@ describe("exportSession", () => {
98
126
 
99
127
  describe("obsidian extension", () => {
100
128
  test("registers the exporter through the extension API and exports a resolved session", async () => {
129
+ // Arrange
101
130
  const registry = new Registry();
131
+
132
+ // Act
102
133
  const record = await registry.load("obsidian", createObsidianExtension(config()));
134
+
135
+ // Assert
103
136
  expect(record.errors).toEqual([]);
104
137
  const exporter = record.exporters.get("obsidian");
105
138
  expect(exporter).toBeDefined();
139
+
140
+ // Act
106
141
  const result = await exporter!(resolvedSession());
142
+
143
+ // Assert
107
144
  expect(result.success).toBe(true);
108
145
  expect(existsSync(result.path!)).toBe(true);
109
146
  });
@@ -23,7 +23,10 @@ describe("titleFrom", () => {
23
23
  });
24
24
 
25
25
  test("caps the title at 50 characters", () => {
26
+ // Arrange
26
27
  const long = "# " + "word ".repeat(20);
28
+
29
+ // Assert
27
30
  expect(titleFrom(long).length).toBeLessThanOrEqual(50);
28
31
  expect(titleFrom(long).endsWith(" ")).toBe(false);
29
32
  });
@@ -54,10 +57,19 @@ describe("uniquePath", () => {
54
57
  });
55
58
 
56
59
  test("appends counting suffixes instead of overwriting", () => {
60
+ // Assert
57
61
  expect(uniquePath(dir, "plan")).toBe(join(dir, "plan.md"));
62
+
63
+ // Act
58
64
  writeFileSync(join(dir, "plan.md"), "");
65
+
66
+ // Assert
59
67
  expect(uniquePath(dir, "plan")).toBe(join(dir, "plan 2.md"));
68
+
69
+ // Act
60
70
  writeFileSync(join(dir, "plan 2.md"), "");
71
+
72
+ // Assert
61
73
  expect(uniquePath(dir, "plan")).toBe(join(dir, "plan 3.md"));
62
74
  });
63
75
  });
package/src/filename.ts CHANGED
@@ -32,7 +32,7 @@ export function titleFrom(content: string, fallback?: string): string {
32
32
  * run in the result becomes the configured separator.
33
33
  */
34
34
  export function formatFilename(format: string, title: string, date: Date, separator: Separator = "space"): string {
35
- const pad = (n: number) => String(n).padStart(2, "0");
35
+ const pad = (value: number) => String(value).padStart(2, "0");
36
36
  const name = format
37
37
  .replaceAll("{YYYY}", String(date.getFullYear()))
38
38
  .replaceAll("{MM}", pad(date.getMonth() + 1))