@edcalderon/versioning 1.0.11 → 1.1.2
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 +46 -0
- package/README.md +50 -0
- package/dist/cli.js +3 -1
- package/dist/extensions/reentry-status/config-manager.d.ts +17 -0
- package/dist/extensions/reentry-status/config-manager.js +187 -0
- package/dist/extensions/reentry-status/constants.d.ts +6 -0
- package/dist/extensions/reentry-status/constants.js +9 -0
- package/dist/extensions/reentry-status/dirty-detection.d.ts +4 -0
- package/dist/extensions/reentry-status/dirty-detection.js +18 -0
- package/dist/extensions/reentry-status/file-manager.d.ts +29 -0
- package/dist/extensions/reentry-status/file-manager.js +144 -0
- package/dist/extensions/reentry-status/github-rest-client.d.ts +29 -0
- package/dist/extensions/reentry-status/github-rest-client.js +72 -0
- package/dist/extensions/reentry-status/github-sync-adapter.d.ts +39 -0
- package/dist/extensions/reentry-status/github-sync-adapter.js +80 -0
- package/dist/extensions/reentry-status/index.d.ts +14 -0
- package/dist/extensions/reentry-status/index.js +30 -0
- package/dist/extensions/reentry-status/models.d.ts +159 -0
- package/dist/extensions/reentry-status/models.js +3 -0
- package/dist/extensions/reentry-status/obsidian-cli-client.d.ts +15 -0
- package/dist/extensions/reentry-status/obsidian-cli-client.js +96 -0
- package/dist/extensions/reentry-status/obsidian-sync-adapter.d.ts +24 -0
- package/dist/extensions/reentry-status/obsidian-sync-adapter.js +80 -0
- package/dist/extensions/reentry-status/reentry-status-manager.d.ts +30 -0
- package/dist/extensions/reentry-status/reentry-status-manager.js +193 -0
- package/dist/extensions/reentry-status/roadmap-parser.d.ts +13 -0
- package/dist/extensions/reentry-status/roadmap-parser.js +32 -0
- package/dist/extensions/reentry-status/roadmap-renderer.d.ts +19 -0
- package/dist/extensions/reentry-status/roadmap-renderer.js +92 -0
- package/dist/extensions/reentry-status/status-renderer.d.ts +10 -0
- package/dist/extensions/reentry-status/status-renderer.js +176 -0
- package/dist/extensions/reentry-status-extension.d.ts +4 -0
- package/dist/extensions/reentry-status-extension.js +528 -0
- package/examples/reentry-status/README.md +18 -0
- package/examples/reentry-status/REENTRY.md.example +14 -0
- package/examples/reentry-status/ROADMAP.md.example +25 -0
- package/examples/reentry-status/reentry.status.v1.1.example.json +42 -0
- package/examples/reentry-status/versioning.config.reentryStatus.example.json +44 -0
- package/package.json +2 -1
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { GitHubConfig, ReentryStatus, SyncResult } from './models';
|
|
2
|
+
export interface GitHubIssue {
|
|
3
|
+
id: number;
|
|
4
|
+
url: string;
|
|
5
|
+
title: string;
|
|
6
|
+
body: string;
|
|
7
|
+
}
|
|
8
|
+
export interface GitHubClient {
|
|
9
|
+
findIssueByTitle(params: {
|
|
10
|
+
owner: string;
|
|
11
|
+
repo: string;
|
|
12
|
+
title: string;
|
|
13
|
+
}): Promise<GitHubIssue | null>;
|
|
14
|
+
createIssue(params: {
|
|
15
|
+
owner: string;
|
|
16
|
+
repo: string;
|
|
17
|
+
title: string;
|
|
18
|
+
body: string;
|
|
19
|
+
labels: string[];
|
|
20
|
+
assignees?: string[];
|
|
21
|
+
}): Promise<GitHubIssue>;
|
|
22
|
+
updateIssue(params: {
|
|
23
|
+
owner: string;
|
|
24
|
+
repo: string;
|
|
25
|
+
issueId: number;
|
|
26
|
+
title?: string;
|
|
27
|
+
body: string;
|
|
28
|
+
labels?: string[];
|
|
29
|
+
assignees?: string[];
|
|
30
|
+
}): Promise<GitHubIssue>;
|
|
31
|
+
}
|
|
32
|
+
export declare class GitHubSyncAdapter {
|
|
33
|
+
private readonly config;
|
|
34
|
+
private readonly client;
|
|
35
|
+
constructor(config: GitHubConfig, client: GitHubClient);
|
|
36
|
+
renderIssueBody(status: ReentryStatus, reentryMarkdown: string): string;
|
|
37
|
+
sync(status: ReentryStatus, reentryMarkdown: string): Promise<SyncResult>;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=github-sync-adapter.d.ts.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GitHubSyncAdapter = void 0;
|
|
4
|
+
const dirty_detection_1 = require("./dirty-detection");
|
|
5
|
+
class GitHubSyncAdapter {
|
|
6
|
+
constructor(config, client) {
|
|
7
|
+
this.config = config;
|
|
8
|
+
this.client = client;
|
|
9
|
+
}
|
|
10
|
+
renderIssueBody(status, reentryMarkdown) {
|
|
11
|
+
// Keep content stable; rely on upstream renderer for milestone + roadmap.
|
|
12
|
+
// Avoid adding timestamps.
|
|
13
|
+
const header = `# Re-entry Status (living)\n\n`;
|
|
14
|
+
return `${header}${reentryMarkdown}`;
|
|
15
|
+
}
|
|
16
|
+
async sync(status, reentryMarkdown) {
|
|
17
|
+
const started = Date.now();
|
|
18
|
+
const timestamp = new Date().toISOString();
|
|
19
|
+
const body = this.renderIssueBody(status, reentryMarkdown);
|
|
20
|
+
const bodyHash = (0, dirty_detection_1.sha256)(body);
|
|
21
|
+
const lastPublishedHash = status.syncMetadata.published?.githubIssueBodySha256;
|
|
22
|
+
if (lastPublishedHash && lastPublishedHash === bodyHash) {
|
|
23
|
+
return {
|
|
24
|
+
target: 'github',
|
|
25
|
+
success: true,
|
|
26
|
+
timestamp,
|
|
27
|
+
duration: Date.now() - started,
|
|
28
|
+
details: { skipped: true, reason: 'unchanged (hash)' }
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const existing = await this.client.findIssueByTitle({
|
|
32
|
+
owner: this.config.owner,
|
|
33
|
+
repo: this.config.repo,
|
|
34
|
+
title: this.config.issue.title
|
|
35
|
+
});
|
|
36
|
+
if (existing) {
|
|
37
|
+
if ((0, dirty_detection_1.bodiesEqual)(existing.body, body)) {
|
|
38
|
+
return {
|
|
39
|
+
target: 'github',
|
|
40
|
+
success: true,
|
|
41
|
+
timestamp,
|
|
42
|
+
duration: Date.now() - started,
|
|
43
|
+
details: { skipped: true, reason: 'unchanged (body)' }
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
await this.client.updateIssue({
|
|
47
|
+
owner: this.config.owner,
|
|
48
|
+
repo: this.config.repo,
|
|
49
|
+
issueId: existing.id,
|
|
50
|
+
body,
|
|
51
|
+
labels: this.config.issue.labels,
|
|
52
|
+
assignees: this.config.issue.assignees
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
target: 'github',
|
|
56
|
+
success: true,
|
|
57
|
+
timestamp,
|
|
58
|
+
duration: Date.now() - started,
|
|
59
|
+
details: { updated: true, issueId: existing.id, url: existing.url, bodyHash }
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const created = await this.client.createIssue({
|
|
63
|
+
owner: this.config.owner,
|
|
64
|
+
repo: this.config.repo,
|
|
65
|
+
title: this.config.issue.title,
|
|
66
|
+
body,
|
|
67
|
+
labels: this.config.issue.labels,
|
|
68
|
+
assignees: this.config.issue.assignees
|
|
69
|
+
});
|
|
70
|
+
return {
|
|
71
|
+
target: 'github',
|
|
72
|
+
success: true,
|
|
73
|
+
timestamp,
|
|
74
|
+
duration: Date.now() - started,
|
|
75
|
+
details: { created: true, issueId: created.id, url: created.url, bodyHash }
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
exports.GitHubSyncAdapter = GitHubSyncAdapter;
|
|
80
|
+
//# sourceMappingURL=github-sync-adapter.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from './constants';
|
|
2
|
+
export * from './config-manager';
|
|
3
|
+
export * from './dirty-detection';
|
|
4
|
+
export * from './file-manager';
|
|
5
|
+
export * from './github-sync-adapter';
|
|
6
|
+
export * from './github-rest-client';
|
|
7
|
+
export * from './models';
|
|
8
|
+
export * from './obsidian-sync-adapter';
|
|
9
|
+
export * from './obsidian-cli-client';
|
|
10
|
+
export * from './roadmap-parser';
|
|
11
|
+
export * from './roadmap-renderer';
|
|
12
|
+
export * from './reentry-status-manager';
|
|
13
|
+
export * from './status-renderer';
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./constants"), exports);
|
|
18
|
+
__exportStar(require("./config-manager"), exports);
|
|
19
|
+
__exportStar(require("./dirty-detection"), exports);
|
|
20
|
+
__exportStar(require("./file-manager"), exports);
|
|
21
|
+
__exportStar(require("./github-sync-adapter"), exports);
|
|
22
|
+
__exportStar(require("./github-rest-client"), exports);
|
|
23
|
+
__exportStar(require("./models"), exports);
|
|
24
|
+
__exportStar(require("./obsidian-sync-adapter"), exports);
|
|
25
|
+
__exportStar(require("./obsidian-cli-client"), exports);
|
|
26
|
+
__exportStar(require("./roadmap-parser"), exports);
|
|
27
|
+
__exportStar(require("./roadmap-renderer"), exports);
|
|
28
|
+
__exportStar(require("./reentry-status-manager"), exports);
|
|
29
|
+
__exportStar(require("./status-renderer"), exports);
|
|
30
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
export type ProjectPhase = 'planning' | 'development' | 'testing' | 'staging' | 'production' | 'maintenance';
|
|
2
|
+
export type SyncTarget = 'files' | 'github' | 'obsidian';
|
|
3
|
+
export type ReentrySchemaVersion = '1.0' | '1.1';
|
|
4
|
+
export interface ReentryMilestoneLink {
|
|
5
|
+
id: string;
|
|
6
|
+
title: string;
|
|
7
|
+
}
|
|
8
|
+
export interface UpdateContext {
|
|
9
|
+
trigger: 'manual' | 'postVersion' | 'postRelease' | 'auto';
|
|
10
|
+
command?: string;
|
|
11
|
+
options?: any;
|
|
12
|
+
gitInfo: {
|
|
13
|
+
branch: string;
|
|
14
|
+
commit: string;
|
|
15
|
+
author: string;
|
|
16
|
+
timestamp: string;
|
|
17
|
+
};
|
|
18
|
+
versioningInfo: {
|
|
19
|
+
versionType?: string;
|
|
20
|
+
oldVersion?: string;
|
|
21
|
+
newVersion?: string;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export interface Milestone {
|
|
25
|
+
id: string;
|
|
26
|
+
title: string;
|
|
27
|
+
description: string;
|
|
28
|
+
status: 'pending' | 'in-progress' | 'completed' | 'blocked';
|
|
29
|
+
dueDate?: string;
|
|
30
|
+
completedDate?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface Blocker {
|
|
33
|
+
id: string;
|
|
34
|
+
description: string;
|
|
35
|
+
severity: 'low' | 'medium' | 'high' | 'critical';
|
|
36
|
+
assignedTo?: string;
|
|
37
|
+
created: string;
|
|
38
|
+
resolved?: boolean;
|
|
39
|
+
resolutionDate?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface NextStep {
|
|
42
|
+
id: string;
|
|
43
|
+
description: string;
|
|
44
|
+
priority: number;
|
|
45
|
+
estimatedEffort?: string;
|
|
46
|
+
dependencies?: string[];
|
|
47
|
+
}
|
|
48
|
+
export interface Risk {
|
|
49
|
+
id: string;
|
|
50
|
+
description: string;
|
|
51
|
+
probability: 'low' | 'medium' | 'high';
|
|
52
|
+
impact: 'low' | 'medium' | 'high';
|
|
53
|
+
mitigation: string;
|
|
54
|
+
}
|
|
55
|
+
export interface Dependency {
|
|
56
|
+
id: string;
|
|
57
|
+
name: string;
|
|
58
|
+
type: 'internal' | 'external' | 'service';
|
|
59
|
+
status: 'healthy' | 'degraded' | 'down';
|
|
60
|
+
version?: string;
|
|
61
|
+
}
|
|
62
|
+
export interface ReentryStatus {
|
|
63
|
+
schemaVersion: ReentrySchemaVersion;
|
|
64
|
+
version: string;
|
|
65
|
+
lastUpdated: string;
|
|
66
|
+
updatedBy: string;
|
|
67
|
+
context: UpdateContext;
|
|
68
|
+
milestone: ReentryMilestoneLink | null;
|
|
69
|
+
roadmapFile: string;
|
|
70
|
+
currentPhase: ProjectPhase;
|
|
71
|
+
milestones: Milestone[];
|
|
72
|
+
blockers: Blocker[];
|
|
73
|
+
nextSteps: NextStep[];
|
|
74
|
+
risks: Risk[];
|
|
75
|
+
dependencies: Dependency[];
|
|
76
|
+
versioning: {
|
|
77
|
+
currentVersion: string;
|
|
78
|
+
previousVersion: string;
|
|
79
|
+
versionType: 'patch' | 'minor' | 'major';
|
|
80
|
+
releaseDate?: string;
|
|
81
|
+
};
|
|
82
|
+
syncMetadata: {
|
|
83
|
+
githubIssueId?: number;
|
|
84
|
+
githubIssueUrl?: string;
|
|
85
|
+
obsidianNotePath?: string;
|
|
86
|
+
lastSyncAttempt: string;
|
|
87
|
+
lastSuccessfulSync: string;
|
|
88
|
+
published?: {
|
|
89
|
+
githubIssueBodySha256?: string;
|
|
90
|
+
obsidianNoteBodySha256?: string;
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export interface GitHubConfig {
|
|
95
|
+
enabled: boolean;
|
|
96
|
+
owner: string;
|
|
97
|
+
repo: string;
|
|
98
|
+
issue: {
|
|
99
|
+
title: string;
|
|
100
|
+
labels: string[];
|
|
101
|
+
assignees?: string[];
|
|
102
|
+
template?: string;
|
|
103
|
+
};
|
|
104
|
+
auth: {
|
|
105
|
+
token: string;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
export interface ObsidianConfig {
|
|
109
|
+
enabled: boolean;
|
|
110
|
+
vaultPath: string;
|
|
111
|
+
notePath: string;
|
|
112
|
+
template?: string;
|
|
113
|
+
frontmatter?: Record<string, any>;
|
|
114
|
+
}
|
|
115
|
+
export interface CustomSection {
|
|
116
|
+
id: string;
|
|
117
|
+
title: string;
|
|
118
|
+
renderer: (status: ReentryStatus) => string;
|
|
119
|
+
}
|
|
120
|
+
export interface ReentryStatusConfig {
|
|
121
|
+
enabled: boolean;
|
|
122
|
+
autoSync: boolean;
|
|
123
|
+
failHard: boolean;
|
|
124
|
+
hooks?: {
|
|
125
|
+
postVersion?: boolean;
|
|
126
|
+
postRelease?: boolean;
|
|
127
|
+
};
|
|
128
|
+
files: {
|
|
129
|
+
jsonPath: string;
|
|
130
|
+
markdownPath: string;
|
|
131
|
+
};
|
|
132
|
+
github?: GitHubConfig;
|
|
133
|
+
obsidian?: ObsidianConfig;
|
|
134
|
+
template?: {
|
|
135
|
+
includeSections: string[];
|
|
136
|
+
excludeSections: string[];
|
|
137
|
+
customSections?: CustomSection[];
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
export interface SyncResult {
|
|
141
|
+
target: SyncTarget;
|
|
142
|
+
success: boolean;
|
|
143
|
+
timestamp: string;
|
|
144
|
+
duration: number;
|
|
145
|
+
details?: any;
|
|
146
|
+
error?: {
|
|
147
|
+
message: string;
|
|
148
|
+
code?: string;
|
|
149
|
+
recoverable: boolean;
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
export interface ErrorContext {
|
|
153
|
+
operation: string;
|
|
154
|
+
component: string;
|
|
155
|
+
severity: 'warning' | 'error' | 'fatal';
|
|
156
|
+
recoverable: boolean;
|
|
157
|
+
userAction?: string;
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=models.d.ts.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ObsidianClient, ObsidianNote } from './obsidian-sync-adapter';
|
|
2
|
+
export declare class ObsidianCliClient implements ObsidianClient {
|
|
3
|
+
getNote(params: {
|
|
4
|
+
vaultPath: string;
|
|
5
|
+
notePath: string;
|
|
6
|
+
}): Promise<ObsidianNote | null>;
|
|
7
|
+
upsertNote(params: {
|
|
8
|
+
vaultPath: string;
|
|
9
|
+
notePath: string;
|
|
10
|
+
content: string;
|
|
11
|
+
}): Promise<ObsidianNote>;
|
|
12
|
+
static isAvailable(): Promise<boolean>;
|
|
13
|
+
static normalizeVaultPath(vaultPath: string): string;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=obsidian-cli-client.d.ts.map
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ObsidianCliClient = void 0;
|
|
37
|
+
const child_process_1 = require("child_process");
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
function escapeCliValue(value) {
|
|
40
|
+
// Obsidian CLI expects newlines escaped as \n.
|
|
41
|
+
return value.replace(/\r\n/g, '\n').replace(/\n/g, '\\n');
|
|
42
|
+
}
|
|
43
|
+
async function runObsidian(args, cwd) {
|
|
44
|
+
return await new Promise((resolve, reject) => {
|
|
45
|
+
const child = (0, child_process_1.spawn)('obsidian', args, {
|
|
46
|
+
cwd,
|
|
47
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
48
|
+
});
|
|
49
|
+
let stdout = '';
|
|
50
|
+
let stderr = '';
|
|
51
|
+
child.stdout.on('data', (d) => (stdout += String(d)));
|
|
52
|
+
child.stderr.on('data', (d) => (stderr += String(d)));
|
|
53
|
+
child.on('error', (err) => reject(err));
|
|
54
|
+
child.on('close', (code) => {
|
|
55
|
+
if (code === 0)
|
|
56
|
+
return resolve(stdout);
|
|
57
|
+
reject(new Error(`obsidian CLI failed (code ${code}): ${stderr || stdout}`));
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
class ObsidianCliClient {
|
|
62
|
+
async getNote(params) {
|
|
63
|
+
const cwd = params.vaultPath;
|
|
64
|
+
const notePath = params.notePath;
|
|
65
|
+
try {
|
|
66
|
+
const output = await runObsidian([`read`, `path=${notePath}`], cwd);
|
|
67
|
+
// CLI prints the file contents.
|
|
68
|
+
return { path: notePath, content: output };
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async upsertNote(params) {
|
|
75
|
+
const cwd = params.vaultPath;
|
|
76
|
+
const notePath = params.notePath;
|
|
77
|
+
// Obsidian CLI `create` can overwrite with the `overwrite` flag.
|
|
78
|
+
const content = escapeCliValue(params.content);
|
|
79
|
+
await runObsidian([`create`, `path=${notePath}`, `content=${content}`, 'overwrite', 'silent'], cwd);
|
|
80
|
+
return { path: notePath, content: params.content };
|
|
81
|
+
}
|
|
82
|
+
static async isAvailable() {
|
|
83
|
+
try {
|
|
84
|
+
await runObsidian(['version']);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
static normalizeVaultPath(vaultPath) {
|
|
92
|
+
return path.resolve(vaultPath);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
exports.ObsidianCliClient = ObsidianCliClient;
|
|
96
|
+
//# sourceMappingURL=obsidian-cli-client.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ObsidianConfig, ReentryStatus, SyncResult } from './models';
|
|
2
|
+
export interface ObsidianNote {
|
|
3
|
+
path: string;
|
|
4
|
+
content: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ObsidianClient {
|
|
7
|
+
getNote(params: {
|
|
8
|
+
vaultPath: string;
|
|
9
|
+
notePath: string;
|
|
10
|
+
}): Promise<ObsidianNote | null>;
|
|
11
|
+
upsertNote(params: {
|
|
12
|
+
vaultPath: string;
|
|
13
|
+
notePath: string;
|
|
14
|
+
content: string;
|
|
15
|
+
}): Promise<ObsidianNote>;
|
|
16
|
+
}
|
|
17
|
+
export declare class ObsidianSyncAdapter {
|
|
18
|
+
private readonly config;
|
|
19
|
+
private readonly client;
|
|
20
|
+
constructor(config: ObsidianConfig, client: ObsidianClient);
|
|
21
|
+
renderNoteContent(status: ReentryStatus, reentryMarkdown: string): string;
|
|
22
|
+
sync(status: ReentryStatus, reentryMarkdown: string): Promise<SyncResult>;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=obsidian-sync-adapter.d.ts.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ObsidianSyncAdapter = void 0;
|
|
4
|
+
const dirty_detection_1 = require("./dirty-detection");
|
|
5
|
+
class ObsidianSyncAdapter {
|
|
6
|
+
constructor(config, client) {
|
|
7
|
+
this.config = config;
|
|
8
|
+
this.client = client;
|
|
9
|
+
}
|
|
10
|
+
renderNoteContent(status, reentryMarkdown) {
|
|
11
|
+
const fm = this.config.frontmatter;
|
|
12
|
+
if (!fm || Object.keys(fm).length === 0)
|
|
13
|
+
return reentryMarkdown;
|
|
14
|
+
const lines = ['---'];
|
|
15
|
+
for (const key of Object.keys(fm).sort()) {
|
|
16
|
+
const value = fm[key];
|
|
17
|
+
if (value === undefined)
|
|
18
|
+
continue;
|
|
19
|
+
if (Array.isArray(value)) {
|
|
20
|
+
lines.push(`${key}:`);
|
|
21
|
+
for (const item of value)
|
|
22
|
+
lines.push(` - ${String(item)}`);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (value && typeof value === 'object') {
|
|
26
|
+
// Keep it simple: embed JSON for nested objects.
|
|
27
|
+
lines.push(`${key}: ${JSON.stringify(value)}`);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
lines.push(`${key}: ${String(value)}`);
|
|
31
|
+
}
|
|
32
|
+
lines.push('---', '');
|
|
33
|
+
// Keep deterministic: do not add timestamps.
|
|
34
|
+
// Also include roadmap pointer and milestone info (already present in REENTRY.md).
|
|
35
|
+
return `${lines.join('\n')}${reentryMarkdown}`;
|
|
36
|
+
}
|
|
37
|
+
async sync(status, reentryMarkdown) {
|
|
38
|
+
const started = Date.now();
|
|
39
|
+
const timestamp = new Date().toISOString();
|
|
40
|
+
const content = this.renderNoteContent(status, reentryMarkdown);
|
|
41
|
+
const contentHash = (0, dirty_detection_1.sha256)(content);
|
|
42
|
+
const lastPublishedHash = status.syncMetadata.published?.obsidianNoteBodySha256;
|
|
43
|
+
if (lastPublishedHash && lastPublishedHash === contentHash) {
|
|
44
|
+
return {
|
|
45
|
+
target: 'obsidian',
|
|
46
|
+
success: true,
|
|
47
|
+
timestamp,
|
|
48
|
+
duration: Date.now() - started,
|
|
49
|
+
details: { skipped: true, reason: 'unchanged (hash)' }
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const existing = await this.client.getNote({
|
|
53
|
+
vaultPath: this.config.vaultPath,
|
|
54
|
+
notePath: this.config.notePath
|
|
55
|
+
});
|
|
56
|
+
if (existing && (0, dirty_detection_1.bodiesEqual)(existing.content, content)) {
|
|
57
|
+
return {
|
|
58
|
+
target: 'obsidian',
|
|
59
|
+
success: true,
|
|
60
|
+
timestamp,
|
|
61
|
+
duration: Date.now() - started,
|
|
62
|
+
details: { skipped: true, reason: 'unchanged (content)' }
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
await this.client.upsertNote({
|
|
66
|
+
vaultPath: this.config.vaultPath,
|
|
67
|
+
notePath: this.config.notePath,
|
|
68
|
+
content
|
|
69
|
+
});
|
|
70
|
+
return {
|
|
71
|
+
target: 'obsidian',
|
|
72
|
+
success: true,
|
|
73
|
+
timestamp,
|
|
74
|
+
duration: Date.now() - started,
|
|
75
|
+
details: { updated: true, notePath: this.config.notePath, contentHash }
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
exports.ObsidianSyncAdapter = ObsidianSyncAdapter;
|
|
80
|
+
//# sourceMappingURL=obsidian-sync-adapter.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { FileManager } from './file-manager';
|
|
2
|
+
import { GitHubConfig, ObsidianConfig, ReentryStatus, SyncResult, SyncTarget, UpdateContext } from './models';
|
|
3
|
+
export interface GitHubSyncer {
|
|
4
|
+
renderIssueBody(status: ReentryStatus, reentryMarkdown: string): string;
|
|
5
|
+
sync(status: ReentryStatus, reentryMarkdown: string): Promise<SyncResult>;
|
|
6
|
+
}
|
|
7
|
+
export interface ObsidianSyncer {
|
|
8
|
+
renderNoteContent(status: ReentryStatus, reentryMarkdown: string): string;
|
|
9
|
+
sync(status: ReentryStatus, reentryMarkdown: string): Promise<SyncResult>;
|
|
10
|
+
}
|
|
11
|
+
export interface ReentryStatusManagerOptions {
|
|
12
|
+
fileManager?: FileManager;
|
|
13
|
+
createGitHubSyncer?: (config: GitHubConfig) => GitHubSyncer;
|
|
14
|
+
createObsidianSyncer?: (config: ObsidianConfig) => ObsidianSyncer;
|
|
15
|
+
isObsidianAvailable?: () => Promise<boolean>;
|
|
16
|
+
}
|
|
17
|
+
export declare class ReentryStatusManager {
|
|
18
|
+
private readonly fileManager;
|
|
19
|
+
private readonly createGitHubSyncer?;
|
|
20
|
+
private readonly createObsidianSyncer?;
|
|
21
|
+
private readonly isObsidianAvailable?;
|
|
22
|
+
constructor(options?: ReentryStatusManagerOptions);
|
|
23
|
+
createInitialStatus(): ReentryStatus;
|
|
24
|
+
loadOrInit(rootConfig: any): Promise<ReentryStatus>;
|
|
25
|
+
updateStatus(rootConfig: any, updater: (current: ReentryStatus) => ReentryStatus): Promise<ReentryStatus>;
|
|
26
|
+
applyContext(rootConfig: any, context: UpdateContext): Promise<ReentryStatus>;
|
|
27
|
+
ensureRoadmapExists(rootConfig: any, status: ReentryStatus): Promise<void>;
|
|
28
|
+
syncAll(rootConfig: any, targets?: SyncTarget[]): Promise<SyncResult[]>;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=reentry-status-manager.d.ts.map
|