pan-stack-cli 0.1.4 → 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 -23
- package/bin/pan.js +206 -20
- package/lib/ai-skills.js +829 -0
- package/lib/create.js +22 -4
- package/lib/discover.js +69 -2
- package/lib/git/git-checkout.js +395 -0
- package/lib/git/git-clone.js +163 -28
- 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 +334 -0
- package/lib/git/helpers/bulk-report.js +202 -0
- package/lib/git/helpers/cli.js +41 -5
- package/lib/git/helpers/colors.js +19 -0
- package/lib/git/helpers/git.js +74 -0
- package/lib/git/helpers/manifest.js +93 -0
- package/lib/git/helpers/repo-select.js +416 -0
- 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/bin/pan.js
CHANGED
|
@@ -4,26 +4,49 @@ const {
|
|
|
4
4
|
findNearestWorkspace,
|
|
5
5
|
loadAllCommandSources,
|
|
6
6
|
findSourcesWithCommand,
|
|
7
|
-
formatProjectList,
|
|
8
7
|
isBuiltinGitCommand,
|
|
9
8
|
isBuiltinWorkspaceCommand,
|
|
10
9
|
} = require("../lib/discover");
|
|
11
10
|
const { listCommands } = require("../lib/manifest");
|
|
12
11
|
const { runWithProjectResolution, runGitScript, sourceLabel } = require("../lib/run-command");
|
|
13
12
|
const { runCreate } = require("../lib/create");
|
|
13
|
+
const { runServe } = require("../lib/serve");
|
|
14
|
+
const { t } = require("../lib/i18n");
|
|
14
15
|
|
|
15
16
|
const GIT_SCRIPT_MAP = {
|
|
16
17
|
clone: "git-clone.js",
|
|
17
18
|
pull: "git-pull.js",
|
|
18
19
|
fetch: "git-fetch-all.js",
|
|
20
|
+
gfa: "git-fetch-all.js",
|
|
19
21
|
push: "git-push.js",
|
|
20
22
|
grv: "git-grv.js",
|
|
21
23
|
gst: "git-gst.js",
|
|
24
|
+
"sync-main": "git-sync-main.js",
|
|
25
|
+
sm: "git-sync-main.js",
|
|
26
|
+
mr: "git-mr.js",
|
|
27
|
+
checkout: "git-checkout.js",
|
|
28
|
+
co: "git-checkout.js",
|
|
22
29
|
};
|
|
23
30
|
|
|
31
|
+
// `pan git` — sổ danh sách lệnh git (kể cả `stats`, vì stats cũng là thống kê Git từ log) ra 1
|
|
32
|
+
// menu, đỡ phải nhớ tên/xem help dài. Sort A-Z (xem "lệnh sort A-Z" trước đó trong CLI).
|
|
33
|
+
const GIT_MENU_COMMANDS = [
|
|
34
|
+
{ name: "checkout", aliases: ["co"], description: "Chuyển/tạo nhánh: pan co [-b|-B] <branch> [repo|all|list]" },
|
|
35
|
+
{ name: "clone", aliases: [], description: "Clone all repositories from .project.yaml" },
|
|
36
|
+
{ name: "fetch", aliases: ["gfa"], description: "Fetch all remotes for all repositories" },
|
|
37
|
+
{ name: "grv", aliases: [], description: "List git remotes -v for workspace + child repos" },
|
|
38
|
+
{ name: "gst", aliases: [], description: "Summary of repos with uncommitted changes" },
|
|
39
|
+
{ name: "mr", aliases: [], description: "Tạo GitLab Merge Request (multi-select repo · target=main mặc định)" },
|
|
40
|
+
{ name: "pull", aliases: [], description: "Pull latest changes for all repositories" },
|
|
41
|
+
{ name: "push", aliases: [], description: "Push all repositories and set upstream" },
|
|
42
|
+
{ name: "stats", aliases: ["graph"], description: "Thống kê đóng góp Git (report HTML / people / review md)" },
|
|
43
|
+
{ name: "sync-main", aliases: ["sm"], description: "Fetch origin/main and merge into current branch (pick repo or all)" },
|
|
44
|
+
].sort((a, b) => a.name.localeCompare(b.name));
|
|
45
|
+
|
|
24
46
|
function parseGlobalArgs(argv) {
|
|
25
47
|
const result = {
|
|
26
48
|
help: false,
|
|
49
|
+
version: false,
|
|
27
50
|
project: "",
|
|
28
51
|
command: "",
|
|
29
52
|
args: [],
|
|
@@ -31,8 +54,12 @@ function parseGlobalArgs(argv) {
|
|
|
31
54
|
|
|
32
55
|
for (let index = 0; index < argv.length; index += 1) {
|
|
33
56
|
const arg = argv[index];
|
|
34
|
-
|
|
57
|
+
const isHelpFlag = arg === "-h" || arg === "--help" || arg === "help";
|
|
58
|
+
const isVersionFlag = arg === "-v" || arg === "--version";
|
|
59
|
+
if (isHelpFlag && !result.command) {
|
|
35
60
|
result.help = true;
|
|
61
|
+
} else if (isVersionFlag && !result.command) {
|
|
62
|
+
result.version = true;
|
|
36
63
|
} else if (arg === "--project") {
|
|
37
64
|
result.project = argv[++index] || "";
|
|
38
65
|
} else if (arg.startsWith("--project=")) {
|
|
@@ -50,14 +77,24 @@ function parseGlobalArgs(argv) {
|
|
|
50
77
|
function printUsage() {
|
|
51
78
|
console.log(`usage: pan <command> [options] [-- args...]
|
|
52
79
|
|
|
53
|
-
workspace commands (built-in):
|
|
54
|
-
|
|
80
|
+
workspace commands (built-in, A-Z):
|
|
81
|
+
ai (skills) Enable/disable agent skills (SKILL.md ↔ .disabled) at workspace root
|
|
82
|
+
checkout (co) Chuyển/tạo nhánh: pan co [-b|-B] <branch> [repo|all|list]
|
|
55
83
|
clone Clone all repositories from .project.yaml
|
|
84
|
+
create Scaffold a new PAN Stack workspace (prompts for folder name)
|
|
85
|
+
fetch (gfa) Fetch all remotes for all repositories
|
|
86
|
+
git Menu chọn lệnh git (kể cả stats) — đỡ nhớ tên: pan git [<command>]
|
|
87
|
+
grv List git remotes -v for workspace + child repos
|
|
88
|
+
gst Summary of repos with uncommitted changes (pan gst --verbose for full git status)
|
|
89
|
+
language (lang) Đổi ngôn ngữ CLI — ghi 'language: <code>' vào .project.yaml gần nhất: pan language vi
|
|
90
|
+
mr Tạo GitLab Merge Request (multi-select repo · target=main mặc định)
|
|
56
91
|
pull Pull latest changes for all repositories
|
|
57
|
-
fetch Fetch all remotes for all repositories
|
|
58
92
|
push Push all repositories and set upstream
|
|
59
|
-
|
|
60
|
-
|
|
93
|
+
select Interactive picker (in ra stdout): sel=$(pan select --multi --all --from "ls *.yaml")
|
|
94
|
+
serve Static server cho thư mục build (vd output panui:gen): pan serve <dir> [-p port]
|
|
95
|
+
stats (graph) Thống kê đóng góp Git từ .project.yaml → báo cáo HTML / people / review md
|
|
96
|
+
sync-main (sm) Fetch origin/main and merge into current branch (pick repo or all)
|
|
97
|
+
ui Pan Studio sync CLI: login/logout/clone/sync/published-html/gen-react-web-code
|
|
61
98
|
|
|
62
99
|
project commands:
|
|
63
100
|
Loaded from .pan.yaml (workspace root + each repo in .project.yaml)
|
|
@@ -67,6 +104,12 @@ options:
|
|
|
67
104
|
--base-dir DIR (git commands) base directory for child repo paths
|
|
68
105
|
--dry-run (git commands) print commands only
|
|
69
106
|
-h, --help Show help
|
|
107
|
+
-v, --version Show pan-stack-cli version
|
|
108
|
+
|
|
109
|
+
tip:
|
|
110
|
+
Run 'pan' (no args) in a terminal → interactive MENU: pick project → pick command → run.
|
|
111
|
+
Navigate with ↑↓, type to filter, hotkeys: h help · v version · b back · x exit.
|
|
112
|
+
Language: default English; set 'language: vi' in .project.yaml (or PAN_LANG=vi) for Vietnamese.
|
|
70
113
|
|
|
71
114
|
env:
|
|
72
115
|
PAN_PROJECT_ROOTS Semicolon-separated workspace roots (; on Windows, : or ; on Unix)
|
|
@@ -77,6 +120,13 @@ examples:
|
|
|
77
120
|
pan create bookshop-stack
|
|
78
121
|
pan clone
|
|
79
122
|
pan grv
|
|
123
|
+
pan mr # interactive multi-select, target=main
|
|
124
|
+
pan mr develop # interactive multi-select, target=develop
|
|
125
|
+
pan mr main all # non-interactive, all repos
|
|
126
|
+
pan mr main csdl-cc-backend,csdl-cc-b-frontend
|
|
127
|
+
pan co -b feat/#1234 # tạo nhánh mới, tương tác chọn repo
|
|
128
|
+
pan co -b feat/#1234 all # tạo nhánh mới cho tất cả repo
|
|
129
|
+
pan co main # switch tất cả sang main
|
|
80
130
|
pan test fe
|
|
81
131
|
pan test --project bookshop-web
|
|
82
132
|
`);
|
|
@@ -84,12 +134,14 @@ examples:
|
|
|
84
134
|
|
|
85
135
|
function printAllCommands(sources) {
|
|
86
136
|
if (sources.length === 0) {
|
|
87
|
-
console.log(
|
|
137
|
+
console.log(t("NO_PAN_YAML_FOUND"));
|
|
88
138
|
return;
|
|
89
139
|
}
|
|
90
140
|
|
|
91
|
-
console.log("
|
|
92
|
-
|
|
141
|
+
console.log(t("LOADED_COMMANDS"));
|
|
142
|
+
// Sort A-Z theo tên hiển thị (project/repo) — dễ dò khi .project.yaml có nhiều repo.
|
|
143
|
+
const sorted = [...sources].sort((a, b) => sourceLabel(a).localeCompare(sourceLabel(b)));
|
|
144
|
+
for (const source of sorted) {
|
|
93
145
|
const commands = listCommands(source);
|
|
94
146
|
if (commands.length === 0) {
|
|
95
147
|
continue;
|
|
@@ -101,14 +153,115 @@ function printAllCommands(sources) {
|
|
|
101
153
|
: "";
|
|
102
154
|
const aliasHint =
|
|
103
155
|
command.aliases.length > 0 ? ` (${command.aliases.join(", ")})` : "";
|
|
156
|
+
const seqHint = command.hasSequence ? " [sequence]" : "";
|
|
104
157
|
const desc = command.description ? ` — ${command.description}` : "";
|
|
105
|
-
console.log(` ${command.name}${aliasHint}${targetHint}${desc}`);
|
|
158
|
+
console.log(` ${command.name}${aliasHint}${targetHint}${seqHint}${desc}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function printGitMenuHelp() {
|
|
164
|
+
console.log(`usage: pan git [<command>] [options] [-- args...]
|
|
165
|
+
|
|
166
|
+
Không truyền <command> → TTY: menu tương tác chọn 1 lệnh git rồi chạy ngay.
|
|
167
|
+
Không TTY: in danh sách dưới đây rồi thoát (không lỗi).
|
|
168
|
+
Có <command> → chạy thẳng, y hệt gõ 'pan <command>' (chỉ là lối gõ khác, dễ nhớ hơn).
|
|
169
|
+
|
|
170
|
+
lệnh (A-Z):
|
|
171
|
+
${GIT_MENU_COMMANDS.map((c) => ` ${c.name}${c.aliases.length ? ` (${c.aliases.join(", ")})` : ""} ${c.description}`).join("\n")}
|
|
172
|
+
`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Chạy 1 lệnh git/stats theo tên (đã resolve alias) — dùng chung bởi 'pan <cmd>' (main) và
|
|
176
|
+
// 'pan git <cmd>' (đỡ nhớ tên khi không chắc, xem printGitMenuHelp).
|
|
177
|
+
function runGitSubcommand(name, args) {
|
|
178
|
+
if (name === "stats" || name === "graph") {
|
|
179
|
+
const { runStats } = require("../lib/stats");
|
|
180
|
+
return runStats(args);
|
|
181
|
+
}
|
|
182
|
+
const gitArgs = [...args];
|
|
183
|
+
if (name === "push" && !gitArgs.includes("--set-upstream")) {
|
|
184
|
+
gitArgs.unshift("--set-upstream");
|
|
185
|
+
}
|
|
186
|
+
runGitScript(GIT_SCRIPT_MAP[name], gitArgs);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function runGitMenu(args) {
|
|
190
|
+
if (args[0] === "-h" || args[0] === "--help") {
|
|
191
|
+
printGitMenuHelp();
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Có sẵn tên lệnh (vd `pan git pull`) → chạy thẳng, khỏi qua menu.
|
|
196
|
+
if (args[0]) {
|
|
197
|
+
const sub = args[0];
|
|
198
|
+
const known = GIT_MENU_COMMANDS.find((c) => c.name === sub || c.aliases.includes(sub));
|
|
199
|
+
if (!known) {
|
|
200
|
+
throw new Error(t("UNKNOWN_BUILTIN_COMMAND__COMMAND", { command: `git ${sub}` }));
|
|
106
201
|
}
|
|
202
|
+
runGitSubcommand(known.name, args.slice(1));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (!process.stdin.isTTY) {
|
|
207
|
+
printGitMenuHelp();
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const { menuSelect } = require("../lib/menu-prompt");
|
|
212
|
+
const choices = GIT_MENU_COMMANDS.map((c) => ({
|
|
213
|
+
name: c.name + (c.aliases.length ? ` (${c.aliases.join(", ")})` : ""),
|
|
214
|
+
value: c.name,
|
|
215
|
+
description: c.description,
|
|
216
|
+
}));
|
|
217
|
+
let picked;
|
|
218
|
+
try {
|
|
219
|
+
picked = await menuSelect({
|
|
220
|
+
message: "pan git · chọn lệnh:",
|
|
221
|
+
choices,
|
|
222
|
+
hotkeys: [{ key: "x", label: t("EXIT_LABEL"), value: "__exit__" }],
|
|
223
|
+
});
|
|
224
|
+
} catch (err) {
|
|
225
|
+
if (String(err && err.message) !== "no-core") throw err;
|
|
226
|
+
const { loadInquirer } = require("../lib/select");
|
|
227
|
+
const inq = await loadInquirer();
|
|
228
|
+
if (!inq || typeof inq.select !== "function") {
|
|
229
|
+
printGitMenuHelp();
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
picked = await inq.select({
|
|
233
|
+
message: "pan git · chọn lệnh:",
|
|
234
|
+
choices: [...choices, { name: `x ${t("EXIT_LABEL")}`, value: "__exit__" }],
|
|
235
|
+
pageSize: 20,
|
|
236
|
+
loop: false,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
if (picked === "__exit__") {
|
|
240
|
+
console.log(t("EXITED"));
|
|
241
|
+
return;
|
|
107
242
|
}
|
|
243
|
+
runGitSubcommand(picked, []);
|
|
108
244
|
}
|
|
109
245
|
|
|
110
246
|
async function main() {
|
|
111
247
|
const parsed = parseGlobalArgs(process.argv.slice(2));
|
|
248
|
+
const pkg = require("../package.json");
|
|
249
|
+
|
|
250
|
+
if (parsed.version || parsed.command === "version") {
|
|
251
|
+
console.log(`pan-stack-cli ${pkg.version}`);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Gõ `pan` trống trên terminal → MENU tương tác (chọn dự án → lệnh → chạy).
|
|
256
|
+
if (!parsed.command && !parsed.help && process.stdin.isTTY) {
|
|
257
|
+
try {
|
|
258
|
+
const { runInteractiveMenu } = require("../lib/menu");
|
|
259
|
+
const handled = await runInteractiveMenu({ printUsage, version: pkg.version });
|
|
260
|
+
if (handled) return;
|
|
261
|
+
} catch (err) {
|
|
262
|
+
console.error(err.message || err);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
112
265
|
|
|
113
266
|
if (parsed.help || !parsed.command) {
|
|
114
267
|
printUsage();
|
|
@@ -128,24 +281,57 @@ async function main() {
|
|
|
128
281
|
return;
|
|
129
282
|
}
|
|
130
283
|
|
|
284
|
+
if (command === "serve") {
|
|
285
|
+
runServe(args);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (command === "select") {
|
|
290
|
+
const { runSelectCli } = require("../lib/select");
|
|
291
|
+
await runSelectCli(args);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (command === "language" || command === "lang") {
|
|
296
|
+
const { runLanguage } = require("../lib/language");
|
|
297
|
+
await runLanguage(args);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (command === "ai" || command === "skills") {
|
|
302
|
+
const { runAiSkills } = require("../lib/ai-skills");
|
|
303
|
+
await runAiSkills(args);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (command === "ui") {
|
|
308
|
+
const { runUi } = require("../lib/ui");
|
|
309
|
+
await runUi(args);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (command === "stats" || command === "graph") {
|
|
314
|
+
const { runStats } = require("../lib/stats");
|
|
315
|
+
await runStats(args);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (command === "git") {
|
|
320
|
+
await runGitMenu(args);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
131
324
|
if (isBuiltinGitCommand(command)) {
|
|
132
|
-
|
|
133
|
-
if (command === "push" && !gitArgs.includes("--set-upstream")) {
|
|
134
|
-
gitArgs.unshift("--set-upstream");
|
|
135
|
-
}
|
|
136
|
-
runGitScript(GIT_SCRIPT_MAP[command], gitArgs);
|
|
325
|
+
runGitSubcommand(command, args);
|
|
137
326
|
return;
|
|
138
327
|
}
|
|
139
328
|
|
|
140
329
|
if (isBuiltinWorkspaceCommand(command)) {
|
|
141
|
-
throw new Error(
|
|
330
|
+
throw new Error(t("UNKNOWN_BUILTIN_COMMAND__COMMAND", { command }));
|
|
142
331
|
}
|
|
143
332
|
|
|
144
333
|
const allSources = loadAllCommandSources();
|
|
145
334
|
const matches = findSourcesWithCommand(command);
|
|
146
|
-
if (matches.length > 1 && !project && process.stdin.isTTY) {
|
|
147
|
-
console.log(formatProjectList(matches, command));
|
|
148
|
-
}
|
|
149
335
|
|
|
150
336
|
await runWithProjectResolution(matches, command, args, project, allSources);
|
|
151
337
|
}
|