staklink 0.3.5 → 0.3.6

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.
@@ -33901,7 +33901,7 @@ var SSEManager = class {
33901
33901
  var sseManager = new SSEManager();
33902
33902
 
33903
33903
  // src/proxy/version.ts
33904
- var VERSION = "0.3.5";
33904
+ var VERSION = "0.3.6";
33905
33905
 
33906
33906
  // node_modules/uuid/dist/esm/stringify.js
33907
33907
  var byteToHex = [];
@@ -79579,6 +79579,387 @@ function gitleaksProtect() {
79579
79579
  }
79580
79580
  }
79581
79581
 
79582
+ // src/proxy/gh.ts
79583
+ var import_child_process4 = require("child_process");
79584
+ var import_util10 = require("util");
79585
+ var execAsync = (0, import_util10.promisify)(import_child_process4.exec);
79586
+ var GitHubCLI = class {
79587
+ token;
79588
+ branch;
79589
+ owner;
79590
+ repo;
79591
+ constructor(token, branch) {
79592
+ this.token = token;
79593
+ this.branch = branch;
79594
+ }
79595
+ /**
79596
+ * Initialize and get repo info from git
79597
+ */
79598
+ async init() {
79599
+ try {
79600
+ const { stdout } = await this.execGH("repo view --json owner,name");
79601
+ const data = JSON.parse(stdout);
79602
+ this.owner = data.owner.login;
79603
+ this.repo = data.name;
79604
+ } catch (error82) {
79605
+ throw new Error(`Failed to initialize: ${error82}`);
79606
+ }
79607
+ }
79608
+ /**
79609
+ * Execute gh CLI command with authentication
79610
+ */
79611
+ async execGH(command) {
79612
+ const fullCommand = `gh ${command}`;
79613
+ try {
79614
+ return await execAsync(fullCommand, {
79615
+ env: {
79616
+ ...process.env,
79617
+ GH_TOKEN: this.token,
79618
+ GITHUB_TOKEN: this.token
79619
+ }
79620
+ });
79621
+ } catch (error82) {
79622
+ throw new Error(
79623
+ `gh command failed: ${error82.message}
79624
+ Stderr: ${error82.stderr}`
79625
+ );
79626
+ }
79627
+ }
79628
+ /**
79629
+ * Sync version of execGH for simple checks
79630
+ */
79631
+ execGHSync(command) {
79632
+ const fullCommand = `gh ${command}`;
79633
+ try {
79634
+ return (0, import_child_process4.execSync)(fullCommand, {
79635
+ encoding: "utf-8",
79636
+ env: {
79637
+ ...process.env,
79638
+ GH_TOKEN: this.token,
79639
+ GITHUB_TOKEN: this.token
79640
+ }
79641
+ });
79642
+ } catch (error82) {
79643
+ throw new Error(
79644
+ `gh command failed: ${error82.message}
79645
+ Stderr: ${error82.stderr}`
79646
+ );
79647
+ }
79648
+ }
79649
+ /**
79650
+ * Check if the branch exists
79651
+ */
79652
+ async branchExists() {
79653
+ try {
79654
+ const { stdout } = await execAsync(
79655
+ "git rev-parse --verify --quiet " + this.branch
79656
+ );
79657
+ return true;
79658
+ } catch {
79659
+ return false;
79660
+ }
79661
+ }
79662
+ /**
79663
+ * Get current branch name
79664
+ */
79665
+ async getCurrentBranch() {
79666
+ const { stdout } = await execAsync("git rev-parse --abbrev-ref HEAD");
79667
+ return stdout.trim();
79668
+ }
79669
+ /**
79670
+ * Checkout the branch
79671
+ */
79672
+ async checkoutBranch() {
79673
+ try {
79674
+ await execAsync(`git checkout ${this.branch}`);
79675
+ } catch (error82) {
79676
+ throw new Error(`Failed to checkout branch ${this.branch}: ${error82}`);
79677
+ }
79678
+ }
79679
+ /**
79680
+ * Create a new PR from the branch
79681
+ */
79682
+ async createPR(options) {
79683
+ const args = [
79684
+ "pr",
79685
+ "create",
79686
+ "--head",
79687
+ this.branch,
79688
+ "--title",
79689
+ `"${options.title}"`
79690
+ ];
79691
+ if (options.body) {
79692
+ args.push("--body", `"${options.body}"`);
79693
+ }
79694
+ if (options.base) {
79695
+ args.push("--base", options.base);
79696
+ }
79697
+ if (options.draft) {
79698
+ args.push("--draft");
79699
+ }
79700
+ if (options.assignees?.length) {
79701
+ args.push("--assignee", options.assignees.join(","));
79702
+ }
79703
+ if (options.reviewers?.length) {
79704
+ args.push("--reviewer", options.reviewers.join(","));
79705
+ }
79706
+ if (options.labels?.length) {
79707
+ args.push("--label", options.labels.join(","));
79708
+ }
79709
+ try {
79710
+ const { stdout } = await this.execGH(args.join(" "));
79711
+ const prUrl = stdout.trim();
79712
+ const prNumber = parseInt(prUrl.split("/").pop() || "0");
79713
+ return await this.getPR(prNumber);
79714
+ } catch (error82) {
79715
+ throw new Error(`Failed to create PR: ${error82}`);
79716
+ }
79717
+ }
79718
+ /**
79719
+ * Find PR by branch name
79720
+ */
79721
+ async findPRByBranch() {
79722
+ try {
79723
+ const { stdout } = await this.execGH(
79724
+ `pr list --head ${this.branch} --json number,title,body,state,url,headRefName,baseRefName,mergeable,mergeStateStatus,isDraft,author,createdAt,updatedAt --limit 1`
79725
+ );
79726
+ const prs = JSON.parse(stdout);
79727
+ if (prs.length === 0) {
79728
+ return null;
79729
+ }
79730
+ const pr = prs[0];
79731
+ return {
79732
+ number: pr.number,
79733
+ title: pr.title,
79734
+ body: pr.body,
79735
+ state: pr.state,
79736
+ url: pr.url,
79737
+ headRefName: pr.headRefName,
79738
+ baseRefName: pr.baseRefName,
79739
+ mergeable: pr.mergeable,
79740
+ mergeStateStatus: pr.mergeStateStatus,
79741
+ isDraft: pr.isDraft,
79742
+ author: pr.author.login,
79743
+ createdAt: pr.createdAt,
79744
+ updatedAt: pr.updatedAt
79745
+ };
79746
+ } catch (error82) {
79747
+ throw new Error(`Failed to find PR: ${error82}`);
79748
+ }
79749
+ }
79750
+ /**
79751
+ * Get PR details by number
79752
+ */
79753
+ async getPR(prNumber) {
79754
+ try {
79755
+ const { stdout } = await this.execGH(
79756
+ `pr view ${prNumber} --json number,title,body,state,url,headRefName,baseRefName,mergeable,mergeStateStatus,isDraft,author,createdAt,updatedAt`
79757
+ );
79758
+ const pr = JSON.parse(stdout);
79759
+ return {
79760
+ number: pr.number,
79761
+ title: pr.title,
79762
+ body: pr.body,
79763
+ state: pr.state,
79764
+ url: pr.url,
79765
+ headRefName: pr.headRefName,
79766
+ baseRefName: pr.baseRefName,
79767
+ mergeable: pr.mergeable,
79768
+ mergeStateStatus: pr.mergeStateStatus,
79769
+ isDraft: pr.isDraft,
79770
+ author: pr.author.login,
79771
+ createdAt: pr.createdAt,
79772
+ updatedAt: pr.updatedAt
79773
+ };
79774
+ } catch (error82) {
79775
+ throw new Error(`Failed to get PR #${prNumber}: ${error82}`);
79776
+ }
79777
+ }
79778
+ /**
79779
+ * Check if PR is auto-mergeable
79780
+ */
79781
+ async isPRMergeable(prNumber) {
79782
+ const pr = await this.getPR(prNumber);
79783
+ const isMergeable = pr.mergeable === "MERGEABLE" && pr.mergeStateStatus === "CLEAN" && pr.state === "OPEN" && !pr.isDraft;
79784
+ let reason;
79785
+ if (!isMergeable) {
79786
+ if (pr.isDraft) {
79787
+ reason = "PR is in draft state";
79788
+ } else if (pr.mergeable === "CONFLICTING") {
79789
+ reason = "PR has merge conflicts";
79790
+ } else if (pr.mergeStateStatus === "BLOCKED") {
79791
+ reason = "PR is blocked by required checks";
79792
+ } else if (pr.mergeStateStatus === "BEHIND") {
79793
+ reason = "Branch is behind base branch";
79794
+ } else if (pr.mergeStateStatus === "UNSTABLE") {
79795
+ reason = "Some checks are failing";
79796
+ } else if (pr.mergeable === "UNKNOWN") {
79797
+ reason = "Mergeability is still being calculated";
79798
+ } else {
79799
+ reason = `Status: ${pr.mergeStateStatus}`;
79800
+ }
79801
+ }
79802
+ return { mergeable: isMergeable, reason };
79803
+ }
79804
+ /**
79805
+ * Merge a PR
79806
+ */
79807
+ async mergePR(prNumber, options) {
79808
+ const {
79809
+ method = "squash",
79810
+ deleteBranch = true,
79811
+ auto = false,
79812
+ commitTitle,
79813
+ commitBody
79814
+ } = options || {};
79815
+ const args = ["pr", "merge", prNumber.toString()];
79816
+ if (auto) {
79817
+ args.push("--auto");
79818
+ }
79819
+ switch (method) {
79820
+ case "squash":
79821
+ args.push("--squash");
79822
+ break;
79823
+ case "rebase":
79824
+ args.push("--rebase");
79825
+ break;
79826
+ case "merge":
79827
+ args.push("--merge");
79828
+ break;
79829
+ }
79830
+ if (deleteBranch) {
79831
+ args.push("--delete-branch");
79832
+ }
79833
+ if (commitTitle) {
79834
+ args.push("--subject", `"${commitTitle}"`);
79835
+ }
79836
+ if (commitBody) {
79837
+ args.push("--body", `"${commitBody}"`);
79838
+ }
79839
+ try {
79840
+ const { stdout } = await this.execGH(args.join(" "));
79841
+ console.log(stdout);
79842
+ } catch (error82) {
79843
+ throw new Error(`Failed to merge PR #${prNumber}: ${error82}`);
79844
+ }
79845
+ }
79846
+ /**
79847
+ * Check if PR is mergeable and merge if ready
79848
+ */
79849
+ async checkAndMerge(prNumber, options) {
79850
+ const { mergeable, reason } = await this.isPRMergeable(prNumber);
79851
+ if (!mergeable) {
79852
+ console.log(`PR #${prNumber} is not mergeable: ${reason}`);
79853
+ return false;
79854
+ }
79855
+ await this.mergePR(prNumber, options);
79856
+ return true;
79857
+ }
79858
+ /**
79859
+ * Wait for PR to be mergeable and then merge
79860
+ */
79861
+ async waitAndMerge(prNumber, options) {
79862
+ const {
79863
+ maxWaitTime = 3e5,
79864
+ // 5 minutes
79865
+ checkInterval = 1e4,
79866
+ // 10 seconds
79867
+ ...mergeOptions
79868
+ } = options || {};
79869
+ const startTime = Date.now();
79870
+ while (Date.now() - startTime < maxWaitTime) {
79871
+ const { mergeable, reason } = await this.isPRMergeable(prNumber);
79872
+ if (mergeable) {
79873
+ await this.mergePR(prNumber, mergeOptions);
79874
+ return true;
79875
+ }
79876
+ if (reason?.includes("conflict")) {
79877
+ throw new Error(`PR #${prNumber} has merge conflicts`);
79878
+ }
79879
+ console.log(`Waiting for PR #${prNumber} to be mergeable... (${reason})`);
79880
+ await new Promise((resolve3) => setTimeout(resolve3, checkInterval));
79881
+ }
79882
+ throw new Error(
79883
+ `Timeout: PR #${prNumber} did not become mergeable within ${maxWaitTime}ms`
79884
+ );
79885
+ }
79886
+ /**
79887
+ * Add comment to PR
79888
+ */
79889
+ async addComment(prNumber, comment) {
79890
+ try {
79891
+ await this.execGH(`pr comment ${prNumber} --body "${comment}"`);
79892
+ } catch (error82) {
79893
+ throw new Error(`Failed to add comment to PR #${prNumber}: ${error82}`);
79894
+ }
79895
+ }
79896
+ /**
79897
+ * Update PR title and/or body
79898
+ */
79899
+ async updatePR(prNumber, updates) {
79900
+ const args = ["pr", "edit", prNumber.toString()];
79901
+ if (updates.title) {
79902
+ args.push("--title", `"${updates.title}"`);
79903
+ }
79904
+ if (updates.body) {
79905
+ args.push("--body", `"${updates.body}"`);
79906
+ }
79907
+ try {
79908
+ await this.execGH(args.join(" "));
79909
+ } catch (error82) {
79910
+ throw new Error(`Failed to update PR #${prNumber}: ${error82}`);
79911
+ }
79912
+ }
79913
+ /**
79914
+ * Close PR
79915
+ */
79916
+ async closePR(prNumber, comment) {
79917
+ try {
79918
+ if (comment) {
79919
+ await this.addComment(prNumber, comment);
79920
+ }
79921
+ await this.execGH(`pr close ${prNumber}`);
79922
+ } catch (error82) {
79923
+ throw new Error(`Failed to close PR #${prNumber}: ${error82}`);
79924
+ }
79925
+ }
79926
+ /**
79927
+ * Reopen PR
79928
+ */
79929
+ async reopenPR(prNumber) {
79930
+ try {
79931
+ await this.execGH(`pr reopen ${prNumber}`);
79932
+ } catch (error82) {
79933
+ throw new Error(`Failed to reopen PR #${prNumber}: ${error82}`);
79934
+ }
79935
+ }
79936
+ /**
79937
+ * Get PR checks status
79938
+ */
79939
+ async getPRChecks(prNumber) {
79940
+ try {
79941
+ const { stdout } = await this.execGH(
79942
+ `pr checks ${prNumber} --json name,state,conclusion`
79943
+ );
79944
+ return JSON.parse(stdout);
79945
+ } catch (error82) {
79946
+ throw new Error(`Failed to get PR checks for #${prNumber}: ${error82}`);
79947
+ }
79948
+ }
79949
+ /**
79950
+ * Get repo info
79951
+ */
79952
+ getRepoInfo() {
79953
+ return { owner: this.owner, repo: this.repo };
79954
+ }
79955
+ /**
79956
+ * Get branch name
79957
+ */
79958
+ getBranch() {
79959
+ return this.branch;
79960
+ }
79961
+ };
79962
+
79582
79963
  // src/proxy/server.ts
