pan-stack-cli 0.2.1 → 0.2.2

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.
Files changed (59) hide show
  1. package/README.md +37 -731
  2. package/bin/pan.js +4955 -269
  3. package/package.json +17 -5
  4. package/lib/ai-skills.js +0 -829
  5. package/lib/create.js +0 -105
  6. package/lib/discover.js +0 -294
  7. package/lib/git/git-checkout.js +0 -395
  8. package/lib/git/git-clone.js +0 -206
  9. package/lib/git/git-fetch-all.js +0 -129
  10. package/lib/git/git-grv.js +0 -125
  11. package/lib/git/git-gst.js +0 -165
  12. package/lib/git/git-mr.js +0 -546
  13. package/lib/git/git-pull.js +0 -137
  14. package/lib/git/git-push.js +0 -185
  15. package/lib/git/git-sync-main.js +0 -334
  16. package/lib/git/helpers/bulk-report.js +0 -202
  17. package/lib/git/helpers/cli.js +0 -188
  18. package/lib/git/helpers/colors.js +0 -19
  19. package/lib/git/helpers/git.js +0 -136
  20. package/lib/git/helpers/manifest.js +0 -223
  21. package/lib/git/helpers/repo-select.js +0 -416
  22. package/lib/git/helpers/repo-summary.js +0 -161
  23. package/lib/i18n.js +0 -61
  24. package/lib/inquirer-theme.js +0 -20
  25. package/lib/language.js +0 -121
  26. package/lib/languages.js +0 -14
  27. package/lib/manifest.js +0 -467
  28. package/lib/menu-prompt.js +0 -201
  29. package/lib/menu.js +0 -134
  30. package/lib/messages.js +0 -647
  31. package/lib/platform.js +0 -140
  32. package/lib/run-command.js +0 -334
  33. package/lib/select.js +0 -0
  34. package/lib/serve.js +0 -75
  35. package/lib/skill-registry-client.js +0 -173
  36. package/lib/stats/aggregate.js +0 -51
  37. package/lib/stats/identity.js +0 -20
  38. package/lib/stats/index.js +0 -189
  39. package/lib/stats/parse.js +0 -46
  40. package/lib/stats/range.js +0 -38
  41. package/lib/stats/report.js +0 -264
  42. package/lib/stats/review.js +0 -157
  43. package/lib/stats/scan.js +0 -92
  44. package/lib/stats/serve.js +0 -25
  45. package/lib/ui/api.js +0 -202
  46. package/lib/ui/clone.js +0 -115
  47. package/lib/ui/config.js +0 -182
  48. package/lib/ui/diff.js +0 -128
  49. package/lib/ui/export-gate.js +0 -79
  50. package/lib/ui/gen-react-mobile-code.js +0 -6
  51. package/lib/ui/gen-react-web-code.js +0 -75
  52. package/lib/ui/index.js +0 -89
  53. package/lib/ui/init.js +0 -252
  54. package/lib/ui/login.js +0 -128
  55. package/lib/ui/logout.js +0 -22
  56. package/lib/ui/palettes.js +0 -103
  57. package/lib/ui/published-html.js +0 -24
  58. package/lib/ui/sync.js +0 -137
  59. package/lib/ui/workspace.js +0 -213
