dahrk-node 0.1.18 → 0.1.20
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/dist/main.js +324 -19
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -1510,6 +1510,14 @@ function createGitService(opts = {}) {
|
|
|
1510
1510
|
};
|
|
1511
1511
|
const netEnv = (authEnv) => authEnv ?? { ...process.env, GIT_TERMINAL_PROMPT: "0" };
|
|
1512
1512
|
const withTokenUser = (gitUrl) => /^https:\/\/[^@/]+@/.test(gitUrl) ? gitUrl : gitUrl.replace(/^https:\/\//, "https://x-access-token@");
|
|
1513
|
+
const resolveRemoteAuth = (gitUrl, credentialToken) => {
|
|
1514
|
+
const auth = credentialToken ? setupAuth(credentialToken) : void 0;
|
|
1515
|
+
return {
|
|
1516
|
+
remote: credentialToken ? withTokenUser(gitUrl) : gitUrl,
|
|
1517
|
+
authEnv: auth?.env,
|
|
1518
|
+
cleanup: () => auth?.cleanup()
|
|
1519
|
+
};
|
|
1520
|
+
};
|
|
1513
1521
|
const mirrorPathFor = (repoId) => join3(mirrorsDir, sanitizeBranchName(repoId));
|
|
1514
1522
|
const listWorktrees = (mirror) => {
|
|
1515
1523
|
let out2;
|
|
@@ -1689,15 +1697,14 @@ function createGitService(opts = {}) {
|
|
|
1689
1697
|
} catch {
|
|
1690
1698
|
}
|
|
1691
1699
|
}
|
|
1692
|
-
const
|
|
1693
|
-
const remote = opts2.credentialToken ? withTokenUser(ref.gitUrl) : ref.gitUrl;
|
|
1700
|
+
const { remote, authEnv, cleanup } = resolveRemoteAuth(ref.gitUrl, opts2.credentialToken);
|
|
1694
1701
|
let pushed = false;
|
|
1695
1702
|
let integration;
|
|
1696
1703
|
try {
|
|
1697
1704
|
let fetched = false;
|
|
1698
1705
|
if (opts2.base) {
|
|
1699
1706
|
try {
|
|
1700
|
-
git(worktreePath, ["fetch", remote, opts2.base], netEnv(
|
|
1707
|
+
git(worktreePath, ["fetch", remote, opts2.base], netEnv(authEnv));
|
|
1701
1708
|
fetched = true;
|
|
1702
1709
|
} catch (e) {
|
|
1703
1710
|
log.warn(`base fetch failed for ${opts2.base}; skipping push-time integration: ${e.message}`);
|
|
@@ -1721,7 +1728,7 @@ function createGitService(opts = {}) {
|
|
|
1721
1728
|
"merge",
|
|
1722
1729
|
"--no-edit",
|
|
1723
1730
|
"FETCH_HEAD"
|
|
1724
|
-
],
|
|
1731
|
+
], authEnv);
|
|
1725
1732
|
integration = "clean";
|
|
1726
1733
|
headSha = git(worktreePath, ["rev-parse", "HEAD"]).trim();
|
|
1727
1734
|
} catch (mergeErr) {
|
|
@@ -1738,10 +1745,10 @@ function createGitService(opts = {}) {
|
|
|
1738
1745
|
throw mergeErr;
|
|
1739
1746
|
}
|
|
1740
1747
|
}
|
|
1741
|
-
git(worktreePath, ["push", remote, `HEAD:refs/heads/${branch}`], netEnv(
|
|
1748
|
+
git(worktreePath, ["push", remote, `HEAD:refs/heads/${branch}`], netEnv(authEnv));
|
|
1742
1749
|
pushed = true;
|
|
1743
1750
|
} finally {
|
|
1744
|
-
|
|
1751
|
+
cleanup();
|
|
1745
1752
|
}
|
|
1746
1753
|
return { headSha, pushed, nothingToCommit: !dirty, commitsAhead, ...integration ? { integration } : {} };
|
|
1747
1754
|
},
|
|
@@ -1752,12 +1759,11 @@ function createGitService(opts = {}) {
|
|
|
1752
1759
|
}
|
|
1753
1760
|
const wipRef = sanitizeBranchName(opts2.branch);
|
|
1754
1761
|
const { headSha, dirty } = commitPending(worktreePath, opts2.message);
|
|
1755
|
-
const
|
|
1756
|
-
const remote = opts2.credentialToken ? withTokenUser(ref.gitUrl) : ref.gitUrl;
|
|
1762
|
+
const { remote, authEnv, cleanup } = resolveRemoteAuth(ref.gitUrl, opts2.credentialToken);
|
|
1757
1763
|
try {
|
|
1758
|
-
git(worktreePath, ["push", "--force", remote, `HEAD:refs/heads/${wipRef}`], netEnv(
|
|
1764
|
+
git(worktreePath, ["push", "--force", remote, `HEAD:refs/heads/${wipRef}`], netEnv(authEnv));
|
|
1759
1765
|
} finally {
|
|
1760
|
-
|
|
1766
|
+
cleanup();
|
|
1761
1767
|
}
|
|
1762
1768
|
return { headSha, pushed: true, nothingToCommit: !dirty, wipRef };
|
|
1763
1769
|
},
|
|
@@ -1772,15 +1778,14 @@ function createGitService(opts = {}) {
|
|
|
1772
1778
|
if (!dirty) return { dirty: false, headSha, pushed: false };
|
|
1773
1779
|
git(worktreePath, ["branch", "--force", wipRef, tailSha]);
|
|
1774
1780
|
let pushed = false;
|
|
1775
|
-
const
|
|
1776
|
-
const remote = opts2.credentialToken ? withTokenUser(ref.gitUrl) : ref.gitUrl;
|
|
1781
|
+
const { remote, authEnv, cleanup } = resolveRemoteAuth(ref.gitUrl, opts2.credentialToken);
|
|
1777
1782
|
try {
|
|
1778
|
-
git(worktreePath, ["push", "--force", remote, `${tailSha}:refs/heads/${wipRef}`], netEnv(
|
|
1783
|
+
git(worktreePath, ["push", "--force", remote, `${tailSha}:refs/heads/${wipRef}`], netEnv(authEnv));
|
|
1779
1784
|
pushed = true;
|
|
1780
1785
|
} catch (e) {
|
|
1781
1786
|
log.warn(`could not push the preserved tail to ${wipRef}: ${e.message}`);
|
|
1782
1787
|
} finally {
|
|
1783
|
-
|
|
1788
|
+
cleanup();
|
|
1784
1789
|
}
|
|
1785
1790
|
git(worktreePath, ["reset", "--hard", headSha]);
|
|
1786
1791
|
git(worktreePath, ["clean", "-fd", "--exclude", SCRATCH_DIR]);
|
|
@@ -2740,12 +2745,30 @@ function tokenise(cmd) {
|
|
|
2740
2745
|
let quoted = false;
|
|
2741
2746
|
let started = false;
|
|
2742
2747
|
let quote = null;
|
|
2748
|
+
const pendingHeredocs = [];
|
|
2743
2749
|
const push = () => {
|
|
2744
2750
|
if (started) tokens.push({ value: cur, quoted });
|
|
2745
2751
|
cur = "";
|
|
2746
2752
|
quoted = false;
|
|
2747
2753
|
started = false;
|
|
2748
2754
|
};
|
|
2755
|
+
const consumeHeredocs = (s) => {
|
|
2756
|
+
let p = s;
|
|
2757
|
+
for (; ; ) {
|
|
2758
|
+
let lineEnd = p;
|
|
2759
|
+
while (lineEnd < cmd.length && cmd[lineEnd] !== "\n") lineEnd++;
|
|
2760
|
+
const line = cmd.slice(p, lineEnd);
|
|
2761
|
+
const { delim, dash } = pendingHeredocs[0];
|
|
2762
|
+
const terminator = dash ? line.replace(/^\t+/, "") : line;
|
|
2763
|
+
const atEnd = lineEnd >= cmd.length;
|
|
2764
|
+
if (terminator === delim) {
|
|
2765
|
+
pendingHeredocs.shift();
|
|
2766
|
+
if (pendingHeredocs.length === 0) return atEnd ? lineEnd : lineEnd + 1;
|
|
2767
|
+
}
|
|
2768
|
+
if (atEnd) return -1;
|
|
2769
|
+
p = lineEnd + 1;
|
|
2770
|
+
}
|
|
2771
|
+
};
|
|
2749
2772
|
for (let i = 0; i < cmd.length; i++) {
|
|
2750
2773
|
const c = cmd[i];
|
|
2751
2774
|
if (quote) {
|
|
@@ -2793,6 +2816,13 @@ function tokenise(cmd) {
|
|
|
2793
2816
|
i = j;
|
|
2794
2817
|
continue;
|
|
2795
2818
|
}
|
|
2819
|
+
if (c === "\n" && pendingHeredocs.length > 0) {
|
|
2820
|
+
push();
|
|
2821
|
+
const resume = consumeHeredocs(i + 1);
|
|
2822
|
+
if (resume < 0) return null;
|
|
2823
|
+
i = resume - 1;
|
|
2824
|
+
continue;
|
|
2825
|
+
}
|
|
2796
2826
|
if (/\s/.test(c)) {
|
|
2797
2827
|
push();
|
|
2798
2828
|
continue;
|
|
@@ -2805,6 +2835,38 @@ function tokenise(cmd) {
|
|
|
2805
2835
|
i += 2;
|
|
2806
2836
|
continue;
|
|
2807
2837
|
}
|
|
2838
|
+
if (two === "<<" && cmd[i + 2] !== "<") {
|
|
2839
|
+
push();
|
|
2840
|
+
let k = i + 2;
|
|
2841
|
+
const dash = cmd[k] === "-";
|
|
2842
|
+
if (dash) k++;
|
|
2843
|
+
while (k < cmd.length && (cmd[k] === " " || cmd[k] === " ")) k++;
|
|
2844
|
+
let delim = "";
|
|
2845
|
+
let dq = null;
|
|
2846
|
+
for (; k < cmd.length; k++) {
|
|
2847
|
+
const d = cmd[k];
|
|
2848
|
+
if (dq) {
|
|
2849
|
+
if (d === dq) dq = null;
|
|
2850
|
+
else delim += d;
|
|
2851
|
+
continue;
|
|
2852
|
+
}
|
|
2853
|
+
if (d === '"' || d === "'") {
|
|
2854
|
+
dq = d;
|
|
2855
|
+
continue;
|
|
2856
|
+
}
|
|
2857
|
+
if (d === "\\" && k + 1 < cmd.length) {
|
|
2858
|
+
delim += cmd[++k];
|
|
2859
|
+
continue;
|
|
2860
|
+
}
|
|
2861
|
+
if (/[\s;&|<>]/.test(d)) break;
|
|
2862
|
+
delim += d;
|
|
2863
|
+
}
|
|
2864
|
+
if (dq) return null;
|
|
2865
|
+
if (!delim) return null;
|
|
2866
|
+
pendingHeredocs.push({ delim, dash });
|
|
2867
|
+
i = k - 1;
|
|
2868
|
+
continue;
|
|
2869
|
+
}
|
|
2808
2870
|
if (["&&", "||", ">>", "2>", "&>"].includes(two)) {
|
|
2809
2871
|
push();
|
|
2810
2872
|
tokens.push({ value: two, quoted: false, operator: two });
|
|
@@ -2820,6 +2882,7 @@ function tokenise(cmd) {
|
|
|
2820
2882
|
started = true;
|
|
2821
2883
|
}
|
|
2822
2884
|
if (quote) return null;
|
|
2885
|
+
if (pendingHeredocs.length > 0) return null;
|
|
2823
2886
|
push();
|
|
2824
2887
|
return { tokens, subshells };
|
|
2825
2888
|
}
|
|
@@ -2949,7 +3012,7 @@ function scanCommand(cmd, roots, cwd = roots.cwd) {
|
|
|
2949
3012
|
}
|
|
2950
3013
|
if (out2.kind !== "ok") return out2;
|
|
2951
3014
|
}
|
|
2952
|
-
return { kind: "ok" };
|
|
3015
|
+
return { kind: "ok", cwd: cwdNow };
|
|
2953
3016
|
}
|
|
2954
3017
|
|
|
2955
3018
|
// ../../packages/edge/src/builtins.ts
|
|
@@ -3028,6 +3091,7 @@ function pathsIn(input) {
|
|
|
3028
3091
|
return out2;
|
|
3029
3092
|
}
|
|
3030
3093
|
function fsConfineRule(roots) {
|
|
3094
|
+
let sessionCwd = roots.cwd;
|
|
3031
3095
|
return {
|
|
3032
3096
|
name: "fs_confine",
|
|
3033
3097
|
evaluate(event) {
|
|
@@ -3038,8 +3102,12 @@ function fsConfineRule(roots) {
|
|
|
3038
3102
|
reason: `${need2 === "write" ? "write to" : "read of"} "${path}" is outside the run's worktree`
|
|
3039
3103
|
});
|
|
3040
3104
|
if (SHELL_TOOLS.has(event.tool)) {
|
|
3041
|
-
const result = scanCommand(commandOf(event.input), roots);
|
|
3105
|
+
const result = scanCommand(commandOf(event.input), roots, sessionCwd);
|
|
3042
3106
|
if (result.kind === "escape") return deny(result.path, result.need);
|
|
3107
|
+
if (result.kind === "ok") {
|
|
3108
|
+
if (result.cwd) sessionCwd = result.cwd;
|
|
3109
|
+
return null;
|
|
3110
|
+
}
|
|
3043
3111
|
if (result.kind === "unparseable") {
|
|
3044
3112
|
return {
|
|
3045
3113
|
verdict: "deny",
|
|
@@ -4533,6 +4601,7 @@ var COMMANDS = /* @__PURE__ */ new Set([
|
|
|
4533
4601
|
"logs",
|
|
4534
4602
|
"diagnose",
|
|
4535
4603
|
"run",
|
|
4604
|
+
"repo",
|
|
4536
4605
|
"service",
|
|
4537
4606
|
"doctor",
|
|
4538
4607
|
"status",
|
|
@@ -4556,6 +4625,7 @@ function parseCli(argv) {
|
|
|
4556
4625
|
flagArgs = rest;
|
|
4557
4626
|
}
|
|
4558
4627
|
if (command === "run") return parseRun(flagArgs);
|
|
4628
|
+
if (command === "repo") return parseRepo(flagArgs);
|
|
4559
4629
|
if (command === "service") return parseService(flagArgs);
|
|
4560
4630
|
if (command === "update") return parseUpdate(flagArgs);
|
|
4561
4631
|
if (command === "logs") return parseLogs(flagArgs);
|
|
@@ -4570,6 +4640,8 @@ function parseCli(argv) {
|
|
|
4570
4640
|
"hub-url": { type: "string" },
|
|
4571
4641
|
ephemeral: { type: "boolean", default: false },
|
|
4572
4642
|
foreground: { type: "boolean", default: false },
|
|
4643
|
+
// `start`: enrol but do not install the service (the operator supervises the node themselves).
|
|
4644
|
+
"no-service": { type: "boolean", default: false },
|
|
4573
4645
|
// `stop` / `restart`: take the node down even though it has work in flight.
|
|
4574
4646
|
force: { type: "boolean", default: false },
|
|
4575
4647
|
// `status`: machine-readable output.
|
|
@@ -4591,7 +4663,8 @@ function parseCli(argv) {
|
|
|
4591
4663
|
ephemeral,
|
|
4592
4664
|
// An ephemeral node mints a throwaway id and persists nothing, so there is nothing coherent to hand to
|
|
4593
4665
|
// a supervisor that restarts it on boot. It is a foreground node by definition.
|
|
4594
|
-
foreground: (values.foreground ?? false) || ephemeral
|
|
4666
|
+
foreground: (values.foreground ?? false) || ephemeral,
|
|
4667
|
+
...values["no-service"] ? { noService: true } : {}
|
|
4595
4668
|
};
|
|
4596
4669
|
if (command === "stop") return { kind: "stop", force };
|
|
4597
4670
|
if (command === "restart") return { kind: "restart", flags, force };
|
|
@@ -4723,6 +4796,43 @@ function parseService(flagArgs) {
|
|
|
4723
4796
|
};
|
|
4724
4797
|
return { kind: "service", flags };
|
|
4725
4798
|
}
|
|
4799
|
+
var REPO_ACTIONS = /* @__PURE__ */ new Set(["add"]);
|
|
4800
|
+
var isRepoAction = (s) => REPO_ACTIONS.has(s);
|
|
4801
|
+
function parseRepo(flagArgs) {
|
|
4802
|
+
let values;
|
|
4803
|
+
let positionals;
|
|
4804
|
+
try {
|
|
4805
|
+
({ values, positionals } = parseArgs({
|
|
4806
|
+
args: flagArgs,
|
|
4807
|
+
options: {
|
|
4808
|
+
name: { type: "string" },
|
|
4809
|
+
"hub-url": { type: "string" },
|
|
4810
|
+
token: { type: "string" },
|
|
4811
|
+
help: { type: "boolean", default: false }
|
|
4812
|
+
},
|
|
4813
|
+
allowPositionals: true
|
|
4814
|
+
}));
|
|
4815
|
+
} catch (e) {
|
|
4816
|
+
return { kind: "error", message: e.message };
|
|
4817
|
+
}
|
|
4818
|
+
if (values.help) return { kind: "help", command: "repo" };
|
|
4819
|
+
if (positionals.length === 0) {
|
|
4820
|
+
return { kind: "error", message: "repo: missing action (`dahrk repo add`)" };
|
|
4821
|
+
}
|
|
4822
|
+
if (positionals.length > 1) {
|
|
4823
|
+
return { kind: "error", message: `repo: unexpected argument "${positionals[1]}" (one action at a time)` };
|
|
4824
|
+
}
|
|
4825
|
+
const action = positionals[0];
|
|
4826
|
+
if (!isRepoAction(action)) {
|
|
4827
|
+
return { kind: "error", message: `repo: unknown action "${action}" (expected add)` };
|
|
4828
|
+
}
|
|
4829
|
+
const flags = {
|
|
4830
|
+
...values.name ? { name: values.name } : {},
|
|
4831
|
+
...values["hub-url"] ? { hubUrl: values["hub-url"] } : {},
|
|
4832
|
+
...values.token ? { token: values.token } : {}
|
|
4833
|
+
};
|
|
4834
|
+
return { kind: "repo", flags };
|
|
4835
|
+
}
|
|
4726
4836
|
function parseUpdate(flagArgs) {
|
|
4727
4837
|
let values;
|
|
4728
4838
|
try {
|
|
@@ -4767,6 +4877,8 @@ function usage(bin, command) {
|
|
|
4767
4877
|
" setting DAHRK_FOREGROUND=1, which is easier to set in a Dockerfile or pm2 config.",
|
|
4768
4878
|
" --ephemeral Do not persist (or read) node id and token; mint a throwaway id (CI / one-shot).",
|
|
4769
4879
|
" Implies --foreground: a node with no persistent identity has nothing to daemonise.",
|
|
4880
|
+
" --no-service Enrol (cache the token) but do not install the always-on service, for a node you",
|
|
4881
|
+
" supervise yourself. Run it later with `dahrk start --foreground` (or your own supervisor).",
|
|
4770
4882
|
"",
|
|
4771
4883
|
"Only one node may run at a time on a host - they would share this machine's node id and race each",
|
|
4772
4884
|
"other for Jobs - so a second `start` refuses rather than dialling the hub twice."
|
|
@@ -4867,6 +4979,30 @@ function usage(bin, command) {
|
|
|
4867
4979
|
" --token <token> Enrolment token to verify against the hub (or set DAHRK_ENROL_TOKEN)."
|
|
4868
4980
|
].join("\n");
|
|
4869
4981
|
}
|
|
4982
|
+
if (command === "repo") {
|
|
4983
|
+
return [
|
|
4984
|
+
`Usage: ${bin} repo add [options]`,
|
|
4985
|
+
"",
|
|
4986
|
+
"Register the current git repository with the hub, so it can run workflows. Run it from inside the",
|
|
4987
|
+
"repo - `cd your-repo && dahrk repo add` - and the node reads the `origin` remote and the current",
|
|
4988
|
+
"branch itself: no form, no pasted git URL, because the node already sits next to the code.",
|
|
4989
|
+
"",
|
|
4990
|
+
"The git URL is registered in the form the host can authenticate. An HTTPS origin is kept as-is; an",
|
|
4991
|
+
"SSH origin is kept when this host has an SSH key, and otherwise normalised to HTTPS with a warning.",
|
|
4992
|
+
"Re-running on an already-registered repo is a no-op, not an error or a duplicate.",
|
|
4993
|
+
"",
|
|
4994
|
+
"This uses the node's existing enrolment - run `dahrk start --token <token>` first if it is not yet",
|
|
4995
|
+
"enrolled. It dials the hub itself, so the daemon need not be running.",
|
|
4996
|
+
"",
|
|
4997
|
+
"Actions:",
|
|
4998
|
+
" add Register the repository in the current directory.",
|
|
4999
|
+
"",
|
|
5000
|
+
"Options:",
|
|
5001
|
+
" --name <name> Display name for the repo (default: the slug from the origin URL).",
|
|
5002
|
+
" --hub-url <url> Hub URL to register with (or set DAHRK_HUB_URL).",
|
|
5003
|
+
" --token <token> Enrolment token to authenticate with (default: the cached one)."
|
|
5004
|
+
].join("\n");
|
|
5005
|
+
}
|
|
4870
5006
|
if (command === "service") {
|
|
4871
5007
|
return [
|
|
4872
5008
|
`Usage: ${bin} service install|uninstall [options]`,
|
|
@@ -4943,6 +5079,7 @@ function usage(bin, command) {
|
|
|
4943
5079
|
" diagnose Write a support bundle you can read, and send on if you choose. Uploads nothing.",
|
|
4944
5080
|
" status Is it up, is it enrolled, what is it working on? (local, dials nothing)",
|
|
4945
5081
|
" run Run a workflow locally (engine-backed), e.g. `run preflight`.",
|
|
5082
|
+
" repo Register the current repository with the hub (`repo add`).",
|
|
4946
5083
|
" doctor Preflight checks: Node, runtimes, hub reachability, token validity.",
|
|
4947
5084
|
" service Install/uninstall the always-on service by hand (`start` does this for you).",
|
|
4948
5085
|
" update Update the client to the latest release (or print how for your channel).",
|
|
@@ -5272,6 +5409,91 @@ async function runDoctor(inputs, deps = {}) {
|
|
|
5272
5409
|
return checks.some((c) => c.status === "fail") ? 1 : 0;
|
|
5273
5410
|
}
|
|
5274
5411
|
|
|
5412
|
+
// src/repo-add.ts
|
|
5413
|
+
import { createHash as createHash4 } from "crypto";
|
|
5414
|
+
var stripRepoSuffix = (s) => s.replace(/\.git$/i, "").replace(/\/+$/, "");
|
|
5415
|
+
function splitOwnerRepo(path) {
|
|
5416
|
+
const parts = stripRepoSuffix(path).split("/").filter(Boolean);
|
|
5417
|
+
if (parts.length < 2) return void 0;
|
|
5418
|
+
const repo = parts[parts.length - 1];
|
|
5419
|
+
const owner = parts.slice(0, -1).join("/");
|
|
5420
|
+
return { owner, repo };
|
|
5421
|
+
}
|
|
5422
|
+
function parseGitRemote(url) {
|
|
5423
|
+
const trimmed = url.trim();
|
|
5424
|
+
if (!trimmed) return void 0;
|
|
5425
|
+
const urlMatch = /^(?:ssh|https?):\/\/(?:[^@/]+@)?([^/:]+)(?::\d+)?\/(.+)$/i.exec(trimmed);
|
|
5426
|
+
if (urlMatch) {
|
|
5427
|
+
const parts = splitOwnerRepo(urlMatch[2]);
|
|
5428
|
+
return parts ? { host: urlMatch[1], ...parts } : void 0;
|
|
5429
|
+
}
|
|
5430
|
+
const scpMatch = /^(?:[^@\s]+@)?([^/:\s]+):(.+)$/.exec(trimmed);
|
|
5431
|
+
if (scpMatch) {
|
|
5432
|
+
const parts = splitOwnerRepo(scpMatch[2]);
|
|
5433
|
+
return parts ? { host: scpMatch[1], ...parts } : void 0;
|
|
5434
|
+
}
|
|
5435
|
+
return void 0;
|
|
5436
|
+
}
|
|
5437
|
+
var httpsFormOf = (r) => `https://${r.host}/${r.owner}/${r.repo}.git`;
|
|
5438
|
+
var isHttpsRemote = (url) => /^https?:\/\//i.test(url.trim());
|
|
5439
|
+
function chooseGitUrl(input) {
|
|
5440
|
+
const originUrl = input.originUrl.trim();
|
|
5441
|
+
if (isHttpsRemote(originUrl)) return { gitUrl: originUrl, converted: false };
|
|
5442
|
+
if (input.sshKeyPresent) return { gitUrl: originUrl, converted: false };
|
|
5443
|
+
const parsed = parseGitRemote(originUrl);
|
|
5444
|
+
if (!parsed) return { gitUrl: originUrl, converted: false };
|
|
5445
|
+
return { gitUrl: httpsFormOf(parsed), converted: true };
|
|
5446
|
+
}
|
|
5447
|
+
var deriveRepoName = (remote) => remote.repo;
|
|
5448
|
+
function deriveRepoId(gitUrl) {
|
|
5449
|
+
const parsed = parseGitRemote(gitUrl);
|
|
5450
|
+
const canonical2 = parsed ? `${parsed.host}/${parsed.owner}/${parsed.repo}`.toLowerCase() : gitUrl.trim().toLowerCase();
|
|
5451
|
+
const hash = createHash4("sha256").update(canonical2).digest("hex").slice(0, 12);
|
|
5452
|
+
const slug = (parsed ? parsed.repo : "repo").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "repo";
|
|
5453
|
+
return `${slug}-${hash}`;
|
|
5454
|
+
}
|
|
5455
|
+
function hubHttpBase(url) {
|
|
5456
|
+
const trimmed = url.trim().replace(/\/+$/, "");
|
|
5457
|
+
if (/^wss:\/\//i.test(trimmed)) return trimmed.replace(/^wss:\/\//i, "https://");
|
|
5458
|
+
if (/^ws:\/\//i.test(trimmed)) return trimmed.replace(/^ws:\/\//i, "http://");
|
|
5459
|
+
return trimmed;
|
|
5460
|
+
}
|
|
5461
|
+
async function readJson(res) {
|
|
5462
|
+
try {
|
|
5463
|
+
const body = await res.json();
|
|
5464
|
+
return body && typeof body === "object" ? body : void 0;
|
|
5465
|
+
} catch {
|
|
5466
|
+
return void 0;
|
|
5467
|
+
}
|
|
5468
|
+
}
|
|
5469
|
+
function driftOf(stored, repo) {
|
|
5470
|
+
if (!stored) return void 0;
|
|
5471
|
+
const drift = {};
|
|
5472
|
+
if (typeof stored.defaultBranch === "string" && stored.defaultBranch !== repo.defaultBranch) drift.branch = stored.defaultBranch;
|
|
5473
|
+
if (typeof stored.name === "string" && stored.name !== repo.name) drift.name = stored.name;
|
|
5474
|
+
return drift.branch || drift.name ? drift : void 0;
|
|
5475
|
+
}
|
|
5476
|
+
async function registerRepo(deps, args) {
|
|
5477
|
+
const url = `${args.base}/config/api/repositories`;
|
|
5478
|
+
let res;
|
|
5479
|
+
try {
|
|
5480
|
+
res = await deps.fetch(url, {
|
|
5481
|
+
method: "POST",
|
|
5482
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${args.token}` },
|
|
5483
|
+
body: JSON.stringify(args.repo)
|
|
5484
|
+
});
|
|
5485
|
+
} catch (e) {
|
|
5486
|
+
return { kind: "error", message: `could not reach the hub at ${args.base}: ${e.message}` };
|
|
5487
|
+
}
|
|
5488
|
+
const stored = await readJson(res);
|
|
5489
|
+
if (res.status === 200 || res.status === 409) {
|
|
5490
|
+
const drift = driftOf(stored, args.repo);
|
|
5491
|
+
return { kind: "already", ...drift ? { drift } : {} };
|
|
5492
|
+
}
|
|
5493
|
+
if (res.ok) return { kind: "registered" };
|
|
5494
|
+
return { kind: "error", message: `the hub rejected the repo (HTTP ${res.status})` };
|
|
5495
|
+
}
|
|
5496
|
+
|
|
5275
5497
|
// src/lock.ts
|
|
5276
5498
|
import { existsSync as existsSync9, mkdirSync as mkdirSync9, readFileSync as readFileSync7, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "fs";
|
|
5277
5499
|
import { dirname as dirname6 } from "path";
|
|
@@ -5681,7 +5903,18 @@ function probeRepo(repoPath) {
|
|
|
5681
5903
|
baseBranch = git(["rev-parse", "--abbrev-ref", "HEAD"]) || void 0;
|
|
5682
5904
|
} catch {
|
|
5683
5905
|
}
|
|
5684
|
-
|
|
5906
|
+
let remoteUrl;
|
|
5907
|
+
try {
|
|
5908
|
+
remoteUrl = git(["remote", "get-url", "origin"]) || void 0;
|
|
5909
|
+
} catch {
|
|
5910
|
+
}
|
|
5911
|
+
return {
|
|
5912
|
+
path: repoPath,
|
|
5913
|
+
isGitRepo: true,
|
|
5914
|
+
headResolves,
|
|
5915
|
+
...baseBranch ? { baseBranch } : {},
|
|
5916
|
+
...remoteUrl ? { remoteUrl } : {}
|
|
5917
|
+
};
|
|
5685
5918
|
}
|
|
5686
5919
|
function gatherHostFacts(repoPath) {
|
|
5687
5920
|
const root = worktreeRoot(process.env);
|
|
@@ -6686,7 +6919,7 @@ async function runStatus(inputs, deps) {
|
|
|
6686
6919
|
}
|
|
6687
6920
|
|
|
6688
6921
|
// src/main.ts
|
|
6689
|
-
var CLIENT_VERSION = "0.1.
|
|
6922
|
+
var CLIENT_VERSION = "0.1.20";
|
|
6690
6923
|
var DEFAULT_HUB_URL = "wss://api.dahrk.ai";
|
|
6691
6924
|
var list = (v) => (v ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
6692
6925
|
var RUNTIMES2 = ["claude-code", "codex", "pi"];
|
|
@@ -6773,6 +7006,12 @@ async function start(flags) {
|
|
|
6773
7006
|
if (wantsForeground(env, flags)) return startForeground(env, flags);
|
|
6774
7007
|
const enrolled = await enrolToDisk(env);
|
|
6775
7008
|
if (enrolled !== 0) return enrolled;
|
|
7009
|
+
if (flags.noService) {
|
|
7010
|
+
out("");
|
|
7011
|
+
out(verdict("ok", "Enrolled. Not installing a service (--no-service)."));
|
|
7012
|
+
out(hint("Run the node with `dahrk start --foreground` (or your own supervisor), or `dahrk start` to install the service."));
|
|
7013
|
+
return 0;
|
|
7014
|
+
}
|
|
6776
7015
|
await offerUpdate(env);
|
|
6777
7016
|
const outcome = await runNodeStart(serviceInputs(env));
|
|
6778
7017
|
if (outcome.kind === "error") return outcome.code;
|
|
@@ -7018,6 +7257,69 @@ async function runWorkflow(flags) {
|
|
|
7018
7257
|
clientVersion: CLIENT_VERSION
|
|
7019
7258
|
});
|
|
7020
7259
|
}
|
|
7260
|
+
async function runRepoAdd(flags) {
|
|
7261
|
+
const env = applyEnvAliases(process.env);
|
|
7262
|
+
if (flags.hubUrl) env.DAHRK_HUB_URL = flags.hubUrl;
|
|
7263
|
+
if (flags.token) env.DAHRK_ENROL_TOKEN = flags.token;
|
|
7264
|
+
const cwd = process.cwd();
|
|
7265
|
+
const probe2 = probeRepo(cwd);
|
|
7266
|
+
if (!probe2.isGitRepo) {
|
|
7267
|
+
out("");
|
|
7268
|
+
out(verdict("fail", `${cwd} is not a git repository.`));
|
|
7269
|
+
out(hint("Run `dahrk repo add` from inside the repository you want to register."));
|
|
7270
|
+
return 1;
|
|
7271
|
+
}
|
|
7272
|
+
if (!probe2.remoteUrl) {
|
|
7273
|
+
out("");
|
|
7274
|
+
out(verdict("fail", "This repository has no `origin` remote, so there is nothing to register."));
|
|
7275
|
+
out(hint("Add one with `git remote add origin <url>`, then run `dahrk repo add` again."));
|
|
7276
|
+
return 1;
|
|
7277
|
+
}
|
|
7278
|
+
const token = resolveEnrolToken(env);
|
|
7279
|
+
if (!token) {
|
|
7280
|
+
out("");
|
|
7281
|
+
out(verdict("fail", "This node is not enrolled, so it cannot register a repository."));
|
|
7282
|
+
out(hint("Enrol it first with `dahrk start --token <token>`."));
|
|
7283
|
+
return 1;
|
|
7284
|
+
}
|
|
7285
|
+
const { gitUrl, converted } = chooseGitUrl({ originUrl: probe2.remoteUrl, sshKeyPresent: sshKeyPresent() });
|
|
7286
|
+
if (converted) {
|
|
7287
|
+
out("");
|
|
7288
|
+
out(verdict("warn", `No SSH key found on this host, so the SSH remote was registered as HTTPS: ${gitUrl}`));
|
|
7289
|
+
out(hint("Add an SSH key (or set up brokered credentials) if this repo must be cloned over SSH - see DHK-252."));
|
|
7290
|
+
}
|
|
7291
|
+
const parsed = parseGitRemote(gitUrl);
|
|
7292
|
+
const name = flags.name ?? (parsed ? deriveRepoName(parsed) : "repo");
|
|
7293
|
+
const repo = {
|
|
7294
|
+
id: deriveRepoId(gitUrl),
|
|
7295
|
+
name,
|
|
7296
|
+
gitUrl,
|
|
7297
|
+
// HEAD's branch is the run's base. An empty repo (no commits) has none yet; default to `main`.
|
|
7298
|
+
defaultBranch: probe2.baseBranch ?? "main"
|
|
7299
|
+
};
|
|
7300
|
+
const base = hubHttpBase(env.DAHRK_HUB_URL ?? DEFAULT_HUB_URL);
|
|
7301
|
+
const result = await registerRepo({ fetch }, { base, token, repo });
|
|
7302
|
+
out("");
|
|
7303
|
+
switch (result.kind) {
|
|
7304
|
+
case "registered":
|
|
7305
|
+
out(verdict("ok", `Registered ${name} with the hub.`));
|
|
7306
|
+
out(kv("URL", gitUrl));
|
|
7307
|
+
out(kv("Branch", repo.defaultBranch));
|
|
7308
|
+
return 0;
|
|
7309
|
+
case "already":
|
|
7310
|
+
out(verdict("ok", `${name} is already registered - nothing to do.`));
|
|
7311
|
+
if (result.drift) {
|
|
7312
|
+
const bits = [];
|
|
7313
|
+
if (result.drift.branch) bits.push(`branch ${result.drift.branch} (this repo is on ${repo.defaultBranch})`);
|
|
7314
|
+
if (result.drift.name) bits.push(`name ${result.drift.name}`);
|
|
7315
|
+
out(hint(`The hub's stored record differs - ${bits.join(", ")} - and was left unchanged.`));
|
|
7316
|
+
}
|
|
7317
|
+
return 0;
|
|
7318
|
+
case "error":
|
|
7319
|
+
out(verdict("fail", `Could not register the repository: ${result.message}`));
|
|
7320
|
+
return 1;
|
|
7321
|
+
}
|
|
7322
|
+
}
|
|
7021
7323
|
async function main() {
|
|
7022
7324
|
const invoked = basename2(process.argv[1] ?? "");
|
|
7023
7325
|
const bin = !invoked || invoked.startsWith("main.") ? "dahrk" : invoked;
|
|
@@ -7064,6 +7366,9 @@ async function main() {
|
|
|
7064
7366
|
case "run":
|
|
7065
7367
|
process.exitCode = await runWorkflow(parsed.flags);
|
|
7066
7368
|
break;
|
|
7369
|
+
case "repo":
|
|
7370
|
+
process.exitCode = await runRepoAdd(parsed.flags);
|
|
7371
|
+
break;
|
|
7067
7372
|
case "service": {
|
|
7068
7373
|
if (parsed.flags.action === "uninstall") {
|
|
7069
7374
|
process.exitCode = await runServiceUninstall();
|