@nalvietnam/avatar-cli 1.11.2 → 1.12.0
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 +369 -249
- 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) {
|
|
@@ -2329,7 +2329,7 @@ function registerGitnexusCommand(program2) {
|
|
|
2329
2329
|
}
|
|
2330
2330
|
|
|
2331
2331
|
// src/commands/init.ts
|
|
2332
|
-
import { select as
|
|
2332
|
+
import { select as select13 } from "@inquirer/prompts";
|
|
2333
2333
|
|
|
2334
2334
|
// src/lib/avatar-ascii-banner.ts
|
|
2335
2335
|
import chalk2 from "chalk";
|
|
@@ -2393,11 +2393,178 @@ ${renderAvatarBanner(opts)}
|
|
|
2393
2393
|
}
|
|
2394
2394
|
|
|
2395
2395
|
// src/lib/handle-remote-access-failure-with-account-switch.ts
|
|
2396
|
+
import { spawnSync as spawnSync16 } from "child_process";
|
|
2397
|
+
import { input as input4, select as select6 } from "@inquirer/prompts";
|
|
2398
|
+
|
|
2399
|
+
// src/lib/reset-folder-git-and-create-new-remote-under-current-user.ts
|
|
2400
|
+
import { spawnSync as spawnSync14 } from "child_process";
|
|
2401
|
+
import { promises as fs9 } from "fs";
|
|
2402
|
+
import { basename, join as join16 } from "path";
|
|
2403
|
+
import { confirm as confirm4, select as select5 } from "@inquirer/prompts";
|
|
2404
|
+
|
|
2405
|
+
// src/lib/execute-gh-repo-create.ts
|
|
2406
|
+
import { spawnSync as spawnSync12 } from "child_process";
|
|
2407
|
+
var RepoAlreadyExistsError = class extends Error {
|
|
2408
|
+
constructor(fullName) {
|
|
2409
|
+
super(`Repo "${fullName}" \u0111\xE3 t\u1ED3n t\u1EA1i tr\xEAn GitHub. \u0110\u1ED5i t\xEAn ho\u1EB7c x\xF3a repo c\u0169.`);
|
|
2410
|
+
this.name = "RepoAlreadyExistsError";
|
|
2411
|
+
}
|
|
2412
|
+
};
|
|
2413
|
+
function executeGhRepoCreate(input8) {
|
|
2414
|
+
const fullName = `${input8.org}/${input8.name}`;
|
|
2415
|
+
const args = [
|
|
2416
|
+
"repo",
|
|
2417
|
+
"create",
|
|
2418
|
+
fullName,
|
|
2419
|
+
`--${input8.visibility}`,
|
|
2420
|
+
"--source",
|
|
2421
|
+
input8.folder,
|
|
2422
|
+
"--remote",
|
|
2423
|
+
"origin",
|
|
2424
|
+
"--push"
|
|
2425
|
+
];
|
|
2426
|
+
const r = spawnSync12("gh", args, { stdio: "inherit" });
|
|
2427
|
+
if (r.status !== 0) {
|
|
2428
|
+
if (r.status === 1) {
|
|
2429
|
+
throw new RepoAlreadyExistsError(fullName);
|
|
2430
|
+
}
|
|
2431
|
+
throw new Error(`gh repo create th\u1EA5t b\u1EA1i (exit ${r.status})`);
|
|
2432
|
+
}
|
|
2433
|
+
return {
|
|
2434
|
+
sshUrl: `git@github.com:${fullName}.git`,
|
|
2435
|
+
httpsUrl: `https://github.com/${fullName}.git`
|
|
2436
|
+
};
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
// src/lib/resolve-github-username-default.ts
|
|
2396
2440
|
import { spawnSync as spawnSync13 } from "child_process";
|
|
2397
|
-
|
|
2441
|
+
function resolveGithubUsernameDefault() {
|
|
2442
|
+
const r = spawnSync13("gh", ["api", "user", "--jq", ".login"], {
|
|
2443
|
+
encoding: "utf8",
|
|
2444
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2445
|
+
});
|
|
2446
|
+
if (r.status !== 0) {
|
|
2447
|
+
throw new Error(`Kh\xF4ng l\u1EA5y \u0111\u01B0\u1EE3c GitHub username: ${r.stderr?.trim()}`);
|
|
2448
|
+
}
|
|
2449
|
+
return r.stdout.trim();
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
// src/lib/validate-repo-name-and-visibility.ts
|
|
2453
|
+
var REPO_NAME_REGEX = /^[a-zA-Z0-9._-]{1,100}$/;
|
|
2454
|
+
var InvalidRepoNameError = class extends Error {
|
|
2455
|
+
constructor(name) {
|
|
2456
|
+
super(
|
|
2457
|
+
`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.`
|
|
2458
|
+
);
|
|
2459
|
+
this.name = "InvalidRepoNameError";
|
|
2460
|
+
}
|
|
2461
|
+
};
|
|
2462
|
+
function validateRepoName(name) {
|
|
2463
|
+
if (!REPO_NAME_REGEX.test(name)) {
|
|
2464
|
+
throw new InvalidRepoNameError(name);
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
function validateRepoVisibility(v) {
|
|
2468
|
+
if (v !== "private" && v !== "public") {
|
|
2469
|
+
throw new Error(`Visibility ph\u1EA3i l\xE0 "private" ho\u1EB7c "public", nh\u1EADn: "${v}"`);
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
// src/lib/create-github-remote-from-folder.ts
|
|
2474
|
+
function createGithubRemoteFromFolder(input8) {
|
|
2475
|
+
validateRepoName(input8.name);
|
|
2476
|
+
validateRepoVisibility(input8.visibility);
|
|
2477
|
+
const org = input8.org ?? resolveGithubUsernameDefault();
|
|
2478
|
+
log.info(`T\u1EA1o GitHub repo ${org}/${input8.name} (${input8.visibility})...`);
|
|
2479
|
+
const urls = executeGhRepoCreate({
|
|
2480
|
+
folder: input8.folder,
|
|
2481
|
+
org,
|
|
2482
|
+
name: input8.name,
|
|
2483
|
+
visibility: input8.visibility
|
|
2484
|
+
});
|
|
2485
|
+
log.success(`\u0110\xE3 t\u1EA1o: ${urls.sshUrl}`);
|
|
2486
|
+
return urls;
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2489
|
+
// src/lib/reset-folder-git-and-create-new-remote-under-current-user.ts
|
|
2490
|
+
function backupTimestamp() {
|
|
2491
|
+
const d = /* @__PURE__ */ new Date();
|
|
2492
|
+
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")}`;
|
|
2493
|
+
}
|
|
2494
|
+
async function backupExistingDotGit(folderPath) {
|
|
2495
|
+
const gitDir = join16(folderPath, ".git");
|
|
2496
|
+
if (!await pathExists(gitDir)) {
|
|
2497
|
+
throw new Error(`.git kh\xF4ng t\u1ED3n t\u1EA1i \u1EDF ${folderPath} \u2014 kh\xF4ng c\u1EA7n reset.`);
|
|
2498
|
+
}
|
|
2499
|
+
const backupName = `.git.backup-${backupTimestamp()}`;
|
|
2500
|
+
const backupPath = join16(folderPath, backupName);
|
|
2501
|
+
await fs9.rename(gitDir, backupPath);
|
|
2502
|
+
log.success(`Backup .git \u2192 ${backupName}`);
|
|
2503
|
+
return backupPath;
|
|
2504
|
+
}
|
|
2505
|
+
function reinitGitInFolder(folderPath) {
|
|
2506
|
+
const r1 = spawnSync14("git", ["-C", folderPath, "init", "-b", "main"], {
|
|
2507
|
+
encoding: "utf8",
|
|
2508
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2509
|
+
});
|
|
2510
|
+
if (r1.status !== 0) {
|
|
2511
|
+
throw new Error(`git init th\u1EA5t b\u1EA1i: ${r1.stderr || r1.stdout}`);
|
|
2512
|
+
}
|
|
2513
|
+
log.success("Git init m\u1EDBi (branch main)");
|
|
2514
|
+
spawnSync14("git", ["-C", folderPath, "add", "-A"], {
|
|
2515
|
+
encoding: "utf8",
|
|
2516
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2517
|
+
});
|
|
2518
|
+
const r3 = spawnSync14(
|
|
2519
|
+
"git",
|
|
2520
|
+
["-C", folderPath, "commit", "-m", "chore: initial commit from existing folder"],
|
|
2521
|
+
{ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }
|
|
2522
|
+
);
|
|
2523
|
+
if (r3.status !== 0) {
|
|
2524
|
+
log.warn(`git commit th\u1EA5t b\u1EA1i (folder c\xF3 th\u1EC3 r\u1ED7ng): ${(r3.stderr || "").slice(0, 200)}`);
|
|
2525
|
+
} else {
|
|
2526
|
+
log.success("Initial commit");
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
async function resetFolderGitAndCreateNewRemoteUnderCurrentUser(opts) {
|
|
2530
|
+
const folderName = basename(opts.folderPath);
|
|
2531
|
+
const repoName = opts.repoName ?? folderName;
|
|
2532
|
+
if (!opts.autoYes) {
|
|
2533
|
+
const confirmed = await confirm4({
|
|
2534
|
+
message: `Folder '${folderName}' s\u1EBD \u0111\u01B0\u1EE3c reset:
|
|
2535
|
+
1. Backup .git \u2192 .git.backup-{timestamp}
|
|
2536
|
+
2. Git init m\u1EDBi (branch main, initial commit)
|
|
2537
|
+
3. T\u1EA1o GitHub repo m\u1EDBi '${repoName}' d\u01B0\u1EDBi account c\u1EE7a b\u1EA1n
|
|
2538
|
+
Ti\u1EBFp t\u1EE5c?`,
|
|
2539
|
+
default: true
|
|
2540
|
+
});
|
|
2541
|
+
if (!confirmed) {
|
|
2542
|
+
throw new Error("User abort reset folder.");
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
const visibility = opts.visibility ?? (opts.autoYes ? "private" : await select5({
|
|
2546
|
+
message: "Visibility cho repo m\u1EDBi?",
|
|
2547
|
+
choices: [
|
|
2548
|
+
{ name: "private (m\u1EB7c \u0111\u1ECBnh)", value: "private" },
|
|
2549
|
+
{ name: "public", value: "public" }
|
|
2550
|
+
]
|
|
2551
|
+
}));
|
|
2552
|
+
const backupPath = await backupExistingDotGit(opts.folderPath);
|
|
2553
|
+
reinitGitInFolder(opts.folderPath);
|
|
2554
|
+
const urls = createGithubRemoteFromFolder({
|
|
2555
|
+
folder: opts.folderPath,
|
|
2556
|
+
name: repoName,
|
|
2557
|
+
visibility,
|
|
2558
|
+
org: opts.org
|
|
2559
|
+
});
|
|
2560
|
+
return {
|
|
2561
|
+
newRemoteUrl: urls.httpsUrl,
|
|
2562
|
+
backupPath
|
|
2563
|
+
};
|
|
2564
|
+
}
|
|
2398
2565
|
|
|
2399
2566
|
// src/lib/verify-git-remote-accessible.ts
|
|
2400
|
-
import { spawnSync as
|
|
2567
|
+
import { spawnSync as spawnSync15 } from "child_process";
|
|
2401
2568
|
var TIMEOUT_MS = 5e3;
|
|
2402
2569
|
function classifyRemoteError(stderr) {
|
|
2403
2570
|
const text = stderr.toLowerCase();
|
|
@@ -2413,7 +2580,7 @@ function classifyRemoteError(stderr) {
|
|
|
2413
2580
|
return "unknown";
|
|
2414
2581
|
}
|
|
2415
2582
|
function tryVerifyGitRemoteAccessible(url) {
|
|
2416
|
-
const r =
|
|
2583
|
+
const r = spawnSync15("git", ["ls-remote", "--exit-code", url, "HEAD"], {
|
|
2417
2584
|
encoding: "utf8",
|
|
2418
2585
|
timeout: TIMEOUT_MS,
|
|
2419
2586
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -2435,7 +2602,7 @@ var RemoteAccessAbortedError = class extends Error {
|
|
|
2435
2602
|
}
|
|
2436
2603
|
};
|
|
2437
2604
|
function getCurrentGhUser() {
|
|
2438
|
-
const r =
|
|
2605
|
+
const r = spawnSync16("gh", ["api", "user", "--jq", ".login"], {
|
|
2439
2606
|
encoding: "utf8",
|
|
2440
2607
|
stdio: ["ignore", "pipe", "pipe"]
|
|
2441
2608
|
});
|
|
@@ -2444,7 +2611,7 @@ function getCurrentGhUser() {
|
|
|
2444
2611
|
}
|
|
2445
2612
|
function triggerGhAuthLoginInteractive() {
|
|
2446
2613
|
log.info("M\u1EDF browser \u0111\u1EC3 switch GitHub account...");
|
|
2447
|
-
const r =
|
|
2614
|
+
const r = spawnSync16("gh", ["auth", "login", "--web"], { stdio: "inherit" });
|
|
2448
2615
|
if (r.status !== 0) {
|
|
2449
2616
|
log.warn(`gh auth login exit ${r.status}. B\u1EA1n c\xF3 th\u1EC3 ch\u1EA1y tay r\u1ED3i quay l\u1EA1i retry.`);
|
|
2450
2617
|
}
|
|
@@ -2478,32 +2645,54 @@ async function handleRemoteAccessFailureWithAccountSwitch(args) {
|
|
|
2478
2645
|
log.dim(` L\xFD do: ${reason}${detail ? ` \u2014 ${detail.slice(0, 150)}` : ""}`);
|
|
2479
2646
|
log.info(getReasonHint(reason, currentUrl, ghUser));
|
|
2480
2647
|
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
|
-
|
|
2648
|
+
const choices = [
|
|
2649
|
+
{
|
|
2650
|
+
name: "Nh\u1EADp l\u1EA1i URL \u0111\xFAng (recommended khi URL sai ch\xEDnh t\u1EA3)",
|
|
2651
|
+
value: "re-input-url"
|
|
2652
|
+
},
|
|
2653
|
+
{
|
|
2654
|
+
name: "Switch GitHub account (gh auth login \u2014 m\u1EDF browser)",
|
|
2655
|
+
value: "switch"
|
|
2656
|
+
},
|
|
2657
|
+
{
|
|
2658
|
+
name: "T\xF4i v\u1EEBa fix (accept invite / s\u1EEDa permission) \u2014 retry verify",
|
|
2659
|
+
value: "retry"
|
|
2660
|
+
}
|
|
2661
|
+
];
|
|
2662
|
+
if (args.folderPath) {
|
|
2663
|
+
choices.push({
|
|
2664
|
+
name: "Reset folder & t\u1EA1o repo M\u1EDAI d\u01B0\u1EDBi account c\u1EE7a t\xF4i (backup .git c\u0169)",
|
|
2665
|
+
value: "reset-folder"
|
|
2666
|
+
});
|
|
2667
|
+
}
|
|
2668
|
+
choices.push({
|
|
2669
|
+
name: "T\u1EA1m ng\u01B0ng init \u2014 ch\u1EA1y l\u1EA1i 'avatar init' sau",
|
|
2670
|
+
value: "abort"
|
|
2501
2671
|
});
|
|
2672
|
+
const action = await select6({ message: "C\xE1ch x\u1EED l\xFD?", choices });
|
|
2502
2673
|
if (action === "abort") {
|
|
2503
2674
|
throw new RemoteAccessAbortedError(
|
|
2504
2675
|
`User ch\u1ECDn t\u1EA1m ng\u01B0ng. Fix access ${currentUrl} r\u1ED3i ch\u1EA1y l\u1EA1i 'avatar init'.`
|
|
2505
2676
|
);
|
|
2506
2677
|
}
|
|
2678
|
+
if (action === "reset-folder") {
|
|
2679
|
+
if (!args.folderPath) {
|
|
2680
|
+
log.warn("Reset folder c\u1EA7n folderPath \u2014 internal error.");
|
|
2681
|
+
continue;
|
|
2682
|
+
}
|
|
2683
|
+
try {
|
|
2684
|
+
const reset = await resetFolderGitAndCreateNewRemoteUnderCurrentUser({
|
|
2685
|
+
folderPath: args.folderPath,
|
|
2686
|
+
visibility: args.defaultVisibility
|
|
2687
|
+
});
|
|
2688
|
+
log.success(`Folder \u0111\xE3 reset. Backup t\u1EA1i: ${reset.backupPath}`);
|
|
2689
|
+
log.success(`Remote m\u1EDBi: ${reset.newRemoteUrl}`);
|
|
2690
|
+
return { resolvedUrl: reset.newRemoteUrl };
|
|
2691
|
+
} catch (err) {
|
|
2692
|
+
log.warn(`Reset folder th\u1EA5t b\u1EA1i: ${err.message}`);
|
|
2693
|
+
continue;
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2507
2696
|
if (action === "re-input-url") {
|
|
2508
2697
|
const newUrl = await input4({
|
|
2509
2698
|
message: "URL git remote (https://github.com/owner/repo.git ho\u1EB7c git@github.com:owner/repo.git):",
|
|
@@ -2528,14 +2717,14 @@ async function handleRemoteAccessFailureWithAccountSwitch(args) {
|
|
|
2528
2717
|
|
|
2529
2718
|
// src/lib/safe-bootstrap-for-dirty-folder.ts
|
|
2530
2719
|
import { readdirSync } from "fs";
|
|
2531
|
-
import { select as
|
|
2720
|
+
import { select as select7 } from "@inquirer/prompts";
|
|
2532
2721
|
import { simpleGit as simpleGit3 } from "simple-git";
|
|
2533
2722
|
|
|
2534
2723
|
// src/lib/check-folder-has-git.ts
|
|
2535
2724
|
import { existsSync as existsSync6, statSync } from "fs";
|
|
2536
|
-
import { join as
|
|
2725
|
+
import { join as join17 } from "path";
|
|
2537
2726
|
function checkFolderHasGit(folderPath) {
|
|
2538
|
-
const gitPath =
|
|
2727
|
+
const gitPath = join17(folderPath, ".git");
|
|
2539
2728
|
if (!existsSync6(gitPath)) return false;
|
|
2540
2729
|
const stat = statSync(gitPath);
|
|
2541
2730
|
return stat.isDirectory() || stat.isFile();
|
|
@@ -2567,7 +2756,7 @@ async function createInitialGitCommit(folderPath) {
|
|
|
2567
2756
|
|
|
2568
2757
|
// src/lib/detect-folder-tech-stack.ts
|
|
2569
2758
|
import { existsSync as existsSync7 } from "fs";
|
|
2570
|
-
import { join as
|
|
2759
|
+
import { join as join18 } from "path";
|
|
2571
2760
|
var SIGNATURES = {
|
|
2572
2761
|
node: ["package.json"],
|
|
2573
2762
|
python: ["pyproject.toml", "requirements.txt", "setup.py", "Pipfile"],
|
|
@@ -2579,7 +2768,7 @@ var SIGNATURES = {
|
|
|
2579
2768
|
function detectFolderTechStack(folderPath) {
|
|
2580
2769
|
const matched = [];
|
|
2581
2770
|
for (const [stack, files] of Object.entries(SIGNATURES)) {
|
|
2582
|
-
if (files.some((f) => existsSync7(
|
|
2771
|
+
if (files.some((f) => existsSync7(join18(folderPath, f)))) {
|
|
2583
2772
|
matched.push(stack);
|
|
2584
2773
|
}
|
|
2585
2774
|
}
|
|
@@ -2588,25 +2777,25 @@ function detectFolderTechStack(folderPath) {
|
|
|
2588
2777
|
|
|
2589
2778
|
// src/lib/gitignore-template-loader.ts
|
|
2590
2779
|
import { readFileSync as readFileSync3 } from "fs";
|
|
2591
|
-
import { dirname as dirname4, join as
|
|
2780
|
+
import { dirname as dirname4, join as join19 } from "path";
|
|
2592
2781
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2593
2782
|
var __dirname = dirname4(fileURLToPath2(import.meta.url));
|
|
2594
2783
|
var CANDIDATE_DIRS = [
|
|
2595
2784
|
// Bundled production: dist/index.js → __dirname = .../dist/, sibling dist/templates
|
|
2596
|
-
|
|
2785
|
+
join19(__dirname, "templates", "gitignore"),
|
|
2597
2786
|
// Legacy bundled: nếu file là dist/lib/*.js (sub-bundle), templates ở dist/templates
|
|
2598
|
-
|
|
2787
|
+
join19(__dirname, "..", "templates", "gitignore"),
|
|
2599
2788
|
// Dev mode (vitest/tsx run src/ trực tiếp): __dirname = src/lib/
|
|
2600
|
-
|
|
2789
|
+
join19(__dirname, "..", "..", "src", "templates", "gitignore"),
|
|
2601
2790
|
// npm-installed alt: __dirname = .../dist/ → package_root/src/templates
|
|
2602
|
-
|
|
2791
|
+
join19(__dirname, "..", "src", "templates", "gitignore")
|
|
2603
2792
|
];
|
|
2604
2793
|
var AVATAR_MARKER_START = "# === avatar ===";
|
|
2605
2794
|
var AVATAR_MARKER_END = "# === /avatar ===";
|
|
2606
2795
|
function readTemplate(stack) {
|
|
2607
2796
|
for (const dir of CANDIDATE_DIRS) {
|
|
2608
2797
|
try {
|
|
2609
|
-
return readFileSync3(
|
|
2798
|
+
return readFileSync3(join19(dir, `${stack}.txt`), "utf8");
|
|
2610
2799
|
} catch {
|
|
2611
2800
|
}
|
|
2612
2801
|
}
|
|
@@ -2621,9 +2810,9 @@ ${readTemplate(s).trim()}`);
|
|
|
2621
2810
|
|
|
2622
2811
|
// src/lib/write-or-merge-gitignore.ts
|
|
2623
2812
|
import { existsSync as existsSync8, readFileSync as readFileSync4, writeFileSync } from "fs";
|
|
2624
|
-
import { join as
|
|
2813
|
+
import { join as join20 } from "path";
|
|
2625
2814
|
function writeOrMergeGitignore(folderPath, avatarBlock) {
|
|
2626
|
-
const path =
|
|
2815
|
+
const path = join20(folderPath, ".gitignore");
|
|
2627
2816
|
if (!existsSync8(path)) {
|
|
2628
2817
|
writeFileSync(path, avatarBlock, "utf8");
|
|
2629
2818
|
return;
|
|
@@ -2665,7 +2854,7 @@ async function promptBootstrapStrategy(state, opts) {
|
|
|
2665
2854
|
if (opts.presetStrategy) return opts.presetStrategy;
|
|
2666
2855
|
if (opts.autoYes) return "stash";
|
|
2667
2856
|
if (state === "empty" || state === "clean") return "commit-all";
|
|
2668
|
-
return await
|
|
2857
|
+
return await select7({
|
|
2669
2858
|
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
2859
|
choices: [
|
|
2671
2860
|
{
|
|
@@ -2799,22 +2988,22 @@ async function safeBootstrapGitInFolder(folderPath, opts = {}) {
|
|
|
2799
2988
|
}
|
|
2800
2989
|
|
|
2801
2990
|
// src/lib/team-pack-submodule-manager.ts
|
|
2802
|
-
import { join as
|
|
2991
|
+
import { join as join21 } from "path";
|
|
2803
2992
|
|
|
2804
2993
|
// src/lib/check-team-pack-access-with-retry-loop.ts
|
|
2805
|
-
import { spawnSync as
|
|
2806
|
-
import { confirm as
|
|
2994
|
+
import { spawnSync as spawnSync17 } from "child_process";
|
|
2995
|
+
import { confirm as confirm5, select as select8 } from "@inquirer/prompts";
|
|
2807
2996
|
import boxen3 from "boxen";
|
|
2808
2997
|
function parseRepoSlugFromGitUrl(url) {
|
|
2809
2998
|
const httpsMatch = url.match(/github\.com[/:]([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
2810
2999
|
return httpsMatch?.[1] ?? null;
|
|
2811
3000
|
}
|
|
2812
3001
|
function checkRepoAccess(repoSlug) {
|
|
2813
|
-
const r =
|
|
3002
|
+
const r = spawnSync17("gh", ["api", `repos/${repoSlug}`], { stdio: "ignore" });
|
|
2814
3003
|
return r.status === 0;
|
|
2815
3004
|
}
|
|
2816
3005
|
function getCurrentGhUser2() {
|
|
2817
|
-
const r =
|
|
3006
|
+
const r = spawnSync17("gh", ["api", "user", "--jq", ".login"], {
|
|
2818
3007
|
encoding: "utf8",
|
|
2819
3008
|
stdio: ["ignore", "pipe", "pipe"]
|
|
2820
3009
|
});
|
|
@@ -2823,13 +3012,13 @@ function getCurrentGhUser2() {
|
|
|
2823
3012
|
}
|
|
2824
3013
|
function triggerGhAuthLoginInteractive2() {
|
|
2825
3014
|
log.info("M\u1EDF browser \u0111\u1EC3 switch GitHub account...");
|
|
2826
|
-
const r =
|
|
3015
|
+
const r = spawnSync17("gh", ["auth", "login", "--web"], { stdio: "inherit" });
|
|
2827
3016
|
if (r.status !== 0) {
|
|
2828
3017
|
log.warn(`gh auth login exit ${r.status}. C\xF3 th\u1EC3 ch\u1EA1y tay r\u1ED3i quay l\u1EA1i retry.`);
|
|
2829
3018
|
}
|
|
2830
3019
|
}
|
|
2831
3020
|
async function copyInfoToClipboardWithConsent(info) {
|
|
2832
|
-
const ok = await
|
|
3021
|
+
const ok = await confirm5({
|
|
2833
3022
|
message: "Copy th\xF4ng tin (GitHub username + email) v\xE0o clipboard \u0111\u1EC3 d\xE1n v\xE0o Slack/email?",
|
|
2834
3023
|
default: true
|
|
2835
3024
|
});
|
|
@@ -2881,7 +3070,7 @@ async function ensureTeamPackAccessWithRetry(args) {
|
|
|
2881
3070
|
while (true) {
|
|
2882
3071
|
const ghUser = getCurrentGhUser2();
|
|
2883
3072
|
const ghUserDisplay = ghUser ?? "(ch\u01B0a gh auth)";
|
|
2884
|
-
const action = await
|
|
3073
|
+
const action = await select8({
|
|
2885
3074
|
message: "C\xE1ch x\u1EED l\xFD?",
|
|
2886
3075
|
choices: [
|
|
2887
3076
|
{
|
|
@@ -2995,7 +3184,7 @@ async function addTeamPackSubmodule(projectRoot, tag, ssoEmail, latest = false)
|
|
|
2995
3184
|
await checkoutBranchHeadInSubmodule(TEAM_PACK_RELATIVE_PATH, DEFAULT_PACK_BRANCH, projectRoot);
|
|
2996
3185
|
return { pinnedTag: `${DEFAULT_PACK_BRANCH} (HEAD)` };
|
|
2997
3186
|
}
|
|
2998
|
-
const submoduleDir =
|
|
3187
|
+
const submoduleDir = join21(projectRoot, TEAM_PACK_RELATIVE_PATH);
|
|
2999
3188
|
const allTags = await listTags(submoduleDir);
|
|
3000
3189
|
const target = pickLatestStableSemVerTag(allTags);
|
|
3001
3190
|
if (target) {
|
|
@@ -3004,7 +3193,7 @@ async function addTeamPackSubmodule(projectRoot, tag, ssoEmail, latest = false)
|
|
|
3004
3193
|
return { pinnedTag: target };
|
|
3005
3194
|
}
|
|
3006
3195
|
async function readPinnedPackVersion(projectRoot) {
|
|
3007
|
-
const submoduleRoot =
|
|
3196
|
+
const submoduleRoot = join21(projectRoot, TEAM_PACK_RELATIVE_PATH);
|
|
3008
3197
|
const tag = await tagAtHead(submoduleRoot);
|
|
3009
3198
|
if (tag) return tag;
|
|
3010
3199
|
const sha = await currentCommitSha(submoduleRoot);
|
|
@@ -3012,97 +3201,13 @@ async function readPinnedPackVersion(projectRoot) {
|
|
|
3012
3201
|
}
|
|
3013
3202
|
|
|
3014
3203
|
// 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
|
-
}
|
|
3204
|
+
import { basename as basename3, join as join27, resolve as resolve2 } from "path";
|
|
3205
|
+
import { input as input7, select as select12 } from "@inquirer/prompts";
|
|
3101
3206
|
|
|
3102
3207
|
// src/lib/check-gh-cli-auth-status.ts
|
|
3103
|
-
import { spawnSync as
|
|
3208
|
+
import { spawnSync as spawnSync18 } from "child_process";
|
|
3104
3209
|
function checkGhCliAuthStatus() {
|
|
3105
|
-
const r =
|
|
3210
|
+
const r = spawnSync18("gh", ["auth", "status"], { stdio: "ignore" });
|
|
3106
3211
|
if (r.error && r.error.code === "ENOENT") {
|
|
3107
3212
|
return "not-installed";
|
|
3108
3213
|
}
|
|
@@ -3110,12 +3215,12 @@ function checkGhCliAuthStatus() {
|
|
|
3110
3215
|
}
|
|
3111
3216
|
|
|
3112
3217
|
// src/lib/detect-package-manager.ts
|
|
3113
|
-
import { spawnSync as
|
|
3218
|
+
import { spawnSync as spawnSync19 } from "child_process";
|
|
3114
3219
|
function hasBinary(name) {
|
|
3115
3220
|
const platform2 = detectHostPlatform();
|
|
3116
3221
|
const probe = platform2 === "win32" ? "where" : "command";
|
|
3117
3222
|
const args = platform2 === "win32" ? [name] : ["-v", name];
|
|
3118
|
-
const r =
|
|
3223
|
+
const r = spawnSync19(probe, args, {
|
|
3119
3224
|
shell: platform2 !== "win32",
|
|
3120
3225
|
stdio: "ignore"
|
|
3121
3226
|
});
|
|
@@ -3131,7 +3236,7 @@ function detectPackageManager() {
|
|
|
3131
3236
|
}
|
|
3132
3237
|
|
|
3133
3238
|
// src/lib/install-gh-cli-via-package-manager.ts
|
|
3134
|
-
import { spawnSync as
|
|
3239
|
+
import { spawnSync as spawnSync20 } from "child_process";
|
|
3135
3240
|
var INSTALL_COMMANDS = {
|
|
3136
3241
|
brew: { cmd: "brew", args: ["install", "gh"] },
|
|
3137
3242
|
apt: { cmd: "sudo", args: ["apt-get", "install", "-y", "gh"] },
|
|
@@ -3142,7 +3247,7 @@ var INSTALL_COMMANDS = {
|
|
|
3142
3247
|
function installGhCliViaPackageManager(pm) {
|
|
3143
3248
|
const spec = INSTALL_COMMANDS[pm];
|
|
3144
3249
|
log.info(`\u0110ang c\xE0i gh CLI qua ${pm}...`);
|
|
3145
|
-
const r =
|
|
3250
|
+
const r = spawnSync20(spec.cmd, spec.args, { stdio: "inherit" });
|
|
3146
3251
|
if (r.status !== 0) {
|
|
3147
3252
|
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
3253
|
}
|
|
@@ -3150,9 +3255,9 @@ function installGhCliViaPackageManager(pm) {
|
|
|
3150
3255
|
}
|
|
3151
3256
|
|
|
3152
3257
|
// src/lib/setup-git-credential-via-gh.ts
|
|
3153
|
-
import { spawnSync as
|
|
3258
|
+
import { spawnSync as spawnSync21 } from "child_process";
|
|
3154
3259
|
function setupGitCredentialViaGh() {
|
|
3155
|
-
const r =
|
|
3260
|
+
const r = spawnSync21("gh", ["auth", "setup-git"], { stdio: "ignore" });
|
|
3156
3261
|
if (r.status !== 0) {
|
|
3157
3262
|
log.warn("gh auth setup-git fail (non-fatal). N\u1EBFu git clone l\u1ED7i 128 \u2192 ch\u1EA1y th\u1EE7 c\xF4ng.");
|
|
3158
3263
|
return;
|
|
@@ -3161,10 +3266,10 @@ function setupGitCredentialViaGh() {
|
|
|
3161
3266
|
}
|
|
3162
3267
|
|
|
3163
3268
|
// src/lib/trigger-gh-cli-auth-login.ts
|
|
3164
|
-
import { spawnSync as
|
|
3269
|
+
import { spawnSync as spawnSync22 } from "child_process";
|
|
3165
3270
|
function triggerGhCliAuthLogin() {
|
|
3166
3271
|
log.info("Kh\u1EDFi \u0111\u1ED9ng \u0111\u0103ng nh\u1EADp GitHub qua gh CLI (browser s\u1EBD m\u1EDF)...");
|
|
3167
|
-
const r =
|
|
3272
|
+
const r = spawnSync22(
|
|
3168
3273
|
"gh",
|
|
3169
3274
|
["auth", "login", "--hostname", "github.com", "--web", "--git-protocol", "ssh"],
|
|
3170
3275
|
{ stdio: "inherit" }
|
|
@@ -3253,28 +3358,28 @@ async function ensureGitHubReady(remoteUrl) {
|
|
|
3253
3358
|
}
|
|
3254
3359
|
|
|
3255
3360
|
// src/lib/add-team-pack-submodule-with-retry-on-network-fail.ts
|
|
3256
|
-
import { spawnSync as
|
|
3257
|
-
import { select as
|
|
3361
|
+
import { spawnSync as spawnSync23 } from "child_process";
|
|
3362
|
+
import { select as select9 } from "@inquirer/prompts";
|
|
3258
3363
|
function isSshPermissionError(message) {
|
|
3259
3364
|
const text = message.toLowerCase();
|
|
3260
3365
|
return text.includes("permission denied (publickey)") || text.includes("publickey)") || text.includes("ssh: could not resolve") || text.includes("host key verification failed");
|
|
3261
3366
|
}
|
|
3262
3367
|
function triggerGhAuthLoginInteractive3() {
|
|
3263
3368
|
log.info("M\u1EDF browser \u0111\u1EC3 switch GitHub account...");
|
|
3264
|
-
const r =
|
|
3369
|
+
const r = spawnSync23("gh", ["auth", "login", "--web"], { stdio: "inherit" });
|
|
3265
3370
|
if (r.status !== 0) {
|
|
3266
3371
|
log.warn(`gh auth login exit ${r.status}. C\xF3 th\u1EC3 ch\u1EA1y tay r\u1ED3i quay l\u1EA1i retry.`);
|
|
3267
3372
|
}
|
|
3268
3373
|
}
|
|
3269
3374
|
function openGithubSshKeysPage() {
|
|
3270
3375
|
log.info("M\u1EDF trang GitHub Settings \u2192 SSH Keys...");
|
|
3271
|
-
const r =
|
|
3376
|
+
const r = spawnSync23("open", ["https://github.com/settings/keys"], { stdio: "ignore" });
|
|
3272
3377
|
if (r.status !== 0) {
|
|
3273
3378
|
log.info("URL: https://github.com/settings/keys");
|
|
3274
3379
|
}
|
|
3275
3380
|
}
|
|
3276
3381
|
async function handleSshPermissionError() {
|
|
3277
|
-
return await
|
|
3382
|
+
return await select9({
|
|
3278
3383
|
message: "SSH permission denied. C\xE1ch x\u1EED l\xFD?",
|
|
3279
3384
|
choices: [
|
|
3280
3385
|
{
|
|
@@ -3448,12 +3553,12 @@ function parseBootstrapStrategyOpts(opts) {
|
|
|
3448
3553
|
}
|
|
3449
3554
|
|
|
3450
3555
|
// src/commands/workspace-scaffold-and-finalize-orchestrator.ts
|
|
3451
|
-
import { join as
|
|
3452
|
-
import { basename } from "path";
|
|
3453
|
-
import { confirm as
|
|
3556
|
+
import { join as join26 } from "path";
|
|
3557
|
+
import { basename as basename2 } from "path";
|
|
3558
|
+
import { confirm as confirm6, input as input6, select as select11 } from "@inquirer/prompts";
|
|
3454
3559
|
|
|
3455
3560
|
// src/lib/create-workspace-remote-via-gh.ts
|
|
3456
|
-
import { spawnSync as
|
|
3561
|
+
import { spawnSync as spawnSync24 } from "child_process";
|
|
3457
3562
|
var CreateWorkspaceRemoteError = class extends Error {
|
|
3458
3563
|
reason;
|
|
3459
3564
|
fullName;
|
|
@@ -3483,20 +3588,20 @@ function classifyGhCreateError(stderr) {
|
|
|
3483
3588
|
return "unknown";
|
|
3484
3589
|
}
|
|
3485
3590
|
function repoExistsOnGitHub(fullName) {
|
|
3486
|
-
const r =
|
|
3591
|
+
const r = spawnSync24("gh", ["repo", "view", fullName, "--json", "name"], {
|
|
3487
3592
|
stdio: "ignore"
|
|
3488
3593
|
});
|
|
3489
3594
|
return r.status === 0;
|
|
3490
3595
|
}
|
|
3491
3596
|
function canCreateInNamespace(org, ghUser) {
|
|
3492
3597
|
if (org.toLowerCase() === ghUser.toLowerCase()) return { ok: true };
|
|
3493
|
-
const r =
|
|
3598
|
+
const r = spawnSync24("gh", ["api", `orgs/${org}/members/${ghUser}`, "--silent"], {
|
|
3494
3599
|
stdio: "ignore"
|
|
3495
3600
|
});
|
|
3496
3601
|
if (r.status === 0) return { ok: true };
|
|
3497
|
-
const orgCheck =
|
|
3602
|
+
const orgCheck = spawnSync24("gh", ["api", `orgs/${org}`, "--silent"], { stdio: "ignore" });
|
|
3498
3603
|
if (orgCheck.status !== 0) {
|
|
3499
|
-
const userCheck =
|
|
3604
|
+
const userCheck = spawnSync24("gh", ["api", `users/${org}`, "--silent"], { stdio: "ignore" });
|
|
3500
3605
|
if (userCheck.status === 0) {
|
|
3501
3606
|
return {
|
|
3502
3607
|
ok: false,
|
|
@@ -3532,7 +3637,7 @@ async function createWorkspaceRemoteViaGh(input8) {
|
|
|
3532
3637
|
);
|
|
3533
3638
|
}
|
|
3534
3639
|
log.info(`T\u1EA1o GitHub repo cho workspace: ${fullName} (${input8.visibility})...`);
|
|
3535
|
-
const r =
|
|
3640
|
+
const r = spawnSync24(
|
|
3536
3641
|
"gh",
|
|
3537
3642
|
[
|
|
3538
3643
|
"repo",
|
|
@@ -3573,7 +3678,7 @@ ${combined}
|
|
|
3573
3678
|
function linkExistingRemoteToWorkspace(args) {
|
|
3574
3679
|
const sshUrl = `git@github.com:${args.fullName}.git`;
|
|
3575
3680
|
const httpsUrl = `https://github.com/${args.fullName}.git`;
|
|
3576
|
-
const addResult =
|
|
3681
|
+
const addResult = spawnSync24(
|
|
3577
3682
|
"git",
|
|
3578
3683
|
["-C", args.workspacePath, "remote", "add", "origin", sshUrl],
|
|
3579
3684
|
{
|
|
@@ -3582,7 +3687,7 @@ function linkExistingRemoteToWorkspace(args) {
|
|
|
3582
3687
|
}
|
|
3583
3688
|
);
|
|
3584
3689
|
if (addResult.status !== 0) {
|
|
3585
|
-
|
|
3690
|
+
spawnSync24("git", ["-C", args.workspacePath, "remote", "set-url", "origin", sshUrl], {
|
|
3586
3691
|
stdio: "ignore"
|
|
3587
3692
|
});
|
|
3588
3693
|
}
|
|
@@ -3591,8 +3696,8 @@ function linkExistingRemoteToWorkspace(args) {
|
|
|
3591
3696
|
}
|
|
3592
3697
|
|
|
3593
3698
|
// src/lib/merge-pack-settings-into-project-settings.ts
|
|
3594
|
-
import { promises as
|
|
3595
|
-
import { join as
|
|
3699
|
+
import { promises as fs10 } from "fs";
|
|
3700
|
+
import { join as join22 } from "path";
|
|
3596
3701
|
async function isStatusLineCommandResolvable(workspacePath, command) {
|
|
3597
3702
|
const trimmed = command.trim();
|
|
3598
3703
|
const match = trimmed.match(/^(node|python|python3|bash|sh)\s+([^\s]+)/);
|
|
@@ -3600,7 +3705,7 @@ async function isStatusLineCommandResolvable(workspacePath, command) {
|
|
|
3600
3705
|
return true;
|
|
3601
3706
|
}
|
|
3602
3707
|
const filePath = match[2];
|
|
3603
|
-
const fullPath = filePath.startsWith("/") ? filePath :
|
|
3708
|
+
const fullPath = filePath.startsWith("/") ? filePath : join22(workspacePath, filePath);
|
|
3604
3709
|
return await pathExists(fullPath);
|
|
3605
3710
|
}
|
|
3606
3711
|
function backupFilename(originalPath) {
|
|
@@ -3634,8 +3739,8 @@ function mergeHooksPerEvent(packHooks, userHooks) {
|
|
|
3634
3739
|
return { merged, touchedEvents: touched };
|
|
3635
3740
|
}
|
|
3636
3741
|
async function mergePackSettingsIntoProjectSettings(workspacePath) {
|
|
3637
|
-
const packTemplatePath =
|
|
3638
|
-
const projectSettingsPath =
|
|
3742
|
+
const packTemplatePath = join22(workspacePath, ".claude", "pack", "templates", "settings.json.tpl");
|
|
3743
|
+
const projectSettingsPath = join22(workspacePath, ".claude", "settings.json");
|
|
3639
3744
|
if (!await pathExists(packTemplatePath)) {
|
|
3640
3745
|
return { action: "no-pack-template", changes: [] };
|
|
3641
3746
|
}
|
|
@@ -3729,18 +3834,18 @@ async function mergePackSettingsIntoProjectSettings(workspacePath) {
|
|
|
3729
3834
|
let backupPath;
|
|
3730
3835
|
if (projectHasSettings) {
|
|
3731
3836
|
backupPath = backupFilename(projectSettingsPath);
|
|
3732
|
-
await
|
|
3837
|
+
await fs10.copyFile(projectSettingsPath, backupPath);
|
|
3733
3838
|
}
|
|
3734
3839
|
await writeJsonAtomic(projectSettingsPath, merged);
|
|
3735
3840
|
return { action: "merged", backupPath, changes };
|
|
3736
3841
|
}
|
|
3737
3842
|
|
|
3738
3843
|
// src/lib/symlink-farm-for-team-pack-mount-dirs.ts
|
|
3739
|
-
import { promises as
|
|
3740
|
-
import { dirname as dirname6, join as
|
|
3844
|
+
import { promises as fs12 } from "fs";
|
|
3845
|
+
import { dirname as dirname6, join as join23, relative as relative2 } from "path";
|
|
3741
3846
|
|
|
3742
3847
|
// src/lib/backup-existing-dir-before-symlink-override.ts
|
|
3743
|
-
import { promises as
|
|
3848
|
+
import { promises as fs11 } from "fs";
|
|
3744
3849
|
function timestamp() {
|
|
3745
3850
|
const d = /* @__PURE__ */ new Date();
|
|
3746
3851
|
const pad = (n) => n.toString().padStart(2, "0");
|
|
@@ -3748,7 +3853,7 @@ function timestamp() {
|
|
|
3748
3853
|
}
|
|
3749
3854
|
async function backupDirBeforeReplace(targetPath) {
|
|
3750
3855
|
const backupPath = `${targetPath}.backup-${timestamp()}`;
|
|
3751
|
-
await
|
|
3856
|
+
await fs11.rename(targetPath, backupPath);
|
|
3752
3857
|
return backupPath;
|
|
3753
3858
|
}
|
|
3754
3859
|
|
|
@@ -3764,7 +3869,7 @@ var TEAM_PACK_MOUNT_DIRS = [
|
|
|
3764
3869
|
];
|
|
3765
3870
|
async function isSymbolicLink(path) {
|
|
3766
3871
|
try {
|
|
3767
|
-
const st = await
|
|
3872
|
+
const st = await fs12.lstat(path);
|
|
3768
3873
|
return st.isSymbolicLink();
|
|
3769
3874
|
} catch {
|
|
3770
3875
|
return false;
|
|
@@ -3777,33 +3882,33 @@ async function syncMountedDir(source, dest, force) {
|
|
|
3777
3882
|
}
|
|
3778
3883
|
if (await pathExists(dest)) {
|
|
3779
3884
|
if (await isSymbolicLink(dest)) {
|
|
3780
|
-
await
|
|
3885
|
+
await fs12.unlink(dest);
|
|
3781
3886
|
} else if (force) {
|
|
3782
3887
|
const backupPath = await backupDirBeforeReplace(dest);
|
|
3783
3888
|
const relativeSource2 = relative2(dirname6(dest), source);
|
|
3784
|
-
await
|
|
3889
|
+
await fs12.symlink(relativeSource2, dest);
|
|
3785
3890
|
return { dir, action: "backed-up-and-linked", backupPath };
|
|
3786
3891
|
} else {
|
|
3787
3892
|
return { dir, action: "skipped-conflict" };
|
|
3788
3893
|
}
|
|
3789
3894
|
}
|
|
3790
3895
|
const relativeSource = relative2(dirname6(dest), source);
|
|
3791
|
-
await
|
|
3896
|
+
await fs12.symlink(relativeSource, dest);
|
|
3792
3897
|
return { dir, action: "created" };
|
|
3793
3898
|
}
|
|
3794
3899
|
async function syncAllMountDirs(packDir, claudeDir, force) {
|
|
3795
3900
|
const results = [];
|
|
3796
3901
|
for (const dir of TEAM_PACK_MOUNT_DIRS) {
|
|
3797
|
-
const source =
|
|
3798
|
-
const dest =
|
|
3902
|
+
const source = join23(packDir, dir);
|
|
3903
|
+
const dest = join23(claudeDir, dir);
|
|
3799
3904
|
results.push(await syncMountedDir(source, dest, force));
|
|
3800
3905
|
}
|
|
3801
3906
|
return results;
|
|
3802
3907
|
}
|
|
3803
3908
|
|
|
3804
3909
|
// src/commands/init-success-rendering-and-helpers.ts
|
|
3805
|
-
import { join as
|
|
3806
|
-
import { input as input5, select as
|
|
3910
|
+
import { join as join25, relative as relative3 } from "path";
|
|
3911
|
+
import { input as input5, select as select10 } from "@inquirer/prompts";
|
|
3807
3912
|
import boxen5 from "boxen";
|
|
3808
3913
|
|
|
3809
3914
|
// src/lib/format-pack-commands-cheatsheet-box.ts
|
|
@@ -3844,7 +3949,7 @@ function formatPackCommandsCheatsheetBox() {
|
|
|
3844
3949
|
|
|
3845
3950
|
// src/commands/init-conflict-detection-helpers.ts
|
|
3846
3951
|
import { readdir } from "fs/promises";
|
|
3847
|
-
import { join as
|
|
3952
|
+
import { join as join24 } from "path";
|
|
3848
3953
|
async function isEmptyOrMissing(path) {
|
|
3849
3954
|
if (!await pathExists(path)) return true;
|
|
3850
3955
|
try {
|
|
@@ -3857,7 +3962,7 @@ async function isEmptyOrMissing(path) {
|
|
|
3857
3962
|
}
|
|
3858
3963
|
async function findAlternativeWorkspaceName(parent, desiredName, maxAttempts = 10) {
|
|
3859
3964
|
for (let i = 2; i < maxAttempts; i++) {
|
|
3860
|
-
const candidate =
|
|
3965
|
+
const candidate = join24(parent, `${desiredName}-${i}`);
|
|
3861
3966
|
if (await isEmptyOrMissing(candidate)) return candidate;
|
|
3862
3967
|
}
|
|
3863
3968
|
return null;
|
|
@@ -3865,7 +3970,7 @@ async function findAlternativeWorkspaceName(parent, desiredName, maxAttempts = 1
|
|
|
3865
3970
|
|
|
3866
3971
|
// src/commands/init-success-rendering-and-helpers.ts
|
|
3867
3972
|
async function resolveWorkspacePath(parent, desiredName, force) {
|
|
3868
|
-
const desired =
|
|
3973
|
+
const desired = join25(parent, desiredName);
|
|
3869
3974
|
if (await isEmptyOrMissing(desired)) return desired;
|
|
3870
3975
|
log.warn(`Workspace path "${desired}" \u0111\xE3 c\xF3 n\u1ED9i dung.`);
|
|
3871
3976
|
while (true) {
|
|
@@ -3880,7 +3985,7 @@ async function resolveWorkspacePath(parent, desiredName, force) {
|
|
|
3880
3985
|
}
|
|
3881
3986
|
choices.push({ name: "Nh\u1EADp t\xEAn workspace kh\xE1c (manual)", value: "manual" });
|
|
3882
3987
|
choices.push({ name: "T\u1EA1m ng\u01B0ng init", value: "abort" });
|
|
3883
|
-
const action = await
|
|
3988
|
+
const action = await select10({
|
|
3884
3989
|
message: "C\xE1ch x\u1EED l\xFD workspace path conflict?",
|
|
3885
3990
|
choices
|
|
3886
3991
|
});
|
|
@@ -3896,7 +4001,7 @@ async function resolveWorkspacePath(parent, desiredName, force) {
|
|
|
3896
4001
|
message: "T\xEAn workspace m\u1EDBi:",
|
|
3897
4002
|
validate: (v) => v.trim().length > 0 ? true : "T\xEAn kh\xF4ng \u0111\u01B0\u1EE3c r\u1ED7ng"
|
|
3898
4003
|
});
|
|
3899
|
-
const newPath =
|
|
4004
|
+
const newPath = join25(parent, newName.trim());
|
|
3900
4005
|
if (await isEmptyOrMissing(newPath)) return newPath;
|
|
3901
4006
|
log.warn(`"${newPath}" c\u0169ng \u0111\xE3 c\xF3 n\u1ED9i dung. Th\u1EED t\xEAn kh\xE1c.`);
|
|
3902
4007
|
}
|
|
@@ -3953,7 +4058,7 @@ async function printInitSuccessBox(rootPath, flow, aiResult = null, gitnexusResu
|
|
|
3953
4058
|
];
|
|
3954
4059
|
process.stdout.write(`${boxen5(lines.join("\n"), { padding: 1, borderStyle: "round" })}
|
|
3955
4060
|
`);
|
|
3956
|
-
const packDir =
|
|
4061
|
+
const packDir = join25(rootPath, TEAM_PACK_RELATIVE_PATH);
|
|
3957
4062
|
if (await pathExists(packDir)) {
|
|
3958
4063
|
process.stdout.write(`
|
|
3959
4064
|
${formatPackCommandsCheatsheetBox()}
|
|
@@ -3969,7 +4074,7 @@ async function getOrCreateOriginRemote(folderPath, opts) {
|
|
|
3969
4074
|
log.success(`Folder \u0111\xE3 c\xF3 remote origin: ${origin.refs.push}`);
|
|
3970
4075
|
return origin.refs.push;
|
|
3971
4076
|
}
|
|
3972
|
-
const shouldCreate = opts.createRemote ?? await
|
|
4077
|
+
const shouldCreate = opts.createRemote ?? await confirm6({
|
|
3973
4078
|
message: "Folder ch\u01B0a c\xF3 remote. T\u1EA1o GitHub repo ngay \u0111\u1EC3 share team?",
|
|
3974
4079
|
default: true
|
|
3975
4080
|
});
|
|
@@ -3978,7 +4083,7 @@ async function getOrCreateOriginRemote(folderPath, opts) {
|
|
|
3978
4083
|
return void 0;
|
|
3979
4084
|
}
|
|
3980
4085
|
await ensureGitHubReady();
|
|
3981
|
-
const visibility = opts.repoVisibility ?? await
|
|
4086
|
+
const visibility = opts.repoVisibility ?? await select11({
|
|
3982
4087
|
message: "Visibility?",
|
|
3983
4088
|
choices: [
|
|
3984
4089
|
{ name: "private (m\u1EB7c \u0111\u1ECBnh)", value: "private" },
|
|
@@ -3987,7 +4092,7 @@ async function getOrCreateOriginRemote(folderPath, opts) {
|
|
|
3987
4092
|
});
|
|
3988
4093
|
const repoName = await input6({
|
|
3989
4094
|
message: "T\xEAn repo:",
|
|
3990
|
-
default:
|
|
4095
|
+
default: basename2(folderPath)
|
|
3991
4096
|
});
|
|
3992
4097
|
const urls = createGithubRemoteFromFolder({
|
|
3993
4098
|
folder: folderPath,
|
|
@@ -4053,10 +4158,10 @@ async function finalizeWorkspaceScaffold(args) {
|
|
|
4053
4158
|
await writeRootClaudeMd(args.workspacePath, vars);
|
|
4054
4159
|
await writeProjectSettings(args.workspacePath, vars);
|
|
4055
4160
|
await appendGitignoreEntries(args.workspacePath);
|
|
4056
|
-
await ensureDir(
|
|
4057
|
-
await ensureDir(
|
|
4058
|
-
await installGitHook(
|
|
4059
|
-
await installGitHook(
|
|
4161
|
+
await ensureDir(join26(args.workspacePath, "notes"));
|
|
4162
|
+
await ensureDir(join26(args.workspacePath, "scripts"));
|
|
4163
|
+
await installGitHook(join26(args.workspacePath, ".git"), "post-merge");
|
|
4164
|
+
await installGitHook(join26(args.workspacePath, ".git", "modules", "src"), "pre-push");
|
|
4060
4165
|
log.success("C\xE0i post-merge (workspace) + pre-push (src/)");
|
|
4061
4166
|
await autoSyncPackOnInit(args.workspacePath);
|
|
4062
4167
|
await appendAuditEntry("init", `flow=${args.flow},workspace=${args.workspaceName}`);
|
|
@@ -4094,12 +4199,12 @@ async function finalizeWorkspaceScaffold(args) {
|
|
|
4094
4199
|
await printInitSuccessBox(args.workspacePath, args.flow, aiResult, gitnexusResult);
|
|
4095
4200
|
}
|
|
4096
4201
|
async function autoSyncPackOnInit(workspacePath) {
|
|
4097
|
-
const packDir =
|
|
4202
|
+
const packDir = join26(workspacePath, TEAM_PACK_RELATIVE_PATH);
|
|
4098
4203
|
if (!await pathExists(packDir)) {
|
|
4099
4204
|
log.dim("Pack submodule kh\xF4ng t\u1ED3n t\u1EA1i (skip auto-sync). C\xF3 th\u1EC3 ch\u1EA1y `avatar sync` sau.");
|
|
4100
4205
|
return;
|
|
4101
4206
|
}
|
|
4102
|
-
const claudeDir =
|
|
4207
|
+
const claudeDir = join26(workspacePath, ".claude");
|
|
4103
4208
|
log.info("Auto-sync pack content v\xE0o .claude/ (symlinks + settings merge)...");
|
|
4104
4209
|
try {
|
|
4105
4210
|
const results = await syncAllMountDirs(packDir, claudeDir, false);
|
|
@@ -4134,13 +4239,13 @@ async function maybeCreateWorkspaceRemote(args) {
|
|
|
4134
4239
|
let shouldCreate = args.createWorkspaceRemote;
|
|
4135
4240
|
if (shouldCreate === void 0) {
|
|
4136
4241
|
if (args.autoYes) return;
|
|
4137
|
-
shouldCreate = await
|
|
4242
|
+
shouldCreate = await confirm6({
|
|
4138
4243
|
message: "T\u1EA1o remote GitHub cho workspace \u0111\u1EC3 share team? (Avatar state)",
|
|
4139
4244
|
default: false
|
|
4140
4245
|
});
|
|
4141
4246
|
}
|
|
4142
4247
|
if (!shouldCreate) return;
|
|
4143
|
-
const visibility = args.repoVisibility ?? (args.autoYes ? "private" : await
|
|
4248
|
+
const visibility = args.repoVisibility ?? (args.autoYes ? "private" : await select11({
|
|
4144
4249
|
message: "Workspace visibility?",
|
|
4145
4250
|
choices: [
|
|
4146
4251
|
{ name: "private (m\u1EB7c \u0111\u1ECBnh, an to\xE0n)", value: "private" },
|
|
@@ -4159,7 +4264,7 @@ async function maybeCreateWorkspaceRemote(args) {
|
|
|
4159
4264
|
} catch (err) {
|
|
4160
4265
|
if (err instanceof CreateWorkspaceRemoteError && err.reason === "repo-exists") {
|
|
4161
4266
|
const fullName = err.fullName;
|
|
4162
|
-
const reuseAction = await
|
|
4267
|
+
const reuseAction = await select11({
|
|
4163
4268
|
message: `Repo '${fullName}' \u0111\xE3 t\u1ED3n t\u1EA1i tr\xEAn GitHub. C\xE1ch x\u1EED l\xFD?`,
|
|
4164
4269
|
choices: [
|
|
4165
4270
|
{
|
|
@@ -4257,9 +4362,24 @@ async function runInitFromExistingFolder(opts, ownerEmail) {
|
|
|
4257
4362
|
presetStrategy: parseBootstrapStrategyOpts(opts),
|
|
4258
4363
|
autoYes: opts.yes
|
|
4259
4364
|
});
|
|
4260
|
-
|
|
4365
|
+
let remoteUrl = await getOrCreateOriginRemote(folderPath, opts);
|
|
4366
|
+
if (remoteUrl) {
|
|
4367
|
+
const verify = tryVerifyGitRemoteAccessible(remoteUrl);
|
|
4368
|
+
if (!verify.ok) {
|
|
4369
|
+
log.warn(`Remote ${remoteUrl} kh\xF4ng accessible (${verify.reason ?? "unknown"}).`);
|
|
4370
|
+
const recovered = await handleRemoteAccessFailureWithAccountSwitch({
|
|
4371
|
+
url: remoteUrl,
|
|
4372
|
+
initialReason: verify.reason ?? "unknown",
|
|
4373
|
+
initialDetail: verify.detail,
|
|
4374
|
+
folderPath,
|
|
4375
|
+
// enable option "Reset folder & tạo repo mới"
|
|
4376
|
+
defaultVisibility: opts.repoVisibility
|
|
4377
|
+
});
|
|
4378
|
+
remoteUrl = recovered.resolvedUrl;
|
|
4379
|
+
}
|
|
4380
|
+
}
|
|
4261
4381
|
const teamOwner = opts.teamOwner ?? await promptTeamOwner(ownerEmail);
|
|
4262
|
-
const inferredName = opts.workspaceName ?? `${
|
|
4382
|
+
const inferredName = opts.workspaceName ?? `${basename3(folderPath)}-avatar-workspace`;
|
|
4263
4383
|
const workspaceName = opts.workspaceName ?? await input7({ message: "T\xEAn workspace:", default: inferredName });
|
|
4264
4384
|
const workspaceParent = resolve2(opts.workspaceParent ?? ".");
|
|
4265
4385
|
const workspacePath = await resolveWorkspacePath(workspaceParent, workspaceName, opts.force);
|
|
@@ -4290,7 +4410,7 @@ async function runInitFromScratch(opts, ownerEmail) {
|
|
|
4290
4410
|
message: "T\xEAn d\u1EF1 \xE1n:",
|
|
4291
4411
|
validate: (v) => v.length > 0 ? true : "T\xEAn b\u1EAFt bu\u1ED9c"
|
|
4292
4412
|
});
|
|
4293
|
-
const visibility = opts.repoVisibility ?? await
|
|
4413
|
+
const visibility = opts.repoVisibility ?? await select12({
|
|
4294
4414
|
message: "Visibility?",
|
|
4295
4415
|
choices: [
|
|
4296
4416
|
{ name: "private (m\u1EB7c \u0111\u1ECBnh)", value: "private" },
|
|
@@ -4300,7 +4420,7 @@ async function runInitFromScratch(opts, ownerEmail) {
|
|
|
4300
4420
|
const teamOwner = opts.teamOwner ?? await promptTeamOwner(ownerEmail);
|
|
4301
4421
|
const workspaceParent = resolve2(opts.workspaceParent ?? ".");
|
|
4302
4422
|
const workspacePath = await resolveWorkspacePath(workspaceParent, projectName, opts.force);
|
|
4303
|
-
const srcPath =
|
|
4423
|
+
const srcPath = join27(workspacePath, "src");
|
|
4304
4424
|
await ensureDir(workspacePath);
|
|
4305
4425
|
await ensureDir(srcPath);
|
|
4306
4426
|
await safeBootstrapGitInFolder(srcPath, { autoYes: true });
|
|
@@ -4617,7 +4737,7 @@ async function runInit(opts) {
|
|
|
4617
4737
|
}
|
|
4618
4738
|
}
|
|
4619
4739
|
async function promptProjectStatus() {
|
|
4620
|
-
return await
|
|
4740
|
+
return await select13({
|
|
4621
4741
|
message: "T\xECnh tr\u1EA1ng d\u1EF1 \xE1n c\u1EE7a b\u1EA1n?",
|
|
4622
4742
|
choices: [
|
|
4623
4743
|
{ name: "1. \u0110\xE3 c\xF3 repo git remote (URL c\xF3 s\u1EB5n)", value: "existing-remote" },
|
|
@@ -4649,7 +4769,7 @@ function registerMcpRunCommand(program2) {
|
|
|
4649
4769
|
}
|
|
4650
4770
|
|
|
4651
4771
|
// src/commands/pack-status-and-version-check.ts
|
|
4652
|
-
import { join as
|
|
4772
|
+
import { join as join28 } from "path";
|
|
4653
4773
|
import boxen7 from "boxen";
|
|
4654
4774
|
var PACK_RELATIVE_PATH = ".claude/pack";
|
|
4655
4775
|
function registerPackCommand(program2) {
|
|
@@ -4670,7 +4790,7 @@ function registerPackCommand(program2) {
|
|
|
4670
4790
|
});
|
|
4671
4791
|
}
|
|
4672
4792
|
async function gatherPackStatus(cwd, doFetch) {
|
|
4673
|
-
const packDir =
|
|
4793
|
+
const packDir = join28(cwd, PACK_RELATIVE_PATH);
|
|
4674
4794
|
if (!await pathExists(packDir) || !await isGitRepo(packDir)) {
|
|
4675
4795
|
return {
|
|
4676
4796
|
installed: false,
|
|
@@ -4759,18 +4879,18 @@ function registerSecretsCommand(program2) {
|
|
|
4759
4879
|
}
|
|
4760
4880
|
|
|
4761
4881
|
// src/commands/status.ts
|
|
4762
|
-
import { promises as
|
|
4763
|
-
import { join as
|
|
4882
|
+
import { promises as fs14 } from "fs";
|
|
4883
|
+
import { join as join30 } from "path";
|
|
4764
4884
|
import boxen8 from "boxen";
|
|
4765
4885
|
|
|
4766
4886
|
// src/lib/pack-backup-manager.ts
|
|
4767
|
-
import { promises as
|
|
4768
|
-
import { join as
|
|
4887
|
+
import { promises as fs13 } from "fs";
|
|
4888
|
+
import { join as join29 } from "path";
|
|
4769
4889
|
var BACKUP_DIR_NAME = "_backup";
|
|
4770
4890
|
async function listBackups(projectRoot) {
|
|
4771
|
-
const dir =
|
|
4891
|
+
const dir = join29(projectRoot, ".claude", BACKUP_DIR_NAME);
|
|
4772
4892
|
if (!await pathExists(dir)) return [];
|
|
4773
|
-
const entries = await
|
|
4893
|
+
const entries = await fs13.readdir(dir, { withFileTypes: true });
|
|
4774
4894
|
return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort().reverse();
|
|
4775
4895
|
}
|
|
4776
4896
|
|
|
@@ -4793,7 +4913,7 @@ function registerStatusCommand(program2) {
|
|
|
4793
4913
|
}
|
|
4794
4914
|
async function gatherStatus(cwd) {
|
|
4795
4915
|
const projectName = cwd.split("/").filter(Boolean).pop() ?? "unknown";
|
|
4796
|
-
const claudeRoot =
|
|
4916
|
+
const claudeRoot = join30(cwd, ".claude");
|
|
4797
4917
|
const hasAvatar = await pathExists(claudeRoot);
|
|
4798
4918
|
if (!hasAvatar) {
|
|
4799
4919
|
return {
|
|
@@ -4806,9 +4926,9 @@ async function gatherStatus(cwd) {
|
|
|
4806
4926
|
hasAvatar: false
|
|
4807
4927
|
};
|
|
4808
4928
|
}
|
|
4809
|
-
const packVersion = await isGitRepo(
|
|
4810
|
-
const pendingDir =
|
|
4811
|
-
const pendingCount = await pathExists(pendingDir) ? (await
|
|
4929
|
+
const packVersion = await isGitRepo(join30(claudeRoot, "pack")) ? await readPinnedPackVersion(cwd).catch(() => null) : null;
|
|
4930
|
+
const pendingDir = join30(claudeRoot, "_pending");
|
|
4931
|
+
const pendingCount = await pathExists(pendingDir) ? (await fs14.readdir(pendingDir)).filter((n) => n.endsWith(".diff.md")).length : 0;
|
|
4812
4932
|
const backupCount = (await listBackups(cwd)).length;
|
|
4813
4933
|
const techStackSummary = await readTechStackFirstLine(claudeRoot);
|
|
4814
4934
|
return {
|
|
@@ -4822,7 +4942,7 @@ async function gatherStatus(cwd) {
|
|
|
4822
4942
|
};
|
|
4823
4943
|
}
|
|
4824
4944
|
async function readTechStackFirstLine(claudeRoot) {
|
|
4825
|
-
const techStackPath =
|
|
4945
|
+
const techStackPath = join30(claudeRoot, "project", "tech-stack.md");
|
|
4826
4946
|
if (!await pathExists(techStackPath)) return "(no tech-stack.md)";
|
|
4827
4947
|
const content = await readText(techStackPath);
|
|
4828
4948
|
const firstNonHeaderLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#") && !l.startsWith(">"));
|
|
@@ -4843,17 +4963,17 @@ function renderStatusBox(s) {
|
|
|
4843
4963
|
}
|
|
4844
4964
|
|
|
4845
4965
|
// src/commands/sync.ts
|
|
4846
|
-
import { join as
|
|
4966
|
+
import { join as join32 } from "path";
|
|
4847
4967
|
|
|
4848
4968
|
// src/lib/preview-team-pack-sync-changes-for-dry-run.ts
|
|
4849
|
-
import { join as
|
|
4969
|
+
import { join as join31 } from "path";
|
|
4850
4970
|
async function inspectMountDir(packDir, claudeDir, dir) {
|
|
4851
|
-
const source =
|
|
4852
|
-
const dest =
|
|
4971
|
+
const source = join31(packDir, dir);
|
|
4972
|
+
const dest = join31(claudeDir, dir);
|
|
4853
4973
|
if (!await pathExists(source)) return "source-missing";
|
|
4854
4974
|
if (!await pathExists(dest)) return "needs-creation";
|
|
4855
|
-
const { promises:
|
|
4856
|
-
const st = await
|
|
4975
|
+
const { promises: fs15 } = await import("fs");
|
|
4976
|
+
const st = await fs15.lstat(dest);
|
|
4857
4977
|
if (st.isSymbolicLink()) return "already-linked";
|
|
4858
4978
|
return "conflict-real-dir";
|
|
4859
4979
|
}
|
|
@@ -4891,8 +5011,8 @@ async function buildSyncPreview(packDir, claudeDir, targetVersion) {
|
|
|
4891
5011
|
var DEFAULT_PACK_BRANCH2 = "main";
|
|
4892
5012
|
async function syncAction(opts) {
|
|
4893
5013
|
const projectRoot = process.cwd();
|
|
4894
|
-
const claudeDir =
|
|
4895
|
-
const packDir =
|
|
5014
|
+
const claudeDir = join32(projectRoot, ".claude");
|
|
5015
|
+
const packDir = join32(projectRoot, TEAM_PACK_RELATIVE_PATH);
|
|
4896
5016
|
if (!await pathExists(packDir)) {
|
|
4897
5017
|
log.error(
|
|
4898
5018
|
`team-ai-pack submodule ch\u01B0a \u0111\u01B0\u1EE3c kh\u1EDFi t\u1EA1o \u1EDF ${TEAM_PACK_RELATIVE_PATH}/.
|
|
@@ -5027,33 +5147,33 @@ function registerToolsCommand(program2) {
|
|
|
5027
5147
|
|
|
5028
5148
|
// src/commands/uninstall.ts
|
|
5029
5149
|
import { relative as relative4 } from "path";
|
|
5030
|
-
import { confirm as
|
|
5150
|
+
import { confirm as confirm7 } from "@inquirer/prompts";
|
|
5031
5151
|
import boxen9 from "boxen";
|
|
5032
5152
|
|
|
5033
5153
|
// src/lib/create-uninstall-backup-snapshot.ts
|
|
5034
5154
|
import { cp, mkdir, writeFile } from "fs/promises";
|
|
5035
5155
|
import { homedir as homedir4 } from "os";
|
|
5036
|
-
import { basename as
|
|
5037
|
-
var UNINSTALL_BACKUPS_DIR =
|
|
5156
|
+
import { basename as basename4, join as join33 } from "path";
|
|
5157
|
+
var UNINSTALL_BACKUPS_DIR = join33(homedir4(), ".avatar", "uninstall-backups");
|
|
5038
5158
|
async function createUninstallBackupSnapshot(projectRoot, artifacts, avatarVersion) {
|
|
5039
|
-
const projectName =
|
|
5159
|
+
const projectName = basename4(projectRoot);
|
|
5040
5160
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
5041
|
-
const backupDir =
|
|
5161
|
+
const backupDir = join33(UNINSTALL_BACKUPS_DIR, `${projectName}-${timestamp2}`);
|
|
5042
5162
|
await mkdir(backupDir, { recursive: true, mode: 448 });
|
|
5043
5163
|
if (artifacts.claudeDir) {
|
|
5044
|
-
await cp(artifacts.claudeDir,
|
|
5164
|
+
await cp(artifacts.claudeDir, join33(backupDir, ".claude"), { recursive: true });
|
|
5045
5165
|
}
|
|
5046
5166
|
if (artifacts.claudeMd) {
|
|
5047
|
-
await cp(artifacts.claudeMd,
|
|
5167
|
+
await cp(artifacts.claudeMd, join33(backupDir, "CLAUDE.md"));
|
|
5048
5168
|
}
|
|
5049
5169
|
if (artifacts.postMergeHook || artifacts.prePushHook) {
|
|
5050
|
-
const hooksBackupDir =
|
|
5170
|
+
const hooksBackupDir = join33(backupDir, "hooks");
|
|
5051
5171
|
await mkdir(hooksBackupDir, { recursive: true });
|
|
5052
5172
|
if (artifacts.postMergeHook) {
|
|
5053
|
-
await cp(artifacts.postMergeHook,
|
|
5173
|
+
await cp(artifacts.postMergeHook, join33(hooksBackupDir, "post-merge"));
|
|
5054
5174
|
}
|
|
5055
5175
|
if (artifacts.prePushHook) {
|
|
5056
|
-
await cp(artifacts.prePushHook,
|
|
5176
|
+
await cp(artifacts.prePushHook, join33(hooksBackupDir, "pre-push"));
|
|
5057
5177
|
}
|
|
5058
5178
|
}
|
|
5059
5179
|
const manifest = {
|
|
@@ -5068,27 +5188,27 @@ async function createUninstallBackupSnapshot(projectRoot, artifacts, avatarVersi
|
|
|
5068
5188
|
prePushHook: !!artifacts.prePushHook
|
|
5069
5189
|
}
|
|
5070
5190
|
};
|
|
5071
|
-
await writeFile(
|
|
5191
|
+
await writeFile(join33(backupDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
|
|
5072
5192
|
return backupDir;
|
|
5073
5193
|
}
|
|
5074
5194
|
|
|
5075
5195
|
// src/lib/detect-avatar-project-artifacts.ts
|
|
5076
5196
|
import { existsSync as existsSync9 } from "fs";
|
|
5077
|
-
import { join as
|
|
5197
|
+
import { join as join34 } from "path";
|
|
5078
5198
|
function existsOrNull(path) {
|
|
5079
5199
|
return existsSync9(path) ? path : null;
|
|
5080
5200
|
}
|
|
5081
5201
|
function detectAvatarProjectArtifacts(projectRoot) {
|
|
5082
|
-
const claudeDir = existsOrNull(
|
|
5083
|
-
const claudeMd = existsOrNull(
|
|
5084
|
-
const postMergeHook = existsOrNull(
|
|
5202
|
+
const claudeDir = existsOrNull(join34(projectRoot, ".claude"));
|
|
5203
|
+
const claudeMd = existsOrNull(join34(projectRoot, "CLAUDE.md"));
|
|
5204
|
+
const postMergeHook = existsOrNull(join34(projectRoot, ".git", "hooks", "post-merge"));
|
|
5085
5205
|
const prePushHook = existsOrNull(
|
|
5086
|
-
|
|
5206
|
+
join34(projectRoot, ".git", "modules", "src", "hooks", "pre-push")
|
|
5087
5207
|
);
|
|
5088
|
-
const gitignorePath = existsOrNull(
|
|
5089
|
-
const gitmodulesPath = existsOrNull(
|
|
5090
|
-
const notesDir = existsOrNull(
|
|
5091
|
-
const scriptsDir = existsOrNull(
|
|
5208
|
+
const gitignorePath = existsOrNull(join34(projectRoot, ".gitignore"));
|
|
5209
|
+
const gitmodulesPath = existsOrNull(join34(projectRoot, ".gitmodules"));
|
|
5210
|
+
const notesDir = existsOrNull(join34(projectRoot, "notes"));
|
|
5211
|
+
const scriptsDir = existsOrNull(join34(projectRoot, "scripts"));
|
|
5092
5212
|
const hasAnyArtifact = !!(claudeDir || claudeMd || postMergeHook || prePushHook);
|
|
5093
5213
|
return {
|
|
5094
5214
|
hasAnyArtifact,
|
|
@@ -5109,11 +5229,11 @@ async function executeUninstallDeletion(artifacts, flags) {
|
|
|
5109
5229
|
if (artifacts.claudeDir) {
|
|
5110
5230
|
if (flags.keepSubmodule) {
|
|
5111
5231
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
5112
|
-
const { join:
|
|
5232
|
+
const { join: join35 } = await import("path");
|
|
5113
5233
|
const entries = await readdir2(artifacts.claudeDir);
|
|
5114
5234
|
for (const entry of entries) {
|
|
5115
5235
|
if (entry === "pack") continue;
|
|
5116
|
-
await rm(
|
|
5236
|
+
await rm(join35(artifacts.claudeDir, entry), { recursive: true, force: true });
|
|
5117
5237
|
}
|
|
5118
5238
|
} else {
|
|
5119
5239
|
await rm(artifacts.claudeDir, { recursive: true, force: true });
|
|
@@ -5206,7 +5326,7 @@ async function runUninstall(opts) {
|
|
|
5206
5326
|
return;
|
|
5207
5327
|
}
|
|
5208
5328
|
if (!opts.yes) {
|
|
5209
|
-
const ok = await
|
|
5329
|
+
const ok = await confirm7({
|
|
5210
5330
|
message: "Ti\u1EBFp t\u1EE5c g\u1EE1 Avatar?",
|
|
5211
5331
|
default: false
|
|
5212
5332
|
});
|