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.
- package/README.md +37 -731
- package/bin/pan.js +4955 -269
- package/package.json +17 -5
- package/lib/ai-skills.js +0 -829
- package/lib/create.js +0 -105
- package/lib/discover.js +0 -294
- package/lib/git/git-checkout.js +0 -395
- package/lib/git/git-clone.js +0 -206
- package/lib/git/git-fetch-all.js +0 -129
- package/lib/git/git-grv.js +0 -125
- package/lib/git/git-gst.js +0 -165
- package/lib/git/git-mr.js +0 -546
- package/lib/git/git-pull.js +0 -137
- package/lib/git/git-push.js +0 -185
- package/lib/git/git-sync-main.js +0 -334
- package/lib/git/helpers/bulk-report.js +0 -202
- package/lib/git/helpers/cli.js +0 -188
- package/lib/git/helpers/colors.js +0 -19
- package/lib/git/helpers/git.js +0 -136
- package/lib/git/helpers/manifest.js +0 -223
- package/lib/git/helpers/repo-select.js +0 -416
- package/lib/git/helpers/repo-summary.js +0 -161
- package/lib/i18n.js +0 -61
- package/lib/inquirer-theme.js +0 -20
- package/lib/language.js +0 -121
- package/lib/languages.js +0 -14
- package/lib/manifest.js +0 -467
- package/lib/menu-prompt.js +0 -201
- package/lib/menu.js +0 -134
- package/lib/messages.js +0 -647
- package/lib/platform.js +0 -140
- package/lib/run-command.js +0 -334
- package/lib/select.js +0 -0
- package/lib/serve.js +0 -75
- package/lib/skill-registry-client.js +0 -173
- package/lib/stats/aggregate.js +0 -51
- package/lib/stats/identity.js +0 -20
- package/lib/stats/index.js +0 -189
- package/lib/stats/parse.js +0 -46
- package/lib/stats/range.js +0 -38
- package/lib/stats/report.js +0 -264
- package/lib/stats/review.js +0 -157
- package/lib/stats/scan.js +0 -92
- package/lib/stats/serve.js +0 -25
- package/lib/ui/api.js +0 -202
- package/lib/ui/clone.js +0 -115
- package/lib/ui/config.js +0 -182
- package/lib/ui/diff.js +0 -128
- package/lib/ui/export-gate.js +0 -79
- package/lib/ui/gen-react-mobile-code.js +0 -6
- package/lib/ui/gen-react-web-code.js +0 -75
- package/lib/ui/index.js +0 -89
- package/lib/ui/init.js +0 -252
- package/lib/ui/login.js +0 -128
- package/lib/ui/logout.js +0 -22
- package/lib/ui/palettes.js +0 -103
- package/lib/ui/published-html.js +0 -24
- package/lib/ui/sync.js +0 -137
- package/lib/ui/workspace.js +0 -213
package/lib/create.js
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
const fs = require("fs");
|
|
2
|
-
const os = require("os");
|
|
3
|
-
const path = require("path");
|
|
4
|
-
const readline = require("readline");
|
|
5
|
-
const { validateFolderName } = require("./platform");
|
|
6
|
-
const { getRegistryBaseUrl, fetchRegistry, downloadAndExtract } = require("./skill-registry-client");
|
|
7
|
-
|
|
8
|
-
const TEMPLATE_NAME = "workspace";
|
|
9
|
-
|
|
10
|
-
function promptFolderName() {
|
|
11
|
-
return new Promise((resolve, reject) => {
|
|
12
|
-
if (!process.stdin.isTTY) {
|
|
13
|
-
reject(new Error("Folder name required: pan create <folder-name>"));
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const rl = readline.createInterface({
|
|
18
|
-
input: process.stdin,
|
|
19
|
-
output: process.stdout,
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
rl.question("Workspace folder name: ", (answer) => {
|
|
23
|
-
rl.close();
|
|
24
|
-
const name = answer.trim();
|
|
25
|
-
if (!name) {
|
|
26
|
-
reject(new Error("Folder name cannot be empty."));
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
resolve(name);
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function copyDir(src, dest) {
|
|
35
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
36
|
-
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
37
|
-
if (entry.name === "template.json") continue; // metadata riêng cho registry, không phải file scaffold
|
|
38
|
-
const srcPath = path.join(src, entry.name);
|
|
39
|
-
const destPath = path.join(dest, entry.name);
|
|
40
|
-
if (entry.isDirectory()) {
|
|
41
|
-
copyDir(srcPath, destPath);
|
|
42
|
-
} else {
|
|
43
|
-
fs.copyFileSync(srcPath, destPath);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function applyPlaceholders(targetDir, folderName) {
|
|
49
|
-
const files = [".project.yaml", "README.md"];
|
|
50
|
-
for (const relativePath of files) {
|
|
51
|
-
const filePath = path.join(targetDir, relativePath);
|
|
52
|
-
if (!fs.existsSync(filePath)) {
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
const content = fs.readFileSync(filePath, "utf8");
|
|
56
|
-
fs.writeFileSync(filePath, content.replaceAll("__PROJECT_NAME__", folderName));
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function runCreate(args = []) {
|
|
61
|
-
let folderName = args[0]?.trim();
|
|
62
|
-
if (!folderName) {
|
|
63
|
-
folderName = await promptFolderName();
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
validateFolderName(folderName);
|
|
67
|
-
|
|
68
|
-
const targetDir = path.resolve(process.cwd(), folderName);
|
|
69
|
-
if (fs.existsSync(targetDir)) {
|
|
70
|
-
throw new Error(`Folder already exists: ${targetDir}`);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Template scaffold không còn bundle sẵn trong pan-stack-cli — tải từ registry server (giống
|
|
74
|
-
// cơ chế `pan ai install`). Cần PAN_SKILL_REGISTRY_URL (hoặc default localhost:4600) chạy được.
|
|
75
|
-
const baseUrl = getRegistryBaseUrl();
|
|
76
|
-
const items = await fetchRegistry(baseUrl);
|
|
77
|
-
const template = items.find((i) => i.type === "template" && i.name === TEMPLATE_NAME);
|
|
78
|
-
if (!template) {
|
|
79
|
-
throw new Error(
|
|
80
|
-
`Không tìm thấy template '${TEMPLATE_NAME}' trên registry server (${baseUrl}). Kiểm tra apps/pan-agent-skill-registry đã build/deploy đủ registry/templates/${TEMPLATE_NAME} chưa.`
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const stagingDir = fs.mkdtempSync(path.join(os.tmpdir(), "pan-create-"));
|
|
85
|
-
let extractedDir;
|
|
86
|
-
try {
|
|
87
|
-
extractedDir = await downloadAndExtract(baseUrl, template, stagingDir);
|
|
88
|
-
copyDir(extractedDir, targetDir);
|
|
89
|
-
} finally {
|
|
90
|
-
fs.rmSync(stagingDir, { recursive: true, force: true });
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
applyPlaceholders(targetDir, folderName);
|
|
94
|
-
|
|
95
|
-
console.log(`Created PAN Stack workspace: ${targetDir}`);
|
|
96
|
-
console.log("");
|
|
97
|
-
console.log("Next steps:");
|
|
98
|
-
console.log(` cd ${folderName}`);
|
|
99
|
-
console.log(" # Edit .project.yaml with your repository URLs");
|
|
100
|
-
console.log(" pan clone");
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
module.exports = {
|
|
104
|
-
runCreate,
|
|
105
|
-
};
|
package/lib/discover.js
DELETED
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
const fs = require("fs");
|
|
2
|
-
const path = require("path");
|
|
3
|
-
const os = require("os");
|
|
4
|
-
const { splitEnvList } = require("./platform");
|
|
5
|
-
const {
|
|
6
|
-
PAN_CONFIG_FILE,
|
|
7
|
-
loadManifest,
|
|
8
|
-
loadPanConfig,
|
|
9
|
-
getCommandDefinition,
|
|
10
|
-
commandAliases,
|
|
11
|
-
validateRepo,
|
|
12
|
-
cleanValue,
|
|
13
|
-
} = require("./manifest");
|
|
14
|
-
|
|
15
|
-
const BUILTIN_GIT_COMMANDS = new Set([
|
|
16
|
-
"clone",
|
|
17
|
-
"pull",
|
|
18
|
-
"sync-main",
|
|
19
|
-
"sm",
|
|
20
|
-
"fetch",
|
|
21
|
-
"gfa",
|
|
22
|
-
"push",
|
|
23
|
-
"grv",
|
|
24
|
-
"gst",
|
|
25
|
-
"mr",
|
|
26
|
-
"checkout",
|
|
27
|
-
"co",
|
|
28
|
-
]);
|
|
29
|
-
const BUILTIN_WORKSPACE_COMMANDS = new Set([
|
|
30
|
-
"create",
|
|
31
|
-
"serve",
|
|
32
|
-
"language",
|
|
33
|
-
"lang",
|
|
34
|
-
"ai",
|
|
35
|
-
"skills",
|
|
36
|
-
"ui",
|
|
37
|
-
"stats",
|
|
38
|
-
"graph",
|
|
39
|
-
"git",
|
|
40
|
-
...BUILTIN_GIT_COMMANDS,
|
|
41
|
-
]);
|
|
42
|
-
|
|
43
|
-
function readProjectRoots() {
|
|
44
|
-
const roots = [];
|
|
45
|
-
const fromEnv = process.env.PAN_PROJECT_ROOTS || "";
|
|
46
|
-
for (const part of splitEnvList(fromEnv)) {
|
|
47
|
-
roots.push(path.resolve(part));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const registry = path.join(os.homedir(), ".pan", "projects");
|
|
51
|
-
if (fs.existsSync(registry)) {
|
|
52
|
-
const content = fs.readFileSync(registry, "utf8");
|
|
53
|
-
for (const line of content.split(/\r?\n/)) {
|
|
54
|
-
const trimmed = line.trim();
|
|
55
|
-
if (!trimmed || trimmed.startsWith("#")) {
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
roots.push(path.resolve(trimmed));
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return [...new Set(roots)];
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function manifestPathInDir(dir) {
|
|
66
|
-
const candidate = path.join(dir, ".project.yaml");
|
|
67
|
-
return fs.existsSync(candidate) ? candidate : null;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function walkUpManifests(startDir) {
|
|
71
|
-
const found = [];
|
|
72
|
-
let current = path.resolve(startDir);
|
|
73
|
-
const seen = new Set();
|
|
74
|
-
|
|
75
|
-
while (true) {
|
|
76
|
-
const manifestPath = manifestPathInDir(current);
|
|
77
|
-
if (manifestPath && !seen.has(manifestPath)) {
|
|
78
|
-
seen.add(manifestPath);
|
|
79
|
-
found.push(manifestPath);
|
|
80
|
-
}
|
|
81
|
-
const parent = path.dirname(current);
|
|
82
|
-
if (parent === current) {
|
|
83
|
-
break;
|
|
84
|
-
}
|
|
85
|
-
current = parent;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return found;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function findNearestProjectManifest(startDir = process.cwd()) {
|
|
92
|
-
const walked = walkUpManifests(startDir);
|
|
93
|
-
return walked[0] || null;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const DISCOVER_SKIP_DIRS = new Set([
|
|
97
|
-
".git", "node_modules", ".pnpm", "dist", "build", ".next", "target", "vendor",
|
|
98
|
-
]);
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Fallback khi không có .project.yaml: quét git repo từ CHÍNH thư mục đang gõ
|
|
102
|
-
* lệnh (startDir) xuống tối đa `maxDepth` cấp — bao gồm chính startDir, thư mục
|
|
103
|
-
* con trực tiếp, và các cấp con tiếp theo (đủ bắt submodule). Dùng chung cho
|
|
104
|
-
* `pan stats` và các lệnh git built-in (`pan pull|push|fetch|checkout|...`) khi
|
|
105
|
-
* chạy ngoài workspace pan-stack (không có `.project.yaml`).
|
|
106
|
-
* @returns {Array<{name,dir}>}
|
|
107
|
-
*/
|
|
108
|
-
function discoverGitRepos(startDir = process.cwd(), maxDepth = 2) {
|
|
109
|
-
const base = path.resolve(startDir);
|
|
110
|
-
const found = [];
|
|
111
|
-
const seen = new Set();
|
|
112
|
-
|
|
113
|
-
function walk(dir, depth) {
|
|
114
|
-
if (depth > maxDepth) return;
|
|
115
|
-
let entries;
|
|
116
|
-
try {
|
|
117
|
-
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
118
|
-
} catch {
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
if (entries.some((e) => e.name === ".git")) {
|
|
122
|
-
const abs = path.resolve(dir);
|
|
123
|
-
if (!seen.has(abs)) {
|
|
124
|
-
seen.add(abs);
|
|
125
|
-
found.push({ name: path.basename(abs), dir: abs });
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
for (const e of entries) {
|
|
129
|
-
if (e.isDirectory() && !DISCOVER_SKIP_DIRS.has(e.name)) walk(path.join(dir, e.name), depth + 1);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
walk(base, 0);
|
|
134
|
-
return found.sort((a, b) => a.name.localeCompare(b.name));
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function findNearestWorkspace(startDir = process.cwd()) {
|
|
138
|
-
const manifestPath = findNearestProjectManifest(startDir);
|
|
139
|
-
if (!manifestPath) {
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
return loadManifest(manifestPath);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function loadCommandSourcesForWorkspace(workspace) {
|
|
146
|
-
const sources = [];
|
|
147
|
-
const workspaceRoot = workspace.root;
|
|
148
|
-
const workspacePan = path.join(workspaceRoot, PAN_CONFIG_FILE);
|
|
149
|
-
|
|
150
|
-
if (fs.existsSync(workspacePan)) {
|
|
151
|
-
sources.push(
|
|
152
|
-
loadPanConfig(workspacePan, {
|
|
153
|
-
workspaceRoot,
|
|
154
|
-
project: workspace.project,
|
|
155
|
-
repoName: workspace.project,
|
|
156
|
-
scope: "workspace",
|
|
157
|
-
})
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
for (const repo of workspace.repositories) {
|
|
162
|
-
try {
|
|
163
|
-
const { name, repoPath } = validateRepo(repo);
|
|
164
|
-
const repoDir = path.resolve(workspaceRoot, repoPath);
|
|
165
|
-
const repoPan = path.join(repoDir, PAN_CONFIG_FILE);
|
|
166
|
-
if (!fs.existsSync(repoPan)) {
|
|
167
|
-
continue;
|
|
168
|
-
}
|
|
169
|
-
sources.push(
|
|
170
|
-
loadPanConfig(repoPan, {
|
|
171
|
-
workspaceRoot,
|
|
172
|
-
project: name,
|
|
173
|
-
repoName: name,
|
|
174
|
-
scope: "repo",
|
|
175
|
-
})
|
|
176
|
-
);
|
|
177
|
-
} catch {
|
|
178
|
-
// skip invalid repo entries
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return sources;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function collectWorkspaces(startDir = process.cwd()) {
|
|
186
|
-
const workspaces = new Map();
|
|
187
|
-
|
|
188
|
-
for (const manifestPath of walkUpManifests(startDir)) {
|
|
189
|
-
try {
|
|
190
|
-
const workspace = loadManifest(manifestPath);
|
|
191
|
-
workspaces.set(workspace.root, workspace);
|
|
192
|
-
} catch {
|
|
193
|
-
// ignore
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
for (const root of readProjectRoots()) {
|
|
198
|
-
const manifestPath = manifestPathInDir(root);
|
|
199
|
-
if (!manifestPath) {
|
|
200
|
-
continue;
|
|
201
|
-
}
|
|
202
|
-
try {
|
|
203
|
-
const workspace = loadManifest(manifestPath);
|
|
204
|
-
workspaces.set(workspace.root, workspace);
|
|
205
|
-
} catch {
|
|
206
|
-
// ignore
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return [...workspaces.values()];
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function loadAllCommandSources(startDir = process.cwd()) {
|
|
214
|
-
const sources = [];
|
|
215
|
-
const seen = new Set();
|
|
216
|
-
|
|
217
|
-
for (const workspace of collectWorkspaces(startDir)) {
|
|
218
|
-
for (const source of loadCommandSourcesForWorkspace(workspace)) {
|
|
219
|
-
const key = source.path;
|
|
220
|
-
if (seen.has(key)) {
|
|
221
|
-
continue;
|
|
222
|
-
}
|
|
223
|
-
seen.add(key);
|
|
224
|
-
sources.push(source);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
return sources;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function sourceMatchesProject(source, projectFlag) {
|
|
232
|
-
const flag = projectFlag.trim();
|
|
233
|
-
return (
|
|
234
|
-
source.project === flag ||
|
|
235
|
-
source.repoName === flag ||
|
|
236
|
-
path.basename(source.root) === flag
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function sourceHasCommand(source, commandName) {
|
|
241
|
-
if (source.commands[commandName]) {
|
|
242
|
-
return true;
|
|
243
|
-
}
|
|
244
|
-
return Object.values(source.commands || {}).some((def) =>
|
|
245
|
-
commandAliases(def).includes(commandName)
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function findSourcesWithCommand(commandName, startDir = process.cwd()) {
|
|
250
|
-
return loadAllCommandSources(startDir).filter((source) =>
|
|
251
|
-
sourceHasCommand(source, commandName)
|
|
252
|
-
);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function formatProjectList(matches, commandName) {
|
|
256
|
-
const lines = [`Command '${commandName}' is defined in multiple places:`];
|
|
257
|
-
matches.forEach((source, index) => {
|
|
258
|
-
const def = source.commands[commandName];
|
|
259
|
-
const desc = def?.description ? ` — ${def.description}` : "";
|
|
260
|
-
const label =
|
|
261
|
-
source.scope === "workspace"
|
|
262
|
-
? `${source.project} (workspace)`
|
|
263
|
-
: source.repoName || source.project;
|
|
264
|
-
lines.push(` ${index + 1}. ${label} (${source.root})${desc}`);
|
|
265
|
-
});
|
|
266
|
-
lines.push("");
|
|
267
|
-
lines.push("Pick a number, or rerun with --project <repo-name>.");
|
|
268
|
-
return lines.join("\n");
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function isBuiltinGitCommand(name) {
|
|
272
|
-
return BUILTIN_GIT_COMMANDS.has(name);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function isBuiltinWorkspaceCommand(name) {
|
|
276
|
-
return BUILTIN_WORKSPACE_COMMANDS.has(name);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
module.exports = {
|
|
280
|
-
BUILTIN_GIT_COMMANDS,
|
|
281
|
-
BUILTIN_WORKSPACE_COMMANDS,
|
|
282
|
-
PAN_CONFIG_FILE,
|
|
283
|
-
findNearestProjectManifest,
|
|
284
|
-
findNearestWorkspace,
|
|
285
|
-
discoverGitRepos,
|
|
286
|
-
loadAllCommandSources,
|
|
287
|
-
loadCommandSourcesForWorkspace,
|
|
288
|
-
findSourcesWithCommand,
|
|
289
|
-
formatProjectList,
|
|
290
|
-
isBuiltinGitCommand,
|
|
291
|
-
isBuiltinWorkspaceCommand,
|
|
292
|
-
getCommandDefinition,
|
|
293
|
-
sourceMatchesProject,
|
|
294
|
-
};
|