silvermoon 0.0.2 → 0.1.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/src/index.js CHANGED
@@ -1,87 +1,66 @@
1
1
  import { resolve } from "node:path";
2
2
 
3
+ import {
4
+ diagnosticProblem,
5
+ } from "./dialogue.js";
3
6
  import { loadConfig } from "./config.js";
4
7
  import {
5
8
  fetchPrimary,
6
9
  indexSnapshot,
7
10
  resolveCommit,
8
11
  runGit,
12
+ sanitizeGitMessage,
9
13
  worktreeSnapshot,
10
14
  withTemporaryTree,
11
15
  withTemporaryWorktree,
12
16
  } from "./git.js";
13
- import { inspectIdeaLayout } from "./idea-layout.js";
17
+ import {
18
+ observeSnapshot,
19
+ unavailableObservation,
20
+ } from "./observation.js";
14
21
 
15
- function ideaSummary(idea) {
16
- const value = {
17
- id: idea.id,
18
- idealRevision: idea.idealRevision,
19
- implementationRevision: idea.implementationRevision,
20
- deploymentRevision: idea.deploymentRevision,
21
- state: idea.state,
22
- };
23
- if (idea.alias !== undefined) value.alias = idea.alias;
24
- return value;
22
+ function targetArgument({ commit, remote, staged, worktree }) {
23
+ if (remote) return { type: "remote" };
24
+ if (commit !== undefined) return { type: "commit", revision: commit };
25
+ if (staged) return { type: "staged" };
26
+ if (worktree) return { type: "worktree" };
27
+ return { type: "head" };
25
28
  }
26
29
 
27
- async function inspectSnapshot({
28
- baseRevision,
29
- gitRoot = root,
30
- historyCommit,
30
+ function failureReport({
31
+ intention,
32
+ problem,
31
33
  root,
32
- snapshotTree,
33
- target = "local",
34
- validateCandidate = false,
34
+ version,
35
35
  }) {
36
- const repositoryRoot = resolve(root);
37
- const loaded = await loadConfig({ root: repositoryRoot });
38
- const layout = loaded.config
39
- ? await inspectIdeaLayout({
40
- baseRevision,
41
- config: loaded.config,
42
- gitRoot,
43
- historyCommit,
44
- root: repositoryRoot,
45
- snapshotTree,
46
- validateCandidate,
47
- })
48
- : { diagnostics: [], ideas: [] };
49
- const diagnostics = [
50
- ...loaded.diagnostics,
51
- ...layout.diagnostics,
52
- ];
53
-
54
- const report = {
55
- command: "check",
56
- ok: diagnostics.every(({ level }) => level !== "error"),
57
- root: repositoryRoot,
58
- diagnostics,
59
- result: {
60
- target,
61
- checked: layout.ideas.length,
62
- ideas: layout.ideas.map(ideaSummary),
63
- },
36
+ return {
37
+ intention,
38
+ observation: unavailableObservation({ problem, root, version }),
64
39
  };
65
- return { config: loaded.config, layout, report };
66
40
  }
67
41
 
68
- function targetFailure(root, code, message, remediation) {
69
- return {
70
- command: "check",
71
- ok: false,
72
- root,
73
- diagnostics: [{ code, level: "error", message, remediation }],
74
- result: null,
75
- };
42
+ async function inspectTree({
43
+ gitRoot,
44
+ root,
45
+ tree,
46
+ userHome,
47
+ version,
48
+ }) {
49
+ return withTemporaryTree(gitRoot, tree, (contentRoot) =>
50
+ observeSnapshot({
51
+ contentRoot,
52
+ gitRoot,
53
+ root,
54
+ projectOnly: true,
55
+ snapshotTree: tree,
56
+ userHome,
57
+ version,
58
+ })
59
+ );
76
60
  }
77
61
 
78
- function finishTarget({ commit, inspected, root, target }) {
79
- const report = inspected.report;
80
- report.root = root;
81
- report.result = { ...report.result, target };
82
- if (commit) report.result.commit = commit;
83
- report.ok = report.diagnostics.every(({ level }) => level !== "error");
84
- return report;
62
+ function finishCheck(intention, observed) {
63
+ return { intention, observation: observed.observation };
85
64
  }
86
65
 
87
66
  export async function checkRepository({
@@ -89,160 +68,214 @@ export async function checkRepository({
89
68
  remote = false,
90
69
  root = process.cwd(),
91
70
  staged = false,
71
+ userHome,
92
72
  worktree = false,
93
73
  } = {}) {
94
- const repositoryRoot = resolve(root);
95
- const targetCount = [remote, commit !== undefined, staged, worktree].filter(Boolean).length;
74
+ const requestedRoot = resolve(root);
75
+ const targetCount = [remote, commit !== undefined, staged, worktree]
76
+ .filter(Boolean).length;
96
77
  if (targetCount > 1) {
97
- return targetFailure(
98
- repositoryRoot,
99
- "check.target.conflict",
100
- "Check targets are mutually exclusive.",
101
- "Choose exactly one of remote, commit, staged, or worktree.",
78
+ const error = new Error(
79
+ "Check targets are mutually exclusive; choose one of remote, commit, staged, or worktree.",
102
80
  );
81
+ error.exitCode = 2;
82
+ throw error;
83
+ }
84
+ const target = targetArgument({ commit, remote, staged, worktree });
85
+ const intention = { command: "check", args: { target } };
86
+ const repository = runGit(requestedRoot, ["rev-parse", "--show-toplevel"]);
87
+ if (!repository.ok) {
88
+ const observed = await observeSnapshot({
89
+ projectOnly: true,
90
+ root: requestedRoot,
91
+ userHome,
92
+ version: target.type === "commit" || target.type === "head"
93
+ ? { type: "commit", commit: null }
94
+ : target.type === "remote"
95
+ ? { type: "remote", commit: null }
96
+ : { type: target.type },
97
+ });
98
+ return finishCheck(intention, observed);
103
99
  }
100
+ const repositoryRoot = resolve(repository.stdout);
104
101
 
105
102
  if (remote) {
103
+ let head;
106
104
  try {
107
- const head = resolveCommit(repositoryRoot, "HEAD");
108
- const bootstrap = await withTemporaryWorktree(repositoryRoot, head, (worktreeRoot, tree) =>
109
- inspectSnapshot({
110
- gitRoot: repositoryRoot,
111
- root: worktreeRoot,
112
- snapshotTree: tree,
113
- target: "remote",
114
- }),
105
+ head = resolveCommit(repositoryRoot, "HEAD");
106
+ } catch (caught) {
107
+ return failureReport({
108
+ intention,
109
+ problem: {
110
+ type: "head-unavailable",
111
+ summary: sanitizeGitMessage(caught.message),
112
+ },
113
+ root: repositoryRoot,
114
+ version: { type: "remote", commit: null },
115
+ });
116
+ }
117
+ let bootstrap;
118
+ try {
119
+ bootstrap = await withTemporaryWorktree(
120
+ repositoryRoot,
121
+ head,
122
+ (contentRoot) => loadConfig({ root: contentRoot }),
115
123
  );
116
- if (!bootstrap.config) {
117
- return finishTarget({ inspected: bootstrap, root: repositoryRoot, target: "remote" });
118
- }
119
- const primary = fetchPrimary(repositoryRoot, bootstrap.config);
120
- const inspected = await withTemporaryWorktree(repositoryRoot, primary, (worktree, tree) =>
121
- inspectSnapshot({
124
+ } catch (caught) {
125
+ return failureReport({
126
+ intention,
127
+ problem: {
128
+ type: "snapshot-inspection-failed",
129
+ summary: sanitizeGitMessage(caught.message),
130
+ },
131
+ root: repositoryRoot,
132
+ version: { type: "remote", commit: null },
133
+ });
134
+ }
135
+ if (!bootstrap.config) {
136
+ return {
137
+ intention,
138
+ observation: {
139
+ state: "project-setup-required",
140
+ observedThrough: "version",
141
+ root: repositoryRoot,
142
+ version: { type: "remote", commit: null },
143
+ problems: bootstrap.diagnostics.map((diagnostic) =>
144
+ diagnosticProblem(diagnostic)
145
+ ),
146
+ },
147
+ };
148
+ }
149
+
150
+ let primary;
151
+ try {
152
+ primary = fetchPrimary(repositoryRoot, bootstrap.config);
153
+ } catch (caught) {
154
+ const summary = sanitizeGitMessage(caught.message);
155
+ return failureReport({
156
+ intention,
157
+ root: repositoryRoot,
158
+ version: { type: "remote", commit: null },
159
+ problem: { type: "primary-fetch-failed", summary },
160
+ });
161
+ }
162
+ try {
163
+ const observed = await withTemporaryWorktree(
164
+ repositoryRoot,
165
+ primary,
166
+ (contentRoot, tree) => observeSnapshot({
167
+ contentRoot,
122
168
  gitRoot: repositoryRoot,
123
- historyCommit: primary,
124
- root: worktree,
169
+ root: repositoryRoot,
170
+ projectOnly: true,
125
171
  snapshotTree: tree,
126
- target: "remote",
172
+ userHome,
173
+ version: { type: "remote", commit: primary },
127
174
  }),
128
175
  );
129
- return finishTarget({
130
- commit: primary,
131
- inspected,
176
+ return finishCheck(intention, observed);
177
+ } catch (caught) {
178
+ return failureReport({
179
+ intention,
180
+ problem: {
181
+ type: "snapshot-inspection-failed",
182
+ summary: sanitizeGitMessage(caught.message),
183
+ },
132
184
  root: repositoryRoot,
133
- target: "remote",
185
+ version: { type: "remote", commit: primary },
134
186
  });
135
- } catch (caught) {
136
- return targetFailure(
137
- repositoryRoot,
138
- "git.fetch.failed",
139
- caught.message,
140
- "Check remote access and retry.",
141
- );
142
187
  }
143
188
  }
144
189
 
145
190
  if (commit !== undefined || (!staged && !worktree)) {
146
191
  const revision = commit ?? "HEAD";
147
- const target = commit === undefined ? "head" : "commit";
148
192
  let resolvedCommit;
149
193
  try {
150
194
  resolvedCommit = resolveCommit(repositoryRoot, revision);
151
195
  } catch (caught) {
152
- return targetFailure(
153
- repositoryRoot,
154
- "git.commit.invalid",
155
- caught.message,
156
- commit === undefined
157
- ? "Create or restore a valid HEAD commit."
158
- : "Choose a commit available in the local repository.",
159
- );
196
+ return failureReport({
197
+ intention,
198
+ problem: {
199
+ type: "commit-unavailable",
200
+ summary: sanitizeGitMessage(caught.message),
201
+ },
202
+ root: repositoryRoot,
203
+ version: { type: "commit", commit: null },
204
+ });
160
205
  }
161
206
  try {
162
- const parent = runGit(repositoryRoot, ["rev-parse", `${resolvedCommit}^1`]);
163
- const inspected = await withTemporaryWorktree(repositoryRoot, resolvedCommit, (worktree, tree) =>
164
- inspectSnapshot({
165
- baseRevision: parent.ok ? parent.stdout : null,
207
+ const observed = await withTemporaryWorktree(
208
+ repositoryRoot,
209
+ resolvedCommit,
210
+ (contentRoot, tree) => observeSnapshot({
211
+ contentRoot,
166
212
  gitRoot: repositoryRoot,
167
- root: worktree,
213
+ root: repositoryRoot,
214
+ projectOnly: true,
168
215
  snapshotTree: tree,
169
- target,
170
- validateCandidate: true,
216
+ userHome,
217
+ version: { type: "commit", commit: resolvedCommit },
171
218
  }),
172
219
  );
173
- return finishTarget({
174
- commit: resolvedCommit,
175
- inspected,
220
+ return finishCheck(intention, observed);
221
+ } catch (caught) {
222
+ return failureReport({
223
+ intention,
224
+ problem: {
225
+ type: "snapshot-inspection-failed",
226
+ summary: sanitizeGitMessage(caught.message),
227
+ },
176
228
  root: repositoryRoot,
177
- target,
229
+ version: { type: "commit", commit: resolvedCommit },
178
230
  });
179
- } catch (caught) {
180
- return targetFailure(
181
- repositoryRoot,
182
- "git.snapshot.failed",
183
- caught.message,
184
- "Resolve the repository state and retry the commit check.",
185
- );
186
231
  }
187
232
  }
188
233
 
189
234
  if (staged) {
190
235
  try {
191
236
  const snapshot = indexSnapshot(repositoryRoot);
192
- const inspected = await withTemporaryTree(repositoryRoot, snapshot.tree, (worktree) =>
193
- inspectSnapshot({
194
- baseRevision: "HEAD",
195
- gitRoot: repositoryRoot,
196
- root: worktree,
197
- snapshotTree: snapshot.tree,
198
- target: "staged",
199
- validateCandidate: true,
200
- }),
201
- );
202
- return finishTarget({
203
- inspected,
237
+ const observed = await inspectTree({
238
+ gitRoot: repositoryRoot,
204
239
  root: repositoryRoot,
205
- target: "staged",
240
+ tree: snapshot.tree,
241
+ userHome,
242
+ version: { type: "staged" },
206
243
  });
244
+ return finishCheck(intention, observed);
207
245
  } catch (caught) {
208
- return targetFailure(
209
- repositoryRoot,
210
- "git.snapshot.failed",
211
- caught.message,
212
- "Resolve the index state and retry the staged check.",
213
- );
214
- }
215
- }
216
-
217
- if (worktree) {
218
- try {
219
- const snapshot = worktreeSnapshot(repositoryRoot);
220
- const inspected = await withTemporaryTree(repositoryRoot, snapshot.tree, (worktree) =>
221
- inspectSnapshot({
222
- baseRevision: "HEAD",
223
- gitRoot: repositoryRoot,
224
- root: worktree,
225
- snapshotTree: snapshot.tree,
226
- target: "worktree",
227
- validateCandidate: true,
228
- }),
229
- );
230
- return finishTarget({
231
- inspected,
246
+ return failureReport({
247
+ intention,
248
+ problem: {
249
+ type: "snapshot-inspection-failed",
250
+ summary: sanitizeGitMessage(caught.message),
251
+ },
232
252
  root: repositoryRoot,
233
- target: "worktree",
253
+ version: { type: "staged" },
234
254
  });
235
- } catch (caught) {
236
- return targetFailure(
237
- repositoryRoot,
238
- "git.snapshot.failed",
239
- caught.message,
240
- "Resolve the worktree state and retry the worktree check.",
241
- );
242
255
  }
243
256
  }
244
257
 
245
- throw new Error("Unreachable check target");
258
+ try {
259
+ const snapshot = worktreeSnapshot(repositoryRoot);
260
+ const observed = await inspectTree({
261
+ gitRoot: repositoryRoot,
262
+ root: repositoryRoot,
263
+ tree: snapshot.tree,
264
+ userHome,
265
+ version: { type: "worktree" },
266
+ });
267
+ return finishCheck(intention, observed);
268
+ } catch (caught) {
269
+ return failureReport({
270
+ intention,
271
+ problem: {
272
+ type: "snapshot-inspection-failed",
273
+ summary: sanitizeGitMessage(caught.message),
274
+ },
275
+ root: repositoryRoot,
276
+ version: { type: "worktree" },
277
+ });
278
+ }
246
279
  }
247
280
 
248
281
  export {