@remit/doctor 0.0.1 → 0.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remit/doctor",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
package/src/cli.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  NO_VERDICT_EXIT_CODE,
7
7
  renderJson,
8
8
  renderLines,
9
+ writeVerdict,
9
10
  } from "./report.js";
10
11
  import { readState } from "./state.js";
11
12
 
@@ -31,7 +32,10 @@ const check = async (): Promise<number> => {
31
32
  setLogLevel(config.logLevel);
32
33
  const state = await readState(config.stateDir);
33
34
  const result = await runCheck(config, state.counters);
34
- process.stdout.write(json ? renderJson(result) : renderLines(result));
35
+ await writeVerdict(
36
+ process.stdout,
37
+ json ? renderJson(result) : renderLines(result),
38
+ );
35
39
  return exitCodeFor(result);
36
40
  };
37
41
 
package/src/index.ts CHANGED
@@ -10,6 +10,7 @@ export {
10
10
  NO_VERDICT_EXIT_CODE,
11
11
  renderJson,
12
12
  renderLines,
13
+ writeVerdict,
13
14
  } from "./report.js";
14
15
  export type { DoctorState } from "./state.js";
15
16
  export { initialState, readState, writeState } from "./state.js";
@@ -1,6 +1,11 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { describe, it } from "node:test";
3
- import { exitCodeFor, renderJson, renderLines } from "./report.js";
3
+ import {
4
+ exitCodeFor,
5
+ renderJson,
6
+ renderLines,
7
+ writeVerdict,
8
+ } from "./report.js";
4
9
  import type { CheckResult } from "./verdict.js";
5
10
 
6
11
  const degraded: CheckResult = {
@@ -163,3 +168,56 @@ describe("exit codes", () => {
163
168
  assert.equal(exitCodeFor(degraded), 1);
164
169
  });
165
170
  });
171
+
172
+ describe("writeVerdict", () => {
173
+ // The whole point: `process.exit` discards a write the kernel could not
174
+ // take, so the verdict has to be out of the process before the code is
175
+ // returned. A stream that reports back-pressure and drains later is what a
176
+ // pipe under `compose exec -T` does with a long degraded verdict.
177
+ const backPressured = () => {
178
+ const chunks: string[] = [];
179
+ let drain: (() => void) | undefined;
180
+ return {
181
+ chunks,
182
+ release: () => drain?.(),
183
+ stream: {
184
+ write(text: string) {
185
+ chunks.push(text);
186
+ return false;
187
+ },
188
+ once(event: string, listener: () => void) {
189
+ if (event === "drain") drain = listener;
190
+ return this;
191
+ },
192
+ } as unknown as NodeJS.WritableStream,
193
+ };
194
+ };
195
+
196
+ it("does not resolve until the stream drains", async () => {
197
+ const sink = backPressured();
198
+ let settled = false;
199
+ const done = writeVerdict(sink.stream, renderLines(degraded)).then(() => {
200
+ settled = true;
201
+ });
202
+ await new Promise((resolve) => setImmediate(resolve));
203
+ assert.equal(settled, false, "resolved before the stream drained");
204
+ sink.release();
205
+ await done;
206
+ assert.equal(sink.chunks.join(""), renderLines(degraded));
207
+ });
208
+
209
+ it("resolves straight away when the write was taken", async () => {
210
+ const chunks: string[] = [];
211
+ const stream = {
212
+ write(text: string) {
213
+ chunks.push(text);
214
+ return true;
215
+ },
216
+ once() {
217
+ assert.fail("waited on drain after a write that was taken");
218
+ },
219
+ } as unknown as NodeJS.WritableStream;
220
+ await writeVerdict(stream, renderJson(healthy));
221
+ assert.equal(chunks.join(""), renderJson(healthy));
222
+ });
223
+ });
package/src/report.ts CHANGED
@@ -104,3 +104,26 @@ export const exitCodeFor = (result: CheckResult): number =>
104
104
  result.verdict === "healthy" ? 0 : 1;
105
105
 
106
106
  export const NO_VERDICT_EXIT_CODE = 2;
107
+
108
+ /**
109
+ * The verdict has to have left the process before it exits.
110
+ *
111
+ * `process.exit` does not flush a pending write, and stdout is a pipe under
112
+ * `compose exec -T`, so a write the kernel could not take in one go is
113
+ * discarded — silently, and only for the verdicts long enough to fill the
114
+ * buffer, which are the degraded ones carrying several reasons. The reader
115
+ * then sees a document that stops mid-token with an exit code saying it is
116
+ * complete. Waiting for the drain and keeping the explicit exit is what closes
117
+ * that without letting a lingering socket hold the process open instead.
118
+ */
119
+ export const writeVerdict = (
120
+ stream: NodeJS.WritableStream,
121
+ text: string,
122
+ ): Promise<void> =>
123
+ new Promise((resolve) => {
124
+ if (stream.write(text)) {
125
+ resolve();
126
+ return;
127
+ }
128
+ stream.once("drain", () => resolve());
129
+ });