pan-stack-cli 0.1.5 → 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/README.md +572 -24
- package/bin/pan.js +204 -21
- package/lib/ai-skills.js +829 -0
- package/lib/create.js +22 -4
- package/lib/discover.js +59 -1
- package/lib/git/git-checkout.js +395 -0
- package/lib/git/git-clone.js +162 -27
- package/lib/git/git-fetch-all.js +100 -33
- package/lib/git/git-grv.js +68 -37
- package/lib/git/git-gst.js +97 -26
- package/lib/git/git-mr.js +546 -0
- package/lib/git/git-pull.js +104 -43
- package/lib/git/git-push.js +150 -41
- package/lib/git/git-sync-main.js +42 -28
- package/lib/git/helpers/bulk-report.js +202 -0
- package/lib/git/helpers/cli.js +40 -5
- package/lib/git/helpers/manifest.js +93 -0
- package/lib/git/helpers/repo-select.js +328 -9
- package/lib/git/helpers/repo-summary.js +161 -0
- package/lib/i18n.js +61 -0
- package/lib/inquirer-theme.js +20 -0
- package/lib/language.js +121 -0
- package/lib/languages.js +14 -0
- package/lib/manifest.js +115 -14
- package/lib/menu-prompt.js +201 -0
- package/lib/menu.js +134 -0
- package/lib/messages.js +647 -0
- package/lib/run-command.js +173 -52
- package/lib/select.js +0 -0
- package/lib/serve.js +75 -0
- package/lib/skill-registry-client.js +173 -0
- package/lib/stats/aggregate.js +51 -0
- package/lib/stats/identity.js +20 -0
- package/lib/stats/index.js +189 -0
- package/lib/stats/parse.js +46 -0
- package/lib/stats/range.js +38 -0
- package/lib/stats/report.js +264 -0
- package/lib/stats/review.js +157 -0
- package/lib/stats/scan.js +92 -0
- package/lib/stats/serve.js +25 -0
- package/lib/ui/api.js +202 -0
- package/lib/ui/clone.js +115 -0
- package/lib/ui/config.js +182 -0
- package/lib/ui/diff.js +128 -0
- package/lib/ui/export-gate.js +79 -0
- package/lib/ui/gen-react-mobile-code.js +6 -0
- package/lib/ui/gen-react-web-code.js +75 -0
- package/lib/ui/index.js +89 -0
- package/lib/ui/init.js +252 -0
- package/lib/ui/login.js +128 -0
- package/lib/ui/logout.js +22 -0
- package/lib/ui/palettes.js +103 -0
- package/lib/ui/published-html.js +24 -0
- package/lib/ui/sync.js +137 -0
- package/lib/ui/workspace.js +213 -0
- package/package.json +10 -3
- package/templates/workspace/.claude/skills/writing-guidelines/SKILL.md +0 -48
- package/templates/workspace/.codex/skills/writing-guidelines/SKILL.md +0 -48
- package/templates/workspace/.cursor/skills/writing-guidelines/SKILL.md +0 -48
- package/templates/workspace/.pan.yaml +0 -25
- package/templates/workspace/.project.yaml +0 -15
- package/templates/workspace/README.md +0 -35
package/lib/git/git-pull.js
CHANGED
|
@@ -1,39 +1,80 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/*
|
|
3
|
-
* Pull latest changes for PAN Stack project repositories
|
|
4
|
-
*
|
|
5
|
-
* Skips repositories that do not exist or are not git checkouts. Failures warn
|
|
6
|
-
* and continue so one broken repo does not block the rest.
|
|
3
|
+
* Pull latest changes for PAN Stack project repositories.
|
|
4
|
+
* In compact summary table: repo · branch · result · changes · conflict.
|
|
7
5
|
*/
|
|
8
6
|
|
|
9
7
|
const { parseCommonArgs, loadManifestRepos, printCommonOptions } = require("./helpers/cli");
|
|
10
|
-
const { ensureRepoExists
|
|
8
|
+
const { ensureRepoExists } = require("./helpers/git");
|
|
9
|
+
const {
|
|
10
|
+
runGitSilent, parsePullOutput, printSummary, statusBadge, bulkColumns, pullModeLabel,
|
|
11
|
+
getCurrentBranch, listConflictedFiles,
|
|
12
|
+
} = require("./helpers/bulk-report");
|
|
13
|
+
const colors = require("./helpers/colors");
|
|
14
|
+
const { t } = require("../i18n");
|
|
11
15
|
|
|
12
16
|
function pullRepo({ name, destination, dryRun, rebase }) {
|
|
13
17
|
if (!ensureRepoExists({ name, destination })) {
|
|
14
|
-
return;
|
|
18
|
+
return { name, skipped: true };
|
|
15
19
|
}
|
|
20
|
+
const branch = getCurrentBranch(destination) || t("GST_DETACHED");
|
|
21
|
+
const gitArgs = ["pull", "--stat"];
|
|
22
|
+
if (rebase) gitArgs.push("--rebase");
|
|
16
23
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
24
|
+
if (dryRun) {
|
|
25
|
+
console.log(colors.dim(`[dry-run] (in ${destination}) git ${gitArgs.join(" ")}`));
|
|
26
|
+
return { name, branch, dryRun: true };
|
|
20
27
|
}
|
|
21
28
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
process.stdout.write(colors.dim(` ↻ ${name} (${branch}) ... `));
|
|
30
|
+
const res = runGitSilent(gitArgs, destination);
|
|
31
|
+
const parsed = parsePullOutput(res.stdout, res.stderr);
|
|
32
|
+
const conflictFiles = parsed.conflict ? listConflictedFiles(destination) : [];
|
|
33
|
+
const ok = res.status === 0 && !parsed.conflict;
|
|
34
|
+
|
|
35
|
+
const shortMsg = parsed.upToDate ? t("BULK_STATUS_UP_TO_DATE")
|
|
36
|
+
: parsed.conflict ? t("BULK_STATUS_CONFLICT")
|
|
37
|
+
: parsed.mode !== "unknown" ? pullModeLabel(parsed.mode)
|
|
38
|
+
: ok ? t("BULK_STATUS_PULLED") : t("BULK_STATUS_FAILED");
|
|
39
|
+
console.log((ok ? colors.green : parsed.conflict ? colors.yellow : colors.red)(shortMsg));
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
name, branch,
|
|
43
|
+
status: res.status,
|
|
44
|
+
upToDate: parsed.upToDate,
|
|
45
|
+
conflict: parsed.conflict,
|
|
46
|
+
conflictFiles,
|
|
47
|
+
mode: parsed.mode,
|
|
48
|
+
filesChanged: parsed.filesChanged,
|
|
49
|
+
insertions: parsed.insertions,
|
|
50
|
+
deletions: parsed.deletions,
|
|
51
|
+
error: res.status !== 0 && !parsed.conflict ? (res.stderr.split("\n")[0] || "").slice(0, 80) : "",
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function row(r) {
|
|
56
|
+
if (r.skipped) return { repo: colors.dim(r.name), branch: "", result: colors.dim(t("BULK_STATUS_SKIPPED")), changes: "", conflict: "" };
|
|
57
|
+
if (r.dryRun) return { repo: r.name, branch: r.branch, result: colors.dim(t("BULK_STATUS_DRY_RUN")), changes: "", conflict: "" };
|
|
58
|
+
const result =
|
|
59
|
+
r.upToDate ? statusBadge("ok", t("BULK_STATUS_UP_TO_DATE"))
|
|
60
|
+
: r.conflict ? statusBadge("warn", t("BULK_STATUS_CONFLICT"))
|
|
61
|
+
: r.error ? statusBadge("fail", t("BULK_STATUS_FAILED"))
|
|
62
|
+
: statusBadge("ok", r.mode && r.mode !== "unknown" ? pullModeLabel(r.mode) : t("BULK_STATUS_PULLED"));
|
|
63
|
+
const filesLabel = r.filesChanged === 1 ? t("BULK_FILES_ONE") : t("BULK_FILES_MANY");
|
|
64
|
+
const changes = r.filesChanged
|
|
65
|
+
? `${r.filesChanged} ${filesLabel} ${colors.green("+" + r.insertions)} ${colors.red("−" + r.deletions)}`
|
|
66
|
+
: colors.dim("—");
|
|
67
|
+
const conflict = r.conflict
|
|
68
|
+
? colors.yellow(`${r.conflictFiles.length} ${r.conflictFiles.length === 1 ? t("BULK_FILES_ONE") : t("BULK_FILES_MANY")}`)
|
|
69
|
+
: colors.dim("—");
|
|
70
|
+
return { repo: r.name, branch: r.branch, result, changes, conflict };
|
|
31
71
|
}
|
|
32
72
|
|
|
33
73
|
function printHelp() {
|
|
34
74
|
console.log(`usage: git-pull.js [--base-dir BASE_DIR] [--dry-run] [--rebase] [manifest]
|
|
35
75
|
|
|
36
76
|
Pull latest changes for repositories listed in a PAN Stack clone manifest.
|
|
77
|
+
Prints a compact summary table (repo · branch · result · changes · conflict).
|
|
37
78
|
|
|
38
79
|
${printCommonOptions()}
|
|
39
80
|
--rebase Use git pull --rebase`);
|
|
@@ -42,35 +83,55 @@ ${printCommonOptions()}
|
|
|
42
83
|
function main() {
|
|
43
84
|
const args = parseCommonArgs(process.argv.slice(2), {
|
|
44
85
|
defaults: { rebase: false },
|
|
45
|
-
handlers: {
|
|
46
|
-
"--rebase": (parsed) => {
|
|
47
|
-
parsed.rebase = true;
|
|
48
|
-
},
|
|
49
|
-
},
|
|
86
|
+
handlers: { "--rebase": (p) => { p.rebase = true; } },
|
|
50
87
|
});
|
|
51
|
-
if (args.help) {
|
|
52
|
-
printHelp();
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
88
|
+
if (args.help) { printHelp(); return; }
|
|
55
89
|
|
|
56
|
-
const { repos } = loadManifestRepos({
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
90
|
+
const { repos } = loadManifestRepos({ manifestPath: args.manifest, baseDir: args.baseDir });
|
|
91
|
+
const results = repos.map((r) => pullRepo({ name: r.name, destination: r.destination, dryRun: args.dryRun, rebase: args.rebase }));
|
|
92
|
+
|
|
93
|
+
const conflicts = results.filter((r) => r.conflict);
|
|
94
|
+
const pulled = results.filter((r) => !r.skipped && !r.dryRun && !r.upToDate && !r.conflict && !r.error);
|
|
95
|
+
const failed = results.filter((r) => r.error);
|
|
96
|
+
const upToDate = results.filter((r) => r.upToDate);
|
|
97
|
+
// Chỉ đưa vào bảng những repo có thay đổi / conflict / lỗi / dry-run (bỏ up-to-date).
|
|
98
|
+
const shown = results.filter((r) => r.dryRun || (!r.skipped && !r.upToDate));
|
|
99
|
+
const rows = shown.map(row);
|
|
100
|
+
|
|
101
|
+
const footer = [];
|
|
102
|
+
footer.push(colors.dim(t("PULL_FOOTER_COUNTS__TOTAL_CHANGED_UPTODATE_CONFLICT_FAILED", {
|
|
103
|
+
total: results.length, changed: pulled.length, upToDate: upToDate.length,
|
|
104
|
+
conflict: conflicts.length, failed: failed.length,
|
|
105
|
+
})));
|
|
106
|
+
if (conflicts.length) {
|
|
107
|
+
footer.push("");
|
|
108
|
+
footer.push(colors.bold(colors.yellow(t("PULL_CONFLICT_HEADER"))));
|
|
109
|
+
for (const c of conflicts) {
|
|
110
|
+
footer.push(` ${colors.cyan(c.name)} (${c.branch}): ${c.conflictFiles.join(", ") || "(git status)"}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
60
113
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
114
|
+
if (rows.length === 0) {
|
|
115
|
+
console.log("");
|
|
116
|
+
console.log(colors.bold(`─── ${t("PULL_TITLE")} ───`));
|
|
117
|
+
console.log(colors.green(t("PULL_ALL_UP_TO_DATE__N", { n: results.length })));
|
|
118
|
+
for (const line of footer) console.log(line);
|
|
119
|
+
} else {
|
|
120
|
+
printSummary({
|
|
121
|
+
title: t("PULL_TITLE"),
|
|
122
|
+
rows,
|
|
123
|
+
columns: bulkColumns([
|
|
124
|
+
{ key: "repo", labelKey: "BULK_COL_REPO" },
|
|
125
|
+
{ key: "branch", labelKey: "BULK_COL_BRANCH" },
|
|
126
|
+
{ key: "result", labelKey: "BULK_COL_RESULT" },
|
|
127
|
+
{ key: "changes", labelKey: "BULK_COL_CHANGES" },
|
|
128
|
+
{ key: "conflict", labelKey: "BULK_COL_CONFLICT" },
|
|
129
|
+
]),
|
|
130
|
+
footerLines: footer,
|
|
67
131
|
});
|
|
68
132
|
}
|
|
69
|
-
}
|
|
70
133
|
|
|
71
|
-
|
|
72
|
-
main();
|
|
73
|
-
} catch (error) {
|
|
74
|
-
console.error(error.message);
|
|
75
|
-
process.exit(1);
|
|
134
|
+
if (conflicts.length || failed.length) process.exitCode = 1;
|
|
76
135
|
}
|
|
136
|
+
|
|
137
|
+
try { main(); } catch (error) { console.error(error.message); process.exit(1); }
|
package/lib/git/git-push.js
CHANGED
|
@@ -1,39 +1,104 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/*
|
|
3
|
-
* Push local commits for PAN Stack project repositories
|
|
4
|
-
*
|
|
5
|
-
* Skips repositories that do not exist or are not git checkouts. Failures warn
|
|
6
|
-
* and continue so one broken repo does not block the rest.
|
|
3
|
+
* Push local commits for PAN Stack project repositories.
|
|
4
|
+
* Compact summary table: repo · branch · result · commits · MR/PR link.
|
|
7
5
|
*/
|
|
8
6
|
|
|
9
7
|
const { parseCommonArgs, loadManifestRepos, printCommonOptions } = require("./helpers/cli");
|
|
10
|
-
const { ensureRepoExists
|
|
8
|
+
const { ensureRepoExists } = require("./helpers/git");
|
|
9
|
+
const {
|
|
10
|
+
runGitSilent, parsePushOutput, printSummary, statusBadge, bulkColumns,
|
|
11
|
+
aheadBehind, aheadLineStats, getCurrentBranch,
|
|
12
|
+
} = require("./helpers/bulk-report");
|
|
13
|
+
const colors = require("./helpers/colors");
|
|
14
|
+
const { t } = require("../i18n");
|
|
15
|
+
|
|
16
|
+
function formatChanges(r) {
|
|
17
|
+
if (!r.filesChanged && !r.insertions && !r.deletions) return colors.dim("—");
|
|
18
|
+
const filesLabel = r.filesChanged === 1 ? t("BULK_FILES_ONE") : t("BULK_FILES_MANY");
|
|
19
|
+
const filesPart = r.filesChanged
|
|
20
|
+
? `${r.filesChanged} ${filesLabel} `
|
|
21
|
+
: "";
|
|
22
|
+
return `${filesPart}${colors.green("+" + (r.insertions || 0))} ${colors.red("−" + (r.deletions || 0))}`;
|
|
23
|
+
}
|
|
11
24
|
|
|
12
25
|
function pushRepo({ name, destination, dryRun, setUpstream }) {
|
|
13
26
|
if (!ensureRepoExists({ name, destination })) {
|
|
14
|
-
return;
|
|
27
|
+
return { name, skipped: true };
|
|
15
28
|
}
|
|
29
|
+
const branch = getCurrentBranch(destination) || t("GST_DETACHED");
|
|
30
|
+
// Đếm commit + line change chờ push TRƯỚC push (sau push @{u} đã bắt kịp HEAD).
|
|
31
|
+
const ab = aheadBehind(destination);
|
|
32
|
+
const commitsAhead = ab ? ab.ahead : 0;
|
|
33
|
+
const lines = commitsAhead > 0
|
|
34
|
+
? aheadLineStats(destination)
|
|
35
|
+
: { insertions: 0, deletions: 0, filesChanged: 0 };
|
|
16
36
|
|
|
17
37
|
const gitArgs = ["push"];
|
|
18
|
-
if (setUpstream)
|
|
19
|
-
|
|
38
|
+
if (setUpstream) gitArgs.push("-u", "origin", "HEAD");
|
|
39
|
+
|
|
40
|
+
if (dryRun) {
|
|
41
|
+
console.log(colors.dim(`[dry-run] (in ${destination}) git ${gitArgs.join(" ")}`));
|
|
42
|
+
return { name, branch, dryRun: true, commitsAhead, ...lines };
|
|
20
43
|
}
|
|
21
44
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
45
|
+
process.stdout.write(colors.dim(` ↑ ${name} (${branch}) ... `));
|
|
46
|
+
const res = runGitSilent(gitArgs, destination);
|
|
47
|
+
const parsed = parsePushOutput(res.stdout, res.stderr);
|
|
48
|
+
const ok = res.status === 0 && !parsed.rejected;
|
|
49
|
+
const shortMsg = parsed.rejected ? t("BULK_STATUS_REJECTED")
|
|
50
|
+
: parsed.upToDate ? t("BULK_STATUS_UP_TO_DATE")
|
|
51
|
+
: parsed.pushed ? t("BULK_STATUS_PUSHED")
|
|
52
|
+
: ok ? t("BULK_STATUS_OK")
|
|
53
|
+
: t("BULK_STATUS_FAILED");
|
|
54
|
+
console.log((ok ? (parsed.upToDate ? colors.dim : colors.green) : colors.red)(shortMsg));
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
name, branch, commitsAhead,
|
|
58
|
+
insertions: lines.insertions,
|
|
59
|
+
deletions: lines.deletions,
|
|
60
|
+
filesChanged: lines.filesChanged,
|
|
61
|
+
status: res.status,
|
|
62
|
+
pushed: parsed.pushed,
|
|
63
|
+
upToDate: parsed.upToDate,
|
|
64
|
+
rejected: parsed.rejected,
|
|
65
|
+
mrLink: parsed.mrLink,
|
|
66
|
+
refs: parsed.refs,
|
|
67
|
+
error: res.status !== 0 && !parsed.rejected ? (res.stderr.split("\n")[0] || "").slice(0, 80) : "",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function row(r) {
|
|
72
|
+
if (r.skipped) {
|
|
73
|
+
return {
|
|
74
|
+
repo: colors.dim(r.name), branch: "", result: colors.dim(t("BULK_STATUS_SKIPPED")),
|
|
75
|
+
commits: "", changes: "", link: "",
|
|
76
|
+
};
|
|
30
77
|
}
|
|
78
|
+
if (r.dryRun) {
|
|
79
|
+
return {
|
|
80
|
+
repo: r.name, branch: r.branch, result: colors.dim(t("BULK_STATUS_DRY_RUN")),
|
|
81
|
+
commits: String(r.commitsAhead || 0), changes: formatChanges(r), link: "",
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const result =
|
|
85
|
+
r.rejected ? statusBadge("fail", t("BULK_STATUS_REJECTED"))
|
|
86
|
+
: r.upToDate ? statusBadge("skip", t("BULK_STATUS_UP_TO_DATE"))
|
|
87
|
+
: r.error ? statusBadge("fail", t("BULK_STATUS_FAILED"))
|
|
88
|
+
: r.pushed ? statusBadge("ok", t("BULK_STATUS_PUSHED"))
|
|
89
|
+
: statusBadge("ok", t("BULK_STATUS_OK"));
|
|
90
|
+
const commits = r.commitsAhead
|
|
91
|
+
? colors.cyan(String(r.commitsAhead))
|
|
92
|
+
: colors.dim("—");
|
|
93
|
+
const link = r.mrLink ? colors.cyan(r.mrLink) : colors.dim("—");
|
|
94
|
+
return { repo: r.name, branch: r.branch, result, commits, changes: formatChanges(r), link };
|
|
31
95
|
}
|
|
32
96
|
|
|
33
97
|
function printHelp() {
|
|
34
98
|
console.log(`usage: git-push.js [--base-dir BASE_DIR] [--dry-run] [--set-upstream] [manifest]
|
|
35
99
|
|
|
36
100
|
Push local commits for repositories listed in a PAN Stack clone manifest.
|
|
101
|
+
Prints a compact summary table (repo · branch · result · commits · changes · MR/PR link).
|
|
37
102
|
|
|
38
103
|
${printCommonOptions()}
|
|
39
104
|
--set-upstream Use git push -u origin HEAD for new branches`);
|
|
@@ -42,35 +107,79 @@ ${printCommonOptions()}
|
|
|
42
107
|
function main() {
|
|
43
108
|
const args = parseCommonArgs(process.argv.slice(2), {
|
|
44
109
|
defaults: { setUpstream: false },
|
|
45
|
-
handlers: {
|
|
46
|
-
"--set-upstream": (parsed) => {
|
|
47
|
-
parsed.setUpstream = true;
|
|
48
|
-
},
|
|
49
|
-
},
|
|
110
|
+
handlers: { "--set-upstream": (p) => { p.setUpstream = true; } },
|
|
50
111
|
});
|
|
51
|
-
if (args.help) {
|
|
52
|
-
printHelp();
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
112
|
+
if (args.help) { printHelp(); return; }
|
|
55
113
|
|
|
56
|
-
const { repos } = loadManifestRepos({
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
114
|
+
const { repos } = loadManifestRepos({ manifestPath: args.manifest, baseDir: args.baseDir });
|
|
115
|
+
const results = repos.map((r) => pushRepo({ name: r.name, destination: r.destination, dryRun: args.dryRun, setUpstream: args.setUpstream }));
|
|
116
|
+
|
|
117
|
+
const pushed = results.filter((r) => r.pushed);
|
|
118
|
+
const rejected = results.filter((r) => r.rejected);
|
|
119
|
+
const failed = results.filter((r) => r.error);
|
|
120
|
+
const upToDate = results.filter((r) => r.upToDate);
|
|
121
|
+
const withLink = results.filter((r) => r.mrLink);
|
|
122
|
+
// Chỉ đưa vào bảng: pushed / rejected / có MR link / lỗi / dry-run (bỏ up-to-date).
|
|
123
|
+
const shown = results.filter((r) => r.dryRun || r.pushed || r.rejected || r.error || r.mrLink);
|
|
124
|
+
const rows = shown.map(row);
|
|
125
|
+
|
|
126
|
+
const linesOf = (list) => list.reduce(
|
|
127
|
+
(acc, r) => ({
|
|
128
|
+
commits: acc.commits + (r.commitsAhead || 0),
|
|
129
|
+
insertions: acc.insertions + (r.insertions || 0),
|
|
130
|
+
deletions: acc.deletions + (r.deletions || 0),
|
|
131
|
+
}),
|
|
132
|
+
{ commits: 0, insertions: 0, deletions: 0 }
|
|
133
|
+
);
|
|
134
|
+
const pushedLines = linesOf(pushed.length ? pushed : shown.filter((r) => r.dryRun && r.commitsAhead));
|
|
60
135
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
136
|
+
const footer = [];
|
|
137
|
+
footer.push(colors.dim(t("PUSH_FOOTER_COUNTS__TOTAL_PUSHED_UPTODATE_REJECTED_FAILED", {
|
|
138
|
+
total: results.length, pushed: pushed.length, upToDate: upToDate.length,
|
|
139
|
+
rejected: rejected.length, failed: failed.length,
|
|
140
|
+
})));
|
|
141
|
+
if (pushedLines.commits > 0 || pushedLines.insertions > 0 || pushedLines.deletions > 0) {
|
|
142
|
+
footer.push(colors.dim(t("PUSH_FOOTER_LINES__COMMITS_ADD_DEL", {
|
|
143
|
+
commits: pushedLines.commits,
|
|
144
|
+
add: pushedLines.insertions,
|
|
145
|
+
del: pushedLines.deletions,
|
|
146
|
+
})));
|
|
147
|
+
}
|
|
148
|
+
if (withLink.length) {
|
|
149
|
+
footer.push("");
|
|
150
|
+
footer.push(colors.bold(t("PUSH_LINKS_HEADER")));
|
|
151
|
+
for (const r of withLink) {
|
|
152
|
+
footer.push(` ${colors.cyan(r.name)} (${r.branch}): ${r.mrLink}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (rejected.length) {
|
|
156
|
+
footer.push("");
|
|
157
|
+
footer.push(colors.yellow(t("PUSH_REJECTED_HEADER")));
|
|
158
|
+
for (const r of rejected) footer.push(` ${r.name}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (rows.length === 0) {
|
|
162
|
+
console.log("");
|
|
163
|
+
console.log(colors.bold(`─── ${t("PUSH_TITLE")} ───`));
|
|
164
|
+
console.log(colors.green(t("PUSH_ALL_UP_TO_DATE__N", { n: results.length })));
|
|
165
|
+
for (const line of footer) console.log(line);
|
|
166
|
+
} else {
|
|
167
|
+
printSummary({
|
|
168
|
+
title: t("PUSH_TITLE"),
|
|
169
|
+
rows,
|
|
170
|
+
columns: bulkColumns([
|
|
171
|
+
{ key: "repo", labelKey: "BULK_COL_REPO" },
|
|
172
|
+
{ key: "branch", labelKey: "BULK_COL_BRANCH" },
|
|
173
|
+
{ key: "result", labelKey: "BULK_COL_RESULT" },
|
|
174
|
+
{ key: "commits", labelKey: "BULK_COL_COMMITS", align: "r" },
|
|
175
|
+
{ key: "changes", labelKey: "BULK_COL_CHANGES" },
|
|
176
|
+
{ key: "link", labelKey: "BULK_COL_LINK" },
|
|
177
|
+
]),
|
|
178
|
+
footerLines: footer,
|
|
67
179
|
});
|
|
68
180
|
}
|
|
69
|
-
}
|
|
70
181
|
|
|
71
|
-
|
|
72
|
-
main();
|
|
73
|
-
} catch (error) {
|
|
74
|
-
console.error(error.message);
|
|
75
|
-
process.exit(1);
|
|
182
|
+
if (rejected.length || failed.length) process.exitCode = 1;
|
|
76
183
|
}
|
|
184
|
+
|
|
185
|
+
try { main(); } catch (error) { console.error(error.message); process.exit(1); }
|
package/lib/git/git-sync-main.js
CHANGED
|
@@ -13,6 +13,7 @@ const {
|
|
|
13
13
|
} = require("./helpers/cli");
|
|
14
14
|
const colors = require("./helpers/colors");
|
|
15
15
|
const { resolveTargets } = require("./helpers/repo-select");
|
|
16
|
+
const { t } = require("../i18n");
|
|
16
17
|
const {
|
|
17
18
|
ensureRepoExists,
|
|
18
19
|
runGit,
|
|
@@ -93,14 +94,16 @@ function printEarlyConflict({ name, branch, ref, files, mode }) {
|
|
|
93
94
|
colors.red(`${ICON.conflict} [conflict] ${name} (${branch})`)
|
|
94
95
|
);
|
|
95
96
|
const detail = colors.yellow(
|
|
96
|
-
|
|
97
|
+
t("SYNCMAIN_CONFLICT_SHORT__MODE_REF_N", { mode, ref, n: files.length })
|
|
97
98
|
);
|
|
98
99
|
console.log(`${headline} — ${detail}`);
|
|
99
100
|
for (const file of files.slice(0, 8)) {
|
|
100
101
|
console.log(colors.dim(` • ${file}`));
|
|
101
102
|
}
|
|
102
103
|
if (files.length > 8) {
|
|
103
|
-
console.log(
|
|
104
|
+
console.log(
|
|
105
|
+
colors.dim(` ${t("SYNCMAIN_MORE_FILES__N", { n: files.length - 8 })}`)
|
|
106
|
+
);
|
|
104
107
|
}
|
|
105
108
|
}
|
|
106
109
|
|
|
@@ -122,14 +125,14 @@ function syncMainRepo({
|
|
|
122
125
|
};
|
|
123
126
|
|
|
124
127
|
if (!ensureRepoExists({ name, destination })) {
|
|
125
|
-
result.message = "
|
|
128
|
+
result.message = t("SYNCMAIN_MSG_NOT_CLONED");
|
|
126
129
|
return result;
|
|
127
130
|
}
|
|
128
131
|
|
|
129
132
|
const branch = getCurrentBranch(destination);
|
|
130
|
-
result.branch = branch || "
|
|
133
|
+
result.branch = branch || t("SYNCMAIN_DETACHED_LABEL");
|
|
131
134
|
if (!branch) {
|
|
132
|
-
result.message = "
|
|
135
|
+
result.message = t("SYNCMAIN_MSG_DETACHED");
|
|
133
136
|
console.log(`${ICON.skip} [skip] ${name}: ${result.message}`);
|
|
134
137
|
return result;
|
|
135
138
|
}
|
|
@@ -139,7 +142,7 @@ function syncMainRepo({
|
|
|
139
142
|
const files = listConflictedFiles(destination);
|
|
140
143
|
result.status = "conflict";
|
|
141
144
|
result.conflictFiles = files;
|
|
142
|
-
result.message =
|
|
145
|
+
result.message = t("SYNCMAIN_MSG_IN_PROGRESS__MODE", { mode: inProgress });
|
|
143
146
|
printEarlyConflict({ name, branch, ref, files, mode: inProgress });
|
|
144
147
|
return result;
|
|
145
148
|
}
|
|
@@ -147,7 +150,7 @@ function syncMainRepo({
|
|
|
147
150
|
const dirtyCount = countDirtyFiles(destination);
|
|
148
151
|
if (dirtyCount > 0) {
|
|
149
152
|
console.log(
|
|
150
|
-
`${ICON.warn} ${colors.yellow(
|
|
153
|
+
`${ICON.warn} ${colors.yellow(t("SYNCMAIN_MSG_DIRTY__NAME_N", { name, n: dirtyCount }))}`
|
|
151
154
|
);
|
|
152
155
|
}
|
|
153
156
|
|
|
@@ -164,14 +167,14 @@ function syncMainRepo({
|
|
|
164
167
|
const fetch = runGitCapture(["fetch", "origin"], { cwd: destination });
|
|
165
168
|
if (fetch.status !== 0) {
|
|
166
169
|
result.status = "error";
|
|
167
|
-
result.message = fetch.stderr || "
|
|
170
|
+
result.message = fetch.stderr || t("SYNCMAIN_MSG_FETCH_FAILED");
|
|
168
171
|
console.log(`${ICON.warn} ${colors.yellow(`[warn] ${name}: ${result.message}`)}`);
|
|
169
172
|
return result;
|
|
170
173
|
}
|
|
171
174
|
|
|
172
175
|
if (!refExists(destination, ref)) {
|
|
173
176
|
result.status = "error";
|
|
174
|
-
result.message =
|
|
177
|
+
result.message = t("SYNCMAIN_MSG_REF_MISSING__REF", { ref });
|
|
175
178
|
console.log(`${ICON.warn} ${colors.yellow(`[warn] ${name}: ${result.message}`)}`);
|
|
176
179
|
return result;
|
|
177
180
|
}
|
|
@@ -188,8 +191,9 @@ function syncMainRepo({
|
|
|
188
191
|
const conflictFiles = listConflictedFiles(destination);
|
|
189
192
|
if (conflictFiles.length > 0 || action !== 0) {
|
|
190
193
|
result.status = "conflict";
|
|
191
|
-
result.conflictFiles =
|
|
192
|
-
|
|
194
|
+
result.conflictFiles =
|
|
195
|
+
conflictFiles.length > 0 ? conflictFiles : [t("SYNCMAIN_MSG_SEE_STATUS")];
|
|
196
|
+
result.message = t("SYNCMAIN_MSG_CONFLICT__MODE", { mode: result.mode });
|
|
193
197
|
printEarlyConflict({
|
|
194
198
|
name,
|
|
195
199
|
branch,
|
|
@@ -201,8 +205,10 @@ function syncMainRepo({
|
|
|
201
205
|
}
|
|
202
206
|
|
|
203
207
|
result.status = "ok";
|
|
204
|
-
result.message = "
|
|
205
|
-
console.log(
|
|
208
|
+
result.message = t("SYNCMAIN_MSG_UPDATED");
|
|
209
|
+
console.log(
|
|
210
|
+
`${ICON.ok} ${colors.green(t("SYNCMAIN_MSG_SYNCED__NAME_BRANCH_REF", { name, branch, ref }))}`
|
|
211
|
+
);
|
|
206
212
|
return result;
|
|
207
213
|
}
|
|
208
214
|
|
|
@@ -215,17 +221,23 @@ function printSummary(results, { ref, rebase }) {
|
|
|
215
221
|
);
|
|
216
222
|
|
|
217
223
|
console.log("");
|
|
218
|
-
console.log(colors.bold("
|
|
224
|
+
console.log(colors.bold(t("SYNCMAIN_HEADER")));
|
|
219
225
|
console.log(
|
|
220
|
-
`${ICON.info}
|
|
226
|
+
`${ICON.info} ${t("SYNCMAIN_INFO__REF_MODE_REPOS", {
|
|
227
|
+
ref, mode: rebase ? "rebase" : "merge", repos: results.length,
|
|
228
|
+
})}`
|
|
221
229
|
);
|
|
222
230
|
console.log(
|
|
223
|
-
|
|
231
|
+
t("SYNCMAIN_COUNTS__OK_CONFLICT_ERR_SKIP", {
|
|
232
|
+
ok: ok.length, conflict: conflicts.length, err: errors.length, skip: skipped.length,
|
|
233
|
+
})
|
|
224
234
|
);
|
|
225
235
|
|
|
226
236
|
if (conflicts.length > 0) {
|
|
227
237
|
console.log("");
|
|
228
|
-
console.log(
|
|
238
|
+
console.log(
|
|
239
|
+
colors.bold(colors.red(`${ICON.conflict} ${t("SYNCMAIN_CONFLICT_HEADER")}`))
|
|
240
|
+
);
|
|
229
241
|
for (const item of conflicts) {
|
|
230
242
|
console.log(colors.yellow(`\n ${item.name} (${item.branch})`));
|
|
231
243
|
console.log(colors.dim(` ${item.destination}`));
|
|
@@ -233,15 +245,15 @@ function printSummary(results, { ref, rebase }) {
|
|
|
233
245
|
console.log(` • ${file}`);
|
|
234
246
|
}
|
|
235
247
|
const hint = item.mode === "rebase"
|
|
236
|
-
? "
|
|
237
|
-
: "
|
|
248
|
+
? t("SYNCMAIN_HINT_REBASE")
|
|
249
|
+
: t("SYNCMAIN_HINT_MERGE");
|
|
238
250
|
console.log(colors.dim(` → ${hint}`));
|
|
239
251
|
}
|
|
240
252
|
}
|
|
241
253
|
|
|
242
254
|
if (errors.length > 0) {
|
|
243
255
|
console.log("");
|
|
244
|
-
console.log(colors.bold(colors.yellow(`${ICON.warn}
|
|
256
|
+
console.log(colors.bold(colors.yellow(`${ICON.warn} ${t("SYNCMAIN_ERRORS_HEADER")}`)));
|
|
245
257
|
for (const item of errors) {
|
|
246
258
|
console.log(` • ${item.name}: ${item.message}`);
|
|
247
259
|
}
|
|
@@ -250,20 +262,18 @@ function printSummary(results, { ref, rebase }) {
|
|
|
250
262
|
if (conflicts.length > 0) {
|
|
251
263
|
console.log("");
|
|
252
264
|
console.log(
|
|
253
|
-
colors.bold(
|
|
254
|
-
colors.red(
|
|
255
|
-
`${ICON.conflict} Kết thúc với conflict — resolve từng repo ở trên rồi commit/rebase --continue`
|
|
256
|
-
)
|
|
257
|
-
)
|
|
265
|
+
colors.bold(colors.red(`${ICON.conflict} ${t("SYNCMAIN_END_CONFLICT")}`))
|
|
258
266
|
);
|
|
259
267
|
} else if (errors.length > 0) {
|
|
260
268
|
console.log("");
|
|
261
269
|
console.log(
|
|
262
|
-
colors.bold(colors.yellow(`${ICON.warn}
|
|
270
|
+
colors.bold(colors.yellow(`${ICON.warn} ${t("SYNCMAIN_END_ERROR")}`))
|
|
263
271
|
);
|
|
264
272
|
} else if (ok.length > 0) {
|
|
265
273
|
console.log("");
|
|
266
|
-
console.log(
|
|
274
|
+
console.log(
|
|
275
|
+
colors.green(`${ICON.ok} ${t("SYNCMAIN_END_ALL_SYNCED__REF", { ref })}`)
|
|
276
|
+
);
|
|
267
277
|
}
|
|
268
278
|
}
|
|
269
279
|
|
|
@@ -288,7 +298,11 @@ async function main() {
|
|
|
288
298
|
});
|
|
289
299
|
|
|
290
300
|
console.log(
|
|
291
|
-
`${ICON.info}
|
|
301
|
+
`${ICON.info} ${t("SYNCMAIN_INTRO__COUNT_MODE_REF", {
|
|
302
|
+
count: targets.length,
|
|
303
|
+
mode: args.rebase ? "rebase" : "merge",
|
|
304
|
+
ref: args.ref,
|
|
305
|
+
})}`
|
|
292
306
|
);
|
|
293
307
|
|
|
294
308
|
const results = [];
|