@ondrej-svec/hog 1.5.0 → 1.6.1
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/cli.js +182 -28
- 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
|
|
|
@@ -1162,7 +1243,6 @@ function useActions({
|
|
|
1162
1243
|
} else {
|
|
1163
1244
|
t.resolve(`#${issue.number} \u2192 ${optionName}`);
|
|
1164
1245
|
}
|
|
1165
|
-
refresh();
|
|
1166
1246
|
}).catch((err) => {
|
|
1167
1247
|
t.reject(`Status change failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1168
1248
|
refresh();
|
|
@@ -1222,8 +1302,16 @@ function useActions({
|
|
|
1222
1302
|
});
|
|
1223
1303
|
}, [toast, refresh]);
|
|
1224
1304
|
const handleCreateIssue = useCallback(
|
|
1225
|
-
async (repo, title, body, labels) => {
|
|
1226
|
-
const
|
|
1305
|
+
async (repo, title, body, dueDate, labels) => {
|
|
1306
|
+
const repoConfig = configRef.current.repos.find((r) => r.name === repo);
|
|
1307
|
+
let effectiveBody = body;
|
|
1308
|
+
if (dueDate && !repoConfig?.dueDateFieldId) {
|
|
1309
|
+
const dueLine = `Due: ${dueDate}`;
|
|
1310
|
+
effectiveBody = body ? `${body}
|
|
1311
|
+
|
|
1312
|
+
${dueLine}` : dueLine;
|
|
1313
|
+
}
|
|
1314
|
+
const args = ["issue", "create", "--repo", repo, "--title", title, "--body", effectiveBody];
|
|
1227
1315
|
if (labels && labels.length > 0) {
|
|
1228
1316
|
for (const label of labels) {
|
|
1229
1317
|
args.push("--label", label);
|
|
@@ -1235,7 +1323,15 @@ function useActions({
|
|
|
1235
1323
|
const output = stdout.trim();
|
|
1236
1324
|
const match = output.match(/\/(\d+)$/);
|
|
1237
1325
|
const issueNumber = match?.[1] ? parseInt(match[1], 10) : 0;
|
|
1238
|
-
const shortName =
|
|
1326
|
+
const shortName = repoConfig?.shortName ?? repo;
|
|
1327
|
+
if (issueNumber > 0 && dueDate && repoConfig?.dueDateFieldId) {
|
|
1328
|
+
const dueDateConfig = {
|
|
1329
|
+
projectNumber: repoConfig.projectNumber,
|
|
1330
|
+
dueDateFieldId: repoConfig.dueDateFieldId
|
|
1331
|
+
};
|
|
1332
|
+
updateProjectItemDateAsync(repo, issueNumber, dueDateConfig, dueDate).catch(() => {
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1239
1335
|
t.resolve(`Created ${shortName}#${issueNumber}`);
|
|
1240
1336
|
refresh();
|
|
1241
1337
|
onOverlayDone();
|
|
@@ -1386,8 +1482,8 @@ function useActions({
|
|
|
1386
1482
|
t.resolve(`Moved ${total} issue${total > 1 ? "s" : ""} to ${optionName}`);
|
|
1387
1483
|
} else {
|
|
1388
1484
|
t.reject(`${ok} moved to ${optionName}, ${failed.length} failed`);
|
|
1485
|
+
refresh();
|
|
1389
1486
|
}
|
|
1390
|
-
refresh();
|
|
1391
1487
|
return failed;
|
|
1392
1488
|
},
|
|
1393
1489
|
[toast, refresh, mutateData]
|
|
@@ -2778,13 +2874,19 @@ function CreateIssueForm({
|
|
|
2778
2874
|
currentLabels: [],
|
|
2779
2875
|
labelCache: labelCache ?? {},
|
|
2780
2876
|
onConfirm: (addLabels) => {
|
|
2781
|
-
onSubmit(
|
|
2877
|
+
onSubmit(
|
|
2878
|
+
selectedRepo.name,
|
|
2879
|
+
title,
|
|
2880
|
+
"",
|
|
2881
|
+
null,
|
|
2882
|
+
addLabels.length > 0 ? addLabels : void 0
|
|
2883
|
+
);
|
|
2782
2884
|
},
|
|
2783
2885
|
onCancel: () => {
|
|
2784
|
-
onSubmit(selectedRepo.name, title, "");
|
|
2886
|
+
onSubmit(selectedRepo.name, title, "", null);
|
|
2785
2887
|
},
|
|
2786
2888
|
onError: () => {
|
|
2787
|
-
onSubmit(selectedRepo.name, title, "");
|
|
2889
|
+
onSubmit(selectedRepo.name, title, "", null);
|
|
2788
2890
|
}
|
|
2789
2891
|
}
|
|
2790
2892
|
)
|
|
@@ -2820,7 +2922,7 @@ function CreateIssueForm({
|
|
|
2820
2922
|
setTitle(trimmed);
|
|
2821
2923
|
setField("labels");
|
|
2822
2924
|
} else {
|
|
2823
|
-
onSubmit(selectedRepo.name, trimmed, "");
|
|
2925
|
+
onSubmit(selectedRepo.name, trimmed, "", null);
|
|
2824
2926
|
}
|
|
2825
2927
|
}
|
|
2826
2928
|
}
|
|
@@ -3109,7 +3211,7 @@ function NlCreateOverlay({
|
|
|
3109
3211
|
setBody(content.trimEnd());
|
|
3110
3212
|
} finally {
|
|
3111
3213
|
onResumeRef.current?.();
|
|
3112
|
-
if (tmpFile) {
|
|
3214
|
+
if (tmpFile && tmpDir) {
|
|
3113
3215
|
try {
|
|
3114
3216
|
rmSync2(tmpDir, { recursive: true, force: true });
|
|
3115
3217
|
} catch {
|
|
@@ -3186,6 +3288,7 @@ function NlCreateOverlay({
|
|
|
3186
3288
|
selectedRepo.name,
|
|
3187
3289
|
parsed.title,
|
|
3188
3290
|
text.trim(),
|
|
3291
|
+
parsed.dueDate,
|
|
3189
3292
|
labels.length > 0 ? labels : void 0
|
|
3190
3293
|
);
|
|
3191
3294
|
}
|
|
@@ -3196,7 +3299,7 @@ function NlCreateOverlay({
|
|
|
3196
3299
|
] });
|
|
3197
3300
|
}
|
|
3198
3301
|
if (parsed) {
|
|
3199
|
-
const labels =
|
|
3302
|
+
const labels = [...parsed.labels];
|
|
3200
3303
|
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
3201
3304
|
/* @__PURE__ */ jsx9(Text9, { color: "cyan", bold: true, children: "\u2728 Creating Issue" }),
|
|
3202
3305
|
/* @__PURE__ */ jsxs9(Box9, { children: [
|
|
@@ -3223,7 +3326,6 @@ function NlCreateOverlay({
|
|
|
3223
3326
|
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Due: " }),
|
|
3224
3327
|
/* @__PURE__ */ jsx9(Text9, { children: formatDue(parsed.dueDate) })
|
|
3225
3328
|
] }) : null,
|
|
3226
|
-
parsed.dueDate && selectedRepo && !hasDueLabelInCache(labelCache, selectedRepo.name) ? /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "\u26A0 No due:* label in this repo \u2014 will try to create label on submit" }) : null,
|
|
3227
3329
|
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Enter:add body Esc:cancel" })
|
|
3228
3330
|
] });
|
|
3229
3331
|
}
|
|
@@ -3245,19 +3347,11 @@ function NlCreateOverlay({
|
|
|
3245
3347
|
] });
|
|
3246
3348
|
}
|
|
3247
3349
|
function buildLabelList(parsed) {
|
|
3248
|
-
|
|
3249
|
-
if (parsed.dueDate) {
|
|
3250
|
-
labels.push(`due:${parsed.dueDate}`);
|
|
3251
|
-
}
|
|
3252
|
-
return labels;
|
|
3253
|
-
}
|
|
3254
|
-
function hasDueLabelInCache(labelCache, repoName) {
|
|
3255
|
-
return (labelCache[repoName] ?? []).some((l) => l.name.startsWith("due:"));
|
|
3350
|
+
return [...parsed.labels];
|
|
3256
3351
|
}
|
|
3257
3352
|
function formatDue(dueDate) {
|
|
3258
3353
|
const d = /* @__PURE__ */ new Date(`${dueDate}T12:00:00`);
|
|
3259
|
-
|
|
3260
|
-
return `${human} (label: due:${dueDate})`;
|
|
3354
|
+
return d.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric" });
|
|
3261
3355
|
}
|
|
3262
3356
|
var init_nl_create_overlay = __esm({
|
|
3263
3357
|
"src/board/components/nl-create-overlay.tsx"() {
|
|
@@ -4140,8 +4234,8 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
4140
4234
|
const pendingPickRef = useRef11(null);
|
|
4141
4235
|
const labelCacheRef = useRef11({});
|
|
4142
4236
|
const handleCreateIssueWithPrompt = useCallback10(
|
|
4143
|
-
(repo, title, body, labels) => {
|
|
4144
|
-
actions.handleCreateIssue(repo, title, body, labels).then((result) => {
|
|
4237
|
+
(repo, title, body, dueDate, labels) => {
|
|
4238
|
+
actions.handleCreateIssue(repo, title, body, dueDate, labels).then((result) => {
|
|
4145
4239
|
if (result) {
|
|
4146
4240
|
pendingPickRef.current = result;
|
|
4147
4241
|
ui.enterConfirmPick();
|
|
@@ -5057,6 +5151,34 @@ function detectStatusField(owner, projectNumber) {
|
|
|
5057
5151
|
if (!statusField) return null;
|
|
5058
5152
|
return { fieldId: statusField.id, options: statusField.options ?? [] };
|
|
5059
5153
|
}
|
|
5154
|
+
var DATE_FIELD_NAME_RE = /^(target\s*date|due\s*date|due|deadline)$/i;
|
|
5155
|
+
function detectDateField(owner, projectNumber) {
|
|
5156
|
+
const fields = listProjectFields(owner, projectNumber);
|
|
5157
|
+
return fields.find((f) => DATE_FIELD_NAME_RE.test(f.name)) ?? null;
|
|
5158
|
+
}
|
|
5159
|
+
function createDateField(owner, projectNumber, fieldName) {
|
|
5160
|
+
try {
|
|
5161
|
+
execFileSync(
|
|
5162
|
+
"gh",
|
|
5163
|
+
[
|
|
5164
|
+
"project",
|
|
5165
|
+
"field-create",
|
|
5166
|
+
String(projectNumber),
|
|
5167
|
+
"--owner",
|
|
5168
|
+
owner,
|
|
5169
|
+
"--name",
|
|
5170
|
+
fieldName,
|
|
5171
|
+
"--data-type",
|
|
5172
|
+
"DATE"
|
|
5173
|
+
],
|
|
5174
|
+
{ encoding: "utf-8", timeout: 3e4 }
|
|
5175
|
+
);
|
|
5176
|
+
const fields = listProjectFields(owner, projectNumber);
|
|
5177
|
+
return fields.find((f) => f.name === fieldName)?.id ?? null;
|
|
5178
|
+
} catch {
|
|
5179
|
+
return null;
|
|
5180
|
+
}
|
|
5181
|
+
}
|
|
5060
5182
|
async function runInit(opts = {}) {
|
|
5061
5183
|
try {
|
|
5062
5184
|
await runWizard(opts);
|
|
@@ -5140,6 +5262,37 @@ Configuring ${repoName}...`);
|
|
|
5140
5262
|
message: " Enter status field ID manually:"
|
|
5141
5263
|
});
|
|
5142
5264
|
}
|
|
5265
|
+
console.log(" Detecting due date field...");
|
|
5266
|
+
let dueDateFieldId;
|
|
5267
|
+
const existingDateField = detectDateField(owner, projectNumber);
|
|
5268
|
+
if (existingDateField) {
|
|
5269
|
+
console.log(` Found date field: "${existingDateField.name}" (${existingDateField.id})`);
|
|
5270
|
+
const useDateField = await confirm({
|
|
5271
|
+
message: ` Use "${existingDateField.name}" for due dates?`,
|
|
5272
|
+
default: true
|
|
5273
|
+
});
|
|
5274
|
+
if (useDateField) {
|
|
5275
|
+
dueDateFieldId = existingDateField.id;
|
|
5276
|
+
}
|
|
5277
|
+
} else {
|
|
5278
|
+
console.log(" No due date field found in this project.");
|
|
5279
|
+
const createField = await confirm({
|
|
5280
|
+
message: ' Create a "Due Date" field for due dates?',
|
|
5281
|
+
default: false
|
|
5282
|
+
});
|
|
5283
|
+
if (createField) {
|
|
5284
|
+
console.log(' Creating "Due Date" field...');
|
|
5285
|
+
const newFieldId = createDateField(owner, projectNumber, "Due Date");
|
|
5286
|
+
if (newFieldId) {
|
|
5287
|
+
dueDateFieldId = newFieldId;
|
|
5288
|
+
console.log(` Created "Due Date" field (${newFieldId})`);
|
|
5289
|
+
} else {
|
|
5290
|
+
console.log(" Could not create field \u2014 due dates will be stored in issue body.");
|
|
5291
|
+
}
|
|
5292
|
+
} else {
|
|
5293
|
+
console.log(" Skipped \u2014 due dates will be stored in issue body.");
|
|
5294
|
+
}
|
|
5295
|
+
}
|
|
5143
5296
|
const completionType = await select({
|
|
5144
5297
|
message: ` When a task is completed, what should happen on GitHub?`,
|
|
5145
5298
|
choices: [
|
|
@@ -5184,6 +5337,7 @@ Configuring ${repoName}...`);
|
|
|
5184
5337
|
shortName,
|
|
5185
5338
|
projectNumber,
|
|
5186
5339
|
statusFieldId,
|
|
5340
|
+
...dueDateFieldId ? { dueDateFieldId } : {},
|
|
5187
5341
|
completionAction
|
|
5188
5342
|
});
|
|
5189
5343
|
}
|
|
@@ -5650,7 +5804,7 @@ function resolveProjectId(projectId) {
|
|
|
5650
5804
|
process.exit(1);
|
|
5651
5805
|
}
|
|
5652
5806
|
var program = new Command();
|
|
5653
|
-
program.name("hog").description("Personal command deck \u2014 unified task dashboard for GitHub Projects + TickTick").version("1.
|
|
5807
|
+
program.name("hog").description("Personal command deck \u2014 unified task dashboard for GitHub Projects + TickTick").version("1.6.1").option("--json", "Force JSON output").option("--human", "Force human-readable output").hook("preAction", (thisCommand) => {
|
|
5654
5808
|
const opts = thisCommand.opts();
|
|
5655
5809
|
if (opts.json) setFormat("json");
|
|
5656
5810
|
if (opts.human) setFormat("human");
|