@plurnk/plurnk-hooks 1.5.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/.env.defaults +11 -0
- package/README.md +66 -0
- package/SPEC.md +48 -0
- package/dist/Module.d.ts +16 -0
- package/dist/Module.d.ts.map +1 -0
- package/dist/Module.js +69 -0
- package/dist/Module.js.map +1 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +69 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -0
package/.env.defaults
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# --- @plurnk/plurnk-hooks : exact command lifecycle hooks ---
|
|
2
|
+
|
|
3
|
+
# Hooks are disabled until both an exact executable and explicit event selection
|
|
4
|
+
# are configured. Arguments are a JSON array; no shell command is interpreted.
|
|
5
|
+
# PLURNK_HOOKS_COMMAND=/usr/bin/node
|
|
6
|
+
# PLURNK_HOOKS_ARGS=["/absolute/path/to/plurnk-hook.mjs"]
|
|
7
|
+
# PLURNK_HOOKS_EVENTS=loop/terminated,loop/proposal,notice/event
|
|
8
|
+
|
|
9
|
+
# One hook process receives one event envelope on stdin. This positive integer
|
|
10
|
+
# bounds each delivery; expiry terminates that process without affecting the loop.
|
|
11
|
+
PLURNK_HOOKS_TIMEOUT_MS=30000
|
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# @plurnk/plurnk-hooks
|
|
2
|
+
|
|
3
|
+
First-party local command hooks for
|
|
4
|
+
[Plurnk](https://github.com/plurnk/plurnk-service). The module selects existing
|
|
5
|
+
core lifecycle events and delivers each unchanged event envelope to one exact
|
|
6
|
+
executable on stdin. It does not define another event bus or invoke a shell.
|
|
7
|
+
|
|
8
|
+
## Configure
|
|
9
|
+
|
|
10
|
+
Put the following in `~/.plurnk/.env`:
|
|
11
|
+
|
|
12
|
+
```dotenv
|
|
13
|
+
PLURNK_HOOKS_COMMAND=/usr/bin/node
|
|
14
|
+
PLURNK_HOOKS_ARGS=["/absolute/path/to/plurnk-hook.mjs"]
|
|
15
|
+
PLURNK_HOOKS_EVENTS=loop/terminated,loop/proposal,notice/event
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`PLURNK_HOOKS_ARGS` is a JSON string array. `PLURNK_HOOKS_EVENTS` is an
|
|
19
|
+
explicit comma-separated selection from:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
log/entry
|
|
23
|
+
loop/proposal
|
|
24
|
+
loop/terminated
|
|
25
|
+
notice/event
|
|
26
|
+
stream/concluded
|
|
27
|
+
stream/event
|
|
28
|
+
workspace/branch-batch
|
|
29
|
+
workspace/created
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Each process receives one line such as:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{"workspaceId":42,"method":"loop/terminated","params":{"workerId":7,"loopId":9,"result":{"status":200}}}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Workspace scope and event-owned worker/loop coordinates are passed through;
|
|
39
|
+
the module does not infer missing coordinates. Standard output and error are
|
|
40
|
+
inherited from the daemon. A spawn, stdin, nonzero-exit, signal, or timeout
|
|
41
|
+
failure is reported to daemon diagnostics without changing loop control flow.
|
|
42
|
+
Event payloads retain the core contract and may contain project or model
|
|
43
|
+
content; treat the command and any downstream sink as trusted.
|
|
44
|
+
|
|
45
|
+
## Test one hook
|
|
46
|
+
|
|
47
|
+
Save this as the script path named in `PLURNK_HOOKS_ARGS`:
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
let input = "";
|
|
51
|
+
process.stdin.setEncoding("utf8");
|
|
52
|
+
for await (const chunk of process.stdin) input += chunk;
|
|
53
|
+
const event = JSON.parse(input);
|
|
54
|
+
process.stdout.write(`${event.method} workspace=${String(event.workspaceId)}\n`);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Test it before restarting the daemon:
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
printf '%s\n' '{"workspaceId":42,"method":"loop/terminated","params":{}}' \
|
|
61
|
+
| /usr/bin/node /absolute/path/to/plurnk-hook.mjs
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The complete contract and event-to-core mapping live in
|
|
65
|
+
[`SPEC.md`](./SPEC.md). Portable defaults live in
|
|
66
|
+
[`.env.defaults`](./.env.defaults).
|
package/SPEC.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Plurnk command hooks specification
|
|
2
|
+
|
|
3
|
+
## §hooks-command-delivery Exact command delivery
|
|
4
|
+
|
|
5
|
+
One selected core event starts one configured executable with its configured
|
|
6
|
+
argument array and `shell: false`. The child receives one complete
|
|
7
|
+
`{ workspaceId, method, params }` JSON envelope followed by a newline on stdin.
|
|
8
|
+
The core-supplied workspace scope and payload pass through without inferred
|
|
9
|
+
coordinates or payload translation. The child inherits the daemon's working
|
|
10
|
+
directory and environment; its standard output and error remain visible in the
|
|
11
|
+
daemon's streams.
|
|
12
|
+
|
|
13
|
+
## §hooks-selection Event selection
|
|
14
|
+
|
|
15
|
+
`PLURNK_HOOKS_EVENTS` is a comma-separated set from the following closed
|
|
16
|
+
inventory. These are core event names, not a hook-local vocabulary or filter
|
|
17
|
+
language.
|
|
18
|
+
|
|
19
|
+
| Exposed event | Owning core notification |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `log/entry` | `{§notifications-log-entry-notify}` |
|
|
22
|
+
| `loop/proposal` | `{§notifications-loop-proposal}` |
|
|
23
|
+
| `loop/terminated` | `{§notifications-loop-terminated}` |
|
|
24
|
+
| `notice/event` | `{§notifications-notice-event}` |
|
|
25
|
+
| `stream/concluded` | `{§notifications-stream-concluded}` |
|
|
26
|
+
| `stream/event` | `{§notifications-stream-event-on-channel-change}` |
|
|
27
|
+
| `workspace/branch-batch` | `{§notifications-workspace-branch-batch}` |
|
|
28
|
+
| `workspace/created` | `{§notifications-workspace-created}` |
|
|
29
|
+
|
|
30
|
+
## §hooks-failure-isolation Failure isolation
|
|
31
|
+
|
|
32
|
+
Configuration errors fail module construction. Delivery runs outside the core
|
|
33
|
+
subscriber call. Spawn, stdin, nonzero-exit, signal, and timeout failures are
|
|
34
|
+
reported to daemon diagnostics and cannot alter loop state or event dispatch.
|
|
35
|
+
Module close first unsubscribes, then settles admitted deliveries; the required
|
|
36
|
+
positive timeout bounds each child process.
|
|
37
|
+
|
|
38
|
+
## §hooks-config Operator configuration
|
|
39
|
+
|
|
40
|
+
| Variable | Contract |
|
|
41
|
+
|---|---|
|
|
42
|
+
| `PLURNK_HOOKS_COMMAND` | One executable; absent disables hooks |
|
|
43
|
+
| `PLURNK_HOOKS_ARGS` | Optional JSON string array; invalid JSON or non-string members fail startup |
|
|
44
|
+
| `PLURNK_HOOKS_EVENTS` | Required with a command; explicit comma-separated names from {§hooks-selection} |
|
|
45
|
+
| `PLURNK_HOOKS_TIMEOUT_MS` | Required positive integer delivery bound |
|
|
46
|
+
|
|
47
|
+
Arguments or events without a command, unknown or duplicate event names,
|
|
48
|
+
shell-shaped command text, and malformed timeouts fail at this module boundary.
|
package/dist/Module.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface HooksSeam {
|
|
2
|
+
subscribeToEvents(handler: (workspaceId: number | null, method: string, params: unknown) => void): () => void;
|
|
3
|
+
}
|
|
4
|
+
export interface ModuleOptions {
|
|
5
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
6
|
+
readonly report?: (message: string, cause: unknown) => void;
|
|
7
|
+
}
|
|
8
|
+
export default class Module {
|
|
9
|
+
#private;
|
|
10
|
+
static init(options?: ModuleOptions): Module;
|
|
11
|
+
private constructor();
|
|
12
|
+
start(seam: HooksSeam): void;
|
|
13
|
+
close(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=Module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Module.d.ts","sourceRoot":"","sources":["../src/Module.ts"],"names":[],"mappings":"AAGA,UAAU,SAAS;IACf,iBAAiB,CACb,OAAO,EAAE,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,GAC/E,MAAM,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC1B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACjC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAC/D;AAED,MAAM,CAAC,OAAO,OAAO,MAAM;;IAOvB,MAAM,CAAC,IAAI,CAAC,OAAO,GAAE,aAAkB,GAAG,MAAM,CAK/C;IAED,OAAO,eAGN;IAED,KAAK,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAkB3B;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAI3B;CAyBJ"}
|
package/dist/Module.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { hookConfig } from "./config.js";
|
|
3
|
+
export default class Module {
|
|
4
|
+
#config;
|
|
5
|
+
#report;
|
|
6
|
+
#active = new Set();
|
|
7
|
+
#unsubscribe = null;
|
|
8
|
+
#started = false;
|
|
9
|
+
static init(options = {}) {
|
|
10
|
+
return new Module(hookConfig(options.env ?? process.env), options.report ?? ((message, cause) => { console.error(`${message}:`, cause); }));
|
|
11
|
+
}
|
|
12
|
+
constructor(config, report) {
|
|
13
|
+
this.#config = config;
|
|
14
|
+
this.#report = report;
|
|
15
|
+
}
|
|
16
|
+
start(seam) {
|
|
17
|
+
if (this.#started)
|
|
18
|
+
throw new Error("hooks module already started");
|
|
19
|
+
this.#started = true;
|
|
20
|
+
if (this.#config === null)
|
|
21
|
+
return;
|
|
22
|
+
this.#unsubscribe = seam.subscribeToEvents((workspaceId, method, params) => {
|
|
23
|
+
if (!this.#config?.events.has(method))
|
|
24
|
+
return;
|
|
25
|
+
let observed;
|
|
26
|
+
observed = this.#deliver({ workspaceId, method, params })
|
|
27
|
+
.catch((cause) => {
|
|
28
|
+
try {
|
|
29
|
+
this.#report(`hook command failed for ${method}`, cause);
|
|
30
|
+
}
|
|
31
|
+
catch (reportCause) {
|
|
32
|
+
console.error(`hook failure reporter failed for ${method}:`, reportCause);
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
.finally(() => { this.#active.delete(observed); });
|
|
36
|
+
this.#active.add(observed);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
async close() {
|
|
40
|
+
this.#unsubscribe?.();
|
|
41
|
+
this.#unsubscribe = null;
|
|
42
|
+
await Promise.all(this.#active);
|
|
43
|
+
}
|
|
44
|
+
#deliver(envelope) {
|
|
45
|
+
const config = this.#config;
|
|
46
|
+
if (config === null)
|
|
47
|
+
return Promise.resolve();
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
const child = spawn(config.command, config.args, {
|
|
50
|
+
shell: false,
|
|
51
|
+
stdio: ["pipe", "inherit", "inherit"],
|
|
52
|
+
signal: AbortSignal.timeout(config.timeoutMs),
|
|
53
|
+
killSignal: "SIGKILL",
|
|
54
|
+
});
|
|
55
|
+
child.once("error", reject);
|
|
56
|
+
child.once("close", (code, signal) => {
|
|
57
|
+
if (code === 0)
|
|
58
|
+
resolve();
|
|
59
|
+
else
|
|
60
|
+
reject(new Error(signal === null
|
|
61
|
+
? `hook command exited with status ${String(code)}`
|
|
62
|
+
: `hook command exited on ${signal}`));
|
|
63
|
+
});
|
|
64
|
+
child.stdin.once("error", reject);
|
|
65
|
+
child.stdin.end(`${JSON.stringify(envelope)}\n`, "utf8");
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=Module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Module.js","sourceRoot":"","sources":["../src/Module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAmC,MAAM,aAAa,CAAC;AAa1E,MAAM,CAAC,OAAO,OAAO,MAAM;IACd,OAAO,CAAoB;IAC3B,OAAO,CAA4C;IACnD,OAAO,GAAG,IAAI,GAAG,EAAiB,CAAC;IAC5C,YAAY,GAAwB,IAAI,CAAC;IACzC,QAAQ,GAAG,KAAK,CAAC;IAEjB,MAAM,CAAC,IAAI,CAAC,OAAO,GAAkB,EAAE;QACnC,OAAO,IAAI,MAAM,CACb,UAAU,CAAC,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,EACtC,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,OAAO,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CACnF,CAAC;IACN,CAAC;IAED,YAAoB,MAAyB,EAAE,MAAiD;QAC5F,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,IAAe;QACjB,IAAI,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;QACnE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI;YAAE,OAAO;QAClC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,iBAAiB,CAAC,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACvE,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,MAAmB,CAAC;gBAAE,OAAO;YAC3D,IAAI,QAAuB,CAAC;YAC5B,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;iBACpD,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;gBACtB,IAAI,CAAC;oBACD,IAAI,CAAC,OAAO,CAAC,2BAA2B,MAAM,EAAE,EAAE,KAAK,CAAC,CAAC;gBAC7D,CAAC;gBAAC,OAAO,WAAW,EAAE,CAAC;oBACnB,OAAO,CAAC,KAAK,CAAC,oCAAoC,MAAM,GAAG,EAAE,WAAW,CAAC,CAAC;gBAC9E,CAAC;YACL,CAAC,CAAC;iBACD,OAAO,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACvD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC/B,CAAC,CAAC,CAAC;IACP,CAAC;IAED,KAAK,CAAC,KAAK;QACP,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;QACtB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QACzB,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;IAED,QAAQ,CAAC,QAAyE;QAC9E,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC;QAC5B,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC9C,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACzC,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE;gBAC7C,KAAK,EAAE,KAAK;gBACZ,KAAK,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,CAAC;gBACrC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC;gBAC7C,UAAU,EAAE,SAAS;aACxB,CAAC,CAAC;YACH,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAC5B,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;gBACjC,IAAI,IAAI,KAAK,CAAC;oBAAE,OAAO,EAAE,CAAC;;oBACrB,MAAM,CAAC,IAAI,KAAK,CACjB,MAAM,KAAK,IAAI;wBACX,CAAC,CAAC,mCAAmC,MAAM,CAAC,IAAI,CAAC,EAAE;wBACnD,CAAC,CAAC,0BAA0B,MAAM,EAAE,CAC3C,CAAC,CAAC;YACP,CAAC,CAAC,CAAC;YACH,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAClC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC7D,CAAC,CAAC,CAAC;IACP,CAAC;CACJ"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const HOOK_EVENTS: readonly ["log/entry", "loop/proposal", "loop/terminated", "notice/event", "stream/concluded", "stream/event", "workspace/branch-batch", "workspace/created"];
|
|
2
|
+
export type HookEvent = typeof HOOK_EVENTS[number];
|
|
3
|
+
export interface HookConfig {
|
|
4
|
+
readonly command: string;
|
|
5
|
+
readonly args: string[];
|
|
6
|
+
readonly events: ReadonlySet<HookEvent>;
|
|
7
|
+
readonly timeoutMs: number;
|
|
8
|
+
}
|
|
9
|
+
export declare const hookConfig: (environment?: NodeJS.ProcessEnv) => HookConfig | null;
|
|
10
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW,YACpB,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EAClB,cAAc,EACd,wBAAwB,EACxB,mBAAmB,CACb,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,OAAO,WAAW,CAAC,MAAM,CAAC,CAAC;AAEnD,MAAM,WAAW,UAAU;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;IACxC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC9B;AAwCD,eAAO,MAAM,UAAU,iBAAiB,MAAM,CAAC,UAAU,KAAiB,UAAU,GAAG,IAqBtF,CAAC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export const HOOK_EVENTS = [
|
|
2
|
+
"log/entry",
|
|
3
|
+
"loop/proposal",
|
|
4
|
+
"loop/terminated",
|
|
5
|
+
"notice/event",
|
|
6
|
+
"stream/concluded",
|
|
7
|
+
"stream/event",
|
|
8
|
+
"workspace/branch-batch",
|
|
9
|
+
"workspace/created",
|
|
10
|
+
];
|
|
11
|
+
const hookArgs = (raw) => {
|
|
12
|
+
if (raw === undefined || raw.length === 0)
|
|
13
|
+
return [];
|
|
14
|
+
let parsed;
|
|
15
|
+
try {
|
|
16
|
+
parsed = JSON.parse(raw);
|
|
17
|
+
}
|
|
18
|
+
catch (cause) {
|
|
19
|
+
throw new Error("PLURNK_HOOKS_ARGS must be a JSON array of strings.", { cause });
|
|
20
|
+
}
|
|
21
|
+
if (!Array.isArray(parsed) || !parsed.every((value) => typeof value === "string")) {
|
|
22
|
+
throw new Error("PLURNK_HOOKS_ARGS must be a JSON array of strings.");
|
|
23
|
+
}
|
|
24
|
+
return parsed;
|
|
25
|
+
};
|
|
26
|
+
const hookEvents = (raw) => {
|
|
27
|
+
if (raw === undefined || raw.trim().length === 0) {
|
|
28
|
+
throw new Error("PLURNK_HOOKS_EVENTS must select at least one event.");
|
|
29
|
+
}
|
|
30
|
+
const known = new Set(HOOK_EVENTS);
|
|
31
|
+
const selected = new Set();
|
|
32
|
+
for (const event of raw.split(",").map((value) => value.trim())) {
|
|
33
|
+
if (!known.has(event))
|
|
34
|
+
throw new Error(`PLURNK_HOOKS_EVENTS names unknown core event '${event}'.`);
|
|
35
|
+
if (selected.has(event)) {
|
|
36
|
+
throw new Error(`PLURNK_HOOKS_EVENTS selects '${event}' more than once.`);
|
|
37
|
+
}
|
|
38
|
+
selected.add(event);
|
|
39
|
+
}
|
|
40
|
+
return selected;
|
|
41
|
+
};
|
|
42
|
+
const hookTimeoutMs = (raw) => {
|
|
43
|
+
const timeoutMs = Number(raw);
|
|
44
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1) {
|
|
45
|
+
throw new Error(`PLURNK_HOOKS_TIMEOUT_MS must be a positive integer; got ${JSON.stringify(raw)}.`);
|
|
46
|
+
}
|
|
47
|
+
return timeoutMs;
|
|
48
|
+
};
|
|
49
|
+
export const hookConfig = (environment = process.env) => {
|
|
50
|
+
const timeoutMs = hookTimeoutMs(environment.PLURNK_HOOKS_TIMEOUT_MS);
|
|
51
|
+
const command = environment.PLURNK_HOOKS_COMMAND?.trim() ?? "";
|
|
52
|
+
if (command.length === 0) {
|
|
53
|
+
if ((environment.PLURNK_HOOKS_ARGS?.length ?? 0) > 0
|
|
54
|
+
|| (environment.PLURNK_HOOKS_EVENTS?.length ?? 0) > 0) {
|
|
55
|
+
throw new Error("PLURNK_HOOKS configuration has companions but no PLURNK_HOOKS_COMMAND.");
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
if (/\s/.test(command)) {
|
|
60
|
+
throw new Error("PLURNK_HOOKS_COMMAND must contain one executable; put arguments in PLURNK_HOOKS_ARGS.");
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
command,
|
|
64
|
+
args: hookArgs(environment.PLURNK_HOOKS_ARGS),
|
|
65
|
+
events: hookEvents(environment.PLURNK_HOOKS_EVENTS),
|
|
66
|
+
timeoutMs,
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { default as Module } from "./Module.ts";
|
|
2
|
+
export { default } from "./Module.ts";
|
|
3
|
+
export type { ModuleOptions } from "./Module.ts";
|
|
4
|
+
export { hookConfig, HOOK_EVENTS } from "./config.ts";
|
|
5
|
+
export type { HookConfig, HookEvent } from "./config.ts";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACtD,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@plurnk/plurnk-hooks",
|
|
3
|
+
"version": "1.5.0",
|
|
4
|
+
"description": "Deliver selected Plurnk lifecycle events to exact local commands.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"plurnk",
|
|
7
|
+
"hooks",
|
|
8
|
+
"notifications",
|
|
9
|
+
"automation"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://github.com/plurnk/plurnk-service/tree/main/plurnk-hooks#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://repo.possumtech.com/plurnk/plurnk-service/issues"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/plurnk/plurnk-service.git",
|
|
18
|
+
"directory": "plurnk-hooks"
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=26"
|
|
22
|
+
},
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"author": "@wikitopian",
|
|
25
|
+
"type": "module",
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"plurnk-dev": "./src/index.ts",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"default": "./dist/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./package.json": "./package.json"
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"dist/**/*",
|
|
39
|
+
"README.md",
|
|
40
|
+
"SPEC.md",
|
|
41
|
+
".env.defaults"
|
|
42
|
+
],
|
|
43
|
+
"scripts": {
|
|
44
|
+
"test:lint": "tsc --noEmit",
|
|
45
|
+
"test:unit": "node --conditions=plurnk-dev --test \"src/**/*.test.ts\"",
|
|
46
|
+
"test": "npm run test:lint && npm run test:unit",
|
|
47
|
+
"build:clean": "rm -rf dist",
|
|
48
|
+
"build:dist": "tsc -p tsconfig.build.json",
|
|
49
|
+
"build": "npm run build:clean && npm run build:dist",
|
|
50
|
+
"prepack": "npm run build",
|
|
51
|
+
"prepublishOnly": "npm audit --audit-level=moderate && npm test"
|
|
52
|
+
}
|
|
53
|
+
}
|