@govtechsg/oobee 0.10.94 → 0.10.96

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/Dockerfile CHANGED
@@ -1,6 +1,6 @@
1
1
  # Use Microsoft Playwright image as base image
2
2
  # Node version is v22
3
- FROM mcr.microsoft.com/playwright:v1.58.2-noble
3
+ FROM mcr.microsoft.com/playwright:v1.61.1-noble
4
4
 
5
5
  # Installation of packages for oobee
6
6
  RUN apt-get update && apt-get install -y --no-install-recommends \
package/INSTALLATION.md CHANGED
@@ -10,10 +10,11 @@ Oobee (CLI) allows software engineers to run Oobee as part of their software dev
10
10
 
11
11
  ## System Requirements
12
12
 
13
- - Oobee (CLI) can run on MacOS version 12 Monterey or above, and a [supported](https://learn.microsoft.com/en-us/windows/release-health/supported-versions-windows-client) version of Windows 10 (64-bit) or Windows 11.
13
+ - Oobee (CLI) can run on MacOS version 15 Sequoia or above, and a [supported](https://learn.microsoft.com/en-us/windows/release-health/supported-versions-windows-client) version of Windows 10 (64-bit) or Windows 11.
14
14
  - Google Chrome browser is [installed](https://www.google.com/chrome).
15
15
  - One-time Internet access is needed to download and install Oobee (CLI).
16
16
  - You are recommended to be logged on to an admin user to run Oobee (CLI).
17
+ - Note that Apple has discontinued support for developing Intel-based apps in [future macOS versions](https://support.apple.com/en-us/102527). Oobee will discontinue support on Intel-based Mac when that happens.
17
18
 
18
19
  ## Windows
19
20
 
package/dist/combine.js CHANGED
@@ -45,6 +45,13 @@ const combineRun = async (details, deviceToScan) => {
45
45
  consoleLogger.info(`Suppressed Crawlee ps-tree locale error: ${err.message}`);
46
46
  return;
47
47
  }
48
+ // Suppress stale Playwright in-process connection errors that fire asynchronously
49
+ // after the browser is closed in writeSummaryPdf. The deferred IPC message arrives
50
+ // via setImmediate after the browser instance has already been disposed.
51
+ if (err.message?.includes('was not bound in the connection')) {
52
+ consoleLogger.info(`Suppressed Playwright post-close connection error: ${err.message}`);
53
+ return;
54
+ }
48
55
  throw err;
49
56
  };
50
57
  process.on('uncaughtException', psTreeHandler);
@@ -1238,6 +1238,18 @@ export const getBrowserToRun = (randomToken, preferredBrowser, isCli = false) =>
1238
1238
  * after checkingUrl and unable to utilise same cookie for scan
1239
1239
  * */
1240
1240
  export const getClonedProfilesWithRandomToken = (browser, randomToken) => {
1241
+ // In Docker, redirect browser temp files away from /tmp to avoid ENOSPC.
1242
+ // Keep the path short — Chrome creates Unix sockets inside TMPDIR-based paths,
1243
+ // and socket paths are limited to 107 bytes on Linux.
1244
+ // Use process.pid to isolate concurrent scan instances.
1245
+ if (fs.existsSync('/.dockerenv')) {
1246
+ const baseDir = getDefaultChromiumDataDir();
1247
+ if (baseDir) {
1248
+ const scanTmpDir = path.join(baseDir, 'tmp', String(process.pid));
1249
+ fs.mkdirSync(scanTmpDir, { recursive: true });
1250
+ process.env.TMPDIR = scanTmpDir;
1251
+ }
1252
+ }
1241
1253
  if (browser === BrowserTypes.CHROME) {
1242
1254
  return cloneChromeProfiles(randomToken);
1243
1255
  }
@@ -66,6 +66,17 @@ export const generateHtmlReport = async (resultDir, htmlFilename = 'report') =>
66
66
  }
67
67
  const scanData = JSON.parse(await fs.readFile(scanDataJsonPath, 'utf8'));
68
68
  const scanItemsAll = JSON.parse(await fs.readFile(scanItemsJsonPath, 'utf8'));
69
+ // Disk space: passed may be absent from scanItems.json (excluded to reduce disk usage).
70
+ // Provide an empty-category fallback so convertItemsToReferences does not crash.
71
+ // To revert (when passed is restored in scanItems.json), remove this block.
72
+ if (!scanItemsAll.passed) {
73
+ scanItemsAll.passed = {
74
+ description: itemTypeDescription.passed,
75
+ totalItems: 0,
76
+ totalRuleIssues: 0,
77
+ rules: [],
78
+ };
79
+ }
69
80
  // Build the lighter scanItems payload used by the HTML report.
70
81
  const lightScanItemsPayload = convertItemsToReferences({
71
82
  items: scanItemsAll,
@@ -75,7 +86,7 @@ export const generateHtmlReport = async (resultDir, htmlFilename = 'report') =>
75
86
  mustFix: ensureCategory(mustFix, 'mustFix'),
76
87
  goodToFix: ensureCategory(goodToFix, 'goodToFix'),
77
88
  needsReview: ensureCategory(needsReview, 'needsReview'),
78
- passed: ensureCategory(scanItemsAll.passed || {}, 'passed'),
89
+ passed: ensureCategory(scanItemsAll.passed, 'passed'),
79
90
  };
80
91
  const pagesScanned = Array.isArray(scanData.pagesScanned) ? scanData.pagesScanned : [];
81
92
  const pagesNotScanned = Array.isArray(scanData.pagesNotScanned) ? scanData.pagesNotScanned : [];
package/dist/logs.js CHANGED
@@ -35,6 +35,10 @@ else {
35
35
  export const errorsTxtPath = path.join(basePath, `${uuid}.txt`);
36
36
  const consoleLogger = createLogger({
37
37
  silent: !(process.env.RUNNING_FROM_PH_GUI || process.env.OOBEE_VERBOSE),
38
+ // exitOnError: false — Winston logs uncaught exceptions but does not call process.exit().
39
+ // Process lifecycle is controlled by psTreeHandler in combine.ts, which suppresses known
40
+ // benign errors (e.g. Playwright post-close connection errors) and rethrows real ones.
41
+ exitOnError: false,
38
42
  format: combine(timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), logFormat),
39
43
  transports: [
40
44
  new transports.Console({ level: 'info' }),
@@ -49,6 +53,8 @@ const consoleLogger = createLogger({
49
53
  // Also used in common functions to not link internal information
50
54
  // if running from mass scanner, log out errors in console
51
55
  const silentLogger = createLogger({
56
+ // exitOnError: false — see consoleLogger comment above.
57
+ exitOnError: false,
52
58
  format: combine(timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), logFormat),
53
59
  transports: [
54
60
  new transports.File({
@@ -227,7 +227,12 @@ const writeJsonFileAndCompressedJsonFile = async (data, storagePath, filename, i
227
227
  const writeJsonAndBase64Files = async (allIssues, storagePath, itemsStore) => {
228
228
  const { items, ...rest } = allIssues;
229
229
  const { jsonFilePath: scanDataJsonFilePath, base64FilePath: scanDataBase64FilePath } = await writeJsonFileAndCompressedJsonFile(rest, storagePath, 'scanData');
230
- const { jsonFilePath: scanItemsJsonFilePath, base64FilePath: scanItemsBase64FilePath } = await writeJsonFileAndCompressedJsonFile({ oobeeAppVersion: allIssues.oobeeAppVersion, ...items }, storagePath, 'scanItems', itemsStore);
230
+ // Disk space: passed items excluded from scanItems.json to reduce disk usage.
231
+ // Passed counts are still in scanData.json and the embedded report payload (scanItems-light).
232
+ // To revert, remove the destructure line and restore the original argument:
233
+ // { oobeeAppVersion: allIssues.oobeeAppVersion, ...items }
234
+ const { passed: _passedItems, ...itemsWithoutPassed } = items;
235
+ const { jsonFilePath: scanItemsJsonFilePath, base64FilePath: scanItemsBase64FilePath } = await writeJsonFileAndCompressedJsonFile({ oobeeAppVersion: allIssues.oobeeAppVersion, ...itemsWithoutPassed }, storagePath, 'scanItems', itemsStore);
231
236
  ['mustFix', 'goodToFix', 'needsReview', 'passed'].forEach(category => {
232
237
  if (items[category].rules && Array.isArray(items[category].rules)) {
233
238
  items[category].rules.forEach(rule => {
@@ -256,6 +256,16 @@ const cleanUpJsonFiles = async (filesToDelete) => {
256
256
  });
257
257
  };
258
258
  const writeSummaryPdf = async (storagePath, pagesScanned, filename = 'summary', browser, _userDataDirectory) => {
259
+ // Flush stale browser temp files before launching PDF browser (Docker only,
260
+ // where TMPDIR is a per-scan subdirectory we control).
261
+ if (process.env.TMPDIR && fs.existsSync('/.dockerenv')) {
262
+ try {
263
+ fs.emptyDirSync(process.env.TMPDIR);
264
+ }
265
+ catch {
266
+ // Best-effort; locked files from prior browser sessions are non-fatal.
267
+ }
268
+ }
259
269
  const renderPdfWithBrowser = async (browserToUse) => {
260
270
  let browserInstance;
261
271
  let context;
@@ -390,14 +400,21 @@ const pushResults = async (pageResults, allIssues, isCustomFlow, itemsStore) =>
390
400
  metadata,
391
401
  itemsCount: items.length,
392
402
  };
393
- await itemsStore.appendPageItems(category, rule, {
394
- url,
395
- pageTitle,
396
- items,
397
- pageIndex,
398
- pageImagePath,
399
- metadata,
400
- });
403
+ // Disk space: skip storing passed items in itemsStore.
404
+ // To revert, remove the `if` guard and use the original call directly:
405
+ // await itemsStore.appendPageItems(category, rule, {
406
+ // url, pageTitle, items, pageIndex, pageImagePath, metadata,
407
+ // });
408
+ if (category !== 'passed') {
409
+ await itemsStore.appendPageItems(category, rule, {
410
+ url,
411
+ pageTitle,
412
+ items,
413
+ pageIndex,
414
+ pageImagePath,
415
+ metadata,
416
+ });
417
+ }
401
418
  }
402
419
  else if (!(url in currRuleFromAllIssues.pagesAffected)) {
403
420
  currRuleFromAllIssues.pagesAffected[url] = {
@@ -405,12 +422,19 @@ const pushResults = async (pageResults, allIssues, isCustomFlow, itemsStore) =>
405
422
  itemsCount: items.length,
406
423
  ...(filePath && { filePath }),
407
424
  };
408
- await itemsStore.appendPageItems(category, rule, {
409
- url,
410
- pageTitle,
411
- items,
412
- ...(filePath && { filePath }),
413
- });
425
+ // Disk space: skip storing passed items in itemsStore.
426
+ // To revert, remove the `if` guard and use the original call directly:
427
+ // await itemsStore.appendPageItems(category, rule, {
428
+ // url, pageTitle, items, ...(filePath && { filePath }),
429
+ // });
430
+ if (category !== 'passed') {
431
+ await itemsStore.appendPageItems(category, rule, {
432
+ url,
433
+ pageTitle,
434
+ items,
435
+ ...(filePath && { filePath }),
436
+ });
437
+ }
414
438
  }
415
439
  }
416
440
  }
@@ -708,7 +732,9 @@ generateJsonFiles = false, preferredBrowser) => {
708
732
  }
709
733
  populateScanPagesDetail(allIssues);
710
734
  // Build htmlGroups in a second pass from disk-backed items
711
- for (const category of ['mustFix', 'goodToFix', 'needsReview', 'passed']) {
735
+ // Disk space: 'passed' removed from this loop (no items stored in itemsStore for passed).
736
+ // To revert, restore: ['mustFix', 'goodToFix', 'needsReview', 'passed']
737
+ for (const category of ['mustFix', 'goodToFix', 'needsReview']) {
712
738
  for (const rule of allIssues.items[category].rules) {
713
739
  for await (const entry of itemsStore.readRuleItems(category, rule.rule)) {
714
740
  buildHtmlGroups(rule, entry.items, entry.url);
package/dist/utils.js CHANGED
@@ -345,6 +345,13 @@ export const cleanUp = async (randomToken, isError = false) => {
345
345
  catch (error) {
346
346
  consoleLogger.warn(`Unable to force remove userDataDirectory: ${error.message}`);
347
347
  }
348
+ if (process.env.TMPDIR && fs.existsSync('/.dockerenv'))
349
+ try {
350
+ fs.rmSync(process.env.TMPDIR, { recursive: true, force: true });
351
+ }
352
+ catch (error) {
353
+ consoleLogger.warn(`Unable to force remove browser tmp dir: ${error.message}`);
354
+ }
348
355
  if (randomToken !== undefined) {
349
356
  const storagePath = getStoragePath(randomToken);
350
357
  try {
@@ -3,7 +3,7 @@
3
3
  * DO NOT EDIT MANUALLY. Re-generate with: node dist/generateOobeeClientScanner.js
4
4
  *
5
5
  * Embedded at generation time:
6
- * App version : 0.10.94
6
+ * App version : 0.10.96
7
7
  * Sentry DSN : (from OOBEE_SENTRY_DSN env var or constants.ts default)
8
8
  * Sentry SDK : @sentry/browser 10.58.0 (loaded from CDN at runtime)
9
9
  *
@@ -34883,7 +34883,7 @@
34883
34883
  // ── Sentry browser telemetry (Sentry JS SDK, loaded from CDN) ────────────
34884
34884
 
34885
34885
  var _oobeeSentryDsn = "https://3b8c7ee46b06f33815a1301b6713ebc3@o4509047624761344.ingest.us.sentry.io/4509327783559168";
34886
- var _oobeeAppVersion = "0.10.94";
34886
+ var _oobeeAppVersion = "0.10.96";
34887
34887
  var _oobeeSentryVersion = "10.58.0";
34888
34888
  var _oobeeSentryInitialized = false;
34889
34889
  var _oobeeSentryLoadPromise = null;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@govtechsg/oobee",
3
3
  "main": "dist/npmIndex.js",
4
- "version": "0.10.94",
4
+ "version": "0.10.96",
5
5
  "type": "module",
6
6
  "author": "Government Technology Agency <info@tech.gov.sg>",
7
7
  "bin": {
@@ -17,7 +17,7 @@
17
17
  "axios": "^1.8.2",
18
18
  "base64-stream": "^1.0.0",
19
19
  "cheerio": "^1.0.0-rc.12",
20
- "crawlee": "^3.13.10",
20
+ "crawlee": "^3.17.0",
21
21
  "ejs": "^3.1.9",
22
22
  "file-type": "^21.3.3",
23
23
  "fs-extra": "^11.2.0",
@@ -31,7 +31,7 @@
31
31
  "mime-types": "^2.1.35",
32
32
  "minimatch": "^10.2.4",
33
33
  "pdfjs-dist": "github:veraPDF/pdfjs-dist#v4.4.168-taggedPdf-0.1.20",
34
- "playwright": "^1.58.2",
34
+ "playwright": "^1.61.1",
35
35
  "prettier": "^3.1.0",
36
36
  "print-message": "^3.0.1",
37
37
  "safe-regex": "^2.1.1",
package/src/combine.ts CHANGED
@@ -89,6 +89,13 @@ const combineRun = async (details: Data, deviceToScan: string) => {
89
89
  consoleLogger.info(`Suppressed Crawlee ps-tree locale error: ${err.message}`);
90
90
  return;
91
91
  }
92
+ // Suppress stale Playwright in-process connection errors that fire asynchronously
93
+ // after the browser is closed in writeSummaryPdf. The deferred IPC message arrives
94
+ // via setImmediate after the browser instance has already been disposed.
95
+ if (err.message?.includes('was not bound in the connection')) {
96
+ consoleLogger.info(`Suppressed Playwright post-close connection error: ${err.message}`);
97
+ return;
98
+ }
92
99
  throw err;
93
100
  };
94
101
  process.on('uncaughtException', psTreeHandler);
@@ -1497,6 +1497,19 @@ export const getBrowserToRun = (
1497
1497
  * after checkingUrl and unable to utilise same cookie for scan
1498
1498
  * */
1499
1499
  export const getClonedProfilesWithRandomToken = (browser: string, randomToken: string): string => {
1500
+ // In Docker, redirect browser temp files away from /tmp to avoid ENOSPC.
1501
+ // Keep the path short — Chrome creates Unix sockets inside TMPDIR-based paths,
1502
+ // and socket paths are limited to 107 bytes on Linux.
1503
+ // Use process.pid to isolate concurrent scan instances.
1504
+ if (fs.existsSync('/.dockerenv')) {
1505
+ const baseDir = getDefaultChromiumDataDir();
1506
+ if (baseDir) {
1507
+ const scanTmpDir = path.join(baseDir, 'tmp', String(process.pid));
1508
+ fs.mkdirSync(scanTmpDir, { recursive: true });
1509
+ process.env.TMPDIR = scanTmpDir;
1510
+ }
1511
+ }
1512
+
1500
1513
  if (browser === BrowserTypes.CHROME) {
1501
1514
  return cloneChromeProfiles(randomToken);
1502
1515
  }
@@ -126,6 +126,18 @@ export const generateHtmlReport = async (
126
126
  const scanData = JSON.parse(await fs.readFile(scanDataJsonPath, 'utf8'));
127
127
  const scanItemsAll = JSON.parse(await fs.readFile(scanItemsJsonPath, 'utf8'));
128
128
 
129
+ // Disk space: passed may be absent from scanItems.json (excluded to reduce disk usage).
130
+ // Provide an empty-category fallback so convertItemsToReferences does not crash.
131
+ // To revert (when passed is restored in scanItems.json), remove this block.
132
+ if (!scanItemsAll.passed) {
133
+ scanItemsAll.passed = {
134
+ description: itemTypeDescription.passed,
135
+ totalItems: 0,
136
+ totalRuleIssues: 0,
137
+ rules: [],
138
+ };
139
+ }
140
+
129
141
  // Build the lighter scanItems payload used by the HTML report.
130
142
  const lightScanItemsPayload = convertItemsToReferences({
131
143
  items: scanItemsAll,
@@ -141,7 +153,7 @@ export const generateHtmlReport = async (
141
153
  mustFix: ensureCategory(mustFix, 'mustFix'),
142
154
  goodToFix: ensureCategory(goodToFix, 'goodToFix'),
143
155
  needsReview: ensureCategory(needsReview, 'needsReview'),
144
- passed: ensureCategory(scanItemsAll.passed || {}, 'passed'),
156
+ passed: ensureCategory(scanItemsAll.passed, 'passed'),
145
157
  };
146
158
 
147
159
  const pagesScanned = Array.isArray(scanData.pagesScanned) ? scanData.pagesScanned : [];
package/src/logs.ts CHANGED
@@ -39,6 +39,10 @@ export const errorsTxtPath = path.join(basePath, `${uuid}.txt`);
39
39
 
40
40
  const consoleLogger = createLogger({
41
41
  silent: !(process.env.RUNNING_FROM_PH_GUI || process.env.OOBEE_VERBOSE),
42
+ // exitOnError: false — Winston logs uncaught exceptions but does not call process.exit().
43
+ // Process lifecycle is controlled by psTreeHandler in combine.ts, which suppresses known
44
+ // benign errors (e.g. Playwright post-close connection errors) and rethrows real ones.
45
+ exitOnError: false,
42
46
  format: combine(timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), logFormat),
43
47
  transports: [
44
48
  new transports.Console({ level: 'info' }),
@@ -54,6 +58,8 @@ const consoleLogger = createLogger({
54
58
  // Also used in common functions to not link internal information
55
59
  // if running from mass scanner, log out errors in console
56
60
  const silentLogger = createLogger({
61
+ // exitOnError: false — see consoleLogger comment above.
62
+ exitOnError: false,
57
63
  format: combine(timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), logFormat),
58
64
  transports: [
59
65
  new transports.File({
@@ -292,9 +292,14 @@ const writeJsonAndBase64Files = async (
292
292
  const { items, ...rest } = allIssues;
293
293
  const { jsonFilePath: scanDataJsonFilePath, base64FilePath: scanDataBase64FilePath } =
294
294
  await writeJsonFileAndCompressedJsonFile(rest, storagePath, 'scanData');
295
+ // Disk space: passed items excluded from scanItems.json to reduce disk usage.
296
+ // Passed counts are still in scanData.json and the embedded report payload (scanItems-light).
297
+ // To revert, remove the destructure line and restore the original argument:
298
+ // { oobeeAppVersion: allIssues.oobeeAppVersion, ...items }
299
+ const { passed: _passedItems, ...itemsWithoutPassed } = items;
295
300
  const { jsonFilePath: scanItemsJsonFilePath, base64FilePath: scanItemsBase64FilePath } =
296
301
  await writeJsonFileAndCompressedJsonFile(
297
- { oobeeAppVersion: allIssues.oobeeAppVersion, ...items },
302
+ { oobeeAppVersion: allIssues.oobeeAppVersion, ...itemsWithoutPassed },
298
303
  storagePath,
299
304
  'scanItems',
300
305
  itemsStore,
@@ -359,6 +359,16 @@ const writeSummaryPdf = async (
359
359
  browser: string,
360
360
  _userDataDirectory: string,
361
361
  ) => {
362
+ // Flush stale browser temp files before launching PDF browser (Docker only,
363
+ // where TMPDIR is a per-scan subdirectory we control).
364
+ if (process.env.TMPDIR && fs.existsSync('/.dockerenv')) {
365
+ try {
366
+ fs.emptyDirSync(process.env.TMPDIR);
367
+ } catch {
368
+ // Best-effort; locked files from prior browser sessions are non-fatal.
369
+ }
370
+ }
371
+
362
372
  const renderPdfWithBrowser = async (browserToUse: string) => {
363
373
  let browserInstance;
364
374
  let context;
@@ -525,26 +535,40 @@ const pushResults = async (pageResults, allIssues, isCustomFlow, itemsStore: Ite
525
535
  metadata,
526
536
  itemsCount: items.length,
527
537
  };
528
- await itemsStore.appendPageItems(category, rule, {
529
- url,
530
- pageTitle,
531
- items,
532
- pageIndex,
533
- pageImagePath,
534
- metadata,
535
- });
538
+ // Disk space: skip storing passed items in itemsStore.
539
+ // To revert, remove the `if` guard and use the original call directly:
540
+ // await itemsStore.appendPageItems(category, rule, {
541
+ // url, pageTitle, items, pageIndex, pageImagePath, metadata,
542
+ // });
543
+ if (category !== 'passed') {
544
+ await itemsStore.appendPageItems(category, rule, {
545
+ url,
546
+ pageTitle,
547
+ items,
548
+ pageIndex,
549
+ pageImagePath,
550
+ metadata,
551
+ });
552
+ }
536
553
  } else if (!(url in currRuleFromAllIssues.pagesAffected)) {
537
554
  currRuleFromAllIssues.pagesAffected[url] = {
538
555
  pageTitle,
539
556
  itemsCount: items.length,
540
557
  ...(filePath && { filePath }),
541
558
  };
542
- await itemsStore.appendPageItems(category, rule, {
543
- url,
544
- pageTitle,
545
- items,
546
- ...(filePath && { filePath }),
547
- });
559
+ // Disk space: skip storing passed items in itemsStore.
560
+ // To revert, remove the `if` guard and use the original call directly:
561
+ // await itemsStore.appendPageItems(category, rule, {
562
+ // url, pageTitle, items, ...(filePath && { filePath }),
563
+ // });
564
+ if (category !== 'passed') {
565
+ await itemsStore.appendPageItems(category, rule, {
566
+ url,
567
+ pageTitle,
568
+ items,
569
+ ...(filePath && { filePath }),
570
+ });
571
+ }
548
572
  }
549
573
  }
550
574
  }
@@ -924,7 +948,9 @@ const generateArtifacts = async (
924
948
  populateScanPagesDetail(allIssues);
925
949
 
926
950
  // Build htmlGroups in a second pass from disk-backed items
927
- for (const category of ['mustFix', 'goodToFix', 'needsReview', 'passed'] as const) {
951
+ // Disk space: 'passed' removed from this loop (no items stored in itemsStore for passed).
952
+ // To revert, restore: ['mustFix', 'goodToFix', 'needsReview', 'passed']
953
+ for (const category of ['mustFix', 'goodToFix', 'needsReview'] as const) {
928
954
  for (const rule of allIssues.items[category].rules) {
929
955
  for await (const entry of itemsStore.readRuleItems(category, rule.rule)) {
930
956
  buildHtmlGroups(rule, entry.items, entry.url);
package/src/utils.ts CHANGED
@@ -388,6 +388,12 @@ export const cleanUp = async (randomToken?: string, isError: boolean = false): P
388
388
  consoleLogger.warn(`Unable to force remove userDataDirectory: ${error.message}`);
389
389
  }
390
390
 
391
+ if (process.env.TMPDIR && fs.existsSync('/.dockerenv')) try {
392
+ fs.rmSync(process.env.TMPDIR, { recursive: true, force: true });
393
+ } catch (error) {
394
+ consoleLogger.warn(`Unable to force remove browser tmp dir: ${error.message}`);
395
+ }
396
+
391
397
  if (randomToken !== undefined) {
392
398
  const storagePath = getStoragePath(randomToken);
393
399