@applica-software-guru/sdd-core 1.3.4 → 1.4.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.
- package/dist/errors.d.ts +7 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +17 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +9 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -1
- package/dist/index.js.map +1 -1
- package/dist/prompt/apply-prompt-generator.d.ts +2 -1
- package/dist/prompt/apply-prompt-generator.d.ts.map +1 -1
- package/dist/prompt/apply-prompt-generator.js +51 -2
- package/dist/prompt/apply-prompt-generator.js.map +1 -1
- package/dist/prompt/draft-prompt-generator.d.ts +8 -0
- package/dist/prompt/draft-prompt-generator.d.ts.map +1 -0
- package/dist/prompt/draft-prompt-generator.js +59 -0
- package/dist/prompt/draft-prompt-generator.js.map +1 -0
- package/dist/remote/api-client.d.ts +38 -0
- package/dist/remote/api-client.d.ts.map +1 -0
- package/dist/remote/api-client.js +101 -0
- package/dist/remote/api-client.js.map +1 -0
- package/dist/remote/state.d.ts +4 -0
- package/dist/remote/state.d.ts.map +1 -0
- package/dist/remote/state.js +35 -0
- package/dist/remote/state.js.map +1 -0
- package/dist/remote/sync-engine.d.ts +7 -0
- package/dist/remote/sync-engine.d.ts.map +1 -0
- package/dist/remote/sync-engine.js +257 -0
- package/dist/remote/sync-engine.js.map +1 -0
- package/dist/remote/types.d.ts +93 -0
- package/dist/remote/types.d.ts.map +1 -0
- package/dist/remote/types.js +3 -0
- package/dist/remote/types.js.map +1 -0
- package/dist/sdd.d.ts +13 -0
- package/dist/sdd.d.ts.map +1 -1
- package/dist/sdd.js +99 -6
- package/dist/sdd.js.map +1 -1
- package/dist/types.d.ts +9 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/errors.ts +16 -0
- package/src/index.ts +23 -1
- package/src/prompt/apply-prompt-generator.ts +61 -2
- package/src/prompt/draft-prompt-generator.ts +74 -0
- package/src/remote/api-client.ts +138 -0
- package/src/remote/state.ts +35 -0
- package/src/remote/sync-engine.ts +296 -0
- package/src/remote/types.ts +102 -0
- package/src/sdd.ts +114 -6
- package/src/types.ts +10 -4
- package/tests/api-client.test.ts +198 -0
- package/tests/cr.test.ts +27 -10
- package/tests/remote-state.test.ts +90 -0
- package/tests/sync-engine.test.ts +341 -0
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/errors.ts
CHANGED
|
@@ -25,3 +25,19 @@ export class ProjectNotInitializedError extends SDDError {
|
|
|
25
25
|
this.name = 'ProjectNotInitializedError';
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
+
|
|
29
|
+
export class RemoteError extends SDDError {
|
|
30
|
+
public statusCode: number;
|
|
31
|
+
constructor(statusCode: number, message: string) {
|
|
32
|
+
super(`Remote error (${statusCode}): ${message}`);
|
|
33
|
+
this.name = 'RemoteError';
|
|
34
|
+
this.statusCode = statusCode;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class RemoteNotConfiguredError extends SDDError {
|
|
39
|
+
constructor() {
|
|
40
|
+
super('Remote not configured. Run "sdd remote init" first.');
|
|
41
|
+
this.name = 'RemoteNotConfiguredError';
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -16,8 +16,9 @@ export type {
|
|
|
16
16
|
Bug,
|
|
17
17
|
BugFrontmatter,
|
|
18
18
|
BugStatus,
|
|
19
|
+
RemoteConfig,
|
|
19
20
|
} from "./types.js";
|
|
20
|
-
export { SDDError, LockFileNotFoundError, ParseError, ProjectNotInitializedError } from "./errors.js";
|
|
21
|
+
export { SDDError, LockFileNotFoundError, ParseError, ProjectNotInitializedError, RemoteError, RemoteNotConfiguredError } from "./errors.js";
|
|
21
22
|
export type { ProjectInfo } from "./scaffold/templates.js";
|
|
22
23
|
export { isSDDProject, readConfig, writeConfig } from "./config/config-manager.js";
|
|
23
24
|
export { runAgent } from "./agent/agent-runner.js";
|
|
@@ -32,3 +33,24 @@ export type {
|
|
|
32
33
|
AdapterFileChange,
|
|
33
34
|
SyncAdaptersResult,
|
|
34
35
|
} from "./scaffold/skill-adapters.js";
|
|
36
|
+
|
|
37
|
+
// Remote sync
|
|
38
|
+
export { generateDraftEnrichmentPrompt } from "./prompt/draft-prompt-generator.js";
|
|
39
|
+
export type { DraftElements } from "./prompt/draft-prompt-generator.js";
|
|
40
|
+
export { resolveApiKey, buildApiConfig, pullDocs, pushDocs, fetchPendingCRs, fetchOpenBugs, markCRAppliedRemote, markBugResolvedRemote, markDocEnriched, markCREnriched, markBugEnriched } from "./remote/api-client.js";
|
|
41
|
+
export type { ApiClientConfig } from "./remote/api-client.js";
|
|
42
|
+
export { readRemoteState, writeRemoteState } from "./remote/state.js";
|
|
43
|
+
export { pushToRemote, pullFromRemote, pullCRsFromRemote, pullBugsFromRemote, getRemoteStatus } from "./remote/sync-engine.js";
|
|
44
|
+
export type {
|
|
45
|
+
RemoteDocResponse,
|
|
46
|
+
RemoteDocBulkResponse,
|
|
47
|
+
RemoteCRResponse,
|
|
48
|
+
RemoteBugResponse,
|
|
49
|
+
RemoteState,
|
|
50
|
+
RemoteDocState,
|
|
51
|
+
PushResult,
|
|
52
|
+
PullResult,
|
|
53
|
+
PullConflict,
|
|
54
|
+
PullEntitiesResult,
|
|
55
|
+
RemoteStatusResult,
|
|
56
|
+
} from "./remote/types.js";
|
|
@@ -1,18 +1,77 @@
|
|
|
1
1
|
import type { Bug, ChangeRequest, StoryFile } from '../types.js';
|
|
2
2
|
import { getFileDiff } from '../git/git.js';
|
|
3
|
+
import type { DraftElements } from './draft-prompt-generator.js';
|
|
3
4
|
|
|
4
5
|
export function generateApplyPrompt(
|
|
5
6
|
bugs: Bug[],
|
|
6
7
|
changeRequests: ChangeRequest[],
|
|
7
8
|
pendingFiles: StoryFile[],
|
|
8
|
-
root: string
|
|
9
|
+
root: string,
|
|
10
|
+
drafts?: DraftElements,
|
|
11
|
+
projectContext?: StoryFile[],
|
|
12
|
+
projectDescription?: string,
|
|
9
13
|
): string | null {
|
|
10
|
-
|
|
14
|
+
const hasDrafts = drafts && (drafts.docs.length > 0 || drafts.crs.length > 0 || drafts.bugs.length > 0);
|
|
15
|
+
if (bugs.length === 0 && changeRequests.length === 0 && pendingFiles.length === 0 && !hasDrafts) {
|
|
11
16
|
return null;
|
|
12
17
|
}
|
|
13
18
|
|
|
14
19
|
const sections: string[] = [];
|
|
15
20
|
|
|
21
|
+
// Draft enrichment section (takes priority)
|
|
22
|
+
if (hasDrafts) {
|
|
23
|
+
sections.push(`# Draft Enrichment\n`);
|
|
24
|
+
|
|
25
|
+
if (projectDescription) {
|
|
26
|
+
sections.push(`## Project\n\n${projectDescription}\n`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Global context for enrichment
|
|
30
|
+
if (projectContext && projectContext.length > 0) {
|
|
31
|
+
const ctxLines = [`## Project context (${projectContext.length} documents)\n`];
|
|
32
|
+
ctxLines.push('Use the following existing documents as context to produce complete, coherent documentation.\n');
|
|
33
|
+
for (const f of projectContext) {
|
|
34
|
+
ctxLines.push(`### \`${f.relativePath}\` — ${f.frontmatter.title}\n`);
|
|
35
|
+
ctxLines.push(f.body.trim());
|
|
36
|
+
ctxLines.push('');
|
|
37
|
+
}
|
|
38
|
+
sections.push(ctxLines.join('\n'));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (drafts!.docs.length > 0) {
|
|
42
|
+
const lines = [`## Draft documents to enrich (${drafts!.docs.length})\n`];
|
|
43
|
+
lines.push('Each draft below contains incomplete human-written content. Produce a complete version for each document, preserving the original intent while adding missing details based on project context.\n');
|
|
44
|
+
for (const f of drafts!.docs) {
|
|
45
|
+
lines.push(`### \`${f.relativePath}\` — ${f.frontmatter.title}\n`);
|
|
46
|
+
lines.push(f.body.trim());
|
|
47
|
+
lines.push('');
|
|
48
|
+
}
|
|
49
|
+
sections.push(lines.join('\n'));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (drafts!.crs.length > 0) {
|
|
53
|
+
const lines = [`## Draft change requests to enrich (${drafts!.crs.length})\n`];
|
|
54
|
+
lines.push('Each draft CR contains a rough description of requested changes. Produce a complete, actionable change request for each.\n');
|
|
55
|
+
for (const cr of drafts!.crs) {
|
|
56
|
+
lines.push(`### \`${cr.relativePath}\` — ${cr.frontmatter.title}\n`);
|
|
57
|
+
lines.push(cr.body.trim());
|
|
58
|
+
lines.push('');
|
|
59
|
+
}
|
|
60
|
+
sections.push(lines.join('\n'));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (drafts!.bugs.length > 0) {
|
|
64
|
+
const lines = [`## Draft bugs to enrich (${drafts!.bugs.length})\n`];
|
|
65
|
+
lines.push('Each draft bug contains a rough description of an issue. Produce a complete bug report for each.\n');
|
|
66
|
+
for (const bug of drafts!.bugs) {
|
|
67
|
+
lines.push(`### \`${bug.relativePath}\` — ${bug.frontmatter.title}\n`);
|
|
68
|
+
lines.push(bug.body.trim());
|
|
69
|
+
lines.push('');
|
|
70
|
+
}
|
|
71
|
+
sections.push(lines.join('\n'));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
16
75
|
// Bugs
|
|
17
76
|
if (bugs.length > 0) {
|
|
18
77
|
const lines = [`## Open bugs (${bugs.length})\n`];
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Bug, ChangeRequest, StoryFile } from '../types.js';
|
|
2
|
+
|
|
3
|
+
export interface DraftElements {
|
|
4
|
+
docs: StoryFile[];
|
|
5
|
+
crs: ChangeRequest[];
|
|
6
|
+
bugs: Bug[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function generateDraftEnrichmentPrompt(
|
|
10
|
+
drafts: DraftElements,
|
|
11
|
+
projectContext: StoryFile[],
|
|
12
|
+
projectDescription: string,
|
|
13
|
+
): string | null {
|
|
14
|
+
const totalDrafts = drafts.docs.length + drafts.crs.length + drafts.bugs.length;
|
|
15
|
+
if (totalDrafts === 0) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const sections: string[] = [];
|
|
20
|
+
|
|
21
|
+
// Project context header
|
|
22
|
+
sections.push(`# Draft Enrichment\n`);
|
|
23
|
+
sections.push(`## Project\n\n${projectDescription}\n`);
|
|
24
|
+
|
|
25
|
+
// Global context: all non-draft documents
|
|
26
|
+
if (projectContext.length > 0) {
|
|
27
|
+
const ctxLines = [`## Project context (${projectContext.length} documents)\n`];
|
|
28
|
+
ctxLines.push('Use the following existing documents as context to produce complete, coherent documentation.\n');
|
|
29
|
+
for (const f of projectContext) {
|
|
30
|
+
ctxLines.push(`### \`${f.relativePath}\` — ${f.frontmatter.title}\n`);
|
|
31
|
+
ctxLines.push(f.body.trim());
|
|
32
|
+
ctxLines.push('');
|
|
33
|
+
}
|
|
34
|
+
sections.push(ctxLines.join('\n'));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Draft documents
|
|
38
|
+
if (drafts.docs.length > 0) {
|
|
39
|
+
const lines = [`## Draft documents to enrich (${drafts.docs.length})\n`];
|
|
40
|
+
lines.push('Each draft below contains incomplete human-written content. Produce a complete version for each document, preserving the original intent while adding missing details based on project context.\n');
|
|
41
|
+
for (const f of drafts.docs) {
|
|
42
|
+
lines.push(`### \`${f.relativePath}\` — ${f.frontmatter.title}\n`);
|
|
43
|
+
lines.push(f.body.trim());
|
|
44
|
+
lines.push('');
|
|
45
|
+
}
|
|
46
|
+
sections.push(lines.join('\n'));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Draft change requests
|
|
50
|
+
if (drafts.crs.length > 0) {
|
|
51
|
+
const lines = [`## Draft change requests to enrich (${drafts.crs.length})\n`];
|
|
52
|
+
lines.push('Each draft CR contains a rough description of requested changes. Produce a complete, actionable change request for each, specifying which documents are affected and what changes should be made.\n');
|
|
53
|
+
for (const cr of drafts.crs) {
|
|
54
|
+
lines.push(`### \`${cr.relativePath}\` — ${cr.frontmatter.title}\n`);
|
|
55
|
+
lines.push(cr.body.trim());
|
|
56
|
+
lines.push('');
|
|
57
|
+
}
|
|
58
|
+
sections.push(lines.join('\n'));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Draft bugs
|
|
62
|
+
if (drafts.bugs.length > 0) {
|
|
63
|
+
const lines = [`## Draft bugs to enrich (${drafts.bugs.length})\n`];
|
|
64
|
+
lines.push('Each draft bug contains a rough description of an issue. Produce a complete bug report for each, including affected components, expected vs actual behavior, and steps to reproduce when possible.\n');
|
|
65
|
+
for (const bug of drafts.bugs) {
|
|
66
|
+
lines.push(`### \`${bug.relativePath}\` — ${bug.frontmatter.title}\n`);
|
|
67
|
+
lines.push(bug.body.trim());
|
|
68
|
+
lines.push('');
|
|
69
|
+
}
|
|
70
|
+
sections.push(lines.join('\n'));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return sections.join('\n\n');
|
|
74
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { SDDConfig } from '../types.js';
|
|
2
|
+
import { RemoteError, RemoteNotConfiguredError } from '../errors.js';
|
|
3
|
+
import type {
|
|
4
|
+
RemoteDocResponse,
|
|
5
|
+
RemoteDocBulkResponse,
|
|
6
|
+
RemoteCRResponse,
|
|
7
|
+
RemoteBugResponse,
|
|
8
|
+
} from './types.js';
|
|
9
|
+
|
|
10
|
+
export interface ApiClientConfig {
|
|
11
|
+
baseUrl: string;
|
|
12
|
+
apiKey: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Resolve API key: SDD_API_KEY env var > config.remote.api-key > null
|
|
17
|
+
*/
|
|
18
|
+
export function resolveApiKey(config: SDDConfig): string | null {
|
|
19
|
+
const envKey = process.env.SDD_API_KEY;
|
|
20
|
+
if (envKey) return envKey;
|
|
21
|
+
return config.remote?.['api-key'] ?? null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Build an ApiClientConfig from the SDD project config.
|
|
26
|
+
* Throws RemoteNotConfiguredError if URL or API key is missing.
|
|
27
|
+
*/
|
|
28
|
+
export function buildApiConfig(config: SDDConfig): ApiClientConfig {
|
|
29
|
+
if (!config.remote?.url) {
|
|
30
|
+
throw new RemoteNotConfiguredError();
|
|
31
|
+
}
|
|
32
|
+
const apiKey = resolveApiKey(config);
|
|
33
|
+
if (!apiKey) {
|
|
34
|
+
throw new RemoteNotConfiguredError();
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
baseUrl: config.remote.url.replace(/\/+$/, ''),
|
|
38
|
+
apiKey,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function request<T>(
|
|
43
|
+
config: ApiClientConfig,
|
|
44
|
+
method: string,
|
|
45
|
+
path: string,
|
|
46
|
+
body?: unknown,
|
|
47
|
+
): Promise<T> {
|
|
48
|
+
const url = `${config.baseUrl}${path}`;
|
|
49
|
+
const headers: Record<string, string> = {
|
|
50
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
51
|
+
'Content-Type': 'application/json',
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const res = await fetch(url, {
|
|
55
|
+
method,
|
|
56
|
+
headers,
|
|
57
|
+
body: body != null ? JSON.stringify(body) : undefined,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (!res.ok) {
|
|
61
|
+
let message: string;
|
|
62
|
+
try {
|
|
63
|
+
const err = (await res.json()) as { detail?: string };
|
|
64
|
+
message = err.detail ?? res.statusText;
|
|
65
|
+
} catch {
|
|
66
|
+
message = res.statusText;
|
|
67
|
+
}
|
|
68
|
+
throw new RemoteError(res.status, message);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return (await res.json()) as T;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** GET /cli/pull-docs */
|
|
75
|
+
export async function pullDocs(config: ApiClientConfig): Promise<RemoteDocResponse[]> {
|
|
76
|
+
return request<RemoteDocResponse[]>(config, 'GET', '/cli/pull-docs');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** POST /cli/push-docs */
|
|
80
|
+
export async function pushDocs(
|
|
81
|
+
config: ApiClientConfig,
|
|
82
|
+
documents: Array<{ path: string; title: string; content: string }>,
|
|
83
|
+
): Promise<RemoteDocBulkResponse> {
|
|
84
|
+
return request<RemoteDocBulkResponse>(config, 'POST', '/cli/push-docs', { documents });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** GET /cli/pending-crs */
|
|
88
|
+
export async function fetchPendingCRs(config: ApiClientConfig): Promise<RemoteCRResponse[]> {
|
|
89
|
+
return request<RemoteCRResponse[]>(config, 'GET', '/cli/pending-crs');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** GET /cli/open-bugs */
|
|
93
|
+
export async function fetchOpenBugs(config: ApiClientConfig): Promise<RemoteBugResponse[]> {
|
|
94
|
+
return request<RemoteBugResponse[]>(config, 'GET', '/cli/open-bugs');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** POST /cli/crs/:crId/applied */
|
|
98
|
+
export async function markCRAppliedRemote(
|
|
99
|
+
config: ApiClientConfig,
|
|
100
|
+
crId: string,
|
|
101
|
+
): Promise<RemoteCRResponse> {
|
|
102
|
+
return request<RemoteCRResponse>(config, 'POST', `/cli/crs/${crId}/applied`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** POST /cli/bugs/:bugId/resolved */
|
|
106
|
+
export async function markBugResolvedRemote(
|
|
107
|
+
config: ApiClientConfig,
|
|
108
|
+
bugId: string,
|
|
109
|
+
): Promise<RemoteBugResponse> {
|
|
110
|
+
return request<RemoteBugResponse>(config, 'POST', `/cli/bugs/${bugId}/resolved`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** POST /cli/docs/:docId/enriched — Notify remote that a draft doc has been enriched */
|
|
114
|
+
export async function markDocEnriched(
|
|
115
|
+
config: ApiClientConfig,
|
|
116
|
+
docId: string,
|
|
117
|
+
content: string,
|
|
118
|
+
): Promise<RemoteDocResponse> {
|
|
119
|
+
return request<RemoteDocResponse>(config, 'POST', `/cli/docs/${docId}/enriched`, { content });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** POST /cli/crs/:crId/enriched — Notify remote that a draft CR has been enriched */
|
|
123
|
+
export async function markCREnriched(
|
|
124
|
+
config: ApiClientConfig,
|
|
125
|
+
crId: string,
|
|
126
|
+
body: string,
|
|
127
|
+
): Promise<RemoteCRResponse> {
|
|
128
|
+
return request<RemoteCRResponse>(config, 'POST', `/cli/crs/${crId}/enriched`, { body });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** POST /cli/bugs/:bugId/enriched — Notify remote that a draft bug has been enriched */
|
|
132
|
+
export async function markBugEnriched(
|
|
133
|
+
config: ApiClientConfig,
|
|
134
|
+
bugId: string,
|
|
135
|
+
body: string,
|
|
136
|
+
): Promise<RemoteBugResponse> {
|
|
137
|
+
return request<RemoteBugResponse>(config, 'POST', `/cli/bugs/${bugId}/enriched`, { body });
|
|
138
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import type { RemoteState } from './types.js';
|
|
5
|
+
|
|
6
|
+
const REMOTE_STATE_FILE = 'remote-state.json';
|
|
7
|
+
|
|
8
|
+
function stateFilePath(root: string): string {
|
|
9
|
+
return resolve(root, '.sdd', REMOTE_STATE_FILE);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function emptyState(): RemoteState {
|
|
13
|
+
return { documents: {} };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function readRemoteState(root: string): Promise<RemoteState> {
|
|
17
|
+
const path = stateFilePath(root);
|
|
18
|
+
if (!existsSync(path)) {
|
|
19
|
+
return emptyState();
|
|
20
|
+
}
|
|
21
|
+
const content = await readFile(path, 'utf-8');
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(content) as RemoteState;
|
|
24
|
+
} catch {
|
|
25
|
+
return emptyState();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function writeRemoteState(root: string, state: RemoteState): Promise<void> {
|
|
30
|
+
const dir = resolve(root, '.sdd');
|
|
31
|
+
if (!existsSync(dir)) {
|
|
32
|
+
await mkdir(dir, { recursive: true });
|
|
33
|
+
}
|
|
34
|
+
await writeFile(stateFilePath(root), JSON.stringify(state, null, 2), 'utf-8');
|
|
35
|
+
}
|