@alipay/agent-payment 1.0.5-dev.3 → 1.0.5
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 +13 -0
- package/bin/install-payguard.js +433 -0
- package/dist/cli.js +1 -1
- package/package.json +5 -4
package/README.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# alipay-agent-payment release
|
|
2
|
+
|
|
3
|
+
This repository contains only the npm publish payload for `@alipay/agent-payment`.
|
|
4
|
+
|
|
5
|
+
Publish flow:
|
|
6
|
+
|
|
7
|
+
1. Generate `.publish` from the source repository.
|
|
8
|
+
2. Sync the generated payload into this repository.
|
|
9
|
+
3. Commit and push a version tag like `v1.0.0`.
|
|
10
|
+
4. The `publish.yml` workflow publishes to npm through OIDC trusted publishing.
|
|
11
|
+
|
|
12
|
+
Before publishing, configure npm Trusted Publishing for this package and point it
|
|
13
|
+
to this repository's `.github/workflows/publish.yml` workflow.
|
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PayGuard 自动安装脚本
|
|
4
|
+
* 在第三方 CLI 的 npm install 阶段执行
|
|
5
|
+
*
|
|
6
|
+
* 功能:
|
|
7
|
+
* 1. 检测 PayGuard 是否已安装且运行
|
|
8
|
+
* 2. 未安装时从 CDN 获取最新版本并下载
|
|
9
|
+
* 3. 执行用户级静默安装
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
https = require('https');
|
|
15
|
+
const { execSync } = require('child_process');
|
|
16
|
+
const os = require('os');
|
|
17
|
+
const crypto = require('crypto');
|
|
18
|
+
const zlib = require('zlib');
|
|
19
|
+
|
|
20
|
+
// 配置
|
|
21
|
+
const CONFIG = {
|
|
22
|
+
// 版本元数据 API 基地址
|
|
23
|
+
metaUrlBase: 'https://mdn.alipayobjects.com/apg_afts/uri/file/as',
|
|
24
|
+
// 用户级安装目录
|
|
25
|
+
installDir: {
|
|
26
|
+
linux: process.env.PAYGUARD_INSTALL_DIR || path.join(os.homedir(), '.payguard'),
|
|
27
|
+
darwin: process.env.PAYGUARD_INSTALL_DIR || path.join(os.homedir(), 'Library/Application Support/com.alipay.agent.payguard/PayGuard.app'),
|
|
28
|
+
win32: process.env.PAYGUARD_INSTALL_DIR || path.join(os.homedir(), 'AppData/Roaming/PayGuard')
|
|
29
|
+
},
|
|
30
|
+
// 系统级安装目录(root/管理员安装)
|
|
31
|
+
systemInstallDir: {
|
|
32
|
+
linux: '/opt/PayGuard',
|
|
33
|
+
darwin: '/Library/Application Support/com.alipay.agent.payguard/PayGuard.app',
|
|
34
|
+
win32: 'C:\\Program Files\\PayGuard'
|
|
35
|
+
},
|
|
36
|
+
// 平台检测映射 (process.platform -> meta 文件名中的平台名)
|
|
37
|
+
platformMap: {
|
|
38
|
+
linux: 'linux',
|
|
39
|
+
darwin: 'macOS',
|
|
40
|
+
win32: 'win32'
|
|
41
|
+
},
|
|
42
|
+
// 架构检测映射 (process.arch -> 统一架构名)
|
|
43
|
+
archMap: {
|
|
44
|
+
x64: 'amd64',
|
|
45
|
+
arm64: 'arm64',
|
|
46
|
+
ia32: 'x86'
|
|
47
|
+
},
|
|
48
|
+
// 架构到文件名格式的映射 (process.arch -> meta 文件名中的架构名)
|
|
49
|
+
metaArchMap: {
|
|
50
|
+
x64: 'x86_64',
|
|
51
|
+
arm64: 'arm64',
|
|
52
|
+
ia32: 'x86'
|
|
53
|
+
},
|
|
54
|
+
// 各平台对应的安装包扩展名
|
|
55
|
+
fileExtension: {
|
|
56
|
+
linux: '.run',
|
|
57
|
+
darwin: '.pkg',
|
|
58
|
+
win32: '.exe'
|
|
59
|
+
},
|
|
60
|
+
// 各平台对应的二进制文件名
|
|
61
|
+
binaryName: {
|
|
62
|
+
linux: 'PayGuard',
|
|
63
|
+
darwin: 'PayGuard',
|
|
64
|
+
win32: 'PayGuard.exe'
|
|
65
|
+
},
|
|
66
|
+
// 各平台二进制文件相对于安装目录的路径(macOS App Bundle 结构特殊)
|
|
67
|
+
binaryRelPath: {
|
|
68
|
+
linux: '',
|
|
69
|
+
darwin: 'Contents/Library/Services/',
|
|
70
|
+
win32: ''
|
|
71
|
+
},
|
|
72
|
+
// 各平台安装命令模板(%s 会被替换为安装包路径)
|
|
73
|
+
installCommand: {
|
|
74
|
+
linux: (pkg) => getLinuxInstallCommand(pkg),
|
|
75
|
+
darwin: (pkg) => `installer -pkg "${pkg}" -target CurrentUserHomeDirectory`,
|
|
76
|
+
win32: (pkg) => `"${pkg}" /S` // 预留,未实现
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// 检测 systemctl 是否可用
|
|
81
|
+
function isSystemctlAvailable() {
|
|
82
|
+
try {
|
|
83
|
+
// 检查 systemctl 命令是否存在
|
|
84
|
+
execSync('which systemctl', { stdio: 'pipe', timeout: 5000 });
|
|
85
|
+
// 检查 systemd 是否真正运行(PID 1)
|
|
86
|
+
const pid1 = fs.readFileSync('/proc/1/comm', 'utf8').trim();
|
|
87
|
+
return pid1 === 'systemd';
|
|
88
|
+
} catch (e) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 获取 Linux 安装命令(根据是否支持 systemctl 动态生成)
|
|
94
|
+
function getLinuxInstallCommand(pkg) {
|
|
95
|
+
if (isSystemctlAvailable()) {
|
|
96
|
+
return `bash "${pkg}"`;
|
|
97
|
+
} else {
|
|
98
|
+
// 容器环境:使用 supervisor 作为服务管理器
|
|
99
|
+
return `bash "${pkg}" --service-manager=supervisor`;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 日志输出(统一前缀)
|
|
104
|
+
function log(msg) {
|
|
105
|
+
console.log(`[PayGuard] ${msg}`);
|
|
106
|
+
}
|
|
107
|
+
function logError(msg) {
|
|
108
|
+
console.error(`[PayGuard] ${msg}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 检测平台
|
|
112
|
+
function getPlatform() {
|
|
113
|
+
const platform = CONFIG.platformMap[process.platform];
|
|
114
|
+
if (!platform) {
|
|
115
|
+
throw new Error(`Unsupported platform: ${process.platform}`);
|
|
116
|
+
}
|
|
117
|
+
return platform;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 检测架构
|
|
121
|
+
function getArch() {
|
|
122
|
+
return CONFIG.archMap[process.arch] || process.arch;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 获取安装目录
|
|
126
|
+
function getInstallDir() {
|
|
127
|
+
return CONFIG.installDir[process.platform];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 定义系统级安装路径(root/系统管理员安装)
|
|
131
|
+
function getSystemInstallDir() {
|
|
132
|
+
return CONFIG.systemInstallDir[process.platform];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 获取平台+架构对应的元数据文件名
|
|
136
|
+
// 格式: PayGuard_${platform}_${arch}_latest_meta.json
|
|
137
|
+
function getMetaFileName() {
|
|
138
|
+
const platform = getPlatform();
|
|
139
|
+
// Windows 强制使用 x86_64 架构,不支持其他架构
|
|
140
|
+
const arch = platform === 'win32' ? 'x86_64' : (CONFIG.metaArchMap[process.arch] || process.arch);
|
|
141
|
+
const metaPlatform = platform === 'win32' ? 'win' : platform; // Windows 平台在文件名中使用 "win"
|
|
142
|
+
|
|
143
|
+
return `PayGuard_${metaPlatform}_${arch}_latest_meta.json`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 获取文件扩展名
|
|
147
|
+
function getFileExtension() {
|
|
148
|
+
return CONFIG.fileExtension[process.platform];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 在目录中查找二进制文件(Windows 支持版本子目录,macOS 支持 App Bundle 子路径)
|
|
152
|
+
// 返回找到的文件路径,或 null
|
|
153
|
+
function findBinaryInDir(baseDir, binaryName, platform) {
|
|
154
|
+
const relPath = CONFIG.binaryRelPath[platform] || '';
|
|
155
|
+
|
|
156
|
+
if (platform !== 'win32') {
|
|
157
|
+
// Linux/macOS: 检查 baseDir/[relPath/]binaryName
|
|
158
|
+
const binaryPath = path.join(baseDir, relPath, binaryName);
|
|
159
|
+
return fs.existsSync(binaryPath) ? binaryPath : null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Windows: 先检查直接路径(baseDir/binaryName),再检查版本子目录(baseDir/0.1.0/binaryName)
|
|
163
|
+
if (!fs.existsSync(baseDir)) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const directPath = path.join(baseDir, binaryName);
|
|
168
|
+
if (fs.existsSync(directPath)) {
|
|
169
|
+
return directPath;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 遍历子目录查找版本号目录(如 0.1.0)
|
|
173
|
+
try {
|
|
174
|
+
const entries = fs.readdirSync(baseDir, { withFileTypes: true });
|
|
175
|
+
for (const entry of entries) {
|
|
176
|
+
if (entry.isDirectory()) {
|
|
177
|
+
const versionedPath = path.join(baseDir, entry.name, binaryName);
|
|
178
|
+
if (fs.existsSync(versionedPath)) {
|
|
179
|
+
return versionedPath;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
} catch (e) {
|
|
184
|
+
// 忽略读取错误
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 检测 PayGuard 是否已安装(检查用户级和系统级)
|
|
191
|
+
function isInstalled() {
|
|
192
|
+
const binaryName = CONFIG.binaryName[process.platform];
|
|
193
|
+
|
|
194
|
+
// 1. 检查用户级安装
|
|
195
|
+
const userInstallDir = getInstallDir();
|
|
196
|
+
const userBinaryPath = findBinaryInDir(userInstallDir, binaryName, process.platform);
|
|
197
|
+
|
|
198
|
+
if (userBinaryPath) {
|
|
199
|
+
log('User installation found');
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// 2. 检查系统级安装(root/管理员安装)
|
|
204
|
+
const systemInstallDir = getSystemInstallDir();
|
|
205
|
+
if (systemInstallDir) {
|
|
206
|
+
const systemBinaryPath = findBinaryInDir(systemInstallDir, binaryName, process.platform);
|
|
207
|
+
|
|
208
|
+
if (systemBinaryPath) {
|
|
209
|
+
log('System installation found (installed by root/admin)');
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// 从 URL 获取 JSON 数据
|
|
218
|
+
function fetchJson(url) {
|
|
219
|
+
return new Promise((resolve, reject) => {
|
|
220
|
+
https.get(url, (response) => {
|
|
221
|
+
if (response.statusCode === 302 || response.statusCode === 301) {
|
|
222
|
+
fetchJson(response.headers.location)
|
|
223
|
+
.then(resolve)
|
|
224
|
+
.catch(reject);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (response.statusCode !== 200) {
|
|
229
|
+
reject(new Error(`Failed to fetch metadata: HTTP ${response.statusCode}`));
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const chunks = [];
|
|
234
|
+
response.on('data', (chunk) => chunks.push(chunk));
|
|
235
|
+
response.on('end', () => {
|
|
236
|
+
try {
|
|
237
|
+
let buffer = Buffer.concat(chunks);
|
|
238
|
+
const contentEncoding = response.headers['content-encoding'];
|
|
239
|
+
if (contentEncoding === 'gzip') {
|
|
240
|
+
buffer = zlib.gunzipSync(buffer);
|
|
241
|
+
}
|
|
242
|
+
resolve(JSON.parse(buffer.toString()));
|
|
243
|
+
} catch (e) {
|
|
244
|
+
reject(new Error(`Failed to parse JSON: ${e.message}`));
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
}).on('error', reject);
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// 计算文件 MD5
|
|
252
|
+
function calculateMd5(filePath) {
|
|
253
|
+
return new Promise((resolve, reject) => {
|
|
254
|
+
const hash = crypto.createHash('md5');
|
|
255
|
+
const stream = fs.createReadStream(filePath);
|
|
256
|
+
stream.on('error', reject);
|
|
257
|
+
stream.on('data', (chunk) => hash.update(chunk));
|
|
258
|
+
stream.on('end', () => resolve(hash.digest('hex')));
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// 计算文件 SHA256
|
|
263
|
+
function calculateSha256(filePath) {
|
|
264
|
+
return new Promise((resolve, reject) => {
|
|
265
|
+
const hash = crypto.createHash('sha256');
|
|
266
|
+
const stream = fs.createReadStream(filePath);
|
|
267
|
+
stream.on('error', reject);
|
|
268
|
+
stream.on('data', (chunk) => hash.update(chunk));
|
|
269
|
+
stream.on('end', () => resolve(hash.digest('hex')));
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// 统一校验文件哈希值
|
|
274
|
+
// algorithm: 'md5' | 'sha256'
|
|
275
|
+
async function verifyChecksum(filePath, algorithm, expectedHash) {
|
|
276
|
+
const algo = algorithm.toLowerCase();
|
|
277
|
+
log(`Verifying ${algo.toUpperCase()} checksum...`);
|
|
278
|
+
|
|
279
|
+
const actualHash = algo === 'sha256'
|
|
280
|
+
? await calculateSha256(filePath)
|
|
281
|
+
: await calculateMd5(filePath);
|
|
282
|
+
|
|
283
|
+
if (actualHash.toLowerCase() !== expectedHash.toLowerCase()) {
|
|
284
|
+
throw new Error(`${algo.toUpperCase()} checksum verification failed`);
|
|
285
|
+
}
|
|
286
|
+
log(`${algo.toUpperCase()} verification passed`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// 下载文件(支持可选的校验)
|
|
290
|
+
async function downloadFile(url, dest, metaInfo = null) {
|
|
291
|
+
return new Promise((resolve, reject) => {
|
|
292
|
+
const file = fs.createWriteStream(dest);
|
|
293
|
+
|
|
294
|
+
https.get(url, (response) => {
|
|
295
|
+
if (response.statusCode === 302 || response.statusCode === 301) {
|
|
296
|
+
// 跟随重定向
|
|
297
|
+
downloadFile(response.headers.location, dest, metaInfo)
|
|
298
|
+
.then(resolve)
|
|
299
|
+
.catch(reject);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (response.statusCode !== 200) {
|
|
304
|
+
reject(new Error(`Download failed: HTTP ${response.statusCode}`));
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const totalSize = parseInt(response.headers['content-length'], 10) || metaInfo?.file_size || 0;
|
|
309
|
+
let downloaded = 0;
|
|
310
|
+
|
|
311
|
+
response.on('data', (chunk) => {
|
|
312
|
+
downloaded += chunk.length;
|
|
313
|
+
if (totalSize > 0) {
|
|
314
|
+
const percent = Math.round((downloaded / totalSize) * 100);
|
|
315
|
+
process.stdout.write(`\r[PayGuard] Downloading: ${percent}%`);
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
response.pipe(file);
|
|
320
|
+
|
|
321
|
+
file.on('finish', async () => {
|
|
322
|
+
file.close();
|
|
323
|
+
log('Download complete');
|
|
324
|
+
|
|
325
|
+
// 验证文件校验和
|
|
326
|
+
if (metaInfo) {
|
|
327
|
+
try {
|
|
328
|
+
const stats = fs.statSync(dest);
|
|
329
|
+
if (metaInfo.file_size && stats.size !== metaInfo.file_size) {
|
|
330
|
+
reject(new Error(`File size mismatch: expected ${metaInfo.file_size}, got ${stats.size}`));
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (metaInfo.sha256) {
|
|
335
|
+
await verifyChecksum(dest, 'sha256', metaInfo.sha256);
|
|
336
|
+
} else if (metaInfo.md5) {
|
|
337
|
+
await verifyChecksum(dest, 'md5', metaInfo.md5);
|
|
338
|
+
}
|
|
339
|
+
} catch (err) {
|
|
340
|
+
reject(new Error(`Verification failed: ${err.message}`));
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
resolve(dest);
|
|
346
|
+
});
|
|
347
|
+
}).on('error', (err) => {
|
|
348
|
+
fs.unlinkSync(dest);
|
|
349
|
+
reject(err);
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// 统一安装函数
|
|
355
|
+
async function installPackage(platform, packagePath) {
|
|
356
|
+
const commandTemplate = CONFIG.installCommand[platform];
|
|
357
|
+
if (!commandTemplate) {
|
|
358
|
+
throw new Error(`Installation not implemented for platform: ${platform}`);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
log(`Installing for ${platform}...`);
|
|
362
|
+
|
|
363
|
+
try {
|
|
364
|
+
const command = commandTemplate(packagePath);
|
|
365
|
+
execSync(command, { stdio: 'inherit' });
|
|
366
|
+
log('Installation complete');
|
|
367
|
+
} finally {
|
|
368
|
+
fs.unlinkSync(packagePath);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// 主安装流程
|
|
373
|
+
async function main() {
|
|
374
|
+
try {
|
|
375
|
+
const platform = getPlatform();
|
|
376
|
+
|
|
377
|
+
log(`Platform: ${platform}`);
|
|
378
|
+
|
|
379
|
+
// 1. 检查是否已安装
|
|
380
|
+
if (isInstalled()) {
|
|
381
|
+
log('Skipping installation (already exists)');
|
|
382
|
+
process.exit(0);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
log('Not installed, fetching latest version info...');
|
|
386
|
+
|
|
387
|
+
// 2. 获取最新版本元数据
|
|
388
|
+
const metaFile = getMetaFileName();
|
|
389
|
+
const metaUrl = `${CONFIG.metaUrlBase}/${metaFile}`;
|
|
390
|
+
// metaUrl = "https://mdn.alipayobjects.com/agentpaypuard/uri/file/as/PayGuard_linux_x86_64_latest_meta.json";
|
|
391
|
+
log(`Fetching metadata: ${metaUrl}`);
|
|
392
|
+
|
|
393
|
+
let metaInfo;
|
|
394
|
+
try {
|
|
395
|
+
metaInfo = await fetchJson(metaUrl);
|
|
396
|
+
log(`Latest version: ${metaInfo.version}`);
|
|
397
|
+
log(`Package: ${metaInfo.file_name}`);
|
|
398
|
+
} catch (err) {
|
|
399
|
+
logError(`Failed to fetch metadata: ${err.message}`);
|
|
400
|
+
throw err;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// 3. 下载到临时目录
|
|
404
|
+
const ext = getFileExtension();
|
|
405
|
+
const tempPath = path.join(os.tmpdir(), `PayGuard_latest${ext}`);
|
|
406
|
+
const downloadUrl = metaInfo.download_url || metaInfo.alias_url;
|
|
407
|
+
|
|
408
|
+
log(`Downloading from: ${downloadUrl}`);
|
|
409
|
+
await downloadFile(downloadUrl, tempPath, metaInfo);
|
|
410
|
+
|
|
411
|
+
// 4. 执行平台特定安装
|
|
412
|
+
await installPackage(process.platform, tempPath);
|
|
413
|
+
|
|
414
|
+
log('Installation successful!');
|
|
415
|
+
log(`Version: ${metaInfo.version}`);
|
|
416
|
+
log(`Install location: ${getInstallDir()}`);
|
|
417
|
+
|
|
418
|
+
} catch (error) {
|
|
419
|
+
logError(`Installation failed: ${error.message}`);
|
|
420
|
+
// 不阻塞 npm install,只打印警告
|
|
421
|
+
logError('You can manually install from: https://docs.payguard.com/install');
|
|
422
|
+
process.exit(0); // 返回 0 不阻塞 npm install
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// 支持跳过安装的环境变量,测试1
|
|
427
|
+
if (process.env.SKIP_PAYGUARD_INSTALL === '1' || process.env.SKIP_PAYGUARD_INSTALL === 'true') {
|
|
428
|
+
log('Skipping installation (SKIP_PAYGUARD_INSTALL is set)');
|
|
429
|
+
process.exit(0);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// 运行
|
|
433
|
+
main();
|