@zhipu/zp-cli 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -9
- package/dist/build-dashboard-CqZVHPH7.mjs +502 -0
- package/dist/build-jobs-m5jbiNTx.mjs +191 -0
- package/dist/cli.mjs +361 -78
- package/package.json +53 -3
package/dist/cli.mjs
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { a as findConfigFile, c as loadConfig, l as templateFileName, n as SCHEMA_FILE_NAME, o as isConfigFormat, r as configFileName, s as listConfigFiles, t as CONFIG_FORMATS } from "./config-BZc9pg7J.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
-
import fs from "node:fs";
|
|
3
|
+
import fs, { mkdtempSync, writeFileSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { Command } from "@commander-js/extra-typings";
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
|
+
import os, { tmpdir } from "node:os";
|
|
8
|
+
import chalk from "chalk";
|
|
7
9
|
import zlib from "node:zlib";
|
|
8
10
|
//#region package.json
|
|
9
|
-
var version = "0.0.
|
|
11
|
+
var version = "0.0.2";
|
|
10
12
|
var description = "内部单命令部署工具";
|
|
11
13
|
//#endregion
|
|
12
14
|
//#region src/utils/deploy-paths.ts
|
|
@@ -33,7 +35,82 @@ function shouldSkipDeployRuntime() {
|
|
|
33
35
|
return process.env.ZPC_DEPLOY_SKIP_RUNTIME === "1";
|
|
34
36
|
}
|
|
35
37
|
//#endregion
|
|
38
|
+
//#region src/utils/tty-shim.ts
|
|
39
|
+
/** 子进程预加载脚本:让 piped stdout/stderr 表现为 TTY,以启用 Vite 等工具的进度覆写。 */
|
|
40
|
+
const TTY_SHIM_CJS = String.raw`'use strict';
|
|
41
|
+
|
|
42
|
+
function patchStdStream(stream) {
|
|
43
|
+
if (!stream || stream.__zpcTtyPatched) return;
|
|
44
|
+
stream.__zpcTtyPatched = true;
|
|
45
|
+
Object.defineProperty(stream, 'isTTY', {
|
|
46
|
+
value: true,
|
|
47
|
+
enumerable: true,
|
|
48
|
+
configurable: true,
|
|
49
|
+
});
|
|
50
|
+
if (stream.columns == null) stream.columns = 120;
|
|
51
|
+
if (stream.rows == null) stream.rows = 30;
|
|
52
|
+
if (typeof stream.clearLine !== 'function') {
|
|
53
|
+
stream.clearLine = function clearLine(dir, cb) {
|
|
54
|
+
if (typeof dir === 'function') {
|
|
55
|
+
cb = dir;
|
|
56
|
+
dir = 0;
|
|
57
|
+
}
|
|
58
|
+
if (dir === 0 || dir === undefined) stream.write('\x1b[2K\r');
|
|
59
|
+
cb?.();
|
|
60
|
+
return true;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (typeof stream.cursorTo !== 'function') {
|
|
64
|
+
stream.cursorTo = function cursorTo(x, y, cb) {
|
|
65
|
+
if (typeof y === 'function') {
|
|
66
|
+
cb = y;
|
|
67
|
+
y = undefined;
|
|
68
|
+
}
|
|
69
|
+
if (typeof x === 'function') {
|
|
70
|
+
cb = x;
|
|
71
|
+
x = 0;
|
|
72
|
+
y = undefined;
|
|
73
|
+
}
|
|
74
|
+
if (y === undefined) stream.write('\x1b[' + (x + 1) + 'G');
|
|
75
|
+
else stream.write('\x1b[' + (y + 1) + ';' + (x + 1) + 'H');
|
|
76
|
+
cb?.();
|
|
77
|
+
return true;
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
patchStdStream(process.stdout);
|
|
83
|
+
patchStdStream(process.stderr);
|
|
84
|
+
`;
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/utils/tty-shim-path.ts
|
|
87
|
+
let cachedShimPath;
|
|
88
|
+
/** 将 tty shim 写入临时目录并缓存路径,供 NODE_OPTIONS --require 使用。 */
|
|
89
|
+
function ensureTtyShimPath() {
|
|
90
|
+
if (cachedShimPath) return cachedShimPath;
|
|
91
|
+
const dir = mkdtempSync(path.join(tmpdir(), "zpc-tty-"));
|
|
92
|
+
cachedShimPath = path.join(dir, "tty-shim.cjs");
|
|
93
|
+
writeFileSync(cachedShimPath, TTY_SHIM_CJS, "utf8");
|
|
94
|
+
return cachedShimPath;
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
36
97
|
//#region src/utils/run.ts
|
|
98
|
+
function killProcessTree(child) {
|
|
99
|
+
if (!child.pid || child.killed) return;
|
|
100
|
+
if (process.platform === "win32") {
|
|
101
|
+
spawn("taskkill", [
|
|
102
|
+
"/pid",
|
|
103
|
+
String(child.pid),
|
|
104
|
+
"/f",
|
|
105
|
+
"/t"
|
|
106
|
+
], {
|
|
107
|
+
stdio: "ignore",
|
|
108
|
+
windowsHide: true
|
|
109
|
+
});
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
child.kill("SIGTERM");
|
|
113
|
+
}
|
|
37
114
|
function runCommand(command, args, opts = {}) {
|
|
38
115
|
return new Promise((resolve, reject) => {
|
|
39
116
|
const useCmd = opts.windowsCmd && process.platform === "win32";
|
|
@@ -64,6 +141,63 @@ function runCommand(command, args, opts = {}) {
|
|
|
64
141
|
});
|
|
65
142
|
});
|
|
66
143
|
}
|
|
144
|
+
function decodeChunk(chunk) {
|
|
145
|
+
return typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
146
|
+
}
|
|
147
|
+
function quoteNodeOption(value) {
|
|
148
|
+
if (!/[ \t"]/u.test(value)) return value;
|
|
149
|
+
return `"${value.replace(/"/g, "\\\"")}"`;
|
|
150
|
+
}
|
|
151
|
+
function buildCapturedEnv(opts) {
|
|
152
|
+
const env = opts.env ? {
|
|
153
|
+
...process.env,
|
|
154
|
+
...opts.env
|
|
155
|
+
} : { ...process.env };
|
|
156
|
+
if (!opts.fakeTty) return env;
|
|
157
|
+
const requireFlag = `--require ${quoteNodeOption(ensureTtyShimPath())}`;
|
|
158
|
+
env.NODE_OPTIONS = env.NODE_OPTIONS ? `${env.NODE_OPTIONS} ${requireFlag}` : requireFlag;
|
|
159
|
+
delete env.CI;
|
|
160
|
+
return env;
|
|
161
|
+
}
|
|
162
|
+
function runCommandCaptured(command, args, opts = {}) {
|
|
163
|
+
const useCmd = opts.windowsCmd && process.platform === "win32";
|
|
164
|
+
const child = spawn(useCmd ? process.env.ComSpec || "cmd.exe" : command, useCmd ? [
|
|
165
|
+
"/d",
|
|
166
|
+
"/s",
|
|
167
|
+
"/c",
|
|
168
|
+
command,
|
|
169
|
+
...args
|
|
170
|
+
] : args, {
|
|
171
|
+
cwd: opts.cwd,
|
|
172
|
+
stdio: [
|
|
173
|
+
"ignore",
|
|
174
|
+
"pipe",
|
|
175
|
+
"pipe"
|
|
176
|
+
],
|
|
177
|
+
windowsHide: true,
|
|
178
|
+
env: buildCapturedEnv(opts)
|
|
179
|
+
});
|
|
180
|
+
child.stdout?.on("data", (chunk) => {
|
|
181
|
+
opts.onStdout?.(decodeChunk(chunk));
|
|
182
|
+
});
|
|
183
|
+
child.stderr?.on("data", (chunk) => {
|
|
184
|
+
opts.onStderr?.(decodeChunk(chunk));
|
|
185
|
+
});
|
|
186
|
+
return {
|
|
187
|
+
promise: new Promise((resolve, reject) => {
|
|
188
|
+
child.on("error", (err) => {
|
|
189
|
+
reject(/* @__PURE__ */ new Error(`无法启动 ${command}: ${err.message}`));
|
|
190
|
+
});
|
|
191
|
+
child.on("exit", (code, signal) => {
|
|
192
|
+
resolve({
|
|
193
|
+
exitCode: code,
|
|
194
|
+
signal
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
}),
|
|
198
|
+
kill: () => killProcessTree(child)
|
|
199
|
+
};
|
|
200
|
+
}
|
|
67
201
|
//#endregion
|
|
68
202
|
//#region src/utils/templates.ts
|
|
69
203
|
const require = createRequire(import.meta.url);
|
|
@@ -304,6 +438,139 @@ async function configureNginxProxies(projectName, apis, force, log) {
|
|
|
304
438
|
log("nginx 已 reload");
|
|
305
439
|
}
|
|
306
440
|
//#endregion
|
|
441
|
+
//#region src/utils/deploy-state.ts
|
|
442
|
+
const DEPLOY_STATE_VERSION = 1;
|
|
443
|
+
const DEPLOY_STATE_FILE = "deploy-state.json";
|
|
444
|
+
function getZpcHome() {
|
|
445
|
+
return process.env.ZPC_HOME ?? path.join(os.homedir(), ".zpc");
|
|
446
|
+
}
|
|
447
|
+
function getDeployStatePath() {
|
|
448
|
+
return path.join(getZpcHome(), DEPLOY_STATE_FILE);
|
|
449
|
+
}
|
|
450
|
+
/** 以项目根目录绝对路径为键(统一分隔符,便于跨平台比对)。 */
|
|
451
|
+
function deployStateProjectKey(root) {
|
|
452
|
+
return path.resolve(root).replace(/\\/g, "/");
|
|
453
|
+
}
|
|
454
|
+
function emptyDeployState() {
|
|
455
|
+
return {
|
|
456
|
+
version: DEPLOY_STATE_VERSION,
|
|
457
|
+
projects: {}
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
function readDeployStateFile() {
|
|
461
|
+
const filePath = getDeployStatePath();
|
|
462
|
+
if (!fs.existsSync(filePath)) return emptyDeployState();
|
|
463
|
+
let parsed;
|
|
464
|
+
try {
|
|
465
|
+
parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
466
|
+
} catch {
|
|
467
|
+
throw new Error(`[deploy] 无法解析 ${filePath},请检查 JSON 格式或删除后重试`);
|
|
468
|
+
}
|
|
469
|
+
if (typeof parsed !== "object" || parsed == null || parsed.version !== DEPLOY_STATE_VERSION || typeof parsed.projects !== "object" || parsed.projects == null) throw new Error(`[deploy] ${filePath} 格式无效,请删除后重试`);
|
|
470
|
+
return parsed;
|
|
471
|
+
}
|
|
472
|
+
function writeDeployStateFile(state) {
|
|
473
|
+
const home = getZpcHome();
|
|
474
|
+
fs.mkdirSync(home, { recursive: true });
|
|
475
|
+
fs.writeFileSync(getDeployStatePath(), `${JSON.stringify(state, null, 2)}\n`, "utf8");
|
|
476
|
+
}
|
|
477
|
+
function hasFullDeployRecord(root) {
|
|
478
|
+
const state = readDeployStateFile();
|
|
479
|
+
return deployStateProjectKey(root) in state.projects;
|
|
480
|
+
}
|
|
481
|
+
function markProjectFullDeployed(root, projectName) {
|
|
482
|
+
const state = readDeployStateFile();
|
|
483
|
+
const key = deployStateProjectKey(root);
|
|
484
|
+
state.projects[key] = {
|
|
485
|
+
projectName,
|
|
486
|
+
root: key,
|
|
487
|
+
fullDeployedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
488
|
+
};
|
|
489
|
+
writeDeployStateFile(state);
|
|
490
|
+
}
|
|
491
|
+
//#endregion
|
|
492
|
+
//#region src/utils/dist-zip.ts
|
|
493
|
+
const ZPC_GITIGNORE_HEADER = "# zpc files";
|
|
494
|
+
const ZPC_DIST_ZIP_IGNORE = "*-dist.zip";
|
|
495
|
+
function distZipFileName(projectName) {
|
|
496
|
+
return `${projectName}-dist.zip`;
|
|
497
|
+
}
|
|
498
|
+
function distZipPath(projectRoot, projectName) {
|
|
499
|
+
return path.join(projectRoot, distZipFileName(projectName));
|
|
500
|
+
}
|
|
501
|
+
function gitignoreHasDistZipRule(content) {
|
|
502
|
+
return content.split(/\r?\n/u).some((line) => line.trim() === ZPC_DIST_ZIP_IGNORE);
|
|
503
|
+
}
|
|
504
|
+
/** 若项目 .gitignore 缺少 *-dist.zip 规则则追加,返回是否新写入。 */
|
|
505
|
+
function ensureZpcGitignore(projectRoot) {
|
|
506
|
+
const gitignorePath = path.join(projectRoot, ".gitignore");
|
|
507
|
+
const existing = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, "utf8") : "";
|
|
508
|
+
if (gitignoreHasDistZipRule(existing)) return false;
|
|
509
|
+
const block = `${existing.length > 0 && !existing.endsWith("\n") ? "\n" : ""}\n${ZPC_GITIGNORE_HEADER}\n${ZPC_DIST_ZIP_IGNORE}\n`;
|
|
510
|
+
fs.writeFileSync(gitignorePath, `${existing}${block}`, "utf8");
|
|
511
|
+
return true;
|
|
512
|
+
}
|
|
513
|
+
//#endregion
|
|
514
|
+
//#region src/utils/incremental-mode.ts
|
|
515
|
+
function isDirectoryNonEmpty(dir) {
|
|
516
|
+
if (!fs.existsSync(dir)) return false;
|
|
517
|
+
try {
|
|
518
|
+
return fs.readdirSync(dir).length > 0;
|
|
519
|
+
} catch {
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
/** 配置中 api 在部署根目录下已存在且非空的目录。 */
|
|
524
|
+
function findNonemptyApiDeployDirs(config) {
|
|
525
|
+
const projectName = config.projectName;
|
|
526
|
+
if (!projectName) return [];
|
|
527
|
+
const found = [];
|
|
528
|
+
for (const item of config.api ?? []) {
|
|
529
|
+
const deployDir = path.join(getApiRoot(), projectName, item.name);
|
|
530
|
+
if (isDirectoryNonEmpty(deployDir)) found.push(deployDir);
|
|
531
|
+
}
|
|
532
|
+
return found;
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* 全量 / 增量判定(pack 只读、deploy 读后写入):
|
|
536
|
+
* 1. ~/.zpc/deploy-state.json 有记录 → 增量
|
|
537
|
+
* 2. 无记录但 api 部署目录非空 → 增量并提示
|
|
538
|
+
* 3. 无记录但 zip 含增量标记 → 增量
|
|
539
|
+
* 4. 否则全量
|
|
540
|
+
*/
|
|
541
|
+
function resolveIncrementalMode(projectRoot, config, options = {}) {
|
|
542
|
+
if (hasFullDeployRecord(projectRoot)) return { incremental: true };
|
|
543
|
+
const existingDeployDirs = findNonemptyApiDeployDirs(config);
|
|
544
|
+
if (existingDeployDirs.length > 0) return {
|
|
545
|
+
incremental: true,
|
|
546
|
+
existingDeployHint: `无 ~/.zpc 部署记录,但检测到 api 部署目录非空:${existingDeployDirs.map((dir) => path.normalize(dir)).join(", ")}。将按增量处理(api 保留 public,跳过 web);若确需全量请清空部署目录并删除 deploy-state 中对应记录`
|
|
547
|
+
};
|
|
548
|
+
if (options.zipIsIncremental) return { incremental: true };
|
|
549
|
+
return { incremental: false };
|
|
550
|
+
}
|
|
551
|
+
//#endregion
|
|
552
|
+
//#region src/utils/log.ts
|
|
553
|
+
function tagged(tag, message) {
|
|
554
|
+
return `${chalk.cyan(`[${tag}]`)} ${message}`;
|
|
555
|
+
}
|
|
556
|
+
/** 带命令前缀的彩色日志:info 默认、success 绿、warn 黄。 */
|
|
557
|
+
function createLogger(tag) {
|
|
558
|
+
return {
|
|
559
|
+
info: (message) => {
|
|
560
|
+
console.log(tagged(tag, message));
|
|
561
|
+
},
|
|
562
|
+
success: (message) => {
|
|
563
|
+
console.log(tagged(tag, chalk.green(message)));
|
|
564
|
+
},
|
|
565
|
+
warn: (message) => {
|
|
566
|
+
console.log(tagged(tag, chalk.yellow(message)));
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
function logError(message) {
|
|
571
|
+
console.error(chalk.red(message));
|
|
572
|
+
}
|
|
573
|
+
//#endregion
|
|
307
574
|
//#region src/utils/zip.ts
|
|
308
575
|
/** ZIP End of Central Directory 签名 */
|
|
309
576
|
const SIG_EOCD = 101010256;
|
|
@@ -419,27 +686,7 @@ async function zipDirectory(stagingDir, zipPath, cwd) {
|
|
|
419
686
|
}
|
|
420
687
|
//#endregion
|
|
421
688
|
//#region src/commands/deploy.ts
|
|
422
|
-
|
|
423
|
-
console.log(`[deploy] ${message}`);
|
|
424
|
-
}
|
|
425
|
-
function envFlag(name) {
|
|
426
|
-
const value = process.env[name];
|
|
427
|
-
return value === "1" || value === "true";
|
|
428
|
-
}
|
|
429
|
-
function resolveFlags(options) {
|
|
430
|
-
const forceFull = Boolean(options.full);
|
|
431
|
-
const force = Boolean(options.force) || envFlag("DEPLOY_FORCE");
|
|
432
|
-
let wantIncremental = Boolean(options.incremental) || envFlag("DEPLOY_INCREMENTAL");
|
|
433
|
-
if (forceFull) wantIncremental = false;
|
|
434
|
-
return {
|
|
435
|
-
wantIncremental,
|
|
436
|
-
forceFull,
|
|
437
|
-
force
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
function hasProjectFiles(dir) {
|
|
441
|
-
return fs.existsSync(path.join(dir, "dist.zip")) && Boolean(findConfigFile(dir));
|
|
442
|
-
}
|
|
689
|
+
const log$2 = createLogger("deploy");
|
|
443
690
|
function resolveProjectDir(dirArg) {
|
|
444
691
|
let dir;
|
|
445
692
|
if (dirArg) {
|
|
@@ -448,39 +695,40 @@ function resolveProjectDir(dirArg) {
|
|
|
448
695
|
dir = fs.statSync(abs).isDirectory() ? abs : path.dirname(abs);
|
|
449
696
|
} else if (process.env.DEPLOY_DIR) dir = path.resolve(process.env.DEPLOY_DIR);
|
|
450
697
|
else dir = process.cwd();
|
|
451
|
-
if (!
|
|
698
|
+
if (!findConfigFile(dir)) throw new Error(`[deploy] ${dir} 中需要 zpc.config.*(配置请单独上传,不要打进压缩包)`);
|
|
452
699
|
return dir;
|
|
453
700
|
}
|
|
454
701
|
async function deployKind(stagingDir, items, kind, destRoot, projectName, incremental) {
|
|
455
702
|
const packed = new Set(listNamedDirs(path.join(stagingDir, kind)));
|
|
456
703
|
const configured = items ?? [];
|
|
457
|
-
for (const extra of packed) if (!configured.some((item) => item.name === extra)) log$
|
|
704
|
+
for (const extra of packed) if (!configured.some((item) => item.name === extra)) log$2.warn(`跳过压缩包中的 ${kind}/${extra}(未在配置中列出)`);
|
|
458
705
|
for (const item of configured) {
|
|
459
706
|
const src = path.join(stagingDir, kind, item.name);
|
|
460
707
|
if (!fs.existsSync(src)) throw new Error(`[deploy] 配置中有 ${kind}/${item.name},但压缩包中没有对应目录`);
|
|
461
708
|
const dest = path.join(destRoot, projectName, item.name);
|
|
462
709
|
const pm2Name = `${projectName}-${item.name}`;
|
|
463
|
-
if (kind === "api" && incremental && fs.existsSync(dest)) deployApiIncremental(src, dest, log$
|
|
710
|
+
if (kind === "api" && incremental && fs.existsSync(dest)) deployApiIncremental(src, dest, log$2.info);
|
|
464
711
|
else {
|
|
465
|
-
if (kind === "api" && incremental && !fs.existsSync(dest)) log$
|
|
466
|
-
deployDir(src, dest, log$
|
|
712
|
+
if (kind === "api" && incremental && !fs.existsSync(dest)) log$2.warn(`${dest} 不存在,增量改为全量部署`);
|
|
713
|
+
deployDir(src, dest, log$2.info);
|
|
467
714
|
}
|
|
468
|
-
if (kind === "api" && !shouldSkipDeployRuntime()) await installAndReloadApi(pm2Name, dest, item, log$
|
|
715
|
+
if (kind === "api" && !shouldSkipDeployRuntime()) await installAndReloadApi(pm2Name, dest, item, log$2.info);
|
|
469
716
|
}
|
|
470
717
|
}
|
|
471
718
|
async function runDeploy(dir, options) {
|
|
472
|
-
const
|
|
719
|
+
const force = Boolean(options.force) || process.env.DEPLOY_FORCE === "1" || process.env.DEPLOY_FORCE === "true";
|
|
473
720
|
const projectDir = resolveProjectDir(dir);
|
|
474
721
|
const stagingDir = path.join(projectDir, ".deploy-staging");
|
|
475
|
-
const zipPath = path.join(projectDir, "dist.zip");
|
|
476
722
|
const { file, config } = await loadConfig(projectDir);
|
|
477
723
|
assertDeployConfig(config);
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
log$
|
|
481
|
-
log$
|
|
724
|
+
const zipPath = distZipPath(projectDir, config.projectName);
|
|
725
|
+
if (!fs.existsSync(zipPath)) throw new Error(`[deploy] 找不到 ${zipPath},请先在该目录执行 zpc pack(配置请单独上传,不要打进压缩包)`);
|
|
726
|
+
log$2.info(`项目目录: ${projectDir}`);
|
|
727
|
+
log$2.info(`压缩包: ${zipPath}`);
|
|
728
|
+
log$2.info(`配置: ${file}`);
|
|
729
|
+
log$2.info(`项目: ${config.projectName}`);
|
|
482
730
|
try {
|
|
483
|
-
log$
|
|
731
|
+
log$2.info(`解压 ${zipPath}`);
|
|
484
732
|
await extractArchive(zipPath, stagingDir);
|
|
485
733
|
} catch (err) {
|
|
486
734
|
fs.rmSync(stagingDir, {
|
|
@@ -490,17 +738,17 @@ async function runDeploy(dir, options) {
|
|
|
490
738
|
const message = err instanceof Error ? err.message : String(err);
|
|
491
739
|
throw new Error(`[deploy] 解压失败: ${message}`);
|
|
492
740
|
}
|
|
493
|
-
const
|
|
494
|
-
|
|
741
|
+
const zipIsIncremental = fs.existsSync(path.join(stagingDir, INCREMENTAL_MARKER));
|
|
742
|
+
const deployMode = resolveIncrementalMode(projectDir, config, { zipIsIncremental });
|
|
743
|
+
const incremental = deployMode.incremental;
|
|
495
744
|
try {
|
|
496
|
-
|
|
497
|
-
if (
|
|
498
|
-
|
|
499
|
-
log$
|
|
500
|
-
if (force) log$1("已启用 --force(允许抢占冲突的 nginx proxy)");
|
|
745
|
+
log$2.info(`模式: ${incremental ? "增量(api 保留 public,跳过 web)" : "全量"}`);
|
|
746
|
+
if (deployMode.existingDeployHint) log$2.warn(deployMode.existingDeployHint);
|
|
747
|
+
else if (!incremental) log$2.info("首次部署该项目,完成后将记录到 ~/.zpc/deploy-state.json");
|
|
748
|
+
if (force) log$2.warn("已启用 --force(允许抢占冲突的 nginx proxy)");
|
|
501
749
|
await deployKind(stagingDir, config.api, "api", getApiRoot(), config.projectName, incremental);
|
|
502
750
|
if (!incremental) await deployKind(stagingDir, config.web, "web", getWebRoot(), config.projectName, false);
|
|
503
|
-
else if (config.web?.length) log$
|
|
751
|
+
else if (config.web?.length) log$2.warn("增量模式跳过 web 部署");
|
|
504
752
|
} finally {
|
|
505
753
|
fs.rmSync(stagingDir, {
|
|
506
754
|
recursive: true,
|
|
@@ -508,21 +756,28 @@ async function runDeploy(dir, options) {
|
|
|
508
756
|
});
|
|
509
757
|
}
|
|
510
758
|
if (!shouldSkipDeployRuntime()) {
|
|
511
|
-
await configureNginxProxies(config.projectName, config.api, force, log$
|
|
759
|
+
await configureNginxProxies(config.projectName, config.api, force, log$2.info);
|
|
512
760
|
if (config.api?.length) await runCommand("pm2", ["save"], { ignoreExit: true });
|
|
513
761
|
}
|
|
762
|
+
if (!hasFullDeployRecord(projectDir)) {
|
|
763
|
+
markProjectFullDeployed(projectDir, config.projectName);
|
|
764
|
+
if (!incremental) log$2.info("已记录全量部署状态到 ~/.zpc/deploy-state.json,下次 deploy 将默认增量");
|
|
765
|
+
else if (deployMode.existingDeployHint) log$2.info("已根据现有部署目录建立部署记录,下次 deploy 将默认增量");
|
|
766
|
+
else if (zipIsIncremental) log$2.info("已记录部署状态,下次 deploy 将默认增量");
|
|
767
|
+
}
|
|
514
768
|
const apiCount = config.api?.length ?? 0;
|
|
515
769
|
const webCount = incremental ? 0 : config.web?.length ?? 0;
|
|
516
770
|
const prefix = `${config.projectName}/<name>`;
|
|
517
|
-
log$
|
|
771
|
+
log$2.success(`完成: api ${apiCount} 个 -> ${getApiRoot()}/${prefix},web ${webCount} 个 -> ${getWebRoot()}/${prefix}`);
|
|
518
772
|
}
|
|
519
773
|
function createDeployCommand() {
|
|
520
|
-
return new Command("deploy").description("按 zpc.config 将 dist.zip 部署到服务器").argument("[dir]", "项目目录,默认为当前目录").option("
|
|
774
|
+
return new Command("deploy").description("按 zpc.config 将 <projectName>-dist.zip 部署到服务器").argument("[dir]", "项目目录,默认为当前目录").option("--force", "允许抢占冲突的 nginx proxy").action(async (dir, options) => {
|
|
521
775
|
await runDeploy(dir, options);
|
|
522
776
|
});
|
|
523
777
|
}
|
|
524
778
|
//#endregion
|
|
525
779
|
//#region src/commands/init.ts
|
|
780
|
+
const log$1 = createLogger("init");
|
|
526
781
|
async function runInit(dir, options) {
|
|
527
782
|
const format = resolveFormat(options.template);
|
|
528
783
|
const root = path.resolve(dir ?? process.cwd());
|
|
@@ -536,10 +791,10 @@ async function runInit(dir, options) {
|
|
|
536
791
|
fs.mkdirSync(root, { recursive: true });
|
|
537
792
|
for (const file of existing) if (file !== dest) fs.rmSync(file);
|
|
538
793
|
fs.copyFileSync(resolveTemplateFile(format), dest);
|
|
539
|
-
|
|
794
|
+
log$1.success(`已写入 ${dest}`);
|
|
540
795
|
if (format === "json") {
|
|
541
796
|
fs.copyFileSync(resolveSchemaFile(), schemaDest);
|
|
542
|
-
|
|
797
|
+
log$1.success(`已写入 ${schemaDest}`);
|
|
543
798
|
} else if (fs.existsSync(schemaDest)) fs.rmSync(schemaDest);
|
|
544
799
|
}
|
|
545
800
|
function resolveFormat(template) {
|
|
@@ -554,13 +809,7 @@ function createInitCommand() {
|
|
|
554
809
|
}
|
|
555
810
|
//#endregion
|
|
556
811
|
//#region src/commands/pack.ts
|
|
557
|
-
|
|
558
|
-
console.log(`[pack] ${message}`);
|
|
559
|
-
}
|
|
560
|
-
function resolveIncremental(options) {
|
|
561
|
-
if (options.full) return false;
|
|
562
|
-
return Boolean(options.incremental);
|
|
563
|
-
}
|
|
812
|
+
const log = createLogger("pack");
|
|
564
813
|
function resolveProjects(root, config, incremental) {
|
|
565
814
|
const projects = [];
|
|
566
815
|
const kinds = incremental ? ["api"] : ["api", "web"];
|
|
@@ -596,18 +845,45 @@ function resolveBuildEnv(project, config) {
|
|
|
596
845
|
}
|
|
597
846
|
return env;
|
|
598
847
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
848
|
+
function shouldUseBuildTui(options) {
|
|
849
|
+
return Boolean(process.stdout.isTTY) && options.tui !== false;
|
|
850
|
+
}
|
|
851
|
+
function toBuildJobSpecs(projects, config) {
|
|
852
|
+
return projects.map((project) => ({
|
|
853
|
+
id: `${project.kind}/${project.name}`,
|
|
854
|
+
label: `${project.kind}/${project.name}`,
|
|
855
|
+
cwd: project.absPath,
|
|
856
|
+
env: resolveBuildEnv(project, config)
|
|
857
|
+
}));
|
|
858
|
+
}
|
|
859
|
+
async function buildAllLegacy(projects, config, concurrency) {
|
|
860
|
+
const limitText = concurrency !== void 0 ? `,最多同时 ${concurrency} 个` : "";
|
|
861
|
+
log.info(`构建 ${projects.length} 个项目${limitText}`);
|
|
862
|
+
const { runWithConcurrency } = await import("./build-jobs-m5jbiNTx.mjs");
|
|
863
|
+
await runWithConcurrency(projects, concurrency ?? 0, async (project) => {
|
|
602
864
|
const env = resolveBuildEnv(project, config);
|
|
603
|
-
if (project.kind === "web" && env.VITE_API_PROXY) log(`build ${project.kind}/${project.name} VITE_API_PROXY=${env.VITE_API_PROXY}`);
|
|
604
|
-
else log(`build ${project.kind}/${project.name} -> ${project.absPath}`);
|
|
865
|
+
if (project.kind === "web" && env.VITE_API_PROXY) log.info(`build ${project.kind}/${project.name} VITE_API_PROXY=${env.VITE_API_PROXY}`);
|
|
866
|
+
else log.info(`build ${project.kind}/${project.name} -> ${project.absPath}`);
|
|
605
867
|
await runCommand("npm", ["run", "build"], {
|
|
606
868
|
cwd: project.absPath,
|
|
607
869
|
env,
|
|
608
870
|
windowsCmd: true
|
|
609
871
|
});
|
|
610
|
-
})
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
async function buildAllWithTui(projects, config, options) {
|
|
875
|
+
const { runBuildDashboard } = await import("./build-dashboard-CqZVHPH7.mjs");
|
|
876
|
+
await runBuildDashboard(toBuildJobSpecs(projects, config), {
|
|
877
|
+
concurrency: options.concurrency,
|
|
878
|
+
stayOnSuccess: options.stay
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
async function buildAll(projects, config, options) {
|
|
882
|
+
if (shouldUseBuildTui(options)) {
|
|
883
|
+
await buildAllWithTui(projects, config, options);
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
await buildAllLegacy(projects, config, options.concurrency);
|
|
611
887
|
}
|
|
612
888
|
function copyRequiredFile(src, destDir, label) {
|
|
613
889
|
if (!fs.existsSync(src)) throw new Error(`[pack] 缺少 ${label}: ${src}`);
|
|
@@ -619,7 +895,7 @@ function stageApi(project, dest, incremental) {
|
|
|
619
895
|
copyRequiredFile(path.join(project.absPath, "package.json"), dest, "package.json");
|
|
620
896
|
copyRequiredFile(path.join(project.absPath, "bootstrap.js"), dest, "bootstrap.js");
|
|
621
897
|
if (incremental) {
|
|
622
|
-
log(`api/${project.name} 增量:跳过 public`);
|
|
898
|
+
log.warn(`api/${project.name} 增量:跳过 public`);
|
|
623
899
|
return;
|
|
624
900
|
}
|
|
625
901
|
const publicDir = path.join(project.absPath, "public");
|
|
@@ -639,26 +915,29 @@ function stageDist(stagingDir, root, projects, incremental) {
|
|
|
639
915
|
const dest = path.join(stagingDir, project.kind, project.name);
|
|
640
916
|
if (project.kind === "api") stageApi(project, dest, incremental);
|
|
641
917
|
else fs.cpSync(project.distPath, dest, { recursive: true });
|
|
642
|
-
log(`收集 ${project.kind}/${project.name} -> ${path.relative(root, dest)}`);
|
|
918
|
+
log.info(`收集 ${project.kind}/${project.name} -> ${path.relative(root, dest)}`);
|
|
643
919
|
}
|
|
644
920
|
}
|
|
645
921
|
async function runPack(dir, options) {
|
|
646
922
|
const root = path.resolve(dir ?? process.cwd());
|
|
647
923
|
if (!fs.existsSync(root)) throw new Error(`[pack] 找不到路径: ${root}`);
|
|
648
924
|
const projectRoot = fs.statSync(root).isDirectory() ? root : path.dirname(root);
|
|
649
|
-
const incremental = resolveIncremental(options);
|
|
650
925
|
const stagingDir = path.join(projectRoot, ".packup-staging");
|
|
651
|
-
const zipPath = path.join(projectRoot, "dist.zip");
|
|
652
926
|
const { file, config } = await loadConfig(projectRoot);
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
log(
|
|
927
|
+
const zipPath = distZipPath(projectRoot, config.projectName);
|
|
928
|
+
const packMode = resolveIncrementalMode(projectRoot, config);
|
|
929
|
+
const incremental = packMode.incremental;
|
|
930
|
+
log.info(`项目目录: ${projectRoot}`);
|
|
931
|
+
log.info(`配置: ${file}`);
|
|
932
|
+
log.info(`项目: ${config.projectName}`);
|
|
933
|
+
log.info(`模式: ${incremental ? "增量(仅 api,不含 public)" : "全量"}`);
|
|
934
|
+
if (packMode.existingDeployHint) log.warn(packMode.existingDeployHint);
|
|
935
|
+
if (options.concurrency !== void 0) log.info(`并行上限: ${options.concurrency}`);
|
|
657
936
|
const projects = resolveProjects(projectRoot, config, incremental);
|
|
658
|
-
await buildAll(projects, config);
|
|
937
|
+
await buildAll(projects, config, options);
|
|
659
938
|
stageDist(stagingDir, projectRoot, projects, incremental);
|
|
660
939
|
try {
|
|
661
|
-
log(`压缩 ${path.relative(projectRoot, zipPath)}`);
|
|
940
|
+
log.info(`压缩 ${path.relative(projectRoot, zipPath)}`);
|
|
662
941
|
await zipDirectory(stagingDir, zipPath, projectRoot);
|
|
663
942
|
} catch (err) {
|
|
664
943
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -669,11 +948,16 @@ async function runPack(dir, options) {
|
|
|
669
948
|
force: true
|
|
670
949
|
});
|
|
671
950
|
}
|
|
672
|
-
log(`完成: ${zipPath}`);
|
|
673
|
-
if (
|
|
951
|
+
log.success(`完成: ${zipPath}`);
|
|
952
|
+
if (ensureZpcGitignore(projectRoot)) log.info("已更新 .gitignore,忽略 *-dist.zip");
|
|
953
|
+
if (incremental) log.info("增量包已生成,部署时将自动识别为增量模式");
|
|
674
954
|
}
|
|
675
955
|
function createPackCommand() {
|
|
676
|
-
return new Command("pack").description("按 zpc.config 构建并打包 dist.zip").argument("[dir]", "项目目录,默认为当前目录").option("-
|
|
956
|
+
return new Command("pack").description("按 zpc.config 构建并打包 <projectName>-dist.zip").argument("[dir]", "项目目录,默认为当前目录").option("--no-tui", "禁用构建 TUI,使用传统输出").option("--stay", "构建成功后保留 TUI,手动退出").option("-c, --concurrency <n>", "最大同时构建数(默认不限制)", (value) => {
|
|
957
|
+
const parsed = Number(value);
|
|
958
|
+
if (!Number.isInteger(parsed) || parsed < 1) throw new Error("--concurrency 必须是大于 0 的整数");
|
|
959
|
+
return parsed;
|
|
960
|
+
}).action(async (dir, options) => {
|
|
677
961
|
await runPack(dir, options);
|
|
678
962
|
});
|
|
679
963
|
}
|
|
@@ -690,8 +974,7 @@ async function runCli(argv = process.argv) {
|
|
|
690
974
|
try {
|
|
691
975
|
await createProgram().parseAsync(argv);
|
|
692
976
|
} catch (err) {
|
|
693
|
-
|
|
694
|
-
console.error(message);
|
|
977
|
+
logError(err instanceof Error ? err.message : String(err));
|
|
695
978
|
process.exitCode = 1;
|
|
696
979
|
}
|
|
697
980
|
}
|
|
@@ -699,4 +982,4 @@ async function runCli(argv = process.argv) {
|
|
|
699
982
|
//#region src/cli.ts
|
|
700
983
|
runCli();
|
|
701
984
|
//#endregion
|
|
702
|
-
export {};
|
|
985
|
+
export { runCommandCaptured as t };
|