@nalvietnam/avatar-cli 1.11.2 → 1.12.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/dist/index.js +378 -253
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -145,8 +145,8 @@ async function writeUserConfig(config) {
|
|
|
145
145
|
}
|
|
146
146
|
async function clearUserConfig() {
|
|
147
147
|
if (await pathExists(USER_CONFIG_PATH)) {
|
|
148
|
-
const { promises:
|
|
149
|
-
await
|
|
148
|
+
const { promises: fs15 } = await import("fs");
|
|
149
|
+
await fs15.unlink(USER_CONFIG_PATH);
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
function isTokenExpired(config) {
|
|
@@ -223,7 +223,7 @@ function spinnerWithElapsed(prefix) {
|
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
// src/lib/check-claude-code-subscription-and-quota.ts
|
|
226
|
-
var QUOTA_VERIFY_TIMEOUT_MS =
|
|
226
|
+
var QUOTA_VERIFY_TIMEOUT_MS = 6e4;
|
|
227
227
|
var QUOTA_VERIFY_PROMPT = "ok";
|
|
228
228
|
function checkClaudeCodeSubscriptionAuth() {
|
|
229
229
|
const result = spawnSync("claude", ["auth", "status"], { encoding: "utf8" });
|
|
@@ -283,7 +283,7 @@ function getQuotaErrorHint(reason) {
|
|
|
283
283
|
case "rate_limit":
|
|
284
284
|
return "B\u1ECB rate limit t\u1EA1m th\u1EDDi. Ch\u1EDD v\xE0i ph\xFAt r\u1ED3i ch\u1EA1y `avatar ai setup`.";
|
|
285
285
|
case "timeout":
|
|
286
|
-
return "
|
|
286
|
+
return "Timeout 60s: (1) m\u1EA1ng VN ch\u1EADm \u2014 th\u1EED VPN, (2) Anthropic API spike \u2014 retry v\xE0i ph\xFAt, (3) token v\u1EABn auth nh\u01B0ng quota h\u1EBFt \xE2m th\u1EA7m \u2014 check t\u1EA1i claude.ai/settings/usage.";
|
|
287
287
|
default:
|
|
288
288
|
return "L\u1ED7i ch\u01B0a bi\u1EBFt. Xem stderr \u1EDF tr\xEAn + ch\u1EA1y `claude --print ok` tay \u0111\u1EC3 debug.";
|
|
289
289
|
}
|
|
@@ -294,8 +294,13 @@ function verifyClaudeCodeQuota() {
|
|
|
294
294
|
timeout: QUOTA_VERIFY_TIMEOUT_MS,
|
|
295
295
|
stdio: ["ignore", "pipe", "pipe"]
|
|
296
296
|
});
|
|
297
|
-
|
|
298
|
-
|
|
297
|
+
const isTimeout = result.signal === "SIGTERM" || result.status === 143 || result.error?.code === "ETIMEDOUT";
|
|
298
|
+
if (isTimeout) {
|
|
299
|
+
return {
|
|
300
|
+
ok: false,
|
|
301
|
+
reason: "timeout",
|
|
302
|
+
detail: `claude --print > ${QUOTA_VERIFY_TIMEOUT_MS / 1e3}s (m\u1EA1ng ch\u1EADm / API rate limit / token revoked kh\xF4ng return error)`
|
|
303
|
+
};
|
|
299
304
|
}
|
|
300
305
|
const stderr = result.stderr || "";
|
|
301
306
|
const stdout = result.stdout || "";
|
|
@@ -2329,7 +2334,7 @@ function registerGitnexusCommand(program2) {
|
|
|
2329
2334
|
}
|
|
2330
2335
|
|
|
2331
2336
|
// src/commands/init.ts
|
|
2332
|
-
import { select as
|
|
2337
|
+
import { select as select13 } from "@inquirer/prompts";
|
|
2333
2338
|
|
|
2334
2339
|
// src/lib/avatar-ascii-banner.ts
|
|
2335
2340
|
import chalk2 from "chalk";
|
|
@@ -2393,11 +2398,178 @@ ${renderAvatarBanner(opts)}
|
|
|
2393
2398
|
}
|
|
2394
2399
|
|
|
2395
2400
|
// src/lib/handle-remote-access-failure-with-account-switch.ts
|
|
2401
|
+
import { spawnSync as spawnSync16 } from "child_process";
|
|
2402
|
+
import { input as input4, select as select6 } from "@inquirer/prompts";
|
|
2403
|
+
|
|
2404
|
+
// src/lib/reset-folder-git-and-create-new-remote-under-current-user.ts
|
|
2405
|
+
import { spawnSync as spawnSync14 } from "child_process";
|
|
2406
|
+
import { promises as fs9 } from "fs";
|
|
2407
|
+
import { basename, join as join16 } from "path";
|
|
2408
|
+
import { confirm as confirm4, select as select5 } from "@inquirer/prompts";
|
|
2409
|
+
|
|
2410
|
+
// src/lib/execute-gh-repo-create.ts
|
|
2411
|
+
import { spawnSync as spawnSync12 } from "child_process";
|
|
2412
|
+
var RepoAlreadyExistsError = class extends Error {
|
|
2413
|
+
constructor(fullName) {
|
|
2414
|
+
super(`Repo "${fullName}" \u0111\xE3 t\u1ED3n t\u1EA1i tr\xEAn GitHub. \u0110\u1ED5i t\xEAn ho\u1EB7c x\xF3a repo c\u0169.`);
|
|
2415
|
+
this.name = "RepoAlreadyExistsError";
|
|
2416
|
+
}
|
|
2417
|
+
};
|
|
2418
|
+
function executeGhRepoCreate(input8) {
|
|
2419
|
+
const fullName = `${input8.org}/${input8.name}`;
|
|
2420
|
+
const args = [
|
|
2421
|
+
"repo",
|
|
2422
|
+
"create",
|
|
2423
|
+
fullName,
|
|
2424
|
+
`--${input8.visibility}`,
|
|
2425
|
+
"--source",
|
|
2426
|
+
input8.folder,
|
|
2427
|
+
"--remote",
|
|
2428
|
+
"origin",
|
|
2429
|
+
"--push"
|
|
2430
|
+
];
|
|
2431
|
+
const r = spawnSync12("gh", args, { stdio: "inherit" });
|
|
2432
|
+
if (r.status !== 0) {
|
|
2433
|
+
if (r.status === 1) {
|
|
2434
|
+
throw new RepoAlreadyExistsError(fullName);
|
|
2435
|
+
}
|
|
2436
|
+
throw new Error(`gh repo create th\u1EA5t b\u1EA1i (exit ${r.status})`);
|
|
2437
|
+
}
|
|
2438
|
+
return {
|
|
2439
|
+
sshUrl: `git@github.com:${fullName}.git`,
|
|
2440
|
+
httpsUrl: `https://github.com/${fullName}.git`
|
|
2441
|
+
};
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
// src/lib/resolve-github-username-default.ts
|
|
2396
2445
|
import { spawnSync as spawnSync13 } from "child_process";
|
|
2397
|
-
|
|
2446
|
+
function resolveGithubUsernameDefault() {
|
|
2447
|
+
const r = spawnSync13("gh", ["api", "user", "--jq", ".login"], {
|
|
2448
|
+
encoding: "utf8",
|
|
2449
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2450
|
+
});
|
|
2451
|
+
if (r.status !== 0) {
|
|
2452
|
+
throw new Error(`Kh\xF4ng l\u1EA5y \u0111\u01B0\u1EE3c GitHub username: ${r.stderr?.trim()}`);
|
|
2453
|
+
}
|
|
2454
|
+
return r.stdout.trim();
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
// src/lib/validate-repo-name-and-visibility.ts
|
|
2458
|
+
var REPO_NAME_REGEX = /^[a-zA-Z0-9._-]{1,100}$/;
|
|
2459
|
+
var InvalidRepoNameError = class extends Error {
|
|
2460
|
+
constructor(name) {
|
|
2461
|
+
super(
|
|
2462
|
+
`T\xEAn repo "${name}" kh\xF4ng h\u1EE3p l\u1EC7. Ch\u1EC9 d\xF9ng ch\u1EEF/s\u1ED1/d\u1EA5u ch\u1EA5m/g\u1EA1ch/underscore, d\xE0i 1-100 k\xFD t\u1EF1.`
|
|
2463
|
+
);
|
|
2464
|
+
this.name = "InvalidRepoNameError";
|
|
2465
|
+
}
|
|
2466
|
+
};
|
|
2467
|
+
function validateRepoName(name) {
|
|
2468
|
+
if (!REPO_NAME_REGEX.test(name)) {
|
|
2469
|
+
throw new InvalidRepoNameError(name);
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
function validateRepoVisibility(v) {
|
|
2473
|
+
if (v !== "private" && v !== "public") {
|
|
2474
|
+
throw new Error(`Visibility ph\u1EA3i l\xE0 "private" ho\u1EB7c "public", nh\u1EADn: "${v}"`);
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2478
|
+
// src/lib/create-github-remote-from-folder.ts
|
|
2479
|
+
function createGithubRemoteFromFolder(input8) {
|
|
2480
|
+
validateRepoName(input8.name);
|
|
2481
|
+
validateRepoVisibility(input8.visibility);
|
|
2482
|
+
const org = input8.org ?? resolveGithubUsernameDefault();
|
|
2483
|
+
log.info(`T\u1EA1o GitHub repo ${org}/${input8.name} (${input8.visibility})...`);
|
|
2484
|
+
const urls = executeGhRepoCreate({
|
|
2485
|
+
folder: input8.folder,
|
|
2486
|
+
org,
|
|
2487
|
+
name: input8.name,
|
|
2488
|
+
visibility: input8.visibility
|
|
2489
|
+
});
|
|
2490
|
+
log.success(`\u0110\xE3 t\u1EA1o: ${urls.sshUrl}`);
|
|
2491
|
+
return urls;
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
// src/lib/reset-folder-git-and-create-new-remote-under-current-user.ts
|
|
2495
|
+
function backupTimestamp() {
|
|
2496
|
+
const d = /* @__PURE__ */ new Date();
|
|
2497
|
+
return `${d.getFullYear().toString().slice(-2)}${String(d.getMonth() + 1).padStart(2, "0")}${String(d.getDate()).padStart(2, "0")}-${String(d.getHours()).padStart(2, "0")}${String(d.getMinutes()).padStart(2, "0")}`;
|
|
2498
|
+
}
|
|
2499
|
+
async function backupExistingDotGit(folderPath) {
|
|
2500
|
+
const gitDir = join16(folderPath, ".git");
|
|
2501
|
+
if (!await pathExists(gitDir)) {
|
|
2502
|
+
throw new Error(`.git kh\xF4ng t\u1ED3n t\u1EA1i \u1EDF ${folderPath} \u2014 kh\xF4ng c\u1EA7n reset.`);
|
|
2503
|
+
}
|
|
2504
|
+
const backupName = `.git.backup-${backupTimestamp()}`;
|
|
2505
|
+
const backupPath = join16(folderPath, backupName);
|
|
2506
|
+
await fs9.rename(gitDir, backupPath);
|
|
2507
|
+
log.success(`Backup .git \u2192 ${backupName}`);
|
|
2508
|
+
return backupPath;
|
|
2509
|
+
}
|
|
2510
|
+
function reinitGitInFolder(folderPath) {
|
|
2511
|
+
const r1 = spawnSync14("git", ["-C", folderPath, "init", "-b", "main"], {
|
|
2512
|
+
encoding: "utf8",
|
|
2513
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2514
|
+
});
|
|
2515
|
+
if (r1.status !== 0) {
|
|
2516
|
+
throw new Error(`git init th\u1EA5t b\u1EA1i: ${r1.stderr || r1.stdout}`);
|
|
2517
|
+
}
|
|
2518
|
+
log.success("Git init m\u1EDBi (branch main)");
|
|
2519
|
+
spawnSync14("git", ["-C", folderPath, "add", "-A"], {
|
|
2520
|
+
encoding: "utf8",
|
|
2521
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2522
|
+
});
|
|
2523
|
+
const r3 = spawnSync14(
|
|
2524
|
+
"git",
|
|
2525
|
+
["-C", folderPath, "commit", "-m", "chore: initial commit from existing folder"],
|
|
2526
|
+
{ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }
|
|
2527
|
+
);
|
|
2528
|
+
if (r3.status !== 0) {
|
|
2529
|
+
log.warn(`git commit th\u1EA5t b\u1EA1i (folder c\xF3 th\u1EC3 r\u1ED7ng): ${(r3.stderr || "").slice(0, 200)}`);
|
|
2530
|
+
} else {
|
|
2531
|
+
log.success("Initial commit");
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
async function resetFolderGitAndCreateNewRemoteUnderCurrentUser(opts) {
|
|
2535
|
+
const folderName = basename(opts.folderPath);
|
|
2536
|
+
const repoName = opts.repoName ?? folderName;
|
|
2537
|
+
if (!opts.autoYes) {
|
|
2538
|
+
const confirmed = await confirm4({
|
|
2539
|
+
message: `Folder '${folderName}' s\u1EBD \u0111\u01B0\u1EE3c reset:
|
|
2540
|
+
1. Backup .git \u2192 .git.backup-{timestamp}
|
|
2541
|
+
2. Git init m\u1EDBi (branch main, initial commit)
|
|
2542
|
+
3. T\u1EA1o GitHub repo m\u1EDBi '${repoName}' d\u01B0\u1EDBi account c\u1EE7a b\u1EA1n
|
|
2543
|
+
Ti\u1EBFp t\u1EE5c?`,
|
|
2544
|
+
default: true
|
|
2545
|
+
});
|
|
2546
|
+
if (!confirmed) {
|
|
2547
|
+
throw new Error("User abort reset folder.");
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
const visibility = opts.visibility ?? (opts.autoYes ? "private" : await select5({
|
|
2551
|
+
message: "Visibility cho repo m\u1EDBi?",
|
|
2552
|
+
choices: [
|
|
2553
|
+
{ name: "private (m\u1EB7c \u0111\u1ECBnh)", value: "private" },
|
|
2554
|
+
{ name: "public", value: "public" }
|
|
2555
|
+
]
|
|
2556
|
+
}));
|
|
2557
|
+
const backupPath = await backupExistingDotGit(opts.folderPath);
|
|
2558
|
+
reinitGitInFolder(opts.folderPath);
|
|
2559
|
+
const urls = createGithubRemoteFromFolder({
|
|
2560
|
+
folder: opts.folderPath,
|
|
2561
|
+
name: repoName,
|
|
2562
|
+
visibility,
|
|
2563
|
+
org: opts.org
|
|
2564
|
+
});
|
|
2565
|
+
return {
|
|
2566
|
+
newRemoteUrl: urls.httpsUrl,
|
|
2567
|
+
backupPath
|
|
2568
|
+
};
|
|
2569
|
+
}
|
|
2398
2570
|
|
|
2399
2571
|
// src/lib/verify-git-remote-accessible.ts
|
|
2400
|
-
import { spawnSync as
|
|
2572
|
+
import { spawnSync as spawnSync15 } from "child_process";
|
|
2401
2573
|
var TIMEOUT_MS = 5e3;
|
|
2402
2574
|
function classifyRemoteError(stderr) {
|
|
2403
2575
|
const text = stderr.toLowerCase();
|
|
@@ -2413,7 +2585,7 @@ function classifyRemoteError(stderr) {
|
|
|
2413
2585
|
return "unknown";
|
|
2414
2586
|
}
|
|
2415
2587
|
function tryVerifyGitRemoteAccessible(url) {
|
|
2416
|
-
const r =
|
|
2588
|
+
const r = spawnSync15("git", ["ls-remote", "--exit-code", url, "HEAD"], {
|
|
2417
2589
|
encoding: "utf8",
|
|
2418
2590
|
timeout: TIMEOUT_MS,
|
|
2419
2591
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -2435,7 +2607,7 @@ var RemoteAccessAbortedError = class extends Error {
|
|
|
2435
2607
|
}
|
|
2436
2608
|
};
|
|
2437
2609
|
function getCurrentGhUser() {
|
|
2438
|
-
const r =
|
|
2610
|
+
const r = spawnSync16("gh", ["api", "user", "--jq", ".login"], {
|
|
2439
2611
|
encoding: "utf8",
|
|
2440
2612
|
stdio: ["ignore", "pipe", "pipe"]
|
|
2441
2613
|
});
|
|
@@ -2444,7 +2616,7 @@ function getCurrentGhUser() {
|
|
|
2444
2616
|
}
|
|
2445
2617
|
function triggerGhAuthLoginInteractive() {
|
|
2446
2618
|
log.info("M\u1EDF browser \u0111\u1EC3 switch GitHub account...");
|
|
2447
|
-
const r =
|
|
2619
|
+
const r = spawnSync16("gh", ["auth", "login", "--web"], { stdio: "inherit" });
|
|
2448
2620
|
if (r.status !== 0) {
|
|
2449
2621
|
log.warn(`gh auth login exit ${r.status}. B\u1EA1n c\xF3 th\u1EC3 ch\u1EA1y tay r\u1ED3i quay l\u1EA1i retry.`);
|
|
2450
2622
|
}
|
|
@@ -2478,32 +2650,54 @@ async function handleRemoteAccessFailureWithAccountSwitch(args) {
|
|
|
2478
2650
|
log.dim(` L\xFD do: ${reason}${detail ? ` \u2014 ${detail.slice(0, 150)}` : ""}`);
|
|
2479
2651
|
log.info(getReasonHint(reason, currentUrl, ghUser));
|
|
2480
2652
|
if (ghUser) log.dim(` gh CLI hi\u1EC7n \u0111ang login: ${ghUser}`);
|
|
2481
|
-
const
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2653
|
+
const choices = [
|
|
2654
|
+
{
|
|
2655
|
+
name: "Nh\u1EADp l\u1EA1i URL \u0111\xFAng (recommended khi URL sai ch\xEDnh t\u1EA3)",
|
|
2656
|
+
value: "re-input-url"
|
|
2657
|
+
},
|
|
2658
|
+
{
|
|
2659
|
+
name: "Switch GitHub account (gh auth login \u2014 m\u1EDF browser)",
|
|
2660
|
+
value: "switch"
|
|
2661
|
+
},
|
|
2662
|
+
{
|
|
2663
|
+
name: "T\xF4i v\u1EEBa fix (accept invite / s\u1EEDa permission) \u2014 retry verify",
|
|
2664
|
+
value: "retry"
|
|
2665
|
+
}
|
|
2666
|
+
];
|
|
2667
|
+
if (args.folderPath) {
|
|
2668
|
+
choices.push({
|
|
2669
|
+
name: "Reset folder & t\u1EA1o repo M\u1EDAI d\u01B0\u1EDBi account c\u1EE7a t\xF4i (backup .git c\u0169)",
|
|
2670
|
+
value: "reset-folder"
|
|
2671
|
+
});
|
|
2672
|
+
}
|
|
2673
|
+
choices.push({
|
|
2674
|
+
name: "T\u1EA1m ng\u01B0ng init \u2014 ch\u1EA1y l\u1EA1i 'avatar init' sau",
|
|
2675
|
+
value: "abort"
|
|
2501
2676
|
});
|
|
2677
|
+
const action = await select6({ message: "C\xE1ch x\u1EED l\xFD?", choices });
|
|
2502
2678
|
if (action === "abort") {
|
|
2503
2679
|
throw new RemoteAccessAbortedError(
|
|
2504
2680
|
`User ch\u1ECDn t\u1EA1m ng\u01B0ng. Fix access ${currentUrl} r\u1ED3i ch\u1EA1y l\u1EA1i 'avatar init'.`
|
|
2505
2681
|
);
|
|
2506
2682
|
}
|
|
2683
|
+
if (action === "reset-folder") {
|
|
2684
|
+
if (!args.folderPath) {
|
|
2685
|
+
log.warn("Reset folder c\u1EA7n folderPath \u2014 internal error.");
|
|
2686
|
+
continue;
|
|
2687
|
+
}
|
|
2688
|
+
try {
|
|
2689
|
+
const reset = await resetFolderGitAndCreateNewRemoteUnderCurrentUser({
|
|
2690
|
+
folderPath: args.folderPath,
|
|
2691
|
+
visibility: args.defaultVisibility
|
|
2692
|
+
});
|
|
2693
|
+
log.success(`Folder \u0111\xE3 reset. Backup t\u1EA1i: ${reset.backupPath}`);
|
|
2694
|
+
log.success(`Remote m\u1EDBi: ${reset.newRemoteUrl}`);
|
|
2695
|
+
return { resolvedUrl: reset.newRemoteUrl };
|
|
2696
|
+
} catch (err) {
|
|
2697
|
+
log.warn(`Reset folder th\u1EA5t b\u1EA1i: ${err.message}`);
|
|
2698
|
+
continue;
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2507
2701
|
if (action === "re-input-url") {
|
|
2508
2702
|
const newUrl = await input4({
|
|
2509
2703
|
message: "URL git remote (https://github.com/owner/repo.git ho\u1EB7c git@github.com:owner/repo.git):",
|
|
@@ -2528,14 +2722,14 @@ async function handleRemoteAccessFailureWithAccountSwitch(args) {
|
|
|
2528
2722
|
|
|
2529
2723
|
// src/lib/safe-bootstrap-for-dirty-folder.ts
|
|
2530
2724
|
import { readdirSync } from "fs";
|
|
2531
|
-
import { select as
|
|
2725
|
+
import { select as select7 } from "@inquirer/prompts";
|
|
2532
2726
|
import { simpleGit as simpleGit3 } from "simple-git";
|
|
2533
2727
|
|
|
2534
2728
|
// src/lib/check-folder-has-git.ts
|
|
2535
2729
|
import { existsSync as existsSync6, statSync } from "fs";
|
|
2536
|
-
import { join as
|
|
2730
|
+
import { join as join17 } from "path";
|
|
2537
2731
|
function checkFolderHasGit(folderPath) {
|
|
2538
|
-
const gitPath =
|
|
2732
|
+
const gitPath = join17(folderPath, ".git");
|
|
2539
2733
|
if (!existsSync6(gitPath)) return false;
|
|
2540
2734
|
const stat = statSync(gitPath);
|
|
2541
2735
|
return stat.isDirectory() || stat.isFile();
|
|
@@ -2567,7 +2761,7 @@ async function createInitialGitCommit(folderPath) {
|
|
|
2567
2761
|
|
|
2568
2762
|
// src/lib/detect-folder-tech-stack.ts
|
|
2569
2763
|
import { existsSync as existsSync7 } from "fs";
|
|
2570
|
-
import { join as
|
|
2764
|
+
import { join as join18 } from "path";
|
|
2571
2765
|
var SIGNATURES = {
|
|
2572
2766
|
node: ["package.json"],
|
|
2573
2767
|
python: ["pyproject.toml", "requirements.txt", "setup.py", "Pipfile"],
|
|
@@ -2579,7 +2773,7 @@ var SIGNATURES = {
|
|
|
2579
2773
|
function detectFolderTechStack(folderPath) {
|
|
2580
2774
|
const matched = [];
|
|
2581
2775
|
for (const [stack, files] of Object.entries(SIGNATURES)) {
|
|
2582
|
-
if (files.some((f) => existsSync7(
|
|
2776
|
+
if (files.some((f) => existsSync7(join18(folderPath, f)))) {
|
|
2583
2777
|
matched.push(stack);
|
|
2584
2778
|
}
|
|
2585
2779
|
}
|
|
@@ -2588,25 +2782,25 @@ function detectFolderTechStack(folderPath) {
|
|
|
2588
2782
|
|
|
2589
2783
|
// src/lib/gitignore-template-loader.ts
|
|
2590
2784
|
import { readFileSync as readFileSync3 } from "fs";
|
|
2591
|
-
import { dirname as dirname4, join as
|
|
2785
|
+
import { dirname as dirname4, join as join19 } from "path";
|
|
2592
2786
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2593
2787
|
var __dirname = dirname4(fileURLToPath2(import.meta.url));
|
|
2594
2788
|
var CANDIDATE_DIRS = [
|
|
2595
2789
|
// Bundled production: dist/index.js → __dirname = .../dist/, sibling dist/templates
|
|
2596
|
-
|
|
2790
|
+
join19(__dirname, "templates", "gitignore"),
|
|
2597
2791
|
// Legacy bundled: nếu file là dist/lib/*.js (sub-bundle), templates ở dist/templates
|
|
2598
|
-
|
|
2792
|
+
join19(__dirname, "..", "templates", "gitignore"),
|
|
2599
2793
|
// Dev mode (vitest/tsx run src/ trực tiếp): __dirname = src/lib/
|
|
2600
|
-
|
|
2794
|
+
join19(__dirname, "..", "..", "src", "templates", "gitignore"),
|
|
2601
2795
|
// npm-installed alt: __dirname = .../dist/ → package_root/src/templates
|
|
2602
|
-
|
|
2796
|
+
join19(__dirname, "..", "src", "templates", "gitignore")
|
|
2603
2797
|
];
|
|
2604
2798
|
var AVATAR_MARKER_START = "# === avatar ===";
|
|
2605
2799
|
var AVATAR_MARKER_END = "# === /avatar ===";
|
|
2606
2800
|
function readTemplate(stack) {
|
|
2607
2801
|
for (const dir of CANDIDATE_DIRS) {
|
|
2608
2802
|
try {
|
|
2609
|
-
return readFileSync3(
|
|
2803
|
+
return readFileSync3(join19(dir, `${stack}.txt`), "utf8");
|
|
2610
2804
|
} catch {
|
|
2611
2805
|
}
|
|
2612
2806
|
}
|
|
@@ -2621,9 +2815,9 @@ ${readTemplate(s).trim()}`);
|
|
|
2621
2815
|
|
|
2622
2816
|
// src/lib/write-or-merge-gitignore.ts
|
|
2623
2817
|
import { existsSync as existsSync8, readFileSync as readFileSync4, writeFileSync } from "fs";
|
|
2624
|
-
import { join as
|
|
2818
|
+
import { join as join20 } from "path";
|
|
2625
2819
|
function writeOrMergeGitignore(folderPath, avatarBlock) {
|
|
2626
|
-
const path =
|
|
2820
|
+
const path = join20(folderPath, ".gitignore");
|
|
2627
2821
|
if (!existsSync8(path)) {
|
|
2628
2822
|
writeFileSync(path, avatarBlock, "utf8");
|
|
2629
2823
|
return;
|
|
@@ -2665,7 +2859,7 @@ async function promptBootstrapStrategy(state, opts) {
|
|
|
2665
2859
|
if (opts.presetStrategy) return opts.presetStrategy;
|
|
2666
2860
|
if (opts.autoYes) return "stash";
|
|
2667
2861
|
if (state === "empty" || state === "clean") return "commit-all";
|
|
2668
|
-
return await
|
|
2862
|
+
return await select7({
|
|
2669
2863
|
message: state === "dirty" ? "Folder c\xF3 changes ch\u01B0a commit. C\xE1ch x\u1EED l\xFD:" : "Folder c\xF3 file ch\u01B0a version. C\xE1ch x\u1EED l\xFD:",
|
|
2670
2864
|
choices: [
|
|
2671
2865
|
{
|
|
@@ -2799,22 +2993,22 @@ async function safeBootstrapGitInFolder(folderPath, opts = {}) {
|
|
|
2799
2993
|
}
|
|
2800
2994
|
|
|
2801
2995
|
// src/lib/team-pack-submodule-manager.ts
|
|
2802
|
-
import { join as
|
|
2996
|
+
import { join as join21 } from "path";
|
|
2803
2997
|
|
|
2804
2998
|
// src/lib/check-team-pack-access-with-retry-loop.ts
|
|
2805
|
-
import { spawnSync as
|
|
2806
|
-
import { confirm as
|
|
2999
|
+
import { spawnSync as spawnSync17 } from "child_process";
|
|
3000
|
+
import { confirm as confirm5, select as select8 } from "@inquirer/prompts";
|
|
2807
3001
|
import boxen3 from "boxen";
|
|
2808
3002
|
function parseRepoSlugFromGitUrl(url) {
|
|
2809
3003
|
const httpsMatch = url.match(/github\.com[/:]([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
2810
3004
|
return httpsMatch?.[1] ?? null;
|
|
2811
3005
|
}
|
|
2812
3006
|
function checkRepoAccess(repoSlug) {
|
|
2813
|
-
const r =
|
|
3007
|
+
const r = spawnSync17("gh", ["api", `repos/${repoSlug}`], { stdio: "ignore" });
|
|
2814
3008
|
return r.status === 0;
|
|
2815
3009
|
}
|
|
2816
3010
|
function getCurrentGhUser2() {
|
|
2817
|
-
const r =
|
|
3011
|
+
const r = spawnSync17("gh", ["api", "user", "--jq", ".login"], {
|
|
2818
3012
|
encoding: "utf8",
|
|
2819
3013
|
stdio: ["ignore", "pipe", "pipe"]
|
|
2820
3014
|
});
|
|
@@ -2823,13 +3017,13 @@ function getCurrentGhUser2() {
|
|
|
2823
3017
|
}
|
|
2824
3018
|
function triggerGhAuthLoginInteractive2() {
|
|
2825
3019
|
log.info("M\u1EDF browser \u0111\u1EC3 switch GitHub account...");
|
|
2826
|
-
const r =
|
|
3020
|
+
const r = spawnSync17("gh", ["auth", "login", "--web"], { stdio: "inherit" });
|
|
2827
3021
|
if (r.status !== 0) {
|
|
2828
3022
|
log.warn(`gh auth login exit ${r.status}. C\xF3 th\u1EC3 ch\u1EA1y tay r\u1ED3i quay l\u1EA1i retry.`);
|
|
2829
3023
|
}
|
|
2830
3024
|
}
|
|
2831
3025
|
async function copyInfoToClipboardWithConsent(info) {
|
|
2832
|
-
const ok = await
|
|
3026
|
+
const ok = await confirm5({
|
|
2833
3027
|
message: "Copy th\xF4ng tin (GitHub username + email) v\xE0o clipboard \u0111\u1EC3 d\xE1n v\xE0o Slack/email?",
|
|
2834
3028
|
default: true
|
|
2835
3029
|
});
|
|
@@ -2881,7 +3075,7 @@ async function ensureTeamPackAccessWithRetry(args) {
|
|
|
2881
3075
|
while (true) {
|
|
2882
3076
|
const ghUser = getCurrentGhUser2();
|
|
2883
3077
|
const ghUserDisplay = ghUser ?? "(ch\u01B0a gh auth)";
|
|
2884
|
-
const action = await
|
|
3078
|
+
const action = await select8({
|
|
2885
3079
|
message: "C\xE1ch x\u1EED l\xFD?",
|
|
2886
3080
|
choices: [
|
|
2887
3081
|
{
|
|
@@ -2995,7 +3189,7 @@ async function addTeamPackSubmodule(projectRoot, tag, ssoEmail, latest = false)
|
|
|
2995
3189
|
await checkoutBranchHeadInSubmodule(TEAM_PACK_RELATIVE_PATH, DEFAULT_PACK_BRANCH, projectRoot);
|
|
2996
3190
|
return { pinnedTag: `${DEFAULT_PACK_BRANCH} (HEAD)` };
|
|
2997
3191
|
}
|
|
2998
|
-
const submoduleDir =
|
|
3192
|
+
const submoduleDir = join21(projectRoot, TEAM_PACK_RELATIVE_PATH);
|
|
2999
3193
|
const allTags = await listTags(submoduleDir);
|
|
3000
3194
|
const target = pickLatestStableSemVerTag(allTags);
|
|
3001
3195
|
if (target) {
|
|
@@ -3004,7 +3198,7 @@ async function addTeamPackSubmodule(projectRoot, tag, ssoEmail, latest = false)
|
|
|
3004
3198
|
return { pinnedTag: target };
|
|
3005
3199
|
}
|
|
3006
3200
|
async function readPinnedPackVersion(projectRoot) {
|
|
3007
|
-
const submoduleRoot =
|
|
3201
|
+
const submoduleRoot = join21(projectRoot, TEAM_PACK_RELATIVE_PATH);
|
|
3008
3202
|
const tag = await tagAtHead(submoduleRoot);
|
|
3009
3203
|
if (tag) return tag;
|
|
3010
3204
|
const sha = await currentCommitSha(submoduleRoot);
|
|
@@ -3012,97 +3206,13 @@ async function readPinnedPackVersion(projectRoot) {
|
|
|
3012
3206
|
}
|
|
3013
3207
|
|
|
3014
3208
|
// src/commands/init-flow-handlers-for-each-project-status.ts
|
|
3015
|
-
import { basename as
|
|
3016
|
-
import { input as input7, select as
|
|
3017
|
-
|
|
3018
|
-
// src/lib/execute-gh-repo-create.ts
|
|
3019
|
-
import { spawnSync as spawnSync15 } from "child_process";
|
|
3020
|
-
var RepoAlreadyExistsError = class extends Error {
|
|
3021
|
-
constructor(fullName) {
|
|
3022
|
-
super(`Repo "${fullName}" \u0111\xE3 t\u1ED3n t\u1EA1i tr\xEAn GitHub. \u0110\u1ED5i t\xEAn ho\u1EB7c x\xF3a repo c\u0169.`);
|
|
3023
|
-
this.name = "RepoAlreadyExistsError";
|
|
3024
|
-
}
|
|
3025
|
-
};
|
|
3026
|
-
function executeGhRepoCreate(input8) {
|
|
3027
|
-
const fullName = `${input8.org}/${input8.name}`;
|
|
3028
|
-
const args = [
|
|
3029
|
-
"repo",
|
|
3030
|
-
"create",
|
|
3031
|
-
fullName,
|
|
3032
|
-
`--${input8.visibility}`,
|
|
3033
|
-
"--source",
|
|
3034
|
-
input8.folder,
|
|
3035
|
-
"--remote",
|
|
3036
|
-
"origin",
|
|
3037
|
-
"--push"
|
|
3038
|
-
];
|
|
3039
|
-
const r = spawnSync15("gh", args, { stdio: "inherit" });
|
|
3040
|
-
if (r.status !== 0) {
|
|
3041
|
-
if (r.status === 1) {
|
|
3042
|
-
throw new RepoAlreadyExistsError(fullName);
|
|
3043
|
-
}
|
|
3044
|
-
throw new Error(`gh repo create th\u1EA5t b\u1EA1i (exit ${r.status})`);
|
|
3045
|
-
}
|
|
3046
|
-
return {
|
|
3047
|
-
sshUrl: `git@github.com:${fullName}.git`,
|
|
3048
|
-
httpsUrl: `https://github.com/${fullName}.git`
|
|
3049
|
-
};
|
|
3050
|
-
}
|
|
3051
|
-
|
|
3052
|
-
// src/lib/resolve-github-username-default.ts
|
|
3053
|
-
import { spawnSync as spawnSync16 } from "child_process";
|
|
3054
|
-
function resolveGithubUsernameDefault() {
|
|
3055
|
-
const r = spawnSync16("gh", ["api", "user", "--jq", ".login"], {
|
|
3056
|
-
encoding: "utf8",
|
|
3057
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
3058
|
-
});
|
|
3059
|
-
if (r.status !== 0) {
|
|
3060
|
-
throw new Error(`Kh\xF4ng l\u1EA5y \u0111\u01B0\u1EE3c GitHub username: ${r.stderr?.trim()}`);
|
|
3061
|
-
}
|
|
3062
|
-
return r.stdout.trim();
|
|
3063
|
-
}
|
|
3064
|
-
|
|
3065
|
-
// src/lib/validate-repo-name-and-visibility.ts
|
|
3066
|
-
var REPO_NAME_REGEX = /^[a-zA-Z0-9._-]{1,100}$/;
|
|
3067
|
-
var InvalidRepoNameError = class extends Error {
|
|
3068
|
-
constructor(name) {
|
|
3069
|
-
super(
|
|
3070
|
-
`T\xEAn repo "${name}" kh\xF4ng h\u1EE3p l\u1EC7. Ch\u1EC9 d\xF9ng ch\u1EEF/s\u1ED1/d\u1EA5u ch\u1EA5m/g\u1EA1ch/underscore, d\xE0i 1-100 k\xFD t\u1EF1.`
|
|
3071
|
-
);
|
|
3072
|
-
this.name = "InvalidRepoNameError";
|
|
3073
|
-
}
|
|
3074
|
-
};
|
|
3075
|
-
function validateRepoName(name) {
|
|
3076
|
-
if (!REPO_NAME_REGEX.test(name)) {
|
|
3077
|
-
throw new InvalidRepoNameError(name);
|
|
3078
|
-
}
|
|
3079
|
-
}
|
|
3080
|
-
function validateRepoVisibility(v) {
|
|
3081
|
-
if (v !== "private" && v !== "public") {
|
|
3082
|
-
throw new Error(`Visibility ph\u1EA3i l\xE0 "private" ho\u1EB7c "public", nh\u1EADn: "${v}"`);
|
|
3083
|
-
}
|
|
3084
|
-
}
|
|
3085
|
-
|
|
3086
|
-
// src/lib/create-github-remote-from-folder.ts
|
|
3087
|
-
function createGithubRemoteFromFolder(input8) {
|
|
3088
|
-
validateRepoName(input8.name);
|
|
3089
|
-
validateRepoVisibility(input8.visibility);
|
|
3090
|
-
const org = input8.org ?? resolveGithubUsernameDefault();
|
|
3091
|
-
log.info(`T\u1EA1o GitHub repo ${org}/${input8.name} (${input8.visibility})...`);
|
|
3092
|
-
const urls = executeGhRepoCreate({
|
|
3093
|
-
folder: input8.folder,
|
|
3094
|
-
org,
|
|
3095
|
-
name: input8.name,
|
|
3096
|
-
visibility: input8.visibility
|
|
3097
|
-
});
|
|
3098
|
-
log.success(`\u0110\xE3 t\u1EA1o: ${urls.sshUrl}`);
|
|
3099
|
-
return urls;
|
|
3100
|
-
}
|
|
3209
|
+
import { basename as basename3, join as join27, resolve as resolve2 } from "path";
|
|
3210
|
+
import { input as input7, select as select12 } from "@inquirer/prompts";
|
|
3101
3211
|
|
|
3102
3212
|
// src/lib/check-gh-cli-auth-status.ts
|
|
3103
|
-
import { spawnSync as
|
|
3213
|
+
import { spawnSync as spawnSync18 } from "child_process";
|
|
3104
3214
|
function checkGhCliAuthStatus() {
|
|
3105
|
-
const r =
|
|
3215
|
+
const r = spawnSync18("gh", ["auth", "status"], { stdio: "ignore" });
|
|
3106
3216
|
if (r.error && r.error.code === "ENOENT") {
|
|
3107
3217
|
return "not-installed";
|
|
3108
3218
|
}
|
|
@@ -3110,12 +3220,12 @@ function checkGhCliAuthStatus() {
|
|
|
3110
3220
|
}
|
|
3111
3221
|
|
|
3112
3222
|
// src/lib/detect-package-manager.ts
|
|
3113
|
-
import { spawnSync as
|
|
3223
|
+
import { spawnSync as spawnSync19 } from "child_process";
|
|
3114
3224
|
function hasBinary(name) {
|
|
3115
3225
|
const platform2 = detectHostPlatform();
|
|
3116
3226
|
const probe = platform2 === "win32" ? "where" : "command";
|
|
3117
3227
|
const args = platform2 === "win32" ? [name] : ["-v", name];
|
|
3118
|
-
const r =
|
|
3228
|
+
const r = spawnSync19(probe, args, {
|
|
3119
3229
|
shell: platform2 !== "win32",
|
|
3120
3230
|
stdio: "ignore"
|
|
3121
3231
|
});
|
|
@@ -3131,7 +3241,7 @@ function detectPackageManager() {
|
|
|
3131
3241
|
}
|
|
3132
3242
|
|
|
3133
3243
|
// src/lib/install-gh-cli-via-package-manager.ts
|
|
3134
|
-
import { spawnSync as
|
|
3244
|
+
import { spawnSync as spawnSync20 } from "child_process";
|
|
3135
3245
|
var INSTALL_COMMANDS = {
|
|
3136
3246
|
brew: { cmd: "brew", args: ["install", "gh"] },
|
|
3137
3247
|
apt: { cmd: "sudo", args: ["apt-get", "install", "-y", "gh"] },
|
|
@@ -3142,7 +3252,7 @@ var INSTALL_COMMANDS = {
|
|
|
3142
3252
|
function installGhCliViaPackageManager(pm) {
|
|
3143
3253
|
const spec = INSTALL_COMMANDS[pm];
|
|
3144
3254
|
log.info(`\u0110ang c\xE0i gh CLI qua ${pm}...`);
|
|
3145
|
-
const r =
|
|
3255
|
+
const r = spawnSync20(spec.cmd, spec.args, { stdio: "inherit" });
|
|
3146
3256
|
if (r.status !== 0) {
|
|
3147
3257
|
throw new Error(`C\xE0i gh CLI th\u1EA5t b\u1EA1i qua ${pm} (exit ${r.status}). C\xE0i tay r\u1ED3i ch\u1EA1y l\u1EA1i.`);
|
|
3148
3258
|
}
|
|
@@ -3150,9 +3260,9 @@ function installGhCliViaPackageManager(pm) {
|
|
|
3150
3260
|
}
|
|
3151
3261
|
|
|
3152
3262
|
// src/lib/setup-git-credential-via-gh.ts
|
|
3153
|
-
import { spawnSync as
|
|
3263
|
+
import { spawnSync as spawnSync21 } from "child_process";
|
|
3154
3264
|
function setupGitCredentialViaGh() {
|
|
3155
|
-
const r =
|
|
3265
|
+
const r = spawnSync21("gh", ["auth", "setup-git"], { stdio: "ignore" });
|
|
3156
3266
|
if (r.status !== 0) {
|
|
3157
3267
|
log.warn("gh auth setup-git fail (non-fatal). N\u1EBFu git clone l\u1ED7i 128 \u2192 ch\u1EA1y th\u1EE7 c\xF4ng.");
|
|
3158
3268
|
return;
|
|
@@ -3161,10 +3271,10 @@ function setupGitCredentialViaGh() {
|
|
|
3161
3271
|
}
|
|
3162
3272
|
|
|
3163
3273
|
// src/lib/trigger-gh-cli-auth-login.ts
|
|
3164
|
-
import { spawnSync as
|
|
3274
|
+
import { spawnSync as spawnSync22 } from "child_process";
|
|
3165
3275
|
function triggerGhCliAuthLogin() {
|
|
3166
3276
|
log.info("Kh\u1EDFi \u0111\u1ED9ng \u0111\u0103ng nh\u1EADp GitHub qua gh CLI (browser s\u1EBD m\u1EDF)...");
|
|
3167
|
-
const r =
|
|
3277
|
+
const r = spawnSync22(
|
|
3168
3278
|
"gh",
|
|
3169
3279
|
["auth", "login", "--hostname", "github.com", "--web", "--git-protocol", "ssh"],
|
|
3170
3280
|
{ stdio: "inherit" }
|
|
@@ -3253,28 +3363,28 @@ async function ensureGitHubReady(remoteUrl) {
|
|
|
3253
3363
|
}
|
|
3254
3364
|
|
|
3255
3365
|
// src/lib/add-team-pack-submodule-with-retry-on-network-fail.ts
|
|
3256
|
-
import { spawnSync as
|
|
3257
|
-
import { select as
|
|
3366
|
+
import { spawnSync as spawnSync23 } from "child_process";
|
|
3367
|
+
import { select as select9 } from "@inquirer/prompts";
|
|
3258
3368
|
function isSshPermissionError(message) {
|
|
3259
3369
|
const text = message.toLowerCase();
|
|
3260
3370
|
return text.includes("permission denied (publickey)") || text.includes("publickey)") || text.includes("ssh: could not resolve") || text.includes("host key verification failed");
|
|
3261
3371
|
}
|
|
3262
3372
|
function triggerGhAuthLoginInteractive3() {
|
|
3263
3373
|
log.info("M\u1EDF browser \u0111\u1EC3 switch GitHub account...");
|
|
3264
|
-
const r =
|
|
3374
|
+
const r = spawnSync23("gh", ["auth", "login", "--web"], { stdio: "inherit" });
|
|
3265
3375
|
if (r.status !== 0) {
|
|
3266
3376
|
log.warn(`gh auth login exit ${r.status}. C\xF3 th\u1EC3 ch\u1EA1y tay r\u1ED3i quay l\u1EA1i retry.`);
|
|
3267
3377
|
}
|
|
3268
3378
|
}
|
|
3269
3379
|
function openGithubSshKeysPage() {
|
|
3270
3380
|
log.info("M\u1EDF trang GitHub Settings \u2192 SSH Keys...");
|
|
3271
|
-
const r =
|
|
3381
|
+
const r = spawnSync23("open", ["https://github.com/settings/keys"], { stdio: "ignore" });
|
|
3272
3382
|
if (r.status !== 0) {
|
|
3273
3383
|
log.info("URL: https://github.com/settings/keys");
|
|
3274
3384
|
}
|
|
3275
3385
|
}
|
|
3276
3386
|
async function handleSshPermissionError() {
|
|
3277
|
-
return await
|
|
3387
|
+
return await select9({
|
|
3278
3388
|
message: "SSH permission denied. C\xE1ch x\u1EED l\xFD?",
|
|
3279
3389
|
choices: [
|
|
3280
3390
|
{
|
|
@@ -3448,12 +3558,12 @@ function parseBootstrapStrategyOpts(opts) {
|
|
|
3448
3558
|
}
|
|
3449
3559
|
|
|
3450
3560
|
// src/commands/workspace-scaffold-and-finalize-orchestrator.ts
|
|
3451
|
-
import { join as
|
|
3452
|
-
import { basename } from "path";
|
|
3453
|
-
import { confirm as
|
|
3561
|
+
import { join as join26 } from "path";
|
|
3562
|
+
import { basename as basename2 } from "path";
|
|
3563
|
+
import { confirm as confirm6, input as input6, select as select11 } from "@inquirer/prompts";
|
|
3454
3564
|
|
|
3455
3565
|
// src/lib/create-workspace-remote-via-gh.ts
|
|
3456
|
-
import { spawnSync as
|
|
3566
|
+
import { spawnSync as spawnSync24 } from "child_process";
|
|
3457
3567
|
var CreateWorkspaceRemoteError = class extends Error {
|
|
3458
3568
|
reason;
|
|
3459
3569
|
fullName;
|
|
@@ -3483,20 +3593,20 @@ function classifyGhCreateError(stderr) {
|
|
|
3483
3593
|
return "unknown";
|
|
3484
3594
|
}
|
|
3485
3595
|
function repoExistsOnGitHub(fullName) {
|
|
3486
|
-
const r =
|
|
3596
|
+
const r = spawnSync24("gh", ["repo", "view", fullName, "--json", "name"], {
|
|
3487
3597
|
stdio: "ignore"
|
|
3488
3598
|
});
|
|
3489
3599
|
return r.status === 0;
|
|
3490
3600
|
}
|
|
3491
3601
|
function canCreateInNamespace(org, ghUser) {
|
|
3492
3602
|
if (org.toLowerCase() === ghUser.toLowerCase()) return { ok: true };
|
|
3493
|
-
const r =
|
|
3603
|
+
const r = spawnSync24("gh", ["api", `orgs/${org}/members/${ghUser}`, "--silent"], {
|
|
3494
3604
|
stdio: "ignore"
|
|
3495
3605
|
});
|
|
3496
3606
|
if (r.status === 0) return { ok: true };
|
|
3497
|
-
const orgCheck =
|
|
3607
|
+
const orgCheck = spawnSync24("gh", ["api", `orgs/${org}`, "--silent"], { stdio: "ignore" });
|
|
3498
3608
|
if (orgCheck.status !== 0) {
|
|
3499
|
-
const userCheck =
|
|
3609
|
+
const userCheck = spawnSync24("gh", ["api", `users/${org}`, "--silent"], { stdio: "ignore" });
|
|
3500
3610
|
if (userCheck.status === 0) {
|
|
3501
3611
|
return {
|
|
3502
3612
|
ok: false,
|
|
@@ -3532,7 +3642,7 @@ async function createWorkspaceRemoteViaGh(input8) {
|
|
|
3532
3642
|
);
|
|
3533
3643
|
}
|
|
3534
3644
|
log.info(`T\u1EA1o GitHub repo cho workspace: ${fullName} (${input8.visibility})...`);
|
|
3535
|
-
const r =
|
|
3645
|
+
const r = spawnSync24(
|
|
3536
3646
|
"gh",
|
|
3537
3647
|
[
|
|
3538
3648
|
"repo",
|
|
@@ -3573,7 +3683,7 @@ ${combined}
|
|
|
3573
3683
|
function linkExistingRemoteToWorkspace(args) {
|
|
3574
3684
|
const sshUrl = `git@github.com:${args.fullName}.git`;
|
|
3575
3685
|
const httpsUrl = `https://github.com/${args.fullName}.git`;
|
|
3576
|
-
const addResult =
|
|
3686
|
+
const addResult = spawnSync24(
|
|
3577
3687
|
"git",
|
|
3578
3688
|
["-C", args.workspacePath, "remote", "add", "origin", sshUrl],
|
|
3579
3689
|
{
|
|
@@ -3582,7 +3692,7 @@ function linkExistingRemoteToWorkspace(args) {
|
|
|
3582
3692
|
}
|
|
3583
3693
|
);
|
|
3584
3694
|
if (addResult.status !== 0) {
|
|
3585
|
-
|
|
3695
|
+
spawnSync24("git", ["-C", args.workspacePath, "remote", "set-url", "origin", sshUrl], {
|
|
3586
3696
|
stdio: "ignore"
|
|
3587
3697
|
});
|
|
3588
3698
|
}
|
|
@@ -3591,8 +3701,8 @@ function linkExistingRemoteToWorkspace(args) {
|
|
|
3591
3701
|
}
|
|
3592
3702
|
|
|
3593
3703
|
// src/lib/merge-pack-settings-into-project-settings.ts
|
|
3594
|
-
import { promises as
|
|
3595
|
-
import { join as
|
|
3704
|
+
import { promises as fs10 } from "fs";
|
|
3705
|
+
import { join as join22 } from "path";
|
|
3596
3706
|
async function isStatusLineCommandResolvable(workspacePath, command) {
|
|
3597
3707
|
const trimmed = command.trim();
|
|
3598
3708
|
const match = trimmed.match(/^(node|python|python3|bash|sh)\s+([^\s]+)/);
|
|
@@ -3600,7 +3710,7 @@ async function isStatusLineCommandResolvable(workspacePath, command) {
|
|
|
3600
3710
|
return true;
|
|
3601
3711
|
}
|
|
3602
3712
|
const filePath = match[2];
|
|
3603
|
-
const fullPath = filePath.startsWith("/") ? filePath :
|
|
3713
|
+
const fullPath = filePath.startsWith("/") ? filePath : join22(workspacePath, filePath);
|
|
3604
3714
|
return await pathExists(fullPath);
|
|
3605
3715
|
}
|
|
3606
3716
|
function backupFilename(originalPath) {
|
|
@@ -3634,8 +3744,8 @@ function mergeHooksPerEvent(packHooks, userHooks) {
|
|
|
3634
3744
|
return { merged, touchedEvents: touched };
|
|
3635
3745
|
}
|
|
3636
3746
|
async function mergePackSettingsIntoProjectSettings(workspacePath) {
|
|
3637
|
-
const packTemplatePath =
|
|
3638
|
-
const projectSettingsPath =
|
|
3747
|
+
const packTemplatePath = join22(workspacePath, ".claude", "pack", "templates", "settings.json.tpl");
|
|
3748
|
+
const projectSettingsPath = join22(workspacePath, ".claude", "settings.json");
|
|
3639
3749
|
if (!await pathExists(packTemplatePath)) {
|
|
3640
3750
|
return { action: "no-pack-template", changes: [] };
|
|
3641
3751
|
}
|
|
@@ -3729,18 +3839,18 @@ async function mergePackSettingsIntoProjectSettings(workspacePath) {
|
|
|
3729
3839
|
let backupPath;
|
|
3730
3840
|
if (projectHasSettings) {
|
|
3731
3841
|
backupPath = backupFilename(projectSettingsPath);
|
|
3732
|
-
await
|
|
3842
|
+
await fs10.copyFile(projectSettingsPath, backupPath);
|
|
3733
3843
|
}
|
|
3734
3844
|
await writeJsonAtomic(projectSettingsPath, merged);
|
|
3735
3845
|
return { action: "merged", backupPath, changes };
|
|
3736
3846
|
}
|
|
3737
3847
|
|
|
3738
3848
|
// src/lib/symlink-farm-for-team-pack-mount-dirs.ts
|
|
3739
|
-
import { promises as
|
|
3740
|
-
import { dirname as dirname6, join as
|
|
3849
|
+
import { promises as fs12 } from "fs";
|
|
3850
|
+
import { dirname as dirname6, join as join23, relative as relative2 } from "path";
|
|
3741
3851
|
|
|
3742
3852
|
// src/lib/backup-existing-dir-before-symlink-override.ts
|
|
3743
|
-
import { promises as
|
|
3853
|
+
import { promises as fs11 } from "fs";
|
|
3744
3854
|
function timestamp() {
|
|
3745
3855
|
const d = /* @__PURE__ */ new Date();
|
|
3746
3856
|
const pad = (n) => n.toString().padStart(2, "0");
|
|
@@ -3748,7 +3858,7 @@ function timestamp() {
|
|
|
3748
3858
|
}
|
|
3749
3859
|
async function backupDirBeforeReplace(targetPath) {
|
|
3750
3860
|
const backupPath = `${targetPath}.backup-${timestamp()}`;
|
|
3751
|
-
await
|
|
3861
|
+
await fs11.rename(targetPath, backupPath);
|
|
3752
3862
|
return backupPath;
|
|
3753
3863
|
}
|
|
3754
3864
|
|
|
@@ -3764,7 +3874,7 @@ var TEAM_PACK_MOUNT_DIRS = [
|
|
|
3764
3874
|
];
|
|
3765
3875
|
async function isSymbolicLink(path) {
|
|
3766
3876
|
try {
|
|
3767
|
-
const st = await
|
|
3877
|
+
const st = await fs12.lstat(path);
|
|
3768
3878
|
return st.isSymbolicLink();
|
|
3769
3879
|
} catch {
|
|
3770
3880
|
return false;
|
|
@@ -3777,33 +3887,33 @@ async function syncMountedDir(source, dest, force) {
|
|
|
3777
3887
|
}
|
|
3778
3888
|
if (await pathExists(dest)) {
|
|
3779
3889
|
if (await isSymbolicLink(dest)) {
|
|
3780
|
-
await
|
|
3890
|
+
await fs12.unlink(dest);
|
|
3781
3891
|
} else if (force) {
|
|
3782
3892
|
const backupPath = await backupDirBeforeReplace(dest);
|
|
3783
3893
|
const relativeSource2 = relative2(dirname6(dest), source);
|
|
3784
|
-
await
|
|
3894
|
+
await fs12.symlink(relativeSource2, dest);
|
|
3785
3895
|
return { dir, action: "backed-up-and-linked", backupPath };
|
|
3786
3896
|
} else {
|
|
3787
3897
|
return { dir, action: "skipped-conflict" };
|
|
3788
3898
|
}
|
|
3789
3899
|
}
|
|
3790
3900
|
const relativeSource = relative2(dirname6(dest), source);
|
|
3791
|
-
await
|
|
3901
|
+
await fs12.symlink(relativeSource, dest);
|
|
3792
3902
|
return { dir, action: "created" };
|
|
3793
3903
|
}
|
|
3794
3904
|
async function syncAllMountDirs(packDir, claudeDir, force) {
|
|
3795
3905
|
const results = [];
|
|
3796
3906
|
for (const dir of TEAM_PACK_MOUNT_DIRS) {
|
|
3797
|
-
const source =
|
|
3798
|
-
const dest =
|
|
3907
|
+
const source = join23(packDir, dir);
|
|
3908
|
+
const dest = join23(claudeDir, dir);
|
|
3799
3909
|
results.push(await syncMountedDir(source, dest, force));
|
|
3800
3910
|
}
|
|
3801
3911
|
return results;
|
|
3802
3912
|
}
|
|
3803
3913
|
|
|
3804
3914
|
// src/commands/init-success-rendering-and-helpers.ts
|
|
3805
|
-
import { join as
|
|
3806
|
-
import { input as input5, select as
|
|
3915
|
+
import { join as join25, relative as relative3 } from "path";
|
|
3916
|
+
import { input as input5, select as select10 } from "@inquirer/prompts";
|
|
3807
3917
|
import boxen5 from "boxen";
|
|
3808
3918
|
|
|
3809
3919
|
// src/lib/format-pack-commands-cheatsheet-box.ts
|
|
@@ -3844,7 +3954,7 @@ function formatPackCommandsCheatsheetBox() {
|
|
|
3844
3954
|
|
|
3845
3955
|
// src/commands/init-conflict-detection-helpers.ts
|
|
3846
3956
|
import { readdir } from "fs/promises";
|
|
3847
|
-
import { join as
|
|
3957
|
+
import { join as join24 } from "path";
|
|
3848
3958
|
async function isEmptyOrMissing(path) {
|
|
3849
3959
|
if (!await pathExists(path)) return true;
|
|
3850
3960
|
try {
|
|
@@ -3857,7 +3967,7 @@ async function isEmptyOrMissing(path) {
|
|
|
3857
3967
|
}
|
|
3858
3968
|
async function findAlternativeWorkspaceName(parent, desiredName, maxAttempts = 10) {
|
|
3859
3969
|
for (let i = 2; i < maxAttempts; i++) {
|
|
3860
|
-
const candidate =
|
|
3970
|
+
const candidate = join24(parent, `${desiredName}-${i}`);
|
|
3861
3971
|
if (await isEmptyOrMissing(candidate)) return candidate;
|
|
3862
3972
|
}
|
|
3863
3973
|
return null;
|
|
@@ -3865,7 +3975,7 @@ async function findAlternativeWorkspaceName(parent, desiredName, maxAttempts = 1
|
|
|
3865
3975
|
|
|
3866
3976
|
// src/commands/init-success-rendering-and-helpers.ts
|
|
3867
3977
|
async function resolveWorkspacePath(parent, desiredName, force) {
|
|
3868
|
-
const desired =
|
|
3978
|
+
const desired = join25(parent, desiredName);
|
|
3869
3979
|
if (await isEmptyOrMissing(desired)) return desired;
|
|
3870
3980
|
log.warn(`Workspace path "${desired}" \u0111\xE3 c\xF3 n\u1ED9i dung.`);
|
|
3871
3981
|
while (true) {
|
|
@@ -3880,7 +3990,7 @@ async function resolveWorkspacePath(parent, desiredName, force) {
|
|
|
3880
3990
|
}
|
|
3881
3991
|
choices.push({ name: "Nh\u1EADp t\xEAn workspace kh\xE1c (manual)", value: "manual" });
|
|
3882
3992
|
choices.push({ name: "T\u1EA1m ng\u01B0ng init", value: "abort" });
|
|
3883
|
-
const action = await
|
|
3993
|
+
const action = await select10({
|
|
3884
3994
|
message: "C\xE1ch x\u1EED l\xFD workspace path conflict?",
|
|
3885
3995
|
choices
|
|
3886
3996
|
});
|
|
@@ -3896,7 +4006,7 @@ async function resolveWorkspacePath(parent, desiredName, force) {
|
|
|
3896
4006
|
message: "T\xEAn workspace m\u1EDBi:",
|
|
3897
4007
|
validate: (v) => v.trim().length > 0 ? true : "T\xEAn kh\xF4ng \u0111\u01B0\u1EE3c r\u1ED7ng"
|
|
3898
4008
|
});
|
|
3899
|
-
const newPath =
|
|
4009
|
+
const newPath = join25(parent, newName.trim());
|
|
3900
4010
|
if (await isEmptyOrMissing(newPath)) return newPath;
|
|
3901
4011
|
log.warn(`"${newPath}" c\u0169ng \u0111\xE3 c\xF3 n\u1ED9i dung. Th\u1EED t\xEAn kh\xE1c.`);
|
|
3902
4012
|
}
|
|
@@ -3953,7 +4063,7 @@ async function printInitSuccessBox(rootPath, flow, aiResult = null, gitnexusResu
|
|
|
3953
4063
|
];
|
|
3954
4064
|
process.stdout.write(`${boxen5(lines.join("\n"), { padding: 1, borderStyle: "round" })}
|
|
3955
4065
|
`);
|
|
3956
|
-
const packDir =
|
|
4066
|
+
const packDir = join25(rootPath, TEAM_PACK_RELATIVE_PATH);
|
|
3957
4067
|
if (await pathExists(packDir)) {
|
|
3958
4068
|
process.stdout.write(`
|
|
3959
4069
|
${formatPackCommandsCheatsheetBox()}
|
|
@@ -3969,7 +4079,7 @@ async function getOrCreateOriginRemote(folderPath, opts) {
|
|
|
3969
4079
|
log.success(`Folder \u0111\xE3 c\xF3 remote origin: ${origin.refs.push}`);
|
|
3970
4080
|
return origin.refs.push;
|
|
3971
4081
|
}
|
|
3972
|
-
const shouldCreate = opts.createRemote ?? await
|
|
4082
|
+
const shouldCreate = opts.createRemote ?? await confirm6({
|
|
3973
4083
|
message: "Folder ch\u01B0a c\xF3 remote. T\u1EA1o GitHub repo ngay \u0111\u1EC3 share team?",
|
|
3974
4084
|
default: true
|
|
3975
4085
|
});
|
|
@@ -3978,7 +4088,7 @@ async function getOrCreateOriginRemote(folderPath, opts) {
|
|
|
3978
4088
|
return void 0;
|
|
3979
4089
|
}
|
|
3980
4090
|
await ensureGitHubReady();
|
|
3981
|
-
const visibility = opts.repoVisibility ?? await
|
|
4091
|
+
const visibility = opts.repoVisibility ?? await select11({
|
|
3982
4092
|
message: "Visibility?",
|
|
3983
4093
|
choices: [
|
|
3984
4094
|
{ name: "private (m\u1EB7c \u0111\u1ECBnh)", value: "private" },
|
|
@@ -3987,7 +4097,7 @@ async function getOrCreateOriginRemote(folderPath, opts) {
|
|
|
3987
4097
|
});
|
|
3988
4098
|
const repoName = await input6({
|
|
3989
4099
|
message: "T\xEAn repo:",
|
|
3990
|
-
default:
|
|
4100
|
+
default: basename2(folderPath)
|
|
3991
4101
|
});
|
|
3992
4102
|
const urls = createGithubRemoteFromFolder({
|
|
3993
4103
|
folder: folderPath,
|
|
@@ -4053,10 +4163,10 @@ async function finalizeWorkspaceScaffold(args) {
|
|
|
4053
4163
|
await writeRootClaudeMd(args.workspacePath, vars);
|
|
4054
4164
|
await writeProjectSettings(args.workspacePath, vars);
|
|
4055
4165
|
await appendGitignoreEntries(args.workspacePath);
|
|
4056
|
-
await ensureDir(
|
|
4057
|
-
await ensureDir(
|
|
4058
|
-
await installGitHook(
|
|
4059
|
-
await installGitHook(
|
|
4166
|
+
await ensureDir(join26(args.workspacePath, "notes"));
|
|
4167
|
+
await ensureDir(join26(args.workspacePath, "scripts"));
|
|
4168
|
+
await installGitHook(join26(args.workspacePath, ".git"), "post-merge");
|
|
4169
|
+
await installGitHook(join26(args.workspacePath, ".git", "modules", "src"), "pre-push");
|
|
4060
4170
|
log.success("C\xE0i post-merge (workspace) + pre-push (src/)");
|
|
4061
4171
|
await autoSyncPackOnInit(args.workspacePath);
|
|
4062
4172
|
await appendAuditEntry("init", `flow=${args.flow},workspace=${args.workspaceName}`);
|
|
@@ -4094,12 +4204,12 @@ async function finalizeWorkspaceScaffold(args) {
|
|
|
4094
4204
|
await printInitSuccessBox(args.workspacePath, args.flow, aiResult, gitnexusResult);
|
|
4095
4205
|
}
|
|
4096
4206
|
async function autoSyncPackOnInit(workspacePath) {
|
|
4097
|
-
const packDir =
|
|
4207
|
+
const packDir = join26(workspacePath, TEAM_PACK_RELATIVE_PATH);
|
|
4098
4208
|
if (!await pathExists(packDir)) {
|
|
4099
4209
|
log.dim("Pack submodule kh\xF4ng t\u1ED3n t\u1EA1i (skip auto-sync). C\xF3 th\u1EC3 ch\u1EA1y `avatar sync` sau.");
|
|
4100
4210
|
return;
|
|
4101
4211
|
}
|
|
4102
|
-
const claudeDir =
|
|
4212
|
+
const claudeDir = join26(workspacePath, ".claude");
|
|
4103
4213
|
log.info("Auto-sync pack content v\xE0o .claude/ (symlinks + settings merge)...");
|
|
4104
4214
|
try {
|
|
4105
4215
|
const results = await syncAllMountDirs(packDir, claudeDir, false);
|
|
@@ -4134,13 +4244,13 @@ async function maybeCreateWorkspaceRemote(args) {
|
|
|
4134
4244
|
let shouldCreate = args.createWorkspaceRemote;
|
|
4135
4245
|
if (shouldCreate === void 0) {
|
|
4136
4246
|
if (args.autoYes) return;
|
|
4137
|
-
shouldCreate = await
|
|
4247
|
+
shouldCreate = await confirm6({
|
|
4138
4248
|
message: "T\u1EA1o remote GitHub cho workspace \u0111\u1EC3 share team? (Avatar state)",
|
|
4139
4249
|
default: false
|
|
4140
4250
|
});
|
|
4141
4251
|
}
|
|
4142
4252
|
if (!shouldCreate) return;
|
|
4143
|
-
const visibility = args.repoVisibility ?? (args.autoYes ? "private" : await
|
|
4253
|
+
const visibility = args.repoVisibility ?? (args.autoYes ? "private" : await select11({
|
|
4144
4254
|
message: "Workspace visibility?",
|
|
4145
4255
|
choices: [
|
|
4146
4256
|
{ name: "private (m\u1EB7c \u0111\u1ECBnh, an to\xE0n)", value: "private" },
|
|
@@ -4159,7 +4269,7 @@ async function maybeCreateWorkspaceRemote(args) {
|
|
|
4159
4269
|
} catch (err) {
|
|
4160
4270
|
if (err instanceof CreateWorkspaceRemoteError && err.reason === "repo-exists") {
|
|
4161
4271
|
const fullName = err.fullName;
|
|
4162
|
-
const reuseAction = await
|
|
4272
|
+
const reuseAction = await select11({
|
|
4163
4273
|
message: `Repo '${fullName}' \u0111\xE3 t\u1ED3n t\u1EA1i tr\xEAn GitHub. C\xE1ch x\u1EED l\xFD?`,
|
|
4164
4274
|
choices: [
|
|
4165
4275
|
{
|
|
@@ -4257,9 +4367,24 @@ async function runInitFromExistingFolder(opts, ownerEmail) {
|
|
|
4257
4367
|
presetStrategy: parseBootstrapStrategyOpts(opts),
|
|
4258
4368
|
autoYes: opts.yes
|
|
4259
4369
|
});
|
|
4260
|
-
|
|
4370
|
+
let remoteUrl = await getOrCreateOriginRemote(folderPath, opts);
|
|
4371
|
+
if (remoteUrl) {
|
|
4372
|
+
const verify = tryVerifyGitRemoteAccessible(remoteUrl);
|
|
4373
|
+
if (!verify.ok) {
|
|
4374
|
+
log.warn(`Remote ${remoteUrl} kh\xF4ng accessible (${verify.reason ?? "unknown"}).`);
|
|
4375
|
+
const recovered = await handleRemoteAccessFailureWithAccountSwitch({
|
|
4376
|
+
url: remoteUrl,
|
|
4377
|
+
initialReason: verify.reason ?? "unknown",
|
|
4378
|
+
initialDetail: verify.detail,
|
|
4379
|
+
folderPath,
|
|
4380
|
+
// enable option "Reset folder & tạo repo mới"
|
|
4381
|
+
defaultVisibility: opts.repoVisibility
|
|
4382
|
+
});
|
|
4383
|
+
remoteUrl = recovered.resolvedUrl;
|
|
4384
|
+
}
|
|
4385
|
+
}
|
|
4261
4386
|
const teamOwner = opts.teamOwner ?? await promptTeamOwner(ownerEmail);
|
|
4262
|
-
const inferredName = opts.workspaceName ?? `${
|
|
4387
|
+
const inferredName = opts.workspaceName ?? `${basename3(folderPath)}-avatar-workspace`;
|
|
4263
4388
|
const workspaceName = opts.workspaceName ?? await input7({ message: "T\xEAn workspace:", default: inferredName });
|
|
4264
4389
|
const workspaceParent = resolve2(opts.workspaceParent ?? ".");
|
|
4265
4390
|
const workspacePath = await resolveWorkspacePath(workspaceParent, workspaceName, opts.force);
|
|
@@ -4290,7 +4415,7 @@ async function runInitFromScratch(opts, ownerEmail) {
|
|
|
4290
4415
|
message: "T\xEAn d\u1EF1 \xE1n:",
|
|
4291
4416
|
validate: (v) => v.length > 0 ? true : "T\xEAn b\u1EAFt bu\u1ED9c"
|
|
4292
4417
|
});
|
|
4293
|
-
const visibility = opts.repoVisibility ?? await
|
|
4418
|
+
const visibility = opts.repoVisibility ?? await select12({
|
|
4294
4419
|
message: "Visibility?",
|
|
4295
4420
|
choices: [
|
|
4296
4421
|
{ name: "private (m\u1EB7c \u0111\u1ECBnh)", value: "private" },
|
|
@@ -4300,7 +4425,7 @@ async function runInitFromScratch(opts, ownerEmail) {
|
|
|
4300
4425
|
const teamOwner = opts.teamOwner ?? await promptTeamOwner(ownerEmail);
|
|
4301
4426
|
const workspaceParent = resolve2(opts.workspaceParent ?? ".");
|
|
4302
4427
|
const workspacePath = await resolveWorkspacePath(workspaceParent, projectName, opts.force);
|
|
4303
|
-
const srcPath =
|
|
4428
|
+
const srcPath = join27(workspacePath, "src");
|
|
4304
4429
|
await ensureDir(workspacePath);
|
|
4305
4430
|
await ensureDir(srcPath);
|
|
4306
4431
|
await safeBootstrapGitInFolder(srcPath, { autoYes: true });
|
|
@@ -4617,7 +4742,7 @@ async function runInit(opts) {
|
|
|
4617
4742
|
}
|
|
4618
4743
|
}
|
|
4619
4744
|
async function promptProjectStatus() {
|
|
4620
|
-
return await
|
|
4745
|
+
return await select13({
|
|
4621
4746
|
message: "T\xECnh tr\u1EA1ng d\u1EF1 \xE1n c\u1EE7a b\u1EA1n?",
|
|
4622
4747
|
choices: [
|
|
4623
4748
|
{ name: "1. \u0110\xE3 c\xF3 repo git remote (URL c\xF3 s\u1EB5n)", value: "existing-remote" },
|
|
@@ -4649,7 +4774,7 @@ function registerMcpRunCommand(program2) {
|
|
|
4649
4774
|
}
|
|
4650
4775
|
|
|
4651
4776
|
// src/commands/pack-status-and-version-check.ts
|
|
4652
|
-
import { join as
|
|
4777
|
+
import { join as join28 } from "path";
|
|
4653
4778
|
import boxen7 from "boxen";
|
|
4654
4779
|
var PACK_RELATIVE_PATH = ".claude/pack";
|
|
4655
4780
|
function registerPackCommand(program2) {
|
|
@@ -4670,7 +4795,7 @@ function registerPackCommand(program2) {
|
|
|
4670
4795
|
});
|
|
4671
4796
|
}
|
|
4672
4797
|
async function gatherPackStatus(cwd, doFetch) {
|
|
4673
|
-
const packDir =
|
|
4798
|
+
const packDir = join28(cwd, PACK_RELATIVE_PATH);
|
|
4674
4799
|
if (!await pathExists(packDir) || !await isGitRepo(packDir)) {
|
|
4675
4800
|
return {
|
|
4676
4801
|
installed: false,
|
|
@@ -4759,18 +4884,18 @@ function registerSecretsCommand(program2) {
|
|
|
4759
4884
|
}
|
|
4760
4885
|
|
|
4761
4886
|
// src/commands/status.ts
|
|
4762
|
-
import { promises as
|
|
4763
|
-
import { join as
|
|
4887
|
+
import { promises as fs14 } from "fs";
|
|
4888
|
+
import { join as join30 } from "path";
|
|
4764
4889
|
import boxen8 from "boxen";
|
|
4765
4890
|
|
|
4766
4891
|
// src/lib/pack-backup-manager.ts
|
|
4767
|
-
import { promises as
|
|
4768
|
-
import { join as
|
|
4892
|
+
import { promises as fs13 } from "fs";
|
|
4893
|
+
import { join as join29 } from "path";
|
|
4769
4894
|
var BACKUP_DIR_NAME = "_backup";
|
|
4770
4895
|
async function listBackups(projectRoot) {
|
|
4771
|
-
const dir =
|
|
4896
|
+
const dir = join29(projectRoot, ".claude", BACKUP_DIR_NAME);
|
|
4772
4897
|
if (!await pathExists(dir)) return [];
|
|
4773
|
-
const entries = await
|
|
4898
|
+
const entries = await fs13.readdir(dir, { withFileTypes: true });
|
|
4774
4899
|
return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort().reverse();
|
|
4775
4900
|
}
|
|
4776
4901
|
|
|
@@ -4793,7 +4918,7 @@ function registerStatusCommand(program2) {
|
|
|
4793
4918
|
}
|
|
4794
4919
|
async function gatherStatus(cwd) {
|
|
4795
4920
|
const projectName = cwd.split("/").filter(Boolean).pop() ?? "unknown";
|
|
4796
|
-
const claudeRoot =
|
|
4921
|
+
const claudeRoot = join30(cwd, ".claude");
|
|
4797
4922
|
const hasAvatar = await pathExists(claudeRoot);
|
|
4798
4923
|
if (!hasAvatar) {
|
|
4799
4924
|
return {
|
|
@@ -4806,9 +4931,9 @@ async function gatherStatus(cwd) {
|
|
|
4806
4931
|
hasAvatar: false
|
|
4807
4932
|
};
|
|
4808
4933
|
}
|
|
4809
|
-
const packVersion = await isGitRepo(
|
|
4810
|
-
const pendingDir =
|
|
4811
|
-
const pendingCount = await pathExists(pendingDir) ? (await
|
|
4934
|
+
const packVersion = await isGitRepo(join30(claudeRoot, "pack")) ? await readPinnedPackVersion(cwd).catch(() => null) : null;
|
|
4935
|
+
const pendingDir = join30(claudeRoot, "_pending");
|
|
4936
|
+
const pendingCount = await pathExists(pendingDir) ? (await fs14.readdir(pendingDir)).filter((n) => n.endsWith(".diff.md")).length : 0;
|
|
4812
4937
|
const backupCount = (await listBackups(cwd)).length;
|
|
4813
4938
|
const techStackSummary = await readTechStackFirstLine(claudeRoot);
|
|
4814
4939
|
return {
|
|
@@ -4822,7 +4947,7 @@ async function gatherStatus(cwd) {
|
|
|
4822
4947
|
};
|
|
4823
4948
|
}
|
|
4824
4949
|
async function readTechStackFirstLine(claudeRoot) {
|
|
4825
|
-
const techStackPath =
|
|
4950
|
+
const techStackPath = join30(claudeRoot, "project", "tech-stack.md");
|
|
4826
4951
|
if (!await pathExists(techStackPath)) return "(no tech-stack.md)";
|
|
4827
4952
|
const content = await readText(techStackPath);
|
|
4828
4953
|
const firstNonHeaderLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#") && !l.startsWith(">"));
|
|
@@ -4843,17 +4968,17 @@ function renderStatusBox(s) {
|
|
|
4843
4968
|
}
|
|
4844
4969
|
|
|
4845
4970
|
// src/commands/sync.ts
|
|
4846
|
-
import { join as
|
|
4971
|
+
import { join as join32 } from "path";
|
|
4847
4972
|
|
|
4848
4973
|
// src/lib/preview-team-pack-sync-changes-for-dry-run.ts
|
|
4849
|
-
import { join as
|
|
4974
|
+
import { join as join31 } from "path";
|
|
4850
4975
|
async function inspectMountDir(packDir, claudeDir, dir) {
|
|
4851
|
-
const source =
|
|
4852
|
-
const dest =
|
|
4976
|
+
const source = join31(packDir, dir);
|
|
4977
|
+
const dest = join31(claudeDir, dir);
|
|
4853
4978
|
if (!await pathExists(source)) return "source-missing";
|
|
4854
4979
|
if (!await pathExists(dest)) return "needs-creation";
|
|
4855
|
-
const { promises:
|
|
4856
|
-
const st = await
|
|
4980
|
+
const { promises: fs15 } = await import("fs");
|
|
4981
|
+
const st = await fs15.lstat(dest);
|
|
4857
4982
|
if (st.isSymbolicLink()) return "already-linked";
|
|
4858
4983
|
return "conflict-real-dir";
|
|
4859
4984
|
}
|
|
@@ -4891,8 +5016,8 @@ async function buildSyncPreview(packDir, claudeDir, targetVersion) {
|
|
|
4891
5016
|
var DEFAULT_PACK_BRANCH2 = "main";
|
|
4892
5017
|
async function syncAction(opts) {
|
|
4893
5018
|
const projectRoot = process.cwd();
|
|
4894
|
-
const claudeDir =
|
|
4895
|
-
const packDir =
|
|
5019
|
+
const claudeDir = join32(projectRoot, ".claude");
|
|
5020
|
+
const packDir = join32(projectRoot, TEAM_PACK_RELATIVE_PATH);
|
|
4896
5021
|
if (!await pathExists(packDir)) {
|
|
4897
5022
|
log.error(
|
|
4898
5023
|
`team-ai-pack submodule ch\u01B0a \u0111\u01B0\u1EE3c kh\u1EDFi t\u1EA1o \u1EDF ${TEAM_PACK_RELATIVE_PATH}/.
|
|
@@ -5027,33 +5152,33 @@ function registerToolsCommand(program2) {
|
|
|
5027
5152
|
|
|
5028
5153
|
// src/commands/uninstall.ts
|
|
5029
5154
|
import { relative as relative4 } from "path";
|
|
5030
|
-
import { confirm as
|
|
5155
|
+
import { confirm as confirm7 } from "@inquirer/prompts";
|
|
5031
5156
|
import boxen9 from "boxen";
|
|
5032
5157
|
|
|
5033
5158
|
// src/lib/create-uninstall-backup-snapshot.ts
|
|
5034
5159
|
import { cp, mkdir, writeFile } from "fs/promises";
|
|
5035
5160
|
import { homedir as homedir4 } from "os";
|
|
5036
|
-
import { basename as
|
|
5037
|
-
var UNINSTALL_BACKUPS_DIR =
|
|
5161
|
+
import { basename as basename4, join as join33 } from "path";
|
|
5162
|
+
var UNINSTALL_BACKUPS_DIR = join33(homedir4(), ".avatar", "uninstall-backups");
|
|
5038
5163
|
async function createUninstallBackupSnapshot(projectRoot, artifacts, avatarVersion) {
|
|
5039
|
-
const projectName =
|
|
5164
|
+
const projectName = basename4(projectRoot);
|
|
5040
5165
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
5041
|
-
const backupDir =
|
|
5166
|
+
const backupDir = join33(UNINSTALL_BACKUPS_DIR, `${projectName}-${timestamp2}`);
|
|
5042
5167
|
await mkdir(backupDir, { recursive: true, mode: 448 });
|
|
5043
5168
|
if (artifacts.claudeDir) {
|
|
5044
|
-
await cp(artifacts.claudeDir,
|
|
5169
|
+
await cp(artifacts.claudeDir, join33(backupDir, ".claude"), { recursive: true });
|
|
5045
5170
|
}
|
|
5046
5171
|
if (artifacts.claudeMd) {
|
|
5047
|
-
await cp(artifacts.claudeMd,
|
|
5172
|
+
await cp(artifacts.claudeMd, join33(backupDir, "CLAUDE.md"));
|
|
5048
5173
|
}
|
|
5049
5174
|
if (artifacts.postMergeHook || artifacts.prePushHook) {
|
|
5050
|
-
const hooksBackupDir =
|
|
5175
|
+
const hooksBackupDir = join33(backupDir, "hooks");
|
|
5051
5176
|
await mkdir(hooksBackupDir, { recursive: true });
|
|
5052
5177
|
if (artifacts.postMergeHook) {
|
|
5053
|
-
await cp(artifacts.postMergeHook,
|
|
5178
|
+
await cp(artifacts.postMergeHook, join33(hooksBackupDir, "post-merge"));
|
|
5054
5179
|
}
|
|
5055
5180
|
if (artifacts.prePushHook) {
|
|
5056
|
-
await cp(artifacts.prePushHook,
|
|
5181
|
+
await cp(artifacts.prePushHook, join33(hooksBackupDir, "pre-push"));
|
|
5057
5182
|
}
|
|
5058
5183
|
}
|
|
5059
5184
|
const manifest = {
|
|
@@ -5068,27 +5193,27 @@ async function createUninstallBackupSnapshot(projectRoot, artifacts, avatarVersi
|
|
|
5068
5193
|
prePushHook: !!artifacts.prePushHook
|
|
5069
5194
|
}
|
|
5070
5195
|
};
|
|
5071
|
-
await writeFile(
|
|
5196
|
+
await writeFile(join33(backupDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
|
|
5072
5197
|
return backupDir;
|
|
5073
5198
|
}
|
|
5074
5199
|
|
|
5075
5200
|
// src/lib/detect-avatar-project-artifacts.ts
|
|
5076
5201
|
import { existsSync as existsSync9 } from "fs";
|
|
5077
|
-
import { join as
|
|
5202
|
+
import { join as join34 } from "path";
|
|
5078
5203
|
function existsOrNull(path) {
|
|
5079
5204
|
return existsSync9(path) ? path : null;
|
|
5080
5205
|
}
|
|
5081
5206
|
function detectAvatarProjectArtifacts(projectRoot) {
|
|
5082
|
-
const claudeDir = existsOrNull(
|
|
5083
|
-
const claudeMd = existsOrNull(
|
|
5084
|
-
const postMergeHook = existsOrNull(
|
|
5207
|
+
const claudeDir = existsOrNull(join34(projectRoot, ".claude"));
|
|
5208
|
+
const claudeMd = existsOrNull(join34(projectRoot, "CLAUDE.md"));
|
|
5209
|
+
const postMergeHook = existsOrNull(join34(projectRoot, ".git", "hooks", "post-merge"));
|
|
5085
5210
|
const prePushHook = existsOrNull(
|
|
5086
|
-
|
|
5211
|
+
join34(projectRoot, ".git", "modules", "src", "hooks", "pre-push")
|
|
5087
5212
|
);
|
|
5088
|
-
const gitignorePath = existsOrNull(
|
|
5089
|
-
const gitmodulesPath = existsOrNull(
|
|
5090
|
-
const notesDir = existsOrNull(
|
|
5091
|
-
const scriptsDir = existsOrNull(
|
|
5213
|
+
const gitignorePath = existsOrNull(join34(projectRoot, ".gitignore"));
|
|
5214
|
+
const gitmodulesPath = existsOrNull(join34(projectRoot, ".gitmodules"));
|
|
5215
|
+
const notesDir = existsOrNull(join34(projectRoot, "notes"));
|
|
5216
|
+
const scriptsDir = existsOrNull(join34(projectRoot, "scripts"));
|
|
5092
5217
|
const hasAnyArtifact = !!(claudeDir || claudeMd || postMergeHook || prePushHook);
|
|
5093
5218
|
return {
|
|
5094
5219
|
hasAnyArtifact,
|
|
@@ -5109,11 +5234,11 @@ async function executeUninstallDeletion(artifacts, flags) {
|
|
|
5109
5234
|
if (artifacts.claudeDir) {
|
|
5110
5235
|
if (flags.keepSubmodule) {
|
|
5111
5236
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
5112
|
-
const { join:
|
|
5237
|
+
const { join: join35 } = await import("path");
|
|
5113
5238
|
const entries = await readdir2(artifacts.claudeDir);
|
|
5114
5239
|
for (const entry of entries) {
|
|
5115
5240
|
if (entry === "pack") continue;
|
|
5116
|
-
await rm(
|
|
5241
|
+
await rm(join35(artifacts.claudeDir, entry), { recursive: true, force: true });
|
|
5117
5242
|
}
|
|
5118
5243
|
} else {
|
|
5119
5244
|
await rm(artifacts.claudeDir, { recursive: true, force: true });
|
|
@@ -5206,7 +5331,7 @@ async function runUninstall(opts) {
|
|
|
5206
5331
|
return;
|
|
5207
5332
|
}
|
|
5208
5333
|
if (!opts.yes) {
|
|
5209
|
-
const ok = await
|
|
5334
|
+
const ok = await confirm7({
|
|
5210
5335
|
message: "Ti\u1EBFp t\u1EE5c g\u1EE1 Avatar?",
|
|
5211
5336
|
default: false
|
|
5212
5337
|
});
|