ai-project-manage-cli 6.0.102 → 6.0.103

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 +1896 -1800
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2389,7 +2389,7 @@ async function runUpdateMessageStatus(options) {
2389
2389
 
2390
2390
  // src/commands/connect.ts
2391
2391
  import WebSocket from "ws";
2392
- import { setTimeout as delay2 } from "node:timers/promises";
2392
+ import { setTimeout as delay3 } from "node:timers/promises";
2393
2393
 
2394
2394
  // src/ws/protocol.ts
2395
2395
  function nonEmptyString(v) {
@@ -2511,8 +2511,67 @@ function validateAgentWsMessage(value, kind) {
2511
2511
  return validateMessagePush(o);
2512
2512
  }
2513
2513
 
2514
+ // src/commands/deploy/deploy-log-utils.ts
2515
+ import { setTimeout as delay } from "node:timers/promises";
2516
+ var DEPLOY_SPAWN_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
2517
+ var DEPLOY_LOG_SYNC_MAX_CHUNK_BYTES = 4 * 1024 * 1024;
2518
+ var DEPLOY_COMPLETE_LOG_MAX_BYTES = 9 * 1024 * 1024;
2519
+ var DEPLOY_LOG_SYNC_MAX_ATTEMPTS = 3;
2520
+ async function retryDeployLogSync(fn, attempts = DEPLOY_LOG_SYNC_MAX_ATTEMPTS) {
2521
+ let lastError;
2522
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
2523
+ try {
2524
+ return await fn();
2525
+ } catch (error) {
2526
+ lastError = error;
2527
+ if (attempt < attempts) {
2528
+ await delay(1e3 * attempt);
2529
+ }
2530
+ }
2531
+ }
2532
+ throw lastError;
2533
+ }
2534
+ function splitUtf8StringByMaxBytes(value, maxBytes) {
2535
+ if (maxBytes <= 0) {
2536
+ throw new Error("maxBytes must be positive");
2537
+ }
2538
+ const bytes = Buffer.from(value, "utf8");
2539
+ if (bytes.length <= maxBytes) {
2540
+ return [value];
2541
+ }
2542
+ const chunks = [];
2543
+ let offset = 0;
2544
+ while (offset < bytes.length) {
2545
+ let end = Math.min(offset + maxBytes, bytes.length);
2546
+ while (end > offset && (bytes[end] & 192) === 128) {
2547
+ end -= 1;
2548
+ }
2549
+ if (end <= offset) {
2550
+ end = Math.min(offset + maxBytes, bytes.length);
2551
+ }
2552
+ chunks.push(bytes.subarray(offset, end).toString("utf8"));
2553
+ offset = end;
2554
+ }
2555
+ return chunks;
2556
+ }
2557
+ function truncateDeployLogForComplete(log2) {
2558
+ const bytes = Buffer.from(log2, "utf8");
2559
+ if (bytes.length <= DEPLOY_COMPLETE_LOG_MAX_BYTES) {
2560
+ return { log: log2, truncated: false };
2561
+ }
2562
+ const tailBudget = Math.max(DEPLOY_COMPLETE_LOG_MAX_BYTES - 512, 1024);
2563
+ const prefix = `[apm] \u65E5\u5FD7\u8FC7\u957F\uFF08${Math.ceil(bytes.length / 1024)}KB\uFF09\uFF0C\u4EC5\u4FDD\u7559\u672B\u5C3E ${Math.floor(tailBudget / 1024)}KB
2564
+ `;
2565
+ let tail = bytes.subarray(bytes.length - tailBudget).toString("utf8");
2566
+ const brokenPrefix = tail.indexOf("\uFFFD");
2567
+ if (brokenPrefix >= 0) {
2568
+ const newline = tail.indexOf("\n", brokenPrefix);
2569
+ tail = newline >= 0 ? tail.slice(newline + 1) : tail.slice(brokenPrefix + 1);
2570
+ }
2571
+ return { log: prefix + tail, truncated: true };
2572
+ }
2573
+
2514
2574
  // src/commands/deploy/deploy-execute.ts
2515
- import { spawnSync as spawnSync6 } from "node:child_process";
2516
2575
  import path5 from "node:path";
2517
2576
 
2518
2577
  // src/commands/deploy/internal/apm-config.ts
@@ -2859,1969 +2918,2005 @@ var DeployExecutionError = class extends Error {
2859
2918
  }
2860
2919
  };
2861
2920
 
2862
- // src/commands/deploy/internal/wisdom-auto-deploy.ts
2863
- import { existsSync as existsSync15, readFileSync as readFileSync13, statSync as statSync7 } from "node:fs";
2864
- import path4 from "node:path";
2865
- import { spawnSync as spawnSync5 } from "node:child_process";
2921
+ // src/commands/deploy/deploy-shell-run.ts
2922
+ import { spawnSync as spawnSync4 } from "node:child_process";
2866
2923
 
2867
- // src/commands/deploy/internal/wisdom-backend-deploy.ts
2924
+ // src/commands/deploy/internal/deploy-shell-env.ts
2925
+ import { spawnSync as spawnSync3 } from "node:child_process";
2926
+ import { dirname as dirname4 } from "node:path";
2927
+
2928
+ // src/commands/daemon.ts
2929
+ import { spawnSync as spawnSync2 } from "child_process";
2930
+ import { setTimeout as delay2 } from "node:timers/promises";
2931
+ import { existsSync as existsSync13, mkdirSync as mkdirSync6, unlinkSync as unlinkSync2, writeFileSync as writeFileSync11 } from "fs";
2932
+ import { join as join15 } from "path";
2933
+
2934
+ // src/commands/connect-lock.ts
2868
2935
  import {
2869
2936
  existsSync as existsSync12,
2870
2937
  mkdirSync as mkdirSync5,
2871
- readdirSync as readdirSync5,
2872
2938
  readFileSync as readFileSync11,
2873
- statSync as statSync6,
2939
+ unlinkSync,
2874
2940
  writeFileSync as writeFileSync10
2875
- } from "node:fs";
2876
- import { spawnSync as spawnSync2 } from "node:child_process";
2877
- import path3 from "node:path";
2878
- import { Client as Client2 } from "ssh2";
2879
- import JSZip2 from "jszip";
2880
- import SftpClient2 from "ssh2-sftp-client";
2881
-
2882
- // src/commands/deploy/internal/wisdom-sftp.ts
2883
- import { readdir as readdir2, readFile as readFile3, unlink, writeFile } from "node:fs/promises";
2884
- import path2 from "node:path";
2885
- import JSZip from "jszip";
2886
- import SftpClient from "ssh2-sftp-client";
2887
-
2888
- // src/commands/deploy/internal/deploy-artifact-minio.ts
2889
- import { readFile as readFile2 } from "node:fs/promises";
2890
-
2891
- // src/commands/deploy/internal/minio.ts
2892
- import { statSync as statSync5 } from "node:fs";
2893
- import { readdir, readFile } from "node:fs/promises";
2894
- import path from "node:path";
2895
- import * as Minio from "minio";
2896
- var DEFAULT_MAX_FILE_SIZE_MB = 50;
2897
- async function isDirectoryPath(dir) {
2941
+ } from "fs";
2942
+ import { join as join14 } from "path";
2943
+ var CONNECT_LOCK_PATH = join14(APM_CONFIG_DIR, "connect.lock");
2944
+ function isProcessAlive(pid) {
2945
+ if (!Number.isInteger(pid) || pid <= 0) return false;
2898
2946
  try {
2899
- const st = statSync5(dir);
2900
- return st.isDirectory();
2947
+ process.kill(pid, 0);
2948
+ return true;
2901
2949
  } catch {
2902
2950
  return false;
2903
2951
  }
2904
2952
  }
