@nemus-cli/nemus 0.2.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.
- package/CHANGELOG.md +86 -0
- package/LICENSE +21 -0
- package/README.md +275 -0
- package/assets/README.md +53 -0
- package/assets/banner.png +0 -0
- package/assets/banner.svg +66 -0
- package/assets/favicon-32.png +0 -0
- package/assets/favicon-64.png +0 -0
- package/assets/icon-256.png +0 -0
- package/assets/icon.svg +23 -0
- package/assets/logo-256.png +0 -0
- package/assets/logo-512.png +0 -0
- package/assets/logo-wordmark.svg +35 -0
- package/assets/logo.svg +32 -0
- package/bin/workspace.js +130 -0
- package/dist/cli/ai-prompt.js +421 -0
- package/dist/cli/dashboard/AgentList.js +61 -0
- package/dist/cli/dashboard/DashboardSidebar.js +280 -0
- package/dist/cli/dashboard/HelpFooter.js +73 -0
- package/dist/cli/dashboard/StatusBadge.js +50 -0
- package/dist/cli/dashboard/index.js +11 -0
- package/dist/cli/dashboard/types.js +2 -0
- package/dist/cli/tui.js +48 -0
- package/dist/commands/ai-prompt.js +22 -0
- package/dist/commands/analyze-deps.js +138 -0
- package/dist/commands/archive.js +113 -0
- package/dist/commands/branch/create.js +119 -0
- package/dist/commands/branch/index.js +104 -0
- package/dist/commands/branch/merge.js +90 -0
- package/dist/commands/branch/rebase.js +84 -0
- package/dist/commands/branch/switch.js +165 -0
- package/dist/commands/cache/index.js +80 -0
- package/dist/commands/cache/manager.js +198 -0
- package/dist/commands/cleanup.js +153 -0
- package/dist/commands/configure-claude.js +55 -0
- package/dist/commands/configure.js +231 -0
- package/dist/commands/create.js +204 -0
- package/dist/commands/dashboard/hook-handler.js +162 -0
- package/dist/commands/dashboard/index.js +258 -0
- package/dist/commands/dashboard/launcher.js +40 -0
- package/dist/commands/dashboard/session-picker.js +106 -0
- package/dist/commands/dashboard/sidebar.js +39 -0
- package/dist/commands/dashboard/workspace-picker.js +95 -0
- package/dist/commands/delete.js +194 -0
- package/dist/commands/deprecated-aliases.js +44 -0
- package/dist/commands/diff.js +129 -0
- package/dist/commands/doctor.js +141 -0
- package/dist/commands/generate-docs.js +77 -0
- package/dist/commands/ghq-status.js +72 -0
- package/dist/commands/go.js +154 -0
- package/dist/commands/history.js +124 -0
- package/dist/commands/list.js +191 -0
- package/dist/commands/mcp/index.js +69 -0
- package/dist/commands/migrate.js +296 -0
- package/dist/commands/remove-repo.js +217 -0
- package/dist/commands/report-bug.js +112 -0
- package/dist/commands/run.js +147 -0
- package/dist/commands/save-context.js +171 -0
- package/dist/commands/sessions.js +129 -0
- package/dist/commands/status.js +138 -0
- package/dist/commands/suite/create.js +212 -0
- package/dist/commands/suite/delete.js +64 -0
- package/dist/commands/suite/export.js +126 -0
- package/dist/commands/suite/import.js +128 -0
- package/dist/commands/suite/index.js +94 -0
- package/dist/commands/suite/list.js +49 -0
- package/dist/commands/suite/use.js +227 -0
- package/dist/commands/sync.js +117 -0
- package/dist/commands/update.js +203 -0
- package/dist/mcp/install.js +550 -0
- package/dist/mcp/server.js +398 -0
- package/dist/mcp/tools.js +922 -0
- package/dist/pi-extensions/permission-sync.ts.template +41 -0
- package/dist/pi-extensions/permissions-gate.ts.template +69 -0
- package/dist/pi-extensions/pr-fix-trigger.ts.template +124 -0
- package/dist/pi-extensions/review-reminder.ts.template +58 -0
- package/dist/pi-extensions/workspace-input-status.ts.template +919 -0
- package/dist/program.js +136 -0
- package/dist/scripts/workspace-table.py +394 -0
- package/dist/types/dashboard.js +11 -0
- package/dist/types/index.js +2 -0
- package/dist/utils/agent-config.js +263 -0
- package/dist/utils/agent-state.js +178 -0
- package/dist/utils/banner.js +24 -0
- package/dist/utils/branch-operations.js +232 -0
- package/dist/utils/bug-report.js +355 -0
- package/dist/utils/cache.js +168 -0
- package/dist/utils/claude-integration.js +617 -0
- package/dist/utils/claude-sessions.js +260 -0
- package/dist/utils/cleanup-operations.js +120 -0
- package/dist/utils/colors.js +26 -0
- package/dist/utils/command-helpers.js +44 -0
- package/dist/utils/config.js +150 -0
- package/dist/utils/context-file.js +33 -0
- package/dist/utils/dashboard-hooks.js +172 -0
- package/dist/utils/dependency-analyzer.js +234 -0
- package/dist/utils/diff-operations.js +80 -0
- package/dist/utils/doc-generator.js +184 -0
- package/dist/utils/ghq-integration.js +332 -0
- package/dist/utils/git-operations.js +237 -0
- package/dist/utils/git-status.js +187 -0
- package/dist/utils/github.js +68 -0
- package/dist/utils/health-checks.js +345 -0
- package/dist/utils/history.js +150 -0
- package/dist/utils/hooks.js +100 -0
- package/dist/utils/logger.js +40 -0
- package/dist/utils/permission-sync.js +681 -0
- package/dist/utils/pi-extensions.js +224 -0
- package/dist/utils/progress.js +92 -0
- package/dist/utils/prompts.js +279 -0
- package/dist/utils/repo-resolver.js +260 -0
- package/dist/utils/retry.js +65 -0
- package/dist/utils/run-operations.js +30 -0
- package/dist/utils/suite.js +159 -0
- package/dist/utils/sync-operations.js +107 -0
- package/dist/utils/tmux-dashboard.js +184 -0
- package/dist/utils/validation.js +153 -0
- package/dist/utils/version-check.js +121 -0
- package/dist/utils/workspace-meta.js +185 -0
- package/install-shell-integration.sh +532 -0
- package/install.sh +401 -0
- package/package.json +90 -0
- package/remove-shell-block.awk +44 -0
- package/scripts/postinstall.js +85 -0
- package/skills/analyze-deps.md +23 -0
- package/skills/archive-workspace.md +26 -0
- package/skills/branch-create.md +18 -0
- package/skills/create-workspace.md +39 -0
- package/skills/delete-workspace.md +37 -0
- package/skills/list-org-repos.md +26 -0
- package/skills/list-suites.md +24 -0
- package/skills/list-workspaces.md +27 -0
- package/skills/refresh-workspace-docs.md +89 -0
- package/skills/remove-repo.md +25 -0
- package/skills/run-command.md +23 -0
- package/skills/save-context.md +68 -0
- package/skills/search-repos.md +31 -0
- package/skills/snapshot-list.md +18 -0
- package/skills/snapshot-save.md +12 -0
- package/skills/switch-branch.md +18 -0
- package/skills/update-cache.md +21 -0
- package/skills/update-workspace.md +66 -0
- package/skills/workspace-cleanup.md +18 -0
- package/skills/workspace-diff.md +23 -0
- package/skills/workspace-doctor.md +23 -0
- package/skills/workspace-info.md +45 -0
- package/skills/workspace-manager/SKILL.md +201 -0
- package/skills/workspace-manager/references/ai-prompt.md +44 -0
- package/skills/workspace-manager/references/analyze-deps.md +45 -0
- package/skills/workspace-manager/references/archive-workspace.md +38 -0
- package/skills/workspace-manager/references/branch-create.md +31 -0
- package/skills/workspace-manager/references/branch-merge.md +22 -0
- package/skills/workspace-manager/references/branch-rebase.md +22 -0
- package/skills/workspace-manager/references/cache-clear.md +18 -0
- package/skills/workspace-manager/references/cache-info.md +22 -0
- package/skills/workspace-manager/references/configure-claude.md +31 -0
- package/skills/workspace-manager/references/configure.md +35 -0
- package/skills/workspace-manager/references/create-workspace.md +45 -0
- package/skills/workspace-manager/references/dashboard.md +61 -0
- package/skills/workspace-manager/references/delete-workspace.md +33 -0
- package/skills/workspace-manager/references/generate-docs.md +36 -0
- package/skills/workspace-manager/references/ghq-status.md +28 -0
- package/skills/workspace-manager/references/go.md +33 -0
- package/skills/workspace-manager/references/history.md +41 -0
- package/skills/workspace-manager/references/list-org-repos.md +26 -0
- package/skills/workspace-manager/references/list-suites.md +21 -0
- package/skills/workspace-manager/references/list-workspaces.md +27 -0
- package/skills/workspace-manager/references/mcp.md +49 -0
- package/skills/workspace-manager/references/remove-repo.md +26 -0
- package/skills/workspace-manager/references/run-command.md +44 -0
- package/skills/workspace-manager/references/search-repos.md +24 -0
- package/skills/workspace-manager/references/sessions.md +27 -0
- package/skills/workspace-manager/references/suite-create.md +22 -0
- package/skills/workspace-manager/references/suite-delete.md +21 -0
- package/skills/workspace-manager/references/suite-export.md +19 -0
- package/skills/workspace-manager/references/suite-import.md +19 -0
- package/skills/workspace-manager/references/suite-use.md +27 -0
- package/skills/workspace-manager/references/switch-branch.md +25 -0
- package/skills/workspace-manager/references/update-cache.md +22 -0
- package/skills/workspace-manager/references/update-workspace.md +50 -0
- package/skills/workspace-manager/references/workspace-cleanup.md +33 -0
- package/skills/workspace-manager/references/workspace-diff.md +27 -0
- package/skills/workspace-manager/references/workspace-doctor.md +27 -0
- package/skills/workspace-manager/references/workspace-info.md +33 -0
- package/skills/workspace-manager/references/workspace-status.md +43 -0
- package/skills/workspace-manager/references/workspace-sync.md +26 -0
- package/skills/workspace-status.md +26 -0
- package/skills/workspace-sync.md +23 -0
- package/src/cli/ai-prompt.test.ts +667 -0
- package/src/cli/ai-prompt.ts +421 -0
- package/src/cli/dashboard/AgentList.tsx +44 -0
- package/src/cli/dashboard/DashboardSidebar.tsx +291 -0
- package/src/cli/dashboard/HelpFooter.tsx +22 -0
- package/src/cli/dashboard/StatusBadge.tsx +23 -0
- package/src/cli/dashboard/index.ts +5 -0
- package/src/cli/dashboard/types.ts +16 -0
- package/src/cli/tui.tsx +51 -0
- package/src/cli/workspace-cli.test.ts +30 -0
- package/src/commands/ai-prompt.ts +21 -0
- package/src/commands/analyze-deps.ts +119 -0
- package/src/commands/archive.ts +114 -0
- package/src/commands/branch/create.ts +94 -0
- package/src/commands/branch/index.ts +74 -0
- package/src/commands/branch/merge.ts +61 -0
- package/src/commands/branch/rebase.ts +54 -0
- package/src/commands/branch/switch.ts +150 -0
- package/src/commands/cache/index.ts +51 -0
- package/src/commands/cache/manager.ts +168 -0
- package/src/commands/cleanup.ts +132 -0
- package/src/commands/configure-claude.ts +55 -0
- package/src/commands/configure.ts +204 -0
- package/src/commands/create.ts +196 -0
- package/src/commands/dashboard/hook-handler.ts +138 -0
- package/src/commands/dashboard/index.ts +248 -0
- package/src/commands/dashboard/launcher.ts +42 -0
- package/src/commands/dashboard/session-picker.ts +73 -0
- package/src/commands/dashboard/sidebar.js +39 -0
- package/src/commands/dashboard/workspace-picker.ts +62 -0
- package/src/commands/delete.test.ts +250 -0
- package/src/commands/delete.ts +171 -0
- package/src/commands/deprecated-aliases.ts +55 -0
- package/src/commands/diff.ts +106 -0
- package/src/commands/doctor.ts +110 -0
- package/src/commands/generate-docs.ts +45 -0
- package/src/commands/ghq-status.ts +70 -0
- package/src/commands/go.ts +125 -0
- package/src/commands/history.ts +136 -0
- package/src/commands/list.test.ts +219 -0
- package/src/commands/list.ts +176 -0
- package/src/commands/mcp/index.ts +39 -0
- package/src/commands/migrate.ts +277 -0
- package/src/commands/remove-repo.ts +194 -0
- package/src/commands/report-bug.ts +88 -0
- package/src/commands/run.ts +125 -0
- package/src/commands/save-context.test.ts +113 -0
- package/src/commands/save-context.ts +141 -0
- package/src/commands/sessions.ts +99 -0
- package/src/commands/status.ts +111 -0
- package/src/commands/suite/create.ts +240 -0
- package/src/commands/suite/delete.ts +65 -0
- package/src/commands/suite/export.ts +97 -0
- package/src/commands/suite/import.ts +100 -0
- package/src/commands/suite/index.ts +66 -0
- package/src/commands/suite/list.ts +54 -0
- package/src/commands/suite/use.ts +232 -0
- package/src/commands/sync.ts +97 -0
- package/src/commands/update.ts +197 -0
- package/src/mcp/install.ts +529 -0
- package/src/mcp/server.ts +579 -0
- package/src/mcp/tools.test.ts +545 -0
- package/src/mcp/tools.ts +1019 -0
- package/src/mcp/update-workspace.test.ts +220 -0
- package/src/pi-extensions/permission-sync.ts.template +41 -0
- package/src/pi-extensions/permissions-gate.ts.template +69 -0
- package/src/pi-extensions/pr-fix-trigger.ts.template +124 -0
- package/src/pi-extensions/review-reminder.ts.template +58 -0
- package/src/pi-extensions/workspace-input-status.ts.template +919 -0
- package/src/program.ts +112 -0
- package/src/scripts/workspace-table.py +394 -0
- package/src/types/dashboard.ts +29 -0
- package/src/types/index.ts +109 -0
- package/src/types/ink.d.ts +171 -0
- package/src/utils/agent-config.test.ts +303 -0
- package/src/utils/agent-config.ts +264 -0
- package/src/utils/agent-state.test.ts +166 -0
- package/src/utils/agent-state.ts +135 -0
- package/src/utils/banner.ts +24 -0
- package/src/utils/branch-operations.ts +237 -0
- package/src/utils/bug-report.test.ts +174 -0
- package/src/utils/bug-report.ts +355 -0
- package/src/utils/cache.ts +150 -0
- package/src/utils/claude-integration.test.ts +381 -0
- package/src/utils/claude-integration.ts +649 -0
- package/src/utils/claude-sessions.test.ts +262 -0
- package/src/utils/claude-sessions.ts +251 -0
- package/src/utils/cleanup-operations.ts +94 -0
- package/src/utils/colors.ts +25 -0
- package/src/utils/command-helpers.ts +46 -0
- package/src/utils/config.test.ts +203 -0
- package/src/utils/config.ts +133 -0
- package/src/utils/context-file.ts +36 -0
- package/src/utils/dashboard-hooks.test.ts +168 -0
- package/src/utils/dashboard-hooks.ts +163 -0
- package/src/utils/dependency-analyzer.ts +243 -0
- package/src/utils/diff-operations.ts +59 -0
- package/src/utils/doc-generator.ts +187 -0
- package/src/utils/ghq-integration.test.ts +195 -0
- package/src/utils/ghq-integration.ts +317 -0
- package/src/utils/git-operations.test.ts +265 -0
- package/src/utils/git-operations.ts +233 -0
- package/src/utils/git-status.ts +171 -0
- package/src/utils/github.ts +74 -0
- package/src/utils/health-checks.ts +359 -0
- package/src/utils/history.ts +140 -0
- package/src/utils/hooks.ts +92 -0
- package/src/utils/logger.ts +37 -0
- package/src/utils/permission-sync.test.ts +593 -0
- package/src/utils/permission-sync.ts +720 -0
- package/src/utils/pi-extensions.test.ts +182 -0
- package/src/utils/pi-extensions.ts +226 -0
- package/src/utils/progress.ts +73 -0
- package/src/utils/prompts.test.ts +169 -0
- package/src/utils/prompts.ts +296 -0
- package/src/utils/repo-resolver.test.ts +313 -0
- package/src/utils/repo-resolver.ts +294 -0
- package/src/utils/retry.test.ts +124 -0
- package/src/utils/retry.ts +85 -0
- package/src/utils/run-operations.ts +38 -0
- package/src/utils/suite.test.ts +256 -0
- package/src/utils/suite.ts +144 -0
- package/src/utils/sync-operations.ts +87 -0
- package/src/utils/tmux-dashboard.ts +193 -0
- package/src/utils/validation.test.ts +139 -0
- package/src/utils/validation.ts +128 -0
- package/src/utils/version-check.test.ts +108 -0
- package/src/utils/version-check.ts +96 -0
- package/src/utils/workspace-meta.ts +173 -0
- package/sync-permissions.sh +137 -0
- package/tsconfig.json +20 -0
- package/uninstall.sh +162 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.describeCloneError = describeCloneError;
|
|
37
|
+
exports.isGhqInstalled = isGhqInstalled;
|
|
38
|
+
exports.warnIfGhqMissing = warnIfGhqMissing;
|
|
39
|
+
exports.getGhqRoot = getGhqRoot;
|
|
40
|
+
exports.ghqRepoExists = ghqRepoExists;
|
|
41
|
+
exports.getGhqRepoPath = getGhqRepoPath;
|
|
42
|
+
exports.ghqGet = ghqGet;
|
|
43
|
+
exports.ghqList = ghqList;
|
|
44
|
+
exports.displayGhqInfo = displayGhqInfo;
|
|
45
|
+
exports.cloneWithGhq = cloneWithGhq;
|
|
46
|
+
exports.getGhqStatus = getGhqStatus;
|
|
47
|
+
const child_process_1 = require("child_process");
|
|
48
|
+
const util_1 = require("util");
|
|
49
|
+
const path = __importStar(require("path"));
|
|
50
|
+
const logger_1 = require("./logger");
|
|
51
|
+
const colors_1 = require("./colors");
|
|
52
|
+
const config_1 = require("./config");
|
|
53
|
+
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
54
|
+
/**
|
|
55
|
+
* Turn a raw child-process clone failure (from exec or execFile) into an
|
|
56
|
+
* actionable message. The child API discards the reason on timeout/buffer kill (you just get "Command
|
|
57
|
+
* failed: git clone …"), so we inspect killed/signal/code and the captured
|
|
58
|
+
* stderr to explain what actually happened and how to fix it.
|
|
59
|
+
*/
|
|
60
|
+
function describeCloneError(error, timeoutMs = config_1.CLONE_TIMEOUT_MS) {
|
|
61
|
+
const e = (error ?? {});
|
|
62
|
+
const stderr = (e.stderr || '').trim();
|
|
63
|
+
const tail = stderr ? `\n git: ${stderr.split('\n').filter(Boolean).slice(-3).join(' ')}` : '';
|
|
64
|
+
// Check maxBuffer FIRST: Node sets killed=true on a maxBuffer kill too, so
|
|
65
|
+
// the timeout heuristic below would otherwise shadow this case.
|
|
66
|
+
if (e.code === 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER') {
|
|
67
|
+
return `Clone output exceeded the buffer limit (very large repo). Install ghq (brew install ghq) for cached clones.${tail}`;
|
|
68
|
+
}
|
|
69
|
+
const timedOut = e.killed === true || e.signal === 'SIGTERM' || e.code === 'ETIMEDOUT';
|
|
70
|
+
if (timedOut) {
|
|
71
|
+
const mins = Math.round(timeoutMs / 60000);
|
|
72
|
+
return `Clone timed out after ${mins} min — the repo may be large or the network slow. `
|
|
73
|
+
+ `Fixes: install ghq for fast cached clones (brew install ghq), switch to SSH (w configure), `
|
|
74
|
+
+ `or raise the limit with WORKSPACE_CLONE_TIMEOUT_MS.${tail}`;
|
|
75
|
+
}
|
|
76
|
+
return `${e.message || 'Unknown error'}${tail}`;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Check if ghq is installed
|
|
80
|
+
*/
|
|
81
|
+
async function isGhqInstalled() {
|
|
82
|
+
try {
|
|
83
|
+
await execAsync('which ghq');
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Verify ghq is installed and, if not, warn with install guidance.
|
|
92
|
+
* ghq gives fast cached clones (shared across workspaces) and avoids the
|
|
93
|
+
* clone-timeout failures large repos hit over plain git-over-HTTPS.
|
|
94
|
+
* Returns true if ghq is available. Non-blocking — clones still work without it.
|
|
95
|
+
*/
|
|
96
|
+
async function warnIfGhqMissing() {
|
|
97
|
+
if (await isGhqInstalled())
|
|
98
|
+
return true;
|
|
99
|
+
(0, logger_1.logWarning)('ghq is not installed — using direct git clone (slower, no shared cache).');
|
|
100
|
+
(0, logger_1.logInfo)(' ghq caches repos so re-cloning across workspaces is near-instant and large repos avoid clone timeouts.');
|
|
101
|
+
(0, logger_1.logInfo)(` Install: ${(0, colors_1.colorize)('brew install ghq', 'cyan')} (macOS) — Linux/Windows: https://github.com/x-motemen/ghq#installation`);
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Get ghq root directory
|
|
106
|
+
*/
|
|
107
|
+
async function getGhqRoot() {
|
|
108
|
+
try {
|
|
109
|
+
const { stdout } = await execAsync('ghq root');
|
|
110
|
+
return stdout.trim();
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Check if a repository exists in ghq
|
|
118
|
+
*/
|
|
119
|
+
async function ghqRepoExists(repoUrl) {
|
|
120
|
+
try {
|
|
121
|
+
const repoPath = await getGhqRepoPath(repoUrl);
|
|
122
|
+
if (!repoPath)
|
|
123
|
+
return false;
|
|
124
|
+
await execAsync(`test -d "${repoPath}"`);
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get the local path for a ghq-managed repository
|
|
133
|
+
*/
|
|
134
|
+
async function getGhqRepoPath(repoUrl) {
|
|
135
|
+
try {
|
|
136
|
+
// Extract org/repo from URL
|
|
137
|
+
// e.g., git@github.com:my-org/my-app.git -> github.com/my-org/my-app
|
|
138
|
+
const match = repoUrl.match(/github\.com[:/]([^/]+)\/([^.]+)/);
|
|
139
|
+
if (!match)
|
|
140
|
+
return null;
|
|
141
|
+
const [, org, repo] = match;
|
|
142
|
+
const ghqRoot = await getGhqRoot();
|
|
143
|
+
if (!ghqRoot)
|
|
144
|
+
return null;
|
|
145
|
+
return path.join(ghqRoot, 'github.com', org, repo);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Clone repository using ghq
|
|
153
|
+
*/
|
|
154
|
+
async function ghqGet(repoUrl) {
|
|
155
|
+
try {
|
|
156
|
+
(0, logger_1.logInfo)(`Using ghq to clone ${(0, colors_1.colorize)(repoUrl, 'cyan')}...`);
|
|
157
|
+
await execAsync(`ghq get "${repoUrl}"`, { timeout: config_1.CLONE_TIMEOUT_MS, maxBuffer: config_1.CLONE_MAX_BUFFER });
|
|
158
|
+
const repoPath = await getGhqRepoPath(repoUrl);
|
|
159
|
+
if (!repoPath) {
|
|
160
|
+
return { success: false, error: 'Could not determine ghq path' };
|
|
161
|
+
}
|
|
162
|
+
(0, logger_1.logSuccess)(`Repository available in ghq: ${(0, colors_1.colorize)(repoPath, 'gray')}`);
|
|
163
|
+
return { success: true, path: repoPath };
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
167
|
+
return { success: false, error: errorMessage };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* List all repositories managed by ghq
|
|
172
|
+
*/
|
|
173
|
+
async function ghqList() {
|
|
174
|
+
try {
|
|
175
|
+
const { stdout } = await execAsync('ghq list');
|
|
176
|
+
return stdout.trim().split('\n').filter(line => line.length > 0);
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Display ghq installation instructions
|
|
184
|
+
*/
|
|
185
|
+
function displayGhqInfo() {
|
|
186
|
+
console.log('\n' + '='.repeat(60));
|
|
187
|
+
console.log((0, colors_1.colorize)('About ghq', 'bright'));
|
|
188
|
+
console.log('='.repeat(60));
|
|
189
|
+
console.log('\nghq is a tool for managing Git repositories in a structured way.');
|
|
190
|
+
console.log('The workspace manager can use ghq for better repository organization.\n');
|
|
191
|
+
console.log('Installation:');
|
|
192
|
+
console.log(' brew install ghq\n');
|
|
193
|
+
console.log('Benefits:');
|
|
194
|
+
console.log(' • Centralized repository cache');
|
|
195
|
+
console.log(' • Consistent directory structure');
|
|
196
|
+
console.log(' • Faster subsequent clones');
|
|
197
|
+
console.log(' • Works alongside workspace isolation\n');
|
|
198
|
+
console.log('After installing ghq, the workspace manager will automatically use it.');
|
|
199
|
+
console.log('='.repeat(60) + '\n');
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Clone repository with ghq integration
|
|
203
|
+
* Falls back to direct git clone if ghq is not available
|
|
204
|
+
*/
|
|
205
|
+
async function cloneWithGhq(repoUrl, targetPath) {
|
|
206
|
+
const hasGhq = await isGhqInstalled();
|
|
207
|
+
if (hasGhq) {
|
|
208
|
+
// Check if repo already exists in ghq cache
|
|
209
|
+
const ghqPath = await getGhqRepoPath(repoUrl);
|
|
210
|
+
let ghqReady = !!(ghqPath && await ghqRepoExists(repoUrl));
|
|
211
|
+
if (!ghqReady) {
|
|
212
|
+
// Not in cache — clone into ghq first (without -u to avoid extra fetch)
|
|
213
|
+
const ghqResult = await ghqGet(repoUrl);
|
|
214
|
+
if (ghqResult.success) {
|
|
215
|
+
ghqReady = true;
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
(0, logger_1.logWarning)(`ghq clone failed: ${ghqResult.error}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// Use git clone --local from ghq cache (hardlinks .git/objects — nearly instant)
|
|
222
|
+
const sourcePath = ghqPath || (await getGhqRepoPath(repoUrl));
|
|
223
|
+
if (ghqReady && sourcePath) {
|
|
224
|
+
try {
|
|
225
|
+
// Refresh the ghq cache BEFORE clone-local so the workspace gets the
|
|
226
|
+
// latest content. Without this, an old cache (cloned weeks/months ago)
|
|
227
|
+
// would serve stale state via hardlinks — user would open a workspace
|
|
228
|
+
// missing dozens of recent commits.
|
|
229
|
+
try {
|
|
230
|
+
await execAsync(`git -C "${sourcePath}" fetch origin --quiet --prune`, { timeout: 90 * 1000 });
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
// Network failure or no upstream — fall through with stale cache,
|
|
234
|
+
// we'll log a warning after the local clone if we can't align to HEAD.
|
|
235
|
+
}
|
|
236
|
+
await execAsync(`git clone --local "${sourcePath}" "${targetPath}"`, { timeout: 60 * 1000 });
|
|
237
|
+
// Reset remote to point to the original repo URL (not the local ghq path)
|
|
238
|
+
await execAsync(`git remote set-url origin "${repoUrl}"`, { cwd: targetPath });
|
|
239
|
+
// Make sure the working tree is at origin's default-branch tip.
|
|
240
|
+
// Robust against:
|
|
241
|
+
// - cache having a non-default branch checked out
|
|
242
|
+
// - fetch above failing (network blip)
|
|
243
|
+
// - refs/remotes/origin/HEAD symref not being set (some git/ghq combos)
|
|
244
|
+
let alignedToHead = false;
|
|
245
|
+
let alignError = null;
|
|
246
|
+
try {
|
|
247
|
+
await execAsync(`git fetch origin --quiet`, { cwd: targetPath, timeout: 60 * 1000 });
|
|
248
|
+
// Explicitly set refs/remotes/origin/HEAD by querying the remote.
|
|
249
|
+
// Without this, symbolic-ref may fail if the local symref wasn’t set.
|
|
250
|
+
await execAsync(`git remote set-head origin --auto`, { cwd: targetPath, timeout: 30 * 1000 })
|
|
251
|
+
.catch(() => { });
|
|
252
|
+
let defaultBranch = null;
|
|
253
|
+
try {
|
|
254
|
+
const headRes = await execAsync(`git symbolic-ref --short refs/remotes/origin/HEAD`, { cwd: targetPath, timeout: 5000 });
|
|
255
|
+
defaultBranch = headRes.stdout.trim().replace(/^origin\//, '');
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
// Fall back to ls-remote query against the actual remote
|
|
259
|
+
try {
|
|
260
|
+
const lsRes = await execAsync(`git ls-remote --symref origin HEAD`, { cwd: targetPath, timeout: 30 * 1000 });
|
|
261
|
+
const match = lsRes.stdout.match(/^ref:\s+refs\/heads\/(\S+)\s+HEAD$/m);
|
|
262
|
+
if (match)
|
|
263
|
+
defaultBranch = match[1];
|
|
264
|
+
}
|
|
265
|
+
catch { /* fall through to common-name fallback */ }
|
|
266
|
+
}
|
|
267
|
+
// Final fallback: try common default branches
|
|
268
|
+
if (!defaultBranch) {
|
|
269
|
+
for (const candidate of ['main', 'master']) {
|
|
270
|
+
const exists = await execAsync(`git rev-parse --verify --quiet origin/${candidate}`, { cwd: targetPath, timeout: 5000 }).then(() => true).catch(() => false);
|
|
271
|
+
if (exists) {
|
|
272
|
+
defaultBranch = candidate;
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (!defaultBranch) {
|
|
278
|
+
throw new Error('could not determine default branch (no origin/HEAD, no main, no master)');
|
|
279
|
+
}
|
|
280
|
+
await execAsync(`git checkout --quiet "${defaultBranch}"`, { cwd: targetPath, timeout: 10 * 1000 });
|
|
281
|
+
await execAsync(`git reset --hard --quiet "origin/${defaultBranch}"`, { cwd: targetPath, timeout: 10 * 1000 });
|
|
282
|
+
alignedToHead = true;
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
alignError = error instanceof Error ? error.message : String(error);
|
|
286
|
+
}
|
|
287
|
+
if (!alignedToHead) {
|
|
288
|
+
// LOUD warning — stale content silently is the worst UX. Surface it.
|
|
289
|
+
(0, logger_1.logWarning)(`⚠️ Could not align ${repoUrl} to origin/HEAD — workspace may have stale content!`);
|
|
290
|
+
if (alignError)
|
|
291
|
+
(0, logger_1.logWarning)(` reason: ${alignError}`);
|
|
292
|
+
(0, logger_1.logWarning)(` manual fix: cd into the repo and run 'git pull --ff-only'`);
|
|
293
|
+
}
|
|
294
|
+
return { success: true, usedGhq: true };
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
298
|
+
(0, logger_1.logWarning)(`Local clone from ghq failed: ${errorMessage}`);
|
|
299
|
+
// Clean up partial clone before fallback
|
|
300
|
+
try {
|
|
301
|
+
await execAsync(`rm -rf "${targetPath}"`);
|
|
302
|
+
}
|
|
303
|
+
catch { /* ignore */ }
|
|
304
|
+
// Fall through to direct clone
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// Fallback to direct git clone
|
|
309
|
+
try {
|
|
310
|
+
await execAsync(`git clone "${repoUrl}" "${targetPath}"`, { timeout: config_1.CLONE_TIMEOUT_MS, maxBuffer: config_1.CLONE_MAX_BUFFER });
|
|
311
|
+
return { success: true, usedGhq: false };
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
return { success: false, error: describeCloneError(error), usedGhq: false };
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Show ghq status
|
|
319
|
+
*/
|
|
320
|
+
async function getGhqStatus() {
|
|
321
|
+
const installed = await isGhqInstalled();
|
|
322
|
+
if (!installed) {
|
|
323
|
+
return { installed: false };
|
|
324
|
+
}
|
|
325
|
+
const root = await getGhqRoot();
|
|
326
|
+
const repos = await ghqList();
|
|
327
|
+
return {
|
|
328
|
+
installed: true,
|
|
329
|
+
root: root || undefined,
|
|
330
|
+
repoCount: repos.length,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.reportCloneResults = exports.cloneRepositories = exports.cloneSingleRepo = void 0;
|
|
37
|
+
const child_process_1 = require("child_process");
|
|
38
|
+
const util_1 = require("util");
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const fs = __importStar(require("fs/promises"));
|
|
41
|
+
const logger_1 = require("./logger");
|
|
42
|
+
const colors_1 = require("./colors");
|
|
43
|
+
const ghq_integration_1 = require("./ghq-integration");
|
|
44
|
+
const retry_1 = require("./retry");
|
|
45
|
+
const progress_1 = require("./progress");
|
|
46
|
+
const config_1 = require("./config");
|
|
47
|
+
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
48
|
+
const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
|
|
49
|
+
const CONCURRENCY_LIMIT = 3;
|
|
50
|
+
const CLONE_TIMEOUT = config_1.CLONE_TIMEOUT_MS;
|
|
51
|
+
const cloneSingleRepo = async (repo, workspacePath, directoryName, useGhq = true, silent = false) => {
|
|
52
|
+
const repoPath = path.join(workspacePath, directoryName);
|
|
53
|
+
const cloneUrl = (0, config_1.getCloneUrl)(repo);
|
|
54
|
+
const displayName = directoryName !== repo.name
|
|
55
|
+
? `${(0, colors_1.colorize)(directoryName, 'cyan')} (${repo.name})`
|
|
56
|
+
: (0, colors_1.colorize)(repo.name, 'cyan');
|
|
57
|
+
try {
|
|
58
|
+
if (!silent)
|
|
59
|
+
(0, logger_1.logInfo)(`Cloning ${displayName}...`);
|
|
60
|
+
await (0, retry_1.withRetry)(async () => {
|
|
61
|
+
// Clean up partial clone before retry
|
|
62
|
+
try {
|
|
63
|
+
await fs.rm(repoPath, { recursive: true, force: true });
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// Ignore cleanup errors
|
|
67
|
+
}
|
|
68
|
+
if (useGhq) {
|
|
69
|
+
const result = await (0, ghq_integration_1.cloneWithGhq)(cloneUrl, repoPath);
|
|
70
|
+
if (!result.success) {
|
|
71
|
+
throw new Error(result.error || 'Clone failed');
|
|
72
|
+
}
|
|
73
|
+
if (!silent) {
|
|
74
|
+
if (result.usedGhq) {
|
|
75
|
+
(0, logger_1.logSuccess)(`Cloned ${displayName} ${(0, colors_1.colorize)('(via ghq)', 'gray')}`);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
(0, logger_1.logSuccess)(`Cloned ${displayName}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
try {
|
|
84
|
+
// execFile (no shell) so repo URLs / paths with spaces or shell
|
|
85
|
+
// metacharacters can't break or inject into the command.
|
|
86
|
+
await execFileAsync('git', ['clone', cloneUrl, repoPath], {
|
|
87
|
+
timeout: CLONE_TIMEOUT,
|
|
88
|
+
maxBuffer: config_1.CLONE_MAX_BUFFER,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
// execFile loses the reason on timeout/buffer kill — explain it.
|
|
93
|
+
throw new Error((0, ghq_integration_1.describeCloneError)(err, CLONE_TIMEOUT));
|
|
94
|
+
}
|
|
95
|
+
if (!silent)
|
|
96
|
+
(0, logger_1.logSuccess)(`Cloned ${displayName}`);
|
|
97
|
+
}
|
|
98
|
+
}, undefined, `Clone ${repo.name}`);
|
|
99
|
+
return {
|
|
100
|
+
repo,
|
|
101
|
+
directoryName,
|
|
102
|
+
status: 'success',
|
|
103
|
+
clonedAt: new Date().toISOString(),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
108
|
+
(0, logger_1.logError)(`Failed to clone ${displayName}: ${errorMessage}`);
|
|
109
|
+
return {
|
|
110
|
+
repo,
|
|
111
|
+
directoryName,
|
|
112
|
+
status: 'failed',
|
|
113
|
+
error: errorMessage,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
exports.cloneSingleRepo = cloneSingleRepo;
|
|
118
|
+
async function cloneLocal(repo, sourcePath, workspacePath, directoryName) {
|
|
119
|
+
const targetPath = path.join(workspacePath, directoryName);
|
|
120
|
+
try {
|
|
121
|
+
// git clone --local creates hardlinks for .git/objects — nearly instant
|
|
122
|
+
await execAsync(`git clone --local "${sourcePath}" "${targetPath}"`, {
|
|
123
|
+
timeout: CLONE_TIMEOUT,
|
|
124
|
+
maxBuffer: config_1.CLONE_MAX_BUFFER,
|
|
125
|
+
});
|
|
126
|
+
// Reset the remote to point to the original repo (not the local source)
|
|
127
|
+
const remoteUrl = (0, config_1.getCloneUrl)(repo);
|
|
128
|
+
await execAsync(`git remote set-url origin "${remoteUrl}"`, {
|
|
129
|
+
cwd: targetPath,
|
|
130
|
+
});
|
|
131
|
+
return {
|
|
132
|
+
repo,
|
|
133
|
+
directoryName,
|
|
134
|
+
status: 'success',
|
|
135
|
+
clonedAt: new Date().toISOString(),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
140
|
+
return {
|
|
141
|
+
repo,
|
|
142
|
+
directoryName,
|
|
143
|
+
status: 'failed',
|
|
144
|
+
error: errorMessage,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const cloneRepositories = async (repoEntries, workspacePath) => {
|
|
149
|
+
// Check if ghq is available
|
|
150
|
+
const hasGhq = await (0, ghq_integration_1.isGhqInstalled)();
|
|
151
|
+
console.log('\n' + '='.repeat(60));
|
|
152
|
+
console.log(`Cloning ${repoEntries.length} repositories (${CONCURRENCY_LIMIT} concurrent clones)`);
|
|
153
|
+
if (hasGhq) {
|
|
154
|
+
console.log((0, colors_1.colorize)('Using ghq for efficient repository management', 'cyan'));
|
|
155
|
+
}
|
|
156
|
+
console.log('='.repeat(60) + '\n');
|
|
157
|
+
const bar = (0, progress_1.createSimpleProgressBar)(repoEntries.length, 'Cloning');
|
|
158
|
+
bar.start(repoEntries.length, 0);
|
|
159
|
+
// Group entries by repo SSH URL to avoid duplicate clones
|
|
160
|
+
const repoGroups = new Map();
|
|
161
|
+
for (const entry of repoEntries) {
|
|
162
|
+
const key = entry.repo.sshUrl;
|
|
163
|
+
if (!repoGroups.has(key)) {
|
|
164
|
+
repoGroups.set(key, []);
|
|
165
|
+
}
|
|
166
|
+
repoGroups.get(key).push(entry);
|
|
167
|
+
}
|
|
168
|
+
const allResults = [];
|
|
169
|
+
let completed = 0;
|
|
170
|
+
// Process unique repo groups with concurrency (different repos clone in parallel)
|
|
171
|
+
const groups = Array.from(repoGroups.values());
|
|
172
|
+
for (let i = 0; i < groups.length; i += CONCURRENCY_LIMIT) {
|
|
173
|
+
const chunk = groups.slice(i, i + CONCURRENCY_LIMIT);
|
|
174
|
+
const chunkPromises = chunk.map(async (entries) => {
|
|
175
|
+
const groupResults = [];
|
|
176
|
+
// Clone first instance normally
|
|
177
|
+
const first = entries[0];
|
|
178
|
+
const firstResult = await (0, exports.cloneSingleRepo)(first.repo, workspacePath, first.directoryName, hasGhq, true);
|
|
179
|
+
groupResults.push(firstResult);
|
|
180
|
+
completed++;
|
|
181
|
+
bar.update(completed);
|
|
182
|
+
// Clone remaining instances via --local from the first
|
|
183
|
+
if (firstResult.status === 'success' && entries.length > 1) {
|
|
184
|
+
const sourcePath = path.join(workspacePath, first.directoryName);
|
|
185
|
+
for (const entry of entries.slice(1)) {
|
|
186
|
+
const result = await cloneLocal(entry.repo, sourcePath, workspacePath, entry.directoryName);
|
|
187
|
+
groupResults.push(result);
|
|
188
|
+
completed++;
|
|
189
|
+
bar.update(completed);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else if (firstResult.status === 'failed' && entries.length > 1) {
|
|
193
|
+
// Mark remaining as failed too
|
|
194
|
+
for (const entry of entries.slice(1)) {
|
|
195
|
+
groupResults.push({
|
|
196
|
+
repo: entry.repo,
|
|
197
|
+
directoryName: entry.directoryName,
|
|
198
|
+
status: 'failed',
|
|
199
|
+
error: 'Primary clone failed',
|
|
200
|
+
});
|
|
201
|
+
completed++;
|
|
202
|
+
bar.update(completed);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return groupResults;
|
|
206
|
+
});
|
|
207
|
+
const chunkResults = await Promise.all(chunkPromises);
|
|
208
|
+
for (const groupResults of chunkResults) {
|
|
209
|
+
allResults.push(...groupResults);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
bar.stop();
|
|
213
|
+
console.log('');
|
|
214
|
+
return allResults;
|
|
215
|
+
};
|
|
216
|
+
exports.cloneRepositories = cloneRepositories;
|
|
217
|
+
const reportCloneResults = (results) => {
|
|
218
|
+
const successful = results.filter(r => r.status === 'success');
|
|
219
|
+
const failed = results.filter(r => r.status === 'failed');
|
|
220
|
+
console.log('\n' + '='.repeat(60));
|
|
221
|
+
console.log('Clone Results Summary');
|
|
222
|
+
console.log('='.repeat(60));
|
|
223
|
+
console.log(`${(0, colors_1.colorize)('✓', 'green')} Successful: ${successful.length}`);
|
|
224
|
+
console.log(`${(0, colors_1.colorize)('✗', 'red')} Failed: ${failed.length}`);
|
|
225
|
+
console.log('='.repeat(60));
|
|
226
|
+
if (failed.length > 0) {
|
|
227
|
+
console.log('\nFailed repositories:');
|
|
228
|
+
failed.forEach(result => {
|
|
229
|
+
const displayName = result.directoryName !== result.repo.name
|
|
230
|
+
? `${result.directoryName} (${result.repo.name})`
|
|
231
|
+
: result.repo.name;
|
|
232
|
+
console.log(` ${(0, colors_1.colorize)('✗', 'red')} ${displayName}: ${result.error}`);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
console.log('');
|
|
236
|
+
};
|
|
237
|
+
exports.reportCloneResults = reportCloneResults;
|