@higherdev/cli 0.35.0 → 0.37.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/dist/actions-runner.js +15 -3
- package/dist/attachments.js +14 -0
- package/dist/tui/App.js +8 -2
- package/dist/tui/TextInput.js +5 -0
- package/package.json +1 -1
package/dist/actions-runner.js
CHANGED
|
@@ -22,6 +22,15 @@ function unitPath(value) {
|
|
|
22
22
|
function unitEnvironment(name, value) {
|
|
23
23
|
return `"${name}=${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/%/g, "%%")}"`;
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* A systemd user service inherits the supplementary groups the user manager had
|
|
27
|
+
* when it started, so a docker group granted later never reaches the runner.
|
|
28
|
+
* Starting through `sg <group>` picks the group up per membership in /etc/group.
|
|
29
|
+
*/
|
|
30
|
+
export function actionsRunnerExecStart(runnerDir, viaGroup) {
|
|
31
|
+
const service = path.join(runnerDir, "bin/runsvc.sh");
|
|
32
|
+
return viaGroup ? `/usr/bin/sg ${viaGroup} -c "${unitPath(service)}"` : unitPath(service);
|
|
33
|
+
}
|
|
25
34
|
export function actionsRunnerSystemdUnit(input) {
|
|
26
35
|
return `[Unit]
|
|
27
36
|
Description=HDX GitHub Actions runner${input.repo ? ` for ${input.repo}` : ""}
|
|
@@ -32,7 +41,7 @@ Wants=network-online.target
|
|
|
32
41
|
Type=simple
|
|
33
42
|
WorkingDirectory=${unitPath(input.runnerDir)}
|
|
34
43
|
Environment=${unitEnvironment("PATH", input.path)}
|
|
35
|
-
ExecStart=${
|
|
44
|
+
ExecStart=${actionsRunnerExecStart(input.runnerDir, input.viaGroup)}
|
|
36
45
|
Restart=always
|
|
37
46
|
RestartSec=5
|
|
38
47
|
KillMode=process
|
|
@@ -105,10 +114,13 @@ export async function installActionsRunner(options, deps = {}) {
|
|
|
105
114
|
throw new Error(`Existing Actions runner in ${runnerDir} belongs to another host or repository.`);
|
|
106
115
|
}
|
|
107
116
|
}
|
|
117
|
+
// Jobs with service containers need the Docker socket; run through the docker group when the user has it.
|
|
118
|
+
const groups = (await run("id", ["-nG"]).catch(() => ({ stdout: "" }))).stdout.trim().split(/\s+/);
|
|
119
|
+
const viaGroup = groups.includes("docker") && await exists("/usr/bin/sg") ? "docker" : null;
|
|
108
120
|
await mkdir(path.dirname(unitPath), { recursive: true });
|
|
109
|
-
await writeFile(unitPath, actionsRunnerSystemdUnit({ runnerDir, path: pathValue, repo: options.repo }), "utf8");
|
|
121
|
+
await writeFile(unitPath, actionsRunnerSystemdUnit({ runnerDir, path: pathValue, repo: options.repo, viaGroup }), "utf8");
|
|
110
122
|
await run("systemctl", ["--user", "daemon-reload"]);
|
|
111
123
|
await run("systemctl", ["--user", "enable", "--now", unitName]);
|
|
112
124
|
return [`GitHub Actions runner ${options.host} registered to ${options.repo}.`,
|
|
113
|
-
`labels: self-hosted, hdx, ${options.host}`, `systemd unit: ${unitPath}`];
|
|
125
|
+
`labels: self-hosted, hdx, ${options.host}`, `systemd unit: ${unitPath}${viaGroup ? ` (via sg ${viaGroup})` : ""}`];
|
|
114
126
|
}
|
package/dist/attachments.js
CHANGED
|
@@ -97,6 +97,20 @@ export function parseTuiAttach(text) {
|
|
|
97
97
|
const paths = key ? words.slice(0, -1) : words;
|
|
98
98
|
return paths.length === 1 ? { path: paths[0], ...(key ? { ticketKey: key } : {}) } : null;
|
|
99
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Text that a Finder drop produces: one or more absolute paths, plain, quoted, or with
|
|
102
|
+
* escaped spaces. Terminal.app inserts a drop as typed text rather than a bracketed
|
|
103
|
+
* paste, so the input component and the Enter handler both check this before treating
|
|
104
|
+
* a line starting with "/" as a slash command.
|
|
105
|
+
*/
|
|
106
|
+
export function looksLikeDroppedPaths(text) {
|
|
107
|
+
const trimmed = text.trim();
|
|
108
|
+
if (!/^(?:\/|'\/|"\/)/.test(trimmed))
|
|
109
|
+
return false;
|
|
110
|
+
// A slash command is one word without a second "/" (e.g. /architect, /ticket HD-1).
|
|
111
|
+
const firstWord = trimmed.split(/\s+/)[0].replace(/^['"]/, "");
|
|
112
|
+
return firstWord.slice(1).includes("/") || /^['"]/.test(trimmed);
|
|
113
|
+
}
|
|
100
114
|
export async function detectDroppedPaths(pasted, isFile = async (path) => (await stat(path)).isFile()) {
|
|
101
115
|
const text = pasted.trim();
|
|
102
116
|
if (!text)
|
package/dist/tui/App.js
CHANGED
|
@@ -33,7 +33,7 @@ import { editFor, editableKeys, nextValue, seedFor, settingsRows } from "./setti
|
|
|
33
33
|
import { appendLines, runLabels, toStreamLines } from "./stream.js";
|
|
34
34
|
import { UI } from "./theme.js";
|
|
35
35
|
import { WorkspaceLoads } from "./workspace-load.js";
|
|
36
|
-
import { attachmentChip, detectDroppedPaths, uploadAttachment } from "../attachments.js";
|
|
36
|
+
import { attachmentChip, detectDroppedPaths, looksLikeDroppedPaths, uploadAttachment } from "../attachments.js";
|
|
37
37
|
let messageSeq = 0;
|
|
38
38
|
const nextId = () => `m${messageSeq++}`;
|
|
39
39
|
const tuiPrompt = (question) => promptOnStdin(question, true);
|
|
@@ -457,7 +457,7 @@ export function App({ initial, availableUpdate: initialUpdate = null, updateDeps
|
|
|
457
457
|
})();
|
|
458
458
|
}, [agentName, config, pendingAttachments, setThread, workspace.id]);
|
|
459
459
|
const receiveDrop = useCallback((pasted) => {
|
|
460
|
-
if (
|
|
460
|
+
if (!looksLikeDroppedPaths(pasted))
|
|
461
461
|
return false;
|
|
462
462
|
void (async () => {
|
|
463
463
|
const paths = await detectDroppedPaths(pasted);
|
|
@@ -520,6 +520,12 @@ export function App({ initial, availableUpdate: initialUpdate = null, updateDeps
|
|
|
520
520
|
}, [view, ticketKey, visibleStory?.latest_headline, visibleStory?.timeline, config]);
|
|
521
521
|
const run = useCallback(async (raw) => {
|
|
522
522
|
const text = raw.trim();
|
|
523
|
+
// A dropped path that reached the line as typed text is an attachment, not a command.
|
|
524
|
+
if (looksLikeDroppedPaths(text) && (await detectDroppedPaths(text)).length) {
|
|
525
|
+
setDraft("");
|
|
526
|
+
receiveDrop(text);
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
523
529
|
if (view === "settings") {
|
|
524
530
|
const open = editingRef.current;
|
|
525
531
|
if (open) {
|
package/dist/tui/TextInput.js
CHANGED
|
@@ -73,6 +73,11 @@ export default function TextInput({ value, onChange, onSubmit, onCancel, onUp, o
|
|
|
73
73
|
// most of them. Ink reports that as ordinary input with key.return
|
|
74
74
|
// false, so without this the Enter is filtered out with the other
|
|
75
75
|
// control bytes and the line just sits at the prompt unsent.
|
|
76
|
+
// A Finder drop in Terminal.app arrives as one multi-character chunk of
|
|
77
|
+
// typed text, not a bracketed paste, so it is offered to the paste
|
|
78
|
+
// handler first.
|
|
79
|
+
if (input.length > 1 && onPasteText?.(input))
|
|
80
|
+
return;
|
|
76
81
|
if (input.length > 1 && /[\r\n]/.test(input)) {
|
|
77
82
|
const parts = input.split(/\r\n|\r|\n/);
|
|
78
83
|
const submits = parts.length === 2 && parts[1] === "";
|