@plurnk/plurnk-execs-git 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 PossumTech Laboratories
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # @plurnk/plurnk-execs-git
2
+
3
+ `git` + GitHub-CLI runtime executor for [plurnk-service](https://github.com/plurnk/plurnk-service)'s `exec` scheme. Drives `EXEC[git]` / `EXEC[gh]` by **shelling the system `git`/`gh` binaries** — no third-party git/gh library; the installed CLIs are the source of truth.
4
+
5
+ A `@plurnk/plurnk-execs-*` sibling built on the [plurnk-execs](https://github.com/plurnk/plurnk-execs) framework.
6
+
7
+ ## Runtime tags
8
+
9
+ | Tag | Glyph | Binary | Available when |
10
+ |---|---|---|---|
11
+ | `git` | 🔀 | `git` | `git` on PATH |
12
+ | `gh` | 🐙 | `gh` | `gh` on PATH **and** authenticated (`gh auth status`) |
13
+
14
+ ```
15
+ <<EXEC[git]:status --short:EXEC
16
+ <<EXEC[gh]:pr create --title "Fix the bug" --body "Closes #5":EXEC
17
+ ```
18
+
19
+ The command is tokenized into **real argv** (`tokenizeArgv`) and the tag's binary is run directly — **never** through a shell. That's deliberate: shelling `git commit -m "costs $5"` would expand `$5` and corrupt the message; passing argv preserves `$`, backticks, and other specials literally. Shell metacharacters (`|`, `;`, `>`) are passed as literal args, not interpreted.
20
+
21
+ ## Effect & gating
22
+
23
+ `effect` is **`host` for every command** (proposal-gated). This is required, not a shortcut: `effect(target)` classifies the target only and must never inspect the command, so `git status` and `git push` are indistinguishable to it. **The service owns all gating** — membership, proposal/confirm, the enable ceiling, and outward-confirm for `push` / `gh pr create`. The executor only runs the command and declares the effect.
24
+
25
+ ## Availability
26
+
27
+ Correct per-tag availability (e.g. `gh` unauthenticated while `git` works) requires the consumer to probe **per-tag**, not per-package (plurnk-service#185).
28
+
29
+ ## Tests
30
+
31
+ `test:lint`, `test:unit`. Live tests auto-skip where the binary is absent.
package/dist/Git.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { SubprocessExecutor } from "@plurnk/plurnk-execs";
2
+ import type { RuntimeAvailability, SpawnArgs } from "@plurnk/plurnk-execs";
3
+ export default class Git extends SubprocessExecutor {
4
+ protected spawnArgs(runtime: string, command: string): SpawnArgs;
5
+ probe(): Promise<RuntimeAvailability>;
6
+ }
7
+ //# sourceMappingURL=Git.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Git.d.ts","sourceRoot":"","sources":["../src/Git.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAc3E,MAAM,CAAC,OAAO,OAAO,GAAI,SAAQ,kBAAkB;cAC5B,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS;IAQ1D,KAAK,IAAI,OAAO,CAAC,mBAAmB,CAAC;CAOvD"}
package/dist/Git.js ADDED
@@ -0,0 +1,42 @@
1
+ import { spawn } from "node:child_process";
2
+ import { SubprocessExecutor } from "@plurnk/plurnk-execs";
3
+ import { tokenizeArgv } from "./tokenizeArgv.js";
4
+ // git + GitHub-CLI executor. Claims the `git` and `gh` tags; the tag IS the
5
+ // binary, so it shells the system `git`/`gh` with the command tokenized into
6
+ // real argv (never a shell line — see tokenizeArgv). No third-party git/gh
7
+ // library: the system binaries are the source of truth.
8
+ //
9
+ // `effect` is `host` for every command (inherited) — proposal-gated. That's not
10
+ // a simplification, it's required: `effect(target)` classifies the TARGET only
11
+ // and must never inspect the command, so `git status` and `git push` are
12
+ // indistinguishable to it. Service owns the proposal/confirm/membership gating
13
+ // (plurnk-execs#5). All run/stream/abort behavior is inherited from
14
+ // SubprocessExecutor.
15
+ export default class Git extends SubprocessExecutor {
16
+ spawnArgs(runtime, command) {
17
+ // runtime is "git" or "gh" — the tag is the executable.
18
+ return { cmd: runtime, args: tokenizeArgv(command), useShell: false };
19
+ }
20
+ // Per-tag availability: `git` needs the binary; `gh` needs the binary AND an
21
+ // authenticated session. (Correct only once the consumer probes per-tag, not
22
+ // per-package — plurnk-service#185.)
23
+ async probe() {
24
+ if (this.runtime === "gh") {
25
+ return runProbe("gh", ["auth", "status"], "gh authenticated", "gh present but not authenticated — run `gh auth login`", "gh not on PATH");
26
+ }
27
+ return runProbe("git", ["--version"], undefined, "git --version failed", "git not on PATH");
28
+ }
29
+ }
30
+ // Spawn a probe command; resolve availability from its exit. Async so the
31
+ // consumer's per-probe timeout can race it (a hung `gh auth status` mustn't
32
+ // wedge boot).
33
+ const runProbe = (bin, args, okDetail, nonzeroDetail, missingDetail) => new Promise((resolve) => {
34
+ let out = "";
35
+ const child = spawn(bin, args, { signal: AbortSignal.timeout(5000) });
36
+ child.stdout?.on("data", (chunk) => { out += chunk.toString("utf8"); });
37
+ child.on("error", () => resolve({ available: false, detail: missingDetail }));
38
+ child.on("close", (code) => resolve(code === 0
39
+ ? { available: true, detail: okDetail ?? (out.trim().split("\n")[0] || undefined) }
40
+ : { available: false, detail: nonzeroDetail }));
41
+ });
42
+ //# sourceMappingURL=Git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Git.js","sourceRoot":"","sources":["../src/Git.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,4EAA4E;AAC5E,6EAA6E;AAC7E,2EAA2E;AAC3E,wDAAwD;AACxD,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,yEAAyE;AACzE,+EAA+E;AAC/E,oEAAoE;AACpE,sBAAsB;AACtB,MAAM,CAAC,OAAO,OAAO,GAAI,SAAQ,kBAAkB;IAC5B,SAAS,CAAC,OAAe,EAAE,OAAe;QACzD,wDAAwD;QACxD,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAC1E,CAAC;IAED,6EAA6E;IAC7E,6EAA6E;IAC7E,qCAAqC;IAC5B,KAAK,CAAC,KAAK;QAChB,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;YACxB,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,kBAAkB,EACxD,wDAAwD,EAAE,gBAAgB,CAAC,CAAC;QACpF,CAAC;QACD,OAAO,QAAQ,CAAC,KAAK,EAAE,CAAC,WAAW,CAAC,EAAE,SAAS,EAAE,sBAAsB,EAAE,iBAAiB,CAAC,CAAC;IAChG,CAAC;CACJ;AAED,0EAA0E;AAC1E,4EAA4E;AAC5E,eAAe;AACf,MAAM,QAAQ,GAAG,CACb,GAAW,EACX,IAAc,EACd,QAA4B,EAC5B,aAAqB,EACrB,aAAqB,EACO,EAAE,CAC9B,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;IACpB,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACtE,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,GAAG,GAAG,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAChF,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC;IAC9E,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC;QAC1C,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,EAAE;QACnF,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC;AACxD,CAAC,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { default as Git } from "./Git.ts";
2
+ export { default } from "./Git.ts";
3
+ export { tokenizeArgv } from "./tokenizeArgv.ts";
4
+ //# 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,GAAG,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { default as Git } from "./Git.js";
2
+ export { default } from "./Git.js";
3
+ export { tokenizeArgv } from "./tokenizeArgv.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,GAAG,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function tokenizeArgv(input: string): string[];
2
+ //# sourceMappingURL=tokenizeArgv.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokenizeArgv.d.ts","sourceRoot":"","sources":["../src/tokenizeArgv.ts"],"names":[],"mappings":"AAMA,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAoCpD"}
@@ -0,0 +1,68 @@
1
+ // Split a command string into argv the way a POSIX shell would tokenize words —
2
+ // honoring single quotes, double quotes, and backslash escapes — but WITHOUT
3
+ // any variable/command expansion. That distinction is the whole point: shelling
4
+ // `git commit -m "costs $5"` would expand `$5` and corrupt the message; passing
5
+ // real argv preserves `$`, backticks, etc. literally. No shell, no metacharacter
6
+ // interpretation (pipes/redirects are passed through as literal args).
7
+ export function tokenizeArgv(input) {
8
+ const args = [];
9
+ let cur = "";
10
+ let inArg = false;
11
+ let i = 0;
12
+ const n = input.length;
13
+ while (i < n) {
14
+ const c = input[i];
15
+ if (c === "'") {
16
+ inArg = true;
17
+ i++;
18
+ while (i < n && input[i] !== "'") {
19
+ cur += input[i];
20
+ i++;
21
+ }
22
+ if (i >= n)
23
+ throw new Error("unterminated single quote");
24
+ i++;
25
+ }
26
+ else if (c === '"') {
27
+ inArg = true;
28
+ i++;
29
+ while (i < n && input[i] !== '"') {
30
+ if (input[i] === "\\" && i + 1 < n && (input[i + 1] === '"' || input[i + 1] === "\\")) {
31
+ cur += input[i + 1];
32
+ i += 2;
33
+ }
34
+ else {
35
+ cur += input[i];
36
+ i++;
37
+ }
38
+ }
39
+ if (i >= n)
40
+ throw new Error("unterminated double quote");
41
+ i++;
42
+ }
43
+ else if (c === "\\") {
44
+ if (i + 1 >= n)
45
+ throw new Error("trailing backslash");
46
+ cur += input[i + 1];
47
+ inArg = true;
48
+ i += 2;
49
+ }
50
+ else if (c === " " || c === "\t" || c === "\n" || c === "\r") {
51
+ if (inArg) {
52
+ args.push(cur);
53
+ cur = "";
54
+ inArg = false;
55
+ }
56
+ i++;
57
+ }
58
+ else {
59
+ cur += c;
60
+ inArg = true;
61
+ i++;
62
+ }
63
+ }
64
+ if (inArg)
65
+ args.push(cur);
66
+ return args;
67
+ }
68
+ //# sourceMappingURL=tokenizeArgv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokenizeArgv.js","sourceRoot":"","sources":["../src/tokenizeArgv.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,6EAA6E;AAC7E,gFAAgF;AAChF,gFAAgF;AAChF,iFAAiF;AACjF,uEAAuE;AACvE,MAAM,UAAU,YAAY,CAAC,KAAa;IACtC,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,KAAK,GAAG,KAAK,CAAC;IAClB,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC;IACvB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACX,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACnB,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;YACZ,KAAK,GAAG,IAAI,CAAC;YAAC,CAAC,EAAE,CAAC;YAClB,OAAO,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;gBAAC,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC;gBAAC,CAAC,EAAE,CAAC;YAAC,CAAC;YAC3D,IAAI,CAAC,IAAI,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;YACzD,CAAC,EAAE,CAAC;QACR,CAAC;aAAM,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;YACnB,KAAK,GAAG,IAAI,CAAC;YAAC,CAAC,EAAE,CAAC;YAClB,OAAO,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;gBAC/B,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,IAAI,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC;oBACpF,GAAG,IAAI,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;oBAAC,CAAC,IAAI,CAAC,CAAC;gBAChC,CAAC;qBAAM,CAAC;oBACJ,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC;oBAAC,CAAC,EAAE,CAAC;gBACzB,CAAC;YACL,CAAC;YACD,IAAI,CAAC,IAAI,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;YACzD,CAAC,EAAE,CAAC;QACR,CAAC;aAAM,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YACpB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC;YACtD,GAAG,IAAI,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAAC,KAAK,GAAG,IAAI,CAAC;YAAC,CAAC,IAAI,CAAC,CAAC;QAC9C,CAAC;aAAM,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YAC7D,IAAI,KAAK,EAAE,CAAC;gBAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAAC,GAAG,GAAG,EAAE,CAAC;gBAAC,KAAK,GAAG,KAAK,CAAC;YAAC,CAAC;YACvD,CAAC,EAAE,CAAC;QACR,CAAC;aAAM,CAAC;YACJ,GAAG,IAAI,CAAC,CAAC;YAAC,KAAK,GAAG,IAAI,CAAC;YAAC,CAAC,EAAE,CAAC;QAChC,CAAC;IACL,CAAC;IACD,IAAI,KAAK;QAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1B,OAAO,IAAI,CAAC;AAChB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@plurnk/plurnk-execs-git",
3
+ "version": "0.1.0",
4
+ "description": "git + GitHub-CLI runtime executor for plurnk-service's exec scheme — shells the system git/gh binaries (no third-party lib) for EXEC[git] / EXEC[gh].",
5
+ "keywords": ["plurnk", "exec", "runtime", "executor", "git", "gh", "github"],
6
+ "homepage": "https://github.com/plurnk/plurnk-execs-git#readme",
7
+ "bugs": {
8
+ "url": "https://github.com/plurnk/plurnk-execs-git/issues"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/plurnk/plurnk-execs-git.git"
13
+ },
14
+ "engines": {
15
+ "node": ">=25"
16
+ },
17
+ "license": "MIT",
18
+ "author": "@wikitopian",
19
+ "type": "module",
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "plurnk": {
24
+ "kind": "exec",
25
+ "runtimes": [
26
+ { "name": "git", "glyph": "🔀" },
27
+ { "name": "gh", "glyph": "🐙" }
28
+ ]
29
+ },
30
+ "exports": {
31
+ ".": {
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
+ ],
41
+ "scripts": {
42
+ "test:lint": "tsc --noEmit",
43
+ "test:unit": "node --test \"src/**/*.test.ts\"",
44
+ "test": "npm run test:lint && npm run test:unit",
45
+ "build:dist": "tsc -p tsconfig.build.json",
46
+ "build": "npm run build:dist",
47
+ "prepare": "npm run build"
48
+ },
49
+ "dependencies": {
50
+ "@plurnk/plurnk-execs": "0.4.1"
51
+ },
52
+ "devDependencies": {
53
+ "@types/node": "^25.8.0",
54
+ "typescript": "^6.0.3"
55
+ }
56
+ }