@haystackeditor/cli 0.13.2 → 0.13.3
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/commands/submit.js +1 -5
- package/dist/index.js +1 -1
- package/dist/utils/git.d.ts +5 -6
- package/dist/utils/git.js +35 -25
- package/package.json +1 -1
package/dist/commands/submit.js
CHANGED
|
@@ -367,11 +367,7 @@ export async function submitCommand(options) {
|
|
|
367
367
|
process.exit(1);
|
|
368
368
|
}
|
|
369
369
|
try {
|
|
370
|
-
pushBranch('origin', currentBranch, {
|
|
371
|
-
token: githubToken,
|
|
372
|
-
owner: remoteInfo.owner,
|
|
373
|
-
repo: remoteInfo.repo,
|
|
374
|
-
});
|
|
370
|
+
pushBranch('origin', currentBranch, { token: githubToken });
|
|
375
371
|
console.log(chalk.green('✓ Branch pushed'));
|
|
376
372
|
}
|
|
377
373
|
catch (err) {
|
package/dist/index.js
CHANGED
|
@@ -38,7 +38,7 @@ const program = new Command();
|
|
|
38
38
|
program
|
|
39
39
|
.name('haystack')
|
|
40
40
|
.description('Haystack CLI — automated PR review, triage, and merge queue')
|
|
41
|
-
.version('0.13.
|
|
41
|
+
.version('0.13.3');
|
|
42
42
|
program
|
|
43
43
|
.command('init')
|
|
44
44
|
.description('Create .haystack.json configuration')
|
package/dist/utils/git.d.ts
CHANGED
|
@@ -54,17 +54,16 @@ export declare function remoteBranchExists(remoteName: string, branchName: strin
|
|
|
54
54
|
export declare function checkoutBranch(branchName: string): void;
|
|
55
55
|
export interface PushAuth {
|
|
56
56
|
token: string;
|
|
57
|
-
owner: string;
|
|
58
|
-
repo: string;
|
|
59
57
|
}
|
|
60
58
|
/**
|
|
61
59
|
* Push current branch to remote with upstream tracking.
|
|
62
60
|
*
|
|
63
61
|
* When `auth` is provided, the push is authenticated with the given GitHub
|
|
64
|
-
* token via GIT_ASKPASS against
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
62
|
+
* token via GIT_ASKPASS against an explicit HTTPS URL, independent of the
|
|
63
|
+
* user's ambient git credential helper and independent of whatever
|
|
64
|
+
* `remote.<name>.url` / `pushurl` they have configured. Remote-tracking
|
|
65
|
+
* refs are then seeded locally so upstream tracking (`-u`-style) works on
|
|
66
|
+
* first push. `.git/config` is never touched.
|
|
68
67
|
*
|
|
69
68
|
* Without `auth`, falls back to `git push -u <remote>` using ambient creds.
|
|
70
69
|
*/
|
package/dist/utils/git.js
CHANGED
|
@@ -166,16 +166,17 @@ export function checkoutBranch(branchName) {
|
|
|
166
166
|
* Push current branch to remote with upstream tracking.
|
|
167
167
|
*
|
|
168
168
|
* When `auth` is provided, the push is authenticated with the given GitHub
|
|
169
|
-
* token via GIT_ASKPASS against
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
169
|
+
* token via GIT_ASKPASS against an explicit HTTPS URL, independent of the
|
|
170
|
+
* user's ambient git credential helper and independent of whatever
|
|
171
|
+
* `remote.<name>.url` / `pushurl` they have configured. Remote-tracking
|
|
172
|
+
* refs are then seeded locally so upstream tracking (`-u`-style) works on
|
|
173
|
+
* first push. `.git/config` is never touched.
|
|
173
174
|
*
|
|
174
175
|
* Without `auth`, falls back to `git push -u <remote>` using ambient creds.
|
|
175
176
|
*/
|
|
176
177
|
export function pushBranch(remoteName, branchName, auth) {
|
|
177
178
|
if (auth) {
|
|
178
|
-
pushBranchWithToken(remoteName, branchName, auth);
|
|
179
|
+
pushBranchWithToken(remoteName, branchName, auth.token);
|
|
179
180
|
return;
|
|
180
181
|
}
|
|
181
182
|
try {
|
|
@@ -188,28 +189,37 @@ export function pushBranch(remoteName, branchName, auth) {
|
|
|
188
189
|
throw translatePushError(err);
|
|
189
190
|
}
|
|
190
191
|
}
|
|
191
|
-
function pushBranchWithToken(remoteName, branchName,
|
|
192
|
+
function pushBranchWithToken(remoteName, branchName, token) {
|
|
193
|
+
// Derive owner/repo from the remote itself so the push URL always matches
|
|
194
|
+
// the tracking ref we'll seed below. No way for caller and remote identity
|
|
195
|
+
// to silently disagree.
|
|
196
|
+
const { owner, repo } = parseRemoteUrl(remoteName);
|
|
197
|
+
const pushUrl = `https://github.com/${owner}/${repo}.git`;
|
|
198
|
+
const repoSlug = `${owner}/${repo}`;
|
|
192
199
|
const askpassPath = path.join(os.tmpdir(), `haystack-askpass-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.sh`);
|
|
193
200
|
// Script reads token from env at call time — keeps it out of the script contents and argv.
|
|
194
201
|
const script = `#!/bin/sh\ncase "$1" in\n Username*) printf '%s' "x-access-token" ;;\n *) printf '%s' "$HAYSTACK_GIT_TOKEN" ;;\nesac\n`;
|
|
195
202
|
fs.writeFileSync(askpassPath, script, { mode: 0o700 });
|
|
203
|
+
const authedEnv = {
|
|
204
|
+
...process.env,
|
|
205
|
+
GIT_ASKPASS: askpassPath,
|
|
206
|
+
GIT_TERMINAL_PROMPT: '0',
|
|
207
|
+
HAYSTACK_GIT_TOKEN: token,
|
|
208
|
+
};
|
|
196
209
|
try {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
});
|
|
210
|
+
// Push to an explicit HTTPS URL with GIT_ASKPASS, bypassing ambient
|
|
211
|
+
// credential helpers entirely. Using a raw URL (not the named remote)
|
|
212
|
+
// means the user's configured url/pushurl/credential-helper settings
|
|
213
|
+
// are untouched — sidesteps precedence and multi-value edge cases
|
|
214
|
+
// around `remote.<name>.url` vs `remote.<name>.pushurl`.
|
|
215
|
+
execSync(`git -c credential.helper= push "${pushUrl}" "HEAD:refs/heads/${branchName}"`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], env: authedEnv });
|
|
216
|
+
// Pushing to a raw URL doesn't update refs/remotes/<name>/<branch>. Seed
|
|
217
|
+
// it from the server's authoritative state with a targeted fetch — this
|
|
218
|
+
// reflects any server-side rewrite or hook side effects, not a fabricated
|
|
219
|
+
// pre-push SHA. Then set upstream tracking to the named remote.
|
|
208
220
|
try {
|
|
209
|
-
execSync(`git
|
|
210
|
-
|
|
211
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
212
|
-
});
|
|
221
|
+
execSync(`git -c credential.helper= fetch "${pushUrl}" "refs/heads/${branchName}:refs/remotes/${remoteName}/${branchName}"`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], env: authedEnv });
|
|
222
|
+
execSync(`git branch --set-upstream-to=${remoteName}/${branchName} "${branchName}"`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
213
223
|
}
|
|
214
224
|
catch (err) {
|
|
215
225
|
// Non-fatal: push succeeded; upstream tracking is a convenience.
|
|
@@ -218,7 +228,7 @@ function pushBranchWithToken(remoteName, branchName, auth) {
|
|
|
218
228
|
}
|
|
219
229
|
}
|
|
220
230
|
catch (err) {
|
|
221
|
-
throw translatePushError(err,
|
|
231
|
+
throw translatePushError(err, repoSlug);
|
|
222
232
|
}
|
|
223
233
|
finally {
|
|
224
234
|
try {
|
|
@@ -231,11 +241,11 @@ function pushBranchWithToken(remoteName, branchName, auth) {
|
|
|
231
241
|
}
|
|
232
242
|
}
|
|
233
243
|
}
|
|
234
|
-
function translatePushError(err,
|
|
244
|
+
function translatePushError(err, repoSlug) {
|
|
235
245
|
const message = err instanceof Error ? err.message : String(err);
|
|
236
246
|
if (message.includes('permission denied') || message.includes('403')) {
|
|
237
|
-
if (
|
|
238
|
-
return new Error(`Permission denied pushing to ${
|
|
247
|
+
if (repoSlug) {
|
|
248
|
+
return new Error(`Permission denied pushing to ${repoSlug}. The Haystack account token lacks write access to this repo — run \`haystack auth list\` and pick an account that's a collaborator.`);
|
|
239
249
|
}
|
|
240
250
|
return new Error('Permission denied. Check your GitHub credentials.');
|
|
241
251
|
}
|