pan-stack-cli 0.1.2 → 0.1.5

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 CHANGED
@@ -1,152 +1,217 @@
1
1
  # pan-stack-cli
2
2
 
3
- CLI toàn cục cho PAN Stack workspace — thay script `./pan` ở root dự án.
4
-
5
- Cài một lần, dùng ở mọi dự án có `.project.yaml`:
3
+ Global CLI for PAN Stack workspaces. Install once, use in any project that has
4
+ `.project.yaml`.
6
5
 
7
6
  ```bash
8
7
  npm i -g pan-stack-cli
9
- # hoặc từ source (trước khi publish npm):
10
- npm i -g /path/to/.pan/pan-stack-cli
8
+ # or from source before npm publish:
9
+ npm i -g /path/to/pan-stack-cli
11
10
  ```
12
11
 
13
- Sau đó gõ `pan` thay cho `./pan`.
12
+ ## What is PAN Stack?
13
+
14
+ PAN Stack is a **workspace shell** for coding with AI agents — not a monorepo that
15
+ contains every product repository. The stack repo holds:
16
+
17
+ - Agent skills and prompts (`.claude`, `.cursor`, `.codex`)
18
+ - Workspace manifests (`.project.yaml`, `.pan.yaml`)
19
+ - Team docs and coordination files
14
20
 
15
- ## Yêu cầu
21
+ Product source code lives in separate repositories listed in `.project.yaml`. The
22
+ `pan` CLI ties them together: clone all repos, run git ops across the workspace,
23
+ and execute project commands defined in `.pan.yaml`.
24
+
25
+ ```text
26
+ bookshop-stack/ # PAN Stack workspace (shell)
27
+ .project.yaml # Which repos belong to this workspace
28
+ .pan.yaml # Workspace-level pan commands
29
+ .claude/skills/ # Claude Code skills
30
+ .cursor/skills/ # Cursor agent skills
31
+ .codex/skills/ # Codex agent skills
32
+ apps/
33
+ web/ # bookshop-web (cloned product repo)
34
+ api/ # bookshop-api (cloned product repo)
35
+ ```
36
+
37
+ Run `pan create` to scaffold this layout with the shared `writing-guidelines`
38
+ skill pre-installed for all three agent folders.
39
+
40
+ ## Requirements
16
41
 
17
42
  - Node.js 18+
18
- - Git (cho `pan clone`, `pull`, `fetch`, `push`, `grv`, `gst`)
19
- - Công cụ riêng từng lệnh dự án (pnpm, docker, curl, …)
43
+ - Git (for `pan clone`, `pull`, `sync-main`, `fetch`, `push`, `grv`, `gst`)
20
44
 
21
- ## Lệnh git (built-in)
45
+ ## Windows support
22
46
 
23
- Đọc `repositories` từ `.project.yaml` gần nhất (walk-up từ thư mục hiện tại):
47
+ Built-in commands (`create`, `clone`, `pull`, `sync-main`, `push`, `fetch`, `grv`, `gst`) run on
48
+ Windows, macOS, and Linux. Notes for Windows:
24
49
 
25
- | Lệnh | Việc |
26
- |------|------|
27
- | `pan clone` | Clone tất cả repo trong manifest |
28
- | `pan pull` | Pull tất cả repo đã clone |
29
- | `pan fetch` | Fetch all remotes |
30
- | `pan push` | Push và set upstream (`-u origin HEAD`) |
31
- | `pan grv` | `git remote -v` — URL đầy đủ cho workspace + từng repo con |
32
- | `pan gst` | `git status` cho workspace + từng repo con (vd `pan gst -sb`) |
50
+ - `shell:` blocks in `.pan.yaml` run through **bash** on all platforms (Git Bash on
51
+ Windows, system bash on macOS/Linux). Use `exec:` for simple one-liners (`npm test`)
52
+ when possible; those work without Bash.
53
+ - Install **Git for Windows** if bash is not already on `PATH`.
54
+ - Set `PAN_PROJECT_ROOTS` with **semicolon** separators (`C:\work\a;C:\work\b`).
55
+ Do not use `:` — it conflicts with drive letters.
56
+ - Optional: set `PAN_GIT_BASH` to the full path of `bash.exe` if Git is installed in
57
+ a non-standard location.
33
58
 
