ai-project-manage-cli 6.0.25 → 6.0.26

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.
Files changed (2) hide show
  1. package/dist/index.js +199 -7
  2. package/package.json +6 -3
package/dist/index.js CHANGED
@@ -540,13 +540,13 @@ async function downloadAttachment(cfg, sessionId, attachmentId) {
540
540
  return Buffer.from(await res.arrayBuffer());
541
541
  }
542
542
  function loadManifest(dir) {
543
- const path8 = join4(dir, MANIFEST_FILE);
544
- if (!existsSync2(path8)) {
543
+ const path10 = join4(dir, MANIFEST_FILE);
544
+ if (!existsSync2(path10)) {
545
545
  return { version: 1, attachments: {} };
546
546
  }
547
547
  try {
548
548
  const parsed = JSON.parse(
549
- readFileSync3(path8, "utf8")
549
+ readFileSync3(path10, "utf8")
550
550
  );
551
551
  if (parsed?.version === 1 && parsed.attachments && typeof parsed.attachments === "object") {
552
552
  return parsed;
@@ -772,8 +772,8 @@ function sanitizeSkillDirName(name) {
772
772
  function listBaseSkillDirNames() {
773
773
  if (!existsSync3(BASE_SKILLS_TEMPLATE_DIR)) return [];
774
774
  return readdirSync2(BASE_SKILLS_TEMPLATE_DIR).filter((name) => {
775
- const path8 = join7(BASE_SKILLS_TEMPLATE_DIR, name);
776
- return statSync2(path8).isDirectory();
775
+ const path10 = join7(BASE_SKILLS_TEMPLATE_DIR, name);
776
+ return statSync2(path10).isDirectory();
777
777
  });
778
778
  }
779
779
  function syncAgentsGuide(apmDir) {
@@ -785,8 +785,8 @@ function syncAgentsGuide(apmDir) {
785
785
  function listBaseRuleFileNames() {
786
786
  if (!existsSync3(BASE_RULES_TEMPLATE_DIR)) return [];
787
787
  return readdirSync2(BASE_RULES_TEMPLATE_DIR).filter((name) => {
788
- const path8 = join7(BASE_RULES_TEMPLATE_DIR, name);
789
- return statSync2(path8).isFile();
788
+ const path10 = join7(BASE_RULES_TEMPLATE_DIR, name);
789
+ return statSync2(path10).isFile();
790
790
  });
791
791
  }
792
792
  function syncBaseRules(rulesDir) {
@@ -1593,6 +1593,31 @@ function resolveFrontendDeployFromApmConfig(cfg) {
1593
1593
  bucket: reqFe(f.bucket, "bucket").trim()
1594
1594
  };
1595
1595
  }
1596
+ function reqWd(v, field) {
1597
+ if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
1598
+ console.error(`apm.config.json \u4E2D wisdomDeploy.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
1599
+ process.exit(1);
1600
+ }
1601
+ return v;
1602
+ }
1603
+ function reqWisdomPositiveInt(v, field) {
1604
+ const n = Number(v);
1605
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
1606
+ console.error(`apm.config.json \u4E2D wisdomDeploy.${field} \u987B\u4E3A\u6B63\u6574\u6570`);
1607
+ process.exit(1);
1608
+ }
1609
+ return n;
1610
+ }
1611
+ function resolveWisdomDeployFromApmConfig(cfg) {
1612
+ const w = cfg.wisdomDeploy ?? {};
1613
+ return {
1614
+ host: reqWd(w.host, "host").trim(),
1615
+ port: reqWisdomPositiveInt(w.port, "port"),
1616
+ username: reqWd(w.username, "username").trim(),
1617
+ password: reqWd(w.password, "password").trim(),
1618
+ remotePath: reqWd(w.remotePath, "remotePath").trim()
1619
+ };
1620
+ }
1596
1621
 
1597
1622
  // src/commands/deploy/internal/backend-deploy/backend-deploy-workflow.ts
1598
1623
  import path4 from "node:path";
@@ -2444,10 +2469,177 @@ function registerDeployFrontendCommands(program) {
2444
2469
  );
2445
2470
  }
2446
2471
 
2472
+ // src/commands/deploy/sftp.ts
2473
+ import path9 from "node:path";
2474
+
2475
+ // src/commands/deploy/internal/wisdom-sftp.ts
2476
+ import { readdir as readdir3, readFile as readFile2, unlink, writeFile } from "node:fs/promises";
2477
+ import path8 from "node:path";
2478
+ import JSZip from "jszip";
2479
+ import SftpClient from "ssh2-sftp-client";
2480
+ async function addDirToZip(dir, zipFolder) {
2481
+ const entries = await readdir3(dir, { withFileTypes: true });
2482
+ for (const entry of entries) {
2483
+ const fullPath = path8.join(dir, entry.name);
2484
+ if (entry.isDirectory()) {
2485
+ const folder = zipFolder.folder(entry.name);
2486
+ if (folder) {
2487
+ await addDirToZip(fullPath, folder);
2488
+ }
2489
+ } else {
2490
+ const content = await readFile2(fullPath);
2491
+ zipFolder.file(entry.name, content);
2492
+ }
2493
+ }
2494
+ }
2495
+ async function zipDirectory(distDir, zipPath) {
2496
+ console.error(`\u538B\u7F29\u76EE\u5F55: ${distDir}`);
2497
+ const zip = new JSZip();
2498
+ await addDirToZip(distDir, zip);
2499
+ const content = await zip.generateAsync({
2500
+ type: "nodebuffer",
2501
+ compression: "DEFLATE",
2502
+ compressionOptions: { level: 6 }
2503
+ });
2504
+ await writeFile(zipPath, content);
2505
+ const sizeMb = (content.length / 1024 / 1024).toFixed(2);
2506
+ console.error(`\u5DF2\u751F\u6210: ${zipPath} (${sizeMb} MB)`);
2507
+ return content.length;
2508
+ }
2509
+ async function ensureRemoteDir(sftp, dir) {
2510
+ const parts = dir.replace(/\\/g, "/").split("/").filter(Boolean);
2511
+ let current = dir.startsWith("/") ? "" : ".";
2512
+ for (const part of parts) {
2513
+ current = current ? `${current}/${part}` : `/${part}`;
2514
+ try {
2515
+ await sftp.mkdir(current, true);
2516
+ } catch {
2517
+ }
2518
+ }
2519
+ }
2520
+ function execCommand(client, command) {
2521
+ return new Promise((resolve5, reject) => {
2522
+ client.exec(command, (err, stream) => {
2523
+ if (err) return reject(err);
2524
+ let stdout = "";
2525
+ let stderr = "";
2526
+ stream.on("close", (code) => {
2527
+ if (code !== 0) {
2528
+ reject(new Error(`\u8FDC\u7A0B\u547D\u4EE4\u5931\u8D25 (${code}): ${stderr || stdout}`));
2529
+ return;
2530
+ }
2531
+ resolve5(stdout);
2532
+ }).on("data", (data) => {
2533
+ stdout += data.toString();
2534
+ });
2535
+ stream.stderr.on("data", (data) => {
2536
+ stderr += data.toString();
2537
+ });
2538
+ });
2539
+ });
2540
+ }
2541
+ async function uploadAndMaybeExtract(settings, localZip, extract) {
2542
+ const remoteZipPath = `${settings.remotePath.replace(/\/$/, "")}/dist.zip`;
2543
+ console.error(
2544
+ `\u8FDE\u63A5 ${settings.username}@${settings.host}:${settings.port} ...`
2545
+ );
2546
+ const sftp = new SftpClient();
2547
+ try {
2548
+ await sftp.connect({
2549
+ host: settings.host,
2550
+ port: settings.port,
2551
+ username: settings.username,
2552
+ password: settings.password,
2553
+ readyTimeout: 2e4,
2554
+ tryKeyboard: true
2555
+ });
2556
+ await ensureRemoteDir(sftp, settings.remotePath);
2557
+ await sftp.put(localZip, remoteZipPath);
2558
+ console.error(` \u2713 ${localZip} -> ${remoteZipPath}`);
2559
+ if (extract) {
2560
+ const target = settings.remotePath.replace(/\/$/, "");
2561
+ const unzipCmd = `unzip -o "${remoteZipPath}" -d "${target}"`;
2562
+ console.error(`\u8FDC\u7A0B\u89E3\u538B: ${unzipCmd}`);
2563
+ await execCommand(
2564
+ sftp.client,
2565
+ unzipCmd
2566
+ );
2567
+ console.error("\u8FDC\u7A0B\u89E3\u538B\u5B8C\u6210!");
2568
+ }
2569
+ console.error(extract ? "\u90E8\u7F72\u5B8C\u6210!" : "\u4E0A\u4F20\u5B8C\u6210!");
2570
+ } finally {
2571
+ await sftp.end();
2572
+ }
2573
+ }
2574
+ async function runWisdomSftpDeploy(params) {
2575
+ const zipPath = path8.join(params.localDir, "..", ".deploy-sftp-dist.zip");
2576
+ const resolvedZipPath = path8.resolve(zipPath);
2577
+ let zipSizeBytes = 0;
2578
+ try {
2579
+ zipSizeBytes = await zipDirectory(params.localDir, resolvedZipPath);
2580
+ await uploadAndMaybeExtract(
2581
+ params.settings,
2582
+ resolvedZipPath,
2583
+ params.extract
2584
+ );
2585
+ } finally {
2586
+ try {
2587
+ await unlink(resolvedZipPath);
2588
+ console.error(`\u5DF2\u6E05\u7406\u672C\u5730\u4E34\u65F6\u6587\u4EF6: ${resolvedZipPath}`);
2589
+ } catch {
2590
+ }
2591
+ }
2592
+ return {
2593
+ ok: true,
2594
+ localDir: params.localDir,
2595
+ host: params.settings.host,
2596
+ remotePath: params.settings.remotePath,
2597
+ zipSizeBytes,
2598
+ extracted: params.extract
2599
+ };
2600
+ }
2601
+
2602
+ // src/commands/deploy/sftp.ts
2603
+ function registerDeploySftpCommands(program) {
2604
+ program.command("deploy-sftp").description(
2605
+ "\u5C06\u6307\u5B9A\u76EE\u5F55\u538B\u7F29\u4E3A zip \u540E\u901A\u8FC7 SFTP \u4E0A\u4F20\u5230\u8FDC\u7A0B\u670D\u52A1\u5668\uFF08\u8FDE\u63A5\u4FE1\u606F\u89C1 apm.config.json wisdomDeploy\uFF09"
2606
+ ).option(
2607
+ "--dir <path>",
2608
+ "\u672C\u5730\u4EA7\u7269\u76EE\u5F55\uFF1B\u5355\u4ED3\u9ED8\u8BA4 apps/web/dist\uFF08\u9700\u5148 rush build / vite build\uFF09",
2609
+ "apps/web/dist"
2610
+ ).option(
2611
+ "--config <path>",
2612
+ "apm.config.json \u8DEF\u5F84\uFF08\u9ED8\u8BA4 .apm/apm.config.json\uFF09"
2613
+ ).option("--extract", "\u4E0A\u4F20\u540E\u5728\u8FDC\u7A0B\u670D\u52A1\u5668\u89E3\u538B zip \u5230 remotePath").action(
2614
+ async (opts) => {
2615
+ const cfg = loadApmConfig({ configPath: opts.config });
2616
+ const settings = resolveWisdomDeployFromApmConfig(cfg);
2617
+ const root = path9.resolve(process.cwd(), opts.dir || "apps/web/dist");
2618
+ if (!await isDirectoryPath(root)) {
2619
+ console.error(`\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${root}`);
2620
+ process.exit(1);
2621
+ }
2622
+ try {
2623
+ const result = await runWisdomSftpDeploy({
2624
+ localDir: root,
2625
+ settings,
2626
+ extract: Boolean(opts.extract)
2627
+ });
2628
+ console.log(JSON.stringify(result, null, 2));
2629
+ } catch (err) {
2630
+ const message = err instanceof Error ? err.message : String(err);
2631
+ console.error(`${opts.extract ? "\u90E8\u7F72" : "\u4E0A\u4F20"}\u5931\u8D25:`, message);
2632
+ process.exit(1);
2633
+ }
2634
+ }
2635
+ );
2636
+ }
2637
+
2447
2638
  // src/commands/deploy/index.ts
2448
2639
  function registerDeployCommands(program) {
2449
2640
  registerDeployBackendCommands(program);
2450
2641
  registerDeployFrontendCommands(program);
2642
+ registerDeploySftpCommands(program);
2451
2643
  }
2452
2644
 
2453
2645
  // src/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "6.0.25",
3
+ "version": "6.0.26",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,
@@ -23,7 +23,8 @@
23
23
  "typescript": "~5.6.0",
24
24
  "esbuild": "~0.28.0",
25
25
  "vitest": "~4.1.5",
26
- "@types/dockerode": "~4.0.1"
26
+ "@types/dockerode": "~4.0.1",
27
+ "@types/ssh2-sftp-client": "~9.0.6"
27
28
  },
28
29
  "engines": {
29
30
  "node": ">=18"
@@ -35,6 +36,8 @@
35
36
  "commander": "~14.0.3",
36
37
  "yaml": "~2.8.4",
37
38
  "minio": "~8.0.7",
38
- "dockerode": "~5.0.0"
39
+ "dockerode": "~5.0.0",
40
+ "ssh2-sftp-client": "~12.0.1",
41
+ "jszip": "~3.10.1"
39
42
  }
40
43
  }