@ghl-ai/aw 0.1.81 → 0.1.82
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 +89 -15
- package/package.json +1 -1
package/commands/push.mjs
CHANGED
|
@@ -572,32 +572,91 @@ async function getProjectSourceRepo(projectRoot) {
|
|
|
572
572
|
return `local/${basename(projectRoot)}`;
|
|
573
573
|
}
|
|
574
574
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
575
|
+
// Network git ops against the docs repo always disable interactive credential
|
|
576
|
+
// prompts (so a missing/incorrect credential fails fast instead of hanging
|
|
577
|
+
// forever) and are time-bounded (so a stalled connection aborts instead of
|
|
578
|
+
// blocking the whole publish). Override the caps with AW_DOCS_GIT_TIMEOUT_MS.
|
|
579
|
+
const AW_DOCS_GIT_ENV = { ...process.env, GIT_TERMINAL_PROMPT: '0', GCM_INTERACTIVE: 'never' };
|
|
580
|
+
const AW_DOCS_CLONE_TIMEOUT_MS = Number(process.env.AW_DOCS_GIT_TIMEOUT_MS) || 180000;
|
|
581
|
+
const AW_DOCS_NET_TIMEOUT_MS = Number(process.env.AW_DOCS_GIT_TIMEOUT_MS) || 120000;
|
|
582
|
+
|
|
583
|
+
async function awDocsSparseEnabled(cloneDir) {
|
|
584
|
+
try {
|
|
585
|
+
const { stdout } = await execFile('git', ['config', '--get', 'core.sparseCheckout'], {
|
|
586
|
+
cwd: cloneDir,
|
|
580
587
|
encoding: 'utf8',
|
|
581
588
|
});
|
|
589
|
+
return stdout.trim() === 'true';
|
|
590
|
+
} catch {
|
|
591
|
+
return false;
|
|
582
592
|
}
|
|
593
|
+
}
|
|
583
594
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
595
|
+
async function ensureAwDocsRepoClone(home, publishConfig, sparseIncludePaths = []) {
|
|
596
|
+
const cloneDir = process.env.AW_DOCS_WORKTREE || join(home, '.aw-ghl-aw-docs');
|
|
597
|
+
const wantSparse = sparseIncludePaths.length > 0;
|
|
598
|
+
const freshClone = !existsSync(join(cloneDir, '.git'));
|
|
599
|
+
|
|
600
|
+
if (freshClone) {
|
|
601
|
+
// A full clone of ghl-aw-docs pulls the entire org's published docs
|
|
602
|
+
// (hundreds of MB / ~170k objects) and is the dominant cause of
|
|
603
|
+
// first-publish timeouts. Clone blobless (no file contents up front) and,
|
|
604
|
+
// when we know which subtree this publish touches, defer checkout and
|
|
605
|
+
// sparse-checkout only that path — turning hundreds of MB into a few MB.
|
|
606
|
+
// Degrades gracefully: if the remote does not support partial clone git
|
|
607
|
+
// ignores the filter, and if sparse-checkout is unavailable the branch
|
|
608
|
+
// checkout below still materialises the full tree.
|
|
609
|
+
const cloneArgs = ['clone', '--filter=blob:none'];
|
|
610
|
+
if (wantSparse) cloneArgs.push('--no-checkout');
|
|
611
|
+
cloneArgs.push(publishConfig.repoUrl, cloneDir);
|
|
612
|
+
await execFile('git', cloneArgs, {
|
|
613
|
+
encoding: 'utf8',
|
|
614
|
+
env: AW_DOCS_GIT_ENV,
|
|
615
|
+
timeout: AW_DOCS_CLONE_TIMEOUT_MS,
|
|
616
|
+
});
|
|
617
|
+
if (wantSparse) {
|
|
618
|
+
await execFile('git', ['sparse-checkout', 'set', '--cone', ...sparseIncludePaths], {
|
|
619
|
+
cwd: cloneDir,
|
|
620
|
+
encoding: 'utf8',
|
|
621
|
+
}).catch(() => { /* sparse unsupported → full checkout below */ });
|
|
622
|
+
}
|
|
587
623
|
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
624
|
+
|
|
625
|
+
// Only validate cleanliness for a REUSED clone. A fresh blobless
|
|
626
|
+
// --no-checkout clone intentionally has no working tree yet (it is
|
|
627
|
+
// materialised by the branch checkout below), which would otherwise
|
|
628
|
+
// register as "dirty".
|
|
629
|
+
if (!freshClone) {
|
|
630
|
+
let status = await getGitStatus(cloneDir);
|
|
631
|
+
if (status.trim() && await selfHealManagedAwCacheDirtiness(cloneDir, status)) {
|
|
632
|
+
status = await getGitStatus(cloneDir);
|
|
633
|
+
}
|
|
634
|
+
if (status.trim()) {
|
|
635
|
+
throw new Error([
|
|
636
|
+
`AW docs repo worktree is dirty: ${cloneDir}`,
|
|
637
|
+
status.trim(),
|
|
638
|
+
'Clean the cached docs repo or set AW_DOCS_WORKTREE to a clean clone before publishing.',
|
|
639
|
+
].join('\n'));
|
|
640
|
+
}
|
|
594
641
|
}
|
|
595
642
|
|
|
596
643
|
await execFile('git', ['fetch', 'origin'], {
|
|
597
644
|
cwd: cloneDir,
|
|
598
645
|
encoding: 'utf8',
|
|
646
|
+
env: AW_DOCS_GIT_ENV,
|
|
647
|
+
timeout: AW_DOCS_NET_TIMEOUT_MS,
|
|
599
648
|
});
|
|
600
649
|
|
|
650
|
+
// Keep the current publish's subtree inside the sparse cone. Only applies to
|
|
651
|
+
// clones that already use sparse-checkout (i.e. the blobless clones we
|
|
652
|
+
// created) — legacy full clones are left untouched so there is no regression.
|
|
653
|
+
if (wantSparse && await awDocsSparseEnabled(cloneDir)) {
|
|
654
|
+
await execFile('git', ['sparse-checkout', 'add', ...sparseIncludePaths], {
|
|
655
|
+
cwd: cloneDir,
|
|
656
|
+
encoding: 'utf8',
|
|
657
|
+
}).catch(() => { /* best effort — checkout below still works */ });
|
|
658
|
+
}
|
|
659
|
+
|
|
601
660
|
const hasPublishBranch = await remoteBranchExists(cloneDir, publishConfig.branch);
|
|
602
661
|
const checkoutStart = hasPublishBranch
|
|
603
662
|
? `origin/${publishConfig.branch}`
|
|
@@ -608,9 +667,13 @@ async function ensureAwDocsRepoClone(home, publishConfig) {
|
|
|
608
667
|
}
|
|
609
668
|
|
|
610
669
|
try {
|
|
670
|
+
// With a blobless clone, checkout lazily fetches the missing blobs for the
|
|
671
|
+
// sparse cone — a network op, so bound it like the other remote calls.
|
|
611
672
|
await execFile('git', ['checkout', '-B', publishConfig.branch, checkoutStart], {
|
|
612
673
|
cwd: cloneDir,
|
|
613
674
|
encoding: 'utf8',
|
|
675
|
+
env: AW_DOCS_GIT_ENV,
|
|
676
|
+
timeout: AW_DOCS_NET_TIMEOUT_MS,
|
|
614
677
|
});
|
|
615
678
|
} catch (e) {
|
|
616
679
|
const healedPath = isCheckoutOverwrittenByAwPathError(e)
|
|
@@ -622,6 +685,8 @@ async function ensureAwDocsRepoClone(home, publishConfig) {
|
|
|
622
685
|
await execFile('git', ['checkout', '-B', publishConfig.branch, checkoutStart], {
|
|
623
686
|
cwd: cloneDir,
|
|
624
687
|
encoding: 'utf8',
|
|
688
|
+
env: AW_DOCS_GIT_ENV,
|
|
689
|
+
timeout: AW_DOCS_NET_TIMEOUT_MS,
|
|
625
690
|
});
|
|
626
691
|
}
|
|
627
692
|
|
|
@@ -629,6 +694,8 @@ async function ensureAwDocsRepoClone(home, publishConfig) {
|
|
|
629
694
|
await execFile('git', ['pull', '--ff-only', 'origin', publishConfig.branch], {
|
|
630
695
|
cwd: cloneDir,
|
|
631
696
|
encoding: 'utf8',
|
|
697
|
+
env: AW_DOCS_GIT_ENV,
|
|
698
|
+
timeout: AW_DOCS_NET_TIMEOUT_MS,
|
|
632
699
|
});
|
|
633
700
|
}
|
|
634
701
|
await removeTrackedAwSymlink(cloneDir);
|
|
@@ -729,6 +796,8 @@ async function commitAndPushAwDocsRepo(docsRepoDir, { message, branch }) {
|
|
|
729
796
|
await execFile('git', ['push', '-u', 'origin', `HEAD:${branch}`], {
|
|
730
797
|
cwd: docsRepoDir,
|
|
731
798
|
encoding: 'utf8',
|
|
799
|
+
env: AW_DOCS_GIT_ENV,
|
|
800
|
+
timeout: AW_DOCS_NET_TIMEOUT_MS,
|
|
732
801
|
});
|
|
733
802
|
}
|
|
734
803
|
|
|
@@ -823,7 +892,12 @@ async function publishProjectAwDocs(cwd, home, dryRun, scope = null) {
|
|
|
823
892
|
const s = fmt.spinner();
|
|
824
893
|
s.start(`Publishing ${files.length} AW doc${files.length > 1 ? 's' : ''} to ${publishConfig.repo}...`);
|
|
825
894
|
try {
|
|
826
|
-
|
|
895
|
+
// Scope the (blobless) docs clone to only the subtree this publish writes,
|
|
896
|
+
// so a fresh clone fetches a few MB instead of the whole org's docs.
|
|
897
|
+
const sparseIncludePaths = isRootScope
|
|
898
|
+
? [`${publishConfig.dest}/${scope.relPrefix}`]
|
|
899
|
+
: [`${publishConfig.dest}/${repoSlug}/${githubUsername}`];
|
|
900
|
+
const docsRepoDir = await ensureAwDocsRepoClone(home, publishConfig, sparseIncludePaths);
|
|
827
901
|
const deleteTarget = isRootScope
|
|
828
902
|
? join(docsRepoDir, publishConfig.dest, scope.relPrefix)
|
|
829
903
|
: scope?.relPrefix
|