@forumone/throughline 1.0.2-next-0ecb757 → 1.0.2-next-8212772

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @forumone/throughline
2
2
 
3
- ## 1.0.2-next-0ecb757
3
+ ## 1.0.2-next-8212772
4
4
 
5
5
  ### Patch Changes
6
6
 
@@ -8,7 +8,16 @@
8
8
 
9
9
  - **Confirming a decision from an approval email works on Postgres.** The action endpoint wrote the token's approver id as a string into `decidedBy`, a relationship to a users collection whose ids are numbers there, so every emailed decision failed on Confirm. It now writes the id in the users collection's type, as `request_approval` and `respond_to_approval` do since 1.0.1.
10
10
  - **A stored request or a recorded decision is reported as one when its event cannot be sent.** `request_approval`, `respond_to_approval` and the email action endpoint wrote first and then threw if `approval/requested`, `approval/decided` or `approval/discussed` failed to send, reporting an action that had landed as failed. The tools now return it with a `warnings` entry, and the endpoint shows the approver their confirmation and logs the warning — the same rule publishing follows. `sendEventSafely` moved to a shared module for it.
11
- - @forumone/throughline-design-system@1.0.2-next-0ecb757
11
+
12
+ - 8212772: `throughline-payload` fails a Payload CLI run that exits 0 without finishing.
13
+
14
+ Intermittently the Payload CLI's config `import()` never settles, and node exits
15
+ by itself with status 0 having printed nothing and done nothing
16
+ (payloadcms/payload#17757). A `migrate` that does that reports success over an
17
+ unmigrated database. Every Payload command that completes calls `process.exit`,
18
+ so the runner now preloads a guard that turns an exit nobody asked for into
19
+ status 70, with a message saying to run it again.
20
+ - @forumone/throughline-design-system@1.0.2-next-8212772
12
21
 
13
22
  ## 1.0.1
14
23
 
@@ -29,6 +29,10 @@ PAYLOAD_CLI_TIMEOUT_MS sets the wall clock; 0 disables it. The default is five
29
29
  minutes, except for the `migrate` family (`migrate`, `migrate:create`, …),
30
30
  which gets none: a large migration is legitimately long, and `migrate:create`
31
31
  can stop at an interactive prompt. Layers 1 and 3 apply regardless.
32
+
33
+ One more failure is the opposite of a hang: the CLI exits 0 having done nothing
34
+ (payloadcms/payload#17757). `payload-exit-guard.mjs` is preloaded into the child
35
+ to make that exit non-zero — see that file.
32
36
  */
33
37
  import { execFile, spawn } from 'node:child_process'
34
38
  import { createRequire } from 'node:module'
@@ -45,6 +49,13 @@ export const DEFAULT_GRACE_MS = 5_000
45
49
  /** Exit code for "gave up waiting", as coreutils `timeout` uses. */
46
50
  export const TIMEOUT_EXIT_CODE = 124
47
51
 
52
+ /** Preloaded into every run, so a CLI that exits 0 without finishing fails. */
53
+ // A URL, not a path: `--import` refuses a Windows drive path.
54
+ export const EXIT_GUARD = new URL('./payload-exit-guard.mjs', import.meta.url).href
55
+
56
+ /** The node argv for one run of the Payload CLI at `bin`. */
57
+ export const payloadArgv = (bin, args) => ['--import', EXIT_GUARD, bin, ...args]
58
+
48
59
  const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
49
60
  const log = (message) => process.stderr.write(`throughline-payload: ${message}\n`)
50
61
 
@@ -337,7 +348,7 @@ export async function main({
337
348
  if (process.platform === 'win32') {
338
349
  // No process groups and no `ps`: run it plainly rather than not at all.
339
350
  if (parsed.mode === 'reap') return
340
- const child = spawn(process.execPath, [resolvePayloadBin(cwd), ...parsed.args], {
351
+ const child = spawn(process.execPath, payloadArgv(resolvePayloadBin(cwd), parsed.args), {
341
352
  stdio: 'inherit',
342
353
  cwd,
343
354
  })
@@ -360,7 +371,7 @@ export async function main({
360
371
  }
361
372
 
362
373
  const store = pidFileStore(root)
363
- const childArgv = [bin, ...parsed.args]
374
+ const childArgv = payloadArgv(bin, parsed.args)
364
375
  const child = spawn(process.execPath, childArgv, { stdio: 'inherit', detached: true, cwd, env })
365
376
 
366
377
  let timer
@@ -0,0 +1,36 @@
1
+ /*
2
+ Preloaded into the Payload CLI by `payload-cli.mjs` (`node --import`), to turn
3
+ one silent failure into a loud one.
4
+
5
+ Every Payload command that finishes calls `process.exit` — `0` once the work is
6
+ done, `1` on an error. Intermittently the config's dynamic `import()` never
7
+ settles instead (payloadcms/payload#17757): nothing is left on the event loop,
8
+ so Node exits by itself, with status 0, having printed nothing and done
9
+ nothing. A `migrate` that does that reports success over an unmigrated
10
+ database; a `generate:*` reports success over a stale file.
11
+
12
+ So a natural exit with status 0 is not success here: it is this. This module
13
+ records whether `process.exit` was called, and if it was not, replaces the 0
14
+ with NO_COMPLETION_EXIT_CODE. Any other status — a non-zero code, an uncaught
15
+ error, a signal — is left exactly as it was.
16
+ */
17
+
18
+ /** EX_SOFTWARE in sysexits.h: an internal error, not a usage or data error. */
19
+ export const NO_COMPLETION_EXIT_CODE = 70
20
+
21
+ let called = false
22
+ const exit = process.exit.bind(process)
23
+ process.exit = (code) => {
24
+ called = true
25
+ return exit(code)
26
+ }
27
+
28
+ process.on('exit', (code) => {
29
+ if (called || code !== 0) return
30
+ process.stderr.write(
31
+ 'throughline-payload: the Payload CLI exited without finishing — it never ' +
32
+ 'called process.exit, so the command did not run (payloadcms/payload#17757). ' +
33
+ 'Run it again.\n',
34
+ )
35
+ process.exitCode = NO_COMPLETION_EXIT_CODE
36
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forumone/throughline",
3
- "version": "1.0.2-next-0ecb757",
3
+ "version": "1.0.2-next-8212772",
4
4
  "description": "Throughline: an editorial platform for Payload CMS. Publishing, approvals, audit, integrations, email and jobs, with every feature reachable over MCP.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -162,7 +162,7 @@
162
162
  "dependencies": {
163
163
  "zod": "^3.23.0",
164
164
  "zod-to-json-schema": "^3.25.2",
165
- "@forumone/throughline-design-system": "1.0.2-next-0ecb757"
165
+ "@forumone/throughline-design-system": "1.0.2-next-8212772"
166
166
  },
167
167
  "devDependencies": {
168
168
  "@payloadcms/db-sqlite": "^3.83.0",
@@ -185,7 +185,7 @@
185
185
  "typescript": "^5.6.0",
186
186
  "vitest": "^4.1.10",
187
187
  "@forumone/throughline-eslint-config": "0.0.1",
188
- "@forumone/throughline-reference-ds": "0.3.10-next-0ecb757",
188
+ "@forumone/throughline-reference-ds": "0.3.10-next-8212772",
189
189
  "@forumone/throughline-tsconfig": "0.0.1"
190
190
  },
191
191
  "scripts": {