Jiann 0278e09fef
fix: adjust the license copy when installing the plugin (#7135)
* fix: license text

* fix: prompt when the plugin version cannot be downloaded
2025-06-30 19:04:03 +08:00

290 lines
7.8 KiB
JavaScript

/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
const { Command } = require('commander');
const axios = require('axios');
const fs = require('fs-extra');
const zlib = require('zlib');
const tar = require('tar');
const path = require('path');
const { createStoragePluginsSymlink } = require('@nocobase/utils/plugin-symlink');
const chalk = require('chalk');
const { getAccessKeyPair, showLicenseInfo, LicenseKeyError } = require('../util');
class Package {
data;
constructor(packageName, packageManager) {
this.packageName = packageName;
this.packageManager = packageManager;
this.outputDir = path.resolve(process.cwd(), `storage/plugins/${this.packageName}`);
}
get token() {
return this.packageManager.getToken();
}
url(path) {
return this.packageManager.url(path);
}
async mkdir() {
if (await fs.exists(this.outputDir)) {
await fs.rm(this.outputDir, { recursive: true, force: true });
}
await fs.mkdirp(this.outputDir);
}
async getInfo() {
try {
const res = await axios.get(this.url(this.packageName), {
headers: {
Authorization: `Bearer ${this.token}`,
},
responseType: 'json',
});
this.data = res.data;
} catch (error) {
return;
}
}
getTarball(version = 'latest') {
if (this.data.versions[version]) {
return [version, this.data.versions[version].dist.tarball];
}
const keys = version.split('.');
const length = keys.length;
if (version.includes('rc')) {
version = version.split('-').shift();
}
if (length === 5) {
keys.pop();
version = keys.join('.');
}
if (version === 'latest') {
version = this.data['dist-tags']['latest'];
} else if (version === 'next') {
version = this.data['dist-tags']['next'];
}
if (!this.data.versions[version]) {
console.log(chalk.redBright(`Download failed: ${this.packageName}@${version} package does not exist`));
}
return [version, this.data.versions[version].dist.tarball];
}
async isDevPackage() {
let file = path.resolve(process.cwd(), 'packages/plugins', this.packageName, 'package.json');
if (await fs.exists(file)) {
return true;
}
file = path.resolve(process.cwd(), 'packages/pro-plugins', this.packageName, 'package.json');
if (await fs.exists(file)) {
return true;
}
return false;
}
async isDownloaded(version) {
const packageFile = path.resolve(process.env.PLUGIN_STORAGE_PATH, this.packageName, 'package.json');
if (await fs.exists(packageFile)) {
const json = await fs.readJson(packageFile);
if (json.version === version) {
return true;
}
}
return false;
}
async download(options = {}) {
if (await this.isDevPackage()) {
console.log(chalk.yellowBright(`Skipped: ${this.packageName} is dev package`));
return;
}
if (await this.isDownloaded(options.version)) {
return;
}
await this.getInfo();
if (!this.data) {
console.log(chalk.redBright(`Download failed: ${this.packageName} package does not exist`));
return;
}
try {
const [version, url] = this.getTarball(options.version);
if (await this.isDownloaded(version)) {
return;
}
const response = await axios({
url,
responseType: 'stream',
method: 'GET',
headers: {
Authorization: `Bearer ${this.token}`,
},
});
await this.mkdir();
await new Promise((resolve, reject) => {
response.data
.pipe(zlib.createGunzip()) // 解压 gzip
.pipe(tar.extract({ cwd: this.outputDir, strip: 1 })) // 解压 tar
.on('finish', resolve)
.on('error', reject);
});
console.log(chalk.greenBright(`Downloaded: ${this.packageName}@${version}`));
} catch (error) {
if (error.response.data && typeof error.response.data.pipe === 'function') {
let errorMessageBuffer = '';
error.response.data.on('data', (chunk) => {
errorMessageBuffer += chunk.toString('utf8'); // 收集错误信息
});
error.response.data.on('end', () => {
if (error.response.status === 403) {
console.error(chalk.redBright('You do not have permission to download this package version.'));
}
});
}
console.log(chalk.redBright(`Download failed: ${this.packageName}`));
}
}
}
class PackageManager {
token;
baseURL;
constructor({ baseURL }) {
this.baseURL = baseURL;
}
getToken() {
return this.token;
}
getBaseURL() {
return this.baseURL;
}
url(path) {
return this.baseURL + path;
}
async login(credentials) {
try {
const res1 = await axios.post(`${this.baseURL}-/verdaccio/sec/login`, credentials, {
responseType: 'json',
});
this.token = res1.data.token;
} catch (error) {
if (error?.response?.data?.error === 'license not valid') {
showLicenseInfo(LicenseKeyError.notValid);
}
console.error(chalk.redBright(`Login failed: ${this.baseURL}`));
}
}
getPackage(packageName) {
return new Package(packageName, this);
}
async getProPackages() {
const res = await axios.get(this.url('pro-packages'), {
headers: {
Authorization: `Bearer ${this.token}`,
},
responseType: 'json',
});
return {
licensed_plugins: res.data?.data || [],
commercial_plugins: res.data?.meta?.commercial_plugins || [],
};
}
async getPackages() {
const pkgs = await this.getProPackages();
if (Array.isArray(pkgs)) {
return {
commercial_plugins: pkgs,
licensed_plugins: pkgs,
};
}
return pkgs;
}
async removePackage(packageName) {
const dir = path.resolve(process.env.PLUGIN_STORAGE_PATH, packageName);
const r = await fs.exists(dir);
if (r) {
console.log(chalk.yellowBright(`Removed: ${packageName}`));
await fs.rm(dir, { force: true, recursive: true });
}
}
async download(options = {}) {
const { version } = options;
if (!this.token) {
return;
}
const { commercial_plugins, licensed_plugins } = await this.getPackages();
for (const pkg of commercial_plugins) {
if (!licensed_plugins.includes(pkg)) {
await this.removePackage(pkg);
}
}
for (const pkg of licensed_plugins) {
await this.getPackage(pkg).download({ version });
}
}
}
/**
*
* @param {Command} cli
*/
module.exports = (cli) => {
const pkg = cli.command('pkg');
pkg
.command('download-pro')
.option('-V, --version [version]')
.action(async () => {
const {
NOCOBASE_PKG_URL = 'https://pkg.nocobase.com/',
NOCOBASE_PKG_USERNAME,
NOCOBASE_PKG_PASSWORD,
} = process.env;
let accessKeyId;
let accessKeySecret;
try {
({ accessKeyId, accessKeySecret } = await getAccessKeyPair());
} catch (e) {
return;
}
if (!(NOCOBASE_PKG_USERNAME && NOCOBASE_PKG_PASSWORD) && !(accessKeyId && accessKeySecret)) {
return;
}
const credentials = accessKeyId
? { username: accessKeyId, password: accessKeySecret }
: { username: NOCOBASE_PKG_USERNAME, password: NOCOBASE_PKG_PASSWORD };
const pm = new PackageManager({ baseURL: NOCOBASE_PKG_URL });
await pm.login(credentials);
const file = path.resolve(__dirname, '../../package.json');
const json = await fs.readJson(file);
await pm.download({ version: json.version });
await createStoragePluginsSymlink();
});
pkg.command('export-all').action(async () => {
console.log('Todo...');
});
};