34
- Tuỳ chọn git (forward xuống script):
59
+ ## Bootstrap a new workspace
35
60
 
36
- - `--base-dir DIR` — thư mục gốc cho path repo con (mặc định: thư mục chứa `.project.yaml`; tự walk-up từ cwd nếu không có ngay tại đó)
37
- - `--dry-run` — chỉ in lệnh
38
- - `[manifest]` — đường dẫn manifest (mặc định: `.project.yaml`)
61
+ ```bash
62
+ mkdir ~/work && cd ~/work
63
+ pan create # prompts for folder name
64
+ # or
65
+ pan create bookshop-stack # non-interactive
39
66
 
40
- ## Lệnh dự án (từ `.pan.yaml`)
67
+ cd bookshop-stack
68
+ # Edit .project.yaml — replace repo_url_to_be_filled with real URLs
69
+ pan clone
70
+ pan install
71
+ ```
41
72
 
42
- Hai lớp cấu hình:
73
+ `pan create` fails if the target folder already exists.
43
74
 
44
- | File | Vai trò |
45
- |------|---------|
46
- | `.project.yaml` | Workspace: danh sách repo (`repositories`) — **không** chứa `commands` |
47
- | `.pan.yaml` | Lệnh `pan` — đặt ở workspace root và/hoặc từng repo con |
75
+ ## Configuration files
48
76
 
49
- CLI load **tất cả** `.pan.yaml` trong workspace (root + mỗi `repositories[].path`). Lệnh trùng tên → hỏi chọn repo (`--project`) hoặc tự chọn nếu arg khớp `targets` (vd `pan test fe`).
77
+ Two files drive the CLI. Keep responsibilities separate:
50
78
 
51
- ### Schema `.pan.yaml` (repo con)
79
+ | File | Role |
80
+ |------|------|
81
+ | `.project.yaml` | Workspace manifest — lists `repositories` (name, url, path). **No commands.** |
82
+ | `.pan.yaml` | `pan` commands — at workspace root and/or inside each child repo |
52
83
 
53
- `cwd` / `exec` tương đối **root repo** (thư mục chứa file `.pan.yaml`):
84
+ ### `.project.yaml` example
54
85
 
55
86
  ```yaml
56
- commands:
57
- be:
58
- description: Deploy backend local
59
- exec: scripts/deploy.sh
87
+ project: bookshop-stack
88
+ managed_by: your-team
89
+
90
+ stack:
91
+ repo: https://github.com/example/bookshop-stack.git
92
+
93
+ repositories:
94
+ - name: bookshop-web
95
+ description: Customer-facing web application
96
+ url: https://github.com/example/bookshop-web.git
97
+ path: apps/web
98
+ - name: bookshop-api
99
+ description: REST API service
100
+ url: https://github.com/example/bookshop-api.git
101
+ path: apps/api
102
+ ```
103
+
104
+ ### `.pan.yaml` — workspace root
105
+
106
+ Orchestration commands. Paths are relative to the workspace root. Use
107
+ `PAN_WORKSPACE_ROOT` in shell blocks:
60
108
 
109
+ ```yaml
110
+ commands:
61
111
  install:
62
112
  alias: i
63
- description: Cài dependencies (pan i hoặc pan install)
113
+ description: Install dependencies across cloned repositories
64
114
  shell: |
65
115
  set -euo pipefail
66
- pnpm install
116
+ root="${PAN_WORKSPACE_ROOT}"
117
+ for dir in apps/web apps/api; do
118
+ [[ -d "$root/$dir" ]] || { echo "Missing: $root/$dir" >&2; exit 1; }
119
+ (cd "$root/$dir" && npm install)
120
+ done
67
121
 
68
- lint:
69
- shell: |
70
- set -euo pipefail
71
- pnpm lint
122
+ test:
123
+ default: web
124
+ description: Run tests (web | api)
125
+ targets:
126
+ web:
127
+ cwd: apps/web
128
+ exec: npm test
129
+ api:
130
+ cwd: apps/api
131
+ exec: npm test
72
132
  ```
73
133
 
74
- Alias: `alias: i` hoặc `aliases: i, ins` (cách nhau bằng dấu phẩy).
134
+ ### `.pan.yaml` — child repo
75
135
 
