@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 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 execFileSync6, spawn as spawn4 } from "child_process";
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
- execFileSync6("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
3029
+ execFileSync7("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
2893
3030
  } catch {
2894
3031
  }
2895
3032
  if (entry.enabled) {
2896
- execFileSync6("launchctl", ["bootstrap", bootstrapDomain(), path]);
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
- execFileSync6("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
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
- execFileSync6("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
3104
+ execFileSync7("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
2968
3105
  } catch {
2969
3106
  }
2970
- execFileSync6("launchctl", ["bootstrap", bootstrapDomain(), path]);
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
- execFileSync6("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
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 execFileSync7, spawn as spawn5 } from "child_process";
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 execFileSync7("crontab", ["-l"], { encoding: "utf8" });
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 execFileSync8, spawn as spawn6 } from "child_process";
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
- execFileSync8("schtasks.exe", args, { stdio: "ignore" });
3360
+ execFileSync9("schtasks.exe", args, { stdio: "ignore" });
3224
3361
  if (!entry.enabled) {
3225
- execFileSync8("schtasks.exe", ["/Change", "/TN", taskName(entry.id), "/DISABLE"], {
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
- execFileSync8("schtasks.exe", ["/Delete", "/TN", taskName(id), "/F"], { stdio: "ignore" });
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 = execFileSync8("schtasks.exe", ["/Query", "/FO", "CSV", "/V"], {
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
- execFileSync8(
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 execFileSync9 } from "child_process";
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 = execFileSync9("git", ["status", "--porcelain"], {
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 = execFileSync9(command, ["--version"], { encoding: "utf8" }).trim();
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.11" : "0.0.0-dev";
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);