@zhipu/zp-cli 0.0.2 → 0.0.3
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 +9 -0
- package/dist/cli.mjs +275 -66
- package/package.json +25 -1
package/README.md
CHANGED
|
@@ -16,8 +16,17 @@ zpc pack
|
|
|
16
16
|
# 在服务器上:目录内需有 <projectName>-dist.zip + zpc.config.*(配置不要打进压缩包)
|
|
17
17
|
zpc deploy
|
|
18
18
|
zpc deploy --force # 允许抢占冲突的 nginx proxy
|
|
19
|
+
|
|
20
|
+
# 盘点部署目录与 nginx 配置,标注已记录 / 未记录 / 记录但缺失
|
|
21
|
+
zpc check
|
|
19
22
|
```
|
|
20
23
|
|
|
24
|
+
`zpc deploy` 若发现无 `~/.zpc` 部署记录、但 api/web 部署目录非空或已有本项目 nginx 配置,会交互询问:
|
|
25
|
+
|
|
26
|
+
1. 清空目标文件夹并覆盖(全量,旧目录备份为带时间戳的 `.bak-*`)
|
|
27
|
+
2. 使用增量部署(api 保留 `public`,跳过 web)
|
|
28
|
+
3. 取消部署(默认;非交互环境同样默认取消)
|
|
29
|
+
|
|
21
30
|
可选环境变量:`DEPLOY_DIR`、`DEPLOY_FORCE`;`ZPC_HOME` 覆盖 `~/.zpc`(部署状态记录);测试可用 `ZPC_API_ROOT` / `ZPC_WEB_ROOT` / `ZPC_NGINX_DEFAULT_D` / `ZPC_DEPLOY_SKIP_RUNTIME`。
|
|
22
31
|
|
|
23
32
|
## 部署约定
|
package/dist/cli.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import os, { tmpdir } from "node:os";
|
|
|
8
8
|
import chalk from "chalk";
|
|
9
9
|
import zlib from "node:zlib";
|
|
10
10
|
//#region package.json
|
|
11
|
-
var version = "0.0.
|
|
11
|
+
var version = "0.0.3";
|
|
12
12
|
var description = "内部单命令部署工具";
|
|
13
13
|
//#endregion
|
|
14
14
|
//#region src/utils/deploy-paths.ts
|
|
@@ -321,17 +321,20 @@ function listNamedDirs(parent) {
|
|
|
321
321
|
if (!fs.existsSync(parent)) return [];
|
|
322
322
|
return fs.readdirSync(parent, { withFileTypes: true }).filter((d) => d.isDirectory() && isSafeDirName(d.name) && !d.name.startsWith(".")).map((d) => d.name);
|
|
323
323
|
}
|
|
324
|
-
|
|
324
|
+
function backupTimestamp() {
|
|
325
|
+
const d = /* @__PURE__ */ new Date();
|
|
326
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
327
|
+
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* 原子风格替换目录:现目录改名为带时间戳的唯一 `.bak-*` 备份 → 拷入新内容。
|
|
331
|
+
* 备份名含时间戳,多次覆盖不会互相覆盖,历史版本得以保留;失败从该备份恢复。
|
|
332
|
+
*/
|
|
325
333
|
function deployDir(src, dest, log) {
|
|
326
334
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
fs.rmSync(bak, {
|
|
331
|
-
recursive: true,
|
|
332
|
-
force: true
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
+
const stamp = backupTimestamp();
|
|
336
|
+
let bak = `${dest}.bak-${stamp}`;
|
|
337
|
+
for (let i = 1; fs.existsSync(bak); i++) bak = `${dest}.bak-${stamp}-${i}`;
|
|
335
338
|
if (fs.existsSync(dest)) {
|
|
336
339
|
log(`备份 ${dest} -> ${bak}`);
|
|
337
340
|
fs.renameSync(dest, bak);
|
|
@@ -488,6 +491,115 @@ function markProjectFullDeployed(root, projectName) {
|
|
|
488
491
|
};
|
|
489
492
|
writeDeployStateFile(state);
|
|
490
493
|
}
|
|
494
|
+
/** 返回 ~/.zpc/deploy-state.json 中已记录的所有 projectName(去重、排序)。 */
|
|
495
|
+
function listRecordedProjectNames() {
|
|
496
|
+
const state = readDeployStateFile();
|
|
497
|
+
return [...new Set(Object.values(state.projects).map((p) => p.projectName))].sort();
|
|
498
|
+
}
|
|
499
|
+
//#endregion
|
|
500
|
+
//#region src/utils/check-state.ts
|
|
501
|
+
/** 从 nginx 配置的 `owned-by: projectName/name` 头解析出 projectName。 */
|
|
502
|
+
function nginxProjectName(text) {
|
|
503
|
+
const owned = parseNginxOwnedBy(text);
|
|
504
|
+
if (!owned) return null;
|
|
505
|
+
const idx = owned.indexOf("/");
|
|
506
|
+
return idx >= 0 ? owned.slice(0, idx) : owned;
|
|
507
|
+
}
|
|
508
|
+
/** 盘点部署痕迹(api/web 目录 + nginx 配置)与部署记录,合并成按系统分组的结果。 */
|
|
509
|
+
function collectSystemChecks() {
|
|
510
|
+
const recorded = new Set(listRecordedProjectNames());
|
|
511
|
+
const apiByProject = /* @__PURE__ */ new Map();
|
|
512
|
+
for (const projectName of listNamedDirs(getApiRoot())) apiByProject.set(projectName, listNamedDirs(path.join(getApiRoot(), projectName)));
|
|
513
|
+
const webByProject = /* @__PURE__ */ new Map();
|
|
514
|
+
for (const projectName of listNamedDirs(getWebRoot())) webByProject.set(projectName, listNamedDirs(path.join(getWebRoot(), projectName)));
|
|
515
|
+
const nginxByProject = /* @__PURE__ */ new Map();
|
|
516
|
+
const unownedNginxConfs = [];
|
|
517
|
+
const nginxDir = getNginxDefaultD();
|
|
518
|
+
if (fs.existsSync(nginxDir)) for (const name of fs.readdirSync(nginxDir)) {
|
|
519
|
+
if (!name.endsWith(".conf")) continue;
|
|
520
|
+
const full = path.join(nginxDir, name);
|
|
521
|
+
let text = "";
|
|
522
|
+
try {
|
|
523
|
+
text = fs.readFileSync(full, "utf8");
|
|
524
|
+
} catch {}
|
|
525
|
+
const projectName = nginxProjectName(text);
|
|
526
|
+
if (projectName) {
|
|
527
|
+
const arr = nginxByProject.get(projectName) ?? [];
|
|
528
|
+
arr.push(full);
|
|
529
|
+
nginxByProject.set(projectName, arr);
|
|
530
|
+
} else unownedNginxConfs.push(full);
|
|
531
|
+
}
|
|
532
|
+
return {
|
|
533
|
+
systems: [.../* @__PURE__ */ new Set([
|
|
534
|
+
...recorded,
|
|
535
|
+
...apiByProject.keys(),
|
|
536
|
+
...webByProject.keys(),
|
|
537
|
+
...nginxByProject.keys()
|
|
538
|
+
])].sort().map((projectName) => ({
|
|
539
|
+
projectName,
|
|
540
|
+
recorded: recorded.has(projectName),
|
|
541
|
+
apiDirs: apiByProject.get(projectName) ?? [],
|
|
542
|
+
webDirs: webByProject.get(projectName) ?? [],
|
|
543
|
+
nginxConfs: nginxByProject.get(projectName) ?? []
|
|
544
|
+
})),
|
|
545
|
+
unownedNginxConfs
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
//#endregion
|
|
549
|
+
//#region src/utils/log.ts
|
|
550
|
+
function tagged(tag, message) {
|
|
551
|
+
return `${chalk.cyan(`[${tag}]`)} ${message}`;
|
|
552
|
+
}
|
|
553
|
+
/** 带命令前缀的彩色日志:info 默认、success 绿、warn 黄。 */
|
|
554
|
+
function createLogger(tag) {
|
|
555
|
+
return {
|
|
556
|
+
info: (message) => {
|
|
557
|
+
console.log(tagged(tag, message));
|
|
558
|
+
},
|
|
559
|
+
success: (message) => {
|
|
560
|
+
console.log(tagged(tag, chalk.green(message)));
|
|
561
|
+
},
|
|
562
|
+
warn: (message) => {
|
|
563
|
+
console.log(tagged(tag, chalk.yellow(message)));
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
function logError(message) {
|
|
568
|
+
console.error(chalk.red(message));
|
|
569
|
+
}
|
|
570
|
+
//#endregion
|
|
571
|
+
//#region src/commands/check.ts
|
|
572
|
+
const log$3 = createLogger("check");
|
|
573
|
+
function systemStatus(system) {
|
|
574
|
+
if (!system.recorded) return "未记录";
|
|
575
|
+
return system.apiDirs.length > 0 || system.webDirs.length > 0 || system.nginxConfs.length > 0 ? "已记录" : "记录但缺失";
|
|
576
|
+
}
|
|
577
|
+
function runCheck() {
|
|
578
|
+
const { systems, unownedNginxConfs } = collectSystemChecks();
|
|
579
|
+
if (systems.length === 0 && unownedNginxConfs.length === 0) {
|
|
580
|
+
log$3.info("未发现任何部署记录或部署痕迹");
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
for (const system of systems) {
|
|
584
|
+
const status = systemStatus(system);
|
|
585
|
+
const head = `[${status}] ${system.projectName}`;
|
|
586
|
+
if (status === "已记录") log$3.info(head);
|
|
587
|
+
else log$3.warn(head);
|
|
588
|
+
if (!(system.apiDirs.length > 0 || system.webDirs.length > 0 || system.nginxConfs.length > 0)) continue;
|
|
589
|
+
if (system.apiDirs.length > 0) log$3.info(` api: ${system.apiDirs.join(", ")}`);
|
|
590
|
+
if (system.webDirs.length > 0) log$3.info(` web: ${system.webDirs.join(", ")}`);
|
|
591
|
+
if (system.nginxConfs.length > 0) log$3.info(` nginx: ${system.nginxConfs.join(", ")}`);
|
|
592
|
+
}
|
|
593
|
+
if (unownedNginxConfs.length > 0) {
|
|
594
|
+
log$3.warn(`[未归属 nginx 配置] ${unownedNginxConfs.length} 个`);
|
|
595
|
+
for (const conf of unownedNginxConfs) log$3.warn(` ${conf}`);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
function createCheckCommand() {
|
|
599
|
+
return new Command("check").description("盘点部署目录与 nginx 配置,标注已记录 / 未记录 / 记录但缺失").action(async () => {
|
|
600
|
+
runCheck();
|
|
601
|
+
});
|
|
602
|
+
}
|
|
491
603
|
//#endregion
|
|
492
604
|
//#region src/utils/dist-zip.ts
|
|
493
605
|
const ZPC_GITIGNORE_HEADER = "# zpc files";
|
|
@@ -511,8 +623,8 @@ function ensureZpcGitignore(projectRoot) {
|
|
|
511
623
|
return true;
|
|
512
624
|
}
|
|
513
625
|
//#endregion
|
|
514
|
-
//#region src/utils/
|
|
515
|
-
function isDirectoryNonEmpty(dir) {
|
|
626
|
+
//#region src/utils/deploy-conflict.ts
|
|
627
|
+
function isDirectoryNonEmpty$1(dir) {
|
|
516
628
|
if (!fs.existsSync(dir)) return false;
|
|
517
629
|
try {
|
|
518
630
|
return fs.readdirSync(dir).length > 0;
|
|
@@ -520,55 +632,80 @@ function isDirectoryNonEmpty(dir) {
|
|
|
520
632
|
return false;
|
|
521
633
|
}
|
|
522
634
|
}
|
|
523
|
-
/**
|
|
524
|
-
function
|
|
635
|
+
/** 收集「无 ~/.zpc 记录」时应提示用户的部署痕迹:api/web 目录非空、已存在/冲突的 nginx 配置。 */
|
|
636
|
+
function collectDeployConflicts(config) {
|
|
525
637
|
const projectName = config.projectName;
|
|
526
|
-
|
|
527
|
-
|
|
638
|
+
const conflicts = [];
|
|
639
|
+
if (!projectName) return conflicts;
|
|
528
640
|
for (const item of config.api ?? []) {
|
|
529
|
-
const
|
|
530
|
-
if (isDirectoryNonEmpty(
|
|
641
|
+
const dir = path.join(getApiRoot(), projectName, item.name);
|
|
642
|
+
if (isDirectoryNonEmpty$1(dir)) conflicts.push({
|
|
643
|
+
kind: "api-dir",
|
|
644
|
+
message: `api 部署目录非空: ${dir}`
|
|
645
|
+
});
|
|
531
646
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
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)));
|
|
647
|
+
for (const item of config.web ?? []) {
|
|
648
|
+
const dir = path.join(getWebRoot(), projectName, item.name);
|
|
649
|
+
if (isDirectoryNonEmpty$1(dir)) conflicts.push({
|
|
650
|
+
kind: "web-dir",
|
|
651
|
+
message: `web 部署目录非空: ${dir}`
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
const defaultD = getNginxDefaultD();
|
|
655
|
+
if (!fs.existsSync(defaultD)) return conflicts;
|
|
656
|
+
const proxies = (config.api ?? []).map((item) => item.proxy).filter((p) => !!p);
|
|
657
|
+
for (const name of fs.readdirSync(defaultD)) {
|
|
658
|
+
if (!name.endsWith(".conf")) continue;
|
|
659
|
+
const full = path.join(defaultD, name);
|
|
660
|
+
let text;
|
|
661
|
+
try {
|
|
662
|
+
text = fs.readFileSync(full, "utf8");
|
|
663
|
+
} catch {
|
|
664
|
+
continue;
|
|
567
665
|
}
|
|
568
|
-
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
|
|
666
|
+
const owned = parseNginxOwnedBy(text);
|
|
667
|
+
if (owned?.startsWith(`${projectName}/`) || name.startsWith(`${projectName}-`)) {
|
|
668
|
+
conflicts.push({
|
|
669
|
+
kind: "nginx",
|
|
670
|
+
message: `已存在本项目 nginx 配置: ${full}`
|
|
671
|
+
});
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
for (const proxy of proxies) if (confDefinesProxy(text, proxy)) {
|
|
675
|
+
conflicts.push({
|
|
676
|
+
kind: "nginx",
|
|
677
|
+
message: `nginx 配置 ${full} 已定义 proxy ${nginxLocation(proxy)}(归属 ${owned ?? "未知"})`
|
|
678
|
+
});
|
|
679
|
+
break;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return conflicts;
|
|
683
|
+
}
|
|
684
|
+
function isInteractive() {
|
|
685
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
686
|
+
}
|
|
687
|
+
/** 交互询问部署方式;非 TTY 环境直接返回取消(默认)。 */
|
|
688
|
+
async function promptDeployResolution() {
|
|
689
|
+
if (!isInteractive()) return "cancel";
|
|
690
|
+
const { select } = await import("@inquirer/prompts");
|
|
691
|
+
return await select({
|
|
692
|
+
message: "检测到已有部署痕迹(无 ~/.zpc 记录),请选择部署方式:",
|
|
693
|
+
choices: [
|
|
694
|
+
{
|
|
695
|
+
name: "清空目标文件夹并覆盖(全量,旧目录将备份)",
|
|
696
|
+
value: "full"
|
|
697
|
+
},
|
|
698
|
+
{
|
|
699
|
+
name: "使用增量部署(api 保留 public,跳过 web)",
|
|
700
|
+
value: "incremental"
|
|
701
|
+
},
|
|
702
|
+
{
|
|
703
|
+
name: "取消部署",
|
|
704
|
+
value: "cancel"
|
|
705
|
+
}
|
|
706
|
+
],
|
|
707
|
+
default: "cancel"
|
|
708
|
+
});
|
|
572
709
|
}
|
|
573
710
|
//#endregion
|
|
574
711
|
//#region src/utils/zip.ts
|
|
@@ -715,7 +852,7 @@ async function deployKind(stagingDir, items, kind, destRoot, projectName, increm
|
|
|
715
852
|
if (kind === "api" && !shouldSkipDeployRuntime()) await installAndReloadApi(pm2Name, dest, item, log$2.info);
|
|
716
853
|
}
|
|
717
854
|
}
|
|
718
|
-
async function runDeploy(dir, options) {
|
|
855
|
+
async function runDeploy(dir, options, prompt = promptDeployResolution) {
|
|
719
856
|
const force = Boolean(options.force) || process.env.DEPLOY_FORCE === "1" || process.env.DEPLOY_FORCE === "true";
|
|
720
857
|
const projectDir = resolveProjectDir(dir);
|
|
721
858
|
const stagingDir = path.join(projectDir, ".deploy-staging");
|
|
@@ -739,12 +876,30 @@ async function runDeploy(dir, options) {
|
|
|
739
876
|
throw new Error(`[deploy] 解压失败: ${message}`);
|
|
740
877
|
}
|
|
741
878
|
const zipIsIncremental = fs.existsSync(path.join(stagingDir, INCREMENTAL_MARKER));
|
|
742
|
-
const
|
|
743
|
-
|
|
879
|
+
const hadRecord = hasFullDeployRecord(projectDir);
|
|
880
|
+
let incremental;
|
|
881
|
+
let resolvedFromConflicts = false;
|
|
882
|
+
if (hadRecord) incremental = true;
|
|
883
|
+
else {
|
|
884
|
+
const conflicts = collectDeployConflicts(config);
|
|
885
|
+
if (conflicts.length > 0) {
|
|
886
|
+
for (const conflict of conflicts) log$2.warn(conflict.message);
|
|
887
|
+
const choice = await prompt();
|
|
888
|
+
if (choice === "cancel") {
|
|
889
|
+
log$2.warn("已取消部署");
|
|
890
|
+
fs.rmSync(stagingDir, {
|
|
891
|
+
recursive: true,
|
|
892
|
+
force: true
|
|
893
|
+
});
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
incremental = choice === "incremental";
|
|
897
|
+
resolvedFromConflicts = true;
|
|
898
|
+
} else incremental = zipIsIncremental;
|
|
899
|
+
}
|
|
744
900
|
try {
|
|
745
901
|
log$2.info(`模式: ${incremental ? "增量(api 保留 public,跳过 web)" : "全量"}`);
|
|
746
|
-
if (
|
|
747
|
-
else if (!incremental) log$2.info("首次部署该项目,完成后将记录到 ~/.zpc/deploy-state.json");
|
|
902
|
+
if (!incremental && !hadRecord) log$2.info("首次部署该项目,完成后将记录到 ~/.zpc/deploy-state.json");
|
|
748
903
|
if (force) log$2.warn("已启用 --force(允许抢占冲突的 nginx proxy)");
|
|
749
904
|
await deployKind(stagingDir, config.api, "api", getApiRoot(), config.projectName, incremental);
|
|
750
905
|
if (!incremental) await deployKind(stagingDir, config.web, "web", getWebRoot(), config.projectName, false);
|
|
@@ -762,7 +917,7 @@ async function runDeploy(dir, options) {
|
|
|
762
917
|
if (!hasFullDeployRecord(projectDir)) {
|
|
763
918
|
markProjectFullDeployed(projectDir, config.projectName);
|
|
764
919
|
if (!incremental) log$2.info("已记录全量部署状态到 ~/.zpc/deploy-state.json,下次 deploy 将默认增量");
|
|
765
|
-
else if (
|
|
920
|
+
else if (resolvedFromConflicts) log$2.info("已根据现有部署目录建立部署记录,下次 deploy 将默认增量");
|
|
766
921
|
else if (zipIsIncremental) log$2.info("已记录部署状态,下次 deploy 将默认增量");
|
|
767
922
|
}
|
|
768
923
|
const apiCount = config.api?.length ?? 0;
|
|
@@ -808,6 +963,57 @@ function createInitCommand() {
|
|
|
808
963
|
});
|
|
809
964
|
}
|
|
810
965
|
//#endregion
|
|
966
|
+
//#region src/utils/incremental-mode.ts
|
|
967
|
+
function isDirectoryNonEmpty(dir) {
|
|
968
|
+
if (!fs.existsSync(dir)) return false;
|
|
969
|
+
try {
|
|
970
|
+
return fs.readdirSync(dir).length > 0;
|
|
971
|
+
} catch {
|
|
972
|
+
return false;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
/** 配置中所有部署目录(api + web)里已存在且非空的目录。 */
|
|
976
|
+
function findNonemptyDeployDirs(config) {
|
|
977
|
+
const projectName = config.projectName;
|
|
978
|
+
if (!projectName) return [];
|
|
979
|
+
const found = [];
|
|
980
|
+
for (const item of config.api ?? []) {
|
|
981
|
+
const deployDir = path.join(getApiRoot(), projectName, item.name);
|
|
982
|
+
if (isDirectoryNonEmpty(deployDir)) found.push(deployDir);
|
|
983
|
+
}
|
|
984
|
+
for (const item of config.web ?? []) {
|
|
985
|
+
const deployDir = path.join(getWebRoot(), projectName, item.name);
|
|
986
|
+
if (isDirectoryNonEmpty(deployDir)) found.push(deployDir);
|
|
987
|
+
}
|
|
988
|
+
return found;
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* 全量 / 增量判定(pack 只读、deploy 读后写入):
|
|
992
|
+
* 1. ~/.zpc/deploy-state.json 有记录 → 增量
|
|
993
|
+
* 2. 无记录但 api/web 部署目录非空 → 交由调用方决策(不直接判定)
|
|
994
|
+
* 3. 无记录但 zip 含增量标记 → 增量
|
|
995
|
+
* 4. 否则全量
|
|
996
|
+
*/
|
|
997
|
+
function resolveIncrementalMode(projectRoot, config, options = {}) {
|
|
998
|
+
if (hasFullDeployRecord(projectRoot)) return {
|
|
999
|
+
incremental: true,
|
|
1000
|
+
existingDeployDirs: []
|
|
1001
|
+
};
|
|
1002
|
+
const existingDeployDirs = findNonemptyDeployDirs(config);
|
|
1003
|
+
if (existingDeployDirs.length > 0) return {
|
|
1004
|
+
incremental: false,
|
|
1005
|
+
existingDeployDirs
|
|
1006
|
+
};
|
|
1007
|
+
if (options.zipIsIncremental) return {
|
|
1008
|
+
incremental: true,
|
|
1009
|
+
existingDeployDirs: []
|
|
1010
|
+
};
|
|
1011
|
+
return {
|
|
1012
|
+
incremental: false,
|
|
1013
|
+
existingDeployDirs: []
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
//#endregion
|
|
811
1017
|
//#region src/commands/pack.ts
|
|
812
1018
|
const log = createLogger("pack");
|
|
813
1019
|
function resolveProjects(root, config, incremental) {
|
|
@@ -926,12 +1132,15 @@ async function runPack(dir, options) {
|
|
|
926
1132
|
const { file, config } = await loadConfig(projectRoot);
|
|
927
1133
|
const zipPath = distZipPath(projectRoot, config.projectName);
|
|
928
1134
|
const packMode = resolveIncrementalMode(projectRoot, config);
|
|
929
|
-
const incremental = packMode.incremental;
|
|
1135
|
+
const incremental = packMode.incremental || packMode.existingDeployDirs.length > 0;
|
|
930
1136
|
log.info(`项目目录: ${projectRoot}`);
|
|
931
1137
|
log.info(`配置: ${file}`);
|
|
932
1138
|
log.info(`项目: ${config.projectName}`);
|
|
933
1139
|
log.info(`模式: ${incremental ? "增量(仅 api,不含 public)" : "全量"}`);
|
|
934
|
-
if (packMode.
|
|
1140
|
+
if (packMode.existingDeployDirs.length > 0) {
|
|
1141
|
+
const listed = packMode.existingDeployDirs.map((dir) => path.normalize(dir)).join(", ");
|
|
1142
|
+
log.warn(`无 ~/.zpc 部署记录,但检测到部署目录非空:${listed}。将按增量处理(api 保留 public,跳过 web);若确需全量请清空部署目录并删除 deploy-state 中对应记录`);
|
|
1143
|
+
}
|
|
935
1144
|
if (options.concurrency !== void 0) log.info(`并行上限: ${options.concurrency}`);
|
|
936
1145
|
const projects = resolveProjects(projectRoot, config, incremental);
|
|
937
1146
|
await buildAll(projects, config, options);
|
|
@@ -964,7 +1173,7 @@ function createPackCommand() {
|
|
|
964
1173
|
//#endregion
|
|
965
1174
|
//#region src/utils/program.ts
|
|
966
1175
|
function createProgram() {
|
|
967
|
-
const program = new Command().name("zpc").description(description).version(version).showHelpAfterError().addCommand(createInitCommand()).addCommand(createPackCommand()).addCommand(createDeployCommand());
|
|
1176
|
+
const program = new Command().name("zpc").description(description).version(version).showHelpAfterError().addCommand(createInitCommand()).addCommand(createPackCommand()).addCommand(createDeployCommand()).addCommand(createCheckCommand());
|
|
968
1177
|
program.action(() => {
|
|
969
1178
|
program.outputHelp();
|
|
970
1179
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhipu/zp-cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "内部单命令部署工具",
|
|
5
5
|
"bin": {
|
|
6
6
|
"zpc": "./dist/cli.mjs"
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@commander-js/extra-typings": "^15.0.0",
|
|
22
|
+
"@inquirer/prompts": "^8.6.0",
|
|
22
23
|
"@vue-tui/components": "^0.3.0",
|
|
23
24
|
"@vue-tui/runtime": "^0.3.0",
|
|
24
25
|
"chalk": "^6.0.0",
|
|
@@ -55,6 +56,21 @@
|
|
|
55
56
|
"@alcalzone/ansi-tokenize": "0.3.0",
|
|
56
57
|
"@babel/parser": "7.29.8",
|
|
57
58
|
"@commander-js/extra-typings": "15.0.0",
|
|
59
|
+
"@inquirer/ansi": "2.0.7",
|
|
60
|
+
"@inquirer/checkbox": "5.2.2",
|
|
61
|
+
"@inquirer/confirm": "6.2.0",
|
|
62
|
+
"@inquirer/core": "12.0.0",
|
|
63
|
+
"@inquirer/editor": "5.3.0",
|
|
64
|
+
"@inquirer/expand": "5.1.2",
|
|
65
|
+
"@inquirer/external-editor": "3.0.4",
|
|
66
|
+
"@inquirer/figures": "2.0.8",
|
|
67
|
+
"@inquirer/input": "5.1.3",
|
|
68
|
+
"@inquirer/number": "4.2.0",
|
|
69
|
+
"@inquirer/password": "5.1.2",
|
|
70
|
+
"@inquirer/prompts": "8.6.0",
|
|
71
|
+
"@inquirer/rawlist": "5.3.2",
|
|
72
|
+
"@inquirer/search": "4.3.0",
|
|
73
|
+
"@inquirer/select": "5.2.2",
|
|
58
74
|
"@vue-tui/components": "0.3.0",
|
|
59
75
|
"@vue-tui/runtime": "0.3.0",
|
|
60
76
|
"@vue/compiler-core": "3.5.41",
|
|
@@ -69,16 +85,24 @@
|
|
|
69
85
|
"5.6.2",
|
|
70
86
|
"6.0.0"
|
|
71
87
|
],
|
|
88
|
+
"chardet": "2.2.0",
|
|
72
89
|
"cli-boxes": "3.0.0",
|
|
73
90
|
"cli-truncate": "6.1.1",
|
|
91
|
+
"cli-width": "4.1.0",
|
|
74
92
|
"commander": "15.0.0",
|
|
75
93
|
"entities": "7.0.1",
|
|
76
94
|
"environment": "1.1.0",
|
|
77
95
|
"estree-walker": "2.0.2",
|
|
96
|
+
"fast-string-truncated-width": "3.0.3",
|
|
97
|
+
"fast-string-width": "3.0.2",
|
|
98
|
+
"fast-wrap-ansi": "0.2.2",
|
|
78
99
|
"get-east-asian-width": "1.6.0",
|
|
100
|
+
"iconv-lite": "0.7.3",
|
|
79
101
|
"is-fullwidth-code-point": "5.1.0",
|
|
80
102
|
"jiti": "2.7.0",
|
|
103
|
+
"mute-stream": "3.0.0",
|
|
81
104
|
"patch-console": "2.0.0",
|
|
105
|
+
"safer-buffer": "2.1.2",
|
|
82
106
|
"signal-exit": "4.1.0",
|
|
83
107
|
"slice-ansi": "9.0.0",
|
|
84
108
|
"source-map-js": "1.2.1",
|