76
- ### Schema `.pan.yaml` (workspace root)
77
-
78
- Lệnh orchestration — `cwd` tương đối workspace root; dùng `PAN_WORKSPACE_ROOT` trong shell:
136
+ Place a `.pan.yaml` inside a cloned repo. `cwd` / `exec` are relative to that
137
+ repo's root:
79
138
 
80
139
  ```yaml
81
140
  commands:
82
- install:
83
- shell: |
84
- cd "${PAN_WORKSPACE_ROOT}/apps/frontend" && pnpm install
141
+ dev:
142
+ description: Start local dev server
143
+ exec: npm run dev
85
144
 
86
- test:
87
- default: fe
88
- targets:
89
- be:
90
- cwd: apps/backend
91
- exec: ./gradlew test
92
- fe:
93
- cwd: apps/frontend
94
- exec: pnpm test
145
+ lint:
146
+ shell: |
147
+ set -euo pipefail
148
+ npm run lint
95
149
  ```
96
150
 
97
- ### Biến môi trường
151
+ Aliases: `alias: i` or `aliases: i, ins` (comma-separated).
98
152
 
99
- | Biến | Ý nghĩa |
100
- |------|---------|
101
- | `PAN_WORKSPACE_ROOT` | Thư mục chứa `.project.yaml` |
102
- | `PAN_REPO_ROOT` | Thư mục chứa `.pan.yaml` đang chạy |
103
- | `PAN_PROJECT_ROOT` | Alias của `PAN_WORKSPACE_ROOT` |
104
- | `PAN_ARG_1`, … | Args sau tên lệnh |
153
+ ## Built-in git commands
105
154
 
106
- ### Nhiều repo trùng tên lệnh
155
+ Read `repositories` from the nearest `.project.yaml` (walk-up from cwd):
107
156
 
108
- CLI quét workspace gần nhất (walk-up `.project.yaml`) rồi load mọi `.pan.yaml` con.
157
+ | Command | Action |
158
+ |---------|--------|
159
+ | `pan clone` | Clone all repositories in the manifest |
160
+ | `pan pull` | Pull all cloned repositories |
161
+ | `pan sync-main [repo\|all]` (`pan sm`) | Fetch + merge `origin/main` into current branch (interactive picker; conflict summary) |
162
+ | `pan fetch` | Fetch all remotes |
163
+ | `pan push` | Push all repos (`-u origin HEAD` by default) |
164
+ | `pan grv` | `git remote -v` for workspace + each child repo |
165
+ | `pan gst` | `git status` for workspace + each child repo |
109
166
 
110
- ## Ví dụ (CSDL-CC)
167
+ Git options:
111
168
 
112
- Trong repo `btp-csdl-cc` (đã có `.project.yaml`):
169
+ - `--base-dir DIR` — base for relative repo paths (default: directory containing `.project.yaml`)
170
+ - `--dry-run` — print commands only
171
+ - `[manifest]` — manifest path (default: `.project.yaml`)
113
172
 
114
- ```bash
115
- cd ~/work/btp/btp-csdl-cc
173
+ Works from child repo directories — the CLI walks up to find `.project.yaml`.
116
174
 
117
- pan clone
118
- pan install
119
- pan be # deploy backend :8080
120
- pan fe # Vite :5173
121
- pan faker # pan-faker :8090
122
- pan test fe # vitest
123
- pan demo uc2 # use case demo qua faker
124
- pan status
125
- pan down # docker compose down (giữ volume)
126
- pan reset -y # xoá volume + deploy lại
127
- ```
175
+ ## Project commands
176
+
177
+ The CLI loads **all** `.pan.yaml` files in the workspace (root + each
178
+ `repositories[].path`). When the same command name appears in multiple repos,
179
+ use `--project <name>` or pick interactively.
180
+
181
+ ### Environment variables
128
182
 
129
- ## `./pan` ở root repo
183
+ | Variable | Meaning |
184
+ |----------|---------|
185
+ | `PAN_WORKSPACE_ROOT` | Directory containing `.project.yaml` |
186
+ | `PAN_REPO_ROOT` | Directory containing the active `.pan.yaml` |
187
+ | `PAN_PROJECT_ROOT` | Alias of `PAN_WORKSPACE_ROOT` |
188
+ | `PAN_ARG_1`, … | Arguments after the command name |
130
189
 
