@inteeka/task-cli 0.1.11 → 0.1.12
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 +157 -19
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2711,6 +2711,143 @@ function clampInt(raw, min, max, fallback) {
|
|
|
2711
2711
|
return Math.min(v, max);
|
|
2712
2712
|
}
|
|
2713
2713
|
|
|
2714
|
+
// src/commands/pr-test.ts
|
|
2715
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
2716
|
+
function registerPrTest(program2) {
|
|
2717
|
+
program2.command("pr-test").description(
|
|
2718
|
+
"Dry-run the full PR pipeline \u2014 cuts a throwaway branch, opens a real PR via the dashboard, then cleans up. Use this to verify your git integration before running task work on real tickets."
|
|
2719
|
+
).option(
|
|
2720
|
+
"--keep",
|
|
2721
|
+
"Leave the PR open and the branches in place after the test (default: clean up)"
|
|
2722
|
+
).option("--silent", "Suppress per-step progress output").action(async (opts) => {
|
|
2723
|
+
await runPrTest(opts);
|
|
2724
|
+
});
|
|
2725
|
+
}
|
|
2726
|
+
async function runPrTest(opts) {
|
|
2727
|
+
const cwd = findRepoRoot();
|
|
2728
|
+
const project = await readProjectConfig(cwd);
|
|
2729
|
+
if (!project) {
|
|
2730
|
+
throw new CliError(
|
|
2731
|
+
CLI_EXIT_CODES.MISCONFIGURATION,
|
|
2732
|
+
"No project link in this repo",
|
|
2733
|
+
"Run 'task link' first."
|
|
2734
|
+
);
|
|
2735
|
+
}
|
|
2736
|
+
const baseBranch = project.cli_base_branch ?? "development";
|
|
2737
|
+
const silent = !!opts.silent;
|
|
2738
|
+
if (!silent) process.stdout.write(`${c.dim(`Step 1/6: assertBaseBranch (${baseBranch})\u2026`)}
|
|
2739
|
+
`);
|
|
2740
|
+
assertBaseBranch(cwd, baseBranch);
|
|
2741
|
+
const timestamp = Date.now();
|
|
2742
|
+
const branchName = `task/pr-test-${timestamp}`;
|
|
2743
|
+
const prTitle = `[task pr-test] CLI dry-run probe ${new Date(timestamp).toISOString()}`;
|
|
2744
|
+
if (!silent) process.stdout.write(`${c.dim(`Step 2/6: createTicketBranch (${branchName})\u2026`)}
|
|
2745
|
+
`);
|
|
2746
|
+
createTicketBranch(cwd, branchName, baseBranch);
|
|
2747
|
+
let pushSucceeded = false;
|
|
2748
|
+
const recover = () => {
|
|
2749
|
+
try {
|
|
2750
|
+
checkoutBranch(cwd, baseBranch);
|
|
2751
|
+
} catch {
|
|
2752
|
+
}
|
|
2753
|
+
deleteLocalBranch(cwd, branchName);
|
|
2754
|
+
};
|
|
2755
|
+
try {
|
|
2756
|
+
if (!silent) process.stdout.write(`${c.dim("Step 3/6: empty commit\u2026")}
|
|
2757
|
+
`);
|
|
2758
|
+
execFileSync6(
|
|
2759
|
+
"git",
|
|
2760
|
+
["commit", "--allow-empty", "-m", `task pr-test: connectivity probe ${timestamp}`],
|
|
2761
|
+
{ cwd, stdio: ["ignore", "pipe", "pipe"] }
|
|
2762
|
+
);
|
|
2763
|
+
if (!silent) process.stdout.write(`${c.dim(`Step 4/6: pushBranch (${branchName})\u2026`)}
|
|
2764
|
+
`);
|
|
2765
|
+
pushBranch(cwd, branchName);
|
|
2766
|
+
pushSucceeded = true;
|
|
2767
|
+
if (!silent) process.stdout.write(`${c.dim("Step 5/6: open PR via dashboard\u2026")}
|
|
2768
|
+
`);
|
|
2769
|
+
const pr = await apiCallOrThrow("POST", `/api/v1/cli/me/git/pr-test`, {
|
|
2770
|
+
body: {
|
|
2771
|
+
project_id: project.project_id,
|
|
2772
|
+
source_branch: branchName,
|
|
2773
|
+
base_branch: baseBranch,
|
|
2774
|
+
title: prTitle
|
|
2775
|
+
}
|
|
2776
|
+
});
|
|
2777
|
+
process.stdout.write(
|
|
2778
|
+
`
|
|
2779
|
+
${c.ok("\u2713 PR opened")} ${c.cyan(pr.pr_url)}
|
|
2780
|
+
repo: ${pr.repo}
|
|
2781
|
+
branch: ${pr.source_branch} \u2192 ${pr.base_branch}
|
|
2782
|
+
number: #${pr.pr_number}
|
|
2783
|
+
|
|
2784
|
+
`
|
|
2785
|
+
);
|
|
2786
|
+
if (opts.keep) {
|
|
2787
|
+
process.stdout.write(
|
|
2788
|
+
`${c.warn("--keep")} ${c.dim("flag set \u2014 PR and branches preserved. Manual cleanup:")}
|
|
2789
|
+
${c.cyan(`task pr-test-cleanup ${pr.pr_number} ${branchName}`)}
|
|
2790
|
+
(or close on GitHub + ${c.cyan(`git push origin --delete ${branchName}`)} + ${c.cyan(`git branch -D ${branchName}`)})
|
|
2791
|
+
`
|
|
2792
|
+
);
|
|
2793
|
+
checkoutBranch(cwd, baseBranch);
|
|
2794
|
+
return;
|
|
2795
|
+
}
|
|
2796
|
+
if (!silent) process.stdout.write(`${c.dim("Step 6/6: cleanup\u2026")}
|
|
2797
|
+
`);
|
|
2798
|
+
const cleanup = await apiCall("POST", `/api/v1/cli/me/git/pr-test/cleanup`, {
|
|
2799
|
+
body: {
|
|
2800
|
+
project_id: project.project_id,
|
|
2801
|
+
pr_number: pr.pr_number,
|
|
2802
|
+
source_branch: branchName
|
|
2803
|
+
}
|
|
2804
|
+
});
|
|
2805
|
+
checkoutBranch(cwd, baseBranch);
|
|
2806
|
+
deleteLocalBranch(cwd, branchName);
|
|
2807
|
+
if (cleanup.ok && cleanup.data) {
|
|
2808
|
+
const { pr_closed, branch_deleted } = cleanup.data;
|
|
2809
|
+
process.stdout.write(
|
|
2810
|
+
`${c.ok("\u2713 Cleanup complete")} \u2014 PR ${pr_closed ? "closed" : c.warn("NOT closed")}, remote branch ${branch_deleted ? "deleted" : c.warn("NOT deleted")}, local branch deleted.
|
|
2811
|
+
`
|
|
2812
|
+
);
|
|
2813
|
+
if (!pr_closed || !branch_deleted) {
|
|
2814
|
+
process.stdout.write(
|
|
2815
|
+
c.dim(` Manually close at: ${pr.pr_url}
|
|
2816
|
+
`) + c.dim(` Manually delete remote: git push origin --delete ${branchName}
|
|
2817
|
+
`)
|
|
2818
|
+
);
|
|
2819
|
+
}
|
|
2820
|
+
} else {
|
|
2821
|
+
process.stdout.write(
|
|
2822
|
+
`${c.warn("Cleanup endpoint failed \u2014 branches & PR may need manual cleanup:")}
|
|
2823
|
+
${c.cyan(pr.pr_url)}
|
|
2824
|
+
${c.cyan(`git push origin --delete ${branchName}`)}
|
|
2825
|
+
`
|
|
2826
|
+
);
|
|
2827
|
+
}
|
|
2828
|
+
process.stdout.write(
|
|
2829
|
+
`
|
|
2830
|
+
${c.ok("\u2713 pr-test passed")} \u2014 the full CLI \u2192 dashboard \u2192 GitHub PR pipeline works.
|
|
2831
|
+
`
|
|
2832
|
+
);
|
|
2833
|
+
} catch (err) {
|
|
2834
|
+
process.stdout.write(`
|
|
2835
|
+
${c.err("\u2717 pr-test failed")}: ${err.message}
|
|
2836
|
+
`);
|
|
2837
|
+
if (pushSucceeded) {
|
|
2838
|
+
process.stdout.write(
|
|
2839
|
+
c.dim(
|
|
2840
|
+
` The branch was pushed but the PR step failed. Manually clean up:
|
|
2841
|
+
git push origin --delete ${branchName}
|
|
2842
|
+
`
|
|
2843
|
+
)
|
|
2844
|
+
);
|
|
2845
|
+
}
|
|
2846
|
+
recover();
|
|
2847
|
+
throw err;
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
|
|
2714
2851
|
// src/commands/scheduled-task.ts
|
|
2715
2852
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2716
2853
|
|
|
@@ -2721,7 +2858,7 @@ import { platform as platform2 } from "os";
|
|
|
2721
2858
|
import { mkdir as mkdir7, readFile as readFile5, writeFile as writeFile8, unlink as unlink3, readdir } from "fs/promises";
|
|
2722
2859
|
import { homedir as homedir6 } from "os";
|
|
2723
2860
|
import { join as join8 } from "path";
|
|
2724
|
-
import { execFileSync as
|
|
2861
|
+
import { execFileSync as execFileSync7, spawn as spawn4 } from "child_process";
|
|
2725
2862
|
|
|
2726
2863
|
// src/scheduler/cron-translate.ts
|
|
2727
2864
|
function translateToLaunchd(cron) {
|
|
@@ -2889,17 +3026,17 @@ var launchdAdapter = {
|
|
|
2889
3026
|
const path = plistPath(entry.id);
|
|
2890
3027
|
await writeFile8(path, buildPlist(entry));
|
|
2891
3028
|
try {
|
|
2892
|
-
|
|
3029
|
+
execFileSync7("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
|
|
2893
3030
|
} catch {
|
|
2894
3031
|
}
|
|
2895
3032
|
if (entry.enabled) {
|
|
2896
|
-
|
|
3033
|
+
execFileSync7("launchctl", ["bootstrap", bootstrapDomain(), path]);
|
|
2897
3034
|
}
|
|
2898
3035
|
},
|
|
2899
3036
|
async remove(id) {
|
|
2900
3037
|
const path = plistPath(id);
|
|
2901
3038
|
try {
|
|
2902
|
-
|
|
3039
|
+
execFileSync7("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
|
|
2903
3040
|
} catch {
|
|
2904
3041
|
}
|
|
2905
3042
|
try {
|
|
@@ -2964,10 +3101,10 @@ var launchdAdapter = {
|
|
|
2964
3101
|
xml = xml.replace(/\s*<key>Disabled<\/key>\s*<true\/>/, "");
|
|
2965
3102
|
await writeFile8(path, xml);
|
|
2966
3103
|
try {
|
|
2967
|
-
|
|
3104
|
+
execFileSync7("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
|
|
2968
3105
|
} catch {
|
|
2969
3106
|
}
|
|
2970
|
-
|
|
3107
|
+
execFileSync7("launchctl", ["bootstrap", bootstrapDomain(), path]);
|
|
2971
3108
|
} else {
|
|
2972
3109
|
if (!/<key>Disabled<\/key>/.test(xml)) {
|
|
2973
3110
|
xml = xml.replace(
|
|
@@ -2977,7 +3114,7 @@ var launchdAdapter = {
|
|
|
2977
3114
|
await writeFile8(path, xml);
|
|
2978
3115
|
}
|
|
2979
3116
|
try {
|
|
2980
|
-
|
|
3117
|
+
execFileSync7("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
|
|
2981
3118
|
} catch {
|
|
2982
3119
|
}
|
|
2983
3120
|
}
|
|
@@ -2985,7 +3122,7 @@ var launchdAdapter = {
|
|
|
2985
3122
|
};
|
|
2986
3123
|
|
|
2987
3124
|
// src/scheduler/cron.ts
|
|
2988
|
-
import { execFileSync as
|
|
3125
|
+
import { execFileSync as execFileSync8, spawn as spawn5 } from "child_process";
|
|
2989
3126
|
|
|
2990
3127
|
// src/scheduler/safe-command.ts
|
|
2991
3128
|
var FORBIDDEN = /[;&|`$()<>\\]/;
|
|
@@ -3040,7 +3177,7 @@ var MARK_OPEN = (id) => `# task-cli:${id}:start`;
|
|
|
3040
3177
|
var MARK_CLOSE = (id) => `# task-cli:${id}:end`;
|
|
3041
3178
|
function readCrontab() {
|
|
3042
3179
|
try {
|
|
3043
|
-
return
|
|
3180
|
+
return execFileSync8("crontab", ["-l"], { encoding: "utf8" });
|
|
3044
3181
|
} catch {
|
|
3045
3182
|
return "";
|
|
3046
3183
|
}
|
|
@@ -3155,7 +3292,7 @@ var cronAdapter = {
|
|
|
3155
3292
|
};
|
|
3156
3293
|
|
|
3157
3294
|
// src/scheduler/windows.ts
|
|
3158
|
-
import { execFileSync as
|
|
3295
|
+
import { execFileSync as execFileSync9, spawn as spawn6 } from "child_process";
|
|
3159
3296
|
var TASK_PREFIX = "TaskCLI_";
|
|
3160
3297
|
function taskName(id) {
|
|
3161
3298
|
return `${TASK_PREFIX}${id.replace(/[^A-Za-z0-9_-]/g, "_")}`;
|
|
@@ -3220,22 +3357,22 @@ function pad(v) {
|
|
|
3220
3357
|
var windowsAdapter = {
|
|
3221
3358
|
async upsert(entry) {
|
|
3222
3359
|
const args = buildSchtasksArgs(entry, entry.command);
|
|
3223
|
-
|
|
3360
|
+
execFileSync9("schtasks.exe", args, { stdio: "ignore" });
|
|
3224
3361
|
if (!entry.enabled) {
|
|
3225
|
-
|
|
3362
|
+
execFileSync9("schtasks.exe", ["/Change", "/TN", taskName(entry.id), "/DISABLE"], {
|
|
3226
3363
|
stdio: "ignore"
|
|
3227
3364
|
});
|
|
3228
3365
|
}
|
|
3229
3366
|
},
|
|
3230
3367
|
async remove(id) {
|
|
3231
3368
|
try {
|
|
3232
|
-
|
|
3369
|
+
execFileSync9("schtasks.exe", ["/Delete", "/TN", taskName(id), "/F"], { stdio: "ignore" });
|
|
3233
3370
|
} catch {
|
|
3234
3371
|
}
|
|
3235
3372
|
},
|
|
3236
3373
|
async list() {
|
|
3237
3374
|
try {
|
|
3238
|
-
const csv =
|
|
3375
|
+
const csv = execFileSync9("schtasks.exe", ["/Query", "/FO", "CSV", "/V"], {
|
|
3239
3376
|
encoding: "utf8"
|
|
3240
3377
|
});
|
|
3241
3378
|
const lines = csv.split(/\r?\n/);
|
|
@@ -3285,7 +3422,7 @@ var windowsAdapter = {
|
|
|
3285
3422
|
},
|
|
3286
3423
|
async setEnabled(id, enabled) {
|
|
3287
3424
|
try {
|
|
3288
|
-
|
|
3425
|
+
execFileSync9(
|
|
3289
3426
|
"schtasks.exe",
|
|
3290
3427
|
["/Change", "/TN", taskName(id), enabled ? "/ENABLE" : "/DISABLE"],
|
|
3291
3428
|
{ stdio: "ignore" }
|
|
@@ -3693,7 +3830,7 @@ function registerConfig(program2) {
|
|
|
3693
3830
|
}
|
|
3694
3831
|
|
|
3695
3832
|
// src/commands/doctor.ts
|
|
3696
|
-
import { execFileSync as
|
|
3833
|
+
import { execFileSync as execFileSync10 } from "child_process";
|
|
3697
3834
|
import { request as request5 } from "undici";
|
|
3698
3835
|
function registerDoctor(program2) {
|
|
3699
3836
|
program2.command("doctor").description("Diagnose your CLI setup").action(async () => {
|
|
@@ -3741,7 +3878,7 @@ function registerDoctor(program2) {
|
|
|
3741
3878
|
});
|
|
3742
3879
|
}
|
|
3743
3880
|
try {
|
|
3744
|
-
const dirty =
|
|
3881
|
+
const dirty = execFileSync10("git", ["status", "--porcelain"], {
|
|
3745
3882
|
cwd: root,
|
|
3746
3883
|
encoding: "utf8"
|
|
3747
3884
|
}).trim();
|
|
@@ -3765,7 +3902,7 @@ function registerDoctor(program2) {
|
|
|
3765
3902
|
}
|
|
3766
3903
|
function checkBinary(name, command) {
|
|
3767
3904
|
try {
|
|
3768
|
-
const out =
|
|
3905
|
+
const out = execFileSync10(command, ["--version"], { encoding: "utf8" }).trim();
|
|
3769
3906
|
return { name, ok: true, detail: out.split("\n")[0] ?? out };
|
|
3770
3907
|
} catch {
|
|
3771
3908
|
return { name, ok: false, detail: `'${command}' not found on PATH` };
|
|
@@ -3773,7 +3910,7 @@ function checkBinary(name, command) {
|
|
|
3773
3910
|
}
|
|
3774
3911
|
|
|
3775
3912
|
// src/commands/version.ts
|
|
3776
|
-
var CLI_VERSION = true ? "0.1.
|
|
3913
|
+
var CLI_VERSION = true ? "0.1.12" : "0.0.0-dev";
|
|
3777
3914
|
function registerVersion(program2) {
|
|
3778
3915
|
program2.command("version").description("Print the CLI version").action(() => {
|
|
3779
3916
|
process.stdout.write(CLI_VERSION + "\n");
|
|
@@ -3797,6 +3934,7 @@ registerTickets(program);
|
|
|
3797
3934
|
registerTicket(program);
|
|
3798
3935
|
registerWork(program);
|
|
3799
3936
|
registerScan(program);
|
|
3937
|
+
registerPrTest(program);
|
|
3800
3938
|
registerScheduledTask(program);
|
|
3801
3939
|
registerRuns(program);
|
|
3802
3940
|
registerConfig(program);
|