@ghl-ai/aw 0.1.36-beta.31 → 0.1.36-beta.33
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/pull.mjs +3 -3
- package/commands/push.mjs +31 -25
- package/commands/status.mjs +4 -4
- package/git.mjs +21 -3
- package/package.json +1 -1
package/commands/pull.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { execSync } from 'node:child_process';
|
|
|
7
7
|
import * as config from '../config.mjs';
|
|
8
8
|
import * as fmt from '../fmt.mjs';
|
|
9
9
|
import { chalk } from '../fmt.mjs';
|
|
10
|
-
import { fetchAndMerge, addToSparseCheckout, isValidClone,
|
|
10
|
+
import { fetchAndMerge, addToSparseCheckout, isValidClone, findNearestWorktree, rebaseOntoOriginMain } from '../git.mjs';
|
|
11
11
|
import { REGISTRY_DIR, REGISTRY_REPO, REGISTRY_URL, DOCS_SOURCE_DIR } from '../constants.mjs';
|
|
12
12
|
import { linkWorkspace } from '../link.mjs';
|
|
13
13
|
import { generateCommands, copyInstructions } from '../integrate.mjs';
|
|
@@ -76,8 +76,8 @@ export async function pullCommand(args) {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
// Always rebase project worktree's current branch onto origin/main
|
|
79
|
-
const localAw =
|
|
80
|
-
if (
|
|
79
|
+
const localAw = cwd !== HOME ? findNearestWorktree(cwd, HOME) : null;
|
|
80
|
+
if (localAw) {
|
|
81
81
|
const rebaseSpinner = log.spinner();
|
|
82
82
|
rebaseSpinner.start('Rebasing local branch onto latest...');
|
|
83
83
|
try {
|
package/commands/push.mjs
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
createPushBranch,
|
|
16
16
|
checkoutMain,
|
|
17
17
|
isValidClone,
|
|
18
|
-
|
|
18
|
+
findNearestWorktree,
|
|
19
19
|
getLocalRegistryDir,
|
|
20
20
|
commitsAheadOfMain,
|
|
21
21
|
logAheadOfMain,
|
|
@@ -320,24 +320,32 @@ function doPush(files, awHome, dryRun, worktreeFlow = false, preStaged = false)
|
|
|
320
320
|
const deletedParts = Object.entries(groupBy(deleted, 'type')).map(([t, items]) => `${items.length} ${singular(t, items.length)} removed`);
|
|
321
321
|
const countParts = [...addedParts, ...deletedParts];
|
|
322
322
|
|
|
323
|
-
|
|
324
|
-
fmt.logInfo('Branching current state (no new changes)');
|
|
325
|
-
} else {
|
|
326
|
-
fmt.logInfo(`${chalk.bold(files.length)} file${files.length > 1 ? 's' : ''} to push (${countParts.join(', ')})`);
|
|
327
|
-
}
|
|
328
|
-
|
|
323
|
+
// ── Dry-run: just list files and exit ──────────────────────────────
|
|
329
324
|
if (dryRun) {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
fmt.
|
|
325
|
+
if (files.length === 0) {
|
|
326
|
+
fmt.logInfo('No new changes — would branch current state');
|
|
327
|
+
} else {
|
|
328
|
+
fmt.logInfo(`${chalk.bold(files.length)} file${files.length > 1 ? 's' : ''} to push (${countParts.join(', ')})`);
|
|
329
|
+
for (const f of files) {
|
|
330
|
+
const ns = chalk.dim(` [${f.namespace}]`);
|
|
331
|
+
const label = f.deleted ? chalk.red('DELETE') : chalk.yellow(f.type);
|
|
332
|
+
fmt.logMessage(` ${label}/${f.slug}${ns}`);
|
|
333
|
+
}
|
|
334
334
|
}
|
|
335
335
|
fmt.logWarn('No changes made (--dry-run)');
|
|
336
336
|
fmt.outro(chalk.dim('Remove --dry-run to push'));
|
|
337
337
|
return;
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
-
//
|
|
340
|
+
// ── Phase 1: Prepare commit ────────────────────────────────────────
|
|
341
|
+
const prepLabel = files.length === 0
|
|
342
|
+
? 'Creating upload branch from HEAD...'
|
|
343
|
+
: `Preparing ${countParts.join(', ')}...`;
|
|
344
|
+
|
|
345
|
+
const s = fmt.spinner();
|
|
346
|
+
s.start(prepLabel);
|
|
347
|
+
|
|
348
|
+
// CODEOWNERS for new namespaces (runs inside spinner so no silent gap)
|
|
341
349
|
const topNamespaces = [...new Set(files.map(f => f.namespace.split('/')[0]))];
|
|
342
350
|
const codeownersPath = join(awHome, 'CODEOWNERS');
|
|
343
351
|
const newNamespaces = [];
|
|
@@ -359,19 +367,14 @@ function doPush(files, awHome, dryRun, worktreeFlow = false, preStaged = false)
|
|
|
359
367
|
const prTitle = generatePrTitle(files, awHome);
|
|
360
368
|
const prBody = generatePrBody(files, newNamespaces, awHome);
|
|
361
369
|
|
|
362
|
-
|
|
363
|
-
s.
|
|
370
|
+
// ── Phase 2: Commit + push branch ──────────────────────────────────
|
|
371
|
+
s.message('Creating branch and pushing...');
|
|
364
372
|
|
|
365
373
|
let finalBranch;
|
|
366
374
|
try {
|
|
367
375
|
if (worktreeFlow) {
|
|
368
|
-
// ── Worktree flow ─────────────────────────────────────────────────
|
|
369
|
-
// Always create a new branch from current state, commit, push, stay there.
|
|
370
|
-
// Every push = one new branch + one new PR. No force-push, no branch reuse.
|
|
371
376
|
finalBranch = createPushBranch(awHome, generateBranchName(files), pathsToStage, commitMsg, preStaged);
|
|
372
377
|
} else {
|
|
373
|
-
// ── Global clone flow ─────────────────────────────────────────────
|
|
374
|
-
// Checkout main → create branch → commit → push → return to main.
|
|
375
378
|
if (!preStaged) {
|
|
376
379
|
try { checkoutMain(awHome); } catch (e) {
|
|
377
380
|
s.stop(chalk.red('Push failed'));
|
|
@@ -382,17 +385,21 @@ function doPush(files, awHome, dryRun, worktreeFlow = false, preStaged = false)
|
|
|
382
385
|
finalBranch = createPushBranch(awHome, generateBranchName(files), pathsToStage, commitMsg, preStaged);
|
|
383
386
|
try { checkoutMain(awHome); } catch { /* best effort */ }
|
|
384
387
|
}
|
|
385
|
-
|
|
388
|
+
const branchLabel = files.length === 0
|
|
389
|
+
? 'Branch created'
|
|
390
|
+
: `Pushed ${countParts.join(', ')}`;
|
|
391
|
+
s.stop(branchLabel);
|
|
386
392
|
} catch (e) {
|
|
387
393
|
s.stop(chalk.red('Push failed'));
|
|
388
394
|
fmt.cancel(`Push failed: ${e.message}`);
|
|
389
395
|
return;
|
|
390
396
|
}
|
|
391
397
|
|
|
398
|
+
// ── Phase 3: Open PR ───────────────────────────────────────────────
|
|
392
399
|
const s2 = fmt.spinner();
|
|
393
|
-
s2.start('
|
|
400
|
+
s2.start('Opening pull request...');
|
|
394
401
|
const { url: prUrl, updated: prUpdated } = createOrUpdatePR(awHome, finalBranch, prTitle, prBody);
|
|
395
|
-
s2.stop(prUpdated ?
|
|
402
|
+
s2.stop(prUpdated ? `PR updated — ${chalk.cyan(prUrl)}` : `PR opened — ${chalk.cyan(prUrl)}`);
|
|
396
403
|
|
|
397
404
|
if (newNamespaces.length > 0) {
|
|
398
405
|
fmt.logInfo(`New namespace${newNamespaces.length > 1 ? 's' : ''} ${chalk.cyan(newNamespaces.join(', '))} — CODEOWNERS entr${newNamespaces.length > 1 ? 'ies' : 'y'} added`);
|
|
@@ -400,7 +407,6 @@ function doPush(files, awHome, dryRun, worktreeFlow = false, preStaged = false)
|
|
|
400
407
|
if (worktreeFlow) {
|
|
401
408
|
fmt.logInfo(chalk.dim(`On branch ${finalBranch} — run aw push again to open a new PR`));
|
|
402
409
|
}
|
|
403
|
-
fmt.logSuccess(`PR: ${chalk.cyan(prUrl)}`);
|
|
404
410
|
fmt.outro('⟁ Push complete');
|
|
405
411
|
}
|
|
406
412
|
|
|
@@ -416,8 +422,8 @@ export function pushCommand(args) {
|
|
|
416
422
|
|
|
417
423
|
// If the project has a local worktree (.aw/ with a .git file), use it so
|
|
418
424
|
// changes made inside the project are correctly detected and committed.
|
|
419
|
-
const localAw =
|
|
420
|
-
const hasWorktree =
|
|
425
|
+
const localAw = findNearestWorktree(cwd, HOME);
|
|
426
|
+
const hasWorktree = localAw !== null;
|
|
421
427
|
const awHome = hasWorktree ? localAw : globalAw;
|
|
422
428
|
const registrySubDir = join(awHome, REGISTRY_DIR);
|
|
423
429
|
const workspaceDir = getLocalRegistryDir(cwd, join(HOME, '.aw_registry'));
|
package/commands/status.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { homedir } from 'node:os';
|
|
|
5
5
|
import * as config from '../config.mjs';
|
|
6
6
|
import * as fmt from '../fmt.mjs';
|
|
7
7
|
import { chalk } from '../fmt.mjs';
|
|
8
|
-
import { detectChanges, getCurrentBranch, commitsAheadOfMain, isValidClone,
|
|
8
|
+
import { detectChanges, getCurrentBranch, commitsAheadOfMain, isValidClone, findNearestWorktree } from '../git.mjs';
|
|
9
9
|
import { REGISTRY_DIR, REGISTRY_REPO, REGISTRY_URL } from '../constants.mjs';
|
|
10
10
|
|
|
11
11
|
export function statusCommand(args) {
|
|
@@ -15,12 +15,12 @@ export function statusCommand(args) {
|
|
|
15
15
|
const GLOBAL_AW_DIR = join(HOME, '.aw_registry');
|
|
16
16
|
|
|
17
17
|
// Use local project worktree if present so changes made there are visible
|
|
18
|
-
const localAw =
|
|
19
|
-
const AW_HOME =
|
|
18
|
+
const localAw = cwd !== HOME ? findNearestWorktree(cwd, HOME) : null;
|
|
19
|
+
const AW_HOME = localAw ?? globalAw;
|
|
20
20
|
|
|
21
21
|
fmt.intro('aw status');
|
|
22
22
|
|
|
23
|
-
if (!
|
|
23
|
+
if (!localAw && !isValidClone(AW_HOME, REGISTRY_URL)) {
|
|
24
24
|
fmt.cancel('Registry not initialized. Run: aw init');
|
|
25
25
|
return;
|
|
26
26
|
}
|
package/git.mjs
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { execSync, exec as execCb } from 'node:child_process';
|
|
4
4
|
import { mkdtempSync, existsSync, lstatSync, rmSync } from 'node:fs';
|
|
5
|
-
import { join, basename } from 'node:path';
|
|
6
|
-
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join, basename, dirname } from 'node:path';
|
|
6
|
+
import { homedir, tmpdir } from 'node:os';
|
|
7
7
|
import { promisify } from 'node:util';
|
|
8
8
|
import { REGISTRY_BASE_BRANCH, REGISTRY_DIR, DOCS_SOURCE_DIR } from './constants.mjs';
|
|
9
9
|
|
|
@@ -468,10 +468,28 @@ export function rebaseOntoOriginMain(awHome) {
|
|
|
468
468
|
* worktree), so isWorktree returns false and the global fallback is used.
|
|
469
469
|
*/
|
|
470
470
|
export function getLocalRegistryDir(cwd, fallback) {
|
|
471
|
-
|
|
471
|
+
const wt = findNearestWorktree(cwd, homedir());
|
|
472
|
+
if (wt) return join(wt, REGISTRY_DIR);
|
|
472
473
|
return fallback;
|
|
473
474
|
}
|
|
474
475
|
|
|
476
|
+
/**
|
|
477
|
+
* Walk up from `startDir` toward `stopDir` looking for the nearest `.aw`
|
|
478
|
+
* linked worktree. Returns the `.aw` path if found, null otherwise.
|
|
479
|
+
* Stops at `stopDir` (usually homedir) so it never escapes the user's tree.
|
|
480
|
+
*/
|
|
481
|
+
export function findNearestWorktree(startDir, stopDir) {
|
|
482
|
+
let dir = startDir;
|
|
483
|
+
while (dir !== stopDir) {
|
|
484
|
+
const candidate = join(dir, '.aw');
|
|
485
|
+
if (isWorktree(candidate)) return candidate;
|
|
486
|
+
const parent = dirname(dir);
|
|
487
|
+
if (parent === dir) break; // filesystem root
|
|
488
|
+
dir = parent;
|
|
489
|
+
}
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
|
|
475
493
|
/**
|
|
476
494
|
* Check if dir is a git linked worktree (has .git as a FILE, not a directory).
|
|
477
495
|
*/
|