@ghl-ai/aw 0.1.36-beta.33 → 0.1.36-beta.35
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/commands/push.mjs +29 -22
- package/git.mjs +12 -7
- package/package.json +1 -1
package/commands/push.mjs
CHANGED
|
@@ -2,8 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import { existsSync, statSync, readFileSync, appendFileSync } from 'node:fs';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
-
import {
|
|
5
|
+
import { exec as execCb, execFile as execFileCb } from 'node:child_process';
|
|
6
|
+
import { promisify } from 'node:util';
|
|
6
7
|
import { homedir } from 'node:os';
|
|
8
|
+
|
|
9
|
+
const exec = promisify(execCb);
|
|
10
|
+
const execFile = promisify(execFileCb);
|
|
7
11
|
import * as fmt from '../fmt.mjs';
|
|
8
12
|
import { chalk } from '../fmt.mjs';
|
|
9
13
|
import { REGISTRY_REPO, REGISTRY_URL, REGISTRY_BASE_BRANCH, REGISTRY_DIR } from '../constants.mjs';
|
|
@@ -258,12 +262,14 @@ function parseRegistryPath(relPath) {
|
|
|
258
262
|
|
|
259
263
|
// ── CODEOWNERS helpers ────────────────────────────────────────────────
|
|
260
264
|
|
|
261
|
-
function getGitHubUser() {
|
|
265
|
+
async function getGitHubUser() {
|
|
262
266
|
try {
|
|
263
|
-
|
|
267
|
+
const { stdout } = await exec('gh api user --jq .login');
|
|
268
|
+
return stdout.trim();
|
|
264
269
|
} catch {
|
|
265
270
|
try {
|
|
266
|
-
|
|
271
|
+
const { stdout } = await exec('git config user.name');
|
|
272
|
+
return stdout.trim();
|
|
267
273
|
} catch {
|
|
268
274
|
return null;
|
|
269
275
|
}
|
|
@@ -279,25 +285,26 @@ function isNewNamespaceInCodeowners(codeownersPath, namespace) {
|
|
|
279
285
|
// ── Create or update PR via gh ────────────────────────────────────────
|
|
280
286
|
|
|
281
287
|
// Returns { url, updated } — updated=true if an existing PR was refreshed.
|
|
282
|
-
function createOrUpdatePR(awHome, branch, prTitle, prBody) {
|
|
288
|
+
async function createOrUpdatePR(awHome, branch, prTitle, prBody) {
|
|
283
289
|
// Check for existing open PR on this branch
|
|
284
290
|
try {
|
|
285
|
-
const
|
|
291
|
+
const { stdout } = await execFile('gh', [
|
|
286
292
|
'pr', 'view', branch, '--json', 'url', '--jq', '.url',
|
|
287
|
-
], { cwd: awHome, encoding: 'utf8'
|
|
293
|
+
], { cwd: awHome, encoding: 'utf8' });
|
|
294
|
+
const url = stdout.trim();
|
|
288
295
|
if (url) return { url, updated: true };
|
|
289
296
|
} catch { /* no existing PR */ }
|
|
290
297
|
|
|
291
298
|
// Create new PR
|
|
292
299
|
try {
|
|
293
|
-
const
|
|
300
|
+
const { stdout } = await execFile('gh', [
|
|
294
301
|
'pr', 'create',
|
|
295
302
|
'--base', REGISTRY_BASE_BRANCH,
|
|
296
303
|
'--head', branch,
|
|
297
304
|
'--title', prTitle,
|
|
298
305
|
'--body', prBody,
|
|
299
|
-
], { cwd: awHome, encoding: 'utf8' })
|
|
300
|
-
return { url, updated: false };
|
|
306
|
+
], { cwd: awHome, encoding: 'utf8' });
|
|
307
|
+
return { url: stdout.trim(), updated: false };
|
|
301
308
|
} catch {
|
|
302
309
|
return {
|
|
303
310
|
url: `https://github.com/${REGISTRY_REPO}/compare/${REGISTRY_BASE_BRANCH}...${branch}?expand=1`,
|
|
@@ -312,7 +319,7 @@ function createOrUpdatePR(awHome, branch, prTitle, prBody) {
|
|
|
312
319
|
// - Always creates a new branch from current state, commits, pushes, stays there.
|
|
313
320
|
// - Every aw push = one new branch + one new PR. No force-push, no reuse.
|
|
314
321
|
// Global flow (worktreeFlow=false): same but returns to main after push.
|
|
315
|
-
function doPush(files, awHome, dryRun, worktreeFlow = false, preStaged = false) {
|
|
322
|
+
async function doPush(files, awHome, dryRun, worktreeFlow = false, preStaged = false) {
|
|
316
323
|
const added = files.filter(f => !f.deleted);
|
|
317
324
|
const deleted = files.filter(f => f.deleted);
|
|
318
325
|
|
|
@@ -349,7 +356,7 @@ function doPush(files, awHome, dryRun, worktreeFlow = false, preStaged = false)
|
|
|
349
356
|
const topNamespaces = [...new Set(files.map(f => f.namespace.split('/')[0]))];
|
|
350
357
|
const codeownersPath = join(awHome, 'CODEOWNERS');
|
|
351
358
|
const newNamespaces = [];
|
|
352
|
-
const ghUser = getGitHubUser();
|
|
359
|
+
const ghUser = await getGitHubUser();
|
|
353
360
|
for (const ns of topNamespaces) {
|
|
354
361
|
if (ghUser && isNewNamespaceInCodeowners(codeownersPath, ns)) {
|
|
355
362
|
newNamespaces.push(ns);
|
|
@@ -373,7 +380,7 @@ function doPush(files, awHome, dryRun, worktreeFlow = false, preStaged = false)
|
|
|
373
380
|
let finalBranch;
|
|
374
381
|
try {
|
|
375
382
|
if (worktreeFlow) {
|
|
376
|
-
finalBranch = createPushBranch(awHome, generateBranchName(files), pathsToStage, commitMsg, preStaged);
|
|
383
|
+
finalBranch = await createPushBranch(awHome, generateBranchName(files), pathsToStage, commitMsg, preStaged);
|
|
377
384
|
} else {
|
|
378
385
|
if (!preStaged) {
|
|
379
386
|
try { checkoutMain(awHome); } catch (e) {
|
|
@@ -382,7 +389,7 @@ function doPush(files, awHome, dryRun, worktreeFlow = false, preStaged = false)
|
|
|
382
389
|
return;
|
|
383
390
|
}
|
|
384
391
|
}
|
|
385
|
-
finalBranch = createPushBranch(awHome, generateBranchName(files), pathsToStage, commitMsg, preStaged);
|
|
392
|
+
finalBranch = await createPushBranch(awHome, generateBranchName(files), pathsToStage, commitMsg, preStaged);
|
|
386
393
|
try { checkoutMain(awHome); } catch { /* best effort */ }
|
|
387
394
|
}
|
|
388
395
|
const branchLabel = files.length === 0
|
|
@@ -398,7 +405,7 @@ function doPush(files, awHome, dryRun, worktreeFlow = false, preStaged = false)
|
|
|
398
405
|
// ── Phase 3: Open PR ───────────────────────────────────────────────
|
|
399
406
|
const s2 = fmt.spinner();
|
|
400
407
|
s2.start('Opening pull request...');
|
|
401
|
-
const { url: prUrl, updated: prUpdated } = createOrUpdatePR(awHome, finalBranch, prTitle, prBody);
|
|
408
|
+
const { url: prUrl, updated: prUpdated } = await createOrUpdatePR(awHome, finalBranch, prTitle, prBody);
|
|
402
409
|
s2.stop(prUpdated ? `PR updated — ${chalk.cyan(prUrl)}` : `PR opened — ${chalk.cyan(prUrl)}`);
|
|
403
410
|
|
|
404
411
|
if (newNamespaces.length > 0) {
|
|
@@ -412,7 +419,7 @@ function doPush(files, awHome, dryRun, worktreeFlow = false, preStaged = false)
|
|
|
412
419
|
|
|
413
420
|
// ── Main command ─────────────────────────────────────────────────────
|
|
414
421
|
|
|
415
|
-
export function pushCommand(args) {
|
|
422
|
+
export async function pushCommand(args) {
|
|
416
423
|
const input = args._positional?.[0];
|
|
417
424
|
const dryRun = args['--dry-run'] === true;
|
|
418
425
|
const cwd = process.cwd();
|
|
@@ -457,7 +464,7 @@ export function pushCommand(args) {
|
|
|
457
464
|
};
|
|
458
465
|
});
|
|
459
466
|
fmt.logInfo(`${chalk.dim('mode:')} staged (${files.length} file${files.length > 1 ? 's' : ''})`);
|
|
460
|
-
doPush(files, awHome, dryRun, worktreeFlow, true);
|
|
467
|
+
await doPush(files, awHome, dryRun, worktreeFlow, true);
|
|
461
468
|
return;
|
|
462
469
|
}
|
|
463
470
|
|
|
@@ -471,7 +478,7 @@ export function pushCommand(args) {
|
|
|
471
478
|
|
|
472
479
|
if (allEntries.length === 0 && worktreeFlow && commitsAheadOfMain(awHome) > 0) {
|
|
473
480
|
fmt.logInfo(`${chalk.dim('mode:')} auto (no new changes — branching current state)`);
|
|
474
|
-
doPush([], awHome, dryRun, worktreeFlow, false);
|
|
481
|
+
await doPush([], awHome, dryRun, worktreeFlow, false);
|
|
475
482
|
return;
|
|
476
483
|
}
|
|
477
484
|
|
|
@@ -498,7 +505,7 @@ export function pushCommand(args) {
|
|
|
498
505
|
// In worktree flow, still push if there are commits ahead of main not yet in a PR
|
|
499
506
|
if (worktreeFlow && commitsAheadOfMain(awHome) > 0) {
|
|
500
507
|
fmt.logInfo(`${chalk.dim('mode:')} auto (no new changes — branching current state)`);
|
|
501
|
-
doPush([], awHome, dryRun, worktreeFlow, false);
|
|
508
|
+
await doPush([], awHome, dryRun, worktreeFlow, false);
|
|
502
509
|
return;
|
|
503
510
|
}
|
|
504
511
|
fmt.cancel('Nothing to push — no staged or modified files.\n\n Stage files in your IDE or use `aw status` to see changes.');
|
|
@@ -506,7 +513,7 @@ export function pushCommand(args) {
|
|
|
506
513
|
}
|
|
507
514
|
|
|
508
515
|
fmt.logInfo(`${chalk.dim('mode:')} auto (${files.length} file${files.length > 1 ? 's' : ''} — stage specific files to push a subset)`);
|
|
509
|
-
doPush(files, awHome, dryRun, worktreeFlow, false);
|
|
516
|
+
await doPush(files, awHome, dryRun, worktreeFlow, false);
|
|
510
517
|
return;
|
|
511
518
|
}
|
|
512
519
|
|
|
@@ -546,7 +553,7 @@ export function pushCommand(args) {
|
|
|
546
553
|
fmt.cancel(`Nothing to push in ${chalk.cyan(input)} — no agents, skills, commands, or evals found.`);
|
|
547
554
|
return;
|
|
548
555
|
}
|
|
549
|
-
doPush(files, awHome, dryRun, worktreeFlow);
|
|
556
|
+
await doPush(files, awHome, dryRun, worktreeFlow);
|
|
550
557
|
return;
|
|
551
558
|
}
|
|
552
559
|
|
|
@@ -580,7 +587,7 @@ export function pushCommand(args) {
|
|
|
580
587
|
? `${REGISTRY_DIR}/${namespacePath}/${parentDir}/${slug}`
|
|
581
588
|
: `${REGISTRY_DIR}/${namespacePath}/${parentDir}/${slug}.md`;
|
|
582
589
|
|
|
583
|
-
doPush([{
|
|
590
|
+
await doPush([{
|
|
584
591
|
absPath,
|
|
585
592
|
registryTarget,
|
|
586
593
|
type: parentDir,
|
package/git.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// git.mjs — Git helpers: sparse checkout (temp) + persistent clone operations.
|
|
2
2
|
|
|
3
3
|
import { execSync, exec as execCb } from 'node:child_process';
|
|
4
|
-
import { mkdtempSync, existsSync, lstatSync, rmSync } from 'node:fs';
|
|
4
|
+
import { mkdtempSync, existsSync, lstatSync, rmSync, readFileSync } from 'node:fs';
|
|
5
5
|
import { join, basename, dirname } from 'node:path';
|
|
6
6
|
import { homedir, tmpdir } from 'node:os';
|
|
7
7
|
import { promisify } from 'node:util';
|
|
@@ -349,9 +349,9 @@ export function updatePushBranch(awHome, pushBranchName) {
|
|
|
349
349
|
*
|
|
350
350
|
* Branch stays on disk for iteration. Returns the branch name.
|
|
351
351
|
*/
|
|
352
|
-
export function createPushBranch(awHome, branchName, files, commitMsg, preStaged = false) {
|
|
352
|
+
export async function createPushBranch(awHome, branchName, files, commitMsg, preStaged = false) {
|
|
353
353
|
try {
|
|
354
|
-
|
|
354
|
+
await exec(`git -C "${awHome}" checkout -b "${branchName}"`);
|
|
355
355
|
} catch (e) {
|
|
356
356
|
throw new Error(`Failed to create branch ${branchName}: ${e.message}`);
|
|
357
357
|
}
|
|
@@ -360,21 +360,21 @@ export function createPushBranch(awHome, branchName, files, commitMsg, preStaged
|
|
|
360
360
|
if (!preStaged) {
|
|
361
361
|
try {
|
|
362
362
|
const quotedFiles = files.map(f => `"${f}"`).join(' ');
|
|
363
|
-
|
|
363
|
+
await exec(`git -C "${awHome}" add ${quotedFiles}`);
|
|
364
364
|
} catch (e) {
|
|
365
365
|
throw new Error(`Failed to stage files: ${e.message}`);
|
|
366
366
|
}
|
|
367
367
|
}
|
|
368
368
|
|
|
369
369
|
try {
|
|
370
|
-
|
|
370
|
+
await exec(`git -C "${awHome}" commit -m "${commitMsg.replace(/"/g, '\\"')}"`);
|
|
371
371
|
} catch (e) {
|
|
372
372
|
throw new Error(`Failed to commit: ${e.message}`);
|
|
373
373
|
}
|
|
374
374
|
}
|
|
375
375
|
|
|
376
376
|
try {
|
|
377
|
-
|
|
377
|
+
await exec(`git -C "${awHome}" push -u origin "${branchName}"`);
|
|
378
378
|
} catch (e) {
|
|
379
379
|
throw new Error(`Failed to push branch: ${e.message}`);
|
|
380
380
|
}
|
|
@@ -496,7 +496,12 @@ export function findNearestWorktree(startDir, stopDir) {
|
|
|
496
496
|
export function isWorktree(dir) {
|
|
497
497
|
const gitPath = join(dir, '.git');
|
|
498
498
|
try {
|
|
499
|
-
|
|
499
|
+
if (!lstatSync(gitPath).isFile()) return false;
|
|
500
|
+
// Verify the gitdir it points to actually exists (guards against stale worktrees)
|
|
501
|
+
const content = readFileSync(gitPath, 'utf8').trim();
|
|
502
|
+
const match = content.match(/^gitdir:\s*(.+)$/);
|
|
503
|
+
if (!match) return false;
|
|
504
|
+
return existsSync(match[1]);
|
|
500
505
|
} catch {
|
|
501
506
|
return false;
|
|
502
507
|
}
|