@aspruyt/xfg 3.1.0 → 3.1.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.
@@ -44,6 +44,17 @@ export declare class GraphQLCommitStrategy implements CommitStrategy {
44
44
  * Execute the createCommitOnBranch GraphQL mutation.
45
45
  */
46
46
  private executeGraphQLMutation;
47
+ /**
48
+ * Build a git command with optional token authentication override.
49
+ * When a token is provided, uses -c url.insteadOf to override the global
50
+ * git config and authenticate with the provided token instead.
51
+ *
52
+ * This is critical for GitHub App authentication where the global git config
53
+ * may have a PAT token embedded, but we need to use the GitHub App installation token.
54
+ *
55
+ * Applies to all remote operations: push, fetch, ls-remote, etc.
56
+ */
57
+ private buildAuthenticatedGitCommand;
47
58
  /**
48
59
  * Ensure the branch exists on the remote and matches local HEAD.
49
60
  * createCommitOnBranch requires the branch to already exist.
@@ -72,7 +72,7 @@ export class GraphQLCommitStrategy {
72
72
  // Ensure the branch exists on remote and is up-to-date with local HEAD
73
73
  // createCommitOnBranch requires the branch to already exist
74
74
  // For PR branches (force=true), we force-update to ensure fresh start from main
75
- await this.ensureBranchExistsOnRemote(branchName, workDir, options.force);
75
+ await this.ensureBranchExistsOnRemote(branchName, workDir, options.force, token, githubInfo.host);
76
76
  // Retry loop for expectedHeadOid mismatch
77
77
  let lastError = null;
78
78
  for (let attempt = 0; attempt <= retries; attempt++) {
@@ -80,7 +80,7 @@ export class GraphQLCommitStrategy {
80
80
  // Fetch from remote to ensure we have the latest HEAD
81
81
  // This is critical for expectedHeadOid to match
82
82
  const safeBranch = escapeShellArg(branchName);
83
- await this.executor.exec(`git fetch origin ${safeBranch}:refs/remotes/origin/${safeBranch}`, workDir);
83
+ await this.executor.exec(this.buildAuthenticatedGitCommand(`fetch origin ${safeBranch}:refs/remotes/origin/${safeBranch}`, token, githubInfo.host), workDir);
84
84
  // Get the remote HEAD SHA for this branch (not local HEAD)
85
85
  const headSha = await this.executor.exec(`git rev-parse origin/${safeBranch}`, workDir);
86
86
  // Build and execute the GraphQL mutation
@@ -171,6 +171,24 @@ export class GraphQLCommitStrategy {
171
171
  pushed: true, // GraphQL commits are pushed directly
172
172
  };
173
173
  }
174
+ /**
175
+ * Build a git command with optional token authentication override.
176
+ * When a token is provided, uses -c url.insteadOf to override the global
177
+ * git config and authenticate with the provided token instead.
178
+ *
179
+ * This is critical for GitHub App authentication where the global git config
180
+ * may have a PAT token embedded, but we need to use the GitHub App installation token.
181
+ *
182
+ * Applies to all remote operations: push, fetch, ls-remote, etc.
183
+ */
184
+ buildAuthenticatedGitCommand(gitArgs, token, host = "github.com") {
185
+ if (!token) {
186
+ return `git ${gitArgs}`;
187
+ }
188
+ // Override URL rewrite to use the provided token
189
+ const urlOverride = `url."https://x-access-token:${token}@${host}/".insteadOf="https://${host}/"`;
190
+ return `git -c ${escapeShellArg(urlOverride)} ${gitArgs}`;
191
+ }
174
192
  /**
175
193
  * Ensure the branch exists on the remote and matches local HEAD.
176
194
  * createCommitOnBranch requires the branch to already exist.
@@ -180,23 +198,23 @@ export class GraphQLCommitStrategy {
180
198
  *
181
199
  * For direct mode (force=false): just ensure branch exists.
182
200
  */
183
- async ensureBranchExistsOnRemote(branchName, workDir, force) {
201
+ async ensureBranchExistsOnRemote(branchName, workDir, force, token, host) {
184
202
  // Branch name was validated in commit(), safe for shell use
185
203
  try {
186
204
  // Check if the branch exists on remote
187
- await this.executor.exec(`git ls-remote --exit-code --heads origin ${escapeShellArg(branchName)}`, workDir);
205
+ await this.executor.exec(this.buildAuthenticatedGitCommand(`ls-remote --exit-code --heads origin ${escapeShellArg(branchName)}`, token, host), workDir);
188
206
  // Branch exists - for PR branches, delete and recreate to ensure fresh from main
189
207
  if (force) {
190
- await this.executor.exec(`git push origin --delete ${escapeShellArg(branchName)}`, workDir);
208
+ await this.executor.exec(this.buildAuthenticatedGitCommand(`push origin --delete ${escapeShellArg(branchName)}`, token, host), workDir);
191
209
  // Now push fresh branch from local HEAD
192
- await this.executor.exec(`git push -u origin HEAD:${escapeShellArg(branchName)}`, workDir);
210
+ await this.executor.exec(this.buildAuthenticatedGitCommand(`push -u origin HEAD:${escapeShellArg(branchName)}`, token, host), workDir);
193
211
  }
194
212
  // For direct mode (force=false), leave existing branch as-is
195
213
  }
196
214
  catch {
197
215
  // Branch doesn't exist on remote, push it
198
216
  // This pushes the current local branch to create it on remote
199
- await this.executor.exec(`git push -u origin HEAD:${escapeShellArg(branchName)}`, workDir);
217
+ await this.executor.exec(this.buildAuthenticatedGitCommand(`push -u origin HEAD:${escapeShellArg(branchName)}`, token, host), workDir);
200
218
  }
201
219
  }
202
220
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aspruyt/xfg",
3
- "version": "3.1.0",
3
+ "version": "3.1.1",
4
4
  "description": "CLI tool for repository-as-code",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",