ai-yuca 1.0.5 → 1.0.6
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/.cdn.cache.json +5 -1
- package/bin/cli.ts +40 -1
- package/dist/bin/cli.js +38 -0
- package/dist/package.json +4 -1
- package/dist/src/cache.d.ts +8 -0
- package/dist/src/cache.js +36 -0
- package/dist/src/deploy.d.ts +8 -0
- package/dist/src/deploy.js +480 -0
- package/dist/src/types/deploy.d.ts +68 -0
- package/dist/src/types/deploy.js +5 -0
- package/dist/src/types/index.d.ts +1 -0
- package/dist/src/types/index.js +2 -0
- package/out/about.html +20 -0
- package/out/index.html +15 -0
- package/package.json +4 -1
- package/src/cache.ts +39 -0
- package/src/deploy.ts +543 -0
- package/src/types/deploy.ts +73 -0
- package/src/types/index.ts +4 -1
package/.cdn.cache.json
CHANGED
package/bin/cli.ts
CHANGED
|
@@ -8,7 +8,8 @@ import { analyze } from '../src/index';
|
|
|
8
8
|
import { createStorageClient, uploadFiles } from '../src/upload';
|
|
9
9
|
import { downloadFiles } from '../src/download';
|
|
10
10
|
import { uploadFilesWithConfig, getConfigSummary } from '../src/uploadWithConfig';
|
|
11
|
-
import {
|
|
11
|
+
import { deployFiles } from '../src/deploy';
|
|
12
|
+
import { AnalyzeOptions, UploadFilesResult, UploadCommandOptions, DownloadCommandOptions, UploadWithConfigOptions, DeployCommandOptions } from '../src/types';
|
|
12
13
|
|
|
13
14
|
const program = new Command();
|
|
14
15
|
|
|
@@ -27,6 +28,44 @@ program
|
|
|
27
28
|
analyze(options);
|
|
28
29
|
});
|
|
29
30
|
|
|
31
|
+
// 添加deploy命令
|
|
32
|
+
program
|
|
33
|
+
.command('deploy')
|
|
34
|
+
.description('部署文件到指定环境')
|
|
35
|
+
.requiredOption('-e, --env <environment>', '部署环境(如:dev、test、production)')
|
|
36
|
+
.option('-c, --config <path>', '指定配置文件路径(默认为项目根目录下的vs.config.json)')
|
|
37
|
+
.option('-k, --key-file <path>', 'GCP服务账号密钥文件路径(可选,不提供则使用应用默认凭证)')
|
|
38
|
+
.option('-s, --source <path>', '自定义源路径(覆盖配置文件中的uploadPath)')
|
|
39
|
+
.option('-d, --destination <path>', '自定义目标路径(覆盖配置文件中的目标路径)')
|
|
40
|
+
.option('--no-recursive', '禁用递归上传目录中的文件')
|
|
41
|
+
.option('--no-compression', '禁用GZIP压缩')
|
|
42
|
+
.option('--no-cache', '禁用文件缓存功能')
|
|
43
|
+
.option('--cache-file <path>', '指定缓存文件路径(默认为.cdn.cache.json)')
|
|
44
|
+
.option('--show-config', '仅显示配置信息,不执行部署')
|
|
45
|
+
.action(async (options: DeployCommandOptions) => {
|
|
46
|
+
try {
|
|
47
|
+
const result = await deployFiles(options);
|
|
48
|
+
|
|
49
|
+
if (result.success) {
|
|
50
|
+
console.log(`\n✅ ${result.message}`);
|
|
51
|
+
console.log(`📦 项目名称: ${result.projectName}`);
|
|
52
|
+
console.log(`🏷️ 版本号: ${result.cdnKey}`);
|
|
53
|
+
console.log(`🌍 环境: ${result.environment}`);
|
|
54
|
+
console.log(`📁 上传文件数: ${result.uploadedFiles}`);
|
|
55
|
+
|
|
56
|
+
if (result.verificationUrl) {
|
|
57
|
+
console.log(`🔗 验证地址: ${result.verificationUrl}`);
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
console.error(`\n❌ ${result.message}`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
} catch (err) {
|
|
64
|
+
console.error(`部署错误: ${err instanceof Error ? err.message : String(err)}`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
30
69
|
// 使用从types导入的UploadCommandOptions接口
|
|
31
70
|
|
|
32
71
|
// 添加upload命令
|
package/dist/bin/cli.js
CHANGED
|
@@ -42,6 +42,7 @@ const index_1 = require("../src/index");
|
|
|
42
42
|
const upload_1 = require("../src/upload");
|
|
43
43
|
const download_1 = require("../src/download");
|
|
44
44
|
const uploadWithConfig_1 = require("../src/uploadWithConfig");
|
|
45
|
+
const deploy_1 = require("../src/deploy");
|
|
45
46
|
const program = new commander_1.Command();
|
|
46
47
|
// 设置版本和描述
|
|
47
48
|
program
|
|
@@ -56,6 +57,43 @@ program
|
|
|
56
57
|
.action((options) => {
|
|
57
58
|
(0, index_1.analyze)(options);
|
|
58
59
|
});
|
|
60
|
+
// 添加deploy命令
|
|
61
|
+
program
|
|
62
|
+
.command('deploy')
|
|
63
|
+
.description('部署文件到指定环境')
|
|
64
|
+
.requiredOption('-e, --env <environment>', '部署环境(如:dev、test、production)')
|
|
65
|
+
.option('-c, --config <path>', '指定配置文件路径(默认为项目根目录下的vs.config.json)')
|
|
66
|
+
.option('-k, --key-file <path>', 'GCP服务账号密钥文件路径(可选,不提供则使用应用默认凭证)')
|
|
67
|
+
.option('-s, --source <path>', '自定义源路径(覆盖配置文件中的uploadPath)')
|
|
68
|
+
.option('-d, --destination <path>', '自定义目标路径(覆盖配置文件中的目标路径)')
|
|
69
|
+
.option('--no-recursive', '禁用递归上传目录中的文件')
|
|
70
|
+
.option('--no-compression', '禁用GZIP压缩')
|
|
71
|
+
.option('--no-cache', '禁用文件缓存功能')
|
|
72
|
+
.option('--cache-file <path>', '指定缓存文件路径(默认为.cdn.cache.json)')
|
|
73
|
+
.option('--show-config', '仅显示配置信息,不执行部署')
|
|
74
|
+
.action(async (options) => {
|
|
75
|
+
try {
|
|
76
|
+
const result = await (0, deploy_1.deployFiles)(options);
|
|
77
|
+
if (result.success) {
|
|
78
|
+
console.log(`\n✅ ${result.message}`);
|
|
79
|
+
console.log(`📦 项目名称: ${result.projectName}`);
|
|
80
|
+
console.log(`🏷️ 版本号: ${result.cdnKey}`);
|
|
81
|
+
console.log(`🌍 环境: ${result.environment}`);
|
|
82
|
+
console.log(`📁 上传文件数: ${result.uploadedFiles}`);
|
|
83
|
+
if (result.verificationUrl) {
|
|
84
|
+
console.log(`🔗 验证地址: ${result.verificationUrl}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
console.error(`\n❌ ${result.message}`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
console.error(`部署错误: ${err instanceof Error ? err.message : String(err)}`);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
59
97
|
// 使用从types导入的UploadCommandOptions接口
|
|
60
98
|
// 添加upload命令
|
|
61
99
|
program
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-yuca",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "一个实用的AI辅助工具",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"types": "dist/src/index.d.ts",
|
|
@@ -31,12 +31,15 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@google-cloud/storage": "^7.17.1",
|
|
34
|
+
"axios": "^1.12.1",
|
|
34
35
|
"commander": "^10.0.0",
|
|
36
|
+
"inquirer": "^12.9.4",
|
|
35
37
|
"md5-file": "^5.0.0"
|
|
36
38
|
},
|
|
37
39
|
"devDependencies": {
|
|
38
40
|
"@types/chai": "^5.2.2",
|
|
39
41
|
"@types/google-cloud__storage": "^2.3.1",
|
|
42
|
+
"@types/inquirer": "^9.0.9",
|
|
40
43
|
"@types/mocha": "^10.0.10",
|
|
41
44
|
"@types/node": "^24.3.1",
|
|
42
45
|
"@types/sinon": "^17.0.4",
|
package/dist/src/cache.d.ts
CHANGED
|
@@ -55,3 +55,11 @@ export declare class CacheManager {
|
|
|
55
55
|
* 创建默认的缓存管理器实例
|
|
56
56
|
*/
|
|
57
57
|
export declare function createCacheManager(cacheFilePath?: string): CacheManager;
|
|
58
|
+
/**
|
|
59
|
+
* 读取最后上传的缓存文件版本号
|
|
60
|
+
*/
|
|
61
|
+
export declare function readLastUploadCacheFile(cacheFile?: string): string | null;
|
|
62
|
+
/**
|
|
63
|
+
* 写入最后部署的版本号
|
|
64
|
+
*/
|
|
65
|
+
export declare function writeLastDeployVersion(cdnKey: string, cacheFile?: string): void;
|
package/dist/src/cache.js
CHANGED
|
@@ -35,6 +35,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.CacheManager = void 0;
|
|
37
37
|
exports.createCacheManager = createCacheManager;
|
|
38
|
+
exports.readLastUploadCacheFile = readLastUploadCacheFile;
|
|
39
|
+
exports.writeLastDeployVersion = writeLastDeployVersion;
|
|
38
40
|
const fs = __importStar(require("fs"));
|
|
39
41
|
const path = __importStar(require("path"));
|
|
40
42
|
const md5File = __importStar(require("md5-file"));
|
|
@@ -146,3 +148,37 @@ exports.CacheManager = CacheManager;
|
|
|
146
148
|
function createCacheManager(cacheFilePath) {
|
|
147
149
|
return new CacheManager(cacheFilePath);
|
|
148
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* 读取最后上传的缓存文件版本号
|
|
153
|
+
*/
|
|
154
|
+
function readLastUploadCacheFile(cacheFile) {
|
|
155
|
+
const cacheFilePath = cacheFile || path.join(process.cwd(), '.cdn.cache.json');
|
|
156
|
+
if (!fs.existsSync(cacheFilePath)) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
const cache = JSON.parse(fs.readFileSync(cacheFilePath, 'utf8'));
|
|
161
|
+
return cache.lastUploadVersion || null;
|
|
162
|
+
}
|
|
163
|
+
catch (_a) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* 写入最后部署的版本号
|
|
169
|
+
*/
|
|
170
|
+
function writeLastDeployVersion(cdnKey, cacheFile) {
|
|
171
|
+
const cacheFilePath = cacheFile || path.join(process.cwd(), '.cdn.cache.json');
|
|
172
|
+
let cache = {};
|
|
173
|
+
if (fs.existsSync(cacheFilePath)) {
|
|
174
|
+
try {
|
|
175
|
+
cache = JSON.parse(fs.readFileSync(cacheFilePath, 'utf8'));
|
|
176
|
+
}
|
|
177
|
+
catch (_a) {
|
|
178
|
+
cache = {};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
cache.lastDeployVersion = cdnKey;
|
|
182
|
+
cache.lastDeployTime = new Date().toISOString();
|
|
183
|
+
fs.writeFileSync(cacheFilePath, JSON.stringify(cache, null, 2));
|
|
184
|
+
}
|
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Deploy命令核心逻辑实现
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
39
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
40
|
+
};
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.deployFiles = deployFiles;
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const axios_1 = __importDefault(require("axios"));
|
|
46
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
47
|
+
const upload_1 = require("./upload");
|
|
48
|
+
const uploadWithConfig_1 = require("./uploadWithConfig");
|
|
49
|
+
const cache_1 = require("./cache");
|
|
50
|
+
const child_process_1 = require("child_process");
|
|
51
|
+
/**
|
|
52
|
+
* 获取环境配置文件
|
|
53
|
+
*/
|
|
54
|
+
const getConfigFiles = (env, config) => {
|
|
55
|
+
return new Promise(async (resolve) => {
|
|
56
|
+
try {
|
|
57
|
+
const { data: sitemap = { projects: [] } } = await axios_1.default.get(`${config.aws.HostName}/${config.aws.prefix}/static/config/${env}.config.json`);
|
|
58
|
+
resolve({ data: sitemap });
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
resolve({ data: { projects: [] } });
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* 验证配置文件
|
|
67
|
+
*/
|
|
68
|
+
function validateConfig(configPath) {
|
|
69
|
+
var _a;
|
|
70
|
+
const configFile = configPath || path.join(process.cwd(), 'vs.config.json');
|
|
71
|
+
if (!fs.existsSync(configFile)) {
|
|
72
|
+
throw new Error(`配置文件不存在: ${configFile}`);
|
|
73
|
+
}
|
|
74
|
+
const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
|
|
75
|
+
// 1. 配置校验阶段:检查发布配置
|
|
76
|
+
if (!((_a = config.deploy) === null || _a === void 0 ? void 0 : _a.baseUrl)) {
|
|
77
|
+
throw new Error('发布失败:.vs.config.json配置缺少 { deploy: { baseUrl: "xxx" } or { baseUrl: ["xxx", "yyy"] } },baseUrl必须以"/"结尾。');
|
|
78
|
+
}
|
|
79
|
+
// 支持字符串或数组类型的baseUrl
|
|
80
|
+
const baseUrls = Array.isArray(config.deploy.baseUrl) ? config.deploy.baseUrl : [config.deploy.baseUrl];
|
|
81
|
+
// 验证每个baseUrl格式
|
|
82
|
+
for (let i = 0; i < baseUrls.length; i++) {
|
|
83
|
+
if (typeof baseUrls[i] !== 'string' || !baseUrls[i].endsWith('/')) {
|
|
84
|
+
throw new Error('发布失败:.vs.config.json配置缺少 { deploy: { baseUrl: "xxx" } or { baseUrl: ["xxx", "yyy"] } },baseUrl必须以"/"结尾。');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return config;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* 读取项目信息
|
|
91
|
+
*/
|
|
92
|
+
function getProjectInfo() {
|
|
93
|
+
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
94
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
95
|
+
throw new Error('package.json 文件不存在');
|
|
96
|
+
}
|
|
97
|
+
return JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 生成版本号
|
|
101
|
+
*/
|
|
102
|
+
function generateVersion() {
|
|
103
|
+
return Date.now().toString(36);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* 获取Git分支名称
|
|
107
|
+
*/
|
|
108
|
+
function getBranchName() {
|
|
109
|
+
try {
|
|
110
|
+
// 尝试从环境变量获取
|
|
111
|
+
if (process.env.GIT_BRANCH) {
|
|
112
|
+
return process.env.GIT_BRANCH;
|
|
113
|
+
}
|
|
114
|
+
// 尝试从git命令获取
|
|
115
|
+
const branch = (0, child_process_1.execSync)('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8' }).trim();
|
|
116
|
+
return branch || 'main';
|
|
117
|
+
}
|
|
118
|
+
catch (_a) {
|
|
119
|
+
return 'main';
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* 读取缓存文件中的版本号
|
|
124
|
+
*/
|
|
125
|
+
function getCachedVersion(cacheFile) {
|
|
126
|
+
const cacheFilePath = cacheFile || path.join(process.cwd(), '.cdn.cache.json');
|
|
127
|
+
if (!fs.existsSync(cacheFilePath)) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
const cache = JSON.parse(fs.readFileSync(cacheFilePath, 'utf8'));
|
|
132
|
+
return cache.lastUploadVersion || null;
|
|
133
|
+
}
|
|
134
|
+
catch (_a) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* 筛选HTML文件
|
|
140
|
+
*/
|
|
141
|
+
function filterHtmlFiles(sourcePath) {
|
|
142
|
+
const files = [];
|
|
143
|
+
function scanDirectory(dir) {
|
|
144
|
+
const items = fs.readdirSync(dir);
|
|
145
|
+
for (const item of items) {
|
|
146
|
+
const fullPath = path.join(dir, item);
|
|
147
|
+
const stat = fs.statSync(fullPath);
|
|
148
|
+
if (stat.isDirectory()) {
|
|
149
|
+
scanDirectory(fullPath);
|
|
150
|
+
}
|
|
151
|
+
else if (path.extname(item).toLowerCase() === '.html') {
|
|
152
|
+
files.push(fullPath);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (fs.statSync(sourcePath).isDirectory()) {
|
|
157
|
+
scanDirectory(sourcePath);
|
|
158
|
+
}
|
|
159
|
+
else if (path.extname(sourcePath).toLowerCase() === '.html') {
|
|
160
|
+
files.push(sourcePath);
|
|
161
|
+
}
|
|
162
|
+
return files;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* 上传文件到GCP
|
|
166
|
+
*/
|
|
167
|
+
async function uploadToGcp(filePath, bucketName, destination, keyFile) {
|
|
168
|
+
try {
|
|
169
|
+
const client = (0, upload_1.createStorageClient)(keyFile ? { keyFilename: keyFile } : {});
|
|
170
|
+
const result = await (0, upload_1.uploadFile)({
|
|
171
|
+
bucketName,
|
|
172
|
+
filePath,
|
|
173
|
+
destination,
|
|
174
|
+
storageClient: client,
|
|
175
|
+
enableCompression: true
|
|
176
|
+
});
|
|
177
|
+
if (result.success) {
|
|
178
|
+
return { success: true, url: result.url };
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
return { success: false, error: result.error };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* 上传版本配置文件
|
|
190
|
+
*/
|
|
191
|
+
async function uploadVersionConfig(versionInfo, bucketName, prefix, env, projectName, keyFile) {
|
|
192
|
+
try {
|
|
193
|
+
const configPath = `${prefix}/static/config/${env}/${projectName}/${versionInfo.cdnKey}.json`;
|
|
194
|
+
const tempFile = path.join(process.cwd(), `temp-${versionInfo.cdnKey}.json`);
|
|
195
|
+
// 写入临时文件
|
|
196
|
+
fs.writeFileSync(tempFile, JSON.stringify(versionInfo, null, 2));
|
|
197
|
+
// 上传到GCP
|
|
198
|
+
const result = await uploadToGcp(tempFile, bucketName, configPath, keyFile);
|
|
199
|
+
// 删除临时文件
|
|
200
|
+
fs.unlinkSync(tempFile);
|
|
201
|
+
return result.success;
|
|
202
|
+
}
|
|
203
|
+
catch (_a) {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* 维护版本列表
|
|
209
|
+
*/
|
|
210
|
+
async function maintainVersionList(versions, bucketName, prefix, env, projectName, keyFile) {
|
|
211
|
+
try {
|
|
212
|
+
// 保留最近50个版本
|
|
213
|
+
const keepVersions = versions.slice(-50);
|
|
214
|
+
const versionKeepPath = `${prefix}/static/config/${env}/${projectName}/version.keep.json`;
|
|
215
|
+
const tempFile = path.join(process.cwd(), 'temp-version-keep.json');
|
|
216
|
+
// 写入临时文件
|
|
217
|
+
fs.writeFileSync(tempFile, JSON.stringify({ versions: keepVersions }, null, 2));
|
|
218
|
+
// 上传到GCP
|
|
219
|
+
const result = await uploadToGcp(tempFile, bucketName, versionKeepPath, keyFile);
|
|
220
|
+
// 删除临时文件
|
|
221
|
+
fs.unlinkSync(tempFile);
|
|
222
|
+
return result.success;
|
|
223
|
+
}
|
|
224
|
+
catch (_a) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* 更新环境配置
|
|
230
|
+
*/
|
|
231
|
+
async function updateEnvironmentConfig(env, projectName, versionInfo, config, keyFile) {
|
|
232
|
+
try {
|
|
233
|
+
// 获取当前环境配置
|
|
234
|
+
const { data: envConfig } = await getConfigFiles(env, config);
|
|
235
|
+
// 查找或创建项目配置
|
|
236
|
+
let projectConfig = envConfig.projects.find((p) => p.name === projectName);
|
|
237
|
+
if (!projectConfig) {
|
|
238
|
+
projectConfig = {
|
|
239
|
+
name: projectName,
|
|
240
|
+
latestVersion: versionInfo.cdnKey,
|
|
241
|
+
versions: [versionInfo]
|
|
242
|
+
};
|
|
243
|
+
envConfig.projects.push(projectConfig);
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
projectConfig.latestVersion = versionInfo.cdnKey;
|
|
247
|
+
projectConfig.versions = projectConfig.versions || [];
|
|
248
|
+
projectConfig.versions.push(versionInfo);
|
|
249
|
+
// 保留最近50个版本
|
|
250
|
+
projectConfig.versions = projectConfig.versions.slice(-50);
|
|
251
|
+
}
|
|
252
|
+
// 上传更新后的环境配置
|
|
253
|
+
const configPath = `${config.aws.prefix}/static/config/${env}.config.json`;
|
|
254
|
+
const tempFile = path.join(process.cwd(), `temp-${env}-config.json`);
|
|
255
|
+
fs.writeFileSync(tempFile, JSON.stringify(envConfig, null, 2));
|
|
256
|
+
const result = await uploadToGcp(tempFile, config.bucketName, configPath, keyFile);
|
|
257
|
+
fs.unlinkSync(tempFile);
|
|
258
|
+
return result.success;
|
|
259
|
+
}
|
|
260
|
+
catch (_a) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* 写入缓存文件
|
|
266
|
+
*/
|
|
267
|
+
function writeCacheFile(cdnKey, cacheFile) {
|
|
268
|
+
const cacheFilePath = cacheFile || path.join(process.cwd(), '.cdn.cache.json');
|
|
269
|
+
let cache = {};
|
|
270
|
+
if (fs.existsSync(cacheFilePath)) {
|
|
271
|
+
try {
|
|
272
|
+
cache = JSON.parse(fs.readFileSync(cacheFilePath, 'utf8'));
|
|
273
|
+
}
|
|
274
|
+
catch (_a) {
|
|
275
|
+
cache = {};
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
cache.lastUploadVersion = cdnKey;
|
|
279
|
+
cache.lastDeployTime = new Date().toISOString();
|
|
280
|
+
fs.writeFileSync(cacheFilePath, JSON.stringify(cache, null, 2));
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* 执行deploy命令
|
|
284
|
+
*/
|
|
285
|
+
async function deployFiles(options) {
|
|
286
|
+
try {
|
|
287
|
+
console.log('🚀 开始部署流程...');
|
|
288
|
+
// 1. 配置校验阶段
|
|
289
|
+
console.log('📋 验证配置文件...');
|
|
290
|
+
const config = validateConfig(options.config);
|
|
291
|
+
// 2. 密钥与客户端初始化阶段
|
|
292
|
+
console.log('⚙️ 继承当前项目中GCP上传的配置...');
|
|
293
|
+
const uploadConfig = (0, uploadWithConfig_1.getConfigSummary)(options.config);
|
|
294
|
+
// 3. 版本与文件准备阶段
|
|
295
|
+
console.log('📦 确定发布版本号...');
|
|
296
|
+
const version = generateVersion();
|
|
297
|
+
const cdnKey = options.cdn ? ((0, cache_1.readLastUploadCacheFile)(options.cacheFile) || version) : version;
|
|
298
|
+
console.log('📖 读取项目信息...');
|
|
299
|
+
const projectInfo = getProjectInfo();
|
|
300
|
+
console.log('🔍 筛选目标文件...');
|
|
301
|
+
const sourcePath = options.source || uploadConfig.sourcePath;
|
|
302
|
+
const files = filterHtmlFiles(sourcePath);
|
|
303
|
+
const cdnFiles = files.map(file => ({ key: path.relative(sourcePath, file) }));
|
|
304
|
+
console.log('📁 定义基础路径...');
|
|
305
|
+
const prefix = `${config.aws.prefix}/${config.upload.s3Static}`;
|
|
306
|
+
if (files.length === 0) {
|
|
307
|
+
throw new Error('未找到需要上传的HTML文件');
|
|
308
|
+
}
|
|
309
|
+
// 4. 文件处理与上传阶段
|
|
310
|
+
console.log('📤 上传原始HTML文件...');
|
|
311
|
+
const uploadedFiles = [];
|
|
312
|
+
for (const file of files) {
|
|
313
|
+
const relativePath = path.relative(sourcePath, file);
|
|
314
|
+
const destination = `${prefix}/${cdnKey}/${relativePath}`;
|
|
315
|
+
console.log(` 上传: ${relativePath}`);
|
|
316
|
+
const result = await (0, upload_1.uploadFile)({
|
|
317
|
+
bucketName: uploadConfig.bucketName,
|
|
318
|
+
filePath: file,
|
|
319
|
+
destination,
|
|
320
|
+
storageClient: (0, upload_1.createStorageClient)(options.keyFile ? { keyFilename: options.keyFile } : {}),
|
|
321
|
+
enableCompression: true
|
|
322
|
+
});
|
|
323
|
+
if (result.success) {
|
|
324
|
+
uploadedFiles.push(relativePath);
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
console.warn(` 警告: ${relativePath} 上传失败: ${result.error}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
// 5. 发布配置文件上传阶段
|
|
331
|
+
console.log('📝 生成发布信息对象...');
|
|
332
|
+
const langCdnHtml = []; // 多语言HTML文件,暂时为空
|
|
333
|
+
const obj = {
|
|
334
|
+
cdnVersion: cdnKey,
|
|
335
|
+
name: projectInfo.name,
|
|
336
|
+
baseUrl: config.deploy.baseUrl,
|
|
337
|
+
links: [...cdnFiles.map(v => v.key), ...langCdnHtml],
|
|
338
|
+
s3Static: config.upload.s3Static,
|
|
339
|
+
branch: getBranchName()
|
|
340
|
+
};
|
|
341
|
+
console.log('📤 上传版本配置文件...');
|
|
342
|
+
const versionConfigPath = `${config.aws.prefix}/static/config/${options.env}/${projectInfo.name}/${cdnKey}.json`;
|
|
343
|
+
const versionConfigContent = JSON.stringify({ obj, files: uploadedFiles }, null, 2);
|
|
344
|
+
// 创建临时文件上传JSON内容
|
|
345
|
+
const tempVersionFile = path.join(process.cwd(), `.temp-version-${cdnKey}.json`);
|
|
346
|
+
fs.writeFileSync(tempVersionFile, versionConfigContent);
|
|
347
|
+
try {
|
|
348
|
+
await (0, upload_1.uploadFile)({
|
|
349
|
+
bucketName: uploadConfig.bucketName,
|
|
350
|
+
filePath: tempVersionFile,
|
|
351
|
+
destination: versionConfigPath,
|
|
352
|
+
storageClient: (0, upload_1.createStorageClient)(options.keyFile ? { keyFilename: options.keyFile } : {}),
|
|
353
|
+
enableCompression: false
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
finally {
|
|
357
|
+
// 清理临时文件
|
|
358
|
+
if (fs.existsSync(tempVersionFile)) {
|
|
359
|
+
fs.unlinkSync(tempVersionFile);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
console.log('🔄 维护版本列表...');
|
|
363
|
+
// 读取现有版本列表,保留最近50个版本
|
|
364
|
+
const versionListPath = `${config.aws.prefix}/static/config/${options.env}/${projectInfo.name}/version.keep.json`;
|
|
365
|
+
let versionList = [];
|
|
366
|
+
try {
|
|
367
|
+
// 这里应该从GCP读取现有版本列表,暂时跳过
|
|
368
|
+
versionList = [cdnKey]; // 简化处理
|
|
369
|
+
}
|
|
370
|
+
catch (_a) {
|
|
371
|
+
versionList = [cdnKey];
|
|
372
|
+
}
|
|
373
|
+
// 保留最近50个版本
|
|
374
|
+
if (versionList.length > 50) {
|
|
375
|
+
versionList = versionList.slice(-50);
|
|
376
|
+
}
|
|
377
|
+
const tempVersionListFile = path.join(process.cwd(), `.temp-version-list-${cdnKey}.json`);
|
|
378
|
+
fs.writeFileSync(tempVersionListFile, JSON.stringify(versionList, null, 2));
|
|
379
|
+
try {
|
|
380
|
+
await (0, upload_1.uploadFile)({
|
|
381
|
+
bucketName: uploadConfig.bucketName,
|
|
382
|
+
filePath: tempVersionListFile,
|
|
383
|
+
destination: versionListPath,
|
|
384
|
+
storageClient: (0, upload_1.createStorageClient)(options.keyFile ? { keyFilename: options.keyFile } : {}),
|
|
385
|
+
enableCompression: false
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
finally {
|
|
389
|
+
// 清理临时文件
|
|
390
|
+
if (fs.existsSync(tempVersionListFile)) {
|
|
391
|
+
fs.unlinkSync(tempVersionListFile);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
// 5. 环境配置同步阶段
|
|
395
|
+
console.log('🔄 同步环境配置...');
|
|
396
|
+
// 检查项目是否存在
|
|
397
|
+
const { data: envConfig } = await getConfigFiles(options.env, config);
|
|
398
|
+
const existingProject = envConfig.projects.find((p) => p.name === projectInfo.name);
|
|
399
|
+
if (!existingProject) {
|
|
400
|
+
const { createProject } = await inquirer_1.default.prompt([
|
|
401
|
+
{
|
|
402
|
+
type: 'confirm',
|
|
403
|
+
name: 'createProject',
|
|
404
|
+
message: `项目 "${projectInfo.name}" 在环境 "${options.env}" 中不存在,是否创建新项目?`,
|
|
405
|
+
default: true
|
|
406
|
+
}
|
|
407
|
+
]);
|
|
408
|
+
if (!createProject) {
|
|
409
|
+
throw new Error('用户取消创建新项目,部署终止');
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
// 创建版本信息对象
|
|
413
|
+
const versionInfo = {
|
|
414
|
+
cdnKey,
|
|
415
|
+
version: cdnKey,
|
|
416
|
+
baseUrl: Array.isArray(config.deploy.baseUrl) ? config.deploy.baseUrl[0] : config.deploy.baseUrl,
|
|
417
|
+
branch: getBranchName(),
|
|
418
|
+
timestamp: Date.now(),
|
|
419
|
+
files: obj.links
|
|
420
|
+
};
|
|
421
|
+
await updateEnvironmentConfig(options.env, projectInfo.name, versionInfo, config, options.keyFile);
|
|
422
|
+
// 6. 发布完成阶段
|
|
423
|
+
console.log('💾 记录发布版本...');
|
|
424
|
+
(0, cache_1.writeLastDeployVersion)(cdnKey);
|
|
425
|
+
console.log('📊 输出发布信息...');
|
|
426
|
+
console.log(`您的项目有${obj.links.length}个静态文件!`);
|
|
427
|
+
console.log(`项目配置:${JSON.stringify(obj, null, '\t')}`);
|
|
428
|
+
console.log(`版本号:【${cdnKey}】`);
|
|
429
|
+
// 测试环境验证(非生产环境)
|
|
430
|
+
if (options.env !== 'production') {
|
|
431
|
+
const testHost = config.deploy.testHost || 'https://test.valleysound.xyz/';
|
|
432
|
+
const baseUrls = Array.isArray(config.deploy.baseUrl) ? config.deploy.baseUrl : [config.deploy.baseUrl];
|
|
433
|
+
console.log('🔍 测试环境验证路径:');
|
|
434
|
+
baseUrls.forEach(baseUrl => {
|
|
435
|
+
const verificationUrl = `${testHost}${baseUrl}?deployCheck=${cdnKey}`;
|
|
436
|
+
console.log(` ${verificationUrl}`);
|
|
437
|
+
});
|
|
438
|
+
// 触发配置更新
|
|
439
|
+
try {
|
|
440
|
+
const updateUrl = `${testHost}/-/xxx/config/update/soon?p=yucang`;
|
|
441
|
+
await axios_1.default.get(updateUrl);
|
|
442
|
+
console.log('✅ 测试环境已更新!');
|
|
443
|
+
}
|
|
444
|
+
catch (error) {
|
|
445
|
+
console.warn('⚠️ 测试环境配置更新失败:', error);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
// 完成提示
|
|
449
|
+
const completionMessage = options.env === 'production'
|
|
450
|
+
? '发布完成!请前往后台切换上线版本'
|
|
451
|
+
: '发布完成!';
|
|
452
|
+
console.log(`✅ ${completionMessage}`);
|
|
453
|
+
const result = {
|
|
454
|
+
success: true,
|
|
455
|
+
cdnKey,
|
|
456
|
+
uploadedFiles: uploadedFiles.length,
|
|
457
|
+
projectName: projectInfo.name,
|
|
458
|
+
environment: options.env,
|
|
459
|
+
message: `成功部署 ${uploadedFiles.length} 个文件到环境 ${options.env}`
|
|
460
|
+
};
|
|
461
|
+
// 生成验证URL(非生产环境)
|
|
462
|
+
if (options.env !== 'production') {
|
|
463
|
+
const baseUrl = Array.isArray(config.deploy.baseUrl) ? config.deploy.baseUrl[0] : config.deploy.baseUrl;
|
|
464
|
+
result.verificationUrl = `${baseUrl}?deployCheck=${cdnKey}`;
|
|
465
|
+
}
|
|
466
|
+
return result;
|
|
467
|
+
}
|
|
468
|
+
catch (error) {
|
|
469
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
470
|
+
console.error(`❌ 部署失败: ${errorMessage}`);
|
|
471
|
+
return {
|
|
472
|
+
success: false,
|
|
473
|
+
cdnKey: '',
|
|
474
|
+
uploadedFiles: 0,
|
|
475
|
+
projectName: '',
|
|
476
|
+
environment: options.env,
|
|
477
|
+
message: `部署失败: ${errorMessage}`
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
}
|