@fluado/enmetu 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +168 -2
- package/dist/adapters/clock/systemClockAdapter.d.ts +6 -0
- package/dist/adapters/clock/systemClockAdapter.js +12 -0
- package/dist/adapters/fs/nodeFileSystemAdapter.d.ts +18 -0
- package/dist/adapters/fs/nodeFileSystemAdapter.js +230 -0
- package/dist/adapters/git/gitFacade.d.ts +148 -0
- package/dist/adapters/git/gitFacade.js +822 -0
- package/dist/adapters/git/gitLocatorFacade.d.ts +8 -0
- package/dist/adapters/git/gitLocatorFacade.js +72 -0
- package/dist/adapters/git/gitProcessAdapter.d.ts +46 -0
- package/dist/adapters/git/gitProcessAdapter.js +261 -0
- package/dist/adapters/log/nullLogger.d.ts +9 -0
- package/dist/adapters/log/nullLogger.js +10 -0
- package/dist/adapters/log/stderrLoggerAdapter.d.ts +10 -0
- package/dist/adapters/log/stderrLoggerAdapter.js +13 -0
- package/dist/adapters/process/nodeProcessAdapter.d.ts +9 -0
- package/dist/adapters/process/nodeProcessAdapter.js +56 -0
- package/dist/adapters/watch/fsWatchAdapter.d.ts +24 -0
- package/dist/adapters/watch/fsWatchAdapter.js +47 -0
- package/dist/cli/commands/hookCommand.d.ts +28 -0
- package/dist/cli/commands/hookCommand.js +90 -0
- package/dist/cli/commands/resumeCommand.d.ts +48 -0
- package/dist/cli/commands/resumeCommand.js +124 -0
- package/dist/cli/commands/runCommand.d.ts +33 -0
- package/dist/cli/commands/runCommand.js +69 -0
- package/dist/cli/commands/statusCommand.d.ts +71 -0
- package/dist/cli/commands/statusCommand.js +249 -0
- package/dist/cli/exitCodes.d.ts +7 -0
- package/dist/cli/exitCodes.js +39 -0
- package/dist/cli/helpText.d.ts +1 -0
- package/dist/cli/helpText.js +16 -0
- package/dist/cli/main.d.ts +8 -0
- package/dist/cli/main.js +49 -0
- package/dist/cli/parseArgs.d.ts +19 -0
- package/dist/cli/parseArgs.js +106 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +32 -0
- package/dist/commit/commitLocalChangesController.d.ts +132 -0
- package/dist/commit/commitLocalChangesController.js +358 -0
- package/dist/commit/gitIndexLock.d.ts +36 -0
- package/dist/commit/gitIndexLock.js +90 -0
- package/dist/commit/repairStaleIndexController.d.ts +90 -0
- package/dist/commit/repairStaleIndexController.js +150 -0
- package/dist/commit/stageGuardedChanges.d.ts +30 -0
- package/dist/commit/stageGuardedChanges.js +52 -0
- package/dist/contracts/IClock.d.ts +7 -0
- package/dist/contracts/IClock.js +1 -0
- package/dist/contracts/IDaemon.d.ts +26 -0
- package/dist/contracts/IDaemon.js +1 -0
- package/dist/contracts/IFileSystem.d.ts +50 -0
- package/dist/contracts/IFileSystem.js +1 -0
- package/dist/contracts/IGitCommitter.d.ts +60 -0
- package/dist/contracts/IGitCommitter.js +1 -0
- package/dist/contracts/IGitConfigReader.d.ts +5 -0
- package/dist/contracts/IGitConfigReader.js +1 -0
- package/dist/contracts/IGitIndexLock.d.ts +44 -0
- package/dist/contracts/IGitIndexLock.js +1 -0
- package/dist/contracts/IGitReader.d.ts +81 -0
- package/dist/contracts/IGitReader.js +1 -0
- package/dist/contracts/IGitRunner.d.ts +49 -0
- package/dist/contracts/IGitRunner.js +1 -0
- package/dist/contracts/IGitWriter.d.ts +39 -0
- package/dist/contracts/IGitWriter.js +1 -0
- package/dist/contracts/IHaltStore.d.ts +14 -0
- package/dist/contracts/IHaltStore.js +1 -0
- package/dist/contracts/IHookInstaller.d.ts +14 -0
- package/dist/contracts/IHookInstaller.js +1 -0
- package/dist/contracts/IInstanceLock.d.ts +26 -0
- package/dist/contracts/IInstanceLock.js +1 -0
- package/dist/contracts/ILogger.d.ts +11 -0
- package/dist/contracts/ILogger.js +1 -0
- package/dist/contracts/IOperationJournal.d.ts +12 -0
- package/dist/contracts/IOperationJournal.js +1 -0
- package/dist/contracts/IPathQuarantine.d.ts +67 -0
- package/dist/contracts/IPathQuarantine.js +1 -0
- package/dist/contracts/IProcessHost.d.ts +11 -0
- package/dist/contracts/IProcessHost.js +1 -0
- package/dist/contracts/IRepositoryLocator.d.ts +28 -0
- package/dist/contracts/IRepositoryLocator.js +1 -0
- package/dist/contracts/IStateRecorder.d.ts +68 -0
- package/dist/contracts/IStateRecorder.js +1 -0
- package/dist/contracts/IStateStore.d.ts +88 -0
- package/dist/contracts/IStateStore.js +1 -0
- package/dist/contracts/ISyncCycle.d.ts +23 -0
- package/dist/contracts/ISyncCycle.js +1 -0
- package/dist/contracts/ISyncCycleRunner.d.ts +4 -0
- package/dist/contracts/ISyncCycleRunner.js +1 -0
- package/dist/contracts/ISyncScheduler.d.ts +6 -0
- package/dist/contracts/ISyncScheduler.js +1 -0
- package/dist/contracts/ITextSink.d.ts +3 -0
- package/dist/contracts/ITextSink.js +1 -0
- package/dist/contracts/IWatcher.d.ts +9 -0
- package/dist/contracts/IWatcher.js +1 -0
- package/dist/contracts/config.d.ts +20 -0
- package/dist/contracts/config.js +1 -0
- package/dist/contracts/errors.d.ts +39 -0
- package/dist/contracts/errors.js +64 -0
- package/dist/contracts/exitCode.d.ts +10 -0
- package/dist/contracts/exitCode.js +17 -0
- package/dist/contracts/gitTypes.d.ts +113 -0
- package/dist/contracts/gitTypes.js +1 -0
- package/dist/contracts/library.d.ts +35 -0
- package/dist/contracts/library.js +1 -0
- package/dist/contracts/logCodes.d.ts +1 -0
- package/dist/contracts/logCodes.js +1 -0
- package/dist/contracts/runtime.d.ts +16 -0
- package/dist/contracts/runtime.js +1 -0
- package/dist/contracts/syncTypes.d.ts +75 -0
- package/dist/contracts/syncTypes.js +6 -0
- package/dist/core/commit/commitMemory.d.ts +3 -0
- package/dist/core/commit/commitMemory.js +9 -0
- package/dist/core/commit/commitMessage.d.ts +6 -0
- package/dist/core/commit/commitMessage.js +12 -0
- package/dist/core/config/parseConfig.d.ts +5 -0
- package/dist/core/config/parseConfig.js +196 -0
- package/dist/core/content/contentRules.d.ts +11 -0
- package/dist/core/content/contentRules.js +50 -0
- package/dist/core/errors/errorMessage.d.ts +6 -0
- package/dist/core/errors/errorMessage.js +14 -0
- package/dist/core/errors/withCycleId.d.ts +7 -0
- package/dist/core/errors/withCycleId.js +38 -0
- package/dist/core/git/enmetuRefs.d.ts +9 -0
- package/dist/core/git/enmetuRefs.js +22 -0
- package/dist/core/git/porcelain.d.ts +51 -0
- package/dist/core/git/porcelain.js +219 -0
- package/dist/core/git/rebaseOwnership.d.ts +28 -0
- package/dist/core/git/rebaseOwnership.js +29 -0
- package/dist/core/git/stoppedRebaseWrites.d.ts +10 -0
- package/dist/core/git/stoppedRebaseWrites.js +22 -0
- package/dist/core/halt/resumeHint.d.ts +6 -0
- package/dist/core/halt/resumeHint.js +9 -0
- package/dist/core/log/formatLogLine.d.ts +3 -0
- package/dist/core/log/formatLogLine.js +11 -0
- package/dist/core/quarantine/keptBlobMessage.d.ts +5 -0
- package/dist/core/quarantine/keptBlobMessage.js +8 -0
- package/dist/core/quarantine/restoreWarnings.d.ts +16 -0
- package/dist/core/quarantine/restoreWarnings.js +36 -0
- package/dist/core/schedule/schedulerStep.d.ts +63 -0
- package/dist/core/schedule/schedulerStep.js +144 -0
- package/dist/core/state/stateGuards.d.ts +21 -0
- package/dist/core/state/stateGuards.js +295 -0
- package/dist/core/state/stateLiveness.d.ts +10 -0
- package/dist/core/state/stateLiveness.js +21 -0
- package/dist/core/state/stateRecordStep.d.ts +28 -0
- package/dist/core/state/stateRecordStep.js +203 -0
- package/dist/core/status/conflictGuide.d.ts +30 -0
- package/dist/core/status/conflictGuide.js +86 -0
- package/dist/core/status/orphanedCommits.d.ts +15 -0
- package/dist/core/status/orphanedCommits.js +24 -0
- package/dist/core/status/statusHeadline.d.ts +46 -0
- package/dist/core/status/statusHeadline.js +130 -0
- package/dist/core/watch/watchFilter.d.ts +2 -0
- package/dist/core/watch/watchFilter.js +34 -0
- package/dist/daemon/daemon.d.ts +55 -0
- package/dist/daemon/daemon.js +277 -0
- package/dist/enmetuInstances.d.ts +65 -0
- package/dist/enmetuInstances.js +333 -0
- package/dist/halt/haltGuardGitWriter.d.ts +23 -0
- package/dist/halt/haltGuardGitWriter.js +61 -0
- package/dist/halt/haltStore.d.ts +13 -0
- package/dist/halt/haltStore.js +49 -0
- package/dist/halt/recordHalt.d.ts +19 -0
- package/dist/halt/recordHalt.js +20 -0
- package/dist/hooks/hookScript.d.ts +22 -0
- package/dist/hooks/hookScript.js +52 -0
- package/dist/hooks/nullHookInstaller.d.ts +5 -0
- package/dist/hooks/nullHookInstaller.js +6 -0
- package/dist/hooks/preCommitCheckController.d.ts +23 -0
- package/dist/hooks/preCommitCheckController.js +22 -0
- package/dist/hooks/preCommitHookInstaller.d.ts +32 -0
- package/dist/hooks/preCommitHookInstaller.js +113 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +38 -0
- package/dist/library/enmetuLibrary.d.ts +16 -0
- package/dist/library/enmetuLibrary.js +62 -0
- package/dist/library/readStateController.d.ts +28 -0
- package/dist/library/readStateController.js +32 -0
- package/dist/library/requestedStopProcessHost.d.ts +28 -0
- package/dist/library/requestedStopProcessHost.js +58 -0
- package/dist/lock/instanceLock.d.ts +26 -0
- package/dist/lock/instanceLock.js +125 -0
- package/dist/preflight/repositoryPreflight.d.ts +20 -0
- package/dist/preflight/repositoryPreflight.js +42 -0
- package/dist/quarantine/pathQuarantine.d.ts +113 -0
- package/dist/quarantine/pathQuarantine.js +504 -0
- package/dist/recovery/enmetuRebaseAbort.d.ts +108 -0
- package/dist/recovery/enmetuRebaseAbort.js +302 -0
- package/dist/recovery/operationJournal.d.ts +13 -0
- package/dist/recovery/operationJournal.js +37 -0
- package/dist/recovery/recoveryController.d.ts +48 -0
- package/dist/recovery/recoveryController.js +112 -0
- package/dist/state/stateFileStore.d.ts +18 -0
- package/dist/state/stateFileStore.js +73 -0
- package/dist/state/stateRecorder.d.ts +26 -0
- package/dist/state/stateRecorder.js +69 -0
- package/dist/state/stateRecordingSyncCycle.d.ts +24 -0
- package/dist/state/stateRecordingSyncCycle.js +71 -0
- package/dist/sync/hookAbortingSyncScheduler.d.ts +16 -0
- package/dist/sync/hookAbortingSyncScheduler.js +27 -0
- package/dist/sync/integrateUpstreamController.d.ts +95 -0
- package/dist/sync/integrateUpstreamController.js +353 -0
- package/dist/sync/syncCycleController.d.ts +71 -0
- package/dist/sync/syncCycleController.js +345 -0
- package/dist/sync/syncCycleRunner.d.ts +24 -0
- package/dist/sync/syncCycleRunner.js +169 -0
- package/dist/sync/syncScheduler.d.ts +32 -0
- package/dist/sync/syncScheduler.js +97 -0
- package/package.json +42 -4
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/** The hard maximum of stateHistory: a typo or a library caller cannot make state.json large. */
|
|
2
|
+
export const MAX_STATE_HISTORY = 100;
|
|
3
|
+
/**
|
|
4
|
+
* A longer message (a halt's) is cut to this many characters and ends in
|
|
5
|
+
* "...". Characters are code points (Array.from), so an astral character is
|
|
6
|
+
* never split into a lone surrogate.
|
|
7
|
+
*/
|
|
8
|
+
export const MAX_STATE_MESSAGE_CHARACTERS = 300;
|
|
9
|
+
/**
|
|
10
|
+
* How many orphaned commits the record keeps, newest last (design 14.15). An
|
|
11
|
+
* orphan needs a person's commit inside enmetu's stopped rebase, so five
|
|
12
|
+
* pending at once is already far beyond what the window produces; the cap
|
|
13
|
+
* keeps state.json bounded (Decision 63) whatever happens.
|
|
14
|
+
*/
|
|
15
|
+
export const MAX_ORPHANED_COMMITS = 5;
|
|
16
|
+
/** An orphaned commit's subject is cut to this many characters, like a message (git's conventional subject width). */
|
|
17
|
+
export const MAX_ORPHAN_SUBJECT_CHARACTERS = 72;
|
|
18
|
+
const ELLIPSIS = '...';
|
|
19
|
+
/**
|
|
20
|
+
* The base of an event that arrives without a started record. Its instance
|
|
21
|
+
* matches no lock holder (pid 0), so liveness never takes it for a live one.
|
|
22
|
+
*/
|
|
23
|
+
const EMPTY_RECORD = {
|
|
24
|
+
version: 1,
|
|
25
|
+
instance: { pid: 0, hostname: '', startedAt: '' },
|
|
26
|
+
state: 'starting',
|
|
27
|
+
reason: null,
|
|
28
|
+
message: '',
|
|
29
|
+
since: '',
|
|
30
|
+
pushed: null,
|
|
31
|
+
unpushed: null,
|
|
32
|
+
lastCycle: null,
|
|
33
|
+
conflict: null,
|
|
34
|
+
history: [],
|
|
35
|
+
orphaned: [],
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Functional core of the state record (design 12.1): the record after an
|
|
39
|
+
* event. A transition is a change of (state, reason); it moves `since` and is
|
|
40
|
+
* appended to the history, which keeps the newest min(historyCap,
|
|
41
|
+
* MAX_STATE_HISTORY) entries. `started` begins a fresh record for the new
|
|
42
|
+
* instance and carries the previous history across the restart; any other
|
|
43
|
+
* event without a previous record applies to EMPTY_RECORD.
|
|
44
|
+
*/
|
|
45
|
+
export function stateRecordStep(previous, event, at, historyCap) {
|
|
46
|
+
const cap = Math.max(0, Math.min(historyCap, MAX_STATE_HISTORY));
|
|
47
|
+
switch (event.type) {
|
|
48
|
+
case 'started':
|
|
49
|
+
return {
|
|
50
|
+
version: 1,
|
|
51
|
+
instance: {
|
|
52
|
+
pid: event.instance.pid,
|
|
53
|
+
hostname: event.instance.hostname,
|
|
54
|
+
startedAt: event.instance.startedAt,
|
|
55
|
+
},
|
|
56
|
+
state: 'starting',
|
|
57
|
+
reason: null,
|
|
58
|
+
message: 'started; the first cycle has not finished',
|
|
59
|
+
since: at,
|
|
60
|
+
pushed: null,
|
|
61
|
+
unpushed: null,
|
|
62
|
+
lastCycle: null,
|
|
63
|
+
conflict: null,
|
|
64
|
+
history: appendTransition(previous?.history ?? [], { state: 'starting', reason: null, at }, cap),
|
|
65
|
+
// An orphaned commit stays on no branch across a restart.
|
|
66
|
+
orphaned: previous?.orphaned ?? [],
|
|
67
|
+
};
|
|
68
|
+
case 'cycleFinished': {
|
|
69
|
+
const base = previous ?? EMPTY_RECORD;
|
|
70
|
+
const part = outcomePart(event.outcome);
|
|
71
|
+
return {
|
|
72
|
+
...withPart(base, part, at, cap),
|
|
73
|
+
pushed: event.upstream.pushed,
|
|
74
|
+
unpushed: event.upstream.unpushed,
|
|
75
|
+
lastCycle: { cycleId: event.cycleId, outcome: event.outcome.kind, at },
|
|
76
|
+
conflict: event.outcome.kind === 'conflict' ? event.conflict : null,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
case 'cycleFailed': {
|
|
80
|
+
// A throw says nothing about whether the state or its reason ended, as
|
|
81
|
+
// in SyncCycleRunner; only the heartbeat moves.
|
|
82
|
+
const base = previous ?? EMPTY_RECORD;
|
|
83
|
+
return {
|
|
84
|
+
...base,
|
|
85
|
+
lastCycle: { cycleId: event.cycleId, outcome: 'failed', at },
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
case 'halted':
|
|
89
|
+
return withPart(previous ?? EMPTY_RECORD, { state: 'halted', reason: event.code, message: event.message }, at, cap);
|
|
90
|
+
case 'stopped':
|
|
91
|
+
return withPart(previous ?? EMPTY_RECORD, { state: 'stopped', reason: null, message: event.message }, at, cap);
|
|
92
|
+
case 'commitOrphaned': {
|
|
93
|
+
// Not a transition: the cycle that orphaned it reports its own outcome.
|
|
94
|
+
const base = previous ?? EMPTY_RECORD;
|
|
95
|
+
return {
|
|
96
|
+
...base,
|
|
97
|
+
orphaned: withOrphan(base.orphaned, {
|
|
98
|
+
commit: event.commit,
|
|
99
|
+
base: event.base,
|
|
100
|
+
subject: cutText(event.subject, MAX_ORPHAN_SUBJECT_CHARACTERS),
|
|
101
|
+
at,
|
|
102
|
+
}),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The list with the orphan appended, the newest MAX_ORPHANED_COMMITS kept. A
|
|
109
|
+
* commit already listed keeps its first entry: a recovery that names the same
|
|
110
|
+
* commit again (a crash after the live line) says nothing new.
|
|
111
|
+
*/
|
|
112
|
+
function withOrphan(orphaned, orphan) {
|
|
113
|
+
if (orphaned.some((entry) => entry.commit === orphan.commit)) {
|
|
114
|
+
return orphaned;
|
|
115
|
+
}
|
|
116
|
+
return [...orphaned, orphan].slice(-MAX_ORPHANED_COMMITS);
|
|
117
|
+
}
|
|
118
|
+
/** The record with a new state part; since and the history move only on a transition. */
|
|
119
|
+
function withPart(base, part, at, cap) {
|
|
120
|
+
const transition = part.state !== base.state || part.reason !== base.reason;
|
|
121
|
+
return {
|
|
122
|
+
...base,
|
|
123
|
+
state: part.state,
|
|
124
|
+
reason: part.reason,
|
|
125
|
+
message: cutMessage(part.message),
|
|
126
|
+
since: transition ? at : base.since,
|
|
127
|
+
history: transition
|
|
128
|
+
? appendTransition(base.history, { state: part.state, reason: part.reason, at }, cap)
|
|
129
|
+
: capHistory(base.history, cap),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function outcomePart(outcome) {
|
|
133
|
+
switch (outcome.kind) {
|
|
134
|
+
case 'synced':
|
|
135
|
+
return {
|
|
136
|
+
state: 'synced',
|
|
137
|
+
reason: null,
|
|
138
|
+
message: 'in sync with the upstream',
|
|
139
|
+
};
|
|
140
|
+
case 'paused':
|
|
141
|
+
return {
|
|
142
|
+
state: 'paused',
|
|
143
|
+
reason: outcome.reason,
|
|
144
|
+
message: `paused: ${outcome.reason}`,
|
|
145
|
+
};
|
|
146
|
+
case 'waiting':
|
|
147
|
+
return {
|
|
148
|
+
state: 'waiting',
|
|
149
|
+
reason: outcome.reason,
|
|
150
|
+
message: outcome.reason === 'PARTIAL_SELECTION_STAGED'
|
|
151
|
+
? 'waiting: a partial selection is staged'
|
|
152
|
+
: 'waiting: a commit hook rejected the commit',
|
|
153
|
+
};
|
|
154
|
+
case 'busy':
|
|
155
|
+
return {
|
|
156
|
+
state: 'busy',
|
|
157
|
+
reason: null,
|
|
158
|
+
message: 'git is busy in this repository',
|
|
159
|
+
};
|
|
160
|
+
case 'offline':
|
|
161
|
+
return {
|
|
162
|
+
state: 'offline',
|
|
163
|
+
reason: null,
|
|
164
|
+
message: 'the remote is unreachable',
|
|
165
|
+
};
|
|
166
|
+
case 'conflict':
|
|
167
|
+
return {
|
|
168
|
+
state: 'conflict',
|
|
169
|
+
reason: null,
|
|
170
|
+
message: 'conflict held: the rebase onto the upstream stopped; local commits kept',
|
|
171
|
+
};
|
|
172
|
+
case 'push-failed':
|
|
173
|
+
return {
|
|
174
|
+
state: 'push-failed',
|
|
175
|
+
reason: null,
|
|
176
|
+
message: 'the last push failed',
|
|
177
|
+
};
|
|
178
|
+
case 'halted':
|
|
179
|
+
return {
|
|
180
|
+
state: 'halted',
|
|
181
|
+
reason: outcome.code,
|
|
182
|
+
message: `halted: ${outcome.code}`,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function appendTransition(history, transition, cap) {
|
|
187
|
+
return capHistory([...history, transition], cap);
|
|
188
|
+
}
|
|
189
|
+
/** The newest `cap` entries; also trims a stored history written under a larger cap. */
|
|
190
|
+
function capHistory(history, cap) {
|
|
191
|
+
return cap === 0 ? [] : history.slice(-cap);
|
|
192
|
+
}
|
|
193
|
+
function cutMessage(message) {
|
|
194
|
+
return cutText(message, MAX_STATE_MESSAGE_CHARACTERS);
|
|
195
|
+
}
|
|
196
|
+
/** Cut on code points, so the record never holds half of a surrogate pair. */
|
|
197
|
+
function cutText(text, maxCharacters) {
|
|
198
|
+
const characters = Array.from(text);
|
|
199
|
+
if (characters.length <= maxCharacters) {
|
|
200
|
+
return text;
|
|
201
|
+
}
|
|
202
|
+
return `${characters.slice(0, maxCharacters - ELLIPSIS.length).join('')}${ELLIPSIS}`;
|
|
203
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Upstream } from '../../contracts/gitTypes.js';
|
|
2
|
+
import type { ConflictKey } from '../../contracts/syncTypes.js';
|
|
3
|
+
/** At most this many paths are named; the rest are counted. */
|
|
4
|
+
export declare const MAX_GUIDE_PATHS = 20;
|
|
5
|
+
export interface ConflictGuideInput {
|
|
6
|
+
/** When the record entered the conflict state. */
|
|
7
|
+
readonly since: string;
|
|
8
|
+
readonly conflict: ConflictKey;
|
|
9
|
+
readonly branchRef: string;
|
|
10
|
+
readonly upstream: Upstream;
|
|
11
|
+
/** Local commits not on the upstream; null when unknown. */
|
|
12
|
+
readonly unpushed: number | null;
|
|
13
|
+
/** pathsOnBothSides of the two sides since their merge base. */
|
|
14
|
+
readonly paths: readonly string[];
|
|
15
|
+
}
|
|
16
|
+
/** One status line: label '' continues the line above. */
|
|
17
|
+
export interface GuideLine {
|
|
18
|
+
readonly label: string;
|
|
19
|
+
readonly value: string;
|
|
20
|
+
}
|
|
21
|
+
/** Paths in the local list that the upstream list also has, in the local order, each once. */
|
|
22
|
+
export declare function pathsOnBothSides(local: readonly string[], upstream: readonly string[]): readonly string[];
|
|
23
|
+
/**
|
|
24
|
+
* The conflict guide of design 12.3: what is held, which paths, that the
|
|
25
|
+
* local commits are kept untouched, and the exact commands for each way a
|
|
26
|
+
* held conflict ends (Decision 49). Every command is plain git a person can
|
|
27
|
+
* paste; the reset names the full object id, because a short id can become
|
|
28
|
+
* ambiguous and a reset to the wrong commit discards work.
|
|
29
|
+
*/
|
|
30
|
+
export declare function conflictGuide(input: ConflictGuideInput): readonly GuideLine[];
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/** At most this many paths are named; the rest are counted. */
|
|
2
|
+
export const MAX_GUIDE_PATHS = 20;
|
|
3
|
+
const BRANCH_PREFIX = 'refs/heads/';
|
|
4
|
+
const SHORT_SHA_LENGTH = 7;
|
|
5
|
+
/** Paths in the local list that the upstream list also has, in the local order, each once. */
|
|
6
|
+
export function pathsOnBothSides(local, upstream) {
|
|
7
|
+
const theirs = new Set(upstream);
|
|
8
|
+
const both = new Set();
|
|
9
|
+
for (const path of local) {
|
|
10
|
+
if (theirs.has(path)) {
|
|
11
|
+
both.add(path);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return Array.from(both);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The conflict guide of design 12.3: what is held, which paths, that the
|
|
18
|
+
* local commits are kept untouched, and the exact commands for each way a
|
|
19
|
+
* held conflict ends (Decision 49). Every command is plain git a person can
|
|
20
|
+
* paste; the reset names the full object id, because a short id can become
|
|
21
|
+
* ambiguous and a reset to the wrong commit discards work.
|
|
22
|
+
*/
|
|
23
|
+
export function conflictGuide(input) {
|
|
24
|
+
const { conflict, upstream } = input;
|
|
25
|
+
const branch = shortBranch(input.branchRef);
|
|
26
|
+
const remoteBranch = shortBranch(upstream.remoteRef);
|
|
27
|
+
const pull = `${upstream.remote} ${remoteBranch}`;
|
|
28
|
+
return [
|
|
29
|
+
{
|
|
30
|
+
label: 'conflict',
|
|
31
|
+
value: `held since ${input.since}: the rebase of ${shortSha(conflict.head)} onto ${shortSha(conflict.upstream)} (${upstream.remote} ${upstream.remoteRef}) stopped and was undone`,
|
|
32
|
+
},
|
|
33
|
+
{ label: 'paths', value: describePaths(input.paths, conflict) },
|
|
34
|
+
{
|
|
35
|
+
label: 'kept',
|
|
36
|
+
value: `${describeLocalCommits(input.unpushed)} on ${branch} untouched; none is pushed and nothing is pulled until the conflict ends`,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
label: 'end it',
|
|
40
|
+
value: '1. wait: when the upstream changes so the replay no longer conflicts, enmetu continues by itself',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
label: '',
|
|
44
|
+
value: `2. rebase by hand: git pull --rebase ${pull}, resolve, git add <path>, git rebase --continue`,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
label: '',
|
|
48
|
+
// --no-ff overrides merge.ff=only, which makes `git pull --no-rebase` refuse
|
|
49
|
+
// a merge of diverged histories (measured with git 2.52; pull.ff=only is
|
|
50
|
+
// already overridden by --no-rebase). While a conflict is held the
|
|
51
|
+
// histories have diverged, so the flag changes nothing else.
|
|
52
|
+
value: `3. merge by hand: git pull --no-rebase --no-ff ${pull}, resolve, git add <path>, git commit`,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
label: '',
|
|
56
|
+
value: `4. give up the local commits: git reset --hard ${conflict.upstream} (this also discards uncommitted changes; the local commits stay in the reflog until git prunes them)`,
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
function describePaths(paths, conflict) {
|
|
61
|
+
if (paths.length === 0) {
|
|
62
|
+
// Two commits, not the three-dot form: that one needs a merge base, which
|
|
63
|
+
// unrelated histories lack (git exits 128), and shows only the upstream side.
|
|
64
|
+
return `none found; compare them with: git diff ${conflict.head} ${conflict.upstream}`;
|
|
65
|
+
}
|
|
66
|
+
const named = paths.slice(0, MAX_GUIDE_PATHS).join(', ');
|
|
67
|
+
const rest = paths.length - MAX_GUIDE_PATHS;
|
|
68
|
+
const more = rest > 0 ? ` and ${rest} more` : '';
|
|
69
|
+
return `${named}${more} (changed on both sides; at least one conflicts)`;
|
|
70
|
+
}
|
|
71
|
+
function describeLocalCommits(unpushed) {
|
|
72
|
+
if (unpushed === null) {
|
|
73
|
+
return 'the local commits stay';
|
|
74
|
+
}
|
|
75
|
+
if (unpushed === 1) {
|
|
76
|
+
return '1 local commit stays';
|
|
77
|
+
}
|
|
78
|
+
return `${unpushed} local commits stay`;
|
|
79
|
+
}
|
|
80
|
+
// A ref outside refs/heads keeps its full name, which git pull accepts too.
|
|
81
|
+
function shortBranch(ref) {
|
|
82
|
+
return ref.startsWith(BRANCH_PREFIX) ? ref.slice(BRANCH_PREFIX.length) : ref;
|
|
83
|
+
}
|
|
84
|
+
function shortSha(sha) {
|
|
85
|
+
return sha.slice(0, SHORT_SHA_LENGTH);
|
|
86
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { OrphanedCommit } from '../../contracts/IStateStore.js';
|
|
2
|
+
import type { GuideLine } from './conflictGuide.js';
|
|
3
|
+
/**
|
|
4
|
+
* The command that puts an orphaned commit on the person's branch (design
|
|
5
|
+
* 14.15). -x records "(cherry picked from commit <id>)" in each new commit,
|
|
6
|
+
* also after a resolved conflict (measured, git 2.52), which is how status
|
|
7
|
+
* tells a picked orphan from a pending one: the pick gives it a new id, so
|
|
8
|
+
* the original never becomes reachable.
|
|
9
|
+
*/
|
|
10
|
+
export declare function orphanRecoveryCommand(orphan: OrphanedCommit): string;
|
|
11
|
+
/**
|
|
12
|
+
* One `orphaned` line per pending orphan, oldest first (design 14.15), after
|
|
13
|
+
* the uncommitted line. The full ids stay in the command, so it can be pasted unchanged.
|
|
14
|
+
*/
|
|
15
|
+
export declare function orphanedCommitLines(pending: readonly OrphanedCommit[]): readonly GuideLine[];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const SHORT_SHA_LENGTH = 7;
|
|
2
|
+
/**
|
|
3
|
+
* The command that puts an orphaned commit on the person's branch (design
|
|
4
|
+
* 14.15). -x records "(cherry picked from commit <id>)" in each new commit,
|
|
5
|
+
* also after a resolved conflict (measured, git 2.52), which is how status
|
|
6
|
+
* tells a picked orphan from a pending one: the pick gives it a new id, so
|
|
7
|
+
* the original never becomes reachable.
|
|
8
|
+
*/
|
|
9
|
+
export function orphanRecoveryCommand(orphan) {
|
|
10
|
+
return `git cherry-pick -x ${orphan.base}..${orphan.commit}`;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* One `orphaned` line per pending orphan, oldest first (design 14.15), after
|
|
14
|
+
* the uncommitted line. The full ids stay in the command, so it can be pasted unchanged.
|
|
15
|
+
*/
|
|
16
|
+
export function orphanedCommitLines(pending) {
|
|
17
|
+
return pending.map((orphan) => {
|
|
18
|
+
const subject = orphan.subject === '' ? '' : ` "${orphan.subject}"`;
|
|
19
|
+
return {
|
|
20
|
+
label: 'orphaned',
|
|
21
|
+
value: `${orphan.commit.slice(0, SHORT_SHA_LENGTH)}${subject} is on no branch since ${orphan.at}; ${orphanRecoveryCommand(orphan)} puts it on yours`,
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { HaltRecord } from '../../contracts/IHaltStore.js';
|
|
2
|
+
import type { OrphanedCommit, StateRecord } from '../../contracts/IStateStore.js';
|
|
3
|
+
import type { StateLiveness } from '../../contracts/library.js';
|
|
4
|
+
/** Local commits not on the upstream, counted by status from git (never from the record). */
|
|
5
|
+
export type UnpushedFact = {
|
|
6
|
+
readonly kind: 'counted';
|
|
7
|
+
readonly ahead: number;
|
|
8
|
+
}
|
|
9
|
+
/** An upstream is configured, but neither its tracking ref nor enmetu's verified ref exists. */
|
|
10
|
+
| {
|
|
11
|
+
readonly kind: 'unknown';
|
|
12
|
+
}
|
|
13
|
+
/** Detached HEAD, or a branch without upstream. */
|
|
14
|
+
| {
|
|
15
|
+
readonly kind: 'no-upstream';
|
|
16
|
+
};
|
|
17
|
+
export interface HeadlineInput {
|
|
18
|
+
readonly halt: HaltRecord | null;
|
|
19
|
+
readonly liveness: StateLiveness;
|
|
20
|
+
readonly record: StateRecord | null;
|
|
21
|
+
readonly unpushed: UnpushedFact;
|
|
22
|
+
/**
|
|
23
|
+
* Paths git status reports changed or untracked, counted live by status:
|
|
24
|
+
* edits inside the quiet window, files the content guard excludes, what a
|
|
25
|
+
* hook rejected or left. Work that exists only on this disk (design 12.2).
|
|
26
|
+
*/
|
|
27
|
+
readonly uncommitted: number;
|
|
28
|
+
/**
|
|
29
|
+
* The recorded orphaned commits git still finds on no local branch, judged
|
|
30
|
+
* live by status (design 14.15); oldest first.
|
|
31
|
+
*/
|
|
32
|
+
readonly orphaned: readonly OrphanedCommit[];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The first line of `enmetu status` (design 12.2): one answer to "is my work
|
|
36
|
+
* safe and pushed?", starting with one of safe, pending, syncing, waiting,
|
|
37
|
+
* attention, paused, offline, busy, starting, halted, running or not running.
|
|
38
|
+
* safe only when nothing is uncommitted and nothing is unpushed.
|
|
39
|
+
*
|
|
40
|
+
* The rows of the 12.2 table in order, first match wins, with design 14.15's
|
|
41
|
+
* orphaned-commit row right after the halt row. stateLiveness never
|
|
42
|
+
* pairs stopped, crashed or running with a missing record; should a caller do
|
|
43
|
+
* so anyway, the line falls back to the "no state recorded" rows, which are
|
|
44
|
+
* true for that input.
|
|
45
|
+
*/
|
|
46
|
+
export declare function statusHeadline(input: HeadlineInput): string;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/** `<sha7>` of design 12.2. */
|
|
2
|
+
const SHORT_SHA_LENGTH = 7;
|
|
3
|
+
/**
|
|
4
|
+
* The first line of `enmetu status` (design 12.2): one answer to "is my work
|
|
5
|
+
* safe and pushed?", starting with one of safe, pending, syncing, waiting,
|
|
6
|
+
* attention, paused, offline, busy, starting, halted, running or not running.
|
|
7
|
+
* safe only when nothing is uncommitted and nothing is unpushed.
|
|
8
|
+
*
|
|
9
|
+
* The rows of the 12.2 table in order, first match wins, with design 14.15's
|
|
10
|
+
* orphaned-commit row right after the halt row. stateLiveness never
|
|
11
|
+
* pairs stopped, crashed or running with a missing record; should a caller do
|
|
12
|
+
* so anyway, the line falls back to the "no state recorded" rows, which are
|
|
13
|
+
* true for that input.
|
|
14
|
+
*/
|
|
15
|
+
export function statusHeadline(input) {
|
|
16
|
+
const work = workText(input.uncommitted, input.unpushed);
|
|
17
|
+
if (input.halt !== null) {
|
|
18
|
+
return `halted: ${input.halt.code} since ${input.halt.at}, ${work}`;
|
|
19
|
+
}
|
|
20
|
+
// A commit on no branch is work that is neither committed on the branch nor
|
|
21
|
+
// pushed, whatever the daemon does now, so it outranks every liveness row;
|
|
22
|
+
// the instance and state lines still say whether enmetu runs (design 14.15).
|
|
23
|
+
const oldest = input.orphaned[0];
|
|
24
|
+
if (oldest !== undefined) {
|
|
25
|
+
const commits = input.orphaned.length === 1
|
|
26
|
+
? '1 commit'
|
|
27
|
+
: `${input.orphaned.length} commits`;
|
|
28
|
+
return `attention: ${commits} on no branch since ${oldest.at}, ${work}`;
|
|
29
|
+
}
|
|
30
|
+
const record = input.record;
|
|
31
|
+
switch (input.liveness) {
|
|
32
|
+
case 'never':
|
|
33
|
+
return `not running: no state recorded, ${work}`;
|
|
34
|
+
case 'stopped':
|
|
35
|
+
return record === null
|
|
36
|
+
? `not running: no state recorded, ${work}`
|
|
37
|
+
: `not running: last state stopped at ${record.since}, ${work}`;
|
|
38
|
+
case 'crashed':
|
|
39
|
+
return record === null
|
|
40
|
+
? `not running: no state recorded, ${work}`
|
|
41
|
+
: `not running: last state ${record.state} at ${record.since}; enmetu ended without recording a stop, ${work}`;
|
|
42
|
+
case 'unrecorded':
|
|
43
|
+
return `running: no state recorded yet, ${work}`;
|
|
44
|
+
case 'running':
|
|
45
|
+
return record === null
|
|
46
|
+
? `running: no state recorded yet, ${work}`
|
|
47
|
+
: runningHeadline(record, input.uncommitted, input.unpushed);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function runningHeadline(record, uncommitted, unpushed) {
|
|
51
|
+
const work = workText(uncommitted, unpushed);
|
|
52
|
+
const since = record.since;
|
|
53
|
+
// A throw says nothing about whether the recorded state still holds
|
|
54
|
+
// (design 12.1), so a failed cycle outranks the state.
|
|
55
|
+
if (record.lastCycle?.outcome === 'failed') {
|
|
56
|
+
return `attention: the last cycle failed at ${record.lastCycle.at}, ${work}`;
|
|
57
|
+
}
|
|
58
|
+
switch (record.state) {
|
|
59
|
+
case 'synced':
|
|
60
|
+
return syncedHeadline(record, uncommitted, unpushed);
|
|
61
|
+
case 'conflict':
|
|
62
|
+
return `attention: conflict since ${since}, ${work}`;
|
|
63
|
+
case 'push-failed':
|
|
64
|
+
return `attention: pushes failing since ${since}, ${work}`;
|
|
65
|
+
case 'waiting':
|
|
66
|
+
if (record.reason === 'PARTIAL_SELECTION_STAGED') {
|
|
67
|
+
return `waiting: a partial selection is staged since ${since}; nothing is committed or pulled until it is committed or unstaged, ${work}`;
|
|
68
|
+
}
|
|
69
|
+
if (record.reason === 'COMMIT_HOOK_REJECTED') {
|
|
70
|
+
return `waiting: a commit hook rejected the last commit since ${since}, ${work}`;
|
|
71
|
+
}
|
|
72
|
+
return `waiting:${reasonText(record)} since ${since}, ${work}`;
|
|
73
|
+
case 'paused':
|
|
74
|
+
return `paused:${reasonText(record)} since ${since}, ${work}`;
|
|
75
|
+
case 'offline':
|
|
76
|
+
return `offline: since ${since}, ${work}`;
|
|
77
|
+
case 'busy':
|
|
78
|
+
return `busy: git is busy since ${since}, ${work}`;
|
|
79
|
+
case 'starting':
|
|
80
|
+
return `starting: since ${since}, ${work}`;
|
|
81
|
+
case 'halted':
|
|
82
|
+
return `halted:${reasonText(record)} since ${since}, ${work}`;
|
|
83
|
+
case 'stopped':
|
|
84
|
+
// stateLiveness judges a stopped record stopped, never running; kept so
|
|
85
|
+
// every input has a line.
|
|
86
|
+
return `not running: last state stopped at ${since}, ${work}`;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** safe only at 0 uncommitted and a counted 0 unpushed (Decision 85). */
|
|
90
|
+
function syncedHeadline(record, uncommitted, unpushed) {
|
|
91
|
+
const pushed = pushedText(record.pushed);
|
|
92
|
+
if (uncommitted > 0) {
|
|
93
|
+
return `pending: ${uncommitted} uncommitted, ${unpushedText(unpushed)}, ${pushed}`;
|
|
94
|
+
}
|
|
95
|
+
if (unpushed.kind === 'counted' && unpushed.ahead === 0) {
|
|
96
|
+
return record.pushed === null
|
|
97
|
+
? 'safe: 0 unpushed'
|
|
98
|
+
: `safe: pushed ${shortSha(record.pushed)}, 0 unpushed`;
|
|
99
|
+
}
|
|
100
|
+
return `syncing: ${unpushedText(unpushed)}, ${pushed}`;
|
|
101
|
+
}
|
|
102
|
+
/** `<W>`: the uncommitted count first when there is one, then `<U>`. */
|
|
103
|
+
function workText(uncommitted, unpushed) {
|
|
104
|
+
const counted = unpushedText(unpushed);
|
|
105
|
+
return uncommitted > 0 ? `${uncommitted} uncommitted, ${counted}` : counted;
|
|
106
|
+
}
|
|
107
|
+
/** `<U>`. */
|
|
108
|
+
function unpushedText(unpushed) {
|
|
109
|
+
switch (unpushed.kind) {
|
|
110
|
+
case 'counted':
|
|
111
|
+
return `${unpushed.ahead} unpushed`;
|
|
112
|
+
case 'unknown':
|
|
113
|
+
return 'unpushed unknown';
|
|
114
|
+
case 'no-upstream':
|
|
115
|
+
return 'no upstream';
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/** `<P>`. */
|
|
119
|
+
function pushedText(pushed) {
|
|
120
|
+
return pushed === null
|
|
121
|
+
? 'nothing pushed yet'
|
|
122
|
+
: `last pushed ${shortSha(pushed)}`;
|
|
123
|
+
}
|
|
124
|
+
/** ` <REASON>`, or nothing for a record that carries none. */
|
|
125
|
+
function reasonText(record) {
|
|
126
|
+
return record.reason === null ? '' : ` ${record.reason}`;
|
|
127
|
+
}
|
|
128
|
+
function shortSha(sha) {
|
|
129
|
+
return sha.slice(0, SHORT_SHA_LENGTH);
|
|
130
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Matched against whole path components, so .gitignore, .github/ and
|
|
2
|
+
// node_modules.md are not noise (D8).
|
|
3
|
+
const NOISE_COMPONENTS = new Set(['.git', 'node_modules']);
|
|
4
|
+
const NOISE_BASENAMES = new Set([
|
|
5
|
+
'.DS_Store',
|
|
6
|
+
'Thumbs.db',
|
|
7
|
+
'.Spotlight-V100',
|
|
8
|
+
'.Trashes',
|
|
9
|
+
'.fseventsd',
|
|
10
|
+
// vim creates and deletes this file to probe whether it may write the directory.
|
|
11
|
+
'4913',
|
|
12
|
+
]);
|
|
13
|
+
const NOISE_BASENAME_PATTERNS = [
|
|
14
|
+
/\.sw[a-p]$/,
|
|
15
|
+
/~$/,
|
|
16
|
+
/^\.#/,
|
|
17
|
+
/^#.*#$/,
|
|
18
|
+
/___jb_(tmp|old)___$/,
|
|
19
|
+
/\.crswap$/,
|
|
20
|
+
/^\.~lock\..*#$/,
|
|
21
|
+
];
|
|
22
|
+
/** filename null (the platform did not say) is a change, never noise. */
|
|
23
|
+
export function isWatchNoise(relativePath) {
|
|
24
|
+
if (relativePath === null) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
const components = relativePath.split('/');
|
|
28
|
+
if (components.some((component) => NOISE_COMPONENTS.has(component))) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
const basename = components[components.length - 1] ?? '';
|
|
32
|
+
return (NOISE_BASENAMES.has(basename) ||
|
|
33
|
+
NOISE_BASENAME_PATTERNS.some((pattern) => pattern.test(basename)));
|
|
34
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type ExitCode } from '../contracts/exitCode.js';
|
|
2
|
+
import type { IClock } from '../contracts/IClock.js';
|
|
3
|
+
import type { IDaemon, IPreflight, IRecovery } from '../contracts/IDaemon.js';
|
|
4
|
+
import type { IHaltStore } from '../contracts/IHaltStore.js';
|
|
5
|
+
import type { IHookInstaller } from '../contracts/IHookInstaller.js';
|
|
6
|
+
import type { IInstanceLock } from '../contracts/IInstanceLock.js';
|
|
7
|
+
import type { ILogger } from '../contracts/ILogger.js';
|
|
8
|
+
import type { IProcessHost } from '../contracts/IProcessHost.js';
|
|
9
|
+
import type { IStateRecorder } from '../contracts/IStateRecorder.js';
|
|
10
|
+
import type { ISyncScheduler } from '../contracts/ISyncScheduler.js';
|
|
11
|
+
import type { IWatcher } from '../contracts/IWatcher.js';
|
|
12
|
+
/** What the STARTED line reports besides the upstream. */
|
|
13
|
+
export interface DaemonSettings {
|
|
14
|
+
readonly root: string;
|
|
15
|
+
readonly quietMs: number;
|
|
16
|
+
readonly pollMs: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A copy of the first stop signal arriving within this window is the same
|
|
20
|
+
* stop, not a second one (Decision 91): a forwarding parent's copy arrives
|
|
21
|
+
* within milliseconds, a person's deliberate second Ctrl-C takes longer.
|
|
22
|
+
*/
|
|
23
|
+
export declare const DUPLICATE_SIGNAL_WINDOW_MS = 500;
|
|
24
|
+
/**
|
|
25
|
+
* Service: the daemon lifecycle of design 1.16, steps 1 to 10. Steps 2 to 10
|
|
26
|
+
* run inside a try/finally that releases the lock on every exit path,
|
|
27
|
+
* including a throw; the final line is logged after the release, so STOPPED
|
|
28
|
+
* means the lock is gone.
|
|
29
|
+
*/
|
|
30
|
+
export declare class Daemon implements IDaemon {
|
|
31
|
+
private readonly settings;
|
|
32
|
+
private readonly lock;
|
|
33
|
+
private readonly halt;
|
|
34
|
+
private readonly recovery;
|
|
35
|
+
private readonly preflight;
|
|
36
|
+
private readonly hookInstaller;
|
|
37
|
+
private readonly watcher;
|
|
38
|
+
private readonly scheduler;
|
|
39
|
+
private readonly processHost;
|
|
40
|
+
private readonly logger;
|
|
41
|
+
/** Lifecycle events for state.json, recorded while the lock is held (design 12.1). */
|
|
42
|
+
private readonly state;
|
|
43
|
+
/** Tells a copy of the first stop signal from a second one (Decision 91). */
|
|
44
|
+
private readonly clock;
|
|
45
|
+
constructor(settings: DaemonSettings, lock: IInstanceLock, halt: IHaltStore, recovery: IRecovery, preflight: IPreflight, hookInstaller: IHookInstaller, watcher: IWatcher, scheduler: ISyncScheduler, processHost: IProcessHost, logger: ILogger,
|
|
46
|
+
/** Lifecycle events for state.json, recorded while the lock is held (design 12.1). */
|
|
47
|
+
state: IStateRecorder,
|
|
48
|
+
/** Tells a copy of the first stop signal from a second one (Decision 91). */
|
|
49
|
+
clock: IClock);
|
|
50
|
+
run(): Promise<ExitCode>;
|
|
51
|
+
/** The holder is this process; its identity is what stateLiveness compares. */
|
|
52
|
+
private recordStarted;
|
|
53
|
+
/** Steps 2 to 10, with the lock held. */
|
|
54
|
+
private runLocked;
|
|
55
|
+
}
|