@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-15",
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": "git+https://github.com/whyour/node-sqlite3.git#v1.1.0",
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": "git+https://github.com/whyour/node-sqlite3.git#v1.1.0",
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 (Object.prototype.hasOwnProperty.call(newEnvObject, key)) {
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
- let stats;
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
- let children;
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 sampleConfig_1 = require("./sampleConfig");
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
- content = await (0, sampleConfig_1.loadSampleFile)(filePath);
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
- });