@@ -1,185 +0,0 @@
1
- #!/usr/bin/env node
2
- /*
3
- * Push local commits for PAN Stack project repositories.
4
- * Compact summary table: repo · branch · result · commits · MR/PR link.
5
- */
6
-
7
- const { parseCommonArgs, loadManifestRepos, printCommonOptions } = require("./helpers/cli");
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
- }
24
-
25
- function pushRepo({ name, destination, dryRun, setUpstream }) {
26
- if (!ensureRepoExists({ name, destination })) {
27
- return { name, skipped: true };
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 };
36
-
37
- const gitArgs = ["push"];
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 };
43
- }
44
-
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
- };
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 };
95
- }
96
-
97
- function printHelp() {
98
- console.log(`usage: git-push.js [--base-dir BASE_DIR] [--dry-run] [--set-upstream] [manifest]
99
-
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).
102
-
103
- ${printCommonOptions()}
104
- --set-upstream Use git push -u origin HEAD for new branches`);
105
- }
106
-
107
- function main() {
108
- const args = parseCommonArgs(process.argv.slice(2), {
109
- defaults: { setUpstream: false },
110
- handlers: { "--set-upstream": (p) => { p.setUpstream = true; } },
111
- });
112
- if (args.help) { printHelp(); return; }
113
-
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));
135
-
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,
179
- });
180
- }
181
-
182
- if (rejected.length || failed.length) process.exitCode = 1;
183
- }
184
-
185
- try { main(); } catch (error) { console.error(error.message); process.exit(1); }
@@ -1,334 +0,0 @@
1
- #!/usr/bin/env node
2
- /*
3
- * Sync origin/main (or --ref) into the current branch for selected PAN Stack repos.
4
- *
5
- * Fetches first, then merge (default) or rebase (--rebase). Interactive repo picker
6
- * when TTY; pass repo name or "all" as first positional argument.
7
- */
8
-
9
- const {
10
- looksLikeManifest,
11
- loadManifestRepos,
12
- printCommonOptions,
13
- } = require("./helpers/cli");
14
- const colors = require("./helpers/colors");
15
- const { resolveTargets } = require("./helpers/repo-select");
16
- const { t } = require("../i18n");
17
- const {
18
- ensureRepoExists,
19
- runGit,
20
- runGitCapture,
21
- getCurrentBranch,
22
- refExists,
23
- listConflictedFiles,
24
- isMergeOrRebaseInProgress,
25
- countDirtyFiles,
26
- } = require("./helpers/git");
27
-
28
- const ICON = {
29
- ok: "✅",
30
- warn: "⚠️",
31
- conflict: "⛔",
32
- skip: "⏭️",
33
- info: "ℹ️",
34
- };
35
-
36
- function printHelp() {
37
- console.log(`usage: pan sync-main [repo|all] [options] (alias: pan sm)
38
-
39
- Fetch remote main (or --ref) and merge into the current branch for workspace and/or child repos.
40
-
41
- positional:
42
- repo|all Repo name from .project.yaml, or "all". Interactive picker if omitted (TTY).
43
-
44
- ${printCommonOptions()}
45
- --ref REF Remote-tracking ref to merge (default: origin/main)
46
- --rebase Use git rebase instead of merge
47
- --no-workspace Only manifest child repos (skip workspace stack root)`);
48
- }
49
-
50
- function parseSyncMainArgs(argv) {
51
- const args = {
52
- help: false,
53
- manifest: "",
54
- baseDir: ".",
55
- dryRun: false,
56
- ref: "origin/main",
57
- rebase: false,
58
- noWorkspace: false,
59
- selection: "",
60
- };
61
-
62
- for (let index = 0; index < argv.length; index += 1) {
63
- const arg = argv[index];
64
- if (arg === "-h" || arg === "--help") {
65
- args.help = true;
66
- } else if (arg === "--base-dir") {
67
- args.baseDir = argv[++index] || ".";
68
- } else if (arg === "--dry-run") {
69
- args.dryRun = true;
70
- } else if (arg === "--ref") {
71
- args.ref = argv[++index] || "origin/main";
72
- } else if (arg === "--rebase") {
73
- args.rebase = true;
74
- } else if (arg === "--no-workspace") {
75
- args.noWorkspace = true;
76
- } else if (!arg.startsWith("-")) {
77
- if (looksLikeManifest(arg) && !args.manifest) {
78
- args.manifest = arg;
79
- } else if (!args.selection) {
80
- args.selection = arg;
81
- } else {
82
- throw new Error(`Unexpected argument: ${arg}`);
83
- }
84
- } else {
85
- throw new Error(`Unexpected argument: ${arg}`);
86
- }
87
- }
88
-
89
- return args;
90
- }
91
-
92
- function printEarlyConflict({ name, branch, ref, files, mode }) {
93
- const headline = colors.bold(
94
- colors.red(`${ICON.conflict} [conflict] ${name} (${branch})`)
95
- );
96
- const detail = colors.yellow(
97
- t("SYNCMAIN_CONFLICT_SHORT__MODE_REF_N", { mode, ref, n: files.length })
98
- );
99
- console.log(`${headline} — ${detail}`);
100
- for (const file of files.slice(0, 8)) {
101
- console.log(colors.dim(` • ${file}`));
102
- }
103
- if (files.length > 8) {
104
- console.log(
105
- colors.dim(` ${t("SYNCMAIN_MORE_FILES__N", { n: files.length - 8 })}`)
106
- );
107
- }
108
- }
109
-
110
- function syncMainRepo({
111
- name,
112
- destination,
113
- dryRun,
114
- ref,
115
- rebase,
116
- }) {
117
- const result = {
118
- name,
119
- destination,
120
- branch: "",
121
- status: "skipped",
122
- message: "",
123
- conflictFiles: [],
124
- mode: rebase ? "rebase" : "merge",
125
- };
126
-
127
- if (!ensureRepoExists({ name, destination })) {
128
- result.message = t("SYNCMAIN_MSG_NOT_CLONED");
129
- return result;
130
- }
131
-
132
- const branch = getCurrentBranch(destination);
133
- result.branch = branch || t("SYNCMAIN_DETACHED_LABEL");
134
- if (!branch) {
135
- result.message = t("SYNCMAIN_MSG_DETACHED");
136
- console.log(`${ICON.skip} [skip] ${name}: ${result.message}`);
137
- return result;
138
- }
139
-
140
- const inProgress = isMergeOrRebaseInProgress(destination);
141
- if (inProgress) {
142
- const files = listConflictedFiles(destination);
143
- result.status = "conflict";
144
- result.conflictFiles = files;
145
- result.message = t("SYNCMAIN_MSG_IN_PROGRESS__MODE", { mode: inProgress });
146
- printEarlyConflict({ name, branch, ref, files, mode: inProgress });
147
- return result;
148
- }
149
-
150
- const dirtyCount = countDirtyFiles(destination);
151
- if (dirtyCount > 0) {
152
- console.log(
153
- `${ICON.warn} ${colors.yellow(t("SYNCMAIN_MSG_DIRTY__NAME_N", { name, n: dirtyCount }))}`
154
- );
155
- }
156
-
157
- if (dryRun) {
158
- console.log(
159
- `[dry-run] (in ${destination}) git fetch origin && git ${rebase ? "rebase" : "merge"} ${ref}`
160
- );
161
- result.status = "dry-run";
162
- result.message = "dry-run";
163
- return result;
164
- }
165
-
166
- console.log(`[fetch] ${name}: ${destination}`);
167
- const fetch = runGitCapture(["fetch", "origin"], { cwd: destination });
168
- if (fetch.status !== 0) {
169
- result.status = "error";
170
- result.message = fetch.stderr || t("SYNCMAIN_MSG_FETCH_FAILED");
171
- console.log(`${ICON.warn} ${colors.yellow(`[warn] ${name}: ${result.message}`)}`);
172
- return result;
173
- }
174
-
175
- if (!refExists(destination, ref)) {
176
- result.status = "error";
177
- result.message = t("SYNCMAIN_MSG_REF_MISSING__REF", { ref });
178
- console.log(`${ICON.warn} ${colors.yellow(`[warn] ${name}: ${result.message}`)}`);
179
- return result;
180
- }
181
-
182
- const gitArgs = rebase ? ["rebase", ref] : ["merge", "--no-edit", ref];
183
- console.log(`[${result.mode}] ${name} (${branch}) ← ${ref}`);
184
- const action = runGit(gitArgs, {
185
- cwd: destination,
186
- dryRun: false,
187
- action: result.mode,
188
- name,
189
- });
190
-
191
- const conflictFiles = listConflictedFiles(destination);
192
- if (conflictFiles.length > 0 || action !== 0) {
193
- result.status = "conflict";
194
- result.conflictFiles =
195
- conflictFiles.length > 0 ? conflictFiles : [t("SYNCMAIN_MSG_SEE_STATUS")];
196
- result.message = t("SYNCMAIN_MSG_CONFLICT__MODE", { mode: result.mode });
197
- printEarlyConflict({
198
- name,
199
- branch,
200
- ref,
201
- files: result.conflictFiles,
202
- mode: result.mode,
203
- });
204
- return result;
205
- }
206
-
207
- result.status = "ok";
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
- );
212
- return result;
213
- }
214
-
215
- function printSummary(results, { ref, rebase }) {
216
- const ok = results.filter((item) => item.status === "ok");
217
- const conflicts = results.filter((item) => item.status === "conflict");
218
- const errors = results.filter((item) => item.status === "error");
219
- const skipped = results.filter(
220
- (item) => item.status === "skipped" || item.status === "dry-run"
221
- );
222
-
223
- console.log("");
224
- console.log(colors.bold(t("SYNCMAIN_HEADER")));
225
- console.log(
226
- `${ICON.info} ${t("SYNCMAIN_INFO__REF_MODE_REPOS", {
227
- ref, mode: rebase ? "rebase" : "merge", repos: results.length,
228
- })}`
229
- );
230
- console.log(
231
- t("SYNCMAIN_COUNTS__OK_CONFLICT_ERR_SKIP", {
232
- ok: ok.length, conflict: conflicts.length, err: errors.length, skip: skipped.length,
233
- })
234
- );
235
-
236
- if (conflicts.length > 0) {
237
- console.log("");
238
- console.log(
239
- colors.bold(colors.red(`${ICON.conflict} ${t("SYNCMAIN_CONFLICT_HEADER")}`))
240
- );
241
- for (const item of conflicts) {
242
- console.log(colors.yellow(`\n ${item.name} (${item.branch})`));
243
- console.log(colors.dim(` ${item.destination}`));
244
- for (const file of item.conflictFiles) {
245
- console.log(` • ${file}`);
246
- }
247
- const hint = item.mode === "rebase"
248
- ? t("SYNCMAIN_HINT_REBASE")
249
- : t("SYNCMAIN_HINT_MERGE");
250
- console.log(colors.dim(` → ${hint}`));
251
- }
252
- }
253
-
254
- if (errors.length > 0) {
255
- console.log("");
256
- console.log(colors.bold(colors.yellow(`${ICON.warn} ${t("SYNCMAIN_ERRORS_HEADER")}`)));
257
- for (const item of errors) {
258
- console.log(` • ${item.name}: ${item.message}`);
259
- }
260
- }
261
-
262
- if (conflicts.length > 0) {
263
- console.log("");
264
- console.log(
265
- colors.bold(colors.red(`${ICON.conflict} ${t("SYNCMAIN_END_CONFLICT")}`))
266
- );
267
- } else if (errors.length > 0) {
268
- console.log("");
269
- console.log(
270
- colors.bold(colors.yellow(`${ICON.warn} ${t("SYNCMAIN_END_ERROR")}`))
271
- );
272
- } else if (ok.length > 0) {
273
- console.log("");
274
- console.log(
275
- colors.green(`${ICON.ok} ${t("SYNCMAIN_END_ALL_SYNCED__REF", { ref })}`)
276
- );
277
- }
278
- }
279
-
280
- async function main() {
281
- const argv = process.argv.slice(2);
282
- const args = parseSyncMainArgs(argv);
283
- if (args.help) {
284
- printHelp();
285
- return;
286
- }
287
-
288
- const { repos, workspaceRoot } = loadManifestRepos({
289
- manifestPath: args.manifest,
290
- baseDir: args.baseDir,
291
- });
292
-
293
- const targets = await resolveTargets({
294
- repos,
295
- workspaceRoot,
296
- includeWorkspace: !args.noWorkspace,
297
- selection: args.selection,
298
- });
299
-
300
- console.log(
301
- `${ICON.info} ${t("SYNCMAIN_INTRO__COUNT_MODE_REF", {
302
- count: targets.length,
303
- mode: args.rebase ? "rebase" : "merge",
304
- ref: args.ref,
305
- })}`
306
- );
307
-
308
- const results = [];
309
- for (const target of targets) {
310
- results.push(
311
- syncMainRepo({
312
- name: target.name,
313
- destination: target.destination,
314
- dryRun: args.dryRun,
315
- ref: args.ref,
316
- rebase: args.rebase,
317
- })
318
- );
319
- console.log("");
320
- }
321
-
322
- printSummary(results, { ref: args.ref, rebase: args.rebase });
323
-
324
- const hasConflict = results.some((item) => item.status === "conflict");
325
- const hasError = results.some((item) => item.status === "error");
326
- if (hasConflict || hasError) {
327
- process.exit(1);
328
- }
329
- }
330
-
331
- main().catch((error) => {
332
- console.error(error.message);
333
- process.exit(1);
334
- });