@ondrej-svec/hog 1.4.0 → 1.6.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/README.md +127 -28
- package/dist/cli.js +289 -40
- package/dist/cli.js.map +1 -1
- package/dist/fetch-worker.js +4 -2
- package/dist/fetch-worker.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -163,6 +163,7 @@ var init_config = __esm({
|
|
|
163
163
|
shortName: z.string().min(1),
|
|
164
164
|
projectNumber: z.number().int().positive(),
|
|
165
165
|
statusFieldId: z.string().min(1),
|
|
166
|
+
dueDateFieldId: z.string().optional(),
|
|
166
167
|
completionAction: COMPLETION_ACTION_SCHEMA,
|
|
167
168
|
statusGroups: z.array(z.string()).optional()
|
|
168
169
|
});
|
|
@@ -532,7 +533,7 @@ function fetchProjectFields(repo, issueNumber, projectNumber) {
|
|
|
532
533
|
const fieldValues = projectItem.fieldValues?.nodes ?? [];
|
|
533
534
|
for (const fv of fieldValues) {
|
|
534
535
|
if (!fv) continue;
|
|
535
|
-
if ("date" in fv && fv.field?.name
|
|
536
|
+
if ("date" in fv && DATE_FIELD_NAME_RE2.test(fv.field?.name ?? "")) {
|
|
536
537
|
fields.targetDate = fv.date;
|
|
537
538
|
}
|
|
538
539
|
if ("name" in fv && fv.field?.name === "Status") {
|
|
@@ -594,7 +595,7 @@ function fetchProjectEnrichment(repo, projectNumber) {
|
|
|
594
595
|
const fieldValues = item.fieldValues?.nodes ?? [];
|
|
595
596
|
for (const fv of fieldValues) {
|
|
596
597
|
if (!fv) continue;
|
|
597
|
-
if ("date" in fv && fv.field?.name
|
|
598
|
+
if ("date" in fv && fv.date && DATE_FIELD_NAME_RE2.test(fv.field?.name ?? "")) {
|
|
598
599
|
enrichment.targetDate = fv.date;
|
|
599
600
|
}
|
|
600
601
|
if ("name" in fv && fv.field?.name === "Status" && fv.name) {
|
|
@@ -824,11 +825,91 @@ async function updateProjectItemStatusAsync(repo, issueNumber, projectConfig) {
|
|
|
824
825
|
`optionId=${optionId}`
|
|
825
826
|
]);
|
|
826
827
|
}
|
|
827
|
-
|
|
828
|
+
async function updateProjectItemDateAsync(repo, issueNumber, projectConfig, dueDate) {
|
|
829
|
+
const [owner, repoName] = repo.split("/");
|
|
830
|
+
const findItemQuery = `
|
|
831
|
+
query($owner: String!, $repo: String!, $issueNumber: Int!) {
|
|
832
|
+
repository(owner: $owner, name: $repo) {
|
|
833
|
+
issue(number: $issueNumber) {
|
|
834
|
+
projectItems(first: 10) {
|
|
835
|
+
nodes {
|
|
836
|
+
id
|
|
837
|
+
project { number }
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
`;
|
|
844
|
+
const findResult = await runGhJsonAsync([
|
|
845
|
+
"api",
|
|
846
|
+
"graphql",
|
|
847
|
+
"-f",
|
|
848
|
+
`query=${findItemQuery}`,
|
|
849
|
+
"-F",
|
|
850
|
+
`owner=${owner}`,
|
|
851
|
+
"-F",
|
|
852
|
+
`repo=${repoName}`,
|
|
853
|
+
"-F",
|
|
854
|
+
`issueNumber=${String(issueNumber)}`
|
|
855
|
+
]);
|
|
856
|
+
const items = findResult?.data?.repository?.issue?.projectItems?.nodes ?? [];
|
|
857
|
+
const projectItem = items.find((item) => item?.project?.number === projectConfig.projectNumber);
|
|
858
|
+
if (!projectItem?.id) return;
|
|
859
|
+
const projectQuery = `
|
|
860
|
+
query($owner: String!) {
|
|
861
|
+
organization(login: $owner) {
|
|
862
|
+
projectV2(number: ${projectConfig.projectNumber}) {
|
|
863
|
+
id
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
`;
|
|
868
|
+
const projectResult = await runGhJsonAsync([
|
|
869
|
+
"api",
|
|
870
|
+
"graphql",
|
|
871
|
+
"-f",
|
|
872
|
+
`query=${projectQuery}`,
|
|
873
|
+
"-F",
|
|
874
|
+
`owner=${owner}`
|
|
875
|
+
]);
|
|
876
|
+
const projectId = projectResult?.data?.organization?.projectV2?.id;
|
|
877
|
+
if (!projectId) return;
|
|
878
|
+
const mutation = `
|
|
879
|
+
mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $date: Date!) {
|
|
880
|
+
updateProjectV2ItemFieldValue(
|
|
881
|
+
input: {
|
|
882
|
+
projectId: $projectId
|
|
883
|
+
itemId: $itemId
|
|
884
|
+
fieldId: $fieldId
|
|
885
|
+
value: { date: $date }
|
|
886
|
+
}
|
|
887
|
+
) {
|
|
888
|
+
projectV2Item { id }
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
`;
|
|
892
|
+
await runGhAsync([
|
|
893
|
+
"api",
|
|
894
|
+
"graphql",
|
|
895
|
+
"-f",
|
|
896
|
+
`query=${mutation}`,
|
|
897
|
+
"-F",
|
|
898
|
+
`projectId=${projectId}`,
|
|
899
|
+
"-F",
|
|
900
|
+
`itemId=${projectItem.id}`,
|
|
901
|
+
"-F",
|
|
902
|
+
`fieldId=${projectConfig.dueDateFieldId}`,
|
|
903
|
+
"-F",
|
|
904
|
+
`date=${dueDate}`
|
|
905
|
+
]);
|
|
906
|
+
}
|
|
907
|
+
var execFileAsync, DATE_FIELD_NAME_RE2;
|
|
828
908
|
var init_github = __esm({
|
|
829
909
|
"src/github.ts"() {
|
|
830
910
|
"use strict";
|
|
831
911
|
execFileAsync = promisify(execFile);
|
|
912
|
+
DATE_FIELD_NAME_RE2 = /^(target\s*date|due\s*date|due|deadline)$/i;
|
|
832
913
|
}
|
|
833
914
|
});
|
|
834
915
|
|
|
@@ -1222,8 +1303,16 @@ function useActions({
|
|
|
1222
1303
|
});
|
|
1223
1304
|
}, [toast, refresh]);
|
|
1224
1305
|
const handleCreateIssue = useCallback(
|
|
1225
|
-
async (repo, title, labels) => {
|
|
1226
|
-
const
|
|
1306
|
+
async (repo, title, body, dueDate, labels) => {
|
|
1307
|
+
const repoConfig = configRef.current.repos.find((r) => r.name === repo);
|
|
1308
|
+
let effectiveBody = body;
|
|
1309
|
+
if (dueDate && !repoConfig?.dueDateFieldId) {
|
|
1310
|
+
const dueLine = `Due: ${dueDate}`;
|
|
1311
|
+
effectiveBody = body ? `${body}
|
|
1312
|
+
|
|
1313
|
+
${dueLine}` : dueLine;
|
|
1314
|
+
}
|
|
1315
|
+
const args = ["issue", "create", "--repo", repo, "--title", title, "--body", effectiveBody];
|
|
1227
1316
|
if (labels && labels.length > 0) {
|
|
1228
1317
|
for (const label of labels) {
|
|
1229
1318
|
args.push("--label", label);
|
|
@@ -1235,7 +1324,15 @@ function useActions({
|
|
|
1235
1324
|
const output = stdout.trim();
|
|
1236
1325
|
const match = output.match(/\/(\d+)$/);
|
|
1237
1326
|
const issueNumber = match?.[1] ? parseInt(match[1], 10) : 0;
|
|
1238
|
-
const shortName =
|
|
1327
|
+
const shortName = repoConfig?.shortName ?? repo;
|
|
1328
|
+
if (issueNumber > 0 && dueDate && repoConfig?.dueDateFieldId) {
|
|
1329
|
+
const dueDateConfig = {
|
|
1330
|
+
projectNumber: repoConfig.projectNumber,
|
|
1331
|
+
dueDateFieldId: repoConfig.dueDateFieldId
|
|
1332
|
+
};
|
|
1333
|
+
updateProjectItemDateAsync(repo, issueNumber, dueDateConfig, dueDate).catch(() => {
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1239
1336
|
t.resolve(`Created ${shortName}#${issueNumber}`);
|
|
1240
1337
|
refresh();
|
|
1241
1338
|
onOverlayDone();
|
|
@@ -2778,13 +2875,19 @@ function CreateIssueForm({
|
|
|
2778
2875
|
currentLabels: [],
|
|
2779
2876
|
labelCache: labelCache ?? {},
|
|
2780
2877
|
onConfirm: (addLabels) => {
|
|
2781
|
-
onSubmit(
|
|
2878
|
+
onSubmit(
|
|
2879
|
+
selectedRepo.name,
|
|
2880
|
+
title,
|
|
2881
|
+
"",
|
|
2882
|
+
null,
|
|
2883
|
+
addLabels.length > 0 ? addLabels : void 0
|
|
2884
|
+
);
|
|
2782
2885
|
},
|
|
2783
2886
|
onCancel: () => {
|
|
2784
|
-
onSubmit(selectedRepo.name, title);
|
|
2887
|
+
onSubmit(selectedRepo.name, title, "", null);
|
|
2785
2888
|
},
|
|
2786
2889
|
onError: () => {
|
|
2787
|
-
onSubmit(selectedRepo.name, title);
|
|
2890
|
+
onSubmit(selectedRepo.name, title, "", null);
|
|
2788
2891
|
}
|
|
2789
2892
|
}
|
|
2790
2893
|
)
|
|
@@ -2820,7 +2923,7 @@ function CreateIssueForm({
|
|
|
2820
2923
|
setTitle(trimmed);
|
|
2821
2924
|
setField("labels");
|
|
2822
2925
|
} else {
|
|
2823
|
-
onSubmit(selectedRepo.name, trimmed);
|
|
2926
|
+
onSubmit(selectedRepo.name, trimmed, "", null);
|
|
2824
2927
|
}
|
|
2825
2928
|
}
|
|
2826
2929
|
}
|
|
@@ -3018,8 +3121,12 @@ var init_help_overlay = __esm({
|
|
|
3018
3121
|
});
|
|
3019
3122
|
|
|
3020
3123
|
// src/board/components/nl-create-overlay.tsx
|
|
3124
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
3125
|
+
import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
3126
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
3127
|
+
import { join as join4 } from "path";
|
|
3021
3128
|
import { Spinner as Spinner2, TextInput as TextInput3 } from "@inkjs/ui";
|
|
3022
|
-
import { Box as Box9, Text as Text9, useInput as useInput9 } from "ink";
|
|
3129
|
+
import { Box as Box9, Text as Text9, useInput as useInput9, useStdin as useStdin2 } from "ink";
|
|
3023
3130
|
import { useCallback as useCallback9, useEffect as useEffect5, useRef as useRef9, useState as useState9 } from "react";
|
|
3024
3131
|
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
3025
3132
|
function NlCreateOverlay({
|
|
@@ -3028,15 +3135,28 @@ function NlCreateOverlay({
|
|
|
3028
3135
|
labelCache,
|
|
3029
3136
|
onSubmit,
|
|
3030
3137
|
onCancel,
|
|
3138
|
+
onPauseRefresh,
|
|
3139
|
+
onResumeRefresh,
|
|
3031
3140
|
onLlmFallback
|
|
3032
3141
|
}) {
|
|
3033
3142
|
const [, setInput] = useState9("");
|
|
3034
3143
|
const [isParsing, setIsParsing] = useState9(false);
|
|
3035
3144
|
const [parsed, setParsed] = useState9(null);
|
|
3036
3145
|
const [parseError, setParseError] = useState9(null);
|
|
3037
|
-
const [
|
|
3146
|
+
const [step, setStep] = useState9("input");
|
|
3147
|
+
const [body, setBody] = useState9("");
|
|
3148
|
+
const [editingBody, setEditingBody] = useState9(false);
|
|
3038
3149
|
const submittedRef = useRef9(false);
|
|
3039
3150
|
const parseParamsRef = useRef9(null);
|
|
3151
|
+
const onSubmitRef = useRef9(onSubmit);
|
|
3152
|
+
const onCancelRef = useRef9(onCancel);
|
|
3153
|
+
const onPauseRef = useRef9(onPauseRefresh);
|
|
3154
|
+
const onResumeRef = useRef9(onResumeRefresh);
|
|
3155
|
+
onSubmitRef.current = onSubmit;
|
|
3156
|
+
onCancelRef.current = onCancel;
|
|
3157
|
+
onPauseRef.current = onPauseRefresh;
|
|
3158
|
+
onResumeRef.current = onResumeRefresh;
|
|
3159
|
+
const { setRawMode } = useStdin2();
|
|
3040
3160
|
const defaultRepoIdx = defaultRepoName ? Math.max(
|
|
3041
3161
|
0,
|
|
3042
3162
|
repos.findIndex((r) => r.name === defaultRepoName)
|
|
@@ -3044,19 +3164,19 @@ function NlCreateOverlay({
|
|
|
3044
3164
|
const [repoIdx, setRepoIdx] = useState9(defaultRepoIdx);
|
|
3045
3165
|
const selectedRepo = repos[repoIdx];
|
|
3046
3166
|
useInput9((inputChar, key) => {
|
|
3047
|
-
if (isParsing) return;
|
|
3167
|
+
if (isParsing || editingBody) return;
|
|
3048
3168
|
if (key.escape) {
|
|
3169
|
+
if (step === "body") {
|
|
3170
|
+
setStep("input");
|
|
3171
|
+
setParsed((p) => p);
|
|
3172
|
+
return;
|
|
3173
|
+
}
|
|
3049
3174
|
onCancel();
|
|
3050
3175
|
return;
|
|
3051
3176
|
}
|
|
3052
|
-
if (parsed) {
|
|
3177
|
+
if (parsed && step === "input") {
|
|
3053
3178
|
if (key.return) {
|
|
3054
|
-
|
|
3055
|
-
submittedRef.current = true;
|
|
3056
|
-
if (!selectedRepo) return;
|
|
3057
|
-
setCreateError(null);
|
|
3058
|
-
const labels = buildLabelList(parsed);
|
|
3059
|
-
onSubmit(selectedRepo.name, parsed.title, labels.length > 0 ? labels : void 0);
|
|
3179
|
+
setStep("body");
|
|
3060
3180
|
return;
|
|
3061
3181
|
}
|
|
3062
3182
|
if (inputChar === "r") {
|
|
@@ -3064,7 +3184,43 @@ function NlCreateOverlay({
|
|
|
3064
3184
|
return;
|
|
3065
3185
|
}
|
|
3066
3186
|
}
|
|
3187
|
+
if (step === "body" && inputChar === "") {
|
|
3188
|
+
setEditingBody(true);
|
|
3189
|
+
}
|
|
3067
3190
|
});
|
|
3191
|
+
useEffect5(() => {
|
|
3192
|
+
if (!editingBody) return;
|
|
3193
|
+
const editorEnv = process.env["VISUAL"] ?? process.env["EDITOR"] ?? "vi";
|
|
3194
|
+
const [cmd, ...extraArgs] = editorEnv.split(" ").filter(Boolean);
|
|
3195
|
+
if (!cmd) {
|
|
3196
|
+
setEditingBody(false);
|
|
3197
|
+
return;
|
|
3198
|
+
}
|
|
3199
|
+
let tmpDir = null;
|
|
3200
|
+
let tmpFile = null;
|
|
3201
|
+
try {
|
|
3202
|
+
onPauseRef.current?.();
|
|
3203
|
+
tmpDir = mkdtempSync2(join4(tmpdir2(), "hog-body-"));
|
|
3204
|
+
tmpFile = join4(tmpDir, "body.md");
|
|
3205
|
+
writeFileSync4(tmpFile, body);
|
|
3206
|
+
const inkInstance = getInkInstance();
|
|
3207
|
+
inkInstance?.clear();
|
|
3208
|
+
setRawMode(false);
|
|
3209
|
+
spawnSync2(cmd, [...extraArgs, tmpFile], { stdio: "inherit" });
|
|
3210
|
+
const content = readFileSync4(tmpFile, "utf-8");
|
|
3211
|
+
setRawMode(true);
|
|
3212
|
+
setBody(content.trimEnd());
|
|
3213
|
+
} finally {
|
|
3214
|
+
onResumeRef.current?.();
|
|
3215
|
+
if (tmpFile && tmpDir) {
|
|
3216
|
+
try {
|
|
3217
|
+
rmSync2(tmpDir, { recursive: true, force: true });
|
|
3218
|
+
} catch {
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
setEditingBody(false);
|
|
3222
|
+
}
|
|
3223
|
+
}, [editingBody, body, setRawMode]);
|
|
3068
3224
|
const handleInputSubmit = useCallback9(
|
|
3069
3225
|
(text) => {
|
|
3070
3226
|
const trimmed = text.trim();
|
|
@@ -3103,8 +3259,48 @@ function NlCreateOverlay({
|
|
|
3103
3259
|
/* @__PURE__ */ jsx9(Spinner2, { label: "Parsing..." })
|
|
3104
3260
|
] });
|
|
3105
3261
|
}
|
|
3262
|
+
if (parsed && step === "body") {
|
|
3263
|
+
if (editingBody) {
|
|
3264
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
3265
|
+
/* @__PURE__ */ jsx9(Text9, { color: "cyan", bold: true, children: "\u2728 Creating Issue" }),
|
|
3266
|
+
/* @__PURE__ */ jsx9(Text9, { color: "cyan", children: "Opening editor for body\u2026" })
|
|
3267
|
+
] });
|
|
3268
|
+
}
|
|
3269
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
3270
|
+
/* @__PURE__ */ jsx9(Text9, { color: "cyan", bold: true, children: "\u2728 Creating Issue" }),
|
|
3271
|
+
/* @__PURE__ */ jsxs9(Box9, { children: [
|
|
3272
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Title: " }),
|
|
3273
|
+
/* @__PURE__ */ jsx9(Text9, { children: parsed.title })
|
|
3274
|
+
] }),
|
|
3275
|
+
/* @__PURE__ */ jsxs9(Box9, { children: [
|
|
3276
|
+
/* @__PURE__ */ jsx9(Text9, { color: "cyan", children: "body: " }),
|
|
3277
|
+
/* @__PURE__ */ jsx9(
|
|
3278
|
+
TextInput3,
|
|
3279
|
+
{
|
|
3280
|
+
defaultValue: body,
|
|
3281
|
+
placeholder: "optional description (ctrl+e for editor)",
|
|
3282
|
+
onChange: setBody,
|
|
3283
|
+
onSubmit: (text) => {
|
|
3284
|
+
if (submittedRef.current) return;
|
|
3285
|
+
submittedRef.current = true;
|
|
3286
|
+
if (!selectedRepo) return;
|
|
3287
|
+
const labels = buildLabelList(parsed);
|
|
3288
|
+
onSubmitRef.current(
|
|
3289
|
+
selectedRepo.name,
|
|
3290
|
+
parsed.title,
|
|
3291
|
+
text.trim(),
|
|
3292
|
+
parsed.dueDate,
|
|
3293
|
+
labels.length > 0 ? labels : void 0
|
|
3294
|
+
);
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
)
|
|
3298
|
+
] }),
|
|
3299
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Enter:create ctrl+e:editor Esc:back" })
|
|
3300
|
+
] });
|
|
3301
|
+
}
|
|
3106
3302
|
if (parsed) {
|
|
3107
|
-
const labels =
|
|
3303
|
+
const labels = [...parsed.labels];
|
|
3108
3304
|
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
3109
3305
|
/* @__PURE__ */ jsx9(Text9, { color: "cyan", bold: true, children: "\u2728 Creating Issue" }),
|
|
3110
3306
|
/* @__PURE__ */ jsxs9(Box9, { children: [
|
|
@@ -3131,9 +3327,7 @@ function NlCreateOverlay({
|
|
|
3131
3327
|
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Due: " }),
|
|
3132
3328
|
/* @__PURE__ */ jsx9(Text9, { children: formatDue(parsed.dueDate) })
|
|
3133
3329
|
] }) : null,
|
|
3134
|
-
|
|
3135
|
-
createError ? /* @__PURE__ */ jsx9(Text9, { color: "red", children: createError }) : null,
|
|
3136
|
-
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Enter:create Esc:cancel" })
|
|
3330
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Enter:add body Esc:cancel" })
|
|
3137
3331
|
] });
|
|
3138
3332
|
}
|
|
3139
3333
|
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
@@ -3154,24 +3348,17 @@ function NlCreateOverlay({
|
|
|
3154
3348
|
] });
|
|
3155
3349
|
}
|
|
3156
3350
|
function buildLabelList(parsed) {
|
|
3157
|
-
|
|
3158
|
-
if (parsed.dueDate) {
|
|
3159
|
-
labels.push(`due:${parsed.dueDate}`);
|
|
3160
|
-
}
|
|
3161
|
-
return labels;
|
|
3162
|
-
}
|
|
3163
|
-
function hasDueLabelInCache(labelCache, repoName) {
|
|
3164
|
-
return (labelCache[repoName] ?? []).some((l) => l.name.startsWith("due:"));
|
|
3351
|
+
return [...parsed.labels];
|
|
3165
3352
|
}
|
|
3166
3353
|
function formatDue(dueDate) {
|
|
3167
3354
|
const d = /* @__PURE__ */ new Date(`${dueDate}T12:00:00`);
|
|
3168
|
-
|
|
3169
|
-
return `${human} (label: due:${dueDate})`;
|
|
3355
|
+
return d.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric" });
|
|
3170
3356
|
}
|
|
3171
3357
|
var init_nl_create_overlay = __esm({
|
|
3172
3358
|
"src/board/components/nl-create-overlay.tsx"() {
|
|
3173
3359
|
"use strict";
|
|
3174
3360
|
init_ai();
|
|
3361
|
+
init_ink_instance();
|
|
3175
3362
|
}
|
|
3176
3363
|
});
|
|
3177
3364
|
|
|
@@ -3411,6 +3598,8 @@ function OverlayRenderer({
|
|
|
3411
3598
|
labelCache,
|
|
3412
3599
|
onSubmit: onCreateIssue,
|
|
3413
3600
|
onCancel: onExitOverlay,
|
|
3601
|
+
onPauseRefresh,
|
|
3602
|
+
onResumeRefresh,
|
|
3414
3603
|
onLlmFallback
|
|
3415
3604
|
}
|
|
3416
3605
|
) : null
|
|
@@ -3711,7 +3900,7 @@ var init_toast_container = __esm({
|
|
|
3711
3900
|
});
|
|
3712
3901
|
|
|
3713
3902
|
// src/board/components/dashboard.tsx
|
|
3714
|
-
import { execFileSync as execFileSync3, spawnSync as
|
|
3903
|
+
import { execFileSync as execFileSync3, spawnSync as spawnSync3 } from "child_process";
|
|
3715
3904
|
import { Spinner as Spinner4 } from "@inkjs/ui";
|
|
3716
3905
|
import { Box as Box16, Text as Text16, useApp, useStdout } from "ink";
|
|
3717
3906
|
import { useCallback as useCallback10, useEffect as useEffect6, useMemo as useMemo2, useRef as useRef11, useState as useState11 } from "react";
|
|
@@ -4046,8 +4235,8 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
4046
4235
|
const pendingPickRef = useRef11(null);
|
|
4047
4236
|
const labelCacheRef = useRef11({});
|
|
4048
4237
|
const handleCreateIssueWithPrompt = useCallback10(
|
|
4049
|
-
(repo, title, labels) => {
|
|
4050
|
-
actions.handleCreateIssue(repo, title, labels).then((result) => {
|
|
4238
|
+
(repo, title, body, dueDate, labels) => {
|
|
4239
|
+
actions.handleCreateIssue(repo, title, body, dueDate, labels).then((result) => {
|
|
4051
4240
|
if (result) {
|
|
4052
4241
|
pendingPickRef.current = result;
|
|
4053
4242
|
ui.enterConfirmPick();
|
|
@@ -4212,7 +4401,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
4212
4401
|
toast.info(`${label} \u2014 ${found.issue.url}`);
|
|
4213
4402
|
return;
|
|
4214
4403
|
}
|
|
4215
|
-
const result =
|
|
4404
|
+
const result = spawnSync3(cmd, args, {
|
|
4216
4405
|
input: found.issue.url,
|
|
4217
4406
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4218
4407
|
});
|
|
@@ -4963,6 +5152,34 @@ function detectStatusField(owner, projectNumber) {
|
|
|
4963
5152
|
if (!statusField) return null;
|
|
4964
5153
|
return { fieldId: statusField.id, options: statusField.options ?? [] };
|
|
4965
5154
|
}
|
|
5155
|
+
var DATE_FIELD_NAME_RE = /^(target\s*date|due\s*date|due|deadline)$/i;
|
|
5156
|
+
function detectDateField(owner, projectNumber) {
|
|
5157
|
+
const fields = listProjectFields(owner, projectNumber);
|
|
5158
|
+
return fields.find((f) => DATE_FIELD_NAME_RE.test(f.name)) ?? null;
|
|
5159
|
+
}
|
|
5160
|
+
function createDateField(owner, projectNumber, fieldName) {
|
|
5161
|
+
try {
|
|
5162
|
+
execFileSync(
|
|
5163
|
+
"gh",
|
|
5164
|
+
[
|
|
5165
|
+
"project",
|
|
5166
|
+
"field-create",
|
|
5167
|
+
String(projectNumber),
|
|
5168
|
+
"--owner",
|
|
5169
|
+
owner,
|
|
5170
|
+
"--name",
|
|
5171
|
+
fieldName,
|
|
5172
|
+
"--data-type",
|
|
5173
|
+
"DATE"
|
|
5174
|
+
],
|
|
5175
|
+
{ encoding: "utf-8", timeout: 3e4 }
|
|
5176
|
+
);
|
|
5177
|
+
const fields = listProjectFields(owner, projectNumber);
|
|
5178
|
+
return fields.find((f) => f.name === fieldName)?.id ?? null;
|
|
5179
|
+
} catch {
|
|
5180
|
+
return null;
|
|
5181
|
+
}
|
|
5182
|
+
}
|
|
4966
5183
|
async function runInit(opts = {}) {
|
|
4967
5184
|
try {
|
|
4968
5185
|
await runWizard(opts);
|
|
@@ -5046,6 +5263,37 @@ Configuring ${repoName}...`);
|
|
|
5046
5263
|
message: " Enter status field ID manually:"
|
|
5047
5264
|
});
|
|
5048
5265
|
}
|
|
5266
|
+
console.log(" Detecting due date field...");
|
|
5267
|
+
let dueDateFieldId;
|
|
5268
|
+
const existingDateField = detectDateField(owner, projectNumber);
|
|
5269
|
+
if (existingDateField) {
|
|
5270
|
+
console.log(` Found date field: "${existingDateField.name}" (${existingDateField.id})`);
|
|
5271
|
+
const useDateField = await confirm({
|
|
5272
|
+
message: ` Use "${existingDateField.name}" for due dates?`,
|
|
5273
|
+
default: true
|
|
5274
|
+
});
|
|
5275
|
+
if (useDateField) {
|
|
5276
|
+
dueDateFieldId = existingDateField.id;
|
|
5277
|
+
}
|
|
5278
|
+
} else {
|
|
5279
|
+
console.log(" No due date field found in this project.");
|
|
5280
|
+
const createField = await confirm({
|
|
5281
|
+
message: ' Create a "Due Date" field for due dates?',
|
|
5282
|
+
default: false
|
|
5283
|
+
});
|
|
5284
|
+
if (createField) {
|
|
5285
|
+
console.log(' Creating "Due Date" field...');
|
|
5286
|
+
const newFieldId = createDateField(owner, projectNumber, "Due Date");
|
|
5287
|
+
if (newFieldId) {
|
|
5288
|
+
dueDateFieldId = newFieldId;
|
|
5289
|
+
console.log(` Created "Due Date" field (${newFieldId})`);
|
|
5290
|
+
} else {
|
|
5291
|
+
console.log(" Could not create field \u2014 due dates will be stored in issue body.");
|
|
5292
|
+
}
|
|
5293
|
+
} else {
|
|
5294
|
+
console.log(" Skipped \u2014 due dates will be stored in issue body.");
|
|
5295
|
+
}
|
|
5296
|
+
}
|
|
5049
5297
|
const completionType = await select({
|
|
5050
5298
|
message: ` When a task is completed, what should happen on GitHub?`,
|
|
5051
5299
|
choices: [
|
|
@@ -5090,6 +5338,7 @@ Configuring ${repoName}...`);
|
|
|
5090
5338
|
shortName,
|
|
5091
5339
|
projectNumber,
|
|
5092
5340
|
statusFieldId,
|
|
5341
|
+
...dueDateFieldId ? { dueDateFieldId } : {},
|
|
5093
5342
|
completionAction
|
|
5094
5343
|
});
|
|
5095
5344
|
}
|
|
@@ -5556,7 +5805,7 @@ function resolveProjectId(projectId) {
|
|
|
5556
5805
|
process.exit(1);
|
|
5557
5806
|
}
|
|
5558
5807
|
var program = new Command();
|
|
5559
|
-
program.name("hog").description("Personal command deck \u2014 unified task dashboard for GitHub Projects + TickTick").version("1.
|
|
5808
|
+
program.name("hog").description("Personal command deck \u2014 unified task dashboard for GitHub Projects + TickTick").version("1.6.0").option("--json", "Force JSON output").option("--human", "Force human-readable output").hook("preAction", (thisCommand) => {
|
|
5560
5809
|
const opts = thisCommand.opts();
|
|
5561
5810
|
if (opts.json) setFormat("json");
|
|
5562
5811
|
if (opts.human) setFormat("human");
|
|
@@ -5993,7 +6242,7 @@ issueCommand.command("create <text>").description("Create a GitHub issue from na
|
|
|
5993
6242
|
console.error("[dry-run] Skipping issue creation.");
|
|
5994
6243
|
return;
|
|
5995
6244
|
}
|
|
5996
|
-
const args = ["issue", "create", "--repo", repo, "--title", parsed.title];
|
|
6245
|
+
const args = ["issue", "create", "--repo", repo, "--title", parsed.title, "--body", ""];
|
|
5997
6246
|
for (const label of labels) {
|
|
5998
6247
|
args.push("--label", label);
|
|
5999
6248
|
}
|