@whyour/qinglong 2.21.0-15 → 2.21.0-16
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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@whyour/qinglong",
|
|
3
3
|
"packageManager": "pnpm@8.3.1",
|
|
4
|
-
"version": "2.21.0-
|
|
4
|
+
"version": "2.21.0-16",
|
|
5
5
|
"description": "Timed task management platform supporting Python3, JavaScript, Shell, Typescript",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
"start:front": "max dev",
|
|
19
19
|
"build:front": "max build",
|
|
20
20
|
"build:back": "tsc -p back/tsconfig.json",
|
|
21
|
-
"test:race": "node -r ts-node/register test/race-conditions.test.js",
|
|
22
21
|
"panel": "npm run build:back && node static/build/app.js",
|
|
23
22
|
"gen:proto": "protoc --experimental_allow_proto3_optional --plugin=./node_modules/.bin/protoc-gen-ts_proto ./back/protos/*.proto --ts_proto_out=./ --ts_proto_opt=outputServices=grpc-js,env=node,esModuleInterop=true,snakeToCamel=false",
|
|
24
23
|
"prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
|
|
@@ -66,7 +65,7 @@
|
|
|
66
65
|
}
|
|
67
66
|
},
|
|
68
67
|
"overrides": {
|
|
69
|
-
"sqlite3": "
|
|
68
|
+
"sqlite3": "npm:@whyour/sqlite3@1.1.0",
|
|
70
69
|
"@codemirror/state": "6.5.4",
|
|
71
70
|
"@codemirror/view": "6.39.16"
|
|
72
71
|
}
|
|
@@ -110,7 +109,7 @@
|
|
|
110
109
|
"request-ip": "3.3.0",
|
|
111
110
|
"sequelize": "^6.37.5",
|
|
112
111
|
"sockjs": "^0.3.24",
|
|
113
|
-
"sqlite3": "
|
|
112
|
+
"sqlite3": "npm:@whyour/sqlite3@1.1.0",
|
|
114
113
|
"toad-scheduler": "^3.0.1",
|
|
115
114
|
"typedi": "^0.10.0",
|
|
116
115
|
"undici": "^7.9.0",
|
|
@@ -98,7 +98,11 @@ function run() {
|
|
|
98
98
|
const newEnvObject = JSON.parse(envStr);
|
|
99
99
|
if (typeof newEnvObject === 'object' && newEnvObject !== null) {
|
|
100
100
|
for (const key in newEnvObject) {
|
|
101
|
-
if (
|
|
101
|
+
if (
|
|
102
|
+
Object.prototype.hasOwnProperty.call(newEnvObject, key) &&
|
|
103
|
+
key !== 'NODE_PATH' &&
|
|
104
|
+
key !== 'QL_NODE_GLOBAL_PATH'
|
|
105
|
+
) {
|
|
102
106
|
process.env[key] = newEnvObject[key];
|
|
103
107
|
}
|
|
104
108
|
}
|
|
@@ -232,34 +232,13 @@ async function readDirs(dir, baseDir = '', blacklist = [], sort = dirSort) {
|
|
|
232
232
|
const result = [];
|
|
233
233
|
for (const file of files) {
|
|
234
234
|
const subPath = path.join(dir, file);
|
|
235
|
-
|
|
236
|
-
try {
|
|
237
|
-
stats = await fs.lstat(subPath);
|
|
238
|
-
}
|
|
239
|
-
catch (error) {
|
|
240
|
-
// Files can disappear between readdir and lstat while logs are pruned.
|
|
241
|
-
// Skip only that expected race and keep surfacing all other I/O errors.
|
|
242
|
-
if (error.code === 'ENOENT') {
|
|
243
|
-
continue;
|
|
244
|
-
}
|
|
245
|
-
throw error;
|
|
246
|
-
}
|
|
235
|
+
const stats = await fs.lstat(subPath);
|
|
247
236
|
const key = path.join(relativePath, file);
|
|
248
237
|
if (blacklist.includes(file) || stats.isSymbolicLink()) {
|
|
249
238
|
continue;
|
|
250
239
|
}
|
|
251
240
|
if (stats.isDirectory()) {
|
|
252
|
-
|
|
253
|
-
try {
|
|
254
|
-
children = await readDirs(subPath, baseDir, blacklist, sort);
|
|
255
|
-
}
|
|
256
|
-
catch (error) {
|
|
257
|
-
// A directory may also be removed after lstat but before recursion.
|
|
258
|
-
if (error.code === 'ENOENT') {
|
|
259
|
-
continue;
|
|
260
|
-
}
|
|
261
|
-
throw error;
|
|
262
|
-
}
|
|
241
|
+
const children = await readDirs(subPath, baseDir, blacklist, sort);
|
|
263
242
|
result.push({
|
|
264
243
|
title: file,
|
|
265
244
|
key,
|
|
@@ -40,7 +40,7 @@ const path_1 = __importStar(require("path"));
|
|
|
40
40
|
const config_1 = __importDefault(require("../config"));
|
|
41
41
|
const util_1 = require("../config/util");
|
|
42
42
|
const i18n_1 = require("../shared/i18n");
|
|
43
|
-
const
|
|
43
|
+
const undici_1 = require("undici");
|
|
44
44
|
let ConfigService = class ConfigService {
|
|
45
45
|
constructor() { }
|
|
46
46
|
async getFile(filePath, res) {
|
|
@@ -65,7 +65,8 @@ let ConfigService = class ConfigService {
|
|
|
65
65
|
return res.send({ code: 403, message: (0, i18n_1.t)('文件无法访问') });
|
|
66
66
|
}
|
|
67
67
|
if (filePath.startsWith('sample/')) {
|
|
68
|
-
|
|
68
|
+
const res = await (0, undici_1.request)(`https://gitlab.com/whyour/qinglong/-/raw/master/${filePath}`);
|
|
69
|
+
content = await res.body.text();
|
|
69
70
|
}
|
|
70
71
|
else if (filePath.startsWith('data/scripts/')) {
|
|
71
72
|
content = await (0, util_1.getFileContentByName)((0, path_1.join)(config_1.default.rootPath, filePath));
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.loadSampleFile = void 0;
|
|
7
|
-
const promises_1 = require("fs/promises");
|
|
8
|
-
const path_1 = require("path");
|
|
9
|
-
const undici_1 = require("undici");
|
|
10
|
-
const config_1 = __importDefault(require("../config"));
|
|
11
|
-
const util_1 = require("../config/util");
|
|
12
|
-
const logger_1 = __importDefault(require("../loaders/logger"));
|
|
13
|
-
const sampleRequestDispatcher = new undici_1.RetryAgent(new undici_1.Agent({
|
|
14
|
-
connect: { timeout: 10000 },
|
|
15
|
-
headersTimeout: 10000,
|
|
16
|
-
bodyTimeout: 10000,
|
|
17
|
-
keepAliveTimeout: 10000,
|
|
18
|
-
keepAliveMaxTimeout: 30000,
|
|
19
|
-
}), {
|
|
20
|
-
maxRetries: 2,
|
|
21
|
-
minTimeout: 200,
|
|
22
|
-
maxTimeout: 1000,
|
|
23
|
-
timeoutFactor: 2,
|
|
24
|
-
statusCodes: [429, 500, 502, 503, 504],
|
|
25
|
-
errorCodes: [
|
|
26
|
-
'ECONNRESET',
|
|
27
|
-
'ECONNREFUSED',
|
|
28
|
-
'ENOTFOUND',
|
|
29
|
-
'ENETDOWN',
|
|
30
|
-
'ENETUNREACH',
|
|
31
|
-
'EHOSTDOWN',
|
|
32
|
-
'EHOSTUNREACH',
|
|
33
|
-
'EPIPE',
|
|
34
|
-
'UND_ERR_CONNECT_TIMEOUT',
|
|
35
|
-
'UND_ERR_HEADERS_TIMEOUT',
|
|
36
|
-
'UND_ERR_BODY_TIMEOUT',
|
|
37
|
-
'UND_ERR_SOCKET',
|
|
38
|
-
],
|
|
39
|
-
});
|
|
40
|
-
const pendingSampleRequests = new Map();
|
|
41
|
-
async function loadSampleFile(filePath) {
|
|
42
|
-
const pendingRequest = pendingSampleRequests.get(filePath);
|
|
43
|
-
if (pendingRequest) {
|
|
44
|
-
return pendingRequest;
|
|
45
|
-
}
|
|
46
|
-
const requestPromise = (async () => {
|
|
47
|
-
const remoteUrl = `https://gitlab.com/whyour/qinglong/-/raw/master/${filePath}`;
|
|
48
|
-
try {
|
|
49
|
-
const response = await (0, undici_1.request)(remoteUrl, {
|
|
50
|
-
dispatcher: sampleRequestDispatcher,
|
|
51
|
-
maxRedirections: 3,
|
|
52
|
-
});
|
|
53
|
-
const content = await response.body.text();
|
|
54
|
-
if (response.statusCode < 200 || response.statusCode >= 300) {
|
|
55
|
-
throw new Error(`GitLab returned HTTP ${response.statusCode} for ${filePath}`);
|
|
56
|
-
}
|
|
57
|
-
return content;
|
|
58
|
-
}
|
|
59
|
-
catch (error) {
|
|
60
|
-
logger_1.default.warn(`[config] Failed to fetch ${filePath}, using bundled sample: ${(0, util_1.errStack)(error)}`);
|
|
61
|
-
return (0, promises_1.readFile)((0, path_1.join)(config_1.default.rootPath, filePath), 'utf8');
|
|
62
|
-
}
|
|
63
|
-
})();
|
|
64
|
-
pendingSampleRequests.set(filePath, requestPromise);
|
|
65
|
-
try {
|
|
66
|
-
return await requestPromise;
|
|
67
|
-
}
|
|
68
|
-
finally {
|
|
69
|
-
pendingSampleRequests.delete(filePath);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
exports.loadSampleFile = loadSampleFile;
|
|
73
|
-
//# sourceMappingURL=sampleConfig.js.map
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
const assert = require('node:assert/strict');
|
|
2
|
-
const fs = require('node:fs/promises');
|
|
3
|
-
const os = require('node:os');
|
|
4
|
-
const path = require('node:path');
|
|
5
|
-
|
|
6
|
-
require('reflect-metadata');
|
|
7
|
-
|
|
8
|
-
async function testReadDirsSkipsDisappearedEntries() {
|
|
9
|
-
const temporaryRoot = await fs.mkdtemp(
|
|
10
|
-
path.join(os.tmpdir(), 'qinglong-read-dirs-'),
|
|
11
|
-
);
|
|
12
|
-
const disappearingFile = path.join(temporaryRoot, 'disappearing.log');
|
|
13
|
-
const disappearingDirectory = path.join(temporaryRoot, 'disappearing-dir');
|
|
14
|
-
const originalLstat = fs.lstat;
|
|
15
|
-
const originalReaddir = fs.readdir;
|
|
16
|
-
|
|
17
|
-
await fs.writeFile(path.join(temporaryRoot, 'stable.log'), 'stable');
|
|
18
|
-
await fs.writeFile(disappearingFile, 'gone');
|
|
19
|
-
await fs.mkdir(disappearingDirectory);
|
|
20
|
-
|
|
21
|
-
fs.lstat = async (filePath, ...args) => {
|
|
22
|
-
if (filePath === disappearingFile) {
|
|
23
|
-
const error = new Error('file disappeared');
|
|
24
|
-
error.code = 'ENOENT';
|
|
25
|
-
throw error;
|
|
26
|
-
}
|
|
27
|
-
return originalLstat(filePath, ...args);
|
|
28
|
-
};
|
|
29
|
-
fs.readdir = async (directoryPath, ...args) => {
|
|
30
|
-
if (directoryPath === disappearingDirectory) {
|
|
31
|
-
const error = new Error('directory disappeared');
|
|
32
|
-
error.code = 'ENOENT';
|
|
33
|
-
throw error;
|
|
34
|
-
}
|
|
35
|
-
return originalReaddir(directoryPath, ...args);
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
try {
|
|
39
|
-
const { readDirs } = require('../back/config/util');
|
|
40
|
-
const entries = await readDirs(temporaryRoot, temporaryRoot);
|
|
41
|
-
assert.deepEqual(
|
|
42
|
-
entries.map((entry) => entry.title),
|
|
43
|
-
['stable.log'],
|
|
44
|
-
);
|
|
45
|
-
} finally {
|
|
46
|
-
fs.lstat = originalLstat;
|
|
47
|
-
fs.readdir = originalReaddir;
|
|
48
|
-
await fs.rm(temporaryRoot, { recursive: true, force: true });
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async function testReadDirsPreservesUnexpectedErrors() {
|
|
53
|
-
const temporaryRoot = await fs.mkdtemp(
|
|
54
|
-
path.join(os.tmpdir(), 'qinglong-read-dirs-error-'),
|
|
55
|
-
);
|
|
56
|
-
const blockedFile = path.join(temporaryRoot, 'blocked.log');
|
|
57
|
-
const originalLstat = fs.lstat;
|
|
58
|
-
|
|
59
|
-
await fs.writeFile(blockedFile, 'blocked');
|
|
60
|
-
fs.lstat = async (filePath, ...args) => {
|
|
61
|
-
if (filePath === blockedFile) {
|
|
62
|
-
const error = new Error('permission denied');
|
|
63
|
-
error.code = 'EACCES';
|
|
64
|
-
throw error;
|
|
65
|
-
}
|
|
66
|
-
return originalLstat(filePath, ...args);
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
const { readDirs } = require('../back/config/util');
|
|
71
|
-
await assert.rejects(
|
|
72
|
-
readDirs(temporaryRoot, temporaryRoot),
|
|
73
|
-
(error) => error.code === 'EACCES',
|
|
74
|
-
);
|
|
75
|
-
} finally {
|
|
76
|
-
fs.lstat = originalLstat;
|
|
77
|
-
await fs.rm(temporaryRoot, { recursive: true, force: true });
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async function testSampleRequestsAreCoalescedAndFallbackLocally() {
|
|
82
|
-
const undici = require('undici');
|
|
83
|
-
const originalRequest = undici.request;
|
|
84
|
-
let releaseRequest;
|
|
85
|
-
let requestCount = 0;
|
|
86
|
-
|
|
87
|
-
undici.request = async () => {
|
|
88
|
-
requestCount += 1;
|
|
89
|
-
await new Promise((resolve) => {
|
|
90
|
-
releaseRequest = resolve;
|
|
91
|
-
});
|
|
92
|
-
return {
|
|
93
|
-
statusCode: 200,
|
|
94
|
-
body: { text: async () => 'remote sample' },
|
|
95
|
-
};
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const { loadSampleFile } = require('../back/services/sampleConfig');
|
|
99
|
-
const pendingLoads = [
|
|
100
|
-
loadSampleFile('sample/config.sample.sh'),
|
|
101
|
-
loadSampleFile('sample/config.sample.sh'),
|
|
102
|
-
loadSampleFile('sample/config.sample.sh'),
|
|
103
|
-
];
|
|
104
|
-
|
|
105
|
-
await new Promise((resolve) => setImmediate(resolve));
|
|
106
|
-
assert.equal(requestCount, 1);
|
|
107
|
-
releaseRequest();
|
|
108
|
-
assert.deepEqual(await Promise.all(pendingLoads), [
|
|
109
|
-
'remote sample',
|
|
110
|
-
'remote sample',
|
|
111
|
-
'remote sample',
|
|
112
|
-
]);
|
|
113
|
-
|
|
114
|
-
const temporaryRoot = await fs.mkdtemp(
|
|
115
|
-
path.join(os.tmpdir(), 'qinglong-sample-fallback-'),
|
|
116
|
-
);
|
|
117
|
-
const bundledSample = path.join(temporaryRoot, 'sample', 'config.sample.sh');
|
|
118
|
-
const config = require('../back/config').default;
|
|
119
|
-
const originalRootPath = config.rootPath;
|
|
120
|
-
|
|
121
|
-
await fs.mkdir(path.dirname(bundledSample), { recursive: true });
|
|
122
|
-
await fs.writeFile(bundledSample, 'bundled sample');
|
|
123
|
-
config.rootPath = temporaryRoot;
|
|
124
|
-
undici.request = async () => {
|
|
125
|
-
const error = new Error('other side closed');
|
|
126
|
-
error.code = 'UND_ERR_SOCKET';
|
|
127
|
-
throw error;
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
try {
|
|
131
|
-
assert.equal(
|
|
132
|
-
await loadSampleFile('sample/config.sample.sh'),
|
|
133
|
-
'bundled sample',
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
undici.request = async () => ({
|
|
137
|
-
statusCode: 503,
|
|
138
|
-
body: { text: async () => 'upstream unavailable' },
|
|
139
|
-
});
|
|
140
|
-
assert.equal(
|
|
141
|
-
await loadSampleFile('sample/config.sample.sh'),
|
|
142
|
-
'bundled sample',
|
|
143
|
-
);
|
|
144
|
-
} finally {
|
|
145
|
-
undici.request = originalRequest;
|
|
146
|
-
config.rootPath = originalRootPath;
|
|
147
|
-
await fs.rm(temporaryRoot, { recursive: true, force: true });
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
async function main() {
|
|
152
|
-
const testRoot = await fs.mkdtemp(
|
|
153
|
-
path.join(os.tmpdir(), 'qinglong-test-root-'),
|
|
154
|
-
);
|
|
155
|
-
process.env.QL_DIR = testRoot;
|
|
156
|
-
await fs.writeFile(path.join(testRoot, '.env'), 'NODE_ENV=test\n');
|
|
157
|
-
await fs.mkdir(path.join(testRoot, 'data', 'syslog'), { recursive: true });
|
|
158
|
-
|
|
159
|
-
try {
|
|
160
|
-
await testReadDirsSkipsDisappearedEntries();
|
|
161
|
-
await testReadDirsPreservesUnexpectedErrors();
|
|
162
|
-
await testSampleRequestsAreCoalescedAndFallbackLocally();
|
|
163
|
-
console.log('race condition regression tests passed');
|
|
164
|
-
} finally {
|
|
165
|
-
const loggerModule =
|
|
166
|
-
require.cache[require.resolve('../back/loaders/logger')];
|
|
167
|
-
loggerModule?.exports?.default?.close();
|
|
168
|
-
await fs.rm(testRoot, { recursive: true, force: true });
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
main().catch((error) => {
|
|
173
|
-
console.error(error);
|
|
174
|
-
process.exitCode = 1;
|
|
175
|
-
});
|