@nocobase/cli 2.0.0-alpha.7 → 2.0.0-alpha.71
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/nocobase.conf.tpl +3 -0
- package/package.json +5 -5
- package/src/commands/client.js +166 -0
- package/src/commands/dev.js +10 -0
- package/src/commands/index.js +1 -0
- package/src/commands/instance-id.js +5 -4
- package/src/commands/perf.js +2 -2
- package/src/commands/pkg.js +42 -16
- package/src/commands/pm2.js +1 -1
- package/src/commands/start.js +25 -23
- package/src/commands/test.js +1 -1
- package/src/commands/update-deps.js +1 -0
- package/src/license.js +76 -0
- package/src/logger.js +75 -0
- package/src/util.js +28 -89
- package/templates/bundle-status.html +338 -0
- package/templates/create-app-package.json +1 -1
- package/templates/plugin/package.json.tpl +3 -3
package/nocobase.conf.tpl
CHANGED
|
@@ -73,7 +73,10 @@ server {
|
|
|
73
73
|
proxy_set_header Upgrade $http_upgrade;
|
|
74
74
|
proxy_set_header Connection 'upgrade';
|
|
75
75
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
76
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
76
77
|
proxy_set_header Host $host;
|
|
78
|
+
proxy_set_header Referer $http_referer;
|
|
79
|
+
proxy_set_header User-Agent $http_user_agent;
|
|
77
80
|
add_header Cache-Control 'no-cache, no-store';
|
|
78
81
|
proxy_cache_bypass $http_upgrade;
|
|
79
82
|
proxy_connect_timeout 600;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/cli",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.71",
|
|
4
4
|
"description": "",
|
|
5
5
|
"license": "AGPL-3.0",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"nocobase": "./bin/index.js"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@nocobase/app": "2.0.0-alpha.
|
|
12
|
-
"@nocobase/license-kit": "^0.
|
|
11
|
+
"@nocobase/app": "2.0.0-alpha.71",
|
|
12
|
+
"@nocobase/license-kit": "^0.3.7",
|
|
13
13
|
"@types/fs-extra": "^11.0.1",
|
|
14
14
|
"@umijs/utils": "3.5.20",
|
|
15
15
|
"chalk": "^4.1.1",
|
|
@@ -27,12 +27,12 @@
|
|
|
27
27
|
"tsx": "^4.19.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@nocobase/devtools": "2.0.0-alpha.
|
|
30
|
+
"@nocobase/devtools": "2.0.0-alpha.71"
|
|
31
31
|
},
|
|
32
32
|
"repository": {
|
|
33
33
|
"type": "git",
|
|
34
34
|
"url": "git+https://github.com/nocobase/nocobase.git",
|
|
35
35
|
"directory": "packages/core/cli"
|
|
36
36
|
},
|
|
37
|
-
"gitHead": "
|
|
37
|
+
"gitHead": "b6fc484eb698fa12fba02dd468a04e39079b1e79"
|
|
38
38
|
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const chalk = require('chalk');
|
|
11
|
+
const { Command } = require('commander');
|
|
12
|
+
const fs = require('fs-extra');
|
|
13
|
+
const { resolve } = require('path');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 复制主应用客户端文件
|
|
17
|
+
* @param {string} source - 源目录路径
|
|
18
|
+
* @param {string} target - 目标目录路径
|
|
19
|
+
*/
|
|
20
|
+
async function copyMainClient(source, target) {
|
|
21
|
+
if (!(await fs.exists(source))) {
|
|
22
|
+
console.log(chalk.yellow(`Source directory does not exist: ${source}`));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
// 确保目标目录存在且为空
|
|
26
|
+
await fs.ensureDir(target);
|
|
27
|
+
await fs.emptyDir(target);
|
|
28
|
+
await fs.copy(source, target, { recursive: true });
|
|
29
|
+
console.log(chalk.green(`Copied main client files from ${source} to ${target}`));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 复制插件客户端文件
|
|
34
|
+
* @param {string} pluginsBaseDir - 插件基础目录路径
|
|
35
|
+
* @param {string} namespace - 命名空间(如 '@nocobase' 或 '@nocobase-example')
|
|
36
|
+
* @param {string} target - 目标目录
|
|
37
|
+
*/
|
|
38
|
+
async function copyPluginClients(pluginsBaseDir, namespace, target) {
|
|
39
|
+
const pluginsDir = resolve(process.cwd(), pluginsBaseDir, namespace);
|
|
40
|
+
if (await fs.exists(pluginsDir)) {
|
|
41
|
+
const pluginNames = await fs.readdir(pluginsDir);
|
|
42
|
+
for (const pluginName of pluginNames) {
|
|
43
|
+
const pluginPath = resolve(pluginsDir, pluginName);
|
|
44
|
+
const pluginDistClient = resolve(pluginPath, 'dist/client');
|
|
45
|
+
if (await fs.exists(pluginDistClient)) {
|
|
46
|
+
const pluginTarget = resolve(target, 'static/plugins', namespace, pluginName, 'dist/client');
|
|
47
|
+
await fs.mkdir(resolve(pluginTarget, '..'), { recursive: true });
|
|
48
|
+
await fs.copy(pluginDistClient, pluginTarget, { recursive: true });
|
|
49
|
+
console.log(chalk.green(`Copied ${namespace}/${pluginName} client files`));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 递归上传目录到 OSS
|
|
57
|
+
* @param {Client} client - OSS 客户端实例
|
|
58
|
+
* @param {string} localDir - 本地目录路径
|
|
59
|
+
* @param {string} ossPrefix - OSS 对象前缀(目录路径)
|
|
60
|
+
*/
|
|
61
|
+
async function uploadDirectoryToOSS(client, localDir, ossPrefix = '') {
|
|
62
|
+
if (!(await fs.exists(localDir))) {
|
|
63
|
+
console.log(chalk.yellow(`Directory does not exist: ${localDir}`));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const stats = await fs.stat(localDir);
|
|
68
|
+
if (!stats.isDirectory()) {
|
|
69
|
+
throw new Error(`${localDir} is not a directory`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const files = await fs.readdir(localDir);
|
|
73
|
+
let uploadedCount = 0;
|
|
74
|
+
|
|
75
|
+
for (const file of files) {
|
|
76
|
+
const filePath = resolve(localDir, file);
|
|
77
|
+
const fileStats = await fs.stat(filePath);
|
|
78
|
+
|
|
79
|
+
if (fileStats.isDirectory()) {
|
|
80
|
+
// 递归处理子目录
|
|
81
|
+
const subOssPrefix = ossPrefix ? `${ossPrefix}/${file}` : file;
|
|
82
|
+
const subCount = await uploadDirectoryToOSS(client, filePath, subOssPrefix);
|
|
83
|
+
uploadedCount += subCount;
|
|
84
|
+
} else {
|
|
85
|
+
// 上传文件
|
|
86
|
+
const ossKey = ossPrefix ? `${ossPrefix}/${file}` : file;
|
|
87
|
+
try {
|
|
88
|
+
await client.put(ossKey, filePath);
|
|
89
|
+
// console.log(chalk.green(`Uploaded: ${ossKey}`));
|
|
90
|
+
uploadedCount++;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error(chalk.red(`Failed to upload ${ossKey}:`), error.message);
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return uploadedCount;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
*
|
|
103
|
+
* @param {Command} cli
|
|
104
|
+
*/
|
|
105
|
+
module.exports = (cli) => {
|
|
106
|
+
cli
|
|
107
|
+
.command('client:extract')
|
|
108
|
+
.allowUnknownOption()
|
|
109
|
+
.action(async () => {
|
|
110
|
+
const version = require('../../package.json').version;
|
|
111
|
+
const target = resolve(process.cwd(), 'storage/dist-client', version);
|
|
112
|
+
const mainClientSource = resolve(process.cwd(), 'node_modules/@nocobase/app/dist/client');
|
|
113
|
+
await copyMainClient(mainClientSource, target);
|
|
114
|
+
await copyPluginClients('packages/plugins', '@nocobase', target);
|
|
115
|
+
await copyPluginClients('packages/plugins', '@nocobase-example', target);
|
|
116
|
+
await copyPluginClients('packages/pro-plugins', '@nocobase', target);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
cli
|
|
120
|
+
.command('client:upload')
|
|
121
|
+
.allowUnknownOption()
|
|
122
|
+
.action(async () => {
|
|
123
|
+
const version = require('../../package.json').version;
|
|
124
|
+
const target = resolve(process.cwd(), 'storage/dist-client', version);
|
|
125
|
+
|
|
126
|
+
// 检查必要的环境变量
|
|
127
|
+
if (
|
|
128
|
+
!process.env.CDN_ALI_OSS_ACCESS_KEY_ID ||
|
|
129
|
+
!process.env.CDN_ALI_OSS_ACCESS_KEY_SECRET ||
|
|
130
|
+
!process.env.CDN_ALI_OSS_BUCKET ||
|
|
131
|
+
!process.env.CDN_ALI_OSS_REGION
|
|
132
|
+
) {
|
|
133
|
+
console.error(
|
|
134
|
+
chalk.red(
|
|
135
|
+
'Missing required environment variables: CDN_ALI_OSS_ACCESS_KEY_ID, CDN_ALI_OSS_ACCESS_KEY_SECRET, CDN_ALI_OSS_BUCKET, CDN_ALI_OSS_REGION',
|
|
136
|
+
),
|
|
137
|
+
);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const Client = require('ali-oss');
|
|
142
|
+
|
|
143
|
+
const client = new Client({
|
|
144
|
+
accessKeyId: process.env.CDN_ALI_OSS_ACCESS_KEY_ID,
|
|
145
|
+
accessKeySecret: process.env.CDN_ALI_OSS_ACCESS_KEY_SECRET,
|
|
146
|
+
bucket: process.env.CDN_ALI_OSS_BUCKET,
|
|
147
|
+
region: process.env.CDN_ALI_OSS_REGION,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
if (!(await fs.exists(target))) {
|
|
151
|
+
console.error(chalk.red(`Target directory does not exist: ${target}`));
|
|
152
|
+
console.log(chalk.yellow('Please run "client:extract" first to generate the client files.'));
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
console.log(chalk.blue(`Uploading directory ${target} to OSS...`));
|
|
157
|
+
const ossPrefix = `${version}`;
|
|
158
|
+
try {
|
|
159
|
+
const uploadedCount = await uploadDirectoryToOSS(client, target, ossPrefix);
|
|
160
|
+
console.log(chalk.green(`Successfully uploaded ${uploadedCount} files to OSS`));
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.error(chalk.red('Upload failed:'), error);
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
};
|
package/src/commands/dev.js
CHANGED
|
@@ -21,6 +21,15 @@ function sleep(ms = 1000) {
|
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
async function buildBundleStatusHtml() {
|
|
25
|
+
const data = await fs.promises.readFile(path.resolve(__dirname, '../../templates/bundle-status.html'), 'utf-8');
|
|
26
|
+
await fs.promises.writeFile(
|
|
27
|
+
path.resolve(process.cwd(), 'node_modules/@umijs/preset-umi/assets/bundle-status.html'),
|
|
28
|
+
data,
|
|
29
|
+
'utf-8',
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
24
33
|
/**
|
|
25
34
|
*
|
|
26
35
|
* @param {Command} cli
|
|
@@ -37,6 +46,7 @@ module.exports = (cli) => {
|
|
|
37
46
|
.allowUnknownOption()
|
|
38
47
|
.action(async (opts) => {
|
|
39
48
|
checkDBDialect();
|
|
49
|
+
await buildBundleStatusHtml();
|
|
40
50
|
let subprocess;
|
|
41
51
|
const runDevClient = () => {
|
|
42
52
|
console.log('starting client', 1 * clientPort);
|
package/src/commands/index.js
CHANGED
|
@@ -13,6 +13,7 @@ const { run, isDev } = require('../util');
|
|
|
13
13
|
const { getInstanceIdAsync } = require('@nocobase/license-kit');
|
|
14
14
|
const path = require('path');
|
|
15
15
|
const fs = require('fs');
|
|
16
|
+
const { logger } = require('../logger');
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
*
|
|
@@ -24,11 +25,11 @@ module.exports = (cli) => {
|
|
|
24
25
|
.description('Generate InstanceID')
|
|
25
26
|
.option('--force', 'Force generate InstanceID')
|
|
26
27
|
.action(async (options) => {
|
|
27
|
-
|
|
28
|
+
logger.info('Generating InstanceID...');
|
|
28
29
|
const dir = path.resolve(process.cwd(), 'storage/.license');
|
|
29
30
|
const filePath = path.resolve(dir, 'instance-id');
|
|
30
31
|
if (fs.existsSync(filePath) && !options.force) {
|
|
31
|
-
|
|
32
|
+
logger.info('InstanceID already exists at ' + filePath);
|
|
32
33
|
return;
|
|
33
34
|
} else {
|
|
34
35
|
if (!fs.existsSync(dir)) {
|
|
@@ -37,9 +38,9 @@ module.exports = (cli) => {
|
|
|
37
38
|
try {
|
|
38
39
|
const instanceId = await getInstanceIdAsync();
|
|
39
40
|
fs.writeFileSync(filePath, instanceId + '\n');
|
|
40
|
-
|
|
41
|
+
logger.info(`InstanceID saved to ${filePath}`);
|
|
41
42
|
} catch (e) {
|
|
42
|
-
|
|
43
|
+
logger.error('Failed to generate InstanceID', e.message || e);
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
46
|
});
|
package/src/commands/perf.js
CHANGED
|
@@ -53,8 +53,8 @@ module.exports = (cli) => {
|
|
|
53
53
|
dotenv.config({ path: envFilePath, override: true });
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
-
if (!process.env.
|
|
57
|
-
throw new Error('Please set
|
|
56
|
+
if (!process.env.TARGET_ORIGIN) {
|
|
57
|
+
throw new Error('Please set TARGET_ORIGIN in environment variables or in .env.perf file');
|
|
58
58
|
}
|
|
59
59
|
const args = command.args.filter((arg) => arg !== file);
|
|
60
60
|
await run(`k6`, ['run', f, ...(args.length ? ['--', ...args] : [])]);
|
package/src/commands/pkg.js
CHANGED
|
@@ -14,8 +14,8 @@ const zlib = require('zlib');
|
|
|
14
14
|
const tar = require('tar');
|
|
15
15
|
const path = require('path');
|
|
16
16
|
const { createStoragePluginsSymlink } = require('@nocobase/utils/plugin-symlink');
|
|
17
|
-
const
|
|
18
|
-
const {
|
|
17
|
+
const { getAccessKeyPair, showLicenseInfo, LicenseKeyError } = require('../license');
|
|
18
|
+
const { logger } = require('../logger');
|
|
19
19
|
|
|
20
20
|
class Package {
|
|
21
21
|
data;
|
|
@@ -78,7 +78,7 @@ class Package {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
if (!this.data.versions[version]) {
|
|
81
|
-
|
|
81
|
+
logger.error(`Download failed: ${this.packageName}@${version} package does not exist`);
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
return [version, this.data.versions[version].dist.tarball];
|
|
@@ -93,10 +93,18 @@ class Package {
|
|
|
93
93
|
if (await fs.exists(file)) {
|
|
94
94
|
return true;
|
|
95
95
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async isDepPackage() {
|
|
100
|
+
const pkg1 = path.resolve(process.cwd(), 'node_modules', this.packageName, 'package.json');
|
|
101
|
+
const pkg2 = path.resolve(process.cwd(), process.env.PLUGIN_STORAGE_PATH, this.packageName, 'package.json');
|
|
102
|
+
if ((await fs.exists(pkg1)) && (await fs.exists(pkg2))) {
|
|
103
|
+
const readPath1 = fs.realpathSync(pkg1);
|
|
104
|
+
const readPath2 = fs.realpathSync(pkg2);
|
|
105
|
+
if (readPath1 !== readPath2) {
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
100
108
|
}
|
|
101
109
|
return false;
|
|
102
110
|
}
|
|
@@ -114,7 +122,11 @@ class Package {
|
|
|
114
122
|
|
|
115
123
|
async download(options = {}) {
|
|
116
124
|
if (await this.isDevPackage()) {
|
|
117
|
-
|
|
125
|
+
logger.info(`Skipped: ${this.packageName} is dev package`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (await this.isDepPackage()) {
|
|
129
|
+
logger.info(`Skipped: ${this.packageName} is dependency package`);
|
|
118
130
|
return;
|
|
119
131
|
}
|
|
120
132
|
if (await this.isDownloaded(options.version)) {
|
|
@@ -122,7 +134,7 @@ class Package {
|
|
|
122
134
|
}
|
|
123
135
|
await this.getInfo();
|
|
124
136
|
if (!this.data) {
|
|
125
|
-
|
|
137
|
+
logger.error(`Download failed: ${this.packageName} package does not exist`);
|
|
126
138
|
return;
|
|
127
139
|
}
|
|
128
140
|
try {
|
|
@@ -146,7 +158,7 @@ class Package {
|
|
|
146
158
|
.on('finish', resolve)
|
|
147
159
|
.on('error', reject);
|
|
148
160
|
});
|
|
149
|
-
|
|
161
|
+
logger.info(`Downloaded: ${this.packageName}@${version}`);
|
|
150
162
|
} catch (error) {
|
|
151
163
|
if (error?.response?.data && typeof error?.response?.data?.pipe === 'function') {
|
|
152
164
|
let errorMessageBuffer = '';
|
|
@@ -155,11 +167,11 @@ class Package {
|
|
|
155
167
|
});
|
|
156
168
|
error.response.data.on?.('end', () => {
|
|
157
169
|
if (error.response.status === 403) {
|
|
158
|
-
|
|
170
|
+
logger.error('You do not have permission to download this package version.');
|
|
159
171
|
}
|
|
160
172
|
});
|
|
161
173
|
}
|
|
162
|
-
|
|
174
|
+
logger.error(`Download failed: ${this.packageName}`);
|
|
163
175
|
}
|
|
164
176
|
}
|
|
165
177
|
}
|
|
@@ -190,12 +202,13 @@ class PackageManager {
|
|
|
190
202
|
responseType: 'json',
|
|
191
203
|
});
|
|
192
204
|
this.token = res1.data.token;
|
|
205
|
+
logger.info('Login success');
|
|
193
206
|
} catch (error) {
|
|
194
207
|
if (error?.response?.data?.error === 'license not valid') {
|
|
195
208
|
showLicenseInfo(LicenseKeyError.notValid);
|
|
196
209
|
}
|
|
197
|
-
|
|
198
|
-
|
|
210
|
+
logger.error(`Login failed: ${this.baseURL}`);
|
|
211
|
+
logger.error(error?.message, { error });
|
|
199
212
|
}
|
|
200
213
|
}
|
|
201
214
|
|
|
@@ -232,7 +245,7 @@ class PackageManager {
|
|
|
232
245
|
const dir = path.resolve(process.env.PLUGIN_STORAGE_PATH, packageName);
|
|
233
246
|
const r = await fs.exists(dir);
|
|
234
247
|
if (r) {
|
|
235
|
-
|
|
248
|
+
logger.info(`Removed: ${packageName}`);
|
|
236
249
|
await fs.rm(dir, { force: true, recursive: true });
|
|
237
250
|
}
|
|
238
251
|
}
|
|
@@ -248,9 +261,11 @@ class PackageManager {
|
|
|
248
261
|
await this.removePackage(pkg);
|
|
249
262
|
}
|
|
250
263
|
}
|
|
264
|
+
logger.info(`Download plugins...`);
|
|
251
265
|
for (const pkg of licensed_plugins) {
|
|
252
266
|
await this.getPackage(pkg).download({ version });
|
|
253
267
|
}
|
|
268
|
+
logger.info('Download plugins done');
|
|
254
269
|
}
|
|
255
270
|
}
|
|
256
271
|
|
|
@@ -268,14 +283,25 @@ module.exports = (cli) => {
|
|
|
268
283
|
NOCOBASE_PKG_URL = 'https://pkg.nocobase.com/',
|
|
269
284
|
NOCOBASE_PKG_USERNAME,
|
|
270
285
|
NOCOBASE_PKG_PASSWORD,
|
|
286
|
+
DISABLE_PKG_DOWNLOAD,
|
|
271
287
|
} = process.env;
|
|
288
|
+
if (DISABLE_PKG_DOWNLOAD === 'true') {
|
|
289
|
+
logger.info('Package download is disabled.');
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
272
292
|
let accessKeyId;
|
|
273
293
|
let accessKeySecret;
|
|
274
294
|
try {
|
|
275
295
|
({ accessKeyId, accessKeySecret } = await getAccessKeyPair());
|
|
276
296
|
} catch (e) {
|
|
297
|
+
// logger.error('Get AccessKey Pair error', e);
|
|
277
298
|
return;
|
|
278
299
|
}
|
|
300
|
+
if (NOCOBASE_PKG_USERNAME && NOCOBASE_PKG_PASSWORD && !accessKeyId && !accessKeySecret) {
|
|
301
|
+
logger.warn(
|
|
302
|
+
'NOCOBASE_PKG_USERNAME and NOCOBASE_PKG_PASSWORD will be deprecated in future versions. Please log in to NocoBase Service and refer to the documentation to learn how to install and upgrade commercial plugins.\n',
|
|
303
|
+
);
|
|
304
|
+
}
|
|
279
305
|
if (!(NOCOBASE_PKG_USERNAME && NOCOBASE_PKG_PASSWORD) && !(accessKeyId && accessKeySecret)) {
|
|
280
306
|
return;
|
|
281
307
|
}
|
|
@@ -290,6 +316,6 @@ module.exports = (cli) => {
|
|
|
290
316
|
await createStoragePluginsSymlink();
|
|
291
317
|
});
|
|
292
318
|
pkg.command('export-all').action(async () => {
|
|
293
|
-
|
|
319
|
+
logger.info('Todo...');
|
|
294
320
|
});
|
|
295
321
|
};
|
package/src/commands/pm2.js
CHANGED
package/src/commands/start.js
CHANGED
|
@@ -56,32 +56,34 @@ module.exports = (cli) => {
|
|
|
56
56
|
await downloadPro();
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
depth: 1, // 只监听第一层目录
|
|
65
|
-
});
|
|
59
|
+
if (process.env.NO_WATCH_PLUGINS === true || process.env.NO_WATCH_PLUGINS === 'true') {
|
|
60
|
+
const restart = _.debounce(async () => {
|
|
61
|
+
console.log('restarting...');
|
|
62
|
+
await run('yarn', ['nocobase', 'pm2-restart']);
|
|
63
|
+
}, 500);
|
|
66
64
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
.on('ready', () => {
|
|
74
|
-
isReady = true;
|
|
75
|
-
})
|
|
76
|
-
.on('addDir', async (pathname) => {
|
|
77
|
-
if (!isReady) return;
|
|
78
|
-
restart();
|
|
79
|
-
})
|
|
80
|
-
.on('unlinkDir', async (pathname) => {
|
|
81
|
-
if (!isReady) return;
|
|
82
|
-
restart();
|
|
65
|
+
const watcher = chokidar.watch('./storage/plugins/**/*', {
|
|
66
|
+
cwd: process.cwd(),
|
|
67
|
+
ignoreInitial: true,
|
|
68
|
+
ignored: /(^|[\/\\])\../, // 忽略隐藏文件
|
|
69
|
+
persistent: true,
|
|
70
|
+
depth: 1, // 只监听第一层目录
|
|
83
71
|
});
|
|
84
72
|
|
|
73
|
+
watcher
|
|
74
|
+
.on('ready', () => {
|
|
75
|
+
isReady = true;
|
|
76
|
+
})
|
|
77
|
+
.on('addDir', async (pathname) => {
|
|
78
|
+
if (!isReady) return;
|
|
79
|
+
restart();
|
|
80
|
+
})
|
|
81
|
+
.on('unlinkDir', async (pathname) => {
|
|
82
|
+
if (!isReady) return;
|
|
83
|
+
restart();
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
85
87
|
if (opts.port) {
|
|
86
88
|
process.env.APP_PORT = opts.port;
|
|
87
89
|
}
|
package/src/commands/test.js
CHANGED
|
@@ -54,7 +54,7 @@ function addTestCommand(name, cli) {
|
|
|
54
54
|
const first = paths?.[0];
|
|
55
55
|
if (!process.env.TEST_ENV && first) {
|
|
56
56
|
const key = first.split(path.sep).join('/');
|
|
57
|
-
if (key.includes('/client/') || key.includes('/flow-engine/')) {
|
|
57
|
+
if (key.includes('/client/') || key.includes('/client-v2/') || key.includes('/flow-engine/')) {
|
|
58
58
|
process.env.TEST_ENV = 'client-side';
|
|
59
59
|
} else {
|
|
60
60
|
process.env.TEST_ENV = 'server-side';
|
|
@@ -62,6 +62,7 @@ module.exports = (cli) => {
|
|
|
62
62
|
if (descJson['devDependencies']?.['@nocobase/devtools']) {
|
|
63
63
|
descJson['devDependencies']['@nocobase/devtools'] = stdout;
|
|
64
64
|
}
|
|
65
|
+
descJson['resolutions'] = sourceJson['resolutions'];
|
|
65
66
|
const json = deepmerge(descJson, sourceJson);
|
|
66
67
|
await writeJSON(descPath, json, { spaces: 2, encoding: 'utf8' });
|
|
67
68
|
await run('yarn', ['install']);
|
package/src/license.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const chalk = require('chalk');
|
|
11
|
+
const { resolve } = require('path');
|
|
12
|
+
const fs = require('fs-extra');
|
|
13
|
+
const { keyDecrypt, getEnvAsync } = require('@nocobase/license-kit');
|
|
14
|
+
const { isEnvMatch } = require('@nocobase/plugin-license/utils/env');
|
|
15
|
+
const { logger } = require('./logger');
|
|
16
|
+
const { pick } = require('lodash');
|
|
17
|
+
|
|
18
|
+
exports.getAccessKeyPair = async function () {
|
|
19
|
+
const keyFile = resolve(process.cwd(), 'storage/.license/license-key');
|
|
20
|
+
if (!fs.existsSync(keyFile)) {
|
|
21
|
+
logger.error('License key not found');
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
logger.info('License key found');
|
|
25
|
+
let keyData = {};
|
|
26
|
+
try {
|
|
27
|
+
const str = fs.readFileSync(keyFile, 'utf-8');
|
|
28
|
+
const keyDataStr = keyDecrypt(str);
|
|
29
|
+
keyData = JSON.parse(keyDataStr);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
showLicenseInfo(LicenseKeyError.parseFailed);
|
|
32
|
+
throw new Error(LicenseKeyError.parseFailed.title);
|
|
33
|
+
}
|
|
34
|
+
const env = await getEnvAsync();
|
|
35
|
+
const isEnvMatched = await isEnvMatch(env, keyData);
|
|
36
|
+
if (!isEnvMatched) {
|
|
37
|
+
showLicenseInfo({
|
|
38
|
+
...LicenseKeyError.notMatch,
|
|
39
|
+
env,
|
|
40
|
+
});
|
|
41
|
+
throw new Error(LicenseKeyError.notMatch.title);
|
|
42
|
+
}
|
|
43
|
+
const { accessKeyId, accessKeySecret } = keyData;
|
|
44
|
+
return { accessKeyId, accessKeySecret };
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const LicenseKeyError = {
|
|
48
|
+
notExist: {
|
|
49
|
+
title: 'License key not found',
|
|
50
|
+
content:
|
|
51
|
+
'Please go to the license settings page to obtain the Instance ID for the current environment, and then generate the license key on the service platform.',
|
|
52
|
+
},
|
|
53
|
+
parseFailed: {
|
|
54
|
+
title: 'Invalid license key format',
|
|
55
|
+
content: 'Please check your license key, or regenerate the license key on the service platform.',
|
|
56
|
+
},
|
|
57
|
+
notMatch: {
|
|
58
|
+
title: 'License key not match current environment',
|
|
59
|
+
content:
|
|
60
|
+
'Please go to the license settings page to obtain the Instance ID for the current environment, and then regenerate the license key on the service platform.',
|
|
61
|
+
},
|
|
62
|
+
notValid: {
|
|
63
|
+
title: 'Invalid license key',
|
|
64
|
+
content:
|
|
65
|
+
'Please go to the license settings page to obtain the Instance ID for the current environment, and then regenerate the license key on the service platform.',
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
exports.LicenseKeyError = LicenseKeyError;
|
|
70
|
+
|
|
71
|
+
function showLicenseInfo({ title, content, env }) {
|
|
72
|
+
logger.error(title + '. ' + content);
|
|
73
|
+
logger.error('Current environment', pick(env, ['sys', 'osVer', 'db']));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
exports.showLicenseInfo = showLicenseInfo;
|
package/src/logger.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const winston = require('winston');
|
|
13
|
+
require('winston-daily-rotate-file');
|
|
14
|
+
|
|
15
|
+
function ensureDir(dir) {
|
|
16
|
+
if (!fs.existsSync(dir)) {
|
|
17
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function createSystemLogger({ dirname, filename, defaultMeta = {} }) {
|
|
22
|
+
ensureDir(dirname);
|
|
23
|
+
|
|
24
|
+
const commonFormat = winston.format.combine(
|
|
25
|
+
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
|
26
|
+
winston.format.printf((info) => {
|
|
27
|
+
const meta = info.meta ? JSON.stringify(info.meta) : '';
|
|
28
|
+
return `${info.timestamp} [${info.level}] ${info.message} ${meta}`;
|
|
29
|
+
}),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const consoleFormat = winston.format.combine(winston.format.colorize(), commonFormat);
|
|
33
|
+
|
|
34
|
+
const fileTransport = new winston.transports.DailyRotateFile({
|
|
35
|
+
dirname,
|
|
36
|
+
filename: `${filename}_%DATE%.log`,
|
|
37
|
+
datePattern: 'YYYY-MM-DD',
|
|
38
|
+
zippedArchive: false,
|
|
39
|
+
format: commonFormat,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const consoleTransport = new winston.transports.Console({
|
|
43
|
+
format: consoleFormat,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const logger = winston.createLogger({
|
|
47
|
+
level: 'info',
|
|
48
|
+
transports: [consoleTransport, fileTransport],
|
|
49
|
+
defaultMeta,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const wrap = (level) => (message, meta) => {
|
|
53
|
+
logger.log({ level, message, meta });
|
|
54
|
+
return logger;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
info: wrap('info'),
|
|
59
|
+
warn: wrap('warn'),
|
|
60
|
+
error: wrap('error'),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const getLoggerFilePath = (...paths) => {
|
|
65
|
+
return path.resolve(process.env.LOGGER_BASE_PATH || path.resolve(process.cwd(), 'storage', 'logs'), ...paths);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const logger = createSystemLogger({
|
|
69
|
+
dirname: getLoggerFilePath('main'),
|
|
70
|
+
filename: 'system',
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
module.exports = {
|
|
74
|
+
logger,
|
|
75
|
+
};
|
package/src/util.js
CHANGED
|
@@ -18,8 +18,6 @@ const dotenv = require('dotenv');
|
|
|
18
18
|
const fs = require('fs-extra');
|
|
19
19
|
const os = require('os');
|
|
20
20
|
const moment = require('moment-timezone');
|
|
21
|
-
const { keyDecrypt, getEnvAsync } = require('@nocobase/license-kit');
|
|
22
|
-
const omit = require('lodash/omit');
|
|
23
21
|
|
|
24
22
|
exports.isPackageValid = (pkg) => {
|
|
25
23
|
try {
|
|
@@ -297,14 +295,26 @@ function buildIndexHtml(force = false) {
|
|
|
297
295
|
fs.copyFileSync(file, tpl);
|
|
298
296
|
}
|
|
299
297
|
const data = fs.readFileSync(tpl, 'utf-8');
|
|
300
|
-
|
|
298
|
+
let replacedData = data
|
|
299
|
+
.replace(/\{\{env.CDN_BASE_URL\}\}/g, process.env.CDN_BASE_URL)
|
|
301
300
|
.replace(/\{\{env.APP_PUBLIC_PATH\}\}/g, process.env.APP_PUBLIC_PATH)
|
|
301
|
+
.replace(/\{\{env.API_CLIENT_SHARE_TOKEN\}\}/g, process.env.API_CLIENT_SHARE_TOKEN || 'false')
|
|
302
302
|
.replace(/\{\{env.API_CLIENT_STORAGE_TYPE\}\}/g, process.env.API_CLIENT_STORAGE_TYPE)
|
|
303
303
|
.replace(/\{\{env.API_CLIENT_STORAGE_PREFIX\}\}/g, process.env.API_CLIENT_STORAGE_PREFIX)
|
|
304
304
|
.replace(/\{\{env.API_BASE_URL\}\}/g, process.env.API_BASE_URL || process.env.API_BASE_PATH)
|
|
305
305
|
.replace(/\{\{env.WS_URL\}\}/g, process.env.WEBSOCKET_URL || '')
|
|
306
306
|
.replace(/\{\{env.WS_PATH\}\}/g, process.env.WS_PATH)
|
|
307
|
+
.replace(/\{\{env.ESM_CDN_BASE_URL\}\}/g, process.env.ESM_CDN_BASE_URL || '')
|
|
308
|
+
.replace(/\{\{env.ESM_CDN_SUFFIX\}\}/g, process.env.ESM_CDN_SUFFIX || '')
|
|
307
309
|
.replace('src="/umi.', `src="${process.env.APP_PUBLIC_PATH}umi.`);
|
|
310
|
+
|
|
311
|
+
if (process.env.CDN_BASE_URL) {
|
|
312
|
+
const appBaseUrl = process.env.CDN_BASE_URL.replace(/\/+$/, '');
|
|
313
|
+
const appPublicPath = process.env.APP_PUBLIC_PATH.replace(/\/+$/, '');
|
|
314
|
+
const re1 = new RegExp(`src="${appPublicPath}/`, 'g');
|
|
315
|
+
const re2 = new RegExp(`href="${appPublicPath}/`, 'g');
|
|
316
|
+
replacedData = replacedData.replace(re1, `src="${appBaseUrl}/`).replace(re2, `href="${appBaseUrl}/`);
|
|
317
|
+
}
|
|
308
318
|
fs.writeFileSync(file, replacedData, 'utf-8');
|
|
309
319
|
}
|
|
310
320
|
|
|
@@ -362,6 +372,7 @@ exports.initEnv = function initEnv() {
|
|
|
362
372
|
APP_PORT: 13000,
|
|
363
373
|
API_BASE_PATH: '/api/',
|
|
364
374
|
API_CLIENT_STORAGE_PREFIX: 'NOCOBASE_',
|
|
375
|
+
API_CLIENT_SHARE_TOKEN: 'false',
|
|
365
376
|
API_CLIENT_STORAGE_TYPE: 'localStorage',
|
|
366
377
|
// DB_DIALECT: 'sqlite',
|
|
367
378
|
DB_STORAGE: 'storage/db/nocobase.sqlite',
|
|
@@ -385,8 +396,12 @@ exports.initEnv = function initEnv() {
|
|
|
385
396
|
PLUGIN_STATICS_PATH: '/static/plugins/',
|
|
386
397
|
LOGGER_BASE_PATH: 'storage/logs',
|
|
387
398
|
APP_SERVER_BASE_URL: '',
|
|
399
|
+
APP_BASE_URL: '',
|
|
400
|
+
CDN_BASE_URL: '',
|
|
388
401
|
APP_PUBLIC_PATH: '/',
|
|
389
402
|
WATCH_FILE: resolve(process.cwd(), 'storage/app.watch.ts'),
|
|
403
|
+
ESM_CDN_BASE_URL: 'https://esm.sh',
|
|
404
|
+
ESM_CDN_SUFFIX: '',
|
|
390
405
|
};
|
|
391
406
|
|
|
392
407
|
if (
|
|
@@ -440,6 +455,16 @@ exports.initEnv = function initEnv() {
|
|
|
440
455
|
process.env.__env_modified__ = true;
|
|
441
456
|
}
|
|
442
457
|
|
|
458
|
+
if (!process.env.CDN_BASE_URL && process.env.APP_PUBLIC_PATH !== '/') {
|
|
459
|
+
process.env.CDN_BASE_URL = process.env.APP_PUBLIC_PATH;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (process.env.CDN_BASE_URL.includes('http') && process.env.CDN_VERSION === 'auto') {
|
|
463
|
+
const version = require('../package.json').version;
|
|
464
|
+
process.env.CDN_BASE_URL = process.env.CDN_BASE_URL.replace(/\/+$/, '') + '/' + version + '/';
|
|
465
|
+
process.env.CDN_VERSION = '';
|
|
466
|
+
}
|
|
467
|
+
|
|
443
468
|
if (!process.env.TZ) {
|
|
444
469
|
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
445
470
|
process.env.TZ = getTimezonesByOffset(process.env.DB_TIMEZONE || timeZone);
|
|
@@ -490,89 +515,3 @@ exports.generatePlugins = function () {
|
|
|
490
515
|
return;
|
|
491
516
|
}
|
|
492
517
|
};
|
|
493
|
-
|
|
494
|
-
async function isEnvMatch(keyData) {
|
|
495
|
-
const env = await getEnvAsync();
|
|
496
|
-
if (env?.container?.id && keyData?.instanceData?.container?.id) {
|
|
497
|
-
return (
|
|
498
|
-
JSON.stringify(omit(env, ['timestamp', 'container', 'hostname', 'mac'])) ===
|
|
499
|
-
JSON.stringify(omit(keyData?.instanceData, ['timestamp', 'container', 'hostname', 'mac']))
|
|
500
|
-
);
|
|
501
|
-
}
|
|
502
|
-
return (
|
|
503
|
-
JSON.stringify(omit(env, ['timestamp', 'mac'])) ===
|
|
504
|
-
JSON.stringify(omit(keyData?.instanceData, ['timestamp', 'mac']))
|
|
505
|
-
);
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
exports.getAccessKeyPair = async function () {
|
|
509
|
-
const keyFile = resolve(process.cwd(), 'storage/.license/license-key');
|
|
510
|
-
if (!fs.existsSync(keyFile)) {
|
|
511
|
-
return {};
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
let keyData = {};
|
|
515
|
-
try {
|
|
516
|
-
const str = fs.readFileSync(keyFile, 'utf-8');
|
|
517
|
-
const keyDataStr = keyDecrypt(str);
|
|
518
|
-
keyData = JSON.parse(keyDataStr);
|
|
519
|
-
} catch (error) {
|
|
520
|
-
showLicenseInfo(LicenseKeyError.parseFailed);
|
|
521
|
-
throw new Error(LicenseKeyError.parseFailed.title);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
const isEnvMatched = await isEnvMatch(keyData);
|
|
525
|
-
if (!isEnvMatched) {
|
|
526
|
-
showLicenseInfo(LicenseKeyError.notMatch);
|
|
527
|
-
throw new Error(LicenseKeyError.notMatch.title);
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
const { accessKeyId, accessKeySecret } = keyData;
|
|
531
|
-
return { accessKeyId, accessKeySecret };
|
|
532
|
-
};
|
|
533
|
-
|
|
534
|
-
const LicenseKeyError = {
|
|
535
|
-
notExist: {
|
|
536
|
-
title: 'License key not found',
|
|
537
|
-
content:
|
|
538
|
-
'Please go to the license settings page to obtain the Instance ID for the current environment, and then generate the license key on the service platform.',
|
|
539
|
-
},
|
|
540
|
-
parseFailed: {
|
|
541
|
-
title: 'Invalid license key format',
|
|
542
|
-
content: 'Please check your license key, or regenerate the license key on the service platform.',
|
|
543
|
-
},
|
|
544
|
-
notMatch: {
|
|
545
|
-
title: 'License key mismatch',
|
|
546
|
-
content:
|
|
547
|
-
'Please go to the license settings page to obtain the Instance ID for the current environment, and then regenerate the license key on the service platform.',
|
|
548
|
-
},
|
|
549
|
-
notValid: {
|
|
550
|
-
title: 'Invalid license key',
|
|
551
|
-
content:
|
|
552
|
-
'Please go to the license settings page to obtain the Instance ID for the current environment, and then regenerate the license key on the service platform.',
|
|
553
|
-
},
|
|
554
|
-
};
|
|
555
|
-
|
|
556
|
-
exports.LicenseKeyError = LicenseKeyError;
|
|
557
|
-
|
|
558
|
-
function showLicenseInfo({ title, content }) {
|
|
559
|
-
const rows = [];
|
|
560
|
-
const length = 80;
|
|
561
|
-
let row = '';
|
|
562
|
-
content.split(' ').forEach((word) => {
|
|
563
|
-
if (row.length + word.length > length) {
|
|
564
|
-
rows.push(row);
|
|
565
|
-
row = '';
|
|
566
|
-
}
|
|
567
|
-
row += word + ' ';
|
|
568
|
-
});
|
|
569
|
-
if (row) {
|
|
570
|
-
rows.push(row);
|
|
571
|
-
}
|
|
572
|
-
console.log(Array(length).fill('-').join(''));
|
|
573
|
-
console.log(chalk.yellow(title));
|
|
574
|
-
console.log(chalk.yellow(rows.join('\n')));
|
|
575
|
-
console.log(Array(length).fill('-').join(''));
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
exports.showLicenseInfo = showLicenseInfo;
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta
|
|
6
|
+
name="viewport"
|
|
7
|
+
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
|
|
8
|
+
/>
|
|
9
|
+
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
|
10
|
+
<title>NocoBase Loading...</title>
|
|
11
|
+
<style data-basic>
|
|
12
|
+
body {
|
|
13
|
+
margin: 0;
|
|
14
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
|
15
|
+
min-height: 100vh;
|
|
16
|
+
display: flex;
|
|
17
|
+
align-items: center;
|
|
18
|
+
justify-content: center;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
color: #333;
|
|
21
|
+
background-color: #f5f5f5;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#loading {
|
|
25
|
+
position: relative;
|
|
26
|
+
padding: 20px 28px 24px 88px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#loading::before {
|
|
30
|
+
position: absolute;
|
|
31
|
+
left: 0;
|
|
32
|
+
top: 50%;
|
|
33
|
+
content: '';
|
|
34
|
+
display: inline-block;
|
|
35
|
+
width: 40px;
|
|
36
|
+
height: 40px;
|
|
37
|
+
border-radius: 50%;
|
|
38
|
+
border: 3px solid #e0e0e0;
|
|
39
|
+
border-top-color: #999;
|
|
40
|
+
animation: loading-spin 0.9s linear infinite;
|
|
41
|
+
transform: translateY(-50%);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#loading h3 {
|
|
45
|
+
margin: 0 0 2px;
|
|
46
|
+
color: #666;
|
|
47
|
+
font-size: 20px;
|
|
48
|
+
font-weight: normal;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#loading small {
|
|
52
|
+
color: #888;
|
|
53
|
+
font-size: 12px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#loading small::after {
|
|
57
|
+
content: 'from source';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
#loading p {
|
|
61
|
+
box-sizing: border-box;
|
|
62
|
+
font-family: Georgia, 'Times New Roman', Times, serif;
|
|
63
|
+
color: #333;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#loading p:empty {
|
|
67
|
+
display: none;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#loading p span {
|
|
71
|
+
font-family: sans-serif;
|
|
72
|
+
padding: 0 6px 0 2px;
|
|
73
|
+
color: #666;
|
|
74
|
+
font-size: 14px;
|
|
75
|
+
vertical-align: bottom;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
#loading p.summary {
|
|
79
|
+
margin: 8px 0;
|
|
80
|
+
height: 28px;
|
|
81
|
+
font-size: 24px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#loading p.detail {
|
|
85
|
+
margin: 2px 0;
|
|
86
|
+
height: 40px;
|
|
87
|
+
font-size: 16px;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
code.progress-details {
|
|
91
|
+
margin-top: 20px;
|
|
92
|
+
font-size: 13px;
|
|
93
|
+
color: #999;
|
|
94
|
+
max-width: calc(100vw - 500px);
|
|
95
|
+
text-align: center;
|
|
96
|
+
white-space: pre-wrap;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@keyframes fade-in-out {
|
|
100
|
+
0% {
|
|
101
|
+
opacity: 0.4;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
100% {
|
|
105
|
+
opacity: 1;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@keyframes loading-spin {
|
|
110
|
+
0% {
|
|
111
|
+
transform: translateY(-50%) rotate(0deg);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
100% {
|
|
115
|
+
transform: translateY(-50%) rotate(360deg);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
</style>
|
|
119
|
+
<style data-mfsu>
|
|
120
|
+
#loading[data-mfsu]::after {
|
|
121
|
+
position: absolute;
|
|
122
|
+
top: 0;
|
|
123
|
+
left: 64px;
|
|
124
|
+
content: '';
|
|
125
|
+
width: 20px;
|
|
126
|
+
height: 22px;
|
|
127
|
+
background: center / 20px 22px;
|
|
128
|
+
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAuCAMAAABteatCAAABKVBMVEUAAAADAgAAAAAAAAAAAAAAAAAAAAAIBwEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASDwIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+2SgAAAD81ycWEwPxzSXOsCBBOAoqIwbjwiOWgBd2ZBJdTw741Ca1mhyhihlHPAswKQcbFwT00CbgvyLWtyG5nh2GchV7aRNYSw1VSA3zzybpxyTmxCTLrR+QehaMdxZvXxFmVxBjVQ85MQglIAXqxyTSsyG/ox6zmRyrkhqpkBqmjhqdhhiBbhRQRAxMQQyKBhJVAAAAM3RSTlMA/vrcEO3F/nA4BvfUqYx4aFAjBP7x6uOugCEdCrSllohUSj0yLRQC/se/k2FWJ7qamFjVvxppAAAB4klEQVQ4y42U10IaQRhG2QVBAaVIMxQNCthT9iwdkSoEEkss6e39HyIaQ4Zhdjee63PxH/h2XCqF4EbU9QQ8bsAb9vxXzNI7iYGeivgdvQ12h8ag09VgaXPF3sujjYwHzq96gNvu3OgSY2PGx1bf7txMkJ+mITBHNufmuKsYMm//nit5y8TODJXzKWTnvW2dL4YFpz5y63NeOkHLyvum8ao05/m91EwLr6mxJR24Sf+DhTeGkOQd4ruw8FpoYcnbCfBd1cwb9CPJKya5Vr32lEBe8kovuG0r3qBLfNslsUW5oXiVWxI7shdB+6R473okj2XvOM6l4g3vYK24OJmJGlK93gU9e5QRYor6wLCg/fWXBvGXs54QmjQZ6c5LMfOCDuX3VcOOsz8z3wvfjyYUBOrjhp1qXgHux//vIAFarVOx8honGkvLsz2u51cD4JucLg6t8sZHYM0jvRCH+yyeazbLsPrMtUg6lISm8D7XwWv5CBTitMRPXgN3xGVFOsnEVBpUPEFqbdGgiwYZ/z71itqgkqM/fPyO1QZ5vLHqv4bnLlvCaCO1QSWi0bl/kUSDDSsBfqgNKtEEN2qDStFN90JpUMnsUZ4+NPidvVIKUBtUDkA0OBBCNDjxWjQ4o4sGR34DhGGszb2iUaMAAAAASUVORK5CYII=');
|
|
129
|
+
animation: fade-in-out alternate infinite 1s;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
#loading[data-mfsu] small::after {
|
|
133
|
+
content: 'Umi with MFSU';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
#loading[data-mfsu]:hover p.summary,
|
|
137
|
+
#loading[data-mfsu]:not(:hover) p.detail {
|
|
138
|
+
display: none;
|
|
139
|
+
}
|
|
140
|
+
</style>
|
|
141
|
+
<style data-progress>
|
|
142
|
+
#loading[data-percent^='2']:not([data-percent='2'])::before,
|
|
143
|
+
#loading[data-percent^='3']:not([data-percent='3'])::before {
|
|
144
|
+
background-position-x: -68px;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
#loading[data-percent^='4']:not([data-percent='4'])::before,
|
|
148
|
+
#loading[data-percent^='5']:not([data-percent='5'])::before {
|
|
149
|
+
background-position-x: -136px;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
#loading[data-percent^='6']:not([data-percent='6'])::before,
|
|
153
|
+
#loading[data-percent^='7']:not([data-percent='7'])::before {
|
|
154
|
+
background-position-x: -204px;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
#loading[data-percent^='8']:not([data-percent='8'])::before,
|
|
158
|
+
#loading[data-percent^='9']:not([data-percent='9'])::before {
|
|
159
|
+
background-position-x: -272px;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
#loading[data-percent='99']::before,
|
|
163
|
+
#loading[data-percent='100']::before {
|
|
164
|
+
background-position-x: -340px !important;
|
|
165
|
+
}
|
|
166
|
+
</style>
|
|
167
|
+
<style data-mako>
|
|
168
|
+
#loading[data-mako]::after {
|
|
169
|
+
position: absolute;
|
|
170
|
+
top: 0;
|
|
171
|
+
left: 64px;
|
|
172
|
+
content: '';
|
|
173
|
+
width: 20px;
|
|
174
|
+
height: 22px;
|
|
175
|
+
background: center / 20px 22px;
|
|
176
|
+
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAuCAMAAABteatCAAABKVBMVEUAAAADAgAAAAAAAAAAAAAAAAAAAAAIBwEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASDwIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+2SgAAAD81ycWEwPxzSXOsCBBOAoqIwbjwiOWgBd2ZBJdTw741Ca1mhyhihlHPAswKQcbFwT00CbgvyLWtyG5nh2GchV7aRNYSw1VSA3zzybpxyTmxCTLrR+QehaMdxZvXxFmVxBjVQ85MQglIAXqxyTSsyG/ox6zmRyrkhqpkBqmjhqdhhiBbhRQRAxMQQyKBhJVAAAAM3RSTlMA/vrcEO3F/nA4BvfUqYx4aFAjBP7x6uOugCEdCrSllohUSj0yLRQC/se/k2FWJ7qamFjVvxppAAAB4klEQVQ4y42U10IaQRhG2QVBAaVIMxQNCthT9iwdkSoEEkss6e39HyIaQ4Zhdjee63PxH/h2XCqF4EbU9QQ8bsAb9vxXzNI7iYGeivgdvQ12h8ag09VgaXPF3sujjYwHzq96gNvu3OgSY2PGx1bf7txMkJ+mITBHNufmuKsYMm//nit5y8TODJXzKWTnvW2dL4YFpz5y63NeOkHLyvum8ao05/m91EwLr6mxJR24Sf+DhTeGkOQd4ruw8FpoYcnbCfBd1cwb9CPJKya5Vr32lEBe8kovuG0r3qBLfNslsUW5oXiVWxI7shdB+6R473okj2XvOM6l4g3vYK24OJmJGlK93gU9e5QRYor6wLCg/fWXBvGXs54QmjQZ6c5LMfOCDuX3VcOOsz8z3wvfjyYUBOrjhp1qXgHux//vIAFarVOx8honGkvLsz2u51cD4JucLg6t8sZHYM0jvRCH+yyeazbLsPrMtUg6lISm8D7XwWv5CBTitMRPXgN3xGVFOsnEVBpUPEFqbdGgiwYZ/z71itqgkqM/fPyO1QZ5vLHqv4bnLlvCaCO1QSWi0bl/kUSDDSsBfqgNKtEEN2qDStFN90JpUMnsUZ4+NPidvVIKUBtUDkA0OBBCNDjxWjQ4o4sGR34DhGGszb2iUaMAAAAASUVORK5CYII=');
|
|
177
|
+
animation: fade-in-out alternate infinite 1s;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
#loading[data-mako] small::after {
|
|
181
|
+
content: 'Umi with Mako';
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
#loading[data-mako] .loader {
|
|
185
|
+
position: fixed;
|
|
186
|
+
left: 0;
|
|
187
|
+
top: 0;
|
|
188
|
+
height: 5px;
|
|
189
|
+
width: 100vw;
|
|
190
|
+
--c:no-repeat linear-gradient(#1d90fe 0 0);
|
|
191
|
+
background: var(--c),var(--c),#cddeee;
|
|
192
|
+
background-size: 60% 100%;
|
|
193
|
+
animation: l16 3s infinite;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
@keyframes l16 {
|
|
197
|
+
0% {background-position:-150% 0,-150% 0}
|
|
198
|
+
66% {background-position: 250% 0,-150% 0}
|
|
199
|
+
100% {background-position: 250% 0, 250% 0}
|
|
200
|
+
}
|
|
201
|
+
</style>
|
|
202
|
+
<style data-utoopack>
|
|
203
|
+
#loading[data-utoopack]::after {
|
|
204
|
+
position: absolute;
|
|
205
|
+
top: 0;
|
|
206
|
+
left: 64px;
|
|
207
|
+
content: '';
|
|
208
|
+
width: 20px;
|
|
209
|
+
height: 22px;
|
|
210
|
+
background: center / 20px 22px;
|
|
211
|
+
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAuCAMAAABteatCAAABKVBMVEUAAAADAgAAAAAAAAAAAAAAAAAAAAAIBwEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASDwIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+2SgAAAD81ycWEwPxzSXOsCBBOAoqIwbjwiOWgBd2ZBJdTw741Ca1mhyhihlHPAswKQcbFwT00CbgvyLWtyG5nh2GchV7aRNYSw1VSA3zzybpxyTmxCTLrR+QehaMdxZvXxFmVxBjVQ85MQglIAXqxyTSsyG/ox6zmRyrkhqpkBqmjhqdhhiBbhRQRAxMQQyKBhJVAAAAM3RSTlMA/vrcEO3F/nA4BvfUqYx4aFAjBP7x6uOugCEdCrSllohUSj0yLRQC/se/k2FWJ7qamFjVvxppAAAB4klEQVQ4y42U10IaQRhG2QVBAaVIMxQNCthT9iwdkSoEEkss6e39HyIaQ4Zhdjee63PxH/h2XCqF4EbU9QQ8bsAb9vxXzNI7iYGeivgdvQ12h8ag09VgaXPF3sujjYwHzq96gNvu3OgSY2PGx1bf7txMkJ+mITBHNufmuKsYMm//nit5y8TODJXzKWTnvW2dL4YFpz5y63NeOkHLyvum8ao05/m91EwLr6mxJR24Sf+DhTeGkOQd4ruw8FpoYcnbCfBd1cwb9CPJKya5Vr32lEBe8kovuG0r3qBLfNslsUW5oXiVWxI7shdB+6R473okj2XvOM6l4g3vYK24OJmJGlK93gU9e5QRYor6wLCg/fWXBvGXs54QmjQZ6c5LMfOCDuX3VcOOsz8z3wvfjyYUBOrjhp1qXgHux//vIAFarVOx8honGkvLsz2u51cD4JucLg6t8sZHYM0jvRCH+yyeazbLsPrMtUg6lISm8D7XwWv5CBTitMRPXgN3xGVFOsnEVBpUPEFqbdGgiwYZ/z71itqgkqM/fPyO1QZ5vLHqv4bnLlvCaCO1QSWi0bl/kUSDDSsBfqgNKtEEN2qDStFN90JpUMnsUZ4+NPidvVIKUBtUDkA0OBBCNDjxWjQ4o4sGR34DhGGszb2iUaMAAAAASUVORK5CYII=');
|
|
212
|
+
animation: fade-in-out alternate infinite 1s;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
#loading[data-utoopack] small::after {
|
|
216
|
+
content: 'Umi with Utoopack';
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
#loading[data-utoopack] .loader {
|
|
220
|
+
position: fixed;
|
|
221
|
+
left: 0;
|
|
222
|
+
top: 0;
|
|
223
|
+
height: 5px;
|
|
224
|
+
width: 100vw;
|
|
225
|
+
--c:no-repeat linear-gradient(#1d90fe 0 0);
|
|
226
|
+
background: var(--c),var(--c),#cddeee;
|
|
227
|
+
background-size: 60% 100%;
|
|
228
|
+
animation: l16 3s infinite;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
@keyframes l16 {
|
|
232
|
+
0% {background-position:-150% 0,-150% 0}
|
|
233
|
+
66% {background-position: 250% 0,-150% 0}
|
|
234
|
+
100% {background-position: 250% 0, 250% 0}
|
|
235
|
+
}
|
|
236
|
+
</style>
|
|
237
|
+
</head>
|
|
238
|
+
|
|
239
|
+
<body>
|
|
240
|
+
<div id="loading">
|
|
241
|
+
<div class="loader"></div>
|
|
242
|
+
<h3>Bundling...</h3>
|
|
243
|
+
<small></small>
|
|
244
|
+
<p class="summary"></p>
|
|
245
|
+
<p class="detail"></p>
|
|
246
|
+
</div>
|
|
247
|
+
<code class="progress-details"></code>
|
|
248
|
+
|
|
249
|
+
<script>
|
|
250
|
+
const loading = document.getElementById('loading');
|
|
251
|
+
const progressDetails = document.querySelector('.progress-details');
|
|
252
|
+
const summary = loading.querySelector('.summary');
|
|
253
|
+
const detail = loading.querySelector('.detail');
|
|
254
|
+
|
|
255
|
+
function check(data) {
|
|
256
|
+
renderStatus(data);
|
|
257
|
+
if (
|
|
258
|
+
data.bundleStatus.done &&
|
|
259
|
+
(!data.mfsuBundleStatus || data.mfsuBundleStatus.done)
|
|
260
|
+
) {
|
|
261
|
+
location.reload();
|
|
262
|
+
} else {
|
|
263
|
+
setTimeout(loadData, 300);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function renderStatus(data) {
|
|
268
|
+
if (data.bundler === 'mako') {
|
|
269
|
+
loading.setAttribute('data-mako', '');
|
|
270
|
+
window.__MAKO_PERCENT = window.__MAKO_PERCENT || 0.1;
|
|
271
|
+
const makoPercent = Math.floor(window.__MAKO_PERCENT * 100);
|
|
272
|
+
loading.setAttribute('data-percent', makoPercent);
|
|
273
|
+
window.__MAKO_PERCENT = window.__MAKO_PERCENT >= 1 ? 0.2 : window.__MAKO_PERCENT + 0.1;
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (data.bundler === 'utoopack') {
|
|
277
|
+
loading.setAttribute('data-utoopack', '');
|
|
278
|
+
window.__UTOO_PERCENT = window.__UTOO_PERCENT || 0.1;
|
|
279
|
+
const utooPercent = Math.floor(window.__UTOO_PERCENT * 100);
|
|
280
|
+
loading.setAttribute('data-percent', utooPercent);
|
|
281
|
+
window.__UTOO_PERCENT = window.__UTOO_PERCENT >= 1 ? 0.2 : window.__UTOO_PERCENT + 0.1;
|
|
282
|
+
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const hasMFSU = Boolean(data.mfsuBundleStatus);
|
|
286
|
+
const hasProgressDetails = Boolean(
|
|
287
|
+
data.bundleStatus.progresses[0].details,
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
if (hasProgressDetails) {
|
|
291
|
+
const details = data.bundleStatus.progresses[0].details;
|
|
292
|
+
if (details.length) {
|
|
293
|
+
progressDetails.innerHTML = details.join('<br />');
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const srcPercent = Math.floor(
|
|
298
|
+
(data.bundleStatus.done
|
|
299
|
+
? 1
|
|
300
|
+
: data.bundleStatus.progresses[0].percent || 0) * 100,
|
|
301
|
+
);
|
|
302
|
+
const mfsuPrecent =
|
|
303
|
+
hasMFSU &&
|
|
304
|
+
Math.floor(
|
|
305
|
+
(data.mfsuBundleStatus.done
|
|
306
|
+
? 1
|
|
307
|
+
: data.mfsuBundleStatus.percent || 0) * 100,
|
|
308
|
+
);
|
|
309
|
+
const totalPercent = hasMFSU
|
|
310
|
+
? Math.floor((srcPercent + mfsuPrecent) / 2)
|
|
311
|
+
: srcPercent;
|
|
312
|
+
const summaryText = totalPercent + '<span>%</span>';
|
|
313
|
+
let detailText = summaryText;
|
|
314
|
+
|
|
315
|
+
if (hasMFSU) {
|
|
316
|
+
loading.setAttribute('data-mfsu', '');
|
|
317
|
+
detailText = [
|
|
318
|
+
srcPercent + '<span>% for src</span>',
|
|
319
|
+
'<br />',
|
|
320
|
+
mfsuPrecent + '<span>% for deps</span>',
|
|
321
|
+
].join('\n');
|
|
322
|
+
detail.innerHTML = detailText;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
loading.setAttribute('data-percent', totalPercent);
|
|
326
|
+
summary.innerHTML = summaryText;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function loadData() {
|
|
330
|
+
fetch('/__umi/api/bundle-status')
|
|
331
|
+
.then((res) => res.json())
|
|
332
|
+
.then(check);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
loadData();
|
|
336
|
+
</script>
|
|
337
|
+
</body>
|
|
338
|
+
</html>
|