79583
79964
  var PORT = parseInt(process.env.STAKLINK_PORT || "15552") || 15552;
79584
79965
  var VSCODE_EXTENSION_URL = `http://localhost:${PORT + 1}`;
@@ -79898,7 +80279,9 @@ async function startProxyServer() {
79898
80279
  console.log(`===> POST /push`);
79899
80280
  const code = req.body;
79900
80281
  const repos = getReposWithChangedFiles(code);
80282
+ const createPR = req.query.pr === "true";
79901
80283
  const commits = [];
80284
+ const prs = {};
79902
80285
  for (const r of repos) {
79903
80286
  console.log(`=> PUSH to ${r.url}`);
79904
80287
  const repo = await NewRepo(r.url);
@@ -79909,8 +80292,41 @@ async function startProxyServer() {
79909
80292
  }
79910
80293
  const link = await repo.getLatestCommitLink();
79911
80294
  commits.push(link);
80295
+ if (createPR && code.git_credentials?.auth_data?.token) {
80296
+ try {
80297
+ console.log(`=> Creating PR for ${r.url}`);
80298
+ const repoName = getRepoNameFromUrl(r.url);
80299
+ const gh = new GitHubCLI(
80300
+ code.git_credentials.auth_data.token,
80301
+ r.branch_name
80302
+ );
80303
+ await gh.init();
80304
+ const existingPR = await gh.findPRByBranch();
80305
+ if (existingPR) {
80306
+ console.log(`=> PR already exists: ${existingPR.url}`);
80307
+ prs[repoName] = existingPR.url;
80308
+ } else {
80309
+ const pr = await gh.createPR({
80310
+ title: r.commit_name || `Changes from ${r.branch_name}`,
80311
+ body: `Automated PR from branch ${r.branch_name}`,
80312
+ base: r.base_branch || "main",
80313
+ draft: false
80314
+ });
80315
+ console.log(`=> PR created: ${pr.url}`);
80316
+ prs[repoName] = pr.url;
80317
+ }
80318
+ } catch (prError) {
80319
+ console.error(`=> Failed to create PR for ${r.url}:`, prError);
80320
+ const repoName = getRepoNameFromUrl(r.url);
80321
+ prs[repoName] = `Error: ${prError instanceof Error ? prError.message : String(prError)}`;
80322
+ }
80323
+ }
80324
+ }
80325
+ const response = { success: true, commits };
80326
+ if (createPR) {
80327
+ response.prs = prs;
79912
80328
  }
79913
- res.json({ success: true, commits });
80329
+ res.json(response);
79914
80330
  } catch (e) {
79915
80331
  fail(res, e);
79916
80332
  }
@@ -10905,7 +10905,7 @@ var glob = Object.assign(glob_, {
10905
10905
  glob.glob = glob;
10906
10906
 
10907
10907
  // src/proxy/version.ts
10908
- var VERSION = "0.3.5";
10908
+ var VERSION = "0.3.6";
10909
10909
 
10910
10910
  // src/cli.ts
10911
10911
  var STAKLINK_PROXY = "staklink-proxy";
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "staklink",
3
3
  "displayName": "staklink",
4
4
  "description": "staklink process manager",
5
- "version": "0.3.5",
5
+ "version": "0.3.6",
6
6
  "type": "module",
7
7
  "publisher": "stakwork",
8
8
  "engines": {