2905
- function sanitizeRelativePath(rel) {
2906
- const norm = rel.replace(/\\/g, "/").replace(/^\/+/, "");
2907
- const segments = norm.split("/").filter(Boolean);
2908
- for (const s of segments) {
2909
- if (s === "." || s === "..") {
2910
- throw new Error(`\u975E\u6CD5\u76F8\u5BF9\u8DEF\u5F84\u7247\u6BB5\uFF1A${s}`);
2911
- }
2953
+ function sleepSync(ms) {
2954
+ const deadline = Date.now() + ms;
2955
+ while (Date.now() < deadline) {
2912
2956
  }
2913
- return segments.join("/");
2914
2957
  }
2915
- async function collectFiles(root) {
2916
- const out = [];
2917
- async function walk(dir, prefix) {
2918
- const entries = await readdir(dir, { withFileTypes: true });
2919
- for (const e of entries) {
2920
- const name = e.name;
2921
- if (name === "." || name === "..") {
2922
- continue;
2923
- }
2924
- const abs = path.join(dir, name);
2925
- const rel = prefix ? `${prefix}/${name}` : name;
2926
- if (e.isDirectory()) {
2927
- await walk(abs, rel);
2928
- } else if (e.isFile()) {
2929
- const st = statSync5(abs);
2930
- out.push({
2931
- absPath: abs,
2932
- relativePath: rel.replace(/\\/g, "/"),
2933
- size: st.size
2934
- });
2935
- }
2958
+ function waitForPm2LockHandoff(timeoutMs = 5e3) {
2959
+ const deadline = Date.now() + timeoutMs;
2960
+ while (Date.now() < deadline) {
2961
+ pruneStaleConnectLock();
2962
+ const lock = readConnectLock();
2963
+ if (!lock || lock.mode !== "pm2" || lock.pid === process.pid || !isProcessAlive(lock.pid)) {
2964
+ return;
2936
2965
  }
2966
+ sleepSync(100);
2937
2967
  }
2938
- await walk(root, "");
2939
- out.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
2940
- return out;
2941
- }
2942
- async function readArtifactFile(absPath) {
2943
- return readFile(absPath);
2944
2968
  }
2945
- function toMB(bytes) {
2946
- return Math.round(bytes / (1024 * 1024) * 1e3) / 1e3;
2947
- }
2948
- var MIME = {
2949
- ".html": "text/html; charset=utf-8",
2950
- ".css": "text/css; charset=utf-8",
2951
- ".js": "application/javascript; charset=utf-8",
2952
- ".json": "application/json; charset=utf-8",
2953
- ".svg": "image/svg+xml",
2954
- ".png": "image/png",
2955
- ".jpg": "image/jpeg",
2956
- ".jpeg": "image/jpeg",
2957
- ".gif": "image/gif",
2958
- ".webp": "image/webp",
2959
- ".woff": "font/woff",
2960
- ".woff2": "font/woff2",
2961
- ".ttf": "font/ttf",
2962
- ".ico": "image/x-icon",
2963
- ".txt": "text/plain; charset=utf-8",
2964
- ".map": "application/json"
2965
- };
2966
- function detectMimeType(filePath) {
2967
- const ext = path.extname(filePath).toLowerCase();
2968
- return MIME[ext] ?? "";
2969
+ function readConnectLock() {
2970
+ if (!existsSync12(CONNECT_LOCK_PATH)) return null;
2971
+ try {
2972
+ const raw = readFileSync11(CONNECT_LOCK_PATH, "utf8");
2973
+ const parsed = JSON.parse(raw);
2974
+ if (typeof parsed.pid !== "number" || parsed.mode !== "foreground" && parsed.mode !== "pm2" || typeof parsed.startedAt !== "string") {
2975
+ return null;
2976
+ }
2977
+ return parsed;
2978
+ } catch {
2979
+ return null;
2980
+ }
2969
2981
  }
2970
- var MinioClient = class {
2971
- inner;
2972
- constructor(opts) {
2973
- const endPoint = opts.endPoint.replace(/^https?:\/\//i, "").split("/")[0] ?? opts.endPoint;
2974
- this.inner = new Minio.Client({
2975
- endPoint,
2976
- port: opts.port,
2977
- useSSL: opts.useSSL,
2978
- accessKey: opts.accessKey,
2979
- secretKey: opts.secretKey
2980
- });
2982
+ function pruneStaleConnectLock() {
2983
+ const lock = readConnectLock();
2984
+ if (!lock) return;
2985
+ if (isProcessAlive(lock.pid)) return;
2986
+ try {
2987
+ unlinkSync(CONNECT_LOCK_PATH);
2988
+ } catch {
2981
2989
  }
2982
- async ensureBucket(bucket) {
2983
- const exists = await this.inner.bucketExists(bucket);
2984
- if (!exists) {
2985
- await this.inner.makeBucket(bucket);
2990
+ }
2991
+ function acquireConnectLock(mode) {
2992
+ pruneStaleConnectLock();
2993
+ const existing = readConnectLock();
2994
+ if (existing && isProcessAlive(existing.pid)) {
2995
+ if (existing.pid === process.pid) {
2996
+ return;
2986
2997
  }
2987
- }
2988
- async deleteObjectsByPrefix(bucket, prefix) {
2989
- const objectsStream = this.inner.listObjectsV2(bucket, prefix, true);
2990
- const keys = [];
2991
- await new Promise((resolve5, reject) => {
2992
- objectsStream.on("data", (obj) => {
2993
- if (obj.name) {
2994
- keys.push(obj.name);
2995
- }
2996
- });
2997
- objectsStream.on("error", reject);
2998
- objectsStream.on("end", resolve5);
2999
- });
3000
- const chunkSize = 500;
3001
- for (let i = 0; i < keys.length; i += chunkSize) {
3002
- const chunk = keys.slice(i, i + chunkSize);
3003
- await this.inner.removeObjects(
3004
- bucket,
3005
- chunk.map((name) => name)
2998
+ if (mode === "pm2" && existing.mode === "pm2") {
2999
+ forceReleaseConnectLock();
3000
+ } else {
3001
+ console.error(
3002
+ `[apm] \u5DF2\u6709 apm connect \u5728\u8FD0\u884C (pid=${existing.pid}, mode=${existing.mode})`
3006
3003
  );
3004
+ process.exit(1);
3007
3005
  }
3008
3006
  }
3009
- async putObject(bucket, objectKey, body, meta) {
3010
- await this.inner.putObject(bucket, objectKey, body, body.length, meta);
3011
- }
3012
- /** 匿名可读当前桶全部对象(便于静态站点直链) */
3013
- async setBucketPublicRead(bucket) {
3014
- const policy = {
3015
- Version: "2012-10-17",
3016
- Statement: [
3017
- {
3018
- Effect: "Allow",
3019
- Principal: { AWS: ["*"] },
3020
- Action: ["s3:GetObject"],
3021
- Resource: [`arn:aws:s3:::${bucket}/*`]
3022
- }
3023
- ]
3024
- };
3025
- await this.inner.setBucketPolicy(bucket, JSON.stringify(policy));
3007
+ mkdirSync5(APM_CONFIG_DIR, { recursive: true });
3008
+ const lock = {
3009
+ pid: process.pid,
3010
+ mode,
3011
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
3012
+ };
3013
+ writeFileSync10(
3014
+ CONNECT_LOCK_PATH,
3015
+ JSON.stringify(lock, null, 2) + "\n",
3016
+ "utf8"
3017
+ );
3018
+ }
3019
+ function releaseConnectLock() {
3020
+ const lock = readConnectLock();
3021
+ if (lock?.pid !== process.pid) return;
3022
+ try {
3023
+ unlinkSync(CONNECT_LOCK_PATH);
3024
+ } catch {
3026
3025
  }
3027
- };
3028
-
3029
- // src/commands/deploy/internal/deploy-artifact-minio.ts
3030
- var APM_DEPLOYMENT_RUN_ID_ENV = "APM_DEPLOYMENT_RUN_ID";
3031
- function formatDeployArtifactTimestamp(date = /* @__PURE__ */ new Date()) {
3032
- const pad = (n) => String(n).padStart(2, "0");
3033
- return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`;
3034
3026
  }
3035
- function sanitizeDeployProjectName(name) {
3036
- const trimmed = name.trim().replace(/\\/g, "/");
3037
- const base = trimmed.split("/").filter(Boolean).pop() ?? trimmed;
3027
+ function forceReleaseConnectLock() {
3038
3028
  try {
3039
- return sanitizeRelativePath(base.replace(/[/\\:*?"<>|]/g, "_"));
3029
+ if (existsSync12(CONNECT_LOCK_PATH)) {
3030
+ unlinkSync(CONNECT_LOCK_PATH);
3031
+ }
3040
3032
  } catch {
3041
- return "project";
3042
3033
  }
3043
3034
  }
3044
- function buildDeployArtifactFileName(kind, projectName, timestamp = formatDeployArtifactTimestamp()) {
3045
- const safeName = sanitizeDeployProjectName(projectName);
3046
- const suffix = kind === "frontend" ? "dist.zip" : "jar.zip";
3047
- return `${timestamp}-${safeName}.${suffix}`;
3048
- }
3049
- function buildDeployArtifactObjectKey(projectName, fileName) {
3050
- const safeProject = sanitizeDeployProjectName(projectName);
3051
- return `deploy/${safeProject}/${fileName}`;
3052
- }
3053
- function resolveDeploymentRunIdFromEnv() {
3054
- const raw = process.env[APM_DEPLOYMENT_RUN_ID_ENV]?.trim();
3055
- return raw || null;
3035
+
3036
+ // src/commands/daemon.ts
3037
+ var PM2_APP_NAME = "apm-connect";
3038
+ var PM2_ECOSYSTEM_PATH = join15(
3039
+ APM_CONFIG_DIR,
3040
+ "connect.ecosystem.config.cjs"
3041
+ );
3042
+ var PM2_CONNECT_ENTRY_PATH = join15(APM_CONFIG_DIR, "connect.entry.cjs");
3043
+ var PM2_CONNECT_LAUNCH_PATH = join15(
3044
+ APM_CONFIG_DIR,
3045
+ "connect.launch.json"
3046
+ );
3047
+ var LEGACY_PM2_ECOSYSTEM_PATH = join15(
3048
+ APM_CONFIG_DIR,
3049
+ "connect.ecosystem.cjs"
3050
+ );
3051
+ var LEGACY_PM2_APP_NAMES = ["connect.ecosystem"];
3052
+ var CONNECT_ENTRY_SCRIPT = `'use strict';
3053
+ const { spawnSync } = require('node:child_process');
3054
+ const { readFileSync } = require('node:fs');
3055
+ const { join } = require('node:path');
3056
+
3057
+ const launchConfigPath = join(__dirname, 'connect.launch.json');
3058
+ let config;
3059
+ try {
3060
+ config = JSON.parse(readFileSync(launchConfigPath, 'utf8'));
3061
+ } catch (err) {
3062
+ console.error('[apm] \u65E0\u6CD5\u8BFB\u53D6 connect.launch.json:', err instanceof Error ? err.message : err);
3063
+ process.exit(1);
3056
3064
  }
3057
- async function fetchDeployArtifactStorage(api) {
3058
- return api.cli.getDeployArtifactStorage(void 0);
3065
+
3066
+ const nodeArgs = [config.apmScript, ...(config.connectArgs || [])];
3067
+ const childEnv = {
3068
+ ...process.env,
3069
+ ...(config.env || {}),
3070
+ APM_CONNECT_UNDER_PM2: '1',
3071
+ };
3072
+
3073
+ const result = spawnSync(config.nodePath, nodeArgs, {
3074
+ cwd: config.cwd || undefined,
3075
+ env: childEnv,
3076
+ stdio: 'inherit',
3077
+ windowsHide: true,
3078
+ });
3079
+
3080
+ if (result.error) {
3081
+ console.error('[apm] connect \u542F\u52A8\u5931\u8D25:', result.error.message);
3082
+ process.exit(1);
3059
3083
  }
3060
- async function uploadDeployArtifactZip(options) {
3061
- const deploymentRunId = options.deploymentRunId ?? resolveDeploymentRunIdFromEnv();
3062
- if (!deploymentRunId && !options.uploadWithoutRunId) {
3063
- return null;
3064
- }
3065
- const cfg = await tryReadApmConfig();
3066
- if (!cfg || !resolveApiKey(cfg)) {
3067
- console.warn("[apm] \u672A\u767B\u5F55\uFF0C\u8DF3\u8FC7 MinIO \u90E8\u7F72\u4EA7\u7269\u5F52\u6863");
3068
- return null;
3069
- }
3070
- const api = options.api ?? createApmApiClient(cfg);
3071
- let storage;
3072
- try {
3073
- storage = await fetchDeployArtifactStorage(api);
3074
- } catch (error) {
3075
- const detail = error instanceof Error ? error.message : String(error);
3076
- console.warn(`[apm] \u83B7\u53D6 MinIO \u914D\u7F6E\u5931\u8D25\uFF0C\u8DF3\u8FC7\u90E8\u7F72\u4EA7\u7269\u5F52\u6863: ${detail}`);
3077
- return null;
3084
+ process.exit(result.status ?? 0);
3085
+ `;
3086
+ function resolveConnectArgs(server) {
3087
+ const args = ["connect"];
3088
+ const trimmed = server?.trim();
3089
+ if (trimmed) {
3090
+ args.push("--server", trimmed.replace(/\/+$/, ""));
3078
3091
  }
3079
- const fileName = buildDeployArtifactFileName(
3080
- options.kind,
3081
- options.projectName,
3082
- options.timestamp
3083
- );
3084
- const objectKey = buildDeployArtifactObjectKey(options.projectName, fileName);
3085
- let body;
3086
- try {
3087
- body = await readFile2(options.zipPath);
3088
- } catch (error) {
3089
- const detail = error instanceof Error ? error.message : String(error);
3090
- console.warn(`[apm] \u8BFB\u53D6\u90E8\u7F72\u4EA7\u7269 zip \u5931\u8D25\uFF0C\u8DF3\u8FC7 MinIO \u5F52\u6863: ${detail}`);
3091
- return null;
3092
+ return args;
3093
+ }
3094
+ var PM2_LAUNCH_ENV_KEYS = [
3095
+ "PATH",
3096
+ "HOME",
3097
+ "USER",
3098
+ "SHELL",
3099
+ "LANG",
3100
+ "LC_ALL",
3101
+ "LC_CTYPE",
3102
+ "NVM_DIR",
3103
+ "NVM_BIN",
3104
+ "NVM_INC",
3105
+ "FNM_DIR",
3106
+ "VOLTA_HOME",
3107
+ "PNPM_HOME",
3108
+ "npm_config_prefix",
3109
+ "NODE_HOME"
3110
+ ];
3111
+ function collectPm2LaunchEnv() {
3112
+ const env = {};
3113
+ for (const key of PM2_LAUNCH_ENV_KEYS) {
3114
+ const value = process.env[key]?.trim();
3115
+ if (value) {
3116
+ env[key] = value;
3117
+ }
3092
3118
  }
3093
- try {
3094
- const client = new MinioClient({
3095
- endPoint: storage.endpoint,
3096
- port: storage.port,
3097
- useSSL: storage.useSsl,
3098
- accessKey: storage.accessKey,
3099
- secretKey: storage.secretKey
3100
- });
3101
- await client.ensureBucket(storage.bucket);
3102
- await client.putObject(storage.bucket, objectKey, body, {
3103
- "Content-Type": "application/zip"
3104
- });
3105
- console.error(
3106
- `[apm] \u672C\u5730\u76F4\u4F20 MinIO\uFF08\u4E0D\u7ECF\u5E73\u53F0\u670D\u52A1\u5668\uFF09: ${storage.endpoint}:${storage.port}/${storage.bucket}/${objectKey} (${(body.length / 1024 / 1024).toFixed(
3107
- 2
3108
- )} MB)`
3109
- );
3110
- } catch (error) {
3111
- const detail = error instanceof Error ? error.message : String(error);
3112
- console.warn(`[apm] MinIO \u90E8\u7F72\u4EA7\u7269\u5F52\u6863\u5931\u8D25: ${detail}`);
3113
- return null;
3119
+ return env;
3120
+ }
3121
+ function buildConnectPm2Ecosystem(options) {
3122
+ const env = {};
3123
+ const baseUrl = options.baseUrl?.trim().replace(/\/+$/, "");
3124
+ if (baseUrl) {
3125
+ env.AI_PM_SERVER = baseUrl;
3114
3126
  }
3115
- if (deploymentRunId) {
3116
- try {
3117
- await api.cli.attachTaskDeploymentArtifact({
3118
- id: deploymentRunId,
3119
- artifactObjectKey: objectKey,
3120
- artifactFileName: fileName
3121
- });
3122
- console.error(
3123
- `[apm] \u5DF2\u7ED1\u5B9A\u90E8\u7F72\u4EA7\u7269\u5230\u8BB0\u5F55 id=${deploymentRunId} file=${fileName}`
3124
- );
3125
- } catch (error) {
3126
- const detail = error instanceof Error ? error.message : String(error);
3127
- console.warn(`[apm] \u7ED1\u5B9A\u90E8\u7F72\u4EA7\u7269\u5230\u8BB0\u5F55\u5931\u8D25: ${detail}`);
3128
- return null;
3129
- }
3127
+ const app = {
3128
+ name: PM2_APP_NAME,
3129
+ script: options.entryScript,
3130
+ cwd: APM_CONFIG_DIR,
3131
+ autorestart: true,
3132
+ min_uptime: "10s",
3133
+ max_restarts: 10,
3134
+ restart_delay: 3e3,
3135
+ exp_backoff_restart_delay: 1e3,
3136
+ max_memory_restart: "2G",
3137
+ kill_timeout: 5e3,
3138
+ shutdown_with_message: true,
3139
+ instances: 1,
3140
+ exec_mode: "fork",
3141
+ env
3142
+ };
3143
+ if (process.platform === "win32") {
3144
+ app.windowsHide = true;
3130
3145
  }
3131
3146
  return {
3132
- objectKey,
3133
- fileName,
3134
- bucket: storage.bucket
3147
+ apps: [app]
3135
3148
  };
3136
3149
  }
3137
-
3138
- // src/commands/deploy/internal/wisdom-sftp.ts
3139
- async function addDirToZip(dir, zipFolder) {
3140
- const entries = await readdir2(dir, { withFileTypes: true });
3141
- for (const entry of entries) {
3142
- const fullPath = path2.join(dir, entry.name);
3143
- if (entry.isDirectory()) {
3144
- const folder = zipFolder.folder(entry.name);
3145
- if (folder) {
3146
- await addDirToZip(fullPath, folder);
3147
- }
3148
- } else {
3149
- const content = await readFile3(fullPath);
3150
- zipFolder.file(entry.name, content);
3151
- }
3152
- }
3150
+ function formatConnectPm2EcosystemFile(ecosystem) {
3151
+ return `module.exports = ${JSON.stringify(ecosystem, null, 2)};
3152
+ `;
3153
3153
  }
3154
- async function zipDirectory(distDir, zipPath) {
3155
- console.error(`\u538B\u7F29\u76EE\u5F55: ${distDir}`);
3156
- const zip = new JSZip();
3157
- await addDirToZip(distDir, zip);
3158
- const content = await zip.generateAsync({
3159
- type: "nodebuffer",
3160
- compression: "DEFLATE",
3161
- compressionOptions: { level: 6 }
3154
+ var useNpmShell2 = process.platform === "win32";
3155
+ function runNpm2(args, options = {}) {
3156
+ return spawnSync2(useNpmShell2 ? "npm.cmd" : "npm", args, {
3157
+ ...options,
3158
+ shell: useNpmShell2
3162
3159
  });
3163
- await writeFile(zipPath, content);
3164
- const sizeMb = (content.length / 1024 / 1024).toFixed(2);
3165
- console.error(`\u5DF2\u751F\u6210: ${zipPath} (${sizeMb} MB)`);
3166
- return content.length;
3167
- }
3168
- var SFTP_UPLOAD_MAX_ATTEMPTS = 3;
3169
- var SFTP_FAST_PUT_OPTIONS = {
3170
- chunkSize: 64 * 1024,
3171
- concurrency: 4
3172
- };
3173
- function buildSftpConnectOptions(settings) {
3174
- return {
3175
- host: settings.host,
3176
- port: settings.port,
3177
- username: settings.username,
3178
- password: settings.password,
3179
- readyTimeout: 3e4,
3180
- tryKeyboard: true,
3181
- keepaliveInterval: 1e4,
3182
- keepaliveCountMax: 3
3183
- };
3184
3160
  }
3185
- async function sleep(ms) {
3186
- await new Promise((resolve5) => setTimeout(resolve5, ms));
3161
+ function resolveNpmGlobalBin() {
3162
+ const binResult = runNpm2(["bin", "-g"], {
3163
+ encoding: "utf8",
3164
+ stdio: ["ignore", "pipe", "pipe"]
3165
+ });
3166
+ if (binResult.status !== 0) return null;
3167
+ return binResult.stdout?.toString().trim() || null;
3187
3168
  }
3188
- async function uploadZipWithRetry(settings, localZip, remoteZipPath) {
3189
- let lastError;
3190
- for (let attempt = 1; attempt <= SFTP_UPLOAD_MAX_ATTEMPTS; attempt++) {
3191
- const sftp = new SftpClient();
3192
- try {
3193
- if (attempt > 1) {
3194
- console.error(
3195
- `SFTP \u4E0A\u4F20\u91CD\u8BD5 (${attempt}/${SFTP_UPLOAD_MAX_ATTEMPTS})...`
3196
- );
3197
- }
3198
- await sftp.connect(buildSftpConnectOptions(settings));
3199
- await ensureRemoteDir(sftp, settings.remotePath);
3200
- await sftp.fastPut(localZip, remoteZipPath, SFTP_FAST_PUT_OPTIONS);
3201
- return sftp;
3202
- } catch (err) {
3203
- lastError = err;
3204
- try {
3205
- await sftp.end();
3206
- } catch {
3207
- }
3208
- if (attempt < SFTP_UPLOAD_MAX_ATTEMPTS) {
3209
- const message = err instanceof Error ? err.message : String(err);
3210
- const delaySec = attempt * 2;
3211
- console.error(`SFTP \u4E0A\u4F20\u5931\u8D25 (${message})\uFF0C${delaySec}s \u540E\u91CD\u8BD5...`);
3212
- await sleep(delaySec * 1e3);
3213
- }
3169
+ function resolvePm2FromNpmRoot() {
3170
+ const rootResult = runNpm2(["root", "-g"], {
3171
+ encoding: "utf8",
3172
+ stdio: ["ignore", "pipe", "pipe"]
3173
+ });
3174
+ if (rootResult.status !== 0) return null;
3175
+ const root = rootResult.stdout?.toString().trim();
3176
+ if (!root) return null;
3177
+ for (const rel of ["pm2/bin/pm2", "pm2/bin/pm2.js"]) {
3178
+ const candidate = join15(root, ...rel.split("/"));
3179
+ if (existsSync13(candidate)) {
3180
+ return candidate;
3214
3181
  }
3215
3182
  }
3216
- throw lastError;
3183
+ return null;
3217
3184
  }
3218
- async function ensureRemoteDir(sftp, dir) {
3219
- const parts = dir.replace(/\\/g, "/").split("/").filter(Boolean);
3220
- let current = dir.startsWith("/") ? "" : ".";
3221
- for (const part of parts) {
3222
- current = current ? `${current}/${part}` : `/${part}`;
3223
- try {
3224
- await sftp.mkdir(current, true);
3225
- } catch {
3226
- }
3185
+ function isPm2NodeScript(path13) {
3186
+ return path13.endsWith(".js") || /[\\/]pm2[\\/]bin[\\/]pm2$/.test(path13);
3187
+ }
3188
+ function buildPm2SpawnTarget(binPath) {
3189
+ if (isPm2NodeScript(binPath)) {
3190
+ return {
3191
+ command: process.execPath,
3192
+ prefixArgs: [binPath],
3193
+ displayPath: binPath
3194
+ };
3227
3195
  }
3196
+ return {
3197
+ command: binPath,
3198
+ prefixArgs: [],
3199
+ displayPath: binPath
3200
+ };
3228
3201
  }
3229
- function execCommand(client, command) {
3230
- return new Promise((resolve5, reject) => {
3231
- client.exec(command, (err, stream) => {
3232
- if (err) return reject(err);
3233
- let stdout = "";
3234
- let stderr = "";
3235
- stream.on("close", (code) => {
3236
- if (code !== 0) {
3237
- reject(new Error(`\u8FDC\u7A0B\u547D\u4EE4\u5931\u8D25 (${code}): ${stderr || stdout}`));
3238
- return;
3239
- }
3240
- resolve5(stdout);
3241
- }).on("data", (data) => {
3242
- stdout += data.toString();
3243
- });
3244
- stream.stderr.on("data", (data) => {
3245
- stderr += data.toString();
3246
- });
3247
- });
3202
+ function spawnPm2Target(target, args, options = {}) {
3203
+ const useShell = useNpmShell2 && target.prefixArgs.length === 0;
3204
+ return spawnSync2(target.command, [...target.prefixArgs, ...args], {
3205
+ encoding: "utf8",
3206
+ env: process.env,
3207
+ shell: useShell,
3208
+ windowsHide: useNpmShell2,
3209
+ ...options
3248
3210
  });
3249
3211
  }
3250
- function shellSingleQuote(value) {
3251
- return `'${value.replace(/'/g, `'"'"'`)}'`;
3252
- }
3253
- function buildClearRemoteDirExceptZipCommand(target) {
3254
- const normalized = target.replace(/\\/g, "/").replace(/\/$/, "");
3255
- const quotedTarget = shellSingleQuote(normalized);
3256
- const script = [
3257
- `T=${quotedTarget}`,
3258
- "S=$(mktemp -d)",
3259
- 'while IFS= read -r -d "" z; do r="${z#${T}/}"',
3260
- 'mkdir -p "${S}/$(dirname "$r")"',
3261
- 'mv "$z" "${S}/${r}"',
3262
- 'done < <(find "$T" -mindepth 1 -type f -iname "*.zip" -print0)',
3263
- 'rm -rf "${T}"/*',
3264
- 'while IFS= read -r -d "" r; do r="${r#./}"',
3265
- 'mkdir -p "${T}/$(dirname "$r")"',
3266
- 'mv "${S}/${r}" "${T}/${r}"',
3267
- 'done < <(cd "$S" 2>/dev/null && find . -type f -print0)',
3268
- 'rm -rf "$S"'
3269
- ].join("; ");
3270
- return `bash -c ${shellSingleQuote(script)}`;
3212
+ function spawnPm2At(binPath, args, options = {}) {
3213
+ return spawnPm2Target(buildPm2SpawnTarget(binPath), args, options);
3271
3214
  }
3272
- async function uploadAndMaybeExtract(settings, localZip, extract) {
3273
- const remoteZipPath = `${settings.remotePath.replace(/\/$/, "")}/dist.zip`;
3274
- console.error(
3275
- `\u8FDE\u63A5 ${settings.username}@${settings.host}:${settings.port} ...`
3276
- );
3277
- const sftp = await uploadZipWithRetry(settings, localZip, remoteZipPath);
3278
- try {
3279
- console.error(` \u2713 ${localZip} -> ${remoteZipPath}`);
3280
- if (extract) {
3281
- const target = settings.remotePath.replace(/\/$/, "");
3282
- const client = sftp.client;
3283
- const clearCmd = buildClearRemoteDirExceptZipCommand(target);
3284
- console.error(`\u8FDC\u7A0B\u6E05\u7406\u89E3\u538B\u76EE\u5F55: ${clearCmd}`);
3285
- await execCommand(client, clearCmd);
3286
- const unzipCmd = `unzip -o "${remoteZipPath}" -d "${target}"`;
3287
- console.error(`\u8FDC\u7A0B\u89E3\u538B: ${unzipCmd}`);
3288
- await execCommand(client, unzipCmd);
3289
- console.error("\u8FDC\u7A0B\u89E3\u538B\u5B8C\u6210!");
3290
- }
3291
- console.error(extract ? "\u90E8\u7F72\u5B8C\u6210!" : "\u4E0A\u4F20\u5B8C\u6210!");
3292
- } finally {
3293
- await sftp.end();
3215
+ function verifyPm2Bin(pm2Bin) {
3216
+ if (!pm2Bin.trim() || !existsSync13(pm2Bin)) {
3217
+ return false;
3294
3218
  }
3219
+ const result = spawnPm2At(pm2Bin, ["--version"], {
3220
+ stdio: ["ignore", "pipe", "pipe"]
3221
+ });
3222
+ return !result.error && result.status === 0;
3295
3223
  }
3296
- async function runWisdomSftpDeploy(params) {
3297
- const zipPath = path2.join(params.localDir, "..", ".deploy-sftp-dist.zip");
3298
- const resolvedZipPath = path2.resolve(zipPath);
3299
- const packOnly = Boolean(params.packOnly);
3300
- let zipSizeBytes = 0;
3301
- let artifactUploaded = false;
3302
- try {
3303
- zipSizeBytes = await zipDirectory(params.localDir, resolvedZipPath);
3304
- if (!packOnly) {
3305
- await uploadAndMaybeExtract(
3306
- params.settings,
3307
- resolvedZipPath,
3308
- params.extract
3309
- );
3224
+ function collectPm2Candidates() {
3225
+ const candidates = [];
3226
+ const globalBin = resolveNpmGlobalBin();
3227
+ if (globalBin) {
3228
+ if (useNpmShell2) {
3229
+ candidates.push(join15(globalBin, "pm2.cmd"));
3230
+ candidates.push(join15(globalBin, "pm2"));
3310
3231
  } else {
3311
- console.error("[apm] \u4EC5\u6253\u5305\u6A21\u5F0F\uFF1A\u8DF3\u8FC7 SFTP \u8FDC\u7A0B\u4E0A\u4F20");
3312
- }
3313
- const artifact = await uploadDeployArtifactZip({
3314
- zipPath: resolvedZipPath,
3315
- projectName: params.projectName,
3316
- kind: "frontend",
3317
- uploadWithoutRunId: packOnly && !resolveDeploymentRunIdFromEnv()
3318
- });
3319
- artifactUploaded = artifact !== null;
3320
- if (packOnly && artifactUploaded) {
3321
- console.error("\u6253\u5305\u5E76\u4E0A\u4F20 MinIO \u5B8C\u6210\uFF08\u672A\u4E0A\u4F20\u8FDC\u7A0B\u670D\u52A1\u5668\uFF09");
3232
+ candidates.push(join15(globalBin, "pm2"));
3322
3233
  }
3323
- } finally {
3324
- try {
3325
- await unlink(resolvedZipPath);
3326
- console.error(`\u5DF2\u6E05\u7406\u672C\u5730\u4E34\u65F6\u6587\u4EF6: ${resolvedZipPath}`);
3327
- } catch {
3234
+ }
3235
+ const fromRoot = resolvePm2FromNpmRoot();
3236
+ if (fromRoot) {
3237
+ candidates.push(fromRoot);
3238
+ }
3239
+ const whichResult = spawnSync2(useNpmShell2 ? "where" : "which", ["pm2"], {
3240
+ encoding: "utf8",
3241
+ shell: useNpmShell2,
3242
+ env: process.env,
3243
+ stdio: ["ignore", "pipe", "pipe"]
3244
+ });
3245
+ if (whichResult.status === 0) {
3246
+ const fromPath = whichResult.stdout?.toString().trim().split(/\r?\n/).map((line) => line.trim()).filter(Boolean) ?? [];
3247
+ if (useNpmShell2) {
3248
+ fromPath.sort((a, b) => {
3249
+ const aCmd = a.toLowerCase().endsWith(".cmd") ? 0 : 1;
3250
+ const bCmd = b.toLowerCase().endsWith(".cmd") ? 0 : 1;
3251
+ return aCmd - bCmd;
3252
+ });
3328
3253
  }
3254
+ candidates.push(...fromPath);
3329
3255
  }
3330
- return {
3331
- ok: true,
3332
- localDir: params.localDir,
3333
- host: params.settings.host,
3334
- remotePath: params.settings.remotePath,
3335
- zipSizeBytes,
3336
- extracted: packOnly ? false : params.extract,
3337
- packOnly,
3338
- artifactUploaded
3339
- };
3340
- }
3341
-
3342
- // src/commands/deploy/internal/wisdom-backend-deploy.ts
3343
- var SPRINGBOOT_JAVA_OPTS = "-XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError -Xms512M -Xmx1G";
3344
- var MAVEN_MODULE = "jeecg-module-system/jeecg-system-start";
3345
- var MAVEN_PROFILE = "dev";
3346
- function log(message) {
3347
- const now = /* @__PURE__ */ new Date();
3348
- const hh = String(now.getHours()).padStart(2, "0");
3349
- const mm = String(now.getMinutes()).padStart(2, "0");
3350
- const ss = String(now.getSeconds()).padStart(2, "0");
3351
- console.error(`[${hh}:${mm}:${ss}] ${message}`);
3352
- }
3353
- function fail(message) {
3354
- log(`ERROR: ${message}`);
3355
- process.exit(1);
3356
- }
3357
- function expandPath(pathStr) {
3358
- const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
3359
- return path3.resolve(pathStr.replace(/^~(?=\/|\\|$)/, home));
3256
+ return candidates;
3360
3257
  }
3361
- function quoteForShell(value) {
3362
- if (process.platform === "win32") {
3363
- return `"${value.replace(/"/g, '""')}"`;
3258
+ function findGlobalPm2() {
3259
+ const seen = /* @__PURE__ */ new Set();
3260
+ for (const candidate of collectPm2Candidates()) {
3261
+ const key = useNpmShell2 ? candidate.toLowerCase() : candidate;
3262
+ if (seen.has(key)) continue;
3263
+ seen.add(key);
3264
+ if (verifyPm2Bin(candidate)) {
3265
+ return candidate;
3266
+ }
3364
3267
  }
3365
- return shellSingleQuote(value);
3268
+ return null;
3366
3269
  }
3367
- function formatMavenLocalRepoArg(repoPath) {
3368
- if (process.platform === "win32") {
3369
- return `-Dmaven.repo.local=${quoteForShell(repoPath)}`;
3270
+ function installGlobalPm2() {
3271
+ console.log("[apm] \u672A\u68C0\u6D4B\u5230\u5168\u5C40 pm2\uFF0C\u6B63\u5728\u5B89\u88C5 npm install -g pm2 \u2026");
3272
+ const result = runNpm2(["install", "-g", "pm2"], {
3273
+ encoding: "utf8",
3274
+ stdio: "inherit"
3275
+ });
3276
+ if (result.status !== 0) {
3277
+ console.error("[apm] \u5B89\u88C5 pm2 \u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u6267\u884C: npm install -g pm2");
3278
+ process.exit(result.status ?? 1);
3370
3279
  }
3371
- return `-Dmaven.repo.local=${repoPath}`;
3372
3280
  }
3373
- function deployCacheDir() {
3374
- return path3.join(workspaceApmDir(), "deploy", ".deploy_cache");
3281
+ function readPm2Version(pm2Bin) {
3282
+ const result = spawnPm2At(pm2Bin, ["--version"], {
3283
+ stdio: ["ignore", "pipe", "pipe"]
3284
+ });
3285
+ if (result.error || result.status !== 0) {
3286
+ return null;
3287
+ }
3288
+ return result.stdout?.toString().trim() || null;
3375
3289
  }
3376
- function manifestFilePath() {
3377
- return path3.join(deployCacheDir(), "manifest.json");
3378
- }
3379
- function getTargetDir(projectRoot) {
3380
- return path3.join(projectRoot, MAVEN_MODULE, "target");
3381
- }
3382
- function relativeKey(projectRoot, filePath) {
3383
- return path3.relative(projectRoot, filePath).split(path3.sep).join("/");
3384
- }
3385
- function fileSignature(filePath) {
3386
- const stat2 = statSync6(filePath);
3387
- return { size: stat2.size, mtime: stat2.mtimeMs / 1e3 };
3388
- }
3389
- function loadManifest3() {
3390
- const manifestPath2 = manifestFilePath();
3391
- if (!existsSync12(manifestPath2)) {
3392
- return {};
3290
+ function logPm2Ready(pm2Bin, installed2) {
3291
+ const version = readPm2Version(pm2Bin);
3292
+ const versionSuffix = version ? ` ${version}` : "";
3293
+ if (installed2) {
3294
+ console.log(`[apm] \u5168\u5C40 pm2 \u5DF2\u5B89\u88C5${versionSuffix}: ${pm2Bin}`);
3295
+ } else {
3296
+ console.log(`[apm] \u5DF2\u68C0\u6D4B\u5230\u5168\u5C40 pm2${versionSuffix}: ${pm2Bin}`);
3393
3297
  }
3394
- return JSON.parse(readFileSync11(manifestPath2, "utf8"));
3395
- }
3396
- function saveManifest3(manifest) {
3397
- const dir = deployCacheDir();
3398
- mkdirSync5(dir, { recursive: true });
3399
- writeFileSync10(manifestFilePath(), JSON.stringify(manifest, null, 2), "utf8");
3400
3298
  }
3401
- function isProjectLibJar(jarName) {
3402
- return jarName.startsWith("jeecg-");
3299
+ function isPm2DaemonOutOfDate(output) {
3300
+ return /In-memory PM2 is out-of-date/i.test(output);
3403
3301
  }
3404
- function shouldUploadLibFile(localPath, remoteAttr, manifest, projectRoot) {
3405
- if (!remoteAttr) {
3406
- return [false, "\u8FDC\u7A0B\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7"];
3302
+ function ensurePm2DaemonUpdated(pm2Bin) {
3303
+ const probe = spawnPm2At(pm2Bin, ["jlist"], {
3304
+ stdio: ["ignore", "pipe", "pipe"]
3305
+ });
3306
+ const combined = `${probe.stdout ?? ""}
3307
+ ${probe.stderr ?? ""}`;
3308
+ if (!isPm2DaemonOutOfDate(combined)) {
3309
+ return;
3407
3310
  }
3408
- const localSize = statSync6(localPath).size;
3409
- const remoteSize = remoteAttr.size;
3410
- if (localSize !== remoteSize) {
3411
- return [true, `\u5927\u5C0F\u53D8\u5316 ${remoteSize} -> ${localSize}`];
3311
+ console.log("[apm] \u68C0\u6D4B\u5230 PM2 \u5185\u5B58\u7248\u672C\u4E0E\u672C\u5730\u4E0D\u4E00\u81F4\uFF0C\u6B63\u5728\u6267\u884C pm2 update \u2026");
3312
+ const update = spawnPm2At(pm2Bin, ["update"], {
3313
+ stdio: "inherit"
3314
+ });
3315
+ if (update.error || update.status !== 0) {
3316
+ console.error("[apm] pm2 update \u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u6267\u884C: pm2 update");
3412
3317
  }
3413
- if (isProjectLibJar(path3.basename(localPath)) && manifest) {
3414
- const key = relativeKey(projectRoot, localPath);
3415
- const current = fileSignature(localPath);
3416
- const previous = manifest[key];
3417
- if (!previous) {
3418
- return [true, "\u9879\u76EE\u6A21\u5757\u672A\u8BB0\u5F55"];
3419
- }
3420
- if (previous.size !== current.size) {
3421
- return [true, "\u9879\u76EE\u6A21\u5757\u5927\u5C0F\u53D8\u5316"];
3422
- }
3423
- if (previous.mtime < current.mtime) {
3424
- return [true, "\u9879\u76EE\u6A21\u5757\u91CD\u65B0\u6784\u5EFA"];
3318
+ }
3319
+ function ensureGlobalPm2(options) {
3320
+ const existing = findGlobalPm2();
3321
+ if (existing) {
3322
+ ensurePm2DaemonUpdated(existing);
3323
+ if (!options?.quiet) {
3324
+ logPm2Ready(existing, false);
3425
3325
  }
3326
+ return existing;
3426
3327
  }
3427
- return [false, "\u5927\u5C0F\u4E00\u81F4\uFF0C\u8DF3\u8FC7"];
3428
- }
3429
- function listLibFilesToUpload(localLibDir, remoteStats, projectRoot, manifest = null) {
3430
- const entries = [];
3431
- const jarFiles = readdirSync5(localLibDir).filter((name) => name.endsWith(".jar")).sort();
3432
- for (const jarName of jarFiles) {
3433
- const jarPath = path3.join(localLibDir, jarName);
3434
- const remoteAttr = remoteStats.get(jarName);
3435
- const [shouldUpload, reason] = shouldUploadLibFile(
3436
- jarPath,
3437
- remoteAttr,
3438
- manifest,
3439
- projectRoot
3328
+ installGlobalPm2();
3329
+ const installed2 = findGlobalPm2();
3330
+ if (!installed2) {
3331
+ const globalBin = resolveNpmGlobalBin();
3332
+ const moduleEntry = resolvePm2FromNpmRoot();
3333
+ console.error(
3334
+ "[apm] \u5B89\u88C5 pm2 \u540E\u4ECD\u65E0\u6CD5\u627E\u5230\u53EF\u6267\u884C\u6587\u4EF6\uFF0C\u8BF7\u624B\u52A8\u6267\u884C: npm install -g pm2"
3440
3335
  );
3441
- if (shouldUpload) {
3442
- entries.push({ path: jarPath, arcname: jarName, reason });
3336
+ if (globalBin) {
3337
+ console.error(`[apm] npm \u5168\u5C40 bin: ${globalBin}`);
3338
+ }
3339
+ if (moduleEntry) {
3340
+ console.error(`[apm] \u5DF2\u68C0\u6D4B\u5230 pm2 \u6A21\u5757: ${moduleEntry}`);
3341
+ console.error(
3342
+ "[apm] \u82E5 pm2 --version \u53EF\u7528\uFF0C\u8BF7\u68C0\u67E5 Node/npm \u5168\u5C40\u5B89\u88C5\u6743\u9650\u6216 PATH"
3343
+ );
3443
3344
  }
3345
+ process.exit(1);
3444
3346
  }
3445
- return entries;
3446
- }
3447
- function updateManifestEntries(manifest, entries, projectRoot) {
3448
- for (const entry of entries) {
3449
- manifest[relativeKey(projectRoot, entry.path)] = fileSignature(entry.path);
3347
+ ensurePm2DaemonUpdated(installed2);
3348
+ if (!options?.quiet) {
3349
+ logPm2Ready(installed2, true);
3450
3350
  }
3451
- return manifest;
3351
+ return installed2;
3452
3352
  }
3453
- async function createUpdatePackage(entries, packageName) {
3454
- const dir = deployCacheDir();
3455
- mkdirSync5(dir, { recursive: true });
3456
- const zipPath = path3.join(dir, packageName);
3457
- log(`\u521B\u5EFA\u66F4\u65B0\u5305: ${path3.basename(zipPath)}\uFF08${entries.length} \u4E2A\u6587\u4EF6\uFF09`);
3458
- const zip = new JSZip2();
3459
- for (const entry of entries) {
3460
- const content = readFileSync11(entry.path);
3461
- zip.file(entry.arcname, content);
3462
- log(` \u6253\u5305: ${entry.arcname} (${entry.reason})`);
3353
+ function resolveApmEntryPath(entryArg = process.argv[1]) {
3354
+ const fromArgv = entryArg?.trim();
3355
+ if (fromArgv && existsSync13(fromArgv)) {
3356
+ return fromArgv;
3463
3357
  }
3464
- const buffer = await zip.generateAsync({
3465
- type: "nodebuffer",
3466
- compression: "DEFLATE",
3467
- compressionOptions: { level: 6 }
3358
+ const npmResult = spawnSync2(useNpmShell2 ? "npm.cmd" : "npm", ["root", "-g"], {
3359
+ encoding: "utf8",
3360
+ shell: useNpmShell2,
3361
+ stdio: ["ignore", "pipe", "pipe"]
3468
3362
  });
3469
- writeFileSync10(zipPath, buffer);
3470
- return zipPath;
3471
- }
3472
- function getMvnExecutable() {
3473
- const isWin = process.platform === "win32";
3474
- const candidates = isWin ? ["mvn.cmd", "mvn.bat", "mvn"] : ["mvn"];
3475
- for (const name of candidates) {
3476
- const result = spawnSync2(isWin ? `where ${name}` : `which ${name}`, {
3477
- encoding: "utf8",
3478
- shell: true
3479
- });
3480
- if (result.status === 0 && result.stdout.trim()) {
3481
- return result.stdout.trim().split(/\r?\n/)[0].trim();
3363
+ if (npmResult.status === 0) {
3364
+ const globalRoot = npmResult.stdout?.toString().trim();
3365
+ if (globalRoot) {
3366
+ const candidate = join15(globalRoot, CLI_PACKAGE_NAME, "dist", "index.js");
3367
+ if (existsSync13(candidate)) {
3368
+ return candidate;
3369
+ }
3482
3370
  }
3483
3371
  }
3484
- fail("\u672A\u627E\u5230 mvn \u547D\u4EE4\uFF0C\u8BF7\u786E\u8BA4 Maven \u5DF2\u5B89\u88C5\u5E76\u52A0\u5165 PATH");
3485
- }
3486
- function runMavenBuild(projectRoot, mavenLocalRepo, repoSource) {
3487
- const mavenRepo = expandPath(mavenLocalRepo);
3488
- const mvn = getMvnExecutable();
3489
- const command = [
3490
- quoteForShell(mvn),
3491
- "clean",
3492
- "package",
3493
- `-P${MAVEN_PROFILE}`,
3494
- formatMavenLocalRepoArg(mavenRepo),
3495
- "-DskipTests"
3496
- ].join(" ");
3497
- log("\u5F00\u59CB Maven \u6784\u5EFA...");
3498
- if (repoSource) {
3499
- log(
3500
- `Maven \u672C\u5730\u4ED3\u5E93: ${mavenRepo} (\u6765\u6E90: ${repoSource.source}/${repoSource.sourceDetail})`
3501
- );
3502
- } else {
3503
- log(`Maven \u672C\u5730\u4ED3\u5E93: ${mavenRepo}`);
3504
- }
3505
- log(`\u6784\u5EFA\u76EE\u5F55: ${projectRoot}`);
3506
- const result = spawnSync2(command, {
3507
- cwd: projectRoot,
3508
- stdio: "inherit",
3509
- shell: true,
3510
- env: process.env
3511
- });
3512
- if (result.status !== 0) {
3513
- fail(`Maven \u6784\u5EFA\u5931\u8D25\uFF0C\u9000\u51FA\u7801: ${result.status ?? 1}`);
3372
+ if (fromArgv) {
3373
+ return fromArgv;
3514
3374
  }
3375
+ console.error("[apm] \u65E0\u6CD5\u89E3\u6790 apm \u5165\u53E3\u8DEF\u5F84");
3376
+ process.exit(1);
3515
3377
  }
3516
- function locateLibDir(projectRoot) {
3517
- const targetDir = getTargetDir(projectRoot);
3518
- if (!existsSync12(targetDir)) {
3519
- fail(`\u6784\u5EFA\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728: ${targetDir}`);
3520
- }
3521
- const libDir = path3.join(targetDir, "lib");
3522
- if (!existsSync12(libDir) || !statSync6(libDir).isDirectory()) {
3523
- fail(`lib \u76EE\u5F55\u4E0D\u5B58\u5728: ${libDir}`);
3378
+ function isRunningUnderPm2() {
3379
+ if (process.env.APM_CONNECT_UNDER_PM2 === "1") {
3380
+ return true;
3524
3381
  }
3525
- const libJars = readdirSync5(libDir).filter((name) => name.endsWith(".jar"));
3526
- if (libJars.length === 0) {
3527
- fail(`lib \u76EE\u5F55\u4E0B\u6CA1\u6709\u4F9D\u8D56 JAR: ${libDir}`);
3382
+ if (process.env.pm_id === void 0) {
3383
+ return false;
3528
3384
  }
3529
- log(`\u5B9A\u4F4D lib \u4EA7\u7269: ${libJars.length} \u4E2A`);
3530
- return libDir;
3385
+ const name = process.env.name;
3386
+ return name === PM2_APP_NAME || name !== void 0 && LEGACY_PM2_APP_NAMES.includes(name);
3531
3387
  }
3532
- function locateMainJar(projectRoot) {
3533
- const targetDir = getTargetDir(projectRoot);
3534
- if (!existsSync12(targetDir)) {
3535
- fail(`\u6784\u5EFA\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728: ${targetDir}`);
3536
- }
3537
- const jarFiles = readdirSync5(targetDir).filter((name) => name.endsWith(".jar") && !name.endsWith(".jar.original")).map((name) => path3.join(targetDir, name)).sort((a, b) => statSync6(b).mtimeMs - statSync6(a).mtimeMs);
3538
- if (jarFiles.length === 0) {
3539
- fail(`target \u76EE\u5F55\u4E0B\u6CA1\u6709\u4E3B JAR: ${targetDir}`);
3388
+ function isConnectPm2Process(app) {
3389
+ if (app.name === PM2_APP_NAME) {
3390
+ return true;
3540
3391
  }
3541
- const mainJar = jarFiles[0];
3542
- log(`\u5B9A\u4F4D\u4E3B JAR \u4EA7\u7269: ${path3.basename(mainJar)}`);
3543
- return mainJar;
3544
- }
3545
- async function connectSsh(config) {
3546
- const client = new Client2();
3547
- log(`\u8FDE\u63A5\u670D\u52A1\u5668 ${config.username}@${config.host}:${config.port}`);
3548
- await new Promise((resolve5, reject) => {
3549
- client.on("ready", () => resolve5()).on("error", (err) => reject(err)).connect({
3550
- host: config.host,
3551
- port: config.port,
3552
- username: config.username,
3553
- password: config.password,
3554
- readyTimeout: 3e4,
3555
- tryKeyboard: true
3556
- });
3557
- });
3558
- const sftp = new SftpClient2();
3559
- await sftp.connect({
3560
- host: config.host,
3561
- port: config.port,
3562
- username: config.username,
3563
- password: config.password,
3564
- readyTimeout: 3e4,
3565
- tryKeyboard: true
3566
- });
3567
- return { client, sftp };
3392
+ return app.name !== void 0 && LEGACY_PM2_APP_NAMES.includes(app.name);
3568
3393
  }
3569
- async function closeSsh(conn) {
3394
+ function listPm2Processes() {
3570
3395
  try {
3571
- await conn.sftp.end();
3396
+ const raw = runPm2Json(["jlist"]);
3397
+ return JSON.parse(raw);
3572
3398
  } catch {
3399
+ return [];
3573
3400
  }
3574
- conn.client.end();
3575
3401
  }
3576
- async function getRemoteFileStats(sftp, remoteDir) {
3577
- const stats = /* @__PURE__ */ new Map();
3578
- try {
3579
- const listing = await sftp.list(remoteDir);
3580
- for (const item of listing) {
3581
- if (item.name.endsWith(".jar")) {
3582
- stats.set(item.name, { filename: item.name, size: item.size });
3583
- }
3584
- }
3585
- } catch {
3586
- }
3587
- return stats;
3402
+ function findPm2ConnectProcess() {
3403
+ return listPm2Processes().find(isConnectPm2Process) ?? null;
3588
3404
  }
3589
- async function uploadUpdatePackage(sftp, zipPath, config) {
3590
- const remoteDir = config.remoteVueDistDir.replace(/\/$/, "");
3591
- const remotePath = `${remoteDir}/${path3.basename(zipPath)}`;
3592
- log(`\u4E0A\u4F20\u66F4\u65B0\u5305 -> ${remotePath}`);
3593
- try {
3594
- await sftp.fastPut(zipPath, remotePath);
3595
- log("\u66F4\u65B0\u5305\u4E0A\u4F20\u6210\u529F");
3596
- } catch (err) {
3597
- fail(`\u66F4\u65B0\u5305\u4E0A\u4F20\u5931\u8D25: ${err instanceof Error ? err.message : err}`);
3598
- }
3599
- return remotePath;
3405
+ function resolvePm2ConnectName(app) {
3406
+ return app?.name ?? PM2_APP_NAME;
3600
3407
  }
3601
- async function uploadFullJar(sftp, localJarPath, config) {
3602
- const remoteDir = config.remoteAppDir.replace(/\/$/, "");
3603
- const remotePath = `${remoteDir}/${config.startupJar}`;
3604
- log(`\u4E0A\u4F20\u5168\u91CF JAR (${path3.basename(localJarPath)}) -> ${remotePath}`);
3605
- try {
3606
- await sftp.mkdir(remoteDir, true);
3607
- await sftp.fastPut(localJarPath, remotePath);
3608
- log("\u5168\u91CF JAR \u4E0A\u4F20\u6210\u529F");
3609
- } catch (err) {
3610
- fail(`\u5168\u91CF JAR \u4E0A\u4F20\u5931\u8D25: ${err instanceof Error ? err.message : err}`);
3408
+ function deletePm2AppIfExists(name) {
3409
+ const pm2Bin = findGlobalPm2();
3410
+ if (!pm2Bin) {
3411
+ return;
3611
3412
  }
3612
- }
3613
- async function runRemoteCommand(client, command, options) {
3614
- const check = options?.check ?? true;
3615
- const stream = options?.stream ?? false;
3616
- const label = options?.label ?? "\u8FDC\u7A0B\u547D\u4EE4";
3617
- return new Promise((resolve5, reject) => {
3618
- client.exec(command, (err, execStream) => {
3619
- if (err) {
3620
- reject(err);
3621
- return;
3622
- }
3623
- let out = "";
3624
- let errText = "";
3625
- execStream.on("data", (data) => {
3626
- const text = data.toString();
3627
- out += text;
3628
- if (stream) {
3629
- process.stdout.write(text);
3630
- }
3631
- });
3632
- execStream.stderr.on("data", (data) => {
3633
- errText += data.toString();
3634
- });
3635
- execStream.on("close", (code) => {
3636
- if (stream && out && !out.endsWith("\n")) {
3637
- process.stdout.write("\n");
3638
- }
3639
- if (check && code !== 0) {
3640
- const combined = `${out}
3641
- ${errText}`.trim();
3642
- fail(
3643
- `${label}\u5931\u8D25 (exit ${code})` + (combined ? `
3644
- \u8F93\u51FA: ${combined}` : "")
3645
- );
3646
- }
3647
- resolve5({ exitCode: code, out: out.trim(), err: errText.trim() });
3648
- });
3649
- });
3413
+ ensurePm2DaemonUpdated(pm2Bin);
3414
+ spawnPm2At(pm2Bin, ["delete", name], {
3415
+ stdio: "ignore"
3650
3416
  });
3651
3417
  }
3652
- function buildExtractUpdatePackageScript(remoteZipPath, remoteLibDir) {
3653
- const quotedZip = shellSingleQuote(remoteZipPath);
3654
- const quotedLib = shellSingleQuote(remoteLibDir);
3655
- return `
3656
- set -e
3657
- TMP=$(mktemp -d)
3658
- trap 'rm -rf "$TMP"' EXIT
3659
- unzip -oq ${quotedZip} -d "$TMP"
3660
- updated=0
3661
- while IFS= read -r -d '' src; do
3662
- name=$(basename "$src")
3663
- dest=${quotedLib}/"$name"
3664
- if [ -f "$dest" ]; then
3665
- cp -f "$src" "$dest"
3666
- echo "\u8986\u76D6: $name"
3667
- updated=$((updated + 1))
3668
- else
3669
- echo "\u8DF3\u8FC7(\u8FDC\u7A0B\u4E0D\u5B58\u5728): $name"
3670
- fi
3671
- done < <(find "$TMP" -name '*.jar' -type f -print0)
3672
- echo "UPDATED_COUNT=$updated"
3673
- `.trim();
3418
+ function removePm2ConnectProcesses() {
3419
+ const seen = /* @__PURE__ */ new Set();
3420
+ for (const app of listPm2Processes()) {
3421
+ if (!isConnectPm2Process(app) || !app.name || seen.has(app.name)) {
3422
+ continue;
3423
+ }
3424
+ seen.add(app.name);
3425
+ deletePm2AppIfExists(app.name);
3426
+ }
3427
+ for (const legacyName of LEGACY_PM2_APP_NAMES) {
3428
+ deletePm2AppIfExists(legacyName);
3429
+ }
3430
+ deletePm2AppIfExists(PM2_APP_NAME);
3674
3431
  }
3675
- async function extractUpdatePackageOnRemote(client, config, remoteZipPath) {
3676
- const script = buildExtractUpdatePackageScript(
3677
- remoteZipPath,
3678
- config.remoteLibDir
3679
- );
3680
- const { out } = await runRemoteCommand(client, script, {
3681
- label: "\u8FDC\u7A0B\u89E3\u538B"
3432
+ function runPm2(args, options) {
3433
+ const pm2Bin = ensureGlobalPm2({ quiet: true });
3434
+ const result = spawnPm2At(pm2Bin, args, {
3435
+ stdio: options?.inherit ? "inherit" : ["ignore", "pipe", "pipe"]
3682
3436
  });
3683
- const match = out.match(/UPDATED_COUNT=(\d+)/);
3684
- if (!match) {
3685
- fail(`\u8FDC\u7A0B\u89E3\u538B\u5931\u8D25\uFF0C\u672A\u83B7\u53D6\u66F4\u65B0\u6570\u91CF
3686
- \u8F93\u51FA: ${out || "(\u7A7A)"}`);
3437
+ if (result.error) {
3438
+ console.error("[apm] \u6267\u884C pm2 \u5931\u8D25:", result.error.message);
3439
+ process.exit(1);
3687
3440
  }
3688
- const updated = Number.parseInt(match[1], 10);
3689
- log(`lib \u89E3\u538B\u5B8C\u6210: \u8986\u76D6 ${updated} \u4E2A`);
3690
- return updated;
3691
- }
3692
- function springbootOutputIndicatesSuccess(action, combined) {
3693
- const lower = combined.toLowerCase();
3694
- if (action === "health") {
3695
- return combined.includes("\u5065\u5EB7\u68C0\u67E5\u901A\u8FC7");
3441
+ if (result.status !== 0) {
3442
+ const stderr = result.stderr?.toString().trim();
3443
+ const stdout = result.stdout?.toString().trim();
3444
+ const detail = stderr || stdout || `exit code ${result.status}`;
3445
+ console.error(`[apm] pm2 ${args.join(" ")} \u5931\u8D25: ${detail}`);
3446
+ process.exit(result.status ?? 1);
3696
3447
  }
3697
- if (action === "start" || action === "restart") {
3698
- return combined.includes("is starting") || lower.includes("is running");
3448
+ }
3449
+ function runPm2Json(args) {
3450
+ const pm2Bin = findGlobalPm2();
3451
+ if (!pm2Bin) {
3452
+ return "[]";
3699
3453
  }
3700
- if (action === "stop") {
3701
- return combined.includes("is stopping") || lower.includes("not running") || lower.includes("please check it");
3454
+ ensurePm2DaemonUpdated(pm2Bin);
3455
+ const result = spawnPm2At(pm2Bin, args, {
3456
+ stdio: ["ignore", "pipe", "pipe"]
3457
+ });
3458
+ if (result.status !== 0) return "[]";
3459
+ return result.stdout?.toString() ?? "[]";
3460
+ }
3461
+ function isPm2ConnectOnline() {
3462
+ const app = findPm2ConnectProcess();
3463
+ if (!app) return false;
3464
+ const status = app.pm2_env?.status;
3465
+ return status === "online" || status === "launching";
3466
+ }
3467
+ function printConnectNotRunningHint() {
3468
+ console.log(`[apm] ${PM2_APP_NAME} \u672A\u5728\u8FD0\u884C`);
3469
+ console.log("[apm] \u542F\u52A8\u5B88\u62A4: apm connect --daemon");
3470
+ }
3471
+ function assertConnectNotRunning() {
3472
+ pruneStaleConnectLock();
3473
+ const lock = readConnectLock();
3474
+ if (lock && isProcessAlive(lock.pid) && lock.pid !== process.pid) {
3475
+ console.error(
3476
+ `[apm] \u5DF2\u6709 apm connect \u5728\u8FD0\u884C (pid=${lock.pid}, mode=${lock.mode})\uFF0C\u8BF7\u5148\u505C\u6B62\u540E\u518D\u542F\u52A8`
3477
+ );
3478
+ process.exit(1);
3702
3479
  }
3703
- if (action === "status") {
3704
- return lower.includes("running") || lower.includes("not running");
3480
+ if (!isRunningUnderPm2() && isPm2ConnectOnline()) {
3481
+ console.error(
3482
+ "[apm] \u5DF2\u6709 apm connect \u5B88\u62A4\u8FDB\u7A0B\u5728\u8FD0\u884C\uFF0C\u8BF7\u5148\u6267\u884C apm daemon stop"
3483
+ );
3484
+ process.exit(1);
3705
3485
  }
3706
- return true;
3707
- }
3708
- function buildRemoteStatusScript(remoteAppDir, appName) {
3709
- const dir = shellSingleQuote(remoteAppDir);
3710
- const jar = shellSingleQuote(appName);
3711
- return `
3712
- set -e
3713
- cd ${dir}
3714
- appName=${jar}
3715
- appIds=$(ps -ef | grep java | grep "$appName" | awk '{print $2}')
3716
- if [ -z "$appIds" ]; then
3717
- echo -e "\\033[31m Not running \\033[0m"
3718
- else
3719
- echo -e "\\033[32m Running [$appIds] \\033[0m"
3720
- fi
3721
- `.trim();
3722
3486
  }
3723
- function buildRemoteRestartScript(remoteAppDir) {
3724
- const dir = shellSingleQuote(remoteAppDir);
3725
- const javaOpts = shellSingleQuote(SPRINGBOOT_JAVA_OPTS);
3726
- return `
3727
- set -e
3728
- cd ${dir}
3729
- releaseApp=$(ls -t | grep '.jar$' | head -n1)
3730
- lastVersionApp=$(ls -t | grep '.jar$' | head -n2 | tail -n1)
3731
- appName=$lastVersionApp
3732
- appIds=$(ps -ef | grep java | grep "$appName" | awk '{print $2}')
3733
- if [ -z "$appIds" ]; then
3734
- echo "Maybe $appName not running, please check it..."
3735
- else
3736
- echo "The $appName is stopping..."
3737
- echo "$appIds" | xargs kill
3738
- fi
3739
- for i in $(seq 15 -1 1); do
3740
- echo -n "$i "
3741
- sleep 1
3742
- done
3743
- echo 0
3744
- if [ ! -d "backup" ]; then
3745
- mkdir backup
3746
- fi
3747
- for i in $(ls | grep '.jar$' | grep -vFx "$releaseApp"); do
3748
- echo "backup $i"
3749
- mv "$i" backup/
3750
- done
3751
- appName=$releaseApp
3752
- count=$(ps -ef | grep java | grep "$appName" | wc -l)
3753
- if [ "$count" != "0" ]; then
3754
- echo "Maybe $appName is running, please check it..."
3755
- else
3756
- echo "The $appName is starting..."
3757
- nohup java -jar "./$appName" ${javaOpts} > nohup.out 2>&1 &
3758
- fi
3759
- `.trim();
3487
+ function resolveRuntimePaths() {
3488
+ return {
3489
+ apmScript: resolveApmEntryPath(),
3490
+ nodePath: process.execPath
3491
+ };
3760
3492
  }
3761
- function normalizeHealthContext(context) {
3762
- let normalized = context.trim() || "/";
3763
- if (!normalized.startsWith("/")) {
3764
- normalized = `/${normalized}`;
3493
+ function reexecConnectWithResolvedPath(options) {
3494
+ const apmScript = resolveApmEntryPath();
3495
+ const args = [apmScript, "connect"];
3496
+ const server = options.server?.trim();
3497
+ if (server) {
3498
+ args.push("--server", server);
3765
3499
  }
3766
- if (!normalized.endsWith("/")) {
3767
- normalized = `${normalized}/`;
3500
+ const result = spawnSync2(process.execPath, args, { stdio: "inherit" });
3501
+ if (result.error) {
3502
+ console.error("[apm] \u91CD\u542F connect \u5931\u8D25:", result.error.message);
3503
+ process.exit(1);
3768
3504
  }
3769
- return normalized;
3505
+ process.exit(result.status ?? 0);
3770
3506
  }
3771
- function buildRemoteHealthScript(port, context, timeoutSecs) {
3772
- const normalizedContext = normalizeHealthContext(context);
3773
- const portStr = String(port);
3774
- const timeoutStr = String(timeoutSecs);
3775
- return `
3776
- set -e
3777
- port=${shellSingleQuote(portStr)}
3778
- context=${shellSingleQuote(normalizedContext)}
3779
- timeout=${shellSingleQuote(timeoutStr)}
3780
- check_url="http://127.0.0.1:${portStr}${normalizedContext}"
3781
- echo "\u5065\u5EB7\u68C0\u67E5: \${check_url} (\u8D85\u65F6 \${timeout}s)"
3782
- deadline=$(($(date +%s) + timeout))
3783
- attempt=0
3784
- while [ $(date +%s) -lt $deadline ]; do
3785
- attempt=$((attempt + 1))
3786
- code=$(curl -s -o /dev/null -w "%{http_code}" "$check_url" 2>/dev/null || echo "000")
3787
- code=$(echo "$code" | tail -n1 | tr -d '[:space:]')
3788
- if [ \${#code} -ge 3 ]; then
3789
- status=\${code:0:3}
3790
- if echo "$status" | grep -qE '^[0-9]+$' && [ "$status" -ge 200 ] && [ "$status" -lt 500 ]; then
3791
- echo -e "\\033[32m\u5065\u5EB7\u68C0\u67E5\u901A\u8FC7 (HTTP \${status})\\033[0m"
3792
- exit 0
3793
- fi
3794
- fi
3795
- remaining=$((deadline - $(date +%s)))
3796
- if [ $remaining -lt 0 ]; then
3797
- remaining=0
3798
- fi
3799
- echo "\u7B49\u5F85\u670D\u52A1\u542F\u52A8... \u7B2C \${attempt} \u6B21\uFF0C\u5269\u4F59 \${remaining}s"
3800
- sleep 5
3801
- done
3802
- echo -e "\\033[31m\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6 (\${timeout}s): \${check_url}\\033[0m"
3803
- exit 1
3804
- `.trim();
3507
+ async function handleConnectAfterUpdate(options) {
3508
+ console.log("[apm] \u66F4\u65B0\u5B8C\u6210\uFF0C\u6B63\u5728\u4EE5\u65B0\u7248\u672C\u91CD\u65B0\u8FDE\u63A5\u2026");
3509
+ await prepareEcosystem(options.server);
3510
+ if (isRunningUnderPm2()) {
3511
+ runPm2(["reload", PM2_ECOSYSTEM_PATH, "--update-env"], { inherit: true });
3512
+ console.log("[apm] \u5DF2\u901A\u77E5 PM2 \u4F7F\u7528\u65B0\u7248\u672C\u91CD\u65B0\u52A0\u8F7D connect");
3513
+ process.exit(0);
3514
+ }
3515
+ reexecConnectWithResolvedPath(options);
3805
3516
  }
3806
- async function runRemoteServiceScript(client, script, action) {
3807
- const { exitCode, out, err } = await runRemoteCommand(client, script, {
3808
- check: false,
3809
- label: action === "status" ? "\u8FDC\u7A0B status" : action === "restart" ? "\u8FDC\u7A0B restart" : "\u5065\u5EB7\u68C0\u67E5"
3517
+ async function resolveBaseUrl(server) {
3518
+ if (server?.trim()) {
3519
+ return server.trim().replace(/\/+$/, "");
3520
+ }
3521
+ const cfg = await tryReadApmConfig();
3522
+ return cfg?.baseUrl;
3523
+ }
3524
+ function writeConnectLaunchFiles(options) {
3525
+ mkdirSync6(APM_CONFIG_DIR, { recursive: true });
3526
+ writeFileSync11(PM2_CONNECT_ENTRY_PATH, CONNECT_ENTRY_SCRIPT, "utf8");
3527
+ const env = {
3528
+ ...collectPm2LaunchEnv()
3529
+ };
3530
+ const baseUrl = options.baseUrl?.trim().replace(/\/+$/, "");
3531
+ if (baseUrl) {
3532
+ env.AI_PM_SERVER = baseUrl;
3533
+ }
3534
+ const launch = {
3535
+ nodePath: options.nodePath,
3536
+ apmScript: options.apmScript,
3537
+ connectArgs: options.connectArgs,
3538
+ cwd: options.cwd,
3539
+ env
3540
+ };
3541
+ writeFileSync11(
3542
+ PM2_CONNECT_LAUNCH_PATH,
3543
+ JSON.stringify(launch, null, 2) + "\n",
3544
+ "utf8"
3545
+ );
3546
+ }
3547
+ function writeEcosystemFile(options) {
3548
+ writeConnectLaunchFiles(options);
3549
+ const ecosystem = buildConnectPm2Ecosystem({
3550
+ entryScript: PM2_CONNECT_ENTRY_PATH,
3551
+ baseUrl: options.baseUrl
3810
3552
  });
3811
- const combined = `${out}
3812
- ${err}`.trim();
3813
- const outputOk = springbootOutputIndicatesSuccess(action, combined);
3814
- if (action === "health") {
3815
- if (exitCode !== 0 || !outputOk) {
3816
- fail(`\u5065\u5EB7\u68C0\u67E5\u5931\u8D25
3817
- \u8F93\u51FA: ${combined || "(\u7A7A)"}`);
3553
+ writeFileSync11(
3554
+ PM2_ECOSYSTEM_PATH,
3555
+ formatConnectPm2EcosystemFile(ecosystem),
3556
+ "utf8"
3557
+ );
3558
+ if (existsSync13(LEGACY_PM2_ECOSYSTEM_PATH)) {
3559
+ try {
3560
+ unlinkSync2(LEGACY_PM2_ECOSYSTEM_PATH);
3561
+ } catch {
3818
3562
  }
3819
- return combined;
3820
3563
  }
3821
- if (exitCode !== 0 && !outputOk) {
3822
- fail(`\u8FDC\u7A0B ${action} \u5931\u8D25
3823
- ${combined}`);
3564
+ }
3565
+ async function prepareEcosystem(server) {
3566
+ await ensureLoggedConfig();
3567
+ const cfg = await ensureApmConfig();
3568
+ const { apmScript, nodePath } = resolveRuntimePaths();
3569
+ const connectArgs = resolveConnectArgs(server);
3570
+ const baseUrl = await resolveBaseUrl(server);
3571
+ writeEcosystemFile({
3572
+ apmScript,
3573
+ nodePath,
3574
+ connectArgs,
3575
+ baseUrl,
3576
+ cwd: process.cwd()
3577
+ });
3578
+ return cfg;
3579
+ }
3580
+ async function runDaemonStart(options) {
3581
+ assertConnectNotRunning();
3582
+ await runUpdate();
3583
+ ensureGlobalPm2();
3584
+ const cfg = await prepareEcosystem(options.server);
3585
+ removePm2ConnectProcesses();
3586
+ runPm2(["start", PM2_ECOSYSTEM_PATH, "--update-env"], { inherit: true });
3587
+ await delay2(1e3);
3588
+ if (!isPm2ConnectOnline()) {
3589
+ console.error(
3590
+ `[apm] ${PM2_APP_NAME} \u542F\u52A8\u540E\u672A\u5904\u4E8E online \u72B6\u6001\uFF0C\u8BF7\u6267\u884C: apm daemon logs -n 200`
3591
+ );
3824
3592
  }
3825
- if (action === "restart" && !outputOk) {
3826
- fail(`\u8FDC\u7A0B restart \u672A\u6210\u529F
3827
- \u8F93\u51FA: ${combined || "(\u7A7A)"}`);
3593
+ console.log(
3594
+ `[apm] ${PM2_APP_NAME} \u5DF2\u7531 PM2 \u542F\u52A8\uFF08server=${options.server?.trim() || cfg.baseUrl}\uFF09`
3595
+ );
3596
+ if (options.follow) {
3597
+ console.log(
3598
+ "[apm] \u6B63\u5728\u8DDF\u8E2A\u65E5\u5FD7\uFF08Ctrl+C \u4EC5\u9000\u51FA\u65E5\u5FD7\u67E5\u770B\uFF0C\u5B88\u62A4\u8FDB\u7A0B\u7EE7\u7EED\u8FD0\u884C\uFF09"
3599
+ );
3600
+ runDaemonLogs({ follow: true });
3601
+ return;
3828
3602
  }
3829
- return combined;
3603
+ console.log("[apm] \u67E5\u770B\u72B6\u6001: apm daemon status");
3604
+ console.log("[apm] \u67E5\u770B\u65E5\u5FD7: apm daemon logs -f");
3605
+ console.log("[apm] \u505C\u6B62: apm daemon stop");
3830
3606
  }
3831
- function stripAnsi(text) {
3832
- return text.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "");
3607
+ async function runDaemonStop() {
3608
+ pruneStaleConnectLock();
3609
+ if (!isPm2ConnectOnline()) {
3610
+ killConnectLockProcessIfAlive();
3611
+ forceReleaseConnectLock();
3612
+ console.log(`[apm] ${PM2_APP_NAME} \u672A\u5728\u8FD0\u884C`);
3613
+ return;
3614
+ }
3615
+ runPm2(["stop", resolvePm2ConnectName(findPm2ConnectProcess())], {
3616
+ inherit: true
3617
+ });
3618
+ for (let i = 0; i < 10; i += 1) {
3619
+ if (!isPm2ConnectOnline()) break;
3620
+ await delay2(500);
3621
+ }
3622
+ if (isPm2ConnectOnline()) {
3623
+ console.log(`[apm] \u4F18\u96C5\u505C\u6B62\u8D85\u65F6\uFF0C\u5F3A\u5236\u79FB\u9664 ${PM2_APP_NAME}\u2026`);
3624
+ runPm2(["delete", resolvePm2ConnectName(findPm2ConnectProcess())], {
3625
+ inherit: true
3626
+ });
3627
+ }
3628
+ killConnectLockProcessIfAlive();
3629
+ forceReleaseConnectLock();
3630
+ console.log(`[apm] ${PM2_APP_NAME} \u5DF2\u505C\u6B62`);
3833
3631
  }
3834
- async function getRunningJar(client, config) {
3835
- const script = buildRemoteStatusScript(
3836
- config.remoteAppDir,
3837
- config.startupJar
3838
- );
3839
- const combined = await runRemoteServiceScript(client, script, "status");
3840
- const text = stripAnsi(combined).trim().toLowerCase();
3841
- if (text.includes("not running")) {
3842
- return null;
3632
+ function killConnectLockProcessIfAlive() {
3633
+ pruneStaleConnectLock();
3634
+ const lock = readConnectLock();
3635
+ if (!lock || !isProcessAlive(lock.pid)) return;
3636
+ try {
3637
+ process.kill(lock.pid, "SIGTERM");
3638
+ } catch {
3639
+ forceReleaseConnectLock();
3640
+ return;
3843
3641
  }
3844
- if (text.includes("running")) {
3845
- return config.startupJar;
3642
+ if (isProcessAlive(lock.pid)) {
3643
+ try {
3644
+ process.kill(lock.pid, "SIGKILL");
3645
+ } catch {
3646
+ }
3846
3647
  }
3847
- return null;
3648
+ forceReleaseConnectLock();
3848
3649
  }
3849
- async function healthCheckService(client, config) {
3850
- log("\u5065\u5EB7\u68C0\u67E5...");
3851
- const script = buildRemoteHealthScript(
3852
- config.healthCheckPort,
3853
- config.healthCheckContext,
3854
- config.healthCheckTimeout
3855
- );
3856
- await runRemoteServiceScript(client, script, "health");
3650
+ async function runDaemonRestart(options) {
3651
+ pruneStaleConnectLock();
3652
+ const lock = readConnectLock();
3653
+ if (lock && isProcessAlive(lock.pid) && !isPm2ConnectOnline()) {
3654
+ console.error(
3655
+ `[apm] \u5DF2\u6709\u524D\u53F0 apm connect \u5728\u8FD0\u884C (pid=${lock.pid})\uFF0C\u8BF7\u5148\u505C\u6B62\u540E\u518D\u542F\u52A8\u5B88\u62A4\u8FDB\u7A0B`
3656
+ );
3657
+ process.exit(1);
3658
+ }
3659
+ await runUpdate();
3660
+ await prepareEcosystem(options.server);
3661
+ removePm2ConnectProcesses();
3662
+ runPm2(["start", PM2_ECOSYSTEM_PATH, "--update-env"], { inherit: true });
3663
+ console.log(`[apm] ${PM2_APP_NAME} \u5DF2\u91CD\u542F`);
3857
3664
  }
3858
- async function restartRemoteService(client, config) {
3859
- const script = buildRemoteRestartScript(config.remoteAppDir);
3860
- await runRemoteServiceScript(client, script, "restart");
3665
+ async function runDaemonDelete() {
3666
+ ensureGlobalPm2();
3667
+ const app = findPm2ConnectProcess();
3668
+ if (!app?.name) {
3669
+ console.log(`[apm] ${PM2_APP_NAME} \u672A\u5728 PM2 \u4E2D\u6CE8\u518C`);
3670
+ return;
3671
+ }
3672
+ runPm2(["delete", app.name], { inherit: true });
3673
+ forceReleaseConnectLock();
3674
+ console.log(`[apm] ${app.name} \u5DF2\u4ECE PM2 \u79FB\u9664`);
3861
3675
  }
3862
- function listAllLibFilesForArchive(libDir) {
3863
- return readdirSync5(libDir).filter((name) => name.endsWith(".jar")).sort().map((jarName) => ({
3864
- path: path3.join(libDir, jarName),
3865
- arcname: jarName,
3866
- reason: "\u4EC5\u6253\u5305\u5F52\u6863"
3867
- }));
3676
+ async function runDaemonStatus() {
3677
+ ensureGlobalPm2();
3678
+ const app = findPm2ConnectProcess();
3679
+ if (!app?.name) {
3680
+ printConnectNotRunningHint();
3681
+ return;
3682
+ }
3683
+ runPm2(["describe", app.name], { inherit: true });
3868
3684
  }
3869
- async function runWisdomBackendDeploy(options) {
3870
- const projectRoot = path3.resolve(options.projectRoot ?? process.cwd());
3871
- const config = options.config;
3872
- const packOnly = Boolean(options.packOnly);
3873
- log(`=== \u81EA\u52A8\u90E8\u7F72: ${config.projectName} ===`);
3874
- log(`\u914D\u7F6E\u6587\u4EF6: ${path3.join(workspaceApmDir(), "apm.config.json")}`);
3875
- log(`\u9879\u76EE\u6839\u76EE\u5F55: ${projectRoot}`);
3876
- log(
3877
- `\u90E8\u7F72\u6A21\u5F0F: ${config.mode}${config.mode === "full" ? "\uFF08\u5168\u91CF JAR\uFF0C\u8DF3\u8FC7 lib \u68C0\u67E5\uFF09" : "\uFF08\u589E\u91CF lib \u66F4\u65B0\uFF09"}${packOnly ? "\uFF1B\u4EC5\u6253\u5305\uFF08\u4E0D\u4E0A\u4F20\u8FDC\u7A0B\uFF09" : ""}`
3878
- );
3879
- runMavenBuild(projectRoot, config.mavenLocalRepo, {
3880
- source: config.mavenLocalRepoSource,
3881
- sourceDetail: config.mavenLocalRepoSourceDetail
3882
- });
3883
- if (packOnly) {
3884
- const uploadWithoutRunId = !resolveDeploymentRunIdFromEnv();
3885
- if (config.mode === "full") {
3886
- const mainJar = locateMainJar(projectRoot);
3887
- const archiveZipPath = await createUpdatePackage(
3888
- [
3889
- {
3890
- path: mainJar,
3891
- arcname: path3.basename(mainJar),
3892
- reason: "\u5168\u91CF JAR \u5F52\u6863"
3893
- }
3894
- ],
3895
- `.deploy-archive-${config.projectName}.jar.zip`
3896
- );
3897
- await uploadDeployArtifactZip({
3898
- zipPath: archiveZipPath,
3899
- projectName: config.projectName,
3900
- kind: "backend",
3901
- uploadWithoutRunId
3902
- });
3903
- } else {
3904
- const libDir = locateLibDir(projectRoot);
3905
- const entries = listAllLibFilesForArchive(libDir);
3906
- if (entries.length === 0) {
3907
- fail("lib \u76EE\u5F55\u4E0B\u6CA1\u6709\u53EF\u5F52\u6863\u7684 JAR");
3908
- }
3909
- const zipPath = await createUpdatePackage(entries, config.packageName);
3910
- await uploadDeployArtifactZip({
3911
- zipPath,
3912
- projectName: config.projectName,
3913
- kind: "backend",
3914
- uploadWithoutRunId
3915
- });
3916
- }
3917
- log("\u4EC5\u6253\u5305\u5B8C\u6210\uFF08\u672A\u4E0A\u4F20\u8FDC\u7A0B\u670D\u52A1\u5668\uFF09");
3685
+ async function runDaemonLogs(options) {
3686
+ ensureGlobalPm2();
3687
+ const app = findPm2ConnectProcess();
3688
+ if (!app?.name) {
3689
+ printConnectNotRunningHint();
3918
3690
  return;
3919
3691
  }
3920
- const conn = await connectSsh(config);
3921
- try {
3922
- if (config.mode === "full") {
3923
- const mainJar = locateMainJar(projectRoot);
3924
- await uploadFullJar(conn.sftp, mainJar, config);
3925
- const archiveZipPath = await createUpdatePackage(
3926
- [
3927
- {
3928
- path: mainJar,
3929
- arcname: path3.basename(mainJar),
3930
- reason: "\u5168\u91CF JAR \u5F52\u6863"
3931
- }
3932
- ],
3933
- `.deploy-archive-${config.projectName}.jar.zip`
3934
- );
3935
- await uploadDeployArtifactZip({
3936
- zipPath: archiveZipPath,
3937
- projectName: config.projectName,
3938
- kind: "backend"
3939
- });
3940
- log("\u91CD\u542F\u670D\u52A1...");
3941
- await restartRemoteService(conn.client, config);
3942
- await healthCheckService(conn.client, config);
3943
- log("\u90E8\u7F72\u5B8C\u6210");
3944
- return;
3945
- }
3946
- const libDir = locateLibDir(projectRoot);
3947
- let manifest = loadManifest3();
3948
- const remoteLibStats = await getRemoteFileStats(
3949
- conn.sftp,
3950
- config.remoteLibDir
3951
- );
3952
- log("\u6536\u96C6 JAR \u66F4\u65B0...");
3953
- const libUploadEntries = listLibFilesToUpload(
3954
- libDir,
3955
- remoteLibStats,
3956
- projectRoot,
3957
- manifest
3958
- );
3959
- let updated = 0;
3960
- if (libUploadEntries.length > 0) {
3961
- const zipPath = await createUpdatePackage(
3962
- libUploadEntries,
3963
- config.packageName
3964
- );
3965
- const remoteZipPath = await uploadUpdatePackage(
3966
- conn.sftp,
3967
- zipPath,
3968
- config
3969
- );
3970
- await uploadDeployArtifactZip({
3971
- zipPath,
3972
- projectName: config.projectName,
3973
- kind: "backend"
3974
- });
3975
- log("\u8FDC\u7A0B\u89E3\u538B lib \u76EE\u5F55\uFF08\u4EC5\u8986\u76D6\u5DF2\u6709 JAR\uFF09...");
3976
- updated = await extractUpdatePackageOnRemote(
3977
- conn.client,
3978
- config,
3979
- remoteZipPath
3980
- );
3981
- } else {
3982
- log("\u65E0 JAR \u9700\u8981\u66F4\u65B0\uFF0C\u8DF3\u8FC7\u66F4\u65B0\u5305\u4E0A\u4F20");
3983
- }
3984
- const runningJar = await getRunningJar(conn.client, config);
3985
- let needRestart = updated > 0;
3986
- if (!needRestart && !runningJar) {
3987
- log("\u670D\u52A1\u672A\u8FD0\u884C\uFF0C\u9700\u8981\u542F\u52A8");
3988
- needRestart = true;
3989
- } else if (!needRestart) {
3990
- log("\u6CA1\u6709\u6587\u4EF6\u9700\u8981\u66F4\u65B0\uFF0C\u8DF3\u8FC7\u91CD\u542F");
3991
- }
3992
- if (needRestart) {
3993
- log("\u91CD\u542F\u670D\u52A1...");
3994
- await restartRemoteService(conn.client, config);
3995
- }
3996
- await healthCheckService(conn.client, config);
3997
- if (libUploadEntries.length > 0) {
3998
- manifest = updateManifestEntries(manifest, libUploadEntries, projectRoot);
3999
- saveManifest3(manifest);
4000
- }
4001
- } finally {
4002
- await closeSsh(conn);
3692
+ const args = ["logs", app.name, "--lines", String(options.lines ?? 100)];
3693
+ if (!options.follow) {
3694
+ args.push("--nostream");
4003
3695
  }
4004
- log("\u90E8\u7F72\u5B8C\u6210");
3696
+ runPm2(args, { inherit: true });
4005
3697
  }
4006
3698
 
4007
3699
  // src/commands/deploy/internal/deploy-shell-env.ts
4008
- import { spawnSync as spawnSync4 } from "node:child_process";
4009
- import { dirname as dirname4 } from "node:path";
4010
-
4011
- // src/commands/daemon.ts
4012
- import { spawnSync as spawnSync3 } from "child_process";
4013
- import { setTimeout as delay } from "node:timers/promises";
4014
- import { existsSync as existsSync14, mkdirSync as mkdirSync7, unlinkSync as unlinkSync2, writeFileSync as writeFileSync12 } from "fs";
4015
- import { join as join15 } from "path";
4016
-
4017
- // src/commands/connect-lock.ts
4018
- import {
4019
- existsSync as existsSync13,
4020
- mkdirSync as mkdirSync6,
4021
- readFileSync as readFileSync12,
4022
- unlinkSync,
4023
- writeFileSync as writeFileSync11
4024
- } from "fs";
4025
- import { join as join14 } from "path";
4026
- var CONNECT_LOCK_PATH = join14(APM_CONFIG_DIR, "connect.lock");
4027
- function isProcessAlive(pid) {
4028
- if (!Number.isInteger(pid) || pid <= 0) return false;
4029
- try {
4030
- process.kill(pid, 0);
4031
- return true;
4032
- } catch {
4033
- return false;
3700
+ var PATH_SEP = process.platform === "win32" ? ";" : ":";
3701
+ var useNpmShell3 = process.platform === "win32";
3702
+ function resolveNpmGlobalBin2() {
3703
+ const result = spawnSync3(useNpmShell3 ? "npm.cmd" : "npm", ["bin", "-g"], {
3704
+ encoding: "utf8",
3705
+ shell: useNpmShell3,
3706
+ stdio: ["ignore", "pipe", "pipe"]
3707
+ });
3708
+ if (result.status !== 0) {
3709
+ return null;
3710
+ }
3711
+ const bin = result.stdout?.toString().trim();
3712
+ return bin || null;
3713
+ }
3714
+ function prependPath(pathValue, segment) {
3715
+ if (!segment) {
3716
+ return pathValue ?? "";
3717
+ }
3718
+ if (!pathValue) {
3719
+ return segment;
4034
3720
  }
4035
- }
4036
- function sleepSync(ms) {
4037
- const deadline = Date.now() + ms;
4038
- while (Date.now() < deadline) {
3721
+ const segments = pathValue.split(PATH_SEP);
3722
+ if (segments.includes(segment)) {
3723
+ return pathValue;
4039
3724
  }
3725
+ return `${segment}${PATH_SEP}${pathValue}`;
4040
3726
  }
4041
- function waitForPm2LockHandoff(timeoutMs = 5e3) {
4042
- const deadline = Date.now() + timeoutMs;
4043
- while (Date.now() < deadline) {
4044
- pruneStaleConnectLock();
4045
- const lock = readConnectLock();
4046
- if (!lock || lock.mode !== "pm2" || lock.pid === process.pid || !isProcessAlive(lock.pid)) {
4047
- return;
4048
- }
4049
- sleepSync(100);
3727
+ function buildDeployShellEnv(extra) {
3728
+ const env = { ...process.env, ...extra };
3729
+ const pathSegments = [dirname4(process.execPath)];
3730
+ const globalBin = resolveNpmGlobalBin2();
3731
+ if (globalBin) {
3732
+ pathSegments.push(globalBin);
4050
3733
  }
4051
- }
4052
- function readConnectLock() {
4053
- if (!existsSync13(CONNECT_LOCK_PATH)) return null;
4054
3734
  try {
4055
- const raw = readFileSync12(CONNECT_LOCK_PATH, "utf8");
4056
- const parsed = JSON.parse(raw);
4057
- if (typeof parsed.pid !== "number" || parsed.mode !== "foreground" && parsed.mode !== "pm2" || typeof parsed.startedAt !== "string") {
4058
- return null;
4059
- }
4060
- return parsed;
3735
+ resolveApmEntryPath();
4061
3736
  } catch {
4062
- return null;
4063
3737
  }
4064
- }
4065
- function pruneStaleConnectLock() {
4066
- const lock = readConnectLock();
4067
- if (!lock) return;
4068
- if (isProcessAlive(lock.pid)) return;
4069
- try {
4070
- unlinkSync(CONNECT_LOCK_PATH);
4071
- } catch {
3738
+ for (const segment of pathSegments) {
3739
+ env.PATH = prependPath(env.PATH, segment);
4072
3740
  }
3741
+ return env;
4073
3742
  }
4074
- function acquireConnectLock(mode) {
4075
- pruneStaleConnectLock();
4076
- const existing = readConnectLock();
4077
- if (existing && isProcessAlive(existing.pid)) {
4078
- if (existing.pid === process.pid) {
4079
- return;
4080
- }
4081
- if (mode === "pm2" && existing.mode === "pm2") {
4082
- forceReleaseConnectLock();
4083
- } else {
4084
- console.error(
4085
- `[apm] \u5DF2\u6709 apm connect \u5728\u8FD0\u884C (pid=${existing.pid}, mode=${existing.mode})`
4086
- );
4087
- process.exit(1);
4088
- }
3743
+
3744
+ // src/commands/deploy/deploy-shell-run.ts
3745
+ function runDeployShellCommand(command, cwd, captureOutput) {
3746
+ const result = spawnSync4(command, {
3747
+ cwd,
3748
+ shell: true,
3749
+ env: buildDeployShellEnv(),
3750
+ encoding: "utf8",
3751
+ maxBuffer: DEPLOY_SPAWN_MAX_BUFFER_BYTES,
3752
+ stdio: captureOutput ? ["inherit", "pipe", "pipe"] : "inherit"
3753
+ });
3754
+ const stdout = captureOutput ? String(result.stdout ?? "") : "";
3755
+ const stderr = captureOutput ? String(result.stderr ?? "") : "";
3756
+ const output = [stdout, stderr].filter(Boolean).join("\n");
3757
+ if (captureOutput) {
3758
+ if (stdout) process.stdout.write(stdout);
3759
+ if (stderr) process.stderr.write(stderr);
4089
3760
  }
4090
- mkdirSync6(APM_CONFIG_DIR, { recursive: true });
4091
- const lock = {
4092
- pid: process.pid,
4093
- mode,
4094
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
4095
- };
4096
- writeFileSync11(
4097
- CONNECT_LOCK_PATH,
4098
- JSON.stringify(lock, null, 2) + "\n",
4099
- "utf8"
4100
- );
4101
- }
4102
- function releaseConnectLock() {
4103
- const lock = readConnectLock();
4104
- if (lock?.pid !== process.pid) return;
4105
- try {
4106
- unlinkSync(CONNECT_LOCK_PATH);
4107
- } catch {
3761
+ if (result.error) {
3762
+ throw new DeployExecutionError(
3763
+ `\u90E8\u7F72\u547D\u4EE4\u6267\u884C\u5931\u8D25: ${result.error.message}`,
3764
+ 1,
3765
+ output
3766
+ );
4108
3767
  }
4109
- }
4110
- function forceReleaseConnectLock() {
4111
- try {
4112
- if (existsSync13(CONNECT_LOCK_PATH)) {
4113
- unlinkSync(CONNECT_LOCK_PATH);
4114
- }
4115
- } catch {
3768
+ if (result.status !== 0) {
3769
+ throw new DeployExecutionError(
3770
+ `\u90E8\u7F72\u547D\u4EE4\u9000\u51FA\u7801 ${result.status ?? "unknown"}: ${command}`,
3771
+ result.status ?? 1,
3772
+ output
3773
+ );
4116
3774
  }
3775
+ return output;
4117
3776
  }
4118
3777
 
4119
- // src/commands/daemon.ts
4120
- var PM2_APP_NAME = "apm-connect";
4121
- var PM2_ECOSYSTEM_PATH = join15(
4122
- APM_CONFIG_DIR,
4123
- "connect.ecosystem.config.cjs"
4124
- );
4125
- var PM2_CONNECT_ENTRY_PATH = join15(APM_CONFIG_DIR, "connect.entry.cjs");
4126
- var PM2_CONNECT_LAUNCH_PATH = join15(
4127
- APM_CONFIG_DIR,
4128
- "connect.launch.json"
4129
- );
4130
- var LEGACY_PM2_ECOSYSTEM_PATH = join15(
4131
- APM_CONFIG_DIR,
4132
- "connect.ecosystem.cjs"
4133
- );
4134
- var LEGACY_PM2_APP_NAMES = ["connect.ecosystem"];
4135
- var CONNECT_ENTRY_SCRIPT = `'use strict';
4136
- const { spawnSync } = require('node:child_process');
4137
- const { readFileSync } = require('node:fs');
4138
- const { join } = require('node:path');
3778
+ // src/commands/deploy/internal/wisdom-auto-deploy.ts
3779
+ import { existsSync as existsSync15, readFileSync as readFileSync13, statSync as statSync7 } from "node:fs";
3780
+ import path4 from "node:path";
4139
3781
 
4140
- const launchConfigPath = join(__dirname, 'connect.launch.json');
4141
- let config;
4142
- try {
4143
- config = JSON.parse(readFileSync(launchConfigPath, 'utf8'));
4144
- } catch (err) {
4145
- console.error('[apm] \u65E0\u6CD5\u8BFB\u53D6 connect.launch.json:', err instanceof Error ? err.message : err);
4146
- process.exit(1);
4147
- }
3782
+ // src/commands/deploy/internal/wisdom-backend-deploy.ts
3783
+ import {
3784
+ existsSync as existsSync14,
3785
+ mkdirSync as mkdirSync7,
3786
+ readdirSync as readdirSync5,
3787
+ readFileSync as readFileSync12,
3788
+ statSync as statSync6,
3789
+ writeFileSync as writeFileSync12
3790
+ } from "node:fs";
3791
+ import { spawnSync as spawnSync5 } from "node:child_process";
3792
+ import path3 from "node:path";
3793
+ import { Client as Client2 } from "ssh2";
3794
+ import JSZip2 from "jszip";
3795
+ import SftpClient2 from "ssh2-sftp-client";
4148
3796
 
4149
- const nodeArgs = [config.apmScript, ...(config.connectArgs || [])];
4150
- const childEnv = {
4151
- ...process.env,
4152
- ...(config.env || {}),
4153
- APM_CONNECT_UNDER_PM2: '1',
4154
- };
3797
+ // src/commands/deploy/internal/wisdom-sftp.ts
3798
+ import { readdir as readdir2, readFile as readFile3, unlink, writeFile } from "node:fs/promises";
3799
+ import path2 from "node:path";
3800
+ import JSZip from "jszip";
3801
+ import SftpClient from "ssh2-sftp-client";
4155
3802
 
4156
- const result = spawnSync(config.nodePath, nodeArgs, {
4157
- cwd: config.cwd || undefined,
4158
- env: childEnv,
4159
- stdio: 'inherit',
4160
- windowsHide: true,
4161
- });
3803
+ // src/commands/deploy/internal/deploy-artifact-minio.ts
3804
+ import { readFile as readFile2 } from "node:fs/promises";
4162
3805
 
4163
- if (result.error) {
4164
- console.error('[apm] connect \u542F\u52A8\u5931\u8D25:', result.error.message);
4165
- process.exit(1);
4166
- }
4167
- process.exit(result.status ?? 0);
4168
- `;
4169
- function resolveConnectArgs(server) {
4170
- const args = ["connect"];
4171
- const trimmed = server?.trim();
4172
- if (trimmed) {
4173
- args.push("--server", trimmed.replace(/\/+$/, ""));
3806
+ // src/commands/deploy/internal/minio.ts
3807
+ import { statSync as statSync5 } from "node:fs";
3808
+ import { readdir, readFile } from "node:fs/promises";
3809
+ import path from "node:path";
3810
+ import * as Minio from "minio";
3811
+ var DEFAULT_MAX_FILE_SIZE_MB = 50;
3812
+ async function isDirectoryPath(dir) {
3813
+ try {
3814
+ const st = statSync5(dir);
3815
+ return st.isDirectory();
3816
+ } catch {
3817
+ return false;
4174
3818
  }
4175
- return args;
4176
3819
  }
4177
- var PM2_LAUNCH_ENV_KEYS = [
4178
- "PATH",
4179
- "HOME",
4180
- "USER",
4181
- "SHELL",
4182
- "LANG",
4183
- "LC_ALL",
4184
- "LC_CTYPE",
4185
- "NVM_DIR",
4186
- "NVM_BIN",
4187
- "NVM_INC",
4188
- "FNM_DIR",
4189
- "VOLTA_HOME",
4190
- "PNPM_HOME",
4191
- "npm_config_prefix",
4192
- "NODE_HOME"
4193
- ];
4194
- function collectPm2LaunchEnv() {
4195
- const env = {};
4196
- for (const key of PM2_LAUNCH_ENV_KEYS) {
4197
- const value = process.env[key]?.trim();
4198
- if (value) {
4199
- env[key] = value;
3820
+ function sanitizeRelativePath(rel) {
3821
+ const norm = rel.replace(/\\/g, "/").replace(/^\/+/, "");
3822
+ const segments = norm.split("/").filter(Boolean);
3823
+ for (const s of segments) {
3824
+ if (s === "." || s === "..") {
3825
+ throw new Error(`\u975E\u6CD5\u76F8\u5BF9\u8DEF\u5F84\u7247\u6BB5\uFF1A${s}`);
4200
3826
  }
4201
3827
  }
4202
- return env;
3828
+ return segments.join("/");
4203
3829
  }
4204
- function buildConnectPm2Ecosystem(options) {
4205
- const env = {};
4206
- const baseUrl = options.baseUrl?.trim().replace(/\/+$/, "");
4207
- if (baseUrl) {
4208
- env.AI_PM_SERVER = baseUrl;
4209
- }
4210
- const app = {
4211
- name: PM2_APP_NAME,
4212
- script: options.entryScript,
4213
- cwd: APM_CONFIG_DIR,
4214
- autorestart: true,
4215
- min_uptime: "10s",
4216
- max_restarts: 10,
4217
- restart_delay: 3e3,
4218
- exp_backoff_restart_delay: 1e3,
4219
- max_memory_restart: "1G",
4220
- kill_timeout: 5e3,
4221
- shutdown_with_message: true,
4222
- instances: 1,
4223
- exec_mode: "fork",
4224
- env
4225
- };
4226
- if (process.platform === "win32") {
4227
- app.windowsHide = true;
3830
+ async function collectFiles(root) {
3831
+ const out = [];
3832
+ async function walk(dir, prefix) {
3833
+ const entries = await readdir(dir, { withFileTypes: true });
3834
+ for (const e of entries) {
3835
+ const name = e.name;
3836
+ if (name === "." || name === "..") {
3837
+ continue;
3838
+ }
3839
+ const abs = path.join(dir, name);
3840
+ const rel = prefix ? `${prefix}/${name}` : name;
3841
+ if (e.isDirectory()) {
3842
+ await walk(abs, rel);
3843
+ } else if (e.isFile()) {
3844
+ const st = statSync5(abs);
3845
+ out.push({
3846
+ absPath: abs,
3847
+ relativePath: rel.replace(/\\/g, "/"),
3848
+ size: st.size
3849
+ });
3850
+ }
3851
+ }
4228
3852
  }
4229
- return {
4230
- apps: [app]
4231
- };
3853
+ await walk(root, "");
3854
+ out.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
3855
+ return out;
4232
3856
  }
4233
- function formatConnectPm2EcosystemFile(ecosystem) {
4234
- return `module.exports = ${JSON.stringify(ecosystem, null, 2)};
4235
- `;
3857
+ async function readArtifactFile(absPath) {
3858
+ return readFile(absPath);
4236
3859
  }
4237
- var useNpmShell2 = process.platform === "win32";
4238
- function runNpm2(args, options = {}) {
4239
- return spawnSync3(useNpmShell2 ? "npm.cmd" : "npm", args, {
4240
- ...options,
4241
- shell: useNpmShell2
4242
- });
3860
+ function toMB(bytes) {
3861
+ return Math.round(bytes / (1024 * 1024) * 1e3) / 1e3;
4243
3862
  }
4244
- function resolveNpmGlobalBin() {
4245
- const binResult = runNpm2(["bin", "-g"], {
4246
- encoding: "utf8",
4247
- stdio: ["ignore", "pipe", "pipe"]
4248
- });
4249
- if (binResult.status !== 0) return null;
4250
- return binResult.stdout?.toString().trim() || null;
3863
+ var MIME = {
3864
+ ".html": "text/html; charset=utf-8",
3865
+ ".css": "text/css; charset=utf-8",
3866
+ ".js": "application/javascript; charset=utf-8",
3867
+ ".json": "application/json; charset=utf-8",
3868
+ ".svg": "image/svg+xml",
3869
+ ".png": "image/png",
3870
+ ".jpg": "image/jpeg",
3871
+ ".jpeg": "image/jpeg",
3872
+ ".gif": "image/gif",
3873
+ ".webp": "image/webp",
3874
+ ".woff": "font/woff",
3875
+ ".woff2": "font/woff2",
3876
+ ".ttf": "font/ttf",
3877
+ ".ico": "image/x-icon",
3878
+ ".txt": "text/plain; charset=utf-8",
3879
+ ".map": "application/json"
3880
+ };
3881
+ function detectMimeType(filePath) {
3882
+ const ext = path.extname(filePath).toLowerCase();
3883
+ return MIME[ext] ?? "";
4251
3884
  }
4252
- function resolvePm2FromNpmRoot() {
4253
- const rootResult = runNpm2(["root", "-g"], {
4254
- encoding: "utf8",
4255
- stdio: ["ignore", "pipe", "pipe"]
4256
- });
4257
- if (rootResult.status !== 0) return null;
4258
- const root = rootResult.stdout?.toString().trim();
4259
- if (!root) return null;
4260
- for (const rel of ["pm2/bin/pm2", "pm2/bin/pm2.js"]) {
4261
- const candidate = join15(root, ...rel.split("/"));
4262
- if (existsSync14(candidate)) {
4263
- return candidate;
3885
+ var MinioClient = class {
3886
+ inner;
3887
+ constructor(opts) {
3888
+ const endPoint = opts.endPoint.replace(/^https?:\/\//i, "").split("/")[0] ?? opts.endPoint;
3889
+ this.inner = new Minio.Client({
3890
+ endPoint,
3891
+ port: opts.port,
3892
+ useSSL: opts.useSSL,
3893
+ accessKey: opts.accessKey,
3894
+ secretKey: opts.secretKey
3895
+ });
3896
+ }
3897
+ async ensureBucket(bucket) {
3898
+ const exists = await this.inner.bucketExists(bucket);
3899
+ if (!exists) {
3900
+ await this.inner.makeBucket(bucket);
4264
3901
  }
4265
3902
  }
4266
- return null;
4267
- }
4268
- function isPm2NodeScript(path13) {
4269
- return path13.endsWith(".js") || /[\\/]pm2[\\/]bin[\\/]pm2$/.test(path13);
4270
- }
4271
- function buildPm2SpawnTarget(binPath) {
4272
- if (isPm2NodeScript(binPath)) {
4273
- return {
4274
- command: process.execPath,
4275
- prefixArgs: [binPath],
4276
- displayPath: binPath
3903
+ async deleteObjectsByPrefix(bucket, prefix) {
3904
+ const objectsStream = this.inner.listObjectsV2(bucket, prefix, true);
3905
+ const keys = [];
3906
+ await new Promise((resolve5, reject) => {
3907
+ objectsStream.on("data", (obj) => {
3908
+ if (obj.name) {
3909
+ keys.push(obj.name);
3910
+ }
3911
+ });
3912
+ objectsStream.on("error", reject);
3913
+ objectsStream.on("end", resolve5);
3914
+ });
3915
+ const chunkSize = 500;
3916
+ for (let i = 0; i < keys.length; i += chunkSize) {
3917
+ const chunk = keys.slice(i, i + chunkSize);
3918
+ await this.inner.removeObjects(
3919
+ bucket,
3920
+ chunk.map((name) => name)
3921
+ );
3922
+ }
3923
+ }
3924
+ async putObject(bucket, objectKey, body, meta) {
3925
+ await this.inner.putObject(bucket, objectKey, body, body.length, meta);
3926
+ }
3927
+ /** 匿名可读当前桶全部对象(便于静态站点直链) */
3928
+ async setBucketPublicRead(bucket) {
3929
+ const policy = {
3930
+ Version: "2012-10-17",
3931
+ Statement: [
3932
+ {
3933
+ Effect: "Allow",
3934
+ Principal: { AWS: ["*"] },
3935
+ Action: ["s3:GetObject"],
3936
+ Resource: [`arn:aws:s3:::${bucket}/*`]
3937
+ }
3938
+ ]
4277
3939
  };
3940
+ await this.inner.setBucketPolicy(bucket, JSON.stringify(policy));
4278
3941
  }
4279
- return {
4280
- command: binPath,
4281
- prefixArgs: [],
4282
- displayPath: binPath
4283
- };
3942
+ };
3943
+
3944
+ // src/commands/deploy/internal/deploy-artifact-minio.ts
3945
+ var APM_DEPLOYMENT_RUN_ID_ENV = "APM_DEPLOYMENT_RUN_ID";
3946
+ function formatDeployArtifactTimestamp(date = /* @__PURE__ */ new Date()) {
3947
+ const pad = (n) => String(n).padStart(2, "0");
3948
+ return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`;
4284
3949
  }
4285
- function spawnPm2Target(target, args, options = {}) {
4286
- const useShell = useNpmShell2 && target.prefixArgs.length === 0;
4287
- return spawnSync3(target.command, [...target.prefixArgs, ...args], {
4288
- encoding: "utf8",
4289
- env: process.env,
4290
- shell: useShell,
4291
- windowsHide: useNpmShell2,
4292
- ...options
4293
- });
3950
+ function sanitizeDeployProjectName(name) {
3951
+ const trimmed = name.trim().replace(/\\/g, "/");
3952
+ const base = trimmed.split("/").filter(Boolean).pop() ?? trimmed;
3953
+ try {
3954
+ return sanitizeRelativePath(base.replace(/[/\\:*?"<>|]/g, "_"));
3955
+ } catch {
3956
+ return "project";
3957
+ }
4294
3958
  }
4295
- function spawnPm2At(binPath, args, options = {}) {
4296
- return spawnPm2Target(buildPm2SpawnTarget(binPath), args, options);
3959
+ function buildDeployArtifactFileName(kind, projectName, timestamp = formatDeployArtifactTimestamp()) {
3960
+ const safeName = sanitizeDeployProjectName(projectName);
3961
+ const suffix = kind === "frontend" ? "dist.zip" : "jar.zip";
3962
+ return `${timestamp}-${safeName}.${suffix}`;
4297
3963
  }
4298
- function verifyPm2Bin(pm2Bin) {
4299
- if (!pm2Bin.trim() || !existsSync14(pm2Bin)) {
4300
- return false;
4301
- }
4302
- const result = spawnPm2At(pm2Bin, ["--version"], {
4303
- stdio: ["ignore", "pipe", "pipe"]
4304
- });
4305
- return !result.error && result.status === 0;
3964
+ function buildDeployArtifactObjectKey(projectName, fileName) {
3965
+ const safeProject = sanitizeDeployProjectName(projectName);
3966
+ return `deploy/${safeProject}/${fileName}`;
4306
3967
  }
4307
- function collectPm2Candidates() {
4308
- const candidates = [];
4309
- const globalBin = resolveNpmGlobalBin();
4310
- if (globalBin) {
4311
- if (useNpmShell2) {
4312
- candidates.push(join15(globalBin, "pm2.cmd"));
4313
- candidates.push(join15(globalBin, "pm2"));
4314
- } else {
4315
- candidates.push(join15(globalBin, "pm2"));
4316
- }
3968
+ function resolveDeploymentRunIdFromEnv() {
3969
+ const raw = process.env[APM_DEPLOYMENT_RUN_ID_ENV]?.trim();
3970
+ return raw || null;
3971
+ }
3972
+ async function fetchDeployArtifactStorage(api) {
3973
+ return api.cli.getDeployArtifactStorage(void 0);
3974
+ }
3975
+ async function uploadDeployArtifactZip(options) {
3976
+ const deploymentRunId = options.deploymentRunId ?? resolveDeploymentRunIdFromEnv();
3977
+ if (!deploymentRunId && !options.uploadWithoutRunId) {
3978
+ return null;
4317
3979
  }
4318
- const fromRoot = resolvePm2FromNpmRoot();
4319
- if (fromRoot) {
4320
- candidates.push(fromRoot);
3980
+ const cfg = await tryReadApmConfig();
3981
+ if (!cfg || !resolveApiKey(cfg)) {
3982
+ console.warn("[apm] \u672A\u767B\u5F55\uFF0C\u8DF3\u8FC7 MinIO \u90E8\u7F72\u4EA7\u7269\u5F52\u6863");
3983
+ return null;
4321
3984
  }
4322
- const whichResult = spawnSync3(useNpmShell2 ? "where" : "which", ["pm2"], {
4323
- encoding: "utf8",
4324
- shell: useNpmShell2,
4325
- env: process.env,
4326
- stdio: ["ignore", "pipe", "pipe"]
4327
- });
4328
- if (whichResult.status === 0) {
4329
- const fromPath = whichResult.stdout?.toString().trim().split(/\r?\n/).map((line) => line.trim()).filter(Boolean) ?? [];
4330
- if (useNpmShell2) {
4331
- fromPath.sort((a, b) => {
4332
- const aCmd = a.toLowerCase().endsWith(".cmd") ? 0 : 1;
4333
- const bCmd = b.toLowerCase().endsWith(".cmd") ? 0 : 1;
4334
- return aCmd - bCmd;
3985
+ const api = options.api ?? createApmApiClient(cfg);
3986
+ let storage;
3987
+ try {
3988
+ storage = await fetchDeployArtifactStorage(api);
3989
+ } catch (error) {
3990
+ const detail = error instanceof Error ? error.message : String(error);
3991
+ console.warn(`[apm] \u83B7\u53D6 MinIO \u914D\u7F6E\u5931\u8D25\uFF0C\u8DF3\u8FC7\u90E8\u7F72\u4EA7\u7269\u5F52\u6863: ${detail}`);
3992
+ return null;
3993
+ }
3994
+ const fileName = buildDeployArtifactFileName(
3995
+ options.kind,
3996
+ options.projectName,
3997
+ options.timestamp
3998
+ );
3999
+ const objectKey = buildDeployArtifactObjectKey(options.projectName, fileName);
4000
+ let body;
4001
+ try {
4002
+ body = await readFile2(options.zipPath);
4003
+ } catch (error) {
4004
+ const detail = error instanceof Error ? error.message : String(error);
4005
+ console.warn(`[apm] \u8BFB\u53D6\u90E8\u7F72\u4EA7\u7269 zip \u5931\u8D25\uFF0C\u8DF3\u8FC7 MinIO \u5F52\u6863: ${detail}`);
4006
+ return null;
4007
+ }
4008
+ try {
4009
+ const client = new MinioClient({
4010
+ endPoint: storage.endpoint,
4011
+ port: storage.port,
4012
+ useSSL: storage.useSsl,
4013
+ accessKey: storage.accessKey,
4014
+ secretKey: storage.secretKey
4015
+ });
4016
+ await client.ensureBucket(storage.bucket);
4017
+ await client.putObject(storage.bucket, objectKey, body, {
4018
+ "Content-Type": "application/zip"
4019
+ });
4020
+ console.error(
4021
+ `[apm] \u672C\u5730\u76F4\u4F20 MinIO\uFF08\u4E0D\u7ECF\u5E73\u53F0\u670D\u52A1\u5668\uFF09: ${storage.endpoint}:${storage.port}/${storage.bucket}/${objectKey} (${(body.length / 1024 / 1024).toFixed(
4022
+ 2
4023
+ )} MB)`
4024
+ );
4025
+ } catch (error) {
4026
+ const detail = error instanceof Error ? error.message : String(error);
4027
+ console.warn(`[apm] MinIO \u90E8\u7F72\u4EA7\u7269\u5F52\u6863\u5931\u8D25: ${detail}`);
4028
+ return null;
4029
+ }
4030
+ if (deploymentRunId) {
4031
+ try {
4032
+ await api.cli.attachTaskDeploymentArtifact({
4033
+ id: deploymentRunId,
4034
+ artifactObjectKey: objectKey,
4035
+ artifactFileName: fileName
4335
4036
  });
4037
+ console.error(
4038
+ `[apm] \u5DF2\u7ED1\u5B9A\u90E8\u7F72\u4EA7\u7269\u5230\u8BB0\u5F55 id=${deploymentRunId} file=${fileName}`
4039
+ );
4040
+ } catch (error) {
4041
+ const detail = error instanceof Error ? error.message : String(error);
4042
+ console.warn(`[apm] \u7ED1\u5B9A\u90E8\u7F72\u4EA7\u7269\u5230\u8BB0\u5F55\u5931\u8D25: ${detail}`);
4043
+ return null;
4336
4044
  }
4337
- candidates.push(...fromPath);
4338
4045
  }
4339
- return candidates;
4046
+ return {
4047
+ objectKey,
4048
+ fileName,
4049
+ bucket: storage.bucket
4050
+ };
4340
4051
  }
4341
- function findGlobalPm2() {
4342
- const seen = /* @__PURE__ */ new Set();
4343
- for (const candidate of collectPm2Candidates()) {
4344
- const key = useNpmShell2 ? candidate.toLowerCase() : candidate;
4345
- if (seen.has(key)) continue;
4346
- seen.add(key);
4347
- if (verifyPm2Bin(candidate)) {
4348
- return candidate;
4052
+
4053
+ // src/commands/deploy/internal/wisdom-sftp.ts
4054
+ async function addDirToZip(dir, zipFolder) {
4055
+ const entries = await readdir2(dir, { withFileTypes: true });
4056
+ for (const entry of entries) {
4057
+ const fullPath = path2.join(dir, entry.name);
4058
+ if (entry.isDirectory()) {
4059
+ const folder = zipFolder.folder(entry.name);
4060
+ if (folder) {
4061
+ await addDirToZip(fullPath, folder);
4062
+ }
4063
+ } else {
4064
+ const content = await readFile3(fullPath);
4065
+ zipFolder.file(entry.name, content);
4349
4066
  }
4350
4067
  }
4351
- return null;
4352
4068
  }
4353
- function installGlobalPm2() {
4354
- console.log("[apm] \u672A\u68C0\u6D4B\u5230\u5168\u5C40 pm2\uFF0C\u6B63\u5728\u5B89\u88C5 npm install -g pm2 \u2026");
4355
- const result = runNpm2(["install", "-g", "pm2"], {
4356
- encoding: "utf8",
4357
- stdio: "inherit"
4069
+ async function zipDirectory(distDir, zipPath) {
4070
+ console.error(`\u538B\u7F29\u76EE\u5F55: ${distDir}`);
4071
+ const zip = new JSZip();
4072
+ await addDirToZip(distDir, zip);
4073
+ const content = await zip.generateAsync({
4074
+ type: "nodebuffer",
4075
+ compression: "DEFLATE",
4076
+ compressionOptions: { level: 6 }
4358
4077
  });
4359
- if (result.status !== 0) {
4360
- console.error("[apm] \u5B89\u88C5 pm2 \u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u6267\u884C: npm install -g pm2");
4361
- process.exit(result.status ?? 1);
4362
- }
4078
+ await writeFile(zipPath, content);
4079
+ const sizeMb = (content.length / 1024 / 1024).toFixed(2);
4080
+ console.error(`\u5DF2\u751F\u6210: ${zipPath} (${sizeMb} MB)`);
4081
+ return content.length;
4363
4082
  }
4364
- function readPm2Version(pm2Bin) {
4365
- const result = spawnPm2At(pm2Bin, ["--version"], {
4366
- stdio: ["ignore", "pipe", "pipe"]
4367
- });
4368
- if (result.error || result.status !== 0) {
4369
- return null;
4370
- }
4371
- return result.stdout?.toString().trim() || null;
4083
+ var SFTP_UPLOAD_MAX_ATTEMPTS = 3;
4084
+ var SFTP_FAST_PUT_OPTIONS = {
4085
+ chunkSize: 64 * 1024,
4086
+ concurrency: 4
4087
+ };
4088
+ function buildSftpConnectOptions(settings) {
4089
+ return {
4090
+ host: settings.host,
4091
+ port: settings.port,
4092
+ username: settings.username,
4093
+ password: settings.password,
4094
+ readyTimeout: 3e4,
4095
+ tryKeyboard: true,
4096
+ keepaliveInterval: 1e4,
4097
+ keepaliveCountMax: 3
4098
+ };
4372
4099
  }
4373
- function logPm2Ready(pm2Bin, installed2) {
4374
- const version = readPm2Version(pm2Bin);
4375
- const versionSuffix = version ? ` ${version}` : "";
4376
- if (installed2) {
4377
- console.log(`[apm] \u5168\u5C40 pm2 \u5DF2\u5B89\u88C5${versionSuffix}: ${pm2Bin}`);
4378
- } else {
4379
- console.log(`[apm] \u5DF2\u68C0\u6D4B\u5230\u5168\u5C40 pm2${versionSuffix}: ${pm2Bin}`);
4380
- }
4100
+ async function sleep(ms) {
4101
+ await new Promise((resolve5) => setTimeout(resolve5, ms));
4381
4102
  }
4382
- function isPm2DaemonOutOfDate(output) {
4383
- return /In-memory PM2 is out-of-date/i.test(output);
4103
+ async function uploadZipWithRetry(settings, localZip, remoteZipPath) {
4104
+ let lastError;
4105
+ for (let attempt = 1; attempt <= SFTP_UPLOAD_MAX_ATTEMPTS; attempt++) {
4106
+ const sftp = new SftpClient();
4107
+ try {
4108
+ if (attempt > 1) {
4109
+ console.error(
4110
+ `SFTP \u4E0A\u4F20\u91CD\u8BD5 (${attempt}/${SFTP_UPLOAD_MAX_ATTEMPTS})...`
4111
+ );
4112
+ }
4113
+ await sftp.connect(buildSftpConnectOptions(settings));
4114
+ await ensureRemoteDir(sftp, settings.remotePath);
4115
+ await sftp.fastPut(localZip, remoteZipPath, SFTP_FAST_PUT_OPTIONS);
4116
+ return sftp;
4117
+ } catch (err) {
4118
+ lastError = err;
4119
+ try {
4120
+ await sftp.end();
4121
+ } catch {
4122
+ }
4123
+ if (attempt < SFTP_UPLOAD_MAX_ATTEMPTS) {
4124
+ const message = err instanceof Error ? err.message : String(err);
4125
+ const delaySec = attempt * 2;
4126
+ console.error(`SFTP \u4E0A\u4F20\u5931\u8D25 (${message})\uFF0C${delaySec}s \u540E\u91CD\u8BD5...`);
4127
+ await sleep(delaySec * 1e3);
4128
+ }
4129
+ }
4130
+ }
4131
+ throw lastError;
4384
4132
  }
4385
- function ensurePm2DaemonUpdated(pm2Bin) {
4386
- const probe = spawnPm2At(pm2Bin, ["jlist"], {
4387
- stdio: ["ignore", "pipe", "pipe"]
4388
- });
4389
- const combined = `${probe.stdout ?? ""}
4390
- ${probe.stderr ?? ""}`;
4391
- if (!isPm2DaemonOutOfDate(combined)) {
4392
- return;
4133
+ async function ensureRemoteDir(sftp, dir) {
4134
+ const parts = dir.replace(/\\/g, "/").split("/").filter(Boolean);
4135
+ let current = dir.startsWith("/") ? "" : ".";
4136
+ for (const part of parts) {
4137
+ current = current ? `${current}/${part}` : `/${part}`;
4138
+ try {
4139
+ await sftp.mkdir(current, true);
4140
+ } catch {
4141
+ }
4393
4142
  }
4394
- console.log("[apm] \u68C0\u6D4B\u5230 PM2 \u5185\u5B58\u7248\u672C\u4E0E\u672C\u5730\u4E0D\u4E00\u81F4\uFF0C\u6B63\u5728\u6267\u884C pm2 update \u2026");
4395
- const update = spawnPm2At(pm2Bin, ["update"], {
4396
- stdio: "inherit"
4143
+ }
4144
+ function execCommand(client, command) {
4145
+ return new Promise((resolve5, reject) => {
4146
+ client.exec(command, (err, stream) => {
4147
+ if (err) return reject(err);
4148
+ let stdout = "";
4149
+ let stderr = "";
4150
+ stream.on("close", (code) => {
4151
+ if (code !== 0) {
4152
+ reject(new Error(`\u8FDC\u7A0B\u547D\u4EE4\u5931\u8D25 (${code}): ${stderr || stdout}`));
4153
+ return;
4154
+ }
4155
+ resolve5(stdout);
4156
+ }).on("data", (data) => {
4157
+ stdout += data.toString();
4158
+ });
4159
+ stream.stderr.on("data", (data) => {
4160
+ stderr += data.toString();
4161
+ });
4162
+ });
4397
4163
  });
4398
- if (update.error || update.status !== 0) {
4399
- console.error("[apm] pm2 update \u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u6267\u884C: pm2 update");
4400
- }
4401
4164
  }
4402
- function ensureGlobalPm2(options) {
4403
- const existing = findGlobalPm2();
4404
- if (existing) {
4405
- ensurePm2DaemonUpdated(existing);
4406
- if (!options?.quiet) {
4407
- logPm2Ready(existing, false);
4165
+ function shellSingleQuote(value) {
4166
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
4167
+ }
4168
+ function buildClearRemoteDirExceptZipCommand(target) {
4169
+ const normalized = target.replace(/\\/g, "/").replace(/\/$/, "");
4170
+ const quotedTarget = shellSingleQuote(normalized);
4171
+ const script = [
4172
+ `T=${quotedTarget}`,
4173
+ "S=$(mktemp -d)",
4174
+ 'while IFS= read -r -d "" z; do r="${z#${T}/}"',
4175
+ 'mkdir -p "${S}/$(dirname "$r")"',
4176
+ 'mv "$z" "${S}/${r}"',
4177
+ 'done < <(find "$T" -mindepth 1 -type f -iname "*.zip" -print0)',
4178
+ 'rm -rf "${T}"/*',
4179
+ 'while IFS= read -r -d "" r; do r="${r#./}"',
4180
+ 'mkdir -p "${T}/$(dirname "$r")"',
4181
+ 'mv "${S}/${r}" "${T}/${r}"',
4182
+ 'done < <(cd "$S" 2>/dev/null && find . -type f -print0)',
4183
+ 'rm -rf "$S"'
4184
+ ].join("; ");
4185
+ return `bash -c ${shellSingleQuote(script)}`;
4186
+ }
4187
+ async function uploadAndMaybeExtract(settings, localZip, extract) {
4188
+ const remoteZipPath = `${settings.remotePath.replace(/\/$/, "")}/dist.zip`;
4189
+ console.error(
4190
+ `\u8FDE\u63A5 ${settings.username}@${settings.host}:${settings.port} ...`
4191
+ );
4192
+ const sftp = await uploadZipWithRetry(settings, localZip, remoteZipPath);
4193
+ try {
4194
+ console.error(` \u2713 ${localZip} -> ${remoteZipPath}`);
4195
+ if (extract) {
4196
+ const target = settings.remotePath.replace(/\/$/, "");
4197
+ const client = sftp.client;
4198
+ const clearCmd = buildClearRemoteDirExceptZipCommand(target);
4199
+ console.error(`\u8FDC\u7A0B\u6E05\u7406\u89E3\u538B\u76EE\u5F55: ${clearCmd}`);
4200
+ await execCommand(client, clearCmd);
4201
+ const unzipCmd = `unzip -o "${remoteZipPath}" -d "${target}"`;
4202
+ console.error(`\u8FDC\u7A0B\u89E3\u538B: ${unzipCmd}`);
4203
+ await execCommand(client, unzipCmd);
4204
+ console.error("\u8FDC\u7A0B\u89E3\u538B\u5B8C\u6210!");
4408
4205
  }
4409
- return existing;
4206
+ console.error(extract ? "\u90E8\u7F72\u5B8C\u6210!" : "\u4E0A\u4F20\u5B8C\u6210!");
4207
+ } finally {
4208
+ await sftp.end();
4410
4209
  }
4411
- installGlobalPm2();
4412
- const installed2 = findGlobalPm2();
4413
- if (!installed2) {
4414
- const globalBin = resolveNpmGlobalBin();
4415
- const moduleEntry = resolvePm2FromNpmRoot();
4416
- console.error(
4417
- "[apm] \u5B89\u88C5 pm2 \u540E\u4ECD\u65E0\u6CD5\u627E\u5230\u53EF\u6267\u884C\u6587\u4EF6\uFF0C\u8BF7\u624B\u52A8\u6267\u884C: npm install -g pm2"
4418
- );
4419
- if (globalBin) {
4420
- console.error(`[apm] npm \u5168\u5C40 bin: ${globalBin}`);
4421
- }
4422
- if (moduleEntry) {
4423
- console.error(`[apm] \u5DF2\u68C0\u6D4B\u5230 pm2 \u6A21\u5757: ${moduleEntry}`);
4424
- console.error(
4425
- "[apm] \u82E5 pm2 --version \u53EF\u7528\uFF0C\u8BF7\u68C0\u67E5 Node/npm \u5168\u5C40\u5B89\u88C5\u6743\u9650\u6216 PATH"
4210
+ }
4211
+ async function runWisdomSftpDeploy(params) {
4212
+ const zipPath = path2.join(params.localDir, "..", ".deploy-sftp-dist.zip");
4213
+ const resolvedZipPath = path2.resolve(zipPath);
4214
+ const packOnly = Boolean(params.packOnly);
4215
+ let zipSizeBytes = 0;
4216
+ let artifactUploaded = false;
4217
+ try {
4218
+ zipSizeBytes = await zipDirectory(params.localDir, resolvedZipPath);
4219
+ if (!packOnly) {
4220
+ await uploadAndMaybeExtract(
4221
+ params.settings,
4222
+ resolvedZipPath,
4223
+ params.extract
4426
4224
  );
4225
+ } else {
4226
+ console.error("[apm] \u4EC5\u6253\u5305\u6A21\u5F0F\uFF1A\u8DF3\u8FC7 SFTP \u8FDC\u7A0B\u4E0A\u4F20");
4427
4227
  }
4428
- process.exit(1);
4429
- }
4430
- ensurePm2DaemonUpdated(installed2);
4431
- if (!options?.quiet) {
4432
- logPm2Ready(installed2, true);
4433
- }
4434
- return installed2;
4435
- }
4436
- function resolveApmEntryPath(entryArg = process.argv[1]) {
4437
- const fromArgv = entryArg?.trim();
4438
- if (fromArgv && existsSync14(fromArgv)) {
4439
- return fromArgv;
4440
- }
4441
- const npmResult = spawnSync3(useNpmShell2 ? "npm.cmd" : "npm", ["root", "-g"], {
4442
- encoding: "utf8",
4443
- shell: useNpmShell2,
4444
- stdio: ["ignore", "pipe", "pipe"]
4445
- });
4446
- if (npmResult.status === 0) {
4447
- const globalRoot = npmResult.stdout?.toString().trim();
4448
- if (globalRoot) {
4449
- const candidate = join15(globalRoot, CLI_PACKAGE_NAME, "dist", "index.js");
4450
- if (existsSync14(candidate)) {
4451
- return candidate;
4452
- }
4228
+ const artifact = await uploadDeployArtifactZip({
4229
+ zipPath: resolvedZipPath,
4230
+ projectName: params.projectName,
4231
+ kind: "frontend",
4232
+ uploadWithoutRunId: packOnly && !resolveDeploymentRunIdFromEnv()
4233
+ });
4234
+ artifactUploaded = artifact !== null;
4235
+ if (packOnly && artifactUploaded) {
4236
+ console.error("\u6253\u5305\u5E76\u4E0A\u4F20 MinIO \u5B8C\u6210\uFF08\u672A\u4E0A\u4F20\u8FDC\u7A0B\u670D\u52A1\u5668\uFF09");
4237
+ }
4238
+ } finally {
4239
+ try {
4240
+ await unlink(resolvedZipPath);
4241
+ console.error(`\u5DF2\u6E05\u7406\u672C\u5730\u4E34\u65F6\u6587\u4EF6: ${resolvedZipPath}`);
4242
+ } catch {
4453
4243
  }
4454
4244
  }
4455
- if (fromArgv) {
4456
- return fromArgv;
4457
- }
4458
- console.error("[apm] \u65E0\u6CD5\u89E3\u6790 apm \u5165\u53E3\u8DEF\u5F84");
4245
+ return {
4246
+ ok: true,
4247
+ localDir: params.localDir,
4248
+ host: params.settings.host,
4249
+ remotePath: params.settings.remotePath,
4250
+ zipSizeBytes,
4251
+ extracted: packOnly ? false : params.extract,
4252
+ packOnly,
4253
+ artifactUploaded
4254
+ };
4255
+ }
4256
+
4257
+ // src/commands/deploy/internal/wisdom-backend-deploy.ts
4258
+ var SPRINGBOOT_JAVA_OPTS = "-XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError -Xms512M -Xmx1G";
4259
+ var MAVEN_MODULE = "jeecg-module-system/jeecg-system-start";
4260
+ var MAVEN_PROFILE = "dev";
4261
+ function log(message) {
4262
+ const now = /* @__PURE__ */ new Date();
4263
+ const hh = String(now.getHours()).padStart(2, "0");
4264
+ const mm = String(now.getMinutes()).padStart(2, "0");
4265
+ const ss = String(now.getSeconds()).padStart(2, "0");
4266
+ console.error(`[${hh}:${mm}:${ss}] ${message}`);
4267
+ }
4268
+ function fail(message) {
4269
+ log(`ERROR: ${message}`);
4459
4270
  process.exit(1);
4460
4271
  }
4461
- function isRunningUnderPm2() {
4462
- if (process.env.APM_CONNECT_UNDER_PM2 === "1") {
4463
- return true;
4464
- }
4465
- if (process.env.pm_id === void 0) {
4466
- return false;
4467
- }
4468
- const name = process.env.name;
4469
- return name === PM2_APP_NAME || name !== void 0 && LEGACY_PM2_APP_NAMES.includes(name);
4272
+ function expandPath(pathStr) {
4273
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
4274
+ return path3.resolve(pathStr.replace(/^~(?=\/|\\|$)/, home));
4470
4275
  }
4471
- function isConnectPm2Process(app) {
4472
- if (app.name === PM2_APP_NAME) {
4473
- return true;
4276
+ function quoteForShell(value) {
4277
+ if (process.platform === "win32") {
4278
+ return `"${value.replace(/"/g, '""')}"`;
4474
4279
  }
4475
- return app.name !== void 0 && LEGACY_PM2_APP_NAMES.includes(app.name);
4280
+ return shellSingleQuote(value);
4476
4281
  }
4477
- function listPm2Processes() {
4478
- try {
4479
- const raw = runPm2Json(["jlist"]);
4480
- return JSON.parse(raw);
4481
- } catch {
4482
- return [];
4282
+ function formatMavenLocalRepoArg(repoPath) {
4283
+ if (process.platform === "win32") {
4284
+ return `-Dmaven.repo.local=${quoteForShell(repoPath)}`;
4483
4285
  }
4286
+ return `-Dmaven.repo.local=${repoPath}`;
4484
4287
  }
4485
- function findPm2ConnectProcess() {
4486
- return listPm2Processes().find(isConnectPm2Process) ?? null;
4288
+ function deployCacheDir() {
4289
+ return path3.join(workspaceApmDir(), "deploy", ".deploy_cache");
4487
4290
  }
4488
- function resolvePm2ConnectName(app) {
4489
- return app?.name ?? PM2_APP_NAME;
4291
+ function manifestFilePath() {
4292
+ return path3.join(deployCacheDir(), "manifest.json");
4490
4293
  }
4491
- function deletePm2AppIfExists(name) {
4492
- const pm2Bin = findGlobalPm2();
4493
- if (!pm2Bin) {
4494
- return;
4294
+ function getTargetDir(projectRoot) {
4295
+ return path3.join(projectRoot, MAVEN_MODULE, "target");
4296
+ }
4297
+ function relativeKey(projectRoot, filePath) {
4298
+ return path3.relative(projectRoot, filePath).split(path3.sep).join("/");
4299
+ }
4300
+ function fileSignature(filePath) {
4301
+ const stat2 = statSync6(filePath);
4302
+ return { size: stat2.size, mtime: stat2.mtimeMs / 1e3 };
4303
+ }
4304
+ function loadManifest3() {
4305
+ const manifestPath2 = manifestFilePath();
4306
+ if (!existsSync14(manifestPath2)) {
4307
+ return {};
4495
4308
  }
4496
- ensurePm2DaemonUpdated(pm2Bin);
4497
- spawnPm2At(pm2Bin, ["delete", name], {
4498
- stdio: "ignore"
4499
- });
4309
+ return JSON.parse(readFileSync12(manifestPath2, "utf8"));
4500
4310
  }
4501
- function removePm2ConnectProcesses() {
4502
- const seen = /* @__PURE__ */ new Set();
4503
- for (const app of listPm2Processes()) {
4504
- if (!isConnectPm2Process(app) || !app.name || seen.has(app.name)) {
4505
- continue;
4506
- }
4507
- seen.add(app.name);
4508
- deletePm2AppIfExists(app.name);
4311
+ function saveManifest3(manifest) {
4312
+ const dir = deployCacheDir();
4313
+ mkdirSync7(dir, { recursive: true });
4314
+ writeFileSync12(manifestFilePath(), JSON.stringify(manifest, null, 2), "utf8");
4315
+ }
4316
+ function isProjectLibJar(jarName) {
4317
+ return jarName.startsWith("jeecg-");
4318
+ }
4319
+ function shouldUploadLibFile(localPath, remoteAttr, manifest, projectRoot) {
4320
+ if (!remoteAttr) {
4321
+ return [false, "\u8FDC\u7A0B\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7"];
4509
4322
  }
4510
- for (const legacyName of LEGACY_PM2_APP_NAMES) {
4511
- deletePm2AppIfExists(legacyName);
4323
+ const localSize = statSync6(localPath).size;
4324
+ const remoteSize = remoteAttr.size;
4325
+ if (localSize !== remoteSize) {
4326
+ return [true, `\u5927\u5C0F\u53D8\u5316 ${remoteSize} -> ${localSize}`];
4512
4327
  }
4513
- deletePm2AppIfExists(PM2_APP_NAME);
4328
+ if (isProjectLibJar(path3.basename(localPath)) && manifest) {
4329
+ const key = relativeKey(projectRoot, localPath);
4330
+ const current = fileSignature(localPath);
4331
+ const previous = manifest[key];
4332
+ if (!previous) {
4333
+ return [true, "\u9879\u76EE\u6A21\u5757\u672A\u8BB0\u5F55"];
4334
+ }
4335
+ if (previous.size !== current.size) {
4336
+ return [true, "\u9879\u76EE\u6A21\u5757\u5927\u5C0F\u53D8\u5316"];
4337
+ }
4338
+ if (previous.mtime < current.mtime) {
4339
+ return [true, "\u9879\u76EE\u6A21\u5757\u91CD\u65B0\u6784\u5EFA"];
4340
+ }
4341
+ }
4342
+ return [false, "\u5927\u5C0F\u4E00\u81F4\uFF0C\u8DF3\u8FC7"];
4514
4343
  }
4515
- function runPm2(args, options) {
4516
- const pm2Bin = ensureGlobalPm2({ quiet: true });
4517
- const result = spawnPm2At(pm2Bin, args, {
4518
- stdio: options?.inherit ? "inherit" : ["ignore", "pipe", "pipe"]
4519
- });
4520
- if (result.error) {
4521
- console.error("[apm] \u6267\u884C pm2 \u5931\u8D25:", result.error.message);
4522
- process.exit(1);
4344
+ function listLibFilesToUpload(localLibDir, remoteStats, projectRoot, manifest = null) {
4345
+ const entries = [];
4346
+ const jarFiles = readdirSync5(localLibDir).filter((name) => name.endsWith(".jar")).sort();
4347
+ for (const jarName of jarFiles) {
4348
+ const jarPath = path3.join(localLibDir, jarName);
4349
+ const remoteAttr = remoteStats.get(jarName);
4350
+ const [shouldUpload, reason] = shouldUploadLibFile(
4351
+ jarPath,
4352
+ remoteAttr,
4353
+ manifest,
4354
+ projectRoot
4355
+ );
4356
+ if (shouldUpload) {
4357
+ entries.push({ path: jarPath, arcname: jarName, reason });
4358
+ }
4523
4359
  }
4524
- if (result.status !== 0) {
4525
- const stderr = result.stderr?.toString().trim();
4526
- const stdout = result.stdout?.toString().trim();
4527
- const detail = stderr || stdout || `exit code ${result.status}`;
4528
- console.error(`[apm] pm2 ${args.join(" ")} \u5931\u8D25: ${detail}`);
4529
- process.exit(result.status ?? 1);
4360
+ return entries;
4361
+ }
4362
+ function updateManifestEntries(manifest, entries, projectRoot) {
4363
+ for (const entry of entries) {
4364
+ manifest[relativeKey(projectRoot, entry.path)] = fileSignature(entry.path);
4530
4365
  }
4366
+ return manifest;
4531
4367
  }
4532
- function runPm2Json(args) {
4533
- const pm2Bin = findGlobalPm2();
4534
- if (!pm2Bin) {
4535
- return "[]";
4368
+ async function createUpdatePackage(entries, packageName) {
4369
+ const dir = deployCacheDir();
4370
+ mkdirSync7(dir, { recursive: true });
4371
+ const zipPath = path3.join(dir, packageName);
4372
+ log(`\u521B\u5EFA\u66F4\u65B0\u5305: ${path3.basename(zipPath)}\uFF08${entries.length} \u4E2A\u6587\u4EF6\uFF09`);
4373
+ const zip = new JSZip2();
4374
+ for (const entry of entries) {
4375
+ const content = readFileSync12(entry.path);
4376
+ zip.file(entry.arcname, content);
4377
+ log(` \u6253\u5305: ${entry.arcname} (${entry.reason})`);
4536
4378
  }
4537
- ensurePm2DaemonUpdated(pm2Bin);
4538
- const result = spawnPm2At(pm2Bin, args, {
4539
- stdio: ["ignore", "pipe", "pipe"]
4379
+ const buffer = await zip.generateAsync({
4380
+ type: "nodebuffer",
4381
+ compression: "DEFLATE",
4382
+ compressionOptions: { level: 6 }
4540
4383
  });
4541
- if (result.status !== 0) return "[]";
4542
- return result.stdout?.toString() ?? "[]";
4543
- }
4544
- function isPm2ConnectOnline() {
4545
- const app = findPm2ConnectProcess();
4546
- if (!app) return false;
4547
- const status = app.pm2_env?.status;
4548
- return status === "online" || status === "launching";
4384
+ writeFileSync12(zipPath, buffer);
4385
+ return zipPath;
4549
4386
  }
4550
- function printConnectNotRunningHint() {
4551
- console.log(`[apm] ${PM2_APP_NAME} \u672A\u5728\u8FD0\u884C`);
4552
- console.log("[apm] \u542F\u52A8\u5B88\u62A4: apm connect --daemon");
4387
+ function getMvnExecutable() {
4388
+ const isWin = process.platform === "win32";
4389
+ const candidates = isWin ? ["mvn.cmd", "mvn.bat", "mvn"] : ["mvn"];
4390
+ for (const name of candidates) {
4391
+ const result = spawnSync5(isWin ? `where ${name}` : `which ${name}`, {
4392
+ encoding: "utf8",
4393
+ shell: true
4394
+ });
4395
+ if (result.status === 0 && result.stdout.trim()) {
4396
+ return result.stdout.trim().split(/\r?\n/)[0].trim();
4397
+ }
4398
+ }
4399
+ fail("\u672A\u627E\u5230 mvn \u547D\u4EE4\uFF0C\u8BF7\u786E\u8BA4 Maven \u5DF2\u5B89\u88C5\u5E76\u52A0\u5165 PATH");
4553
4400
  }
4554
- function assertConnectNotRunning() {
4555
- pruneStaleConnectLock();
4556
- const lock = readConnectLock();
4557
- if (lock && isProcessAlive(lock.pid) && lock.pid !== process.pid) {
4558
- console.error(
4559
- `[apm] \u5DF2\u6709 apm connect \u5728\u8FD0\u884C (pid=${lock.pid}, mode=${lock.mode})\uFF0C\u8BF7\u5148\u505C\u6B62\u540E\u518D\u542F\u52A8`
4401
+ function runMavenBuild(projectRoot, mavenLocalRepo, repoSource) {
4402
+ const mavenRepo = expandPath(mavenLocalRepo);
4403
+ const mvn = getMvnExecutable();
4404
+ const command = [
4405
+ quoteForShell(mvn),
4406
+ "clean",
4407
+ "package",
4408
+ `-P${MAVEN_PROFILE}`,
4409
+ formatMavenLocalRepoArg(mavenRepo),
4410
+ "-DskipTests"
4411
+ ].join(" ");
4412
+ log("\u5F00\u59CB Maven \u6784\u5EFA...");
4413
+ if (repoSource) {
4414
+ log(
4415
+ `Maven \u672C\u5730\u4ED3\u5E93: ${mavenRepo} (\u6765\u6E90: ${repoSource.source}/${repoSource.sourceDetail})`
4560
4416
  );
4561
- process.exit(1);
4417
+ } else {
4418
+ log(`Maven \u672C\u5730\u4ED3\u5E93: ${mavenRepo}`);
4562
4419
  }
4563
- if (!isRunningUnderPm2() && isPm2ConnectOnline()) {
4564
- console.error(
4565
- "[apm] \u5DF2\u6709 apm connect \u5B88\u62A4\u8FDB\u7A0B\u5728\u8FD0\u884C\uFF0C\u8BF7\u5148\u6267\u884C apm daemon stop"
4566
- );
4567
- process.exit(1);
4420
+ log(`\u6784\u5EFA\u76EE\u5F55: ${projectRoot}`);
4421
+ const result = spawnSync5(command, {
4422
+ cwd: projectRoot,
4423
+ stdio: "inherit",
4424
+ shell: true,
4425
+ env: process.env
4426
+ });
4427
+ if (result.status !== 0) {
4428
+ fail(`Maven \u6784\u5EFA\u5931\u8D25\uFF0C\u9000\u51FA\u7801: ${result.status ?? 1}`);
4568
4429
  }
4569
4430
  }
4570
- function resolveRuntimePaths() {
4571
- return {
4572
- apmScript: resolveApmEntryPath(),
4573
- nodePath: process.execPath
4574
- };
4431
+ function locateLibDir(projectRoot) {
4432
+ const targetDir = getTargetDir(projectRoot);
4433
+ if (!existsSync14(targetDir)) {
4434
+ fail(`\u6784\u5EFA\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728: ${targetDir}`);
4435
+ }
4436
+ const libDir = path3.join(targetDir, "lib");
4437
+ if (!existsSync14(libDir) || !statSync6(libDir).isDirectory()) {
4438
+ fail(`lib \u76EE\u5F55\u4E0D\u5B58\u5728: ${libDir}`);
4439
+ }
4440
+ const libJars = readdirSync5(libDir).filter((name) => name.endsWith(".jar"));
4441
+ if (libJars.length === 0) {
4442
+ fail(`lib \u76EE\u5F55\u4E0B\u6CA1\u6709\u4F9D\u8D56 JAR: ${libDir}`);
4443
+ }
4444
+ log(`\u5B9A\u4F4D lib \u4EA7\u7269: ${libJars.length} \u4E2A`);
4445
+ return libDir;
4575
4446
  }
4576
- function reexecConnectWithResolvedPath(options) {
4577
- const apmScript = resolveApmEntryPath();
4578
- const args = [apmScript, "connect"];
4579
- const server = options.server?.trim();
4580
- if (server) {
4581
- args.push("--server", server);
4447
+ function locateMainJar(projectRoot) {
4448
+ const targetDir = getTargetDir(projectRoot);
4449
+ if (!existsSync14(targetDir)) {
4450
+ fail(`\u6784\u5EFA\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728: ${targetDir}`);
4582
4451
  }
4583
- const result = spawnSync3(process.execPath, args, { stdio: "inherit" });
4584
- if (result.error) {
4585
- console.error("[apm] \u91CD\u542F connect \u5931\u8D25:", result.error.message);
4586
- process.exit(1);
4452
+ const jarFiles = readdirSync5(targetDir).filter((name) => name.endsWith(".jar") && !name.endsWith(".jar.original")).map((name) => path3.join(targetDir, name)).sort((a, b) => statSync6(b).mtimeMs - statSync6(a).mtimeMs);
4453
+ if (jarFiles.length === 0) {
4454
+ fail(`target \u76EE\u5F55\u4E0B\u6CA1\u6709\u4E3B JAR: ${targetDir}`);
4587
4455
  }
4588
- process.exit(result.status ?? 0);
4456
+ const mainJar = jarFiles[0];
4457
+ log(`\u5B9A\u4F4D\u4E3B JAR \u4EA7\u7269: ${path3.basename(mainJar)}`);
4458
+ return mainJar;
4589
4459
  }
4590
- async function handleConnectAfterUpdate(options) {
4591
- console.log("[apm] \u66F4\u65B0\u5B8C\u6210\uFF0C\u6B63\u5728\u4EE5\u65B0\u7248\u672C\u91CD\u65B0\u8FDE\u63A5\u2026");
4592
- await prepareEcosystem(options.server);
4593
- if (isRunningUnderPm2()) {
4594
- runPm2(["reload", PM2_ECOSYSTEM_PATH, "--update-env"], { inherit: true });
4595
- console.log("[apm] \u5DF2\u901A\u77E5 PM2 \u4F7F\u7528\u65B0\u7248\u672C\u91CD\u65B0\u52A0\u8F7D connect");
4596
- process.exit(0);
4460
+ async function connectSsh(config) {
4461
+ const client = new Client2();
4462
+ log(`\u8FDE\u63A5\u670D\u52A1\u5668 ${config.username}@${config.host}:${config.port}`);
4463
+ await new Promise((resolve5, reject) => {
4464
+ client.on("ready", () => resolve5()).on("error", (err) => reject(err)).connect({
4465
+ host: config.host,
4466
+ port: config.port,
4467
+ username: config.username,
4468
+ password: config.password,
4469
+ readyTimeout: 3e4,
4470
+ tryKeyboard: true
4471
+ });
4472
+ });
4473
+ const sftp = new SftpClient2();
4474
+ await sftp.connect({
4475
+ host: config.host,
4476
+ port: config.port,
4477
+ username: config.username,
4478
+ password: config.password,
4479
+ readyTimeout: 3e4,
4480
+ tryKeyboard: true
4481
+ });
4482
+ return { client, sftp };
4483
+ }
4484
+ async function closeSsh(conn) {
4485
+ try {
4486
+ await conn.sftp.end();
4487
+ } catch {
4597
4488
  }
4598
- reexecConnectWithResolvedPath(options);
4489
+ conn.client.end();
4599
4490
  }
4600
- async function resolveBaseUrl(server) {
4601
- if (server?.trim()) {
4602
- return server.trim().replace(/\/+$/, "");
4491
+ async function getRemoteFileStats(sftp, remoteDir) {
4492
+ const stats = /* @__PURE__ */ new Map();
4493
+ try {
4494
+ const listing = await sftp.list(remoteDir);
4495
+ for (const item of listing) {
4496
+ if (item.name.endsWith(".jar")) {
4497
+ stats.set(item.name, { filename: item.name, size: item.size });
4498
+ }
4499
+ }
4500
+ } catch {
4603
4501
  }
4604
- const cfg = await tryReadApmConfig();
4605
- return cfg?.baseUrl;
4502
+ return stats;
4606
4503
  }
4607
- function writeConnectLaunchFiles(options) {
4608
- mkdirSync7(APM_CONFIG_DIR, { recursive: true });
4609
- writeFileSync12(PM2_CONNECT_ENTRY_PATH, CONNECT_ENTRY_SCRIPT, "utf8");
4610
- const env = {
4611
- ...collectPm2LaunchEnv()
4612
- };
4613
- const baseUrl = options.baseUrl?.trim().replace(/\/+$/, "");
4614
- if (baseUrl) {
4615
- env.AI_PM_SERVER = baseUrl;
4504
+ async function uploadUpdatePackage(sftp, zipPath, config) {
4505
+ const remoteDir = config.remoteVueDistDir.replace(/\/$/, "");
4506
+ const remotePath = `${remoteDir}/${path3.basename(zipPath)}`;
4507
+ log(`\u4E0A\u4F20\u66F4\u65B0\u5305 -> ${remotePath}`);
4508
+ try {
4509
+ await sftp.fastPut(zipPath, remotePath);
4510
+ log("\u66F4\u65B0\u5305\u4E0A\u4F20\u6210\u529F");
4511
+ } catch (err) {
4512
+ fail(`\u66F4\u65B0\u5305\u4E0A\u4F20\u5931\u8D25: ${err instanceof Error ? err.message : err}`);
4616
4513
  }
4617
- const launch = {
4618
- nodePath: options.nodePath,
4619
- apmScript: options.apmScript,
4620
- connectArgs: options.connectArgs,
4621
- cwd: options.cwd,
4622
- env
4623
- };
4624
- writeFileSync12(
4625
- PM2_CONNECT_LAUNCH_PATH,
4626
- JSON.stringify(launch, null, 2) + "\n",
4627
- "utf8"
4628
- );
4514
+ return remotePath;
4629
4515
  }
4630
- function writeEcosystemFile(options) {
4631
- writeConnectLaunchFiles(options);
4632
- const ecosystem = buildConnectPm2Ecosystem({
4633
- entryScript: PM2_CONNECT_ENTRY_PATH,
4634
- baseUrl: options.baseUrl
4635
- });
4636
- writeFileSync12(
4637
- PM2_ECOSYSTEM_PATH,
4638
- formatConnectPm2EcosystemFile(ecosystem),
4639
- "utf8"
4640
- );
4641
- if (existsSync14(LEGACY_PM2_ECOSYSTEM_PATH)) {
4642
- try {
4643
- unlinkSync2(LEGACY_PM2_ECOSYSTEM_PATH);
4644
- } catch {
4645
- }
4516
+ async function uploadFullJar(sftp, localJarPath, config) {
4517
+ const remoteDir = config.remoteAppDir.replace(/\/$/, "");
4518
+ const remotePath = `${remoteDir}/${config.startupJar}`;
4519
+ log(`\u4E0A\u4F20\u5168\u91CF JAR (${path3.basename(localJarPath)}) -> ${remotePath}`);
4520
+ try {
4521
+ await sftp.mkdir(remoteDir, true);
4522
+ await sftp.fastPut(localJarPath, remotePath);
4523
+ log("\u5168\u91CF JAR \u4E0A\u4F20\u6210\u529F");
4524
+ } catch (err) {
4525
+ fail(`\u5168\u91CF JAR \u4E0A\u4F20\u5931\u8D25: ${err instanceof Error ? err.message : err}`);
4646
4526
  }
4647
4527
  }
4648
- async function prepareEcosystem(server) {
4649
- await ensureLoggedConfig();
4650
- const cfg = await ensureApmConfig();
4651
- const { apmScript, nodePath } = resolveRuntimePaths();
4652
- const connectArgs = resolveConnectArgs(server);
4653
- const baseUrl = await resolveBaseUrl(server);
4654
- writeEcosystemFile({
4655
- apmScript,
4656
- nodePath,
4657
- connectArgs,
4658
- baseUrl,
4659
- cwd: process.cwd()
4528
+ async function runRemoteCommand(client, command, options) {
4529
+ const check = options?.check ?? true;
4530
+ const stream = options?.stream ?? false;
4531
+ const label = options?.label ?? "\u8FDC\u7A0B\u547D\u4EE4";
4532
+ return new Promise((resolve5, reject) => {
4533
+ client.exec(command, (err, execStream) => {
4534
+ if (err) {
4535
+ reject(err);
4536
+ return;
4537
+ }
4538
+ let out = "";
4539
+ let errText = "";
4540
+ execStream.on("data", (data) => {
4541
+ const text = data.toString();
4542
+ out += text;
4543
+ if (stream) {
4544
+ process.stdout.write(text);
4545
+ }
4546
+ });
4547
+ execStream.stderr.on("data", (data) => {
4548
+ errText += data.toString();
4549
+ });
4550
+ execStream.on("close", (code) => {
4551
+ if (stream && out && !out.endsWith("\n")) {
4552
+ process.stdout.write("\n");
4553
+ }
4554
+ if (check && code !== 0) {
4555
+ const combined = `${out}
4556
+ ${errText}`.trim();
4557
+ fail(
4558
+ `${label}\u5931\u8D25 (exit ${code})` + (combined ? `
4559
+ \u8F93\u51FA: ${combined}` : "")
4560
+ );
4561
+ }
4562
+ resolve5({ exitCode: code, out: out.trim(), err: errText.trim() });
4563
+ });
4564
+ });
4660
4565
  });
4661
- return cfg;
4662
4566
  }
4663
- async function runDaemonStart(options) {
4664
- assertConnectNotRunning();
4665
- await runUpdate();
4666
- ensureGlobalPm2();
4667
- const cfg = await prepareEcosystem(options.server);
4668
- removePm2ConnectProcesses();
4669
- runPm2(["start", PM2_ECOSYSTEM_PATH, "--update-env"], { inherit: true });
4670
- await delay(1e3);
4671
- if (!isPm2ConnectOnline()) {
4672
- console.error(
4673
- `[apm] ${PM2_APP_NAME} \u542F\u52A8\u540E\u672A\u5904\u4E8E online \u72B6\u6001\uFF0C\u8BF7\u6267\u884C: apm daemon logs -n 200`
4674
- );
4675
- }
4676
- console.log(
4677
- `[apm] ${PM2_APP_NAME} \u5DF2\u7531 PM2 \u542F\u52A8\uFF08server=${options.server?.trim() || cfg.baseUrl}\uFF09`
4678
- );
4679
- if (options.follow) {
4680
- console.log(
4681
- "[apm] \u6B63\u5728\u8DDF\u8E2A\u65E5\u5FD7\uFF08Ctrl+C \u4EC5\u9000\u51FA\u65E5\u5FD7\u67E5\u770B\uFF0C\u5B88\u62A4\u8FDB\u7A0B\u7EE7\u7EED\u8FD0\u884C\uFF09"
4682
- );
4683
- runDaemonLogs({ follow: true });
4684
- return;
4685
- }
4686
- console.log("[apm] \u67E5\u770B\u72B6\u6001: apm daemon status");
4687
- console.log("[apm] \u67E5\u770B\u65E5\u5FD7: apm daemon logs -f");
4688
- console.log("[apm] \u505C\u6B62: apm daemon stop");
4567
+ function buildExtractUpdatePackageScript(remoteZipPath, remoteLibDir) {
4568
+ const quotedZip = shellSingleQuote(remoteZipPath);
4569
+ const quotedLib = shellSingleQuote(remoteLibDir);
4570
+ return `
4571
+ set -e
4572
+ TMP=$(mktemp -d)
4573
+ trap 'rm -rf "$TMP"' EXIT
4574
+ unzip -oq ${quotedZip} -d "$TMP"
4575
+ updated=0
4576
+ while IFS= read -r -d '' src; do
4577
+ name=$(basename "$src")
4578
+ dest=${quotedLib}/"$name"
4579
+ if [ -f "$dest" ]; then
4580
+ cp -f "$src" "$dest"
4581
+ echo "\u8986\u76D6: $name"
4582
+ updated=$((updated + 1))
4583
+ else
4584
+ echo "\u8DF3\u8FC7(\u8FDC\u7A0B\u4E0D\u5B58\u5728): $name"
4585
+ fi
4586
+ done < <(find "$TMP" -name '*.jar' -type f -print0)
4587
+ echo "UPDATED_COUNT=$updated"
4588
+ `.trim();
4689
4589
  }
4690
- async function runDaemonStop() {
4691
- pruneStaleConnectLock();
4692
- if (!isPm2ConnectOnline()) {
4693
- killConnectLockProcessIfAlive();
4694
- forceReleaseConnectLock();
4695
- console.log(`[apm] ${PM2_APP_NAME} \u672A\u5728\u8FD0\u884C`);
4696
- return;
4697
- }
4698
- runPm2(["stop", resolvePm2ConnectName(findPm2ConnectProcess())], {
4699
- inherit: true
4590
+ async function extractUpdatePackageOnRemote(client, config, remoteZipPath) {
4591
+ const script = buildExtractUpdatePackageScript(
4592
+ remoteZipPath,
4593
+ config.remoteLibDir
4594
+ );
4595
+ const { out } = await runRemoteCommand(client, script, {
4596
+ label: "\u8FDC\u7A0B\u89E3\u538B"
4700
4597
  });
4701
- for (let i = 0; i < 10; i += 1) {
4702
- if (!isPm2ConnectOnline()) break;
4703
- await delay(500);
4704
- }
4705
- if (isPm2ConnectOnline()) {
4706
- console.log(`[apm] \u4F18\u96C5\u505C\u6B62\u8D85\u65F6\uFF0C\u5F3A\u5236\u79FB\u9664 ${PM2_APP_NAME}\u2026`);
4707
- runPm2(["delete", resolvePm2ConnectName(findPm2ConnectProcess())], {
4708
- inherit: true
4709
- });
4598
+ const match = out.match(/UPDATED_COUNT=(\d+)/);
4599
+ if (!match) {
4600
+ fail(`\u8FDC\u7A0B\u89E3\u538B\u5931\u8D25\uFF0C\u672A\u83B7\u53D6\u66F4\u65B0\u6570\u91CF
4601
+ \u8F93\u51FA: ${out || "(\u7A7A)"}`);
4710
4602
  }
4711
- killConnectLockProcessIfAlive();
4712
- forceReleaseConnectLock();
4713
- console.log(`[apm] ${PM2_APP_NAME} \u5DF2\u505C\u6B62`);
4603
+ const updated = Number.parseInt(match[1], 10);
4604
+ log(`lib \u89E3\u538B\u5B8C\u6210: \u8986\u76D6 ${updated} \u4E2A`);
4605
+ return updated;
4714
4606
  }
4715
- function killConnectLockProcessIfAlive() {
4716
- pruneStaleConnectLock();
4717
- const lock = readConnectLock();
4718
- if (!lock || !isProcessAlive(lock.pid)) return;
4719
- try {
4720
- process.kill(lock.pid, "SIGTERM");
4721
- } catch {
4722
- forceReleaseConnectLock();
4723
- return;
4607
+ function springbootOutputIndicatesSuccess(action, combined) {
4608
+ const lower = combined.toLowerCase();
4609
+ if (action === "health") {
4610
+ return combined.includes("\u5065\u5EB7\u68C0\u67E5\u901A\u8FC7");
4724
4611
  }
4725
- if (isProcessAlive(lock.pid)) {
4726
- try {
4727
- process.kill(lock.pid, "SIGKILL");
4728
- } catch {
4729
- }
4612
+ if (action === "start" || action === "restart") {
4613
+ return combined.includes("is starting") || lower.includes("is running");
4730
4614
  }
4731
- forceReleaseConnectLock();
4732
- }
4733
- async function runDaemonRestart(options) {
4734
- pruneStaleConnectLock();
4735
- const lock = readConnectLock();
4736
- if (lock && isProcessAlive(lock.pid) && !isPm2ConnectOnline()) {
4737
- console.error(
4738
- `[apm] \u5DF2\u6709\u524D\u53F0 apm connect \u5728\u8FD0\u884C (pid=${lock.pid})\uFF0C\u8BF7\u5148\u505C\u6B62\u540E\u518D\u542F\u52A8\u5B88\u62A4\u8FDB\u7A0B`
4739
- );
4740
- process.exit(1);
4615
+ if (action === "stop") {
4616
+ return combined.includes("is stopping") || lower.includes("not running") || lower.includes("please check it");
4741
4617
  }
4742
- await runUpdate();
4743
- await prepareEcosystem(options.server);
4744
- removePm2ConnectProcesses();
4745
- runPm2(["start", PM2_ECOSYSTEM_PATH, "--update-env"], { inherit: true });
4746
- console.log(`[apm] ${PM2_APP_NAME} \u5DF2\u91CD\u542F`);
4747
- }
4748
- async function runDaemonDelete() {
4749
- ensureGlobalPm2();
4750
- const app = findPm2ConnectProcess();
4751
- if (!app?.name) {
4752
- console.log(`[apm] ${PM2_APP_NAME} \u672A\u5728 PM2 \u4E2D\u6CE8\u518C`);
4753
- return;
4618
+ if (action === "status") {
4619
+ return lower.includes("running") || lower.includes("not running");
4754
4620
  }
4755
- runPm2(["delete", app.name], { inherit: true });
4756
- forceReleaseConnectLock();
4757
- console.log(`[apm] ${app.name} \u5DF2\u4ECE PM2 \u79FB\u9664`);
4621
+ return true;
4758
4622
  }
4759
- async function runDaemonStatus() {
4760
- ensureGlobalPm2();
4761
- const app = findPm2ConnectProcess();
4762
- if (!app?.name) {
4763
- printConnectNotRunningHint();
4764
- return;
4765
- }
4766
- runPm2(["describe", app.name], { inherit: true });
4623
+ function buildRemoteStatusScript(remoteAppDir, appName) {
4624
+ const dir = shellSingleQuote(remoteAppDir);
4625
+ const jar = shellSingleQuote(appName);
4626
+ return `
4627
+ set -e
4628
+ cd ${dir}
4629
+ appName=${jar}
4630
+ appIds=$(ps -ef | grep java | grep "$appName" | awk '{print $2}')
4631
+ if [ -z "$appIds" ]; then
4632
+ echo -e "\\033[31m Not running \\033[0m"
4633
+ else
4634
+ echo -e "\\033[32m Running [$appIds] \\033[0m"
4635
+ fi
4636
+ `.trim();
4767
4637
  }
4768
- async function runDaemonLogs(options) {
4769
- ensureGlobalPm2();
4770
- const app = findPm2ConnectProcess();
4771
- if (!app?.name) {
4772
- printConnectNotRunningHint();
4773
- return;
4638
+ function buildRemoteRestartScript(remoteAppDir) {
4639
+ const dir = shellSingleQuote(remoteAppDir);
4640
+ const javaOpts = shellSingleQuote(SPRINGBOOT_JAVA_OPTS);
4641
+ return `
4642
+ set -e
4643
+ cd ${dir}
4644
+ releaseApp=$(ls -t | grep '.jar$' | head -n1)
4645
+ lastVersionApp=$(ls -t | grep '.jar$' | head -n2 | tail -n1)
4646
+ appName=$lastVersionApp
4647
+ appIds=$(ps -ef | grep java | grep "$appName" | awk '{print $2}')
4648
+ if [ -z "$appIds" ]; then
4649
+ echo "Maybe $appName not running, please check it..."
4650
+ else
4651
+ echo "The $appName is stopping..."
4652
+ echo "$appIds" | xargs kill
4653
+ fi
4654
+ for i in $(seq 15 -1 1); do
4655
+ echo -n "$i "
4656
+ sleep 1
4657
+ done
4658
+ echo 0
4659
+ if [ ! -d "backup" ]; then
4660
+ mkdir backup
4661
+ fi
4662
+ for i in $(ls | grep '.jar$' | grep -vFx "$releaseApp"); do
4663
+ echo "backup $i"
4664
+ mv "$i" backup/
4665
+ done
4666
+ appName=$releaseApp
4667
+ count=$(ps -ef | grep java | grep "$appName" | wc -l)
4668
+ if [ "$count" != "0" ]; then
4669
+ echo "Maybe $appName is running, please check it..."
4670
+ else
4671
+ echo "The $appName is starting..."
4672
+ nohup java -jar "./$appName" ${javaOpts} > nohup.out 2>&1 &
4673
+ fi
4674
+ `.trim();
4675
+ }
4676
+ function normalizeHealthContext(context) {
4677
+ let normalized = context.trim() || "/";
4678
+ if (!normalized.startsWith("/")) {
4679
+ normalized = `/${normalized}`;
4774
4680
  }
4775
- const args = ["logs", app.name, "--lines", String(options.lines ?? 100)];
4776
- if (!options.follow) {
4777
- args.push("--nostream");
4681
+ if (!normalized.endsWith("/")) {
4682
+ normalized = `${normalized}/`;
4778
4683
  }
4779
- runPm2(args, { inherit: true });
4684
+ return normalized;
4780
4685
  }
4781
-
4782
- // src/commands/deploy/internal/deploy-shell-env.ts
4783
- var PATH_SEP = process.platform === "win32" ? ";" : ":";
4784
- var useNpmShell3 = process.platform === "win32";
4785
- function resolveNpmGlobalBin2() {
4786
- const result = spawnSync4(useNpmShell3 ? "npm.cmd" : "npm", ["bin", "-g"], {
4787
- encoding: "utf8",
4788
- shell: useNpmShell3,
4789
- stdio: ["ignore", "pipe", "pipe"]
4686
+ function buildRemoteHealthScript(port, context, timeoutSecs) {
4687
+ const normalizedContext = normalizeHealthContext(context);
4688
+ const portStr = String(port);
4689
+ const timeoutStr = String(timeoutSecs);
4690
+ return `
4691
+ set -e
4692
+ port=${shellSingleQuote(portStr)}
4693
+ context=${shellSingleQuote(normalizedContext)}
4694
+ timeout=${shellSingleQuote(timeoutStr)}
4695
+ check_url="http://127.0.0.1:${portStr}${normalizedContext}"
4696
+ echo "\u5065\u5EB7\u68C0\u67E5: \${check_url} (\u8D85\u65F6 \${timeout}s)"
4697
+ deadline=$(($(date +%s) + timeout))
4698
+ attempt=0
4699
+ while [ $(date +%s) -lt $deadline ]; do
4700
+ attempt=$((attempt + 1))
4701
+ code=$(curl -s -o /dev/null -w "%{http_code}" "$check_url" 2>/dev/null || echo "000")
4702
+ code=$(echo "$code" | tail -n1 | tr -d '[:space:]')
4703
+ if [ \${#code} -ge 3 ]; then
4704
+ status=\${code:0:3}
4705
+ if echo "$status" | grep -qE '^[0-9]+$' && [ "$status" -ge 200 ] && [ "$status" -lt 500 ]; then
4706
+ echo -e "\\033[32m\u5065\u5EB7\u68C0\u67E5\u901A\u8FC7 (HTTP \${status})\\033[0m"
4707
+ exit 0
4708
+ fi
4709
+ fi
4710
+ remaining=$((deadline - $(date +%s)))
4711
+ if [ $remaining -lt 0 ]; then
4712
+ remaining=0
4713
+ fi
4714
+ echo "\u7B49\u5F85\u670D\u52A1\u542F\u52A8... \u7B2C \${attempt} \u6B21\uFF0C\u5269\u4F59 \${remaining}s"
4715
+ sleep 5
4716
+ done
4717
+ echo -e "\\033[31m\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6 (\${timeout}s): \${check_url}\\033[0m"
4718
+ exit 1
4719
+ `.trim();
4720
+ }
4721
+ async function runRemoteServiceScript(client, script, action) {
4722
+ const { exitCode, out, err } = await runRemoteCommand(client, script, {
4723
+ check: false,
4724
+ label: action === "status" ? "\u8FDC\u7A0B status" : action === "restart" ? "\u8FDC\u7A0B restart" : "\u5065\u5EB7\u68C0\u67E5"
4790
4725
  });
4791
- if (result.status !== 0) {
4792
- return null;
4726
+ const combined = `${out}
4727
+ ${err}`.trim();
4728
+ const outputOk = springbootOutputIndicatesSuccess(action, combined);
4729
+ if (action === "health") {
4730
+ if (exitCode !== 0 || !outputOk) {
4731
+ fail(`\u5065\u5EB7\u68C0\u67E5\u5931\u8D25
4732
+ \u8F93\u51FA: ${combined || "(\u7A7A)"}`);
4733
+ }
4734
+ return combined;
4793
4735
  }
4794
- const bin = result.stdout?.toString().trim();
4795
- return bin || null;
4796
- }
4797
- function prependPath(pathValue, segment) {
4798
- if (!segment) {
4799
- return pathValue ?? "";
4736
+ if (exitCode !== 0 && !outputOk) {
4737
+ fail(`\u8FDC\u7A0B ${action} \u5931\u8D25
4738
+ ${combined}`);
4800
4739
  }
4801
- if (!pathValue) {
4802
- return segment;
4740
+ if (action === "restart" && !outputOk) {
4741
+ fail(`\u8FDC\u7A0B restart \u672A\u6210\u529F
4742
+ \u8F93\u51FA: ${combined || "(\u7A7A)"}`);
4743
+ }
4744
+ return combined;
4745
+ }
4746
+ function stripAnsi(text) {
4747
+ return text.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "");
4748
+ }
4749
+ async function getRunningJar(client, config) {
4750
+ const script = buildRemoteStatusScript(
4751
+ config.remoteAppDir,
4752
+ config.startupJar
4753
+ );
4754
+ const combined = await runRemoteServiceScript(client, script, "status");
4755
+ const text = stripAnsi(combined).trim().toLowerCase();
4756
+ if (text.includes("not running")) {
4757
+ return null;
4803
4758
  }
4804
- const segments = pathValue.split(PATH_SEP);
4805
- if (segments.includes(segment)) {
4806
- return pathValue;
4759
+ if (text.includes("running")) {
4760
+ return config.startupJar;
4807
4761
  }
4808
- return `${segment}${PATH_SEP}${pathValue}`;
4762
+ return null;
4809
4763
  }
4810
- function buildDeployShellEnv(extra) {
4811
- const env = { ...process.env, ...extra };
4812
- const pathSegments = [dirname4(process.execPath)];
4813
- const globalBin = resolveNpmGlobalBin2();
4814
- if (globalBin) {
4815
- pathSegments.push(globalBin);
4764
+ async function healthCheckService(client, config) {
4765
+ log("\u5065\u5EB7\u68C0\u67E5...");
4766
+ const script = buildRemoteHealthScript(
4767
+ config.healthCheckPort,
4768
+ config.healthCheckContext,
4769
+ config.healthCheckTimeout
4770
+ );
4771
+ await runRemoteServiceScript(client, script, "health");
4772
+ }
4773
+ async function restartRemoteService(client, config) {
4774
+ const script = buildRemoteRestartScript(config.remoteAppDir);
4775
+ await runRemoteServiceScript(client, script, "restart");
4776
+ }
4777
+ function listAllLibFilesForArchive(libDir) {
4778
+ return readdirSync5(libDir).filter((name) => name.endsWith(".jar")).sort().map((jarName) => ({
4779
+ path: path3.join(libDir, jarName),
4780
+ arcname: jarName,
4781
+ reason: "\u4EC5\u6253\u5305\u5F52\u6863"
4782
+ }));
4783
+ }
4784
+ async function runWisdomBackendDeploy(options) {
4785
+ const projectRoot = path3.resolve(options.projectRoot ?? process.cwd());
4786
+ const config = options.config;
4787
+ const packOnly = Boolean(options.packOnly);
4788
+ log(`=== \u81EA\u52A8\u90E8\u7F72: ${config.projectName} ===`);
4789
+ log(`\u914D\u7F6E\u6587\u4EF6: ${path3.join(workspaceApmDir(), "apm.config.json")}`);
4790
+ log(`\u9879\u76EE\u6839\u76EE\u5F55: ${projectRoot}`);
4791
+ log(
4792
+ `\u90E8\u7F72\u6A21\u5F0F: ${config.mode}${config.mode === "full" ? "\uFF08\u5168\u91CF JAR\uFF0C\u8DF3\u8FC7 lib \u68C0\u67E5\uFF09" : "\uFF08\u589E\u91CF lib \u66F4\u65B0\uFF09"}${packOnly ? "\uFF1B\u4EC5\u6253\u5305\uFF08\u4E0D\u4E0A\u4F20\u8FDC\u7A0B\uFF09" : ""}`
4793
+ );
4794
+ runMavenBuild(projectRoot, config.mavenLocalRepo, {
4795
+ source: config.mavenLocalRepoSource,
4796
+ sourceDetail: config.mavenLocalRepoSourceDetail
4797
+ });
4798
+ if (packOnly) {
4799
+ const uploadWithoutRunId = !resolveDeploymentRunIdFromEnv();
4800
+ if (config.mode === "full") {
4801
+ const mainJar = locateMainJar(projectRoot);
4802
+ const archiveZipPath = await createUpdatePackage(
4803
+ [
4804
+ {
4805
+ path: mainJar,
4806
+ arcname: path3.basename(mainJar),
4807
+ reason: "\u5168\u91CF JAR \u5F52\u6863"
4808
+ }
4809
+ ],
4810
+ `.deploy-archive-${config.projectName}.jar.zip`
4811
+ );
4812
+ await uploadDeployArtifactZip({
4813
+ zipPath: archiveZipPath,
4814
+ projectName: config.projectName,
4815
+ kind: "backend",
4816
+ uploadWithoutRunId
4817
+ });
4818
+ } else {
4819
+ const libDir = locateLibDir(projectRoot);
4820
+ const entries = listAllLibFilesForArchive(libDir);
4821
+ if (entries.length === 0) {
4822
+ fail("lib \u76EE\u5F55\u4E0B\u6CA1\u6709\u53EF\u5F52\u6863\u7684 JAR");
4823
+ }
4824
+ const zipPath = await createUpdatePackage(entries, config.packageName);
4825
+ await uploadDeployArtifactZip({
4826
+ zipPath,
4827
+ projectName: config.projectName,
4828
+ kind: "backend",
4829
+ uploadWithoutRunId
4830
+ });
4831
+ }
4832
+ log("\u4EC5\u6253\u5305\u5B8C\u6210\uFF08\u672A\u4E0A\u4F20\u8FDC\u7A0B\u670D\u52A1\u5668\uFF09");
4833
+ return;
4816
4834
  }
4835
+ const conn = await connectSsh(config);
4817
4836
  try {
4818
- resolveApmEntryPath();
4819
- } catch {
4820
- }
4821
- for (const segment of pathSegments) {
4822
- env.PATH = prependPath(env.PATH, segment);
4837
+ if (config.mode === "full") {
4838
+ const mainJar = locateMainJar(projectRoot);
4839
+ await uploadFullJar(conn.sftp, mainJar, config);
4840
+ const archiveZipPath = await createUpdatePackage(
4841
+ [
4842
+ {
4843
+ path: mainJar,
4844
+ arcname: path3.basename(mainJar),
4845
+ reason: "\u5168\u91CF JAR \u5F52\u6863"
4846
+ }
4847
+ ],
4848
+ `.deploy-archive-${config.projectName}.jar.zip`
4849
+ );
4850
+ await uploadDeployArtifactZip({
4851
+ zipPath: archiveZipPath,
4852
+ projectName: config.projectName,
4853
+ kind: "backend"
4854
+ });
4855
+ log("\u91CD\u542F\u670D\u52A1...");
4856
+ await restartRemoteService(conn.client, config);
4857
+ await healthCheckService(conn.client, config);
4858
+ log("\u90E8\u7F72\u5B8C\u6210");
4859
+ return;
4860
+ }
4861
+ const libDir = locateLibDir(projectRoot);
4862
+ let manifest = loadManifest3();
4863
+ const remoteLibStats = await getRemoteFileStats(
4864
+ conn.sftp,
4865
+ config.remoteLibDir
4866
+ );
4867
+ log("\u6536\u96C6 JAR \u66F4\u65B0...");
4868
+ const libUploadEntries = listLibFilesToUpload(
4869
+ libDir,
4870
+ remoteLibStats,
4871
+ projectRoot,
4872
+ manifest
4873
+ );
4874
+ let updated = 0;
4875
+ if (libUploadEntries.length > 0) {
4876
+ const zipPath = await createUpdatePackage(
4877
+ libUploadEntries,
4878
+ config.packageName
4879
+ );
4880
+ const remoteZipPath = await uploadUpdatePackage(
4881
+ conn.sftp,
4882
+ zipPath,
4883
+ config
4884
+ );
4885
+ await uploadDeployArtifactZip({
4886
+ zipPath,
4887
+ projectName: config.projectName,
4888
+ kind: "backend"
4889
+ });
4890
+ log("\u8FDC\u7A0B\u89E3\u538B lib \u76EE\u5F55\uFF08\u4EC5\u8986\u76D6\u5DF2\u6709 JAR\uFF09...");
4891
+ updated = await extractUpdatePackageOnRemote(
4892
+ conn.client,
4893
+ config,
4894
+ remoteZipPath
4895
+ );
4896
+ } else {
4897
+ log("\u65E0 JAR \u9700\u8981\u66F4\u65B0\uFF0C\u8DF3\u8FC7\u66F4\u65B0\u5305\u4E0A\u4F20");
4898
+ }
4899
+ const runningJar = await getRunningJar(conn.client, config);
4900
+ let needRestart = updated > 0;
4901
+ if (!needRestart && !runningJar) {
4902
+ log("\u670D\u52A1\u672A\u8FD0\u884C\uFF0C\u9700\u8981\u542F\u52A8");
4903
+ needRestart = true;
4904
+ } else if (!needRestart) {
4905
+ log("\u6CA1\u6709\u6587\u4EF6\u9700\u8981\u66F4\u65B0\uFF0C\u8DF3\u8FC7\u91CD\u542F");
4906
+ }
4907
+ if (needRestart) {
4908
+ log("\u91CD\u542F\u670D\u52A1...");
4909
+ await restartRemoteService(conn.client, config);
4910
+ }
4911
+ await healthCheckService(conn.client, config);
4912
+ if (libUploadEntries.length > 0) {
4913
+ manifest = updateManifestEntries(manifest, libUploadEntries, projectRoot);
4914
+ saveManifest3(manifest);
4915
+ }
4916
+ } finally {
4917
+ await closeSsh(conn);
4823
4918
  }
4824
- return env;
4919
+ log("\u90E8\u7F72\u5B8C\u6210");
4825
4920
  }
4826
4921
 
4827
4922
  // src/commands/deploy/internal/wisdom-auto-deploy.ts
@@ -4866,30 +4961,6 @@ function resolveFrontendBuildCommand(env, cwd) {
4866
4961
  }
4867
4962
  return null;
4868
4963
  }
4869
- function runShellCommand(command, cwd, captureOutput) {
4870
- const result = spawnSync5(command, {
4871
- cwd,
4872
- shell: true,
4873
- env: buildDeployShellEnv(),
4874
- encoding: "utf8",
4875
- stdio: captureOutput ? ["inherit", "pipe", "pipe"] : "inherit"
4876
- });
4877
- const stdout = captureOutput ? String(result.stdout ?? "") : "";
4878
- const stderr = captureOutput ? String(result.stderr ?? "") : "";
4879
- const output = [stdout, stderr].filter(Boolean).join("\n");
4880
- if (captureOutput) {
4881
- if (stdout) process.stdout.write(stdout);
4882
- if (stderr) process.stderr.write(stderr);
4883
- }
4884
- if (result.status !== 0) {
4885
- throw new DeployExecutionError(
4886
- `\u90E8\u7F72\u547D\u4EE4\u9000\u51FA\u7801 ${result.status ?? "unknown"}: ${command}`,
4887
- result.status ?? 1,
4888
- output
4889
- );
4890
- }
4891
- return output;
4892
- }
4893
4964
  function resolveFrontendDistDir(cwd) {
4894
4965
  const candidates = ["dist", "apps/web/dist"];
4895
4966
  for (const rel of candidates) {
@@ -4924,7 +4995,7 @@ async function runWisdomAutoDeploy(options) {
4924
4995
  throw new DeployExecutionError(lines.join("\n"), 1, lines.join("\n"));
4925
4996
  }
4926
4997
  console.error(`[apm] \u524D\u7AEF\u4EC5\u6253\u5305\u6A21\u5F0F\uFF1A\u6267\u884C ${buildCmd}`);
4927
- runShellCommand(buildCmd, cwd, captureOutput);
4998
+ runDeployShellCommand(buildCmd, cwd, captureOutput);
4928
4999
  const projectName = (cfg.name ?? "").trim();
4929
5000
  if (!projectName) {
4930
5001
  throw new DeployExecutionError(
@@ -4950,7 +5021,7 @@ async function runWisdomAutoDeploy(options) {
4950
5021
  );
4951
5022
  throw new DeployExecutionError(lines.join("\n"), 1, lines.join("\n"));
4952
5023
  }
4953
- runShellCommand(deployCmd, cwd, captureOutput);
5024
+ runDeployShellCommand(deployCmd, cwd, captureOutput);
4954
5025
  return { projectType };
4955
5026
  }
4956
5027
  const settings = resolveWisdomBackendDeployFromApmConfig(cfg);
@@ -5114,30 +5185,6 @@ async function syncDeployBaselineBeforeDeploy(cwd) {
5114
5185
  }
5115
5186
 
5116
5187
  // src/commands/deploy/deploy-execute.ts
5117
- function runShellCommand2(command, cwd, captureOutput) {
5118
- const result = spawnSync6(command, {
5119
- cwd,
5120
- shell: true,
5121
- env: buildDeployShellEnv(),
5122
- encoding: "utf8",
5123
- stdio: captureOutput ? ["inherit", "pipe", "pipe"] : "inherit"
5124
- });
5125
- const stdout = captureOutput ? String(result.stdout ?? "") : "";
5126
- const stderr = captureOutput ? String(result.stderr ?? "") : "";
5127
- const output = [stdout, stderr].filter(Boolean).join("\n");
5128
- if (captureOutput) {
5129
- if (stdout) process.stdout.write(stdout);
5130
- if (stderr) process.stderr.write(stderr);
5131
- }
5132
- if (result.status !== 0) {
5133
- throw new DeployExecutionError(
5134
- `\u90E8\u7F72\u547D\u4EE4\u9000\u51FA\u7801 ${result.status ?? "unknown"}: ${command}`,
5135
- result.status ?? 1,
5136
- output
5137
- );
5138
- }
5139
- return output;
5140
- }
5141
5188
  async function executeDeploy(options) {
5142
5189
  const cwd = resolveWorkdirPath(options.cwd ?? process.cwd());
5143
5190
  const apmConfigPath = options.configPath ?? path5.join(workspaceApmDir(cwd), "apm.config.json");
@@ -5190,7 +5237,7 @@ async function executeDeploy(options) {
5190
5237
  const lines = formatDeployNotConfiguredLines(options.env, deployCommands);
5191
5238
  throw new DeployExecutionError(lines.join("\n"), 1, lines.join("\n"));
5192
5239
  }
5193
- const commandOutput = runShellCommand2(command, cwd, captureOutput);
5240
+ const commandOutput = runDeployShellCommand(command, cwd, captureOutput);
5194
5241
  if (commandOutput.trim()) {
5195
5242
  outputParts.push(commandOutput);
5196
5243
  }
@@ -5209,9 +5256,32 @@ var DEPLOY_LOG_SYNC_INTERVAL_MS = 3e4;
5209
5256
  function resolveDeployCommand(environment, packOnly) {
5210
5257
  return packOnly ? `apm deploy ${environment} --pack-only` : `apm deploy ${environment}`;
5211
5258
  }
5259
+ function logDeploySyncFailure(error) {
5260
+ console.error(
5261
+ "[apm] deploy log sync failed:",
5262
+ error instanceof Error ? error.message : String(error)
5263
+ );
5264
+ }
5212
5265
  function createDeployLogSyncer(api, deploymentRunId) {
5213
5266
  let lastSyncedLog = "";
5214
5267
  let latestLog = "";
5268
+ let timer = null;
5269
+ const syncDelta = async (delta, append) => {
5270
+ const chunks = splitUtf8StringByMaxBytes(
5271
+ delta,
5272
+ DEPLOY_LOG_SYNC_MAX_CHUNK_BYTES
5273
+ );
5274
+ for (let index = 0; index < chunks.length; index += 1) {
5275
+ const chunk = chunks[index];
5276
+ await retryDeployLogSync(
5277
+ () => api.cli.syncTaskDeploymentLog({
5278
+ id: deploymentRunId,
5279
+ log: chunk,
5280
+ append: append || index > 0
5281
+ })
5282
+ );
5283
+ }
5284
+ };
5215
5285
  const syncIfChanged = async () => {
5216
5286
  if (!latestLog || latestLog === lastSyncedLog) {
5217
5287
  return;
@@ -5220,34 +5290,58 @@ function createDeployLogSyncer(api, deploymentRunId) {
5220
5290
  if (!delta) {
5221
5291
  return;
5222
5292
  }
5223
- await api.cli.syncTaskDeploymentLog({
5224
- id: deploymentRunId,
5225
- log: delta,
5226
- append: lastSyncedLog.length > 0
5227
- });
5293
+ await syncDelta(delta, lastSyncedLog.length > 0);
5228
5294
  lastSyncedLog = latestLog;
5229
5295
  };
5230
- const timer = setInterval(() => {
5231
- void syncIfChanged().catch((error) => {
5232
- console.error(
5233
- "[apm] deploy log sync failed:",
5234
- error instanceof Error ? error.message : String(error)
5235
- );
5236
- });
5296
+ timer = setInterval(() => {
5297
+ void syncIfChanged().catch(logDeploySyncFailure);
5237
5298
  }, DEPLOY_LOG_SYNC_INTERVAL_MS);
5238
5299
  return {
5239
5300
  updateLog(log2) {
5240
5301
  latestLog = log2;
5241
5302
  },
5242
5303
  async flush() {
5243
- clearInterval(timer);
5304
+ if (timer) {
5305
+ clearInterval(timer);
5306
+ timer = null;
5307
+ }
5244
5308
  await syncIfChanged();
5245
5309
  },
5310
+ async flushSafe() {
5311
+ if (timer) {
5312
+ clearInterval(timer);
5313
+ timer = null;
5314
+ }
5315
+ try {
5316
+ await syncIfChanged();
5317
+ return true;
5318
+ } catch (error) {
5319
+ logDeploySyncFailure(error);
5320
+ return false;
5321
+ }
5322
+ },
5246
5323
  dispose() {
5247
- clearInterval(timer);
5324
+ if (timer) {
5325
+ clearInterval(timer);
5326
+ timer = null;
5327
+ }
5248
5328
  }
5249
5329
  };
5250
5330
  }
5331
+ async function finalizeTaskDeployment(api, deploymentRunId, logSyncer, input) {
5332
+ const synced = await logSyncer.flushSafe();
5333
+ const payload = {
5334
+ id: deploymentRunId,
5335
+ status: input.status
5336
+ };
5337
+ if (input.error) {
5338
+ payload.error = input.error;
5339
+ }
5340
+ if (!synced) {
5341
+ payload.log = truncateDeployLogForComplete(input.fullLog).log;
5342
+ }
5343
+ await retryDeployLogSync(() => api.cli.completeTaskDeployment(payload));
5344
+ }
5251
5345
  function captureDeployConsole(logSyncer) {
5252
5346
  const chunks = [];
5253
5347
  const appendLog = (line) => {
@@ -5304,11 +5398,9 @@ async function handleInboundDeploy(cfg, msg, signal) {
5304
5398
  if (output.trim()) {
5305
5399
  capture.appendLog(output);
5306
5400
  }
5307
- await logSyncer.flush();
5308
- await api.cli.completeTaskDeployment({
5309
- id: deploymentRunId,
5401
+ await finalizeTaskDeployment(api, deploymentRunId, logSyncer, {
5310
5402
  status: "SUCCESS",
5311
- log: capture.getLog()
5403
+ fullLog: capture.getLog()
5312
5404
  });
5313
5405
  console.log(`[apm] deploy success id=${deploymentRunId}`);
5314
5406
  } catch (error) {
@@ -5321,13 +5413,18 @@ async function handleInboundDeploy(cfg, msg, signal) {
5321
5413
  } else {
5322
5414
  capture.appendLog(deployError.message);
5323
5415
  }
5324
- await logSyncer.flush();
5325
- await api.cli.completeTaskDeployment({
5326
- id: deploymentRunId,
5327
- status: "FAILED",
5328
- error: deployError.message,
5329
- log: capture.getLog()
5330
- });
5416
+ try {
5417
+ await finalizeTaskDeployment(api, deploymentRunId, logSyncer, {
5418
+ status: "FAILED",
5419
+ fullLog: capture.getLog(),
5420
+ error: deployError.message
5421
+ });
5422
+ } catch (finalizeError) {
5423
+ console.error(
5424
+ `[apm] deploy finalize failed id=${deploymentRunId}:`,
5425
+ finalizeError instanceof Error ? finalizeError.message : String(finalizeError)
5426
+ );
5427
+ }
5331
5428
  console.error(
5332
5429
  `[apm] deploy failed id=${deploymentRunId}: ${deployError.message}`
5333
5430
  );
@@ -6599,7 +6696,7 @@ async function runConnect(options) {
6599
6696
  shutdownAbort.abort();
6600
6697
  logAbortSignalStats(shutdownAbort.signal, "connect:shutdown-after-abort");
6601
6698
  try {
6602
- await Promise.race([Promise.all(activeTasks), delay2(drainMs)]);
6699
+ await Promise.race([Promise.all(activeTasks), delay3(drainMs)]);
6603
6700
  } catch {
6604
6701
  }
6605
6702
  releaseConnectLock();
@@ -6797,10 +6894,9 @@ function normalizeDeployEnvironment(env) {
6797
6894
  }
6798
6895
  async function completeTrackedDeploy(api, deploymentRunId, logSyncer, input) {
6799
6896
  logSyncer.updateLog(input.log);
6800
- await logSyncer.flush();
6801
- await api.cli.completeTaskDeployment({
6802
- id: deploymentRunId,
6897
+ await finalizeTaskDeployment(api, deploymentRunId, logSyncer, {
6803
6898
  status: input.status,
6899
+ fullLog: input.log,
6804
6900
  error: input.error
6805
6901
  });
6806
6902
  }