131
- Script `./pan` là shim mỏng:
190
+ ### Registry (optional)
132
191
 
133
- 1. Dùng `pan` global nếu đã cài
134
- 2. Fallback `node .pan/pan-stack-cli/bin/pan.js`
192
+ - `PAN_PROJECT_ROOTS` — extra workspace roots (`;` on Windows, `:` or `;` on Unix)
193
+ - `~/.pan/projects` — one workspace root per line
135
194
 
136
- ## Cấu trúc package
195
+ ## Package layout
137
196
 
138
197
  ```text
139
- .pan/pan-stack-cli/
140
- bin/pan.js # entry CLI
141
- lib/ # manifest, discover, run-command, git/
142
- lib/git/ # git-clone, pull, fetch, push, grv, gst (+ helpers)
143
- references/ # hướng dẫn publish npm
198
+ pan-stack-cli/
199
+ bin/pan.js # CLI entry
200
+ lib/
201
+ create.js # pan create scaffold
202
+ discover.js # workspace discovery
203
+ platform.js # Windows/cross-platform helpers
204
+ manifest.js # YAML parsing
205
+ run-command.js # command execution
206
+ git/ # clone, pull, sync-main, push, fetch, grv, gst
207
+ templates/workspace/ # pan create template
208
+ references/ # npm publish docs
144
209
  ```
145
210
 
146
- ## Phát triển local
211
+ ## Local development
147
212
 
148
- Cài global từ source (khuyên dùng trước publish): [references/npm-development.md](references/npm-development.md).
213
+ Install globally from source: [references/npm-development.md](references/npm-development.md).
149
214
 
150
- ## Publish npm
215
+ ## Publish to npm
151
216
 
152
- Xem [references/npm-release.md](references/npm-release.md).
217
+ See [references/npm-release.md](references/npm-release.md).
package/bin/pan.js CHANGED
@@ -6,9 +6,11 @@ const {
6
6
  findSourcesWithCommand,
7
7
  formatProjectList,
8
8
  isBuiltinGitCommand,
9
+ isBuiltinWorkspaceCommand,
9
10
  } = require("../lib/discover");
10
11
  const { listCommands } = require("../lib/manifest");
11
12
  const { runWithProjectResolution, runGitScript, sourceLabel } = require("../lib/run-command");
13
+ const { runCreate } = require("../lib/create");
12
14
 
13
15
  const GIT_SCRIPT_MAP = {
14
16
  clone: "git-clone.js",
@@ -17,6 +19,8 @@ const GIT_SCRIPT_MAP = {
17
19
  push: "git-push.js",
18
20
  grv: "git-grv.js",
19
21
  gst: "git-gst.js",
22
+ "sync-main": "git-sync-main.js",
23
+ sm: "git-sync-main.js",
20
24
  };
21
25
 
22
26
  function parseGlobalArgs(argv) {
@@ -48,43 +52,46 @@ function parseGlobalArgs(argv) {
48
52
  function printUsage() {
49
53
  console.log(`usage: pan <command> [options] [-- args...]
50
54
 
51
- repo commands (built-in):
55
+ workspace commands (built-in):
56
+ create Scaffold a new PAN Stack workspace (prompts for folder name)
52
57
  clone Clone all repositories from .project.yaml
53
58
  pull Pull latest changes for all repositories
59
+ sync-main (sm) Fetch origin/main and merge into current branch (pick repo or all)
54
60
  fetch Fetch all remotes for all repositories
55
61
  push Push all repositories and set upstream
56
- grv List git remotes -v (URL đầy đủ) cho workspace + repo con
57
- gst Git status cho workspace + repo con (forward args: pan gst -sb)
62
+ grv List git remotes -v for workspace + child repos
63
+ gst Git status for workspace + child repos (e.g. pan gst -sb)
58
64
 
59
65
  project commands:
60
- Gom từ .pan.yaml (workspace root + từng repo con trong .project.yaml)
66
+ Loaded from .pan.yaml (workspace root + each repo in .project.yaml)
61
67
 
62
68
  options:
63
- --project NAME Chọn repo khi nhiều .pan.yaml có cùng lệnh
64
- --base-dir DIR (git commands) thư mục gốc repo con
65
- --dry-run (git commands) chỉ in lệnh
66
- -h, --help Hiện trợ giúp
69
+ --project NAME Pick a repo when multiple .pan.yaml files define the same command
70
+ --base-dir DIR (git commands) base directory for child repo paths
71
+ --dry-run (git commands) print commands only
72
+ -h, --help Show help
67
73
 
68
74
  env:
69
- PAN_PROJECT_ROOTS Danh sách root workspace (phân cách bằng , hoặc ;)
70
- ~/.pan/projects Mỗi dòng một workspace root
75
+ PAN_PROJECT_ROOTS Semicolon-separated workspace roots (; on Windows, : or ; on Unix)
76
+ ~/.pan/projects One workspace root per line
71
77
 
72
78
  examples:
79
+ pan create
80
+ pan create bookshop-stack
73
81
  pan clone
74
82
  pan grv
75
- pan be
76
83
  pan test fe
77
- pan test --project csdl-cc-b-frontend-e2e
84
+ pan test --project bookshop-web
78
85
  `);
79
86
  }
80
87
 
81
88
  function printAllCommands(sources) {
82
89
  if (sources.length === 0) {
83
- console.log("Chưa tìm thấy .pan.yaml (workspace hoặc repo con).");
90
+ console.log("No .pan.yaml found (workspace or child repos).");
84
91
  return;
85
92
  }
86
93
 
87
- console.log("Lệnh đã load:");
94
+ console.log("Loaded commands:");
88
95
  for (const source of sources) {
89
96
  const commands = listCommands(source);
90
97
  if (commands.length === 0) {
@@ -119,6 +126,11 @@ async function main() {
119
126
 
120
127
  const { command, args, project } = parsed;
121
128
 
129
+ if (command === "create") {
130
+ await runCreate(args);
131
+ return;
132
+ }
133
+
122
134
  if (isBuiltinGitCommand(command)) {
123
135
  const gitArgs = [...args];
124
136
  if (command === "push" && !gitArgs.includes("--set-upstream")) {
@@ -128,6 +140,10 @@ async function main() {
128
140
  return;
129
141
  }
130
142
 
143
+ if (isBuiltinWorkspaceCommand(command)) {
144
+ throw new Error(`Unknown built-in command: ${command}`);
145
+ }
146
+
131
147
  const allSources = loadAllCommandSources();
132
148
  const matches = findSourcesWithCommand(command);
133
149
  if (matches.length > 1 && !project && process.stdin.isTTY) {
package/lib/create.js ADDED
@@ -0,0 +1,87 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const readline = require("readline");
4
+ const { validateFolderName } = require("./platform");
5
+
6
+ const TEMPLATE_ROOT = path.join(__dirname, "..", "templates", "workspace");
7
+
8
+ function promptFolderName() {
9
+ return new Promise((resolve, reject) => {
10
+ if (!process.stdin.isTTY) {
11
+ reject(new Error("Folder name required: pan create <folder-name>"));
12
+ return;
13
+ }
14
+
15
+ const rl = readline.createInterface({
16
+ input: process.stdin,
17
+ output: process.stdout,
18
+ });
19
+
20
+ rl.question("Workspace folder name: ", (answer) => {
21
+ rl.close();
22
+ const name = answer.trim();
23
+ if (!name) {
24
+ reject(new Error("Folder name cannot be empty."));
25
+ return;
26
+ }
27
+ resolve(name);
28
+ });
29
+ });
30
+ }
31
+
32
+ function copyDir(src, dest) {
33
+ fs.mkdirSync(dest, { recursive: true });
34
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
35
+ const srcPath = path.join(src, entry.name);
36
+ const destPath = path.join(dest, entry.name);
37
+ if (entry.isDirectory()) {
38
+ copyDir(srcPath, destPath);
39
+ } else {
40
+ fs.copyFileSync(srcPath, destPath);
41
+ }
42
+ }
43
+ }
44
+
45
+ function applyPlaceholders(targetDir, folderName) {
46
+ const files = [".project.yaml", "README.md"];
47
+ for (const relativePath of files) {
48
+ const filePath = path.join(targetDir, relativePath);
49
+ if (!fs.existsSync(filePath)) {
50
+ continue;
51
+ }
52
+ const content = fs.readFileSync(filePath, "utf8");
53
+ fs.writeFileSync(filePath, content.replaceAll("__PROJECT_NAME__", folderName));
54
+ }
55
+ }
56
+
57
+ async function runCreate(args = []) {
58
+ let folderName = args[0]?.trim();
59
+ if (!folderName) {
60
+ folderName = await promptFolderName();
61
+ }
62
+
63
+ validateFolderName(folderName);
64
+
65
+ const targetDir = path.resolve(process.cwd(), folderName);
66
+ if (fs.existsSync(targetDir)) {
67
+ throw new Error(`Folder already exists: ${targetDir}`);
68
+ }
69
+
70
+ if (!fs.existsSync(TEMPLATE_ROOT)) {
71
+ throw new Error(`Template not found: ${TEMPLATE_ROOT}`);
72
+ }
73
+
74
+ copyDir(TEMPLATE_ROOT, targetDir);
75
+ applyPlaceholders(targetDir, folderName);
76
+
77
+ console.log(`Created PAN Stack workspace: ${targetDir}`);
78
+ console.log("");
79
+ console.log("Next steps:");
80
+ console.log(` cd ${folderName}`);
81
+ console.log(" # Edit .project.yaml with your repository URLs");
82
+ console.log(" pan clone");
83
+ }
84
+
85
+ module.exports = {
86
+ runCreate,
87
+ };
package/lib/discover.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const fs = require("fs");
2
2
  const path = require("path");
3
3
  const os = require("os");
4
+ const { splitEnvList } = require("./platform");
4
5
  const {
5
6
  PAN_CONFIG_FILE,
6
7
  loadManifest,
@@ -11,16 +12,23 @@ const {
11
12
  cleanValue,
12
13
  } = require("./manifest");
13
14
 
14
- const BUILTIN_GIT_COMMANDS = new Set(["clone", "pull", "fetch", "push", "grv", "gst"]);
15
+ const BUILTIN_GIT_COMMANDS = new Set([
16
+ "clone",
17
+ "pull",
18
+ "sync-main",
19
+ "sm",
20
+ "fetch",
21
+ "push",
22
+ "grv",
23
+ "gst",
24
+ ]);
25
+ const BUILTIN_WORKSPACE_COMMANDS = new Set(["create", ...BUILTIN_GIT_COMMANDS]);
15
26
 
16
27
  function readProjectRoots() {
17
28
  const roots = [];
18
29
  const fromEnv = process.env.PAN_PROJECT_ROOTS || "";
19
- for (const part of fromEnv.split(/[:;,]/)) {
20
- const trimmed = part.trim();
21
- if (trimmed) {
22
- roots.push(path.resolve(trimmed));
23
- }
30
+ for (const part of splitEnvList(fromEnv)) {
31
+ roots.push(path.resolve(part));
24
32
  }
25
33
 
26
34
  const registry = path.join(os.homedir(), ".pan", "projects");
@@ -188,7 +196,7 @@ function findSourcesWithCommand(commandName, startDir = process.cwd()) {
188
196
  }
189
197
 
190
198
  function formatProjectList(matches, commandName) {
191
- const lines = [`Lệnh '${commandName}' có ở nhiều nơi:`];
199
+ const lines = [`Command '${commandName}' is defined in multiple places:`];
192
200
  matches.forEach((source, index) => {
193
201
  const def = source.commands[commandName];
194
202
  const desc = def?.description ? ` — ${def.description}` : "";
@@ -199,7 +207,7 @@ function formatProjectList(matches, commandName) {
199
207
  lines.push(` ${index + 1}. ${label} (${source.root})${desc}`);
200
208
  });
201
209
  lines.push("");
202
- lines.push("Chọn số thứ tự, hoặc chạy lại với --project <tên-repo>.");
210
+ lines.push("Pick a number, or rerun with --project <repo-name>.");
203
211
  return lines.join("\n");
204
212
  }
205
213
 
@@ -207,8 +215,13 @@ function isBuiltinGitCommand(name) {
207
215
  return BUILTIN_GIT_COMMANDS.has(name);
208
216
  }
209
217
 
218
+ function isBuiltinWorkspaceCommand(name) {
219
+ return BUILTIN_WORKSPACE_COMMANDS.has(name);
220
+ }
221
+
210
222
  module.exports = {
211
223
  BUILTIN_GIT_COMMANDS,
224
+ BUILTIN_WORKSPACE_COMMANDS,
212
225
  PAN_CONFIG_FILE,
213
226
  findNearestProjectManifest,
214
227
  findNearestWorkspace,
@@ -217,6 +230,7 @@ module.exports = {
217
230
  findSourcesWithCommand,
218
231
  formatProjectList,
219
232
  isBuiltinGitCommand,
233
+ isBuiltinWorkspaceCommand,
220
234
  getCommandDefinition,
221
235
  sourceMatchesProject,
222
236
  };
@@ -10,6 +10,7 @@
10
10
  const fs = require("fs");
11
11
  const path = require("path");
12
12
  const { spawnSync } = require("child_process");
13
+ const { spawnOptions } = require("../platform");
13
14
  const { parseCommonArgs, loadManifestRepos, printCommonOptions } = require("./helpers/cli");
14
15
 
15
16
  function cloneRepo({ name, url, destination, dryRun }) {
@@ -26,7 +27,7 @@ function cloneRepo({ name, url, destination, dryRun }) {
26
27
  }
27
28
 
28
29
  console.log(`[clone] ${name}: ${url} -> ${destination}`);
29
- const result = spawnSync(command[0], command.slice(1), { stdio: "inherit" });
30
+ const result = spawnSync(command[0], command.slice(1), spawnOptions({ stdio: "inherit" }));
30
31
  if (result.status !== 0) {
31
32
  console.log(`[warn] ${name}: clone failed, check repo access or url`);
32
33
  }
@@ -2,7 +2,7 @@
2
2
  /*
3
3
  * List git remotes (git remote -v) for PAN Stack project repositories.
4
4
  *
5
- * Alias tương đương grv trong oh-my-zsh — hiển thị URL đầy đủ (fetch/push).
5
+ * oh-my-zsh-style alias for grv — full remote URLs (fetch/push).
6
6
  */
7
7
 
8
8
  const { parseCommonArgs, loadManifestRepos, printCommonOptions } = require("./helpers/cli");
@@ -18,7 +18,7 @@ function printRepoRemotes({ label, destination, dryRun }) {
18
18
  console.log(destination);
19
19
  const lines = listRemotesVerbose(destination);
20
20
  if (lines.length === 0) {
21
- console.log("(không có remote)");
21
+ console.log("(no remotes)");
22
22
  } else {
23
23
  for (const line of lines) {
24
24
  console.log(line);
@@ -45,7 +45,7 @@ function printHelp() {
45
45
  List git remotes with full URLs (git remote -v) for each repository.
46
46
 
47
47
  ${printCommonOptions()}
48
- --no-workspace Skip workspace root (chỉ repo trong manifest)`);
48
+ --no-workspace Skip workspace root (manifest repos only)`);
49
49
  }
50
50
 
51
51
  function main() {
@@ -2,7 +2,7 @@
2
2
  /*
3
3
  * Git status for PAN Stack project repositories.
4
4
  *
5
- * Alias tương đương gst trong oh-my-zsh — chạy git status trên workspace + repo con.
5
+ * oh-my-zsh-style alias for gst — git status on workspace + child repos.
6
6
  */
7
7
 
8
8
  const { parseCommonArgs, loadManifestRepos, printCommonOptions } = require("./helpers/cli");
@@ -38,10 +38,10 @@ function printHelp() {
38
38
  console.log(`usage: git-gst.js [--base-dir BASE_DIR] [--dry-run] [--no-workspace] [manifest] [git status args...]
39
39
 
40
40
  Run git status for workspace root and each repository in the manifest.
41
- Extra args are forwarded to git status (vd: -s, -sb).
41
+ Extra args are forwarded to git status (e.g. -s, -sb).
42
42
 
43
43
  ${printCommonOptions()}
44
- --no-workspace Skip workspace root (chỉ repo trong manifest)`);
44
+ --no-workspace Skip workspace root (manifest repos only)`);
45
45
  }
46
46
 
47
47
  function main() {